@pellux/goodvibes-terminal-shell 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/conformance.d.ts +11 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +10 -0
- package/dist/gateway-verbs.d.ts +49 -0
- package/dist/gateway-verbs.d.ts.map +1 -0
- package/dist/gateway-verbs.js +51 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/render-scheduler.d.ts +59 -0
- package/dist/render-scheduler.d.ts.map +1 -0
- package/dist/render-scheduler.js +72 -0
- package/dist/terminal-lifecycle.d.ts +123 -0
- package/dist/terminal-lifecycle.d.ts.map +1 -0
- package/dist/terminal-lifecycle.js +94 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @pellux/goodvibes-terminal-shell
|
|
2
|
+
|
|
3
|
+
Shared terminal-shell plumbing for GoodVibes daemon front-ends. Two front-ends drive a full-screen terminal UI over the same daemon runtime, and they must keep a specific slice of that runtime wiring **identical**. When copies of it drift, real defects ship. This package is the single home for that slice, so each front-end consumes one implementation instead of maintaining a parallel copy.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @pellux/goodvibes-terminal-shell
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What belongs here
|
|
12
|
+
|
|
13
|
+
Plumbing that must not drift between front-ends:
|
|
14
|
+
|
|
15
|
+
- **Gateway verb-group composition** — `attachWsOnlyGatewayVerbHandlers(catalog, deps)` binds the ws-only verb DESCRIPTORS (`fleet.*`, `checkpoints.*`, `sessions.search`, `push.*`) to their HANDLERS together, so a verb can never be descriptor-present but handler-absent. A descriptor with no handler answers `501 "Gateway method is not invokable"` over both websocket and HTTP invoke; registering the two together is what prevents that. `createArchivableFleetRegistry(deps)` builds the one shared, archive-aware process registry the `fleet.*` verbs query.
|
|
16
|
+
- **Terminal enter/restore sequencing** — `createTerminalLifecycle(deps)` owns the alt-screen enter, the idempotent synchronous restore (leave the alt screen, or clear the primary viewport **without** `ESC[3J` so scrollback survives; show the cursor on the screen the shell prompt lands on), and the restored-state gate `isTerminalRestored()`. The canonical escape sequences live in `TERMINAL_ESCAPES`.
|
|
17
|
+
- **Render-tick coalescing** — `createRenderScheduler(renderNow, scheduleFlush?, isReleased?)` collapses a within-tick burst of render requests into exactly one composite. Wire its third `isReleased` argument to the lifecycle's `isTerminalRestored()` so a late frame after teardown cannot paint over the restored shell.
|
|
18
|
+
- **The descriptor/handler conformance gate** — see below.
|
|
19
|
+
|
|
20
|
+
Every capability is a thin, dependency-injected wrapper: the front-end passes its concrete managers (process registry, checkpoint manager, session broker, secrets manager, approval broker, shell paths, terminal I/O) in, and this package owns the wiring.
|
|
21
|
+
|
|
22
|
+
## What does NOT belong here
|
|
23
|
+
|
|
24
|
+
Surface that front-ends legitimately diverge on, and which must stay in each app:
|
|
25
|
+
|
|
26
|
+
- Panels, views, and read-models
|
|
27
|
+
- Rendering, layout, and theming
|
|
28
|
+
- Keybindings and input handling
|
|
29
|
+
- Command surfaces and slash commands
|
|
30
|
+
- Application shutdown policy (draining services, persisting sessions, exit codes) — each app calls this package's `restoreTerminal()` for the terminal hand-back, but owns its own teardown.
|
|
31
|
+
|
|
32
|
+
## The conformance gate
|
|
33
|
+
|
|
34
|
+
The exact regression this package exists to prevent — a registered descriptor with no handler — is catchable in your own CI. Compose your daemon/gateway catalog exactly as production does, then assert every descriptor is invokable:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { assertEveryDescriptorHasHandler } from '@pellux/goodvibes-terminal-shell/conformance';
|
|
38
|
+
|
|
39
|
+
test('every registered gateway descriptor has a handler', () => {
|
|
40
|
+
const catalog = composeMyDaemonCatalog();
|
|
41
|
+
assertEveryDescriptorHasHandler(catalog); // throws with the offending ids
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`findMethodsMissingHandlers(catalog, options)` returns the offending ids instead of throwing. Both accept `onlyIds` / `ignoreIds` for catalogs whose builtin descriptors get handlers from a different layer. The catalog is read through a narrow structural view, so any `GatewayMethodCatalog`-shaped object works.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* conformance.ts — the descriptor/handler drift gate.
|
|
3
|
+
*
|
|
4
|
+
* The gate's implementation now lives once in the contracts package
|
|
5
|
+
* (@pellux/goodvibes-contracts/testing) so every consuming front-end runs the
|
|
6
|
+
* SAME kit rather than a divergent local copy. This module re-exports it so
|
|
7
|
+
* @pellux/goodvibes-terminal-shell's public surface is unchanged — the single
|
|
8
|
+
* source moved, the consumer contract did not.
|
|
9
|
+
*/
|
|
10
|
+
export { findMethodsMissingHandlers, assertEveryDescriptorHasHandler, type GatewayCatalogConformanceView, type ConformanceOptions, } from '@pellux/goodvibes-contracts/testing';
|
|
11
|
+
//# sourceMappingURL=conformance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EACL,0BAA0B,EAC1B,+BAA+B,EAC/B,KAAK,6BAA6B,EAClC,KAAK,kBAAkB,GACxB,MAAM,qCAAqC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* conformance.ts — the descriptor/handler drift gate.
|
|
3
|
+
*
|
|
4
|
+
* The gate's implementation now lives once in the contracts package
|
|
5
|
+
* (@pellux/goodvibes-contracts/testing) so every consuming front-end runs the
|
|
6
|
+
* SAME kit rather than a divergent local copy. This module re-exports it so
|
|
7
|
+
* @pellux/goodvibes-terminal-shell's public surface is unchanged — the single
|
|
8
|
+
* source moved, the consumer contract did not.
|
|
9
|
+
*/
|
|
10
|
+
export { findMethodsMissingHandlers, assertEveryDescriptorHasHandler, } from '@pellux/goodvibes-contracts/testing';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gateway-verbs.ts — attach handlers for every ws-only gateway verb group, and
|
|
3
|
+
* build the archive-aware fleet registry, in one shared place both daemon
|
|
4
|
+
* front-ends consume.
|
|
5
|
+
*
|
|
6
|
+
* The GatewayMethodCatalog's builtin DESCRIPTORS make fleet.* (including the
|
|
7
|
+
* archive verbs), checkpoints.*, sessions.search, and push.* appear in the
|
|
8
|
+
* contract, but a descriptor without an attached handler answers
|
|
9
|
+
* 501 "Gateway method is not invokable" over both websocket and HTTP invoke.
|
|
10
|
+
* A composition root that registers those descriptors but never calls the SDK's
|
|
11
|
+
* registration entry point ships exactly that 501 for the whole ws-only family
|
|
12
|
+
* — the regression this package exists to make impossible to reintroduce
|
|
13
|
+
* independently in each front-end. `attachWsOnlyGatewayVerbHandlers` binds the
|
|
14
|
+
* descriptors and their handlers together, and the conformance helper in
|
|
15
|
+
* ./conformance.ts gates that no descriptor is ever left handler-less.
|
|
16
|
+
*
|
|
17
|
+
* This module is a thin, dependency-injected wrapper over the SDK's own
|
|
18
|
+
* registration entry points. Front-ends pass their concrete managers in; the
|
|
19
|
+
* package owns the wiring so it cannot drift between them.
|
|
20
|
+
*/
|
|
21
|
+
import { type GatewayMethodCatalog, type GatewayVerbGroupDeps } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
22
|
+
import { type ArchivableProcessRegistry, type ProcessRegistryDeps } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
23
|
+
/**
|
|
24
|
+
* Attach handlers for the ws-only gateway verb groups (fleet.* / checkpoints.* /
|
|
25
|
+
* sessions.search / push.*) onto an existing catalog. Descriptors and handlers
|
|
26
|
+
* are registered together, so a verb can never be descriptor-present but
|
|
27
|
+
* handler-absent (the 501 regression class).
|
|
28
|
+
*
|
|
29
|
+
* Call once, at composition time, after the injected managers all exist. The
|
|
30
|
+
* dependency shape (`GatewayVerbGroupDeps`) is owned by the SDK; this wrapper
|
|
31
|
+
* exists so each front-end has a single named call site instead of reaching
|
|
32
|
+
* into the SDK's registration internals directly and diverging.
|
|
33
|
+
*/
|
|
34
|
+
export declare function attachWsOnlyGatewayVerbHandlers(gatewayMethods: GatewayMethodCatalog, deps: GatewayVerbGroupDeps): void;
|
|
35
|
+
/**
|
|
36
|
+
* Build the one shared, archive-aware process registry that aggregates a
|
|
37
|
+
* front-end's runtime managers (agents, WRFC chains, workflows, watchers,
|
|
38
|
+
* background processes). Constructed once per composition — not per consumer —
|
|
39
|
+
* so the coalesced tick and the agent-activity side-table are shared, not
|
|
40
|
+
* duplicated. Archive-aware: finished agent/swarm subtrees can be moved out of
|
|
41
|
+
* the live fleet view into a session-scoped archive (see the SDK's
|
|
42
|
+
* fleet/archive.ts).
|
|
43
|
+
*
|
|
44
|
+
* This is the registry the fleet.* gateway verbs above query, so building it
|
|
45
|
+
* here keeps the registry and its verb handlers on the same shared seam.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createArchivableFleetRegistry(deps: ProcessRegistryDeps): ArchivableProcessRegistry;
|
|
48
|
+
export type { GatewayVerbGroupDeps, ProcessRegistryDeps, ArchivableProcessRegistry };
|
|
49
|
+
//# sourceMappingURL=gateway-verbs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-verbs.d.ts","sourceRoot":"","sources":["../src/gateway-verbs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EAC1B,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACzB,MAAM,8CAA8C,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,wBAAgB,+BAA+B,CAC7C,cAAc,EAAE,oBAAoB,EACpC,IAAI,EAAE,oBAAoB,GACzB,IAAI,CAEN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,mBAAmB,GAAG,yBAAyB,CAElG;AAED,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gateway-verbs.ts — attach handlers for every ws-only gateway verb group, and
|
|
3
|
+
* build the archive-aware fleet registry, in one shared place both daemon
|
|
4
|
+
* front-ends consume.
|
|
5
|
+
*
|
|
6
|
+
* The GatewayMethodCatalog's builtin DESCRIPTORS make fleet.* (including the
|
|
7
|
+
* archive verbs), checkpoints.*, sessions.search, and push.* appear in the
|
|
8
|
+
* contract, but a descriptor without an attached handler answers
|
|
9
|
+
* 501 "Gateway method is not invokable" over both websocket and HTTP invoke.
|
|
10
|
+
* A composition root that registers those descriptors but never calls the SDK's
|
|
11
|
+
* registration entry point ships exactly that 501 for the whole ws-only family
|
|
12
|
+
* — the regression this package exists to make impossible to reintroduce
|
|
13
|
+
* independently in each front-end. `attachWsOnlyGatewayVerbHandlers` binds the
|
|
14
|
+
* descriptors and their handlers together, and the conformance helper in
|
|
15
|
+
* ./conformance.ts gates that no descriptor is ever left handler-less.
|
|
16
|
+
*
|
|
17
|
+
* This module is a thin, dependency-injected wrapper over the SDK's own
|
|
18
|
+
* registration entry points. Front-ends pass their concrete managers in; the
|
|
19
|
+
* package owns the wiring so it cannot drift between them.
|
|
20
|
+
*/
|
|
21
|
+
import { registerGatewayVerbGroups, } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
22
|
+
import { createProcessRegistry, withFleetArchive, } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
23
|
+
/**
|
|
24
|
+
* Attach handlers for the ws-only gateway verb groups (fleet.* / checkpoints.* /
|
|
25
|
+
* sessions.search / push.*) onto an existing catalog. Descriptors and handlers
|
|
26
|
+
* are registered together, so a verb can never be descriptor-present but
|
|
27
|
+
* handler-absent (the 501 regression class).
|
|
28
|
+
*
|
|
29
|
+
* Call once, at composition time, after the injected managers all exist. The
|
|
30
|
+
* dependency shape (`GatewayVerbGroupDeps`) is owned by the SDK; this wrapper
|
|
31
|
+
* exists so each front-end has a single named call site instead of reaching
|
|
32
|
+
* into the SDK's registration internals directly and diverging.
|
|
33
|
+
*/
|
|
34
|
+
export function attachWsOnlyGatewayVerbHandlers(gatewayMethods, deps) {
|
|
35
|
+
registerGatewayVerbGroups(gatewayMethods, deps);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build the one shared, archive-aware process registry that aggregates a
|
|
39
|
+
* front-end's runtime managers (agents, WRFC chains, workflows, watchers,
|
|
40
|
+
* background processes). Constructed once per composition — not per consumer —
|
|
41
|
+
* so the coalesced tick and the agent-activity side-table are shared, not
|
|
42
|
+
* duplicated. Archive-aware: finished agent/swarm subtrees can be moved out of
|
|
43
|
+
* the live fleet view into a session-scoped archive (see the SDK's
|
|
44
|
+
* fleet/archive.ts).
|
|
45
|
+
*
|
|
46
|
+
* This is the registry the fleet.* gateway verbs above query, so building it
|
|
47
|
+
* here keeps the registry and its verb handlers on the same shared seam.
|
|
48
|
+
*/
|
|
49
|
+
export function createArchivableFleetRegistry(deps) {
|
|
50
|
+
return withFleetArchive(createProcessRegistry(deps));
|
|
51
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pellux/goodvibes-terminal-shell
|
|
3
|
+
*
|
|
4
|
+
* Shared terminal-shell plumbing for GoodVibes daemon front-ends. This is the
|
|
5
|
+
* single home for the runtime wiring that two front-ends must keep identical:
|
|
6
|
+
* gateway verb-group composition, terminal enter/restore sequencing, and
|
|
7
|
+
* render-tick coalescing. Each capability is a thin, dependency-injected wrapper
|
|
8
|
+
* so a front-end's composition root becomes a few named calls into this package
|
|
9
|
+
* instead of a hand-maintained copy that drifts.
|
|
10
|
+
*
|
|
11
|
+
* See ./conformance for the descriptor/handler gate a consumer runs against its
|
|
12
|
+
* own composition in CI.
|
|
13
|
+
*/
|
|
14
|
+
export { attachWsOnlyGatewayVerbHandlers, createArchivableFleetRegistry, type GatewayVerbGroupDeps, type ProcessRegistryDeps, type ArchivableProcessRegistry, } from './gateway-verbs.js';
|
|
15
|
+
export { TERMINAL_ESCAPES, createTerminalLifecycle, type TerminalEscapes, type TerminalSequenceSet, type TerminalLifecycleDeps, type TerminalLifecycle, } from './terminal-lifecycle.js';
|
|
16
|
+
export { createRenderScheduler, type RenderScheduler, } from './render-scheduler.js';
|
|
17
|
+
export { findMethodsMissingHandlers, assertEveryDescriptorHasHandler, type GatewayCatalogConformanceView, type ConformanceOptions, } from './conformance.js';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,+BAA+B,EAC/B,6BAA6B,EAC7B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,GAC/B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,GACvB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,0BAA0B,EAC1B,+BAA+B,EAC/B,KAAK,6BAA6B,EAClC,KAAK,kBAAkB,GACxB,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pellux/goodvibes-terminal-shell
|
|
3
|
+
*
|
|
4
|
+
* Shared terminal-shell plumbing for GoodVibes daemon front-ends. This is the
|
|
5
|
+
* single home for the runtime wiring that two front-ends must keep identical:
|
|
6
|
+
* gateway verb-group composition, terminal enter/restore sequencing, and
|
|
7
|
+
* render-tick coalescing. Each capability is a thin, dependency-injected wrapper
|
|
8
|
+
* so a front-end's composition root becomes a few named calls into this package
|
|
9
|
+
* instead of a hand-maintained copy that drifts.
|
|
10
|
+
*
|
|
11
|
+
* See ./conformance for the descriptor/handler gate a consumer runs against its
|
|
12
|
+
* own composition in CI.
|
|
13
|
+
*/
|
|
14
|
+
export { attachWsOnlyGatewayVerbHandlers, createArchivableFleetRegistry, } from './gateway-verbs.js';
|
|
15
|
+
export { TERMINAL_ESCAPES, createTerminalLifecycle, } from './terminal-lifecycle.js';
|
|
16
|
+
export { createRenderScheduler, } from './render-scheduler.js';
|
|
17
|
+
export { findMethodsMissingHandlers, assertEveryDescriptorHasHandler, } from './conformance.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render-scheduler.ts — same-tick render coalescing for the terminal shell.
|
|
3
|
+
*
|
|
4
|
+
* A front-end fans out its own direct render() calls across turn/stream/input
|
|
5
|
+
* wiring (many invocation contexts). Each one previously ran a full synchronous
|
|
6
|
+
* composite the instant it was called. When several fire within a single
|
|
7
|
+
* event-loop tick — a streaming burst is the canonical case — only the LAST
|
|
8
|
+
* frame is ever visible; the earlier composites are immediately overwritten.
|
|
9
|
+
* This scheduler collapses every schedule() call made within one tick into a
|
|
10
|
+
* single composite flushed on the microtask queue, so the tick produces exactly
|
|
11
|
+
* one frame. That frame is byte-identical to what the last synchronous render()
|
|
12
|
+
* would have produced, because the coalesced flush runs after all of the tick's
|
|
13
|
+
* synchronous state mutations, reading the same final state the last direct
|
|
14
|
+
* render() would have read.
|
|
15
|
+
*
|
|
16
|
+
* This is deliberately SEPARATE from any cross-tick, throttled (~60fps)
|
|
17
|
+
* coalescer a front-end may run for its panel/input/runtime fan-out — that kind
|
|
18
|
+
* caps repaint RATE across ticks. This one collapses the WITHIN-tick burst with
|
|
19
|
+
* no added latency: a microtask flushes at the tail of the current tick, before
|
|
20
|
+
* the event loop yields to I/O, so the frame still lands in the same turn it was
|
|
21
|
+
* requested in.
|
|
22
|
+
*
|
|
23
|
+
* flushNow() preserves a synchronous immediate path for callers that genuinely
|
|
24
|
+
* need the frame composited before they return. Terminal resize is the known
|
|
25
|
+
* case: the resize handler resets the compositor diff and must repaint against
|
|
26
|
+
* the new dimensions synchronously rather than waiting for the microtask.
|
|
27
|
+
* flushNow() also clears any pending coalesced flush, so a resize (or any
|
|
28
|
+
* immediate paint) followed by an already-queued microtask never double-composites.
|
|
29
|
+
*/
|
|
30
|
+
export interface RenderScheduler {
|
|
31
|
+
/**
|
|
32
|
+
* Coalesced request: schedule a composite for the current tick. N calls within
|
|
33
|
+
* one tick collapse into exactly one composite, flushed on the microtask queue.
|
|
34
|
+
*/
|
|
35
|
+
readonly schedule: () => void;
|
|
36
|
+
/**
|
|
37
|
+
* Immediate request: run the composite synchronously right now, and cancel any
|
|
38
|
+
* pending coalesced flush so the tick still composites only once. Use only where
|
|
39
|
+
* a caller genuinely requires synchronous output (terminal resize).
|
|
40
|
+
*/
|
|
41
|
+
readonly flushNow: () => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build a render scheduler around `renderNow` (the synchronous composite closure).
|
|
45
|
+
*
|
|
46
|
+
* `scheduleFlush` is the deferral primitive; it defaults to `queueMicrotask` so
|
|
47
|
+
* bursts coalesce within the current tick. Tests and benchmarks inject a manual
|
|
48
|
+
* queue to flush deterministically.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createRenderScheduler(renderNow: () => void, scheduleFlush?: (flush: () => void) => void,
|
|
51
|
+
/**
|
|
52
|
+
* When provided and returning true, every composite path becomes a no-op.
|
|
53
|
+
* Wire this to the terminal lifecycle's isTerminalRestored(): once the exit
|
|
54
|
+
* teardown has handed the terminal back to the shell, a late frame (async
|
|
55
|
+
* shutdown races, stray timers) would paint cursor-positioned content over
|
|
56
|
+
* the user's primary screen and strand the next prompt mid-screen.
|
|
57
|
+
*/
|
|
58
|
+
isReleased?: () => boolean): RenderScheduler;
|
|
59
|
+
//# sourceMappingURL=render-scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-scheduler.d.ts","sourceRoot":"","sources":["../src/render-scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,IAAI,EACrB,aAAa,GAAE,CAAC,KAAK,EAAE,MAAM,IAAI,KAAK,IAAqB;AAC3D;;;;;;GAMG;AACH,UAAU,GAAE,MAAM,OAAqB,GACtC,eAAe,CA2BjB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render-scheduler.ts — same-tick render coalescing for the terminal shell.
|
|
3
|
+
*
|
|
4
|
+
* A front-end fans out its own direct render() calls across turn/stream/input
|
|
5
|
+
* wiring (many invocation contexts). Each one previously ran a full synchronous
|
|
6
|
+
* composite the instant it was called. When several fire within a single
|
|
7
|
+
* event-loop tick — a streaming burst is the canonical case — only the LAST
|
|
8
|
+
* frame is ever visible; the earlier composites are immediately overwritten.
|
|
9
|
+
* This scheduler collapses every schedule() call made within one tick into a
|
|
10
|
+
* single composite flushed on the microtask queue, so the tick produces exactly
|
|
11
|
+
* one frame. That frame is byte-identical to what the last synchronous render()
|
|
12
|
+
* would have produced, because the coalesced flush runs after all of the tick's
|
|
13
|
+
* synchronous state mutations, reading the same final state the last direct
|
|
14
|
+
* render() would have read.
|
|
15
|
+
*
|
|
16
|
+
* This is deliberately SEPARATE from any cross-tick, throttled (~60fps)
|
|
17
|
+
* coalescer a front-end may run for its panel/input/runtime fan-out — that kind
|
|
18
|
+
* caps repaint RATE across ticks. This one collapses the WITHIN-tick burst with
|
|
19
|
+
* no added latency: a microtask flushes at the tail of the current tick, before
|
|
20
|
+
* the event loop yields to I/O, so the frame still lands in the same turn it was
|
|
21
|
+
* requested in.
|
|
22
|
+
*
|
|
23
|
+
* flushNow() preserves a synchronous immediate path for callers that genuinely
|
|
24
|
+
* need the frame composited before they return. Terminal resize is the known
|
|
25
|
+
* case: the resize handler resets the compositor diff and must repaint against
|
|
26
|
+
* the new dimensions synchronously rather than waiting for the microtask.
|
|
27
|
+
* flushNow() also clears any pending coalesced flush, so a resize (or any
|
|
28
|
+
* immediate paint) followed by an already-queued microtask never double-composites.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Build a render scheduler around `renderNow` (the synchronous composite closure).
|
|
32
|
+
*
|
|
33
|
+
* `scheduleFlush` is the deferral primitive; it defaults to `queueMicrotask` so
|
|
34
|
+
* bursts coalesce within the current tick. Tests and benchmarks inject a manual
|
|
35
|
+
* queue to flush deterministically.
|
|
36
|
+
*/
|
|
37
|
+
export function createRenderScheduler(renderNow, scheduleFlush = queueMicrotask,
|
|
38
|
+
/**
|
|
39
|
+
* When provided and returning true, every composite path becomes a no-op.
|
|
40
|
+
* Wire this to the terminal lifecycle's isTerminalRestored(): once the exit
|
|
41
|
+
* teardown has handed the terminal back to the shell, a late frame (async
|
|
42
|
+
* shutdown races, stray timers) would paint cursor-positioned content over
|
|
43
|
+
* the user's primary screen and strand the next prompt mid-screen.
|
|
44
|
+
*/
|
|
45
|
+
isReleased = () => false) {
|
|
46
|
+
let scheduled = false;
|
|
47
|
+
const flush = () => {
|
|
48
|
+
// A synchronous flushNow() (or a superseding immediate paint) before this
|
|
49
|
+
// microtask ran clears `scheduled`, turning this into a no-op so a single
|
|
50
|
+
// tick never composites twice.
|
|
51
|
+
if (!scheduled)
|
|
52
|
+
return;
|
|
53
|
+
scheduled = false;
|
|
54
|
+
if (isReleased())
|
|
55
|
+
return;
|
|
56
|
+
renderNow();
|
|
57
|
+
};
|
|
58
|
+
const schedule = () => {
|
|
59
|
+
if (scheduled)
|
|
60
|
+
return;
|
|
61
|
+
scheduled = true;
|
|
62
|
+
scheduleFlush(flush);
|
|
63
|
+
};
|
|
64
|
+
const flushNow = () => {
|
|
65
|
+
// Satisfy any pending coalesced flush, then composite synchronously.
|
|
66
|
+
scheduled = false;
|
|
67
|
+
if (isReleased())
|
|
68
|
+
return;
|
|
69
|
+
renderNow();
|
|
70
|
+
};
|
|
71
|
+
return { schedule, flushNow };
|
|
72
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* terminal-lifecycle.ts — shared terminal enter/restore sequencing for GoodVibes
|
|
3
|
+
* daemon front-ends.
|
|
4
|
+
*
|
|
5
|
+
* Both front-ends drive a full-screen terminal UI and must enter and later
|
|
6
|
+
* restore the terminal identically. When these sequences diverge between them,
|
|
7
|
+
* exit teardown leaves the wrong screen active or wipes the user's scrollback —
|
|
8
|
+
* a defect class that recurs whenever the two copies drift. This module is the
|
|
9
|
+
* single home for the escape sequences and the enter/restore ordering, so both
|
|
10
|
+
* front-ends share one implementation.
|
|
11
|
+
*
|
|
12
|
+
* What lives here is only the terminal-state sequencing and the restored-state
|
|
13
|
+
* gate. The graceful application shutdown (draining services, persisting
|
|
14
|
+
* sessions, exit codes) is front-end-specific and stays in each app's own
|
|
15
|
+
* process-lifecycle wiring; that wiring calls `restoreTerminal()` from this
|
|
16
|
+
* module for the synchronous terminal hand-back.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The canonical control sequences the shell writes to enter and leave terminal
|
|
20
|
+
* mode (alt screen, mouse, cursor, keyboard-extension, paste, and focus
|
|
21
|
+
* reporting). Shared verbatim so the two front-ends cannot drift on the bytes.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TERMINAL_ESCAPES: {
|
|
24
|
+
readonly ALT_SCREEN_ENTER: "\u001B[?1049h";
|
|
25
|
+
readonly ALT_SCREEN_EXIT: "\u001B[?1049l";
|
|
26
|
+
readonly MOUSE_ENABLE: "\u001B[?1000h\u001B[?1002h\u001B[?1006h";
|
|
27
|
+
readonly MOUSE_DISABLE: "\u001B[?1006l\u001B[?1002l\u001B[?1000l";
|
|
28
|
+
readonly CURSOR_HIDE: "\u001B[?25l";
|
|
29
|
+
readonly CURSOR_SHOW: "\u001B[?25h";
|
|
30
|
+
readonly KEYBOARD_EXT_ENABLE: "\u001B[>4;2m\u001B[?1u";
|
|
31
|
+
readonly KEYBOARD_EXT_DISABLE: "\u001B[>4;0m\u001B[?1l";
|
|
32
|
+
readonly PASTE_ENABLE: "\u001B[?2004h";
|
|
33
|
+
readonly PASTE_DISABLE: "\u001B[?2004l";
|
|
34
|
+
readonly FOCUS_ENABLE: "\u001B[?1004h";
|
|
35
|
+
readonly FOCUS_DISABLE: "\u001B[?1004l";
|
|
36
|
+
/**
|
|
37
|
+
* Clear viewport + home the cursor, WITHOUT ESC[3J. The 3J form wipes the
|
|
38
|
+
* user's PRIMARY-screen scrollback on several emulators; the restore path
|
|
39
|
+
* must never use it. Used by the no-alt-screen restore path.
|
|
40
|
+
*/
|
|
41
|
+
readonly CLEAR_VIEWPORT_HOME: "\u001B[2J\u001B[H";
|
|
42
|
+
};
|
|
43
|
+
export type TerminalEscapes = typeof TERMINAL_ESCAPES;
|
|
44
|
+
/**
|
|
45
|
+
* The subset of escape sequences the enter/restore paths compose. Defaults to
|
|
46
|
+
* TERMINAL_ESCAPES; overridable only for tests that assert exact byte output.
|
|
47
|
+
*/
|
|
48
|
+
export interface TerminalSequenceSet {
|
|
49
|
+
readonly ALT_SCREEN_ENTER: string;
|
|
50
|
+
readonly ALT_SCREEN_EXIT: string;
|
|
51
|
+
readonly MOUSE_ENABLE: string;
|
|
52
|
+
readonly MOUSE_DISABLE: string;
|
|
53
|
+
readonly CURSOR_HIDE: string;
|
|
54
|
+
readonly CURSOR_SHOW: string;
|
|
55
|
+
readonly KEYBOARD_EXT_ENABLE: string;
|
|
56
|
+
readonly KEYBOARD_EXT_DISABLE: string;
|
|
57
|
+
readonly PASTE_ENABLE: string;
|
|
58
|
+
readonly PASTE_DISABLE: string;
|
|
59
|
+
readonly FOCUS_ENABLE: string;
|
|
60
|
+
readonly FOCUS_DISABLE: string;
|
|
61
|
+
readonly CLEAR_VIEWPORT_HOME: string;
|
|
62
|
+
}
|
|
63
|
+
export interface TerminalLifecycleDeps {
|
|
64
|
+
/** Raw write to the terminal output stream (typically stdout.write bound). */
|
|
65
|
+
readonly write: (data: string) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Whether the alt screen is skipped. Mirrors the front-end's --no-alt-screen
|
|
68
|
+
* flag: when true, enter never switches to the alt screen and restore clears
|
|
69
|
+
* the primary viewport (without 3J) instead of leaving the alt screen.
|
|
70
|
+
*/
|
|
71
|
+
readonly noAltScreen: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Wrap the restore write so it is not suppressed by an output guard. When a
|
|
74
|
+
* front-end installs a stdout guard, restore must still reach the real
|
|
75
|
+
* terminal. Defaults to calling the write directly.
|
|
76
|
+
*/
|
|
77
|
+
readonly guardedWrite?: (write: () => void) => void;
|
|
78
|
+
/**
|
|
79
|
+
* Dispose the front-end's terminal output guard, if any. Called AFTER the
|
|
80
|
+
* restore write so a crash stack reaches the real stderr instead of being
|
|
81
|
+
* suppressed by the guard. No-op by default.
|
|
82
|
+
*/
|
|
83
|
+
readonly disposeOutputGuard?: () => void;
|
|
84
|
+
/**
|
|
85
|
+
* Set raw mode on the input stream. Called last during restore; wrapped in a
|
|
86
|
+
* try/catch because stdin may not be a TTY. No-op by default.
|
|
87
|
+
*/
|
|
88
|
+
readonly setRawMode?: (enabled: boolean) => void;
|
|
89
|
+
/** Escape sequences; defaults to the shared TERMINAL_ESCAPES. */
|
|
90
|
+
readonly escapes?: TerminalSequenceSet;
|
|
91
|
+
}
|
|
92
|
+
export interface TerminalLifecycle {
|
|
93
|
+
/**
|
|
94
|
+
* Compose and write the enter sequence: alt-screen (unless noAltScreen),
|
|
95
|
+
* clear + home, hide cursor, then enable mouse / keyboard-extension / paste /
|
|
96
|
+
* focus reporting. Idempotent-safe to call once at startup.
|
|
97
|
+
*/
|
|
98
|
+
readonly enterTerminal: () => void;
|
|
99
|
+
/**
|
|
100
|
+
* Idempotent, synchronous-only terminal restore. Safe to call from
|
|
101
|
+
* process 'exit', signal handlers, uncaughtException, and the graceful exit
|
|
102
|
+
* path. Disposes the output guard AFTER the restore write so a crash stack
|
|
103
|
+
* still reaches the real stderr.
|
|
104
|
+
*/
|
|
105
|
+
readonly restoreTerminal: () => void;
|
|
106
|
+
/**
|
|
107
|
+
* True once restoreTerminal has run. The compositor must never write another
|
|
108
|
+
* frame after this: the terminal is back on the user's primary screen, and a
|
|
109
|
+
* late cursor-positioned frame (async shutdown races, stray timers) would
|
|
110
|
+
* paint over shell content and strand the prompt mid-screen. Wire the render
|
|
111
|
+
* scheduler's `isReleased` to this.
|
|
112
|
+
*/
|
|
113
|
+
readonly isTerminalRestored: () => boolean;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build the shared terminal lifecycle around a front-end's injected terminal
|
|
117
|
+
* I/O. Behavior mirrors the reference front-end exactly: enter switches to the
|
|
118
|
+
* alt screen (unless disabled) and enables the input modes; restore leaves the
|
|
119
|
+
* alt screen (or clears the primary viewport without 3J), shows the cursor on
|
|
120
|
+
* the screen the shell prompt lands on, then hands input back.
|
|
121
|
+
*/
|
|
122
|
+
export declare function createTerminalLifecycle(deps: TerminalLifecycleDeps): TerminalLifecycle;
|
|
123
|
+
//# sourceMappingURL=terminal-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-lifecycle.d.ts","sourceRoot":"","sources":["../src/terminal-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;IAa3B;;;;OAIG;;CAEK,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,OAAO,gBAAgB,CAAC;AAEtD;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IACpD;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;CACxC;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,IAAI,CAAC;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,EAAE,MAAM,OAAO,CAAC;CAC5C;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,qBAAqB,GAAG,iBAAiB,CAmDtF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* terminal-lifecycle.ts — shared terminal enter/restore sequencing for GoodVibes
|
|
3
|
+
* daemon front-ends.
|
|
4
|
+
*
|
|
5
|
+
* Both front-ends drive a full-screen terminal UI and must enter and later
|
|
6
|
+
* restore the terminal identically. When these sequences diverge between them,
|
|
7
|
+
* exit teardown leaves the wrong screen active or wipes the user's scrollback —
|
|
8
|
+
* a defect class that recurs whenever the two copies drift. This module is the
|
|
9
|
+
* single home for the escape sequences and the enter/restore ordering, so both
|
|
10
|
+
* front-ends share one implementation.
|
|
11
|
+
*
|
|
12
|
+
* What lives here is only the terminal-state sequencing and the restored-state
|
|
13
|
+
* gate. The graceful application shutdown (draining services, persisting
|
|
14
|
+
* sessions, exit codes) is front-end-specific and stays in each app's own
|
|
15
|
+
* process-lifecycle wiring; that wiring calls `restoreTerminal()` from this
|
|
16
|
+
* module for the synchronous terminal hand-back.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The canonical control sequences the shell writes to enter and leave terminal
|
|
20
|
+
* mode (alt screen, mouse, cursor, keyboard-extension, paste, and focus
|
|
21
|
+
* reporting). Shared verbatim so the two front-ends cannot drift on the bytes.
|
|
22
|
+
*/
|
|
23
|
+
export const TERMINAL_ESCAPES = {
|
|
24
|
+
ALT_SCREEN_ENTER: '\x1b[?1049h',
|
|
25
|
+
ALT_SCREEN_EXIT: '\x1b[?1049l',
|
|
26
|
+
MOUSE_ENABLE: '\x1b[?1000h\x1b[?1002h\x1b[?1006h',
|
|
27
|
+
MOUSE_DISABLE: '\x1b[?1006l\x1b[?1002l\x1b[?1000l',
|
|
28
|
+
CURSOR_HIDE: '\x1b[?25l',
|
|
29
|
+
CURSOR_SHOW: '\x1b[?25h',
|
|
30
|
+
KEYBOARD_EXT_ENABLE: '\x1b[>4;2m\x1b[?1u',
|
|
31
|
+
KEYBOARD_EXT_DISABLE: '\x1b[>4;0m\x1b[?1l',
|
|
32
|
+
PASTE_ENABLE: '\x1b[?2004h',
|
|
33
|
+
PASTE_DISABLE: '\x1b[?2004l',
|
|
34
|
+
FOCUS_ENABLE: '\x1b[?1004h',
|
|
35
|
+
FOCUS_DISABLE: '\x1b[?1004l',
|
|
36
|
+
/**
|
|
37
|
+
* Clear viewport + home the cursor, WITHOUT ESC[3J. The 3J form wipes the
|
|
38
|
+
* user's PRIMARY-screen scrollback on several emulators; the restore path
|
|
39
|
+
* must never use it. Used by the no-alt-screen restore path.
|
|
40
|
+
*/
|
|
41
|
+
CLEAR_VIEWPORT_HOME: '\x1b[2J\x1b[H',
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Build the shared terminal lifecycle around a front-end's injected terminal
|
|
45
|
+
* I/O. Behavior mirrors the reference front-end exactly: enter switches to the
|
|
46
|
+
* alt screen (unless disabled) and enables the input modes; restore leaves the
|
|
47
|
+
* alt screen (or clears the primary viewport without 3J), shows the cursor on
|
|
48
|
+
* the screen the shell prompt lands on, then hands input back.
|
|
49
|
+
*/
|
|
50
|
+
export function createTerminalLifecycle(deps) {
|
|
51
|
+
const escapes = deps.escapes ?? TERMINAL_ESCAPES;
|
|
52
|
+
const guardedWrite = deps.guardedWrite ?? ((write) => write());
|
|
53
|
+
const disposeOutputGuard = deps.disposeOutputGuard ?? (() => { });
|
|
54
|
+
const setRawMode = deps.setRawMode ?? (() => { });
|
|
55
|
+
const enterTerminal = () => {
|
|
56
|
+
guardedWrite(() => deps.write((deps.noAltScreen ? '' : escapes.ALT_SCREEN_ENTER)
|
|
57
|
+
+ escapes.CLEAR_VIEWPORT_HOME
|
|
58
|
+
+ escapes.CURSOR_HIDE
|
|
59
|
+
+ escapes.MOUSE_ENABLE
|
|
60
|
+
+ escapes.KEYBOARD_EXT_ENABLE
|
|
61
|
+
+ escapes.PASTE_ENABLE
|
|
62
|
+
+ escapes.FOCUS_ENABLE));
|
|
63
|
+
};
|
|
64
|
+
let terminalRestored = false;
|
|
65
|
+
const restoreTerminal = () => {
|
|
66
|
+
if (terminalRestored)
|
|
67
|
+
return;
|
|
68
|
+
terminalRestored = true;
|
|
69
|
+
// Alt-screen path: just leave the alt screen — 1049l restores the primary
|
|
70
|
+
// screen and cursor exactly as they were at launch. Clearing first is
|
|
71
|
+
// pointless (the alt screen is discarded) and actively harmful: a clear
|
|
72
|
+
// with ESC[3J wipes the PRIMARY scrollback on several emulators even when
|
|
73
|
+
// issued from the alt screen.
|
|
74
|
+
// No-alt path: the compositor painted over the primary screen, so clear
|
|
75
|
+
// the viewport and home the cursor — but WITHOUT 3J, the user's scrollback
|
|
76
|
+
// is theirs. CURSOR_SHOW goes AFTER the screen switch so visibility
|
|
77
|
+
// applies to the screen the shell prompt lands on.
|
|
78
|
+
const exitScreen = deps.noAltScreen ? escapes.CLEAR_VIEWPORT_HOME : escapes.ALT_SCREEN_EXIT;
|
|
79
|
+
guardedWrite(() => deps.write(escapes.PASTE_DISABLE + escapes.KEYBOARD_EXT_DISABLE + escapes.MOUSE_DISABLE + escapes.FOCUS_DISABLE
|
|
80
|
+
+ exitScreen + escapes.CURSOR_SHOW));
|
|
81
|
+
// Dispose the guard AFTER the restore write so a crash stack reaches the
|
|
82
|
+
// real stderr instead of being suppressed by the guard.
|
|
83
|
+
disposeOutputGuard();
|
|
84
|
+
try {
|
|
85
|
+
setRawMode(false);
|
|
86
|
+
}
|
|
87
|
+
catch { /* input stream may not be a TTY */ }
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
enterTerminal,
|
|
91
|
+
restoreTerminal,
|
|
92
|
+
isTerminalRestored: () => terminalRestored,
|
|
93
|
+
};
|
|
94
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pellux/goodvibes-terminal-shell",
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"engines": {
|
|
5
|
+
"bun": "1.3.10",
|
|
6
|
+
"node": ">=22.0.0"
|
|
7
|
+
},
|
|
8
|
+
"description": "Shared terminal-shell plumbing for GoodVibes daemon front-ends: gateway verb-group composition, terminal enter/restore sequencing, render-tick coalescing, and a descriptor/handler conformance gate.",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./conformance": {
|
|
18
|
+
"types": "./dist/conformance.d.ts",
|
|
19
|
+
"import": "./dist/conformance.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/mgd34msu/goodvibes-sdk.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/mgd34msu/goodvibes-sdk/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/mgd34msu/goodvibes-sdk",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"goodvibes",
|
|
38
|
+
"sdk",
|
|
39
|
+
"terminal",
|
|
40
|
+
"daemon",
|
|
41
|
+
"gateway"
|
|
42
|
+
],
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@pellux/goodvibes-contracts": "1.7.0",
|
|
45
|
+
"@pellux/goodvibes-sdk": "1.7.0"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
}
|
|
50
|
+
}
|