@crouton-kit/crouter 0.3.27 → 0.3.29

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 (343) hide show
  1. package/dist/build-root.js +4 -2
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
  5. package/dist/builtin-memory/crouter-development/personas.md +1 -1
  6. package/dist/builtin-memory/crouter-development/plugins.md +20 -18
  7. package/dist/builtin-memory/design.md +1 -1
  8. package/dist/builtin-memory/development.md +1 -1
  9. package/dist/builtin-memory/internal/INDEX.md +1 -1
  10. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  12. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  13. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  14. package/dist/builtin-memory/planning.md +1 -1
  15. package/dist/builtin-memory/spec.md +6 -4
  16. package/dist/builtin-personas/orchestration-kernel.md +4 -5
  17. package/dist/builtin-personas/runtime-base.md +8 -4
  18. package/dist/builtin-personas/spec/PERSONA.md +8 -4
  19. package/dist/builtin-personas/spec/orchestrator.md +5 -3
  20. package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
  21. package/dist/builtin-views/canvas/core.mjs +586 -0
  22. package/dist/builtin-views/canvas/text.mjs +58 -0
  23. package/dist/builtin-views/canvas/tui.mjs +165 -0
  24. package/dist/builtin-views/canvas/web.jsx +120 -0
  25. package/dist/builtin-views/git-pr/core.mjs +673 -0
  26. package/dist/builtin-views/git-pr/text.mjs +84 -0
  27. package/dist/builtin-views/git-pr/tui.mjs +302 -0
  28. package/dist/builtin-views/git-pr/web.jsx +216 -0
  29. package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
  30. package/dist/builtin-views/inbox/core.mjs +1273 -0
  31. package/dist/builtin-views/inbox/text.mjs +73 -0
  32. package/dist/builtin-views/inbox/tui.mjs +312 -0
  33. package/dist/builtin-views/inbox/web.jsx +188 -0
  34. package/dist/builtin-views/linkedin/core.mjs +906 -0
  35. package/dist/builtin-views/linkedin/text.mjs +69 -0
  36. package/dist/builtin-views/linkedin/tui.mjs +427 -0
  37. package/dist/builtin-views/linkedin/web.jsx +206 -0
  38. package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
  39. package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
  40. package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
  41. package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
  42. package/dist/cli.js +3 -3
  43. package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
  44. package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
  45. package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
  46. package/dist/clients/attach/__tests__/git-info.test.js +31 -0
  47. package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
  48. package/dist/clients/attach/__tests__/reconnect-giveup.test.js +30 -0
  49. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  50. package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
  51. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  52. package/dist/clients/attach/attach-cmd.js +445 -41
  53. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  54. package/dist/clients/attach/auth-pickers.js +194 -0
  55. package/dist/clients/attach/canvas-panels.d.ts +12 -0
  56. package/dist/clients/attach/canvas-panels.js +78 -0
  57. package/dist/clients/attach/chat-view.d.ts +27 -2
  58. package/dist/clients/attach/chat-view.js +98 -9
  59. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  60. package/dist/clients/attach/clipboard-image.js +150 -26
  61. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  62. package/dist/clients/attach/clipboard-text.js +39 -0
  63. package/dist/clients/attach/config-load.d.ts +54 -7
  64. package/dist/clients/attach/config-load.js +99 -3
  65. package/dist/clients/attach/context-message.d.ts +12 -0
  66. package/dist/clients/attach/context-message.js +72 -0
  67. package/dist/clients/attach/git-info.d.ts +20 -0
  68. package/dist/clients/attach/git-info.js +44 -0
  69. package/dist/clients/attach/graph-overlay.d.ts +43 -0
  70. package/dist/clients/attach/graph-overlay.js +306 -0
  71. package/dist/clients/attach/input-controller.d.ts +81 -13
  72. package/dist/clients/attach/input-controller.js +307 -67
  73. package/dist/clients/attach/pickers.d.ts +58 -0
  74. package/dist/clients/attach/pickers.js +171 -0
  75. package/dist/clients/attach/slash-commands.d.ts +44 -1
  76. package/dist/clients/attach/slash-commands.js +250 -27
  77. package/dist/clients/attach/titled-editor.d.ts +33 -0
  78. package/dist/clients/attach/titled-editor.js +84 -0
  79. package/dist/clients/attach/view-socket.d.ts +37 -2
  80. package/dist/clients/attach/view-socket.js +131 -7
  81. package/dist/clients/web/dev-server.d.ts +7 -0
  82. package/dist/clients/web/dev-server.js +59 -0
  83. package/dist/clients/web/events.d.ts +14 -0
  84. package/dist/clients/web/events.js +151 -0
  85. package/dist/clients/web/server.d.ts +18 -0
  86. package/dist/clients/web/server.js +450 -0
  87. package/dist/clients/web/web-cmd.d.ts +2 -0
  88. package/dist/clients/web/web-cmd.js +120 -0
  89. package/dist/commands/canvas.js +1 -2
  90. package/dist/commands/chord.js +1 -1
  91. package/dist/commands/dashboard.js +5 -1
  92. package/dist/commands/human/prompts.js +3 -3
  93. package/dist/commands/human/queue.d.ts +17 -0
  94. package/dist/commands/human/queue.js +111 -4
  95. package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
  96. package/dist/commands/memory/lint.d.ts +5 -4
  97. package/dist/commands/memory/lint.js +23 -7
  98. package/dist/commands/memory/read.js +2 -2
  99. package/dist/commands/memory/shared.d.ts +7 -1
  100. package/dist/commands/memory/shared.js +18 -3
  101. package/dist/commands/memory/write.js +20 -7
  102. package/dist/commands/memory.js +4 -4
  103. package/dist/commands/node.d.ts +1 -1
  104. package/dist/commands/node.js +64 -65
  105. package/dist/commands/pkg.js +1 -2
  106. package/dist/commands/revive.js +11 -3
  107. package/dist/commands/sys/feedback.d.ts +1 -0
  108. package/dist/commands/sys/feedback.js +163 -0
  109. package/dist/commands/sys/sync.d.ts +1 -0
  110. package/dist/commands/sys/sync.js +187 -0
  111. package/dist/commands/sys.js +4 -2
  112. package/dist/commands/view-cycle.js +2 -2
  113. package/dist/commands/view-list.js +8 -8
  114. package/dist/commands/view-new.js +21 -17
  115. package/dist/commands/view-pick.js +1 -1
  116. package/dist/commands/view-run.js +35 -14
  117. package/dist/commands/view.js +8 -6
  118. package/dist/commands/workspace.d.ts +2 -0
  119. package/dist/commands/workspace.js +161 -0
  120. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  121. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  122. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  123. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  124. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  125. package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
  126. package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
  127. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  128. package/dist/core/__tests__/close.test.js +14 -20
  129. package/dist/core/__tests__/context-intro.test.js +19 -19
  130. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  131. package/dist/core/__tests__/draw-style.test.js +1 -1
  132. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  133. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  134. package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
  135. package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
  136. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  137. package/dist/core/__tests__/focuses.test.js +25 -13
  138. package/dist/core/__tests__/fork.test.js +22 -5
  139. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  140. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  141. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  142. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  143. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  144. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  145. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  146. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  147. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  148. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  149. package/dist/core/__tests__/helpers/harness.js +19 -36
  150. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  151. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  152. package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
  153. package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
  154. package/dist/core/__tests__/lifecycle.test.js +1 -19
  155. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  156. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  157. package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
  158. package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
  159. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  160. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  161. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  162. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  163. package/dist/core/__tests__/reset.test.js +19 -79
  164. package/dist/core/__tests__/steer-note.test.js +9 -7
  165. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  166. package/dist/core/bootstrap.d.ts +0 -3
  167. package/dist/core/bootstrap.js +1 -143
  168. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  169. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  170. package/dist/core/canvas/browse/app.js +131 -8
  171. package/dist/core/canvas/browse/model.d.ts +23 -5
  172. package/dist/core/canvas/browse/model.js +68 -13
  173. package/dist/core/canvas/browse/render.d.ts +7 -1
  174. package/dist/core/canvas/browse/render.js +178 -47
  175. package/dist/core/canvas/canvas.js +1 -1
  176. package/dist/core/canvas/focuses.d.ts +4 -4
  177. package/dist/core/canvas/focuses.js +7 -7
  178. package/dist/core/canvas/nav-model.d.ts +162 -0
  179. package/dist/core/canvas/nav-model.js +487 -0
  180. package/dist/core/canvas/paths.d.ts +7 -0
  181. package/dist/core/canvas/paths.js +9 -0
  182. package/dist/core/canvas/render.d.ts +68 -9
  183. package/dist/core/canvas/render.js +195 -61
  184. package/dist/core/canvas/types.d.ts +16 -23
  185. package/dist/core/config.js +1 -4
  186. package/dist/core/memory-resolver.js +43 -5
  187. package/dist/core/runtime/bearings.d.ts +1 -1
  188. package/dist/core/runtime/bearings.js +8 -8
  189. package/dist/core/runtime/branded-host.d.ts +16 -0
  190. package/dist/core/runtime/branded-host.js +127 -0
  191. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  192. package/dist/core/runtime/broker-sdk.d.ts +0 -12
  193. package/dist/core/runtime/broker-sdk.js +77 -6
  194. package/dist/core/runtime/broker.d.ts +5 -1
  195. package/dist/core/runtime/broker.js +520 -59
  196. package/dist/core/runtime/close.d.ts +12 -2
  197. package/dist/core/runtime/close.js +37 -16
  198. package/dist/core/runtime/front-door.js +14 -7
  199. package/dist/core/runtime/host.d.ts +14 -34
  200. package/dist/core/runtime/host.js +14 -50
  201. package/dist/core/runtime/launch.d.ts +12 -9
  202. package/dist/core/runtime/launch.js +27 -15
  203. package/dist/core/runtime/lifecycle.d.ts +1 -1
  204. package/dist/core/runtime/lifecycle.js +15 -19
  205. package/dist/core/runtime/nodes.d.ts +0 -55
  206. package/dist/core/runtime/nodes.js +6 -74
  207. package/dist/core/runtime/placement.d.ts +91 -334
  208. package/dist/core/runtime/placement.js +262 -828
  209. package/dist/core/runtime/promote.d.ts +2 -0
  210. package/dist/core/runtime/promote.js +24 -6
  211. package/dist/core/runtime/recap.d.ts +8 -0
  212. package/dist/core/runtime/recap.js +107 -0
  213. package/dist/core/runtime/recycle.js +25 -61
  214. package/dist/core/runtime/reset.d.ts +43 -37
  215. package/dist/core/runtime/reset.js +131 -218
  216. package/dist/core/runtime/revive.d.ts +9 -29
  217. package/dist/core/runtime/revive.js +45 -135
  218. package/dist/core/runtime/spawn.d.ts +15 -11
  219. package/dist/core/runtime/spawn.js +168 -136
  220. package/dist/core/runtime/surface-bg.d.ts +11 -0
  221. package/dist/core/runtime/surface-bg.js +68 -0
  222. package/dist/core/runtime/tmux.d.ts +15 -43
  223. package/dist/core/runtime/tmux.js +38 -80
  224. package/dist/core/scope.d.ts +1 -2
  225. package/dist/core/scope.js +1 -5
  226. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  227. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  228. package/dist/core/skill-sync/builtins.d.ts +42 -0
  229. package/dist/core/skill-sync/builtins.js +112 -0
  230. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  231. package/dist/core/skill-sync/claude-plugins.js +71 -0
  232. package/dist/core/skill-sync/engine.d.ts +42 -0
  233. package/dist/core/skill-sync/engine.js +633 -0
  234. package/dist/core/skill-sync/export.d.ts +23 -0
  235. package/dist/core/skill-sync/export.js +86 -0
  236. package/dist/core/skill-sync/manifest.d.ts +64 -0
  237. package/dist/core/skill-sync/manifest.js +181 -0
  238. package/dist/core/skill-sync/profile.d.ts +76 -0
  239. package/dist/core/skill-sync/profile.js +173 -0
  240. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  241. package/dist/core/skill-sync/snapshot.js +120 -0
  242. package/dist/core/spawn.d.ts +14 -0
  243. package/dist/core/spawn.js +29 -9
  244. package/dist/core/substrate/index.d.ts +2 -2
  245. package/dist/core/substrate/index.js +7 -7
  246. package/dist/core/substrate/injected-store.d.ts +10 -0
  247. package/dist/core/substrate/injected-store.js +55 -0
  248. package/dist/core/substrate/on-read.js +68 -6
  249. package/dist/core/substrate/render.d.ts +8 -11
  250. package/dist/core/substrate/render.js +29 -43
  251. package/dist/core/substrate/schema.d.ts +16 -11
  252. package/dist/core/substrate/schema.js +32 -31
  253. package/dist/core/tui/host.d.ts +12 -4
  254. package/dist/core/tui/host.js +280 -149
  255. package/dist/core/view/bridge.d.ts +10 -0
  256. package/dist/core/view/bridge.js +31 -0
  257. package/dist/core/view/chrome.d.ts +9 -0
  258. package/dist/core/view/chrome.js +22 -0
  259. package/dist/core/view/contract.d.ts +171 -0
  260. package/dist/core/view/contract.js +23 -0
  261. package/dist/core/view/loader.d.ts +31 -0
  262. package/dist/core/view/loader.js +188 -0
  263. package/dist/core/view/transport-local.d.ts +7 -0
  264. package/dist/core/view/transport-local.js +70 -0
  265. package/dist/core/view/transport.d.ts +4 -0
  266. package/dist/core/view/transport.js +15 -0
  267. package/dist/daemon/crtrd.d.ts +38 -27
  268. package/dist/daemon/crtrd.js +303 -376
  269. package/dist/daemon/manage.js +6 -1
  270. package/dist/index.js +1 -1
  271. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  272. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  273. package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
  274. package/dist/pi-extensions/canvas-nav.js +35 -385
  275. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  276. package/dist/pi-extensions/canvas-recap.js +236 -0
  277. package/dist/pi-extensions/canvas-resume.js +2 -1
  278. package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
  279. package/dist/pi-extensions/canvas-stophook.js +119 -103
  280. package/dist/pi-extensions/canvas-view.js +2 -1
  281. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  282. package/dist/pi-extensions/widget-order-bus.js +34 -0
  283. package/dist/prompts/view.d.ts +2 -2
  284. package/dist/prompts/view.js +145 -73
  285. package/dist/types.d.ts +1 -6
  286. package/dist/types.js +1 -3
  287. package/dist/web/ViewChrome.d.ts +7 -0
  288. package/dist/web/ViewChrome.js +28 -0
  289. package/dist/web/ViewPane.d.ts +39 -0
  290. package/dist/web/ViewPane.js +48 -0
  291. package/dist/web/index.d.ts +6 -0
  292. package/dist/web/index.js +16 -0
  293. package/dist/web/runtime.d.ts +39 -0
  294. package/dist/web/runtime.js +133 -0
  295. package/dist/web/states.d.ts +24 -0
  296. package/dist/web/states.js +24 -0
  297. package/dist/web/transport-http.d.ts +5 -0
  298. package/dist/web/transport-http.js +28 -0
  299. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  300. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  301. package/dist/web-client/index.html +13 -0
  302. package/package.json +20 -6
  303. package/dist/builtin-views/canvas/client.mjs +0 -303
  304. package/dist/builtin-views/canvas/view.mjs +0 -576
  305. package/dist/builtin-views/git-pr/client.mjs +0 -440
  306. package/dist/builtin-views/git-pr/view.mjs +0 -675
  307. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  308. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  309. package/dist/builtin-views/inbox/view.mjs +0 -889
  310. package/dist/builtin-views/linkedin/client.mjs +0 -610
  311. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  312. package/dist/commands/pkg/bridge.d.ts +0 -1
  313. package/dist/commands/pkg/bridge.js +0 -137
  314. package/dist/commands/skill/author.d.ts +0 -3
  315. package/dist/commands/skill/author.js +0 -140
  316. package/dist/commands/skill/shared.d.ts +0 -3
  317. package/dist/commands/skill/shared.js +0 -19
  318. package/dist/commands/skill.d.ts +0 -2
  319. package/dist/commands/skill.js +0 -21
  320. package/dist/commands/tmux-spread.d.ts +0 -2
  321. package/dist/commands/tmux-spread.js +0 -144
  322. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  323. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  324. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  325. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  326. package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
  327. package/dist/core/__tests__/home-session.test.js +0 -186
  328. package/dist/core/__tests__/relaunch.test.js +0 -335
  329. package/dist/core/bridge-map.d.ts +0 -19
  330. package/dist/core/bridge-map.js +0 -73
  331. package/dist/core/tui/contract.d.ts +0 -83
  332. package/dist/core/tui/contract.js +0 -8
  333. package/dist/core/tui/loader.d.ts +0 -16
  334. package/dist/core/tui/loader.js +0 -94
  335. package/dist/prompts/skill.d.ts +0 -2
  336. package/dist/prompts/skill.js +0 -650
  337. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  338. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  339. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  340. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  341. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  342. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
  343. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
@@ -7,9 +7,9 @@
7
7
  // UI unless you switch to it. Bring one forefront with select-window (within a
8
8
  // root) or switch-client + select-window (across roots). done/dead nodes close
9
9
  // their window; reviving opens a fresh one.
10
- import { spawn, spawnSync } from 'node:child_process';
10
+ import { spawnSync } from 'node:child_process';
11
11
  import { readConfig } from '../config.js';
12
- import { nodeSession } from './nodes.js';
12
+ import { surfaceTmuxStyleArgs } from './surface-bg.js';
13
13
  // ---------------------------------------------------------------------------
14
14
  // Shell quoting + tmux invocation
15
15
  // ---------------------------------------------------------------------------
@@ -115,6 +115,8 @@ export function splitWindow(targetPane, opts) {
115
115
  'split-window',
116
116
  '-d',
117
117
  ...(opts.vertical === true ? [] : ['-h']),
118
+ ...(opts.before === true ? ['-b'] : []),
119
+ ...(opts.size !== undefined ? ['-l', String(opts.size)] : []),
118
120
  '-P',
119
121
  '-F',
120
122
  '#{pane_id}',
@@ -141,6 +143,12 @@ export function focusWindow(session, window) {
141
143
  export function closeWindow(window) {
142
144
  return tmux(['kill-window', '-t', window]).ok;
143
145
  }
146
+ /** Rename a window (`tmux rename-window -t <window> <name>`). Used by the
147
+ * root relaunch to re-title the viewer window when its pane is re-pointed at
148
+ * the freshly-minted node. Best-effort; false if tmux fails. */
149
+ export function renameWindow(window, name) {
150
+ return tmux(['rename-window', '-t', window, name]).ok;
151
+ }
144
152
  /** Close a single PANE. Its window closes automatically once this was the last
145
153
  * pane, but sibling panes survive — so co-located nodes (several agents sharing
146
154
  * one window via swap-pane focus) are torn down one at a time instead of all
@@ -191,6 +199,14 @@ export function paneExists(pane) {
191
199
  const r = tmux(['display-message', '-p', '-t', pane, '#{pane_id}']);
192
200
  return r.ok && r.stdout === pane;
193
201
  }
202
+ /** Does this pane exist AND have its command still RUNNING (`#{pane_dead}` = 0)?
203
+ * Distinguishes a pane genuinely hosting a live process from a remain-on-exit
204
+ * corpse frozen after exit (`pane_dead` = 1). Node viewer panes run `crtr attach`
205
+ * as the pane command, so pane-running ⟹ a live viewer occupies it. */
206
+ export function paneRunning(pane) {
207
+ const r = tmux(['display-message', '-p', '-t', pane, '#{pane_id}\t#{pane_dead}']);
208
+ return r.ok && r.stdout === `${pane}\t0`;
209
+ }
194
210
  /** Every live pane id on the server (`list-panes -a`), as a Set for membership
195
211
  * probes. Returns null when tmux is unreachable (no server / transient failure)
196
212
  * so callers can tell "no panes" apart from "can't tell" — a GC pass must skip,
@@ -224,27 +240,6 @@ export function getPaneOption(pane, name) {
224
240
  const r = tmux(['show-options', '-p', '-t', pane, '-q', '-v', name]);
225
241
  return r.ok ? r.stdout : undefined;
226
242
  }
227
- /** Relocate a pane into another session as its own window WITHOUT killing the
228
- * process in it — `break-pane -d` moves the pane out of its current window (the
229
- * pi keeps generating) into a fresh window in `session`; `-d` leaves the caller's
230
- * client where it is rather than following the pane to the background, and `-a`
231
- * allocates the next free window index (same dodge as openNodeWindow). The
232
- * "detach to background" driver behind `node lifecycle --detach`. Best-effort;
233
- * false if tmux refuses (e.g. the pane is gone). The caller reconciles presence
234
- * so the canvas follows the move. */
235
- export function breakPaneToSession(pane, session) {
236
- return tmux(['break-pane', '-d', '-a', '-s', pane, '-t', `${session}:`]).ok;
237
- }
238
- /** Swap `targetPane` into `callerPane`'s layout slot, IN PLACE. `-d` keeps the
239
- * caller's window active, so the target's pane appears where the caller is
240
- * rather than navigating the client off to the target's window. The caller's
241
- * old pane lives on in the target's former window — the move is reversible
242
- * (focusing back swaps it in again). Best-effort; never throws. */
243
- export function swapPaneInPlace(targetPane, callerPane) {
244
- if (targetPane === callerPane)
245
- return true;
246
- return tmux(['swap-pane', '-d', '-s', targetPane, '-t', callerPane]).ok;
247
- }
248
243
  /** The `respawn-pane -k` argv for `opts`. `-k` kills the pane's current process
249
244
  * (e.g. a yielding pi) and re-execs `command` in the SAME pane, preserving its
250
245
  * `%id` (§1.5 F3: a frozen focus pane resumes in place, no new window). The
@@ -262,24 +257,6 @@ function respawnPaneArgs(opts) {
262
257
  opts.command,
263
258
  ];
264
259
  }
265
- /** Re-exec a command in an EXISTING pane, in place — DETACHED. Spawned in its own
266
- * process group (unref'd) so the request reaches the tmux server even though
267
- * `-k` tears down the caller's own pi mid-flight. Used when a node respawns ITS
268
- * OWN pane (refresh-yield): the dispatch can't be awaited because it kills the
269
- * awaiter. Returns true once the request was dispatched. */
270
- export function respawnPaneDetached(opts) {
271
- try {
272
- const child = spawn('tmux', respawnPaneArgs(opts), {
273
- detached: true,
274
- stdio: 'ignore',
275
- });
276
- child.unref();
277
- return true;
278
- }
279
- catch {
280
- return false;
281
- }
282
- }
283
260
  /** Re-exec a command in an EXISTING pane, in place — SYNCHRONOUS. Runs the
284
261
  * `respawn-pane` to completion and reports the real exit status. Used when the
285
262
  * caller is NOT the pane being respawned (e.g. the daemon resuming a frozen
@@ -287,12 +264,6 @@ export function respawnPaneDetached(opts) {
287
264
  export function respawnPaneSync(opts) {
288
265
  return tmux(respawnPaneArgs(opts)).ok;
289
266
  }
290
- /** @deprecated Use respawnPaneDetached. Retained so existing refresh-yield
291
- * callers stay green while the placement layer migrates onto the explicit
292
- * sync/detached split. */
293
- export function respawnPane(opts) {
294
- return respawnPaneDetached(opts);
295
- }
296
267
  // ---------------------------------------------------------------------------
297
268
  // pi command assembly
298
269
  // ---------------------------------------------------------------------------
@@ -347,34 +318,8 @@ export function switchClient(session) {
347
318
  return tmux(['switch-client', '-t', session]).ok;
348
319
  }
349
320
  // ---------------------------------------------------------------------------
350
- // Multi-pane layout (used by `canvas tmux-spread`)
321
+ // send-keys (chrome used by the prefix menu + nav bindings)
351
322
  // ---------------------------------------------------------------------------
352
- /** Move a source pane into a destination window (`tmux join-pane`). The source
353
- * pane's running process (e.g. a child's live pi) is preserved; its now-empty
354
- * source window auto-closes. Best-effort; false if tmux fails. */
355
- export function joinPane(srcPane, dstWindow) {
356
- return tmux(['join-pane', '-s', srcPane, '-t', dstWindow]).ok;
357
- }
358
- /** Apply a named tmux layout to a window (`tmux select-layout`). Use
359
- * `main-vertical` for one wide pane on the left + the rest stacked right.
360
- * Best-effort; never throws. */
361
- export function selectLayout(window, layout) {
362
- return tmux(['select-layout', '-t', window, layout]).ok;
363
- }
364
- /** Set a tmux window option (`tmux set-window-option`). Used to size the main
365
- * pane (`main-pane-width`) before a main-vertical layout. Best-effort. */
366
- export function setWindowOption(window, name, value) {
367
- return tmux(['set-window-option', '-t', window, name, value]).ok;
368
- }
369
- /** Toggle `remain-on-exit` on a window (§1.5 F3). `on` keeps a focus pane on
370
- * screen after its pi exits — the viewport survives (F1), the final transcript
371
- * is preserved, and `respawn-pane -k` can resurrect the node into the SAME pane
372
- * id. NOTE (§1.5/§2.5, spike-confirmed): a dead/frozen pane is reaped only by
373
- * `kill-pane`/`respawn-pane`, NEVER by toggling this off — the toggle does not
374
- * reap an already-dead pane. Best-effort; never throws. */
375
- export function setRemainOnExit(window, on) {
376
- return tmux(['set-window-option', '-t', window, 'remain-on-exit', on ? 'on' : 'off']).ok;
377
- }
378
323
  /** Type a literal (e.g. a `/graph` slash command) into a pane and press Enter
379
324
  * (`tmux send-keys -t <pane> '<text>' Enter`). Requires the pane's editor be
380
325
  * empty, same limitation as the menu's `/promote` item. Best-effort. */
@@ -389,7 +334,7 @@ export function sendKeysEnter(pane, text) {
389
334
  // ---------------------------------------------------------------------------
390
335
  /** Reserved mnemonic keys owned by the built-in menu items below — a custom
391
336
  * `prefixBind` may not claim these (the built-in item wins). */
392
- const RESERVED_MENU_KEYS = new Set(['o', 'r', 'd', 'D', 'x', 'b']);
337
+ const RESERVED_MENU_KEYS = new Set(['o', 'r', 'd', 'D', 'x']);
393
338
  /** Bind Alt+C to the crouter action menu. Best-effort; false if tmux fails.
394
339
  * The built-in items (promote/demote/detach/close/browse) are static; the canvas-nav
395
340
  * chords (graph/manager/expand/report-N + any custom prefixBind) are appended
@@ -397,7 +342,6 @@ const RESERVED_MENU_KEYS = new Set(['o', 'r', 'd', 'D', 'x', 'b']);
397
342
  * the `__graph__` sentinel, a `send-keys '/graph'`) so the menu stays static
398
343
  * while behaviour is config-driven. */
399
344
  export function installMenuBinding() {
400
- const sess = nodeSession();
401
345
  const title = ' crtr ';
402
346
  const items = [
403
347
  // Promote types `/promote` into the agent's pane rather than shelling out:
@@ -411,15 +355,13 @@ export function installMenuBinding() {
411
355
  // `d` runs `node demote`: flip the agent to TERMINAL in place — no finalize,
412
356
  // no kill — it keeps running where it is, and because it is now terminal it
413
357
  // is forced to push a final up the spine when it finishes. `D` runs `node
414
- // demote --detach`, which ALSO detaches it to the background `crtr` session
415
- // (frees the pane; the pi keeps generating). Neither ends it.
358
+ // demote --detach`, which ALSO closes the agent's viewer pane (frees the
359
+ // pane; the detached broker keeps generating). Neither ends it.
416
360
  { name: 'demote to terminal', key: 'd', cmd: `run-shell "crtr node demote --pane '#{pane_id}' >/dev/null 2>&1"` },
417
361
  { name: 'detach to background', key: 'D', cmd: `run-shell "crtr node demote --pane '#{pane_id}' --detach >/dev/null 2>&1"` },
418
362
  // Close cascades down the subscribes_to spine (kills the subtree's windows,
419
363
  // marks them canceled); revivable. Output discarded — the keypress just acts.
420
364
  { name: 'close agent + subtree', key: 'x', cmd: `run-shell "crtr node close --pane '#{pane_id}' >/dev/null 2>&1"` },
421
- // Re-keyed g→b so `g` is free for the canvas-nav GRAPH toggle (below).
422
- { name: 'browse background agents', key: 'b', cmd: `switch-client -t ${sess}` },
423
365
  ];
424
366
  // Canvas-nav chords from config (default: g→graph, m→manager, e→expand). The
425
367
  // `__graph__` sentinel toggles the in-pi GRAPH modal via send-keys; every
@@ -449,6 +391,17 @@ export function installMenuBinding() {
449
391
  cmd: `run-shell "crtr canvas chord --pane '#{pane_id}' --key ${n} >/dev/null 2>&1"`,
450
392
  });
451
393
  }
394
+ // Alt+C TOGGLE: re-pressing Alt+C while the menu is open should DISMISS it.
395
+ // display-menu is modal, so the root `M-c` binding is shadowed while the menu
396
+ // is up; tmux instead assembles the re-pressed Esc+c into the meta key `M-c`
397
+ // and delivers it to the menu's own key handler. An unhandled key is ignored
398
+ // (the menu just stays open) and the trailing `c` never leaks to the editor
399
+ // (verified empirically in tmux 3.6b: no native ESC-cancel + c-leak occurs for
400
+ // an atomic keypress). We catch that `M-c` with a menu item keyed to it whose
401
+ // command is empty — selecting it runs nothing and closes the menu. The row
402
+ // doubles as the menu's self-documenting dismiss hint. Placed last so it reads
403
+ // as chrome below the actions; `M-c` collides with no mnemonic above.
404
+ items.push({ name: 'close menu', key: 'M-c', cmd: '' });
452
405
  // tmux's -x sets the menu's LEFT edge. To sit the box INSIDE the pane's
453
406
  // top-right corner, shift x left by the box width (longest line + tmux chrome:
454
407
  // borders + padding + the right-aligned mnemonic-key column) via format math.
@@ -460,6 +413,11 @@ export function installMenuBinding() {
460
413
  const args = [
461
414
  'bind-key', '-n', 'M-c', 'display-menu',
462
415
  '-T', `#[align=centre]${title}`,
416
+ // Frame the menu on the theme's distinct-surface background so it doesn't
417
+ // blend into the pane behind it (CTO ruling). Empty in the front door (theme
418
+ // not loaded there); the attach viewer re-installs this with the style once
419
+ // it has themed — see attach-cmd.ts.
420
+ ...surfaceTmuxStyleArgs(),
463
421
  '-x', `#{e|-:#{pane_right},${boxW + nudgeX}}`,
464
422
  '-y', `#{e|+:#{pane_top},${nudgeY}}`,
465
423
  ];
@@ -9,7 +9,6 @@ export declare function requireScopeRoot(scope: Scope): string;
9
9
  export declare function ensureProjectScopeRoot(startDir?: string): string;
10
10
  export declare function pluginsDir(scope: Scope): string | null;
11
11
  export declare function marketplacesDir(scope: Scope): string | null;
12
- export declare function scopeSkillsDir(scope: Scope): string | null;
13
12
  /** Where substrate memory documents live per scope. Builtin memory is the
14
13
  * special case — its own package dir (builtinMemoryRoot), since the builtin
15
14
  * scope has no scopeRoot — mirroring viewsDir's builtin handling. User and
@@ -20,7 +19,7 @@ export declare function scopeMemoryDir(scope: Scope): string | null;
20
19
  export declare function pluginMemoryDir(plugin: InstalledPlugin): string;
21
20
  /** Where view definition dirs live per scope. Builtin views sit directly under
22
21
  * builtinViewsRoot() (no `views/` segment — they ARE the builtin views dir),
23
- * matching the loader's `<root>/<name>/view.mjs` resolution. */
22
+ * matching the loader's `<root>/<name>/core.mjs` resolution. */
24
23
  export declare function viewsDir(scope: Scope): string | null;
25
24
  export declare function resolveScopeArg(scopeArg: string | undefined): Scope | 'all';
26
25
  export declare function listScopes(scopeArg: string | undefined): Scope[];
@@ -87,10 +87,6 @@ export function marketplacesDir(scope) {
87
87
  const root = scopeRoot(scope);
88
88
  return root ? join(root, 'marketplaces') : null;
89
89
  }
90
- export function scopeSkillsDir(scope) {
91
- const root = scopeRoot(scope);
92
- return root ? join(root, 'skills') : null;
93
- }
94
90
  /** Where substrate memory documents live per scope. Builtin memory is the
95
91
  * special case — its own package dir (builtinMemoryRoot), since the builtin
96
92
  * scope has no scopeRoot — mirroring viewsDir's builtin handling. User and
@@ -108,7 +104,7 @@ export function pluginMemoryDir(plugin) {
108
104
  }
109
105
  /** Where view definition dirs live per scope. Builtin views sit directly under
110
106
  * builtinViewsRoot() (no `views/` segment — they ARE the builtin views dir),
111
- * matching the loader's `<root>/<name>/view.mjs` resolution. */
107
+ * matching the loader's `<root>/<name>/core.mjs` resolution. */
112
108
  export function viewsDir(scope) {
113
109
  if (scope === 'builtin')
114
110
  return builtinViewsRoot();
@@ -0,0 +1,57 @@
1
+ // Regression test for the `--dry-run` `wrote` count (found by P8 integration).
2
+ //
3
+ // Bug: under `--dry-run`, reconcilePair returned `wrote: counts.writes` — the
4
+ // WOULD-BE endpoint write total — even though nothing is written to disk. That
5
+ // contradicts the `sys sync -h` contract ("wrote … 0 under --dry-run") and the
6
+ // rendered "DRY RUN (nothing written)" header, which together promise 0. The
7
+ // fix makes `wrote` reflect ACTUAL writes (0 under dry-run) while still using
8
+ // the would-be counts internally to decide synced-vs-noop.
9
+ //
10
+ // This test FAILS on the pre-fix code (wrote === number of files that WOULD be
11
+ // written, e.g. 2 for a one-sided bootstrap) and PASSES on the current code.
12
+ //
13
+ // Run: node --import tsx/esm --test src/core/skill-sync/__tests__/dry-run-wrote-count.test.ts
14
+ import { test, before, after } from 'node:test';
15
+ import assert from 'node:assert/strict';
16
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';
17
+ import { tmpdir, homedir } from 'node:os';
18
+ import { join } from 'node:path';
19
+ import { reconcilePair } from '../engine.js';
20
+ import { DEFAULT_PROFILE } from '../profile.js';
21
+ // The engine resolves user-scope endpoints + the snapshot store off homedir(),
22
+ // which reads $HOME at call time — so isolating $HOME isolates everything.
23
+ let home;
24
+ let prevHome;
25
+ const PAIR = {
26
+ id: 'dryrun-regression',
27
+ crtr: { scope: 'user', name: 'dr' },
28
+ claude: { scope: 'user', name: 'dr' },
29
+ };
30
+ before(() => {
31
+ prevHome = process.env.HOME;
32
+ home = mkdtempSync(join(tmpdir(), 'crtr-sync-dryrun-'));
33
+ process.env.HOME = home;
34
+ // One-sided bootstrap: only the crtr side exists. A real sync here would
35
+ // materialize the Claude side + write the snapshot (2 endpoint files written).
36
+ const crtrDir = join(home, '.crouter', 'memory', 'dr');
37
+ mkdirSync(crtrDir, { recursive: true });
38
+ writeFileSync(join(crtrDir, 'SKILL.md'), '---\nkind: knowledge\nwhen-and-why-to-read: "When the regression applies, this doc should be read."\n---\n# DR\nbody\n', 'utf8');
39
+ });
40
+ after(() => {
41
+ if (prevHome === undefined)
42
+ delete process.env.HOME;
43
+ else
44
+ process.env.HOME = prevHome;
45
+ rmSync(home, { recursive: true, force: true });
46
+ assert.equal(homedir(), prevHome ?? homedir()); // sanity: $HOME restored
47
+ });
48
+ test('--dry-run reports wrote === 0 and writes nothing to disk', () => {
49
+ const res = reconcilePair(PAIR, DEFAULT_PROFILE, { dryRun: true });
50
+ // Status still reflects that a real run WOULD sync this pair...
51
+ assert.equal(res.status, 'synced', 'dry-run should still classify the pending change as synced');
52
+ // ...but the `wrote` count is ACTUAL writes — zero under --dry-run.
53
+ assert.equal(res.wrote, 0, 'dry-run must report wrote === 0 (contract: 0 under --dry-run)');
54
+ // And nothing was actually written: no Claude bundle, no snapshot.
55
+ assert.ok(!existsSync(join(home, '.claude', 'skills', 'dr', 'SKILL.md')), 'dry-run must not materialize the Claude endpoint');
56
+ assert.ok(!existsSync(join(home, '.crouter', 'skill-sync', 'snapshots', PAIR.id)), 'dry-run must not write the snapshot base');
57
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Built-in export registry for the skill-sync subsystem.
3
+ *
4
+ * These are the crtr-*generated* host artifacts — the `crtr-skills` boot skill
5
+ * and any CLI slash-commands. Unlike the user-authored bidirectional manifest
6
+ * pairs (engine.ts), an export pair has a single source of truth (code), so it
7
+ * is one-way: render → marker-guarded write (see export.ts). The generators
8
+ * (`bootSkillBody`, `renderSlashTemplate`) and their version markers were moved
9
+ * here verbatim from bootstrap.ts when skill-sync became the sole host writer —
10
+ * marker strings are byte-identical so already-deployed artifacts roll forward.
11
+ */
12
+ import type { RootDef } from '../command.js';
13
+ /** A host runtime crtr exports generated artifacts into. */
14
+ export type Host = 'claude' | 'pi';
15
+ /** One destination for an export artifact. Only targets whose host root exists
16
+ * are written — crtr never creates `~/.claude` or `~/.pi` itself (export.ts). */
17
+ export interface HostTarget {
18
+ host: Host;
19
+ /** The leaf dir the artifact is written into (never the host root). */
20
+ dir: () => string;
21
+ /** `bundle` → `<dir>/<name>/SKILL.md` ; `file` → `<dir>/<name>.md`. */
22
+ layout: 'bundle' | 'file';
23
+ }
24
+ /** One crtr-generated export artifact and every host it reaches. */
25
+ export interface ExportPair {
26
+ /** Stable id for logging, e.g. `@boot/crtr-skills` or `@cmd/<name>`. */
27
+ id: string;
28
+ /** On-disk artifact name (bundle dir name or file stem). */
29
+ name: string;
30
+ /** Marker prefix identifying a crtr-owned file (the clobber guard). */
31
+ markerPrefix: string;
32
+ /** Full file content incl. frontmatter + version marker. */
33
+ render: () => string;
34
+ /** Every host destination. Targets with an absent host root are skipped. */
35
+ targets: HostTarget[];
36
+ }
37
+ export declare const BOOT_SKILL_MARKER_PREFIX = "<!-- crtr-boot-skill v";
38
+ export declare const SLASH_CMD_MARKER_PREFIX = "<!-- crtr-mode-cmd v";
39
+ /** The crtr-generated export artifacts for this command tree: the boot skill
40
+ * (both hosts, bundle layout) plus one pair per opted-in slash command (both
41
+ * hosts, file layout). */
42
+ export declare function builtinExportPairs(root: RootDef): ExportPair[];
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Built-in export registry for the skill-sync subsystem.
3
+ *
4
+ * These are the crtr-*generated* host artifacts — the `crtr-skills` boot skill
5
+ * and any CLI slash-commands. Unlike the user-authored bidirectional manifest
6
+ * pairs (engine.ts), an export pair has a single source of truth (code), so it
7
+ * is one-way: render → marker-guarded write (see export.ts). The generators
8
+ * (`bootSkillBody`, `renderSlashTemplate`) and their version markers were moved
9
+ * here verbatim from bootstrap.ts when skill-sync became the sole host writer —
10
+ * marker strings are byte-identical so already-deployed artifacts roll forward.
11
+ */
12
+ import { homedir } from 'node:os';
13
+ import { join } from 'node:path';
14
+ import { collectSlashSpecs } from '../command.js';
15
+ // ── Boot skill ───────────────────────────────────────────────────────────────
16
+ const BOOT_SKILL_NAME = 'crtr-skills';
17
+ const BOOT_SKILL_MARKER = '<!-- crtr-boot-skill v2 -->';
18
+ export const BOOT_SKILL_MARKER_PREFIX = '<!-- crtr-boot-skill v';
19
+ function bootSkillBody() {
20
+ return `---
21
+ name: crtr-skills
22
+ description: 'Author, list, search, and load crtr memory documents (knowledge, preferences) via the crtr CLI. These are durable agent memory — markdown future LLM sessions load on demand. Use when the user wants to remember/save knowledge, build a context primer, or recall a previously saved doc. Triggers: "save", "remember", "build context for", "what skills do we have", "skill for X".'
23
+ argument-hint: [topic or verb]
24
+ ---
25
+
26
+ ${BOOT_SKILL_MARKER}
27
+
28
+ # /crtr-skills — crtr memory router
29
+
30
+ crtr memory documents = durable agent memory (kind: knowledge or preference).
31
+ Written for **future LLM sessions**, not the user. \`crtr memory\` is the
32
+ surface — author, discover, and load them.
33
+
34
+ ## Route by intent
35
+
36
+ - **Capture** ("save", "remember", "build context for", "make a skill"):
37
+ \`crtr memory write <name> --kind <knowledge|preference>\` with the
38
+ body piped on stdin — e.g.
39
+ \`crtr memory write $ARGUMENTS --kind knowledge <<'EOF' … EOF\`. Run
40
+ \`crtr memory write -h\` for the full frontmatter schema
41
+ (--when-and-why-to-read, --short-form, --scope, visibility rungs), then
42
+ \`crtr memory lint\` to validate what you wrote.
43
+ - **Find** ("what do we have on X"): \`crtr memory find "$ARGUMENTS"\` →
44
+ \`crtr memory read <name>\` on the best hit (add --body/--grep to search
45
+ bodies).
46
+ - **Load by name**: \`crtr memory read <name>\`.
47
+ - **List all**: \`crtr memory list\`.
48
+
49
+ If \`$ARGUMENTS\` is empty, ask the user what they want before running.
50
+
51
+ ## Rules
52
+
53
+ - CLI stdout is the prompt — act on it, don't paraphrase to the user.
54
+ - Append \`-h\` at any leaf (\`crtr memory write -h\`) for its full schema
55
+ before authoring.
56
+ - If \`crtr\` is not on PATH, tell the user and stop.
57
+ `;
58
+ }
59
+ // ── Slash commands ───────────────────────────────────────────────────────────
60
+ const SLASH_CMD_MARKER = '<!-- crtr-mode-cmd v1 -->';
61
+ export const SLASH_CMD_MARKER_PREFIX = '<!-- crtr-mode-cmd v';
62
+ /** Render a SlashSpec to a full template file (frontmatter + marker + body). */
63
+ function renderSlashTemplate(spec) {
64
+ const hint = spec.argumentHint !== undefined
65
+ ? `argument-hint: ${JSON.stringify(spec.argumentHint)}\n`
66
+ : '';
67
+ return `---\ndescription: ${spec.description}\n${hint}---\n\n${SLASH_CMD_MARKER}\n\n${spec.body}\n`;
68
+ }
69
+ // ── Host roots ───────────────────────────────────────────────────────────────
70
+ function claudeSkillsRoot() {
71
+ return join(homedir(), '.claude', 'skills');
72
+ }
73
+ function piSkillsRoot() {
74
+ return join(homedir(), '.pi', 'agent', 'skills');
75
+ }
76
+ function claudeCommandsRoot() {
77
+ return join(homedir(), '.claude', 'commands');
78
+ }
79
+ function piPromptsRoot() {
80
+ return join(homedir(), '.pi', 'agent', 'prompts');
81
+ }
82
+ // ── Registry ─────────────────────────────────────────────────────────────────
83
+ /** The crtr-generated export artifacts for this command tree: the boot skill
84
+ * (both hosts, bundle layout) plus one pair per opted-in slash command (both
85
+ * hosts, file layout). */
86
+ export function builtinExportPairs(root) {
87
+ const pairs = [
88
+ {
89
+ id: '@boot/crtr-skills',
90
+ name: BOOT_SKILL_NAME,
91
+ markerPrefix: BOOT_SKILL_MARKER_PREFIX,
92
+ render: bootSkillBody,
93
+ targets: [
94
+ { host: 'claude', dir: claudeSkillsRoot, layout: 'bundle' },
95
+ { host: 'pi', dir: piSkillsRoot, layout: 'bundle' },
96
+ ],
97
+ },
98
+ ];
99
+ for (const spec of collectSlashSpecs(root)) {
100
+ pairs.push({
101
+ id: `@cmd/${spec.name}`,
102
+ name: spec.name,
103
+ markerPrefix: SLASH_CMD_MARKER_PREFIX,
104
+ render: () => renderSlashTemplate(spec),
105
+ targets: [
106
+ { host: 'claude', dir: claudeCommandsRoot, layout: 'file' },
107
+ { host: 'pi', dir: piPromptsRoot, layout: 'file' },
108
+ ],
109
+ });
110
+ }
111
+ return pairs;
112
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Claude-plugin endpoint resolution for skill-sync.
3
+ *
4
+ * The `claude-plugin` endpoint scope addresses a skill IN PLACE at the path
5
+ * where Claude actually installs the owning plugin — `<installPath>/skills/<name>`
6
+ * — instead of forcing a copy under `~/.claude/skills/`. The install path is read
7
+ * from Claude's OWN registry (`~/.claude/plugins/installed_plugins.json`), keyed
8
+ * by the marketplace-qualified plugin key (`<plugin>@<marketplace>`), gated on the
9
+ * enable state in `~/.claude/settings.json` → `enabledPlugins`.
10
+ *
11
+ * This is Claude's registry, deliberately NOT crtr's plugin registry
12
+ * (`resolver.ts` / `findPluginByName`): the two are independent installs.
13
+ *
14
+ * Strictness (no lenient fallback): a key that is unknown, disabled, or whose
15
+ * recorded install path is missing on disk is a HARD error naming it.
16
+ */
17
+ /**
18
+ * Resolve a Claude plugin's on-disk install path from its marketplace-qualified
19
+ * key (`<plugin>@<marketplace>`, e.g. `devcore@crouton-kit`). Throws naming the
20
+ * key when it is not installed, not enabled, or has no install path present on
21
+ * disk.
22
+ */
23
+ export declare function resolveClaudePluginInstallPath(key: string): string;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Claude-plugin endpoint resolution for skill-sync.
3
+ *
4
+ * The `claude-plugin` endpoint scope addresses a skill IN PLACE at the path
5
+ * where Claude actually installs the owning plugin — `<installPath>/skills/<name>`
6
+ * — instead of forcing a copy under `~/.claude/skills/`. The install path is read
7
+ * from Claude's OWN registry (`~/.claude/plugins/installed_plugins.json`), keyed
8
+ * by the marketplace-qualified plugin key (`<plugin>@<marketplace>`), gated on the
9
+ * enable state in `~/.claude/settings.json` → `enabledPlugins`.
10
+ *
11
+ * This is Claude's registry, deliberately NOT crtr's plugin registry
12
+ * (`resolver.ts` / `findPluginByName`): the two are independent installs.
13
+ *
14
+ * Strictness (no lenient fallback): a key that is unknown, disabled, or whose
15
+ * recorded install path is missing on disk is a HARD error naming it.
16
+ */
17
+ import { existsSync, readFileSync } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { join } from 'node:path';
20
+ import { usage } from '../errors.js';
21
+ function claudeDir() {
22
+ return join(homedir(), '.claude');
23
+ }
24
+ /**
25
+ * Resolve a Claude plugin's on-disk install path from its marketplace-qualified
26
+ * key (`<plugin>@<marketplace>`, e.g. `devcore@crouton-kit`). Throws naming the
27
+ * key when it is not installed, not enabled, or has no install path present on
28
+ * disk.
29
+ */
30
+ export function resolveClaudePluginInstallPath(key) {
31
+ const registryPath = join(claudeDir(), 'plugins', 'installed_plugins.json');
32
+ if (!existsSync(registryPath)) {
33
+ throw usage(`skill-sync: Claude plugin registry not found at ${registryPath}`);
34
+ }
35
+ let registry;
36
+ try {
37
+ registry = JSON.parse(readFileSync(registryPath, 'utf8'));
38
+ }
39
+ catch (err) {
40
+ throw usage(`skill-sync: invalid ${registryPath}: ${err.message}`);
41
+ }
42
+ const entries = registry.plugins?.[key];
43
+ if (!Array.isArray(entries) || entries.length === 0) {
44
+ throw usage(`skill-sync: Claude plugin "${key}" is not installed ` +
45
+ `(no entry in ${registryPath})`);
46
+ }
47
+ if (!isClaudePluginEnabled(key)) {
48
+ throw usage(`skill-sync: Claude plugin "${key}" is installed but not enabled ` +
49
+ `(settings.json enabledPlugins)`);
50
+ }
51
+ const install = entries.find((e) => e.installPath && existsSync(e.installPath));
52
+ if (!install?.installPath) {
53
+ throw usage(`skill-sync: Claude plugin "${key}" has no install path present on disk`);
54
+ }
55
+ return install.installPath;
56
+ }
57
+ /** True iff `enabledPlugins[key] === true` in `~/.claude/settings.json`. A
58
+ * missing or unparseable settings file means "not enabled" — the caller turns
59
+ * that into a hard error naming the key. */
60
+ function isClaudePluginEnabled(key) {
61
+ const settingsPath = join(claudeDir(), 'settings.json');
62
+ if (!existsSync(settingsPath))
63
+ return false;
64
+ try {
65
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
66
+ return settings.enabledPlugins?.[key] === true;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Reconcile engine (Phase P6) — the single, per-pair, direction-agnostic
3
+ * reconcile for bidirectional crtr ↔ Claude skill sync.
4
+ *
5
+ * `reconcilePair` is the ONLY reconcile routine (R-U3): there is no forward
6
+ * path + reverse path. Body and asset reconciliation is a symmetric 3-way merge
7
+ * (base = snapshot, ours = crtr, theirs = claude); frontmatter is the only
8
+ * asymmetric axis and all of that asymmetry lives in the translation profile
9
+ * (P5). The snapshot (P3) is the sole change-detector (R-U4): nothing here reads
10
+ * mtimes, fingerprints, or a stamp file.
11
+ *
12
+ * Strictness (R-U6): a conflict — overlapping body/asset merge, a both-sides
13
+ * non-equivalent frontmatter divergence, an asset deleted-on-one ∧ edited-on-the
14
+ * other, or a first-sync divergence with no common base — writes NOTHING for the
15
+ * pair, leaves both endpoints + the snapshot byte-untouched, and surfaces a
16
+ * git-style conflict report. There is no "best-effort" partial write and no
17
+ * silent side-pick.
18
+ *
19
+ * `--dry-run` (R-O1): every merge is computed and the would-be status returned,
20
+ * but no endpoint file, snapshot, or conflict report is touched.
21
+ */
22
+ import type { Pair } from './manifest.js';
23
+ import type { TranslationProfile } from './profile.js';
24
+ /** The outcome of reconciling one pair. `wrote` counts endpoint files
25
+ * created/updated (never deletions, never the snapshot bookkeeping). */
26
+ export interface PairResult {
27
+ id: string;
28
+ status: 'synced' | 'conflict' | 'noop';
29
+ wrote: number;
30
+ }
31
+ export interface ReconcileOpts {
32
+ dryRun: boolean;
33
+ }
34
+ /**
35
+ * Reconcile a single enrolled pair (R-E2). Resolves both endpoints, loads (or
36
+ * seeds) the snapshot, 3-way merges body + assets and translate-and-owns the
37
+ * frontmatter, then either writes through cleanly to both sides or reports the
38
+ * conflict and skips. Never throws on a *content* conflict (that is a reported
39
+ * outcome); throws only on a structural/configuration error (an unresolvable
40
+ * endpoint, both endpoints missing) so the caller surfaces it as a hard stop.
41
+ */
42
+ export declare function reconcilePair(pair: Pair, profile: TranslationProfile, opts: ReconcileOpts): PairResult;