@north-light/crouter 0.3.164 → 0.3.165

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 (155) hide show
  1. package/dist/build-root.d.ts +6 -6
  2. package/dist/build-root.js +12 -13
  3. package/dist/builtin-memory/00-runtime-base.md +3 -4
  4. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  5. package/dist/builtin-memory/02-lifecycle/00-terminal.md +2 -0
  6. package/dist/builtin-memory/04-orchestration-kernel.md +16 -18
  7. package/dist/builtin-memory/05-kinds/advisor/00-base.md +0 -2
  8. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -9
  9. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -3
  10. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +2 -10
  11. package/dist/builtin-memory/05-kinds/explore/00-base.md +2 -2
  12. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +0 -2
  13. package/dist/builtin-memory/05-kinds/general/00-base.md +3 -5
  14. package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +0 -4
  15. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -3
  16. package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +12 -0
  17. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -2
  18. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -2
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -2
  20. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -1
  21. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -2
  22. package/dist/builtin-memory/05-kinds/review/00-base.md +0 -2
  23. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -3
  24. package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -1
  25. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +2 -4
  26. package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -1
  27. package/dist/builtin-memory/advisor/council.md +40 -0
  28. package/dist/builtin-memory/design.md +8 -11
  29. package/dist/builtin-memory/development.md +6 -9
  30. package/dist/builtin-memory/internal/INDEX.md +4 -5
  31. package/dist/builtin-memory/internal/agent-shaping.md +5 -7
  32. package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -3
  33. package/dist/builtin-memory/internal/marketplaces.md +13 -14
  34. package/dist/builtin-memory/internal/nodes-and-canvas.md +4 -4
  35. package/dist/builtin-memory/internal/plugins.md +61 -45
  36. package/dist/builtin-memory/planning.md +4 -7
  37. package/dist/builtin-memory/spec.md +9 -12
  38. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +8 -22
  39. package/dist/cli.js +1 -1
  40. package/dist/clients/attach/__tests__/attach-keybindings.test.js +17 -4
  41. package/dist/clients/attach/__tests__/context-message.test.js +31 -20
  42. package/dist/clients/attach/__tests__/crtr-output.test.js +39 -30
  43. package/dist/clients/attach/render/chat-view.d.ts +20 -20
  44. package/dist/clients/attach/render/chat-view.js +42 -54
  45. package/dist/clients/attach/render/{frozen-history.d.ts → condensed-history.d.ts} +1 -18
  46. package/dist/clients/attach/render/condensed-history.js +60 -0
  47. package/dist/clients/attach/render/context-message.js +9 -14
  48. package/dist/clients/attach/session/bindings.d.ts +5 -3
  49. package/dist/clients/attach/session/bindings.js +7 -13
  50. package/dist/clients/attach/session/keys.d.ts +2 -0
  51. package/dist/clients/attach/session/keys.js +36 -37
  52. package/dist/clients/attach/session/profile-files.d.ts +8 -0
  53. package/dist/clients/attach/session/profile-files.js +157 -0
  54. package/dist/clients/attach/viewer.js +525 -525
  55. package/dist/commands/node.js +2 -2
  56. package/dist/commands/pkg/plugin-inspect.js +6 -7
  57. package/dist/commands/pkg/plugin-manage.d.ts +1 -1
  58. package/dist/commands/pkg/plugin-manage.js +131 -19
  59. package/dist/commands/pkg/plugin.js +2 -2
  60. package/dist/commands/pkg.js +6 -11
  61. package/dist/commands/profile/env.js +3 -3
  62. package/dist/commands/sys/config.js +17 -76
  63. package/dist/commands/sys/doctor.js +5 -91
  64. package/dist/commands/sys/setup-wizard.d.ts +9 -11
  65. package/dist/commands/sys/setup-wizard.js +47 -81
  66. package/dist/core/__tests__/base-worker-prompt.test.js +18 -21
  67. package/dist/core/__tests__/command-plugins-surfaces.test.js +39 -5
  68. package/dist/core/__tests__/command-plugins.test.js +36 -16
  69. package/dist/core/__tests__/review-model-floor.test.js +2 -2
  70. package/dist/core/__tests__/tmux-surface.test.js +10 -1
  71. package/dist/core/command-manifests/manifest.d.ts +24 -0
  72. package/dist/core/{configured-clis → command-manifests}/manifest.js +21 -25
  73. package/dist/core/command-manifests/registry.d.ts +1 -2
  74. package/dist/core/command-manifests/registry.js +2 -2
  75. package/dist/core/command-manifests/schema.d.ts +11 -13
  76. package/dist/core/command-manifests/schema.js +53 -192
  77. package/dist/core/command-plugins/compose.d.ts +0 -6
  78. package/dist/core/command-plugins/compose.js +28 -75
  79. package/dist/core/command-plugins/discovery.d.ts +25 -58
  80. package/dist/core/command-plugins/discovery.js +152 -259
  81. package/dist/core/command-plugins/endpoint.d.ts +24 -0
  82. package/dist/core/command-plugins/endpoint.js +48 -0
  83. package/dist/core/command-plugins/store.d.ts +16 -0
  84. package/dist/core/command-plugins/store.js +64 -0
  85. package/dist/core/command-plugins/{adapter.d.ts → transport/exec-invoke.d.ts} +3 -3
  86. package/dist/core/command-plugins/{adapter.js → transport/exec-invoke.js} +4 -4
  87. package/dist/core/{configured-clis/fetch.d.ts → command-plugins/transport/http-fetch.d.ts} +9 -18
  88. package/dist/core/{configured-clis/fetch.js → command-plugins/transport/http-fetch.js} +15 -35
  89. package/dist/core/{configured-clis/invoker.d.ts → command-plugins/transport/http-invoke.d.ts} +7 -7
  90. package/dist/core/{configured-clis/invoker.js → command-plugins/transport/http-invoke.js} +21 -23
  91. package/dist/core/command.d.ts +8 -9
  92. package/dist/core/command.js +6 -9
  93. package/dist/core/config.js +6 -10
  94. package/dist/core/env-name.d.ts +6 -0
  95. package/dist/core/env-name.js +9 -0
  96. package/dist/core/io.d.ts +1 -1
  97. package/dist/core/keybindings/__tests__/resolve.test.js +40 -3
  98. package/dist/core/keybindings/attach-control.d.ts +37 -0
  99. package/dist/core/keybindings/attach-control.js +38 -0
  100. package/dist/core/keybindings/catalog.d.ts +5 -4
  101. package/dist/core/keybindings/catalog.js +16 -8
  102. package/dist/core/keybindings/index.d.ts +1 -0
  103. package/dist/core/keybindings/index.js +1 -0
  104. package/dist/core/keybindings/types.d.ts +1 -1
  105. package/dist/core/preview-registry.js +41 -74
  106. package/dist/core/runtime/bearings.d.ts +2 -5
  107. package/dist/core/runtime/bearings.js +2 -5
  108. package/dist/core/runtime/front-door.d.ts +1 -1
  109. package/dist/core/runtime/front-door.js +2 -2
  110. package/dist/core/runtime/kickoff.d.ts +3 -3
  111. package/dist/core/runtime/kickoff.js +4 -3
  112. package/dist/core/runtime/situational-context.d.ts +1 -1
  113. package/dist/core/runtime/situational-context.js +1 -1
  114. package/dist/core/runtime/spawn.js +9 -9
  115. package/dist/core/runtime/tmux.js +29 -2
  116. package/dist/core/scope.d.ts +0 -5
  117. package/dist/core/scope.js +0 -10
  118. package/dist/core/user-settings.d.ts +193 -0
  119. package/dist/core/user-settings.js +252 -0
  120. package/dist/index.d.ts +4 -0
  121. package/dist/index.js +3 -0
  122. package/dist/pi-extensions/canvas-stophook.js +3 -2
  123. package/dist/shared/generated-context.d.ts +34 -0
  124. package/dist/shared/generated-context.js +98 -0
  125. package/dist/types.d.ts +23 -16
  126. package/dist/types.js +3 -14
  127. package/dist/web-client/assets/{index-NIuSCOHM.js → index-BMTOGuOZ.js} +19 -19
  128. package/dist/web-client/assets/index-DvA6Zw-R.css +2 -0
  129. package/dist/web-client/index.html +2 -2
  130. package/dist/web-client/sw.js +1 -1
  131. package/docs/public-api.md +1 -0
  132. package/package.json +1 -1
  133. package/runtime.lock.json +2 -2
  134. package/dist/builtin-memory/05-kinds/product/00-base.md +0 -25
  135. package/dist/builtin-memory/05-kinds/product/01-orchestrator.md +0 -15
  136. package/dist/builtin-memory/05-kinds/product/teardown.md +0 -15
  137. package/dist/builtin-memory/internal/workflow-codification.md +0 -82
  138. package/dist/builtin-memory/product.md +0 -80
  139. package/dist/clients/attach/render/frozen-history.js +0 -100
  140. package/dist/commands/pkg/cli-inspect.d.ts +0 -17
  141. package/dist/commands/pkg/cli-inspect.js +0 -190
  142. package/dist/commands/pkg/cli-manage.d.ts +0 -3
  143. package/dist/commands/pkg/cli-manage.js +0 -206
  144. package/dist/commands/pkg/cli.d.ts +0 -1
  145. package/dist/commands/pkg/cli.js +0 -14
  146. package/dist/core/configured-clis/cache.d.ts +0 -16
  147. package/dist/core/configured-clis/cache.js +0 -57
  148. package/dist/core/configured-clis/compose.d.ts +0 -14
  149. package/dist/core/configured-clis/compose.js +0 -60
  150. package/dist/core/configured-clis/discovery.d.ts +0 -47
  151. package/dist/core/configured-clis/discovery.js +0 -173
  152. package/dist/core/configured-clis/manifest.d.ts +0 -24
  153. package/dist/core/configured-clis/registration.d.ts +0 -40
  154. package/dist/core/configured-clis/registration.js +0 -201
  155. package/dist/web-client/assets/index-CqLKj8Xu.css +0 -2
@@ -1,11 +1,4 @@
1
- // Shared command-manifest schema: common descriptors, strict validators,
2
- // and leaf-dialect specialization for plugins vs configured CLIs.
3
- //
4
- // The descriptor vocabulary (DeclRootEntry, DeclBranch, DeclNode, InputParam,
5
- // Field) is transport-neutral and reused by both plugins (local executable) and
6
- // configured CLIs (remote HTTP). Leaves specialize into two strict dialects:
7
- // PluginDeclLeaf requires only outputKind: 'object'; ConfiguredCliDeclLeaf
8
- // requires only rest (and derives outputKind from rest.streaming).
1
+ // Unified command-manifest schema. Transport is the sole leaf-dialect discriminator.
9
2
  // ---------------------------------------------------------------------------
10
3
  // Validation helpers
11
4
  // ---------------------------------------------------------------------------
@@ -15,15 +8,15 @@ const KEBAB = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
15
8
  const BRANCH_KEYS = new Set([
16
9
  'kind', 'name', 'description', 'whenToUse', 'tier', 'rootEntry', 'summary', 'model', 'children',
17
10
  ]);
18
- /** Only the plugin dialect may declare `passthrough`; the configured-CLI dialect
19
- * keeps BRANCH_KEYS, so a remote manifest declaring it fails as an unknown key. */
20
- const PLUGIN_BRANCH_KEYS = new Set([...BRANCH_KEYS, 'passthrough']);
11
+ /** Only exec transport may declare `passthrough`; HTTP manifests keep
12
+ * BRANCH_KEYS, so a declared passthrough fails as an unknown key. */
13
+ const EXEC_BRANCH_KEYS = new Set([...BRANCH_KEYS, 'passthrough']);
21
14
  const PASSTHROUGH_KEYS = new Set(['bin', 'installHint']);
22
15
  const LEAF_KEYS_COMMON = new Set([
23
16
  'kind', 'name', 'description', 'whenToUse', 'tier', 'summary', 'params', 'output', 'effects',
24
17
  ]);
25
- const PLUGIN_LEAF_KEYS = new Set([...LEAF_KEYS_COMMON, 'outputKind']);
26
- const CONFIGURED_LEAF_KEYS = new Set([...LEAF_KEYS_COMMON, 'rest']);
18
+ const EXEC_LEAF_KEYS = new Set([...LEAF_KEYS_COMMON, 'outputKind']);
19
+ const HTTP_LEAF_KEYS = new Set([...LEAF_KEYS_COMMON, 'rest']);
27
20
  const ROOT_ENTRY_KEYS = new Set(['concept', 'description', 'whenToUse']);
28
21
  const FILE_ENCODINGS = new Set(['text', 'base64']);
29
22
  const PARAM_KEYS_BY_KIND = {
@@ -342,107 +335,97 @@ function checkCommon(raw, path, issue) {
342
335
  }
343
336
  return true;
344
337
  }
345
- // ---------------------------------------------------------------------------
346
- // Node validators (plugin and configured CLI dialects)
347
- // ---------------------------------------------------------------------------
348
- /** Validate a node for the plugin dialect (leaf must have outputKind: 'object'). */
349
- export function validatePluginNode(raw, path, topLevel, issue) {
338
+ export function validateCommandNode(raw, path, topLevel, transport, issue) {
339
+ const pathString = path.join('.');
350
340
  if (!isObject(raw)) {
351
- issue('command_node_invalid', `node must be an object`, typeName(raw), 'a branch or leaf node object', 'Fix the node.', path);
341
+ issue('command_node_invalid', 'node must be an object', typeName(raw), 'a branch or leaf node object', 'Fix the node.', pathString);
352
342
  return null;
353
343
  }
354
344
  const kind = raw['kind'];
355
345
  if (kind === 'branch')
356
- return validatePluginBranch(raw, path, topLevel, issue);
346
+ return validateBranch(raw, pathString, topLevel, transport, issue);
357
347
  if (kind === 'leaf') {
358
348
  if (topLevel) {
359
- issue('command_node_invalid', `top-level node must be a branch`, 'leaf', 'a branch node with a rootEntry', 'Wrap the command in a top-level branch (noun) with a rootEntry.', `${path}.kind`);
349
+ issue('command_node_invalid', 'top-level node must be a branch', 'leaf', 'a branch node with a rootEntry', 'Wrap the command in a top-level branch (noun) with a rootEntry.', `${pathString}.kind`);
360
350
  return null;
361
351
  }
362
- return validatePluginLeaf(raw, path, issue);
352
+ return validateLeaf(raw, pathString, transport, issue);
363
353
  }
364
- issue('command_node_invalid', `node kind must be "branch" or "leaf"`, String(kind), 'branch | leaf', 'Set kind to branch or leaf.', `${path}.kind`);
354
+ issue('command_node_invalid', 'node kind must be "branch" or "leaf"', String(kind), 'branch | leaf', 'Set kind to branch or leaf.', `${pathString}.kind`);
365
355
  return null;
366
356
  }
367
- function validatePluginBranch(raw, path, topLevel, issue) {
368
- if (!checkKeys(raw, PLUGIN_BRANCH_KEYS, path, issue))
357
+ function validateBranch(raw, path, topLevel, transport, issue) {
358
+ if (!checkKeys(raw, transport === 'exec' ? EXEC_BRANCH_KEYS : BRANCH_KEYS, path, issue))
369
359
  return null;
370
360
  if (!checkCommon(raw, path, issue))
371
361
  return null;
372
362
  const hasRoot = raw['rootEntry'] !== undefined;
373
363
  if (topLevel && !hasRoot) {
374
- issue('command_node_invalid', `top-level branch requires a rootEntry`, '(missing)', 'a rootEntry { concept, description, whenToUse }', 'Add a rootEntry to the top-level branch.', `${path}.rootEntry`);
364
+ issue('command_node_invalid', 'top-level branch requires a rootEntry', '(missing)', 'a rootEntry { concept, description, whenToUse }', 'Add a rootEntry to the top-level branch.', `${path}.rootEntry`);
375
365
  return null;
376
366
  }
377
367
  if (!topLevel && hasRoot) {
378
- issue('command_node_invalid', `nested branch must not declare a rootEntry`, 'rootEntry', 'no rootEntry (only top-level branches carry one)', 'Remove rootEntry from the nested branch.', `${path}.rootEntry`);
368
+ issue('command_node_invalid', 'nested branch must not declare a rootEntry', 'rootEntry', 'no rootEntry (only top-level branches carry one)', 'Remove rootEntry from the nested branch.', `${path}.rootEntry`);
379
369
  return null;
380
370
  }
381
371
  let rootEntry;
382
372
  if (hasRoot) {
383
- const re = validateRootEntry(raw['rootEntry'], `${path}.rootEntry`, issue);
384
- if (re === null)
373
+ const validatedRootEntry = validateRootEntry(raw['rootEntry'], `${path}.rootEntry`, issue);
374
+ if (validatedRootEntry === null)
385
375
  return null;
386
- rootEntry = re;
376
+ rootEntry = validatedRootEntry;
387
377
  }
388
378
  if (typeof raw['summary'] !== 'string' || raw['summary'].length === 0) {
389
- issue('command_node_invalid', `branch summary must be a non-empty string`, typeName(raw['summary']), 'a one-line summary string', 'Add a summary.', `${path}.summary`);
379
+ issue('command_node_invalid', 'branch summary must be a non-empty string', typeName(raw['summary']), 'a one-line summary string', 'Add a summary.', `${path}.summary`);
390
380
  return null;
391
381
  }
392
382
  if (raw['model'] !== undefined && typeof raw['model'] !== 'string') {
393
- issue('command_node_invalid', `branch model must be a string`, typeName(raw['model']), 'a string or omitted', 'Fix or remove model.', `${path}.model`);
383
+ issue('command_node_invalid', 'branch model must be a string', typeName(raw['model']), 'a string or omitted', 'Fix or remove model.', `${path}.model`);
394
384
  return null;
395
385
  }
396
386
  let passthrough;
397
- if (raw['passthrough'] !== undefined) {
398
- const pt = validatePassthrough(raw['passthrough'], `${path}.passthrough`, issue);
399
- if (pt === null)
387
+ if (transport === 'exec' && raw['passthrough'] !== undefined) {
388
+ const validated = validatePassthrough(raw['passthrough'], `${path}.passthrough`, issue);
389
+ if (validated === null)
400
390
  return null;
401
- passthrough = pt;
391
+ passthrough = validated;
402
392
  }
403
393
  const children = raw['children'];
404
394
  if (passthrough !== undefined) {
405
- // A passthrough branch intercepts every argv token after it, so a child
406
- // would be unreachable — declare the emptiness rather than implying it.
407
395
  if (!Array.isArray(children) || children.length !== 0) {
408
- issue('command_node_invalid', `a passthrough branch must declare children: []`, Array.isArray(children) ? `${children.length} children` : typeName(children), 'an empty children array', 'Set children to [] — a passthrough branch forwards all argv and can own no subcommands.', `${path}.children`);
396
+ issue('command_node_invalid', 'a passthrough branch must declare children: []', Array.isArray(children) ? `${children.length} children` : typeName(children), 'an empty children array', 'Set children to [] — a passthrough branch forwards all argv and can own no subcommands.', `${path}.children`);
409
397
  return null;
410
398
  }
411
399
  }
412
- else if (!Array.isArray(children) || children.length === 0) {
413
- issue('command_node_invalid', `branch children must be a non-empty array`, typeName(children), 'a non-empty array of branch/leaf nodes', 'Add at least one child.', `${path}.children`);
400
+ else if (!Array.isArray(children)) {
401
+ issue('command_node_invalid', 'branch children must be an array', typeName(children), 'an array of branch/leaf nodes (may be empty for populated by mounts)', 'Fix children.', `${path}.children`);
414
402
  return null;
415
403
  }
416
404
  const validated = [];
417
405
  const childNames = new Set();
418
406
  for (let i = 0; i < children.length; i++) {
419
- const c = validatePluginNode(children[i], `${path}.children[${i}]`, false, issue);
420
- if (c === null)
407
+ const child = validateCommandNode(children[i], [...path.split('.'), `children[${i}]`], false, transport, issue);
408
+ if (child === null)
421
409
  return null;
422
- if (childNames.has(c.name)) {
423
- issue('command_node_invalid', `duplicate child name`, c.name, 'unique child names within a branch', 'Rename the duplicate child.', `${path}.children[${i}].name`);
410
+ if (childNames.has(child.name)) {
411
+ issue('command_node_invalid', 'duplicate child name', child.name, 'unique child names within a branch', 'Rename the duplicate child.', `${path}.children[${i}].name`);
424
412
  return null;
425
413
  }
426
- childNames.add(c.name);
427
- validated.push(c);
414
+ childNames.add(child.name);
415
+ validated.push(child);
428
416
  }
429
417
  return {
430
- kind: 'branch',
431
- name: raw['name'],
432
- description: raw['description'],
433
- whenToUse: raw['whenToUse'],
418
+ kind: 'branch', name: raw['name'], description: raw['description'], whenToUse: raw['whenToUse'],
434
419
  ...(raw['tier'] !== undefined ? { tier: raw['tier'] } : {}),
435
- ...(rootEntry !== undefined ? { rootEntry } : {}),
436
- summary: raw['summary'],
420
+ ...(rootEntry !== undefined ? { rootEntry } : {}), summary: raw['summary'],
437
421
  ...(raw['model'] !== undefined ? { model: raw['model'] } : {}),
438
- ...(passthrough !== undefined ? { passthrough } : {}),
439
- children: validated,
422
+ ...(passthrough !== undefined ? { passthrough } : {}), children: validated,
440
423
  };
441
424
  }
442
425
  /** `passthrough: { bin, installHint }` — both required non-empty strings. */
443
426
  function validatePassthrough(raw, path, issue) {
444
427
  if (!isObject(raw)) {
445
- issue('command_node_invalid', `passthrough must be an object`, typeName(raw), 'an object { bin, installHint }', 'Fix or remove passthrough.', path);
428
+ issue('command_node_invalid', 'passthrough must be an object', typeName(raw), 'an object { bin, installHint }', 'Fix or remove passthrough.', path);
446
429
  return null;
447
430
  }
448
431
  if (!checkKeys(raw, PASSTHROUGH_KEYS, path, issue))
@@ -455,133 +438,21 @@ function validatePassthrough(raw, path, issue) {
455
438
  }
456
439
  return { bin: raw['bin'], installHint: raw['installHint'] };
457
440
  }
458
- function validatePluginLeaf(raw, path, issue) {
459
- if (!checkKeys(raw, PLUGIN_LEAF_KEYS, path, issue))
460
- return null;
461
- if (!checkCommon(raw, path, issue))
462
- return null;
463
- if (typeof raw['summary'] !== 'string' || raw['summary'].length === 0) {
464
- issue('command_node_invalid', `leaf summary must be a non-empty string`, typeName(raw['summary']), 'a one-line summary string', 'Add a summary.', `${path}.summary`);
465
- return null;
466
- }
467
- if (raw['outputKind'] !== 'object') {
468
- issue('command_node_invalid', `leaf outputKind must be "object" in v1`, String(raw['outputKind']), 'exactly "object"', 'Set outputKind to "object".', `${path}.outputKind`);
469
- return null;
470
- }
471
- const params = validateParams(raw['params'], `${path}.params`, issue);
472
- if (params === null)
473
- return null;
474
- const output = validateOutput(raw['output'], `${path}.output`, issue);
475
- if (output === null)
476
- return null;
477
- const effects = raw['effects'];
478
- if (!Array.isArray(effects) || effects.length === 0 || !effects.every((e) => typeof e === 'string')) {
479
- issue('command_node_invalid', `leaf effects must be a non-empty string array`, typeName(effects), 'a non-empty array of strings (["None. Read-only."] for read-only leaves)', 'Declare every persistent effect.', `${path}.effects`);
480
- return null;
481
- }
482
- return {
483
- kind: 'leaf',
484
- name: raw['name'],
485
- description: raw['description'],
486
- whenToUse: raw['whenToUse'],
487
- ...(raw['tier'] !== undefined ? { tier: raw['tier'] } : {}),
488
- summary: raw['summary'],
489
- params,
490
- output,
491
- outputKind: 'object',
492
- effects: effects,
493
- };
494
- }
495
- /** Validate a node for the configured CLI dialect (leaf must have rest). */
496
- export function validateConfiguredCliNode(raw, path, topLevel, issue) {
497
- if (!isObject(raw)) {
498
- issue('command_node_invalid', `node must be an object`, typeName(raw), 'a branch or leaf node object', 'Fix the node.', path);
499
- return null;
500
- }
501
- const kind = raw['kind'];
502
- if (kind === 'branch')
503
- return validateConfiguredCliBranch(raw, path, topLevel, issue);
504
- if (kind === 'leaf') {
505
- if (topLevel) {
506
- issue('command_node_invalid', `top-level node must be a branch`, 'leaf', 'a branch node with a rootEntry', 'Wrap the command in a top-level branch (noun) with a rootEntry.', `${path}.kind`);
507
- return null;
508
- }
509
- return validateConfiguredCliLeaf(raw, path, issue);
510
- }
511
- issue('command_node_invalid', `node kind must be "branch" or "leaf"`, String(kind), 'branch | leaf', 'Set kind to branch or leaf.', `${path}.kind`);
512
- return null;
513
- }
514
- function validateConfiguredCliBranch(raw, path, topLevel, issue) {
515
- if (!checkKeys(raw, BRANCH_KEYS, path, issue))
441
+ function validateLeaf(raw, path, transport, issue) {
442
+ if (!checkKeys(raw, transport === 'exec' ? EXEC_LEAF_KEYS : HTTP_LEAF_KEYS, path, issue))
516
443
  return null;
517
444
  if (!checkCommon(raw, path, issue))
518
445
  return null;
519
- const hasRoot = raw['rootEntry'] !== undefined;
520
- if (topLevel && !hasRoot) {
521
- issue('command_node_invalid', `top-level branch requires a rootEntry`, '(missing)', 'a rootEntry { concept, description, whenToUse }', 'Add a rootEntry to the top-level branch.', `${path}.rootEntry`);
522
- return null;
523
- }
524
- if (!topLevel && hasRoot) {
525
- issue('command_node_invalid', `nested branch must not declare a rootEntry`, 'rootEntry', 'no rootEntry (only top-level branches carry one)', 'Remove rootEntry from the nested branch.', `${path}.rootEntry`);
526
- return null;
527
- }
528
- let rootEntry;
529
- if (hasRoot) {
530
- const re = validateRootEntry(raw['rootEntry'], `${path}.rootEntry`, issue);
531
- if (re === null)
532
- return null;
533
- rootEntry = re;
534
- }
535
446
  if (typeof raw['summary'] !== 'string' || raw['summary'].length === 0) {
536
- issue('command_node_invalid', `branch summary must be a non-empty string`, typeName(raw['summary']), 'a one-line summary string', 'Add a summary.', `${path}.summary`);
447
+ issue('command_node_invalid', 'leaf summary must be a non-empty string', typeName(raw['summary']), 'a one-line summary string', 'Add a summary.', `${path}.summary`);
537
448
  return null;
538
449
  }
539
- if (raw['model'] !== undefined && typeof raw['model'] !== 'string') {
540
- issue('command_node_invalid', `branch model must be a string`, typeName(raw['model']), 'a string or omitted', 'Fix or remove model.', `${path}.model`);
541
- return null;
542
- }
543
- const children = raw['children'];
544
- if (!Array.isArray(children)) {
545
- issue('command_node_invalid', `branch children must be an array`, typeName(children), 'an array of branch/leaf nodes (may be empty for populated by mounts)', 'Fix children.', `${path}.children`);
546
- return null;
547
- }
548
- const validated = [];
549
- const childNames = new Set();
550
- for (let i = 0; i < children.length; i++) {
551
- const c = validateConfiguredCliNode(children[i], `${path}.children[${i}]`, false, issue);
552
- if (c === null)
553
- return null;
554
- if (childNames.has(c.name)) {
555
- issue('command_node_invalid', `duplicate child name`, c.name, 'unique child names within a branch', 'Rename the duplicate child.', `${path}.children[${i}].name`);
556
- return null;
557
- }
558
- childNames.add(c.name);
559
- validated.push(c);
560
- }
561
- return {
562
- kind: 'branch',
563
- name: raw['name'],
564
- description: raw['description'],
565
- whenToUse: raw['whenToUse'],
566
- ...(raw['tier'] !== undefined ? { tier: raw['tier'] } : {}),
567
- ...(rootEntry !== undefined ? { rootEntry } : {}),
568
- summary: raw['summary'],
569
- ...(raw['model'] !== undefined ? { model: raw['model'] } : {}),
570
- children: validated,
571
- };
572
- }
573
- function validateConfiguredCliLeaf(raw, path, issue) {
574
- if (!checkKeys(raw, CONFIGURED_LEAF_KEYS, path, issue))
575
- return null;
576
- if (!checkCommon(raw, path, issue))
577
- return null;
578
- if (typeof raw['summary'] !== 'string' || raw['summary'].length === 0) {
579
- issue('command_node_invalid', `leaf summary must be a non-empty string`, typeName(raw['summary']), 'a one-line summary string', 'Add a summary.', `${path}.summary`);
450
+ if (transport === 'exec' && raw['outputKind'] !== 'object') {
451
+ issue('command_node_invalid', 'leaf outputKind must be "object" in v1', String(raw['outputKind']), 'exactly "object"', 'Set outputKind to "object".', `${path}.outputKind`);
580
452
  return null;
581
453
  }
582
- // Configured CLI leaves require `rest`, not `outputKind`
583
- if (raw['rest'] === undefined) {
584
- issue('command_node_invalid', `configured CLI leaf requires a rest mapping`, '(missing)', 'a rest object with method, path, and params', 'Add a rest mapping.', `${path}.rest`);
454
+ if (transport === 'http' && raw['rest'] === undefined) {
455
+ issue('command_node_invalid', 'HTTP leaf requires a rest mapping', '(missing)', 'a rest object with method, path, and params', 'Add a rest mapping.', `${path}.rest`);
585
456
  return null;
586
457
  }
587
458
  const params = validateParams(raw['params'], `${path}.params`, issue);
@@ -591,26 +462,16 @@ function validateConfiguredCliLeaf(raw, path, issue) {
591
462
  if (output === null)
592
463
  return null;
593
464
  const effects = raw['effects'];
594
- if (!Array.isArray(effects) || effects.length === 0 || !effects.every((e) => typeof e === 'string')) {
595
- issue('command_node_invalid', `leaf effects must be a non-empty string array`, typeName(effects), 'a non-empty array of strings (["None. Read-only."] for read-only leaves)', 'Declare every persistent effect.', `${path}.effects`);
465
+ if (!Array.isArray(effects) || effects.length === 0 || !effects.every((effect) => typeof effect === 'string')) {
466
+ issue('command_node_invalid', 'leaf effects must be a non-empty string array', typeName(effects), 'a non-empty array of strings (["None. Read-only."] for read-only leaves)', 'Declare every persistent effect.', `${path}.effects`);
596
467
  return null;
597
468
  }
598
- // Validate the REST mapping (includes coalesced body source rule)
469
+ const base = { kind: 'leaf', name: raw['name'], description: raw['description'], whenToUse: raw['whenToUse'],
470
+ ...(raw['tier'] !== undefined ? { tier: raw['tier'] } : {}), summary: raw['summary'], params, output, effects: effects };
471
+ if (transport === 'exec')
472
+ return { ...base, outputKind: 'object' };
599
473
  const rest = validateRestMapping(raw['rest'], path, params, issue);
600
- if (rest === null)
601
- return null;
602
- return {
603
- kind: 'leaf',
604
- name: raw['name'],
605
- description: raw['description'],
606
- whenToUse: raw['whenToUse'],
607
- ...(raw['tier'] !== undefined ? { tier: raw['tier'] } : {}),
608
- summary: raw['summary'],
609
- params,
610
- output,
611
- effects: effects,
612
- rest,
613
- };
474
+ return rest === null ? null : { ...base, rest };
614
475
  }
615
476
  // ---------------------------------------------------------------------------
616
477
  // REST mapping validation (§4.4)
@@ -1,11 +1,5 @@
1
1
  import type { BranchDef } from '../command.js';
2
2
  import type { CommandRegistrySnapshot } from '../command-manifests/registry.js';
3
3
  import type { ValidatedContribution } from './discovery.js';
4
- /** Adapt legacy ValidatedContribution (plugin format) to transport-neutral
5
- * CommandContribution. Returns a snapshot conforming to the unified registry
6
- * contract, with an adaptLeaf callback that wraps executeExternalLeaf. */
7
4
  export declare function adaptPluginContributions(contributions: ValidatedContribution[]): CommandRegistrySnapshot;
8
- /** Map an accepted registry snapshot to real top-level BranchDefs, ready to
9
- * concatenate beside core subtrees in `buildRoot()`. Consumes transport-neutral
10
- * CommandContribution entries from a unified registry snapshot. */
11
5
  export declare function composeExternalSubtrees(snapshot: CommandRegistrySnapshot): BranchDef[];
@@ -1,88 +1,41 @@
1
- // Tree composer — adapt transport-neutral contributions into ordinary crtr
2
- // BranchDef/LeafDef values via the SAME defineBranch/defineRoot path core uses,
3
- // so external contributions get child-owned parent representations and the
4
- // existing help renderer with no second help engine. External nodes are
5
- // static-only (no dynamicState, no custom renderer); each leaf's run closure is
6
- // supplied by the contribution's adaptLeaf callback. Composition is deterministic:
7
- // contributions arrive already sorted by name from registry resolution.
8
1
  import { defineBranch, defineLeaf } from '../command.js';
9
- import { executeExternalLeaf } from './adapter.js';
10
- /** Adapt legacy ValidatedContribution (plugin format) to transport-neutral
11
- * CommandContribution. Returns a snapshot conforming to the unified registry
12
- * contract, with an adaptLeaf callback that wraps executeExternalLeaf. */
2
+ import { executeExternalLeaf } from './transport/exec-invoke.js';
3
+ import { invokeHttpLeaf } from './transport/http-invoke.js';
13
4
  export function adaptPluginContributions(contributions) {
14
- const adapted = contributions.map((vc) => ({
15
- contributor: {
16
- kind: 'plugin',
17
- name: vc.plugin.name,
18
- scope: vc.plugin.scope, // plugins are never builtin
19
- },
20
- node: vc.node,
21
- adaptLeaf: (leaf, commandPath) => ({
22
- outputKind: 'object',
23
- run: async (input) => executeExternalLeaf({
24
- plugin: vc.plugin,
25
- // Discovery rejects a leaf-bearing manifest that omits `executable`,
26
- // so a contribution reaching adaptLeaf always carries one.
27
- executable: vc.executable,
28
- commandPath: Array.from(commandPath),
29
- output: leaf.output,
30
- }, input),
31
- }),
32
- }));
33
5
  return {
34
- contributions: adapted,
6
+ contributions: contributions.map((contribution) => ({
7
+ contributor: { name: contribution.plugin.name, scope: contribution.plugin.scope },
8
+ node: contribution.node,
9
+ adaptLeaf: (leaf, commandPath) => {
10
+ const transport = contribution.transport;
11
+ if (transport.kind === 'exec')
12
+ return {
13
+ outputKind: 'object',
14
+ run: async (input) => executeExternalLeaf({ plugin: contribution.plugin, executable: transport.executable, commandPath: [...commandPath], output: leaf.output }, input),
15
+ };
16
+ const httpLeaf = leaf;
17
+ return {
18
+ outputKind: httpLeaf.rest.streaming === true ? 'jsonl' : 'object',
19
+ run: (input, context) => invokeHttpLeaf({
20
+ registration: { name: contribution.plugin.name, endpoint: transport.endpoint, ...(transport.authEnv !== undefined ? { authEnv: transport.authEnv } : {}) },
21
+ ...(contribution.manifest?.baseUrl !== undefined ? { baseUrl: contribution.manifest.baseUrl } : {}),
22
+ ...(contribution.manifest?.timeouts !== undefined ? { timeouts: contribution.manifest.timeouts } : {}),
23
+ params: httpLeaf.params, rest: httpLeaf.rest, output: httpLeaf.output, commandPath: [...commandPath],
24
+ }, input, context),
25
+ };
26
+ },
27
+ })),
35
28
  issues: [],
36
29
  };
37
30
  }
38
- /** Map an accepted registry snapshot to real top-level BranchDefs, ready to
39
- * concatenate beside core subtrees in `buildRoot()`. Consumes transport-neutral
40
- * CommandContribution entries from a unified registry snapshot. */
41
31
  export function composeExternalSubtrees(snapshot) {
42
- return snapshot.contributions.map((c) => buildBranch(c, c.node, [c.node.name]));
32
+ return snapshot.contributions.map((contribution) => buildBranch(contribution, contribution.node, [contribution.node.name]));
43
33
  }
44
34
  function buildBranch(contribution, node, path) {
45
- const rootEntry = node.rootEntry !== undefined
46
- ? {
47
- concept: node.rootEntry.concept,
48
- // Explicit field mapping: declarative description→desc, whenToUse→useWhen.
49
- desc: node.rootEntry.description,
50
- useWhen: node.rootEntry.whenToUse,
51
- }
52
- : undefined;
53
- return defineBranch({
54
- name: node.name,
55
- description: node.description,
56
- whenToUse: node.whenToUse,
57
- ...(node.tier !== undefined ? { tier: node.tier } : {}),
58
- ...(rootEntry !== undefined ? { rootEntry } : {}),
59
- ...(node.passthrough !== undefined ? { passthrough: node.passthrough } : {}),
60
- help: {
61
- name: path.join(' '),
62
- summary: node.summary,
63
- ...(node.model !== undefined ? { model: node.model } : {}),
64
- },
65
- children: node.children.map((child) => child.kind === 'branch'
66
- ? buildBranch(contribution, child, [...path, child.name])
67
- : buildLeaf(contribution, child, [...path, child.name])),
68
- });
35
+ const rootEntry = node.rootEntry === undefined ? undefined : { concept: node.rootEntry.concept, desc: node.rootEntry.description, useWhen: node.rootEntry.whenToUse };
36
+ return defineBranch({ name: node.name, description: node.description, whenToUse: node.whenToUse, ...(node.tier !== undefined ? { tier: node.tier } : {}), ...(rootEntry !== undefined ? { rootEntry } : {}), help: { name: path.join(' '), summary: node.summary, ...(node.model !== undefined ? { model: node.model } : {}) }, ...(node.passthrough !== undefined ? { passthrough: node.passthrough } : {}), children: node.children.map((child) => child.kind === 'branch' ? buildBranch(contribution, child, [...path, child.name]) : buildLeaf(contribution, child, [...path, child.name])) });
69
37
  }
70
38
  function buildLeaf(contribution, node, path) {
71
- // Use the contribution's adapter to get the outputKind and run implementation.
72
39
  const adapted = contribution.adaptLeaf(node, path);
73
- return defineLeaf({
74
- name: node.name,
75
- description: node.description,
76
- whenToUse: node.whenToUse,
77
- ...(node.tier !== undefined ? { tier: node.tier } : {}),
78
- help: {
79
- name: path.join(' '),
80
- summary: node.summary,
81
- params: node.params,
82
- output: node.output,
83
- outputKind: adapted.outputKind,
84
- effects: node.effects,
85
- },
86
- run: adapted.run,
87
- });
40
+ return defineLeaf({ name: node.name, description: node.description, whenToUse: node.whenToUse, ...(node.tier !== undefined ? { tier: node.tier } : {}), help: { name: path.join(' '), summary: node.summary, params: node.params, output: node.output, outputKind: adapted.outputKind, effects: node.effects }, run: adapted.run });
88
41
  }
@@ -1,74 +1,41 @@
1
- import type { InstalledPlugin } from '../../types.js';
2
- import { type DeclBranch as DeclBranchGeneric, type PluginDeclLeaf, type CommandManifestIssue } from '../command-manifests/schema.js';
3
- /** A stable, doctor-renderable validation issue. Never thrown — accumulated
4
- * and surfaced through `pkg plugin show` / `sys doctor`. */
1
+ import type { InstalledPlugin, PluginTransport } from '../../types.js';
2
+ import { type ValidatedCommandManifest } from '../command-manifests/manifest.js';
3
+ import type { CommandManifestIssue, DeclBranch as DeclBranchGeneric, DeclLeaf as DeclLeafGeneric } from '../command-manifests/schema.js';
4
+ import type { CommandRegistrySnapshot } from '../command-manifests/registry.js';
5
+ import type { HttpPluginManifestStore } from './store.js';
5
6
  export interface CommandDiscoveryIssue extends CommandManifestIssue {
6
- /** The offending plugin's name. */
7
7
  plugin: string;
8
8
  }
9
- export type { DeclRootEntry } from '../command-manifests/schema.js';
10
- export type { CommandIssueCode } from '../command-manifests/schema.js';
11
- export type DeclLeaf = PluginDeclLeaf;
12
- export type DeclBranch = DeclBranchGeneric<PluginDeclLeaf>;
13
- export type DeclNode = DeclBranchGeneric<PluginDeclLeaf> | PluginDeclLeaf;
14
- /** One accepted top-level contribution: a validated branch node plus the
15
- * plugin identity and canonical executable the adapter needs at run time. */
9
+ export type { DeclRootEntry, CommandIssueCode, TransportKind } from '../command-manifests/schema.js';
10
+ export type DeclLeaf = DeclLeafGeneric;
11
+ export type DeclBranch = DeclBranchGeneric<DeclLeafGeneric>;
12
+ export type DeclNode = DeclBranch | DeclLeaf;
16
13
  export interface ValidatedContribution {
17
14
  plugin: InstalledPlugin;
18
- /** Absolute, realpath-resolved executable path. Absent only for a manifest
19
- * that declares no leaf at all — a passthrough-only contribution execs the
20
- * external binary it names and never speaks the leaf protocol. */
21
- executable?: string;
22
- /** The validated top-level branch node (always kind 'branch'). */
23
- node: DeclBranchGeneric<PluginDeclLeaf>;
15
+ transport: PluginTransport;
16
+ node: DeclBranchGeneric<DeclLeafGeneric>;
17
+ manifest: ValidatedCommandManifest;
24
18
  }
25
- /** Per-plugin validation result — the Phase 2 lifecycle entry point. Reports
26
- * accepted contributions and every issue without executing the binary. */
27
19
  export interface PluginCommandValidation {
28
20
  plugin: InstalledPlugin;
29
- /** Absolute path to the plugin's commands.json (whether or not it loaded). */
30
21
  manifestPath: string;
31
- /** Absolute, resolved executable path when it passed path/exec-bit safety. */
32
- executable?: string;
22
+ transport?: PluginTransport;
33
23
  contributions: ValidatedContribution[];
34
24
  issues: CommandDiscoveryIssue[];
25
+ absentStore?: boolean;
26
+ httpManifestStore?: HttpPluginManifestStore;
35
27
  }
36
- /** The invocation-local registry snapshot the tree composer consumes. */
37
- export interface CommandRegistrySnapshot {
38
- /** Accepted top-level contributions, deterministically ordered by name. */
39
- contributions: ValidatedContribution[];
40
- /** Every discovery issue across all effective command plugins. */
41
- issues: CommandDiscoveryIssue[];
28
+ export interface AbsentStoreHydration {
29
+ hydratedAny: boolean;
30
+ diagnostics: string[];
42
31
  }
43
- /** The effective, deduped set of enabled plugins that declare a command
44
- * manifest, in scope-precedence order. Enumerated with existing machinery
45
- * only: `projectScopeRoots()` (profile-widened, nearest-first) via
46
- * `listInstalledPluginsInRoot('project', root)`, then user scope. Dedup is by
47
- * plugin NAME across ALL copies (highest precedence wins) BEFORE the enabled
48
- * filter — so a disabled higher-precedence copy suppresses lower-precedence
49
- * copies of the same name. */
50
32
  export declare function effectiveCommandPlugins(startDir?: string, profileId?: string | null): InstalledPlugin[];
51
- /** Get raw plugin candidates before the cross-plugin collision pass.
52
- * Each plugin validation includes its contributions and per-plugin issues;
53
- * call resolveCommandRegistry (or a later unified collision handler) to
54
- * produce the final snapshot with cross-plugin collision detection. */
33
+ export declare function validatePluginCommands(plugin: InstalledPlugin, reservedNames?: ReadonlySet<string>): PluginCommandValidation;
55
34
  export declare function discoverPluginCommandCandidates(startDir?: string, profileId?: string | null): PluginCommandValidation[];
56
- /** Validate every effective command plugin and compose an invocation-local
57
- * registry snapshot. `reservedNames` are the core top-level subtree names —
58
- * a contribution claiming one is silently skipped from the tree and recorded
59
- * as a `command_collision` issue (core always wins). A plugin-vs-plugin
60
- * top-level name collision drops all claimants with an issue. Accepted
61
- * contributions are sorted by name for deterministic help/order. */
62
- export declare function discoverCommandContributions(reservedNames: ReadonlySet<string>, startDir?: string, profileId?: string | null): CommandRegistrySnapshot;
63
- /** Validate every effective command plugin, including the cross-plugin
64
- * top-level name collision pass: a name claimed by more than one plugin is
65
- * dropped from EVERY claimant's contributions and a `command_collision`
66
- * issue is recorded on EACH claimant's validation, so per-plugin surfaces
67
- * (`sys doctor`, `pkg plugin show`) see it too. This is the entry point for
68
- * any surface that reports on mounted commands — the per-plugin
69
- * `validatePluginCommands` cannot see other plugins' claims. */
70
35
  export declare function validateEffectiveCommandPlugins(reservedNames: ReadonlySet<string>, startDir?: string, profileId?: string | null): PluginCommandValidation[];
71
- /** Validate one plugin's command manifest without executing its binary.
72
- * `reservedNames` are the core top-level names used for the core-collision
73
- * check; pass an empty set to skip it. Never throws. */
74
- export declare function validatePluginCommands(plugin: InstalledPlugin, reservedNames?: ReadonlySet<string>): PluginCommandValidation;
36
+ export declare function discoverCommandContributions(reservedNames: ReadonlySet<string>, startDir?: string, profileId?: string | null): {
37
+ contributions: ValidatedContribution[];
38
+ issues: CommandDiscoveryIssue[];
39
+ };
40
+ export declare function buildExternalCommandSnapshot(reservedCoreNames: ReadonlySet<string>): CommandRegistrySnapshot;
41
+ export declare function hydrateAbsentHttpPlugins(): Promise<AbsentStoreHydration>;