@crouton-kit/crouter 0.3.70 → 0.3.79
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.
- package/README.md +1 -1
- package/dist/build-root.d.ts +12 -4
- package/dist/build-root.js +25 -6
- package/dist/builtin-memory/00-runtime-base.md +3 -10
- package/dist/builtin-memory/04-base-worker.md +18 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +82 -5
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +1115 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +234 -71
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +2 -516
- package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
- package/dist/builtin-views/canvas/tui.mjs +8 -9
- package/dist/builtin-views/chat/tui.mjs +12 -12
- package/dist/builtin-views/git-pr/tui.mjs +7 -8
- package/dist/builtin-views/inbox/tui.mjs +27 -41
- package/dist/builtin-views/linkedin/tui.mjs +23 -37
- package/dist/builtin-views/prompt-review/tui.mjs +11 -11
- package/dist/builtin-views/settings/tui.mjs +11 -11
- package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -6
- package/dist/clients/attach/__tests__/attach-keybindings.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +113 -0
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
- package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/mermaid-render.test.js +28 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
- package/dist/clients/attach/attach-cmd.d.ts +9 -1
- package/dist/clients/attach/attach-cmd.js +841 -792
- package/dist/clients/attach/auth-pickers.d.ts +0 -12
- package/dist/clients/attach/auth-pickers.js +64 -15
- package/dist/clients/attach/canvas-panels.js +2 -3
- package/dist/clients/attach/chat-view.d.ts +7 -8
- package/dist/clients/attach/chat-view.js +119 -79
- package/dist/clients/attach/edit-diff-render.d.ts +6 -0
- package/dist/clients/attach/edit-diff-render.js +263 -0
- package/dist/clients/attach/graph-overlay.d.ts +12 -2
- package/dist/clients/attach/graph-overlay.js +83 -33
- package/dist/clients/attach/input-controller.d.ts +28 -2
- package/dist/clients/attach/input-controller.js +38 -5
- package/dist/clients/attach/mermaid-render.js +5 -2
- package/dist/clients/attach/pickers.d.ts +8 -7
- package/dist/clients/attach/pickers.js +11 -17
- package/dist/clients/attach/slash-commands.d.ts +9 -0
- package/dist/clients/attach/slash-commands.js +127 -7
- package/dist/clients/attach/titled-editor.d.ts +12 -1
- package/dist/clients/attach/titled-editor.js +103 -8
- package/dist/commands/canvas-browse.js +2 -2
- package/dist/commands/human/queue.js +3 -4
- package/dist/commands/memory/lint.js +39 -5
- package/dist/commands/memory/write.js +1 -0
- package/dist/commands/node.js +9 -2
- package/dist/commands/pkg/plugin-inspect.js +22 -1
- package/dist/commands/pkg/plugin-manage.js +31 -9
- package/dist/commands/surface-tmux-spread.js +1 -3
- package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
- package/dist/commands/sys/__tests__/setup-core.test.js +158 -1
- package/dist/commands/sys/config.js +18 -21
- package/dist/commands/sys/doctor.js +42 -4
- package/dist/commands/sys/setup-core.d.ts +49 -1
- package/dist/commands/sys/setup-core.js +161 -4
- package/dist/commands/sys/setup.d.ts +88 -0
- package/dist/commands/sys/setup.js +943 -169
- package/dist/commands/view-pick.d.ts +4 -0
- package/dist/commands/view-pick.js +17 -7
- package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
- package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +34 -9
- package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +1 -0
- package/dist/core/__tests__/command-plugins-surfaces.test.js +298 -0
- package/dist/core/__tests__/command-plugins.test.d.ts +1 -0
- package/dist/core/__tests__/command-plugins.test.js +444 -0
- package/dist/core/__tests__/fault-classifier.test.js +15 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +6 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +9 -1
- package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
- package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
- package/dist/core/__tests__/preview-registry-sync.test.js +30 -1
- package/dist/core/__tests__/scope-crouter-home-fence.test.d.ts +1 -0
- package/dist/core/__tests__/scope-crouter-home-fence.test.js +55 -0
- package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
- package/dist/core/__tests__/stream-watchdog.test.js +70 -0
- package/dist/core/__tests__/tmux-surface.test.js +72 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.d.ts +6 -0
- package/dist/core/canvas/browse/app.js +230 -41
- package/dist/core/canvas/browse/model.d.ts +10 -6
- package/dist/core/canvas/browse/model.js +20 -12
- package/dist/core/canvas/browse/pins.d.ts +4 -0
- package/dist/core/canvas/browse/pins.js +29 -0
- package/dist/core/canvas/browse/render.d.ts +13 -1
- package/dist/core/canvas/browse/render.js +72 -15
- package/dist/core/canvas/nav-model.js +5 -11
- package/dist/core/canvas/paths.d.ts +5 -0
- package/dist/core/canvas/paths.js +7 -0
- package/dist/core/command-plugins/adapter.d.ts +15 -0
- package/dist/core/command-plugins/adapter.js +145 -0
- package/dist/core/command-plugins/compose.d.ts +5 -0
- package/dist/core/command-plugins/compose.js +56 -0
- package/dist/core/command-plugins/discovery.d.ts +104 -0
- package/dist/core/command-plugins/discovery.js +565 -0
- package/dist/core/config.d.ts +48 -0
- package/dist/core/config.js +228 -110
- package/dist/core/fault-classifier.js +1 -1
- package/dist/core/fs-utils.d.ts +1 -0
- package/dist/core/fs-utils.js +15 -1
- package/dist/core/keybindings/__tests__/bespoke-consumers.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +40 -0
- package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
- package/dist/core/keybindings/catalog.d.ts +14 -0
- package/dist/core/keybindings/catalog.js +257 -0
- package/dist/core/keybindings/index.d.ts +5 -0
- package/dist/core/keybindings/index.js +4 -0
- package/dist/core/keybindings/match.d.ts +29 -0
- package/dist/core/keybindings/match.js +99 -0
- package/dist/core/keybindings/persistence.d.ts +14 -0
- package/dist/core/keybindings/persistence.js +31 -0
- package/dist/core/keybindings/resolve.d.ts +14 -0
- package/dist/core/keybindings/resolve.js +305 -0
- package/dist/core/keybindings/types.d.ts +37 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/memory-resolver.d.ts +1 -1
- package/dist/core/memory-resolver.js +10 -3
- package/dist/core/predicate.d.ts +5 -3
- package/dist/core/predicate.js +5 -3
- package/dist/core/profiles/select.d.ts +6 -0
- package/dist/core/profiles/select.js +86 -52
- package/dist/core/provider-management.d.ts +12 -0
- package/dist/core/provider-management.js +24 -0
- package/dist/core/runtime/broker-protocol.d.ts +33 -5
- package/dist/core/runtime/broker.js +164 -14
- package/dist/core/runtime/launch.d.ts +39 -6
- package/dist/core/runtime/launch.js +78 -20
- package/dist/core/runtime/naming.js +3 -3
- package/dist/core/runtime/pi-cli.d.ts +6 -0
- package/dist/core/runtime/pi-cli.js +16 -2
- package/dist/core/runtime/pi-vendored.d.ts +8 -0
- package/dist/core/runtime/pi-vendored.js +14 -0
- package/dist/core/runtime/placement.d.ts +2 -2
- package/dist/core/runtime/placement.js +4 -1
- package/dist/core/runtime/promote.js +4 -0
- package/dist/core/runtime/recap.d.ts +3 -3
- package/dist/core/runtime/recap.js +75 -47
- package/dist/core/runtime/recycle.js +6 -1
- package/dist/core/runtime/reset.js +5 -0
- package/dist/core/runtime/session-list-cache.d.ts +33 -0
- package/dist/core/runtime/session-list-cache.js +338 -0
- package/dist/core/runtime/spawn.js +52 -45
- package/dist/core/runtime/stream-watchdog.d.ts +26 -0
- package/dist/core/runtime/stream-watchdog.js +75 -0
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +2 -2
- package/dist/core/runtime/tmux.d.ts +18 -15
- package/dist/core/runtime/tmux.js +217 -110
- package/dist/core/scope.js +27 -4
- package/dist/core/subscription-state.d.ts +90 -0
- package/dist/core/subscription-state.js +762 -0
- package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
- package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
- package/dist/core/tui/host.d.ts +30 -1
- package/dist/core/tui/host.js +148 -34
- package/dist/core/view/contract.d.ts +17 -3
- package/dist/daemon/crtrd.js +253 -12
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
- package/dist/pi-extensions/canvas-recap.d.ts +4 -0
- package/dist/pi-extensions/canvas-recap.js +73 -46
- package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
- package/dist/pi-extensions/canvas-stophook.js +33 -4
- package/dist/types.d.ts +9 -26
- package/dist/types.js +1 -28
- package/dist/web-client/assets/{index-DiFuLcp6.js → index--md2ylfi.js} +1 -1
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +8 -4
- package/dist/pi-extensions/canvas-nav.d.ts +0 -43
- package/dist/pi-extensions/canvas-nav.js +0 -640
- package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
- package/dist/pi-extensions/widget-order-bus.js +0 -34
|
@@ -26,9 +26,11 @@ export const PROMPT_LINES = 3;
|
|
|
26
26
|
export const REPLY_LINES = 3;
|
|
27
27
|
export const PREVIEW_BODY = PROMPT_LINES + REPLY_LINES;
|
|
28
28
|
export const PREVIEW_HEIGHT = PREVIEW_BODY + 2;
|
|
29
|
-
// header = title + tab bar + status line + column header + separator (+
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// header = title + tab bar + status line + column header + separator (+ input line)
|
|
30
|
+
// `inputLine` is true whenever a text-input mode is open (search / rename / message)
|
|
31
|
+
// — each adds one prompt row above the column header.
|
|
32
|
+
export function headerHeight(inputLine) {
|
|
33
|
+
return 5 + (inputLine ? 1 : 0);
|
|
32
34
|
}
|
|
33
35
|
// ── Row table layout ──────────────────────────────────────────────────────────
|
|
34
36
|
// A row is three zones: a fixed-width STATUS rail (glyph + word) on the left, the
|
|
@@ -57,6 +59,7 @@ const FG_CYAN = '36';
|
|
|
57
59
|
const FG_GRAY = '90'; // bright-black
|
|
58
60
|
const FG_BRIGHT_YELLOW = '93';
|
|
59
61
|
const FG_BRIGHT_CYAN = '96'; // query-match highlight (ties to the cyan search accent)
|
|
62
|
+
const FG_MAGENTA = '35'; // pin star (distinct from the yellow ⚑ asks flag)
|
|
60
63
|
// STATUS_GLYPH + STATUS_COLOR are imported from ../status-glyph.js (the shared
|
|
61
64
|
// canvas-graph map). STATUS_COLOR there is NUMERIC SGR (e.g. 32) — wrap with
|
|
62
65
|
// `sgr()` where a string ANSI code is needed (Span fg / template literals accept
|
|
@@ -232,7 +235,7 @@ function assembleRow(left, right, width, color, lineBase, fill) {
|
|
|
232
235
|
}
|
|
233
236
|
/** One table row: `<status rail> <indent><collapse> <name> <flags>` ........ `<meta>`.
|
|
234
237
|
* `showCwd` adds the project column (All-dirs view); `now` drives the age. */
|
|
235
|
-
function rowLine(row, tree, width, isCursor, query, caps, showCwd, now) {
|
|
238
|
+
function rowLine(row, tree, width, isCursor, query, caps, showCwd, now, isPinned) {
|
|
236
239
|
const node = tree.nodes.get(row.id);
|
|
237
240
|
if (node === undefined)
|
|
238
241
|
return '';
|
|
@@ -254,12 +257,15 @@ function rowLine(row, tree, width, isCursor, query, caps, showCwd, now) {
|
|
|
254
257
|
// label live in the rail). Dim yellow so it reads as secondary detail.
|
|
255
258
|
if (r.hanging != null)
|
|
256
259
|
flags.push({ text: ` · ${faultSummary(r.hanging, now)}`, style: { fg: FG_YELLOW, dim: true } });
|
|
260
|
+
// Pin marker travels just before the name so a starred node is scannable in any
|
|
261
|
+
// tab/sort. Magenta keeps it distinct from the yellow ⚑ asks flag.
|
|
262
|
+
const pinMark = isPinned ? [{ text: '★ ', style: { fg: FG_MAGENTA, bold: true } }] : [];
|
|
257
263
|
const status = statusRail(r);
|
|
258
264
|
const right = metaCluster(r, now, showCwd);
|
|
259
265
|
const treeLead = `${indent}${collapse} `;
|
|
260
|
-
// Clip the NAME (only) so gutter + status + tree + name + flags + meta all
|
|
261
|
-
// the cluster is dropped first (assembleRow) when even a 4-col name won't fit
|
|
262
|
-
const fixed = spansWidth([gutter]) + spansWidth(status) + [...treeLead].length + spansWidth(flags);
|
|
266
|
+
// Clip the NAME (only) so gutter + status + tree + pin + name + flags + meta all
|
|
267
|
+
// fit; the cluster is dropped first (assembleRow) when even a 4-col name won't fit.
|
|
268
|
+
const fixed = spansWidth([gutter]) + spansWidth(status) + [...treeLead].length + spansWidth(pinMark) + spansWidth(flags);
|
|
263
269
|
const rightW = spansWidth(right);
|
|
264
270
|
const nameMax = Math.max(4, width - fixed - rightW - 1);
|
|
265
271
|
const nameStyle = { dim: !isCursor && terminal, bold: isCursor }; // dim terminal names; bold the cursor row
|
|
@@ -267,6 +273,7 @@ function rowLine(row, tree, width, isCursor, query, caps, showCwd, now) {
|
|
|
267
273
|
gutter,
|
|
268
274
|
...status,
|
|
269
275
|
{ text: treeLead, style: { dim: true } },
|
|
276
|
+
...pinMark,
|
|
270
277
|
...nameSpans(clip(r.name, nameMax), query, nameStyle),
|
|
271
278
|
...flags,
|
|
272
279
|
];
|
|
@@ -403,16 +410,63 @@ function previewPanel(r, width, caps, now, query) {
|
|
|
403
410
|
}
|
|
404
411
|
return out;
|
|
405
412
|
}
|
|
413
|
+
/** The keybinding help overlay — a full-screen cheatsheet that replaces the body
|
|
414
|
+
* while `?` is held open. Grouped by intent so the whole key surface is legible in
|
|
415
|
+
* one glance (the footer only ever advertises the common few). */
|
|
416
|
+
const HELP_ROWS = [
|
|
417
|
+
['Move', '↑/↓ j/k', 'up / down'],
|
|
418
|
+
['', 'g / G', 'top / bottom'],
|
|
419
|
+
['', '.', 'jump to this node (recenter)'],
|
|
420
|
+
['Tree', '→/← h/l', 'expand / collapse'],
|
|
421
|
+
['', 'space', 'toggle fold'],
|
|
422
|
+
['Tabs', 'Tab / 1-5', 'All · Live · Dormant · Attention · Pinned'],
|
|
423
|
+
['Find', '/', 'fuzzy search (name · kind · id · prompts)'],
|
|
424
|
+
['Pin', 'f', 'star / unstar — floats to top, Pinned tab'],
|
|
425
|
+
['Open', '⏎', 'resume the node'],
|
|
426
|
+
['Act', 'm', 'message the node (no resume)'],
|
|
427
|
+
['', 'R', 'rename the node'],
|
|
428
|
+
['', 'x', 'close-out node + subtree'],
|
|
429
|
+
['', 'K', 'revive now (a hanging node)'],
|
|
430
|
+
['View', 's', 'cycle sort (attention/tree/relevance/recency)'],
|
|
431
|
+
['', 'c', 'toggle cwd scope'],
|
|
432
|
+
['', 'r', 'toggle residents-only'],
|
|
433
|
+
['', 'p', 'toggle preview panel'],
|
|
434
|
+
['Quit', 'q / Esc', ''],
|
|
435
|
+
];
|
|
436
|
+
function renderHelp(size, caps) {
|
|
437
|
+
const cols = Math.max(20, size.cols);
|
|
438
|
+
const rows = Math.max(8, size.rows);
|
|
439
|
+
const width = cols - 1;
|
|
440
|
+
const lines = [];
|
|
441
|
+
lines.push(`${BOLD}Canvas navigator — keys${RESET}`);
|
|
442
|
+
lines.push('');
|
|
443
|
+
const dim = (s) => `${DIM}${s}${RESET}`;
|
|
444
|
+
for (const [group, keys, desc] of HELP_ROWS) {
|
|
445
|
+
const g = group.padEnd(6);
|
|
446
|
+
const k = keys.padEnd(12);
|
|
447
|
+
const line = ` ${dim(g)}${BOLD}${k}${RESET}${desc === '' ? '' : ` ${dim(desc)}`}`;
|
|
448
|
+
lines.push(clip(line, width));
|
|
449
|
+
}
|
|
450
|
+
lines.push('');
|
|
451
|
+
lines.push(dim(' ? or any key to close'));
|
|
452
|
+
while (lines.length < rows - 1)
|
|
453
|
+
lines.push('');
|
|
454
|
+
const body = lines.map((l) => `${l}${ESC}K`).join('\r\n');
|
|
455
|
+
return `${ESC}H${body}${ESC}J`;
|
|
456
|
+
}
|
|
406
457
|
/**
|
|
407
458
|
* Render the whole frame. Returns a single string that, written as-is, repaints
|
|
408
459
|
* the screen in place. `caps` gates all hue (defaults to a no-color frame so
|
|
409
460
|
* existing callers / non-TTY paths stay color-free).
|
|
410
461
|
*/
|
|
411
462
|
export function renderFrame(state, size, caps = { color: false, color256: false }) {
|
|
463
|
+
if (state.help === true)
|
|
464
|
+
return renderHelp(size, caps);
|
|
412
465
|
const cols = Math.max(20, size.cols);
|
|
413
466
|
const rows = Math.max(8, size.rows);
|
|
414
467
|
const width = cols - 1; // leave the last column for \x1b[K
|
|
415
468
|
const now = Date.now();
|
|
469
|
+
const inputMode = state.search || state.rename === true || state.message === true;
|
|
416
470
|
const lines = [];
|
|
417
471
|
// line 1 — title + right-aligned status summary (each glyph in its status hue).
|
|
418
472
|
const title = `${BOLD}Canvas${RESET} — ${state.totalNodes} nodes`;
|
|
@@ -428,10 +482,12 @@ export function renderFrame(state, size, caps = { color: false, color256: false
|
|
|
428
482
|
lines.push(tabBar(state.tab, caps.color));
|
|
429
483
|
// line 3 — status line (scope · sort · filter).
|
|
430
484
|
lines.push(statusLine(state));
|
|
431
|
-
// line 4 —
|
|
432
|
-
if (
|
|
433
|
-
const
|
|
434
|
-
|
|
485
|
+
// line 4 — the text-input prompt, only while a search / rename / message is open.
|
|
486
|
+
if (inputMode) {
|
|
487
|
+
const prefix = state.rename === true ? 'rename ▸' : state.message === true ? 'msg ▸' : '/';
|
|
488
|
+
const buf = state.rename === true ? (state.renameBuf ?? '') : state.message === true ? (state.messageBuf ?? '') : state.query;
|
|
489
|
+
const p = caps.color ? `${ESC}${FG_CYAN}m${prefix}${RESET}` : prefix;
|
|
490
|
+
lines.push(`${p} ${buf}▎`);
|
|
435
491
|
}
|
|
436
492
|
// column header (table labels) + separator.
|
|
437
493
|
const showCwd = state.cwdScope === null; // project column only matters across dirs
|
|
@@ -451,7 +507,8 @@ export function renderFrame(state, size, caps = { color: false, color256: false
|
|
|
451
507
|
}
|
|
452
508
|
else {
|
|
453
509
|
for (let i = start; i < end; i++) {
|
|
454
|
-
|
|
510
|
+
const vr = state.visible[i];
|
|
511
|
+
lines.push(rowLine(vr, state.tree, width, i === state.cursor, state.query, caps, showCwd, now, state.pinned.has(vr.id)));
|
|
455
512
|
}
|
|
456
513
|
for (let i = end - start; i < viewport; i++)
|
|
457
514
|
lines.push('');
|
|
@@ -477,11 +534,11 @@ export function renderFrame(state, size, caps = { color: false, color256: false
|
|
|
477
534
|
const selRow = state.visible[state.cursor];
|
|
478
535
|
const cursorRow = selRow !== undefined ? state.tree.nodes.get(selRow.id)?.row : undefined;
|
|
479
536
|
const hangingHint = cursorRow?.hanging != null ? 'K revive now ' : '';
|
|
480
|
-
const footer =
|
|
537
|
+
const footer = inputMode
|
|
481
538
|
? '⏎ commit Esc cancel ⌫ delete'
|
|
482
539
|
: state.readOnly === true
|
|
483
|
-
? '↑↓ move →/← tree
|
|
484
|
-
: `${hangingHint}↑↓ move
|
|
540
|
+
? '↑↓ move →/← tree / search f pin Tab tabs ? more q quit — read-only remote canvas'
|
|
541
|
+
: `${hangingHint}↑↓ move ⏎ resume / search f pin m msg x close R rename ? more q quit`;
|
|
485
542
|
lines.push(`${DIM}${clip(footer, width)}${RESET}`);
|
|
486
543
|
}
|
|
487
544
|
// Assemble: home, each line cleared to EOL, then clear below.
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
// nav-model.ts — the pure model/render layer
|
|
1
|
+
// nav-model.ts — the pure model/render layer for attach's canvas chrome.
|
|
2
2
|
//
|
|
3
|
-
// Extracted from src/pi-extensions/canvas-nav.ts so a SINGLE source feeds both
|
|
4
|
-
// the legacy pi extension (canvas-nav) and the headless `crtr surface attach` viewer.
|
|
5
3
|
// Everything here is pi-API-free: it reads the canvas (sqlite + meta.json + node
|
|
6
|
-
// telemetry) and produces ANSI-styled strings
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// Mutable interaction state that the legacy extension held at module scope —
|
|
11
|
-
// the per-node ask counts and the manual fold overrides — is threaded through
|
|
12
|
-
// parameters here (see `asks` and `FoldState`) so this layer owns no singletons
|
|
13
|
-
// and each consumer keeps its own state.
|
|
4
|
+
// telemetry) and produces ANSI-styled strings plus a flattened graph model.
|
|
5
|
+
// Per-view interaction state, including ask counts and manual fold overrides, is
|
|
6
|
+
// threaded through parameters (see `asks` and `FoldState`) so this layer owns no
|
|
7
|
+
// singletons and each consumer keeps its own state.
|
|
14
8
|
import { execFileSync } from 'node:child_process';
|
|
15
9
|
import { existsSync, readFileSync } from 'node:fs';
|
|
16
10
|
import { join } from 'node:path';
|
|
@@ -23,6 +23,11 @@ export declare function canvasDbPath(): string;
|
|
|
23
23
|
* local-disk hosts. Parent dir is created on use so both paths just work. */
|
|
24
24
|
export declare function viewSocketPath(nodeId: string): string;
|
|
25
25
|
export declare function nodesRoot(): string;
|
|
26
|
+
/** Global, cwd-agnostic broker cache of per-session picker metadata, keyed by
|
|
27
|
+
* absolute `.jsonl` path. Shared across every node's broker (paths are absolute)
|
|
28
|
+
* and survives broker restarts so the first `/resume` open after a restart is
|
|
29
|
+
* also fast. Machine-local performance state — safe to delete; rebuilt on demand. */
|
|
30
|
+
export declare function sessionListCachePath(): string;
|
|
26
31
|
export declare function nodeDir(nodeId: string): string;
|
|
27
32
|
export declare function contextDir(nodeId: string): string;
|
|
28
33
|
/** Replace the literal env-var tokens `$CRTR_CONTEXT_DIR` / `$CRTR_NODE_ID` in
|
|
@@ -63,6 +63,13 @@ export function viewSocketPath(nodeId) {
|
|
|
63
63
|
export function nodesRoot() {
|
|
64
64
|
return join(crtrHome(), 'nodes');
|
|
65
65
|
}
|
|
66
|
+
/** Global, cwd-agnostic broker cache of per-session picker metadata, keyed by
|
|
67
|
+
* absolute `.jsonl` path. Shared across every node's broker (paths are absolute)
|
|
68
|
+
* and survives broker restarts so the first `/resume` open after a restart is
|
|
69
|
+
* also fast. Machine-local performance state — safe to delete; rebuilt on demand. */
|
|
70
|
+
export function sessionListCachePath() {
|
|
71
|
+
return join(crtrHome(), 'session-list-cache.json');
|
|
72
|
+
}
|
|
66
73
|
export function nodeDir(nodeId) {
|
|
67
74
|
return join(nodesRoot(), nodeId);
|
|
68
75
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { InstalledPlugin } from '../../types.js';
|
|
2
|
+
import type { Field } from '../help.js';
|
|
3
|
+
export interface ExternalLeafSpec {
|
|
4
|
+
plugin: InstalledPlugin;
|
|
5
|
+
/** Absolute, resolved executable path. */
|
|
6
|
+
executable: string;
|
|
7
|
+
/** Full canonical command path, e.g. ['app', 'show']. */
|
|
8
|
+
commandPath: string[];
|
|
9
|
+
/** Declared leaf output fields — validated against the result before render. */
|
|
10
|
+
output: Field[];
|
|
11
|
+
}
|
|
12
|
+
/** Execute one external leaf and return its result object (crtr renders it via
|
|
13
|
+
* the generic renderer; `--json` mirrors it). Throws a CrtrError for any
|
|
14
|
+
* protocol violation or the plugin's own recoverable error. */
|
|
15
|
+
export declare function executeExternalLeaf(spec: ExternalLeafSpec, input: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// External leaf adapter — the execution seam for a command plugin's leaf.
|
|
2
|
+
//
|
|
3
|
+
// On explicit leaf invocation only (never on discovery/help), direct-spawn the
|
|
4
|
+
// plugin's canonical executable with `--crtr-command-protocol 1`, write one
|
|
5
|
+
// JSON request to stdin, and parse exactly one JSON response envelope from
|
|
6
|
+
// stdout. The envelope's `ok` is the source of truth; crtr owns rendering and
|
|
7
|
+
// error normalization. The executable inherits the caller's cwd + full
|
|
8
|
+
// environment and owns its own time budget (no crtr timeout) — this is a trust
|
|
9
|
+
// boundary, not a sandbox (installed code can already read the caller's files).
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { CrtrError } from '../errors.js';
|
|
12
|
+
import { diag } from '../io.js';
|
|
13
|
+
import { ExitCode } from '../../types.js';
|
|
14
|
+
const MAX_STDOUT = 10 * 1024 * 1024; // 10 MiB hard cap on plugin stdout.
|
|
15
|
+
const RESERVED_CODES = new Set(['internal', 'unknown_path', 'command_collision', 'plugin_protocol_error']);
|
|
16
|
+
// Strict snake_case: no leading/trailing underscore, no consecutive underscores.
|
|
17
|
+
const SNAKE = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/;
|
|
18
|
+
const NEXT = 'Run `crtr pkg plugin show` or `crtr sys doctor` to inspect the plugin.';
|
|
19
|
+
/** Execute one external leaf and return its result object (crtr renders it via
|
|
20
|
+
* the generic renderer; `--json` mirrors it). Throws a CrtrError for any
|
|
21
|
+
* protocol violation or the plugin's own recoverable error. */
|
|
22
|
+
export function executeExternalLeaf(spec, input) {
|
|
23
|
+
const request = {
|
|
24
|
+
protocolVersion: 1,
|
|
25
|
+
command: spec.commandPath,
|
|
26
|
+
input,
|
|
27
|
+
context: {
|
|
28
|
+
cwd: process.cwd(),
|
|
29
|
+
plugin: {
|
|
30
|
+
name: spec.plugin.name,
|
|
31
|
+
version: spec.plugin.version ?? spec.plugin.manifest.version ?? null,
|
|
32
|
+
scope: spec.plugin.scope,
|
|
33
|
+
root: spec.plugin.root,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
const res = spawnSync(spec.executable, ['--crtr-command-protocol', '1'], {
|
|
38
|
+
input: JSON.stringify(request),
|
|
39
|
+
cwd: process.cwd(),
|
|
40
|
+
env: process.env,
|
|
41
|
+
maxBuffer: MAX_STDOUT,
|
|
42
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
});
|
|
45
|
+
if (res.error !== undefined) {
|
|
46
|
+
const err = res.error;
|
|
47
|
+
// spawnSync signals a maxBuffer overrun as ENOBUFS (and kills the child);
|
|
48
|
+
// the exec-family code is accepted too for robustness across Node versions.
|
|
49
|
+
if (err.code === 'ENOBUFS' || err.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
|
|
50
|
+
throw protocolError(spec, `stdout exceeded the 10 MiB cap`, 'one JSON envelope under 10 MiB on stdout');
|
|
51
|
+
}
|
|
52
|
+
throw protocolError(spec, `executable could not be run (${err.code ?? err.message})`, 'a runnable command executable');
|
|
53
|
+
}
|
|
54
|
+
if (res.signal !== null) {
|
|
55
|
+
throw protocolError(spec, `executable was killed by signal ${res.signal}`, 'a clean exit with one JSON envelope on stdout');
|
|
56
|
+
}
|
|
57
|
+
const stdout = res.stdout ?? '';
|
|
58
|
+
let envelope;
|
|
59
|
+
try {
|
|
60
|
+
envelope = JSON.parse(stdout.trim());
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw protocolError(spec, `stdout was not a single JSON envelope`, 'exactly one JSON envelope on stdout');
|
|
64
|
+
}
|
|
65
|
+
if (!isObject(envelope)) {
|
|
66
|
+
throw protocolError(spec, `stdout was not a JSON object`, 'a { protocolVersion: 1, ok: boolean, ... } envelope');
|
|
67
|
+
}
|
|
68
|
+
if (envelope['protocolVersion'] !== 1) {
|
|
69
|
+
throw protocolError(spec, `unsupported protocolVersion ${String(envelope['protocolVersion'])}`, 'protocolVersion exactly 1');
|
|
70
|
+
}
|
|
71
|
+
if (typeof envelope['ok'] !== 'boolean') {
|
|
72
|
+
throw protocolError(spec, `envelope is missing a boolean "ok"`, 'a boolean ok field');
|
|
73
|
+
}
|
|
74
|
+
const ok = envelope['ok'];
|
|
75
|
+
// `ok` is the source of truth; an exit-code mismatch is a soft diagnostic.
|
|
76
|
+
if (ok && res.status !== 0) {
|
|
77
|
+
diag(`crtr: ${label(spec)} returned ok:true but exited ${res.status} (honoring the envelope)`);
|
|
78
|
+
}
|
|
79
|
+
else if (!ok && res.status === 0) {
|
|
80
|
+
diag(`crtr: ${label(spec)} returned ok:false but exited 0 (honoring the envelope)`);
|
|
81
|
+
}
|
|
82
|
+
if (ok) {
|
|
83
|
+
const result = envelope['result'];
|
|
84
|
+
if (!isObject(result)) {
|
|
85
|
+
throw protocolError(spec, `success envelope has no result object`, 'result: an object matching the declared output');
|
|
86
|
+
}
|
|
87
|
+
validateResult(spec, result);
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
// Recoverable command failure → structured CrtrError with the plugin's code.
|
|
91
|
+
const error = envelope['error'];
|
|
92
|
+
if (!isObject(error) || typeof error['code'] !== 'string' || typeof error['message'] !== 'string') {
|
|
93
|
+
throw protocolError(spec, `error envelope is missing a { code, message }`, 'error: { code, message, ... }');
|
|
94
|
+
}
|
|
95
|
+
const code = error['code'];
|
|
96
|
+
if (!SNAKE.test(code) || RESERVED_CODES.has(code)) {
|
|
97
|
+
throw protocolError(spec, `error code "${code}" is invalid or reserved`, `a lowercase snake_case code not in {${[...RESERVED_CODES].join(', ')}}`);
|
|
98
|
+
}
|
|
99
|
+
throw new CrtrError(code, error['message'], ExitCode.GENERAL, {
|
|
100
|
+
received: error['received'],
|
|
101
|
+
field: typeof error['field'] === 'string' ? error['field'] : undefined,
|
|
102
|
+
next: typeof error['next'] === 'string' ? error['next'] : NEXT,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** Enforce declared top-level output field presence + primitive/container
|
|
106
|
+
* type. A missing required field or a type mismatch is a protocol error. */
|
|
107
|
+
function validateResult(spec, result) {
|
|
108
|
+
for (const f of spec.output) {
|
|
109
|
+
const present = Object.prototype.hasOwnProperty.call(result, f.name) && result[f.name] !== undefined && result[f.name] !== null;
|
|
110
|
+
if (f.required && !present) {
|
|
111
|
+
throw protocolError(spec, `result is missing required output field "${f.name}"`, `a "${f.name}" field of type ${f.type}`);
|
|
112
|
+
}
|
|
113
|
+
if (present && !typeMatches(f.type, result[f.name])) {
|
|
114
|
+
throw protocolError(spec, `output field "${f.name}" has the wrong type`, `${f.name}: ${f.type}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Loose type check over the declared type vocabulary. Only the recognized
|
|
119
|
+
* primitive/container categories are enforced; an unrecognized declared type
|
|
120
|
+
* falls back to presence-only. */
|
|
121
|
+
function typeMatches(type, v) {
|
|
122
|
+
const t = type.toLowerCase();
|
|
123
|
+
if (t === 'string' || t === 'path')
|
|
124
|
+
return typeof v === 'string';
|
|
125
|
+
if (t === 'int' || t === 'integer')
|
|
126
|
+
return typeof v === 'number' && Number.isInteger(v);
|
|
127
|
+
if (t === 'number' || t === 'float')
|
|
128
|
+
return typeof v === 'number';
|
|
129
|
+
if (t === 'bool' || t === 'boolean')
|
|
130
|
+
return typeof v === 'boolean';
|
|
131
|
+
if (t === 'array' || t.endsWith('[]'))
|
|
132
|
+
return Array.isArray(v);
|
|
133
|
+
if (t === 'object')
|
|
134
|
+
return isObject(v);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
function protocolError(spec, received, expected) {
|
|
138
|
+
return new CrtrError('plugin_protocol_error', `${label(spec)}: ${received}. Expected ${expected}.`, ExitCode.GENERAL, { received, next: NEXT });
|
|
139
|
+
}
|
|
140
|
+
function label(spec) {
|
|
141
|
+
return `plugin "${spec.plugin.name}" command \`${spec.commandPath.join(' ')}\``;
|
|
142
|
+
}
|
|
143
|
+
function isObject(v) {
|
|
144
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
145
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BranchDef } from '../command.js';
|
|
2
|
+
import type { CommandRegistrySnapshot } from './discovery.js';
|
|
3
|
+
/** Map an accepted registry snapshot to real top-level BranchDefs, ready to
|
|
4
|
+
* concatenate beside core subtrees in `buildRoot()`. */
|
|
5
|
+
export declare function composeExternalSubtrees(snapshot: CommandRegistrySnapshot): BranchDef[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Tree composer — adapt validated declarative nodes 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
|
+
// the external adapter. Composition is deterministic: contributions arrive
|
|
7
|
+
// already sorted by name from discovery.
|
|
8
|
+
import { defineBranch, defineLeaf } from '../command.js';
|
|
9
|
+
import { executeExternalLeaf } from './adapter.js';
|
|
10
|
+
/** Map an accepted registry snapshot to real top-level BranchDefs, ready to
|
|
11
|
+
* concatenate beside core subtrees in `buildRoot()`. */
|
|
12
|
+
export function composeExternalSubtrees(snapshot) {
|
|
13
|
+
return snapshot.contributions.map((c) => buildBranch(c, c.node, [c.node.name]));
|
|
14
|
+
}
|
|
15
|
+
function buildBranch(contribution, node, path) {
|
|
16
|
+
const rootEntry = node.rootEntry !== undefined
|
|
17
|
+
? {
|
|
18
|
+
concept: node.rootEntry.concept,
|
|
19
|
+
// Explicit field mapping: declarative description→desc, whenToUse→useWhen.
|
|
20
|
+
desc: node.rootEntry.description,
|
|
21
|
+
useWhen: node.rootEntry.whenToUse,
|
|
22
|
+
}
|
|
23
|
+
: undefined;
|
|
24
|
+
return defineBranch({
|
|
25
|
+
name: node.name,
|
|
26
|
+
description: node.description,
|
|
27
|
+
whenToUse: node.whenToUse,
|
|
28
|
+
...(node.tier !== undefined ? { tier: node.tier } : {}),
|
|
29
|
+
...(rootEntry !== undefined ? { rootEntry } : {}),
|
|
30
|
+
help: {
|
|
31
|
+
name: path.join(' '),
|
|
32
|
+
summary: node.summary,
|
|
33
|
+
...(node.model !== undefined ? { model: node.model } : {}),
|
|
34
|
+
},
|
|
35
|
+
children: node.children.map((child) => child.kind === 'branch'
|
|
36
|
+
? buildBranch(contribution, child, [...path, child.name])
|
|
37
|
+
: buildLeaf(contribution, child, [...path, child.name])),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function buildLeaf(contribution, node, path) {
|
|
41
|
+
return defineLeaf({
|
|
42
|
+
name: node.name,
|
|
43
|
+
description: node.description,
|
|
44
|
+
whenToUse: node.whenToUse,
|
|
45
|
+
...(node.tier !== undefined ? { tier: node.tier } : {}),
|
|
46
|
+
help: {
|
|
47
|
+
name: path.join(' '),
|
|
48
|
+
summary: node.summary,
|
|
49
|
+
params: node.params,
|
|
50
|
+
output: node.output,
|
|
51
|
+
outputKind: 'object',
|
|
52
|
+
effects: node.effects,
|
|
53
|
+
},
|
|
54
|
+
run: async (input) => executeExternalLeaf({ plugin: contribution.plugin, executable: contribution.executable, commandPath: path, output: node.output }, input),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { InstalledPlugin } from '../../types.js';
|
|
2
|
+
import type { InputParam, Field } from '../help.js';
|
|
3
|
+
/** A stable, doctor-renderable validation issue. Never thrown — accumulated
|
|
4
|
+
* and surfaced through `pkg plugin show` / `sys doctor`. */
|
|
5
|
+
export interface CommandDiscoveryIssue {
|
|
6
|
+
/** Stable code the surfaces branch on. */
|
|
7
|
+
code: CommandIssueCode;
|
|
8
|
+
/** The offending plugin's name. */
|
|
9
|
+
plugin: string;
|
|
10
|
+
/** Dotted path to the offending field/node within the manifest, when known. */
|
|
11
|
+
path?: string;
|
|
12
|
+
message: string;
|
|
13
|
+
received: string;
|
|
14
|
+
expected: string;
|
|
15
|
+
next: string;
|
|
16
|
+
}
|
|
17
|
+
export type CommandIssueCode = 'command_manifest_unreadable' | 'command_manifest_invalid' | 'command_schema_version' | 'command_path_unsafe' | 'command_not_executable' | 'command_parent_invalid' | 'command_node_invalid' | 'command_collision';
|
|
18
|
+
/** The validated declarative node schema (static-only — no closures, no
|
|
19
|
+
* dynamicState, no renderers). Params/output reuse the existing public help
|
|
20
|
+
* vocabulary so composition maps them straight through. */
|
|
21
|
+
export interface DeclRootEntry {
|
|
22
|
+
concept: string;
|
|
23
|
+
description: string;
|
|
24
|
+
whenToUse: string;
|
|
25
|
+
}
|
|
26
|
+
export interface DeclBranch {
|
|
27
|
+
kind: 'branch';
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
whenToUse: string;
|
|
31
|
+
tier?: 'normal' | 'common' | 'important';
|
|
32
|
+
/** Required on a top-level branch, forbidden on a nested one. */
|
|
33
|
+
rootEntry?: DeclRootEntry;
|
|
34
|
+
summary: string;
|
|
35
|
+
model?: string;
|
|
36
|
+
children: DeclNode[];
|
|
37
|
+
}
|
|
38
|
+
export interface DeclLeaf {
|
|
39
|
+
kind: 'leaf';
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
whenToUse: string;
|
|
43
|
+
tier?: 'normal' | 'common' | 'important';
|
|
44
|
+
summary: string;
|
|
45
|
+
params: InputParam[];
|
|
46
|
+
output: Field[];
|
|
47
|
+
outputKind: 'object';
|
|
48
|
+
effects: string[];
|
|
49
|
+
}
|
|
50
|
+
export type DeclNode = DeclBranch | DeclLeaf;
|
|
51
|
+
/** One accepted top-level contribution: a validated branch node plus the
|
|
52
|
+
* plugin identity and canonical executable the adapter needs at run time. */
|
|
53
|
+
export interface ValidatedContribution {
|
|
54
|
+
plugin: InstalledPlugin;
|
|
55
|
+
/** Absolute, realpath-resolved executable path. */
|
|
56
|
+
executable: string;
|
|
57
|
+
/** The validated top-level branch node (always kind 'branch'). */
|
|
58
|
+
node: DeclBranch;
|
|
59
|
+
}
|
|
60
|
+
/** Per-plugin validation result — the Phase 2 lifecycle entry point. Reports
|
|
61
|
+
* accepted contributions and every issue without executing the binary. */
|
|
62
|
+
export interface PluginCommandValidation {
|
|
63
|
+
plugin: InstalledPlugin;
|
|
64
|
+
/** Absolute path to the plugin's commands.json (whether or not it loaded). */
|
|
65
|
+
manifestPath: string;
|
|
66
|
+
/** Absolute, resolved executable path when it passed path/exec-bit safety. */
|
|
67
|
+
executable?: string;
|
|
68
|
+
contributions: ValidatedContribution[];
|
|
69
|
+
issues: CommandDiscoveryIssue[];
|
|
70
|
+
}
|
|
71
|
+
/** The invocation-local registry snapshot the tree composer consumes. */
|
|
72
|
+
export interface CommandRegistrySnapshot {
|
|
73
|
+
/** Accepted top-level contributions, deterministically ordered by name. */
|
|
74
|
+
contributions: ValidatedContribution[];
|
|
75
|
+
/** Every discovery issue across all effective command plugins. */
|
|
76
|
+
issues: CommandDiscoveryIssue[];
|
|
77
|
+
}
|
|
78
|
+
/** The effective, deduped set of enabled plugins that declare a command
|
|
79
|
+
* manifest, in scope-precedence order. Enumerated with existing machinery
|
|
80
|
+
* only: `projectScopeRoots()` (profile-widened, nearest-first) via
|
|
81
|
+
* `listInstalledPluginsInRoot('project', root)`, then user scope. Dedup is by
|
|
82
|
+
* plugin NAME across ALL copies (highest precedence wins) BEFORE the enabled
|
|
83
|
+
* filter — so a disabled higher-precedence copy suppresses lower-precedence
|
|
84
|
+
* copies of the same name. */
|
|
85
|
+
export declare function effectiveCommandPlugins(startDir?: string, profileId?: string | null): InstalledPlugin[];
|
|
86
|
+
/** Validate every effective command plugin and compose an invocation-local
|
|
87
|
+
* registry snapshot. `reservedNames` are the core top-level subtree names —
|
|
88
|
+
* a contribution claiming one is silently skipped from the tree and recorded
|
|
89
|
+
* as a `command_collision` issue (core always wins). A plugin-vs-plugin
|
|
90
|
+
* top-level name collision drops all claimants with an issue. Accepted
|
|
91
|
+
* contributions are sorted by name for deterministic help/order. */
|
|
92
|
+
export declare function discoverCommandContributions(reservedNames: ReadonlySet<string>, startDir?: string, profileId?: string | null): CommandRegistrySnapshot;
|
|
93
|
+
/** Validate every effective command plugin, including the cross-plugin
|
|
94
|
+
* top-level name collision pass: a name claimed by more than one plugin is
|
|
95
|
+
* dropped from EVERY claimant's contributions and a `command_collision`
|
|
96
|
+
* issue is recorded on EACH claimant's validation, so per-plugin surfaces
|
|
97
|
+
* (`sys doctor`, `pkg plugin show`) see it too. This is the entry point for
|
|
98
|
+
* any surface that reports on mounted commands — the per-plugin
|
|
99
|
+
* `validatePluginCommands` cannot see other plugins' claims. */
|
|
100
|
+
export declare function validateEffectiveCommandPlugins(reservedNames: ReadonlySet<string>, startDir?: string, profileId?: string | null): PluginCommandValidation[];
|
|
101
|
+
/** Validate one plugin's command manifest without executing its binary.
|
|
102
|
+
* `reservedNames` are the core top-level names used for the core-collision
|
|
103
|
+
* check; pass an empty set to skip it. Never throws. */
|
|
104
|
+
export declare function validatePluginCommands(plugin: InstalledPlugin, reservedNames?: ReadonlySet<string>): PluginCommandValidation;
|