@aragon-agent/cli 0.6.35 → 0.6.37

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 (146) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +3173 -3071
  3. package/dist/agent/agent-mode.d.ts +20 -4
  4. package/dist/agent/agent-mode.js +20 -4
  5. package/dist/agent/controller.d.ts +38 -3
  6. package/dist/agent/controller.js +64 -4
  7. package/dist/agent/headless.js +31 -0
  8. package/dist/agent/reducer.d.ts +13 -2
  9. package/dist/agent/reducer.js +1 -0
  10. package/dist/cli.js +12 -0
  11. package/dist/commands/agents-command.d.ts +15 -0
  12. package/dist/commands/agents-command.js +27 -0
  13. package/dist/commands/builtins.js +131 -4
  14. package/dist/commands/perf.d.ts +11 -0
  15. package/dist/commands/perf.js +2 -1
  16. package/dist/commands/registry.d.ts +14 -0
  17. package/dist/config/app-paths.d.ts +15 -0
  18. package/dist/config/app-paths.js +33 -2
  19. package/dist/config/env.d.ts +4 -1
  20. package/dist/config/env.js +28 -2
  21. package/dist/config/load.d.ts +10 -0
  22. package/dist/config/load.js +8 -1
  23. package/dist/config/schema.d.ts +68 -0
  24. package/dist/config/schema.js +39 -0
  25. package/dist/config/store.js +12 -1
  26. package/dist/config/ui-state.d.ts +18 -0
  27. package/dist/config/ui-state.js +9 -0
  28. package/dist/exec/events.d.ts +25 -0
  29. package/dist/exec/events.js +4 -0
  30. package/dist/exec/index.js +16 -1
  31. package/dist/exec/runner.js +10 -0
  32. package/dist/proc/limits.d.ts +16 -0
  33. package/dist/proc/limits.js +16 -0
  34. package/dist/proc/supervisor.d.ts +20 -1
  35. package/dist/proc/supervisor.js +42 -14
  36. package/dist/prompts/command.d.ts +34 -0
  37. package/dist/prompts/command.js +237 -0
  38. package/dist/prompts/copy.d.ts +89 -0
  39. package/dist/prompts/copy.js +131 -0
  40. package/dist/prompts/fill.d.ts +51 -0
  41. package/dist/prompts/fill.js +46 -0
  42. package/dist/prompts/limits.d.ts +53 -0
  43. package/dist/prompts/limits.js +53 -0
  44. package/dist/prompts/normalize.d.ts +53 -0
  45. package/dist/prompts/normalize.js +190 -0
  46. package/dist/prompts/resolve.d.ts +31 -0
  47. package/dist/prompts/resolve.js +60 -0
  48. package/dist/prompts/search.d.ts +58 -0
  49. package/dist/prompts/search.js +146 -0
  50. package/dist/prompts/store.d.ts +86 -0
  51. package/dist/prompts/store.js +453 -0
  52. package/dist/prompts/types.d.ts +204 -0
  53. package/dist/prompts/types.js +13 -0
  54. package/dist/session/checkpoint.d.ts +8 -1
  55. package/dist/session/checkpoint.js +6 -0
  56. package/dist/session/persist.d.ts +19 -1
  57. package/dist/session/persist.js +13 -1
  58. package/dist/session/startup-session.d.ts +7 -2
  59. package/dist/session/startup-session.js +5 -2
  60. package/dist/session/store.js +3 -0
  61. package/dist/session/validate-session.js +64 -1
  62. package/dist/team/limits.d.ts +25 -0
  63. package/dist/team/limits.js +26 -0
  64. package/dist/team/runtime.js +27 -2
  65. package/dist/team/settled.d.ts +57 -0
  66. package/dist/team/settled.js +87 -0
  67. package/dist/team/subagent.js +48 -2
  68. package/dist/team/timeline.d.ts +74 -0
  69. package/dist/team/timeline.js +101 -0
  70. package/dist/team/types.d.ts +24 -1
  71. package/dist/todo/acceptance.d.ts +120 -0
  72. package/dist/todo/acceptance.js +399 -0
  73. package/dist/todo/follow-through.d.ts +48 -23
  74. package/dist/todo/follow-through.js +110 -44
  75. package/dist/todo/limits.d.ts +62 -1
  76. package/dist/todo/limits.js +62 -1
  77. package/dist/todo/panel-rows.d.ts +29 -39
  78. package/dist/todo/panel-rows.js +37 -83
  79. package/dist/todo/plan-sections.d.ts +60 -0
  80. package/dist/todo/plan-sections.js +108 -0
  81. package/dist/todo/prompt.d.ts +17 -12
  82. package/dist/todo/prompt.js +59 -40
  83. package/dist/todo/row-window.d.ts +51 -0
  84. package/dist/todo/row-window.js +107 -0
  85. package/dist/todo/store.d.ts +139 -31
  86. package/dist/todo/store.js +271 -73
  87. package/dist/todo/todo-tool.d.ts +33 -8
  88. package/dist/todo/todo-tool.js +186 -30
  89. package/dist/todo/types.d.ts +58 -0
  90. package/dist/ui/App.js +310 -46
  91. package/dist/ui/Composer.d.ts +6 -2
  92. package/dist/ui/Composer.js +2 -2
  93. package/dist/ui/Header.d.ts +9 -1
  94. package/dist/ui/Header.js +2 -2
  95. package/dist/ui/PromptInput.d.ts +21 -1
  96. package/dist/ui/PromptInput.js +21 -1
  97. package/dist/ui/ScrollbackShell.d.ts +19 -19
  98. package/dist/ui/ScrollbackShell.js +8 -1
  99. package/dist/ui/StatusBar.d.ts +5 -0
  100. package/dist/ui/StatusBar.js +1 -0
  101. package/dist/ui/TeamPanel.d.ts +26 -44
  102. package/dist/ui/TeamPanel.js +46 -135
  103. package/dist/ui/TodoPanel.d.ts +17 -14
  104. package/dist/ui/TodoPanel.js +146 -38
  105. package/dist/ui/Transcript.js +1 -1
  106. package/dist/ui/composer-input.d.ts +10 -0
  107. package/dist/ui/composer-input.js +57 -24
  108. package/dist/ui/entries/TodoCard.d.ts +26 -11
  109. package/dist/ui/entries/TodoCard.js +56 -15
  110. package/dist/ui/glyphs.d.ts +19 -0
  111. package/dist/ui/glyphs.js +4 -0
  112. package/dist/ui/layout/agents-board.d.ts +102 -0
  113. package/dist/ui/layout/agents-board.js +389 -0
  114. package/dist/ui/layout/cells.d.ts +61 -0
  115. package/dist/ui/layout/cells.js +156 -0
  116. package/dist/ui/layout/header-hints.d.ts +13 -3
  117. package/dist/ui/layout/header-hints.js +48 -14
  118. package/dist/ui/layout/status-detail-layout.js +9 -0
  119. package/dist/ui/layout/status-layout.d.ts +13 -0
  120. package/dist/ui/layout/team-row.d.ts +139 -0
  121. package/dist/ui/layout/team-row.js +246 -0
  122. package/dist/ui/layout/todo-layout.d.ts +38 -0
  123. package/dist/ui/layout/todo-layout.js +75 -0
  124. package/dist/ui/layout/virtual-window.js +23 -3
  125. package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
  126. package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
  127. package/dist/ui/overlays/HelpOverlay.js +17 -2
  128. package/dist/ui/overlays/PromptEditorPage.d.ts +48 -0
  129. package/dist/ui/overlays/PromptEditorPage.js +227 -0
  130. package/dist/ui/overlays/PromptLibraryOverlay.d.ts +37 -0
  131. package/dist/ui/overlays/PromptLibraryOverlay.js +191 -0
  132. package/dist/ui/overlays/PromptPicker.d.ts +36 -0
  133. package/dist/ui/overlays/PromptPicker.js +129 -0
  134. package/dist/ui/overlays/PromptTextArea.d.ts +86 -0
  135. package/dist/ui/overlays/PromptTextArea.js +141 -0
  136. package/dist/ui/overlays/prompt-library-state.d.ts +162 -0
  137. package/dist/ui/overlays/prompt-library-state.js +335 -0
  138. package/dist/ui/overlays/prompt-picker-state.d.ts +71 -0
  139. package/dist/ui/overlays/prompt-picker-state.js +98 -0
  140. package/dist/ui/scrollback/band.js +14 -1
  141. package/dist/ui/team-status.d.ts +25 -0
  142. package/dist/ui/team-status.js +31 -3
  143. package/dist/ui/use-startup-notices.d.ts +35 -1
  144. package/dist/ui/use-startup-notices.js +29 -2
  145. package/dist/ui/use-wheel-routing.js +9 -1
  146. package/package.json +2 -2
@@ -79,6 +79,22 @@ export const PROC_LIMITS = {
79
79
  outputCoalesceMs: 100,
80
80
  /** `SIGTERM` -> `SIGKILL` escalation window for a graceful stop. */
81
81
  stopGraceMs: 3000,
82
+ /**
83
+ * How long to KEEP ASKING whether the `SIGKILL` actually landed before
84
+ * declaring the job `stopped`.
85
+ *
86
+ * THIS NUMBER IS NOT A GUESS ABOUT THE PLATFORM. `killTree` spawns `taskkill`,
87
+ * which is a process of its own: a Windows tree kill of a shell plus its node
88
+ * child measures ~0.8 s on the reference machine (while starting `cmd` costs
89
+ * 50 ms), so the fixed 250 ms this used to allow expired first and `stop()`
90
+ * reported `stopped` with the child still running - AC-18 caught exactly that.
91
+ * The wait stays BOUNDED against the opposite failure (a promise that never
92
+ * resolves); at this deadline the record keeps the honest `killIncomplete`
93
+ * flag instead.
94
+ */
95
+ killVerifyMs: 2000,
96
+ /** Liveness poll interval while waiting for a `SIGKILL` to land. */
97
+ killVerifyPollMs: 50,
82
98
  /** Below this many terminal columns the status chip degrades to `[N]`. */
83
99
  statusCompactCols: 92,
84
100
  // --- long-task-jobs: bounded waiting, progress, notices, log mirror --------
@@ -82,12 +82,31 @@ export declare class ProcSupervisor implements ProcSupervisorPort {
82
82
  *
83
83
  * `SIGTERM` FIRST, `SIGKILL` AFTER `stopGraceMs` — the graceful ladder, which
84
84
  * belongs here rather than in `reapSync` because this path has a live event
85
- * loop to run it on. It then CHECKS `process.kill(pid, 0)` and records
85
+ * loop to run it on. It then POLLS `process.kill(pid, 0)` for the kill to land,
86
+ * waiting at most `killVerifyMs` so the promise always settles, and records
86
87
  * `killIncomplete` when the pid is still there (P2-6): an honest warning beats
87
88
  * a silent leak, and `killTree` cannot know on its own whether it worked.
88
89
  */
89
90
  stop(id: string): Promise<ServiceSnapshot[]>;
90
91
  private stopOne;
92
+ /**
93
+ * Wait for the `SIGKILL` to LAND before calling the job `stopped` (AC-18).
94
+ *
95
+ * THE WINDOW IS NOT THE PLATFORM'S TO PROMISE. This used to wait a fixed
96
+ * 250 ms and then settle whether or not the pid was still there - but the kill
97
+ * it waits for is a `taskkill` PROCESS, and a Windows tree kill measures
98
+ * ~0.8 s here (while starting `cmd` costs 50 ms). So `stop()` resolved while
99
+ * the child was still running: the card, the model and the snapshot all said
100
+ * `stopped` about a live server, which is the one claim this class exists to
101
+ * make honestly.
102
+ *
103
+ * The poll ASKS THE OS instead of assuming, and stops the moment the pid is
104
+ * gone or the child's own `exit` handler settles first (`finish` clears this
105
+ * timer). It stays BOUNDED anyway, because a promise that never resolves is
106
+ * the failure this feature was written to remove: at `killVerifyMs` we settle
107
+ * regardless and keep `killIncomplete`, the warning `bash_output` renders.
108
+ */
109
+ private settleAfterKill;
91
110
  /**
92
111
  * SYNCHRONOUS, TOTAL, IDEMPOTENT. The only entry point a signal hook,
93
112
  * `process.on('exit')` or `handleFatal` may call.
@@ -301,7 +301,8 @@ export class ProcSupervisor {
301
301
  *
302
302
  * `SIGTERM` FIRST, `SIGKILL` AFTER `stopGraceMs` — the graceful ladder, which
303
303
  * belongs here rather than in `reapSync` because this path has a live event
304
- * loop to run it on. It then CHECKS `process.kill(pid, 0)` and records
304
+ * loop to run it on. It then POLLS `process.kill(pid, 0)` for the kill to land,
305
+ * waiting at most `killVerifyMs` so the promise always settles, and records
305
306
  * `killIncomplete` when the pid is still there (P2-6): an honest warning beats
306
307
  * a silent leak, and `killTree` cannot know on its own whether it worked.
307
308
  */
@@ -335,19 +336,7 @@ export class ProcSupervisor {
335
336
  if (isTerminalStatus(record.status))
336
337
  return;
337
338
  killTree(record.pid, 'SIGKILL');
338
- // Give the SIGKILL one tick to land before declaring the outcome, then
339
- // settle whether or not `exit` fired: a promise that never resolves is
340
- // the failure this whole feature exists to remove.
341
- const settle = setTimeout(() => {
342
- if (isTerminalStatus(record.status))
343
- return;
344
- record.status = 'stopped';
345
- record.endedAt = Date.now();
346
- if (isAlive(record.pid))
347
- record.killIncomplete = true;
348
- this.finish(entry, 'stopped');
349
- }, 250);
350
- settle.unref?.();
339
+ this.settleAfterKill(entry);
351
340
  }, PROC_LIMITS.stopGraceMs);
352
341
  entry.escalation.unref?.();
353
342
  // NO SECOND `'exit'` LISTENER HERE. The one `start()` registered already
@@ -356,6 +345,45 @@ export class ProcSupervisor {
356
345
  // reads as the thing deciding the status.
357
346
  });
358
347
  }
348
+ /**
349
+ * Wait for the `SIGKILL` to LAND before calling the job `stopped` (AC-18).
350
+ *
351
+ * THE WINDOW IS NOT THE PLATFORM'S TO PROMISE. This used to wait a fixed
352
+ * 250 ms and then settle whether or not the pid was still there - but the kill
353
+ * it waits for is a `taskkill` PROCESS, and a Windows tree kill measures
354
+ * ~0.8 s here (while starting `cmd` costs 50 ms). So `stop()` resolved while
355
+ * the child was still running: the card, the model and the snapshot all said
356
+ * `stopped` about a live server, which is the one claim this class exists to
357
+ * make honestly.
358
+ *
359
+ * The poll ASKS THE OS instead of assuming, and stops the moment the pid is
360
+ * gone or the child's own `exit` handler settles first (`finish` clears this
361
+ * timer). It stays BOUNDED anyway, because a promise that never resolves is
362
+ * the failure this feature was written to remove: at `killVerifyMs` we settle
363
+ * regardless and keep `killIncomplete`, the warning `bash_output` renders.
364
+ */
365
+ settleAfterKill(entry) {
366
+ const deadline = Date.now() + PROC_LIMITS.killVerifyMs;
367
+ const check = () => {
368
+ const { record } = entry;
369
+ // The child's own `exit` handler won the race and already settled this.
370
+ if (isTerminalStatus(record.status))
371
+ return;
372
+ const isGone = !isAlive(record.pid);
373
+ if (!isGone && Date.now() < deadline) {
374
+ entry.escalation = setTimeout(check, PROC_LIMITS.killVerifyPollMs);
375
+ entry.escalation.unref?.();
376
+ return;
377
+ }
378
+ record.status = 'stopped';
379
+ record.endedAt = Date.now();
380
+ if (!isGone)
381
+ record.killIncomplete = true;
382
+ this.finish(entry, 'stopped');
383
+ };
384
+ entry.escalation = setTimeout(check, PROC_LIMITS.killVerifyPollMs);
385
+ entry.escalation.unref?.();
386
+ }
359
387
  // -----------------------------------------------------------------------
360
388
  // Exit reaping (G5 / I-9)
361
389
  // -----------------------------------------------------------------------
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `/prompts` and its subcommands (quick-prompt-library §4.3).
3
+ *
4
+ * ASCII ONLY - `src/prompts/**` is inside `glyphs.test.ts::inScope`. Every user-facing
5
+ * sentence comes from `copy.ts`.
6
+ *
7
+ * THE COMMAND DOES NOT REACH THE UI THROUGH A CLOSURE. `registerBuiltinCommands(registry)`
8
+ * receives only the registry, so there is no context in which to build a port (F-33).
9
+ * The capability therefore arrives on `CommandContext.prompts`, an OPTIONAL field -
10
+ * `CommandContext` is constructed by literal in dozens of fixtures, and a required
11
+ * field would break every one of them for a feature none of them is about (F-14).
12
+ *
13
+ * THREE THINGS COME FROM THE PORT, and nothing else does: opening the picker, opening
14
+ * the manager on a page, and resolving a reference against the LIVE snapshot. Every
15
+ * MUTATION goes through `store.ts` directly, because that module is the only write
16
+ * authority (I-6) and routing a write through a UI port would make the overlay a
17
+ * second one.
18
+ *
19
+ * `<n|title>` IS RESOLVED BY `resolvePromptRef`, never by "the first match". Titles may
20
+ * repeat, and `rm` has no recycle bin, so an ambiguous reference is a NAMED failure
21
+ * with candidates rather than a guess (D-22).
22
+ */
23
+ import type { CommandContext, CommandRegistry } from '../commands/registry.js';
24
+ import type { QuickPrompt } from './types.js';
25
+ export declare function registerPromptCommands(registry: CommandRegistry): void;
26
+ /** `1. title [fav] #tag 12 uses` - no body, ever (D-17). */
27
+ export declare function promptListLine(prompt: QuickPrompt, at: number): string;
28
+ /**
29
+ * The dispatcher.
30
+ *
31
+ * `enabled=false` is answered BEFORE any subcommand, and ANSWERED rather than ignored:
32
+ * the command stays registered precisely so this sentence can exist (D-19 / AC-30).
33
+ */
34
+ export declare function runPromptCommand(ctx: CommandContext): Promise<void>;
@@ -0,0 +1,237 @@
1
+ /**
2
+ * `/prompts` and its subcommands (quick-prompt-library §4.3).
3
+ *
4
+ * ASCII ONLY - `src/prompts/**` is inside `glyphs.test.ts::inScope`. Every user-facing
5
+ * sentence comes from `copy.ts`.
6
+ *
7
+ * THE COMMAND DOES NOT REACH THE UI THROUGH A CLOSURE. `registerBuiltinCommands(registry)`
8
+ * receives only the registry, so there is no context in which to build a port (F-33).
9
+ * The capability therefore arrives on `CommandContext.prompts`, an OPTIONAL field -
10
+ * `CommandContext` is constructed by literal in dozens of fixtures, and a required
11
+ * field would break every one of them for a feature none of them is about (F-14).
12
+ *
13
+ * THREE THINGS COME FROM THE PORT, and nothing else does: opening the picker, opening
14
+ * the manager on a page, and resolving a reference against the LIVE snapshot. Every
15
+ * MUTATION goes through `store.ts` directly, because that module is the only write
16
+ * authority (I-6) and routing a write through a UI port would make the overlay a
17
+ * second one.
18
+ *
19
+ * `<n|title>` IS RESOLVED BY `resolvePromptRef`, never by "the first match". Titles may
20
+ * repeat, and `rm` has no recycle bin, so an ambiguous reference is a NAMED failure
21
+ * with candidates rather than a guess (D-22).
22
+ */
23
+ import { PROMPT_DISABLED_COPY, PROMPT_HISTORY_REMINDER_COPY, PROMPT_LIST_EMPTY_COPY, PROMPT_SHOW_WARNING_COPY, PROMPT_UNKNOWN_SUBCOMMAND_COPY, PROMPT_UNAVAILABLE_COPY, promptAmbiguousCopy, promptDeletedCopy, promptErrorCopy, promptFavoriteCopy, promptInvalidSortCopy, promptListFooterCopy, promptMovedCopy, promptMoveRangeCopy, promptNotFoundCopy, promptRmConfirmCopy, promptSavedCopy, promptSortedCopy, } from './copy.js';
24
+ import { promptIndexOf } from './resolve.js';
25
+ import { createPrompt, deletePrompt, loadPromptLibrary, reorderPrompt, reorderPrompts, setPromptFavorite, sortPromptIds, } from './store.js';
26
+ /**
27
+ * `/prompts sort <key>`.
28
+ *
29
+ * `name` is TITLE order - the word the user typed has to be the order they get - and the
30
+ * other two promote a picker view into the persistent manual order.
31
+ */
32
+ const SORT_KEYS = {
33
+ name: 'name',
34
+ recent: 'recent',
35
+ favorite: 'favorite',
36
+ };
37
+ export function registerPromptCommands(registry) {
38
+ registry.register({
39
+ name: 'prompts',
40
+ description: 'Quick prompt library: pick, list, add, edit, remove, reorder',
41
+ run: (ctx) => runPromptCommand(ctx),
42
+ });
43
+ }
44
+ function report(ctx, result, success) {
45
+ if (result.ok) {
46
+ ctx.notify('info', success);
47
+ return;
48
+ }
49
+ ctx.notify('warn', promptErrorCopy(result.code));
50
+ }
51
+ /**
52
+ * `<n|title>` -> entry, or `null` after reporting why not.
53
+ *
54
+ * THE CANDIDATES ARE PRINTED on an ambiguous reference, indexed, and the command does
55
+ * not proceed: `/prompts rm` cannot be undone, so a refusal that costs one more
56
+ * command is strictly better than a deletion the user cannot identify afterwards.
57
+ */
58
+ function resolveOrReport(ctx, ref) {
59
+ const resolution = ctx.prompts?.resolve(ref);
60
+ if (!resolution) {
61
+ ctx.notify('warn', PROMPT_UNAVAILABLE_COPY);
62
+ return null;
63
+ }
64
+ if (resolution.ok)
65
+ return resolution.prompt;
66
+ if (resolution.code === 'ambiguous') {
67
+ const candidates = resolution.candidates.map((prompt, at) => `${at + 1}) ${prompt.title}`);
68
+ ctx.notify('warn', promptAmbiguousCopy(ref, candidates));
69
+ return null;
70
+ }
71
+ ctx.notify('warn', promptNotFoundCopy(ref));
72
+ return null;
73
+ }
74
+ /** `1. title [fav] #tag 12 uses` - no body, ever (D-17). */
75
+ export function promptListLine(prompt, at) {
76
+ const marks = [
77
+ prompt.favorite === true ? '[fav]' : '',
78
+ (prompt.tags ?? []).map((tag) => `#${tag}`).join(' '),
79
+ `${prompt.useCount} uses`,
80
+ ].filter((part) => part.length > 0).join(' ');
81
+ return `${at + 1}. ${prompt.title} ${marks}`;
82
+ }
83
+ function runList(ctx, library) {
84
+ if (library.prompts.length === 0) {
85
+ ctx.notify('info', PROMPT_LIST_EMPTY_COPY);
86
+ return;
87
+ }
88
+ const lines = library.prompts.map((prompt, at) => promptListLine(prompt, at));
89
+ ctx.notify('info', `${lines.join('\n')}\n${promptListFooterCopy(library.prompts.length)}`);
90
+ }
91
+ function runShow(ctx, ref) {
92
+ const prompt = resolveOrReport(ctx, ref);
93
+ if (!prompt)
94
+ return;
95
+ // THE ONLY PATH THAT PUTS A BODY INTO THE TRANSCRIPT (D-17), and it says so first.
96
+ ctx.notify('info', PROMPT_SHOW_WARNING_COPY);
97
+ const tags = (prompt.tags ?? []).map((tag) => `#${tag}`).join(' ');
98
+ const header = [prompt.title, tags].filter((part) => part.length > 0).join(' ');
99
+ ctx.notify('info', `${header}\n${prompt.content}`);
100
+ }
101
+ /** `add <title> :: <body>`. Reached only when the user typed content on the line. */
102
+ function runAddWithArgs(ctx, rest) {
103
+ const split = rest.split('::');
104
+ if (split.length < 2) {
105
+ // A `::`-less `add <something>` is ambiguous between a title and a body, and
106
+ // guessing wrong would store the wrong field. Point at the editor instead.
107
+ ctx.notify('warn', PROMPT_HISTORY_REMINDER_COPY);
108
+ return;
109
+ }
110
+ const title = (split[0] ?? '').trim();
111
+ const content = split.slice(1).join('::').trim();
112
+ report(ctx, createPrompt({ title, content }), promptSavedCopy(title));
113
+ }
114
+ function runRemove(ctx, rest, library) {
115
+ const confirmed = /(^|\s)--yes\s*$/.test(rest);
116
+ const ref = rest.replace(/(^|\s)--yes\s*$/, '').trim();
117
+ const prompt = resolveOrReport(ctx, ref);
118
+ if (!prompt)
119
+ return;
120
+ if (!confirmed) {
121
+ ctx.notify('warn', promptRmConfirmCopy(prompt.title, ref));
122
+ return;
123
+ }
124
+ if (promptIndexOf(library, prompt.id) < 0) {
125
+ ctx.notify('warn', promptNotFoundCopy(ref));
126
+ return;
127
+ }
128
+ report(ctx, deletePrompt(prompt.id), promptDeletedCopy(prompt.title));
129
+ }
130
+ /** `move <ref> <to>`: `to` is 1-based and must exist; an out-of-range move is refused. */
131
+ function runMove(ctx, rest, library) {
132
+ const parts = rest.split(/\s+/).filter((part) => part.length > 0);
133
+ const prompt = resolveOrReport(ctx, parts[0] ?? '');
134
+ if (!prompt)
135
+ return;
136
+ const to = Number.parseInt(parts[1] ?? '', 10);
137
+ if (!Number.isFinite(to) || to < 1 || to > library.prompts.length) {
138
+ ctx.notify('warn', promptMoveRangeCopy(to, library.prompts.length));
139
+ return;
140
+ }
141
+ const from = promptIndexOf(library, prompt.id);
142
+ report(ctx, reorderPrompt(from, to - 1), promptMovedCopy(prompt.title, to));
143
+ }
144
+ function runSort(ctx, raw, library) {
145
+ const key = SORT_KEYS[raw.trim().toLowerCase()];
146
+ if (!key) {
147
+ ctx.notify('warn', promptInvalidSortCopy(raw.trim()));
148
+ return;
149
+ }
150
+ const ids = sortPromptIds(library, key);
151
+ const unchanged = ids.length === library.prompts.length
152
+ && ids.every((id, at) => id === library.prompts[at]?.id);
153
+ // A NO-OP IS NOT A WRITE. Reporting "sorted" after an unnecessary rewrite would
154
+ // also teach the user that asking twice costs a disk write.
155
+ if (unchanged) {
156
+ ctx.notify('info', promptSortedCopy(key));
157
+ return;
158
+ }
159
+ report(ctx, reorderPrompts(ids), promptSortedCopy(key));
160
+ }
161
+ /**
162
+ * The dispatcher.
163
+ *
164
+ * `enabled=false` is answered BEFORE any subcommand, and ANSWERED rather than ignored:
165
+ * the command stays registered precisely so this sentence can exist (D-19 / AC-30).
166
+ */
167
+ export async function runPromptCommand(ctx) {
168
+ // `=== false`, not `!enabled`: a `CliConfig` built without the section (a fixture, an
169
+ // embedded host) must read as ENABLED, which is the documented default.
170
+ if (ctx.controller.getConfig().prompts?.enabled === false) {
171
+ ctx.notify('info', PROMPT_DISABLED_COPY);
172
+ return;
173
+ }
174
+ const port = ctx.prompts;
175
+ if (!port) {
176
+ ctx.notify('warn', PROMPT_UNAVAILABLE_COPY);
177
+ return;
178
+ }
179
+ const args = ctx.args.trim();
180
+ const space = args.search(/\s/);
181
+ const verb = (space === -1 ? args : args.slice(0, space)).toLowerCase();
182
+ const rest = space === -1 ? '' : args.slice(space + 1).trim();
183
+ const library = loadPromptLibrary();
184
+ if (verb.length === 0) {
185
+ port.openPicker();
186
+ return;
187
+ }
188
+ if (verb === 'manage') {
189
+ port.openManager({ kind: 'list' });
190
+ return;
191
+ }
192
+ if (verb === 'list') {
193
+ runList(ctx, library);
194
+ return;
195
+ }
196
+ if (verb === 'show') {
197
+ runShow(ctx, rest);
198
+ return;
199
+ }
200
+ if (verb === 'add') {
201
+ if (rest.length === 0) {
202
+ port.openManager({ kind: 'create' });
203
+ return;
204
+ }
205
+ runAddWithArgs(ctx, rest);
206
+ return;
207
+ }
208
+ if (verb === 'edit') {
209
+ const prompt = resolveOrReport(ctx, rest);
210
+ if (!prompt)
211
+ return;
212
+ const entry = { kind: 'edit', id: prompt.id };
213
+ port.openManager(entry);
214
+ return;
215
+ }
216
+ if (verb === 'rm' || verb === 'remove' || verb === 'delete') {
217
+ runRemove(ctx, rest, library);
218
+ return;
219
+ }
220
+ if (verb === 'favorite' || verb === 'fav') {
221
+ const prompt = resolveOrReport(ctx, rest);
222
+ if (!prompt)
223
+ return;
224
+ const next = prompt.favorite !== true;
225
+ report(ctx, setPromptFavorite(prompt.id, next), promptFavoriteCopy(prompt.title, next));
226
+ return;
227
+ }
228
+ if (verb === 'move') {
229
+ runMove(ctx, rest, library);
230
+ return;
231
+ }
232
+ if (verb === 'sort') {
233
+ runSort(ctx, rest, library);
234
+ return;
235
+ }
236
+ ctx.notify('warn', PROMPT_UNKNOWN_SUBCOMMAND_COPY);
237
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Chinese copy for the quick-prompt library (quick-prompt-library section 9.10).
3
+ *
4
+ * ASCII ONLY ON DISK. Every string below is written as `\uXXXX` escapes, the way
5
+ * `ui/interaction-copy.ts` does it, because `src/prompts/**` and `src/ui/**` are
6
+ * both inside `glyphs.test.ts::inScope` and that scanner rejects any raw non-ASCII
7
+ * byte. The escapes are the point: the product speaks English in its chrome
8
+ * (labels, footers, the overlays' own vocabulary) and the sentences that explain a
9
+ * failure to THIS user are here, in one table, where a translator can find them
10
+ * and where the API surfaces that build them cannot drift from each other.
11
+ *
12
+ * WHY ONE TABLE AND NOT STRINGS AT THE CALL SITES: the same failure is reported by
13
+ * a toast, by a `/prompts` subcommand and by the editor's error row. Three copies
14
+ * of one sentence is three wordings waiting to happen.
15
+ */
16
+ import type { PromptSortKey, PromptWriteError } from './types.js';
17
+ /** `\u5df2\u586b\u5165\u300cx\u300d\uff088 \u884c\uff0c\u53ef\u7ee7\u7eed\u7f16\u8f91\uff09`. */
18
+ export declare function promptFilledCopy(title: string, lines: number): string;
19
+ /** `\u5df2\u63d2\u5165\u5230\u5149\u6807\u5904\uff0c\u539f\u5185\u5bb9\u4fdd\u7559\u3002` */
20
+ export declare const PROMPT_INSERTED_COPY = "\u5DF2\u63D2\u5165\u5230\u5149\u6807\u5904\uFF0C\u539F\u5185\u5BB9\u4FDD\u7559\u3002";
21
+ /** `\u5df2\u4fdd\u5b58\u300cx\u300d` */
22
+ export declare function promptSavedCopy(title: string): string;
23
+ /** `\u5df2\u5220\u9664\u300cx\u300d` */
24
+ export declare function promptDeletedCopy(title: string): string;
25
+ /** `\u5df2\u6536\u85cf\u300cx\u300d` / `\u5df2\u53d6\u6d88\u6536\u85cf\u300cx\u300d` */
26
+ export declare function promptFavoriteCopy(title: string, favorite: boolean): string;
27
+ /** `\u5df2\u628a\u300cx\u300d\u79fb\u52a8\u5230\u7b2c n \u4f4d\u3002` */
28
+ export declare function promptMovedCopy(title: string, to: number): string;
29
+ /** `\u5df2\u6309 name \u91cd\u6392\u5e76\u4fdd\u5b58\u3002` */
30
+ export declare function promptSortedCopy(key: PromptSortKey): string;
31
+ /** The kill switch's honest answer (D-19 / AC-30). */
32
+ export declare const PROMPT_DISABLED_COPY = "\u63D0\u793A\u8BCD\u5E93\u5DF2\u5173\u95ED\uFF08prompts.enabled=false\uFF09\u3002";
33
+ /** The port is absent - a test fixture or a build without the overlay. */
34
+ export declare const PROMPT_UNAVAILABLE_COPY = "Prompt library is not available in this build.";
35
+ /** `\u5e93\u6587\u4ef6\u635f\u574f\u2026\u4ee5\u7a7a\u5e93\u7ee7\u7eed` - the one-shot notice after a `.bak` repair (D-4). */
36
+ export declare const PROMPT_CORRUPT_COPY = "\u5E93\u6587\u4EF6\u635F\u574F\u6216\u683C\u5F0F\u4E0D\u7B26\uFF0C\u5DF2\u5907\u4EFD\u4E3A quick-prompts.json.bak\uFF0C\u672C\u6B21\u4EE5\u7A7A\u5E93\u7EE7\u7EED\u3002";
37
+ /** `\u5df2\u8df3\u8fc7 n \u6761\u65e0\u6cd5\u8bc6\u522b\u7684\u8bb0\u5f55\u3002` */
38
+ export declare function promptSkippedCopy(count: number): string;
39
+ /**
40
+ * `\u5df2\u5e94\u7528\uff0c\u4f46\u5199\u5165\u78c1\u76d8\u5931\u8d25\uff1b\u9000\u51fa\u540e\u53ef\u80fd\u4e22\u5931\u3002` (D-4: never roll back memory).
41
+ *
42
+ * ONE SENTENCE for `write_failed`, and it says BOTH halves: that the change is in effect
43
+ * and that it may not survive the process. A shorter "write failed" would read as
44
+ * "nothing happened", which is the one thing this path does not do.
45
+ */
46
+ export declare const PROMPT_WRITE_FAILED_COPY = "\u5DF2\u5E94\u7528\uFF0C\u4F46\u5199\u5165\u78C1\u76D8\u5931\u8D25\uFF1B\u9000\u51FA\u540E\u53EF\u80FD\u4E22\u5931\u3002";
47
+ /** `\u627e\u4e0d\u5230\u300cx\u300d\u5bf9\u5e94\u7684\u63d0\u793a\u8bcd\u3002` */
48
+ export declare function promptNotFoundCopy(ref: string): string;
49
+ /**
50
+ * `\u300cx\u300d\u5339\u914d n \u6761\uff0c\u8bf7\u7528\u5e8f\u53f7\uff1a\u2026` (D-22).
51
+ *
52
+ * THE CANDIDATES ARE THE WHOLE POINT of this message: without them the user knows
53
+ * only that their command failed, and the next thing they try is the same command.
54
+ */
55
+ export declare function promptAmbiguousCopy(ref: string, candidates: readonly string[]): string;
56
+ /** Printed above a body that is about to enter the transcript (D-17). */
57
+ export declare const PROMPT_SHOW_WARNING_COPY = "\u4EE5\u4E0B\u6B63\u6587\u5C06\u8FDB\u5165\u4F1A\u8BDD\u8BB0\u5F55\uFF08/save \u4F1A\u5BFC\u51FA\u5B83\uFF09\u3002";
58
+ /** `\u5c06\u5220\u9664\u300cx\u300d\u2026\u786e\u8ba4\u8bf7\u518d\u6267\u884c\u4e00\u6b21\uff1a/prompts rm x --yes` (D-13). */
59
+ export declare function promptRmConfirmCopy(title: string, ref: string): string;
60
+ /** `\u76ee\u6807\u4f4d\u7f6e n \u8d85\u51fa\u8303\u56f4\uff081-m\uff09\u3002` */
61
+ export declare function promptMoveRangeCopy(to: number, max: number): string;
62
+ /** `\u65e0\u6548\u7684\u6392\u5e8f\u65b9\u5f0f\u300cx\u300d\uff0c\u53ef\u7528 name / recent / favorite\u3002` */
63
+ export declare function promptInvalidSortCopy(raw: string): string;
64
+ /** `\u63d0\u793a\u8bcd\u5e93\u4e3a\u7a7a\uff0c\u7528 /prompts add \u65b0\u5efa\u4e00\u6761\u3002` */
65
+ export declare const PROMPT_LIST_EMPTY_COPY = "\u63D0\u793A\u8BCD\u5E93\u4E3A\u7A7A\uFF0C\u7528 /prompts add \u65B0\u5EFA\u4E00\u6761\u3002";
66
+ /** The reminder that a body typed on a command line lands in `prompt-history`. */
67
+ export declare const PROMPT_HISTORY_REMINDER_COPY = "\u63D0\u793A\uFF1A\u5199\u5728\u547D\u4EE4\u884C\u91CC\u7684\u6B63\u6587\u4F1A\u8FDB\u5165 prompt-history\uFF1B\u63A8\u8350\u7528 /prompts add \u540E\u5728\u7F16\u8F91\u5668\u91CC\u8F93\u5165\u3002";
68
+ /** `\u5df2\u5220\u9664\u300cx\u300d` is reused for the `--yes` path; this is the "nothing happened" half. */
69
+ export declare const PROMPT_UNKNOWN_SUBCOMMAND_COPY = "\u672A\u77E5\u5B50\u547D\u4EE4\u3002\u53EF\u7528\uFF1Alist / show / add / edit / rm / move / favorite / sort / manage\u3002";
70
+ /**
71
+ * The one-shot key-migration notice (D-20 / AC-32).
72
+ *
73
+ * `Ctrl+N` IS SPELLED OUT IN FULL, never `^N` (P1-7): `^N` is already this
74
+ * product's word for "transcript line offset" (the header's scroll chip and the
75
+ * help overlay's `^N` row), so the abbreviation would be a second meaning for the
76
+ * same glyph in the same product.
77
+ */
78
+ export declare const PROMPT_KEY_NOTICE_COPY = "Ctrl+P \u73B0\u5728\u6253\u5F00\u63D0\u793A\u8BCD\u5E93\uFF1B\u5207\u6362 BUILD/PLAN \u8BF7\u7528 Shift+Tab \u6216 Ctrl+N\uFF08\u6216 /plan\uFF09\u3002";
79
+ /** The library page's empty state (the list page draws this instead of nothing). */
80
+ export declare const PROMPT_EMPTY_TITLE_COPY = "No prompts yet.";
81
+ export declare const PROMPT_EMPTY_BODY_COPY = "Press a to create your first prompt.";
82
+ /** `\uff08\u5171 n \u6761\uff09` suffix for `/prompts list`. */
83
+ export declare function promptListFooterCopy(count: number): string;
84
+ /**
85
+ * One sentence per stable error code (D-12). THE CODE IS THE CONTRACT; this table is
86
+ * the only place it becomes words, so the editor's title row, the list's toast and a
87
+ * command's refusal cannot describe the same failure three different ways.
88
+ */
89
+ export declare function promptErrorCopy(code: PromptWriteError): string;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Chinese copy for the quick-prompt library (quick-prompt-library section 9.10).
3
+ *
4
+ * ASCII ONLY ON DISK. Every string below is written as `\uXXXX` escapes, the way
5
+ * `ui/interaction-copy.ts` does it, because `src/prompts/**` and `src/ui/**` are
6
+ * both inside `glyphs.test.ts::inScope` and that scanner rejects any raw non-ASCII
7
+ * byte. The escapes are the point: the product speaks English in its chrome
8
+ * (labels, footers, the overlays' own vocabulary) and the sentences that explain a
9
+ * failure to THIS user are here, in one table, where a translator can find them
10
+ * and where the API surfaces that build them cannot drift from each other.
11
+ *
12
+ * WHY ONE TABLE AND NOT STRINGS AT THE CALL SITES: the same failure is reported by
13
+ * a toast, by a `/prompts` subcommand and by the editor's error row. Three copies
14
+ * of one sentence is three wordings waiting to happen.
15
+ */
16
+ /** `\u5df2\u586b\u5165\u300cx\u300d\uff088 \u884c\uff0c\u53ef\u7ee7\u7eed\u7f16\u8f91\uff09`. */
17
+ export function promptFilledCopy(title, lines) {
18
+ return `\u5df2\u586b\u5165\u300c${title}\u300d\uff08${lines} \u884c\uff0c\u53ef\u7ee7\u7eed\u7f16\u8f91\uff09`;
19
+ }
20
+ /** `\u5df2\u63d2\u5165\u5230\u5149\u6807\u5904\uff0c\u539f\u5185\u5bb9\u4fdd\u7559\u3002` */
21
+ export const PROMPT_INSERTED_COPY = '\u5df2\u63d2\u5165\u5230\u5149\u6807\u5904\uff0c\u539f\u5185\u5bb9\u4fdd\u7559\u3002';
22
+ /** `\u5df2\u4fdd\u5b58\u300cx\u300d` */
23
+ export function promptSavedCopy(title) {
24
+ return `\u5df2\u4fdd\u5b58\u300c${title}\u300d`;
25
+ }
26
+ /** `\u5df2\u5220\u9664\u300cx\u300d` */
27
+ export function promptDeletedCopy(title) {
28
+ return `\u5df2\u5220\u9664\u300c${title}\u300d`;
29
+ }
30
+ /** `\u5df2\u6536\u85cf\u300cx\u300d` / `\u5df2\u53d6\u6d88\u6536\u85cf\u300cx\u300d` */
31
+ export function promptFavoriteCopy(title, favorite) {
32
+ return favorite
33
+ ? `\u5df2\u6536\u85cf\u300c${title}\u300d`
34
+ : `\u5df2\u53d6\u6d88\u6536\u85cf\u300c${title}\u300d`;
35
+ }
36
+ /** `\u5df2\u628a\u300cx\u300d\u79fb\u52a8\u5230\u7b2c n \u4f4d\u3002` */
37
+ export function promptMovedCopy(title, to) {
38
+ return `\u5df2\u628a\u300c${title}\u300d\u79fb\u52a8\u5230\u7b2c ${to} \u4f4d\u3002`;
39
+ }
40
+ /** `\u5df2\u6309 name \u91cd\u6392\u5e76\u4fdd\u5b58\u3002` */
41
+ export function promptSortedCopy(key) {
42
+ return `\u5df2\u6309 ${key} \u91cd\u6392\u5e76\u4fdd\u5b58\u3002`;
43
+ }
44
+ /** The kill switch's honest answer (D-19 / AC-30). */
45
+ export const PROMPT_DISABLED_COPY = '\u63d0\u793a\u8bcd\u5e93\u5df2\u5173\u95ed\uff08prompts.enabled=false\uff09\u3002';
46
+ /** The port is absent - a test fixture or a build without the overlay. */
47
+ export const PROMPT_UNAVAILABLE_COPY = 'Prompt library is not available in this build.';
48
+ /** `\u5e93\u6587\u4ef6\u635f\u574f\u2026\u4ee5\u7a7a\u5e93\u7ee7\u7eed` - the one-shot notice after a `.bak` repair (D-4). */
49
+ export const PROMPT_CORRUPT_COPY = '\u5e93\u6587\u4ef6\u635f\u574f\u6216\u683c\u5f0f\u4e0d\u7b26\uff0c\u5df2\u5907\u4efd\u4e3a quick-prompts.json.bak\uff0c\u672c\u6b21\u4ee5\u7a7a\u5e93\u7ee7\u7eed\u3002';
50
+ /** `\u5df2\u8df3\u8fc7 n \u6761\u65e0\u6cd5\u8bc6\u522b\u7684\u8bb0\u5f55\u3002` */
51
+ export function promptSkippedCopy(count) {
52
+ return `\u5df2\u8df3\u8fc7 ${count} \u6761\u65e0\u6cd5\u8bc6\u522b\u7684\u8bb0\u5f55\u3002`;
53
+ }
54
+ /**
55
+ * `\u5df2\u5e94\u7528\uff0c\u4f46\u5199\u5165\u78c1\u76d8\u5931\u8d25\uff1b\u9000\u51fa\u540e\u53ef\u80fd\u4e22\u5931\u3002` (D-4: never roll back memory).
56
+ *
57
+ * ONE SENTENCE for `write_failed`, and it says BOTH halves: that the change is in effect
58
+ * and that it may not survive the process. A shorter "write failed" would read as
59
+ * "nothing happened", which is the one thing this path does not do.
60
+ */
61
+ export const PROMPT_WRITE_FAILED_COPY = '\u5df2\u5e94\u7528\uff0c\u4f46\u5199\u5165\u78c1\u76d8\u5931\u8d25\uff1b\u9000\u51fa\u540e\u53ef\u80fd\u4e22\u5931\u3002';
62
+ /** `\u627e\u4e0d\u5230\u300cx\u300d\u5bf9\u5e94\u7684\u63d0\u793a\u8bcd\u3002` */
63
+ export function promptNotFoundCopy(ref) {
64
+ return `\u627e\u4e0d\u5230\u300c${ref}\u300d\u5bf9\u5e94\u7684\u63d0\u793a\u8bcd\u3002`;
65
+ }
66
+ /**
67
+ * `\u300cx\u300d\u5339\u914d n \u6761\uff0c\u8bf7\u7528\u5e8f\u53f7\uff1a\u2026` (D-22).
68
+ *
69
+ * THE CANDIDATES ARE THE WHOLE POINT of this message: without them the user knows
70
+ * only that their command failed, and the next thing they try is the same command.
71
+ */
72
+ export function promptAmbiguousCopy(ref, candidates) {
73
+ const list = candidates.join('\u3001');
74
+ return `\u300c${ref}\u300d\u5339\u914d ${candidates.length} \u6761\uff0c\u8bf7\u7528\u5e8f\u53f7\uff1a${list}`;
75
+ }
76
+ /** Printed above a body that is about to enter the transcript (D-17). */
77
+ export const PROMPT_SHOW_WARNING_COPY = '\u4ee5\u4e0b\u6b63\u6587\u5c06\u8fdb\u5165\u4f1a\u8bdd\u8bb0\u5f55\uff08/save \u4f1a\u5bfc\u51fa\u5b83\uff09\u3002';
78
+ /** `\u5c06\u5220\u9664\u300cx\u300d\u2026\u786e\u8ba4\u8bf7\u518d\u6267\u884c\u4e00\u6b21\uff1a/prompts rm x --yes` (D-13). */
79
+ export function promptRmConfirmCopy(title, ref) {
80
+ return `\u5c06\u5220\u9664\u300c${title}\u300d\u3002\u6b64\u64cd\u4f5c\u4e0d\u53ef\u64a4\u9500\uff1b\u786e\u8ba4\u8bf7\u518d\u6267\u884c\u4e00\u6b21\uff1a/prompts rm ${ref} --yes`;
81
+ }
82
+ /** `\u76ee\u6807\u4f4d\u7f6e n \u8d85\u51fa\u8303\u56f4\uff081-m\uff09\u3002` */
83
+ export function promptMoveRangeCopy(to, max) {
84
+ return `\u76ee\u6807\u4f4d\u7f6e ${to} \u8d85\u51fa\u8303\u56f4\uff081-${max}\uff09\u3002`;
85
+ }
86
+ /** `\u65e0\u6548\u7684\u6392\u5e8f\u65b9\u5f0f\u300cx\u300d\uff0c\u53ef\u7528 name / recent / favorite\u3002` */
87
+ export function promptInvalidSortCopy(raw) {
88
+ return `\u65e0\u6548\u7684\u6392\u5e8f\u65b9\u5f0f\u300c${raw}\u300d\uff0c\u53ef\u7528 name / recent / favorite\u3002`;
89
+ }
90
+ /** `\u63d0\u793a\u8bcd\u5e93\u4e3a\u7a7a\uff0c\u7528 /prompts add \u65b0\u5efa\u4e00\u6761\u3002` */
91
+ export const PROMPT_LIST_EMPTY_COPY = '\u63d0\u793a\u8bcd\u5e93\u4e3a\u7a7a\uff0c\u7528 /prompts add \u65b0\u5efa\u4e00\u6761\u3002';
92
+ /** The reminder that a body typed on a command line lands in `prompt-history`. */
93
+ export const PROMPT_HISTORY_REMINDER_COPY = '\u63d0\u793a\uff1a\u5199\u5728\u547d\u4ee4\u884c\u91cc\u7684\u6b63\u6587\u4f1a\u8fdb\u5165 prompt-history\uff1b\u63a8\u8350\u7528 /prompts add \u540e\u5728\u7f16\u8f91\u5668\u91cc\u8f93\u5165\u3002';
94
+ /** `\u5df2\u5220\u9664\u300cx\u300d` is reused for the `--yes` path; this is the "nothing happened" half. */
95
+ export const PROMPT_UNKNOWN_SUBCOMMAND_COPY = '\u672a\u77e5\u5b50\u547d\u4ee4\u3002\u53ef\u7528\uff1alist / show / add / edit / rm / move / favorite / sort / manage\u3002';
96
+ /**
97
+ * The one-shot key-migration notice (D-20 / AC-32).
98
+ *
99
+ * `Ctrl+N` IS SPELLED OUT IN FULL, never `^N` (P1-7): `^N` is already this
100
+ * product's word for "transcript line offset" (the header's scroll chip and the
101
+ * help overlay's `^N` row), so the abbreviation would be a second meaning for the
102
+ * same glyph in the same product.
103
+ */
104
+ export const PROMPT_KEY_NOTICE_COPY = 'Ctrl+P \u73b0\u5728\u6253\u5f00\u63d0\u793a\u8bcd\u5e93\uff1b\u5207\u6362 BUILD/PLAN \u8bf7\u7528 Shift+Tab \u6216 Ctrl+N\uff08\u6216 /plan\uff09\u3002';
105
+ /** The library page's empty state (the list page draws this instead of nothing). */
106
+ export const PROMPT_EMPTY_TITLE_COPY = 'No prompts yet.';
107
+ export const PROMPT_EMPTY_BODY_COPY = 'Press a to create your first prompt.';
108
+ /** `\uff08\u5171 n \u6761\uff09` suffix for `/prompts list`. */
109
+ export function promptListFooterCopy(count) {
110
+ return `\uff08\u5171 ${count} \u6761\uff09`;
111
+ }
112
+ /**
113
+ * One sentence per stable error code (D-12). THE CODE IS THE CONTRACT; this table is
114
+ * the only place it becomes words, so the editor's title row, the list's toast and a
115
+ * command's refusal cannot describe the same failure three different ways.
116
+ */
117
+ export function promptErrorCopy(code) {
118
+ switch (code) {
119
+ case 'title_required': return '\u6807\u9898\u4e0d\u80fd\u4e3a\u7a7a';
120
+ case 'content_required': return '\u6b63\u6587\u4e0d\u80fd\u4e3a\u7a7a';
121
+ case 'title_too_long': return '\u6807\u9898\u8d85\u8fc7 80 \u5b57';
122
+ case 'content_too_long': return '\u6b63\u6587\u8d85\u8fc7 8000 \u5b57';
123
+ case 'description_too_long': return '\u63cf\u8ff0\u8d85\u8fc7 200 \u5b57';
124
+ case 'tag_too_long': return '\u6807\u7b7e\u8d85\u8fc7 24 \u5b57';
125
+ case 'too_many_tags': return '\u6807\u7b7e\u6700\u591a 6 \u4e2a';
126
+ case 'too_many_prompts': return '\u6700\u591a 200 \u6761\uff0c\u8bf7\u5148\u5220\u9664';
127
+ case 'not_found': return '\u627e\u4e0d\u5230\u8be5\u63d0\u793a\u8bcd';
128
+ case 'ambiguous': return '\u5339\u914d\u5230\u591a\u6761\uff0c\u8bf7\u7528\u5e8f\u53f7';
129
+ case 'write_failed': return PROMPT_WRITE_FAILED_COPY;
130
+ }
131
+ }