cloding 0.1.0 → 0.1.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/README.md +1 -1
- package/bin/cloding.js +137 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ cloding -m haiku # Use Claude Haiku 4.5
|
|
|
27
27
|
cloding -m sonnet # Use Claude Sonnet 4
|
|
28
28
|
cloding -m opus # Use Claude Opus 4.6
|
|
29
29
|
cloding -m deepseek # Use DeepSeek Coder V3
|
|
30
|
-
cloding -p "
|
|
30
|
+
cloding -p "add dark mode" # Non-interactive, single prompt
|
|
31
31
|
cloding --list-models # Show all models with pricing
|
|
32
32
|
cloding -m meta-llama/llama-4-scout # Any OpenRouter model ID works
|
|
33
33
|
```
|
package/bin/cloding.js
CHANGED
|
@@ -16,9 +16,36 @@
|
|
|
16
16
|
* cloding docker run "prompt" # Run in a Docker container
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
const { spawn,
|
|
19
|
+
const { spawn, spawnSync } = require("child_process");
|
|
20
20
|
const path = require("path");
|
|
21
21
|
const fs = require("fs");
|
|
22
|
+
const os = require("os");
|
|
23
|
+
|
|
24
|
+
// ──────────────────────────────────────────────
|
|
25
|
+
// Constants
|
|
26
|
+
// ──────────────────────────────────────────────
|
|
27
|
+
const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
28
|
+
const DEFAULT_MODEL = "qwen";
|
|
29
|
+
const DOCKER_IMAGE = "cloding:latest";
|
|
30
|
+
const DOCKER_NETWORK = "cloding-net";
|
|
31
|
+
|
|
32
|
+
// ──────────────────────────────────────────────
|
|
33
|
+
// Signal forwarding — relay SIGINT/SIGTERM to child processes
|
|
34
|
+
// ──────────────────────────────────────────────
|
|
35
|
+
function forwardSignals(child) {
|
|
36
|
+
const handler = (signal) => {
|
|
37
|
+
if (child && !child.killed) {
|
|
38
|
+
child.kill(signal);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
process.on("SIGINT", handler);
|
|
42
|
+
process.on("SIGTERM", handler);
|
|
43
|
+
// Clean up listeners when child exits to avoid leaks
|
|
44
|
+
child.on("exit", () => {
|
|
45
|
+
process.removeListener("SIGINT", handler);
|
|
46
|
+
process.removeListener("SIGTERM", handler);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
22
49
|
|
|
23
50
|
// ──────────────────────────────────────────────
|
|
24
51
|
// .env loader (no dependencies)
|
|
@@ -47,8 +74,9 @@ function loadEnvFile() {
|
|
|
47
74
|
) {
|
|
48
75
|
val = val.slice(1, -1);
|
|
49
76
|
}
|
|
50
|
-
// Don't override existing env vars
|
|
51
|
-
|
|
77
|
+
// Don't override existing env vars (check existence, not truthiness —
|
|
78
|
+
// empty string values like ANTHROPIC_API_KEY="" must be preserved)
|
|
79
|
+
if (!(key in process.env)) {
|
|
52
80
|
process.env[key] = val;
|
|
53
81
|
}
|
|
54
82
|
}
|
|
@@ -63,12 +91,35 @@ function loadEnvFile() {
|
|
|
63
91
|
// ──────────────────────────────────────────────
|
|
64
92
|
function loadModels() {
|
|
65
93
|
const modelsPath = path.join(__dirname, "..", "models.json");
|
|
94
|
+
let models;
|
|
66
95
|
try {
|
|
67
|
-
|
|
96
|
+
models = JSON.parse(fs.readFileSync(modelsPath, "utf8"));
|
|
68
97
|
} catch {
|
|
69
98
|
console.error("Error: Could not load models.json");
|
|
70
99
|
process.exit(1);
|
|
71
100
|
}
|
|
101
|
+
|
|
102
|
+
// Validate structure
|
|
103
|
+
if (!models || typeof models !== "object" || Array.isArray(models)) {
|
|
104
|
+
console.error("Error: models.json must be a JSON object");
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
for (const [shortcut, m] of Object.entries(models)) {
|
|
108
|
+
if (!m.id || typeof m.id !== "string") {
|
|
109
|
+
console.error(`Error: models.json: "${shortcut}" missing required "id" (string)`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
if (!m.name || typeof m.name !== "string") {
|
|
113
|
+
console.error(`Error: models.json: "${shortcut}" missing required "name" (string)`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
if (typeof m.in !== "number" || typeof m.out !== "number") {
|
|
117
|
+
console.error(`Error: models.json: "${shortcut}" requires numeric "in" and "out" costs`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return models;
|
|
72
123
|
}
|
|
73
124
|
|
|
74
125
|
// ──────────────────────────────────────────────
|
|
@@ -148,10 +199,14 @@ function parseArgs(argv) {
|
|
|
148
199
|
// Display helpers
|
|
149
200
|
// ──────────────────────────────────────────────
|
|
150
201
|
function printVersion() {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
202
|
+
try {
|
|
203
|
+
const pkg = JSON.parse(
|
|
204
|
+
fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")
|
|
205
|
+
);
|
|
206
|
+
console.log(`cloding v${pkg.version}`);
|
|
207
|
+
} catch {
|
|
208
|
+
console.log("cloding (unknown version)");
|
|
209
|
+
}
|
|
155
210
|
}
|
|
156
211
|
|
|
157
212
|
function printHelp() {
|
|
@@ -183,7 +238,7 @@ DOCKER COMMANDS:
|
|
|
183
238
|
cloding docker help Show Docker help
|
|
184
239
|
|
|
185
240
|
MODELS (shortcuts):
|
|
186
|
-
qwen Qwen 3 Coder $0.07/$0.30 per Mtok (default, ~
|
|
241
|
+
qwen Qwen 3 Coder $0.07/$0.30 per Mtok (default, ~71x cheaper)
|
|
187
242
|
haiku Claude Haiku 4.5 $0.80/$4.00 per Mtok
|
|
188
243
|
sonnet Claude Sonnet 4 $3.00/$15.00 per Mtok
|
|
189
244
|
opus Claude Opus 4.6 $15.00/$75.00 per Mtok
|
|
@@ -223,9 +278,10 @@ function printModels(models) {
|
|
|
223
278
|
const opusOut = models.opus ? models.opus.out : 75.0;
|
|
224
279
|
|
|
225
280
|
for (const [shortcut, m] of Object.entries(models)) {
|
|
226
|
-
const savings = opusOut / m.out;
|
|
281
|
+
const savings = m.out > 0 ? Math.round(opusOut / m.out) : 0;
|
|
227
282
|
const savingsStr =
|
|
228
|
-
shortcut === "opus" ? " baseline" :
|
|
283
|
+
shortcut === "opus" ? " baseline" :
|
|
284
|
+
savings > 0 ? ` ${savings}x cheaper` : " n/a";
|
|
229
285
|
console.log(
|
|
230
286
|
` ${shortcut.padEnd(11)} ${m.name.padEnd(24)} $${m.in.toFixed(2).padStart(6)} $${m.out.toFixed(2).padStart(6)}${savingsStr}`
|
|
231
287
|
);
|
|
@@ -242,7 +298,7 @@ function printModels(models) {
|
|
|
242
298
|
function resolveModel(modelArg, models) {
|
|
243
299
|
if (!modelArg) {
|
|
244
300
|
// Use default from env, or fall back to qwen
|
|
245
|
-
const defaultModel = process.env.CLODING_DEFAULT_MODEL ||
|
|
301
|
+
const defaultModel = process.env.CLODING_DEFAULT_MODEL || DEFAULT_MODEL;
|
|
246
302
|
return resolveModel(defaultModel, models);
|
|
247
303
|
}
|
|
248
304
|
|
|
@@ -261,16 +317,10 @@ function resolveModel(modelArg, models) {
|
|
|
261
317
|
};
|
|
262
318
|
}
|
|
263
319
|
|
|
264
|
-
// ──────────────────────────────────────────────
|
|
265
|
-
// Docker helpers
|
|
266
|
-
// ──────────────────────────────────────────────
|
|
267
|
-
const DOCKER_IMAGE = "cloding:latest";
|
|
268
|
-
const DOCKER_NETWORK = "cloding-net";
|
|
269
|
-
|
|
270
320
|
function dockerAvailable() {
|
|
271
321
|
try {
|
|
272
|
-
|
|
273
|
-
return
|
|
322
|
+
const result = spawnSync("docker", ["--version"], { stdio: "ignore" });
|
|
323
|
+
return result.status === 0;
|
|
274
324
|
} catch {
|
|
275
325
|
return false;
|
|
276
326
|
}
|
|
@@ -278,11 +328,10 @@ function dockerAvailable() {
|
|
|
278
328
|
|
|
279
329
|
function dockerImageExists() {
|
|
280
330
|
try {
|
|
281
|
-
|
|
282
|
-
const result = execSync(`docker images -q ${DOCKER_IMAGE}`, {
|
|
331
|
+
const result = spawnSync("docker", ["images", "-q", DOCKER_IMAGE], {
|
|
283
332
|
encoding: "utf8",
|
|
284
333
|
});
|
|
285
|
-
return result.trim().length > 0;
|
|
334
|
+
return (result.stdout || "").trim().length > 0;
|
|
286
335
|
} catch {
|
|
287
336
|
return false;
|
|
288
337
|
}
|
|
@@ -305,10 +354,7 @@ function getDockerfilePath() {
|
|
|
305
354
|
function ensureNetwork() {
|
|
306
355
|
try {
|
|
307
356
|
// Safe: constant network name, no user input. Uses spawnSync for safety.
|
|
308
|
-
|
|
309
|
-
"docker", ["network", "create", DOCKER_NETWORK],
|
|
310
|
-
{ stdio: "ignore" }
|
|
311
|
-
);
|
|
357
|
+
spawnSync("docker", ["network", "create", DOCKER_NETWORK], { stdio: "ignore" });
|
|
312
358
|
// Ignore errors — network may already exist
|
|
313
359
|
} catch {
|
|
314
360
|
// Network already exists, that's fine
|
|
@@ -332,6 +378,7 @@ COMMANDS:
|
|
|
332
378
|
|
|
333
379
|
OPTIONS (for run/shell):
|
|
334
380
|
-m, --model <name> Model shortcut or OpenRouter ID (default: qwen)
|
|
381
|
+
-p, --prompt <text> Prompt text (alternative to positional argument)
|
|
335
382
|
-w, --workspace <path> Mount a local directory as /workspace (default: cwd)
|
|
336
383
|
--memory <limit> Container memory limit (default: 2g)
|
|
337
384
|
--cpus <limit> Container CPU limit (default: 1.0)
|
|
@@ -372,6 +419,7 @@ function dockerBuild() {
|
|
|
372
419
|
const child = spawn("docker", ["build", "-t", DOCKER_IMAGE, dockerDir], {
|
|
373
420
|
stdio: "inherit",
|
|
374
421
|
});
|
|
422
|
+
forwardSignals(child);
|
|
375
423
|
|
|
376
424
|
child.on("exit", (code) => {
|
|
377
425
|
if (code === 0) {
|
|
@@ -412,6 +460,11 @@ function dockerRun(dockerArgs, models, interactive) {
|
|
|
412
460
|
if (i + 1 >= dockerArgs.length) { console.error("Error: --model requires a value."); process.exit(1); }
|
|
413
461
|
modelArg = dockerArgs[++i];
|
|
414
462
|
break;
|
|
463
|
+
case "-p":
|
|
464
|
+
case "--prompt":
|
|
465
|
+
if (i + 1 >= dockerArgs.length) { console.error("Error: --prompt requires a value."); process.exit(1); }
|
|
466
|
+
prompt = dockerArgs[++i];
|
|
467
|
+
break;
|
|
415
468
|
case "-w":
|
|
416
469
|
case "--workspace":
|
|
417
470
|
if (i + 1 >= dockerArgs.length) { console.error("Error: --workspace requires a path."); process.exit(1); }
|
|
@@ -474,9 +527,13 @@ function dockerRun(dockerArgs, models, interactive) {
|
|
|
474
527
|
// Resolve model
|
|
475
528
|
const model = resolveModel(modelArg, models);
|
|
476
529
|
|
|
477
|
-
// Validate workspace exists
|
|
530
|
+
// Validate workspace exists and is a directory
|
|
478
531
|
if (!fs.existsSync(workspace)) {
|
|
479
|
-
console.error(`Error: Workspace
|
|
532
|
+
console.error(`Error: Workspace not found: ${workspace}`);
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
if (!fs.statSync(workspace).isDirectory()) {
|
|
536
|
+
console.error(`Error: Workspace path is not a directory: ${workspace}`);
|
|
480
537
|
process.exit(1);
|
|
481
538
|
}
|
|
482
539
|
|
|
@@ -489,6 +546,17 @@ function dockerRun(dockerArgs, models, interactive) {
|
|
|
489
546
|
containerName = `cloding-${interactive ? "shell" : "run"}-${suffix}`;
|
|
490
547
|
}
|
|
491
548
|
|
|
549
|
+
// Write env vars to a temp file so the API key doesn't leak in `ps aux`
|
|
550
|
+
const envFileContent = [
|
|
551
|
+
`ANTHROPIC_BASE_URL=${OPENROUTER_BASE_URL}`,
|
|
552
|
+
`ANTHROPIC_AUTH_TOKEN=${apiKey}`,
|
|
553
|
+
`ANTHROPIC_API_KEY=`,
|
|
554
|
+
`ANTHROPIC_MODEL=${model.id}`,
|
|
555
|
+
`CLAUDECODE=`,
|
|
556
|
+
].join("\n") + "\n";
|
|
557
|
+
const envFilePath = path.join(os.tmpdir(), `cloding-env-${Date.now()}.tmp`);
|
|
558
|
+
fs.writeFileSync(envFilePath, envFileContent, { mode: 0o600 });
|
|
559
|
+
|
|
492
560
|
// Build docker command — uses spawn with argument array (safe, no shell injection)
|
|
493
561
|
const cmd = ["docker", "run"];
|
|
494
562
|
|
|
@@ -506,11 +574,7 @@ function dockerRun(dockerArgs, models, interactive) {
|
|
|
506
574
|
"--memory", memory,
|
|
507
575
|
"--cpus", cpus,
|
|
508
576
|
"-v", `${workspace}:/workspace`,
|
|
509
|
-
"-
|
|
510
|
-
"-e", `ANTHROPIC_AUTH_TOKEN=${apiKey}`,
|
|
511
|
-
"-e", `ANTHROPIC_API_KEY=`,
|
|
512
|
-
"-e", `ANTHROPIC_MODEL=${model.id}`,
|
|
513
|
-
"-e", `CLAUDECODE=`,
|
|
577
|
+
"--env-file", envFilePath,
|
|
514
578
|
DOCKER_IMAGE
|
|
515
579
|
);
|
|
516
580
|
|
|
@@ -530,21 +594,31 @@ function dockerRun(dockerArgs, models, interactive) {
|
|
|
530
594
|
console.log(` Workspace: ${workspace} → /workspace`);
|
|
531
595
|
console.log(` Resources: ${memory} RAM, ${cpus} CPUs`);
|
|
532
596
|
|
|
533
|
-
if (model.in > 0 && models.opus) {
|
|
534
|
-
const savings = (models.opus.out / model.out)
|
|
597
|
+
if (model.in > 0 && model.out > 0 && models.opus) {
|
|
598
|
+
const savings = Math.round(models.opus.out / model.out);
|
|
535
599
|
if (savings > 1) {
|
|
536
600
|
console.log(` \x1b[32m${savings}x cheaper than Opus\x1b[0m`);
|
|
537
601
|
}
|
|
538
602
|
}
|
|
539
603
|
console.log("");
|
|
540
604
|
|
|
605
|
+
// Clean up env file on exit (contains API key)
|
|
606
|
+
function cleanupEnvFile() {
|
|
607
|
+
try { fs.unlinkSync(envFilePath); } catch {}
|
|
608
|
+
}
|
|
609
|
+
|
|
541
610
|
// Spawn docker — uses argument array (no shell interpretation)
|
|
542
611
|
const child = spawn(cmd[0], cmd.slice(1), {
|
|
543
612
|
stdio: "inherit",
|
|
544
613
|
});
|
|
614
|
+
forwardSignals(child);
|
|
545
615
|
|
|
546
|
-
child.on("exit", (code) =>
|
|
616
|
+
child.on("exit", (code) => {
|
|
617
|
+
cleanupEnvFile();
|
|
618
|
+
process.exit(code ?? 0);
|
|
619
|
+
});
|
|
547
620
|
child.on("error", (err) => {
|
|
621
|
+
cleanupEnvFile();
|
|
548
622
|
console.error(`Error launching Docker: ${err.message}`);
|
|
549
623
|
console.error("Make sure Docker is installed and running.");
|
|
550
624
|
process.exit(1);
|
|
@@ -556,7 +630,7 @@ function dockerStatus() {
|
|
|
556
630
|
|
|
557
631
|
try {
|
|
558
632
|
// Safe: uses spawnSync with argument array, no shell injection possible
|
|
559
|
-
const result =
|
|
633
|
+
const result = spawnSync(
|
|
560
634
|
"docker",
|
|
561
635
|
["ps", "--filter", "name=cloding", "--format", "table {{.Names}}\t{{.Status}}\t{{.Image}}\t{{.Ports}}"],
|
|
562
636
|
{ encoding: "utf8" }
|
|
@@ -583,7 +657,7 @@ function dockerStop() {
|
|
|
583
657
|
|
|
584
658
|
try {
|
|
585
659
|
// Safe: uses spawnSync with argument array
|
|
586
|
-
const result =
|
|
660
|
+
const result = spawnSync(
|
|
587
661
|
"docker",
|
|
588
662
|
["ps", "-q", "--filter", "name=cloding"],
|
|
589
663
|
{ encoding: "utf8" }
|
|
@@ -601,13 +675,13 @@ function dockerStop() {
|
|
|
601
675
|
|
|
602
676
|
for (const id of ids) {
|
|
603
677
|
try {
|
|
604
|
-
const inspect =
|
|
678
|
+
const inspect = spawnSync(
|
|
605
679
|
"docker", ["inspect", "--format", "{{.Name}}", id],
|
|
606
680
|
{ encoding: "utf8" }
|
|
607
681
|
);
|
|
608
682
|
const name = (inspect.stdout || id).trim().replace(/^\//, "");
|
|
609
683
|
|
|
610
|
-
|
|
684
|
+
spawnSync(
|
|
611
685
|
"docker", ["stop", "-t", "5", id],
|
|
612
686
|
{ stdio: "ignore" }
|
|
613
687
|
);
|
|
@@ -630,7 +704,7 @@ function dockerClean() {
|
|
|
630
704
|
|
|
631
705
|
try {
|
|
632
706
|
// Safe: uses spawnSync with argument array
|
|
633
|
-
const result =
|
|
707
|
+
const result = spawnSync(
|
|
634
708
|
"docker",
|
|
635
709
|
["ps", "-aq", "--filter", "name=cloding", "--filter", "status=exited"],
|
|
636
710
|
{ encoding: "utf8" }
|
|
@@ -648,13 +722,13 @@ function dockerClean() {
|
|
|
648
722
|
|
|
649
723
|
for (const id of ids) {
|
|
650
724
|
try {
|
|
651
|
-
const inspect =
|
|
725
|
+
const inspect = spawnSync(
|
|
652
726
|
"docker", ["inspect", "--format", "{{.Name}}", id],
|
|
653
727
|
{ encoding: "utf8" }
|
|
654
728
|
);
|
|
655
729
|
const name = (inspect.stdout || id).trim().replace(/^\//, "");
|
|
656
730
|
|
|
657
|
-
|
|
731
|
+
spawnSync(
|
|
658
732
|
"docker", ["rm", id],
|
|
659
733
|
{ stdio: "ignore" }
|
|
660
734
|
);
|
|
@@ -782,11 +856,17 @@ function main() {
|
|
|
782
856
|
const pythonArgs = ["-m", "osq", ...args.pipelineArgs];
|
|
783
857
|
console.log(`Running pipeline: python ${pythonArgs.join(" ")}`);
|
|
784
858
|
|
|
859
|
+
// Pipeline inherits env but needs OpenRouter vars and CLAUDECODE stripped
|
|
860
|
+
const pipelineEnv = { ...process.env };
|
|
861
|
+
pipelineEnv.OPENROUTER_API_KEY = apiKey;
|
|
862
|
+
delete pipelineEnv.CLAUDECODE;
|
|
863
|
+
|
|
785
864
|
const child = spawn("python", pythonArgs, {
|
|
786
865
|
cwd: pipelineDir,
|
|
787
866
|
stdio: "inherit",
|
|
788
|
-
env:
|
|
867
|
+
env: pipelineEnv,
|
|
789
868
|
});
|
|
869
|
+
forwardSignals(child);
|
|
790
870
|
|
|
791
871
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
792
872
|
child.on("error", (err) => {
|
|
@@ -802,7 +882,7 @@ function main() {
|
|
|
802
882
|
|
|
803
883
|
// Build env for claude
|
|
804
884
|
const claudeEnv = { ...process.env };
|
|
805
|
-
claudeEnv.ANTHROPIC_BASE_URL =
|
|
885
|
+
claudeEnv.ANTHROPIC_BASE_URL = OPENROUTER_BASE_URL;
|
|
806
886
|
claudeEnv.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
807
887
|
claudeEnv.ANTHROPIC_API_KEY = "";
|
|
808
888
|
claudeEnv.ANTHROPIC_MODEL = model.id;
|
|
@@ -817,15 +897,15 @@ function main() {
|
|
|
817
897
|
}
|
|
818
898
|
|
|
819
899
|
// Print banner
|
|
820
|
-
const shortcut = args.model || process.env.CLODING_DEFAULT_MODEL ||
|
|
900
|
+
const shortcut = args.model || process.env.CLODING_DEFAULT_MODEL || DEFAULT_MODEL;
|
|
821
901
|
const costInfo =
|
|
822
902
|
model.in > 0
|
|
823
903
|
? ` ($${model.in}/$${model.out} per Mtok)`
|
|
824
904
|
: "";
|
|
825
905
|
console.log(`\x1b[36m⚡ cloding\x1b[0m → ${model.name}${costInfo}`);
|
|
826
906
|
|
|
827
|
-
if (model.in > 0 && models.opus) {
|
|
828
|
-
const savings = (models.opus.out / model.out)
|
|
907
|
+
if (model.in > 0 && model.out > 0 && models.opus) {
|
|
908
|
+
const savings = Math.round(models.opus.out / model.out);
|
|
829
909
|
if (savings > 1) {
|
|
830
910
|
console.log(`\x1b[32m ${savings}x cheaper than Opus\x1b[0m`);
|
|
831
911
|
}
|
|
@@ -833,25 +913,15 @@ function main() {
|
|
|
833
913
|
console.log("");
|
|
834
914
|
|
|
835
915
|
// Launch claude
|
|
836
|
-
// On Windows, npm globals are .cmd shims that need shell resolution.
|
|
837
|
-
// We
|
|
838
|
-
// DEP0190 deprecation — instead the args are baked into the command string.
|
|
916
|
+
// On Windows, npm globals are .cmd shims that need shell:true for resolution.
|
|
917
|
+
// We pass args as an array and let Node handle escaping — never build a command string.
|
|
839
918
|
const isWin = process.platform === "win32";
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
env: claudeEnv,
|
|
847
|
-
shell: true,
|
|
848
|
-
});
|
|
849
|
-
} else {
|
|
850
|
-
child = spawn("claude", claudeArgs, {
|
|
851
|
-
stdio: "inherit",
|
|
852
|
-
env: claudeEnv,
|
|
853
|
-
});
|
|
854
|
-
}
|
|
919
|
+
const child = spawn("claude", claudeArgs, {
|
|
920
|
+
stdio: "inherit",
|
|
921
|
+
env: claudeEnv,
|
|
922
|
+
shell: isWin,
|
|
923
|
+
});
|
|
924
|
+
forwardSignals(child);
|
|
855
925
|
|
|
856
926
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
857
927
|
child.on("error", (err) => {
|