@brandry/claude-jsonl-compressor 1.0.0-rc.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/SKILL.md ADDED
@@ -0,0 +1,340 @@
1
+ ---
2
+ name: claude-jsonl-compressor
3
+ description: Compress one Claude Code JSONL session with strict active-branch isolation, model-authored semantic summaries by default, recent raw context for rewind, validated compact-style output, transactional backup/replacement for one live .claude/projects file, and an independent byte-preserving Read.pages compatibility repair. Use for compressing, shrinking, summarizing, preflighting, replacing, or repairing Claude CLI/Claude Code JSONL transcripts.
4
+ ---
5
+
6
+ # Claude JSONL Compressor
7
+
8
+ Operate on exactly one authoritative JSONL. Never merge another branch or session automatically.
9
+
10
+ Public package: `1.0.0-rc.1`. Internal engine: `v10`. Model-pack schema: `v11`.
11
+
12
+ ## Safety Invariants
13
+
14
+ 1. Model-assisted semantic summary generation is the default. Use deterministic summary only when the user explicitly requests fallback.
15
+ 2. Determine the active branch from structure before reading text for semantic importance.
16
+ 3. The physically last `type: "last-prompt"` record is the automatic authority. Do not skip a malformed latest pointer to revive an older one.
17
+ 4. Rewound/inactive branch text must not enter the model pack, model summary, deterministic appendix, prior-summary verbatim block, recent raw records, side records, or output API-message chain.
18
+ 5. Only the active-chain old segment may be summarized. The model cannot select a leaf, change the partition, or restore excluded records.
19
+ 6. Preserve the recent active suffix byte-for-field except for the single parent edge that connects its first record to the new compact summary and optional explicit `sessionId` normalization.
20
+ 7. Preserve unknown fields on retained records. Project the authoritative `last-prompt` object and retain its unknown fields; output exactly one final pointer.
21
+ 8. Never install `tiktoken`, `regex`, PyYAML, or another package for this workflow. Runtime code uses the Python standard library.
22
+ 9. Never write process files inside `.claude`. A live replacement may leave only the requested JSONL and its numbered backup beside it.
23
+ 10. Do not run Claude CLI for validation unless the user explicitly asks. Structural validation is mandatory regardless.
24
+ 11. Do not put project-specific facts, paths, identifiers, or prior-session content into this skill.
25
+ 12. Treat the model summary as model-authored and source-anchored, not model-validated truth. Require v11 request binding and one exact source excerpt per mandatory semantic/prior-summary record.
26
+
27
+ ## Resolve The Operation First
28
+
29
+ ### Candidate Mode
30
+
31
+ Use when the user provides distinct input and output paths.
32
+
33
+ - Do not modify the input.
34
+ - Write the candidate to the requested output.
35
+ - Write `<output>.report.md` and `<output>.validation.json` beside it.
36
+ - Refuse identical input/output paths.
37
+ - Refuse a direct output under `.claude/projects`; use live replacement mode there.
38
+
39
+ ### Live Replacement Mode
40
+
41
+ Use when the user explicitly asks to compress one existing `.claude/projects/<project>/<session>.jsonl` in place, including prompts that give the same path as input and output.
42
+
43
+ - Treat the path as `--input`; do not pass `--output`.
44
+ - Require an existing regular `.jsonl` target under `.claude/projects`; use `--replace-original`, `--confirm-session-closed`, and a work directory outside the entire `.claude` tree. The flag records caller acknowledgement; it does not detect a process lock.
45
+ - The script creates `<session>.jsonl.backup`, then `.backup1`, `.backup2`, and so on with exclusive creation.
46
+ - Candidate, model pack, model summary, validation and report files stay under `--work-dir`.
47
+ - The filename stem remains the target session ID unless the user explicitly supplies another one.
48
+ - Replacement occurs only after candidate validation and a full-byte SHA-256 source recheck.
49
+ - A failed post-replacement validation restores the original bytes and returns an error.
50
+
51
+ ### Read.pages Compatibility Repair
52
+
53
+ This is a separate operation. Do not invoke it implicitly during compression.
54
+
55
+ - It removes only the exact `pages` member from structured assistant `tool_use` blocks whose name is exactly `Read`, whose `input.file_path` exists, and whose tool ID has a matching `tool_result` in the selected scope.
56
+ - Default scope is the strict active chain. `--scope all` is explicit.
57
+ - Pending calls are reported and left unchanged.
58
+ - All bytes outside the planned JSON-member deletion spans remain identical, including BOM, CRLF/LF, escaping, Unicode and unknown fields.
59
+
60
+ ## Mandatory Preflight
61
+
62
+ Resolve the skill root without hardcoding a user's machine:
63
+
64
+ ```powershell
65
+ $skill = "$env:USERPROFILE\.codex\skills\claude-jsonl-compressor"
66
+ ```
67
+
68
+ Analyze the authoritative resume path before making a model pack or candidate:
69
+
70
+ ```powershell
71
+ python "$skill\scripts\compress_claude_jsonl.py" `
72
+ --input "C:\path\session.jsonl" `
73
+ --analyze-resume-path
74
+ ```
75
+
76
+ Strict active mode stops before writing any pack, candidate, sidecar or backup when the authority is absent, malformed, dangling, cyclic, has unsafe/recurring session lineage, has ordinary-message physical parent inversion, contains a malformed `parentUuid`, or is UUID-ambiguous.
77
+
78
+ After partitioning, run the shared validator on the authoritative logical active chain plus its projected pointer. Old malformed tool exchanges, duplicate tool IDs, or compact-pair metadata on that chain must stop before semantic evidence generation; damage confined to excluded inactive branches remains excluded and does not become summary text.
79
+
80
+ When strict preflight reports an unusual or ambiguous topology, stop with zero writes and explain the structural status without copying excluded transcript text. Ask the user to confirm a specific recovery control only when one is applicable. Do not infer confirmation from the original compression request, and never retry automatically in compatibility mode. The Python CLI remains non-interactive.
81
+
82
+ Recovery controls:
83
+
84
+ - Use `--resume-leaf UUID` only when the user explicitly identifies the desired leaf. The report marks `manualOverride: true` and labels the mode `active-chain-manual-override`, not default strict `active-chain`.
85
+ - Use `--preserve-physical-tail` only when the user explicitly requests compatibility behavior. It does not provide inactive-branch exclusion guarantees.
86
+ - Default post-pointer extension is zero. Use `--max-post-last-prompt-extension N` only for a physically post-pointer, direct, same-session, tool-result-only closure of every pending tool ID. Ordinary user/assistant conversation, system/hook records, partial closure and unrelated results are rejected.
87
+
88
+ Observed-format compatibility remains narrow and deterministic:
89
+
90
+ - An acyclic parent chain may contain physically inverted edges only when every such edge is same-session `attachment -> attachment`. Output serializes those records in logical parent order.
91
+ - A mixed-session active chain is accepted only when session runs move forward without returning to an earlier session and both the final leaf and authoritative pointer use the final session. Every earlier session is forced into `summaryIndexes`; the recent raw suffix contains only the final session.
92
+ - If tool pairing would move the raw cut back across that session transition, stop. Do not normalize or invent a cross-session raw exchange.
93
+
94
+ ## Default Model-Assisted Workflow
95
+
96
+ The Python program does not call a model. Codex performs the semantic step between two deterministic script passes.
97
+
98
+ This addresses the 1M-versus-200k context problem by removing inactive branches, recent raw records, low-value payload repetition and non-semantic structure before model review. The bounded pack contains a complete full-text ledger of every non-empty older active human message and every older active assistant `text`/`thinking` message, plus selected source/tool evidence and line anchors. A source-text warning such as U+FFFD is reported but does not discard the rest of a mandatory record. The default pack ceilings are 500,000 characters and a conservative 150,000-token local estimate, leaving room in a typical 200k summarizer context. If mandatory evidence does not fit, generation stops instead of sampling it away; raise a ceiling only when the chosen model can read the result.
99
+
100
+ ### Pass 1: Generate The Evidence Pack
101
+
102
+ Use identical selection settings in both passes:
103
+
104
+ ```powershell
105
+ python "$skill\scripts\compress_claude_jsonl.py" `
106
+ --input "C:\path\session.jsonl" `
107
+ --write-model-pack "C:\work\run\session.model-pack.md" `
108
+ --target-ratio 0.30 `
109
+ --min-recent-records 120 `
110
+ --summary-char-budget 60000 `
111
+ --target-estimated-tokens 150000 `
112
+ --model-pack-char-budget 500000 `
113
+ --model-pack-estimated-token-budget 150000
114
+ ```
115
+
116
+ The two pack ceilings apply together. Complete human/assistant semantic records,
117
+ prior summaries, handoff lines and required groups are mandatory. Optional
118
+ source/tool/system/error evidence stops at either ceiling and reports
119
+ `evidence_truncated`. Never install a tokenizer for this workflow.
120
+
121
+ When the user gives an approximate compressed Messages-token ceiling, pass it directly instead of inventing a tokenizer workflow:
122
+
123
+ ```powershell
124
+ --target-estimated-tokens 150000
125
+ ```
126
+
127
+ This candidate-output gate is distinct from `--model-pack-estimated-token-budget`.
128
+ It is the built-in zero-dependency estimate for transcript Messages only. It
129
+ covers complete retained structured message payloads, including thinking,
130
+ `tool_use.input`, `tool_result`, and `toolUseResult`. It excludes the system
131
+ prompt, tool schemas, MCP, agents, skills, memory files and runtime additions.
132
+ Never claim it predicts Claude `/context` total exactly. Treat `--target-ratio`
133
+ only as approximate byte planning; only an explicit
134
+ `--target-estimated-tokens` value is a hard candidate-output estimate gate.
135
+
136
+ Require `--summary-char-budget >= 4000`. Do not weaken this floor or publish a blank compact summary.
137
+
138
+ For `.claude` input, the pack path must be outside `.claude`.
139
+
140
+ ### Model Step: Write The Summary
141
+
142
+ Read the generated pack and write a Markdown summary from that pack only.
143
+
144
+ Copy its leading metadata comment exactly. Schema v11 binds:
145
+
146
+ - `source_sha256`
147
+ - `summary_source_sha256`
148
+ - `evidence_anchor_lines_digest`
149
+ - `required_anchor_groups_digest`
150
+ - `handoff_summary_digest`
151
+ - `pack_request_digest`
152
+ - `required_claim_sources_digest`
153
+
154
+ Summary rules:
155
+
156
+ 1. Cite every substantive JSONL-backed statement with one or more displayed `L<number>` anchors.
157
+ 2. Cite every handoff-backed statement with a displayed `H<number>` anchor.
158
+ 3. Never cite lines or H anchors absent from the pack. Cite at least one displayed L anchor from every required coverage group, including every individual human/assistant semantic record and each prior compact summary group.
159
+ 4. Preserve chronology and event time. When decisions conflict, identify the later current decision and retain the earlier decision as superseded history with its reason.
160
+ 5. Preserve user goals, exact constraints, final instructions, questions and wording that changes interpretation.
161
+ 6. Preserve assistant/model research decisions, reasons, evidence checks, rejected routes, uncertainty and supersessions in any language.
162
+ 7. Weight evidence in this order:
163
+ - hard user constraints and current goals
164
+ - current decisions and supersessions
165
+ - assistant/model research conclusions with reasons and verification
166
+ - source/tool/file evidence supporting those decisions
167
+ - unresolved risks and unknowns
168
+ - ordinary progress, repeated commands and low-information logs
169
+ 8. For humanities, law, art, design, brand strategy, planning, history, feasibility and document research, preserve historical nuance, provenance, interpretive changes and minority/abandoned positions that explain the current conclusion.
170
+ 9. For software and engineering, preserve contracts, architecture decisions, failure causes, migrations, compatibility constraints, tests and operational state.
171
+ 10. Use all nine exact `##` sections printed under `Required Final Summary Shape`, in order; every section needs an L or H evidence anchor. Do not add HTML comments or Markdown headings beyond the exact leading metadata comment and required headings.
172
+ 11. Use the exact whole line `Unknown from provided anchors.` when evidence is insufficient. Do not append a claim to that line; every other substantive line needs visible L/H support.
173
+ 12. Treat every explicitly supplied handoff line as complete evidence. Cite every generated early/middle/late/latest H coverage group. If the complete handoff and mandatory pack sections do not fit either pack ceiling, stop; raise `--model-pack-char-budget` or `--model-pack-estimated-token-budget` only within the summarizing model's capacity.
174
+ 13. Do not edit JSONL, UUIDs, parent links, tool pairs, compact records or pointer records manually.
175
+ 14. Under `## Evidence and Source Anchors`, include exactly one `### Mandatory Evidence Coverage` subsection. For every anchor under `Required Claim Support`, add exactly `- L42 support_text_json="exact source substring" disposition=covered`; the JSON string must be a meaningful exact substring of that L record. Do not substitute generic anchor prose.
176
+
177
+ Schema v11 gives each non-empty older active human message and each older active assistant `text`/`thinking` message its own full-text required L group and claim-source entry. It also reserves early/middle/late/latest, source/tool and prior-summary coverage. Every active-chain prior compact summary and every physical line of an explicitly supplied handoff are included in full. If mandatory evidence does not fit either pack ceiling, stop and report the capacity boundary. Do not truncate, sample or bypass the gate.
178
+
179
+ ### Pass 2: Validate And Compress
180
+
181
+ ```powershell
182
+ python "$skill\scripts\compress_claude_jsonl.py" `
183
+ --input "C:\path\session.jsonl" `
184
+ --output "C:\path\compressed.jsonl" `
185
+ --target-ratio 0.30 `
186
+ --min-recent-records 120 `
187
+ --summary-char-budget 60000 `
188
+ --target-estimated-tokens 150000 `
189
+ --model-pack-char-budget 500000 `
190
+ --model-pack-estimated-token-budget 150000 `
191
+ --model-summary "C:\work\run\session.model-summary.md"
192
+ ```
193
+
194
+ Repeat every non-default pass-1 option in pass 2, including candidate token target, checkpoint policy, explicit leaf, handoff file, both model-pack budgets, templates and prior-summary policy. The v11 `pack_request_digest` binds those options and loaded resources; the script rejects a summary whose request, source, evidence, claim-source hashes or anchors do not match the regenerated pack.
195
+
196
+ Use this only on explicit request for deterministic fallback:
197
+
198
+ ```powershell
199
+ --deterministic-summary
200
+ ```
201
+
202
+ Do not silently choose deterministic fallback because model authoring is inconvenient.
203
+
204
+ ## Live Replacement Commands
205
+
206
+ Generate the model pack outside `.claude`, write the model summary, then run:
207
+
208
+ ```powershell
209
+ python "$skill\scripts\compress_claude_jsonl.py" `
210
+ --input "$env:USERPROFILE\.claude\projects\PROJECT\SESSION.jsonl" `
211
+ --replace-original `
212
+ --confirm-session-closed `
213
+ --work-dir "C:\work\claude-compression\SESSION-TIMESTAMP" `
214
+ --model-pack-estimated-token-budget 150000 `
215
+ --target-estimated-tokens 150000 `
216
+ --model-summary "C:\work\claude-compression\SESSION-TIMESTAMP\session.model-summary.md"
217
+ ```
218
+
219
+ By default the backup stays beside the live JSONL. Use an external backup directory only when explicitly requested:
220
+
221
+ ```powershell
222
+ --backup-dir "C:\work\claude-compression\SESSION-TIMESTAMP\backups"
223
+ ```
224
+
225
+ Do not hand-copy a candidate over a live session after a refusal.
226
+
227
+ ## Checkpoint Policy
228
+
229
+ Conversation rewind topology and file checkpoints are separate planes.
230
+
231
+ - `--checkpoint-policy active-correlated` is the default. It keeps only recent UUID-less `file-history-snapshot` records that structurally correlate to retained active records.
232
+ - `--checkpoint-policy none` keeps no UUID-less file-history snapshots.
233
+ - `--checkpoint-policy preserve-recent` is rejected in strict active-chain mode. It is meaningful only with explicit `--preserve-physical-tail`, whose report is labeled `physical-tail-compatibility` and which has no inactive-branch isolation guarantee.
234
+ - `--max-file-history-snapshots N` caps retained snapshots.
235
+
236
+ Never claim that compressed JSONL alone guarantees complete file-state rewind. The report states what snapshot side records were retained.
237
+
238
+ ## Repeated Compression
239
+
240
+ Default behavior folds old compact summaries into one current compact summary. The output must contain one current compact pair.
241
+
242
+ Treat an older `preservedMessages` list as a historical snapshot. A later rewind may make its tail diverge from the current authoritative chain; report that warning, exclude the old tail, and continue only when the current chain itself is valid. Require every newly generated candidate to rebuild the snapshot so it exactly matches the candidate's current chain.
243
+
244
+ When the user explicitly asks to preserve existing summaries verbatim, repeat this flag in both model-pack and compression passes:
245
+
246
+ ```powershell
247
+ --preserve-prior-summaries-verbatim
248
+ ```
249
+
250
+ The script may expand the summary character budget to 1.5x. If exact preservation still does not fit, it uses the normal folded path and reports `fallback-folded`. It does not stack old compact pairs into the active chain.
251
+
252
+ For third and later rounds, apply chronology again. Prior summaries are historical evidence, not automatically current truth. Preserve old decisions and reasons while marking later supersessions.
253
+
254
+ ## Read.pages Repair Commands
255
+
256
+ Scan without writing:
257
+
258
+ ```powershell
259
+ python "$skill\scripts\repair_claude_jsonl.py" `
260
+ --input "C:\path\session.jsonl" `
261
+ --scan-only
262
+ ```
263
+
264
+ Write a separate candidate and require the expected patch count:
265
+
266
+ ```powershell
267
+ python "$skill\scripts\repair_claude_jsonl.py" `
268
+ --input "C:\path\session.jsonl" `
269
+ --output "C:\path\session.repaired.jsonl" `
270
+ --expect-matches 2
271
+ ```
272
+
273
+ Replace one live session transactionally:
274
+
275
+ ```powershell
276
+ python "$skill\scripts\repair_claude_jsonl.py" `
277
+ --input "$env:USERPROFILE\.claude\projects\PROJECT\SESSION.jsonl" `
278
+ --replace-original `
279
+ --confirm-session-closed `
280
+ --work-dir "C:\work\claude-repair\SESSION-TIMESTAMP" `
281
+ --expect-matches 2
282
+ ```
283
+
284
+ Use `--scope all` only when the user explicitly wants inactive physical branches repaired too.
285
+
286
+ An automatic repair additionally requires exactly one later result with the same non-empty `sessionId` and a `sourceToolAssistantUUID` equal to the Read tool-use assistant UUID. Candidate publication must re-read and validate the actual published bytes and prove an idempotent second scan before success.
287
+
288
+ ## Validation And Stop Condition
289
+
290
+ Before reporting success, require fresh evidence for the selected operation:
291
+
292
+ Compression:
293
+
294
+ - candidate validation `ok: true`
295
+ - no duplicate UUID, missing parent, cross-session parent or tool-pair error
296
+ - no empty/duplicate active tool ID; a partial ordered-subset result is accepted only as an explicit compatibility warning and count
297
+ - exactly one current Codex compact boundary/summary pair on the final pointer chain
298
+ - one projected final `last-prompt`
299
+ - dead-branch counts reported without branch text
300
+ - any observed attachment-order/session-lineage compatibility is explicitly reported, and output raw records remain one current-session chain
301
+ - explicit `--target-estimated-tokens` ceiling met under the complete-structure local estimate; an approximate ratio alone is not a hard success claim
302
+ - input unchanged in candidate mode
303
+ - backup bytes equal original bytes in live mode
304
+ - replacement validation `ok: true` in live mode
305
+
306
+ Repair:
307
+
308
+ - expected match count satisfied when supplied
309
+ - byte validation `ok: true`
310
+ - UUID/parent and tool-ID signatures unchanged
311
+ - second pass finds zero patchable matches
312
+ - shared full-transcript validation `ok: true`
313
+ - input unchanged in candidate mode
314
+ - numbered backup equals original in live mode
315
+
316
+ Structural validation alone is an observed-format check, not an Anthropic format guarantee. If the user permits Claude CLI testing, report `/resume`, `/context`, recent conversation rewind and recent file rewind as separate observations.
317
+
318
+ ## Session Location
319
+
320
+ Prefer an exact path or filename. To locate one session without reading transcript bodies:
321
+
322
+ ```powershell
323
+ python "$skill\scripts\claude_session_tools.py" `
324
+ --root "$env:USERPROFILE\.claude\projects" `
325
+ --query "SESSION.jsonl"
326
+ ```
327
+
328
+ Use `--scan-titles` only when the user supplies a title and permits title scanning. Multiple matches are an error. Never broaden a single-target run into directory-wide compression.
329
+
330
+ ## Failure Rules
331
+
332
+ - A strict topology failure produces no pack, candidate, sidecar or backup.
333
+ - For a special or ambiguous topology, report the strict failure and pause. Offer only the exact explicit control that matches the diagnosis, state the lost guarantee, and require a new user confirmation before running it. Manual/spliced files generally require `--preserve-physical-tail`, which forfeits inactive-branch and rewind isolation.
334
+ - A model-summary validation failure requires regenerating the pack/summary with identical settings; do not weaken validation.
335
+ - A tool-pair failure requires moving the cut earlier or diagnosing source inconsistency; do not invent tool results.
336
+ - A source hash change aborts live replacement.
337
+ - Live replacement requires the Claude process for that session to be closed. The transaction validates immutable candidate bytes, exclusively creates and verifies a numbered backup, captures the actual old target, verifies its full SHA-256, installs the candidate, and verifies the published bytes and structure. If another process recreates the target during capture, preserve the external target and recovery backups and fail without publishing. Parent-directory fsync is best effort and reported because platform support differs.
338
+ - A write/fsync/validation/replace failure returns nonzero. The transaction restores the captured original bytes when replacement began; if restoration itself fails, it raises a high-priority error and retains the numbered backup for recovery.
339
+ - If a valid live replacement commits but final sidecar/report publication fails, return exit code 3 with `committed-report-failed` and the committed hashes/backup labels. Do not rerun blindly or describe that state as an uncommitted failure.
340
+ - Keep reports and temporary work outside `.claude`; do not leave ad hoc files in live session directories.
@@ -0,0 +1,7 @@
1
+ interface:
2
+ display_name: "Claude JSONL Compressor"
3
+ short_description: "Compress one Claude JSONL with a source-anchored model summary."
4
+ default_prompt: "Use $claude-jsonl-compressor on exactly one Claude Code JSONL. Run strict physically-last last-prompt preflight before semantic work; stop on malformed or ambiguous topology and exclude rewound, inactive, or unattributed branches from every evidence and output layer. Use the model-assisted two-pass workflow by default, preserve a recent raw suffix and only correlated snapshots, repeat digest-bound options across passes, and validate the candidate. For live replacement, require closed-session acknowledgement, an external work directory, a numbered verified backup, and transactional replacement. Run Read.pages repair only when explicitly requested; never install dependencies or broaden the operation to another session."
5
+
6
+ policy:
7
+ allow_implicit_invocation: true
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ require("./run-python.cjs").runPython("compress_claude_jsonl.py");
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ require("./run-python.cjs").runPython("repair_claude_jsonl.py");
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const path = require("node:path");
5
+ const { spawn } = require("node:child_process");
6
+
7
+ function candidates() {
8
+ const configured = process.env.PYTHON;
9
+ const list = [];
10
+ if (configured) {
11
+ list.push({ command: configured, prefix: [] });
12
+ }
13
+ if (process.platform === "win32") {
14
+ list.push({ command: "py", prefix: ["-3"] });
15
+ list.push({ command: "python", prefix: [] });
16
+ list.push({ command: "python3", prefix: [] });
17
+ } else {
18
+ list.push({ command: "python3", prefix: [] });
19
+ list.push({ command: "python", prefix: [] });
20
+ }
21
+ return list;
22
+ }
23
+
24
+ function runPython(scriptName) {
25
+ const scriptPath = path.resolve(__dirname, "..", "scripts", scriptName);
26
+ const options = candidates();
27
+
28
+ function launch(index) {
29
+ if (index >= options.length) {
30
+ process.stderr.write("ERROR: Python 3.10 or newer was not found. Set PYTHON to an executable path.\n");
31
+ process.exitCode = 127;
32
+ return;
33
+ }
34
+ const selected = options[index];
35
+ const child = spawn(
36
+ selected.command,
37
+ [...selected.prefix, "-B", scriptPath, ...process.argv.slice(2)],
38
+ { shell: false, stdio: "inherit", windowsHide: true }
39
+ );
40
+ let started = false;
41
+ child.once("spawn", () => {
42
+ started = true;
43
+ });
44
+ child.once("error", (error) => {
45
+ if (!started && error && error.code === "ENOENT") {
46
+ launch(index + 1);
47
+ return;
48
+ }
49
+ process.stderr.write(`ERROR: failed to launch Python: ${error.message}\n`);
50
+ process.exitCode = 1;
51
+ });
52
+ child.once("exit", (code, signal) => {
53
+ if (signal) {
54
+ try {
55
+ process.kill(process.pid, signal);
56
+ } catch (_error) {
57
+ process.exitCode = 1;
58
+ }
59
+ return;
60
+ }
61
+ process.exitCode = Number.isInteger(code) ? code : 1;
62
+ });
63
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
64
+ process.once(signal, () => {
65
+ try {
66
+ child.kill(signal);
67
+ } catch (_error) {
68
+ process.exitCode = 1;
69
+ }
70
+ });
71
+ }
72
+ }
73
+
74
+ launch(0);
75
+ }
76
+
77
+ module.exports = { runPython };