@north-light/crouter 0.3.169 → 0.3.171

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 (122) hide show
  1. package/dist/build-root.d.ts +1 -16
  2. package/dist/build-root.js +1 -25
  3. package/dist/builtin-memory/insights/capture.md +66 -0
  4. package/dist/builtin-memory/insights/init.md +45 -0
  5. package/dist/builtin-memory/internal/plugins.md +12 -11
  6. package/dist/cli.js +2 -6
  7. package/dist/clients/attach/__tests__/attach-keybindings.test.js +7 -1
  8. package/dist/clients/attach/__tests__/context-message.test.js +50 -3
  9. package/dist/clients/attach/__tests__/diagram.test.js +1 -1
  10. package/dist/clients/attach/chrome/header.d.ts +10 -0
  11. package/dist/clients/attach/chrome/header.js +35 -0
  12. package/dist/clients/attach/chrome/status-line.d.ts +3 -0
  13. package/dist/clients/attach/chrome/status-line.js +5 -0
  14. package/dist/clients/attach/chrome/widgets.js +7 -18
  15. package/dist/clients/attach/input/controller.d.ts +10 -0
  16. package/dist/clients/attach/input/controller.js +3 -0
  17. package/dist/clients/attach/overlays/help.d.ts +33 -0
  18. package/dist/clients/attach/overlays/help.js +204 -0
  19. package/dist/clients/attach/render/chat-view.d.ts +81 -9
  20. package/dist/clients/attach/render/chat-view.js +434 -90
  21. package/dist/clients/attach/render/context-message.js +3 -3
  22. package/dist/clients/attach/render/diagram.js +2 -2
  23. package/dist/clients/attach/render/measured-container.d.ts +94 -0
  24. package/dist/clients/attach/render/measured-container.js +307 -0
  25. package/dist/clients/attach/render/transcript-copy.d.ts +9 -0
  26. package/dist/clients/attach/render/transcript-copy.js +81 -0
  27. package/dist/clients/attach/render/viewport.d.ts +29 -0
  28. package/dist/clients/attach/render/viewport.js +160 -0
  29. package/dist/clients/attach/session/bindings.d.ts +5 -1
  30. package/dist/clients/attach/session/bindings.js +16 -0
  31. package/dist/clients/attach/session/context.d.ts +5 -3
  32. package/dist/clients/attach/session/frame.d.ts +34 -0
  33. package/dist/clients/attach/session/frame.js +173 -0
  34. package/dist/clients/attach/session/frames.d.ts +2 -0
  35. package/dist/clients/attach/session/frames.js +3 -0
  36. package/dist/clients/attach/session/input-wiring.d.ts +10 -4
  37. package/dist/clients/attach/session/input-wiring.js +13 -13
  38. package/dist/clients/attach/session/keys.d.ts +4 -1
  39. package/dist/clients/attach/session/keys.js +9 -4
  40. package/dist/clients/attach/session/layout.d.ts +10 -8
  41. package/dist/clients/attach/session/layout.js +32 -31
  42. package/dist/clients/attach/session/mouse.d.ts +15 -0
  43. package/dist/clients/attach/session/mouse.js +61 -0
  44. package/dist/clients/attach/slash/dispatch.d.ts +10 -0
  45. package/dist/clients/attach/slash/dispatch.js +35 -2
  46. package/dist/clients/attach/viewer.js +605 -581
  47. package/dist/clients/web/dev-server.d.ts +4 -1
  48. package/dist/clients/web/dev-server.js +8 -19
  49. package/dist/clients/web/web-cmd.js +1 -1
  50. package/dist/commands/human/prompts.js +16 -6
  51. package/dist/commands/pkg/plugin-manage.js +143 -68
  52. package/dist/commands/sys/__tests__/setup-core.test.js +4 -2
  53. package/dist/commands/sys/setup-core.js +21 -16
  54. package/dist/core/__tests__/human-deliver.test.js +62 -2
  55. package/dist/core/clipboard-text.d.ts +8 -0
  56. package/dist/core/clipboard-text.js +18 -0
  57. package/dist/core/command-manifests/registry.d.ts +1 -3
  58. package/dist/core/command-plugins/bundle.d.ts +29 -0
  59. package/dist/core/command-plugins/bundle.js +198 -0
  60. package/dist/core/command-plugins/discovery.d.ts +0 -8
  61. package/dist/core/command-plugins/discovery.js +2 -39
  62. package/dist/core/command-plugins/transport/http-fetch.d.ts +4 -30
  63. package/dist/core/command-plugins/transport/http-fetch.js +17 -95
  64. package/dist/core/command.d.ts +9 -13
  65. package/dist/core/command.js +12 -17
  66. package/dist/core/config.js +2 -1
  67. package/dist/core/keybindings/__tests__/resolve.test.js +4 -0
  68. package/dist/core/keybindings/attach-control.d.ts +3 -0
  69. package/dist/core/keybindings/attach-control.js +1 -0
  70. package/dist/core/keybindings/catalog.d.ts +2 -2
  71. package/dist/core/keybindings/catalog.js +8 -0
  72. package/dist/core/runtime/broker-protocol.d.ts +13 -1
  73. package/dist/core/runtime/broker.js +213 -5
  74. package/dist/core/runtime/canvas-extensions.d.ts +3 -0
  75. package/dist/core/runtime/canvas-extensions.js +3 -0
  76. package/dist/core/runtime/front-door.js +4 -4
  77. package/dist/core/runtime/kickoff.d.ts +1 -5
  78. package/dist/core/runtime/kickoff.js +1 -5
  79. package/dist/core/runtime/naming.d.ts +7 -0
  80. package/dist/core/runtime/naming.js +5 -2
  81. package/dist/core/runtime/node-read.js +15 -0
  82. package/dist/core/runtime/package-health.d.ts +6 -21
  83. package/dist/core/runtime/package-health.js +12 -123
  84. package/dist/core/runtime/stop-guard.d.ts +1 -1
  85. package/dist/core/runtime/stop-guard.js +3 -3
  86. package/dist/core/runtime/tool-group-summary.d.ts +19 -0
  87. package/dist/core/runtime/tool-group-summary.js +117 -0
  88. package/dist/core/termrender/code-doc.d.ts +12 -0
  89. package/dist/core/termrender/code-doc.js +91 -0
  90. package/dist/core/termrender/display.d.ts +12 -0
  91. package/dist/core/termrender/display.js +19 -0
  92. package/dist/core/termrender/termrender.d.ts +73 -0
  93. package/dist/core/termrender/termrender.js +795 -0
  94. package/dist/core/termrender/version.d.ts +1 -0
  95. package/dist/core/termrender/version.js +1 -0
  96. package/dist/core/user-settings.d.ts +14 -0
  97. package/dist/core/user-settings.js +20 -0
  98. package/dist/daemon/crtrd.js +5 -6
  99. package/dist/index.d.ts +2 -0
  100. package/dist/index.js +1 -0
  101. package/dist/pi-extensions/__tests__/canvas-structured-output.test.d.ts +1 -0
  102. package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +63 -0
  103. package/dist/pi-extensions/canvas-stophook.js +2 -2
  104. package/dist/pi-extensions/canvas-structured-output.js +52 -8
  105. package/dist/pi-extensions/summary-tool.d.ts +33 -0
  106. package/dist/pi-extensions/summary-tool.js +62 -0
  107. package/dist/shared/generated-context.d.ts +19 -4
  108. package/dist/shared/generated-context.js +98 -7
  109. package/dist/shared/tool-groups.d.ts +23 -0
  110. package/dist/shared/tool-groups.js +60 -0
  111. package/dist/types.d.ts +11 -0
  112. package/dist/types.js +1 -0
  113. package/dist/web-client/assets/{index-U_NZ66VE.js → index-D7I3gHCL.js} +26 -19
  114. package/dist/web-client/index.html +1 -1
  115. package/dist/web-client/sw.js +1 -1
  116. package/package.json +3 -1
  117. package/runtime.lock.json +62 -3
  118. package/scripts/postinstall.mjs +14 -1
  119. package/dist/core/command-plugins/store.d.ts +0 -16
  120. package/dist/core/command-plugins/store.js +0 -64
  121. package/dist/core/runtime/fault-recovery-nudge.d.ts +0 -3
  122. package/dist/core/runtime/fault-recovery-nudge.js +0 -3
@@ -24,20 +24,5 @@ export declare function resolveRoot(first: string | undefined): Promise<RootDef>
24
24
  * Composition is invocation-local and reads only stored manifest bytes — no
25
25
  * cache, no persistence, ZERO network — so a plugin enable/update/remove or an
26
26
  * HTTP-transport plugin manifest replacement takes effect on the next invocation
27
- * automatically. The one network path (absent-store hydration, §5.1) is
28
- * isolated in `hydrateAbsentStoresAndRebuild`, off this composition path. */
27
+ * automatically. Composition performs no network I/O. */
29
28
  export declare function buildRoot(): Promise<RootDef>;
30
- /** The ONE unknown-first-token retry step (amended spec §5.1, §8), invoked by
31
- * dispatch only when an unknown FIRST token misses (not a core command, not
32
- * any stored external contribution) — never for a deeper unknown token under a
33
- * known command. Hydrate EVERY registered HTTP-transport plugin whose manifest store
34
- * is absent exactly once, write one stderr diagnostic per FAILED hydration,
35
- * and — if anything was hydrated — return a freshly rebuilt root so dispatch
36
- * can re-walk the original argv once. Returns undefined when nothing was
37
- * hydrated (no absent stores, or every attempt failed): the caller then
38
- * surfaces the plain `unknown_path`/USAGE error, with the failed-hydration
39
- * diagnostics already emitted and NO NETWORK precedence over that outcome.
40
- * When every registration holds a stored manifest this performs ZERO network
41
- * I/O and returns undefined. It is the ONLY network the dispatch path can
42
- * trigger; composition (`buildRoot`) never fetches. */
43
- export declare function hydrateAbsentStoresAndRebuild(): Promise<RootDef | undefined>;
@@ -1,5 +1,4 @@
1
1
  import { defineRoot } from './core/command.js';
2
- import { diag } from './core/io.js';
3
2
  const TAGLINE = 'crtr: agentic runtime.';
4
3
  /** Lazy registry: subtree name → dynamic importer that builds its BranchDef.
5
4
  * The whole point is that each `import()` only compiles that one subtree's
@@ -52,8 +51,7 @@ export async function resolveRoot(first) {
52
51
  * Composition is invocation-local and reads only stored manifest bytes — no
53
52
  * cache, no persistence, ZERO network — so a plugin enable/update/remove or an
54
53
  * HTTP-transport plugin manifest replacement takes effect on the next invocation
55
- * automatically. The one network path (absent-store hydration, §5.1) is
56
- * isolated in `hydrateAbsentStoresAndRebuild`, off this composition path. */
54
+ * automatically. Composition performs no network I/O. */
57
55
  export async function buildRoot() {
58
56
  const core = await Promise.all(SUBTREE_NAMES.map((n) => SUBTREE_LOADERS[n]()));
59
57
  // Dynamic import keeps external command discovery/compose (plugin +
@@ -71,25 +69,3 @@ export async function buildRoot() {
71
69
  const external = composeExternalSubtrees(snapshot);
72
70
  return defineRoot({ tagline: TAGLINE, globals: [], subtrees: [...core, ...external] });
73
71
  }
74
- /** The ONE unknown-first-token retry step (amended spec §5.1, §8), invoked by
75
- * dispatch only when an unknown FIRST token misses (not a core command, not
76
- * any stored external contribution) — never for a deeper unknown token under a
77
- * known command. Hydrate EVERY registered HTTP-transport plugin whose manifest store
78
- * is absent exactly once, write one stderr diagnostic per FAILED hydration,
79
- * and — if anything was hydrated — return a freshly rebuilt root so dispatch
80
- * can re-walk the original argv once. Returns undefined when nothing was
81
- * hydrated (no absent stores, or every attempt failed): the caller then
82
- * surfaces the plain `unknown_path`/USAGE error, with the failed-hydration
83
- * diagnostics already emitted and NO NETWORK precedence over that outcome.
84
- * When every registration holds a stored manifest this performs ZERO network
85
- * I/O and returns undefined. It is the ONLY network the dispatch path can
86
- * trigger; composition (`buildRoot`) never fetches. */
87
- export async function hydrateAbsentStoresAndRebuild() {
88
- const { hydrateAbsentHttpPlugins } = await import('./core/command-plugins/discovery.js');
89
- const { hydratedAny, diagnostics } = await hydrateAbsentHttpPlugins();
90
- for (const line of diagnostics)
91
- diag(line);
92
- if (!hydratedAny)
93
- return undefined;
94
- return buildRoot();
95
- }
@@ -0,0 +1,66 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When an initialized insight listener or a taste-capture preference identifies a possible reusable principle in user-supplied information, this knowledge should be read because immediate review separates durable user insight from plausible-sounding agent extrapolation before it shapes future decisions.
4
+ short-form: Extract one coherent principle from user input, review terse candidate truths immediately, then create or revise semantic memory only from what the user approves.
5
+ system-prompt-visibility: none
6
+ file-read-visibility: none
7
+ rationale: Agents have both missed deeper user insight hidden in ordinary feedback and saved their own unreviewed interpretation as fundamental truth; the user needs to edit, delete, or sharpen the inferred principle before it becomes standing semantic memory.
8
+ ---
9
+
10
+ # Confirm a user-derived insight before saving it
11
+
12
+ The calling memory defines the domain, destination, and final document shape. This workflow owns the extraction and review boundary. No semantic memory is written until the user approves the inferred truth.
13
+
14
+ ## Decide whether there is a principle
15
+
16
+ Eligible source material is information supplied or validated by the user: ordinary messages, corrections, humanloop answers, anchored review comments and line edits, and responses to claims an agent surfaced. Model output alone is not user insight.
17
+
18
+ A source episode qualifies only when you can state a coherent, non-obvious principle that would inform materially different future decisions. Routine requirements clarification, implementation feedback, wording changes, plan convergence, and facts already recorded in code or a specification do not qualify.
19
+
20
+ A correction is a signal, not proof. Infer the deeper reason beneath the immediate symptom, but do not save the literal conversational before-and-after as the principle.
21
+
22
+ ## Find the existing home
23
+
24
+ Search the destination directory before drafting. If the source refines, bounds, or strengthens an existing principle, propose a rewrite of that document. Create a new document only for a distinct read trigger that can evolve independently. One document may accumulate insight from several source episodes.
25
+
26
+ ## Review one coherent principle immediately
27
+
28
+ Write a small Markdown artifact under the current node's absolute context directory and queue it with `crtr human review`. Do not batch it with unrelated principles and do not ask a separate confirmation question first.
29
+
30
+ Use this shape:
31
+
32
+ ```markdown
33
+ # Proposed insight: <short principle name>
34
+
35
+ ## Source context
36
+
37
+ <Only enough user words or surrounding context to judge the inference. Prefer an exact quote.>
38
+
39
+ ## Current truth
40
+
41
+ <Include only when revising an existing memory.>
42
+
43
+ ## Proposed durable truth
44
+
45
+ - <terse candidate principle or facet>
46
+ - <another candidate only when it adds a real distinction or boundary>
47
+ ```
48
+
49
+ Candidate bullets are reverse-engineered hypotheses, not polished prose. Generate several when the deeper insight has multiple plausible formulations, and accept that the user may delete most of them. Do not pad to a target count, explain every bullet, or hide uncertainty in a long essay.
50
+
51
+ The review is asynchronous. Continue other work or end the turn and let the submitted review wake this node; never poll it.
52
+
53
+ ## Apply only approved truth
54
+
55
+ Apply direct line edits and explicit comments from the review. If feedback requires you to invent a materially new formulation rather than transcribe the user's correction, revise the artifact and review that formulation again. If no candidate survives, save nothing.
56
+
57
+ Then run `crtr memory write -h` and create or rewrite the approved destination document. For an initialized `insights/<topic>/` listener:
58
+
59
+ - store the principle as knowledge unless its actual use is to direct behavior;
60
+ - keep the body to the current distilled truth as short declarative bullets;
61
+ - use `none` visibility on both axes so the topic index is the one router; and
62
+ - add a link to its canonical principle document in the topic index without duplicating the insight there.
63
+
64
+ When the calling preference routes to `taste/` or another established destination, preserve that destination's own body and routing contract while keeping the same review-before-write boundary.
65
+
66
+ Source context and agent reasoning stay in the review artifact. Do not copy them into the approved memory or add provenance machinery. Run `crtr memory lint` after every create or rewrite and fix every finding.
@@ -0,0 +1,45 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When the user invokes /insights:init to begin gathering user-derived insight about a domain, this knowledge should be read because a correctly scoped listener preserves uniquely valuable knowledge without creating a parallel store or background system.
4
+ short-form: Initialize passive insight gathering for a domain at the narrowest durable scope; create one routed topic index that reviews user-derived principles before saving them.
5
+ system-prompt-visibility: none
6
+ file-read-visibility: none
7
+ slash: true
8
+ rationale: Ordinary conversations, corrections, humanloop answers, and review comments carry unique user knowledge that agents inconsistently recognize or save; when agents do infer a deeper principle, they have written it without first letting the user correct the extrapolation.
9
+ ---
10
+
11
+ # /insights:init — begin passive domain listening
12
+
13
+ Initialize an ordinary memory directory that listens for user-derived insight about this domain. This version is passive: do not start research, schedule work, create a standing node, or proactively question the user after initialization.
14
+
15
+ **Requested domain:** $ARGUMENTS
16
+
17
+ ## Establish the topic and scope
18
+
19
+ Turn the request into a short path-safe topic slug and a compact domain boundary that preserves the user's meaning rather than reducing it to a single keyword. If the request is empty or too vague to distinguish relevant from unrelated information, ask one focused question through `crtr human ask`.
20
+
21
+ Choose the narrowest durable scope that reaches every future conversation where this domain matters:
22
+
23
+ - project when the insight is meaningful only inside one project;
24
+ - profile when it spans the selected profile's projects but should not follow the user elsewhere; or
25
+ - user when it concerns the person, a market, a craft, or a domain that crosses workspaces.
26
+
27
+ Infer the scope from the request and current workspace. Ask through `crtr human ask` only when more than one scope is genuinely plausible, and settle scope before writing anything. Never use node scope for an ongoing listener.
28
+
29
+ Search the chosen scope before creating. If `insights/<topic>` already represents the same domain, refine that listener rather than creating an overlapping directory.
30
+
31
+ ## Create the listener
32
+
33
+ Run `crtr memory write -h`, then create `insights/<topic>/INDEX.md` at the chosen scope as a preference with system-prompt visibility `preview` and file-read visibility `none`.
34
+
35
+ Its routing line must name the actual domain trigger: when user-supplied information, a correction, or a user response relates to this domain, read the preference because recognizing the underlying principle preserves knowledge future decisions can use.
36
+
37
+ Keep the body short. It contains:
38
+
39
+ - the domain boundary, including exclusions needed to prevent false triggers;
40
+ - a directive to follow [[insights/capture]] whenever a source episode may reveal a reusable principle; and
41
+ - an `Approved insights` section containing document links to principle memories as they are approved.
42
+
43
+ Do not copy the capture workflow into the listener. The link keeps that process in one maintained place. Do not create placeholder principle documents.
44
+
45
+ Run `crtr memory lint`, fix every finding, then report the canonical listener name, selected scope, and the domain boundary. Initialization is complete once the routed listener exists; no independent work follows.
@@ -64,12 +64,12 @@ The `<plugin-name>` directory IS the plugin. The manifest's `name` field must ma
64
64
  | Field | Required | Notes |
65
65
  |---|---|---|
66
66
  | `name` | yes | Must match the directory name. Lowercase kebab. |
67
- | `version` | yes | Semver. Marketplace CI may bump automatically — see marketplaces knowledge. |
67
+ | `version` | yes for authored plugins | Semver. Marketplace CI may bump automatically; archive installs synthesize a 12-character content identifier. |
68
68
  | `description` | yes | One sentence. |
69
- | `source` | recommended | Git URL where the plugin lives. Used by `crtr pkg plugin update --name <name>`. |
69
+ | `source` | recommended | Human-facing URL where the plugin lives. Source-installed plugin updates derive provenance from their checkout or marketplace registration, not this descriptive field. |
70
70
  | `owner` | optional | Author info. |
71
71
  | `commands` | optional | Plugin-root-relative path to a `commands.json` command manifest. It must appear with `transport`; together they make the plugin contribute `crtr` commands — see [Plugin commands](#plugin-commands). |
72
- | `transport` | optional | Required exactly when `commands` is present. `{ "kind": "exec", "executable": "bin/cmd.js" }` runs executable leaves; a passthrough-only exec manifest may omit `executable`. `{ "kind": "http", "endpoint": "https://…", "authEnv": "TOKEN_NAME" }` calls a remote HTTP command surface. |
72
+ | `transport` | optional | Required exactly when `commands` is present. `{ "kind": "exec", "executable": "bin/cmd.js" }` runs executable leaves; a passthrough-only exec manifest may omit `executable`. `{ "kind": "http", "endpoint": "https://…", "authEnv": "TOKEN_NAME" }` calls a remote HTTP command surface. Archive-installed plugins also carry crouter-synthesized `bundle` provenance; authors do not add it. |
73
73
 
74
74
  ## Scopes
75
75
 
@@ -96,9 +96,10 @@ Four ways a plugin lands in a scope:
96
96
  - `crtr pkg market update --name <mkt>` refreshes the marketplace and every installed plugin it sources; relative sources follow the checkout, while remote-source checkouts pull their own Git remote.
97
97
  - See [[internal/marketplaces]].
98
98
 
99
- 3. **From an HTTP endpoint** (`crtr pkg plugin install --endpoint <url> --name <name> --scope user`):
100
- - Fetches the served `commands.json`, then writes the plugin manifest and exact fetched bytes into the selected scope. The endpoint must use `https:`; `http:` is accepted only for `localhost`, `127.0.0.1`, `[::1]`, `::1`, or `host.docker.internal`, with no credentials or fragment.
101
- - The install fails loudly and writes nothing when that fetch fails. Reinstalling the same HTTP plugin replaces its endpoint/auth-env declaration and stored manifest; it conflicts with an existing non-HTTP plugin of the same name.
99
+ 3. **From an HTTP directory archive** (`crtr pkg plugin install --endpoint <url> --name <name> --scope user`):
100
+ - Fetches one authenticated uncompressed `application/x-tar` archive containing `bundle.json`, `commands.json`, and `memory/**/*.md`. crouter validates every member before writing, then materializes the complete package with a synthesized `.crouter-plugin/plugin.json`. The endpoint must use `https:`; `http:` is accepted only for `localhost`, `127.0.0.1`, `[::1]`, `::1`, or `host.docker.internal`, with no credentials or fragment.
101
+ - The synthesized `bundle` field records the archive endpoint and optional credential variable name for installs and updates. Its distinct `transport` field records the archive endpoint's origin for command invocation. crouter stores the environment variable name, never its token.
102
+ - Reinstalling a bundle plugin replaces commands and memory together. Fetch or validation failure leaves the prior package intact. A commands-only HTTP plugin predating this archive contract must be removed and reinstalled.
102
103
 
103
104
  4. **Authored in place** (you're writing the plugin in a working repo):
104
105
  - Symlink for tight dev loop: `ln -s $(pwd) ~/.crouter/plugins/<name>`.
@@ -136,7 +137,7 @@ Standard semver:
136
137
  | New doc, new section, new example | minor (0.1.0 → 0.2.0) |
137
138
  | Removed doc, renamed doc, changed manifest schema | major (0.1.0 → 1.0.0) |
138
139
 
139
- `crtr pkg plugin update --name <name>` pulls source updates for ordinary and exec-transport plugins, while an HTTP-transport plugin unconditionally refetches and replaces its stored `commands.json`. A failed HTTP refresh preserves the prior bytes and exits nonzero. Plugins published through a marketplace may have their `version` field bumped automatically by CI — see [[internal/marketplaces]].
140
+ `crtr pkg plugin update --name <name>` pulls source updates for ordinary plugins, including source-installed plugins whose command transport is HTTP. An archive plugin instead re-fetches its recorded `bundle` endpoint and replaces its complete package atomically; its version is the first 12 hex characters of the fetched archive SHA-256. A failed named update preserves the prior package and exits nonzero; bulk update reports the failure and continues. Plugins published through a marketplace may have their `version` field bumped automatically by CI — see [[internal/marketplaces]].
140
141
 
141
142
  ## Enable/disable
142
143
 
@@ -180,12 +181,12 @@ Beyond docs, a plugin may contribute **top-level `crtr` commands** — new noun
180
181
  An HTTP-transport plugin replaces that declaration with:
181
182
 
182
183
  ```json
183
- "transport": { "kind": "http", "endpoint": "https://example.com/v1/cli/manifest", "authEnv": "DEPLOY_TOKEN" }
184
+ "transport": { "kind": "http", "endpoint": "https://example.com", "authEnv": "DEPLOY_TOKEN" }
184
185
  ```
185
186
 
186
- For `exec`, `executable` is required when the manifest has any executable leaf; a passthrough-only manifest omits it. When declared, it is plugin-root-relative, resolves inside the plugin root to a regular file, and carries the POSIX exec bit. For `http`, `endpoint` is an absolute endpoint and `authEnv` is optional. crtr stores only the environment variable **name**, never its credential; it reads the credential when fetching the manifest or invoking a leaf.
187
+ For `exec`, `executable` is required when the manifest has any executable leaf; a passthrough-only manifest omits it. When declared, it is plugin-root-relative, resolves inside the plugin root to a regular file, and carries the POSIX exec bit. For `http`, `transport.endpoint` is the backend origin and `authEnv` is optional. crouter stores only the environment variable **name**, never its credential; it reads the credential when invoking a leaf. A crouter-materialized archive plugin additionally records its archive URL separately in `bundle` for installation and update.
187
188
 
188
- Only an installed, **enabled** plugin's command manifest contributes. Discovery is per-invocation: enable, disable, update, and remove take effect on the next `crtr` call — no daemon restart or cache clearing.
189
+ Only an installed, **enabled** plugin's command manifest contributes. Discovery is per-invocation and reads the installed `commands.json` only: enable, disable, update, and remove take effect on the next `crtr` call with no daemon restart, cache clearing, or network fetch.
189
190
 
190
191
  ### commands.json shape
191
192
 
@@ -239,7 +240,7 @@ For an exec leaf, crtr writes exactly one JSON request to the executable's stdin
239
240
 
240
241
  - **Generate an exec plugin's `commands.json` from its command definitions — never hand-write it.** The manifest must stay in lockstep with the executable's command surface; drifted params or output fields surface as a validation issue or `plugin_protocol_error` at invocation.
241
242
  - **A required output field must be non-null.** The adapter treats an explicit `null` for a declared-required field as absent → `plugin_protocol_error`. If a value is optional, declare it `required: false`; if it is required, return a real value.
242
- - **HTTP plugin manifests are fetched bytes.** `crtr pkg plugin install --endpoint <url> --name <name>` fetches before writing the plugin, then stores the exact response at the plugin's declared `commands` path. `crtr pkg plugin update` unconditionally refetches HTTP plugins; failures preserve the prior bytes and exit nonzero. The stored manifest is authoritative with no TTL, ETag, or revalidation. If its file is missing or unparseable, an unknown-first-token miss fetches each affected HTTP plugin once; diagnostics name `crtr pkg plugin update <name>`.
243
+ - **HTTP archive plugins are complete package bytes.** `crtr pkg plugin install --endpoint <url> --name <name>` fetches and validates the complete archive before replacing the plugin directory. `crtr pkg plugin update` uses the recorded `bundle` provenance to do the same replacement. The installed `commands.json` and memory tree are authoritative with no TTL, ETag, revalidation, or dispatch-time network path.
243
244
 
244
245
  ### Validation and command collisions
245
246
 
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { openSync } from 'node:fs';
4
4
  import { enableCompileCache } from 'node:module';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { runCli } from './core/command.js';
7
- import { resolveRoot, hydrateAbsentStoresAndRebuild } from './build-root.js';
7
+ import { resolveRoot } from './build-root.js';
8
8
  import { maybeBootRoot } from './core/runtime/front-door.js';
9
9
  import { maybeAutoUpdate } from './core/auto-update.js';
10
10
  import { ensureOfficialMarketplace, ensureProjectScope } from './core/bootstrap.js';
@@ -57,12 +57,8 @@ async function main() {
57
57
  ensureOfficialMarketplace(process.argv);
58
58
  ensureProjectScope(process.argv);
59
59
  maybeAutoUpdate(process.argv);
60
- // Pass the one-shot unknown-first-token retry seam: on a first-token miss with
61
- // absent HTTP-plugin stores, hydrate them once, rebuild, and re-walk.
62
- // A recognized core first token resolves in `resolveRoot`'s fast path and never
63
- // reaches a first-token miss, so the seam stays off the core fast path.
64
60
  mark('cli.dispatch_start');
65
- await runCli(root, process.argv, { onUnknownFirstTokenMiss: hydrateAbsentStoresAndRebuild });
61
+ await runCli(root, process.argv);
66
62
  mark('cli.dispatch_done');
67
63
  }
68
64
  main().catch((err) => {
@@ -28,6 +28,7 @@ test('attach direct and private control input dispatch through the same action m
28
28
  'crtr.attach.detach': ['ctrl+x'],
29
29
  'crtr.attach.clear-or-detach': [],
30
30
  'crtr.attach.graph.toggle': [],
31
+ 'crtr.attach.help.toggle': ['alt+shift+k'],
31
32
  'crtr.mode.cycle': ['alt+x'],
32
33
  'crtr.attach.model-ladder.next': ['alt+z'],
33
34
  'crtr.attach.model-ladder.previous': ['alt+y'],
@@ -40,6 +41,7 @@ test('attach direct and private control input dispatch through the same action m
40
41
  'crtr.attach.detach': () => calls.push('detach'),
41
42
  'crtr.attach.clear-or-detach': () => calls.push('clear'),
42
43
  'crtr.attach.graph.toggle': () => calls.push('graph'),
44
+ 'crtr.attach.help.toggle': () => calls.push('help'),
43
45
  'crtr.mode.cycle': () => calls.push('mode'),
44
46
  'crtr.attach.model-ladder.next': () => calls.push('ladder'),
45
47
  'crtr.attach.model-ladder.previous': () => calls.push('ladder-back'),
@@ -57,12 +59,16 @@ test('attach direct and private control input dispatch through the same action m
57
59
  assert.equal(dispatchAttachBinding(bindings, '\x1by', actions), true);
58
60
  assert.equal(dispatchAttachBinding(bindings, '\x1bM', actions), false, 'old ladder-back key must not reach the listener action');
59
61
  assert.equal(dispatchAttachBinding(bindings, '\x1b[72;4u', actions), true);
62
+ assert.equal(dispatchAttachBinding(bindings, '\x1b[75;4u', actions), true);
60
63
  assert.equal(dispatchAttachBinding(bindings, '\x06', actions), false, 'disabled direct binding must stay disabled');
61
64
  const privateInput = encodeAttachControlInput('crtr.attach.profile-files');
62
65
  const privateAction = decodeAttachControlInput(privateInput);
63
66
  assert.equal(privateAction, 'crtr.attach.profile-files');
64
67
  dispatchAttachAction(privateAction, actions);
65
- assert.deepEqual(calls, ['detach', 'mode', 'ladder', 'ladder-back', 'inspect', 'profile-files']);
68
+ const helpPrivateInput = encodeAttachControlInput('crtr.attach.help.toggle');
69
+ assert.equal(decodeAttachControlInput(helpPrivateInput), 'crtr.attach.help.toggle');
70
+ dispatchAttachAction('crtr.attach.help.toggle', actions);
71
+ assert.deepEqual(calls, ['detach', 'mode', 'ladder', 'ladder-back', 'inspect', 'help', 'profile-files', 'help']);
66
72
  assert.equal(decodeAttachControlInput(`${privateInput}x`), undefined, 'whole-input decoding must be exact');
67
73
  assert.equal(decodeAttachControlInput('\x1b_crtr;crtr.attach.not-real\x1b\\'), undefined, 'unknown actions must stay ordinary input');
68
74
  assert.deepEqual(extractAttachControlInput(`before${privateInput}after`), {
@@ -1,7 +1,7 @@
1
1
  // Regression coverage for the attach viewer's display-only folding of
2
2
  // crouter-generated context. The body must stay agent-visible and expandable,
3
- // while bearings, situational updates, fresh-revive kickoffs, context nudges,
4
- // and node inbox digests share one compact renderer by default.
3
+ // while bearings, situational updates, fresh-revive kickoffs, runtime steering,
4
+ // recovery prompts, and node inbox digests share one compact renderer by default.
5
5
  import assert from 'node:assert/strict';
6
6
  import { readFileSync } from 'node:fs';
7
7
  import { dirname, join } from 'node:path';
@@ -13,7 +13,7 @@ import { applyTheme, attachMarkdownTheme } from '../config.js';
13
13
  import { ChatView } from '../render/chat-view.js';
14
14
  import { ContextMessageComponent } from '../render/context-message.js';
15
15
  import { styleAttachMarkdownHeadings } from '../render/markdown-headings.js';
16
- import { CONTEXT_NUDGE_CUSTOM_TYPE, REVIVE_KICKOFF_SENTINEL, generatedContextPresentation, } from '../../../shared/generated-context.js';
16
+ import { AUTH_FAULT_RECOVERY_BODY, CONNECTION_FAULT_RECOVERY_BODY, CONTEXT_NUDGE_CUSTOM_TYPE, PROVIDER_FAULT_RECOVERY_BODY, REVIVE_KICKOFF_SENTINEL, RUNTIME_RESTART_CONTINUATION, STALL_REPROMPT, formatModelFallbackRecovery, formatPersonaTransition, formatStructuredOutputReprompt, generatedContextPresentation, } from '../../../shared/generated-context.js';
17
17
  const HERE = dirname(fileURLToPath(import.meta.url));
18
18
  const CHAT_VIEW = readFileSync(join(HERE, '..', 'render/chat-view.ts'), 'utf8');
19
19
  const identity = (s) => s;
@@ -51,6 +51,27 @@ test('generated rows add no leading spacer after prior transcript content', asyn
51
51
  assert.notEqual(generatedIndex, -1);
52
52
  assert.match(lines[generatedIndex - 1] ?? '', /before/, 'the collapsed row follows prior content directly');
53
53
  });
54
+ test('Ctrl+O expands generated context while showing individual summarized calls', async () => {
55
+ const container = new Container();
56
+ const tui = { requestRender() { }, terminal: { rows: 40 } };
57
+ const view = new ChatView(tui, container, { toolOutputExpanded: false });
58
+ const internals = view;
59
+ internals.summarizeToolCalls = true;
60
+ internals.foldSettledTools = true;
61
+ internals.showGroupSummaries = true;
62
+ await view.applySnapshot({
63
+ messages: [{ role: 'user', content: RUNTIME_RESTART_CONTINUATION }],
64
+ stats: {},
65
+ state: { isStreaming: false },
66
+ });
67
+ assert.doesNotMatch(container.render(80).join('\n'), /runtime-restart-continuation/);
68
+ assert.deepEqual(view.toggleToolsExpanded(), { kind: 'summaries', shown: false });
69
+ assert.equal(internals.toolOutputExpanded, true);
70
+ assert.match(container.render(80).join('\n'), /runtime-restart-continuation/);
71
+ assert.deepEqual(view.toggleToolsExpanded(), { kind: 'summaries', shown: true });
72
+ assert.equal(internals.toolOutputExpanded, false);
73
+ assert.doesNotMatch(container.render(80).join('\n'), /runtime-restart-continuation/);
74
+ });
54
75
  test('the crtr-context label/hint keep their defaults when callers omit the new params', () => {
55
76
  const bearings = new ContextMessageComponent('some bearings body', false, identity, identity);
56
77
  const line = bearings.render(80).join('\n');
@@ -80,6 +101,32 @@ test('fresh-revive kickoff and both context-nudge roles classify as generated co
80
101
  assert.equal(inbox?.label, 'crtr inbox');
81
102
  assert.equal(inbox?.summary, '2 updates from child-node-123');
82
103
  });
104
+ test('runtime steering and recovery prompts classify without hiding their full bodies', () => {
105
+ const personaBody = formatPersonaTransition('You are now an orchestrator.');
106
+ const persona = generatedContextPresentation({ role: 'user', content: personaBody });
107
+ assert.equal(persona?.kind, 'persona-transition');
108
+ assert.equal(persona?.body, personaBody);
109
+ const structuredBody = formatStructuredOutputReprompt(JSON.stringify({ type: 'object' }, null, 2));
110
+ assert.equal(generatedContextPresentation({ role: 'user', content: STALL_REPROMPT })?.summary, 'completion required');
111
+ assert.equal(generatedContextPresentation({ role: 'user', content: structuredBody })?.summary, 'structured output required');
112
+ const recoveries = [
113
+ [AUTH_FAULT_RECOVERY_BODY, 'provider credentials updated'],
114
+ [CONNECTION_FAULT_RECOVERY_BODY, 'network connection restored'],
115
+ [PROVIDER_FAULT_RECOVERY_BODY, 'provider retry'],
116
+ [formatModelFallbackRecovery('old/model', 'new/model:high', 'credential'), 'model route changed'],
117
+ [formatModelFallbackRecovery('custom(provider)/model', 'new/model:high', 'not-found'), 'model route changed'],
118
+ [RUNTIME_RESTART_CONTINUATION, 'runtime restarted'],
119
+ ];
120
+ for (const [body, summary] of recoveries) {
121
+ const presentation = generatedContextPresentation({ role: 'user', content: body });
122
+ assert.equal(presentation?.kind, 'recovery');
123
+ assert.equal(presentation?.summary, summary);
124
+ assert.equal(presentation?.body, body);
125
+ }
126
+ assert.equal(generatedContextPresentation({ role: 'user', content: 'continue' }), null, 'a human one-word continue is not mistaken for runtime recovery');
127
+ const unmarkedModelRecovery = formatModelFallbackRecovery('custom(provider)/model', 'new/model:high', 'not-found').split('\n').slice(1, -1).join('\n');
128
+ assert.equal(generatedContextPresentation({ role: 'user', content: unmarkedModelRecovery }), null, 'a human message matching the recovery prose lacks the producer marker');
129
+ });
83
130
  test('render/chat-view.ts routes the shared generated-context classification through the foldable component', () => {
84
131
  assert.match(CHAT_VIEW, /generatedContextPresentation\(message\)/);
85
132
  assert.match(CHAT_VIEW, /new ContextMessageComponent\(/);
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import test from 'node:test';
3
- import { ensureRenderer, isRendererReady } from '@crouton-kit/humanloop';
3
+ import { ensureRenderer, isRendererReady } from '../../../core/termrender/termrender.js';
4
4
  import { transformDiagramFences } from '../render/diagram.js';
5
5
  // This asserts a REAL termrender render (box-drawing glyphs), so it only holds
6
6
  // once the managed renderer is provisioned — true in the full tier and on a dev
@@ -0,0 +1,10 @@
1
+ import { Container } from '@earendil-works/pi-tui';
2
+ import type { SessionState } from '../../../core/session-model/session-state.js';
3
+ import type { AttachSession } from '../session/context.js';
4
+ export interface RecapHeader {
5
+ /** The component ViewerFrame renders as its header region. */
6
+ readonly component: Container;
7
+ /** Repaint from the live `crtr-recap` widget state. Empty widget → 0 rows. */
8
+ render(): void;
9
+ }
10
+ export declare function createRecapHeader(ctx: Pick<AttachSession, 'tui' | 'pal'>, state: () => SessionState): RecapHeader;
@@ -0,0 +1,35 @@
1
+ // chrome/header.ts — the pinned recap header above the transcript.
2
+ //
3
+ // The broker's retained `crtr-recap` widget is painted here rather than in the
4
+ // bottom cockpit so it stays visible while the transcript scrolls.
5
+ import { Container, Text, wrapTextWithAnsi } from '@earendil-works/pi-tui';
6
+ import { DynamicBorder } from '@earendil-works/pi-coding-agent';
7
+ /** A recap line is `Goal:`/`State:` + body — bold the label, faint the body.
8
+ * Styled BEFORE wrapping (wrapTextWithAnsi carries SGR state across wraps), so a
9
+ * wrapped continuation stays faint. Unlabeled lines are all-faint. */
10
+ const styleRecapLine = (line, pal) => {
11
+ const match = /^(Goal|State):\s*/.exec(line);
12
+ if (match === null)
13
+ return pal.faint(line);
14
+ return `${pal.bold(match[1] + ':')} ${pal.faint(line.slice(match[0].length))}`;
15
+ };
16
+ export function createRecapHeader(ctx, state) {
17
+ const { tui, pal } = ctx;
18
+ const component = new Container();
19
+ const render = () => {
20
+ component.clear();
21
+ const widget = state().display.widgets['crtr-recap'];
22
+ if (widget !== undefined && widget.lines.length > 0) {
23
+ const span = Math.max(1, (process.stdout.columns ?? 80) - 2);
24
+ for (const line of widget.lines) {
25
+ const styled = styleRecapLine(line, pal);
26
+ for (const wrapped of wrapTextWithAnsi(styled, span)) {
27
+ component.addChild(new Text(wrapped, 1, 0));
28
+ }
29
+ }
30
+ component.addChild(new DynamicBorder(pal.border));
31
+ }
32
+ tui.requestRender();
33
+ };
34
+ return { component, render };
35
+ }
@@ -20,6 +20,9 @@ export interface StatusLineHooks {
20
20
  * a LOCAL in-tmux attach: a remote attach's local inbox registry cannot open
21
21
  * the remote canvas's filesystem tickets. */
22
22
  inboxAffordance: boolean;
23
+ /** Whether mouse reporting is currently enabled. When false, displays a
24
+ * low-priority 'mouse off' indicator in the status bar. */
25
+ mouseEnabled: () => boolean;
23
26
  }
24
27
  export interface AttachStatusLine {
25
28
  /** Repaint the bar from current state. */
@@ -76,6 +76,11 @@ export function createStatusLine(s, hooks) {
76
76
  segs.push(pal.muted(`⚑ ${total} · ${inboxOpenHint(bindings)}`));
77
77
  }
78
78
  }
79
+ // Mouse reporting state: show "mouse off" when reporting is disabled, so
80
+ // users are aware that tmux drag-select and copy-mode mouse work again.
81
+ if (!hooks.mouseEnabled()) {
82
+ segs.push(pal.muted('mouse off'));
83
+ }
79
84
  // Lowest-priority segment (R4): the Alt+C -> b hint, shown only once the
80
85
  // oldest still-foreground bash job has crossed the threshold. Read straight
81
86
  // off this node's job files rather than tracked from relayed tool-execution
@@ -7,33 +7,22 @@ import { DynamicBorder } from '@earendil-works/pi-coding-agent';
7
7
  export function createChromeWidgets(ctx, state) {
8
8
  const { tui, pal } = ctx;
9
9
  const { chrome, aboveWidgets, belowWidgets, queued } = ctx.containers;
10
- /** A recap line is `Goal:`/`State:` + body — bold the label, faint the body.
11
- * Styled BEFORE wrapping (wrapTextWithAnsi carries SGR state across wraps),
12
- * so a wrapped continuation stays faint. Unlabeled lines are all-faint. */
13
- const styleRecapLine = (line) => {
14
- const m = /^(Goal|State):\s*/.exec(line);
15
- if (m === null)
16
- return pal.faint(line);
17
- return `${pal.bold(m[1] + ':')} ${pal.faint(line.slice(m[0].length))}`;
18
- };
19
- /** Paint serializable extension widgets into their fixed cockpit slots. The
20
- * recap sits immediately above the editor border and uses the faint palette:
21
- * persistent orientation, but visually a side note rather than primary UI. */
10
+ /** Paint serializable extension widgets into their fixed cockpit slots. */
22
11
  const renderExtensionWidgets = () => {
23
12
  const session = state();
24
13
  aboveWidgets.clear();
25
14
  belowWidgets.clear();
26
- // The brief is an inline annotation, so do not give it the chat/chrome divider
27
- // that would make it read as a separate panel.
15
+ // The chrome container is the pinned transcript/cockpit divider. Recap is
16
+ // painted by the pinned header instead of being part of this cockpit slot.
28
17
  chrome.clear();
29
- if (session.display.widgets['crtr-recap'] === undefined)
30
- chrome.addChild(new DynamicBorder(pal.border));
18
+ chrome.addChild(new DynamicBorder(pal.border));
31
19
  const span = Math.max(1, (process.stdout.columns ?? 80) - 2);
32
20
  for (const [key, widget] of Object.entries(session.display.widgets)) {
21
+ if (key === 'crtr-recap')
22
+ continue;
33
23
  const target = widget.placement === 'belowEditor' ? belowWidgets : aboveWidgets;
34
24
  for (const line of widget.lines) {
35
- const styled = key === 'crtr-recap' ? styleRecapLine(line) : line;
36
- for (const wrapped of wrapTextWithAnsi(styled, span)) {
25
+ for (const wrapped of wrapTextWithAnsi(line, span)) {
37
26
  target.addChild(new Text(wrapped, 1, 0));
38
27
  }
39
28
  }
@@ -1,6 +1,7 @@
1
1
  import type { CustomEditor } from '@earendil-works/pi-coding-agent';
2
2
  import type { Component, KeybindingsManager, TUI } from '@earendil-works/pi-tui';
3
3
  import { type BrokerDataFrame, type ClientRole, type BrokerSnapshot, type ClientToBroker, type RpcExtensionUIRequest, type RpcExtensionUIResponse } from '../../../core/runtime/broker-protocol.js';
4
+ import type { CopyTarget } from '../render/transcript-copy.js';
4
5
  import type { ReadOpRequest } from '../../../core/broker-client/index.js';
5
6
  import { type Picker, type PickerControls } from '../overlays/pickers.js';
6
7
  export interface InputControllerHooks {
@@ -33,12 +34,21 @@ export interface InputControllerHooks {
33
34
  /** OPTIONAL: toggle the GRAPH overlay — forwarded to the slash context so
34
35
  * `/graph` opens/closes it (Unit Q wires it from runAttach). */
35
36
  onGraph?: () => void;
37
+ /** OPTIONAL: toggle the Help overlay — forwarded to the slash context so
38
+ * `/hotkeys` opens/closes it. */
39
+ onHelp?: () => void;
36
40
  /** OPTIONAL: detach the viewer — forwarded to the slash context so `/quit`
37
41
  * tears down the TUI/socket (the engine keeps running). Wired by viewer.ts. */
38
42
  onQuit?: () => void;
39
43
  /** OPTIONAL: copy the last assistant message to the clipboard — forwarded to
40
44
  * the slash context so `/copy` works. Wired by viewer.ts (it owns ChatView). */
41
45
  onCopy?: () => void;
46
+ /** OPTIONAL: copy a viewer-local transcript target — forwarded to the slash
47
+ * context so `/copy-message`, `/copy-tool`, and `/copy-transcript` work. */
48
+ onCopyTarget?: (target: CopyTarget) => void;
49
+ /** OPTIONAL: toggle mouse wheel reporting — forwarded to the slash context so
50
+ * `/mouse` can change viewer-local mouse state. */
51
+ onToggleMouse?: () => void;
42
52
  /** OPTIONAL: recolor the editor's name chip — forwarded to the slash context
43
53
  * so `/color` paints it (`null` clears). Wired by viewer.ts (it owns the
44
54
  * TitledEditor). */
@@ -240,8 +240,11 @@ export class InputController {
240
240
  remote: this.hooks.remote,
241
241
  role: this.hooks.role,
242
242
  onGraph: this.hooks.onGraph,
243
+ onHelp: this.hooks.onHelp,
243
244
  onQuit: this.hooks.onQuit,
244
245
  onCopy: this.hooks.onCopy,
246
+ onCopyTarget: this.hooks.onCopyTarget,
247
+ onToggleMouse: this.hooks.onToggleMouse,
245
248
  onColor: this.hooks.onColor,
246
249
  onNodeMetadata: this.hooks.onNodeMetadata,
247
250
  onFoldTools: this.hooks.onFoldTools,
@@ -0,0 +1,33 @@
1
+ import { type Component, type TUI } from '@earendil-works/pi-tui';
2
+ import type { AttachPalette } from '../config.js';
3
+ import type { AttachBindings } from '../session/bindings.js';
4
+ /** A bounded, focused reference for the attach viewer's viewport controls. */
5
+ export declare class HelpOverlay implements Component {
6
+ private readonly tui;
7
+ private readonly pal;
8
+ private handle;
9
+ private bindings;
10
+ private scrollTop;
11
+ private lastContentRows;
12
+ constructor(tui: TUI, pal: AttachPalette);
13
+ /** Re-resolve key spellings after `/reload` (mirrors GraphOverlay.setBindings). */
14
+ setBindings(bindings: AttachBindings): void;
15
+ toggle(): void;
16
+ close(): void;
17
+ isOpen(): boolean;
18
+ invalidate(): void;
19
+ render(width: number): string[];
20
+ handleInput(data: string): boolean;
21
+ private open;
22
+ private matchesBinding;
23
+ private scrollBy;
24
+ private pageSize;
25
+ private body;
26
+ private lines;
27
+ private binding;
28
+ private wrap;
29
+ private style;
30
+ private boxRow;
31
+ private borderRow;
32
+ private renderNarrow;
33
+ }