@crouton-kit/crouter 0.3.27 → 0.3.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-root.js +4 -2
- 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 +8 -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 +3 -3
- 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__/reconnect-giveup.test.js +30 -0
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +445 -41
- 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 +12 -0
- package/dist/clients/attach/canvas-panels.js +78 -0
- 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 +43 -0
- package/dist/clients/attach/graph-overlay.js +306 -0
- package/dist/clients/attach/input-controller.d.ts +81 -13
- package/dist/clients/attach/input-controller.js +307 -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 +44 -1
- package/dist/clients/attach/slash-commands.js +250 -27
- 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 +37 -2
- package/dist/clients/attach/view-socket.js +131 -7
- 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 +1 -1
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/human/prompts.js +3 -3
- package/dist/commands/human/queue.d.ts +17 -0
- package/dist/commands/human/queue.js +111 -4
- package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
- package/dist/commands/memory/lint.d.ts +5 -4
- package/dist/commands/memory/lint.js +23 -7
- 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 +20 -7
- 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/feedback.d.ts +1 -0
- package/dist/commands/sys/feedback.js +163 -0
- package/dist/commands/sys/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +4 -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__/broker-snapshot-history.test.d.ts +1 -0
- package/dist/core/__tests__/broker-snapshot-history.test.js +105 -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 +24 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +32 -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__/human-stranded-deliver.test.d.ts +1 -0
- package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
- package/dist/core/__tests__/lifecycle.test.js +1 -19
- 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.d.ts +1 -0
- package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
- 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.d.ts +162 -0
- package/dist/core/canvas/nav-model.js +487 -0
- package/dist/core/canvas/paths.d.ts +7 -0
- package/dist/core/canvas/paths.js +9 -0
- 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-sdk.d.ts +0 -12
- package/dist/core/runtime/broker-sdk.js +77 -6
- package/dist/core/runtime/broker.d.ts +5 -1
- package/dist/core/runtime/broker.js +520 -59
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +14 -7
- 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 -334
- package/dist/core/runtime/placement.js +262 -828
- 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 +45 -135
- package/dist/core/runtime/spawn.d.ts +15 -11
- package/dist/core/runtime/spawn.js +168 -136
- 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 -43
- package/dist/core/runtime/tmux.js +38 -80
- 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/spawn.d.ts +14 -0
- package/dist/core/spawn.js +29 -9
- package/dist/core/substrate/index.d.ts +2 -2
- package/dist/core/substrate/index.js +7 -7
- package/dist/core/substrate/injected-store.d.ts +10 -0
- package/dist/core/substrate/injected-store.js +55 -0
- 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 +16 -11
- package/dist/core/substrate/schema.js +32 -31
- 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 +29 -20
- package/dist/pi-extensions/canvas-nav.js +35 -385
- 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 +15 -13
- package/dist/pi-extensions/canvas-stophook.js +119 -103
- 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 -221
- 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__/reconnect-giveup.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
package/dist/core/tui/host.js
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
// host.ts — the alt-screen loop that hosts a
|
|
1
|
+
// host.ts — the alt-screen loop that hosts a dual-target ViewCore + TuiPresenter
|
|
2
|
+
// (src/core/view/contract.ts).
|
|
2
3
|
//
|
|
3
4
|
// Models browse/app.ts's loop, generalized: the host owns the screen, input,
|
|
4
|
-
// chrome, and the single-flight async lane
|
|
5
|
-
//
|
|
5
|
+
// chrome, and the single-flight async lane. State is immutable (the core's
|
|
6
|
+
// intents update it via `ctx.set`); presenters are pure reads. A keystroke maps
|
|
7
|
+
// through the TuiPresenter's keymap to a named intent the host dispatches.
|
|
6
8
|
//
|
|
7
|
-
// • TTY gate: !process.stdin.isTTY →
|
|
9
|
+
// • TTY gate: !process.stdin.isTTY → text.dump(state) (or a synthesized
|
|
10
|
+
// one-line dump) to stdout, exit 0.
|
|
8
11
|
// • setup/restore terminal (alt-screen, raw); restore exactly once, however we
|
|
9
12
|
// leave (quit / Ctrl-C / crash / process exit).
|
|
10
|
-
// • single-flight async lane: at most ONE async
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
+
// • single-flight async lane: at most ONE async intent in flight; a busy
|
|
14
|
+
// indicator shows in the chrome; keystrokes that arrive mid-flight are
|
|
15
|
+
// DROPPED (this paces fetches/sends).
|
|
13
16
|
// • chrome: title row + separator on top; an error banner + a footer (status
|
|
14
|
-
// left, keymap hints right) on the bottom; the
|
|
15
|
-
// • loop: parseKeypress →
|
|
17
|
+
// left, keymap hints right) on the bottom; the presenter gets the content Rect.
|
|
18
|
+
// • loop: parseKeypress → keymap → dispatch(intent) → render; refreshMs polling;
|
|
16
19
|
// resize → render (NOT refresh) so a resize mid-fetch repaints from current
|
|
17
|
-
// state without re-entering the in-flight
|
|
20
|
+
// state without re-entering the in-flight intent.
|
|
18
21
|
import { setupTerminal, restoreTerminal, getTerminalSize, parseKeypress, } from './terminal.js';
|
|
19
22
|
import { createDraw, detectColorCaps } from './draw.js';
|
|
23
|
+
import { initialChrome, deriveState } from '../view/chrome.js';
|
|
24
|
+
import { createLocalTransport } from '../view/transport-local.js';
|
|
20
25
|
// Numeric SGR codes only (a color NAME emits a broken CSI — see draw.ts guard).
|
|
21
26
|
const FG = { cyan: '36', green: '32', yellow: '33', red: '31', grey: '90' };
|
|
22
27
|
/** The persistent state signal: word + glyph + hue, derived from host signals so
|
|
@@ -43,19 +48,6 @@ const BANNER = {
|
|
|
43
48
|
action: { glyph: '▸', fg: FG.yellow },
|
|
44
49
|
error: { glyph: '✗', fg: FG.red },
|
|
45
50
|
};
|
|
46
|
-
/** Derive the one state chip from host signals (resume guidance): busy→working,
|
|
47
|
-
* error banner→blocked, action banner→attention, else ready (once loaded) / idle.
|
|
48
|
-
* An info banner does not block — the chip stays ready/idle and the cyan banner
|
|
49
|
-
* carries the notice. */
|
|
50
|
-
function deriveState(c) {
|
|
51
|
-
if (c.busy)
|
|
52
|
-
return 'working';
|
|
53
|
-
if (c.banner?.level === 'error')
|
|
54
|
-
return 'blocked';
|
|
55
|
-
if (c.banner?.level === 'action')
|
|
56
|
-
return 'attention';
|
|
57
|
-
return c.loaded ? 'ready' : 'idle';
|
|
58
|
-
}
|
|
59
51
|
/** Color-code the transient footer status by kind (not dim, so it leads): a
|
|
60
52
|
* trailing … or an in-progress verb → cyan; a completed verb → green; else plain.
|
|
61
53
|
* The words carry meaning; the hue is decorative (mono-safe). */
|
|
@@ -187,37 +179,140 @@ function drawChrome(draw, size, manifest, c, now = Date.now()) {
|
|
|
187
179
|
return { row: top, col: 0, width: cols, height };
|
|
188
180
|
}
|
|
189
181
|
export { drawChrome };
|
|
190
|
-
|
|
191
|
-
|
|
182
|
+
// ── Dual-target core host (runCoreView) ─────────────────────────────────────
|
|
183
|
+
//
|
|
184
|
+
// Hosts a ViewCore + TuiPresenter under the dual-target contract
|
|
185
|
+
// (src/core/view/contract.ts). The model is the immutable-state + intents thunk
|
|
186
|
+
// runtime: the core owns all state + behavior, presenters are pure reads, and a
|
|
187
|
+
// keystroke maps through the keymap to a named intent the host dispatches.
|
|
188
|
+
/** Tokens a keystroke can match a keymap binding's `keys` against. Arrows →
|
|
189
|
+
* up/down/left/right, return → return|enter, escape → escape|esc, a printable
|
|
190
|
+
* char → the char itself (+ 'space' for ' '), ctrl+x → ctrl+x|c-x. */
|
|
191
|
+
function keyTokens(input, key) {
|
|
192
|
+
const t = [];
|
|
193
|
+
if (key.upArrow)
|
|
194
|
+
t.push('up');
|
|
195
|
+
if (key.downArrow)
|
|
196
|
+
t.push('down');
|
|
197
|
+
if (key.leftArrow)
|
|
198
|
+
t.push('left');
|
|
199
|
+
if (key.rightArrow)
|
|
200
|
+
t.push('right');
|
|
201
|
+
if (key.return)
|
|
202
|
+
t.push('return', 'enter');
|
|
203
|
+
if (key.escape)
|
|
204
|
+
t.push('escape', 'esc');
|
|
205
|
+
if (key.tab)
|
|
206
|
+
t.push('tab');
|
|
207
|
+
if (key.shiftTab)
|
|
208
|
+
t.push('shifttab');
|
|
209
|
+
if (key.backspace)
|
|
210
|
+
t.push('backspace');
|
|
211
|
+
if (key.ctrl && input)
|
|
212
|
+
t.push(`ctrl+${input}`, `c-${input}`);
|
|
213
|
+
if (!key.ctrl && !key.escape && input.length === 1)
|
|
214
|
+
t.push(input);
|
|
215
|
+
if (input === ' ')
|
|
216
|
+
t.push('space');
|
|
217
|
+
return t;
|
|
218
|
+
}
|
|
219
|
+
/** A printable text edit (for the capture line-editor): a non-empty input with
|
|
220
|
+
* no special-key flag set. */
|
|
221
|
+
function isPrintable(input, key) {
|
|
222
|
+
if (key.ctrl || key.meta || key.escape || key.return || key.tab || key.backspace
|
|
223
|
+
|| key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.shiftTab)
|
|
224
|
+
return false;
|
|
225
|
+
return input.length >= 1;
|
|
226
|
+
}
|
|
227
|
+
/** Strip control chars so a paste can't smuggle ANSI into the draft buffer. */
|
|
228
|
+
function sanitizePrintable(s) {
|
|
229
|
+
return Array.from(s).filter((ch) => ch.charCodeAt(0) >= 32).join('');
|
|
230
|
+
}
|
|
231
|
+
/** The first non-capture binding whose key matches and whose `when` (if any)
|
|
232
|
+
* passes — returns the intent name + resolved payload. */
|
|
233
|
+
function matchBinding(keymap, input, key, state) {
|
|
234
|
+
const tokens = keyTokens(input, key);
|
|
235
|
+
for (const b of keymap) {
|
|
236
|
+
if ('capture' in b)
|
|
237
|
+
continue;
|
|
238
|
+
if (b.when && !b.when(state))
|
|
239
|
+
continue;
|
|
240
|
+
if (b.keys.some((k) => tokens.includes(k))) {
|
|
241
|
+
return { intent: b.intent, payload: b.payload ? b.payload(state) : undefined };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
/** The active text-capture binding for this state, if any (compose-mode entry). */
|
|
247
|
+
function activeCapture(keymap, state) {
|
|
248
|
+
for (const b of keymap) {
|
|
249
|
+
if ('capture' in b && b.when(state))
|
|
250
|
+
return b;
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
/** Synthesize a one-line dump for a view that ships no text.mjs: `<title>` plus
|
|
255
|
+
* ` — <n> items` if state has an obvious top-level list. */
|
|
256
|
+
function synthDump(title, state) {
|
|
257
|
+
if (state && typeof state === 'object') {
|
|
258
|
+
for (const v of Object.values(state)) {
|
|
259
|
+
if (Array.isArray(v))
|
|
260
|
+
return `${title} — ${v.length} items`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return title;
|
|
264
|
+
}
|
|
265
|
+
/** Run a READ source / WRITE command through a transport → typed Result. */
|
|
266
|
+
async function runRequest(transport, src, args) {
|
|
267
|
+
const raw = await transport.send(src.request(args));
|
|
268
|
+
return src.parse(raw);
|
|
269
|
+
}
|
|
270
|
+
/** Host a dual-target ViewCore + TuiPresenter in the alt screen until it quits
|
|
271
|
+
* (or Ctrl-C). `text` is the optional text presenter for the piped path. */
|
|
272
|
+
export async function runCoreView(core, tui, text, opts = {}) {
|
|
192
273
|
const options = Object.freeze({ ...(opts.options ?? {}) });
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
options,
|
|
196
|
-
setStatus(msg) { chrome.status = msg; },
|
|
197
|
-
setBanner(msg, level) { chrome.banner = msg == null ? null : { msg, level }; },
|
|
198
|
-
setError(msg) { chrome.banner = msg == null ? null : { msg, level: 'error' }; },
|
|
199
|
-
setSubtitle(s) { chrome.subtitle = s; },
|
|
200
|
-
setMode(mode) { chrome.mode = mode; },
|
|
201
|
-
};
|
|
202
|
-
// ── Non-TTY / piped path: build state, best-effort load, dump, exit 0. ──
|
|
274
|
+
const transport = createLocalTransport({ cwd: process.cwd() });
|
|
275
|
+
// ── Non-TTY / piped path: init, best-effort refresh, dump, exit 0. ──
|
|
203
276
|
if (!process.stdin.isTTY) {
|
|
204
|
-
|
|
205
|
-
|
|
277
|
+
let dstate = core.init(options);
|
|
278
|
+
const dchrome = initialChrome();
|
|
279
|
+
const dsignal = {
|
|
280
|
+
setStatus() { }, setBanner(m, l) { dchrome.banner = { msg: m, level: l }; }, clearBanner() { dchrome.banner = null; },
|
|
281
|
+
setSubtitle() { }, setMode() { }, quit() { },
|
|
282
|
+
};
|
|
283
|
+
const dctx = {
|
|
284
|
+
get state() { return dstate; },
|
|
285
|
+
set(next) { dstate = typeof next === 'function' ? next(dstate) : next; },
|
|
286
|
+
resolve: (s, a) => runRequest(transport, s, a),
|
|
287
|
+
execute: (c, a) => runRequest(transport, c, a),
|
|
288
|
+
signal: dsignal,
|
|
289
|
+
dispatch: async (name, payload) => { const it = core.intents[name]; if (it)
|
|
290
|
+
await it(dctx, payload); },
|
|
291
|
+
};
|
|
292
|
+
const refresh = core.intents['refresh'];
|
|
293
|
+
if (refresh) {
|
|
206
294
|
try {
|
|
207
|
-
await
|
|
295
|
+
await refresh(dctx, undefined);
|
|
208
296
|
}
|
|
209
297
|
catch { /* dump current state regardless */ }
|
|
210
298
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
text += '\n';
|
|
216
|
-
process.stdout.write(text);
|
|
299
|
+
let out = text ? text.dump(dstate, { banner: dchrome.banner }) : synthDump(core.manifest.title, dstate);
|
|
300
|
+
if (!out.endsWith('\n'))
|
|
301
|
+
out += '\n';
|
|
302
|
+
process.stdout.write(out);
|
|
217
303
|
return;
|
|
218
304
|
}
|
|
219
305
|
const caps = detectColorCaps();
|
|
220
|
-
|
|
306
|
+
let state = core.init(options);
|
|
307
|
+
const chrome = initialChrome();
|
|
308
|
+
let tick = 0;
|
|
309
|
+
// Footer hints come from the keymap bindings' `hint` field (single source of
|
|
310
|
+
// truth) — projected into the (ChromeManifest, Chrome) shape drawChrome reads.
|
|
311
|
+
const hints = [];
|
|
312
|
+
for (const b of tui.keymap) {
|
|
313
|
+
if (b.hint)
|
|
314
|
+
hints.push(b.hint);
|
|
315
|
+
}
|
|
221
316
|
// Restore the terminal exactly once, however we leave.
|
|
222
317
|
let restored = false;
|
|
223
318
|
const cleanup = () => {
|
|
@@ -233,23 +328,24 @@ export async function runView(view, opts = {}) {
|
|
|
233
328
|
const render = () => {
|
|
234
329
|
const size = getTerminalSize();
|
|
235
330
|
const { draw, frame } = createDraw(size, caps);
|
|
236
|
-
const
|
|
331
|
+
const chromeManifest = { ...core.manifest, keymap: hints };
|
|
332
|
+
const tickChrome = { ...chrome, tick };
|
|
333
|
+
const content = drawChrome(draw, size, chromeManifest, tickChrome);
|
|
237
334
|
try {
|
|
238
|
-
|
|
335
|
+
tui.render(state, draw, content);
|
|
239
336
|
}
|
|
240
337
|
catch (e) {
|
|
241
338
|
chrome.banner = { msg: `render error: ${errText(e)}`, level: 'error' };
|
|
242
339
|
}
|
|
243
340
|
process.stdout.write(frame());
|
|
244
341
|
};
|
|
245
|
-
// Busy-tick repaint:
|
|
246
|
-
//
|
|
247
|
-
// NEVER re-enters a hook, and Ctrl-C still escapes (handled before the drop).
|
|
342
|
+
// Busy-tick repaint: advance the spinner + repaint while an async intent runs
|
|
343
|
+
// so live setStatus narration shows. Render-only — never re-enters an intent.
|
|
248
344
|
let tickTimer;
|
|
249
345
|
const startBusyTick = () => {
|
|
250
346
|
if (tickTimer)
|
|
251
347
|
return;
|
|
252
|
-
tickTimer = setInterval(() => {
|
|
348
|
+
tickTimer = setInterval(() => { tick++; render(); }, 120);
|
|
253
349
|
};
|
|
254
350
|
const stopBusyTick = () => {
|
|
255
351
|
if (tickTimer) {
|
|
@@ -257,120 +353,155 @@ export async function runView(view, opts = {}) {
|
|
|
257
353
|
tickTimer = undefined;
|
|
258
354
|
}
|
|
259
355
|
};
|
|
260
|
-
//
|
|
261
|
-
|
|
356
|
+
// Loop control hoisted so signal.quit() can finish from inside an intent.
|
|
357
|
+
let done = false;
|
|
358
|
+
let resolveLoop = () => { };
|
|
359
|
+
const loopDone = new Promise((res) => { resolveLoop = res; });
|
|
360
|
+
let loopTimer;
|
|
361
|
+
const finish = () => {
|
|
362
|
+
if (done)
|
|
363
|
+
return;
|
|
364
|
+
done = true;
|
|
365
|
+
if (loopTimer)
|
|
366
|
+
clearInterval(loopTimer);
|
|
367
|
+
stopBusyTick();
|
|
368
|
+
cleanup();
|
|
369
|
+
resolveLoop();
|
|
370
|
+
};
|
|
371
|
+
const signal = {
|
|
372
|
+
setStatus(msg) { chrome.status = msg; },
|
|
373
|
+
setBanner(msg, level) { chrome.banner = msg == null ? null : { msg, level }; },
|
|
374
|
+
clearBanner() { chrome.banner = null; },
|
|
375
|
+
setSubtitle(s) { chrome.subtitle = s; },
|
|
376
|
+
setMode(mode) { chrome.mode = mode; },
|
|
377
|
+
quit() { finish(); },
|
|
378
|
+
};
|
|
379
|
+
// The single-flight busy lane lives in dispatch. The INPUT layer drops
|
|
380
|
+
// keystrokes while busy (paces fetches); a chained ctx.dispatch from inside an
|
|
381
|
+
// intent runs inline without re-toggling the lane.
|
|
262
382
|
let busy = false;
|
|
263
|
-
const
|
|
264
|
-
if (
|
|
383
|
+
const markRefreshed = (name) => {
|
|
384
|
+
if (name === 'refresh') {
|
|
385
|
+
chrome.loaded = true;
|
|
386
|
+
chrome.lastRefresh = Date.now();
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
const makeCtx = () => ({
|
|
390
|
+
get state() { return state; },
|
|
391
|
+
set(next) { state = typeof next === 'function' ? next(state) : next; render(); },
|
|
392
|
+
resolve: (s, a) => runRequest(transport, s, a),
|
|
393
|
+
execute: (c, a) => runRequest(transport, c, a),
|
|
394
|
+
signal,
|
|
395
|
+
dispatch: (name, payload) => dispatch(name, payload),
|
|
396
|
+
});
|
|
397
|
+
const dispatch = async (intentName, payload) => {
|
|
398
|
+
if (done)
|
|
265
399
|
return;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
400
|
+
const intent = core.intents[intentName];
|
|
401
|
+
if (!intent) {
|
|
402
|
+
chrome.banner = { msg: `unknown intent: ${intentName}`, level: 'error' };
|
|
403
|
+
render();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const ctx = makeCtx();
|
|
407
|
+
let res;
|
|
270
408
|
try {
|
|
271
|
-
|
|
409
|
+
res = intent(ctx, payload);
|
|
272
410
|
}
|
|
273
411
|
catch (e) {
|
|
274
412
|
chrome.banner = { msg: errText(e), level: 'error' };
|
|
275
|
-
|
|
276
|
-
finally {
|
|
277
|
-
busy = false;
|
|
278
|
-
chrome.busy = false;
|
|
279
|
-
chrome.loaded = true;
|
|
280
|
-
chrome.lastRefresh = Date.now();
|
|
281
|
-
stopBusyTick();
|
|
413
|
+
markRefreshed(intentName);
|
|
282
414
|
render();
|
|
415
|
+
return;
|
|
283
416
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const finish = () => {
|
|
292
|
-
if (done)
|
|
293
|
-
return;
|
|
294
|
-
done = true;
|
|
295
|
-
if (timer)
|
|
296
|
-
clearInterval(timer);
|
|
297
|
-
stopBusyTick();
|
|
298
|
-
cleanup();
|
|
299
|
-
resolveLoop();
|
|
300
|
-
};
|
|
301
|
-
if (typeof view.manifest.refreshMs === 'number' && view.manifest.refreshMs > 0) {
|
|
302
|
-
timer = setInterval(() => { void runRefresh(); }, view.manifest.refreshMs);
|
|
303
|
-
}
|
|
304
|
-
const apply = async (action) => {
|
|
305
|
-
switch (action.type) {
|
|
306
|
-
case 'render':
|
|
307
|
-
render();
|
|
308
|
-
break;
|
|
309
|
-
case 'refresh':
|
|
310
|
-
await runRefresh();
|
|
311
|
-
break;
|
|
312
|
-
case 'quit':
|
|
313
|
-
finish();
|
|
314
|
-
break;
|
|
315
|
-
case 'none': break;
|
|
417
|
+
if (res instanceof Promise) {
|
|
418
|
+
const wasBusy = busy;
|
|
419
|
+
if (!wasBusy) {
|
|
420
|
+
busy = true;
|
|
421
|
+
chrome.busy = true;
|
|
422
|
+
render();
|
|
423
|
+
startBusyTick();
|
|
316
424
|
}
|
|
317
|
-
};
|
|
318
|
-
const onData = async (data) => {
|
|
319
|
-
if (done)
|
|
320
|
-
return;
|
|
321
|
-
let parsed;
|
|
322
425
|
try {
|
|
323
|
-
|
|
426
|
+
await res;
|
|
324
427
|
}
|
|
325
|
-
catch {
|
|
326
|
-
|
|
428
|
+
catch (e) {
|
|
429
|
+
chrome.banner = { msg: errText(e), level: 'error' };
|
|
327
430
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
431
|
+
finally {
|
|
432
|
+
if (!wasBusy) {
|
|
433
|
+
busy = false;
|
|
434
|
+
chrome.busy = false;
|
|
435
|
+
stopBusyTick();
|
|
436
|
+
}
|
|
437
|
+
markRefreshed(intentName);
|
|
438
|
+
render();
|
|
333
439
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
markRefreshed(intentName);
|
|
443
|
+
render();
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
// ── Mount: init → loading frame → first refresh (if the view has one). ──
|
|
447
|
+
setupTerminal();
|
|
448
|
+
render();
|
|
449
|
+
if (core.intents['refresh'])
|
|
450
|
+
await dispatch('refresh');
|
|
451
|
+
// Capture-mode buffer: the host's line-editor draft while a `capture` binding's
|
|
452
|
+
// when(state) is true; reset to '' whenever no capture binding is active.
|
|
453
|
+
let captureBuf = '';
|
|
454
|
+
if (typeof core.manifest.refreshMs === 'number' && core.manifest.refreshMs > 0) {
|
|
455
|
+
loopTimer = setInterval(() => { if (!busy)
|
|
456
|
+
void dispatch('refresh'); }, core.manifest.refreshMs);
|
|
457
|
+
}
|
|
458
|
+
const onData = async (data) => {
|
|
459
|
+
if (done)
|
|
460
|
+
return;
|
|
461
|
+
let parsed;
|
|
462
|
+
try {
|
|
463
|
+
parsed = parseKeypress(data);
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const { input, key } = parsed;
|
|
469
|
+
// Ctrl-C is the universal escape hatch — works even mid-flight.
|
|
470
|
+
if (key.ctrl && input === 'c') {
|
|
471
|
+
finish();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
// Drop keystrokes while an async intent is in flight (paces fetch/send).
|
|
475
|
+
if (busy)
|
|
476
|
+
return;
|
|
477
|
+
// Text-capture: while a capture binding is active, printable/backspace edit
|
|
478
|
+
// the host buffer and dispatch capture(nextDraft); other keys (return/escape)
|
|
479
|
+
// fall through to the keymap so submit/cancel can be bound.
|
|
480
|
+
const cap = activeCapture(tui.keymap, state);
|
|
481
|
+
if (cap) {
|
|
482
|
+
if (isPrintable(input, key)) {
|
|
483
|
+
captureBuf += sanitizePrintable(input);
|
|
484
|
+
await dispatch(cap.capture, captureBuf);
|
|
340
485
|
return;
|
|
341
486
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
busy = true;
|
|
347
|
-
chrome.busy = true;
|
|
348
|
-
render();
|
|
349
|
-
startBusyTick();
|
|
350
|
-
try {
|
|
351
|
-
action = await r;
|
|
352
|
-
}
|
|
353
|
-
finally {
|
|
354
|
-
busy = false;
|
|
355
|
-
chrome.busy = false;
|
|
356
|
-
stopBusyTick();
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
action = r;
|
|
361
|
-
}
|
|
362
|
-
await apply(action);
|
|
363
|
-
}
|
|
364
|
-
catch (e) {
|
|
365
|
-
chrome.banner = { msg: errText(e), level: 'error' };
|
|
366
|
-
render();
|
|
487
|
+
if (key.backspace) {
|
|
488
|
+
captureBuf = captureBuf.slice(0, -1);
|
|
489
|
+
await dispatch(cap.capture, captureBuf);
|
|
490
|
+
return;
|
|
367
491
|
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
captureBuf = '';
|
|
495
|
+
}
|
|
496
|
+
const m = matchBinding(tui.keymap, input, key, state);
|
|
497
|
+
if (m)
|
|
498
|
+
await dispatch(m.intent, m.payload);
|
|
499
|
+
};
|
|
500
|
+
process.stdin.on('data', (d) => { void onData(d); });
|
|
501
|
+
// Resize → repaint from current state (never re-enter an in-flight intent).
|
|
502
|
+
process.stdout.on('resize', () => { if (!done)
|
|
503
|
+
render(); });
|
|
504
|
+
await loopDone;
|
|
374
505
|
}
|
|
375
506
|
function errText(e) {
|
|
376
507
|
if (e instanceof Error)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Transport } from './transport.js';
|
|
2
|
+
export interface BridgeResult {
|
|
3
|
+
status: number;
|
|
4
|
+
body: string;
|
|
5
|
+
}
|
|
6
|
+
/** Decode a POST body as a SourceRequest and run it through `transport`,
|
|
7
|
+
* returning the HTTP status + JSON-encoded RawResponse the bridge should send
|
|
8
|
+
* back. Never throws — a malformed body or a transport failure both come back
|
|
9
|
+
* as a RawResponse with ok:false. */
|
|
10
|
+
export declare function runSourceRequest(transport: Transport, rawBody: string): Promise<BridgeResult>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// bridge.ts — the server side of the local web transport (Node only).
|
|
2
|
+
//
|
|
3
|
+
// `crtr view serve` runs the portable core CLIENT-SIDE in the browser; its
|
|
4
|
+
// sources resolve over an HTTP Transport that POSTs each SourceRequest here.
|
|
5
|
+
// This helper decodes that POST body and runs it through any Transport (the
|
|
6
|
+
// local exec/file/http one today) — the single endpoint a cloud deploy
|
|
7
|
+
// replaces. Kept transport-agnostic + framework-free so it is trivially
|
|
8
|
+
// testable and tear-out-able.
|
|
9
|
+
function bad(stderr) {
|
|
10
|
+
const raw = { ok: false, stdout: '', stderr };
|
|
11
|
+
return { status: 400, body: JSON.stringify(raw) };
|
|
12
|
+
}
|
|
13
|
+
/** Decode a POST body as a SourceRequest and run it through `transport`,
|
|
14
|
+
* returning the HTTP status + JSON-encoded RawResponse the bridge should send
|
|
15
|
+
* back. Never throws — a malformed body or a transport failure both come back
|
|
16
|
+
* as a RawResponse with ok:false. */
|
|
17
|
+
export async function runSourceRequest(transport, rawBody) {
|
|
18
|
+
let req;
|
|
19
|
+
try {
|
|
20
|
+
req = JSON.parse(rawBody);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return bad('bridge: request body is not valid JSON');
|
|
24
|
+
}
|
|
25
|
+
const kind = req.kind;
|
|
26
|
+
if (!req || typeof req !== 'object' || (kind !== 'exec' && kind !== 'file' && kind !== 'http')) {
|
|
27
|
+
return bad(`bridge: request body is not a SourceRequest (kind must be exec|file|http, got ${JSON.stringify(kind)})`);
|
|
28
|
+
}
|
|
29
|
+
const raw = await transport.send(req);
|
|
30
|
+
return { status: 200, body: JSON.stringify(raw) };
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ChromeState } from './contract.js';
|
|
2
|
+
export type ChipState = 'working' | 'blocked' | 'attention' | 'ready' | 'idle';
|
|
3
|
+
/** Derive the state chip from host signals: busy→working, error banner→blocked,
|
|
4
|
+
* action banner→attention, else ready (once loaded) / idle. An info banner does
|
|
5
|
+
* not block. An explicit interaction mode (setMode) overrides the chip — that
|
|
6
|
+
* precedence is applied by each target's chrome renderer, not here. */
|
|
7
|
+
export declare function deriveState(c: ChromeState): ChipState;
|
|
8
|
+
/** A fresh chrome record (the shape both hosts start from). */
|
|
9
|
+
export declare function initialChrome(): ChromeState;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// chrome.ts — portable chrome-state derivation shared by both targets.
|
|
2
|
+
//
|
|
3
|
+
// The TUI's drawChrome and the web's <ViewChrome> both derive the one state
|
|
4
|
+
// chip from the same host signals via deriveState, so the two targets never
|
|
5
|
+
// drift on what "blocked" / "working" / "ready" mean.
|
|
6
|
+
/** Derive the state chip from host signals: busy→working, error banner→blocked,
|
|
7
|
+
* action banner→attention, else ready (once loaded) / idle. An info banner does
|
|
8
|
+
* not block. An explicit interaction mode (setMode) overrides the chip — that
|
|
9
|
+
* precedence is applied by each target's chrome renderer, not here. */
|
|
10
|
+
export function deriveState(c) {
|
|
11
|
+
if (c.busy)
|
|
12
|
+
return 'working';
|
|
13
|
+
if (c.banner?.level === 'error')
|
|
14
|
+
return 'blocked';
|
|
15
|
+
if (c.banner?.level === 'action')
|
|
16
|
+
return 'attention';
|
|
17
|
+
return c.loaded ? 'ready' : 'idle';
|
|
18
|
+
}
|
|
19
|
+
/** A fresh chrome record (the shape both hosts start from). */
|
|
20
|
+
export function initialChrome() {
|
|
21
|
+
return { status: null, banner: null, subtitle: null, mode: null, busy: false, loaded: false, lastRefresh: 0 };
|
|
22
|
+
}
|