@sayknow-cli/coding-agent 0.3.7 → 0.3.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 (248) hide show
  1. package/dist/types/append-only-mode.d.ts +31 -0
  2. package/dist/types/capability/rule.d.ts +8 -0
  3. package/dist/types/cli/completion-cli.d.ts +41 -0
  4. package/dist/types/cli/config-cli.d.ts +1 -0
  5. package/dist/types/cli/fixture-report.d.ts +20 -0
  6. package/dist/types/cli/fixture-report.test.d.ts +1 -0
  7. package/dist/types/cli/nofile-limit.d.ts +11 -0
  8. package/dist/types/cli/notify-cli.d.ts +7 -0
  9. package/dist/types/cli/update-cli.d.ts +22 -0
  10. package/dist/types/cli.d.ts +124 -1
  11. package/dist/types/commands/completion.d.ts +29 -0
  12. package/dist/types/commands/config.d.ts +3 -0
  13. package/dist/types/commands/deep-interview.d.ts +3 -0
  14. package/dist/types/config/file-lock.d.ts +7 -6
  15. package/dist/types/config/keybindings.d.ts +2 -2
  16. package/dist/types/config/model-profile-activation.d.ts +6 -0
  17. package/dist/types/config/model-profiles.d.ts +1 -0
  18. package/dist/types/config/settings-schema.d.ts +105 -1
  19. package/dist/types/config/settings.d.ts +8 -0
  20. package/dist/types/discovery/index.d.ts +1 -0
  21. package/dist/types/export/html/template.generated.d.ts +1 -1
  22. package/dist/types/export/ttsr.d.ts +14 -3
  23. package/dist/types/extensibility/extensions/runner.d.ts +2 -1
  24. package/dist/types/extensibility/extensions/types.d.ts +8 -0
  25. package/dist/types/goals/runtime.d.ts +1 -0
  26. package/dist/types/hooks/native-skill-hook.d.ts +12 -0
  27. package/dist/types/main.d.ts +4 -1
  28. package/dist/types/modes/components/assistant-message.d.ts +5 -1
  29. package/dist/types/modes/components/model-selector.d.ts +4 -0
  30. package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
  31. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  32. package/dist/types/modes/components/status-line.d.ts +9 -0
  33. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  34. package/dist/types/modes/controllers/event-controller.d.ts +5 -0
  35. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  36. package/dist/types/modes/interactive-mode.d.ts +2 -0
  37. package/dist/types/modes/print-mode.d.ts +13 -1
  38. package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
  39. package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
  40. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
  41. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
  42. package/dist/types/modes/types.d.ts +1 -0
  43. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  44. package/dist/types/notifications/config.d.ts +14 -1
  45. package/dist/types/notifications/daemon-paths.d.ts +1 -0
  46. package/dist/types/notifications/index.d.ts +6 -3
  47. package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
  48. package/dist/types/notifications/telegram-daemon.d.ts +23 -3
  49. package/dist/types/notifications/telegram-reference.d.ts +0 -5
  50. package/dist/types/notifications/threaded-render.d.ts +6 -0
  51. package/dist/types/runtime-api-key.d.ts +7 -0
  52. package/dist/types/sdk.d.ts +3 -0
  53. package/dist/types/session/agent-session.d.ts +61 -6
  54. package/dist/types/session/agent-storage.d.ts +1 -0
  55. package/dist/types/session/file-mention-pruning.d.ts +8 -0
  56. package/dist/types/session/messages.d.ts +4 -0
  57. package/dist/types/session/session-manager.d.ts +17 -1
  58. package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
  59. package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
  60. package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
  61. package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
  62. package/dist/types/skc-runtime/state-writer.d.ts +3 -0
  63. package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
  64. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
  65. package/dist/types/skill-state/active-state.d.ts +18 -1
  66. package/dist/types/system-prompt.d.ts +18 -2
  67. package/dist/types/task/executor.d.ts +3 -0
  68. package/dist/types/task/fork-context-budget.d.ts +7 -0
  69. package/dist/types/task/token-log.d.ts +30 -0
  70. package/dist/types/task/token-log.test.d.ts +1 -0
  71. package/dist/types/tools/bisect.d.ts +81 -0
  72. package/dist/types/tools/computer.d.ts +2 -2
  73. package/dist/types/tools/index.d.ts +1 -0
  74. package/dist/types/tools/search.d.ts +2 -2
  75. package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
  76. package/dist/types/utils/file-mentions.d.ts +14 -0
  77. package/dist/types/utils/git.d.ts +32 -0
  78. package/dist/types/web/scrapers/youtube.d.ts +8 -0
  79. package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
  80. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
  81. package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
  82. package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
  83. package/package.json +8 -8
  84. package/src/append-only-mode.ts +58 -0
  85. package/src/autoresearch/prompt.md +1 -1
  86. package/src/autoresearch/tools/log-experiment.ts +1 -1
  87. package/src/capability/rule.ts +9 -0
  88. package/src/cli/completion-cli.ts +234 -0
  89. package/src/cli/config-cli.ts +61 -9
  90. package/src/cli/fast-help.ts +1 -0
  91. package/src/cli/fixture-report.test.ts +230 -0
  92. package/src/cli/fixture-report.ts +171 -0
  93. package/src/cli/nofile-limit.ts +48 -0
  94. package/src/cli/notify-cli.ts +92 -8
  95. package/src/cli/telegram-cli.ts +1 -1
  96. package/src/cli/update-cli.ts +85 -9
  97. package/src/cli.ts +29 -1
  98. package/src/commands/completion.ts +84 -0
  99. package/src/commands/config.ts +2 -0
  100. package/src/commands/contribution-prep.ts +1 -1
  101. package/src/commands/deep-interview.ts +2 -1
  102. package/src/config/file-lock.ts +73 -34
  103. package/src/config/keybindings.ts +2 -2
  104. package/src/config/model-profile-activation.ts +65 -0
  105. package/src/config/model-profiles.ts +31 -2
  106. package/src/config/settings-schema.ts +68 -1
  107. package/src/config/settings.ts +22 -0
  108. package/src/defaults/skc/skills/team/SKILL.md +17 -14
  109. package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
  110. package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
  111. package/src/defaults/skc-defaults.ts +9 -0
  112. package/src/discovery/helpers.ts +10 -0
  113. package/src/discovery/index.ts +1 -0
  114. package/src/export/html/template.generated.ts +1 -1
  115. package/src/export/html/template.js +60 -2
  116. package/src/export/ttsr.ts +96 -18
  117. package/src/extensibility/extensions/runner.ts +183 -219
  118. package/src/extensibility/extensions/types.ts +9 -0
  119. package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
  120. package/src/goals/runtime.ts +10 -2
  121. package/src/hindsight/backend.ts +1 -3
  122. package/src/hooks/native-skill-hook.ts +124 -4
  123. package/src/internal-urls/docs-index.generated.ts +14 -5
  124. package/src/main.ts +95 -43
  125. package/src/memories/index.ts +12 -1
  126. package/src/modes/components/assistant-message.ts +241 -82
  127. package/src/modes/components/custom-editor.ts +1 -0
  128. package/src/modes/components/model-selector.ts +54 -11
  129. package/src/modes/components/queued-message-selector.ts +92 -0
  130. package/src/modes/components/settings-selector.ts +4 -0
  131. package/src/modes/components/status-line.ts +167 -54
  132. package/src/modes/components/tool-execution.ts +44 -19
  133. package/src/modes/components/welcome.ts +181 -44
  134. package/src/modes/controllers/command-controller.ts +40 -4
  135. package/src/modes/controllers/event-controller.ts +66 -47
  136. package/src/modes/controllers/extension-ui-controller.ts +4 -10
  137. package/src/modes/controllers/input-controller.ts +180 -18
  138. package/src/modes/controllers/selector-controller.ts +127 -24
  139. package/src/modes/interactive-mode.ts +51 -16
  140. package/src/modes/print-mode.ts +43 -4
  141. package/src/modes/prompt-action-autocomplete.ts +21 -20
  142. package/src/modes/rpc/rpc-mode.ts +29 -3
  143. package/src/modes/rpc/rpc-types.ts +9 -0
  144. package/src/modes/shared/agent-wire/command-validation.ts +4 -0
  145. package/src/modes/shared/agent-wire/event-contract.ts +22 -0
  146. package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
  147. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  148. package/src/modes/types.ts +1 -0
  149. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  150. package/src/modes/utils/ui-helpers.ts +20 -7
  151. package/src/notifications/config.ts +25 -5
  152. package/src/notifications/daemon-paths.ts +2 -0
  153. package/src/notifications/index.ts +117 -14
  154. package/src/notifications/lifecycle-commands.ts +37 -7
  155. package/src/notifications/lifecycle-orchestrator.ts +19 -6
  156. package/src/notifications/telegram-daemon-control.ts +5 -3
  157. package/src/notifications/telegram-daemon.ts +302 -51
  158. package/src/notifications/telegram-reference.ts +0 -16
  159. package/src/notifications/threaded-render.ts +17 -1
  160. package/src/prompts/agents/architect.md +19 -10
  161. package/src/prompts/agents/critic.md +16 -18
  162. package/src/prompts/agents/executor.md +14 -11
  163. package/src/prompts/agents/planner.md +18 -13
  164. package/src/prompts/ci-green-request.md +3 -8
  165. package/src/prompts/goals/goal-continuation.md +0 -5
  166. package/src/prompts/goals/goal-mode-active.md +0 -5
  167. package/src/prompts/system/agent-creation-architect.md +2 -2
  168. package/src/prompts/system/plan-mode-active.md +1 -1
  169. package/src/prompts/system/plan-mode-subagent.md +3 -3
  170. package/src/prompts/system/project-prompt.md +0 -13
  171. package/src/prompts/system/subagent-system-prompt.md +3 -2
  172. package/src/prompts/system/system-prompt.md +29 -17
  173. package/src/prompts/system/volatile-project-context.md +10 -0
  174. package/src/prompts/system/web-search.md +4 -7
  175. package/src/prompts/tools/apply-patch.md +6 -0
  176. package/src/prompts/tools/ast-edit.md +1 -0
  177. package/src/prompts/tools/bash.md +1 -1
  178. package/src/prompts/tools/bisect.md +22 -0
  179. package/src/prompts/tools/browser.md +1 -1
  180. package/src/prompts/tools/computer.md +2 -2
  181. package/src/prompts/tools/cron.md +0 -2
  182. package/src/prompts/tools/eval.md +2 -2
  183. package/src/prompts/tools/find.md +1 -1
  184. package/src/prompts/tools/github.md +6 -6
  185. package/src/prompts/tools/goal.md +1 -3
  186. package/src/prompts/tools/image-gen.md +12 -5
  187. package/src/prompts/tools/irc.md +5 -4
  188. package/src/prompts/tools/job.md +2 -2
  189. package/src/prompts/tools/lsp.md +1 -1
  190. package/src/prompts/tools/monitor.md +2 -2
  191. package/src/prompts/tools/patch.md +2 -2
  192. package/src/prompts/tools/replace.md +2 -21
  193. package/src/prompts/tools/rewind.md +1 -1
  194. package/src/prompts/tools/search-tool-bm25.md +1 -11
  195. package/src/prompts/tools/search.md +1 -1
  196. package/src/prompts/tools/skill.md +3 -2
  197. package/src/prompts/tools/ssh.md +2 -1
  198. package/src/prompts/tools/subagent.md +2 -2
  199. package/src/prompts/tools/task.md +4 -5
  200. package/src/prompts/tools/todo-write.md +1 -1
  201. package/src/prompts/tools/vim.md +1 -0
  202. package/src/prompts/tools/web-search.md +2 -0
  203. package/src/prompts/tools/write.md +8 -2
  204. package/src/runtime-api-key.ts +16 -0
  205. package/src/sdk.ts +52 -28
  206. package/src/session/agent-session.ts +1011 -141
  207. package/src/session/agent-storage.ts +4 -0
  208. package/src/session/file-mention-pruning.ts +64 -0
  209. package/src/session/messages.ts +4 -0
  210. package/src/session/session-manager.ts +103 -4
  211. package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
  212. package/src/skc-runtime/deep-interview-recorder.ts +62 -8
  213. package/src/skc-runtime/deep-interview-state.ts +44 -2
  214. package/src/skc-runtime/session-state-sidecar.ts +263 -20
  215. package/src/skc-runtime/state-runtime.ts +12 -2
  216. package/src/skc-runtime/state-writer.ts +15 -0
  217. package/src/skc-runtime/ultragoal-guard.ts +55 -66
  218. package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
  219. package/src/skc-runtime/ultragoal-runtime.ts +1663 -159
  220. package/src/skc-runtime/workflow-manifest.generated.json +113 -2
  221. package/src/skc-runtime/workflow-manifest.ts +74 -1
  222. package/src/skill-state/active-state.ts +141 -5
  223. package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
  224. package/src/slash-commands/builtin-registry.ts +218 -71
  225. package/src/system-prompt.ts +57 -16
  226. package/src/task/executor.ts +132 -24
  227. package/src/task/fork-context-advisory.ts +2 -7
  228. package/src/task/fork-context-budget.ts +9 -0
  229. package/src/task/index.ts +13 -6
  230. package/src/task/output-manager.ts +20 -8
  231. package/src/task/receipt.ts +26 -7
  232. package/src/task/token-log.test.ts +188 -0
  233. package/src/task/token-log.ts +179 -0
  234. package/src/tools/bisect.ts +367 -0
  235. package/src/tools/computer.ts +246 -43
  236. package/src/tools/index.ts +3 -0
  237. package/src/tools/output-meta.ts +116 -4
  238. package/src/tools/search.ts +8 -2
  239. package/src/tools/skill.ts +15 -0
  240. package/src/tools/telegram-send.ts +6 -0
  241. package/src/tools/ultragoal-ask-guard.ts +39 -5
  242. package/src/utils/file-mentions.ts +39 -7
  243. package/src/utils/git.ts +73 -0
  244. package/src/web/scrapers/youtube.ts +24 -12
  245. package/src/workflow/workflow-intent-diff.ts +29 -6
  246. package/src/workflow/workflow-intent-report-consumer.ts +221 -0
  247. package/src/workflow/workflow-intent-report-schema.ts +129 -0
  248. package/src/workflow/workflow-intent-report.ts +135 -0
@@ -0,0 +1,234 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import type { ArgDescriptor, CommandCtor, CommandEntry, FlagDescriptor } from "@sayknow-cli/utils/cli";
5
+
6
+ export type FigName = string | string[];
7
+
8
+ export interface FigArg {
9
+ name: string;
10
+ description?: string;
11
+ isOptional?: boolean;
12
+ isVariadic?: boolean;
13
+ suggestions?: string[];
14
+ }
15
+
16
+ export interface FigOption {
17
+ name: FigName;
18
+ description?: string;
19
+ args?: FigArg;
20
+ isRepeatable?: boolean;
21
+ }
22
+
23
+ export interface FigSubcommand {
24
+ name: FigName;
25
+ description?: string;
26
+ options?: FigOption[];
27
+ args?: FigArg | FigArg[];
28
+ }
29
+
30
+ export interface FigSpec extends FigSubcommand {
31
+ name: string;
32
+ subcommands?: FigSubcommand[];
33
+ }
34
+
35
+ export type InshellisenseIndexStatus = "created" | "updated" | "already-compatible" | "overwritten";
36
+
37
+ export interface InstallSkcInshellisenseSpecOptions {
38
+ dir?: string;
39
+ force?: boolean;
40
+ }
41
+
42
+ export interface InstallSkcInshellisenseSpecResult {
43
+ directory: string;
44
+ specPath: string;
45
+ indexPath: string;
46
+ indexStatus: InshellisenseIndexStatus;
47
+ }
48
+
49
+ const GENERATED_INDEX_MARKER = "// Generated by skc completion inshellisense";
50
+ const BUILTIN_ROOT_OPTIONS: FigOption[] = [
51
+ { name: ["-h", "--help"], description: "Show help" },
52
+ { name: ["-v", "--version"], description: "Show version" },
53
+ ];
54
+ const BUILTIN_COMMAND_OPTIONS: FigOption[] = [{ name: ["-h", "--help"], description: "Show help" }];
55
+
56
+ export function defaultInshellisenseSpecDir(homeDir: string = os.homedir()): string {
57
+ return path.join(homeDir, ".fig", "autocomplete", "build");
58
+ }
59
+
60
+ function nameList(name: FigName): string[] {
61
+ return Array.isArray(name) ? name : [name];
62
+ }
63
+
64
+ function optionKey(option: FigOption): string {
65
+ return nameList(option.name).join("\u0000");
66
+ }
67
+
68
+ function combineOptions(primary: FigOption[], secondary: FigOption[]): FigOption[] {
69
+ const seen = new Set<string>();
70
+ const combined: FigOption[] = [];
71
+ for (const option of [...primary, ...secondary]) {
72
+ const key = optionKey(option);
73
+ if (seen.has(key)) continue;
74
+ seen.add(key);
75
+ combined.push(option);
76
+ }
77
+ return combined;
78
+ }
79
+
80
+ function commandEntryName(entry: CommandEntry): FigName {
81
+ const names = [entry.name, ...(entry.aliases ?? [])];
82
+ return names.length === 1 ? names[0]! : names;
83
+ }
84
+
85
+ function flagName(name: string, descriptor: FlagDescriptor): FigName {
86
+ const longName = `--${name}`;
87
+ if (!descriptor.char) return longName;
88
+ return [`-${descriptor.char}`, longName];
89
+ }
90
+
91
+ function argSuggestions(options: readonly string[] | undefined): string[] | undefined {
92
+ return options && options.length > 0 ? [...options] : undefined;
93
+ }
94
+
95
+ function optionArgName(name: string, descriptor: FlagDescriptor): string {
96
+ if (descriptor.options && descriptor.options.length > 0) return name;
97
+ return descriptor.kind === "integer" ? "int" : "value";
98
+ }
99
+
100
+ function figOptionFromFlag(name: string, descriptor: FlagDescriptor): FigOption {
101
+ const option: FigOption = {
102
+ name: flagName(name, descriptor),
103
+ };
104
+ if (descriptor.description) option.description = descriptor.description;
105
+ if (descriptor.multiple) option.isRepeatable = true;
106
+ if (descriptor.kind !== "boolean") {
107
+ const arg: FigArg = { name: optionArgName(name, descriptor) };
108
+ const suggestions = argSuggestions(descriptor.options);
109
+ if (suggestions) arg.suggestions = suggestions;
110
+ option.args = arg;
111
+ }
112
+ return option;
113
+ }
114
+
115
+ function figOptionsFromFlags(flags: Record<string, FlagDescriptor> | undefined): FigOption[] {
116
+ if (!flags) return [];
117
+ return Object.entries(flags).map(([name, descriptor]) => figOptionFromFlag(name, descriptor));
118
+ }
119
+
120
+ function figArgFromDescriptor(name: string, descriptor: ArgDescriptor): FigArg {
121
+ const arg: FigArg = { name };
122
+ if (descriptor.description) arg.description = descriptor.description;
123
+ if (!descriptor.required) arg.isOptional = true;
124
+ if (descriptor.multiple) arg.isVariadic = true;
125
+ const suggestions = argSuggestions(descriptor.options);
126
+ if (suggestions) arg.suggestions = suggestions;
127
+ return arg;
128
+ }
129
+
130
+ function figArgsFromDescriptors(args: Record<string, ArgDescriptor> | undefined): FigArg | FigArg[] | undefined {
131
+ if (!args) return undefined;
132
+ const values = Object.entries(args).map(([name, descriptor]) => figArgFromDescriptor(name, descriptor));
133
+ if (values.length === 0) return undefined;
134
+ return values.length === 1 ? values[0] : values;
135
+ }
136
+
137
+ function commandToSubcommand(entry: CommandEntry, command: CommandCtor): FigSubcommand {
138
+ const subcommand: FigSubcommand = { name: commandEntryName(entry) };
139
+ if (command.description) subcommand.description = command.description;
140
+ const options = combineOptions(BUILTIN_COMMAND_OPTIONS, figOptionsFromFlags(command.flags));
141
+ if (options.length > 0) subcommand.options = options;
142
+ const args = figArgsFromDescriptors(command.args);
143
+ if (args) subcommand.args = args;
144
+ return subcommand;
145
+ }
146
+
147
+ export async function buildSkcFigSpec(entries: CommandEntry[], rootCommandOverride?: CommandCtor): Promise<FigSpec> {
148
+ const subcommands: FigSubcommand[] = [];
149
+ let rootCommand: CommandCtor | undefined = rootCommandOverride;
150
+
151
+ for (const entry of entries) {
152
+ if (entry.name === "launch" && rootCommandOverride) {
153
+ continue;
154
+ }
155
+ let command: CommandCtor;
156
+ try {
157
+ command = await entry.load();
158
+ } catch {
159
+ if (entry.name !== "launch") {
160
+ subcommands.push({ name: commandEntryName(entry) });
161
+ }
162
+ continue;
163
+ }
164
+ if (entry.name === "launch") {
165
+ rootCommand = command;
166
+ continue;
167
+ }
168
+ if (command.hidden) continue;
169
+ subcommands.push(commandToSubcommand(entry, command));
170
+ }
171
+
172
+ const spec: FigSpec = {
173
+ name: "skc",
174
+ description: rootCommand?.description ?? "Sayknow-CLI CLI",
175
+ };
176
+ const rootOptions = combineOptions(BUILTIN_ROOT_OPTIONS, figOptionsFromFlags(rootCommand?.flags));
177
+ if (rootOptions.length > 0) spec.options = rootOptions;
178
+ const rootArgs = figArgsFromDescriptors(rootCommand?.args);
179
+ if (rootArgs) spec.args = rootArgs;
180
+ if (subcommands.length > 0) spec.subcommands = subcommands;
181
+ return spec;
182
+ }
183
+
184
+ export function renderFigSpecModule(spec: FigSpec): string {
185
+ return `const completionSpec = ${JSON.stringify(spec, null, "\t")};\n\nexport default completionSpec;\n`;
186
+ }
187
+
188
+ export function renderInshellisenseIndexModule(): string {
189
+ return `${GENERATED_INDEX_MARKER}\n\nexport default ["skc"];\nexport const diffVersionedCompletions = [];\n`;
190
+ }
191
+
192
+ async function fileTextIfExists(filePath: string): Promise<string | undefined> {
193
+ try {
194
+ return await Bun.file(filePath).text();
195
+ } catch (error) {
196
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined;
197
+ throw error;
198
+ }
199
+ }
200
+
201
+ function indexDeclaresSkc(content: string): boolean {
202
+ return /["']skc["']/.test(content);
203
+ }
204
+
205
+ async function resolveIndexStatus(indexPath: string, force: boolean | undefined): Promise<InshellisenseIndexStatus> {
206
+ const existing = await fileTextIfExists(indexPath);
207
+ if (existing === undefined) return "created";
208
+ if (existing.includes(GENERATED_INDEX_MARKER)) {
209
+ return existing === renderInshellisenseIndexModule() ? "already-compatible" : "updated";
210
+ }
211
+ if (indexDeclaresSkc(existing)) return "already-compatible";
212
+ if (force) return "overwritten";
213
+ throw new Error(
214
+ `inshellisense index already exists at ${indexPath} and does not list skc; rerun with --force to overwrite it or pass --dir to install into a SKC-only specs directory`,
215
+ );
216
+ }
217
+
218
+ export async function installSkcInshellisenseSpec(
219
+ spec: FigSpec,
220
+ options: InstallSkcInshellisenseSpecOptions = {},
221
+ ): Promise<InstallSkcInshellisenseSpecResult> {
222
+ const directory = path.resolve(options.dir ?? defaultInshellisenseSpecDir());
223
+ const specPath = path.join(directory, "skc.js");
224
+ const indexPath = path.join(directory, "index.js");
225
+ const indexStatus = await resolveIndexStatus(indexPath, options.force);
226
+
227
+ await fs.mkdir(directory, { recursive: true });
228
+ await Bun.write(specPath, renderFigSpecModule(spec));
229
+ if (indexStatus !== "already-compatible") {
230
+ await Bun.write(indexPath, renderInshellisenseIndexModule());
231
+ }
232
+
233
+ return { directory, specPath, indexPath, indexStatus };
234
+ }
@@ -33,6 +33,7 @@ export interface ConfigCommandArgs {
33
33
  value?: string;
34
34
  flags: {
35
35
  json?: boolean;
36
+ showSecrets?: boolean;
36
37
  };
37
38
  }
38
39
  // =============================================================================
@@ -47,6 +48,43 @@ type CliSettingDef = {
47
48
  };
48
49
 
49
50
  const ALL_SETTING_PATHS = Object.keys(SETTINGS_SCHEMA) as SettingPath[];
51
+ const REDACTED_SECRET_VALUE = "<redacted>";
52
+ const SECRET_SETTING_WORDS = new Set(["token", "secret", "password", "passwd", "pwd", "credential", "credentials"]);
53
+ const SECRET_SETTING_COMPOUND_PREFIXES = [
54
+ "api",
55
+ "auth",
56
+ "access",
57
+ "refresh",
58
+ "bearer",
59
+ "session",
60
+ "client",
61
+ "broker",
62
+ "bot",
63
+ "basic",
64
+ ];
65
+ const SECRET_SETTING_COMPOUND_SUFFIXES = ["token", "secret", "password", "credential"];
66
+
67
+ function isSecretSettingSegment(segment: string): boolean {
68
+ const normalized = segment.toLowerCase();
69
+ if (SECRET_SETTING_WORDS.has(normalized)) return true;
70
+ if (/api[-_]?key/i.test(segment)) return true;
71
+ const words = normalized.split(/[-_]/).filter(Boolean);
72
+ if (words.some(word => SECRET_SETTING_WORDS.has(word))) return true;
73
+ return SECRET_SETTING_COMPOUND_PREFIXES.some(prefix =>
74
+ SECRET_SETTING_COMPOUND_SUFFIXES.some(suffix => normalized === `${prefix}${suffix}`),
75
+ );
76
+ }
77
+
78
+ function isSecretSettingPath(path: string): boolean {
79
+ return path.split(".").some(segment => isSecretSettingSegment(segment));
80
+ }
81
+
82
+ function redactConfigValue(path: string, value: unknown, showSecrets?: boolean): unknown {
83
+ if (showSecrets || value === undefined || value === null || !isSecretSettingPath(path)) {
84
+ return value;
85
+ }
86
+ return REDACTED_SECRET_VALUE;
87
+ }
50
88
 
51
89
  /** Find setting definition by path */
52
90
  function findSettingDef(path: string): CliSettingDef | undefined {
@@ -105,6 +143,8 @@ export function parseConfigArgs(args: string[]): ConfigCommandArgs | undefined {
105
143
  const arg = args[i];
106
144
  if (arg === "--json") {
107
145
  result.flags.json = true;
146
+ } else if (arg === "--show-secrets") {
147
+ result.flags.showSecrets = true;
108
148
  } else if (!arg.startsWith("-")) {
109
149
  positionalArgs.push(arg);
110
150
  }
@@ -265,14 +305,15 @@ export async function runConfigCommand(cmd: ConfigCommandArgs): Promise<void> {
265
305
  }
266
306
  }
267
307
 
268
- function handleList(flags: { json?: boolean }): void {
308
+ function handleList(flags: { json?: boolean; showSecrets?: boolean }): void {
269
309
  const defs = ALL_SETTING_PATHS.map(path => findSettingDef(path)).filter((def): def is CliSettingDef => !!def);
270
310
 
271
311
  if (flags.json) {
272
312
  const result: Record<string, { value: unknown; type: string; description: string }> = {};
273
313
  for (const def of defs) {
314
+ const value = settings.get(def.path);
274
315
  result[def.path] = {
275
- value: settings.get(def.path),
316
+ value: redactConfigValue(def.path, value, flags.showSecrets),
276
317
  type: def.type,
277
318
  description: def.description,
278
319
  };
@@ -301,7 +342,8 @@ function handleList(flags: { json?: boolean }): void {
301
342
  console.log(chalk.bold.blue(`[${group}]`));
302
343
  for (const def of groups[group]) {
303
344
  const value = settings.get(def.path);
304
- const valueStr = formatValue(value);
345
+ const displayValue = redactConfigValue(def.path, value, flags.showSecrets);
346
+ const valueStr = formatValue(displayValue);
305
347
  const typeStr = getTypeDisplay(def);
306
348
  console.log(` ${chalk.white(def.path)} = ${valueStr} ${chalk.dim(typeStr)}`);
307
349
  }
@@ -309,7 +351,7 @@ function handleList(flags: { json?: boolean }): void {
309
351
  }
310
352
  }
311
353
 
312
- function handleGet(key: string | undefined, flags: { json?: boolean }): void {
354
+ function handleGet(key: string | undefined, flags: { json?: boolean; showSecrets?: boolean }): void {
313
355
  if (!key) {
314
356
  console.error(chalk.red(`Usage: ${APP_NAME} config get <key>`));
315
357
  console.error(chalk.dim(`\nRun '${APP_NAME} config list' to see available keys`));
@@ -324,16 +366,23 @@ function handleGet(key: string | undefined, flags: { json?: boolean }): void {
324
366
  }
325
367
 
326
368
  const value = settings.get(def.path);
369
+ const displayValue = redactConfigValue(def.path, value, flags.showSecrets);
327
370
 
328
371
  if (flags.json) {
329
- console.log(JSON.stringify({ key: def.path, value, type: def.type, description: def.description }, null, 2));
372
+ console.log(
373
+ JSON.stringify({ key: def.path, value: displayValue, type: def.type, description: def.description }, null, 2),
374
+ );
330
375
  return;
331
376
  }
332
377
 
333
- console.log(formatValue(value));
378
+ console.log(formatValue(displayValue));
334
379
  }
335
380
 
336
- async function handleSet(key: string | undefined, value: string | undefined, flags: { json?: boolean }): Promise<void> {
381
+ async function handleSet(
382
+ key: string | undefined,
383
+ value: string | undefined,
384
+ flags: { json?: boolean; showSecrets?: boolean },
385
+ ): Promise<void> {
337
386
  if (!key || value === undefined) {
338
387
  console.error(chalk.red(`Usage: ${APP_NAME} config set <key> <value>`));
339
388
  console.error(chalk.dim(`\nRun '${APP_NAME} config list' to see available keys`));
@@ -355,11 +404,12 @@ async function handleSet(key: string | undefined, value: string | undefined, fla
355
404
  }
356
405
 
357
406
  const newValue = settings.get(def.path);
407
+ const displayValue = redactConfigValue(def.path, newValue, flags.showSecrets);
358
408
 
359
409
  if (flags.json) {
360
- console.log(JSON.stringify({ key: def.path, value: newValue }));
410
+ console.log(JSON.stringify({ key: def.path, value: displayValue }));
361
411
  } else {
362
- console.log(chalk.green(`${theme.status.success} Set ${def.path} = ${formatValue(newValue)}`));
412
+ console.log(chalk.green(`${theme.status.success} Set ${def.path} = ${formatValue(displayValue)}`));
363
413
  }
364
414
  }
365
415
 
@@ -409,6 +459,7 @@ ${chalk.bold("Commands:")}
409
459
 
410
460
  ${chalk.bold("Options:")}
411
461
  --json Output as JSON
462
+ --show-secrets Show secret-like setting values without redaction (unsafe)
412
463
 
413
464
  ${chalk.bold("Examples:")}
414
465
  ${APP_NAME} config list
@@ -418,6 +469,7 @@ ${chalk.bold("Examples:")}
418
469
  ${APP_NAME} config set defaultThinkingLevel medium
419
470
  ${APP_NAME} config reset steeringMode
420
471
  ${APP_NAME} config list --json
472
+ ${APP_NAME} config get auth.broker.token --show-secrets
421
473
  ${APP_NAME} config init-xdg
422
474
 
423
475
  ${chalk.bold("Boolean Values:")}
@@ -15,6 +15,7 @@ export function getExtraHelpText(): string {
15
15
  ${APP_NAME} deep-interview - Run requirements interview workflow
16
16
  ${APP_NAME} skills - List/read embedded workflow skills
17
17
  ${APP_NAME} config - List, get, and set configuration values
18
+ ${APP_NAME} stats - View AI usage statistics (tokens, cost, requests)
18
19
  ${APP_NAME} notify - Send or test notifications
19
20
  ${APP_NAME} daemon - Manage background daemon helpers
20
21
  ${APP_NAME} mcp - Manage MCP server registrations
@@ -0,0 +1,230 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { sessionRoot } from "../skc-runtime/session-layout";
6
+ import type { TaskTokenLog } from "../task/types";
7
+ import { buildFixtureReport, type LiveRunReportShape, runFixtureReport } from "./fixture-report";
8
+
9
+ function expectParseReportCompatible(report: LiveRunReportShape, fixtureId: string): void {
10
+ expect(report.schemaVersion).toBe(1);
11
+ expect(typeof report.binaryId).toBe("string");
12
+ expect(report.binaryId.length).toBeGreaterThan(0);
13
+ expect(report.fixtureId).toBe(fixtureId);
14
+ for (const value of Object.values(report.totals)) {
15
+ expect(typeof value).toBe("number");
16
+ expect(Number.isFinite(value)).toBe(true);
17
+ }
18
+ expect(report.receiptArtifactRatio).toBeNull();
19
+ expect(report.spawnDecisions).toBeNull();
20
+ expect(report.roi).toBeNull();
21
+ }
22
+
23
+ describe("fixture report", () => {
24
+ it("reports empty logs as a schema-v1 null-cache-hit report", () => {
25
+ const report = buildFixtureReport("empty-fixture", []);
26
+
27
+ expectParseReportCompatible(report, "empty-fixture");
28
+ expect(report.totals).toEqual({
29
+ turns: 0,
30
+ inputTokens: 0,
31
+ outputTokens: 0,
32
+ cacheReadTokens: 0,
33
+ cacheWriteTokens: 0,
34
+ totalTokens: 0,
35
+ });
36
+ expect(report.cacheHitRate).toBeNull();
37
+ });
38
+
39
+ it("sums deterministic fixed-fixture raw token buckets without folding cache into input", () => {
40
+ const logs: TaskTokenLog[] = [
41
+ {
42
+ subagentId: "root",
43
+ agent: "main",
44
+ turn: 1,
45
+ at: "2026-01-01T00:00:00.000Z",
46
+ input: 100,
47
+ output: 20,
48
+ cacheRead: 40,
49
+ cacheWrite: 10,
50
+ totalTokens: 170,
51
+ model: "fixture-model",
52
+ },
53
+ {
54
+ subagentId: "1-executor",
55
+ agent: "executor",
56
+ turn: 1,
57
+ at: "2026-01-01T00:00:01.000Z",
58
+ input: 60,
59
+ output: 15,
60
+ cacheRead: 20,
61
+ cacheWrite: 5,
62
+ totalTokens: 100,
63
+ model: "fixture-model",
64
+ },
65
+ ];
66
+ const report = buildFixtureReport("fixed-fixture", logs);
67
+ const inputTokens = logs.reduce((sum, log) => sum + log.input, 0);
68
+ const outputTokens = logs.reduce((sum, log) => sum + log.output, 0);
69
+ const cacheReadTokens = logs.reduce((sum, log) => sum + log.cacheRead, 0);
70
+ const cacheWriteTokens = logs.reduce((sum, log) => sum + log.cacheWrite, 0);
71
+ const totalTokens = inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
72
+
73
+ expectParseReportCompatible(report, "fixed-fixture");
74
+ expect(report.totals).toEqual({
75
+ turns: logs.length,
76
+ inputTokens,
77
+ outputTokens,
78
+ cacheReadTokens,
79
+ cacheWriteTokens,
80
+ totalTokens,
81
+ });
82
+ expect(report.cacheHitRate).toBe(cacheReadTokens / (inputTokens + cacheReadTokens));
83
+ });
84
+
85
+ it("emits only parseReport-compatible schema-v1 fields", () => {
86
+ const report = buildFixtureReport("shape-fixture", [
87
+ {
88
+ subagentId: "root",
89
+ turn: 1,
90
+ at: "2026-01-01T00:00:00.000Z",
91
+ input: 0,
92
+ output: 0,
93
+ cacheRead: 1,
94
+ cacheWrite: 0,
95
+ totalTokens: 1,
96
+ },
97
+ ]);
98
+
99
+ expect(Object.keys(report).sort()).toEqual([
100
+ "binaryId",
101
+ "cacheHitRate",
102
+ "fixtureId",
103
+ "receiptArtifactRatio",
104
+ "roi",
105
+ "schemaVersion",
106
+ "spawnDecisions",
107
+ "totals",
108
+ ]);
109
+ expect(Object.keys(report.totals).sort()).toEqual([
110
+ "cacheReadTokens",
111
+ "cacheWriteTokens",
112
+ "inputTokens",
113
+ "outputTokens",
114
+ "totalTokens",
115
+ "turns",
116
+ ]);
117
+ expectParseReportCompatible(report, "shape-fixture");
118
+ });
119
+ });
120
+
121
+ async function captureRun(fixtureId: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {
122
+ const originalCwd = process.cwd();
123
+ const originalStdout = process.stdout.write.bind(process.stdout);
124
+ const originalStderr = process.stderr.write.bind(process.stderr);
125
+ let exitCode = 0;
126
+ let stdout = "";
127
+ let stderr = "";
128
+ process.stdout.write = ((chunk: string | Uint8Array): boolean => {
129
+ stdout += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8");
130
+ return true;
131
+ }) as typeof process.stdout.write;
132
+ process.stderr.write = ((chunk: string | Uint8Array): boolean => {
133
+ stderr += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8");
134
+ return true;
135
+ }) as typeof process.stderr.write;
136
+ try {
137
+ exitCode = await runFixtureReport(fixtureId);
138
+ return { stdout, stderr, exitCode };
139
+ } finally {
140
+ process.stdout.write = originalStdout;
141
+ process.stderr.write = originalStderr;
142
+ process.chdir(originalCwd);
143
+ }
144
+ }
145
+
146
+ describe("runFixtureReport CLI degradation", () => {
147
+ it("exits non-zero without emitting JSON for an unknown fixture id", async () => {
148
+ await withTempCwd(async () => {
149
+ const { stdout, stderr, exitCode } = await captureRun("definitely-not-a-real-session-id");
150
+ expect(exitCode).toBe(1);
151
+ expect(stdout).toBe("");
152
+ expect(stderr).toContain("unknown fixture id");
153
+ });
154
+ });
155
+
156
+ it("reads only the requested session token-log directory", async () => {
157
+ await withTempCwd(async () => {
158
+ const firstSession = "fixture-session-a";
159
+ const secondSession = "fixture-session-b";
160
+ await writeTokenLog(firstSession, { input: 100, output: 10, cacheRead: 0, cacheWrite: 0, totalTokens: 110 });
161
+ await writeTokenLog(secondSession, { input: 900, output: 90, cacheRead: 0, cacheWrite: 0, totalTokens: 990 });
162
+
163
+ const { stdout, exitCode } = await captureRun(firstSession);
164
+ const report = JSON.parse(stdout) as LiveRunReportShape;
165
+
166
+ expect(exitCode).toBe(0);
167
+ expect(report.fixtureId).toBe(firstSession);
168
+ expect(report.totals.totalTokens).toBe(110);
169
+ });
170
+ });
171
+
172
+ it("emits deterministic PR9 default-candidate fixture reports", async () => {
173
+ await withTempCwd(async () => {
174
+ const { stdout, exitCode } = await captureRun("pr9.read-artifact-spill-threshold.after");
175
+ const report = JSON.parse(stdout) as LiveRunReportShape;
176
+
177
+ expect(exitCode).toBe(0);
178
+ expectParseReportCompatible(report, "pr9.read-artifact-spill-threshold.after");
179
+ expect(report.totals.totalTokens).toBe(20_100);
180
+ });
181
+ });
182
+
183
+ it("exits non-zero without emitting JSON for a corrupt token-log", async () => {
184
+ await withTempCwd(async () => {
185
+ const id = "corrupt-session";
186
+ const dir = join(sessionRoot(process.cwd(), id), "token-logs");
187
+ await mkdir(dir, { recursive: true });
188
+ await writeFile(join(dir, "token-log.jsonl"), "not json\n{}\n", "utf-8");
189
+ const { stdout, stderr, exitCode } = await captureRun(id);
190
+ expect(exitCode).toBe(1);
191
+ expect(stdout).toBe("");
192
+ expect(stderr).toContain("failed to build fixture report");
193
+ });
194
+ });
195
+ });
196
+
197
+ async function withTempCwd(fn: (cwd: string) => Promise<void>): Promise<void> {
198
+ const originalCwd = process.cwd();
199
+ const dir = await mkdtemp(join(tmpdir(), "skc-fixture-report-"));
200
+ process.chdir(dir);
201
+ try {
202
+ await fn(dir);
203
+ } finally {
204
+ // Restore the real cwd BEFORE removing the temp dir, otherwise the process
205
+ // is left sitting in a deleted directory and later tests in the same
206
+ // process fail with ENOENT on relative-path/process.cwd() operations.
207
+ process.chdir(originalCwd);
208
+ await rm(dir, { recursive: true, force: true });
209
+ }
210
+ }
211
+
212
+ async function writeTokenLog(
213
+ sessionId: string,
214
+ metrics: { input: number; output: number; cacheRead: number; cacheWrite: number; totalTokens: number },
215
+ ): Promise<void> {
216
+ const dir = join(sessionRoot(process.cwd(), sessionId), "token-logs");
217
+ await mkdir(dir, { recursive: true });
218
+ await writeFile(
219
+ join(dir, "token-log.jsonl"),
220
+ `${JSON.stringify({
221
+ subagentId: "root",
222
+ agent: "main",
223
+ turn: 1,
224
+ at: "2026-01-01T00:00:00.000Z",
225
+ model: "fixture-model",
226
+ ...metrics,
227
+ })}\n`,
228
+ "utf-8",
229
+ );
230
+ }