debugsk 0.0.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/dist/cli.js ADDED
@@ -0,0 +1,906 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var import_promises3 = __toESM(require("fs/promises"));
28
+ var import_node_os = __toESM(require("os"));
29
+ var import_node_path4 = __toESM(require("path"));
30
+ var import_promises4 = __toESM(require("readline/promises"));
31
+ var import_minimist = __toESM(require("minimist"));
32
+
33
+ // src/constants.ts
34
+ var DEFAULT_HOST = "127.0.0.1";
35
+ var DEFAULT_PORT = 7242;
36
+ var DEFAULT_BASE_PATH = "/";
37
+ var DEFAULT_LOGS_DIR = ".logs";
38
+ var DEFAULT_MAX_BODY_KB = 256;
39
+
40
+ // src/process.ts
41
+ function isProcessAlive(pid) {
42
+ if (!pid) return false;
43
+ try {
44
+ process.kill(pid, 0);
45
+ return true;
46
+ } catch (error) {
47
+ const err = error;
48
+ return err.code === "EPERM";
49
+ }
50
+ }
51
+ async function terminateProcess(pid, timeoutMs) {
52
+ if (!isProcessAlive(pid)) return true;
53
+ try {
54
+ process.kill(pid, "SIGTERM");
55
+ } catch (error) {
56
+ const err = error;
57
+ if (err.code === "ESRCH") return true;
58
+ }
59
+ const start = Date.now();
60
+ while (Date.now() - start < timeoutMs) {
61
+ if (!isProcessAlive(pid)) return true;
62
+ await sleep(100);
63
+ }
64
+ try {
65
+ process.kill(pid, "SIGKILL");
66
+ } catch (error) {
67
+ const err = error;
68
+ if (err.code === "ESRCH") return true;
69
+ }
70
+ return !isProcessAlive(pid);
71
+ }
72
+ function sleep(ms) {
73
+ return new Promise((resolve) => setTimeout(resolve, ms));
74
+ }
75
+
76
+ // src/paths.ts
77
+ var import_node_path = __toESM(require("path"));
78
+ function resolveLogsDir({ cwd, logsDir }) {
79
+ const dir = logsDir && logsDir.length > 0 ? logsDir : DEFAULT_LOGS_DIR;
80
+ return import_node_path.default.resolve(cwd, dir);
81
+ }
82
+ function getRuntimeDir(logsDir) {
83
+ return logsDir;
84
+ }
85
+ function getRuntimePath(logsDir) {
86
+ return import_node_path.default.join(getRuntimeDir(logsDir), "runtime.json");
87
+ }
88
+ function getPidPath(logsDir) {
89
+ return import_node_path.default.join(getRuntimeDir(logsDir), "server.pid");
90
+ }
91
+ function getSessionLogPath(logsDir, sessionId, runId, _timestamp) {
92
+ const safeSession = sanitizePathSegment(sessionId);
93
+ const safeRun = sanitizePathSegment(runId);
94
+ return import_node_path.default.join(getRuntimeDir(logsDir), `${safeSession}-${safeRun}.jsonl`);
95
+ }
96
+ function sanitizePathSegment(value) {
97
+ return value.replace(/[\\/]/g, "_").replace(/\.\./g, "_").trim() || "unknown";
98
+ }
99
+
100
+ // src/runtime.ts
101
+ var import_promises = __toESM(require("fs/promises"));
102
+ async function readRuntime(logsDir) {
103
+ try {
104
+ const raw = await import_promises.default.readFile(getRuntimePath(logsDir), "utf8");
105
+ return JSON.parse(raw);
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+ async function writeRuntime(logsDir, info) {
111
+ await import_promises.default.mkdir(getRuntimeDir(logsDir), { recursive: true });
112
+ await import_promises.default.writeFile(getRuntimePath(logsDir), JSON.stringify(info, null, 2), "utf8");
113
+ }
114
+ async function writePid(logsDir, pid) {
115
+ await import_promises.default.mkdir(getRuntimeDir(logsDir), { recursive: true });
116
+ await import_promises.default.writeFile(getPidPath(logsDir), String(pid), "utf8");
117
+ }
118
+ async function removePid(logsDir) {
119
+ try {
120
+ await import_promises.default.unlink(getPidPath(logsDir));
121
+ } catch {
122
+ return;
123
+ }
124
+ }
125
+ async function markStopped(logsDir) {
126
+ const current = await readRuntime(logsDir);
127
+ if (!current) return;
128
+ const updated = {
129
+ ...current,
130
+ running: false,
131
+ stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
132
+ };
133
+ await writeRuntime(logsDir, updated);
134
+ }
135
+
136
+ // src/server.ts
137
+ var import_node_http = __toESM(require("http"));
138
+ var import_promises2 = __toESM(require("fs/promises"));
139
+ var import_node_path3 = __toESM(require("path"));
140
+
141
+ // src/net.ts
142
+ var import_node_net = __toESM(require("net"));
143
+ async function resolvePort(host, port) {
144
+ if (!port || port === 0) {
145
+ return getRandomPort(host);
146
+ }
147
+ try {
148
+ await checkPort(host, port);
149
+ return port;
150
+ } catch {
151
+ return getRandomPort(host);
152
+ }
153
+ }
154
+ function formatBaseUrl(host, port) {
155
+ const needsBrackets = host.includes(":") && !host.startsWith("[");
156
+ const safeHost = needsBrackets ? `[${host}]` : host;
157
+ return `http://${safeHost}:${port}`;
158
+ }
159
+ function checkPort(host, port) {
160
+ return new Promise((resolve, reject) => {
161
+ const server = import_node_net.default.createServer();
162
+ server.once("error", (error) => {
163
+ server.close();
164
+ reject(error);
165
+ });
166
+ server.listen(port, host, () => {
167
+ server.close(() => resolve());
168
+ });
169
+ });
170
+ }
171
+ function getRandomPort(host) {
172
+ return new Promise((resolve, reject) => {
173
+ const server = import_node_net.default.createServer();
174
+ server.once("error", (error) => {
175
+ server.close();
176
+ reject(error);
177
+ });
178
+ server.listen(0, host, () => {
179
+ const address = server.address();
180
+ if (!address || typeof address === "string") {
181
+ server.close(() => reject(new Error("Failed to resolve port")));
182
+ return;
183
+ }
184
+ const selected = address.port;
185
+ server.close(() => resolve(selected));
186
+ });
187
+ });
188
+ }
189
+
190
+ // src/version.ts
191
+ var import_node_fs = __toESM(require("fs"));
192
+ var import_node_path2 = __toESM(require("path"));
193
+ function getPackageVersion() {
194
+ try {
195
+ const pkgPath = findPackageJsonPath();
196
+ const raw = import_node_fs.default.readFileSync(pkgPath, "utf8");
197
+ const data = JSON.parse(raw);
198
+ return data.version ?? "0.0.0";
199
+ } catch {
200
+ return "0.0.0";
201
+ }
202
+ }
203
+ function findPackageJsonPath() {
204
+ let current = import_node_path2.default.resolve(__dirname);
205
+ for (let i = 0; i < 5; i += 1) {
206
+ const candidate = import_node_path2.default.join(current, "package.json");
207
+ if (import_node_fs.default.existsSync(candidate)) return candidate;
208
+ current = import_node_path2.default.dirname(current);
209
+ }
210
+ return import_node_path2.default.join(process.cwd(), "package.json");
211
+ }
212
+
213
+ // src/server.ts
214
+ var REQUIRED_FIELDS = ["timestamp", "message", "sessionId", "runId", "hypothesisId"];
215
+ var CORS_HEADERS = {
216
+ "Access-Control-Allow-Origin": "*",
217
+ "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
218
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Debug-Token",
219
+ "Access-Control-Max-Age": "86400"
220
+ };
221
+ async function startServer(options) {
222
+ const logsDir = resolveLogsDir({ cwd: options.cwd, logsDir: options.logsDir });
223
+ const host = options.host;
224
+ const basePath = normalizeBasePath(options.basePath);
225
+ const port = await resolvePort(host, options.port);
226
+ const baseUrl = formatBaseUrl(host, port);
227
+ let idleTimer;
228
+ const server = import_node_http.default.createServer(async (req, res) => {
229
+ applyCorsHeaders(res);
230
+ try {
231
+ const url = new URL(req.url ?? "/", baseUrl);
232
+ const normalizedPath = stripBasePath(url.pathname, basePath);
233
+ if (normalizedPath == null) {
234
+ respondJson(res, 404, { ok: false, error: "not_found" });
235
+ return;
236
+ }
237
+ if (req.method === "OPTIONS") {
238
+ respondNoContent(res, 204);
239
+ return;
240
+ }
241
+ if (req.method === "GET" && normalizedPath === "/health") {
242
+ respondJson(res, 200, { ok: true });
243
+ return;
244
+ }
245
+ if (req.method === "POST" && normalizedPath.startsWith("/ingest/")) {
246
+ const streamId = decodeURIComponent(normalizedPath.replace("/ingest/", ""));
247
+ if (!authorizeRequest(req, options.token)) {
248
+ respondJson(res, 401, { ok: false, error: "unauthorized" });
249
+ return;
250
+ }
251
+ let payload;
252
+ try {
253
+ payload = await readJsonBody(req, options.maxBodyKB);
254
+ } catch (error) {
255
+ if (error instanceof BodyTooLargeError) {
256
+ respondJson(res, 413, { ok: false, error: "body_too_large" });
257
+ return;
258
+ }
259
+ respondJson(res, 400, { ok: false, error: "invalid_json" });
260
+ return;
261
+ }
262
+ if (!payload || typeof payload !== "object") {
263
+ respondJson(res, 400, { ok: false, error: "invalid_body" });
264
+ return;
265
+ }
266
+ const missing = REQUIRED_FIELDS.filter((field) => !(field in payload));
267
+ if (missing.length > 0) {
268
+ respondJson(res, 400, { ok: false, error: "missing_fields", missing });
269
+ return;
270
+ }
271
+ const event = payload;
272
+ const sessionId = String(event.sessionId);
273
+ const runId = String(event.runId);
274
+ const timestamp = event.timestamp;
275
+ const logPath = getSessionLogPath(logsDir, sessionId, runId, timestamp);
276
+ await import_promises2.default.mkdir(import_node_path3.default.dirname(logPath), { recursive: true });
277
+ const enriched = {
278
+ ...event,
279
+ _meta: {
280
+ streamId,
281
+ receivedAt: Date.now()
282
+ }
283
+ };
284
+ await import_promises2.default.appendFile(logPath, `${JSON.stringify(enriched)}
285
+ `, "utf8");
286
+ if (options.idleTtlMs && options.idleTtlMs > 0) {
287
+ resetIdleTimer(options.idleTtlMs);
288
+ }
289
+ respondJson(res, 200, { ok: true, written: true, path: logPath });
290
+ return;
291
+ }
292
+ respondJson(res, 404, { ok: false, error: "not_found" });
293
+ } catch {
294
+ respondJson(res, 500, { ok: false, error: "internal_error" });
295
+ }
296
+ });
297
+ const close = async () => {
298
+ if (idleTimer) clearTimeout(idleTimer);
299
+ await new Promise((resolve, reject) => {
300
+ server.close((err) => {
301
+ if (err) reject(err);
302
+ else resolve();
303
+ });
304
+ });
305
+ };
306
+ await new Promise((resolve, reject) => {
307
+ server.once("error", reject);
308
+ server.listen(port, host, () => resolve());
309
+ });
310
+ const runtime = {
311
+ ok: true,
312
+ version: getPackageVersion(),
313
+ pid: process.pid,
314
+ cwd: options.cwd,
315
+ logsDir,
316
+ running: true,
317
+ server: {
318
+ host,
319
+ port,
320
+ baseUrl,
321
+ basePath,
322
+ endpoints: {
323
+ health: `${basePath}/health`,
324
+ ingestTemplate: `${basePath}/ingest/{streamId}`
325
+ }
326
+ },
327
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
328
+ options: {
329
+ maxBodyKB: options.maxBodyKB,
330
+ tokenEnabled: Boolean(options.token),
331
+ idleTtlMs: options.idleTtlMs
332
+ }
333
+ };
334
+ await writeRuntime(logsDir, runtime);
335
+ await writePid(logsDir, process.pid);
336
+ if (options.idleTtlMs && options.idleTtlMs > 0) {
337
+ resetIdleTimer(options.idleTtlMs);
338
+ }
339
+ return { server, host, port, basePath, baseUrl, logsDir, close };
340
+ function resetIdleTimer(ttl) {
341
+ if (idleTimer) clearTimeout(idleTimer);
342
+ idleTimer = setTimeout(async () => {
343
+ await close();
344
+ await markStopped(logsDir);
345
+ await removePid(logsDir);
346
+ process.exit(0);
347
+ }, ttl);
348
+ }
349
+ }
350
+ function normalizeBasePath(basePath) {
351
+ if (!basePath || basePath === "/") return "";
352
+ const trimmed = basePath.startsWith("/") ? basePath : `/${basePath}`;
353
+ return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
354
+ }
355
+ function stripBasePath(pathname, basePath) {
356
+ if (!basePath) return pathname;
357
+ if (!pathname.startsWith(basePath)) return null;
358
+ const sliced = pathname.slice(basePath.length);
359
+ return sliced.length === 0 ? "/" : sliced;
360
+ }
361
+ function authorizeRequest(req, token) {
362
+ if (!token) return true;
363
+ const authHeader = req.headers.authorization;
364
+ if (authHeader && authHeader === `Bearer ${token}`) return true;
365
+ const headerToken = req.headers["x-debug-token"];
366
+ if (typeof headerToken === "string" && headerToken === token) return true;
367
+ return false;
368
+ }
369
+ function respondJson(res, status, payload) {
370
+ const data = JSON.stringify(payload);
371
+ res.statusCode = status;
372
+ res.setHeader("Content-Type", "application/json");
373
+ res.setHeader("Content-Length", Buffer.byteLength(data));
374
+ res.end(data);
375
+ }
376
+ function respondNoContent(res, status) {
377
+ res.statusCode = status;
378
+ res.end();
379
+ }
380
+ function applyCorsHeaders(res) {
381
+ for (const [key, value] of Object.entries(CORS_HEADERS)) {
382
+ res.setHeader(key, value);
383
+ }
384
+ }
385
+ var BodyTooLargeError = class extends Error {
386
+ };
387
+ function readJsonBody(req, maxBodyKB) {
388
+ const maxBytes = maxBodyKB * 1024;
389
+ return new Promise((resolve, reject) => {
390
+ const chunks = [];
391
+ let size = 0;
392
+ let finished = false;
393
+ req.on("data", (chunk) => {
394
+ if (finished) return;
395
+ size += chunk.length;
396
+ if (size > maxBytes) {
397
+ finished = true;
398
+ req.destroy();
399
+ reject(new BodyTooLargeError());
400
+ return;
401
+ }
402
+ chunks.push(chunk);
403
+ });
404
+ req.on("end", () => {
405
+ if (finished) return;
406
+ finished = true;
407
+ try {
408
+ const raw = Buffer.concat(chunks).toString("utf8");
409
+ resolve(raw.length === 0 ? {} : JSON.parse(raw));
410
+ } catch (error) {
411
+ reject(error);
412
+ }
413
+ });
414
+ req.on("error", (error) => {
415
+ if (finished) return;
416
+ finished = true;
417
+ reject(error);
418
+ });
419
+ });
420
+ }
421
+
422
+ // src/cli.ts
423
+ var args = (0, import_minimist.default)(process.argv.slice(2), {
424
+ boolean: ["json", "force", "help", "user"],
425
+ string: ["host", "base-path", "logs-dir", "token"],
426
+ alias: {
427
+ h: "help",
428
+ u: "user"
429
+ }
430
+ });
431
+ var primary = String(args._[0] ?? "help");
432
+ var secondary = args._[1] ? String(args._[1]) : void 0;
433
+ var scope = primary === "server" || primary === "codex" ? primary : "server";
434
+ var command = scope === "server" ? String(primary === "server" ? secondary ?? "help" : primary) : String(secondary ?? "help");
435
+ var json = Boolean(args.json);
436
+ var SKILL_NAME = "code-debug-skill";
437
+ void main().catch((error) => {
438
+ outputError(error instanceof Error ? error.message : "unexpected_error");
439
+ process.exitCode = 1;
440
+ });
441
+ async function main() {
442
+ if (args.help || command === "help") {
443
+ printHelp();
444
+ return;
445
+ }
446
+ if (scope === "codex") {
447
+ await handleCodex(command);
448
+ return;
449
+ }
450
+ switch (command) {
451
+ case "start":
452
+ await handleStart();
453
+ return;
454
+ case "run":
455
+ if (!process.env.DEBUGSK_CHILD) {
456
+ outputError("use_start_command");
457
+ return;
458
+ }
459
+ await handleRun();
460
+ return;
461
+ case "status":
462
+ await handleStatus();
463
+ return;
464
+ case "stop":
465
+ await handleStop();
466
+ return;
467
+ default:
468
+ outputError(`unknown_command:${command}`);
469
+ }
470
+ }
471
+ function getCommonOptions() {
472
+ const host = String(args.host ?? DEFAULT_HOST);
473
+ const port = parseNumber(args.port, DEFAULT_PORT) ?? DEFAULT_PORT;
474
+ const basePath = String(args["base-path"] ?? DEFAULT_BASE_PATH);
475
+ const logsDir = args["logs-dir"] ? String(args["logs-dir"]) : void 0;
476
+ const maxBodyKB = parseNumber(args["max-body-kb"], DEFAULT_MAX_BODY_KB) ?? DEFAULT_MAX_BODY_KB;
477
+ const token = args.token ? String(args.token) : void 0;
478
+ const idleTtlMs = parseNumber(args["idle-ttl-ms"], void 0);
479
+ return { host, port, basePath, logsDir, maxBodyKB, token, idleTtlMs };
480
+ }
481
+ async function handleStart() {
482
+ const { host, port, basePath, logsDir, maxBodyKB, token, idleTtlMs } = getCommonOptions();
483
+ const cwd = process.cwd();
484
+ const resolvedLogsDir = resolveLogsDir({ cwd, logsDir });
485
+ const runtime = await readRuntime(resolvedLogsDir);
486
+ if (runtime && !runtime.options) {
487
+ outputLegacyRuntimeError();
488
+ return;
489
+ }
490
+ if (runtime && isProcessAlive(runtime.pid) && !args.force) {
491
+ const payload2 = buildStartOutput(runtime, true);
492
+ outputStart(payload2);
493
+ return;
494
+ }
495
+ if (runtime && isProcessAlive(runtime.pid) && args.force) {
496
+ await terminateProcess(runtime.pid, 3e3);
497
+ await markStopped(resolvedLogsDir);
498
+ await removePid(resolvedLogsDir);
499
+ }
500
+ if (runtime && !isProcessAlive(runtime.pid)) {
501
+ await markStopped(resolvedLogsDir);
502
+ await removePid(resolvedLogsDir);
503
+ }
504
+ const spawnResult = await spawnBackground({
505
+ host,
506
+ port,
507
+ basePath,
508
+ logsDir,
509
+ maxBodyKB,
510
+ token,
511
+ idleTtlMs
512
+ });
513
+ const finalRuntime = await waitForRuntime(resolvedLogsDir, 5e3);
514
+ if (!finalRuntime) {
515
+ outputError("start_failed");
516
+ return;
517
+ }
518
+ const ok = await waitForHealth(finalRuntime, 5e3);
519
+ if (!ok) {
520
+ outputError("health_check_failed");
521
+ return;
522
+ }
523
+ const payload = buildStartOutput(finalRuntime, false, spawnResult?.pid);
524
+ outputStart(payload);
525
+ }
526
+ async function handleRun() {
527
+ const { host, port, basePath, logsDir, maxBodyKB, token, idleTtlMs } = getCommonOptions();
528
+ const cwd = process.cwd();
529
+ const started = await startServer({
530
+ host,
531
+ port,
532
+ basePath,
533
+ logsDir,
534
+ maxBodyKB,
535
+ token,
536
+ idleTtlMs,
537
+ cwd
538
+ });
539
+ installSignalHandlers(started.logsDir, started.close);
540
+ if (!json) {
541
+ logInfo(`debugsk server listening at ${started.baseUrl}`);
542
+ }
543
+ }
544
+ async function handleStatus() {
545
+ const cwd = process.cwd();
546
+ const logsDir = args["logs-dir"] ? String(args["logs-dir"]) : void 0;
547
+ const resolvedLogsDir = resolveLogsDir({ cwd, logsDir });
548
+ const runtime = await readRuntime(resolvedLogsDir);
549
+ if (!runtime) {
550
+ outputStatus({ ok: false, running: false, error: "not_running" });
551
+ return;
552
+ }
553
+ if (!runtime.options) {
554
+ outputLegacyRuntimeError();
555
+ return;
556
+ }
557
+ const running = isProcessAlive(runtime.pid);
558
+ const payload = {
559
+ ...runtime,
560
+ running
561
+ };
562
+ outputStatus(payload);
563
+ }
564
+ async function handleStop() {
565
+ const cwd = process.cwd();
566
+ const logsDir = args["logs-dir"] ? String(args["logs-dir"]) : void 0;
567
+ const resolvedLogsDir = resolveLogsDir({ cwd, logsDir });
568
+ const runtime = await readRuntime(resolvedLogsDir);
569
+ if (!runtime) {
570
+ outputStop({ ok: true, stopped: false, running: false });
571
+ return;
572
+ }
573
+ const stopped = await terminateProcess(runtime.pid, 3e3);
574
+ if (stopped) {
575
+ await markStopped(resolvedLogsDir);
576
+ await removePid(resolvedLogsDir);
577
+ }
578
+ outputStop({ ok: true, stopped, pid: runtime.pid });
579
+ }
580
+ async function handleCodex(subcommand) {
581
+ switch (subcommand) {
582
+ case "install":
583
+ await codexInstall(false);
584
+ return;
585
+ case "update":
586
+ await codexInstall(true);
587
+ return;
588
+ case "remove":
589
+ await codexRemove();
590
+ return;
591
+ default:
592
+ outputError(`unknown_command:codex:${subcommand}`);
593
+ }
594
+ }
595
+ async function codexInstall(force) {
596
+ const source = await resolveSkillSource();
597
+ const { dest, scope: scope2 } = await resolveCodexDest({ createIfMissing: true });
598
+ const exists = await pathExists(dest);
599
+ if (exists && !force) {
600
+ outputError("codex_skill_already_installed");
601
+ return;
602
+ }
603
+ await ensureSafeDest(dest);
604
+ if (exists) {
605
+ await import_promises3.default.rm(dest, { recursive: true, force: true });
606
+ }
607
+ await import_promises3.default.mkdir(import_node_path4.default.dirname(dest), { recursive: true });
608
+ await import_promises3.default.cp(source, dest, { recursive: true });
609
+ outputJsonResult({
610
+ ok: true,
611
+ action: force ? "update" : "install",
612
+ scope: scope2,
613
+ dest,
614
+ source,
615
+ replaced: exists
616
+ });
617
+ }
618
+ async function codexRemove() {
619
+ const { dest, scope: scope2, homeExists } = await resolveCodexDest({ createIfMissing: false });
620
+ if (!homeExists) {
621
+ outputJsonResult({
622
+ ok: true,
623
+ action: "remove",
624
+ scope: scope2,
625
+ dest,
626
+ removed: false,
627
+ reason: "codex_home_missing"
628
+ });
629
+ return;
630
+ }
631
+ await ensureSafeDest(dest);
632
+ const exists = await pathExists(dest);
633
+ if (exists) {
634
+ await import_promises3.default.rm(dest, { recursive: true, force: true });
635
+ }
636
+ outputJsonResult({
637
+ ok: true,
638
+ action: "remove",
639
+ scope: scope2,
640
+ dest,
641
+ removed: exists
642
+ });
643
+ }
644
+ async function resolveSkillSource() {
645
+ const packageRoot = import_node_path4.default.resolve(__dirname, "..");
646
+ const repoRoot = import_node_path4.default.resolve(packageRoot, "..", "..");
647
+ const repoSource = import_node_path4.default.join(repoRoot, "skills", SKILL_NAME);
648
+ if (await pathExists(repoSource)) return repoSource;
649
+ const packaged = import_node_path4.default.join(packageRoot, "skills", SKILL_NAME);
650
+ if (await pathExists(packaged)) return packaged;
651
+ throw new Error(`missing_skill_source:${SKILL_NAME}`);
652
+ }
653
+ async function resolveCodexDest({
654
+ createIfMissing
655
+ }) {
656
+ const useUser = Boolean(args.user);
657
+ const scope2 = useUser ? "user" : "local";
658
+ const codexHome = useUser ? import_node_path4.default.join(import_node_os.default.homedir(), ".codex") : import_node_path4.default.join(process.cwd(), ".codex");
659
+ const exists = await pathExists(codexHome);
660
+ if (!exists && createIfMissing) {
661
+ await ensureCodexHome(codexHome, scope2);
662
+ }
663
+ return { dest: import_node_path4.default.join(codexHome, "skills", SKILL_NAME), scope: scope2, homeExists: exists };
664
+ }
665
+ async function ensureSafeDest(dest) {
666
+ const resolved = import_node_path4.default.resolve(dest);
667
+ const root = import_node_path4.default.parse(resolved).root;
668
+ if (resolved === root || resolved === import_node_os.default.homedir()) {
669
+ throw new Error("unsafe_dest");
670
+ }
671
+ }
672
+ async function ensureCodexHome(codexHome, scope2) {
673
+ if (await pathExists(codexHome)) return;
674
+ if (!process.stdin.isTTY) {
675
+ throw new Error("codex_home_missing");
676
+ }
677
+ const prompt = `Create ${codexHome} for ${scope2} scope? [y/N]: `;
678
+ const approved = await confirmPrompt(prompt);
679
+ if (!approved) {
680
+ throw new Error("codex_home_missing");
681
+ }
682
+ await import_promises3.default.mkdir(codexHome, { recursive: true });
683
+ }
684
+ async function confirmPrompt(prompt) {
685
+ const rl = import_promises4.default.createInterface({ input: process.stdin, output: process.stderr });
686
+ const answer = await rl.question(prompt);
687
+ rl.close();
688
+ return /^y(es)?$/i.test(answer.trim());
689
+ }
690
+ async function pathExists(target) {
691
+ try {
692
+ await import_promises3.default.access(target);
693
+ return true;
694
+ } catch {
695
+ return false;
696
+ }
697
+ }
698
+ async function spawnBackground(options) {
699
+ const nodeArgs = [...process.execArgv, process.argv[1]];
700
+ const cmdArgs = ["run"];
701
+ cmdArgs.push("--host", options.host);
702
+ cmdArgs.push("--port", String(options.port));
703
+ cmdArgs.push("--base-path", options.basePath);
704
+ cmdArgs.push("--max-body-kb", String(options.maxBodyKB));
705
+ if (options.logsDir) cmdArgs.push("--logs-dir", options.logsDir);
706
+ if (options.token) cmdArgs.push("--token", options.token);
707
+ if (options.idleTtlMs) cmdArgs.push("--idle-ttl-ms", String(options.idleTtlMs));
708
+ const spawn = await import("child_process");
709
+ const child = spawn.spawn(process.execPath, [...nodeArgs, ...cmdArgs], {
710
+ detached: true,
711
+ stdio: "ignore",
712
+ env: {
713
+ ...process.env,
714
+ DEBUGSK_CHILD: "1"
715
+ }
716
+ });
717
+ child.unref();
718
+ return child.pid ? { pid: child.pid } : null;
719
+ }
720
+ async function waitForRuntime(logsDir, timeoutMs) {
721
+ const start = Date.now();
722
+ while (Date.now() - start < timeoutMs) {
723
+ const runtime = await readRuntime(logsDir);
724
+ if (runtime && runtime.pid) return runtime;
725
+ await sleep2(100);
726
+ }
727
+ return null;
728
+ }
729
+ async function waitForHealth(runtime, timeoutMs) {
730
+ const url = new URL(runtime.server.endpoints.health, runtime.server.baseUrl);
731
+ const start = Date.now();
732
+ while (Date.now() - start < timeoutMs) {
733
+ const ok = await checkHealth(url);
734
+ if (ok) return true;
735
+ await sleep2(100);
736
+ }
737
+ return false;
738
+ }
739
+ async function checkHealth(url) {
740
+ const http2 = await import("http");
741
+ return new Promise((resolve) => {
742
+ const req = http2.get(url, (res) => {
743
+ res.resume();
744
+ resolve(res.statusCode === 200);
745
+ });
746
+ req.on("error", () => resolve(false));
747
+ });
748
+ }
749
+ function buildStartOutput(runtime, existing, spawnedPid) {
750
+ const ingestPath = runtime.server.endpoints.ingestTemplate;
751
+ const ingestUrl = new URL(ingestPath.replace("{streamId}", "{streamId}"), runtime.server.baseUrl);
752
+ const snippetHeaders = {
753
+ "Content-Type": "application/json"
754
+ };
755
+ const tokenEnabled = runtime.options.tokenEnabled;
756
+ if (tokenEnabled) {
757
+ snippetHeaders.Authorization = "Bearer YOUR_TOKEN";
758
+ }
759
+ const jsFetch = `fetch("${ingestUrl.toString()}",{method:"POST",headers:${JSON.stringify(
760
+ snippetHeaders
761
+ )},body:JSON.stringify({...})}).catch(()=>{});`;
762
+ return {
763
+ ok: true,
764
+ version: runtime.version,
765
+ pid: runtime.pid,
766
+ cwd: runtime.cwd,
767
+ logsDir: runtime.logsDir,
768
+ server: {
769
+ host: runtime.server.host,
770
+ port: runtime.server.port,
771
+ baseUrl: runtime.server.baseUrl,
772
+ endpoints: runtime.server.endpoints
773
+ },
774
+ snippets: {
775
+ jsFetch
776
+ },
777
+ existing,
778
+ spawnedPid
779
+ };
780
+ }
781
+ function installSignalHandlers(logsDir, close) {
782
+ const shutdown = async () => {
783
+ await close();
784
+ await markStopped(logsDir);
785
+ await removePid(logsDir);
786
+ process.exit(0);
787
+ };
788
+ process.on("SIGTERM", () => void shutdown());
789
+ process.on("SIGINT", () => void shutdown());
790
+ }
791
+ function outputJson(payload) {
792
+ process.stdout.write(`${JSON.stringify(payload)}
793
+ `);
794
+ }
795
+ function outputJsonResult(payload) {
796
+ if (json) {
797
+ outputJson(payload);
798
+ return;
799
+ }
800
+ const action = String(payload.action ?? "result");
801
+ const ok = payload.ok ? "ok" : "error";
802
+ const dest = payload.dest ? `dest=${payload.dest}` : "";
803
+ process.stdout.write(`${action} ${ok} ${dest}
804
+ `);
805
+ }
806
+ function outputError(message) {
807
+ if (json) {
808
+ outputJson({ ok: false, error: message });
809
+ } else {
810
+ process.stderr.write(`${message}
811
+ `);
812
+ }
813
+ }
814
+ function outputLegacyRuntimeError() {
815
+ const error = "legacy_runtime_incompatible";
816
+ const hint = "delete .logs/runtime.json or the entire .logs directory, then retry";
817
+ if (json) {
818
+ outputJson({ ok: false, error, hint });
819
+ } else {
820
+ process.stderr.write(`${error}: ${hint}
821
+ `);
822
+ }
823
+ }
824
+ function logInfo(message) {
825
+ process.stderr.write(`${message}
826
+ `);
827
+ }
828
+ function outputStart(payload) {
829
+ if (json) {
830
+ outputJson(payload);
831
+ return;
832
+ }
833
+ const server = payload.server;
834
+ const pid = payload.pid;
835
+ const logsDir = payload.logsDir;
836
+ process.stdout.write(
837
+ `started pid=${pid} baseUrl=${server.baseUrl} logsDir=${logsDir}
838
+ `
839
+ );
840
+ }
841
+ function outputStatus(payload) {
842
+ if (json) {
843
+ outputJson(payload);
844
+ return;
845
+ }
846
+ const running = payload.running ? "running" : "stopped";
847
+ const pid = payload.pid ? `pid=${payload.pid}` : "pid=unknown";
848
+ process.stdout.write(`status ${running} ${pid}
849
+ `);
850
+ }
851
+ function outputStop(payload) {
852
+ if (json) {
853
+ outputJson(payload);
854
+ return;
855
+ }
856
+ const stopped = payload.stopped ? "stopped" : "not-stopped";
857
+ const pid = payload.pid ? `pid=${payload.pid}` : "";
858
+ process.stdout.write(`stop ${stopped} ${pid}
859
+ `);
860
+ }
861
+ function printHelp() {
862
+ const text = `debugsk
863
+
864
+ Usage:
865
+ server start --json Start server in background and print JSON
866
+ server status --json Show server status
867
+ server stop --json Stop background server
868
+
869
+ codex install Install Codex skill (user scope)
870
+ codex update Update Codex skill (user scope)
871
+ codex remove Remove Codex skill (user scope)
872
+
873
+ Aliases:
874
+ start/status/stop Same as "server <command>"
875
+
876
+ Options:
877
+ --host (default ${DEFAULT_HOST})
878
+ --port (default ${DEFAULT_PORT})
879
+ --base-path (default ${DEFAULT_BASE_PATH})
880
+ --logs-dir (default ${DEFAULT_LOGS_DIR})
881
+ --max-body-kb (default ${DEFAULT_MAX_BODY_KB})
882
+ --token (optional)
883
+ --idle-ttl-ms (optional)
884
+ --force (restart even if already running)
885
+ --json (stdout JSON only)
886
+ -u, --user (install Codex skill to user scope)
887
+ `;
888
+ if (json) {
889
+ outputJson({ ok: true, help: text });
890
+ } else {
891
+ process.stdout.write(`${text}
892
+ `);
893
+ }
894
+ }
895
+ function parseNumber(value, fallback) {
896
+ if (value === void 0 || value === null || value === "") {
897
+ return fallback;
898
+ }
899
+ const parsed = Number(value);
900
+ if (Number.isFinite(parsed)) return parsed;
901
+ return fallback;
902
+ }
903
+ function sleep2(ms) {
904
+ return new Promise((resolve) => setTimeout(resolve, ms));
905
+ }
906
+ //# sourceMappingURL=cli.js.map