agent-trellis 0.7.0 → 0.8.2

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 (302) hide show
  1. package/README.md +48 -153
  2. package/dist/adapters/claude-code.js +3 -2
  3. package/dist/adapters/codex.js +3 -2
  4. package/dist/adapters/jsonMcp.d.ts +3 -1
  5. package/dist/adapters/jsonMcp.js +18 -4
  6. package/dist/adapters/kimi-code.d.ts +21 -0
  7. package/dist/adapters/kimi-code.js +107 -0
  8. package/dist/adapters/kiro.js +3 -2
  9. package/dist/adapters/mcpPlan.d.ts +6 -1
  10. package/dist/adapters/mcpPlan.js +106 -8
  11. package/dist/adapters/pi.js +22 -3
  12. package/dist/adapters/symlinkPlan.d.ts +7 -0
  13. package/dist/adapters/symlinkPlan.js +16 -7
  14. package/dist/adapters/zcode.d.ts +21 -0
  15. package/dist/adapters/zcode.js +234 -0
  16. package/dist/cli.js +145 -29
  17. package/dist/commands/doctor.d.ts +7 -2
  18. package/dist/commands/doctor.js +40 -2
  19. package/dist/commands/init.d.ts +4 -2
  20. package/dist/commands/init.js +29 -13
  21. package/dist/commands/kimi.d.ts +16 -0
  22. package/dist/commands/kimi.js +64 -0
  23. package/dist/commands/manage.d.ts +39 -0
  24. package/dist/commands/manage.js +126 -0
  25. package/dist/commands/mcp.d.ts +77 -14
  26. package/dist/commands/mcp.js +169 -29
  27. package/dist/commands/mcpGateway.d.ts +6 -0
  28. package/dist/commands/mcpGateway.js +35 -14
  29. package/dist/commands/mcpImport.d.ts +46 -0
  30. package/dist/commands/mcpImport.js +359 -0
  31. package/dist/commands/memory.d.ts +17 -3
  32. package/dist/commands/memory.js +27 -8
  33. package/dist/commands/migrate.d.ts +16 -5
  34. package/dist/commands/migrate.js +93 -17
  35. package/dist/commands/onboard.d.ts +74 -2
  36. package/dist/commands/onboard.js +469 -71
  37. package/dist/commands/rollback.js +28 -2
  38. package/dist/commands/secretsAudit.js +4 -1
  39. package/dist/commands/skill.d.ts +52 -6
  40. package/dist/commands/skill.js +114 -15
  41. package/dist/commands/sync.js +5 -1
  42. package/dist/core/adapter.d.ts +11 -1
  43. package/dist/core/canonical.d.ts +15 -7
  44. package/dist/core/canonical.js +101 -13
  45. package/dist/core/capabilitySelection.d.ts +45 -0
  46. package/dist/core/capabilitySelection.js +124 -0
  47. package/dist/core/types.d.ts +25 -2
  48. package/dist/core/types.js +8 -0
  49. package/dist/lib/agentBridge.d.ts +169 -0
  50. package/dist/lib/agentBridge.js +367 -0
  51. package/dist/lib/agentBridgeProvider.d.ts +14 -0
  52. package/dist/lib/agentBridgeProvider.js +95 -0
  53. package/dist/lib/backup.d.ts +32 -0
  54. package/dist/lib/backup.js +20 -1
  55. package/dist/lib/builtinSkills.d.ts +2 -0
  56. package/dist/lib/builtinSkills.js +4 -0
  57. package/dist/lib/chatAgents.d.ts +42 -0
  58. package/dist/lib/chatAgents.js +59 -0
  59. package/dist/lib/cliMetadata.d.ts +1 -0
  60. package/dist/lib/cliMetadata.js +14 -0
  61. package/dist/lib/envVarNames.d.ts +3 -0
  62. package/dist/lib/envVarNames.js +19 -0
  63. package/dist/lib/gatewayBackend.d.ts +28 -0
  64. package/dist/lib/gatewayBackend.js +72 -1
  65. package/dist/lib/installAgent.d.ts +7 -5
  66. package/dist/lib/installAgent.js +4 -3
  67. package/dist/lib/mcpConnect.js +5 -1
  68. package/dist/lib/mcpMigrateRead.d.ts +2 -0
  69. package/dist/lib/mcpMigrateRead.js +22 -1
  70. package/dist/lib/mcpRuntime.d.ts +46 -0
  71. package/dist/lib/mcpRuntime.js +185 -0
  72. package/dist/lib/mcpStatusProvider.d.ts +10 -0
  73. package/dist/lib/mcpStatusProvider.js +25 -0
  74. package/dist/lib/mcpToolRegistry.d.ts +18 -0
  75. package/dist/lib/mcpToolRegistry.js +107 -14
  76. package/dist/lib/memoryGraph.d.ts +1 -1
  77. package/dist/lib/memoryGraph.js +9 -3
  78. package/dist/lib/memoryProvider.d.ts +58 -0
  79. package/dist/lib/memoryProvider.js +175 -0
  80. package/dist/lib/memoryStatus.d.ts +17 -0
  81. package/dist/lib/memoryStatus.js +88 -0
  82. package/dist/lib/nativeMemory.d.ts +19 -0
  83. package/dist/lib/nativeMemory.js +129 -0
  84. package/dist/lib/realHomeSnapshot.js +5 -0
  85. package/dist/lib/runtimeControlProvider.d.ts +16 -0
  86. package/dist/lib/runtimeControlProvider.js +170 -0
  87. package/dist/lib/secretEnv.d.ts +2 -1
  88. package/dist/lib/secretEnv.js +6 -2
  89. package/dist/lib/skillProvider.d.ts +13 -0
  90. package/dist/lib/skillProvider.js +210 -0
  91. package/dist/lib/taskProvider.d.ts +7 -0
  92. package/dist/lib/taskProvider.js +43 -0
  93. package/dist/lib/taskStore.d.ts +46 -0
  94. package/dist/lib/taskStore.js +156 -0
  95. package/dist/lib/terminalPicker.d.ts +23 -49
  96. package/dist/lib/terminalPicker.js +80 -218
  97. package/dist/lib/zcodeOwnership.d.ts +17 -0
  98. package/dist/lib/zcodeOwnership.js +37 -0
  99. package/dist/pi-bridge/bundle.js +476 -37
  100. package/dist/pi-bridge/index.js +42 -21
  101. package/dist/pi-bridge/schemaTranslate.d.ts +4 -8
  102. package/dist/pi-bridge/schemaTranslate.js +4 -8
  103. package/dist/probes/codex.d.ts +7 -0
  104. package/dist/probes/codex.js +37 -5
  105. package/dist/probes/kimi-code.d.ts +12 -0
  106. package/dist/probes/kimi-code.js +55 -0
  107. package/dist/probes/zcode.d.ts +32 -0
  108. package/dist/probes/zcode.js +140 -0
  109. package/dist/sdk.d.ts +1 -1
  110. package/docs/architecture.md +230 -28
  111. package/docs/assets/brand-v3/trellis-mark-v3-a.png +0 -0
  112. package/docs/assets/brand-v3/trellis-mark-v3-a.svg +10 -0
  113. package/docs/assets/poster-series-v3/README.md +60 -0
  114. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.png +0 -0
  115. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.svg +51 -0
  116. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.png +0 -0
  117. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.svg +51 -0
  118. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.png +0 -0
  119. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.svg +48 -0
  120. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.png +0 -0
  121. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.svg +48 -0
  122. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.png +0 -0
  123. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.svg +56 -0
  124. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.png +0 -0
  125. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.svg +56 -0
  126. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.png +0 -0
  127. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.svg +47 -0
  128. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.png +0 -0
  129. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.svg +47 -0
  130. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.png +0 -0
  131. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.svg +51 -0
  132. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.png +0 -0
  133. package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.svg +51 -0
  134. package/docs/getting-started.md +496 -42
  135. package/docs/overview.md +134 -0
  136. package/docs/overview.zh-CN.md +102 -0
  137. package/docs/research.md +59 -0
  138. package/docs/roadmap.md +11 -6
  139. package/docs/video/README.md +76 -0
  140. package/docs/video/build/audio/01-opening.aiff +0 -0
  141. package/docs/video/build/audio/01-opening.mp3 +0 -0
  142. package/docs/video/build/audio/02-pain.aiff +0 -0
  143. package/docs/video/build/audio/02-pain.mp3 +0 -0
  144. package/docs/video/build/audio/03-solution.aiff +0 -0
  145. package/docs/video/build/audio/03-solution.mp3 +0 -0
  146. package/docs/video/build/audio/04-install.aiff +0 -0
  147. package/docs/video/build/audio/04-install.mp3 +0 -0
  148. package/docs/video/build/audio/04-setup.mp3 +0 -0
  149. package/docs/video/build/audio/05-dryrun.mp3 +0 -0
  150. package/docs/video/build/audio/05-init.aiff +0 -0
  151. package/docs/video/build/audio/05-init.mp3 +0 -0
  152. package/docs/video/build/audio/05-interactive.mp3 +0 -0
  153. package/docs/video/build/audio/06-dryrun.aiff +0 -0
  154. package/docs/video/build/audio/06-dryrun.mp3 +0 -0
  155. package/docs/video/build/audio/06-onboard.mp3 +0 -0
  156. package/docs/video/build/audio/06-run.mp3 +0 -0
  157. package/docs/video/build/audio/07-onboard.aiff +0 -0
  158. package/docs/video/build/audio/07-onboard.mp3 +0 -0
  159. package/docs/video/build/audio/07-verify.mp3 +0 -0
  160. package/docs/video/build/audio/08-closing.mp3 +0 -0
  161. package/docs/video/build/audio/08-doctor.aiff +0 -0
  162. package/docs/video/build/audio/08-doctor.mp3 +0 -0
  163. package/docs/video/build/audio/09-rollback.aiff +0 -0
  164. package/docs/video/build/audio/09-rollback.mp3 +0 -0
  165. package/docs/video/build/audio/10-principles.aiff +0 -0
  166. package/docs/video/build/audio/10-principles.mp3 +0 -0
  167. package/docs/video/build/audio/11-closing.aiff +0 -0
  168. package/docs/video/build/audio/11-closing.mp3 +0 -0
  169. package/docs/video/build/frames/01-opening-0.png +0 -0
  170. package/docs/video/build/frames/02-pain-1.png +0 -0
  171. package/docs/video/build/frames/02-pain-2.png +0 -0
  172. package/docs/video/build/frames/03-solution-3.png +0 -0
  173. package/docs/video/build/frames/04-install-4.png +0 -0
  174. package/docs/video/build/frames/04-setup-4.png +0 -0
  175. package/docs/video/build/frames/04-setup-5.png +0 -0
  176. package/docs/video/build/frames/05-dryrun-6.png +0 -0
  177. package/docs/video/build/frames/05-init-5.png +0 -0
  178. package/docs/video/build/frames/05-interactive-5.png +0 -0
  179. package/docs/video/build/frames/05-interactive-6.png +0 -0
  180. package/docs/video/build/frames/06-dryrun-6.png +0 -0
  181. package/docs/video/build/frames/06-dryrun-7.png +0 -0
  182. package/docs/video/build/frames/06-onboard-7.png +0 -0
  183. package/docs/video/build/frames/06-onboard-8.png +0 -0
  184. package/docs/video/build/frames/06-run-7.png +0 -0
  185. package/docs/video/build/frames/06-run-8.png +0 -0
  186. package/docs/video/build/frames/07-onboard-10.png +0 -0
  187. package/docs/video/build/frames/07-onboard-7.png +0 -0
  188. package/docs/video/build/frames/07-onboard-8.png +0 -0
  189. package/docs/video/build/frames/07-onboard-9.png +0 -0
  190. package/docs/video/build/frames/07-verify-10.png +0 -0
  191. package/docs/video/build/frames/07-verify-8.png +0 -0
  192. package/docs/video/build/frames/07-verify-9.png +0 -0
  193. package/docs/video/build/frames/08-closing-10.png +0 -0
  194. package/docs/video/build/frames/08-closing-11.png +0 -0
  195. package/docs/video/build/frames/08-doctor-11.png +0 -0
  196. package/docs/video/build/frames/08-doctor-9.png +0 -0
  197. package/docs/video/build/frames/09-rollback-10.png +0 -0
  198. package/docs/video/build/frames/09-rollback-12.png +0 -0
  199. package/docs/video/build/frames/10-principles-11.png +0 -0
  200. package/docs/video/build/frames/10-principles-13.png +0 -0
  201. package/docs/video/build/frames/11-closing-12.png +0 -0
  202. package/docs/video/build/frames/11-closing-14.png +0 -0
  203. package/docs/video/build/segments/01-opening.mp4 +0 -0
  204. package/docs/video/build/segments/01-opening.txt +3 -0
  205. package/docs/video/build/segments/02-pain.mp4 +0 -0
  206. package/docs/video/build/segments/02-pain.txt +5 -0
  207. package/docs/video/build/segments/03-solution.mp4 +0 -0
  208. package/docs/video/build/segments/03-solution.txt +3 -0
  209. package/docs/video/build/segments/04-install.mp4 +0 -0
  210. package/docs/video/build/segments/04-install.txt +3 -0
  211. package/docs/video/build/segments/04-setup.mp4 +0 -0
  212. package/docs/video/build/segments/04-setup.txt +3 -0
  213. package/docs/video/build/segments/05-dryrun.mp4 +0 -0
  214. package/docs/video/build/segments/05-dryrun.txt +3 -0
  215. package/docs/video/build/segments/05-init.mp4 +0 -0
  216. package/docs/video/build/segments/05-init.txt +3 -0
  217. package/docs/video/build/segments/05-interactive.mp4 +0 -0
  218. package/docs/video/build/segments/05-interactive.txt +5 -0
  219. package/docs/video/build/segments/06-dryrun.mp4 +0 -0
  220. package/docs/video/build/segments/06-dryrun.txt +3 -0
  221. package/docs/video/build/segments/06-onboard.mp4 +0 -0
  222. package/docs/video/build/segments/06-onboard.txt +5 -0
  223. package/docs/video/build/segments/06-run.mp4 +0 -0
  224. package/docs/video/build/segments/06-run.txt +3 -0
  225. package/docs/video/build/segments/07-onboard.mp4 +0 -0
  226. package/docs/video/build/segments/07-onboard.txt +5 -0
  227. package/docs/video/build/segments/07-verify.mp4 +0 -0
  228. package/docs/video/build/segments/07-verify.txt +5 -0
  229. package/docs/video/build/segments/08-closing.mp4 +0 -0
  230. package/docs/video/build/segments/08-closing.txt +3 -0
  231. package/docs/video/build/segments/08-doctor.mp4 +0 -0
  232. package/docs/video/build/segments/08-doctor.txt +3 -0
  233. package/docs/video/build/segments/09-rollback.mp4 +0 -0
  234. package/docs/video/build/segments/09-rollback.txt +3 -0
  235. package/docs/video/build/segments/10-principles.mp4 +0 -0
  236. package/docs/video/build/segments/10-principles.txt +3 -0
  237. package/docs/video/build/segments/11-closing.mp4 +0 -0
  238. package/docs/video/build/segments/11-closing.txt +3 -0
  239. package/docs/video/build/segments/final.txt +8 -0
  240. package/docs/video/build-ai/audio/01-hook.mp3 +0 -0
  241. package/docs/video/build-ai/audio/02-pain.mp3 +0 -0
  242. package/docs/video/build-ai/audio/03-solution.mp3 +0 -0
  243. package/docs/video/build-ai/audio/04-how.mp3 +0 -0
  244. package/docs/video/build-ai/audio/05-safe.mp3 +0 -0
  245. package/docs/video/build-ai/audio/06-closing.mp3 +0 -0
  246. package/docs/video/build-ai/clips/01-hook.mp4 +0 -0
  247. package/docs/video/build-ai/clips/02-pain.mp4 +0 -0
  248. package/docs/video/build-ai/clips/03-solution.mp4 +0 -0
  249. package/docs/video/build-ai/clips/04-how.mp4 +0 -0
  250. package/docs/video/build-ai/clips/05-safe.mp4 +0 -0
  251. package/docs/video/build-ai/clips/06-closing.mp4 +0 -0
  252. package/docs/video/build-ai/first-frame-02-pain.png +0 -0
  253. package/docs/video/build-ai/first-frame-03-solution.png +0 -0
  254. package/docs/video/build-ai/first-frame-04-how.png +0 -0
  255. package/docs/video/build-ai/first-frame-05-safe.png +0 -0
  256. package/docs/video/build-ai/segments/01-hook.caption.txt +2 -0
  257. package/docs/video/build-ai/segments/01-hook.mp4 +0 -0
  258. package/docs/video/build-ai/segments/02-pain.mp4 +0 -0
  259. package/docs/video/build-ai/segments/03-solution.mp4 +0 -0
  260. package/docs/video/build-ai/segments/04-how.mp4 +0 -0
  261. package/docs/video/build-ai/segments/05-safe.mp4 +0 -0
  262. package/docs/video/build-ai/segments/06-closing.mp4 +0 -0
  263. package/docs/video/build-ai/segments/cap-01-hook.png +0 -0
  264. package/docs/video/build-ai/segments/cap-02-pain.png +0 -0
  265. package/docs/video/build-ai/segments/cap-03-solution.png +0 -0
  266. package/docs/video/build-ai/segments/cap-04-how.png +0 -0
  267. package/docs/video/build-ai/segments/cap-05-safe.png +0 -0
  268. package/docs/video/build-ai/segments/cap-06-closing.png +0 -0
  269. package/docs/video/build-ai/segments/final.txt +6 -0
  270. package/docs/video/build-posters/audio/01-hook.mp3 +0 -0
  271. package/docs/video/build-posters/audio/02-pain.mp3 +0 -0
  272. package/docs/video/build-posters/audio/03-solution.mp3 +0 -0
  273. package/docs/video/build-posters/audio/04-how.mp3 +0 -0
  274. package/docs/video/build-posters/audio/05-safe.mp3 +0 -0
  275. package/docs/video/build-posters/audio/06-closing.mp3 +0 -0
  276. package/docs/video/build-posters/cap-01-hook.png +0 -0
  277. package/docs/video/build-posters/cap-02-pain.png +0 -0
  278. package/docs/video/build-posters/cap-03-solution.png +0 -0
  279. package/docs/video/build-posters/cap-04-how.png +0 -0
  280. package/docs/video/build-posters/cap-05-safe.png +0 -0
  281. package/docs/video/build-posters/cap-06-closing.png +0 -0
  282. package/docs/video/build-posters/card-01-hook.png +0 -0
  283. package/docs/video/build-posters/card-06-closing.png +0 -0
  284. package/docs/video/build-posters/segments/01-hook.mp4 +0 -0
  285. package/docs/video/build-posters/segments/02-pain.mp4 +0 -0
  286. package/docs/video/build-posters/segments/03-solution.mp4 +0 -0
  287. package/docs/video/build-posters/segments/04-how.mp4 +0 -0
  288. package/docs/video/build-posters/segments/05-safe.mp4 +0 -0
  289. package/docs/video/build-posters/segments/06-closing.mp4 +0 -0
  290. package/docs/video/build-posters/segments/final.txt +6 -0
  291. package/docs/video/generate-video-ai.mjs +334 -0
  292. package/docs/video/generate-video-posters.mjs +204 -0
  293. package/docs/video/generate-video.mjs +426 -0
  294. package/docs/video/outline.zh-CN.md +102 -0
  295. package/docs/video/trellis-demo-video-ai.mp4 +0 -0
  296. package/docs/video/trellis-demo-video.mp4 +0 -0
  297. package/docs/video/voiceover.zh-CN.md +49 -0
  298. package/package.json +6 -2
  299. package/schema/builtin-skills/trellis-runtime/SKILL.md +117 -0
  300. package/schema/capability-selection.example.yaml +23 -0
  301. package/schema/scope.example.yaml +1 -1
  302. package/schema/servers.example.yaml +15 -1
@@ -0,0 +1,46 @@
1
+ import type { AgentId } from "../core/types.js";
2
+ export type TaskStatus = "pending" | "claimed" | "in_progress" | "blocked" | "completed";
3
+ export interface TaskContext {
4
+ workspace?: string;
5
+ commit?: string;
6
+ references?: string[];
7
+ note?: string;
8
+ }
9
+ export interface TaskHistoryEntry {
10
+ at: string;
11
+ agent: AgentId;
12
+ action: string;
13
+ status: TaskStatus;
14
+ note?: string;
15
+ }
16
+ export interface TaskRecord {
17
+ id: string;
18
+ objective: string;
19
+ fromAgent: AgentId;
20
+ toAgent?: AgentId;
21
+ status: TaskStatus;
22
+ context?: TaskContext;
23
+ constraints?: string[];
24
+ result?: string;
25
+ claimedBy?: AgentId;
26
+ leaseUntil?: string;
27
+ createdAt: string;
28
+ updatedAt: string;
29
+ history: TaskHistoryEntry[];
30
+ }
31
+ export declare function taskStorePath(homeDir: string): string;
32
+ export declare function listTasks(homeDir: string, filter?: {
33
+ status?: TaskStatus;
34
+ toAgent?: AgentId;
35
+ }): TaskRecord[];
36
+ export declare function readTask(homeDir: string, id: string): TaskRecord;
37
+ export declare function createTask(homeDir: string, input: {
38
+ objective: string;
39
+ fromAgent: AgentId;
40
+ toAgent?: AgentId;
41
+ context?: TaskContext;
42
+ constraints?: string[];
43
+ }): TaskRecord;
44
+ export declare function claimTask(homeDir: string, id: string, agent: AgentId, leaseMs?: number): TaskRecord;
45
+ export declare function updateTask(homeDir: string, id: string, agent: AgentId, status: TaskStatus, note?: string, result?: string): TaskRecord;
46
+ export declare function handoffTask(homeDir: string, id: string, agent: AgentId, toAgent: AgentId, note?: string): TaskRecord;
@@ -0,0 +1,156 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ const SECRET_LIKE = /(sk-|ghp_|glpat-|mcpr_|token\s*=|password\s*=|secret\s*=|authorization\s*:)/i;
5
+ export function taskStorePath(homeDir) {
6
+ return join(homeDir, ".trellis", "tasks", "tasks.json");
7
+ }
8
+ function readFile(path) {
9
+ if (!existsSync(path))
10
+ return { tasks: [] };
11
+ try {
12
+ const value = JSON.parse(readFileSync(path, "utf8"));
13
+ return { tasks: Array.isArray(value.tasks) ? value.tasks : [] };
14
+ }
15
+ catch {
16
+ throw new Error(`could not parse task store ${path}`);
17
+ }
18
+ }
19
+ function writeFile(path, value) {
20
+ mkdirSync(dirname(path), { recursive: true });
21
+ const temporary = `${path}.${process.pid}.tmp`;
22
+ writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
23
+ renameSync(temporary, path);
24
+ }
25
+ function assertSafeText(label, value) {
26
+ if (!value.trim())
27
+ throw new Error(`${label} must not be empty`);
28
+ if (SECRET_LIKE.test(value))
29
+ throw new Error(`${label} looks like it contains a credential or secret-like value`);
30
+ }
31
+ function assertSafeContext(context) {
32
+ if (!context)
33
+ return;
34
+ for (const [key, value] of Object.entries(context)) {
35
+ if (typeof value === "string")
36
+ assertSafeText(`context.${key}`, value);
37
+ if (Array.isArray(value))
38
+ value.forEach((item) => assertSafeText(`context.${key}`, item));
39
+ }
40
+ }
41
+ function now() {
42
+ return new Date().toISOString();
43
+ }
44
+ function assertAgent(value, label) {
45
+ if (value !== "claude-code" && value !== "codex" && value !== "kiro" && value !== "pi" && value !== "kimi-code" && value !== "zcode") {
46
+ throw new Error(`${label} must be a supported Agent id`);
47
+ }
48
+ }
49
+ function taskOrThrow(file, id) {
50
+ const task = file.tasks.find((candidate) => candidate.id === id);
51
+ if (!task)
52
+ throw new Error(`task "${id}" was not found`);
53
+ return task;
54
+ }
55
+ function addHistory(task, agent, action, status, note) {
56
+ task.status = status;
57
+ task.updatedAt = now();
58
+ task.history.push({ at: task.updatedAt, agent, action, status, ...(note ? { note } : {}) });
59
+ }
60
+ export function listTasks(homeDir, filter) {
61
+ const tasks = readFile(taskStorePath(homeDir)).tasks;
62
+ return tasks
63
+ .filter((task) => !filter?.status || task.status === filter.status)
64
+ .filter((task) => !filter?.toAgent || task.toAgent === filter.toAgent)
65
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
66
+ }
67
+ export function readTask(homeDir, id) {
68
+ return taskOrThrow(readFile(taskStorePath(homeDir)), id);
69
+ }
70
+ export function createTask(homeDir, input) {
71
+ assertSafeText("objective", input.objective);
72
+ assertAgent(input.fromAgent, "fromAgent");
73
+ if (input.toAgent)
74
+ assertAgent(input.toAgent, "toAgent");
75
+ assertSafeContext(input.context);
76
+ input.constraints?.forEach((value) => assertSafeText("constraint", value));
77
+ const timestamp = now();
78
+ const task = {
79
+ id: `task-${randomUUID()}`,
80
+ objective: input.objective,
81
+ fromAgent: input.fromAgent,
82
+ ...(input.toAgent ? { toAgent: input.toAgent } : {}),
83
+ status: "pending",
84
+ ...(input.context ? { context: input.context } : {}),
85
+ ...(input.constraints?.length ? { constraints: input.constraints } : {}),
86
+ createdAt: timestamp,
87
+ updatedAt: timestamp,
88
+ history: [{ at: timestamp, agent: input.fromAgent, action: "created", status: "pending" }],
89
+ };
90
+ const file = readFile(taskStorePath(homeDir));
91
+ file.tasks.push(task);
92
+ writeFile(taskStorePath(homeDir), file);
93
+ return task;
94
+ }
95
+ export function claimTask(homeDir, id, agent, leaseMs = 30 * 60 * 1000) {
96
+ assertAgent(agent, "agent");
97
+ if (!Number.isFinite(leaseMs) || leaseMs < 1_000 || leaseMs > 24 * 60 * 60 * 1000)
98
+ throw new Error("leaseMs must be between 1000 and 86400000");
99
+ const path = taskStorePath(homeDir);
100
+ const file = readFile(path);
101
+ const task = taskOrThrow(file, id);
102
+ const timestamp = Date.now();
103
+ if (task.leaseUntil && new Date(task.leaseUntil).getTime() <= timestamp) {
104
+ task.claimedBy = undefined;
105
+ task.leaseUntil = undefined;
106
+ if (task.status === "claimed" || task.status === "in_progress")
107
+ task.status = "pending";
108
+ }
109
+ if (task.status !== "pending" || task.claimedBy)
110
+ throw new Error(`task "${id}" is already claimed or is not pending`);
111
+ task.claimedBy = agent;
112
+ task.leaseUntil = new Date(timestamp + leaseMs).toISOString();
113
+ addHistory(task, agent, "claimed", "claimed");
114
+ writeFile(path, file);
115
+ return task;
116
+ }
117
+ export function updateTask(homeDir, id, agent, status, note, result) {
118
+ assertAgent(agent, "agent");
119
+ if (!["pending", "claimed", "in_progress", "blocked", "completed"].includes(status))
120
+ throw new Error(`invalid task status "${status}"`);
121
+ if (note)
122
+ assertSafeText("note", note);
123
+ if (result)
124
+ assertSafeText("result", result);
125
+ const path = taskStorePath(homeDir);
126
+ const file = readFile(path);
127
+ const task = taskOrThrow(file, id);
128
+ if (task.claimedBy && task.claimedBy !== agent)
129
+ throw new Error(`task "${id}" is claimed by another Agent`);
130
+ if (status === "completed" && result)
131
+ task.result = result;
132
+ addHistory(task, agent, status === "completed" ? "completed" : "updated", status, note);
133
+ if (status === "completed") {
134
+ task.claimedBy = undefined;
135
+ task.leaseUntil = undefined;
136
+ }
137
+ writeFile(path, file);
138
+ return task;
139
+ }
140
+ export function handoffTask(homeDir, id, agent, toAgent, note) {
141
+ assertAgent(agent, "agent");
142
+ assertAgent(toAgent, "toAgent");
143
+ if (note)
144
+ assertSafeText("note", note);
145
+ const path = taskStorePath(homeDir);
146
+ const file = readFile(path);
147
+ const task = taskOrThrow(file, id);
148
+ if (task.claimedBy && task.claimedBy !== agent)
149
+ throw new Error(`task "${id}" is claimed by another Agent`);
150
+ task.toAgent = toAgent;
151
+ task.claimedBy = undefined;
152
+ task.leaseUntil = undefined;
153
+ addHistory(task, agent, "handed-off", "pending", note);
154
+ writeFile(path, file);
155
+ return task;
156
+ }
@@ -1,66 +1,40 @@
1
1
  /**
2
- * Minimal, dependency-free arrow-key/checkbox terminal picker
3
- * (trellis-onboard-interactive-picker design.md D1) — the interaction
4
- * surface `trellis onboard`'s two prompts need (at most four rows, no
5
- * search, no pagination) doesn't need a full prompt library; this
6
- * hand-rolls just enough raw-mode key handling and ANSI rendering for
7
- * that bounded case, matching the project's existing narrow-dependency
8
- * precedent (src/lib/envVarNames.ts, src/lib/secretEnv.ts).
2
+ * Styled interactive prompts used by `trellis onboard`.
9
3
  *
10
- * `input`/`output` are injectable (never a CLI flag) purely so tests can
11
- * drive the real key-parsing/render loop against a plain stream instead
12
- * of a real TTY — same seam pattern (`homeDir`, etc.) used everywhere
13
- * else in this project.
4
+ * The onboarding orchestration depends on semantic values (an index or a set
5
+ * of indices), not on a particular terminal renderer. Clack owns raw-mode
6
+ * handling, color, wrapping, cursor cleanup, and cancellation; the injected
7
+ * streams keep the prompt loop testable without a real terminal.
14
8
  */
15
9
  import type { Readable, Writable } from "node:stream";
16
10
  export interface PickerStreams {
17
11
  input: NodeJS.ReadStream | Readable;
18
12
  output: NodeJS.WriteStream | Writable;
19
13
  }
14
+ export interface PickerOption {
15
+ label: string;
16
+ disabled?: boolean;
17
+ }
20
18
  /**
21
- * True only when both streams are real, raw-mode-capable terminals — the
22
- * exact gate deciding picker vs. the pre-existing numbered-typing prompt
23
- * (design.md D2). A stream lacking `setRawMode` (piped input, some
24
- * minimal TTYs) always falls back, never hangs or guesses.
19
+ * Clack is only started for a real interactive terminal. Piped input keeps
20
+ * the existing numbered fallback in onboard instead of hanging in raw mode.
25
21
  */
26
22
  export declare function canUseInteractivePicker(streams?: PickerStreams): boolean;
27
- /** Wrapping index navigation — pure, exported for direct unit testing. */
23
+ /** Wrapping index navigation retained as a small public utility for callers/tests. */
28
24
  export declare function nextIndex(current: number, delta: number, length: number): number;
29
- /** Pure single-index toggle — exported for direct unit testing. */
25
+ /** Pure single-index toggle retained for callers/tests. */
30
26
  export declare function toggled(checked: readonly boolean[], index: number): boolean[];
31
- /** A row's *visible* width, excluding the color codes wrapped around it —
32
- * they add characters that occupy zero terminal columns. Needed to
33
- * compute how many physical lines a row actually wraps to (see
34
- * `physicalLineCount`); counting `row.length` directly would overcount
35
- * and under-clear on the next redraw. */
36
- export declare function visibleWidth(row: string): number;
37
- /**
38
- * How many physical terminal lines one logical row occupies once the
39
- * terminal wraps it — `Math.ceil(visibleWidth / columns)`, at least 1.
40
- * `clearLines`/`moveCursorUp` need this, not `rows.length`: a picker
41
- * item whose rendered text is longer than the terminal is wide (a long
42
- * agent summary, a narrow terminal, or both) wraps onto more than one
43
- * physical line, and erasing/moving by the *logical* row count instead
44
- * leaves the wrapped remainder of the previous frame on screen — found
45
- * via a real mirasim terminal session where a (much longer,
46
- * pre-truncation) row's wrapped tail was never cleared, so every redraw
47
- * appended a new, only-partially-overwritten copy instead of replacing
48
- * the old one.
49
- */
50
- export declare function physicalLineCount(row: string, columns: number): number;
51
- export declare function totalPhysicalLines(rows: readonly string[], columns: number): number;
52
27
  /**
53
- * Single-select: Up/Down/j/k moves the highlight, Enter confirms. Resolves
54
- * the confirmed index, or `null` on Ctrl+C cancel. Caller (onboard.ts)
55
- * must have already confirmed `canUseInteractivePicker()` — this function
56
- * does not re-check, and assumes `streams.input` genuinely supports raw
57
- * mode.
28
+ * Single-select prompt. The returned number is the original item index, so
29
+ * callers do not depend on display labels or prompt-library values.
58
30
  */
59
- export declare function runSingleSelectPicker(items: string[], streams?: PickerStreams): Promise<number | null>;
31
+ export declare function runSingleSelectPicker(items: readonly (string | PickerOption)[], streams?: PickerStreams, message?: string): Promise<number | null>;
60
32
  /**
61
- * Multi-select (checkbox): Up/Down/j/k moves the highlight, Space toggles
62
- * the current row, Enter confirms. Resolves the checked indices at
63
- * confirm time, or `null` on Ctrl+C cancel. Same precondition as
64
- * `runSingleSelectPicker`.
33
+ * Checkbox prompt. The returned indices preserve the existing onboard
34
+ * contract, while Clack handles checked state, wrapping, colors, and cleanup.
65
35
  */
66
- export declare function runMultiSelectPicker(items: string[], initiallyChecked: readonly boolean[], streams?: PickerStreams): Promise<number[] | null>;
36
+ export declare function runMultiSelectPicker(items: string[], initiallyChecked: readonly boolean[], streams?: PickerStreams, message?: string): Promise<number[] | null>;
37
+ /** Searchable multiselect for large skill/MCP inventories. It returns the
38
+ * selected labels as stable names, preserving the semantic adapter contract
39
+ * while avoiding a wall of dozens of rows in the onboarding terminal. */
40
+ export declare function runSearchMultiSelectPicker(items: readonly string[], streams?: PickerStreams, message?: string): Promise<string[] | null>;
@@ -1,248 +1,110 @@
1
1
  /**
2
- * Minimal, dependency-free arrow-key/checkbox terminal picker
3
- * (trellis-onboard-interactive-picker design.md D1) — the interaction
4
- * surface `trellis onboard`'s two prompts need (at most four rows, no
5
- * search, no pagination) doesn't need a full prompt library; this
6
- * hand-rolls just enough raw-mode key handling and ANSI rendering for
7
- * that bounded case, matching the project's existing narrow-dependency
8
- * precedent (src/lib/envVarNames.ts, src/lib/secretEnv.ts).
2
+ * Styled interactive prompts used by `trellis onboard`.
9
3
  *
10
- * `input`/`output` are injectable (never a CLI flag) purely so tests can
11
- * drive the real key-parsing/render loop against a plain stream instead
12
- * of a real TTY — same seam pattern (`homeDir`, etc.) used everywhere
13
- * else in this project.
4
+ * The onboarding orchestration depends on semantic values (an index or a set
5
+ * of indices), not on a particular terminal renderer. Clack owns raw-mode
6
+ * handling, color, wrapping, cursor cleanup, and cancellation; the injected
7
+ * streams keep the prompt loop testable without a real terminal.
14
8
  */
9
+ /**
10
+ * `@clack/prompts` (and its `@clack/core` dependency) is loaded lazily,
11
+ * inside each picker function below, rather than via a top-level
12
+ * `import` — deliberately, not an oversight. A top-level import runs the
13
+ * moment ANY module that transitively imports this file is evaluated,
14
+ * which for `trellis-gui`'s sidecar means every process start, since
15
+ * `onboard.ts` (imported unconditionally by the sidecar's `/plan/onboard`
16
+ * route) imports these functions even though the sidecar's `json: true`
17
+ * contract (see `planApply.ts`'s `OnboardWizardInput` doc comment)
18
+ * guarantees they are never actually called. Compiling the sidecar to a
19
+ * single-file binary (tasks.md 5.1) surfaced a real, current limitation
20
+ * in `@yao-pkg/pkg`'s ESM support: `@clack/prompts`' `exports`-map
21
+ * `.mjs` entry fails to resolve inside pkg's snapshot filesystem
22
+ * (`Cannot find module '.../@clack/prompts/dist/index.mjs'`), which
23
+ * previously crashed the packaged sidecar at startup even though its own
24
+ * code never uses the interactive picker at all. A dynamic `import()`
25
+ * only resolves at the point it actually runs, so the sidecar (whose
26
+ * `!opts.json` gates keep every real call site below unreached) never
27
+ * triggers that resolution attempt — while a real, interactive CLI run
28
+ * (`tsx`/`node`, not a pkg snapshot) resolves the dynamic import exactly
29
+ * as it would a static one, so this is behavior-preserving for the CLI.
30
+ */
31
+ async function clack() {
32
+ return import("@clack/prompts");
33
+ }
15
34
  function defaultStreams() {
16
- return { input: process.stdin, output: process.stdout };
35
+ return { input: process.stdin, output: process.stderr };
17
36
  }
18
37
  /**
19
- * True only when both streams are real, raw-mode-capable terminals — the
20
- * exact gate deciding picker vs. the pre-existing numbered-typing prompt
21
- * (design.md D2). A stream lacking `setRawMode` (piped input, some
22
- * minimal TTYs) always falls back, never hangs or guesses.
38
+ * Clack is only started for a real interactive terminal. Piped input keeps
39
+ * the existing numbered fallback in onboard instead of hanging in raw mode.
23
40
  */
24
41
  export function canUseInteractivePicker(streams = defaultStreams()) {
25
42
  const input = streams.input;
26
43
  const output = streams.output;
27
44
  return Boolean(input.isTTY && output.isTTY && typeof input.setRawMode === "function");
28
45
  }
29
- /** Wrapping index navigation — pure, exported for direct unit testing. */
46
+ /** Wrapping index navigation retained as a small public utility for callers/tests. */
30
47
  export function nextIndex(current, delta, length) {
31
48
  return ((current + delta) % length + length) % length;
32
49
  }
33
- /** Pure single-index toggle — exported for direct unit testing. */
50
+ /** Pure single-index toggle retained for callers/tests. */
34
51
  export function toggled(checked, index) {
35
52
  const next = [...checked];
36
53
  next[index] = !next[index];
37
54
  return next;
38
55
  }
39
- const ESC = "\u001b";
40
- const CTRL_C = "\u0003";
41
- const HIDE_CURSOR = `${ESC}[?25l`;
42
- const SHOW_CURSOR = `${ESC}[?25h`;
43
- const ANSI_ESCAPE_RE = /\x1b\[[0-9;]*m/g;
44
- /** A row's *visible* width, excluding the color codes wrapped around it —
45
- * they add characters that occupy zero terminal columns. Needed to
46
- * compute how many physical lines a row actually wraps to (see
47
- * `physicalLineCount`); counting `row.length` directly would overcount
48
- * and under-clear on the next redraw. */
49
- export function visibleWidth(row) {
50
- return row.replace(ANSI_ESCAPE_RE, "").length;
56
+ function wasCancelled(value, cancelSymbol, isCancelFn) {
57
+ return value === cancelSymbol || isCancelFn(value);
51
58
  }
52
59
  /**
53
- * How many physical terminal lines one logical row occupies once the
54
- * terminal wraps it — `Math.ceil(visibleWidth / columns)`, at least 1.
55
- * `clearLines`/`moveCursorUp` need this, not `rows.length`: a picker
56
- * item whose rendered text is longer than the terminal is wide (a long
57
- * agent summary, a narrow terminal, or both) wraps onto more than one
58
- * physical line, and erasing/moving by the *logical* row count instead
59
- * leaves the wrapped remainder of the previous frame on screen — found
60
- * via a real mirasim terminal session where a (much longer,
61
- * pre-truncation) row's wrapped tail was never cleared, so every redraw
62
- * appended a new, only-partially-overwritten copy instead of replacing
63
- * the old one.
60
+ * Single-select prompt. The returned number is the original item index, so
61
+ * callers do not depend on display labels or prompt-library values.
64
62
  */
65
- export function physicalLineCount(row, columns) {
66
- if (columns <= 0)
67
- return 1;
68
- return Math.max(1, Math.ceil(visibleWidth(row) / columns));
69
- }
70
- export function totalPhysicalLines(rows, columns) {
71
- return rows.reduce((sum, row) => sum + physicalLineCount(row, columns), 0);
72
- }
73
- /** `output.columns` only exists on a real TTY; test-injected plain
74
- * streams (`PickerStreams`) have none. Defaulting to 80 — the standard
75
- * terminal width, and this project's own test fixtures — keeps the
76
- * wrap-aware math above exercised (and testable) even without a real
77
- * terminal, rather than silently reverting to "assume no row ever wraps"
78
- * for every non-TTY stream. */
79
- function columnsOf(output) {
80
- return output.columns ?? 80;
81
- }
82
- /** Parses one raw input chunk into a single logical key — arrow escape
83
- * sequences, j/k, space, enter, or Ctrl+C. Anything else is ignored. */
84
- function parseKey(chunk) {
85
- if (chunk === CTRL_C)
86
- return "cancel";
87
- if (chunk === "\r" || chunk === "\n")
88
- return "confirm";
89
- if (chunk === " ")
90
- return "toggle";
91
- if (chunk === `${ESC}[A` || chunk === "k")
92
- return "up";
93
- if (chunk === `${ESC}[B` || chunk === "j")
94
- return "down";
95
- return null;
96
- }
97
- function withRawMode(streams, body) {
98
- const input = streams.input;
99
- const output = streams.output;
100
- const canSetRawMode = typeof input.setRawMode === "function";
101
- if (canSetRawMode)
102
- input.setRawMode(true);
103
- input.resume();
104
- input.setEncoding("utf-8");
105
- output.write(HIDE_CURSOR);
106
- const restore = () => {
107
- output.write(SHOW_CURSOR);
108
- if (canSetRawMode)
109
- input.setRawMode(false);
110
- input.pause();
111
- };
112
- // Second-layer safety net (design.md D6): a thrown error the caller's
113
- // own try/finally doesn't get a chance to run for (process killed
114
- // externally) must still not leave the terminal in raw mode/cursor
115
- // hidden.
116
- process.once("exit", restore);
117
- return body().finally(() => {
118
- restore();
119
- process.removeListener("exit", restore);
63
+ export async function runSingleSelectPicker(items, streams = defaultStreams(), message = "请选择一个迁移来源") {
64
+ const { select, CANCEL_SYMBOL, isCancel } = await clack();
65
+ const result = await select({
66
+ message,
67
+ options: items.map((item, value) => ({
68
+ label: typeof item === "string" ? item : item.label,
69
+ value,
70
+ ...(typeof item === "string" || item.disabled === undefined ? {} : { disabled: item.disabled }),
71
+ })),
72
+ input: streams.input,
73
+ output: streams.output,
74
+ showInstructions: true,
120
75
  });
121
- }
122
- function moveCursorUp(output, lines) {
123
- if (lines > 0)
124
- output.write(`${ESC}[${lines}A`);
125
- }
126
- function clearLines(output, lines) {
127
- for (let i = 0; i < lines; i++) {
128
- output.write(`${ESC}[2K`);
129
- if (i < lines - 1)
130
- output.write(`${ESC}[1B`);
131
- }
132
- moveCursorUp(output, lines - 1);
133
- }
134
- /**
135
- * `previousPhysicalLines` must be the actual on-screen line count the
136
- * *previous* call to this function produced (`totalPhysicalLines` of
137
- * that frame's rows), not `rows.length` — see `physicalLineCount`'s doc
138
- * comment for why a wrapped row makes those two numbers diverge.
139
- */
140
- function renderRows(output, rows, previousPhysicalLines) {
141
- if (previousPhysicalLines > 0) {
142
- clearLines(output, previousPhysicalLines);
143
- }
144
- for (const row of rows) {
145
- output.write(`${row}\n`);
146
- }
147
- }
148
- /**
149
- * A real foreground color (bold cyan), not just reverse video —
150
- * differentiating the highlighted row must not depend on a host
151
- * terminal correctly inverting fore/background, which a real mirasim
152
- * terminal session showed no visible effect from at all. The `>`/` `
153
- * marker stays regardless, as a plain-text fallback for a host that
154
- * strips color entirely.
155
- */
156
- function highlightRow(text, isHighlighted) {
157
- return isHighlighted ? `> ${ESC}[1;36m${text}${ESC}[0m` : ` ${text}`;
76
+ return wasCancelled(result, CANCEL_SYMBOL, isCancel) ? null : result;
158
77
  }
159
78
  /**
160
- * Single-select: Up/Down/j/k moves the highlight, Enter confirms. Resolves
161
- * the confirmed index, or `null` on Ctrl+C cancel. Caller (onboard.ts)
162
- * must have already confirmed `canUseInteractivePicker()` — this function
163
- * does not re-check, and assumes `streams.input` genuinely supports raw
164
- * mode.
79
+ * Checkbox prompt. The returned indices preserve the existing onboard
80
+ * contract, while Clack handles checked state, wrapping, colors, and cleanup.
165
81
  */
166
- export async function runSingleSelectPicker(items, streams = defaultStreams()) {
167
- const { input, output } = streams;
168
- return withRawMode(streams, () => {
169
- return new Promise((resolve) => {
170
- let highlighted = 0;
171
- let physicalLines = 0;
172
- const columns = columnsOf(output);
173
- const draw = () => {
174
- const rows = items.map((label, i) => highlightRow(label, i === highlighted));
175
- renderRows(output, rows, physicalLines);
176
- physicalLines = totalPhysicalLines(rows, columns);
177
- };
178
- draw();
179
- const onData = (chunk) => {
180
- const key = parseKey(chunk.toString("utf-8"));
181
- if (key === "up") {
182
- highlighted = nextIndex(highlighted, -1, items.length);
183
- draw();
184
- }
185
- else if (key === "down") {
186
- highlighted = nextIndex(highlighted, 1, items.length);
187
- draw();
188
- }
189
- else if (key === "confirm") {
190
- input.removeListener("data", onData);
191
- resolve(highlighted);
192
- }
193
- else if (key === "cancel") {
194
- input.removeListener("data", onData);
195
- resolve(null);
196
- }
197
- };
198
- input.on("data", onData);
199
- });
82
+ export async function runMultiSelectPicker(items, initiallyChecked, streams = defaultStreams(), message = "请选择要启用的项目") {
83
+ const { multiselect, CANCEL_SYMBOL, isCancel } = await clack();
84
+ const result = await multiselect({
85
+ message,
86
+ options: items.map((label, value) => ({ label, value })),
87
+ initialValues: items.flatMap((_, index) => (initiallyChecked[index] ? [index] : [])),
88
+ input: streams.input,
89
+ output: streams.output,
90
+ required: false,
91
+ showInstructions: true,
200
92
  });
201
- }
202
- /**
203
- * Multi-select (checkbox): Up/Down/j/k moves the highlight, Space toggles
204
- * the current row, Enter confirms. Resolves the checked indices at
205
- * confirm time, or `null` on Ctrl+C cancel. Same precondition as
206
- * `runSingleSelectPicker`.
207
- */
208
- export async function runMultiSelectPicker(items, initiallyChecked, streams = defaultStreams()) {
209
- const { input, output } = streams;
210
- return withRawMode(streams, () => {
211
- return new Promise((resolve) => {
212
- let highlighted = 0;
213
- let checked = [...initiallyChecked];
214
- let physicalLines = 0;
215
- const columns = columnsOf(output);
216
- const draw = () => {
217
- const rows = items.map((label, i) => highlightRow(`[${checked[i] ? "x" : " "}] ${label}`, i === highlighted));
218
- renderRows(output, rows, physicalLines);
219
- physicalLines = totalPhysicalLines(rows, columns);
220
- };
221
- draw();
222
- const onData = (chunk) => {
223
- const key = parseKey(chunk.toString("utf-8"));
224
- if (key === "up") {
225
- highlighted = nextIndex(highlighted, -1, items.length);
226
- draw();
227
- }
228
- else if (key === "down") {
229
- highlighted = nextIndex(highlighted, 1, items.length);
230
- draw();
231
- }
232
- else if (key === "toggle") {
233
- checked = toggled(checked, highlighted);
234
- draw();
235
- }
236
- else if (key === "confirm") {
237
- input.removeListener("data", onData);
238
- resolve(checked.flatMap((isChecked, i) => (isChecked ? [i] : [])));
239
- }
240
- else if (key === "cancel") {
241
- input.removeListener("data", onData);
242
- resolve(null);
243
- }
244
- };
245
- input.on("data", onData);
246
- });
93
+ return wasCancelled(result, CANCEL_SYMBOL, isCancel) ? null : [...result].sort((left, right) => left - right);
94
+ }
95
+ /** Searchable multiselect for large skill/MCP inventories. It returns the
96
+ * selected labels as stable names, preserving the semantic adapter contract
97
+ * while avoiding a wall of dozens of rows in the onboarding terminal. */
98
+ export async function runSearchMultiSelectPicker(items, streams = defaultStreams(), message = "选择要迁移的项目") {
99
+ const { autocompleteMultiselect, CANCEL_SYMBOL, isCancel } = await clack();
100
+ const result = await autocompleteMultiselect({
101
+ message,
102
+ placeholder: "输入关键词筛选",
103
+ options: items.map((value) => ({ label: value, value })),
104
+ initialValues: [...items],
105
+ input: streams.input,
106
+ output: streams.output,
107
+ required: false,
247
108
  });
109
+ return wasCancelled(result, CANCEL_SYMBOL, isCancel) ? null : result;
248
110
  }
@@ -0,0 +1,17 @@
1
+ /** Ownership record for the two ZCode native-Skill controls Trellis changes
2
+ * during strict Runtime delivery. MCP ownership stays in mcpOwnership.ts;
3
+ * this separate record lets a later native/both delivery restore the user's
4
+ * prior values without treating an ordinary config key as an MCP server. */
5
+ export interface ZcodeSkillControlValues {
6
+ featuresSkill: boolean | null;
7
+ skillsEnabled: boolean | null;
8
+ }
9
+ export interface ZcodeSettingsOwnership {
10
+ configPath: string;
11
+ managed: ZcodeSkillControlValues;
12
+ previous: ZcodeSkillControlValues;
13
+ }
14
+ export declare function zcodeSettingsOwnershipPath(homeDir: string): string;
15
+ export declare function loadZcodeSettingsOwnership(homeDir: string): ZcodeSettingsOwnership | undefined;
16
+ export declare function saveZcodeSettingsOwnership(homeDir: string, value: ZcodeSettingsOwnership): void;
17
+ export declare function forgetZcodeSettingsOwnership(homeDir: string): void;