@crouton-kit/crouter 0.3.13 → 0.3.15

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 (233) hide show
  1. package/dist/build-root.d.ts +8 -0
  2. package/dist/build-root.js +30 -0
  3. package/dist/builtin-personas/design/base.md +3 -7
  4. package/dist/builtin-personas/design/orchestrator.md +4 -3
  5. package/dist/builtin-personas/developer/base.md +3 -7
  6. package/dist/builtin-personas/developer/orchestrator.md +5 -4
  7. package/dist/builtin-personas/explore/base.md +3 -7
  8. package/dist/builtin-personas/explore/orchestrator.md +1 -5
  9. package/dist/builtin-personas/general/base.md +2 -4
  10. package/dist/builtin-personas/general/orchestrator.md +2 -4
  11. package/dist/builtin-personas/lifecycle/resident.md +2 -0
  12. package/dist/builtin-personas/lifecycle/terminal.md +6 -0
  13. package/dist/builtin-personas/orchestration-kernel.md +42 -3
  14. package/dist/builtin-personas/plan/base.md +3 -5
  15. package/dist/builtin-personas/plan/orchestrator.md +5 -4
  16. package/dist/builtin-personas/plan/reviewers/architecture-fit/base.md +9 -0
  17. package/dist/builtin-personas/plan/reviewers/code-smells/base.md +9 -0
  18. package/dist/builtin-personas/plan/reviewers/pattern-consistency/base.md +9 -0
  19. package/dist/builtin-personas/plan/reviewers/requirements-coverage/base.md +9 -0
  20. package/dist/builtin-personas/plan/reviewers/security/base.md +9 -0
  21. package/dist/builtin-personas/review/base.md +3 -5
  22. package/dist/builtin-personas/review/orchestrator.md +2 -6
  23. package/dist/builtin-personas/runtime-base.md +3 -19
  24. package/dist/builtin-personas/spec/base.md +3 -5
  25. package/dist/builtin-personas/spec/orchestrator.md +4 -3
  26. package/dist/builtin-personas/spine/has-manager.md +10 -0
  27. package/dist/builtin-personas/spine/no-manager.md +2 -0
  28. package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +96 -0
  29. package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +49 -0
  30. package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +49 -0
  31. package/dist/builtin-skills/skills/planning/SKILL.md +1 -1
  32. package/dist/builtin-skills/skills/spec/SKILL.md +2 -2
  33. package/dist/cli.js +6 -29
  34. package/dist/commands/__tests__/human.test.js +73 -2
  35. package/dist/commands/attention.js +76 -7
  36. package/dist/commands/canvas-prune.d.ts +2 -0
  37. package/dist/commands/canvas-prune.js +66 -0
  38. package/dist/commands/canvas.js +5 -8
  39. package/dist/commands/chord.d.ts +2 -0
  40. package/dist/commands/chord.js +143 -0
  41. package/dist/commands/daemon.js +8 -5
  42. package/dist/commands/dashboard.js +2 -0
  43. package/dist/commands/human/prompts.js +28 -27
  44. package/dist/commands/human/queue.d.ts +1 -0
  45. package/dist/commands/human/queue.js +105 -2
  46. package/dist/commands/human/shared.d.ts +28 -18
  47. package/dist/commands/human/shared.js +53 -60
  48. package/dist/commands/human.js +6 -14
  49. package/dist/commands/node.d.ts +11 -0
  50. package/dist/commands/node.js +381 -87
  51. package/dist/commands/pkg/market-inspect.js +6 -4
  52. package/dist/commands/pkg/market-manage.js +10 -6
  53. package/dist/commands/pkg/market.js +2 -4
  54. package/dist/commands/pkg/plugin-inspect.js +6 -4
  55. package/dist/commands/pkg/plugin-manage.js +12 -7
  56. package/dist/commands/pkg/plugin.js +2 -4
  57. package/dist/commands/pkg.js +0 -4
  58. package/dist/commands/push.js +178 -15
  59. package/dist/commands/revive.js +5 -3
  60. package/dist/commands/skill/author.js +6 -4
  61. package/dist/commands/skill/find.js +8 -5
  62. package/dist/commands/skill/read.js +2 -0
  63. package/dist/commands/skill/state.js +6 -4
  64. package/dist/commands/skill.js +0 -6
  65. package/dist/commands/sys/config.js +21 -7
  66. package/dist/commands/sys/doctor.js +2 -0
  67. package/dist/commands/sys/update.js +4 -0
  68. package/dist/commands/sys.js +0 -6
  69. package/dist/commands/tmux-spread.d.ts +2 -0
  70. package/dist/commands/tmux-spread.js +130 -0
  71. package/dist/core/__tests__/canvas-inbox-watcher.test.js +25 -0
  72. package/dist/core/__tests__/child-followup.test.d.ts +1 -0
  73. package/dist/core/__tests__/child-followup.test.js +83 -0
  74. package/dist/core/__tests__/close.test.d.ts +1 -0
  75. package/dist/core/__tests__/close.test.js +148 -0
  76. package/dist/core/__tests__/context-intro.test.d.ts +1 -0
  77. package/dist/core/__tests__/context-intro.test.js +196 -0
  78. package/dist/core/__tests__/daemon-boot.test.d.ts +1 -0
  79. package/dist/core/__tests__/daemon-boot.test.js +93 -0
  80. package/dist/core/__tests__/daemon-liveness.test.d.ts +1 -0
  81. package/dist/core/__tests__/daemon-liveness.test.js +223 -0
  82. package/dist/core/__tests__/focuses.test.d.ts +1 -0
  83. package/dist/core/__tests__/focuses.test.js +259 -0
  84. package/dist/core/__tests__/fork.test.d.ts +1 -0
  85. package/dist/core/__tests__/fork.test.js +91 -0
  86. package/dist/core/__tests__/home-session.test.d.ts +1 -0
  87. package/dist/core/__tests__/home-session.test.js +153 -0
  88. package/dist/core/__tests__/human-cancel-guard.test.d.ts +1 -0
  89. package/dist/core/__tests__/human-cancel-guard.test.js +49 -0
  90. package/dist/core/__tests__/keystone.test.d.ts +1 -0
  91. package/dist/core/__tests__/keystone.test.js +185 -0
  92. package/dist/core/__tests__/kickoff.test.d.ts +1 -0
  93. package/dist/core/__tests__/kickoff.test.js +89 -0
  94. package/dist/core/__tests__/lifecycle.test.d.ts +1 -0
  95. package/dist/core/__tests__/lifecycle.test.js +178 -0
  96. package/dist/core/__tests__/listing-completeness.test.d.ts +1 -0
  97. package/dist/core/__tests__/listing-completeness.test.js +31 -0
  98. package/dist/core/__tests__/memory.test.d.ts +1 -0
  99. package/dist/core/__tests__/memory.test.js +152 -0
  100. package/dist/core/__tests__/migration.test.d.ts +1 -0
  101. package/dist/core/__tests__/migration.test.js +238 -0
  102. package/dist/core/__tests__/pane-column.test.d.ts +1 -0
  103. package/dist/core/__tests__/pane-column.test.js +153 -0
  104. package/dist/core/__tests__/passive-subscription.test.d.ts +1 -0
  105. package/dist/core/__tests__/passive-subscription.test.js +164 -0
  106. package/dist/core/__tests__/persona-compose.test.d.ts +1 -0
  107. package/dist/core/__tests__/persona-compose.test.js +53 -0
  108. package/dist/core/__tests__/persona-subkind.test.d.ts +1 -0
  109. package/dist/core/__tests__/persona-subkind.test.js +62 -0
  110. package/dist/core/__tests__/persona.test.d.ts +1 -0
  111. package/dist/core/__tests__/persona.test.js +107 -0
  112. package/dist/core/__tests__/placement-focus.test.d.ts +1 -0
  113. package/dist/core/__tests__/placement-focus.test.js +244 -0
  114. package/dist/core/__tests__/placement-reconcile.test.d.ts +1 -0
  115. package/dist/core/__tests__/placement-reconcile.test.js +212 -0
  116. package/dist/core/__tests__/placement-revive.test.d.ts +1 -0
  117. package/dist/core/__tests__/placement-revive.test.js +238 -0
  118. package/dist/core/__tests__/placement-teardown.test.d.ts +1 -0
  119. package/dist/core/__tests__/placement-teardown.test.js +183 -0
  120. package/dist/core/__tests__/prune.test.d.ts +1 -0
  121. package/dist/core/__tests__/prune.test.js +116 -0
  122. package/dist/core/__tests__/push-final-guard.test.d.ts +1 -0
  123. package/dist/core/__tests__/push-final-guard.test.js +71 -0
  124. package/dist/core/__tests__/relaunch.test.d.ts +1 -0
  125. package/dist/core/__tests__/relaunch.test.js +328 -0
  126. package/dist/core/__tests__/reset.test.js +26 -7
  127. package/dist/core/__tests__/revive.test.d.ts +1 -0
  128. package/dist/core/__tests__/revive.test.js +217 -0
  129. package/dist/core/__tests__/spawn-root.test.d.ts +1 -0
  130. package/dist/core/__tests__/spawn-root.test.js +73 -0
  131. package/dist/core/__tests__/steer-note.test.d.ts +1 -0
  132. package/dist/core/__tests__/steer-note.test.js +39 -0
  133. package/dist/core/__tests__/stop-guard.test.d.ts +1 -0
  134. package/dist/core/__tests__/stop-guard.test.js +82 -0
  135. package/dist/core/__tests__/subcommand-tier.test.d.ts +1 -0
  136. package/dist/core/__tests__/subcommand-tier.test.js +99 -0
  137. package/dist/core/__tests__/tmux-surface.test.d.ts +1 -0
  138. package/dist/core/__tests__/tmux-surface.test.js +106 -0
  139. package/dist/core/__tests__/unknown-path.test.js +8 -2
  140. package/dist/core/canvas/attention.d.ts +10 -0
  141. package/dist/core/canvas/attention.js +40 -0
  142. package/dist/core/canvas/canvas.d.ts +66 -7
  143. package/dist/core/canvas/canvas.js +209 -21
  144. package/dist/core/canvas/db.d.ts +8 -0
  145. package/dist/core/canvas/db.js +206 -4
  146. package/dist/core/canvas/focuses.d.ts +22 -0
  147. package/dist/core/canvas/focuses.js +80 -0
  148. package/dist/core/canvas/index.d.ts +3 -0
  149. package/dist/core/canvas/index.js +3 -0
  150. package/dist/core/canvas/labels.d.ts +27 -0
  151. package/dist/core/canvas/labels.js +36 -0
  152. package/dist/core/canvas/paths.d.ts +4 -0
  153. package/dist/core/canvas/paths.js +6 -0
  154. package/dist/core/canvas/render.js +25 -10
  155. package/dist/core/canvas/telemetry.d.ts +14 -0
  156. package/dist/core/canvas/telemetry.js +35 -0
  157. package/dist/core/canvas/types.d.ts +115 -12
  158. package/dist/core/command.d.ts +25 -1
  159. package/dist/core/command.js +48 -7
  160. package/dist/core/config.js +36 -2
  161. package/dist/core/feed/feed.js +14 -12
  162. package/dist/core/feed/inbox.d.ts +3 -1
  163. package/dist/core/feed/inbox.js +45 -5
  164. package/dist/core/feed/passive.d.ts +17 -0
  165. package/dist/core/feed/passive.js +92 -0
  166. package/dist/core/help.d.ts +59 -13
  167. package/dist/core/help.js +73 -28
  168. package/dist/core/personas/index.d.ts +1 -1
  169. package/dist/core/personas/index.js +1 -1
  170. package/dist/core/personas/loader.d.ts +40 -1
  171. package/dist/core/personas/loader.js +63 -1
  172. package/dist/core/personas/resolve.d.ts +13 -6
  173. package/dist/core/personas/resolve.js +46 -34
  174. package/dist/core/runtime/bearings.d.ts +20 -0
  175. package/dist/core/runtime/bearings.js +92 -0
  176. package/dist/core/runtime/close.d.ts +14 -0
  177. package/dist/core/runtime/close.js +151 -0
  178. package/dist/core/runtime/demote.d.ts +14 -0
  179. package/dist/core/runtime/demote.js +120 -0
  180. package/dist/core/runtime/front-door.js +1 -1
  181. package/dist/core/runtime/kickoff.d.ts +32 -6
  182. package/dist/core/runtime/kickoff.js +111 -37
  183. package/dist/core/runtime/launch.d.ts +29 -6
  184. package/dist/core/runtime/launch.js +85 -13
  185. package/dist/core/runtime/lifecycle.d.ts +13 -0
  186. package/dist/core/runtime/lifecycle.js +86 -0
  187. package/dist/core/runtime/memory.d.ts +43 -0
  188. package/dist/core/runtime/memory.js +165 -0
  189. package/dist/core/runtime/naming.d.ts +22 -0
  190. package/dist/core/runtime/naming.js +166 -0
  191. package/dist/core/runtime/nodes.d.ts +32 -1
  192. package/dist/core/runtime/nodes.js +60 -10
  193. package/dist/core/runtime/persona.d.ts +25 -0
  194. package/dist/core/runtime/persona.js +139 -0
  195. package/dist/core/runtime/placement.d.ts +287 -0
  196. package/dist/core/runtime/placement.js +663 -0
  197. package/dist/core/runtime/presence.d.ts +7 -32
  198. package/dist/core/runtime/presence.js +90 -110
  199. package/dist/core/runtime/promote.d.ts +18 -7
  200. package/dist/core/runtime/promote.js +70 -65
  201. package/dist/core/runtime/reset.d.ts +47 -4
  202. package/dist/core/runtime/reset.js +223 -52
  203. package/dist/core/runtime/revive.d.ts +26 -2
  204. package/dist/core/runtime/revive.js +166 -39
  205. package/dist/core/runtime/roadmap.d.ts +5 -4
  206. package/dist/core/runtime/roadmap.js +9 -16
  207. package/dist/core/runtime/spawn.d.ts +20 -5
  208. package/dist/core/runtime/spawn.js +169 -44
  209. package/dist/core/runtime/stop-guard.d.ts +1 -1
  210. package/dist/core/runtime/stop-guard.js +18 -8
  211. package/dist/core/runtime/tmux.d.ts +106 -21
  212. package/dist/core/runtime/tmux.js +249 -45
  213. package/dist/core/spawn.js +15 -0
  214. package/dist/daemon/crtrd.d.ts +12 -1
  215. package/dist/daemon/crtrd.js +152 -34
  216. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.d.ts +1 -0
  217. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +266 -0
  218. package/dist/pi-extensions/canvas-commands.d.ts +34 -0
  219. package/dist/pi-extensions/canvas-commands.js +103 -0
  220. package/dist/pi-extensions/canvas-context-intro.d.ts +70 -0
  221. package/dist/pi-extensions/canvas-context-intro.js +164 -0
  222. package/dist/pi-extensions/canvas-goal-capture.d.ts +21 -0
  223. package/dist/pi-extensions/canvas-goal-capture.js +67 -0
  224. package/dist/pi-extensions/canvas-inbox-watcher.js +11 -0
  225. package/dist/pi-extensions/canvas-nav.d.ts +12 -4
  226. package/dist/pi-extensions/canvas-nav.js +586 -262
  227. package/dist/pi-extensions/canvas-passive-context.d.ts +32 -0
  228. package/dist/pi-extensions/canvas-passive-context.js +114 -0
  229. package/dist/pi-extensions/canvas-stophook.d.ts +16 -0
  230. package/dist/pi-extensions/canvas-stophook.js +344 -228
  231. package/dist/types.d.ts +28 -0
  232. package/dist/types.js +16 -0
  233. package/package.json +1 -1
@@ -8,6 +8,8 @@ import { resolveScopeArg, projectScopeRoot } from '../../core/scope.js';
8
8
  // ---------------------------------------------------------------------------
9
9
  const marketList = defineLeaf({
10
10
  name: 'list',
11
+ description: 'list registered marketplaces',
12
+ whenToUse: 'listing which marketplaces are registered, with their git URL, ref, and scope',
11
13
  help: {
12
14
  name: 'pkg market inspect list',
13
15
  summary: 'list registered marketplaces',
@@ -76,6 +78,8 @@ const marketList = defineLeaf({
76
78
  // ---------------------------------------------------------------------------
77
79
  const marketBrowse = defineLeaf({
78
80
  name: 'browse',
81
+ description: 'list plugins available in a marketplace',
82
+ whenToUse: 'exploring what a marketplace offers so you can decide before installing — lists every plugin in a marketplace index with its description, keywords, version, and whether it is already installed. Reach for this to pick which plugin to pull, then install it by name with `pkg market manage install`',
79
83
  help: {
80
84
  name: 'pkg market inspect browse',
81
85
  summary: 'list plugins available in a marketplace',
@@ -145,13 +149,11 @@ const marketBrowse = defineLeaf({
145
149
  });
146
150
  export const marketInspectBranch = defineBranch({
147
151
  name: 'inspect',
152
+ description: 'list or browse marketplaces',
153
+ whenToUse: 'reading marketplace metadata to decide before you install — list registered marketplaces, or browse the plugins available in one marketplace. Read-only; switch to `pkg market manage` to add a marketplace or install a plugin from it',
148
154
  help: {
149
155
  name: 'pkg market inspect',
150
156
  summary: 'read marketplace metadata without modifying state',
151
- children: [
152
- { name: 'list', desc: 'list registered marketplaces', useWhen: 'seeing which marketplaces are configured' },
153
- { name: 'browse', desc: 'list plugins available in a marketplace', useWhen: 'exploring what a marketplace offers before installing' },
154
- ],
155
157
  },
156
158
  children: [marketList, marketBrowse],
157
159
  });
@@ -14,6 +14,8 @@ import { resolveInstallScope } from './shared.js';
14
14
  // ---------------------------------------------------------------------------
15
15
  const marketAdd = defineLeaf({
16
16
  name: 'add',
17
+ description: 'add a marketplace by git URL',
18
+ whenToUse: 'registering a new marketplace by git URL so its plugins become installable by name',
17
19
  help: {
18
20
  name: 'pkg market manage add',
19
21
  summary: 'add a marketplace by git URL',
@@ -74,6 +76,8 @@ const marketAdd = defineLeaf({
74
76
  // ---------------------------------------------------------------------------
75
77
  const marketRemove = defineLeaf({
76
78
  name: 'remove',
79
+ description: 'remove a marketplace',
80
+ whenToUse: 'unregistering a marketplace: deletes it and any plugins installed from it',
77
81
  help: {
78
82
  name: 'pkg market manage remove',
79
83
  summary: 'remove a marketplace and its directory',
@@ -148,6 +152,8 @@ const marketRemove = defineLeaf({
148
152
  // ---------------------------------------------------------------------------
149
153
  const marketUpdate = defineLeaf({
150
154
  name: 'update',
155
+ description: 'pull latest marketplace index',
156
+ whenToUse: 'refreshing the plugin index of one named marketplace from git, or of all registered marketplaces when no name is given',
151
157
  help: {
152
158
  name: 'pkg market manage update',
153
159
  summary: 'git pull updates for one or all registered marketplaces',
@@ -234,6 +240,8 @@ const marketUpdate = defineLeaf({
234
240
  // ---------------------------------------------------------------------------
235
241
  const marketInstall = defineLeaf({
236
242
  name: 'install',
243
+ description: 'install a plugin from a marketplace',
244
+ whenToUse: 'installing a plugin by name from an already-registered marketplace, e.g. `pkg market manage install --marketplace <m> --plugin <p>`',
237
245
  help: {
238
246
  name: 'pkg market manage install',
239
247
  summary: 'install a plugin from an added marketplace by plugin name',
@@ -302,15 +310,11 @@ const marketInstall = defineLeaf({
302
310
  });
303
311
  export const marketManageBranch = defineBranch({
304
312
  name: 'manage',
313
+ description: 'add, remove, update, install',
314
+ whenToUse: 'changing marketplace state: add or remove a marketplace, refresh its index, or install a plugin from it',
305
315
  help: {
306
316
  name: 'pkg market manage',
307
317
  summary: 'add, remove, update, or install from marketplaces',
308
- children: [
309
- { name: 'add', desc: 'add a marketplace by git URL', useWhen: 'registering a new marketplace source' },
310
- { name: 'remove', desc: 'remove a marketplace', useWhen: 'unregistering a marketplace' },
311
- { name: 'update', desc: 'pull latest marketplace index', useWhen: 'refreshing the marketplace plugin list' },
312
- { name: 'install', desc: 'install a plugin from a marketplace', useWhen: 'adding a plugin sourced from a registered marketplace' },
313
- ],
314
318
  },
315
319
  children: [marketAdd, marketRemove, marketUpdate, marketInstall],
316
320
  });
@@ -3,14 +3,12 @@ import { marketManageBranch } from './market-manage.js';
3
3
  import { marketInspectBranch } from './market-inspect.js';
4
4
  export const marketBranch = defineBranch({
5
5
  name: 'market',
6
+ description: 'manage marketplace sources and install from them',
7
+ whenToUse: 'using curated plugin collections instead of raw git URLs — register a marketplace, browse its index of plugins, then install them by name. Use `pkg plugin` instead when you already have a specific git URL or local plugin to install directly, or when inspecting and toggling plugins that are already installed',
6
8
  help: {
7
9
  name: 'pkg market',
8
10
  summary: 'manage and browse plugin marketplaces',
9
11
  model: 'Marketplaces are git repos containing a .crouter-marketplace/marketplace.json index of plugins.',
10
- children: [
11
- { name: 'manage', desc: 'add, remove, update, install', useWhen: 'changing marketplace or marketplace-sourced plugin state' },
12
- { name: 'inspect', desc: 'list or browse marketplaces', useWhen: 'reading marketplace metadata' },
13
- ],
14
12
  },
15
13
  children: [marketManageBranch, marketInspectBranch],
16
14
  });
@@ -8,6 +8,8 @@ import { resolveScopeArg, projectScopeRoot } from '../../core/scope.js';
8
8
  // ---------------------------------------------------------------------------
9
9
  const pluginList = defineLeaf({
10
10
  name: 'list',
11
+ description: 'paginated list of installed plugins',
12
+ whenToUse: 'enumerating which plugins are installed across user and project scope, optionally including disabled ones',
11
13
  help: {
12
14
  name: 'pkg plugin inspect list',
13
15
  summary: 'paginated list of installed plugins',
@@ -76,6 +78,8 @@ const pluginList = defineLeaf({
76
78
  // ---------------------------------------------------------------------------
77
79
  const pluginShow = defineLeaf({
78
80
  name: 'show',
81
+ description: 'read plugin manifest and skill inventory',
82
+ whenToUse: 'reading one installed plugin in detail to decide before you enable, disable, or remove it — returns the full manifest, the skills it provides, its scope, and whether it is currently enabled. Use `pkg plugin inspect list` instead to enumerate plugins rather than drill into one',
79
83
  help: {
80
84
  name: 'pkg plugin inspect show',
81
85
  summary: 'read plugin manifest and metadata by name',
@@ -130,13 +134,11 @@ const pluginShow = defineLeaf({
130
134
  });
131
135
  export const pluginInspectBranch = defineBranch({
132
136
  name: 'inspect',
137
+ description: 'list or show installed plugins',
138
+ whenToUse: 'reading installed plugin metadata to decide before you change anything — list what is installed, or show the manifest and skills of one plugin. Read-only; switch to `pkg plugin manage` to actually install, enable, disable, or remove',
133
139
  help: {
134
140
  name: 'pkg plugin inspect',
135
141
  summary: 'read plugin metadata without modifying state',
136
- children: [
137
- { name: 'list', desc: 'paginated list of installed plugins', useWhen: 'enumerating what plugins are installed' },
138
- { name: 'show', desc: 'read plugin manifest and skill inventory', useWhen: 'inspecting a specific plugin\'s details' },
139
- ],
140
142
  },
141
143
  children: [pluginList, pluginShow],
142
144
  });
@@ -14,6 +14,8 @@ import { isGitUrl, setPluginEnabled, resolveInstallScope } from './shared.js';
14
14
  // ---------------------------------------------------------------------------
15
15
  const pluginInstall = defineLeaf({
16
16
  name: 'install',
17
+ description: 'install from a git URL',
18
+ whenToUse: 'installing a plugin directly from a git URL into a scope, e.g. `pkg plugin manage install https://github.com/org/repo`',
17
19
  help: {
18
20
  name: 'pkg plugin manage install',
19
21
  summary: 'install a plugin from a git URL into the given scope',
@@ -86,6 +88,8 @@ const pluginInstall = defineLeaf({
86
88
  // ---------------------------------------------------------------------------
87
89
  const pluginRemove = defineLeaf({
88
90
  name: 'remove',
91
+ description: 'remove plugin and directory',
92
+ whenToUse: 'uninstalling a plugin: deletes its directory and removes it from config',
89
93
  help: {
90
94
  name: 'pkg plugin manage remove',
91
95
  summary: 'remove a plugin and its directory from the given scope',
@@ -141,6 +145,8 @@ const pluginRemove = defineLeaf({
141
145
  // ---------------------------------------------------------------------------
142
146
  const pluginEnable = defineLeaf({
143
147
  name: 'enable',
148
+ description: 'enable a plugin',
149
+ whenToUse: 'reactivating a plugin that was previously disabled so its skills resolve again',
144
150
  help: {
145
151
  name: 'pkg plugin manage enable',
146
152
  summary: 'enable a plugin in the given scope',
@@ -160,6 +166,8 @@ const pluginEnable = defineLeaf({
160
166
  });
161
167
  const pluginDisable = defineLeaf({
162
168
  name: 'disable',
169
+ description: 'disable without removing',
170
+ whenToUse: 'hiding a plugin from resolution without deleting it, so you can re-enable it later',
163
171
  help: {
164
172
  name: 'pkg plugin manage disable',
165
173
  summary: 'disable a plugin (keeps files, hides from resolution)',
@@ -183,6 +191,8 @@ const pluginDisable = defineLeaf({
183
191
  // ---------------------------------------------------------------------------
184
192
  const pluginUpdate = defineLeaf({
185
193
  name: 'update',
194
+ description: 'pull latest from git',
195
+ whenToUse: 'pulling the latest git version of one named plugin, or of all installed plugins when no name is given',
186
196
  help: {
187
197
  name: 'pkg plugin manage update',
188
198
  summary: 'git pull updates for one or all installed plugins',
@@ -279,16 +289,11 @@ const pluginUpdate = defineLeaf({
279
289
  });
280
290
  export const pluginManageBranch = defineBranch({
281
291
  name: 'manage',
292
+ description: 'install, remove, enable, disable, update',
293
+ whenToUse: 'changing plugin state: install, remove, enable, disable, or update installed plugins',
282
294
  help: {
283
295
  name: 'pkg plugin manage',
284
296
  summary: 'install, remove, enable, disable, or update plugins',
285
- children: [
286
- { name: 'install', desc: 'install from a git URL', useWhen: 'adding a new plugin' },
287
- { name: 'remove', desc: 'remove plugin and directory', useWhen: 'uninstalling a plugin' },
288
- { name: 'enable', desc: 'enable a plugin', useWhen: 'activating a disabled plugin' },
289
- { name: 'disable', desc: 'disable without removing', useWhen: 'temporarily hiding a plugin' },
290
- { name: 'update', desc: 'pull latest from git', useWhen: 'updating a plugin to its latest version' },
291
- ],
292
297
  },
293
298
  children: [pluginInstall, pluginRemove, pluginEnable, pluginDisable, pluginUpdate],
294
299
  });
@@ -3,14 +3,12 @@ import { pluginManageBranch } from './plugin-manage.js';
3
3
  import { pluginInspectBranch } from './plugin-inspect.js';
4
4
  export const pluginBranch = defineBranch({
5
5
  name: 'plugin',
6
+ description: 'install and manage plugins',
7
+ whenToUse: 'working with individual plugins directly — installing one from a git URL, inspecting or showing what a plugin provides, or enabling, disabling, removing, and updating an installed plugin. Use `pkg market` instead when you want a curated collection: browsing a marketplace index and installing plugins by name from it rather than handling raw git URLs yourself',
6
8
  help: {
7
9
  name: 'pkg plugin',
8
10
  summary: 'install and manage plugins that extend crtr with skills',
9
11
  model: 'Plugins are git repos or local directories containing a .crouter-plugin/plugin.json manifest and a skills/ directory.',
10
- children: [
11
- { name: 'manage', desc: 'install, remove, enable, disable, update', useWhen: 'changing plugin state' },
12
- { name: 'inspect', desc: 'list or show installed plugins', useWhen: 'reading plugin metadata' },
13
- ],
14
12
  },
15
13
  children: [pluginManageBranch, pluginInspectBranch],
16
14
  });
@@ -15,10 +15,6 @@ export function registerPkg() {
15
15
  help: {
16
16
  name: 'pkg',
17
17
  summary: 'manage plugins and plugin marketplaces',
18
- children: [
19
- { name: 'plugin', desc: 'install and manage plugins', useWhen: 'working with individual plugins directly' },
20
- { name: 'market', desc: 'manage marketplace sources and install from them', useWhen: 'using curated plugin collections' },
21
- ],
22
18
  },
23
19
  children: [pluginBranch, marketBranch],
24
20
  });
@@ -1,9 +1,10 @@
1
1
  // `crtr push` + `crtr feed` — the one verb up the graph, and its read side.
2
2
  //
3
- // crtr push update [body] — routine progress (also auto-emitted every stop)
3
+ // crtr push update [body] — routine progress to subscribers
4
4
  // crtr push urgent [body] — force-wake subscribers
5
5
  // crtr push final [body] — finish: write result, mark node done, close window
6
6
  // crtr feed read — drain the caller's (or a named) inbox into a digest
7
+ // crtr feed peek — live state of the nodes below you, cursor untouched
7
8
  //
8
9
  // "push" is THE verb a node uses to talk to its managers; the tier is the
9
10
  // subcommand. The caller's node is resolved from CRTR_NODE_ID (injected by the
@@ -12,6 +13,7 @@
12
13
  import { defineBranch, defineLeaf } from '../core/command.js';
13
14
  import { InputError, readStdinRaw } from '../core/io.js';
14
15
  import { push } from '../core/feed/feed.js';
16
+ import { getNode, subscribersOf, subscriptionsOf, fullName } from '../core/canvas/index.js';
15
17
  import { readInboxSince, readCursor, writeCursor, coalesce, } from '../core/feed/inbox.js';
16
18
  function requireCallerNode() {
17
19
  const id = process.env['CRTR_NODE_ID'];
@@ -29,14 +31,24 @@ const TIER_BLURB = {
29
31
  urgent: 'force-wake subscribers (inbox tier urgent)',
30
32
  final: 'finish: write the canonical result, mark the node done, close its window',
31
33
  };
34
+ const TIER_WHENTOUSE = {
35
+ update: 'you have routine progress worth surfacing to your managers but nothing that needs them to act right now — a checkpoint, a finished sub-step, a status note while you keep working, a heads-up on a decision you made. Fans a lightweight pointer to every subscriber without forcing a wake. Nothing is pushed automatically — your managers see only what you push explicitly, so reach for this whenever you want a progress signal to reach them. Use push urgent instead when a manager must see it immediately, push final when the work is actually done.',
36
+ urgent: 'something your managers must see and act on immediately — you are blocked and need a decision, you hit an error that derails the plan, a discovery changes the scope, or a child reported something that has to travel further up the chain now. Same report mechanism as push update, but it force-wakes every subscriber instead of waiting for them to drain their feed on their own time. Use push update instead for progress that can wait, push final when you are handing back a finished result rather than raising an alarm.',
37
+ final: 'the work this node was spawned to do is complete and you are ready to hand back the canonical result — this writes that result, marks the node done, and closes its window, so it is the LAST thing you do here, not a progress note. Any node finishes this way. Use push update or push urgent instead while work is still in flight, and do not reach for this on a node working directly with the user: it has no manager to report up to and would close mid-conversation (the guard blocks it unless you pass --force after the user confirms).',
38
+ };
32
39
  function makeTierLeaf(tier) {
33
40
  return defineLeaf({
34
41
  name: tier,
42
+ description: TIER_BLURB[tier],
43
+ whenToUse: TIER_WHENTOUSE[tier],
35
44
  help: {
36
45
  name: `push ${tier}`,
37
46
  summary: TIER_BLURB[tier],
38
47
  params: [
39
- { kind: 'stdin', name: 'body', required: true, constraint: 'Report body (markdown). Positional or stdin.' },
48
+ { kind: 'stdin', name: 'body', required: true, constraint: `Report body (markdown). Positional or stdin — use stdin/heredoc for large bodies (\`crtr push ${tier} <<'EOF' … EOF\`).` },
49
+ ...(tier === 'final'
50
+ ? [{ kind: 'flag', name: 'force', type: 'bool', required: false, default: false, constraint: 'Override the guard that blocks `push final` on a human-attended node (a resident with no one to report up to). Only pass this after the user explicitly confirms they want this node finished.' }]
51
+ : []),
40
52
  ],
41
53
  output: [
42
54
  { name: 'report_path', type: 'string', required: true, constraint: 'Path of the written report.' },
@@ -58,6 +70,38 @@ function makeTierLeaf(tier) {
58
70
  if (body === '') {
59
71
  throw new InputError({ error: 'missing_body', message: 'no report body', field: 'body', next: 'Pass the body as an argument or on stdin.' });
60
72
  }
73
+ if (tier === 'final') {
74
+ const node = getNode(nodeId);
75
+ // A 2nd `push final` in one turn (or finalizing an already dead/canceled
76
+ // node) is an illegal finalize-from-terminal: the underlying transition()
77
+ // throws a raw Error that surfaces as an `internal` "crtr bug" on stderr.
78
+ // Catch it here as a CLEAN user-facing error — the node is simply already
79
+ // finished, so there is nothing to do.
80
+ if (node !== null && node.status !== 'active' && node.status !== 'idle') {
81
+ throw new InputError({
82
+ error: 'already_finalized',
83
+ message: `This node is already ${node.status} — \`push final\` finishes a node once and cannot finalize it again.`,
84
+ next: 'Nothing to do here: the node is already finished. Do not push again from this node.',
85
+ });
86
+ }
87
+ // A RESIDENT node with no subscribers is human-driven and has no one to
88
+ // submit a canonical result to: `push final` fans to subscribers, and a
89
+ // resident root conversation has none. Finishing it would close its window
90
+ // mid-conversation. Block that unless the user confirms (--force). Keyed on
91
+ // lifecycle, NOT subscriber-count alone: a TERMINAL node with no subscribers
92
+ // was deliberately terminalized to owe a final — it self-completes here
93
+ // (records the result, reaps) rather than being blocked for lack of a recipient.
94
+ if (input['force'] !== true) {
95
+ const noRecipient = node !== null && node.lifecycle === 'resident' && subscribersOf(nodeId).length === 0;
96
+ if (noRecipient) {
97
+ throw new InputError({
98
+ error: 'no_final_recipient',
99
+ message: 'This node is working directly with the user — it has no manager to submit a final result to. `push final` would close its window mid-conversation.',
100
+ next: 'You almost certainly do NOT need to finish here — just keep working with the user. If the user has explicitly asked you to finish and close this node, confirm with them first, then rerun with `crtr push final --force "<result>"`.',
101
+ });
102
+ }
103
+ }
104
+ }
61
105
  const result = await push(nodeId, { kind: tier, body });
62
106
  return { report_path: result.reportPath, delivered_to: result.deliveredTo, status: tier === 'final' ? 'done' : 'active' };
63
107
  },
@@ -77,18 +121,21 @@ function makeTierLeaf(tier) {
77
121
  // ---------------------------------------------------------------------------
78
122
  const feedReadLeaf = defineLeaf({
79
123
  name: 'read',
124
+ description: 'drain unread pointers into a digest',
125
+ whenToUse: 'you want to PROACTIVELY poll what the nodes you subscribe to — your children and anyone you follow — have reported before the watcher wakes you, draining the unread pointers in your inbox into one coalesced digest. NOTE: when a subscriber push wakes you, that wake message already IS this digest (the watcher drains your inbox to wake you), so don\'t re-run feed read to "open" it — dereference the refs in the digest you already have. Reach for read to poll before the next wake, to inspect a worker inbox via --node, or with --all to re-read the whole history (full message bodies included) after the cursor has advanced.',
80
126
  help: {
81
127
  name: 'feed read',
82
128
  summary: 'drain unread inbox pointers for the caller (or a named node) into a compact digest',
83
129
  params: [
84
130
  { kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node whose inbox to read. Defaults to CRTR_NODE_ID. Use to inspect a worker\'s inbox as an orchestrator.' },
85
- { kind: 'flag', name: 'all', type: 'bool', required: false, default: false, constraint: 'Ignore the cursor and return everything from the start.' },
131
+ { kind: 'flag', name: 'all', type: 'bool', required: false, default: false, constraint: 'Ignore the cursor and return everything from the start — use to re-read history the wake already drained.' },
86
132
  ],
87
133
  output: [
88
134
  { name: 'node_id', type: 'string', required: true, constraint: 'Node whose inbox was read.' },
89
135
  { name: 'digest', type: 'string', required: true, constraint: 'Coalesced digest — paste directly into a prompt.' },
90
136
  { name: 'entries', type: 'object[]', required: true, constraint: 'Raw InboxEntry objects.' },
91
137
  { name: 'cursor', type: 'string', required: true, constraint: 'New cursor ISO written after draining.' },
138
+ { name: 'inbox_total', type: 'number', required: true, constraint: 'Total entries in the inbox (read + unread). Distinguishes a never-used inbox from one already drained at wake.' },
92
139
  ],
93
140
  outputKind: 'object',
94
141
  effects: ['Advances nodes/<nodeId>/inbox.jsonl.cursor.', 'Read-only on inbox.jsonl itself.'],
@@ -99,6 +146,7 @@ const feedReadLeaf = defineLeaf({
99
146
  : requireCallerNode();
100
147
  const cursor = input['all'] === true ? undefined : readCursor(nodeId);
101
148
  const entries = readInboxSince(nodeId, cursor);
149
+ const total = readInboxSince(nodeId, undefined).length;
102
150
  const newCursor = entries.length > 0 ? entries[entries.length - 1].ts : cursor ?? new Date().toISOString();
103
151
  writeCursor(nodeId, newCursor);
104
152
  return {
@@ -106,17 +154,138 @@ const feedReadLeaf = defineLeaf({
106
154
  digest: coalesce(entries),
107
155
  entries: entries,
108
156
  cursor: newCursor,
157
+ inbox_total: total,
109
158
  };
110
159
  },
111
160
  render: (r) => {
112
161
  const n = Array.isArray(r['entries']) ? r['entries'].length : 0;
113
- const digest = typeof r['digest'] === 'string' && r['digest'].trim() !== ''
114
- ? r['digest']
115
- : 'Inbox empty — nothing new from your subscriptions.';
162
+ const rawDigest = typeof r['digest'] === 'string' ? r['digest'] : '';
163
+ if (n > 0 && rawDigest.trim() !== '') {
164
+ return `<feed node="${r['node_id']}" unread="${n}">\n${rawDigest}\n</feed>`;
165
+ }
166
+ // Empty drain has two distinct causes — say which, honestly. An inbox that
167
+ // already holds entries was drained when the watcher woke you (the wake
168
+ // message WAS that digest); a never-used inbox simply has nothing yet.
169
+ const total = typeof r['inbox_total'] === 'number' ? r['inbox_total'] : 0;
170
+ const digest = total > 0
171
+ ? 'Nothing unread — but your inbox is not empty. The watcher drains your inbox to wake you, so the entries you already saw in your wake message are the same ones you would read here; that is why this is empty. Re-read the whole history (full message bodies included) with `crtr feed read --all`, or dereference the refs from the wake digest you already have.'
172
+ : 'Inbox empty — nothing has arrived from your subscriptions yet. Expected while workers run: a worker that has not pushed yet leaves no pointer, and it will wake you the moment it does. The wake is automatic — just continue your own work or end your turn. (Reach for `crtr feed peek` only if you suspect a worker died, not to confirm a live one.)';
116
173
  return `<feed node="${r['node_id']}" unread="${n}">\n${digest}\n</feed>`;
117
174
  },
118
175
  });
119
176
  // ---------------------------------------------------------------------------
177
+ // feed peek — live state of the nodes below you, without draining anything
178
+ // ---------------------------------------------------------------------------
179
+ const STATUS_GLYPH = {
180
+ active: '●', idle: '○', done: '✓', dead: '✗', canceled: '⊘',
181
+ };
182
+ /** Coarse "Nm ago" age from an ISO timestamp — enough to read staleness at a glance. */
183
+ function fmtAge(iso) {
184
+ const ms = Date.now() - new Date(iso).getTime();
185
+ if (!Number.isFinite(ms) || ms < 0)
186
+ return 'just now';
187
+ const s = Math.floor(ms / 1000);
188
+ if (s < 60)
189
+ return `${s}s ago`;
190
+ const m = Math.floor(s / 60);
191
+ if (m < 60)
192
+ return `${m}m ago`;
193
+ const h = Math.floor(m / 60);
194
+ if (h < 24)
195
+ return `${h}h ago`;
196
+ return `${Math.floor(h / 24)}d ago`;
197
+ }
198
+ const feedPeekLeaf = defineLeaf({
199
+ name: 'peek',
200
+ description: 'live state of the nodes below you, without draining anything',
201
+ whenToUse: 'you are about to end a turn and want to confirm your workers are running before you chill — peek shows every node you subscribe to (the workers below you) with its live status (working/idle/done/dead), how long it has run, its cycle count, and whether it has pushed yet, plus a one-line verdict on whether it is safe to yield. Non-destructive: it never advances your inbox cursor, so a later `feed read` still delivers undrained reports. Reach for it exactly when the feed reads empty but you have outstanding children — that empty feed is EXPECTED (a worker that has not pushed yet contributes no inbox pointer); peek confirms those workers are alive and running async so you can stop and wait instead of polling.',
202
+ help: {
203
+ name: 'feed peek',
204
+ summary: 'show the live state of every node you subscribe to (the workers below you) with a yield-or-not verdict; never drains the inbox',
205
+ params: [
206
+ { kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node whose subscriptions to peek. Defaults to CRTR_NODE_ID. Use to inspect a worker\'s downstream as an orchestrator.' },
207
+ ],
208
+ output: [
209
+ { name: 'node_id', type: 'string', required: true, constraint: 'Node that was peeked.' },
210
+ { name: 'unread', type: 'number', required: true, constraint: 'Inbox pointers not yet drained by `feed read`.' },
211
+ { name: 'children', type: 'object[]', required: true, constraint: 'One row per subscription: {node_id, kind, name, status, active, spawned, cycles, last_push}.' },
212
+ ],
213
+ outputKind: 'object',
214
+ effects: ['Read-only: reads canvas.db edges + node metas + inbox.jsonl.', 'Does NOT advance the inbox cursor — peek leaves the feed intact for a later `feed read`.'],
215
+ },
216
+ run: async (input) => {
217
+ const nodeId = typeof input['node'] === 'string' && input['node'].trim() !== ''
218
+ ? input['node'].trim()
219
+ : requireCallerNode();
220
+ // Read the WHOLE inbox to find each child's last push, but with no cursor
221
+ // write — peek is non-destructive by contract. `unread` is computed from the
222
+ // persisted cursor so peek can tell you a `feed read` would deliver something.
223
+ const all = readInboxSince(nodeId, undefined);
224
+ const unread = readInboxSince(nodeId, readCursor(nodeId)).length;
225
+ const children = subscriptionsOf(nodeId).map((s) => {
226
+ const n = getNode(s.node_id);
227
+ const fromMe = all.filter((e) => e.from === s.node_id);
228
+ const last = fromMe.length > 0 ? fromMe[fromMe.length - 1] : undefined;
229
+ return {
230
+ node_id: s.node_id,
231
+ kind: n?.kind ?? '?',
232
+ name: n !== null ? fullName(n) : s.node_id,
233
+ status: n?.status ?? 'dead',
234
+ active: s.active,
235
+ spawned: n?.created ?? s.created,
236
+ cycles: n?.cycles ?? 0,
237
+ last_push: last !== undefined
238
+ ? { kind: last.kind, ts: last.ts, ref: last.ref ?? null, label: last.label }
239
+ : null,
240
+ };
241
+ });
242
+ return { node_id: nodeId, unread, children };
243
+ },
244
+ render: (r) => {
245
+ const id = r['node_id'];
246
+ const unread = r['unread'] ?? 0;
247
+ const kids = r['children'] ?? [];
248
+ if (kids.length === 0) {
249
+ const tail = unread > 0
250
+ ? `${unread} unread report${unread === 1 ? '' : 's'} sit in your inbox — run \`crtr feed read\` to absorb ${unread === 1 ? 'it' : 'them'}.`
251
+ : 'If you spawned workers, they have finished and detached, or you never subscribed. Nothing will wake you.';
252
+ return `<peek node="${id}" subscriptions="0" unread="${unread}" verdict="empty">\nNo nodes below you. ${tail}\n</peek>`;
253
+ }
254
+ const working = kids.filter((k) => k.status === 'active' || k.status === 'idle');
255
+ const liveWaking = working.filter((k) => k.active); // active sub to a live node = it will wake me
256
+ const done = kids.filter((k) => k.status === 'done' || k.status === 'canceled');
257
+ const dead = kids.filter((k) => k.status === 'dead');
258
+ let verdict;
259
+ let line;
260
+ if (dead.length > 0) {
261
+ verdict = 'attention';
262
+ line = `\u26a0 ${dead.length} below you ${dead.length === 1 ? 'is' : 'are'} dead and will NOT wake you. Inspect with \`crtr node inspect show <id>\`, then re-delegate or proceed without ${dead.length === 1 ? 'it' : 'them'}.`;
263
+ }
264
+ else if (liveWaking.length > 0) {
265
+ verdict = 'working';
266
+ line = `Safe to yield \u2014 ${liveWaking.length} worker${liveWaking.length === 1 ? '' : 's'} running async will wake you on the next push. Nothing to do now; end your turn and chill.`;
267
+ }
268
+ else if (unread > 0) {
269
+ verdict = 'ready';
270
+ line = `Nothing still running, but ${unread} unread report${unread === 1 ? '' : 's'} \u2014 run \`crtr feed read\` to absorb ${unread === 1 ? 'it' : 'them'}, then continue or finish.`;
271
+ }
272
+ else {
273
+ verdict = 'idle';
274
+ line = 'Everything below you has finished and been drained \u2014 nothing is running. Continue your own work, or `crtr push final` to finish.';
275
+ }
276
+ const rows = kids.map((k) => {
277
+ const sub = k.active ? '' : ' (passive)';
278
+ const push = k.last_push !== null
279
+ ? `pushed ${fmtAge(k.last_push.ts)} [${k.last_push.kind}]${k.last_push.ref !== null ? ` ref:${k.last_push.ref}` : ''}`
280
+ : 'no push yet';
281
+ const glyph = STATUS_GLYPH[k.status] ?? '?';
282
+ return ` ${glyph} ${k.node_id} ${k.kind} ${k.name}${sub} \u00b7 ${k.status} \u00b7 spawned ${fmtAge(k.spawned)} \u00b7 cyc ${k.cycles} \u00b7 ${push}`;
283
+ }).join('\n');
284
+ const attrs = `node="${id}" subscriptions="${kids.length}" working="${working.length}" done="${done.length}" dead="${dead.length}" unread="${unread}" verdict="${verdict}"`;
285
+ return `<peek ${attrs}>\n${line}\n\n${rows}\n</peek>`;
286
+ },
287
+ });
288
+ // ---------------------------------------------------------------------------
120
289
  // Registration
121
290
  // ---------------------------------------------------------------------------
122
291
  export function registerPush() {
@@ -130,12 +299,7 @@ export function registerPush() {
130
299
  help: {
131
300
  name: 'push',
132
301
  summary: 'push a report to your subscribers',
133
- model: 'A push writes a markdown report to the node\'s reports/ history and fans a lightweight pointer to every subscriber\'s inbox (not the content — they dereference lazily). The stophook auto-pushes an `update` every stop, so the feed is continuous; you push explicitly for intentional signals. `push final` is how ANY node finishes: write the canonical result, mark done, close the window.',
134
- children: [
135
- { name: 'update', desc: TIER_BLURB.update, useWhen: 'sharing routine progress explicitly' },
136
- { name: 'urgent', desc: TIER_BLURB.urgent, useWhen: 'something your managers must see now' },
137
- { name: 'final', desc: TIER_BLURB.final, useWhen: 'the work is done — this finishes the node' },
138
- ],
302
+ model: 'A push writes a markdown report to the node\'s reports/ history and fans a lightweight pointer to every subscriber\'s inbox (not the content — they dereference lazily). Nothing is pushed automatically the feed contains only what a node pushes explicitly, so push whenever you want a manager to see something. Pipe large bodies via stdin/heredoc (`crtr push <tier> <<\'EOF\' EOF`). `push final` is how ANY node finishes: write the canonical result, mark done, close the window.',
139
303
  },
140
304
  children: [makeTierLeaf('update'), makeTierLeaf('urgent'), makeTierLeaf('final')],
141
305
  });
@@ -151,9 +315,8 @@ export function registerFeed() {
151
315
  help: {
152
316
  name: 'feed',
153
317
  summary: 'read the per-node inbox feed',
154
- model: 'Each node has an inbox.jsonl that accumulates ~30-token pointers from publishers it subscribes to. `feed read` coalesces unread pointers into one digest; dereference the reports that matter by reading their ref paths.',
155
- children: [{ name: 'read', desc: 'drain unread pointers into a digest', useWhen: 'checking what your subscriptions pushed' }],
318
+ model: 'Each node has an inbox.jsonl that accumulates ~30-token pointers from publishers it subscribes to. The watcher drains this inbox to wake you, so the wake message you receive ALREADY IS the coalesced digest \u2014 dereference the reports that matter by reading their ref paths (a push carries a ref; a direct message inlines its full body). `feed read` is for PROACTIVELY polling before a wake (it advances the cursor); after a wake it reads empty because the cursor already moved \u2014 use `--all` to re-read history. An empty feed is normal while workers run \u2014 a worker that has not pushed leaves no pointer \u2014 so use `feed peek` to see the live state of the nodes below you (and whether to yield) without draining anything.',
156
319
  },
157
- children: [feedReadLeaf],
320
+ children: [feedReadLeaf, feedPeekLeaf],
158
321
  });
159
322
  }
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Bypasses the daemon: directly opens a fresh tmux window for a node that is
4
4
  // done, idle, or dead. Default behavior resumes the saved pi conversation
5
- // (--resume); pass --fresh to start a clean pi session against the context dir.
5
+ // (--session <id>); pass --fresh to start a clean pi session against the context dir.
6
6
  import { defineLeaf } from '../core/command.js';
7
7
  import { InputError } from '../core/io.js';
8
8
  import { reviveNode } from '../core/runtime/revive.js';
@@ -12,6 +12,8 @@ import { getNode } from '../core/canvas/index.js';
12
12
  // ---------------------------------------------------------------------------
13
13
  export const reviveLeaf = defineLeaf({
14
14
  name: 'revive',
15
+ description: 'reopen a window for a done/idle/dead/canceled node',
16
+ whenToUse: 'you want to bring a dormant node back yourself — reopen a window for one that is done, idle, dead, or canceled: resume a node you closed with `node close`, reopen a finished worker for a follow-up, or restart a crashed one now instead of waiting. Resumes the saved conversation by default; pass `--fresh` to restart clean against its context dir. You rarely need this for crashes — the daemon auto-revives those; reach for it to bring a node back on demand, or to revive a canceled node the daemon will never touch on its own',
15
17
  help: {
16
18
  name: 'canvas revive',
17
19
  summary: 'open a fresh tmux window for a node, optionally resuming its saved pi conversation',
@@ -28,13 +30,13 @@ export const reviveLeaf = defineLeaf({
28
30
  type: 'bool',
29
31
  required: false,
30
32
  default: false,
31
- constraint: 'When set, start a clean pi session (no --resume). Default: resume the saved conversation.',
33
+ constraint: 'When set, start a clean pi session (no --session). Default: resume the saved conversation.',
32
34
  },
33
35
  ],
34
36
  output: [
35
37
  { name: 'window', type: 'string', required: false, constraint: 'New tmux window id.' },
36
38
  { name: 'session', type: 'string', required: true, constraint: 'Tmux session the node was placed in.' },
37
- { name: 'resumed', type: 'boolean', required: true, constraint: 'True when pi was told to --resume the saved conversation.' },
39
+ { name: 'resumed', type: 'boolean', required: true, constraint: 'True when pi was told to --session the saved conversation.' },
38
40
  ],
39
41
  outputKind: 'object',
40
42
  effects: [
@@ -12,6 +12,8 @@ import { skillCreatePrompt, skillTemplatePrompt } from '../../prompts/skill.js';
12
12
  import { VALID_TYPES, resolveWriteScope } from './shared.js';
13
13
  export const authorGuide = defineLeaf({
14
14
  name: 'guide',
15
+ description: 'load authoring workflow + skeleton for a type',
16
+ whenToUse: 'writing a new skill and you want the authoring workflow plus the template skeleton — call it once with no --type for the template picker, then again with --type for the full workflow and skeleton of that type.',
15
17
  help: {
16
18
  name: 'skill author guide',
17
19
  summary: 'load the skill authoring workflow — two stages: omit type to pick one, pass type for its full skeleton',
@@ -40,6 +42,8 @@ export const authorGuide = defineLeaf({
40
42
  });
41
43
  export const authorScaffold = defineLeaf({
42
44
  name: 'scaffold',
45
+ description: 'create an empty SKILL.md stub',
46
+ whenToUse: 'creating the empty SKILL.md stub at a `<plugin>/<skill>` qualifier before you fill in content — it writes the frontmatter and file, then points you at the authoring guide.',
43
47
  help: {
44
48
  name: 'skill author scaffold',
45
49
  summary: 'create an empty SKILL.md stub at the given qualifier',
@@ -135,13 +139,11 @@ export const authorScaffold = defineLeaf({
135
139
  });
136
140
  export const authorBranch = defineBranch({
137
141
  name: 'author',
142
+ description: 'create and scaffold skills',
143
+ whenToUse: 'you have a reusable workflow, methodology, or hard-won convention worth capturing so future agents adopt it instead of re-deriving it — author carries you from picking a template through scaffolding the file. Reach for it when a task just taught you a repeatable procedure, when the same guidance keeps getting re-explained across sessions, or when the house conventions for a tool deserve to be written down once. Start with `crtr skill author guide` for the template picker and authoring workflow; use `crtr skill author scaffold` to stub the SKILL.md file.',
138
144
  help: {
139
145
  name: 'skill author',
140
146
  summary: 'create and scaffold new skills',
141
- children: [
142
- { name: 'guide', desc: 'load authoring workflow + skeleton for a type', useWhen: 'writing a new skill and need the template and instructions' },
143
- { name: 'scaffold', desc: 'create an empty SKILL.md stub', useWhen: 'initializing the file before writing content' },
144
- ],
145
147
  },
146
148
  children: [authorGuide, authorScaffold],
147
149
  });
@@ -8,6 +8,8 @@ import { paginate } from '../../core/pagination.js';
8
8
  import { walkFiles, readText } from '../../core/fs-utils.js';
9
9
  export const findList = defineLeaf({
10
10
  name: 'list',
11
+ description: 'paginated list of installed skills',
12
+ whenToUse: 'browse the whole catalog of installed skills, paginated, when you want to see everything that is available rather than hunt for a particular topic — supports --scope, --plugin, and --full filters to narrow or enrich the listing. Use `crtr skill find search` instead when you already have a topic or keyword in mind.',
11
13
  help: {
12
14
  name: 'skill find list',
13
15
  summary: 'paginated list of installed skills',
@@ -92,6 +94,8 @@ export const findList = defineLeaf({
92
94
  });
93
95
  export const findSearch = defineLeaf({
94
96
  name: 'search',
97
+ description: 'keyword search across name/description/keywords',
98
+ whenToUse: 'you have a topic, keyword, or problem in mind but not the exact skill name — search ranks installed skills by how well your terms match their name, description, and keywords (add --search-body to also look inside SKILL.md bodies). Reach for it to answer questions like is there a skill for writing tests, anything on prompt design, do we have a debugging workflow. Use `crtr skill find list` instead to browse the whole catalog when you have no particular topic in mind, and `crtr skill find grep` when you need an exact regex or literal-string match across skill bodies rather than a ranked topic match.',
95
99
  help: {
96
100
  name: 'skill find search',
97
101
  summary: 'search skills by name, description, and keywords',
@@ -177,6 +181,8 @@ export const findSearch = defineLeaf({
177
181
  });
178
182
  export const findGrep = defineLeaf({
179
183
  name: 'grep',
184
+ description: 'regex search across SKILL.md bodies',
185
+ whenToUse: 'you need to find which skills contain a specific literal string or regex pattern in their SKILL.md body — an exact text match, not a ranked topic search: locating every skill that mentions a command name, a flag, or an exact phrase. Use `crtr skill find search` instead when you want skills matched by topic or keyword rather than exact body text.',
180
186
  help: {
181
187
  name: 'skill find grep',
182
188
  summary: 'search skill file contents for a regex pattern',
@@ -241,14 +247,11 @@ export const findGrep = defineLeaf({
241
247
  });
242
248
  export const findBranch = defineBranch({
243
249
  name: 'find',
250
+ description: 'list, search, or grep skills',
251
+ whenToUse: 'you do not yet know which skill applies and need to discover what is installed — list to browse the whole catalog, search to rank skills by topic or keyword, grep to match exact text inside skill bodies.',
244
252
  help: {
245
253
  name: 'skill find',
246
254
  summary: 'discover skills by listing, keyword search, or body grep',
247
- children: [
248
- { name: 'list', desc: 'paginated list of installed skills', useWhen: 'enumerating all available skills' },
249
- { name: 'search', desc: 'keyword search across name/description/keywords', useWhen: 'looking for skills matching a topic' },
250
- { name: 'grep', desc: 'regex search across SKILL.md bodies', useWhen: 'finding skills containing specific text or patterns' },
251
- ],
252
255
  },
253
256
  children: [findList, findSearch, findGrep],
254
257
  });