@oh-my-pi/pi-coding-agent 15.5.15 → 15.7.0

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 (274) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/types/capability/rule-buckets.d.ts +30 -0
  3. package/dist/types/capability/rule.d.ts +7 -0
  4. package/dist/types/cli/classify-install-target.d.ts +0 -10
  5. package/dist/types/cli/completion-gen.d.ts +80 -0
  6. package/dist/types/cli/initial-message.d.ts +1 -1
  7. package/dist/types/cli/tiny-models-cli.d.ts +9 -0
  8. package/dist/types/commands/complete.d.ts +6 -0
  9. package/dist/types/commands/completions.d.ts +13 -0
  10. package/dist/types/commands/setup.d.ts +10 -1
  11. package/dist/types/commands/tiny-models.d.ts +22 -0
  12. package/dist/types/commit/analysis/conventional.d.ts +1 -1
  13. package/dist/types/commit/analysis/summary.d.ts +1 -1
  14. package/dist/types/commit/changelog/generate.d.ts +1 -1
  15. package/dist/types/commit/changelog/index.d.ts +2 -2
  16. package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
  17. package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
  18. package/dist/types/config/model-id-affixes.d.ts +10 -0
  19. package/dist/types/config/settings-schema.d.ts +402 -17
  20. package/dist/types/discovery/builtin-defaults.d.ts +1 -0
  21. package/dist/types/discovery/builtin-rules/index.d.ts +7 -0
  22. package/dist/types/discovery/helpers.d.ts +1 -1
  23. package/dist/types/discovery/index.d.ts +1 -0
  24. package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
  25. package/dist/types/edit/hashline/block-resolver.d.ts +9 -0
  26. package/dist/types/edit/hashline/index.d.ts +1 -0
  27. package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
  28. package/dist/types/eval/py/kernel.d.ts +3 -0
  29. package/dist/types/eval/py/runtime.d.ts +11 -1
  30. package/dist/types/export/html/template.generated.d.ts +1 -1
  31. package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
  32. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
  33. package/dist/types/internal-urls/local-protocol.d.ts +2 -1
  34. package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
  35. package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
  36. package/dist/types/internal-urls/router.d.ts +8 -1
  37. package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
  38. package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
  39. package/dist/types/internal-urls/types.d.ts +26 -0
  40. package/dist/types/main.d.ts +1 -0
  41. package/dist/types/memory-backend/index.d.ts +1 -0
  42. package/dist/types/memory-backend/resolve.d.ts +2 -1
  43. package/dist/types/memory-backend/types.d.ts +7 -1
  44. package/dist/types/mnemosyne/backend.d.ts +4 -0
  45. package/dist/types/mnemosyne/config.d.ts +29 -0
  46. package/dist/types/mnemosyne/index.d.ts +3 -0
  47. package/dist/types/mnemosyne/state.d.ts +72 -0
  48. package/dist/types/modes/components/custom-editor.d.ts +2 -3
  49. package/dist/types/modes/components/hook-selector.d.ts +27 -0
  50. package/dist/types/modes/components/index.d.ts +2 -0
  51. package/dist/types/modes/components/segment-track.d.ts +22 -0
  52. package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
  53. package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
  54. package/dist/types/modes/components/welcome.d.ts +22 -0
  55. package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
  56. package/dist/types/modes/gradient-highlight.d.ts +23 -0
  57. package/dist/types/modes/interactive-mode.d.ts +7 -4
  58. package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
  59. package/dist/types/modes/orchestrate.d.ts +10 -0
  60. package/dist/types/modes/setup-wizard/index.d.ts +16 -0
  61. package/dist/types/modes/setup-wizard/scenes/glyph.d.ts +2 -0
  62. package/dist/types/modes/setup-wizard/scenes/outro.d.ts +2 -0
  63. package/dist/types/modes/setup-wizard/scenes/providers.d.ts +2 -0
  64. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +19 -0
  65. package/dist/types/modes/setup-wizard/scenes/splash.d.ts +11 -0
  66. package/dist/types/modes/setup-wizard/scenes/theme.d.ts +2 -0
  67. package/dist/types/modes/setup-wizard/scenes/types.d.ts +43 -0
  68. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +19 -0
  69. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +14 -0
  70. package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
  71. package/dist/types/modes/theme/shimmer.d.ts +2 -0
  72. package/dist/types/modes/theme/theme.d.ts +11 -0
  73. package/dist/types/modes/types.d.ts +5 -1
  74. package/dist/types/modes/ultrathink.d.ts +3 -3
  75. package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
  76. package/dist/types/sdk.d.ts +3 -0
  77. package/dist/types/session/agent-session.d.ts +33 -0
  78. package/dist/types/system-prompt.d.ts +2 -0
  79. package/dist/types/task/executor.d.ts +2 -0
  80. package/dist/types/task/render.d.ts +5 -1
  81. package/dist/types/tiny/device.d.ts +78 -0
  82. package/dist/types/tiny/dtype.d.ts +85 -0
  83. package/dist/types/tiny/models.d.ts +185 -0
  84. package/dist/types/tiny/text.d.ts +19 -0
  85. package/dist/types/tiny/title-client.d.ts +32 -0
  86. package/dist/types/tiny/title-protocol.d.ts +74 -0
  87. package/dist/types/tiny/worker.d.ts +2 -0
  88. package/dist/types/tools/bash.d.ts +3 -2
  89. package/dist/types/tools/eval.d.ts +1 -1
  90. package/dist/types/tools/index.d.ts +7 -4
  91. package/dist/types/tools/memory-edit.d.ts +40 -0
  92. package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
  93. package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
  94. package/dist/types/tools/memory-render.d.ts +60 -0
  95. package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
  96. package/dist/types/tools/todo-write.d.ts +8 -0
  97. package/dist/types/tools/tool-result.d.ts +2 -0
  98. package/dist/types/tui/code-cell.d.ts +2 -0
  99. package/dist/types/tui/output-block.d.ts +17 -0
  100. package/dist/types/utils/title-generator.d.ts +3 -0
  101. package/package.json +18 -14
  102. package/scripts/build-binary.ts +1 -0
  103. package/src/capability/rule-buckets.ts +64 -0
  104. package/src/capability/rule.ts +8 -0
  105. package/src/cli/completion-gen.ts +550 -0
  106. package/src/cli/setup-cli.ts +5 -3
  107. package/src/cli/tiny-models-cli.ts +127 -0
  108. package/src/cli-commands.ts +3 -0
  109. package/src/cli.ts +9 -15
  110. package/src/commands/complete.ts +66 -0
  111. package/src/commands/completions.ts +60 -0
  112. package/src/commands/setup.ts +29 -4
  113. package/src/commands/tiny-models.ts +36 -0
  114. package/src/config/model-equivalence.ts +43 -2
  115. package/src/config/model-id-affixes.ts +64 -0
  116. package/src/config/model-registry.ts +84 -10
  117. package/src/config/settings-schema.ts +275 -15
  118. package/src/discovery/builtin-defaults.ts +39 -0
  119. package/src/discovery/builtin-rules/index.ts +48 -0
  120. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  121. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  122. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  123. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  124. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  125. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  126. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  127. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  128. package/src/discovery/builtin-rules/ts-no-any.md +56 -0
  129. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  130. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  131. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +50 -0
  132. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  133. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  134. package/src/discovery/index.ts +1 -0
  135. package/src/edit/hashline/block-resolver.ts +14 -0
  136. package/src/edit/hashline/diff.ts +9 -8
  137. package/src/edit/hashline/execute.ts +2 -1
  138. package/src/edit/hashline/index.ts +1 -0
  139. package/src/eval/__tests__/shared-executors.test.ts +36 -0
  140. package/src/eval/js/shared/local-module-loader.ts +13 -1
  141. package/src/eval/js/shared/rewrite-imports.ts +31 -26
  142. package/src/eval/py/kernel.ts +37 -15
  143. package/src/eval/py/runtime.ts +57 -28
  144. package/src/export/html/template.generated.ts +1 -1
  145. package/src/export/html/template.js +0 -12
  146. package/src/export/ttsr.ts +2 -0
  147. package/src/internal-urls/agent-protocol.ts +18 -1
  148. package/src/internal-urls/artifact-protocol.ts +19 -1
  149. package/src/internal-urls/docs-index.generated.ts +8 -7
  150. package/src/internal-urls/local-protocol.ts +14 -1
  151. package/src/internal-urls/memory-protocol.ts +6 -1
  152. package/src/internal-urls/omp-protocol.ts +5 -1
  153. package/src/internal-urls/router.ts +20 -1
  154. package/src/internal-urls/rule-protocol.ts +8 -1
  155. package/src/internal-urls/skill-protocol.ts +8 -1
  156. package/src/internal-urls/types.ts +27 -0
  157. package/src/lsp/render.ts +1 -1
  158. package/src/main.ts +18 -1
  159. package/src/mcp/oauth-flow.ts +2 -2
  160. package/src/memory-backend/index.ts +1 -0
  161. package/src/memory-backend/resolve.ts +4 -1
  162. package/src/memory-backend/types.ts +8 -1
  163. package/src/mnemosyne/backend.ts +374 -0
  164. package/src/mnemosyne/config.ts +160 -0
  165. package/src/mnemosyne/index.ts +3 -0
  166. package/src/mnemosyne/state.ts +548 -0
  167. package/src/modes/acp/acp-agent.ts +11 -6
  168. package/src/modes/components/agent-dashboard.ts +4 -4
  169. package/src/modes/components/custom-editor.ts +3 -2
  170. package/src/modes/components/diff.ts +2 -2
  171. package/src/modes/components/extensions/extension-list.ts +3 -2
  172. package/src/modes/components/footer.ts +5 -6
  173. package/src/modes/components/history-search.ts +3 -3
  174. package/src/modes/components/hook-selector.ts +92 -8
  175. package/src/modes/components/index.ts +2 -0
  176. package/src/modes/components/mcp-add-wizard.ts +3 -3
  177. package/src/modes/components/model-selector.ts +5 -4
  178. package/src/modes/components/oauth-selector.ts +3 -3
  179. package/src/modes/components/segment-track.ts +52 -0
  180. package/src/modes/components/session-observer-overlay.ts +19 -13
  181. package/src/modes/components/session-selector.ts +3 -3
  182. package/src/modes/components/settings-defs.ts +7 -0
  183. package/src/modes/components/status-line/context-thresholds.ts +11 -0
  184. package/src/modes/components/status-line/segments.ts +2 -2
  185. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  186. package/src/modes/components/tips.txt +13 -0
  187. package/src/modes/components/tool-execution.ts +72 -4
  188. package/src/modes/components/tree-selector.ts +3 -3
  189. package/src/modes/components/user-message-selector.ts +3 -3
  190. package/src/modes/components/welcome.ts +102 -43
  191. package/src/modes/controllers/command-controller.ts +16 -1
  192. package/src/modes/controllers/extension-ui-controller.ts +3 -1
  193. package/src/modes/controllers/input-controller.ts +69 -21
  194. package/src/modes/gradient-highlight.ts +70 -0
  195. package/src/modes/interactive-mode.ts +75 -114
  196. package/src/modes/internal-url-autocomplete.ts +143 -0
  197. package/src/modes/orchestrate.ts +36 -0
  198. package/src/modes/prompt-action-autocomplete.ts +12 -0
  199. package/src/modes/setup-wizard/index.ts +88 -0
  200. package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
  201. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  202. package/src/modes/setup-wizard/scenes/providers.ts +69 -0
  203. package/src/modes/setup-wizard/scenes/sign-in.ts +193 -0
  204. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  205. package/src/modes/setup-wizard/scenes/theme.ts +299 -0
  206. package/src/modes/setup-wizard/scenes/types.ts +48 -0
  207. package/src/modes/setup-wizard/scenes/web-search.ts +128 -0
  208. package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
  209. package/src/modes/theme/shimmer.ts +5 -0
  210. package/src/modes/theme/theme.ts +44 -20
  211. package/src/modes/types.ts +6 -1
  212. package/src/modes/ultrathink.ts +9 -53
  213. package/src/modes/utils/keybinding-matchers.ts +11 -0
  214. package/src/prompts/system/memory-consolidation-system.md +8 -0
  215. package/src/prompts/system/memory-extraction-system.md +26 -0
  216. package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +6 -17
  217. package/src/prompts/system/system-prompt.md +2 -0
  218. package/src/prompts/system/tiny-title-system.md +8 -0
  219. package/src/prompts/tools/memory-edit.md +8 -0
  220. package/src/prompts/tools/read.md +4 -0
  221. package/src/prompts/tools/task.md +4 -7
  222. package/src/sdk.ts +13 -21
  223. package/src/session/agent-session.ts +128 -44
  224. package/src/slash-commands/builtin-registry.ts +18 -1
  225. package/src/system-prompt.ts +4 -0
  226. package/src/task/commands.ts +1 -5
  227. package/src/task/executor.ts +8 -0
  228. package/src/task/index.ts +2 -0
  229. package/src/task/render.ts +69 -26
  230. package/src/tiny/device.ts +117 -0
  231. package/src/tiny/dtype.ts +101 -0
  232. package/src/tiny/models.ts +218 -0
  233. package/src/tiny/text.ts +54 -0
  234. package/src/tiny/title-client.ts +395 -0
  235. package/src/tiny/title-protocol.ts +51 -0
  236. package/src/tiny/worker.ts +587 -0
  237. package/src/tools/bash.ts +74 -29
  238. package/src/tools/browser/tab-worker.ts +1 -1
  239. package/src/tools/eval.ts +9 -4
  240. package/src/tools/index.ts +17 -22
  241. package/src/tools/memory-edit.ts +59 -0
  242. package/src/tools/memory-recall.ts +100 -0
  243. package/src/tools/memory-reflect.ts +88 -0
  244. package/src/tools/memory-render.ts +185 -0
  245. package/src/tools/memory-retain.ts +91 -0
  246. package/src/tools/read.ts +1 -0
  247. package/src/tools/renderers.ts +4 -2
  248. package/src/tools/todo-write.ts +128 -29
  249. package/src/tools/tool-result.ts +8 -0
  250. package/src/tui/code-cell.ts +6 -1
  251. package/src/tui/output-block.ts +199 -38
  252. package/src/utils/title-generator.ts +115 -13
  253. package/dist/types/tools/recipe/index.d.ts +0 -46
  254. package/dist/types/tools/recipe/render.d.ts +0 -36
  255. package/dist/types/tools/recipe/runner.d.ts +0 -60
  256. package/dist/types/tools/recipe/runners/cargo.d.ts +0 -16
  257. package/dist/types/tools/recipe/runners/index.d.ts +0 -2
  258. package/dist/types/tools/recipe/runners/just.d.ts +0 -2
  259. package/dist/types/tools/recipe/runners/make.d.ts +0 -2
  260. package/dist/types/tools/recipe/runners/pkg.d.ts +0 -2
  261. package/dist/types/tools/recipe/runners/task.d.ts +0 -2
  262. package/src/prompts/tools/recipe.md +0 -16
  263. package/src/tools/hindsight-recall.ts +0 -69
  264. package/src/tools/hindsight-reflect.ts +0 -58
  265. package/src/tools/hindsight-retain.ts +0 -57
  266. package/src/tools/recipe/index.ts +0 -81
  267. package/src/tools/recipe/render.ts +0 -19
  268. package/src/tools/recipe/runner.ts +0 -219
  269. package/src/tools/recipe/runners/cargo.ts +0 -131
  270. package/src/tools/recipe/runners/index.ts +0 -8
  271. package/src/tools/recipe/runners/just.ts +0 -73
  272. package/src/tools/recipe/runners/make.ts +0 -101
  273. package/src/tools/recipe/runners/pkg.ts +0 -167
  274. package/src/tools/recipe/runners/task.ts +0 -72
@@ -0,0 +1,550 @@
1
+ /**
2
+ * Shell-completion generation (bash, zsh, fish).
3
+ *
4
+ * Single source of truth: the declarative `flags`/`args` descriptors carried by
5
+ * each `Command` subclass plus the registered subcommand table. {@link buildSpec}
6
+ * walks that metadata — the same data `renderCommandBody` renders for `--help` —
7
+ * and {@link generateCompletion} emits a self-contained completion script. Adding
8
+ * a flag to a command's static `flags` therefore propagates into completions with
9
+ * no edits here.
10
+ *
11
+ * Static candidates (enum `options`, the builtin tool list) are baked into the
12
+ * script. A small set of flags resolve dynamic candidates (the live model
13
+ * catalog and on-disk sessions) by calling back into `<bin> __complete <kind>`
14
+ * — see `commands/complete.ts`. The flag→source mapping below is the only manual
15
+ * knob and is keyed by flag name so it stays stable as flags are added.
16
+ */
17
+ import type { ArgDescriptor, CliConfig, CommandCtor, FlagDescriptor } from "@oh-my-pi/pi-utils/cli";
18
+ import { BUILTIN_TOOLS } from "../tools";
19
+
20
+ export type Shell = "bash" | "zsh" | "fish";
21
+
22
+ /** How a flag/positional value should be completed. */
23
+ export type ValueSource =
24
+ | { kind: "flag" } // boolean — takes no value
25
+ | { kind: "value" } // takes a value with no completable candidates (e.g. integer, free text)
26
+ | { kind: "enum"; values: readonly string[] } // static single value
27
+ | { kind: "list"; values: readonly string[] } // static comma-separated list
28
+ | { kind: "models"; multiple: boolean } // dynamic: live model catalog
29
+ | { kind: "sessions" } // dynamic: on-disk sessions
30
+ | { kind: "file" }
31
+ | { kind: "dir" };
32
+
33
+ export interface CompletionFlag {
34
+ /** Long name without the leading `--`. */
35
+ name: string;
36
+ /** Short character without the leading `-`. */
37
+ char?: string;
38
+ description: string;
39
+ value: ValueSource;
40
+ /** Flag may appear multiple times (oclif `multiple`). */
41
+ repeatable: boolean;
42
+ }
43
+
44
+ export interface CompletionArg {
45
+ name: string;
46
+ description: string;
47
+ value: ValueSource;
48
+ }
49
+
50
+ export interface CompletionCommand {
51
+ name: string;
52
+ aliases: readonly string[];
53
+ description: string;
54
+ flags: CompletionFlag[];
55
+ args: CompletionArg[];
56
+ }
57
+
58
+ export interface CompletionSpec {
59
+ bin: string;
60
+ /** Flags/args of the default (no-subcommand) command. */
61
+ root: { flags: CompletionFlag[]; args: CompletionArg[] };
62
+ commands: CompletionCommand[];
63
+ }
64
+
65
+ // --- Flag/arg value classification (the single manual mapping) ----------------
66
+
67
+ /** Single-value flags resolved against the live model catalog. */
68
+ const MODEL_FLAGS: Record<string, true> = { model: true, smol: true, slow: true, plan: true };
69
+ /** Single-value flags resolved against on-disk sessions. */
70
+ const SESSION_FLAGS: Record<string, true> = { resume: true, fork: true, session: true };
71
+ /** Flags whose value is a directory path. */
72
+ const DIR_FLAGS: Record<string, true> = { "session-dir": true, "plugin-dir": true };
73
+
74
+ function flagValue(name: string, desc: FlagDescriptor): ValueSource {
75
+ if (desc.kind === "boolean") return { kind: "flag" };
76
+ if (desc.options && desc.options.length > 0) return { kind: "enum", values: desc.options };
77
+ if (MODEL_FLAGS[name]) return { kind: "models", multiple: false };
78
+ if (name === "models") return { kind: "models", multiple: true };
79
+ if (SESSION_FLAGS[name]) return { kind: "sessions" };
80
+ if (name === "tools") return { kind: "list", values: Object.keys(BUILTIN_TOOLS) };
81
+ if (DIR_FLAGS[name]) return { kind: "dir" };
82
+ if (desc.kind === "integer") return { kind: "value" };
83
+ return { kind: "file" };
84
+ }
85
+
86
+ function argValue(desc: ArgDescriptor): ValueSource {
87
+ if (desc.options && desc.options.length > 0) return { kind: "enum", values: desc.options };
88
+ return { kind: "file" };
89
+ }
90
+
91
+ function buildFlags(Cmd: CommandCtor): CompletionFlag[] {
92
+ const out: CompletionFlag[] = [];
93
+ const flags = Cmd.flags ?? {};
94
+ for (const name in flags) {
95
+ const desc = flags[name];
96
+ out.push({
97
+ name,
98
+ char: desc.char,
99
+ description: desc.description ?? "",
100
+ value: flagValue(name, desc),
101
+ repeatable: Boolean(desc.multiple),
102
+ });
103
+ }
104
+ return out;
105
+ }
106
+
107
+ function buildArgs(Cmd: CommandCtor): CompletionArg[] {
108
+ const out: CompletionArg[] = [];
109
+ const args = Cmd.args ?? {};
110
+ for (const name in args) {
111
+ const desc = args[name];
112
+ out.push({ name, description: desc.description ?? "", value: argValue(desc) });
113
+ }
114
+ return out;
115
+ }
116
+
117
+ /**
118
+ * Build a {@link CompletionSpec} from loaded command classes.
119
+ *
120
+ * @param rootName Entry name of the default command (its flags become top-level
121
+ * flags; it is excluded from the subcommand list).
122
+ * @param aliasMap Canonical-name → aliases (merged from the registration table
123
+ * and the command class's static `aliases`).
124
+ */
125
+ export function buildSpec(
126
+ config: CliConfig,
127
+ rootName: string,
128
+ aliasMap: Map<string, readonly string[]>,
129
+ ): CompletionSpec {
130
+ const commands: CompletionCommand[] = [];
131
+ let root: CompletionSpec["root"] = { flags: [], args: [] };
132
+ for (const [name, Cmd] of config.commands) {
133
+ const flags = buildFlags(Cmd);
134
+ const args = buildArgs(Cmd);
135
+ if (name === rootName) {
136
+ root = { flags, args };
137
+ continue;
138
+ }
139
+ if (Cmd.hidden) continue;
140
+ commands.push({
141
+ name,
142
+ aliases: aliasMap.get(name) ?? [],
143
+ description: Cmd.description ?? "",
144
+ flags,
145
+ args,
146
+ });
147
+ }
148
+ commands.sort((a, b) => a.name.localeCompare(b.name));
149
+ return { bin: config.bin, root, commands };
150
+ }
151
+
152
+ // --- Shared helpers -----------------------------------------------------------
153
+
154
+ /** Every value source except a bare boolean flag consumes the following token. */
155
+ function takesValue(v: ValueSource): boolean {
156
+ return v.kind !== "flag";
157
+ }
158
+
159
+ /** All token forms (`name` + aliases) under which a subcommand can be invoked. */
160
+ function commandTokens(c: CompletionCommand): string[] {
161
+ return [c.name, ...c.aliases];
162
+ }
163
+
164
+ export function generateCompletion(shell: Shell, spec: CompletionSpec): string {
165
+ switch (shell) {
166
+ case "bash":
167
+ return generateBash(spec);
168
+ case "zsh":
169
+ return generateZsh(spec);
170
+ case "fish":
171
+ return generateFish(spec);
172
+ }
173
+ }
174
+
175
+ // --- bash ---------------------------------------------------------------------
176
+
177
+ /** Escape for use inside a bash double-quoted `compgen -W "…"` word list. */
178
+ function bashWords(values: readonly string[]): string {
179
+ return values.join(" ").replace(/"/g, '\\"');
180
+ }
181
+
182
+ /** bash snippet that fills COMPREPLY for a flag value, then `return 0`. */
183
+ function bashValueBranch(bin: string, v: ValueSource): string {
184
+ switch (v.kind) {
185
+ case "flag":
186
+ case "value":
187
+ return "return 0";
188
+ case "enum":
189
+ return `COMPREPLY=( $(compgen -W "${bashWords(v.values)}" -- "$cur") ); return 0`;
190
+ case "list":
191
+ return `_omp_comma "${bashWords(v.values)}"; return 0`;
192
+ case "models":
193
+ return v.multiple
194
+ ? `_omp_comma "$(command ${bin} __complete models 2>/dev/null | cut -f1)"; return 0`
195
+ : `COMPREPLY=( $(compgen -W "$(command ${bin} __complete models -- "$cur" 2>/dev/null | cut -f1)" -- "$cur") ); return 0`;
196
+ case "sessions":
197
+ return `COMPREPLY=( $(compgen -W "$(command ${bin} __complete sessions -- "$cur" 2>/dev/null | cut -f1)" -- "$cur") ); return 0`;
198
+ case "file":
199
+ return `COMPREPLY=( $(compgen -f -- "$cur") ); compopt -o filenames; return 0`;
200
+ case "dir":
201
+ return `COMPREPLY=( $(compgen -d -- "$cur") ); compopt -o filenames; return 0`;
202
+ }
203
+ }
204
+
205
+ /** Build the `case "$prev" in …` arms for every value-taking flag in scope. */
206
+ function bashFlagCase(bin: string, flags: CompletionFlag[]): string {
207
+ const lines: string[] = [];
208
+ for (const f of flags) {
209
+ if (!takesValue(f.value)) continue;
210
+ const labels = [`--${f.name}`, ...(f.char ? [`-${f.char}`] : [])];
211
+ lines.push(`\t\t${labels.join("|")})\n\t\t\t${bashValueBranch(bin, f.value)}\n\t\t\t;;`);
212
+ }
213
+ return lines.join("\n");
214
+ }
215
+
216
+ function bashFlagWords(flags: CompletionFlag[]): string {
217
+ const words: string[] = [];
218
+ for (const f of flags) {
219
+ words.push(`--${f.name}`);
220
+ if (f.char) words.push(`-${f.char}`);
221
+ }
222
+ return words.join(" ");
223
+ }
224
+
225
+ function generateBash(spec: CompletionSpec): string {
226
+ const { bin } = spec;
227
+ const parts: string[] = [];
228
+ parts.push(`# bash completion for ${bin} — generated by \`${bin} completions bash\``);
229
+ parts.push("");
230
+
231
+ // Comma-aware static/dynamic list completion helper.
232
+ parts.push(`_omp_comma() {
233
+ local words="$1" realcur prefix
234
+ realcur="\${cur##*,}"
235
+ prefix="\${cur%"$realcur"}"
236
+ local -a matches
237
+ matches=( $(compgen -W "$words" -- "$realcur") )
238
+ local i
239
+ for (( i=0; i < \${#matches[@]}; i++ )); do matches[i]="$prefix\${matches[i]}"; done
240
+ COMPREPLY=( "\${matches[@]}" )
241
+ compopt -o nospace 2>/dev/null
242
+ }`);
243
+ parts.push("");
244
+
245
+ // Root handler: top-level flags + subcommand names.
246
+ const subTokens = spec.commands.flatMap(commandTokens).sort();
247
+ parts.push(`_omp_root() {
248
+ case "$prev" in
249
+ ${bashFlagCase(bin, spec.root.flags)}
250
+ esac
251
+ if [[ "$cur" == -* ]]; then
252
+ COMPREPLY=( $(compgen -W "${bashFlagWords(spec.root.flags)}" -- "$cur") )
253
+ else
254
+ COMPREPLY=( $(compgen -W "${bashWords(subTokens)} ${bashFlagWords(spec.root.flags)}" -- "$cur") )
255
+ fi
256
+ }`);
257
+ parts.push("");
258
+
259
+ // Per-subcommand handlers.
260
+ for (const c of spec.commands) {
261
+ const argEnum = c.args.find(a => a.value.kind === "enum");
262
+ const argWords = argEnum && argEnum.value.kind === "enum" ? bashWords(argEnum.value.values) : "";
263
+ const fileArg = c.args.some(a => a.value.kind === "file");
264
+ const elseBranch = argWords
265
+ ? `COMPREPLY=( $(compgen -W "${argWords}" -- "$cur") )`
266
+ : fileArg
267
+ ? `COMPREPLY=( $(compgen -f -- "$cur") ); compopt -o filenames`
268
+ : ":";
269
+ parts.push(`_omp_cmd_${bashFn(c.name)}() {
270
+ case "$prev" in
271
+ ${bashFlagCase(bin, c.flags)}
272
+ esac
273
+ if [[ "$cur" == -* ]]; then
274
+ COMPREPLY=( $(compgen -W "${bashFlagWords(c.flags)}" -- "$cur") )
275
+ else
276
+ ${elseBranch}
277
+ fi
278
+ }`);
279
+ parts.push("");
280
+ }
281
+
282
+ // Dispatcher.
283
+ const dispatch: string[] = [];
284
+ for (const c of spec.commands) {
285
+ dispatch.push(`\t\t${commandTokens(c).join("|")})\n\t\t\t_omp_cmd_${bashFn(c.name)}\n\t\t\t;;`);
286
+ }
287
+ parts.push(`_omp() {
288
+ local cur prev cmd i
289
+ cur="\${COMP_WORDS[COMP_CWORD]}"
290
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
291
+ cmd=""
292
+ for (( i=1; i < COMP_CWORD; i++ )); do
293
+ case "\${COMP_WORDS[i]}" in
294
+ -*) ;;
295
+ *) cmd="\${COMP_WORDS[i]}"; break ;;
296
+ esac
297
+ done
298
+ case "$cmd" in
299
+ ${dispatch.join("\n")}
300
+ *) _omp_root ;;
301
+ esac
302
+ }
303
+ complete -F _omp ${bin}`);
304
+ parts.push("");
305
+ return `${parts.join("\n")}\n`;
306
+ }
307
+
308
+ function bashFn(name: string): string {
309
+ return name.replace(/[^A-Za-z0-9]/g, "_");
310
+ }
311
+
312
+ // --- zsh ----------------------------------------------------------------------
313
+
314
+ /** Sanitize a description for embedding in a single-quoted zsh `_arguments` spec. */
315
+ function zshDesc(s: string): string {
316
+ return s
317
+ .replace(/'/g, "’")
318
+ .replace(/\[/g, "(")
319
+ .replace(/\]/g, ")")
320
+ .replace(/[\r\n]+/g, " ")
321
+ .replace(/:/g, " ")
322
+ .trim();
323
+ }
324
+
325
+ function zshAction(v: ValueSource): string {
326
+ switch (v.kind) {
327
+ case "flag":
328
+ return "";
329
+ case "value":
330
+ return ":value:";
331
+ case "enum":
332
+ return `:value:(${v.values.join(" ")})`;
333
+ case "list":
334
+ return ":value:_omp_tools";
335
+ case "models":
336
+ return v.multiple ? ":models:_omp_models_list" : ":model:_omp_call models";
337
+ case "sessions":
338
+ return ":session:_omp_call sessions";
339
+ case "file":
340
+ return ":file:_files";
341
+ case "dir":
342
+ return ":dir:_files -/";
343
+ }
344
+ }
345
+
346
+ function zshFlagSpec(f: CompletionFlag): string {
347
+ const body = `[${zshDesc(f.description)}]${zshAction(f.value)}`;
348
+ if (f.char && f.repeatable) return `'*'{-${f.char},--${f.name}}'${body}'`;
349
+ if (f.char) return `'(-${f.char} --${f.name})'{-${f.char},--${f.name}}'${body}'`;
350
+ if (f.repeatable) return `'*--${f.name}${body}'`;
351
+ return `'--${f.name}${body}'`;
352
+ }
353
+
354
+ function zshArgSpec(f: CompletionArg): string {
355
+ switch (f.value.kind) {
356
+ case "enum":
357
+ return `':${f.name}:(${f.value.values.join(" ")})'`;
358
+ default:
359
+ return `':${f.name}:_files'`;
360
+ }
361
+ }
362
+
363
+ function generateZsh(spec: CompletionSpec): string {
364
+ const { bin } = spec;
365
+ // The `:value:_omp_tools` action references this helper; bake its candidates
366
+ // from the spec's `list` flag so the generator stays a pure function of its
367
+ // input (bash/fish read `v.values` inline for the same reason).
368
+ const listFlag = [...spec.root.flags, ...spec.commands.flatMap(c => c.flags)].find(f => f.value.kind === "list");
369
+ const toolNames = listFlag?.value.kind === "list" ? listFlag.value.values.join(" ") : "";
370
+ const parts: string[] = [];
371
+ parts.push(`#compdef ${bin}`);
372
+ parts.push(`# zsh completion for ${bin} — generated by \`${bin} completions zsh\``);
373
+ parts.push("");
374
+
375
+ // Dynamic helpers (single source: `<bin> __complete <kind>` → value<TAB>desc).
376
+ parts.push(`_omp_call() {
377
+ local kind=$1
378
+ local -a items
379
+ local line
380
+ for line in "\${(@f)$(command ${bin} __complete $kind -- "$PREFIX" 2>/dev/null)}"; do
381
+ [[ -z $line ]] && continue
382
+ items+=( "\${line//$'\\t'/:}" )
383
+ done
384
+ _describe -t "$kind" "$kind" items
385
+ }
386
+ _omp_models_list() {
387
+ local -a items
388
+ local line
389
+ for line in "\${(@f)$(command ${bin} __complete models 2>/dev/null)}"; do
390
+ [[ -z $line ]] && continue
391
+ items+=( "\${line%%$'\\t'*}" )
392
+ done
393
+ _values -s , 'models' $items
394
+ }
395
+ _omp_tools() { _values -s , 'tools' ${toolNames} }`);
396
+ parts.push("");
397
+
398
+ // Subcommand description table.
399
+ const cmdRows = spec.commands.map(c => `\t\t'${c.name}:${zshDesc(c.description)}'`).join("\n");
400
+ parts.push(`_omp_commands() {
401
+ local -a commands
402
+ commands=(
403
+ ${cmdRows}
404
+ )
405
+ _describe -t commands 'command' commands
406
+ }`);
407
+ parts.push("");
408
+
409
+ // Per-subcommand argument functions.
410
+ for (const c of spec.commands) {
411
+ const specs = ["'(-h --help)'{-h,--help}'[Show help]'", ...c.flags.map(zshFlagSpec), ...c.args.map(zshArgSpec)];
412
+ parts.push(`_omp_cmd_${bashFn(c.name)}() {
413
+ _arguments -s \\
414
+ ${specs.join(" \\\n\t\t")}
415
+ }`);
416
+ parts.push("");
417
+ }
418
+
419
+ // Top-level dispatch.
420
+ const aliasArms = spec.commands
421
+ .map(c => `\t\t\t${commandTokens(c).join("|")}) _omp_cmd_${bashFn(c.name)} ;;`)
422
+ .join("\n");
423
+ const rootSpecs = [
424
+ "'(-h --help)'{-h,--help}'[Show help]'",
425
+ "'(-v --version)'{-v,--version}'[Show version]'",
426
+ ...spec.root.flags.map(zshFlagSpec),
427
+ "'1: :_omp_commands'",
428
+ "'*::arg:->args'",
429
+ ];
430
+ parts.push(`_omp() {
431
+ local curcontext="$curcontext" state line
432
+ typeset -A opt_args
433
+ _arguments -C -s \\
434
+ ${rootSpecs.join(" \\\n\t\t")}
435
+ case $state in
436
+ args)
437
+ case $line[1] in
438
+ ${aliasArms}
439
+ esac
440
+ ;;
441
+ esac
442
+ }
443
+ # Works both ways: autoloaded from $fpath (file named _omp) or eval'd from a
444
+ # startup file. When autoloaded, funcstack[1] is _omp and we invoke it; when
445
+ # sourced/eval'd we register it with compdef instead.
446
+ if [ "$funcstack[1]" = "_omp" ]; then
447
+ _omp "$@"
448
+ else
449
+ compdef _omp ${bin}
450
+ fi`);
451
+ parts.push("");
452
+ return `${parts.join("\n")}\n`;
453
+ }
454
+
455
+ // --- fish ---------------------------------------------------------------------
456
+
457
+ function fishDesc(s: string): string {
458
+ return s
459
+ .replace(/'/g, "’")
460
+ .replace(/[\r\n]+/g, " ")
461
+ .trim();
462
+ }
463
+
464
+ function fishValue(bin: string, v: ValueSource): string {
465
+ switch (v.kind) {
466
+ case "flag":
467
+ return "";
468
+ case "value":
469
+ return "-x";
470
+ case "enum":
471
+ case "list":
472
+ return `-x -a '${v.values.join(" ")}'`;
473
+ case "models":
474
+ return `-x -a '(command ${bin} __complete models -- (commandline -ct))'`;
475
+ case "sessions":
476
+ return `-x -a '(command ${bin} __complete sessions -- (commandline -ct))'`;
477
+ case "file":
478
+ return "-r -F";
479
+ case "dir":
480
+ return "-x -a '(__fish_complete_directories (commandline -ct))'";
481
+ }
482
+ }
483
+
484
+ function fishFlagLine(bin: string, cond: string, f: CompletionFlag): string {
485
+ const segs = [`complete -c ${bin}`, `-n '${cond}'`];
486
+ if (f.char) segs.push(`-s ${f.char}`);
487
+ segs.push(`-l ${f.name}`);
488
+ if (f.description) segs.push(`-d '${fishDesc(f.description)}'`);
489
+ const val = fishValue(bin, f.value);
490
+ if (val) segs.push(val);
491
+ return segs.join(" ");
492
+ }
493
+
494
+ function generateFish(spec: CompletionSpec): string {
495
+ const { bin } = spec;
496
+ const lines: string[] = [];
497
+ lines.push(`# fish completion for ${bin} — generated by \`${bin} completions fish\``);
498
+ lines.push("");
499
+
500
+ const allTokens = spec.commands.flatMap(commandTokens);
501
+ lines.push(`function __fish_omp_no_subcommand`);
502
+ lines.push(`\tfor i in (commandline -opc)`);
503
+ lines.push(`\t\tif contains -- $i ${allTokens.join(" ")}`);
504
+ lines.push(`\t\t\treturn 1`);
505
+ lines.push(`\t\tend`);
506
+ lines.push(`\tend`);
507
+ lines.push(`\treturn 0`);
508
+ lines.push(`end`);
509
+ lines.push("");
510
+
511
+ const rootCond = "__fish_omp_no_subcommand";
512
+
513
+ // Subcommand names.
514
+ for (const c of spec.commands) {
515
+ for (const token of commandTokens(c)) {
516
+ lines.push(`complete -c ${bin} -f -n '${rootCond}' -a '${token}' -d '${fishDesc(c.description)}'`);
517
+ }
518
+ }
519
+ lines.push("");
520
+
521
+ // Top-level flags.
522
+ for (const f of spec.root.flags) {
523
+ lines.push(fishFlagLine(bin, rootCond, f));
524
+ }
525
+ lines.push("");
526
+
527
+ // Per-subcommand flags and positional args.
528
+ for (const c of spec.commands) {
529
+ const cond = `__fish_seen_subcommand_from ${commandTokens(c).join(" ")}`;
530
+ for (const f of c.flags) {
531
+ lines.push(fishFlagLine(bin, cond, f));
532
+ }
533
+ // Positionals: fish conditions can't gate on position, so emit enum
534
+ // candidates (if any) and otherwise a single file completion — never both,
535
+ // and never duplicated across multiple file-typed positionals.
536
+ const enumArgs = c.args.filter(a => a.value.kind === "enum");
537
+ if (enumArgs.length > 0) {
538
+ for (const a of enumArgs) {
539
+ if (a.value.kind !== "enum") continue;
540
+ lines.push(
541
+ `complete -c ${bin} -f -n '${cond}' -a '${a.value.values.join(" ")}' -d '${fishDesc(a.description)}'`,
542
+ );
543
+ }
544
+ } else if (c.args.some(a => a.value.kind === "file")) {
545
+ lines.push(`complete -c ${bin} -F -n '${cond}'`);
546
+ }
547
+ }
548
+ lines.push("");
549
+ return `${lines.join("\n")}\n`;
550
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Setup CLI command handler.
3
3
  *
4
- * Handles `omp setup <component>` to install dependencies for optional features.
4
+ * Handles `omp setup` for onboarding and `omp setup <component>` for optional dependencies.
5
5
  */
6
6
  import * as path from "node:path";
7
7
  import { $which, APP_NAME, getPythonEnvDir } from "@oh-my-pi/pi-utils";
@@ -207,9 +207,10 @@ async function handleSttSetup(flags: { json?: boolean; check?: boolean }): Promi
207
207
  * Print setup command help.
208
208
  */
209
209
  export function printSetupHelp(): void {
210
- console.log(`${chalk.bold(`${APP_NAME} setup`)} - Install dependencies for optional features
210
+ console.log(`${chalk.bold(`${APP_NAME} setup`)} - Run onboarding or install dependencies for optional features
211
211
 
212
212
  ${chalk.bold("Usage:")}
213
+ ${APP_NAME} setup Run the onboarding wizard
213
214
  ${APP_NAME} setup <component> [options]
214
215
 
215
216
  ${chalk.bold("Components:")}
@@ -221,7 +222,8 @@ ${chalk.bold("Options:")}
221
222
  --json Output status as JSON
222
223
 
223
224
  ${chalk.bold("Examples:")}
224
- ${APP_NAME} setup python Install Python execution dependencies
225
+ ${APP_NAME} setup Run the onboarding wizard
226
+ ${APP_NAME} setup python Check Python execution dependencies
225
227
  ${APP_NAME} setup stt Install speech-to-text dependencies
226
228
  ${APP_NAME} setup stt --check Check if STT dependencies are available
227
229
  ${APP_NAME} setup python --check Check if Python execution is available
@@ -0,0 +1,127 @@
1
+ import { formatBytes } from "@oh-my-pi/pi-utils";
2
+ import chalk from "chalk";
3
+ import {
4
+ DEFAULT_TINY_TITLE_LOCAL_MODEL_KEY,
5
+ getTinyLocalModelSpec,
6
+ isTinyLocalModelKey,
7
+ TINY_LOCAL_MODELS,
8
+ type TinyLocalModelKey,
9
+ } from "../tiny/models";
10
+ import { shutdownTinyTitleClient, tinyTitleClient } from "../tiny/title-client";
11
+ import type { TinyTitleProgressEvent } from "../tiny/title-protocol";
12
+
13
+ export type TinyModelsAction = "download" | "list";
14
+
15
+ export interface TinyModelsCommandArgs {
16
+ action: TinyModelsAction;
17
+ model?: string;
18
+ flags: {
19
+ json?: boolean;
20
+ };
21
+ }
22
+
23
+ interface ProgressReporter {
24
+ onProgress(event: TinyTitleProgressEvent): void;
25
+ finish(ok: boolean): void;
26
+ }
27
+
28
+ interface DownloadResult {
29
+ model: TinyLocalModelKey;
30
+ ok: boolean;
31
+ }
32
+
33
+ function writeLine(text = ""): void {
34
+ process.stdout.write(`${text}\n`);
35
+ }
36
+
37
+ function resolveModels(model: string | undefined): TinyLocalModelKey[] {
38
+ if (!model) return [DEFAULT_TINY_TITLE_LOCAL_MODEL_KEY];
39
+ if (model === "all") return TINY_LOCAL_MODELS.map(spec => spec.key);
40
+ if (!isTinyLocalModelKey(model)) {
41
+ const values = TINY_LOCAL_MODELS.map(spec => spec.key).join(", ");
42
+ throw new Error(`Unknown tiny local model: ${model}. Expected one of: ${values}, all`);
43
+ }
44
+ return [model];
45
+ }
46
+
47
+ function listModels(json: boolean | undefined): void {
48
+ if (json) {
49
+ writeLine(JSON.stringify({ models: TINY_LOCAL_MODELS }));
50
+ return;
51
+ }
52
+ writeLine(chalk.bold("Tiny local models"));
53
+ for (const spec of TINY_LOCAL_MODELS) {
54
+ const defaultMark = spec.key === DEFAULT_TINY_TITLE_LOCAL_MODEL_KEY ? chalk.cyan(" default") : "";
55
+ writeLine(`${chalk.cyan(spec.key)}${defaultMark}`);
56
+ writeLine(` ${spec.label} — ${spec.description}`);
57
+ }
58
+ }
59
+
60
+ function makeProgressReporter(modelKey: TinyLocalModelKey, json: boolean | undefined): ProgressReporter {
61
+ if (json || !process.stdout.isTTY) {
62
+ return { onProgress: () => undefined, finish: () => undefined };
63
+ }
64
+ const label = getTinyLocalModelSpec(modelKey)?.label ?? modelKey;
65
+ let lastWidth = 0;
66
+ let lastProgress = -1;
67
+ const render = (event: TinyTitleProgressEvent): void => {
68
+ const progress = event.progress ?? lastProgress;
69
+ if (progress >= 0 && progress < lastProgress + 1 && event.status !== "ready") return;
70
+ if (progress >= 0) lastProgress = progress;
71
+ const ratio = progress >= 0 ? Math.max(0, Math.min(1, progress / 100)) : 0;
72
+ const barWidth = 30;
73
+ const filled = Math.round(ratio * barWidth);
74
+ const bar = `${"█".repeat(filled)}${"░".repeat(barWidth - filled)}`;
75
+ const pct = progress >= 0 ? `${Math.floor(progress).toString().padStart(3, " ")}%` : " --%";
76
+ const bytes = event.loaded && event.total ? ` ${formatBytes(event.loaded)}/${formatBytes(event.total)}` : "";
77
+ const file = event.file ? ` ${event.file.split("/").at(-1) ?? event.file}` : "";
78
+ const statusLabel = event.status === "ready" ? "Ready" : "Downloading";
79
+ const line = `${chalk.cyan(statusLabel)} ${label} [${bar}] ${pct}${bytes}${file}`;
80
+ process.stdout.write(`\r${line.padEnd(lastWidth)}`);
81
+ lastWidth = line.length;
82
+ };
83
+ return {
84
+ onProgress(event) {
85
+ if (event.modelKey !== modelKey) return;
86
+ render(event);
87
+ },
88
+ finish(ok) {
89
+ const suffix = ok ? chalk.green("done") : chalk.red("failed");
90
+ process.stdout.write(`\r${`${label}: ${suffix}`.padEnd(lastWidth)}\n`);
91
+ },
92
+ };
93
+ }
94
+
95
+ async function downloadOne(modelKey: TinyLocalModelKey, json: boolean | undefined): Promise<DownloadResult> {
96
+ const label = getTinyLocalModelSpec(modelKey)?.label ?? modelKey;
97
+ if (!json && !process.stdout.isTTY) writeLine(`Downloading ${label} (${modelKey})...`);
98
+ const progress = makeProgressReporter(modelKey, json);
99
+ const ok = await tinyTitleClient.downloadModel(modelKey, { onProgress: progress.onProgress });
100
+ progress.finish(ok);
101
+ if (!json && !process.stdout.isTTY) writeLine(ok ? `Downloaded ${label}.` : `Failed to download ${label}.`);
102
+ return { model: modelKey, ok };
103
+ }
104
+
105
+ export async function runTinyModelsCommand(command: TinyModelsCommandArgs): Promise<void> {
106
+ if (command.action === "list") {
107
+ listModels(command.flags.json);
108
+ return;
109
+ }
110
+
111
+ const models = resolveModels(command.model);
112
+ const results: DownloadResult[] = [];
113
+ try {
114
+ for (const model of models) {
115
+ results.push(await downloadOne(model, command.flags.json));
116
+ }
117
+ } finally {
118
+ await shutdownTinyTitleClient();
119
+ }
120
+
121
+ if (command.flags.json) {
122
+ writeLine(JSON.stringify({ results }));
123
+ }
124
+ if (results.some(result => !result.ok)) {
125
+ throw new Error("One or more tiny title models failed to download");
126
+ }
127
+ }