kfc-code-cli 0.0.1-alpha.7 → 0.0.1-alpha.8

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.
Files changed (2) hide show
  1. package/dist/main.mjs +263 -143
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -95084,26 +95084,21 @@ function pinoToLogger(p) {
95084
95084
  async function createKimiAgent() {
95085
95085
  const workDir = process.cwd();
95086
95086
  const version = getVersion();
95087
- const { client, dispose: disposeClient, maxContextSize, defaultModel: modelAlias, defaultThinking, defaultYolo, defaultPlanMode, theme, defaultEditor, availableModels, syncSessionRuntime } = await createDefaultSoulPlusWireClient({
95087
+ const { client, dispose, maxContextSize, defaultModel, defaultThinking, defaultYolo, defaultPlanMode, availableModels, syncSessionRuntime } = await createDefaultSoulPlusWireClient({
95088
95088
  workspaceDir: workDir,
95089
95089
  userAgent: `KimiCLI/${version}`,
95090
95090
  defaultHeaders: buildKimiDefaultHeaders(version),
95091
95091
  logger: pinoToLogger(getLogger()).child({ component: "session" })
95092
95092
  });
95093
95093
  client.onRequest("hook.request", async () => ({ ok: true }));
95094
- const dispose = async () => {
95095
- await disposeClient();
95096
- };
95097
95094
  return {
95098
95095
  client,
95099
- model: modelAlias,
95096
+ model: defaultModel,
95100
95097
  defaultThinking,
95101
95098
  defaultModes: {
95102
95099
  yolo: defaultYolo,
95103
95100
  planMode: defaultPlanMode
95104
95101
  },
95105
- theme,
95106
- defaultEditor,
95107
95102
  availableModels,
95108
95103
  maxContextSize,
95109
95104
  syncSessionRuntime,
@@ -95254,6 +95249,40 @@ function shellQuote(path) {
95254
95249
  return `'${path.replace(/'/g, "'\\''")}'`;
95255
95250
  }
95256
95251
  //#endregion
95252
+ //#region src/tui/components/media/image-thumbnail.ts
95253
+ /**
95254
+ * Transcript-side rendering of a pasted image.
95255
+ *
95256
+ * On terminals that speak the Kitty graphics protocol or iTerm2 inline
95257
+ * image protocol (detected by pi-tui's `getCapabilities()`), we show
95258
+ * the actual image. Everywhere else we fall back to a one-line text
95259
+ * marker matching the placeholder the user sees in the input box —
95260
+ * this keeps the transcript readable on Terminal.app / Linux default
95261
+ * terminals / `script` recordings without extra chrome.
95262
+ *
95263
+ * Height is capped at ~12 rows so a single screenshot can't monopolize
95264
+ * the viewport; pi-tui handles proportional scaling internally.
95265
+ */
95266
+ const MAX_IMAGE_ROWS = 12;
95267
+ var ImageThumbnail = class extends Container {
95268
+ constructor(attachment, colors) {
95269
+ super();
95270
+ const caps = getCapabilities();
95271
+ if (!(caps.images === "kitty" || caps.images === "iterm2")) {
95272
+ this.addChild(new Text(chalk.dim.cyan(` ${attachment.placeholder}`), 0, 0));
95273
+ return;
95274
+ }
95275
+ const image = new Image(Buffer.from(attachment.bytes).toString("base64"), attachment.mime, { fallbackColor: (s) => chalk.hex(colors.textDim)(s) }, {
95276
+ maxHeightCells: MAX_IMAGE_ROWS,
95277
+ filename: attachment.placeholder
95278
+ }, {
95279
+ widthPx: attachment.width,
95280
+ heightPx: attachment.height
95281
+ });
95282
+ this.addChild(image);
95283
+ }
95284
+ };
95285
+ //#endregion
95257
95286
  //#region src/tui/components/messages/assistant-message.ts
95258
95287
  const BULLET$1 = "⏺ ";
95259
95288
  const INDENT$1 = " ";
@@ -95293,44 +95322,41 @@ var AssistantMessageComponent = class {
95293
95322
  }
95294
95323
  };
95295
95324
  //#endregion
95296
- //#region src/tui/components/media/image-thumbnail.ts
95325
+ //#region src/tui/components/messages/skill-activation.ts
95297
95326
  /**
95298
- * Transcript-side rendering of a pasted image.
95327
+ * Skill activation card.
95299
95328
  *
95300
- * On terminals that speak the Kitty graphics protocol or iTerm2 inline
95301
- * image protocol (detected by pi-tui's `getCapabilities()`), we show
95302
- * the actual image. Everywhere else we fall back to a one-line text
95303
- * marker matching the placeholder the user sees in the input box —
95304
- * this keeps the transcript readable on Terminal.app / Linux default
95305
- * terminals / `script` recordings without extra chrome.
95329
+ * 用户跑 `/skill:foo bar` 时不再把 SKILL.md 正文铺在 user 气泡里 ——
95330
+ * 只显示一张紧凑卡片:
95306
95331
  *
95307
- * Height is capped at ~12 rows so a single screenshot can't monopolize
95308
- * the viewport; pi-tui handles proportional scaling internally.
95332
+ * Activated skill: foo
95333
+ * bar
95334
+ *
95335
+ * 第二行(args)可省。skill 正文已经经由 `client.prompt` 进了 LLM
95336
+ * 的上下文,用户视角无需再看到。
95337
+ *
95338
+ * Resume 重放仍会通过磁盘上的 user_message WAL 把 skill 正文当 user
95339
+ * 消息渲染(核心层未引入 skill_activated WAL 类型)—— 视为已知限制。
95309
95340
  */
95310
- const MAX_IMAGE_ROWS = 12;
95311
- var ImageThumbnail = class extends Container {
95312
- constructor(attachment, colors) {
95341
+ const ARGS_PREVIEW_MAX = 200;
95342
+ var SkillActivationComponent = class extends Container {
95343
+ constructor(name, args, colors) {
95313
95344
  super();
95314
- const caps = getCapabilities();
95315
- if (!(caps.images === "kitty" || caps.images === "iterm2")) {
95316
- this.addChild(new Text(chalk.dim.cyan(` ${attachment.placeholder}`), 0, 0));
95317
- return;
95345
+ this.addChild(new Spacer(1));
95346
+ const head = chalk.hex(colors.primary).bold("▶ Activated skill: ") + chalk.hex(colors.user).bold(name);
95347
+ this.addChild(new Text(head, 0, 0));
95348
+ const trimmed = args?.trim() ?? "";
95349
+ if (trimmed.length > 0) {
95350
+ const preview = trimmed.length > ARGS_PREVIEW_MAX ? trimmed.slice(0, ARGS_PREVIEW_MAX) + "…" : trimmed;
95351
+ this.addChild(new Text(" " + chalk.hex(colors.textDim)(preview), 0, 0));
95318
95352
  }
95319
- const image = new Image(Buffer.from(attachment.bytes).toString("base64"), attachment.mime, { fallbackColor: (s) => chalk.hex(colors.textDim)(s) }, {
95320
- maxHeightCells: MAX_IMAGE_ROWS,
95321
- filename: attachment.placeholder
95322
- }, {
95323
- widthPx: attachment.width,
95324
- heightPx: attachment.height
95325
- });
95326
- this.addChild(image);
95327
95353
  }
95328
95354
  };
95329
95355
  //#endregion
95330
95356
  //#region src/tui/components/messages/thinking.ts
95331
95357
  const BULLET = "⏺ ";
95332
95358
  const INDENT = " ";
95333
- const PREVIEW_LINES$1 = 5;
95359
+ const PREVIEW_LINES$1 = 3;
95334
95360
  const SPINNER_FRAMES = [
95335
95361
  "⠋",
95336
95362
  "⠙",
@@ -95384,7 +95410,7 @@ var ThinkingComponent = class {
95384
95410
  const visibleLines = contentLines.length > PREVIEW_LINES$1 ? contentLines.slice(contentLines.length - PREVIEW_LINES$1) : contentLines;
95385
95411
  return [
95386
95412
  "",
95387
- INDENT + chalk.hex(this.color)(SPINNER_FRAMES[this.spinnerFrame] ?? SPINNER_FRAMES[0]) + chalk.hex(this.color)(" thinking..."),
95413
+ chalk.hex(this.color)(`${SPINNER_FRAMES[this.spinnerFrame] ?? SPINNER_FRAMES[0]} `) + chalk.hex(this.color)("thinking..."),
95388
95414
  ...visibleLines.map((line) => INDENT + line)
95389
95415
  ];
95390
95416
  }
@@ -95586,7 +95612,7 @@ function extractApprovedPlan(output) {
95586
95612
  }
95587
95613
  const STREAMING_FIELD_RE$1 = /"(path|file_path|command|pattern|query|url|description|title|name)"\s*:\s*"((?:\\.|[^"\\])*)"/g;
95588
95614
  function unescapeJsonString$1(s) {
95589
- return s.replace(/\\(["\\/bfnrt])/g, (_, ch) => {
95615
+ return s.replaceAll(/\\(["\\/bfnrt])/g, (_, ch) => {
95590
95616
  switch (ch) {
95591
95617
  case "n": return "\n";
95592
95618
  case "t": return " ";
@@ -95648,7 +95674,7 @@ function extractPartialStringField(text, key) {
95648
95674
  const hex = text.slice(i + 2, i + 6);
95649
95675
  const code = Number.parseInt(hex, 16);
95650
95676
  if (Number.isNaN(code)) return out;
95651
- out += String.fromCharCode(code);
95677
+ out += String.fromCodePoint(code);
95652
95678
  i += 6;
95653
95679
  continue;
95654
95680
  }
@@ -96101,10 +96127,11 @@ var WelcomeComponent = class {
96101
96127
  "",
96102
96128
  ...infoLines
96103
96129
  ];
96104
- const lines = [];
96105
- lines.push("");
96106
- lines.push(primary("╭" + "─".repeat(width - 2) + "╮"));
96107
- lines.push(primary("│") + " ".repeat(width - 2) + primary("│"));
96130
+ const lines = [
96131
+ "",
96132
+ primary("╭" + "─".repeat(width - 2) + "╮"),
96133
+ primary("│") + " ".repeat(width - 2) + primary("│")
96134
+ ];
96108
96135
  for (const content of contentLines) {
96109
96136
  const truncated = truncateToWidth(content, innerWidth, "…");
96110
96137
  const vis = visibleWidth(truncated);
@@ -96250,6 +96277,7 @@ function createTranscriptComponent(state, entry) {
96250
96277
  }
96251
96278
  return msg;
96252
96279
  }
96280
+ case "skill_activation": return new SkillActivationComponent(entry.skillName ?? entry.content, entry.skillArgs, state.colors);
96253
96281
  case "assistant": return createAssistantEntry(state, entry.content);
96254
96282
  case "thinking": return new ThinkingComponent(entry.content, state.colors, true);
96255
96283
  case "tool_call":
@@ -96392,7 +96420,7 @@ function buildImagePart(att) {
96392
96420
  }
96393
96421
  //#endregion
96394
96422
  //#region src/tui/theme/pi-tui-theme.ts
96395
- const HEADING_HASH_PREFIX = /^((?:\x1b\[[0-9;]*m)*)#{1,6}[ \t]+/;
96423
+ const HEADING_HASH_PREFIX = /^((?:\x1B\[[0-9;]*m)*)#{1,6}[ \t]+/;
96396
96424
  function createMarkdownTheme(colors) {
96397
96425
  const stripHash = (text) => text.replace(HEADING_HASH_PREFIX, "$1");
96398
96426
  return {
@@ -96638,7 +96666,7 @@ function fetchSnapshot(gitRoot) {
96638
96666
  const seen = /* @__PURE__ */ new Set();
96639
96667
  for (const path of tracked) seen.add(path);
96640
96668
  for (const path of untracked) seen.add(path);
96641
- const merged = [...seen].sort();
96669
+ const merged = [...seen].toSorted();
96642
96670
  const files = merged.length > MAX_ENTRIES ? merged.slice(0, MAX_ENTRIES) : merged;
96643
96671
  return {
96644
96672
  files,
@@ -96794,6 +96822,7 @@ function createAuthCommands(deps = {}) {
96794
96822
  aliases: [],
96795
96823
  description: "Clear OAuth credentials",
96796
96824
  mode: "both",
96825
+ priority: 40,
96797
96826
  async execute(_args, _ctx) {
96798
96827
  if (deps.client === void 0) return ok$2("OAuth is unavailable: no wire client is attached.");
96799
96828
  if (!(await deps.client.authStatus(providerName)).providers.some((provider) => provider.provider_name === providerName && provider.has_token)) return ok$2("Not logged in.");
@@ -96805,6 +96834,7 @@ function createAuthCommands(deps = {}) {
96805
96834
  aliases: [],
96806
96835
  description: "Start OAuth device code login flow",
96807
96836
  mode: "both",
96837
+ priority: 40,
96808
96838
  async execute(_args, ctx) {
96809
96839
  if (deps.client === void 0) return ok$2("OAuth is unavailable: no wire client is attached.");
96810
96840
  const disposeDeviceCodeHandler = deps.client.onRequest("auth.device_code", (req) => {
@@ -96853,6 +96883,7 @@ const shellCommands = [
96853
96883
  aliases: ["quit", "q"],
96854
96884
  description: "Exit the application",
96855
96885
  mode: "both",
96886
+ priority: 20,
96856
96887
  async execute() {
96857
96888
  return { type: "exit" };
96858
96889
  }
@@ -96862,6 +96893,7 @@ const shellCommands = [
96862
96893
  aliases: ["h", "?"],
96863
96894
  description: "Show available commands and shortcuts",
96864
96895
  mode: "both",
96896
+ priority: 80,
96865
96897
  async execute(_args, _ctx) {
96866
96898
  return ok$1("__show_help__");
96867
96899
  }
@@ -96871,6 +96903,7 @@ const shellCommands = [
96871
96903
  aliases: [],
96872
96904
  description: "Show version information",
96873
96905
  mode: "both",
96906
+ priority: 20,
96874
96907
  async execute(_args, ctx) {
96875
96908
  return ok$1(`Kimi Code v${ctx.appState.version}`);
96876
96909
  }
@@ -96880,6 +96913,7 @@ const shellCommands = [
96880
96913
  aliases: [],
96881
96914
  description: "Start a fresh session in the current workspace",
96882
96915
  mode: "both",
96916
+ priority: 80,
96883
96917
  async execute() {
96884
96918
  return {
96885
96919
  type: "reload",
@@ -96892,6 +96926,7 @@ const shellCommands = [
96892
96926
  aliases: ["resume"],
96893
96927
  description: "Browse and resume sessions",
96894
96928
  mode: "both",
96929
+ priority: 80,
96895
96930
  async execute(_args, _ctx) {
96896
96931
  return ok$1("__show_sessions__");
96897
96932
  }
@@ -96901,6 +96936,7 @@ const shellCommands = [
96901
96936
  aliases: ["rename"],
96902
96937
  description: "Set or show session title",
96903
96938
  mode: "both",
96939
+ priority: 60,
96904
96940
  async execute(args, ctx) {
96905
96941
  const trimmed = args.trim();
96906
96942
  if (trimmed.length === 0) {
@@ -96921,6 +96957,7 @@ const shellCommands = [
96921
96957
  aliases: ["yes"],
96922
96958
  description: "Toggle auto-approve mode",
96923
96959
  mode: "both",
96960
+ priority: 100,
96924
96961
  async execute(args, ctx) {
96925
96962
  let enabled;
96926
96963
  if (args === "on") enabled = true;
@@ -96937,6 +96974,7 @@ const shellCommands = [
96937
96974
  aliases: [],
96938
96975
  description: "Toggle plan mode",
96939
96976
  mode: "both",
96977
+ priority: 100,
96940
96978
  async execute(args, ctx) {
96941
96979
  const subcmd = args.trim().toLowerCase();
96942
96980
  if (subcmd === "view") {
@@ -96964,6 +97002,7 @@ const shellCommands = [
96964
97002
  aliases: [],
96965
97003
  description: "Switch LLM model",
96966
97004
  mode: "both",
97005
+ priority: 100,
96967
97006
  async execute(args, ctx) {
96968
97007
  const trimmed = args.trim();
96969
97008
  if (trimmed.length === 0) return ok$1("__show_model_picker__");
@@ -96976,6 +97015,7 @@ const shellCommands = [
96976
97015
  aliases: ["status"],
96977
97016
  description: "Show session tokens + context window + plan quotas",
96978
97017
  mode: "both",
97018
+ priority: 60,
96979
97019
  async execute() {
96980
97020
  return ok$1("__show_usage__");
96981
97021
  }
@@ -96995,6 +97035,7 @@ const soulCommands = [{
96995
97035
  aliases: [],
96996
97036
  description: "Compact the conversation context",
96997
97037
  mode: "both",
97038
+ priority: 80,
96998
97039
  async execute(args, ctx) {
96999
97040
  const customInstruction = args.trim() || void 0;
97000
97041
  await ctx.client.compact(ctx.appState.sessionId, customInstruction);
@@ -97077,11 +97118,17 @@ var SlashCommandRegistry = class {
97077
97118
  }
97078
97119
  return [...bestScores.values()].toSorted((a, b) => b.score - a.score || a.def.name.localeCompare(b.def.name)).map(({ def }) => def);
97079
97120
  }
97080
- /** List all registered commands, optionally filtered by mode. */
97121
+ /**
97122
+ * List all registered commands, optionally filtered by mode.
97123
+ * Built-in commands come first, sorted alphabetically; skill commands
97124
+ * follow in their original registration order.
97125
+ */
97081
97126
  listAll(mode) {
97082
97127
  const all = [...this.commands.values()];
97083
- if (mode === void 0) return all.toSorted((a, b) => a.name.localeCompare(b.name));
97084
- return all.filter((def) => def.mode === mode || def.mode === "both").toSorted((a, b) => a.name.localeCompare(b.name));
97128
+ const filtered = mode === void 0 ? all : all.filter((def) => def.mode === mode || def.mode === "both");
97129
+ const builtins = filtered.filter((def) => !def.name.startsWith(SKILL_COMMAND_PREFIX)).toSorted((a, b) => (b.priority ?? 0) - (a.priority ?? 0) || a.name.localeCompare(b.name));
97130
+ const skills = filtered.filter((def) => def.name.startsWith(SKILL_COMMAND_PREFIX));
97131
+ return [...builtins, ...skills];
97085
97132
  }
97086
97133
  /** Get the number of registered commands. */
97087
97134
  get size() {
@@ -97652,9 +97699,7 @@ var TodoPanelComponent = class {
97652
97699
  render(width) {
97653
97700
  if (this.todos.length === 0) return [];
97654
97701
  const c = this.colors;
97655
- const lines = [];
97656
- lines.push(chalk.hex(c.border)("─".repeat(width)));
97657
- lines.push(chalk.hex(c.primary).bold(" Todo"));
97702
+ const lines = [chalk.hex(c.border)("─".repeat(width)), chalk.hex(c.primary).bold(" Todo")];
97658
97703
  for (const todo of this.todos) lines.push(renderRow(todo, c));
97659
97704
  return lines.map((line) => truncateToWidth(line, width));
97660
97705
  }
@@ -98024,7 +98069,7 @@ function enqueueMessage(state, text) {
98024
98069
  }
98025
98070
  function recallLastQueued(state) {
98026
98071
  if (state.queuedMessages.length === 0) return void 0;
98027
- const last = state.queuedMessages[state.queuedMessages.length - 1];
98072
+ const last = state.queuedMessages.at(-1);
98028
98073
  state.queuedMessages = state.queuedMessages.slice(0, -1);
98029
98074
  return last.text;
98030
98075
  }
@@ -98090,6 +98135,66 @@ function sendMessageInternal(state, addEntry, input, options) {
98090
98135
  });
98091
98136
  });
98092
98137
  }
98138
+ /**
98139
+ * Activate a skill: render a compact activation card (no skill body in
98140
+ * the transcript) and forward the prepared prompt — `skill.content +
98141
+ * "\n\nUser request:\n" + args` — to core via `client.prompt`. Keeps
98142
+ * the skill body out of the user-visible transcript while still feeding
98143
+ * it into the LLM exactly as the legacy path did.
98144
+ */
98145
+ function sendSkillActivation(state, addEntry, skillName, skillArgs, fullPrompt) {
98146
+ addEntry({
98147
+ id: nextTranscriptId(),
98148
+ kind: "skill_activation",
98149
+ turnId: void 0,
98150
+ renderMode: "plain",
98151
+ content: `Activated skill: ${skillName}`,
98152
+ skillName,
98153
+ skillArgs
98154
+ });
98155
+ state.currentTurnId = void 0;
98156
+ state.assistantDraft = "";
98157
+ state.assistantStreamActive = false;
98158
+ state.thinkingDraft = "";
98159
+ disposeActiveThinkingComponent(state);
98160
+ state.activeToolCalls.clear();
98161
+ state.streamingToolCallArguments.clear();
98162
+ state.livePane = {
98163
+ ...state.livePane,
98164
+ mode: "waiting",
98165
+ thinkingText: "",
98166
+ assistantText: "",
98167
+ pendingToolCall: null,
98168
+ pendingApproval: null,
98169
+ pendingQuestion: null
98170
+ };
98171
+ state.appState.isStreaming = true;
98172
+ state.appState.streamingPhase = "waiting";
98173
+ state.appState.streamingStartTime = Date.now();
98174
+ state.footer.setState(state.appState);
98175
+ state.ui.requestRender();
98176
+ state.client.prompt(state.appState.sessionId, { input: fullPrompt }).catch((error) => {
98177
+ const message = error instanceof Error ? error.message : String(error);
98178
+ state.appState.isStreaming = false;
98179
+ state.appState.streamingPhase = "idle";
98180
+ state.livePane = {
98181
+ mode: "idle",
98182
+ thinkingText: "",
98183
+ assistantText: "",
98184
+ pendingToolCall: null,
98185
+ pendingApproval: null,
98186
+ pendingQuestion: null
98187
+ };
98188
+ state.footer.setState(state.appState);
98189
+ addEntry({
98190
+ id: nextTranscriptId(),
98191
+ kind: "status",
98192
+ renderMode: "plain",
98193
+ content: `Skill "${skillName}" failed: ${message}`,
98194
+ color: state.colors.error
98195
+ });
98196
+ });
98197
+ }
98093
98198
  /** Send from user input: enqueue if busy, otherwise send immediately. */
98094
98199
  function sendMessage(state, addEntry, input, options) {
98095
98200
  if (state.appState.isStreaming || state.appState.isCompacting) {
@@ -98789,36 +98894,71 @@ function endCompaction(state, tokensBefore, tokensAfter) {
98789
98894
  state.ui.requestRender();
98790
98895
  }
98791
98896
  //#endregion
98897
+ //#region src/tui/commands/skill-commands.ts
98898
+ const SKILL_ACTIVATION_SENTINEL_PREFIX = "__activate_skill__:";
98899
+ async function fetchSkills(client, sessionId) {
98900
+ const skills = (await client.listSkills(sessionId))?.skills ?? [];
98901
+ const out = [];
98902
+ for (const s of skills) {
98903
+ if (typeof s?.name !== "string" || s.name.length === 0) continue;
98904
+ if (typeof s.content !== "string") continue;
98905
+ out.push({
98906
+ name: s.name,
98907
+ content: s.content,
98908
+ ...typeof s.description === "string" ? { description: s.description } : {}
98909
+ });
98910
+ }
98911
+ return out;
98912
+ }
98913
+ function buildSkillCommand(skill) {
98914
+ return {
98915
+ name: `${SKILL_COMMAND_PREFIX}${skill.name}`,
98916
+ aliases: [],
98917
+ description: skill.description ?? "",
98918
+ mode: "both",
98919
+ async execute(args) {
98920
+ const trimmed = args.trim();
98921
+ const prompt = trimmed.length > 0 ? `${skill.content}\n\nUser request:\n${trimmed}` : skill.content;
98922
+ const payload = {
98923
+ name: skill.name,
98924
+ args: trimmed,
98925
+ prompt
98926
+ };
98927
+ return {
98928
+ type: "ok",
98929
+ message: `${SKILL_ACTIVATION_SENTINEL_PREFIX}${JSON.stringify(payload)}`
98930
+ };
98931
+ }
98932
+ };
98933
+ }
98934
+ //#endregion
98792
98935
  //#region src/tui/commands/skill-dispatch.ts
98793
98936
  /**
98794
- * Attempt to dispatch `/name args` as a skill activation.
98937
+ * 探测 `/name args` 是否对应某个 skill。命中返回 activate(含拼好的
98938
+ * prompt);未命中或查询失败返回 unknown,message 由调用方推到 transcript。
98795
98939
  */
98796
98940
  async function tryDispatchSkill(client, sessionId, name, args) {
98797
98941
  let skills;
98798
98942
  try {
98799
98943
  skills = (await client.listSkills(sessionId))?.skills ?? [];
98800
- } catch (err) {
98944
+ } catch (error) {
98801
98945
  return {
98802
- matched: false,
98803
- message: `Unknown command: /${name} (skill lookup failed: ${err instanceof Error ? err.message : String(err)})`
98946
+ kind: "unknown",
98947
+ message: `Unknown command: /${name} (skill lookup failed: ${error instanceof Error ? error.message : String(error)})`
98804
98948
  };
98805
98949
  }
98806
- if (!skills.some((s) => s.name === name)) return {
98807
- matched: false,
98950
+ const skill = skills.find((s) => s.name === name);
98951
+ if (skill === void 0 || typeof skill.content !== "string") return {
98952
+ kind: "unknown",
98808
98953
  message: `Unknown command: /${name}`
98809
98954
  };
98810
- try {
98811
- await client.activateSkill(sessionId, name, args);
98812
- return {
98813
- matched: true,
98814
- message: `Skill "${name}" activated.`
98815
- };
98816
- } catch (err) {
98817
- return {
98818
- matched: true,
98819
- message: `Skill "${name}" failed: ${err instanceof Error ? err.message : String(err)}`
98820
- };
98821
- }
98955
+ const trimmed = args.trim();
98956
+ return {
98957
+ kind: "activate",
98958
+ name,
98959
+ args: trimmed,
98960
+ prompt: trimmed.length > 0 ? `${skill.content}\n\nUser request:\n${trimmed}` : skill.content
98961
+ };
98822
98962
  }
98823
98963
  //#endregion
98824
98964
  //#region src/tui/commands/dispatch.ts
@@ -98827,7 +98967,12 @@ async function dispatchSlashCommand(input, state, buildCtx, addEntry) {
98827
98967
  if (!parsed) return false;
98828
98968
  const def = state.registry.find(parsed.name);
98829
98969
  if (!def) {
98970
+ const ctx = buildCtx();
98830
98971
  const result = await tryDispatchSkill(state.client, state.appState.sessionId, parsed.name, parsed.args);
98972
+ if (result.kind === "activate") {
98973
+ ctx.activateSkill(result.name, result.args, result.prompt);
98974
+ return true;
98975
+ }
98831
98976
  addEntry({
98832
98977
  id: `slash-${Date.now()}`,
98833
98978
  kind: "status",
@@ -98895,6 +99040,24 @@ async function dispatchSlashCommand(input, state, buildCtx, addEntry) {
98895
99040
  ctx.sendAsMessage(msg);
98896
99041
  return true;
98897
99042
  }
99043
+ if (result.message.startsWith("__activate_skill__:")) {
99044
+ const raw = result.message.slice(19);
99045
+ let payload;
99046
+ try {
99047
+ payload = JSON.parse(raw);
99048
+ } catch (error) {
99049
+ addEntry({
99050
+ id: `slash-err-${Date.now()}`,
99051
+ kind: "status",
99052
+ renderMode: "plain",
99053
+ content: `Skill activation failed: malformed payload (${error instanceof Error ? error.message : String(error)})`,
99054
+ color: state.colors.error
99055
+ });
99056
+ return true;
99057
+ }
99058
+ ctx.activateSkill(payload.name, payload.args, payload.prompt);
99059
+ return true;
99060
+ }
98898
99061
  addEntry({
98899
99062
  id: `slash-${Date.now()}`,
98900
99063
  kind: "status",
@@ -98986,7 +99149,7 @@ function handleContentDelta(ectx, data) {
98986
99149
  //#region src/tui/handlers/tool.ts
98987
99150
  const STREAMING_FIELD_RE = /"(path|file_path|command|pattern|query|url|description|title|name)"\s*:\s*"((?:\\.|[^"\\])*)"/g;
98988
99151
  function unescapeJsonString(s) {
98989
- return s.replace(/\\(["\\/bfnrt])/g, (_, ch) => {
99152
+ return s.replaceAll(/\\(["\\/bfnrt])/g, (_, ch) => {
98990
99153
  switch (ch) {
98991
99154
  case "n": return "\n";
98992
99155
  case "t": return " ";
@@ -99269,7 +99432,7 @@ var ChoicePickerComponent = class extends Container {
99269
99432
  super();
99270
99433
  this.opts = opts;
99271
99434
  const currentIdx = opts.options.findIndex((o) => o.value === opts.currentValue);
99272
- this.selectedIndex = currentIdx >= 0 ? currentIdx : 0;
99435
+ this.selectedIndex = Math.max(currentIdx, 0);
99273
99436
  }
99274
99437
  handleInput(data) {
99275
99438
  if (matchesKey(data, Key.escape)) {
@@ -99292,12 +99455,13 @@ var ChoicePickerComponent = class extends Container {
99292
99455
  }
99293
99456
  render(width) {
99294
99457
  const { colors } = this.opts;
99295
- const lines = [];
99296
- lines.push(chalk.hex(colors.primary)("─".repeat(width)));
99297
- lines.push(chalk.hex(colors.primary).bold(` ${this.opts.title}`));
99298
99458
  const hint = this.opts.hint ?? "↑↓ navigate · Enter select · Esc cancel";
99299
- lines.push(chalk.hex(colors.textMuted)(` ${hint}`));
99300
- lines.push("");
99459
+ const lines = [
99460
+ chalk.hex(colors.primary)("".repeat(width)),
99461
+ chalk.hex(colors.primary).bold(` ${this.opts.title}`),
99462
+ chalk.hex(colors.textMuted)(` ${hint}`),
99463
+ ""
99464
+ ];
99301
99465
  for (let i = 0; i < this.opts.options.length; i++) {
99302
99466
  const opt = this.opts.options[i];
99303
99467
  const isSelected = i === this.selectedIndex;
@@ -99455,31 +99619,28 @@ var HelpPanelComponent = class extends Container {
99455
99619
  const muted = chalk.hex(c.textMuted);
99456
99620
  const kbdColor = chalk.hex(c.warning);
99457
99621
  const slashColor = chalk.hex(c.primary);
99458
- const lines = [];
99459
- lines.push(accent("─".repeat(width)));
99460
- lines.push(accent.bold(" help ") + muted("· Esc / Enter / q to close · ↑↓ scroll"));
99461
- lines.push("");
99462
- lines.push(` ${dim("Sure, Kimi is ready to help! Just send a message to get started.")}`);
99463
- lines.push("");
99464
- lines.push(` ${chalk.bold("Keyboard shortcuts")}`);
99465
99622
  const shortcuts = this.opts.shortcuts ?? DEFAULT_KEYBOARD_SHORTCUTS;
99466
99623
  const kbdWidth = Math.max(8, ...shortcuts.map((s) => s.keys.length));
99467
- for (const s of shortcuts) lines.push(` ${kbdColor(s.keys.padEnd(kbdWidth))} ${dim(s.description)}`);
99468
- lines.push("");
99469
- lines.push(` ${chalk.bold("Slash commands")}`);
99470
- const sortedCmds = [...this.opts.commands].sort((a, b) => a.name.localeCompare(b.name));
99624
+ const sortedCmds = [...this.opts.commands].toSorted((a, b) => a.name.localeCompare(b.name));
99471
99625
  const cmdLabels = sortedCmds.map((c) => {
99472
99626
  const aliases = c.aliases.length > 0 ? ` (${c.aliases.map((a) => "/" + a).join(", ")})` : "";
99473
99627
  return `/${c.name}${aliases}`;
99474
99628
  });
99475
99629
  const cmdWidth = Math.max(12, ...cmdLabels.map((l) => l.length));
99476
- for (let i = 0; i < sortedCmds.length; i++) {
99477
- const cmd = sortedCmds[i];
99478
- const label = cmdLabels[i];
99479
- lines.push(` ${slashColor(label.padEnd(cmdWidth))} ${dim(cmd.description)}`);
99480
- }
99481
- lines.push("");
99482
- lines.push(accent("".repeat(width)));
99630
+ const lines = [
99631
+ accent("─".repeat(width)),
99632
+ accent.bold(" help ") + muted("· Esc / Enter / q to close · ↑↓ scroll"),
99633
+ "",
99634
+ ` ${dim("Sure, Kimi is ready to help! Just send a message to get started.")}`,
99635
+ "",
99636
+ ` ${chalk.bold("Keyboard shortcuts")}`,
99637
+ ...shortcuts.map((s) => ` ${kbdColor(s.keys.padEnd(kbdWidth))} ${dim(s.description)}`),
99638
+ "",
99639
+ ` ${chalk.bold("Slash commands")}`,
99640
+ ...sortedCmds.map((cmd, i) => ` ${slashColor(cmdLabels[i].padEnd(cmdWidth))} ${dim(cmd.description)}`),
99641
+ "",
99642
+ accent("─".repeat(width))
99643
+ ];
99483
99644
  const content = lines.slice(1, lines.length - 1);
99484
99645
  const maxVisible = Math.max(5, this.opts.maxVisible ?? 24);
99485
99646
  if (content.length > maxVisible) {
@@ -99490,7 +99651,7 @@ var HelpPanelComponent = class extends Container {
99490
99651
  lines[0],
99491
99652
  ...slice,
99492
99653
  scrollInfo,
99493
- lines[lines.length - 1]
99654
+ lines.at(-1)
99494
99655
  ].map((line) => truncateToWidth(line, width));
99495
99656
  }
99496
99657
  this.scrollTop = 0;
@@ -99664,8 +99825,7 @@ var SessionPickerComponent = class extends Container {
99664
99825
  }
99665
99826
  render(width) {
99666
99827
  const colors = this.colors;
99667
- const lines = [];
99668
- lines.push(chalk.hex(colors.primary)("─".repeat(width)));
99828
+ const lines = [chalk.hex(colors.primary)("─".repeat(width))];
99669
99829
  if (this.loading) {
99670
99830
  lines.push(chalk.hex(colors.primary).bold("Sessions"));
99671
99831
  lines.push(chalk.hex(colors.textMuted)("Loading sessions..."));
@@ -101209,10 +101369,7 @@ function registerReverseRPCHandlers(state, client, callbacks) {
101209
101369
  showPanel: (payload) => showQuestionDialog(state, payload),
101210
101370
  hidePanel: () => hideQuestionDialog(state)
101211
101371
  });
101212
- const disposers = [];
101213
- disposers.push(client.onRequest("approval.request", createApprovalRequestHandler(state)));
101214
- disposers.push(client.onRequest("question.ask", createQuestionAskHandler(state)));
101215
- return disposers;
101372
+ return [client.onRequest("approval.request", createApprovalRequestHandler(state)), client.onRequest("question.ask", createQuestionAskHandler(state))];
101216
101373
  }
101217
101374
  //#endregion
101218
101375
  //#region src/utils/clipboard/clipboard-native.ts
@@ -101554,44 +101711,6 @@ function readUInt32BE(b, off) {
101554
101711
  return b[off] * 16777216 + (b[off + 1] << 16) + (b[off + 2] << 8) + b[off + 3] >>> 0;
101555
101712
  }
101556
101713
  //#endregion
101557
- //#region src/tui/commands/skill-commands.ts
101558
- async function fetchSkills(client, sessionId) {
101559
- const skills = (await client.listSkills(sessionId))?.skills ?? [];
101560
- const out = [];
101561
- for (const s of skills) {
101562
- if (typeof s?.name !== "string" || s.name.length === 0) continue;
101563
- if (typeof s.content !== "string") continue;
101564
- out.push({
101565
- name: s.name,
101566
- content: s.content,
101567
- ...typeof s.description === "string" ? { description: s.description } : {}
101568
- });
101569
- }
101570
- return out;
101571
- }
101572
- function buildSkillCommand(skill) {
101573
- return {
101574
- name: `${SKILL_COMMAND_PREFIX}${skill.name}`,
101575
- aliases: [],
101576
- description: skill.description ?? "",
101577
- mode: "both",
101578
- async execute(args, ctx) {
101579
- const trimmed = args.trim();
101580
- const prompt = trimmed.length > 0 ? `${skill.content}\n\nUser request:\n${trimmed}` : skill.content;
101581
- try {
101582
- await ctx.client.prompt(ctx.appState.sessionId, { input: prompt });
101583
- } catch (err) {
101584
- const msg = err instanceof Error ? err.message : String(err);
101585
- return {
101586
- type: "ok",
101587
- message: `Skill "${skill.name}" failed: ${msg}`
101588
- };
101589
- }
101590
- return { type: "ok" };
101591
- }
101592
- };
101593
- }
101594
- //#endregion
101595
101714
  //#region src/tui/components/editor/file-mention-provider.ts
101596
101715
  /**
101597
101716
  * `@file` autocomplete provider for the input box.
@@ -101701,7 +101820,7 @@ function rankForEmptyQuery(files, snapshot) {
101701
101820
  const result = [];
101702
101821
  const cap = MAX_SUGGESTIONS_WHEN_EMPTY;
101703
101822
  const inFiles = new Set(files);
101704
- const byRecency = [...snapshot.recencyOrder.entries()].filter(([path]) => inFiles.has(path)).sort((a, b) => a[1] - b[1]);
101823
+ const byRecency = [...snapshot.recencyOrder.entries()].filter(([path]) => inFiles.has(path)).toSorted((a, b) => a[1] - b[1]);
101705
101824
  for (const [path] of byRecency) {
101706
101825
  if (result.length >= cap) break;
101707
101826
  if (picked.has(path)) continue;
@@ -101709,7 +101828,7 @@ function rankForEmptyQuery(files, snapshot) {
101709
101828
  result.push(path);
101710
101829
  }
101711
101830
  if (result.length < cap) {
101712
- const byMtime = files.filter((p) => !picked.has(p) && snapshot.mtimeByPath.has(p)).sort((a, b) => (snapshot.mtimeByPath.get(b) ?? 0) - (snapshot.mtimeByPath.get(a) ?? 0));
101831
+ const byMtime = files.filter((p) => !picked.has(p) && snapshot.mtimeByPath.has(p)).toSorted((a, b) => (snapshot.mtimeByPath.get(b) ?? 0) - (snapshot.mtimeByPath.get(a) ?? 0));
101713
101832
  for (const path of byMtime) {
101714
101833
  if (result.length >= cap) break;
101715
101834
  picked.add(path);
@@ -101717,7 +101836,7 @@ function rankForEmptyQuery(files, snapshot) {
101717
101836
  }
101718
101837
  }
101719
101838
  if (result.length < cap) {
101720
- const rest = files.filter((p) => !picked.has(p)).sort((a, b) => basename(a).localeCompare(basename(b)) || a.localeCompare(b));
101839
+ const rest = files.filter((p) => !picked.has(p)).toSorted((a, b) => basename(a).localeCompare(basename(b)) || a.localeCompare(b));
101721
101840
  for (const path of rest) {
101722
101841
  if (result.length >= cap) break;
101723
101842
  result.push(path);
@@ -102238,6 +102357,7 @@ var KimiTUI = class {
102238
102357
  showUsage(this.state);
102239
102358
  },
102240
102359
  sendAsMessage: (text) => sendMessage(this.state, (e) => this.addEntry(e), text),
102360
+ activateSkill: (name, args, fullPrompt) => sendSkillActivation(this.state, (e) => this.addEntry(e), name, args, fullPrompt),
102241
102361
  performReload: (action) => performReload(this.state, action, this.buildInputHooks())
102242
102362
  };
102243
102363
  }
@@ -102285,9 +102405,9 @@ async function runShell(opts, version) {
102285
102405
  isReplaying: false,
102286
102406
  streamingPhase: "idle",
102287
102407
  streamingStartTime: 0,
102288
- theme: ctx.theme,
102408
+ theme: "dark",
102289
102409
  version,
102290
- editorCommand: ctx.defaultEditor.length > 0 ? ctx.defaultEditor : null,
102410
+ editorCommand: null,
102291
102411
  availableModels: ctx.availableModels,
102292
102412
  sessionTitle: null
102293
102413
  };
@@ -102421,7 +102541,7 @@ async function promptForInstallConfirmation(options) {
102421
102541
  const output = options.output ?? process.stdout;
102422
102542
  const choices = createInstallPromptChoices(options.target);
102423
102543
  let selectedIndex = getDefaultInstallPromptSelection(choices);
102424
- return await new Promise((resolve) => {
102544
+ return new Promise((resolve) => {
102425
102545
  let lineCount = 0;
102426
102546
  const hadRawMode = "isRaw" in input ? input.isRaw === true : false;
102427
102547
  const canSetRawMode = typeof input.setRawMode === "function";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kfc-code-cli",
3
- "version": "0.0.1-alpha.7",
3
+ "version": "0.0.1-alpha.8",
4
4
  "description": "KFC crazy ",
5
5
  "license": "MIT",
6
6
  "bin": {