feat(cli): add terminal-UI i18n runtime and upgrade Ink to 7 (U6)

Add a synchronous Node-side i18next instance built from the generated
@fusion/i18n CLI catalog map (no async backend, first frame localized), with
locale precedence --lang flag -> GlobalSettings.language -> env (LC_ALL/LANG/..)
-> en. Wrap the Ink DashboardApp render in <I18nextProvider> and thread a
--lang flag through runDashboard.

Upgrade ink 6.8 -> 7.0 (native CJK double-width measurement) and raise the
react/@types/react peer floor to ^19.2.0. A spike test confirms react-i18next
works under Ink's custom reconciler: localized first frame + re-render on
changeLanguage (including CJK), retiring the KTD1 unknown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-03 08:28:52 -07:00
parent 677fb9c46c
commit 454b6cd0e9
7 changed files with 255 additions and 103 deletions

View File

@@ -60,13 +60,13 @@
"dockerode": "^4.0.12",
"express": "^5.1.0",
"i18next": "^26.3.1",
"ink": "^6.8.0",
"ink": "^7.0.5",
"ink-spinner": "^5.0.0",
"ink-text-input": "^6.0.0",
"ioredis": "^5.6.0",
"multer": "^2.1.1",
"node-pty": "npm:@homebridge/node-pty-prebuilt-multiarch@^0.13.1",
"react": "^19.0.0",
"react": "^19.2.0",
"react-i18next": "^17.0.8"
},
"peerDependencies": {
@@ -93,7 +93,7 @@
"@fusion/pi-claude-cli": "workspace:*",
"@fusion/pi-llama-cpp": "workspace:*",
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"@types/react": "^19.2.0",
"@vitest/coverage-v8": "^3.1.0",
"cross-env": "^7.0.0",
"esbuild": "^0.25.12",

View File

@@ -733,7 +733,9 @@ async function main() {
const noAuth = args.includes("--no-auth");
const dashTokenIdx = args.indexOf("--token");
const token = dashTokenIdx !== -1 && dashTokenIdx + 1 < args.length ? args[dashTokenIdx + 1] : undefined;
await runDashboard(port, { paused, dev, interactive, host, noAuth, token });
const dashLangIdx = args.indexOf("--lang");
const lang = dashLangIdx !== -1 && dashLangIdx + 1 < args.length ? args[dashLangIdx + 1] : undefined;
await runDashboard(port, { paused, dev, interactive, host, noAuth, token, lang });
break;
}

View File

@@ -157,6 +157,9 @@ export class DashboardTUI {
// will reapply the auto policy.
mouseEnabled: boolean = false;
// Optional `--lang` override; highest-precedence locale source for the TUI.
lang?: string;
constructor() {
this.logBuffer = new LogRingBuffer();
}
@@ -669,6 +672,20 @@ export class DashboardTUI {
const { render } = await import("ink");
const { createElement } = await import("react");
const { DashboardApp } = await import("./app.js");
const { I18nextProvider } = await import("react-i18next");
const { initCliI18n, resolveCliLocale } = await import("../../i18n/index.js");
const { GlobalSettingsStore } = await import("@fusion/core");
// Resolve locale: --lang flag → persisted GlobalSettings → env → en.
let settingLanguage: string | undefined;
try {
settingLanguage = (await new GlobalSettingsStore().getSettings())?.language;
} catch {
// Settings unreadable — fall back to env/default.
}
const i18n = initCliI18n(
resolveCliLocale({ flag: this.lang, setting: settingLanguage, env: process.env }),
);
// Enter the terminal's alternate-screen buffer before mounting Ink so
// the TUI gets a dedicated fullscreen surface that doesn't share
@@ -683,7 +700,7 @@ export class DashboardTUI {
}
this.inkInstance = render(
createElement(DashboardApp, { controller: this }),
createElement(I18nextProvider, { i18n }, createElement(DashboardApp, { controller: this })),
);
// Mouse mode must be enabled AFTER Ink mounts (which calls

View File

@@ -686,7 +686,7 @@ async function resolveDashboardAuthToken(opts: { noAuth?: boolean; token?: strin
return tokenManager.generateToken();
}
export async function runDashboard(port: number, opts: { paused?: boolean; dev?: boolean; interactive?: boolean; open?: boolean; host?: string; noAuth?: boolean; token?: string } = {}) {
export async function runDashboard(port: number, opts: { paused?: boolean; dev?: boolean; interactive?: boolean; open?: boolean; host?: string; noAuth?: boolean; token?: string; lang?: string } = {}) {
// Default to localhost so the dashboard (and its shell-capable terminal API)
// is not exposed on the LAN. Pass --host 0.0.0.0 explicitly to opt-in.
const selectedHost = opts.host ?? "127.0.0.1";
@@ -752,6 +752,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
if (isTTY) {
tui = new DashboardTUI();
tui.lang = opts.lang;
void startupUpdateStatusPromise.then((updateStatus) => {
tui?.setUpdateStatus(updateStatus);
});

View File

@@ -0,0 +1,69 @@
import { Text } from "ink";
import { render } from "ink-testing-library";
import { createElement } from "react";
import { I18nextProvider, useTranslation } from "react-i18next";
import { describe, expect, it } from "vitest";
import { cliI18n, detectEnvLocale, initCliI18n, resolveCliLocale } from "../index.js";
describe("detectEnvLocale", () => {
it("parses POSIX locale env values to a supported locale", () => {
expect(detectEnvLocale({ LANG: "fr_FR.UTF-8" })).toBe("fr");
expect(detectEnvLocale({ LC_ALL: "es_ES.UTF-8" })).toBe("es");
expect(detectEnvLocale({ LANG: "zh_CN.UTF-8" })).toBe("zh-CN");
expect(detectEnvLocale({ LANG: "zh_TW" })).toBe("zh-TW");
});
it("honors precedence LC_ALL > LC_MESSAGES > LANG > LANGUAGE", () => {
expect(detectEnvLocale({ LC_ALL: "fr_FR", LANG: "es_ES" })).toBe("fr");
expect(detectEnvLocale({ LC_MESSAGES: "es_ES", LANG: "fr_FR" })).toBe("es");
});
it("falls back to a bare language and undefined for unsupported", () => {
expect(detectEnvLocale({ LANG: "zh" })).toBe("zh-CN");
expect(detectEnvLocale({ LANG: "de_DE.UTF-8" })).toBeUndefined();
expect(detectEnvLocale({})).toBeUndefined();
});
});
describe("resolveCliLocale precedence", () => {
it("flag overrides setting and env", () => {
expect(resolveCliLocale({ flag: "zh-TW", setting: "fr", env: { LANG: "es_ES" } })).toBe("zh-TW");
});
it("setting overrides env", () => {
expect(resolveCliLocale({ setting: "fr", env: { LANG: "es_ES" } })).toBe("fr");
});
it("env used when no flag/setting", () => {
expect(resolveCliLocale({ env: { LANG: "es_ES.UTF-8" } })).toBe("es");
});
it("defaults to en", () => {
expect(resolveCliLocale({ env: {} })).toBe("en");
expect(resolveCliLocale({ flag: "de", env: {} })).toBe("en");
});
});
// The load-bearing spike: react-i18next must work under Ink's custom reconciler.
function Loading() {
const { t } = useTranslation("cli");
return createElement(Text, null, t("tui.loading", "Loading…"));
}
describe("react-i18next under the Ink reconciler", () => {
it("renders a localized first frame synchronously", () => {
const i18n = initCliI18n("en");
const { lastFrame } = render(
createElement(I18nextProvider, { i18n }, createElement(Loading)),
);
expect(lastFrame()).toContain("Loading…");
});
it("re-renders on changeLanguage", async () => {
const i18n = initCliI18n("en");
cliI18n.addResourceBundle("zh-CN", "cli", { tui: { loading: "加载中…" } }, true, true);
const { lastFrame } = render(
createElement(I18nextProvider, { i18n }, createElement(Loading)),
);
expect(lastFrame()).toContain("Loading…");
await i18n.changeLanguage("zh-CN");
expect(lastFrame()).toContain("加载中…");
});
});

View File

@@ -0,0 +1,78 @@
import { DEFAULT_LOCALE, isLocale, type Locale } from "@fusion/core";
import {
baseInitOptions,
CLI_NAMESPACES,
cliResources,
DEFAULT_NAMESPACE,
} from "@fusion/i18n";
import i18next, { type i18n as I18nInstance, type Resource } from "i18next";
import { initReactI18next } from "react-i18next";
/**
* Terminal-UI i18next instance.
*
* Unlike the dashboard, the CLI bundles all catalogs statically (via the
* generated @fusion/i18n cli map) and initializes synchronously (inline
* resources, no async backend) so the very first rendered Ink frame is
* already localized — no flash of untranslated keys.
*/
/**
* Parse a POSIX locale environment value (e.g. `fr_FR.UTF-8`, `zh_CN`,
* `zh-Hant`) into a supported {@link Locale}, or undefined when none matches.
*/
export function detectEnvLocale(env: NodeJS.ProcessEnv = process.env): Locale | undefined {
const raw = env.LC_ALL || env.LC_MESSAGES || env.LANG || env.LANGUAGE;
if (!raw) return undefined;
// Strip encoding/modifier (`.UTF-8`, `@euro`) and normalize `_` to `-`.
const code = raw.split(/[.:@\s]/)[0].replace("_", "-");
if (isLocale(code)) return code;
const lang = code.split("-")[0].toLowerCase();
if (isLocale(lang)) return lang;
// A bare `zh` resolves to Simplified, mirroring the shared fallback chain.
if (lang === "zh") return "zh-CN";
return undefined;
}
/**
* Resolve the active CLI locale with precedence:
* `--lang flag → persisted GlobalSettings.language → environment → en`.
*/
export function resolveCliLocale(opts: {
flag?: string | undefined;
setting?: string | undefined;
env?: NodeJS.ProcessEnv;
} = {}): Locale {
const { flag, setting, env = process.env } = opts;
if (isLocale(flag)) return flag;
if (isLocale(setting)) return setting;
return detectEnvLocale(env) ?? DEFAULT_LOCALE;
}
let initialized = false;
/** Initialize (synchronously) or switch the CLI i18next instance to `locale`. */
export function initCliI18n(locale: Locale): I18nInstance {
if (!initialized) {
void i18next.use(initReactI18next).init({
...baseInitOptions(),
lng: locale,
ns: [...CLI_NAMESPACES],
defaultNS: DEFAULT_NAMESPACE,
// Inline resources + no async backend => init completes synchronously,
// so the first rendered Ink frame is already localized (i18next v26
// dropped the old `initImmediate` flag; this is now the default for
// backend-less, resource-inlined init).
resources: cliResources as unknown as Resource,
react: { useSuspense: false },
});
initialized = true;
} else if (i18next.language !== locale) {
void i18next.changeLanguage(locale);
}
return i18next;
}
export { i18next as cliI18n };

179
pnpm-lock.yaml generated
View File

@@ -46,10 +46,10 @@ importers:
dependencies:
'@earendil-works/pi-ai':
specifier: ^0.78.0
version: 0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)
version: 0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)
'@earendil-works/pi-coding-agent':
specifier: ^0.78.0
version: 0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)
version: 0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)
dockerode:
specifier: ^4.0.12
version: 4.0.12
@@ -60,14 +60,14 @@ importers:
specifier: ^26.3.1
version: 26.3.1(typescript@5.9.3)
ink:
specifier: ^6.8.0
version: 6.8.0(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4)
specifier: ^7.0.5
version: 7.0.5(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4)
ink-spinner:
specifier: ^5.0.0
version: 5.0.0(ink@6.8.0(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4))(react@19.2.4)
version: 5.0.0(ink@7.0.5(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4))(react@19.2.4)
ink-text-input:
specifier: ^6.0.0
version: 6.0.0(ink@6.8.0(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4))(react@19.2.4)
version: 6.0.0(ink@7.0.5(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4))(react@19.2.4)
ioredis:
specifier: ^5.6.0
version: 5.10.1
@@ -78,7 +78,7 @@ importers:
specifier: npm:@homebridge/node-pty-prebuilt-multiarch@^0.13.1
version: '@homebridge/node-pty-prebuilt-multiarch@0.13.1'
react:
specifier: ^19.0.0
specifier: ^19.2.0
version: 19.2.4
react-i18next:
specifier: ^17.0.8
@@ -106,7 +106,7 @@ importers:
specifier: ^25.5.2
version: 25.5.2
'@types/react':
specifier: ^19.0.0
specifier: ^19.2.0
version: 19.2.14
'@vitest/coverage-v8':
specifier: ^3.1.0
@@ -562,10 +562,10 @@ importers:
dependencies:
'@earendil-works/pi-ai':
specifier: '*'
version: 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)
version: 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-coding-agent':
specifier: '*'
version: 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)
version: 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)
devDependencies:
'@types/node':
specifier: ^25.5.2
@@ -1017,8 +1017,8 @@ packages:
'@adobe/css-tools@4.4.4':
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
'@alcalzone/ansi-tokenize@0.2.5':
resolution: {integrity: sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==}
'@alcalzone/ansi-tokenize@0.3.0':
resolution: {integrity: sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==}
engines: {node: '>=18'}
'@ampproject/remapping@2.3.0':
@@ -3550,9 +3550,9 @@ packages:
resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
engines: {node: '>=8'}
cli-boxes@3.0.0:
resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
engines: {node: '>=10'}
cli-boxes@4.0.1:
resolution: {integrity: sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw==}
engines: {node: '>=18.20 <19 || >=20.10'}
cli-cursor@3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
@@ -3578,9 +3578,9 @@ packages:
resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==}
engines: {node: '>=8'}
cli-truncate@5.2.0:
resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
engines: {node: '>=20'}
cli-truncate@6.0.0:
resolution: {integrity: sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA==}
engines: {node: '>=22'}
cli-width@4.1.0:
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
@@ -3943,9 +3943,6 @@ packages:
resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==}
engines: {node: '>= 0.4.0'}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -4616,12 +4613,12 @@ packages:
ink: '>=5'
react: '>=18'
ink@6.8.0:
resolution: {integrity: sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==}
engines: {node: '>=20'}
ink@7.0.5:
resolution: {integrity: sha512-zWNjGHQPxSeiSAmDUOq+QPQ6CfmMhmNi85vrJIuy4prafKKUSoZlXEy4wbM7LuLuF1pDURk7qvF4fxrQlLxv3w==}
engines: {node: '>=22'}
peerDependencies:
'@types/react': '>=19.0.0'
react: '>=19.0.0'
'@types/react': '>=19.2.0'
react: '>=19.2.0'
react-devtools-core: '>=6.1.2'
peerDependenciesMeta:
'@types/react':
@@ -6134,9 +6131,9 @@ packages:
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
engines: {node: '>=10'}
slice-ansi@8.0.0:
resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
engines: {node: '>=20'}
slice-ansi@9.0.0:
resolution: {integrity: sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==}
engines: {node: '>=22'}
smart-buffer@4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
@@ -6235,10 +6232,6 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
string-width@7.2.0:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
string-width@8.2.0:
resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==}
engines: {node: '>=20'}
@@ -6767,6 +6760,10 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
wrap-ansi@10.0.0:
resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==}
engines: {node: '>=20'}
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
@@ -6779,10 +6776,6 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
wrap-ansi@9.0.2:
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
engines: {node: '>=18'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -6916,7 +6909,7 @@ snapshots:
'@adobe/css-tools@4.4.4': {}
'@alcalzone/ansi-tokenize@0.2.5':
'@alcalzone/ansi-tokenize@0.3.0':
dependencies:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
@@ -7650,9 +7643,9 @@ snapshots:
- ws
- zod
'@earendil-works/pi-agent-core@0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)':
'@earendil-works/pi-agent-core@0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)':
dependencies:
'@earendil-works/pi-ai': 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)
'@earendil-works/pi-ai': 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)
ignore: 7.0.5
typebox: 1.1.38
yaml: 2.9.0
@@ -7664,9 +7657,9 @@ snapshots:
- ws
- zod
'@earendil-works/pi-agent-core@0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)':
'@earendil-works/pi-agent-core@0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)':
dependencies:
'@earendil-works/pi-ai': 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-ai': 0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)
ignore: 7.0.5
typebox: 1.1.38
yaml: 2.9.0
@@ -7726,26 +7719,6 @@ snapshots:
- ws
- zod
'@earendil-works/pi-ai@0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)':
dependencies:
'@anthropic-ai/sdk': 0.91.1(zod@3.25.76)
'@aws-sdk/client-bedrock-runtime': 3.1048.0
'@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))
'@mistralai/mistralai': 2.2.1
'@smithy/node-http-handler': 4.7.3
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
openai: 6.26.0(ws@8.20.0)(zod@3.25.76)
partial-json: 0.1.7
typebox: 1.1.38
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
- bufferutil
- supports-color
- utf-8-validate
- ws
- zod
'@earendil-works/pi-ai@0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)':
dependencies:
'@anthropic-ai/sdk': 0.91.1(zod@4.3.6)
@@ -7766,6 +7739,26 @@ snapshots:
- ws
- zod
'@earendil-works/pi-ai@0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)':
dependencies:
'@anthropic-ai/sdk': 0.91.1(zod@3.25.76)
'@aws-sdk/client-bedrock-runtime': 3.1048.0
'@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))
'@mistralai/mistralai': 2.2.1
'@smithy/node-http-handler': 4.7.3
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
openai: 6.26.0(ws@8.20.0)(zod@3.25.76)
partial-json: 0.1.7
typebox: 1.1.38
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
- bufferutil
- supports-color
- utf-8-validate
- ws
- zod
'@earendil-works/pi-ai@0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)':
dependencies:
'@anthropic-ai/sdk': 0.91.1(zod@4.3.6)
@@ -7835,10 +7828,10 @@ snapshots:
- ws
- zod
'@earendil-works/pi-coding-agent@0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)':
'@earendil-works/pi-coding-agent@0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)':
dependencies:
'@earendil-works/pi-agent-core': 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)
'@earendil-works/pi-ai': 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)
'@earendil-works/pi-agent-core': 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-ai': 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-tui': 0.77.0
'@silvia-odwyer/photon-node': 0.3.4
chalk: 5.6.2
@@ -7864,11 +7857,11 @@ snapshots:
- ws
- zod
'@earendil-works/pi-coding-agent@0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)':
'@earendil-works/pi-coding-agent@0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)':
dependencies:
'@earendil-works/pi-agent-core': 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-ai': 0.77.0(@modelcontextprotocol/sdk@1.28.0(zod@4.3.6))(ws@8.20.0)(zod@4.3.6)
'@earendil-works/pi-tui': 0.77.0
'@earendil-works/pi-agent-core': 0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)
'@earendil-works/pi-ai': 0.78.0(@modelcontextprotocol/sdk@1.28.0(zod@3.25.76))(ws@8.20.0)(zod@3.25.76)
'@earendil-works/pi-tui': 0.78.0
'@silvia-odwyer/photon-node': 0.3.4
chalk: 5.6.2
cross-spawn: 7.0.6
@@ -10106,7 +10099,7 @@ snapshots:
ci-info@4.4.0: {}
cli-boxes@3.0.0: {}
cli-boxes@4.0.1: {}
cli-cursor@3.1.0:
dependencies:
@@ -10130,9 +10123,9 @@ snapshots:
string-width: 4.2.3
optional: true
cli-truncate@5.2.0:
cli-truncate@6.0.0:
dependencies:
slice-ansi: 8.0.0
slice-ansi: 9.0.0
string-width: 8.2.0
cli-width@4.1.0: {}
@@ -10529,8 +10522,6 @@ snapshots:
dependencies:
sax: 1.1.4
emoji-regex@10.6.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -11351,33 +11342,33 @@ snapshots:
ini@4.1.3: {}
ink-spinner@5.0.0(ink@6.8.0(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4))(react@19.2.4):
ink-spinner@5.0.0(ink@7.0.5(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4))(react@19.2.4):
dependencies:
cli-spinners: 2.9.2
ink: 6.8.0(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4)
ink: 7.0.5(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4)
react: 19.2.4
ink-testing-library@4.0.0(@types/react@19.2.14):
optionalDependencies:
'@types/react': 19.2.14
ink-text-input@6.0.0(ink@6.8.0(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4))(react@19.2.4):
ink-text-input@6.0.0(ink@7.0.5(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4))(react@19.2.4):
dependencies:
chalk: 5.6.2
ink: 6.8.0(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4)
ink: 7.0.5(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4)
react: 19.2.4
type-fest: 4.41.0
ink@6.8.0(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4):
ink@7.0.5(@types/react@19.2.14)(react-devtools-core@7.0.1)(react@19.2.4):
dependencies:
'@alcalzone/ansi-tokenize': 0.2.5
'@alcalzone/ansi-tokenize': 0.3.0
ansi-escapes: 7.3.0
ansi-styles: 6.2.3
auto-bind: 5.0.1
chalk: 5.6.2
cli-boxes: 3.0.0
cli-boxes: 4.0.1
cli-cursor: 4.0.0
cli-truncate: 5.2.0
cli-truncate: 6.0.0
code-excerpt: 4.0.0
es-toolkit: 1.45.1
indent-string: 5.0.0
@@ -11387,13 +11378,13 @@ snapshots:
react-reconciler: 0.33.0(react@19.2.4)
scheduler: 0.27.0
signal-exit: 3.0.7
slice-ansi: 8.0.0
slice-ansi: 9.0.0
stack-utils: 2.0.6
string-width: 8.2.0
terminal-size: 4.0.1
type-fest: 5.5.0
widest-line: 6.0.0
wrap-ansi: 9.0.2
wrap-ansi: 10.0.0
ws: 8.20.0
yoga-layout: 3.2.1
optionalDependencies:
@@ -13205,7 +13196,7 @@ snapshots:
astral-regex: 2.0.0
is-fullwidth-code-point: 3.0.0
slice-ansi@8.0.0:
slice-ansi@9.0.0:
dependencies:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
@@ -13307,12 +13298,6 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.2.0
string-width@7.2.0:
dependencies:
emoji-regex: 10.6.0
get-east-asian-width: 1.6.0
strip-ansi: 7.2.0
string-width@8.2.0:
dependencies:
get-east-asian-width: 1.6.0
@@ -13945,6 +13930,12 @@ snapshots:
word-wrap@1.2.5: {}
wrap-ansi@10.0.0:
dependencies:
ansi-styles: 6.2.3
string-width: 8.2.0
strip-ansi: 7.2.0
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
@@ -13963,12 +13954,6 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.2.0
wrap-ansi@9.0.2:
dependencies:
ansi-styles: 6.2.3
string-width: 7.2.0
strip-ansi: 7.2.0
wrappy@1.0.2: {}
ws@7.5.10: {}