@crouton-kit/crouter 0.3.18 → 0.3.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/dist/build-root.js +6 -0
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +164 -0
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +58 -0
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +60 -0
  5. package/dist/builtin-memory/crouter-development/personas.md +117 -0
  6. package/dist/builtin-memory/crouter-development/plugins.md +163 -0
  7. package/dist/builtin-memory/design.md +60 -0
  8. package/dist/builtin-memory/development.md +116 -0
  9. package/dist/builtin-memory/planning.md +66 -0
  10. package/dist/builtin-memory/spec.md +99 -0
  11. package/dist/builtin-personas/design/PERSONA.md +1 -0
  12. package/dist/builtin-personas/design/orchestrator.md +2 -1
  13. package/dist/builtin-personas/developer/PERSONA.md +1 -0
  14. package/dist/builtin-personas/developer/orchestrator.md +2 -1
  15. package/dist/builtin-personas/explore/PERSONA.md +1 -0
  16. package/dist/builtin-personas/explore/orchestrator.md +4 -0
  17. package/dist/builtin-personas/general/PERSONA.md +1 -0
  18. package/dist/builtin-personas/general/orchestrator.md +4 -0
  19. package/dist/builtin-personas/lifecycle/resident.md +1 -1
  20. package/dist/builtin-personas/lifecycle/terminal.md +5 -2
  21. package/dist/builtin-personas/orchestration-kernel.md +15 -24
  22. package/dist/builtin-personas/plan/PERSONA.md +1 -0
  23. package/dist/builtin-personas/plan/orchestrator.md +2 -1
  24. package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -0
  25. package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -0
  26. package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -0
  27. package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -0
  28. package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -0
  29. package/dist/builtin-personas/review/PERSONA.md +1 -0
  30. package/dist/builtin-personas/review/orchestrator.md +4 -0
  31. package/dist/builtin-personas/runtime-base.md +6 -3
  32. package/dist/builtin-personas/spec/PERSONA.md +1 -0
  33. package/dist/builtin-personas/spec/orchestrator.md +2 -1
  34. package/dist/builtin-personas/waiting.md +8 -0
  35. package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
  36. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
  37. package/dist/builtin-views/_lib/states.mjs +161 -0
  38. package/dist/builtin-views/canvas/client.mjs +303 -0
  39. package/dist/builtin-views/canvas/view.mjs +576 -0
  40. package/dist/builtin-views/git-pr/client.mjs +440 -0
  41. package/dist/builtin-views/git-pr/view.mjs +675 -0
  42. package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
  43. package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
  44. package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
  45. package/dist/builtin-views/inbox/view.mjs +889 -0
  46. package/dist/builtin-views/linkedin/client.mjs +610 -0
  47. package/dist/builtin-views/linkedin/view.mjs +1171 -0
  48. package/dist/clients/attach/attach-cmd.d.ts +2 -0
  49. package/dist/clients/attach/attach-cmd.js +354 -0
  50. package/dist/clients/attach/chat-view.d.ts +77 -0
  51. package/dist/clients/attach/chat-view.js +450 -0
  52. package/dist/clients/attach/clipboard-image.d.ts +16 -0
  53. package/dist/clients/attach/clipboard-image.js +113 -0
  54. package/dist/clients/attach/config-load.d.ts +31 -0
  55. package/dist/clients/attach/config-load.js +113 -0
  56. package/dist/clients/attach/extension-dialogs.d.ts +29 -0
  57. package/dist/clients/attach/extension-dialogs.js +101 -0
  58. package/dist/clients/attach/input-controller.d.ts +54 -0
  59. package/dist/clients/attach/input-controller.js +204 -0
  60. package/dist/clients/attach/slash-commands.d.ts +36 -0
  61. package/dist/clients/attach/slash-commands.js +200 -0
  62. package/dist/clients/attach/view-socket.d.ts +48 -0
  63. package/dist/clients/attach/view-socket.js +126 -0
  64. package/dist/commands/attention.js +3 -3
  65. package/dist/commands/canvas-prune.js +1 -1
  66. package/dist/commands/daemon.js +3 -2
  67. package/dist/commands/human/prompts.js +1 -1
  68. package/dist/commands/human/queue.js +54 -8
  69. package/dist/commands/human/shared.d.ts +2 -0
  70. package/dist/commands/memory/find.d.ts +1 -0
  71. package/dist/commands/memory/find.js +180 -0
  72. package/dist/commands/memory/lint.d.ts +1 -0
  73. package/dist/commands/memory/lint.js +140 -0
  74. package/dist/commands/memory/list.d.ts +1 -0
  75. package/dist/commands/memory/list.js +79 -0
  76. package/dist/commands/memory/read.js +103 -0
  77. package/dist/commands/memory/shared.d.ts +30 -0
  78. package/dist/commands/memory/shared.js +122 -0
  79. package/dist/commands/memory/write.d.ts +1 -0
  80. package/dist/commands/memory/write.js +85 -0
  81. package/dist/commands/memory.d.ts +2 -0
  82. package/dist/commands/memory.js +27 -0
  83. package/dist/commands/node.d.ts +3 -2
  84. package/dist/commands/node.js +610 -61
  85. package/dist/commands/pkg/market-manage.js +1 -1
  86. package/dist/commands/push.js +6 -6
  87. package/dist/commands/revive.js +1 -1
  88. package/dist/commands/skill/author.js +1 -1
  89. package/dist/commands/skill/shared.d.ts +1 -8
  90. package/dist/commands/skill/shared.js +2 -55
  91. package/dist/commands/skill.js +9 -14
  92. package/dist/commands/sys/doctor.js +1 -1
  93. package/dist/commands/sys/update.js +1 -1
  94. package/dist/commands/view-cycle.d.ts +2 -0
  95. package/dist/commands/view-cycle.js +125 -0
  96. package/dist/commands/view-list.d.ts +2 -0
  97. package/dist/commands/view-list.js +66 -0
  98. package/dist/commands/view-new.d.ts +2 -0
  99. package/dist/commands/view-new.js +70 -0
  100. package/dist/commands/view-pick.d.ts +2 -0
  101. package/dist/commands/view-pick.js +119 -0
  102. package/dist/commands/view-run.d.ts +2 -0
  103. package/dist/commands/view-run.js +191 -0
  104. package/dist/commands/view.d.ts +2 -0
  105. package/dist/commands/view.js +29 -0
  106. package/dist/core/__tests__/broker-attach-limits.test.d.ts +1 -0
  107. package/dist/core/__tests__/broker-attach-limits.test.js +157 -0
  108. package/dist/core/__tests__/broker-attach-stream.test.d.ts +1 -0
  109. package/dist/core/__tests__/broker-attach-stream.test.js +125 -0
  110. package/dist/core/__tests__/broker-crash-teardown.test.d.ts +1 -0
  111. package/dist/core/__tests__/broker-crash-teardown.test.js +116 -0
  112. package/dist/core/__tests__/broker-dialogs.test.d.ts +1 -0
  113. package/dist/core/__tests__/broker-dialogs.test.js +126 -0
  114. package/dist/core/__tests__/broker-dormant-wake.test.d.ts +1 -0
  115. package/dist/core/__tests__/broker-dormant-wake.test.js +51 -0
  116. package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
  117. package/dist/core/__tests__/broker-lifecycle.test.js +87 -0
  118. package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
  119. package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
  120. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.d.ts +1 -0
  121. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +102 -0
  122. package/dist/core/__tests__/canvas-inbox-watcher.test.js +2 -36
  123. package/dist/core/__tests__/cascade-close.test.js +12 -2
  124. package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
  125. package/dist/core/__tests__/child-death-wake.test.js +245 -0
  126. package/dist/core/__tests__/context-intro.test.js +76 -62
  127. package/dist/core/__tests__/daemon-boot.test.js +14 -5
  128. package/dist/core/__tests__/daemon-liveness.test.js +34 -9
  129. package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
  130. package/dist/core/__tests__/detach-focus.test.js +206 -0
  131. package/dist/core/__tests__/draw-style.test.d.ts +1 -0
  132. package/dist/core/__tests__/draw-style.test.js +258 -0
  133. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
  134. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
  135. package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
  136. package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
  137. package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
  138. package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
  139. package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
  140. package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
  141. package/dist/core/__tests__/helpers/broker-clients.d.ts +43 -0
  142. package/dist/core/__tests__/helpers/broker-clients.js +178 -0
  143. package/dist/core/__tests__/helpers/harness.d.ts +9 -0
  144. package/dist/core/__tests__/helpers/harness.js +111 -1
  145. package/dist/core/__tests__/home-session.test.js +41 -8
  146. package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
  147. package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
  148. package/dist/core/__tests__/kickoff.test.js +37 -3
  149. package/dist/core/__tests__/live-mutation-verbs.test.d.ts +1 -0
  150. package/dist/core/__tests__/live-mutation-verbs.test.js +175 -0
  151. package/dist/core/__tests__/live-mutation.test.js +6 -132
  152. package/dist/core/__tests__/memory.test.js +23 -115
  153. package/dist/core/__tests__/placement-focus.test.js +5 -0
  154. package/dist/core/__tests__/placement-teardown.test.js +54 -11
  155. package/dist/core/__tests__/relaunch.test.js +4 -3
  156. package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
  157. package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
  158. package/dist/core/__tests__/spawn-root.test.js +10 -0
  159. package/dist/core/__tests__/spike-harness.test.js +1 -0
  160. package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
  161. package/dist/core/__tests__/wake-bearings.test.js +156 -0
  162. package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
  163. package/dist/core/__tests__/wake-origin.test.js +110 -0
  164. package/dist/core/bootstrap.js +1 -1
  165. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  166. package/dist/core/canvas/browse/app.js +24 -2
  167. package/dist/core/canvas/browse/model.d.ts +38 -2
  168. package/dist/core/canvas/browse/model.js +134 -10
  169. package/dist/core/canvas/browse/render.d.ts +6 -12
  170. package/dist/core/canvas/browse/render.js +72 -104
  171. package/dist/core/canvas/canvas.js +16 -23
  172. package/dist/core/canvas/db.js +46 -0
  173. package/dist/core/canvas/index.d.ts +1 -0
  174. package/dist/core/canvas/index.js +1 -0
  175. package/dist/core/canvas/pid.d.ts +4 -0
  176. package/dist/core/canvas/pid.js +23 -0
  177. package/dist/core/canvas/render.d.ts +11 -2
  178. package/dist/core/canvas/render.js +69 -0
  179. package/dist/core/canvas/types.d.ts +91 -3
  180. package/dist/core/canvas/wakeups.d.ts +76 -0
  181. package/dist/core/canvas/wakeups.js +185 -0
  182. package/dist/core/config.js +4 -1
  183. package/dist/core/frontmatter.js +37 -124
  184. package/dist/core/memory-resolver.d.ts +49 -0
  185. package/dist/core/memory-resolver.js +141 -0
  186. package/dist/core/personas/loader.d.ts +7 -0
  187. package/dist/core/personas/loader.js +36 -5
  188. package/dist/core/personas/resolve.js +13 -3
  189. package/dist/core/predicate.d.ts +63 -0
  190. package/dist/core/predicate.js +189 -0
  191. package/dist/core/resolver.js +26 -5
  192. package/dist/core/runtime/bearings.d.ts +53 -12
  193. package/dist/core/runtime/bearings.js +132 -59
  194. package/dist/core/runtime/broker-cli.d.ts +1 -0
  195. package/dist/core/runtime/broker-cli.js +46 -0
  196. package/dist/core/runtime/broker-protocol.d.ts +332 -0
  197. package/dist/core/runtime/broker-protocol.js +153 -0
  198. package/dist/core/runtime/broker-sdk.d.ts +48 -0
  199. package/dist/core/runtime/broker-sdk.js +72 -0
  200. package/dist/core/runtime/broker.d.ts +55 -0
  201. package/dist/core/runtime/broker.js +1128 -0
  202. package/dist/core/runtime/close.js +35 -6
  203. package/dist/core/runtime/host.d.ts +53 -0
  204. package/dist/core/runtime/host.js +186 -0
  205. package/dist/core/runtime/kickoff.d.ts +2 -1
  206. package/dist/core/runtime/kickoff.js +91 -5
  207. package/dist/core/runtime/launch.d.ts +54 -4
  208. package/dist/core/runtime/launch.js +85 -5
  209. package/dist/core/runtime/lifecycle.js +23 -6
  210. package/dist/core/runtime/memory.d.ts +2 -42
  211. package/dist/core/runtime/memory.js +11 -162
  212. package/dist/core/runtime/nodes.d.ts +37 -0
  213. package/dist/core/runtime/nodes.js +60 -1
  214. package/dist/core/runtime/persona.js +21 -11
  215. package/dist/core/runtime/pi-vendored.d.ts +18 -0
  216. package/dist/core/runtime/pi-vendored.js +49 -0
  217. package/dist/core/runtime/placement.d.ts +32 -14
  218. package/dist/core/runtime/placement.js +206 -52
  219. package/dist/core/runtime/promote.d.ts +0 -6
  220. package/dist/core/runtime/promote.js +4 -12
  221. package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
  222. package/dist/core/runtime/{demote.js → recycle.js} +27 -11
  223. package/dist/core/runtime/reset.js +11 -8
  224. package/dist/core/runtime/revive.d.ts +2 -0
  225. package/dist/core/runtime/revive.js +34 -34
  226. package/dist/core/runtime/spawn.d.ts +23 -0
  227. package/dist/core/runtime/spawn.js +77 -23
  228. package/dist/core/runtime/stop-guard.d.ts +1 -1
  229. package/dist/core/runtime/stop-guard.js +6 -1
  230. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  231. package/dist/core/runtime/tmux-chrome.js +1 -1
  232. package/dist/core/runtime/tmux.d.ts +28 -0
  233. package/dist/core/runtime/tmux.js +80 -6
  234. package/dist/core/scope.d.ts +11 -0
  235. package/dist/core/scope.js +39 -0
  236. package/dist/core/spawn.d.ts +5 -0
  237. package/dist/core/spawn.js +20 -2
  238. package/dist/core/substrate/gate.d.ts +13 -0
  239. package/dist/core/substrate/gate.js +21 -0
  240. package/dist/core/substrate/index.d.ts +7 -0
  241. package/dist/core/substrate/index.js +18 -0
  242. package/dist/core/substrate/on-read.d.ts +14 -0
  243. package/dist/core/substrate/on-read.js +292 -0
  244. package/dist/core/substrate/render.d.ts +25 -0
  245. package/dist/core/substrate/render.js +256 -0
  246. package/dist/core/substrate/schema.d.ts +76 -0
  247. package/dist/core/substrate/schema.js +124 -0
  248. package/dist/core/substrate/session-cache.d.ts +30 -0
  249. package/dist/core/substrate/session-cache.js +77 -0
  250. package/dist/core/substrate/subject.d.ts +41 -0
  251. package/dist/core/substrate/subject.js +54 -0
  252. package/dist/core/tui/contract.d.ts +83 -0
  253. package/dist/core/tui/contract.js +8 -0
  254. package/dist/core/tui/draw.d.ts +96 -0
  255. package/dist/core/tui/draw.js +339 -0
  256. package/dist/core/tui/host.d.ts +29 -0
  257. package/dist/core/tui/host.js +379 -0
  258. package/dist/core/tui/loader.d.ts +16 -0
  259. package/dist/core/tui/loader.js +94 -0
  260. package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
  261. package/dist/core/wake.d.ts +86 -0
  262. package/dist/core/wake.js +308 -0
  263. package/dist/daemon/crtrd.d.ts +29 -4
  264. package/dist/daemon/crtrd.js +662 -46
  265. package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
  266. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
  267. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
  268. package/dist/pi-extensions/canvas-commands.d.ts +3 -0
  269. package/dist/pi-extensions/canvas-commands.js +10 -0
  270. package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
  271. package/dist/pi-extensions/canvas-context-intro.js +55 -15
  272. package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
  273. package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
  274. package/dist/pi-extensions/canvas-nav.d.ts +3 -0
  275. package/dist/pi-extensions/canvas-nav.js +87 -23
  276. package/dist/pi-extensions/canvas-stophook.js +17 -8
  277. package/dist/pi-extensions/canvas-view.d.ts +21 -0
  278. package/dist/pi-extensions/canvas-view.js +75 -0
  279. package/dist/prompts/skill.js +19 -26
  280. package/dist/prompts/view.d.ts +7 -0
  281. package/dist/prompts/view.js +101 -0
  282. package/dist/types.d.ts +4 -0
  283. package/dist/types.js +1 -0
  284. package/package.json +9 -4
  285. package/dist/commands/skill/find.d.ts +0 -4
  286. package/dist/commands/skill/find.js +0 -257
  287. package/dist/commands/skill/read.js +0 -91
  288. /package/dist/commands/{skill → memory}/read.d.ts +0 -0
  289. /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
@@ -0,0 +1,83 @@
1
+ import type { Key } from './terminal.js';
2
+ import type { Draw, Rect } from './draw.js';
3
+ export interface ViewManifest {
4
+ id: string;
5
+ title: string;
6
+ subtitle?: string;
7
+ description: string;
8
+ refreshMs?: number;
9
+ keymap?: KeyHint[];
10
+ }
11
+ export interface KeyHint {
12
+ keys: string;
13
+ label: string;
14
+ }
15
+ /** Severity of a {@link ViewHost.setBanner} banner — drives glyph + hue + the
16
+ * derived state chip (error→blocked/red, action→attention/yellow, info→neutral). */
17
+ export type BannerLevel = 'info' | 'action' | 'error';
18
+ /** The current host banner, threaded into {@link ViewModule.dump} so the static
19
+ * (non-TTY) path can surface guidance without the view mirroring it into state. */
20
+ export interface Banner {
21
+ msg: string;
22
+ level: BannerLevel;
23
+ }
24
+ /** Optional host context passed to {@link ViewModule.dump} on the piped path. */
25
+ export interface DumpContext {
26
+ banner: Banner | null;
27
+ }
28
+ export interface ViewHost {
29
+ /** CLI flags forwarded verbatim, e.g. { port: '9222', target: '...' }. */
30
+ readonly options: Readonly<Record<string, string>>;
31
+ /** Transient status line (left of the footer): "Loading…", "Sent". */
32
+ setStatus(msg: string | null): void;
33
+ /** Severity-coded guidance banner above the footer (info/action/error). The
34
+ * level drives the banner glyph + hue AND the derived title state chip. */
35
+ setBanner(msg: string, level: BannerLevel): void;
36
+ /** Sticky error banner above the footer; null clears. Back-compat shorthand
37
+ * for setBanner(msg, 'error'). */
38
+ setError(msg: string | null): void;
39
+ /** Dynamic title subtitle: a dim ` · <s>` after the bold title (e.g. the live
40
+ * unread count "3 unread"). A non-null value OVERRIDES the static
41
+ * {@link ViewManifest.subtitle}; `null` clears back to the manifest default
42
+ * (or nothing). The title still leads — the subtitle is muted. */
43
+ setSubtitle(s: string | null): void;
44
+ /** Explicit interaction-mode chip override (compose/react). When set it WINS
45
+ * the title state chip over the derived state, rendering the mode word in the
46
+ * compose accent (yellow `33` bold) with the `✎` glyph (`react` → `☺`), so
47
+ * entering an input mode is unmistakable. `null` returns to derived behavior. */
48
+ setMode(mode: string | null): void;
49
+ }
50
+ export type ViewAction = {
51
+ type: 'render';
52
+ } | {
53
+ type: 'refresh';
54
+ } | {
55
+ type: 'quit';
56
+ } | {
57
+ type: 'none';
58
+ };
59
+ export interface ViewKey {
60
+ input: string;
61
+ key: Key;
62
+ }
63
+ export interface ViewModule<S = unknown> {
64
+ manifest: ViewManifest;
65
+ /** Build initial state. CHEAP + synchronous-ish — no screen, no slow fetch.
66
+ * Slow data loads on the first refresh() so the host can paint a loading state. */
67
+ init(host: ViewHost): S | Promise<S>;
68
+ /** Fetch/poll. Mutates state in place. Host calls it on launch, on `refreshMs`,
69
+ * and whenever a hook returns { type:'refresh' }. The host runs it in the
70
+ * single-flight lane (busy indicator; never re-entrant). */
71
+ refresh?(state: S, host: ViewHost): Promise<void>;
72
+ /** Paint the view into `content` (the body rect, host chrome excluded).
73
+ * Pure: reads state, calls draw.*; returns nothing. NEVER writes ANSI. */
74
+ render(state: S, draw: Draw, content: Rect): void;
75
+ /** Handle one keystroke. Mutates state; returns the next action. May be async
76
+ * (open thread / send) — the host serializes async hooks in the single lane. */
77
+ onKey?(k: ViewKey, state: S, host: ViewHost): ViewAction | Promise<ViewAction>;
78
+ /** Static text for the non-TTY / piped path (exit 0). Snapshot of current
79
+ * state. The host threads its current banner via the optional `ctx` so a view
80
+ * can surface guidance without mirroring it into state (older views ignore the
81
+ * arg and read their own state). */
82
+ dump(state: S, ctx?: DumpContext): string;
83
+ }
@@ -0,0 +1,8 @@
1
+ // contract.ts — the view contract every `crtr view` module implements.
2
+ //
3
+ // A view is a self-contained ESM module whose DEFAULT export is a `ViewModule`.
4
+ // State is a single mutable object the view owns (mirrors browse/app.ts's
5
+ // BrowseState); hooks mutate it in place and return a `ViewAction` telling the
6
+ // host what to do next. The host injects `Draw` (from ./draw) + `ViewHost`; a
7
+ // view imports NOTHING from crtr internals.
8
+ export {};
@@ -0,0 +1,96 @@
1
+ export interface Size {
2
+ cols: number;
3
+ rows: number;
4
+ }
5
+ export interface Rect {
6
+ row: number;
7
+ col: number;
8
+ width: number;
9
+ height: number;
10
+ }
11
+ export interface Style {
12
+ fg?: string;
13
+ bg?: string;
14
+ bold?: boolean;
15
+ dim?: boolean;
16
+ reverse?: boolean;
17
+ }
18
+ export interface Span {
19
+ text: string;
20
+ style?: Style;
21
+ }
22
+ /** An SGR fg/bg parameter is digits and semicolons only (e.g. '32', '1;36', '236').
23
+ * Guards styleSpan against a non-numeric value (a color name) producing a broken
24
+ * escape sequence. */
25
+ export declare function isSgrParams(v: string): boolean;
26
+ /** Style one chunk of text. Hue (fg/bg) is gated on `color`; bold/dim/reverse
27
+ * are not. After the styled text we return to `lineBase` (not a bare reset) so a
28
+ * row-level background/dim persists across spans instead of bleeding or being
29
+ * cleared. */
30
+ export declare function styleSpan(text: string, style: Style | undefined, color: boolean, lineBase: string): string;
31
+ /** Assemble styled spans into one line clipped to `width` visible cols. When
32
+ * `fill`, pad the remainder with spaces (under `lineBase`) so a cursor-row
33
+ * background spans the full width. Always RESET-terminated so no color bleeds
34
+ * into the next line. */
35
+ export declare function assemble(spans: Span[], width: number, color: boolean, lineBase: string, fill: boolean): string;
36
+ /** Truncate to `max` visible cols (plain text, no ANSI). */
37
+ export declare function clip(text: string, max: number): string;
38
+ export interface ColorCaps {
39
+ /** Any hue (fg/bg color) allowed. */
40
+ color: boolean;
41
+ /** 256-color bg allowed — drives the subtle cursor-row background. */
42
+ color256: boolean;
43
+ }
44
+ /** Detect color capability. Honors `NO_COLOR` and `TERM=dumb`, and only emits
45
+ * hue when stdout is a TTY. `color256` additionally requires a 256/truecolor
46
+ * terminal (for the cursor-row background; otherwise we fall back to reverse). */
47
+ export declare function detectColorCaps(stream?: {
48
+ isTTY?: boolean;
49
+ }, env?: NodeJS.ProcessEnv): ColorCaps;
50
+ /** One pre-styled list row (the view styles its own spans; `list` windows + the
51
+ * cursor highlight are the host's job). An optional `right` group is flush-right
52
+ * on the row (e.g. a timestamp) via spansRight; the left `spans` are clipped to
53
+ * leave room for it, and the cursor-row highlight merges over both. */
54
+ export interface ListItemRow {
55
+ spans: Span[];
56
+ right?: Span[];
57
+ }
58
+ /** Adjusted scroll the view stores back so the cursor stays visible. */
59
+ export interface ListResult {
60
+ scroll: number;
61
+ }
62
+ export interface Draw {
63
+ readonly size: Size;
64
+ readonly caps: ColorCaps;
65
+ /** Styled spans at an absolute cell, clipped to maxWidth (default → edge). */
66
+ spans(row: number, col: number, spans: Span[], maxWidth?: number): void;
67
+ /** Flush-right span placement (mirror of `spans`): the group's last visible
68
+ * cell lands just before `rightCol` (i.e. start col = rightCol − visibleWidth,
69
+ * rightCol exclusive). If the group exceeds `maxWidth` (default → the room left
70
+ * of rightCol) the LEFT end is clipped with a leading `…`. */
71
+ spansRight(row: number, rightCol: number, spans: Span[], maxWidth?: number): void;
72
+ /** Convenience single span. */
73
+ text(row: number, col: number, text: string, style?: Style): void;
74
+ /** Dim horizontal rule across [fromCol,toCol) (default full width). */
75
+ hline(row: number, fromCol?: number, toCol?: number, ch?: string): void;
76
+ /** Dim vertical rule down column `col` across [fromRow,toRow) (default full
77
+ * height). `ch` defaults to `│`; pass `|` as the ASCII fallback. */
78
+ vline(col: number, fromRow?: number, toRow?: number, ch?: string): void;
79
+ /** Single-line box border around rect (optional title in the top edge). */
80
+ box(rect: Rect, title?: string): void;
81
+ /** Split a rect into N columns by weights. */
82
+ columns(rect: Rect, weights: number[]): Rect[];
83
+ /** Scrollable list within rect: windows `items` to fit height, highlights the
84
+ * cursor row (256-bg or reverse fallback, like browse). Returns adjusted
85
+ * scroll so the cursor stays visible — the view stores it in state. */
86
+ list(rect: Rect, items: ListItemRow[], cursor: number, scroll: number): ListResult;
87
+ }
88
+ /** A live Draw plus the host-side serializer. */
89
+ export interface DrawHandle {
90
+ draw: Draw;
91
+ /** Serialize the buffer to a full repaint frame (home + per-line clear + clear
92
+ * below) — identical framing to browse's renderFrame. */
93
+ frame(): string;
94
+ }
95
+ /** Create a screen-sized cell buffer + the absolute-cell Draw API over it. */
96
+ export declare function createDraw(size: Size, caps: ColorCaps): DrawHandle;
@@ -0,0 +1,339 @@
1
+ // draw.ts — the generic immediate-mode drawing surface shared by every crtr
2
+ // raw-ANSI TUI (the `crtr view` host and `canvas browse`).
3
+ //
4
+ // The span/color primitives (Span, styleSpan, assemble, clip, ColorCaps,
5
+ // detectColorCaps) are extracted verbatim-in-behavior from
6
+ // canvas/browse/render.ts so there is ONE copy; browse imports them back.
7
+ //
8
+ // On top of those primitives this module adds the `Draw` factory: a line/cell
9
+ // buffer covering the screen that a view fills via absolute-cell helpers
10
+ // (spans/text/hline/box/columns/list). The host allocates one Draw per frame,
11
+ // draws its chrome into it, hands the view a content Rect, then serializes the
12
+ // buffer to a single repaint frame (`\x1b[H` + per-line `\x1b[K` + `\x1b[J`) —
13
+ // exactly like browse's renderFrame does today.
14
+ //
15
+ // COLOR: hue (fg/bg) is gated on `caps.color`; structural SGR (bold/dim/reverse)
16
+ // is always allowed. 256-color bg (the cursor-row highlight) additionally needs
17
+ // `caps.color256`, falling back to reverse.
18
+ // ── ANSI ────────────────────────────────────────────────────────────────────
19
+ const ESC = '\x1b[';
20
+ const RESET = `${ESC}0m`;
21
+ const REVERSE = `${ESC}7m`;
22
+ const DIM = `${ESC}2m`;
23
+ const BOLD = `${ESC}1m`;
24
+ /** An SGR fg/bg parameter is digits and semicolons only (e.g. '32', '1;36', '236').
25
+ * Guards styleSpan against a non-numeric value (a color name) producing a broken
26
+ * escape sequence. */
27
+ export function isSgrParams(v) {
28
+ return v.length > 0 && /^[0-9;]+$/.test(v);
29
+ }
30
+ /** Style one chunk of text. Hue (fg/bg) is gated on `color`; bold/dim/reverse
31
+ * are not. After the styled text we return to `lineBase` (not a bare reset) so a
32
+ * row-level background/dim persists across spans instead of bleeding or being
33
+ * cleared. */
34
+ export function styleSpan(text, style, color, lineBase) {
35
+ if (text === '')
36
+ return '';
37
+ let pre = '';
38
+ if (style?.dim)
39
+ pre += DIM;
40
+ if (style?.bold)
41
+ pre += BOLD;
42
+ if (style?.reverse)
43
+ pre += REVERSE;
44
+ // fg is an SGR parameter string (e.g. '32' or '1;36'); bg is a 256-color index.
45
+ // Reject anything else so a bad value (e.g. a color NAME like 'green') degrades
46
+ // to no-color instead of emitting an invalid CSI that prints as literal garbage.
47
+ if (color && style?.fg && isSgrParams(style.fg))
48
+ pre += `${ESC}${style.fg}m`;
49
+ if (color && style?.bg && isSgrParams(style.bg))
50
+ pre += `${ESC}48;5;${style.bg}m`;
51
+ if (pre === '')
52
+ return text; // inherits lineBase / default
53
+ return `${pre}${text}${RESET}${lineBase}`;
54
+ }
55
+ /** Assemble styled spans into one line clipped to `width` visible cols. When
56
+ * `fill`, pad the remainder with spaces (under `lineBase`) so a cursor-row
57
+ * background spans the full width. Always RESET-terminated so no color bleeds
58
+ * into the next line. */
59
+ export function assemble(spans, width, color, lineBase, fill) {
60
+ let used = 0;
61
+ let body = '';
62
+ for (const span of spans) {
63
+ if (used >= width)
64
+ break;
65
+ if (span.text === '')
66
+ continue;
67
+ let t = span.text;
68
+ const remaining = width - used;
69
+ let cut = false;
70
+ if (t.length > remaining) {
71
+ t = t.slice(0, Math.max(0, remaining - 1)) + '…';
72
+ cut = true;
73
+ }
74
+ body += styleSpan(t, span.style, color, lineBase);
75
+ used += t.length;
76
+ if (cut)
77
+ break;
78
+ }
79
+ if (fill && used < width)
80
+ body += ' '.repeat(width - used);
81
+ return lineBase === '' ? body : `${lineBase}${body}${RESET}`;
82
+ }
83
+ /** Truncate to `max` visible cols (plain text, no ANSI). */
84
+ export function clip(text, max) {
85
+ if (max <= 0)
86
+ return '';
87
+ return text.length <= max ? text : text.slice(0, Math.max(0, max - 1)) + '…';
88
+ }
89
+ /** Detect color capability. Honors `NO_COLOR` and `TERM=dumb`, and only emits
90
+ * hue when stdout is a TTY. `color256` additionally requires a 256/truecolor
91
+ * terminal (for the cursor-row background; otherwise we fall back to reverse). */
92
+ export function detectColorCaps(stream = process.stdout, env = process.env) {
93
+ const term = env['TERM'] ?? '';
94
+ const color = stream.isTTY === true && !env['NO_COLOR'] && term !== 'dumb';
95
+ const colorTerm = env['COLORTERM'] ?? '';
96
+ const color256 = color && (/256|direct/i.test(term) || /truecolor|24bit/i.test(colorTerm));
97
+ return { color, color256 };
98
+ }
99
+ function mergeStyle(base, top) {
100
+ if (!top)
101
+ return base;
102
+ return {
103
+ fg: top.fg ?? base.fg,
104
+ bg: top.bg ?? base.bg,
105
+ bold: top.bold || base.bold,
106
+ dim: top.dim || base.dim,
107
+ reverse: top.reverse || base.reverse,
108
+ };
109
+ }
110
+ function sameStyle(a, b) {
111
+ if (a === b)
112
+ return true;
113
+ if (!a || !b)
114
+ return false;
115
+ return a.fg === b.fg && a.bg === b.bg
116
+ && !!a.bold === !!b.bold && !!a.dim === !!b.dim && !!a.reverse === !!b.reverse;
117
+ }
118
+ /** Create a screen-sized cell buffer + the absolute-cell Draw API over it. */
119
+ export function createDraw(size, caps) {
120
+ const cols = Math.max(1, size.cols);
121
+ const rows = Math.max(1, size.rows);
122
+ const grid = [];
123
+ for (let r = 0; r < rows; r++) {
124
+ const line = new Array(cols);
125
+ for (let c = 0; c < cols; c++)
126
+ line[c] = { ch: ' ', style: undefined };
127
+ grid.push(line);
128
+ }
129
+ /** Place spans starting at (row, col), clipped to `limit` visible cells; each
130
+ * span's style optionally merged under `base` (the cursor-row highlight). */
131
+ const place = (row, col, spans, maxWidth, base) => {
132
+ if (row < 0 || row >= rows)
133
+ return;
134
+ const edge = cols - col;
135
+ const limit = Math.min(maxWidth ?? edge, edge);
136
+ if (limit <= 0)
137
+ return;
138
+ let x = col;
139
+ let drawn = 0;
140
+ for (const span of spans) {
141
+ if (drawn >= limit)
142
+ break;
143
+ const style = base ? mergeStyle(base, span.style) : span.style;
144
+ for (const ch of Array.from(span.text)) {
145
+ if (drawn >= limit)
146
+ break;
147
+ if (x >= 0 && x < cols)
148
+ grid[row][x] = { ch, style };
149
+ x++;
150
+ drawn++;
151
+ }
152
+ }
153
+ };
154
+ const fillStyle = (row, col, width, style) => {
155
+ if (row < 0 || row >= rows)
156
+ return;
157
+ const end = Math.min(cols, col + width);
158
+ for (let x = Math.max(0, col); x < end; x++)
159
+ grid[row][x] = { ch: ' ', style };
160
+ };
161
+ /** Visible (column) width of a span group — ANSI-free cell count. */
162
+ const spanWidth = (spans) => {
163
+ let n = 0;
164
+ for (const s of spans)
165
+ n += Array.from(s.text).length;
166
+ return n;
167
+ };
168
+ /** Flatten spans to per-cell {ch,style}, then re-group consecutive same-style
169
+ * cells back into spans — used to left-clip a flush-right group. */
170
+ const regroup = (cells) => {
171
+ const out = [];
172
+ for (const c of cells) {
173
+ const last = out[out.length - 1];
174
+ if (last && sameStyle(last.style, c.style))
175
+ last.text += c.ch;
176
+ else
177
+ out.push({ text: c.ch, style: c.style });
178
+ }
179
+ return out;
180
+ };
181
+ /** Flush-right placement shared by draw.spansRight and the list right-group.
182
+ * Lays `spans` so the group ends just before `rightCol` (exclusive); clips the
183
+ * LEFT end with a leading `…` when the group exceeds the effective width.
184
+ * `base` (the cursor-row highlight) merges under each cell, like `place`. */
185
+ const placeRight = (row, rightCol, spans, maxWidth, base) => {
186
+ if (row < 0 || row >= rows)
187
+ return;
188
+ const cap = Math.max(0, rightCol); // room available left of rightCol
189
+ const max = Math.min(maxWidth ?? cap, cap);
190
+ if (max <= 0)
191
+ return;
192
+ let cells = [];
193
+ for (const s of spans)
194
+ for (const ch of Array.from(s.text))
195
+ cells.push({ ch, style: s.style });
196
+ if (cells.length === 0)
197
+ return;
198
+ if (cells.length > max) {
199
+ const drop = cells.length - max + 1; // make room for the leading …
200
+ cells = cells.slice(drop);
201
+ cells.unshift({ ch: '…', style: cells[0]?.style });
202
+ }
203
+ const startCol = rightCol - cells.length;
204
+ place(row, startCol, regroup(cells), undefined, base);
205
+ };
206
+ const draw = {
207
+ size: { cols, rows },
208
+ caps,
209
+ spans(row, col, spans, maxWidth) {
210
+ place(row, col, spans, maxWidth);
211
+ },
212
+ spansRight(row, rightCol, spans, maxWidth) {
213
+ placeRight(row, rightCol, spans, maxWidth);
214
+ },
215
+ text(row, col, text, style) {
216
+ place(row, col, [{ text, style }], undefined);
217
+ },
218
+ hline(row, fromCol = 0, toCol = cols, ch = '─') {
219
+ if (row < 0 || row >= rows)
220
+ return;
221
+ const a = Math.max(0, Math.min(cols, fromCol));
222
+ const b = Math.max(0, Math.min(cols, toCol));
223
+ for (let x = a; x < b; x++)
224
+ grid[row][x] = { ch, style: { dim: true } };
225
+ },
226
+ vline(col, fromRow = 0, toRow = rows, ch = '│') {
227
+ if (col < 0 || col >= cols)
228
+ return;
229
+ const a = Math.max(0, Math.min(rows, fromRow));
230
+ const b = Math.max(0, Math.min(rows, toRow));
231
+ for (let r = a; r < b; r++)
232
+ grid[r][col] = { ch, style: { dim: true } };
233
+ },
234
+ box(rect, title) {
235
+ const { row, col, width, height } = rect;
236
+ if (width < 2 || height < 2)
237
+ return;
238
+ const bs = { dim: true };
239
+ const right = col + width - 1;
240
+ const bottom = row + height - 1;
241
+ const horiz = '─'.repeat(Math.max(0, width - 2));
242
+ place(row, col, [{ text: `┌${horiz}┐`, style: bs }], width);
243
+ place(bottom, col, [{ text: `└${horiz}┘`, style: bs }], width);
244
+ for (let r = row + 1; r < bottom; r++) {
245
+ place(r, col, [{ text: '│', style: bs }], 1);
246
+ place(r, right, [{ text: '│', style: bs }], 1);
247
+ }
248
+ if (title !== undefined && title !== '' && width > 4) {
249
+ place(row, col + 2, [{ text: ` ${title} ` }], width - 4);
250
+ }
251
+ },
252
+ columns(rect, weights) {
253
+ const n = weights.length;
254
+ if (n === 0)
255
+ return [];
256
+ const total = weights.reduce((a, b) => a + (b > 0 ? b : 0), 0) || 1;
257
+ const out = [];
258
+ let x = rect.col;
259
+ let used = 0;
260
+ for (let i = 0; i < n; i++) {
261
+ const isLast = i === n - 1;
262
+ const w = isLast
263
+ ? Math.max(0, rect.width - used)
264
+ : Math.max(0, Math.floor((rect.width * Math.max(0, weights[i])) / total));
265
+ out.push({ row: rect.row, col: x, width: w, height: rect.height });
266
+ x += w;
267
+ used += w;
268
+ }
269
+ return out;
270
+ },
271
+ list(rect, items, cursor, scroll) {
272
+ const height = Math.max(0, rect.height);
273
+ let sc = scroll;
274
+ if (height > 0) {
275
+ if (cursor < sc)
276
+ sc = cursor;
277
+ if (cursor >= sc + height)
278
+ sc = cursor - height + 1;
279
+ }
280
+ if (sc < 0)
281
+ sc = 0;
282
+ if (sc > Math.max(0, items.length - 1))
283
+ sc = Math.max(0, items.length - 1);
284
+ const end = Math.min(items.length, sc + height);
285
+ for (let i = sc; i < end; i++) {
286
+ const rowIdx = rect.row + (i - sc);
287
+ const item = items[i];
288
+ const base = i === cursor
289
+ ? (caps.color256 ? { bg: '236' } : { reverse: true })
290
+ : undefined;
291
+ if (base)
292
+ fillStyle(rowIdx, rect.col, rect.width, base);
293
+ if (item.right && item.right.length) {
294
+ // Right-flush group (e.g. a timestamp); clip the left spans so they
295
+ // never collide, and merge the cursor highlight over both.
296
+ const rightWidth = spanWidth(item.right);
297
+ const leftLimit = Math.max(0, rect.width - rightWidth - 1);
298
+ place(rowIdx, rect.col, item.spans, leftLimit, base);
299
+ placeRight(rowIdx, rect.col + rect.width, item.right, rightWidth, base);
300
+ }
301
+ else {
302
+ place(rowIdx, rect.col, item.spans, rect.width, base);
303
+ }
304
+ }
305
+ return { scroll: sc };
306
+ },
307
+ };
308
+ const serializeRow = (cells) => {
309
+ // Trim trailing plain blanks (clear-to-EOL covers them); keep styled tails
310
+ // (e.g. a cursor-row background fill) intact.
311
+ let last = -1;
312
+ for (let i = cells.length - 1; i >= 0; i--) {
313
+ const c = cells[i];
314
+ if (c.ch !== ' ' || c.style !== undefined) {
315
+ last = i;
316
+ break;
317
+ }
318
+ }
319
+ if (last < 0)
320
+ return '';
321
+ let out = '';
322
+ let i = 0;
323
+ while (i <= last) {
324
+ const style = cells[i].style;
325
+ let text = '';
326
+ while (i <= last && sameStyle(cells[i].style, style)) {
327
+ text += cells[i].ch;
328
+ i++;
329
+ }
330
+ out += styleSpan(text, style, caps.color, '');
331
+ }
332
+ return out;
333
+ };
334
+ const frame = () => {
335
+ const body = grid.map((cells) => `${serializeRow(cells)}${ESC}K`).join('\r\n');
336
+ return `${ESC}H${body}${ESC}J`;
337
+ };
338
+ return { draw, frame };
339
+ }
@@ -0,0 +1,29 @@
1
+ import { type Draw, type Rect, type Size } from './draw.js';
2
+ import type { ViewModule, ViewManifest, BannerLevel } from './contract.js';
3
+ export interface RunViewOptions {
4
+ /** CLI flags forwarded verbatim to the view via host.options. */
5
+ options?: Record<string, string>;
6
+ }
7
+ /** The host-tracked chrome state. `banner` + `busy` + `loaded` derive the title
8
+ * state chip; `tick` advances the spinner under the busy-tick repaint. */
9
+ export interface Chrome {
10
+ status: string | null;
11
+ banner: {
12
+ msg: string;
13
+ level: BannerLevel;
14
+ } | null;
15
+ busy: boolean;
16
+ loaded: boolean;
17
+ lastRefresh: number;
18
+ tick: number;
19
+ subtitle: string | null;
20
+ mode: string | null;
21
+ }
22
+ /** Draw the host chrome into `draw` and return the content Rect for the view.
23
+ * Three zones: title row (state rail + title/subtitle + state chip + liveness),
24
+ * a hairline, and a footer (status left, keymap right) with a severity banner
25
+ * on the row above it when set. */
26
+ declare function drawChrome(draw: Draw, size: Size, manifest: ViewManifest, c: Chrome, now?: number): Rect;
27
+ export { drawChrome };
28
+ /** Host a view in the alt screen until it quits (or Ctrl-C). */
29
+ export declare function runView<S>(view: ViewModule<S>, opts?: RunViewOptions): Promise<void>;