@nforma.ai/nforma 0.2.1
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/LICENSE +22 -0
- package/README.md +1024 -0
- package/agents/qgsd-codebase-mapper.md +764 -0
- package/agents/qgsd-debugger.md +1201 -0
- package/agents/qgsd-executor.md +472 -0
- package/agents/qgsd-integration-checker.md +443 -0
- package/agents/qgsd-phase-researcher.md +502 -0
- package/agents/qgsd-plan-checker.md +643 -0
- package/agents/qgsd-planner.md +1182 -0
- package/agents/qgsd-project-researcher.md +621 -0
- package/agents/qgsd-quorum-orchestrator.md +628 -0
- package/agents/qgsd-quorum-slot-worker.md +41 -0
- package/agents/qgsd-quorum-synthesizer.md +133 -0
- package/agents/qgsd-quorum-test-worker.md +37 -0
- package/agents/qgsd-quorum-worker.md +161 -0
- package/agents/qgsd-research-synthesizer.md +239 -0
- package/agents/qgsd-roadmapper.md +660 -0
- package/agents/qgsd-verifier.md +628 -0
- package/bin/accept-debug-invariant.cjs +165 -0
- package/bin/account-manager.cjs +719 -0
- package/bin/aggregate-requirements.cjs +466 -0
- package/bin/analyze-assumptions.cjs +757 -0
- package/bin/analyze-state-space.cjs +921 -0
- package/bin/attribute-trace-divergence.cjs +150 -0
- package/bin/auth-drivers/gh-cli.cjs +93 -0
- package/bin/auth-drivers/index.cjs +46 -0
- package/bin/auth-drivers/pool.cjs +67 -0
- package/bin/auth-drivers/simple.cjs +95 -0
- package/bin/autoClosePtoF.cjs +110 -0
- package/bin/blessed-terminal.cjs +350 -0
- package/bin/build-phase-index.cjs +472 -0
- package/bin/call-quorum-slot.cjs +541 -0
- package/bin/ccr-secure-config.cjs +99 -0
- package/bin/ccr-secure-start.cjs +83 -0
- package/bin/check-bundled-sdks.cjs +177 -0
- package/bin/check-coverage-guard.cjs +112 -0
- package/bin/check-liveness-fairness.cjs +95 -0
- package/bin/check-mcp-health.cjs +123 -0
- package/bin/check-provider-health.cjs +395 -0
- package/bin/check-results-exit.cjs +24 -0
- package/bin/check-spec-sync.cjs +360 -0
- package/bin/check-trace-redaction.cjs +271 -0
- package/bin/check-trace-schema-drift.cjs +99 -0
- package/bin/compareDrift.cjs +21 -0
- package/bin/conformance-schema.cjs +12 -0
- package/bin/count-scenarios.cjs +420 -0
- package/bin/debt-dedup.cjs +144 -0
- package/bin/debt-ledger.cjs +61 -0
- package/bin/debt-retention.cjs +76 -0
- package/bin/debt-state-machine.cjs +80 -0
- package/bin/detect-coverage-gaps.cjs +204 -0
- package/bin/detect-project-intent.cjs +362 -0
- package/bin/export-prism-constants.cjs +164 -0
- package/bin/extract-annotations.cjs +633 -0
- package/bin/extractFormalExpected.cjs +104 -0
- package/bin/fingerprint-drift.cjs +24 -0
- package/bin/fingerprint-issue.cjs +46 -0
- package/bin/formal-core.cjs +519 -0
- package/bin/formal-ref-linker.cjs +141 -0
- package/bin/formal-test-sync.cjs +788 -0
- package/bin/generate-formal-specs.cjs +588 -0
- package/bin/generate-petri-net.cjs +397 -0
- package/bin/generate-phase-spec.cjs +249 -0
- package/bin/generate-proposed-changes.cjs +194 -0
- package/bin/generate-tla-cfg.cjs +122 -0
- package/bin/generate-traceability-matrix.cjs +701 -0
- package/bin/generate-triage-bundle.cjs +300 -0
- package/bin/gh-account-rotate.cjs +34 -0
- package/bin/initialize-model-registry.cjs +105 -0
- package/bin/install-formal-tools.cjs +382 -0
- package/bin/install.js +2424 -0
- package/bin/isNumericThreshold.cjs +34 -0
- package/bin/issue-classifier.cjs +151 -0
- package/bin/levenshtein.cjs +74 -0
- package/bin/lint-formal-models.cjs +580 -0
- package/bin/load-baseline-requirements.cjs +275 -0
- package/bin/manage-agents-core.cjs +815 -0
- package/bin/migrate-formal-dir.cjs +172 -0
- package/bin/migrate-planning.cjs +206 -0
- package/bin/migrate-to-slots.cjs +255 -0
- package/bin/nForma.cjs +2726 -0
- package/bin/observe-config.cjs +353 -0
- package/bin/observe-debt-writer.cjs +140 -0
- package/bin/observe-handler-grafana.cjs +128 -0
- package/bin/observe-handler-internal.cjs +301 -0
- package/bin/observe-handler-logstash.cjs +153 -0
- package/bin/observe-handler-prometheus.cjs +185 -0
- package/bin/observe-handlers.cjs +436 -0
- package/bin/observe-registry.cjs +131 -0
- package/bin/observe-render.cjs +168 -0
- package/bin/planning-paths.cjs +167 -0
- package/bin/polyrepo.cjs +560 -0
- package/bin/prism-priority.cjs +153 -0
- package/bin/probe-quorum-slots.cjs +167 -0
- package/bin/promote-model.cjs +225 -0
- package/bin/propose-debug-invariants.cjs +165 -0
- package/bin/providers.json +392 -0
- package/bin/pty-proxy.py +129 -0
- package/bin/qgsd-solve.cjs +2477 -0
- package/bin/quorum-consensus-gate.cjs +238 -0
- package/bin/quorum-formal-context.cjs +183 -0
- package/bin/quorum-slot-dispatch.cjs +934 -0
- package/bin/read-policy.cjs +60 -0
- package/bin/requirement-map.cjs +63 -0
- package/bin/requirements-core.cjs +247 -0
- package/bin/resolve-cli.cjs +101 -0
- package/bin/review-mcp-logs.cjs +294 -0
- package/bin/run-account-manager-tlc.cjs +188 -0
- package/bin/run-account-pool-alloy.cjs +158 -0
- package/bin/run-alloy.cjs +153 -0
- package/bin/run-audit-alloy.cjs +187 -0
- package/bin/run-breaker-tlc.cjs +181 -0
- package/bin/run-formal-check.cjs +395 -0
- package/bin/run-formal-verify.cjs +701 -0
- package/bin/run-installer-alloy.cjs +188 -0
- package/bin/run-oauth-rotation-prism.cjs +132 -0
- package/bin/run-oscillation-tlc.cjs +202 -0
- package/bin/run-phase-tlc.cjs +228 -0
- package/bin/run-prism.cjs +446 -0
- package/bin/run-protocol-tlc.cjs +201 -0
- package/bin/run-quorum-composition-alloy.cjs +155 -0
- package/bin/run-sensitivity-sweep.cjs +231 -0
- package/bin/run-stop-hook-tlc.cjs +188 -0
- package/bin/run-tlc.cjs +467 -0
- package/bin/run-transcript-alloy.cjs +173 -0
- package/bin/run-uppaal.cjs +264 -0
- package/bin/secrets.cjs +134 -0
- package/bin/sensitivity-report.cjs +219 -0
- package/bin/sensitivity-sweep-feedback.cjs +194 -0
- package/bin/set-secret.cjs +29 -0
- package/bin/setup-telemetry-cron.sh +36 -0
- package/bin/sweepPtoF.cjs +63 -0
- package/bin/sync-baseline-requirements.cjs +290 -0
- package/bin/task-envelope.cjs +360 -0
- package/bin/telemetry-collector.cjs +229 -0
- package/bin/unified-mcp-server.mjs +735 -0
- package/bin/update-agents.cjs +369 -0
- package/bin/update-scoreboard.cjs +1134 -0
- package/bin/validate-debt-entry.cjs +207 -0
- package/bin/validate-invariant.cjs +419 -0
- package/bin/validate-memory.cjs +389 -0
- package/bin/validate-requirements-haiku.cjs +435 -0
- package/bin/validate-traces.cjs +438 -0
- package/bin/verify-formal-results.cjs +124 -0
- package/bin/verify-quorum-health.cjs +273 -0
- package/bin/write-check-result.cjs +106 -0
- package/bin/xstate-to-tla.cjs +483 -0
- package/bin/xstate-trace-walker.cjs +205 -0
- package/commands/qgsd/add-phase.md +43 -0
- package/commands/qgsd/add-requirement.md +24 -0
- package/commands/qgsd/add-todo.md +47 -0
- package/commands/qgsd/audit-milestone.md +37 -0
- package/commands/qgsd/check-todos.md +45 -0
- package/commands/qgsd/cleanup.md +18 -0
- package/commands/qgsd/close-formal-gaps.md +33 -0
- package/commands/qgsd/complete-milestone.md +136 -0
- package/commands/qgsd/debug.md +166 -0
- package/commands/qgsd/discuss-phase.md +83 -0
- package/commands/qgsd/execute-phase.md +117 -0
- package/commands/qgsd/fix-tests.md +27 -0
- package/commands/qgsd/formal-test-sync.md +32 -0
- package/commands/qgsd/health.md +22 -0
- package/commands/qgsd/help.md +22 -0
- package/commands/qgsd/insert-phase.md +32 -0
- package/commands/qgsd/join-discord.md +18 -0
- package/commands/qgsd/list-phase-assumptions.md +46 -0
- package/commands/qgsd/map-codebase.md +71 -0
- package/commands/qgsd/map-requirements.md +20 -0
- package/commands/qgsd/mcp-restart.md +176 -0
- package/commands/qgsd/mcp-set-model.md +134 -0
- package/commands/qgsd/mcp-setup.md +1371 -0
- package/commands/qgsd/mcp-status.md +274 -0
- package/commands/qgsd/mcp-update.md +238 -0
- package/commands/qgsd/new-milestone.md +44 -0
- package/commands/qgsd/new-project.md +42 -0
- package/commands/qgsd/observe.md +260 -0
- package/commands/qgsd/pause-work.md +38 -0
- package/commands/qgsd/plan-milestone-gaps.md +34 -0
- package/commands/qgsd/plan-phase.md +44 -0
- package/commands/qgsd/polyrepo.md +50 -0
- package/commands/qgsd/progress.md +24 -0
- package/commands/qgsd/queue.md +54 -0
- package/commands/qgsd/quick.md +133 -0
- package/commands/qgsd/quorum-test.md +275 -0
- package/commands/qgsd/quorum.md +707 -0
- package/commands/qgsd/reapply-patches.md +110 -0
- package/commands/qgsd/remove-phase.md +31 -0
- package/commands/qgsd/research-phase.md +189 -0
- package/commands/qgsd/resume-work.md +40 -0
- package/commands/qgsd/set-profile.md +34 -0
- package/commands/qgsd/settings.md +39 -0
- package/commands/qgsd/solve.md +565 -0
- package/commands/qgsd/sync-baselines.md +119 -0
- package/commands/qgsd/triage.md +233 -0
- package/commands/qgsd/update.md +37 -0
- package/commands/qgsd/verify-work.md +38 -0
- package/hooks/dist/config-loader.js +297 -0
- package/hooks/dist/conformance-schema.cjs +12 -0
- package/hooks/dist/gsd-context-monitor.js +64 -0
- package/hooks/dist/qgsd-check-update.js +62 -0
- package/hooks/dist/qgsd-circuit-breaker.js +682 -0
- package/hooks/dist/qgsd-precompact.js +156 -0
- package/hooks/dist/qgsd-prompt.js +653 -0
- package/hooks/dist/qgsd-session-start.js +122 -0
- package/hooks/dist/qgsd-slot-correlator.js +58 -0
- package/hooks/dist/qgsd-spec-regen.js +86 -0
- package/hooks/dist/qgsd-statusline.js +91 -0
- package/hooks/dist/qgsd-stop.js +553 -0
- package/hooks/dist/qgsd-token-collector.js +133 -0
- package/hooks/dist/unified-mcp-server.mjs +669 -0
- package/package.json +95 -0
- package/scripts/build-hooks.js +46 -0
- package/scripts/postinstall.js +48 -0
- package/scripts/secret-audit.sh +45 -0
- package/templates/qgsd.json +49 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* blessed-terminal.cjs
|
|
4
|
+
*
|
|
5
|
+
* Drop-in replacement for blessed-xterm's XTerm widget.
|
|
6
|
+
* Uses @xterm/headless (pure JS, no native addons) + child_process.spawn
|
|
7
|
+
* instead of node-pty (native C++ addon).
|
|
8
|
+
*
|
|
9
|
+
* API surface used by nforma.cjs:
|
|
10
|
+
* new BlessedTerminal({ shell, args, cwd, cursorType, scrollback,
|
|
11
|
+
* ignoreKeys, top, left, right, bottom,
|
|
12
|
+
* border, style, label, tags })
|
|
13
|
+
* term.show() / term.hide() / term.focus() — inherited from blessed.Box
|
|
14
|
+
* term.terminate() — kill child process
|
|
15
|
+
* term.on('exit', (code, signal) => ...) — child process exited
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const blessed = require('blessed');
|
|
19
|
+
const { spawn } = require('child_process');
|
|
20
|
+
const { Terminal } = require('@xterm/headless');
|
|
21
|
+
|
|
22
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Convert an xterm.js CellData into a blessed sattr integer.
|
|
26
|
+
* blessed sattr format: (flags << 18) | (fg << 9) | bg
|
|
27
|
+
* flags bits: 0=bold, 1=underline, 2=blink, 3=inverse
|
|
28
|
+
* fg/bg: 0-255 palette, 256=default bg, 257=default fg
|
|
29
|
+
*/
|
|
30
|
+
function cellToSattr(cell) {
|
|
31
|
+
// Flags
|
|
32
|
+
let flags = 0;
|
|
33
|
+
if (cell.isBold()) flags |= 1;
|
|
34
|
+
if (cell.isUnderline()) flags |= 2;
|
|
35
|
+
if (cell.isBlink()) flags |= 4;
|
|
36
|
+
if (cell.isInverse()) flags |= 8;
|
|
37
|
+
|
|
38
|
+
// Foreground color
|
|
39
|
+
let fg;
|
|
40
|
+
if (cell.isFgDefault()) {
|
|
41
|
+
fg = 257; // blessed default fg sentinel
|
|
42
|
+
} else if (cell.isFgPalette()) {
|
|
43
|
+
fg = cell.getFgColor() & 0xff;
|
|
44
|
+
} else if (cell.isFgRGB()) {
|
|
45
|
+
// RGB — map to nearest 256-color index
|
|
46
|
+
const raw = cell.getFgColor(); // 0xRRGGBB packed as int
|
|
47
|
+
fg = rgbToAnsi256((raw >> 16) & 0xff, (raw >> 8) & 0xff, raw & 0xff);
|
|
48
|
+
} else {
|
|
49
|
+
fg = 257;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Background color
|
|
53
|
+
let bg;
|
|
54
|
+
if (cell.isBgDefault()) {
|
|
55
|
+
bg = 256; // blessed default bg sentinel
|
|
56
|
+
} else if (cell.isBgPalette()) {
|
|
57
|
+
bg = cell.getBgColor() & 0xff;
|
|
58
|
+
} else if (cell.isBgRGB()) {
|
|
59
|
+
const raw = cell.getBgColor();
|
|
60
|
+
bg = rgbToAnsi256((raw >> 16) & 0xff, (raw >> 8) & 0xff, raw & 0xff);
|
|
61
|
+
} else {
|
|
62
|
+
bg = 256;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (flags << 18) | (fg << 9) | bg;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Map RGB values to nearest xterm-256 palette index.
|
|
70
|
+
* Uses the standard 6x6x6 cube (indices 16-231) and grayscale ramp (232-255).
|
|
71
|
+
*/
|
|
72
|
+
function rgbToAnsi256(r, g, b) {
|
|
73
|
+
// Check grayscale ramp first (232-255): 24 steps from rgb(8,8,8) to rgb(238,238,238)
|
|
74
|
+
if (r === g && g === b) {
|
|
75
|
+
if (r < 8) return 16; // black
|
|
76
|
+
if (r > 248) return 231; // white
|
|
77
|
+
return Math.round((r - 8) / 247 * 24) + 232;
|
|
78
|
+
}
|
|
79
|
+
// 6x6x6 cube (indices 16-231)
|
|
80
|
+
const ri = Math.round(r / 255 * 5);
|
|
81
|
+
const gi = Math.round(g / 255 * 5);
|
|
82
|
+
const bi = Math.round(b / 255 * 5);
|
|
83
|
+
return 16 + (36 * ri) + (6 * gi) + bi;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── BlessedTerminal ─────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
class BlessedTerminal extends blessed.Box {
|
|
89
|
+
constructor(options) {
|
|
90
|
+
// Separate terminal-specific options from blessed Box options
|
|
91
|
+
const {
|
|
92
|
+
shell = process.env.SHELL || '/bin/sh',
|
|
93
|
+
args = [],
|
|
94
|
+
cwd = process.cwd(),
|
|
95
|
+
cursorType, // ignored — blessed has no cursor API
|
|
96
|
+
scrollback = 1000,
|
|
97
|
+
ignoreKeys = [],
|
|
98
|
+
...boxOptions
|
|
99
|
+
} = options || {};
|
|
100
|
+
|
|
101
|
+
// Defaults for blessed Box
|
|
102
|
+
boxOptions.scrollable = false;
|
|
103
|
+
super(boxOptions);
|
|
104
|
+
|
|
105
|
+
this._shellCmd = shell;
|
|
106
|
+
this._shellArgs = args;
|
|
107
|
+
this._shellCwd = cwd;
|
|
108
|
+
this._scrollback = scrollback;
|
|
109
|
+
this._ignoreKeys = ignoreKeys;
|
|
110
|
+
|
|
111
|
+
// Deferred: we initialize _term and _child lazily on first render or
|
|
112
|
+
// when the widget is attached to a screen (whichever comes first).
|
|
113
|
+
this._term = null;
|
|
114
|
+
this._child = null;
|
|
115
|
+
this._ready = false;
|
|
116
|
+
this._destroyed = false;
|
|
117
|
+
|
|
118
|
+
// Input routing state (mirrors blessed-xterm skipInputDataOnce pattern)
|
|
119
|
+
this._skipInputDataOnce = false;
|
|
120
|
+
this._skipInputDataAlways = false;
|
|
121
|
+
|
|
122
|
+
// Listeners we need to remove on destroy
|
|
123
|
+
this._inputDataListener = null;
|
|
124
|
+
this._keypressListener = null;
|
|
125
|
+
this._resizeListener = null;
|
|
126
|
+
|
|
127
|
+
// Start as soon as we're attached to a screen
|
|
128
|
+
this.on('attach', () => this._init());
|
|
129
|
+
this.on('destroy', () => this._cleanup());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ─── Initialise xterm + child process ───────────────────────────────────
|
|
133
|
+
|
|
134
|
+
_init() {
|
|
135
|
+
if (this._ready || this._destroyed) return;
|
|
136
|
+
this._ready = true;
|
|
137
|
+
|
|
138
|
+
const cols = Math.max(this.width - (this.ileft + this.iright), 1);
|
|
139
|
+
const rows = Math.max(this.height - (this.itop + this.ibottom), 1);
|
|
140
|
+
|
|
141
|
+
// @xterm/headless terminal — parses VT100/ANSI and maintains buffer
|
|
142
|
+
this._term = new Terminal({
|
|
143
|
+
cols,
|
|
144
|
+
rows,
|
|
145
|
+
scrollback: this._scrollback,
|
|
146
|
+
allowProposedApi: true,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// child_process.spawn — pure JS, no native addons
|
|
150
|
+
const env = Object.assign({}, process.env, {
|
|
151
|
+
TERM: 'xterm-256color',
|
|
152
|
+
COLORTERM: 'truecolor',
|
|
153
|
+
FORCE_COLOR: '3',
|
|
154
|
+
COLUMNS: String(cols),
|
|
155
|
+
LINES: String(rows),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
this._child = spawn(this._shellCmd, this._shellArgs, {
|
|
159
|
+
cwd: this._shellCwd,
|
|
160
|
+
env,
|
|
161
|
+
stdio: 'pipe',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Pipe child stdout/stderr → xterm parser
|
|
165
|
+
this._child.stdout.on('data', (data) => {
|
|
166
|
+
if (!this._destroyed) this._term.write(data.toString());
|
|
167
|
+
if (this.screen) this.screen.render();
|
|
168
|
+
});
|
|
169
|
+
this._child.stderr.on('data', (data) => {
|
|
170
|
+
if (!this._destroyed) this._term.write(data.toString());
|
|
171
|
+
if (this.screen) this.screen.render();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Child exit → emit 'exit' on widget
|
|
175
|
+
this._child.on('exit', (code, signal) => {
|
|
176
|
+
if (!this._destroyed) this.emit('exit', code, signal);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Handle spawn errors gracefully
|
|
180
|
+
this._child.on('error', (err) => {
|
|
181
|
+
if (!this._destroyed) {
|
|
182
|
+
this._term.write(`\r\nFailed to start: ${err.message}\r\n`);
|
|
183
|
+
if (this.screen) this.screen.render();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Wire up keyboard input routing
|
|
188
|
+
this._wireInput();
|
|
189
|
+
|
|
190
|
+
// Resize handler
|
|
191
|
+
this._resizeListener = () => this._handleResize();
|
|
192
|
+
this.screen.on('resize', this._resizeListener);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ─── Input routing ──────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
_wireInput() {
|
|
198
|
+
if (!this.screen) return;
|
|
199
|
+
|
|
200
|
+
// Raw input data → child stdin (when this widget is focused)
|
|
201
|
+
this._inputDataListener = (data) => {
|
|
202
|
+
if (this.screen.focused !== this) return;
|
|
203
|
+
if (this._skipInputDataAlways) return;
|
|
204
|
+
if (this._skipInputDataOnce) { this._skipInputDataOnce = false; return; }
|
|
205
|
+
if (this._child && this._child.stdin && !this._child.stdin.destroyed) {
|
|
206
|
+
try { this._child.stdin.write(data); } catch (_) {}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
this.screen.program.input.on('data', this._inputDataListener);
|
|
210
|
+
|
|
211
|
+
// Keypress → implement ignoreKeys (skip next raw data event for ignored keys)
|
|
212
|
+
this._keypressListener = (ch, key) => {
|
|
213
|
+
if (this.screen.focused !== this) return;
|
|
214
|
+
if (!key) return;
|
|
215
|
+
if (this._ignoreKeys.indexOf(key.full) >= 0) {
|
|
216
|
+
this._skipInputDataOnce = true;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
this.screen.on('keypress', this._keypressListener);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── Resize handler ─────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
_handleResize() {
|
|
225
|
+
if (!this._term || this._destroyed) return;
|
|
226
|
+
const cols = Math.max(this.width - (this.ileft + this.iright), 1);
|
|
227
|
+
const rows = Math.max(this.height - (this.itop + this.ibottom), 1);
|
|
228
|
+
try { this._term.resize(cols, rows); } catch (_) {}
|
|
229
|
+
// Note: with piped stdio there's no SIGWINCH to send, so the child
|
|
230
|
+
// process may not reflow. This is acceptable for Claude CLI output.
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ─── Render override — bridge xterm buffer → blessed screen lines ────────
|
|
234
|
+
|
|
235
|
+
render() {
|
|
236
|
+
const ret = this._render();
|
|
237
|
+
if (!ret) return;
|
|
238
|
+
|
|
239
|
+
if (!this._term || this._destroyed) return ret;
|
|
240
|
+
|
|
241
|
+
const xi = ret.xi + this.ileft;
|
|
242
|
+
const xl = ret.xl - this.iright;
|
|
243
|
+
const yi = ret.yi + this.itop;
|
|
244
|
+
const yl = ret.yl - this.ibottom;
|
|
245
|
+
|
|
246
|
+
const buf = this._term.buffer.active;
|
|
247
|
+
const cols = this._term.cols;
|
|
248
|
+
const rows = this._term.rows;
|
|
249
|
+
|
|
250
|
+
for (let y = Math.max(yi, 0); y < yl; y++) {
|
|
251
|
+
const screenY = y;
|
|
252
|
+
const termY = buf.viewportY + (y - yi);
|
|
253
|
+
|
|
254
|
+
if (termY < 0 || termY >= rows + buf.viewportY) continue;
|
|
255
|
+
|
|
256
|
+
const sline = this.screen.lines[screenY];
|
|
257
|
+
if (!sline) continue;
|
|
258
|
+
|
|
259
|
+
const tline = buf.getLine(termY);
|
|
260
|
+
if (!tline) {
|
|
261
|
+
// Blank this screen line
|
|
262
|
+
for (let x = Math.max(xi, 0); x < xl; x++) {
|
|
263
|
+
if (!sline[x]) continue;
|
|
264
|
+
sline[x][0] = 0x20200; // default attrs (fg=257, bg=256 in blessed)
|
|
265
|
+
sline[x][1] = ' ';
|
|
266
|
+
}
|
|
267
|
+
sline.dirty = true;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let dirty = false;
|
|
272
|
+
for (let x = Math.max(xi, 0); x < xl; x++) {
|
|
273
|
+
const screenX = x;
|
|
274
|
+
const termX = x - xi;
|
|
275
|
+
|
|
276
|
+
if (termX >= cols) {
|
|
277
|
+
if (sline[screenX]) { sline[screenX][0] = 0x20200; sline[screenX][1] = ' '; dirty = true; }
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!sline[screenX]) continue;
|
|
282
|
+
|
|
283
|
+
const cell = tline.getCell(termX);
|
|
284
|
+
if (!cell) {
|
|
285
|
+
sline[screenX][0] = 0x20200;
|
|
286
|
+
sline[screenX][1] = ' ';
|
|
287
|
+
dirty = true;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const ch = cell.getChars() || ' ';
|
|
292
|
+
const sattr = cellToSattr(cell);
|
|
293
|
+
|
|
294
|
+
if (sline[screenX][0] !== sattr || sline[screenX][1] !== ch) {
|
|
295
|
+
sline[screenX][0] = sattr;
|
|
296
|
+
sline[screenX][1] = ch;
|
|
297
|
+
dirty = true;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (dirty) sline.dirty = true;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return ret;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ─── Public API ─────────────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* terminate() — kill child process gracefully, then force-kill after 2s.
|
|
310
|
+
*/
|
|
311
|
+
terminate() {
|
|
312
|
+
if (!this._child) return;
|
|
313
|
+
const child = this._child;
|
|
314
|
+
try { child.kill('SIGTERM'); } catch (_) {}
|
|
315
|
+
const timer = setTimeout(() => {
|
|
316
|
+
try { child.kill('SIGKILL'); } catch (_) {}
|
|
317
|
+
}, 2000);
|
|
318
|
+
if (timer.unref) timer.unref(); // don't keep process alive
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ─── Cleanup ────────────────────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
_cleanup() {
|
|
324
|
+
if (this._destroyed) return;
|
|
325
|
+
this._destroyed = true;
|
|
326
|
+
|
|
327
|
+
// Remove listeners
|
|
328
|
+
if (this._inputDataListener && this.screen && this.screen.program && this.screen.program.input) {
|
|
329
|
+
try { this.screen.program.input.removeListener('data', this._inputDataListener); } catch (_) {}
|
|
330
|
+
}
|
|
331
|
+
if (this._keypressListener && this.screen) {
|
|
332
|
+
try { this.screen.removeListener('keypress', this._keypressListener); } catch (_) {}
|
|
333
|
+
}
|
|
334
|
+
if (this._resizeListener && this.screen) {
|
|
335
|
+
try { this.screen.removeListener('resize', this._resizeListener); } catch (_) {}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Kill child process
|
|
339
|
+
try { this.terminate(); } catch (_) {}
|
|
340
|
+
this._child = null;
|
|
341
|
+
|
|
342
|
+
// Dispose xterm terminal
|
|
343
|
+
if (this._term) {
|
|
344
|
+
try { this._term.dispose(); } catch (_) {}
|
|
345
|
+
this._term = null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
module.exports = BlessedTerminal;
|