neko-harness 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/neko.js CHANGED
@@ -46,6 +46,9 @@ var __esm = (fn, res, err) => () => {
46
46
  };
47
47
 
48
48
  // packages/core/src/application-version.ts
49
+ function upgradeCommand(platform) {
50
+ return platform === "win32" ? "bun add --global neko-harness@latest" : "neko update";
51
+ }
49
52
  function stableVersion(value) {
50
53
  return typeof value === "string" && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value) && value.split(".").every((n) => Number.isSafeInteger(Number(n)));
51
54
  }
@@ -76,7 +79,7 @@ function updateAction(input) {
76
79
  return "install";
77
80
  return "none";
78
81
  }
79
- var RUNNING_VERSION = "0.2.7", APPLICATION_PROTOCOL = 1, ApplicationProtocolError;
82
+ var RUNNING_VERSION = "0.2.9", APPLICATION_PROTOCOL = 1, ApplicationProtocolError;
80
83
  var init_application_version = __esm(() => {
81
84
  ApplicationProtocolError = class ApplicationProtocolError extends Error {
82
85
  constructor() {
@@ -329,6 +332,10 @@ async function acquireUpdateLock(path) {
329
332
  };
330
333
  }
331
334
  async function updateCommand(options) {
335
+ if ((options.platform ?? process.platform) === "win32")
336
+ return `\u8BF7\u5148\u9000\u51FA neko\uFF0C\u7136\u540E\u8FD0\u884C\uFF1A
337
+ ${upgradeCommand("win32")}
338
+ \u5B8C\u6210\u540E\u91CD\u65B0\u542F\u52A8 neko\u3002`;
332
339
  if (!stableVersion(options.runningVersion))
333
340
  throw new Error("\u5F53\u524D\u5B89\u88C5\u65B9\u5F0F\u4E0D\u652F\u6301\u81EA\u5347\u7EA7\uFF0C\u8BF7\u6309\u539F\u65B9\u5F0F\u66F4\u65B0\u3002");
334
341
  const run = options.run ?? runBun;
@@ -3108,39 +3115,39 @@ var init_palette = __esm(() => {
3108
3115
  "syntaxPunctuation"
3109
3116
  ];
3110
3117
  DARK_PALETTE = {
3111
- panel: { bg: "#38313C" },
3112
- selection: { bg: "#523349", fg: "#FFFFFF", bold: true },
3113
- userBg: { bg: tianyi.s900 },
3114
- toolPendingBg: { bg: "#2C2733" },
3115
- toolSuccessBg: { bg: "#223128" },
3116
- toolErrorBg: { bg: "#382631" },
3117
- codeBg: { bg: "#222A38" },
3118
- accent: { fg: sakura.s300 },
3119
- model: { fg: tianyi.s300 },
3120
- muted: { fg: ink.s400 },
3121
- dim: { fg: ink.s500 },
3122
- thinking: { fg: "#93A1B5", italic: true },
3123
- ok: { fg: "#9DCB9A" },
3124
- warning: { fg: "#FFC79B" },
3125
- error: { fg: "#FF8FA3" },
3126
- userText: { fg: ink.s50 },
3127
- toolTitle: { fg: sakura.s200 },
3128
- toolOutput: { fg: ink.s400 },
3129
- codeFg: { fg: sakura.s200 },
3130
- border: { fg: ink.s600 },
3131
- borderMuted: { fg: ink.s700 },
3132
- diffAdded: { fg: "#9DCB9A" },
3133
- diffRemoved: { fg: "#FF8FA3" },
3134
- diffContext: { fg: ink.s500 },
3135
- syntaxComment: { fg: ink.s500, italic: true },
3136
- syntaxKeyword: { fg: sakura.s300, bold: true },
3137
- syntaxFunction: { fg: sakura.s200 },
3138
- syntaxVariable: { fg: ink.s100 },
3139
- syntaxString: { fg: tianyi.s200 },
3140
- syntaxNumber: { fg: tianyi.s300 },
3141
- syntaxType: { fg: tianyi.s300 },
3142
- syntaxOperator: { fg: ink.s300 },
3143
- syntaxPunctuation: { fg: ink.s400 }
3118
+ panel: { bg: "#24283B", fg: "#C0CAF5" },
3119
+ selection: { bg: "#343B58", fg: "#E1E7FF", bold: true },
3120
+ userBg: { bg: "#292E42", fg: "#C0CAF5" },
3121
+ toolPendingBg: { bg: "#222638", fg: "#C0CAF5" },
3122
+ toolSuccessBg: { bg: "#232C32", fg: "#C0CAF5" },
3123
+ toolErrorBg: { bg: "#302735", fg: "#C0CAF5" },
3124
+ codeBg: { bg: "#1A1B26", fg: "#C0CAF5" },
3125
+ accent: { fg: "#7AA2F7" },
3126
+ model: { fg: "#7DCFFF" },
3127
+ muted: { fg: "#A9B1D6" },
3128
+ dim: { fg: "#9AA5CE" },
3129
+ thinking: { fg: "#9AA5CE", italic: true },
3130
+ ok: { fg: "#9ECE6A" },
3131
+ warning: { fg: "#E0AF68" },
3132
+ error: { fg: "#F7768E" },
3133
+ userText: { fg: "#C0CAF5" },
3134
+ toolTitle: { fg: "#7AA2F7" },
3135
+ toolOutput: { fg: "#C0CAF5" },
3136
+ codeFg: { fg: "#C0CAF5" },
3137
+ border: { fg: "#737DA2" },
3138
+ borderMuted: { fg: "#545E7C" },
3139
+ diffAdded: { fg: "#9ECE6A" },
3140
+ diffRemoved: { fg: "#F7768E" },
3141
+ diffContext: { fg: "#9AA5CE" },
3142
+ syntaxComment: { fg: "#8993B3", italic: true },
3143
+ syntaxKeyword: { fg: "#BB9AF7", bold: true },
3144
+ syntaxFunction: { fg: "#7AA2F7" },
3145
+ syntaxVariable: { fg: "#C0CAF5" },
3146
+ syntaxString: { fg: "#9ECE6A" },
3147
+ syntaxNumber: { fg: "#FF9E64" },
3148
+ syntaxType: { fg: "#7DCFFF" },
3149
+ syntaxOperator: { fg: "#89DDFF" },
3150
+ syntaxPunctuation: { fg: "#A9B1D6" }
3144
3151
  };
3145
3152
  LIGHT_PALETTE = {
3146
3153
  panel: { bg: ink.s50 },
@@ -3180,7 +3187,42 @@ var init_palette = __esm(() => {
3180
3187
  });
3181
3188
 
3182
3189
  // packages/tui/src/theme/slots.ts
3183
- import { getTerminalColorMode, parseColor, styleText } from "@earendil-works/pi-tui";
3190
+ import {
3191
+ backgroundAnsi,
3192
+ foregroundAnsi,
3193
+ getTerminalColorMode,
3194
+ parseColor,
3195
+ styleText
3196
+ } from "@earendil-works/pi-tui";
3197
+ function surfaceText(text, fg, bg) {
3198
+ return text.replace(/\x1b\[([0-9;:]*)m/g, (sequence, parameters) => {
3199
+ const codes = parameters.split(";");
3200
+ let resetFg = false;
3201
+ let resetBg = false;
3202
+ for (let i = 0;i < codes.length; i++) {
3203
+ const code = Number(codes[i].split(":")[0]);
3204
+ if (code === 0) {
3205
+ resetFg = true;
3206
+ resetBg = true;
3207
+ } else if (code === 39)
3208
+ resetFg = true;
3209
+ else if (code === 49)
3210
+ resetBg = true;
3211
+ else if (code === 38 || code === 48) {
3212
+ if (code === 38)
3213
+ resetFg = false;
3214
+ else
3215
+ resetBg = false;
3216
+ if (!codes[i].includes(":"))
3217
+ i += codes[i + 1] === "2" ? 4 : codes[i + 1] === "5" ? 2 : 0;
3218
+ } else if (code >= 30 && code <= 37 || code >= 90 && code <= 97)
3219
+ resetFg = false;
3220
+ else if (code >= 40 && code <= 47 || code >= 100 && code <= 107)
3221
+ resetBg = false;
3222
+ }
3223
+ return sequence + (resetFg ? fg : "") + (resetBg ? bg : "");
3224
+ });
3225
+ }
3184
3226
  function createTheme(palette) {
3185
3227
  const colorCache = new Map;
3186
3228
  const colorOf = (hex) => {
@@ -3194,14 +3236,19 @@ function createTheme(palette) {
3194
3236
  const theme = {};
3195
3237
  for (const name of SLOT_NAMES) {
3196
3238
  const style = palette[name];
3197
- theme[name] = (text) => styleText(text, {
3198
- ...style.fg !== undefined ? { fg: colorOf(style.fg) } : {},
3199
- ...style.bg !== undefined ? { bg: colorOf(style.bg) } : {},
3200
- ...style.bold !== undefined ? { bold: style.bold } : {},
3201
- ...style.italic !== undefined ? { italic: style.italic } : {},
3202
- ...style.strikethrough !== undefined ? { strikethrough: style.strikethrough } : {},
3203
- ...style.underline !== undefined ? { underline: style.underline } : {}
3204
- }, getTerminalColorMode());
3239
+ theme[name] = (text) => {
3240
+ const mode = getTerminalColorMode();
3241
+ if (palette === DARK_PALETTE && style.fg && style.bg)
3242
+ text = surfaceText(text, foregroundAnsi(colorOf(style.fg), mode), backgroundAnsi(colorOf(style.bg), mode));
3243
+ return styleText(text, {
3244
+ ...style.fg !== undefined ? { fg: colorOf(style.fg) } : {},
3245
+ ...style.bg !== undefined ? { bg: colorOf(style.bg) } : {},
3246
+ ...style.bold !== undefined ? { bold: style.bold } : {},
3247
+ ...style.italic !== undefined ? { italic: style.italic } : {},
3248
+ ...style.strikethrough !== undefined ? { strikethrough: style.strikethrough } : {},
3249
+ ...style.underline !== undefined ? { underline: style.underline } : {}
3250
+ }, mode);
3251
+ };
3205
3252
  }
3206
3253
  return theme;
3207
3254
  }
@@ -12879,6 +12926,7 @@ var THINKING_COLLAPSED_LABEL = "\u2727 Thinking\u2026 (click to expand)", Assist
12879
12926
  var init_assistant_message = __esm(() => {
12880
12927
  init_theme();
12881
12928
  AssistantMessageComponent = class AssistantMessageComponent extends Container {
12929
+ #renderTheme = getActiveThemeMode();
12882
12930
  #lastContent;
12883
12931
  #isStreaming = false;
12884
12932
  #thinkingExpanded = false;
@@ -12889,6 +12937,10 @@ var init_assistant_message = __esm(() => {
12889
12937
  this.updateContent(content, isStreaming);
12890
12938
  }
12891
12939
  render(width) {
12940
+ if (this.#renderTheme !== getActiveThemeMode()) {
12941
+ this.#renderTheme = getActiveThemeMode();
12942
+ this.#rebuild();
12943
+ }
12892
12944
  this.#renderWidth = width;
12893
12945
  return super.render(width);
12894
12946
  }
@@ -13741,8 +13793,46 @@ var init_picker = __esm(() => {
13741
13793
  init_theme();
13742
13794
  });
13743
13795
 
13796
+ // packages/tui/src/components/themed-text.ts
13797
+ import { Text as Text3, TruncatedText } from "@earendil-works/pi-tui";
13798
+
13799
+ class ThemedTruncatedText {
13800
+ #content;
13801
+ #paddingX;
13802
+ #paddingY;
13803
+ constructor(content, paddingX = 1, paddingY = 0) {
13804
+ this.#content = content;
13805
+ this.#paddingX = paddingX;
13806
+ this.#paddingY = paddingY;
13807
+ }
13808
+ invalidate() {}
13809
+ render(width) {
13810
+ return new TruncatedText(this.#content(), this.#paddingX, this.#paddingY).render(width);
13811
+ }
13812
+ }
13813
+ var ThemedText;
13814
+ var init_themed_text = __esm(() => {
13815
+ ThemedText = class ThemedText extends Text3 {
13816
+ #raw;
13817
+ #color;
13818
+ constructor(text, color, paddingX = 1, paddingY = 0) {
13819
+ super("", paddingX, paddingY);
13820
+ this.#raw = text;
13821
+ this.#color = color;
13822
+ }
13823
+ setText(text) {
13824
+ this.#raw = text;
13825
+ this.invalidate();
13826
+ }
13827
+ render(width) {
13828
+ super.setText(this.#color(this.#raw));
13829
+ return super.render(width);
13830
+ }
13831
+ };
13832
+ });
13833
+
13744
13834
  // packages/tui/src/components/tool-execution.ts
13745
- import { Box, Container as Container3, MouseRegion as MouseRegion2, Text as Text3 } from "@earendil-works/pi-tui";
13835
+ import { Box, Container as Container3, MouseRegion as MouseRegion2, Text as Text4 } from "@earendil-works/pi-tui";
13746
13836
  function currentToolFrame() {
13747
13837
  return SPINNER_FRAMES[spinnerFrame];
13748
13838
  }
@@ -13782,6 +13872,7 @@ var init_tool_execution = __esm(() => {
13782
13872
  init_theme();
13783
13873
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
13784
13874
  ToolExecutionComponent = class ToolExecutionComponent extends Container3 {
13875
+ #renderTheme = getActiveThemeMode();
13785
13876
  #state;
13786
13877
  #expanded = false;
13787
13878
  #box;
@@ -13789,12 +13880,19 @@ var init_tool_execution = __esm(() => {
13789
13880
  constructor(state) {
13790
13881
  super();
13791
13882
  this.#state = state;
13792
- this.#text = new Text3("", 0, 0);
13883
+ this.#text = new Text4("", 0, 0);
13793
13884
  this.#box = new Box(1, 0, theme.toolPendingBg);
13794
13885
  this.#box.addChild(new MouseRegion2(this.#text, (event) => this.#onClick(event)));
13795
13886
  this.addChild(this.#box);
13796
13887
  this.rebuild();
13797
13888
  }
13889
+ render(width) {
13890
+ if (this.#renderTheme !== getActiveThemeMode()) {
13891
+ this.#renderTheme = getActiveThemeMode();
13892
+ this.rebuild();
13893
+ }
13894
+ return super.render(width);
13895
+ }
13798
13896
  get isRunning() {
13799
13897
  return this.#state.phase === "running";
13800
13898
  }
@@ -13852,11 +13950,12 @@ ${theme.error(state.note)}`;
13852
13950
  });
13853
13951
 
13854
13952
  // packages/tui/src/components/user-message.ts
13855
- import { Container as Container4, Markdown as Markdown2, MouseRegion as MouseRegion3, Text as Text4 } from "@earendil-works/pi-tui";
13953
+ import { Container as Container4, Markdown as Markdown2, MouseRegion as MouseRegion3, Text as Text5 } from "@earendil-works/pi-tui";
13856
13954
  var UserMessageComponent;
13857
13955
  var init_user_message = __esm(() => {
13858
13956
  init_theme();
13859
13957
  UserMessageComponent = class UserMessageComponent extends Container4 {
13958
+ #renderTheme = getActiveThemeMode();
13860
13959
  #text;
13861
13960
  #skill;
13862
13961
  #defaultImagePrompt;
@@ -13877,6 +13976,10 @@ var init_user_message = __esm(() => {
13877
13976
  this.#rebuild();
13878
13977
  }
13879
13978
  render(width) {
13979
+ if (this.#renderTheme !== getActiveThemeMode()) {
13980
+ this.#renderTheme = getActiveThemeMode();
13981
+ this.#rebuild();
13982
+ }
13880
13983
  const bar = theme.userBg(theme.accent("\u258D"));
13881
13984
  return super.render(Math.max(1, width - 1)).map((line) => `${bar}${line}`);
13882
13985
  }
@@ -13884,14 +13987,14 @@ var init_user_message = __esm(() => {
13884
13987
  this.clear();
13885
13988
  const prompt = this.#defaultImagePrompt;
13886
13989
  if (prompt) {
13887
- this.addChild(new MouseRegion3(new Text4(theme.dim(`${this.#expanded ? "v" : ">"} Default image prompt \xB7 Click to ${this.#expanded ? "hide" : "view"}`), 1, 0, theme.userBg), (event) => {
13990
+ this.addChild(new MouseRegion3(new Text5(theme.dim(`${this.#expanded ? "v" : ">"} Default image prompt \xB7 Click to ${this.#expanded ? "hide" : "view"}`), 1, 0, theme.userBg), (event) => {
13888
13991
  if (event.type !== "click" || event.button !== "left")
13889
13992
  return;
13890
13993
  this.setExpanded(!this.#expanded);
13891
13994
  return { handled: true };
13892
13995
  }));
13893
13996
  if (this.#expanded)
13894
- this.addChild(new Text4(prompt.text, 1, 0, theme.userBg));
13997
+ this.addChild(new Text5(prompt.text, 1, 0, theme.userBg));
13895
13998
  if (!this.#text.trim())
13896
13999
  return;
13897
14000
  }
@@ -13901,7 +14004,7 @@ var init_user_message = __esm(() => {
13901
14004
  const preview = args.length > 100 ? `${args.slice(0, 99)}\u2026` : args;
13902
14005
  const head = `${theme.toolTitle(`\u2726 skill:${invocation.name}`)}${preview ? ` ${theme.userText(preview)}` : ""}`;
13903
14006
  const source = theme.dim(`skill body from ${invocation.location} (collapsed)`);
13904
- this.addChild(new Text4(`${head}
14007
+ this.addChild(new Text5(`${head}
13905
14008
  ${source}`, 1, 0, theme.userBg));
13906
14009
  return;
13907
14010
  }
@@ -13995,6 +14098,21 @@ async function queryTerminalMode(ui, onLateReply) {
13995
14098
  }
13996
14099
  return modeFromColors(colors) ?? detectColorFgBgMode() ?? "light";
13997
14100
  }
14101
+ function followTerminalTheme(ui, apply) {
14102
+ let active = true;
14103
+ const accept = (mode) => {
14104
+ if (active)
14105
+ apply(mode);
14106
+ };
14107
+ ui.setTerminalColorSchemeNotifications(true);
14108
+ const unsubscribe = ui.onTerminalColorSchemeChange(accept);
14109
+ queryTerminalMode(ui, accept).then(accept, () => {});
14110
+ return () => {
14111
+ active = false;
14112
+ unsubscribe();
14113
+ ui.setTerminalColorSchemeNotifications(false);
14114
+ };
14115
+ }
13998
14116
  var THEME_QUERY_TIMEOUT_MS = 100;
13999
14117
 
14000
14118
  // packages/core/src/file-mentions.ts
@@ -16815,7 +16933,7 @@ var init_image_input = __esm(() => {
16815
16933
  });
16816
16934
 
16817
16935
  // packages/tui/src/layout.ts
16818
- import { Text as Text5, VStack } from "@earendil-works/pi-tui";
16936
+ import { Text as Text6, VStack } from "@earendil-works/pi-tui";
16819
16937
  function createAppLayout(parts) {
16820
16938
  const normal = (v) => {
16821
16939
  parts.footer.panelOpen = parts.panelOpen();
@@ -16825,7 +16943,7 @@ function createAppLayout(parts) {
16825
16943
  };
16826
16944
  return new VStack([
16827
16945
  {
16828
- component: new Text5("Terminal too small. Resize to 40x10. Ctrl+C Exit", 0, 0),
16946
+ component: new Text6("Terminal too small. Resize to 40x10. Ctrl+C Exit", 0, 0),
16829
16947
  visible: (v) => !normal(v),
16830
16948
  grow: 1
16831
16949
  },
@@ -16833,7 +16951,7 @@ function createAppLayout(parts) {
16833
16951
  { component: parts.panel, basis: 0, grow: 1, minSize: 1, visible: (v) => normal(v) && parts.panelOpen() },
16834
16952
  { component: parts.notices, maxSize: 1, shrink: 0, visible: normal },
16835
16953
  {
16836
- component: parts.busy ?? new Text5("", 0, 0),
16954
+ component: parts.busy ?? new Text6("", 0, 0),
16837
16955
  maxSize: 2,
16838
16956
  shrink: 0,
16839
16957
  visible: (v) => normal(v) && (parts.busyVisible?.() ?? false)
@@ -17009,6 +17127,7 @@ class SkillDetailView {
17009
17127
 
17010
17128
  class SkillPreviewView {
17011
17129
  #header = null;
17130
+ #renderTheme = getActiveThemeMode();
17012
17131
  #markdown = null;
17013
17132
  #error = "";
17014
17133
  #offset = 0;
@@ -17019,7 +17138,7 @@ class SkillPreviewView {
17019
17138
  constructor(name, client, panel) {
17020
17139
  this.#panel = panel;
17021
17140
  client.skillMarket.preview({ name }).then((preview) => {
17022
- this.#header = [`${theme.dim("files:")} ${preview.files.join(" ")}`, ""];
17141
+ this.#header = preview.files.join(" ");
17023
17142
  this.#markdown = new Markdown3(preview.body, 0, 0, getMarkdownTheme());
17024
17143
  this.#panel.changed();
17025
17144
  }).catch((error) => {
@@ -17039,7 +17158,11 @@ class SkillPreviewView {
17039
17158
  lines(width, height) {
17040
17159
  if (this.#markdown === null || this.#header === null)
17041
17160
  return [this.#error !== "" ? theme.error(`Error: ${this.#error}`) : theme.dim("Loading preview...")];
17042
- const flat = [...this.#header, ...this.#markdown.render(width)];
17161
+ if (this.#renderTheme !== getActiveThemeMode()) {
17162
+ this.#renderTheme = getActiveThemeMode();
17163
+ this.#markdown.invalidate();
17164
+ }
17165
+ const flat = [`${theme.dim("files:")} ${this.#header}`, "", ...this.#markdown.render(width)];
17043
17166
  const max = Math.max(0, flat.length - height);
17044
17167
  this.#offset = Math.min(this.#offset, max);
17045
17168
  return flat.slice(this.#offset, this.#offset + height);
@@ -17076,18 +17199,70 @@ var init_skill_market_ui = __esm(() => {
17076
17199
  });
17077
17200
 
17078
17201
  // packages/tui/src/version-ui.ts
17079
- import { matchesKey as matchesKey6, Text as Text6 } from "@earendil-works/pi-tui";
17080
- function versionNotice(state, installed) {
17081
- const action = updateAction({ gateway: state, frontend: RUNNING_VERSION, installed });
17202
+ import { matchesKey as matchesKey6, Text as Text7 } from "@earendil-works/pi-tui";
17203
+ function actionFor(state, installed, runningVersion) {
17204
+ const action = updateAction({ gateway: state, frontend: runningVersion, installed });
17205
+ return action === "install" && !newer(state?.latestVersion, runningVersion) ? "none" : action;
17206
+ }
17207
+ function versionNotice(state, installed, options = {}) {
17208
+ const action = actionFor(state, installed, options.runningVersion ?? RUNNING_VERSION);
17082
17209
  return {
17083
17210
  none: "",
17084
- install: "Update available \xB7 Run neko update",
17211
+ install: `Update available \xB7 Run ${upgradeCommand(options.platform ?? process.platform)} \xB7 /version`,
17085
17212
  "restart-service": "Updated \xB7 Restart neko \xB7 /version",
17086
17213
  "restart-tui": "Updated \xB7 Restart neko tui",
17087
17214
  "refresh-page": "",
17088
- incompatible: "Protocol incompatible \xB7 Update and restart neko"
17215
+ incompatible: "Protocol incompatible \xB7 Update and restart neko \xB7 /version"
17089
17216
  }[action];
17090
17217
  }
17218
+ function versionOverview(state, installed, options = {}) {
17219
+ const running = options.runningVersion ?? RUNNING_VERSION;
17220
+ const command = upgradeCommand(options.platform ?? process.platform);
17221
+ const current = `Current: ${stableVersion(running) ? `v${running}` : running}`;
17222
+ const action = actionFor(state, installed, running);
17223
+ let status;
17224
+ switch (action) {
17225
+ case "incompatible":
17226
+ status = `Protocol incompatible
17227
+ Exit neko, run ${command}, then restart neko.`;
17228
+ break;
17229
+ case "restart-service":
17230
+ status = `Restart neko
17231
+ Finish tasks and close TUI/Web and any neko serve sharing this gateway. Then run neko tui or neko web.`;
17232
+ break;
17233
+ case "restart-tui":
17234
+ status = `Restart neko tui
17235
+ Finish tasks, exit this TUI, then run neko tui.`;
17236
+ break;
17237
+ case "install":
17238
+ status = `Update available
17239
+ Latest: v${state.latestVersion}
17240
+ Exit neko, then run:
17241
+ ${command}
17242
+ Restart neko after updating.`;
17243
+ break;
17244
+ default:
17245
+ if (!stableVersion(running) || state?.checkState === "development")
17246
+ status = "Development build";
17247
+ else if (!state)
17248
+ status = "Check not available";
17249
+ else if (state.checkState === "checking")
17250
+ status = "Checking for updates...";
17251
+ else if (state.checkState === "failed")
17252
+ status = "Check failed \xB7 Retry";
17253
+ else if (state.checkState === "unknown")
17254
+ status = "Not checked";
17255
+ else if (!stableVersion(installed) || !stableVersion(state.installedVersion) || !stableVersion(state.runningVersion) || !stableVersion(state.latestVersion) || state.checkedAt === null)
17256
+ status = "Check not available";
17257
+ else
17258
+ status = "Up to date";
17259
+ }
17260
+ if (state?.checkState === "failed" && !status.includes("Check failed"))
17261
+ status += `
17262
+ Check failed \xB7 Retry`;
17263
+ return `${current}
17264
+ ${status}`;
17265
+ }
17091
17266
 
17092
17267
  class VersionDetail {
17093
17268
  help = "Up/Down Scroll \xB7 Esc Back";
@@ -17097,7 +17272,7 @@ class VersionDetail {
17097
17272
  this.body = body;
17098
17273
  }
17099
17274
  lines(width, height) {
17100
- const lines = new Text6(this.body, 0, 0).render(width);
17275
+ const lines = new Text7(this.body, 0, 0).render(width);
17101
17276
  this.#offset = Math.min(this.#offset, Math.max(0, lines.length - height));
17102
17277
  return lines.slice(this.#offset, this.#offset + height);
17103
17278
  }
@@ -17111,31 +17286,34 @@ class VersionDetail {
17111
17286
  return true;
17112
17287
  }
17113
17288
  }
17114
- function openVersion(panel, read, check) {
17289
+ function openVersion(panel, read, check, options = {}) {
17115
17290
  return panel.show("Version", async () => [
17116
- { value: "info", label: "Versions and next step" },
17117
- { value: "check", label: "Check for updates" }
17291
+ { value: "info", label: "Version" },
17292
+ { value: "check", label: "Check for updates" },
17293
+ { value: "details", label: "Version details" }
17118
17294
  ], async (value) => {
17295
+ let checked;
17119
17296
  let failed = false;
17120
17297
  if (value === "check") {
17121
17298
  try {
17122
- const checked = await check();
17123
- failed = checked.checkState === "failed";
17299
+ checked = await check();
17124
17300
  } catch {
17125
17301
  failed = true;
17126
17302
  }
17127
17303
  }
17128
17304
  const { state, installed } = await read();
17129
- return new VersionDetail(`TUI: ${RUNNING_VERSION}
17305
+ if (value === "details")
17306
+ return new VersionDetail(`TUI: ${options.runningVersion ?? RUNNING_VERSION}
17130
17307
  Installed: ${installed ?? "unknown"}
17131
17308
  Gateway: ${state?.runningVersion ?? "unknown"}
17132
17309
  Gateway installed: ${state?.installedVersion ?? "unknown"}
17133
- Latest: ${state?.latestVersion ?? "unknown"}
17134
-
17135
- ${failed ? "Check failed \xB7 Retry" : versionNotice(state, installed) || (state?.checkState === "ok" && installed ? "Up to date" : "Check not available")}
17136
-
17137
- Run neko update in a terminal.
17138
- To restart the gateway: finish your tasks, close TUI/Web and any neko serve using this data directory, then run neko tui or neko web.`);
17310
+ Latest: ${state?.latestVersion ?? "unknown"}`);
17311
+ const result = failed && state ? { ...state, checkState: "failed" } : checked ?? state;
17312
+ let body = versionOverview(result, installed, options);
17313
+ if (failed && !body.includes("Check failed"))
17314
+ body += `
17315
+ Check failed \xB7 Retry`;
17316
+ return new VersionDetail(body);
17139
17317
  });
17140
17318
  }
17141
17319
  var init_version_ui = __esm(() => {
@@ -17183,8 +17361,6 @@ import {
17183
17361
  matchesKey as matchesKey7,
17184
17362
  ProcessTerminal,
17185
17363
  ScrollView,
17186
- Text as Text7,
17187
- TruncatedText,
17188
17364
  TuiAltScreen
17189
17365
  } from "@earendil-works/pi-tui";
17190
17366
 
@@ -17405,7 +17581,7 @@ class NekoTuiApp {
17405
17581
  const notice = versionNotice(this.#gateway.client.application.info.value ?? null, installed);
17406
17582
  this.#versionNotice.clear();
17407
17583
  if (notice)
17408
- this.#versionNotice.addChild(new TruncatedText(formatNotice(notice, "info"), 1, 0));
17584
+ this.#versionNotice.addChild(new ThemedTruncatedText(() => formatNotice(notice, "info"), 1, 0));
17409
17585
  this.#ui.requestRender();
17410
17586
  };
17411
17587
  this.#versionStop = this.#gateway.client.application.info.subscribe(() => void refreshVersion());
@@ -17735,7 +17911,7 @@ class NekoTuiApp {
17735
17911
  const options = [
17736
17912
  { value: "auto", label: "Auto", description: "Follow the terminal background" },
17737
17913
  { value: "light", label: "Light \xB7 sakura-day", description: "Brand light theme" },
17738
- { value: "dark", label: "Dark \xB7 sakura-night", description: "Brand dark theme" }
17914
+ { value: "dark", label: "Dark", description: "Cool dark theme" }
17739
17915
  ];
17740
17916
  const ready = this.#panel.show("Theme", async () => options.map((option) => ({
17741
17917
  value: option.value,
@@ -17806,7 +17982,7 @@ class NekoTuiApp {
17806
17982
  this.#ui.requestRender();
17807
17983
  }, 5000);
17808
17984
  this.#notices.clear();
17809
- this.#notices.addChild(new TruncatedText(formatNotice(text, level), 1, 0));
17985
+ this.#notices.addChild(new ThemedTruncatedText(() => formatNotice(text, level), 1, 0));
17810
17986
  this.#ui.requestRender();
17811
17987
  }
17812
17988
  scheduleApply() {
@@ -17947,7 +18123,7 @@ class NekoTuiApp {
17947
18123
  this.#items.set(item.key, { kind: "tool", component });
17948
18124
  return;
17949
18125
  }
17950
- const component = new Text7(theme.dim(item.text), 1, 0);
18126
+ const component = new ThemedText(item.text, theme.dim, 1, 0);
17951
18127
  this.#imageTranscript.addItem(component);
17952
18128
  this.#items.set(item.key, { kind: "system", component });
17953
18129
  }
@@ -17965,7 +18141,7 @@ class NekoTuiApp {
17965
18141
  return;
17966
18142
  }
17967
18143
  if (entry.kind === "system" && item.type === "system") {
17968
- entry.component.setText(theme.dim(item.text));
18144
+ entry.component.setText(item.text);
17969
18145
  }
17970
18146
  }
17971
18147
  #toolCard(call) {
@@ -18054,13 +18230,10 @@ class NekoTuiApp {
18054
18230
  #startThemeDetection() {
18055
18231
  if (this.#themePreference !== "auto" || this.#terminalSchemeUnsubscribe !== undefined)
18056
18232
  return;
18057
- this.#ui.setTerminalColorSchemeNotifications(true);
18058
- this.#terminalSchemeUnsubscribe = this.#ui.onTerminalColorSchemeChange((scheme) => {
18059
- this.#applyThemeMode(scheme);
18233
+ this.#terminalSchemeUnsubscribe = followTerminalTheme(this.#ui, (mode) => {
18234
+ if (!this.#exitStarted && this.#themePreference === "auto")
18235
+ this.#applyThemeMode(mode);
18060
18236
  });
18061
- queryTerminalMode(this.#ui, (late) => {
18062
- this.#applyThemeMode(late);
18063
- }).then((mode) => this.#applyThemeMode(mode), () => {});
18064
18237
  }
18065
18238
  #applyThemeMode(mode) {
18066
18239
  if (this.#exitStarted || mode === getActiveThemeMode())
@@ -18146,6 +18319,7 @@ var init_tui = __esm(() => {
18146
18319
  init_login();
18147
18320
  init_notice();
18148
18321
  init_picker();
18322
+ init_themed_text();
18149
18323
  init_tool_execution();
18150
18324
  init_user_message();
18151
18325
  init_welcome();
@@ -18231,19 +18405,19 @@ function parseArgs(argv) {
18231
18405
  ...theme !== undefined ? { theme } : {}
18232
18406
  };
18233
18407
  }
18234
- var USAGE = `Usage: neko tui [options]
18408
+ var USAGE = `\u7528\u6CD5: neko tui [\u9009\u9879]
18235
18409
 
18236
- Options:
18237
- --cwd <dir> Workspace (default: launch directory; not an access sandbox)
18238
- --model <ref> provider/modelId for a spawned gateway
18239
- --storage <path> SQLite storage (default: ~/.neko/neko.sqlite)
18240
- --socket-path <path> Gateway Unix socket\uFF08POSIX\uFF1BWindows \u7ECF\u7AEF\u53E3\u6587\u4EF6\u53D1\u73B0 ws \u5730\u5740\uFF09
18241
- --theme <mode> light / dark / auto (overrides NEKO_THEME)
18242
- -h, --help Show help
18410
+ \u9009\u9879:
18411
+ --cwd <dir> \u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u542F\u52A8\u76EE\u5F55\uFF1B\u975E\u8BBF\u95EE\u6C99\u7BB1\uFF09
18412
+ --model <ref> \u6A21\u578B\uFF0C\u683C\u5F0F provider/modelId\uFF08\u7528\u4E8E\u62C9\u8D77\u7684\u7F51\u5173\uFF09
18413
+ --storage <path> SQLite \u5B58\u50A8\u6587\u4EF6\uFF08\u9ED8\u8BA4 ~/.neko/neko.sqlite\uFF09
18414
+ --socket-path <path> \u7F51\u5173 Unix socket \u8DEF\u5F84\uFF08\u4EC5 POSIX\uFF1BWindows \u7ECF\u7AEF\u53E3\u6587\u4EF6\u53D1\u73B0 ws \u5730\u5740\uFF09
18415
+ --theme <mode> \u4E3B\u9898 light / dark / auto\uFF08\u8986\u76D6 NEKO_THEME\uFF09
18416
+ -h, --help \u663E\u793A\u6B64\u5E2E\u52A9
18243
18417
 
18244
- Resumes the most recently used session in this workspace, or creates one.
18245
- A gateway is started when needed and stopped when its owning TUI exits.
18246
- Configure DeepSeek with /login or DEEPSEEK_API_KEY.`;
18418
+ \u6062\u590D\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6700\u8FD1\u4F7F\u7528\u7684\u4F1A\u8BDD\uFF0C\u4E0D\u5B58\u5728\u5219\u521B\u5EFA\u3002
18419
+ \u6309\u9700\u62C9\u8D77\u7F51\u5173\uFF0C\u5C5E\u4E3B TUI \u9000\u51FA\u65F6\u968F\u4E4B\u505C\u6B62\u3002
18420
+ \u53EF\u7528 /login \u6216 DEEPSEEK_API_KEY \u914D\u7F6E\u51ED\u636E\u3002`;
18247
18421
  var init_main = __esm(async () => {
18248
18422
  init_tui();
18249
18423
  try {
@@ -18706,7 +18880,7 @@ function parseArgs2(argv) {
18706
18880
  ...model !== undefined ? { model } : {}
18707
18881
  };
18708
18882
  }
18709
- var USAGE2 = `\u7528\u6CD5: main.ts [\u9009\u9879]
18883
+ var USAGE2 = `\u7528\u6CD5: neko web [\u9009\u9879]
18710
18884
 
18711
18885
  \u9009\u9879:
18712
18886
  --port <n> \u9759\u6001 HTTP \u7AEF\u53E3\uFF08\u9ED8\u8BA4 8322\uFF09
@@ -18717,12 +18891,11 @@ var USAGE2 = `\u7528\u6CD5: main.ts [\u9009\u9879]
18717
18891
  --storage <path> SQLite \u5B58\u50A8\u6587\u4EF6\uFF08\u9ED8\u8BA4 ~/.neko/neko.sqlite\uFF09
18718
18892
  --cwd <dir> \u4F1A\u8BDD\u5DE5\u5177\u7684\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09
18719
18893
  --model <ref> \u6A21\u578B\uFF0C\u683C\u5F0F provider/modelId\uFF08\u9ED8\u8BA4 deepseek/deepseek-flash\uFF09
18720
- --no-open \u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u9002\u7528\u4E8E\u8FDC\u7A0B\u6216\u65E0\u684C\u9762\u73AF\u5883\uFF09
18894
+ --no-open \u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u9002\u7528\u4E8E\u8FDC\u7A0B\u6216\u65E0\u684C\u9762\u73AF\u5883\uFF09
18721
18895
  -h, --help \u663E\u793A\u6B64\u5E2E\u52A9
18722
18896
 
18723
- \u53EF\u5728 Web \u8BBE\u7F6E\u9875\u914D\u7F6E Key\uFF1Bgateway \u5B50\u8FDB\u7A0B\u4E5F\u4F1A\u7EE7\u627F DEEPSEEK_API_KEY\u3002`;
18897
+ \u53EF\u5728 Web \u8BBE\u7F6E\u9875\u914D\u7F6E Key\uFF1B\u7F51\u5173\u5B50\u8FDB\u7A0B\u4E5F\u4F1A\u7EE7\u627F DEEPSEEK_API_KEY\u3002`;
18724
18898
  var init_main2 = __esm(async () => {
18725
- init_src();
18726
18899
  init_open_browser();
18727
18900
  init_server();
18728
18901
  try {
@@ -18737,14 +18910,12 @@ var init_main2 = __esm(async () => {
18737
18910
  ...options.storagePath !== undefined ? { storagePath: options.storagePath } : {},
18738
18911
  ...options.cwd !== undefined ? { cwd: options.cwd } : {},
18739
18912
  ...options.model !== undefined ? { model: options.model } : {},
18740
- onLog: (line) => console.log(` [gateway] ${line}`)
18913
+ onLog: (line) => console.log(`[gateway] ${line}`)
18741
18914
  }
18742
18915
  });
18743
- const model = options.model ?? DEFAULT_MODEL;
18744
- console.log(`neko web \u5DF2\u542F\u52A8: ${server.url}`);
18745
- console.log(` \u7F51\u5173: ${server.gateway.wsUrl}\uFF08serverId: ${server.gateway.serverId}\uFF09`);
18746
- console.log(` \u6A21\u578B: ${model.provider}/${model.modelId}`);
18747
- console.log("\u6309 Ctrl+C \u505C\u6B62\u3002");
18916
+ console.log(`[web] neko web \u5DF2\u542F\u52A8: ${server.url}`);
18917
+ console.log(`[web] gateway: ${server.gateway.wsUrl}\uFF08serverId: ${server.gateway.serverId}\uFF09`);
18918
+ console.log("[web] \u6309 Ctrl+C \u505C\u6B62\u3002");
18748
18919
  const shutdown = () => {
18749
18920
  console.log(`
18750
18921
  \u6B63\u5728\u5173\u95ED\u2026`);
@@ -18852,7 +19023,7 @@ function parseGatewayArgs(argv) {
18852
19023
  options.port = port;
18853
19024
  return options;
18854
19025
  }
18855
- var GATEWAY_USAGE = `\u7528\u6CD5: main.ts [\u9009\u9879]
19026
+ var GATEWAY_USAGE = `\u7528\u6CD5: neko serve [\u9009\u9879]
18856
19027
 
18857
19028
  \u9009\u9879:
18858
19029
  --host <ip> WebSocket \u76D1\u542C\u5730\u5740\uFF08\u9ED8\u8BA4 127.0.0.1\uFF0C\u4EC5\u672C\u673A\uFF09
@@ -18896,22 +19067,26 @@ var init_main3 = __esm(async () => {
18896
19067
  console.log(` Unix socket: ${gateway.socketPath}`);
18897
19068
  if (gateway.wsUrl !== undefined)
18898
19069
  console.log(` WebSocket: ${gateway.wsUrl}`);
18899
- console.log("Ctrl-C \u505C\u6B62\u3002");
19070
+ console.log("\u6309 Ctrl+C \u505C\u6B62\u3002");
18900
19071
  process.on("SIGINT", stop);
18901
19072
  process.on("SIGTERM", stop);
18902
19073
  });
18903
19074
 
18904
19075
  // packages/cli/src/main.ts
18905
19076
  init_application_version();
18906
- var USAGE3 = `\u7528\u6CD5: neko <command> [options]
19077
+ var USAGE3 = `\u7528\u6CD5: neko <\u547D\u4EE4> [\u9009\u9879]
18907
19078
 
18908
19079
  \u547D\u4EE4:
18909
- tui \u542F\u52A8\u7EC8\u7AEF\u4EA4\u4E92\u754C\u9762\uFF08\u81EA\u52A8\u62C9\u8D77 gateway\uFF09
18910
- web \u542F\u52A8 Web UI \u9759\u6001\u6258\u7BA1\uFF08\u81EA\u52A8\u62C9\u8D77 gateway\uFF09
18911
- update \u5347\u7EA7\u5F53\u524D Bun \u5168\u5C40\u5B89\u88C5
18912
- serve \u4EC5\u542F\u52A8 gateway\uFF08WebSocket\uFF1BPOSIX \u53E6\u6709 Unix socket\uFF09
19080
+ tui \u542F\u52A8\u7EC8\u7AEF\u4EA4\u4E92\u754C\u9762\uFF08\u81EA\u52A8\u62C9\u8D77\u7F51\u5173\uFF09
19081
+ web \u542F\u52A8 Web UI \u9759\u6001\u6258\u7BA1\uFF08\u81EA\u52A8\u62C9\u8D77\u7F51\u5173\uFF09
19082
+ serve \u4EC5\u542F\u52A8\u7F51\u5173\uFF08WebSocket\uFF1BPOSIX \u53E6\u6709 Unix socket\uFF09
19083
+ update \u5347\u7EA7\u5F53\u524D Bun \u5168\u5C40\u5B89\u88C5\uFF08Windows \u663E\u793A\u624B\u52A8\u5347\u7EA7\u547D\u4EE4\uFF09
19084
+
19085
+ \u9009\u9879:
19086
+ -v, --version \u663E\u793A\u7248\u672C\u53F7
19087
+ -h, --help \u663E\u793A\u6B64\u5E2E\u52A9
18913
19088
 
18914
- \u5404\u547D\u4EE4\u7684\u9009\u9879\u900F\u4F20\uFF0C\u8BE6\u89C1 neko <command> --help\u3002
19089
+ \u5404\u547D\u4EE4\u7684\u9009\u9879\u8BE6\u89C1 neko <\u547D\u4EE4> --help\u3002
18915
19090
  \u53EF\u901A\u8FC7 TUI /login\u3001Web \u8BBE\u7F6E\u9875\u6216 DEEPSEEK_API_KEY / TAVILY_API_KEY \u914D\u7F6E\u51ED\u636E\u3002`;
18916
19091
  var [command2, ...rest] = process.argv.slice(2);
18917
19092
  switch (command2) {
@@ -18919,8 +19094,12 @@ switch (command2) {
18919
19094
  if (rest.length) {
18920
19095
  if (rest.length === 1 && (rest[0] === "--help" || rest[0] === "-h"))
18921
19096
  console.log(`\u7528\u6CD5: neko update
19097
+
18922
19098
  \u5347\u7EA7\u5F53\u524D Bun \u5168\u5C40\u5B89\u88C5\uFF1B\u5B8C\u6210\u540E\u91CD\u65B0\u542F\u52A8 neko\u3002
18923
- -h, --help \u663E\u793A\u5E2E\u52A9`);
19099
+ Windows\uFF1A\u5148\u9000\u51FA neko\uFF0C\u518D\u8FD0\u884C bun add --global neko-harness@latest\u3002
19100
+
19101
+ \u9009\u9879:
19102
+ -h, --help \u663E\u793A\u6B64\u5E2E\u52A9`);
18924
19103
  else {
18925
19104
  console.error("\u7528\u6CD5: neko update");
18926
19105
  process.exitCode = 1;