@crouton-kit/crouter 0.3.28 → 0.3.30
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/dist/build-root.d.ts +15 -7
- package/dist/build-root.js +48 -34
- package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +20 -18
- package/dist/builtin-memory/design.md +1 -1
- package/dist/builtin-memory/development.md +1 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/planning.md +1 -1
- package/dist/builtin-memory/spec.md +6 -4
- package/dist/builtin-personas/orchestration-kernel.md +4 -5
- package/dist/builtin-personas/runtime-base.md +5 -4
- package/dist/builtin-personas/spec/PERSONA.md +8 -4
- package/dist/builtin-personas/spec/orchestrator.md +5 -3
- package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
- package/dist/builtin-views/canvas/core.mjs +586 -0
- package/dist/builtin-views/canvas/text.mjs +58 -0
- package/dist/builtin-views/canvas/tui.mjs +165 -0
- package/dist/builtin-views/canvas/web.jsx +120 -0
- package/dist/builtin-views/git-pr/core.mjs +673 -0
- package/dist/builtin-views/git-pr/text.mjs +84 -0
- package/dist/builtin-views/git-pr/tui.mjs +302 -0
- package/dist/builtin-views/git-pr/web.jsx +216 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
- package/dist/builtin-views/inbox/core.mjs +1273 -0
- package/dist/builtin-views/inbox/text.mjs +73 -0
- package/dist/builtin-views/inbox/tui.mjs +312 -0
- package/dist/builtin-views/inbox/web.jsx +188 -0
- package/dist/builtin-views/linkedin/core.mjs +906 -0
- package/dist/builtin-views/linkedin/text.mjs +69 -0
- package/dist/builtin-views/linkedin/tui.mjs +427 -0
- package/dist/builtin-views/linkedin/web.jsx +206 -0
- package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
- package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
- package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
- package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
- package/dist/cli.js +22 -17
- package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
- package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
- package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
- package/dist/clients/attach/__tests__/git-info.test.js +31 -0
- package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +282 -46
- package/dist/clients/attach/auth-pickers.d.ts +9 -0
- package/dist/clients/attach/auth-pickers.js +194 -0
- package/dist/clients/attach/canvas-panels.d.ts +4 -2
- package/dist/clients/attach/canvas-panels.js +45 -17
- package/dist/clients/attach/chat-view.d.ts +27 -2
- package/dist/clients/attach/chat-view.js +98 -9
- package/dist/clients/attach/clipboard-image.d.ts +47 -8
- package/dist/clients/attach/clipboard-image.js +150 -26
- package/dist/clients/attach/clipboard-text.d.ts +3 -0
- package/dist/clients/attach/clipboard-text.js +39 -0
- package/dist/clients/attach/config-load.d.ts +54 -7
- package/dist/clients/attach/config-load.js +99 -3
- package/dist/clients/attach/context-message.d.ts +12 -0
- package/dist/clients/attach/context-message.js +72 -0
- package/dist/clients/attach/git-info.d.ts +20 -0
- package/dist/clients/attach/git-info.js +44 -0
- package/dist/clients/attach/graph-overlay.d.ts +13 -4
- package/dist/clients/attach/graph-overlay.js +59 -19
- package/dist/clients/attach/input-controller.d.ts +75 -13
- package/dist/clients/attach/input-controller.js +305 -67
- package/dist/clients/attach/pickers.d.ts +58 -0
- package/dist/clients/attach/pickers.js +171 -0
- package/dist/clients/attach/slash-commands.d.ts +22 -0
- package/dist/clients/attach/slash-commands.js +91 -25
- package/dist/clients/attach/titled-editor.d.ts +33 -0
- package/dist/clients/attach/titled-editor.js +84 -0
- package/dist/clients/attach/view-socket.d.ts +18 -1
- package/dist/clients/attach/view-socket.js +70 -1
- package/dist/clients/web/dev-server.d.ts +7 -0
- package/dist/clients/web/dev-server.js +59 -0
- package/dist/clients/web/events.d.ts +14 -0
- package/dist/clients/web/events.js +151 -0
- package/dist/clients/web/server.d.ts +18 -0
- package/dist/clients/web/server.js +450 -0
- package/dist/clients/web/web-cmd.d.ts +2 -0
- package/dist/clients/web/web-cmd.js +120 -0
- package/dist/commands/canvas.js +1 -2
- package/dist/commands/chord.js +4 -25
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
- package/dist/commands/memory/lint.js +14 -2
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/memory/shared.d.ts +7 -1
- package/dist/commands/memory/shared.js +18 -3
- package/dist/commands/memory/write.js +8 -4
- package/dist/commands/memory.js +4 -4
- package/dist/commands/node.d.ts +1 -1
- package/dist/commands/node.js +64 -65
- package/dist/commands/pkg.js +1 -2
- package/dist/commands/revive.js +11 -3
- package/dist/commands/sys/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +3 -2
- package/dist/commands/view-cycle.js +2 -2
- package/dist/commands/view-list.js +8 -8
- package/dist/commands/view-new.js +21 -17
- package/dist/commands/view-pick.js +1 -1
- package/dist/commands/view-run.js +35 -14
- package/dist/commands/view.js +8 -6
- package/dist/commands/workspace.d.ts +2 -0
- package/dist/commands/workspace.js +161 -0
- package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
- package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
- package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
- package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
- package/dist/core/__tests__/child-death-wake.test.js +56 -111
- package/dist/core/__tests__/close.test.js +14 -20
- package/dist/core/__tests__/context-intro.test.js +19 -19
- package/dist/core/__tests__/daemon-boot.test.js +30 -23
- package/dist/core/__tests__/draw-style.test.js +1 -1
- package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +17 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +22 -2
- package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
- package/dist/core/__tests__/focuses.test.js +25 -13
- package/dist/core/__tests__/fork.test.js +22 -5
- package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
- package/dist/core/__tests__/full/cascade-close.test.js +22 -8
- package/dist/core/__tests__/full/detach-focus.test.js +66 -169
- package/dist/core/__tests__/helpers/harness.d.ts +6 -9
- package/dist/core/__tests__/helpers/harness.js +19 -36
- package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
- package/dist/core/__tests__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/listing-completeness.test.js +2 -2
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
- package/dist/core/__tests__/memory-resolver.test.js +1 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -2
- package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
- package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
- package/dist/core/__tests__/relaunch-root.test.js +114 -0
- package/dist/core/__tests__/reset.test.js +19 -79
- package/dist/core/__tests__/steer-note.test.js +9 -7
- package/dist/core/__tests__/tmux-surface.test.js +13 -7
- package/dist/core/bootstrap.d.ts +0 -3
- package/dist/core/bootstrap.js +1 -143
- package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
- package/dist/core/canvas/browse/app.js +131 -8
- package/dist/core/canvas/browse/model.d.ts +23 -5
- package/dist/core/canvas/browse/model.js +68 -13
- package/dist/core/canvas/browse/render.d.ts +7 -1
- package/dist/core/canvas/browse/render.js +178 -47
- package/dist/core/canvas/canvas.js +1 -1
- package/dist/core/canvas/focuses.d.ts +4 -4
- package/dist/core/canvas/focuses.js +7 -7
- package/dist/core/canvas/nav-model.js +8 -7
- package/dist/core/canvas/render.d.ts +68 -9
- package/dist/core/canvas/render.js +195 -61
- package/dist/core/canvas/types.d.ts +16 -23
- package/dist/core/config.js +1 -4
- package/dist/core/memory-resolver.js +43 -5
- package/dist/core/runtime/bearings.d.ts +1 -1
- package/dist/core/runtime/bearings.js +8 -8
- package/dist/core/runtime/branded-host.d.ts +16 -0
- package/dist/core/runtime/branded-host.js +127 -0
- package/dist/core/runtime/broker-protocol.d.ts +212 -6
- package/dist/core/runtime/broker.d.ts +4 -1
- package/dist/core/runtime/broker.js +494 -58
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +6 -14
- package/dist/core/runtime/host.d.ts +14 -34
- package/dist/core/runtime/host.js +14 -50
- package/dist/core/runtime/launch.d.ts +12 -9
- package/dist/core/runtime/launch.js +27 -15
- package/dist/core/runtime/lifecycle.d.ts +1 -1
- package/dist/core/runtime/lifecycle.js +15 -19
- package/dist/core/runtime/nodes.d.ts +0 -55
- package/dist/core/runtime/nodes.js +6 -74
- package/dist/core/runtime/placement.d.ts +91 -335
- package/dist/core/runtime/placement.js +291 -858
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +24 -6
- package/dist/core/runtime/recap.d.ts +8 -0
- package/dist/core/runtime/recap.js +107 -0
- package/dist/core/runtime/recycle.js +25 -61
- package/dist/core/runtime/reset.d.ts +43 -37
- package/dist/core/runtime/reset.js +131 -218
- package/dist/core/runtime/revive.d.ts +9 -29
- package/dist/core/runtime/revive.js +40 -139
- package/dist/core/runtime/spawn.d.ts +15 -16
- package/dist/core/runtime/spawn.js +165 -194
- package/dist/core/runtime/surface-bg.d.ts +11 -0
- package/dist/core/runtime/surface-bg.js +68 -0
- package/dist/core/runtime/tmux.d.ts +15 -52
- package/dist/core/runtime/tmux.js +35 -99
- package/dist/core/scope.d.ts +1 -2
- package/dist/core/scope.js +1 -5
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
- package/dist/core/skill-sync/builtins.d.ts +42 -0
- package/dist/core/skill-sync/builtins.js +112 -0
- package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
- package/dist/core/skill-sync/claude-plugins.js +71 -0
- package/dist/core/skill-sync/engine.d.ts +42 -0
- package/dist/core/skill-sync/engine.js +633 -0
- package/dist/core/skill-sync/export.d.ts +23 -0
- package/dist/core/skill-sync/export.js +86 -0
- package/dist/core/skill-sync/manifest.d.ts +64 -0
- package/dist/core/skill-sync/manifest.js +181 -0
- package/dist/core/skill-sync/profile.d.ts +76 -0
- package/dist/core/skill-sync/profile.js +173 -0
- package/dist/core/skill-sync/snapshot.d.ts +57 -0
- package/dist/core/skill-sync/snapshot.js +120 -0
- package/dist/core/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.js +3 -3
- package/dist/core/substrate/on-read.js +68 -6
- package/dist/core/substrate/render.d.ts +8 -11
- package/dist/core/substrate/render.js +29 -43
- package/dist/core/substrate/schema.d.ts +10 -3
- package/dist/core/substrate/schema.js +6 -3
- package/dist/core/tui/host.d.ts +12 -4
- package/dist/core/tui/host.js +280 -149
- package/dist/core/view/bridge.d.ts +10 -0
- package/dist/core/view/bridge.js +31 -0
- package/dist/core/view/chrome.d.ts +9 -0
- package/dist/core/view/chrome.js +22 -0
- package/dist/core/view/contract.d.ts +171 -0
- package/dist/core/view/contract.js +23 -0
- package/dist/core/view/loader.d.ts +31 -0
- package/dist/core/view/loader.js +188 -0
- package/dist/core/view/transport-local.d.ts +7 -0
- package/dist/core/view/transport-local.js +70 -0
- package/dist/core/view/transport.d.ts +4 -0
- package/dist/core/view/transport.js +15 -0
- package/dist/daemon/crtrd.d.ts +38 -27
- package/dist/daemon/crtrd.js +303 -376
- package/dist/daemon/manage.js +6 -1
- package/dist/index.js +1 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
- package/dist/pi-extensions/canvas-context-intro.js +4 -4
- package/dist/pi-extensions/canvas-doc-substrate.js +16 -16
- package/dist/pi-extensions/canvas-nav.js +5 -0
- package/dist/pi-extensions/canvas-recap.d.ts +37 -0
- package/dist/pi-extensions/canvas-recap.js +236 -0
- package/dist/pi-extensions/canvas-resume.js +2 -1
- package/dist/pi-extensions/canvas-stophook.d.ts +14 -12
- package/dist/pi-extensions/canvas-stophook.js +87 -101
- package/dist/pi-extensions/canvas-view.js +2 -1
- package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
- package/dist/pi-extensions/widget-order-bus.js +34 -0
- package/dist/prompts/view.d.ts +2 -2
- package/dist/prompts/view.js +145 -73
- package/dist/types.d.ts +1 -6
- package/dist/types.js +1 -3
- package/dist/web/ViewChrome.d.ts +7 -0
- package/dist/web/ViewChrome.js +28 -0
- package/dist/web/ViewPane.d.ts +39 -0
- package/dist/web/ViewPane.js +48 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +16 -0
- package/dist/web/runtime.d.ts +39 -0
- package/dist/web/runtime.js +133 -0
- package/dist/web/states.d.ts +24 -0
- package/dist/web/states.js +24 -0
- package/dist/web/transport-http.d.ts +5 -0
- package/dist/web/transport-http.js +28 -0
- package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
- package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
- package/dist/web-client/index.html +13 -0
- package/package.json +20 -6
- package/dist/builtin-views/canvas/client.mjs +0 -303
- package/dist/builtin-views/canvas/view.mjs +0 -576
- package/dist/builtin-views/git-pr/client.mjs +0 -440
- package/dist/builtin-views/git-pr/view.mjs +0 -675
- package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
- package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
- package/dist/builtin-views/inbox/view.mjs +0 -889
- package/dist/builtin-views/linkedin/client.mjs +0 -610
- package/dist/builtin-views/linkedin/view.mjs +0 -1171
- package/dist/commands/pkg/bridge.d.ts +0 -1
- package/dist/commands/pkg/bridge.js +0 -137
- package/dist/commands/skill/author.d.ts +0 -3
- package/dist/commands/skill/author.js +0 -140
- package/dist/commands/skill/shared.d.ts +0 -3
- package/dist/commands/skill/shared.js +0 -19
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +0 -21
- package/dist/commands/tmux-spread.d.ts +0 -2
- package/dist/commands/tmux-spread.js +0 -144
- package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
- package/dist/core/__tests__/full/placement-focus.test.js +0 -309
- package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
- package/dist/core/__tests__/full/placement-revive.test.js +0 -238
- package/dist/core/__tests__/full/placement-teardown.test.js +0 -297
- package/dist/core/__tests__/home-session.test.js +0 -186
- package/dist/core/__tests__/relaunch.test.js +0 -335
- package/dist/core/bridge-map.d.ts +0 -19
- package/dist/core/bridge-map.js +0 -73
- package/dist/core/tui/contract.d.ts +0 -83
- package/dist/core/tui/contract.js +0 -8
- package/dist/core/tui/loader.d.ts +0 -16
- package/dist/core/tui/loader.js +0 -94
- package/dist/prompts/skill.d.ts +0 -2
- package/dist/prompts/skill.js +0 -650
- /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
- /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/titled-editor.test.d.ts} +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// `crtr sys sync` — the SOLE entry point for bidirectional crtr ↔ Claude skill
|
|
2
|
+
// sync (R-U1). Loads the layered manifest (P2), reconciles each enrolled pair
|
|
3
|
+
// once via the direction-agnostic engine (P6), summarizes the outcome, and
|
|
4
|
+
// sets the exit code: non-zero IFF any pair conflicted (R-E7). A malformed
|
|
5
|
+
// manifest (R-U7) or an unknown `--pair` id (OD-3) aborts hard before any
|
|
6
|
+
// reconcile runs — no lenient fallback, no partial write.
|
|
7
|
+
import { defineLeaf } from '../../core/command.js';
|
|
8
|
+
import { usage } from '../../core/errors.js';
|
|
9
|
+
import { emit, isJsonOutput } from '../../core/io.js';
|
|
10
|
+
import { ExitCode } from '../../types.js';
|
|
11
|
+
import { readLayeredManifest } from '../../core/skill-sync/manifest.js';
|
|
12
|
+
import { resolveProfile } from '../../core/skill-sync/profile.js';
|
|
13
|
+
import { reconcilePair } from '../../core/skill-sync/engine.js';
|
|
14
|
+
/** Render the per-pair status table + summary line as agent-ready markdown.
|
|
15
|
+
* Shown for both the clean and the conflict exit so the caller always sees the
|
|
16
|
+
* full partition. */
|
|
17
|
+
function renderSync(results, summary, dryRun) {
|
|
18
|
+
const parts = [];
|
|
19
|
+
parts.push(dryRun
|
|
20
|
+
? 'crtr ↔ Claude skill sync — DRY RUN (nothing written):'
|
|
21
|
+
: 'crtr ↔ Claude skill sync:');
|
|
22
|
+
if (results.length === 0) {
|
|
23
|
+
parts.push('No enrolled pairs.');
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const rows = results
|
|
27
|
+
.map((r) => `| ${r.id} | ${r.status} | ${r.wrote} |`)
|
|
28
|
+
.join('\n');
|
|
29
|
+
parts.push(`| pair | status | wrote |\n| --- | --- | --- |\n${rows}`);
|
|
30
|
+
}
|
|
31
|
+
parts.push(`synced: ${summary.synced.length} · conflicts: ${summary.conflicts.length} · ` +
|
|
32
|
+
`noop: ${summary.noop.length} · files written: ${summary.wrote}`);
|
|
33
|
+
if (summary.conflicts.length > 0) {
|
|
34
|
+
parts.push(`Conflicts (NOT written): ${summary.conflicts.join(', ')}. ` +
|
|
35
|
+
'Each conflicting pair has a git-style report under ' +
|
|
36
|
+
'`~/.crouter/skill-sync/conflicts/<id>.md` — resolve the endpoints by hand, then re-run.');
|
|
37
|
+
}
|
|
38
|
+
return parts.join('\n\n');
|
|
39
|
+
}
|
|
40
|
+
/** Drift label for the read-only `--status` view, derived from a dry-run
|
|
41
|
+
* reconcile: `noop` = already in agreement, `synced` = a clean merge the engine
|
|
42
|
+
* WOULD write, `conflict` = diverged and blocked. These are the only three
|
|
43
|
+
* statuses `reconcilePair` returns. */
|
|
44
|
+
const DRIFT = {
|
|
45
|
+
noop: 'in-sync',
|
|
46
|
+
synced: 'would-write',
|
|
47
|
+
conflict: 'conflict',
|
|
48
|
+
};
|
|
49
|
+
/** Render one endpoint as `scope/name` (or `<scope>:<plugin>/<name>` for a
|
|
50
|
+
* plugin-scoped side) so a reader sees where each half of the pair lives. */
|
|
51
|
+
function endpointLabel(ep) {
|
|
52
|
+
return ep.scope === 'plugin' || ep.scope === 'claude-plugin'
|
|
53
|
+
? `${ep.scope}:${ep.plugin}/${ep.name}`
|
|
54
|
+
: `${ep.scope}/${ep.name}`;
|
|
55
|
+
}
|
|
56
|
+
/** Render the read-only `--status` inventory: every selected pair with its
|
|
57
|
+
* crtr↔Claude endpoints and drift state, computed via a dry-run reconcile so
|
|
58
|
+
* nothing is written. `results` is keyed back to `pairs` by id. */
|
|
59
|
+
function renderStatus(pairs, results) {
|
|
60
|
+
const parts = [];
|
|
61
|
+
parts.push('crtr ↔ Claude skill-sync pairs (read-only; nothing written):');
|
|
62
|
+
if (pairs.length === 0) {
|
|
63
|
+
parts.push('No enrolled pairs.');
|
|
64
|
+
return parts.join('\n\n');
|
|
65
|
+
}
|
|
66
|
+
const byId = new Map(results.map((r) => [r.id, r]));
|
|
67
|
+
const rows = pairs
|
|
68
|
+
.map((p) => {
|
|
69
|
+
const drift = DRIFT[byId.get(p.id).status];
|
|
70
|
+
return `| ${p.id} | ${endpointLabel(p.crtr)} | ${endpointLabel(p.claude)} | ${drift} |`;
|
|
71
|
+
})
|
|
72
|
+
.join('\n');
|
|
73
|
+
parts.push(`| pair | crtr | claude | drift |\n| --- | --- | --- | --- |\n${rows}`);
|
|
74
|
+
const inSync = results.filter((r) => r.status === 'noop').length;
|
|
75
|
+
const wouldWrite = results.filter((r) => r.status === 'synced').length;
|
|
76
|
+
const conflict = results.filter((r) => r.status === 'conflict').length;
|
|
77
|
+
parts.push(`in-sync: ${inSync} · would-write: ${wouldWrite} · conflict: ${conflict}`);
|
|
78
|
+
return parts.join('\n\n');
|
|
79
|
+
}
|
|
80
|
+
export const sysSyncLeaf = defineLeaf({
|
|
81
|
+
name: 'sync',
|
|
82
|
+
description: 'reconcile crtr ↔ Claude skill pairs (bidirectional)',
|
|
83
|
+
whenToUse: 'you want enrolled crtr skills and their Claude SKILL.md counterparts brought back into agreement. Performs the single, direction-agnostic 3-way reconcile over every pair in the skill-sync manifest (or just one via --pair), writing both sides + the merge base when clean and a conflict report when not.',
|
|
84
|
+
help: {
|
|
85
|
+
name: 'sys sync',
|
|
86
|
+
summary: 'reconcile enrolled crtr ↔ Claude skill pairs (bidirectional 3-way merge)',
|
|
87
|
+
params: [
|
|
88
|
+
{
|
|
89
|
+
kind: 'flag',
|
|
90
|
+
name: 'pair',
|
|
91
|
+
type: 'string',
|
|
92
|
+
required: false,
|
|
93
|
+
constraint: 'Reconcile only the pair with this id. Unknown id → hard error naming it + the valid ids, nothing written. Default: every enrolled pair.',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
kind: 'flag',
|
|
97
|
+
name: 'dry-run',
|
|
98
|
+
type: 'bool',
|
|
99
|
+
required: false,
|
|
100
|
+
constraint: 'Compute every merge and report the would-be status, but write nothing — no endpoint, snapshot, or conflict report touched.',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
kind: 'flag',
|
|
104
|
+
name: 'status',
|
|
105
|
+
type: 'bool',
|
|
106
|
+
required: false,
|
|
107
|
+
constraint: 'Read-only inventory mode: instead of reconciling, list every enrolled pair (honoring --pair) with its crtr↔Claude endpoints and drift state (in-sync / would-write / conflict), computed via a dry-run reconcile. Writes nothing and never exits non-zero on drift; a malformed manifest or unknown --pair id still aborts hard.',
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
output: [
|
|
111
|
+
{ name: 'synced', type: 'string[]', required: true, constraint: 'Pair ids written through cleanly to both endpoints + the merge base.' },
|
|
112
|
+
{ name: 'conflicts', type: 'string[]', required: true, constraint: 'Pair ids that diverged and were NOT written; each has a report under ~/.crouter/skill-sync/conflicts/<id>.md. Non-empty → exit non-zero.' },
|
|
113
|
+
{ name: 'noop', type: 'string[]', required: true, constraint: 'Pair ids already in agreement — nothing to do.' },
|
|
114
|
+
{ name: 'wrote', type: 'number', required: true, constraint: 'Total endpoint files created/updated across all pairs (0 under --dry-run).' },
|
|
115
|
+
],
|
|
116
|
+
outputKind: 'object',
|
|
117
|
+
effects: [
|
|
118
|
+
'For each clean pair: writes the merged SKILL.md + assets to BOTH the crtr and Claude endpoints and updates the per-pair merge snapshot under ~/.crouter/skill-sync/snapshots/<id>/.',
|
|
119
|
+
'For each conflicting pair: writes NOTHING to the endpoints; writes a git-style conflict report to ~/.crouter/skill-sync/conflicts/<id>.md.',
|
|
120
|
+
'Exits non-zero iff any pair conflicted. A malformed manifest or an unknown --pair id aborts hard (non-zero) before any reconcile and writes nothing.',
|
|
121
|
+
'With --dry-run: read-only — computes every merge and the would-be summary, touches no endpoint, snapshot, or report.',
|
|
122
|
+
'With --status: read-only — emits the pair inventory + drift table (in-sync / would-write / conflict) instead of reconciling; writes nothing and exits zero regardless of drift.',
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
run: async (input) => {
|
|
126
|
+
const pairFilter = input['pair'];
|
|
127
|
+
const dryRun = input['dryRun'] ?? false;
|
|
128
|
+
const statusMode = input['status'] ?? false;
|
|
129
|
+
// Malformed manifest throws here (R-U7) → dispatcher's handle() → non-zero.
|
|
130
|
+
const { pairs } = readLayeredManifest();
|
|
131
|
+
let selected = pairs;
|
|
132
|
+
if (pairFilter !== undefined) {
|
|
133
|
+
selected = pairs.filter((p) => p.id === pairFilter);
|
|
134
|
+
if (selected.length === 0) {
|
|
135
|
+
// OD-3 / R-O2 — unknown id aborts hard, naming the bad id + valid ids,
|
|
136
|
+
// nothing written.
|
|
137
|
+
const valid = pairs.map((p) => p.id);
|
|
138
|
+
throw usage(`unknown pair id: ${pairFilter}`, {
|
|
139
|
+
received: pairFilter,
|
|
140
|
+
next: `Valid pair ids: ${valid.length > 0 ? valid.join(', ') : '(none)'}. Run \`crtr sys sync -h\`.`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// --status: read-only inventory. Run the same engine in dry-run mode and
|
|
145
|
+
// render endpoints + drift; never write, never exit non-zero on drift.
|
|
146
|
+
if (statusMode) {
|
|
147
|
+
const results = selected.map((p) => reconcilePair(p, resolveProfile(p.frontmatter), { dryRun: true }));
|
|
148
|
+
if (isJsonOutput()) {
|
|
149
|
+
emit({
|
|
150
|
+
pairs: selected.map((p, i) => ({
|
|
151
|
+
id: p.id,
|
|
152
|
+
crtr: p.crtr,
|
|
153
|
+
claude: p.claude,
|
|
154
|
+
drift: DRIFT[results[i].status],
|
|
155
|
+
})),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
process.stdout.write(renderStatus(selected, results) + '\n');
|
|
160
|
+
}
|
|
161
|
+
process.exit(ExitCode.SUCCESS);
|
|
162
|
+
}
|
|
163
|
+
// One conflict must NOT abort the loop (R-X3): collect every pair's result,
|
|
164
|
+
// partition, then decide the exit code once. A structural error (an
|
|
165
|
+
// unresolvable endpoint, both sides missing) is a hard config fault and is
|
|
166
|
+
// allowed to propagate — only content conflicts are a collected outcome.
|
|
167
|
+
const results = [];
|
|
168
|
+
for (const pair of selected) {
|
|
169
|
+
const profile = resolveProfile(pair.frontmatter);
|
|
170
|
+
results.push(reconcilePair(pair, profile, { dryRun }));
|
|
171
|
+
}
|
|
172
|
+
const summary = {
|
|
173
|
+
synced: results.filter((r) => r.status === 'synced').map((r) => r.id),
|
|
174
|
+
conflicts: results.filter((r) => r.status === 'conflict').map((r) => r.id),
|
|
175
|
+
noop: results.filter((r) => r.status === 'noop').map((r) => r.id),
|
|
176
|
+
wrote: results.reduce((n, r) => n + r.wrote, 0),
|
|
177
|
+
};
|
|
178
|
+
if (isJsonOutput()) {
|
|
179
|
+
emit(summary);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
process.stdout.write(renderSync(results, summary, dryRun) + '\n');
|
|
183
|
+
}
|
|
184
|
+
// R-E7 — exit non-zero IFF a conflict was surfaced.
|
|
185
|
+
process.exit(summary.conflicts.length > 0 ? ExitCode.GENERAL : ExitCode.SUCCESS);
|
|
186
|
+
},
|
|
187
|
+
});
|
package/dist/commands/sys.js
CHANGED
|
@@ -4,19 +4,20 @@ import { defineBranch } from '../core/command.js';
|
|
|
4
4
|
import { configBranch } from './sys/config.js';
|
|
5
5
|
import { sysDoctorLeaf } from './sys/doctor.js';
|
|
6
6
|
import { sysFeedbackLeaf } from './sys/feedback.js';
|
|
7
|
+
import { sysSyncLeaf } from './sys/sync.js';
|
|
7
8
|
import { sysUpdateLeaf, sysVersionLeaf } from './sys/update.js';
|
|
8
9
|
export function registerSys() {
|
|
9
10
|
return defineBranch({
|
|
10
11
|
name: 'sys',
|
|
11
12
|
rootEntry: {
|
|
12
13
|
concept: 'crtr configuration, diagnostics, and self-management',
|
|
13
|
-
desc: 'config, doctor, update, version, feedback',
|
|
14
|
+
desc: 'config, doctor, sync, update, version, feedback',
|
|
14
15
|
useWhen: 'managing the crtr installation',
|
|
15
16
|
},
|
|
16
17
|
help: {
|
|
17
18
|
name: 'sys',
|
|
18
19
|
summary: 'crtr system configuration, diagnostics, and self-management',
|
|
19
20
|
},
|
|
20
|
-
children: [configBranch, sysDoctorLeaf, sysFeedbackLeaf, sysUpdateLeaf, sysVersionLeaf],
|
|
21
|
+
children: [configBranch, sysDoctorLeaf, sysFeedbackLeaf, sysSyncLeaf, sysUpdateLeaf, sysVersionLeaf],
|
|
21
22
|
});
|
|
22
23
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// tmux-only; a pane with no @crtr_view tag (not a view monitor) is a no-op.
|
|
16
16
|
// Output is discarded by the keybinding, so this just acts.
|
|
17
17
|
import { defineLeaf } from '../core/command.js';
|
|
18
|
-
import { listViews, resolveView,
|
|
18
|
+
import { listViews, resolveView, loadCore } from '../core/view/loader.js';
|
|
19
19
|
// Commands reach the tmux driver through placement.ts (the sanctioned
|
|
20
20
|
// model-over-driver seam, §5.1) — never `./tmux.js` directly.
|
|
21
21
|
import { inTmux, currentTmux, getPaneOption, setPaneOption, paneCurrentPath, respawnPaneSync, piCommand, } from '../core/runtime/placement.js';
|
|
@@ -82,7 +82,7 @@ export const viewCycleLeaf = defineLeaf({
|
|
|
82
82
|
if (rv === null)
|
|
83
83
|
continue;
|
|
84
84
|
try {
|
|
85
|
-
await
|
|
85
|
+
await loadCore(rv);
|
|
86
86
|
targetId = cand;
|
|
87
87
|
break;
|
|
88
88
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// `crtr view list` — enumerate every resolvable view across scopes.
|
|
2
2
|
//
|
|
3
|
-
// Lists id · title · description · scope. Loads each view to read its
|
|
4
|
-
// title/description; a malformed view is shown with an error marker (it
|
|
5
|
-
// crashes the listing). Plain text — pipeable as-is. The raw array is
|
|
6
|
-
// behind --json for tooling.
|
|
3
|
+
// Lists id · title · description · scope. Loads each view's core.mjs to read its
|
|
4
|
+
// manifest title/description; a malformed view is shown with an error marker (it
|
|
5
|
+
// never crashes the listing). Plain text — pipeable as-is. The raw array is
|
|
6
|
+
// available behind --json for tooling.
|
|
7
7
|
import { defineLeaf } from '../core/command.js';
|
|
8
|
-
import { listViews,
|
|
8
|
+
import { listViews, loadCore } from '../core/view/loader.js';
|
|
9
9
|
export const viewListLeaf = defineLeaf({
|
|
10
10
|
name: 'list',
|
|
11
11
|
description: 'enumerate available views (id · title · description · scope)',
|
|
@@ -24,11 +24,11 @@ export const viewListLeaf = defineLeaf({
|
|
|
24
24
|
const rows = [];
|
|
25
25
|
for (const r of listViews()) {
|
|
26
26
|
try {
|
|
27
|
-
const
|
|
27
|
+
const core = await loadCore(r);
|
|
28
28
|
rows.push({
|
|
29
29
|
id: r.id,
|
|
30
|
-
title:
|
|
31
|
-
description:
|
|
30
|
+
title: core.manifest.title,
|
|
31
|
+
description: core.manifest.description ?? '',
|
|
32
32
|
scope: r.scope,
|
|
33
33
|
});
|
|
34
34
|
}
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
// `crtr view new <name>` — scaffold a runnable view
|
|
1
|
+
// `crtr view new <name>` — scaffold a runnable dual-target view into a scope dir.
|
|
2
2
|
//
|
|
3
|
-
// Writes
|
|
4
|
-
// (
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// Writes the four contract files (core.mjs + tui.mjs + web.jsx + text.mjs) under
|
|
4
|
+
// <viewsDir(scope)>/<name>/ from the stubs in prompts/view.ts (mkdir -p first).
|
|
5
|
+
// Refuses if a view of that name already resolves. The scaffold runs as-is via
|
|
6
|
+
// `crtr view run <name>` (TUI) and `crtr view serve <name>` (web). Mirrors how
|
|
7
|
+
// skill authoring scaffolds (resolve scope → ensure dir → write → point at run).
|
|
7
8
|
import { mkdirSync, existsSync } from 'node:fs';
|
|
8
9
|
import { writeFileSync } from 'node:fs';
|
|
9
10
|
import { join } from 'node:path';
|
|
10
11
|
import { defineLeaf } from '../core/command.js';
|
|
11
12
|
import { usage } from '../core/errors.js';
|
|
12
|
-
import { resolveView } from '../core/
|
|
13
|
+
import { resolveView, findViewDir } from '../core/view/loader.js';
|
|
13
14
|
import { viewsDir, projectScopeRoot, ensureProjectScopeRoot } from '../core/scope.js';
|
|
14
15
|
import { viewScaffold } from '../prompts/view.js';
|
|
15
16
|
export const viewNewLeaf = defineLeaf({
|
|
16
17
|
name: 'new',
|
|
17
|
-
description: 'scaffold a runnable view
|
|
18
|
-
whenToUse: 'you want to author a new view — this writes
|
|
18
|
+
description: 'scaffold a runnable dual-target view directory into a scope dir',
|
|
19
|
+
whenToUse: 'you want to author a new view — this writes the four contract files (core.mjs + tui.mjs + web.jsx + text.mjs, with the contract documented inline) into the user or project scope, ready to open immediately with `crtr view run <name>` (TUI) or `crtr view serve <name>` (web) and to edit from there. Reach for it to start a new view rather than hand-writing the directory',
|
|
19
20
|
help: {
|
|
20
21
|
name: 'view new',
|
|
21
|
-
summary: 'scaffold <viewsDir(scope)>/<name>/
|
|
22
|
+
summary: 'scaffold <viewsDir(scope)>/<name>/{core,tui,text}.mjs + web.jsx from the stubs; refuses if the name already resolves',
|
|
22
23
|
params: [
|
|
23
24
|
{ kind: 'positional', name: 'name', required: true, constraint: 'View id / directory name. Must not already resolve as a view.' },
|
|
24
25
|
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Where to scaffold. Default: project if available, else user.' },
|
|
@@ -26,13 +27,14 @@ export const viewNewLeaf = defineLeaf({
|
|
|
26
27
|
{ kind: 'flag', name: 'description', type: 'string', required: false, constraint: 'Manifest description (picker + `view list`). Default: empty.' },
|
|
27
28
|
],
|
|
28
29
|
output: [
|
|
29
|
-
{ name: 'path', type: 'string', required: true, constraint: 'Absolute path to the scaffolded view.
|
|
30
|
+
{ name: 'path', type: 'string', required: true, constraint: 'Absolute path to the scaffolded view directory.' },
|
|
31
|
+
{ name: 'files', type: 'array', required: true, constraint: 'The filenames written into the view directory.' },
|
|
30
32
|
{ name: 'scope', type: 'string', required: true, constraint: 'The scope it was written to (user|project).' },
|
|
31
33
|
{ name: 'run', type: 'string', required: true, constraint: 'The command to run the new view.' },
|
|
32
34
|
],
|
|
33
35
|
outputKind: 'object',
|
|
34
36
|
effects: [
|
|
35
|
-
'Creates the view directory and
|
|
37
|
+
'Creates the view directory and the four contract files (core.mjs + tui.mjs + web.jsx + text.mjs) at the resolved location (mkdir -p).',
|
|
36
38
|
'Initializes the project scope dir if --scope project is chosen and none exists yet.',
|
|
37
39
|
],
|
|
38
40
|
},
|
|
@@ -48,7 +50,7 @@ export const viewNewLeaf = defineLeaf({
|
|
|
48
50
|
// Refuse if a view of this name already resolves (any scope wins).
|
|
49
51
|
const existing = resolveView(name);
|
|
50
52
|
if (existing !== null) {
|
|
51
|
-
throw usage(`view already exists: ${name} (${existing.scope}) at ${existing.
|
|
53
|
+
throw usage(`view already exists: ${name} (${existing.scope}) at ${existing.dir}. ` +
|
|
52
54
|
`Pick another name, or edit it directly.`);
|
|
53
55
|
}
|
|
54
56
|
// Ensure the project scope root exists before computing its views dir.
|
|
@@ -59,12 +61,14 @@ export const viewNewLeaf = defineLeaf({
|
|
|
59
61
|
throw usage(`no ${scope} scope available to scaffold into — try --scope user.`);
|
|
60
62
|
}
|
|
61
63
|
const dir = join(baseDir, name);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
throw usage(`view file already exists: ${entry}`);
|
|
64
|
+
if (findViewDir(name) !== null || existsSync(join(dir, 'core.mjs'))) {
|
|
65
|
+
throw usage(`view directory already exists: ${dir}`);
|
|
65
66
|
}
|
|
66
67
|
mkdirSync(dir, { recursive: true });
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
const files = viewScaffold({ id: name, title, description });
|
|
69
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
70
|
+
writeFileSync(join(dir, filename), content, 'utf8');
|
|
71
|
+
}
|
|
72
|
+
return { path: dir, files: Object.keys(files), scope, run: `crtr view run ${name}` };
|
|
69
73
|
},
|
|
70
74
|
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// plain list and exits 0. Reuses the core/tui terminal + draw primitives.
|
|
8
8
|
import { execFileSync } from 'node:child_process';
|
|
9
9
|
import { defineLeaf } from '../core/command.js';
|
|
10
|
-
import { listViews } from '../core/
|
|
10
|
+
import { listViews } from '../core/view/loader.js';
|
|
11
11
|
import { setupTerminal, restoreTerminal, getTerminalSize, parseKeypress, } from '../core/tui/terminal.js';
|
|
12
12
|
import { createDraw, detectColorCaps } from '../core/tui/draw.js';
|
|
13
13
|
export const viewPickLeaf = defineLeaf({
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
// `crtr view run <name>` — resolve, load, and host a view.
|
|
1
|
+
// `crtr view run <name>` — resolve, load, and host a view's TUI target.
|
|
2
2
|
//
|
|
3
|
-
// Resolves the view across scopes (project→user→builtin),
|
|
4
|
-
//
|
|
5
|
-
// non-TTY path: when stdin is NOT a TTY it
|
|
6
|
-
// so `crtr view run <name> | cat`
|
|
3
|
+
// Resolves the view across scopes (project→user→builtin), loads its core.mjs +
|
|
4
|
+
// tui.mjs (+ optional text.mjs), then hands them to the dual-target core host.
|
|
5
|
+
// The host owns the screen and the non-TTY path: when stdin is NOT a TTY it
|
|
6
|
+
// prints the text presenter's dump and exits 0, so `crtr view run <name> | cat`
|
|
7
|
+
// works anywhere. A view that ships no core.mjs is not a view — run fails with a
|
|
8
|
+
// clear error naming the contract, never a fallback. The interactive path is
|
|
7
9
|
// tmux-only (crtr is tmux-only) — outside tmux with a TTY we notify + no-op,
|
|
8
10
|
// never a non-tmux fallback. --port / --target forward verbatim onto
|
|
9
11
|
// host.options (camelCased, stringified); crtr does not interpret them.
|
|
@@ -17,8 +19,8 @@
|
|
|
17
19
|
// a pipe (non-TTY) placement is meaningless, so they degrade to the static dump.
|
|
18
20
|
import { defineLeaf } from '../core/command.js';
|
|
19
21
|
import { InputError } from '../core/io.js';
|
|
20
|
-
import {
|
|
21
|
-
import { resolveView,
|
|
22
|
+
import { runCoreView } from '../core/tui/host.js';
|
|
23
|
+
import { resolveView, findViewDir, loadCore, loadTui, loadText, requireTui, listViews, } from '../core/view/loader.js';
|
|
22
24
|
// Commands reach the tmux driver through placement.ts (the sanctioned
|
|
23
25
|
// model-over-driver seam, §5.1) — never `./tmux.js` directly.
|
|
24
26
|
import { inTmux, currentTmux, openNodeWindow, splitWindow, piCommand, setPaneOption, } from '../core/runtime/placement.js';
|
|
@@ -66,6 +68,17 @@ export const viewRunLeaf = defineLeaf({
|
|
|
66
68
|
}
|
|
67
69
|
const r = resolveView(name);
|
|
68
70
|
if (r === null) {
|
|
71
|
+
// A directory exists but holds no core.mjs — it is not a valid view (e.g. a
|
|
72
|
+
// pre-contract `view.mjs`-only dir). Name the contract, don't fall back.
|
|
73
|
+
const dir = findViewDir(name);
|
|
74
|
+
if (dir !== null) {
|
|
75
|
+
throw new InputError({
|
|
76
|
+
error: 'view_invalid',
|
|
77
|
+
message: `'${name}' is not a valid view: ${dir} has no core.mjs`,
|
|
78
|
+
received: name,
|
|
79
|
+
next: `A view is a directory containing core.mjs (the portable core) plus optional tui.mjs (render + keymap), web.jsx, and text.mjs. Scaffold the shape with \`crtr view new ${name}\`.`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
69
82
|
const avail = listViews().map((v) => v.id);
|
|
70
83
|
throw new InputError({
|
|
71
84
|
error: 'view_not_found',
|
|
@@ -81,13 +94,22 @@ export const viewRunLeaf = defineLeaf({
|
|
|
81
94
|
options.port = String(port);
|
|
82
95
|
if (target !== undefined)
|
|
83
96
|
options.target = target;
|
|
97
|
+
// Load + host the resolved view via the matching path (works for both the
|
|
98
|
+
// non-TTY dump and the interactive alt-screen — each host handles the TTY
|
|
99
|
+
// gate internally).
|
|
100
|
+
const hostView = async () => {
|
|
101
|
+
requireTui(r);
|
|
102
|
+
const core = await loadCore(r);
|
|
103
|
+
const tui = await loadTui(r);
|
|
104
|
+
const txt = await loadText(r);
|
|
105
|
+
await runCoreView(core, tui, txt, { options });
|
|
106
|
+
};
|
|
84
107
|
// --window / --split: open the view as a persistent monitor in a new pane.
|
|
85
108
|
if (asWindow || asSplit) {
|
|
86
109
|
// Placement is tmux-only. In a pipe (non-TTY) it is meaningless — degrade
|
|
87
|
-
// to the static dump (
|
|
110
|
+
// to the static dump (the host handles the non-TTY path internally).
|
|
88
111
|
if (!process.stdin.isTTY) {
|
|
89
|
-
|
|
90
|
-
await runView(v, { options });
|
|
112
|
+
await hostView();
|
|
91
113
|
return;
|
|
92
114
|
}
|
|
93
115
|
if (!inTmux()) {
|
|
@@ -136,9 +158,8 @@ export const viewRunLeaf = defineLeaf({
|
|
|
136
158
|
}
|
|
137
159
|
return { hosted: 'split', view: name, pane };
|
|
138
160
|
}
|
|
139
|
-
const v = await loadView(r);
|
|
140
161
|
// The interactive path is tmux-only; the piped/non-TTY dump path works
|
|
141
|
-
// anywhere (
|
|
162
|
+
// anywhere (the host handles it internally), so only guard when stdin is a
|
|
142
163
|
// TTY. Outside tmux with a TTY: notify + no-op — never a non-tmux fallback.
|
|
143
164
|
if (process.stdin.isTTY && !inTmux()) {
|
|
144
165
|
throw new InputError({
|
|
@@ -162,10 +183,10 @@ export const viewRunLeaf = defineLeaf({
|
|
|
162
183
|
catch { /* best-effort */ }
|
|
163
184
|
}
|
|
164
185
|
try {
|
|
165
|
-
await
|
|
186
|
+
await hostView();
|
|
166
187
|
}
|
|
167
188
|
finally {
|
|
168
|
-
// On a clean quit (q)
|
|
189
|
+
// On a clean quit (q) the host returns to the shell/pi that launched this
|
|
169
190
|
// pane — clear the monitor tag so a stray Alt+V ]/[ can't respawn-pane -k
|
|
170
191
|
// (and kill) whatever now owns the pane. When the cycle itself replaces
|
|
171
192
|
// this view it SIGKILLs us mid-await (this finally never runs); cycle + the
|
package/dist/commands/view.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// `crtr view` — the TUI view library.
|
|
2
2
|
//
|
|
3
|
-
// Views are switchable
|
|
4
|
-
// the
|
|
5
|
-
// what's available, `run` hosts
|
|
6
|
-
// piped), `new` scaffolds a fresh
|
|
7
|
-
// /view popup. Each leaf owns
|
|
3
|
+
// Views are switchable surfaces with two targets from one portable core.mjs:
|
|
4
|
+
// the tmux TUI (`run`) and a React+Tailwind web page. This branch assembles the
|
|
5
|
+
// TUI-side leaves: `list` enumerates what's available, `run` hosts the TUI
|
|
6
|
+
// target (interactive in tmux, static dump when piped), `new` scaffolds a fresh
|
|
7
|
+
// view directory, and the hidden `pick` backs the /view popup. Each leaf owns
|
|
8
|
+
// its own help one level down. The web target is no longer served per-view here:
|
|
9
|
+
// it is hosted by the unified `crtr web serve` (the shell's /view/<id> route).
|
|
8
10
|
import { defineBranch } from '../core/command.js';
|
|
9
11
|
import { viewListLeaf } from './view-list.js';
|
|
10
12
|
import { viewRunLeaf } from './view-run.js';
|
|
@@ -22,7 +24,7 @@ export function registerView() {
|
|
|
22
24
|
help: {
|
|
23
25
|
name: 'view',
|
|
24
26
|
summary: 'host and author switchable raw-ANSI terminal views',
|
|
25
|
-
model: '`list` when you do not know which views exist — a flat roster (id/title/description/scope) across project→user→builtin. `run <name>` opens
|
|
27
|
+
model: '`list` when you do not know which views exist — a flat roster (id/title/description/scope) across project→user→builtin. `run <name>` opens the TUI target full-screen in the current pane (tmux-only interactive; piped it prints the view\'s static dump and exits 0), forwarding --port/--target onto the view; pass --window/--split to open it as a persistent monitor (new window / split) you flip between with Alt+V then ]/[. The web target (React+Tailwind) is served by the unified `crtr web serve` (the shell\'s /view/<id> route), not per-view here. `cycle` switches a monitor pane to the next/prev view in place (what those keys drive). `new <name>` scaffolds a runnable view directory (core.mjs + tui.mjs + web.jsx + text.mjs) you edit. `pick` is a hidden raw-ANSI picker the /view popup shells. Append `-h` at any leaf for its schema.',
|
|
26
28
|
},
|
|
27
29
|
children: [viewListLeaf, viewRunLeaf, viewCycleLeaf, viewNewLeaf, viewPickLeaf],
|
|
28
30
|
});
|