debuganything 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +15 -0
- package/DISCLAIMER.md +101 -0
- package/GEMINI.md +97 -0
- package/LICENSE +21 -0
- package/README.md +283 -2
- package/assets/README.md +9 -0
- package/assets/debuganything-logo.png +0 -0
- package/assets/debuganything-mark-512.png +0 -0
- package/dist/chunk-32IDKAZK.js +9 -0
- package/dist/chunk-32IDKAZK.js.map +1 -0
- package/dist/chunk-5BXTDASF.js +135 -0
- package/dist/chunk-5BXTDASF.js.map +1 -0
- package/dist/chunk-5RAKTYW5.js +292 -0
- package/dist/chunk-5RAKTYW5.js.map +1 -0
- package/dist/chunk-7MMXFUUN.js +79 -0
- package/dist/chunk-7MMXFUUN.js.map +1 -0
- package/dist/chunk-D742WJPT.js +436 -0
- package/dist/chunk-D742WJPT.js.map +1 -0
- package/dist/chunk-EMR42ZJQ.js +102 -0
- package/dist/chunk-EMR42ZJQ.js.map +1 -0
- package/dist/chunk-MLDOO673.js +1416 -0
- package/dist/chunk-MLDOO673.js.map +1 -0
- package/dist/chunk-NFL22T3H.js +309 -0
- package/dist/chunk-NFL22T3H.js.map +1 -0
- package/dist/chunk-STFC7C5C.js +245 -0
- package/dist/chunk-STFC7C5C.js.map +1 -0
- package/dist/chunk-VGYXHXTY.js +1015 -0
- package/dist/chunk-VGYXHXTY.js.map +1 -0
- package/dist/chunk-YY6M5RIJ.js +215 -0
- package/dist/chunk-YY6M5RIJ.js.map +1 -0
- package/dist/cli.js +254 -0
- package/dist/cli.js.map +1 -0
- package/dist/docker-ORXYMES4.js +23 -0
- package/dist/docker-ORXYMES4.js.map +1 -0
- package/dist/hook-QYLLBUNK.js +14 -0
- package/dist/hook-QYLLBUNK.js.map +1 -0
- package/dist/import-CT55AEEE.js +268 -0
- package/dist/import-CT55AEEE.js.map +1 -0
- package/dist/investigate-2GFD6EWB.js +102 -0
- package/dist/investigate-2GFD6EWB.js.map +1 -0
- package/dist/lsof-GBLZJBJR.js +22 -0
- package/dist/lsof-GBLZJBJR.js.map +1 -0
- package/dist/psmem-UB3VCMG3.js +20 -0
- package/dist/psmem-UB3VCMG3.js.map +1 -0
- package/dist/report_html-2ENM7HLC.js +99 -0
- package/dist/report_html-2ENM7HLC.js.map +1 -0
- package/dist/server.js +190 -0
- package/dist/server.js.map +1 -0
- package/dist/setup-NTPRUTMT.js +265 -0
- package/dist/setup-NTPRUTMT.js.map +1 -0
- package/dist/target-WJE4WFYA.js +36 -0
- package/dist/target-WJE4WFYA.js.map +1 -0
- package/gemini-extension.json +9 -0
- package/hooks/debuganything-hook.mjs +36 -0
- package/hooks/hooks.json +10 -0
- package/package.json +85 -3
- package/server.json +30 -0
- package/skills/debug-anything/SKILL.md +102 -0
- package/skills/inspect-product/SKILL.md +138 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
assertToolResult
|
|
4
|
+
} from "./chunk-D742WJPT.js";
|
|
5
|
+
import {
|
|
6
|
+
capPayload,
|
|
7
|
+
fileSize,
|
|
8
|
+
redactDeep
|
|
9
|
+
} from "./chunk-STFC7C5C.js";
|
|
10
|
+
|
|
11
|
+
// src/evidence.ts
|
|
12
|
+
import { writeFile, readFile } from "fs/promises";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
var ResultBuilder = class {
|
|
15
|
+
constructor(tool, session, target) {
|
|
16
|
+
this.tool = tool;
|
|
17
|
+
this.session = session;
|
|
18
|
+
this.target = target;
|
|
19
|
+
}
|
|
20
|
+
tool;
|
|
21
|
+
session;
|
|
22
|
+
target;
|
|
23
|
+
evidence = [];
|
|
24
|
+
findings = [];
|
|
25
|
+
artifacts = [];
|
|
26
|
+
limitations = [];
|
|
27
|
+
suggested = [];
|
|
28
|
+
started = Date.now();
|
|
29
|
+
startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
30
|
+
error;
|
|
31
|
+
get artifactDir() {
|
|
32
|
+
if (!this.session) throw new Error("no session");
|
|
33
|
+
return join(this.session.dir, "artifacts");
|
|
34
|
+
}
|
|
35
|
+
/** Path for a new artifact file inside the session. */
|
|
36
|
+
artifactPath(name) {
|
|
37
|
+
const safe = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
38
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "");
|
|
39
|
+
return join(this.artifactDir, `${stamp}-${this.tool}-${safe}`);
|
|
40
|
+
}
|
|
41
|
+
/** True when this builder runs without a filesystem (browser / analyzeText). */
|
|
42
|
+
get inMemory() {
|
|
43
|
+
return this.session?.dir === ":memory:";
|
|
44
|
+
}
|
|
45
|
+
/** Write a text/JSON artifact and register it. Returns the path. In-memory sessions register without writing. */
|
|
46
|
+
async writeArtifact(name, content, kind, description) {
|
|
47
|
+
const bytes = typeof content === "string" ? new TextEncoder().encode(content).length : content.byteLength;
|
|
48
|
+
if (this.inMemory) {
|
|
49
|
+
const path2 = `(pasted) ${name.replace(/[^A-Za-z0-9._ -]/g, "_")}`;
|
|
50
|
+
this.artifacts.push({ path: path2, kind, bytes, description });
|
|
51
|
+
return path2;
|
|
52
|
+
}
|
|
53
|
+
const path = this.artifactPath(name);
|
|
54
|
+
await writeFile(path, content);
|
|
55
|
+
this.artifacts.push({ path, kind, bytes, description });
|
|
56
|
+
return path;
|
|
57
|
+
}
|
|
58
|
+
/** Register an artifact that was already written by a subprocess. */
|
|
59
|
+
async addArtifact(path, kind, opts = {}) {
|
|
60
|
+
const a = { path, kind, bytes: await fileSize(path), truncated: opts.truncated, description: opts.description };
|
|
61
|
+
this.artifacts.push(a);
|
|
62
|
+
return a;
|
|
63
|
+
}
|
|
64
|
+
/** Add evidence; returns its id for use in findings. */
|
|
65
|
+
evidenceOf(kind, summary, data, opts = {}) {
|
|
66
|
+
const id = `e${this.evidence.length + 1}`;
|
|
67
|
+
this.evidence.push({ id, kind, summary, data, artifact: opts.artifact, lines: opts.lines });
|
|
68
|
+
return id;
|
|
69
|
+
}
|
|
70
|
+
/** Add a finding. Throws if no evidence ids are given — by design. */
|
|
71
|
+
finding(severity, summary, evidenceIds, confidence = "observed", detail) {
|
|
72
|
+
if (evidenceIds.length === 0) throw new Error(`refusing to add finding without evidence: ${summary}`);
|
|
73
|
+
const known = new Set(this.evidence.map((e) => e.id));
|
|
74
|
+
for (const id2 of evidenceIds) if (!known.has(id2)) throw new Error(`finding cites unknown evidence id ${id2}`);
|
|
75
|
+
const id = `f${this.findings.length + 1}`;
|
|
76
|
+
this.findings.push({ id, severity, summary, evidence_ids: evidenceIds, confidence, detail });
|
|
77
|
+
return id;
|
|
78
|
+
}
|
|
79
|
+
get findingCount() {
|
|
80
|
+
return this.findings.length;
|
|
81
|
+
}
|
|
82
|
+
get evidenceCount() {
|
|
83
|
+
return this.evidence.length;
|
|
84
|
+
}
|
|
85
|
+
limitation(msg) {
|
|
86
|
+
if (!this.limitations.includes(msg)) this.limitations.push(msg);
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
next(...tools) {
|
|
90
|
+
for (const t of tools) if (!this.suggested.includes(t)) this.suggested.push(t);
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
fail(message) {
|
|
94
|
+
this.error = message;
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
/** Finalize: redact, cap, validate. */
|
|
98
|
+
build(ok = this.error === void 0) {
|
|
99
|
+
const raw = {
|
|
100
|
+
ok,
|
|
101
|
+
tool: this.tool,
|
|
102
|
+
session_id: this.session?.id ?? "",
|
|
103
|
+
target: this.target,
|
|
104
|
+
duration_ms: Date.now() - this.started,
|
|
105
|
+
findings: this.findings,
|
|
106
|
+
evidence: redactDeep(this.evidence),
|
|
107
|
+
limitations: this.limitations.map((l) => l),
|
|
108
|
+
suggested_next: this.suggested,
|
|
109
|
+
artifacts: this.artifacts,
|
|
110
|
+
error: this.error,
|
|
111
|
+
started_at: this.startedAt
|
|
112
|
+
};
|
|
113
|
+
const capped = capPayload(raw);
|
|
114
|
+
return assertToolResult(capped);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
function failedResult(tool, session, target, error, limitations = [], suggested = []) {
|
|
118
|
+
const b = new ResultBuilder(tool, session, target);
|
|
119
|
+
for (const l of limitations) b.limitation(l);
|
|
120
|
+
b.next(...suggested);
|
|
121
|
+
return b.fail(error).build();
|
|
122
|
+
}
|
|
123
|
+
function humanBytes(n) {
|
|
124
|
+
if (n < 1024) return `${n} B`;
|
|
125
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
126
|
+
if (n < 1024 ** 3) return `${(n / 1024 ** 2).toFixed(1)} MB`;
|
|
127
|
+
return `${(n / 1024 ** 3).toFixed(2)} GB`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
ResultBuilder,
|
|
132
|
+
failedResult,
|
|
133
|
+
humanBytes
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=chunk-5BXTDASF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/evidence.ts"],"sourcesContent":["/**\n * Evidence helpers: artifact writer, excerpt/pointer helpers, and a small\n * builder that makes it hard to produce a finding without evidence.\n */\nimport { writeFile, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { Artifact, ArtifactKind, Evidence, EvidenceKind, Finding, Severity, Confidence, Target, ToolResult, Session } from \"./contracts.js\";\nimport { assertToolResult } from \"./contracts.js\";\nimport { capPayload, redactDeep, LIMITS, fileSize } from \"./safety.js\";\n\nexport class ResultBuilder {\n private evidence: Evidence[] = [];\n private findings: Finding[] = [];\n private artifacts: Artifact[] = [];\n readonly limitations: string[] = [];\n readonly suggested: string[] = [];\n private started = Date.now();\n private startedAt = new Date().toISOString();\n private error?: string;\n\n constructor(\n readonly tool: string,\n readonly session: Pick<Session, \"id\" | \"dir\"> | null,\n readonly target: Target,\n ) {}\n\n get artifactDir(): string {\n if (!this.session) throw new Error(\"no session\");\n return join(this.session.dir, \"artifacts\");\n }\n\n /** Path for a new artifact file inside the session. */\n artifactPath(name: string): string {\n const safe = name.replace(/[^A-Za-z0-9._-]/g, \"_\");\n const stamp = new Date().toISOString().replace(/[-:]/g, \"\").replace(/\\.\\d+Z$/, \"\");\n return join(this.artifactDir, `${stamp}-${this.tool}-${safe}`);\n }\n\n /** True when this builder runs without a filesystem (browser / analyzeText). */\n get inMemory(): boolean {\n return this.session?.dir === \":memory:\";\n }\n\n /** Write a text/JSON artifact and register it. Returns the path. In-memory sessions register without writing. */\n async writeArtifact(name: string, content: string | Buffer, kind: ArtifactKind, description?: string): Promise<string> {\n const bytes = typeof content === \"string\" ? new TextEncoder().encode(content).length : content.byteLength;\n if (this.inMemory) {\n const path = `(pasted) ${name.replace(/[^A-Za-z0-9._ -]/g, \"_\")}`;\n this.artifacts.push({ path, kind, bytes, description });\n return path;\n }\n const path = this.artifactPath(name);\n await writeFile(path, content);\n this.artifacts.push({ path, kind, bytes, description });\n return path;\n }\n\n /** Register an artifact that was already written by a subprocess. */\n async addArtifact(path: string, kind: ArtifactKind, opts: { truncated?: boolean; description?: string } = {}): Promise<Artifact> {\n const a: Artifact = { path, kind, bytes: await fileSize(path), truncated: opts.truncated, description: opts.description };\n this.artifacts.push(a);\n return a;\n }\n\n /** Add evidence; returns its id for use in findings. */\n evidenceOf(kind: EvidenceKind, summary: string, data: unknown, opts: { artifact?: string; lines?: [number, number] } = {}): string {\n const id = `e${this.evidence.length + 1}`;\n this.evidence.push({ id, kind, summary, data, artifact: opts.artifact, lines: opts.lines });\n return id;\n }\n\n /** Add a finding. Throws if no evidence ids are given — by design. */\n finding(severity: Severity, summary: string, evidenceIds: string[], confidence: Confidence = \"observed\", detail?: string): string {\n if (evidenceIds.length === 0) throw new Error(`refusing to add finding without evidence: ${summary}`);\n const known = new Set(this.evidence.map((e) => e.id));\n for (const id of evidenceIds) if (!known.has(id)) throw new Error(`finding cites unknown evidence id ${id}`);\n const id = `f${this.findings.length + 1}`;\n this.findings.push({ id, severity, summary, evidence_ids: evidenceIds, confidence, detail });\n return id;\n }\n\n get findingCount(): number {\n return this.findings.length;\n }\n\n get evidenceCount(): number {\n return this.evidence.length;\n }\n\n limitation(msg: string): this {\n if (!this.limitations.includes(msg)) this.limitations.push(msg);\n return this;\n }\n\n next(...tools: string[]): this {\n for (const t of tools) if (!this.suggested.includes(t)) this.suggested.push(t);\n return this;\n }\n\n fail(message: string): this {\n this.error = message;\n return this;\n }\n\n /** Finalize: redact, cap, validate. */\n build(ok = this.error === undefined): ToolResult {\n const raw: ToolResult = {\n ok,\n tool: this.tool,\n session_id: this.session?.id ?? \"\",\n target: this.target,\n duration_ms: Date.now() - this.started,\n findings: this.findings,\n evidence: redactDeep(this.evidence),\n limitations: this.limitations.map((l) => l),\n suggested_next: this.suggested,\n artifacts: this.artifacts,\n error: this.error,\n started_at: this.startedAt,\n };\n const capped = capPayload(raw);\n return assertToolResult(capped);\n }\n}\n\n/** Build a failed result quickly (e.g. tool missing). */\nexport function failedResult(tool: string, session: Pick<Session, \"id\" | \"dir\"> | null, target: Target, error: string, limitations: string[] = [], suggested: string[] = []): ToolResult {\n const b = new ResultBuilder(tool, session, target);\n for (const l of limitations) b.limitation(l);\n b.next(...suggested);\n return b.fail(error).build();\n}\n\n/** Read an excerpt of lines [from, to] (1-based inclusive) from a text artifact, capped. */\nexport async function excerpt(path: string, from: number, to: number): Promise<string> {\n const text = await readFile(path, \"utf8\");\n const lines = text.split(\"\\n\");\n const end = Math.min(to, from + LIMITS.MAX_EXCERPT_LINES - 1, lines.length);\n return lines.slice(Math.max(0, from - 1), end).join(\"\\n\");\n}\n\n/** Find line numbers (1-based) in `text` matching `re`; returns [first, last] or undefined. */\nexport function lineRange(text: string, re: RegExp): [number, number] | undefined {\n const lines = text.split(\"\\n\");\n let first = -1;\n let last = -1;\n for (let i = 0; i < lines.length; i++) {\n if (re.test(lines[i]!)) {\n if (first < 0) first = i + 1;\n last = i + 1;\n }\n }\n return first > 0 ? [first, last] : undefined;\n}\n\nexport function humanBytes(n: number): string {\n if (n < 1024) return `${n} B`;\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;\n if (n < 1024 ** 3) return `${(n / 1024 ** 2).toFixed(1)} MB`;\n return `${(n / 1024 ** 3).toFixed(2)} GB`;\n}\n\nexport function pct(part: number, total: number): string {\n if (!total) return \"0%\";\n return `${((100 * part) / total).toFixed(1)}%`;\n}\n"],"mappings":"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","names":["path","id"]}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
platform
|
|
4
|
+
} from "./chunk-D742WJPT.js";
|
|
5
|
+
import {
|
|
6
|
+
assertPid,
|
|
7
|
+
assertSafeIdentifier
|
|
8
|
+
} from "./chunk-STFC7C5C.js";
|
|
9
|
+
|
|
10
|
+
// src/target.ts
|
|
11
|
+
import { readFile, readdir, readlink } from "fs/promises";
|
|
12
|
+
import { existsSync } from "fs";
|
|
13
|
+
import { spawn } from "child_process";
|
|
14
|
+
import { createWriteStream } from "fs";
|
|
15
|
+
import { join } from "path";
|
|
16
|
+
import { execa } from "execa";
|
|
17
|
+
function parsePs(commRaw, argsRaw = "") {
|
|
18
|
+
const args = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const line of argsRaw.split("\n")) {
|
|
20
|
+
const m = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
21
|
+
if (m) args.set(Number(m[1]), m[2].trim());
|
|
22
|
+
}
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const line of commRaw.split("\n")) {
|
|
25
|
+
const t = line.trim();
|
|
26
|
+
if (!t) continue;
|
|
27
|
+
const m = /^(\d+)\s+(\d+)\s+(\S+)\s+([\d.]+)\s+([\d.]+)\s+(\d+)\s+(.*)$/.exec(t);
|
|
28
|
+
if (!m) continue;
|
|
29
|
+
const pid = Number(m[1]);
|
|
30
|
+
const a = args.get(pid) ?? "";
|
|
31
|
+
let comm = basename(m[7].trim());
|
|
32
|
+
const argv0 = basename(a.split(" ")[0] ?? "");
|
|
33
|
+
if (argv0 && argv0.length > comm.length && argv0.startsWith(comm)) comm = argv0;
|
|
34
|
+
out.push({ pid, ppid: Number(m[2]), user: m[3], cpu: Number(m[4]), mem: Number(m[5]), rss_kb: Number(m[6]), comm, args: a });
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
var COMM_COL = process.platform === "darwin" ? "ucomm=" : "comm=";
|
|
39
|
+
function basename(p) {
|
|
40
|
+
const i = p.lastIndexOf("/");
|
|
41
|
+
return i >= 0 ? p.slice(i + 1) : p;
|
|
42
|
+
}
|
|
43
|
+
async function listProcesses(filter, limit = 100) {
|
|
44
|
+
if (platform() === "win32") throw new Error("list_processes is not implemented on Windows yet");
|
|
45
|
+
const [r1, r2] = await Promise.all([
|
|
46
|
+
execa("ps", ["-axo", `pid=,ppid=,user=,%cpu=,%mem=,rss=,${COMM_COL}`], { reject: false, timeout: 1e4, maxBuffer: 64 * 1024 * 1024 }),
|
|
47
|
+
execa("ps", ["-axo", "pid=,args="], { reject: false, timeout: 1e4, maxBuffer: 64 * 1024 * 1024 })
|
|
48
|
+
]);
|
|
49
|
+
let procs = parsePs(r1.stdout, r2.stdout);
|
|
50
|
+
if (platform() === "linux") await annotateContainers(procs);
|
|
51
|
+
if (filter) {
|
|
52
|
+
const f = filter.toLowerCase();
|
|
53
|
+
procs = procs.filter((p) => p.comm.toLowerCase().includes(f) || p.args.toLowerCase().includes(f) || p.user.toLowerCase().includes(f) || String(p.pid) === f);
|
|
54
|
+
}
|
|
55
|
+
procs.sort((a, b) => b.cpu - a.cpu || b.rss_kb - a.rss_kb);
|
|
56
|
+
return procs.slice(0, limit);
|
|
57
|
+
}
|
|
58
|
+
function containerIdFromCgroup(cgroup) {
|
|
59
|
+
const m = /(?:docker|cri-containerd|containerd|libpod|podman)[-/]([0-9a-f]{12,64})/.exec(cgroup) ?? /\/([0-9a-f]{64})(?:\.scope)?$/m.exec(cgroup);
|
|
60
|
+
return m?.[1]?.slice(0, 12);
|
|
61
|
+
}
|
|
62
|
+
async function annotateContainers(procs) {
|
|
63
|
+
await Promise.all(
|
|
64
|
+
procs.map(async (p) => {
|
|
65
|
+
try {
|
|
66
|
+
const cg = await readFile(`/proc/${p.pid}/cgroup`, "utf8");
|
|
67
|
+
const id = containerIdFromCgroup(cg);
|
|
68
|
+
if (id) p.container = id;
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
async function processInfo(pid) {
|
|
75
|
+
const [r1, r2] = await Promise.all([
|
|
76
|
+
execa("ps", ["-o", `pid=,ppid=,user=,%cpu=,%mem=,rss=,${COMM_COL}`, "-p", String(pid)], { reject: false, timeout: 5e3 }),
|
|
77
|
+
execa("ps", ["-o", "pid=,args=", "-p", String(pid)], { reject: false, timeout: 5e3 })
|
|
78
|
+
]);
|
|
79
|
+
return parsePs(r1.stdout, r2.stdout)[0];
|
|
80
|
+
}
|
|
81
|
+
async function descendantPids(pid) {
|
|
82
|
+
const r = await execa("ps", ["-axo", "pid=,ppid="], { reject: false, timeout: 5e3 });
|
|
83
|
+
const children = /* @__PURE__ */ new Map();
|
|
84
|
+
for (const line of r.stdout.split("\n")) {
|
|
85
|
+
const m = /^\s*(\d+)\s+(\d+)/.exec(line);
|
|
86
|
+
if (!m) continue;
|
|
87
|
+
const c = Number(m[1]);
|
|
88
|
+
const p = Number(m[2]);
|
|
89
|
+
if (!children.has(p)) children.set(p, []);
|
|
90
|
+
children.get(p).push(c);
|
|
91
|
+
}
|
|
92
|
+
const out = [];
|
|
93
|
+
const stack = [pid];
|
|
94
|
+
while (stack.length) {
|
|
95
|
+
const cur = stack.pop();
|
|
96
|
+
for (const c of children.get(cur) ?? []) {
|
|
97
|
+
out.push(c);
|
|
98
|
+
stack.push(c);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
function detectRuntime(comm, args) {
|
|
104
|
+
const s = `${comm} ${args}`.toLowerCase();
|
|
105
|
+
if (/(^|\/|\s)python[\d.]*(\s|$)|uvicorn|gunicorn|celery|flask|django|\.py(\s|$)/.test(s)) return "python";
|
|
106
|
+
if (/(^|\/|\s)(node|nodejs|bun|deno|tsx|ts-node)(\s|$)|\.m?[jt]s(\s|$)|next-server|electron/.test(s)) return "node";
|
|
107
|
+
if (/(^|\/|\s)java(\s|$)|\.jar(\s|$)|jvm/.test(s)) return "java";
|
|
108
|
+
if (/(^|\/|\s)go(\s|$)|\/go\/bin\//.test(s)) return "go";
|
|
109
|
+
if (comm) return "native";
|
|
110
|
+
return "unknown";
|
|
111
|
+
}
|
|
112
|
+
function guessKind(target, kind) {
|
|
113
|
+
if (kind) return kind;
|
|
114
|
+
if (/^\d+$/.test(target.trim())) return "pid";
|
|
115
|
+
if (target.includes(" ") || target.startsWith("./") || target.startsWith("/")) return "command";
|
|
116
|
+
if (/^[a-z0-9-]+\/[a-z0-9.-]+$/i.test(target) && !target.includes(":")) return "pod";
|
|
117
|
+
return "container";
|
|
118
|
+
}
|
|
119
|
+
async function resolveTarget(input, opts = {}) {
|
|
120
|
+
const kind = guessKind(input, opts.kind);
|
|
121
|
+
switch (kind) {
|
|
122
|
+
case "pid":
|
|
123
|
+
return resolvePid(Number(input.trim()));
|
|
124
|
+
case "container":
|
|
125
|
+
return resolveContainer(input.trim());
|
|
126
|
+
case "pod":
|
|
127
|
+
return resolvePod(input.trim());
|
|
128
|
+
case "command":
|
|
129
|
+
return spawnCommand(input, opts.captureDir);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function resolvePid(pid, extra = {}) {
|
|
133
|
+
assertPid(pid);
|
|
134
|
+
const info = await processInfo(pid);
|
|
135
|
+
if (!info) throw new Error(`no such process: ${pid}`);
|
|
136
|
+
const pids = await descendantPids(pid);
|
|
137
|
+
let cmdline = info.args;
|
|
138
|
+
if (platform() === "linux") {
|
|
139
|
+
try {
|
|
140
|
+
cmdline = (await readFile(`/proc/${pid}/cmdline`, "utf8")).replace(/\0/g, " ").trim() || cmdline;
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
kind: "pid",
|
|
146
|
+
id: String(pid),
|
|
147
|
+
pid,
|
|
148
|
+
comm: info.comm,
|
|
149
|
+
cmdline,
|
|
150
|
+
pids,
|
|
151
|
+
runtime: detectRuntime(info.comm, cmdline),
|
|
152
|
+
host: "local",
|
|
153
|
+
...extra
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function resolveContainer(name) {
|
|
157
|
+
assertSafeIdentifier(name, "container name");
|
|
158
|
+
const { dockerInspectForAttach } = await import("./docker-ORXYMES4.js");
|
|
159
|
+
const info = await dockerInspectForAttach(name);
|
|
160
|
+
if (!info) throw new Error(`container not found: ${name} (docker inspect returned nothing)`);
|
|
161
|
+
if (!info.pid) throw new Error(`container ${name} is not running (no host pid)`);
|
|
162
|
+
const canSeeHostPid = platform() === "linux" && existsSync(`/proc/${info.pid}`);
|
|
163
|
+
const base = {
|
|
164
|
+
kind: "container",
|
|
165
|
+
id: name,
|
|
166
|
+
pid: canSeeHostPid ? info.pid : void 0,
|
|
167
|
+
comm: info.comm,
|
|
168
|
+
cmdline: info.cmdline,
|
|
169
|
+
runtime: detectRuntime(info.comm ?? "", info.cmdline ?? ""),
|
|
170
|
+
host: "local",
|
|
171
|
+
container: { runtime: "docker", id: info.id, name: info.name, image: info.image }
|
|
172
|
+
};
|
|
173
|
+
if (canSeeHostPid) {
|
|
174
|
+
try {
|
|
175
|
+
base.pids = await descendantPids(info.pid);
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return base;
|
|
180
|
+
}
|
|
181
|
+
async function resolvePod(ref) {
|
|
182
|
+
assertSafeIdentifier(ref, "pod reference");
|
|
183
|
+
const [ns, name] = ref.includes("/") ? ref.split("/", 2) : ["default", ref];
|
|
184
|
+
const r = await execa("kubectl", ["get", "pod", name, "-n", ns, "-o", "jsonpath={.status.containerStatuses[0].containerID}"], { reject: false, timeout: 1e4 });
|
|
185
|
+
if (r.exitCode !== 0) throw new Error(`kubectl get pod failed: ${r.stderr.trim() || "kubectl not available"}`);
|
|
186
|
+
const containerId = r.stdout.trim().replace(/^[a-z-]+:\/\//, "");
|
|
187
|
+
if (!containerId) throw new Error(`pod ${ref} has no running container`);
|
|
188
|
+
const t = await resolveContainer(containerId.slice(0, 12)).catch(() => void 0);
|
|
189
|
+
return {
|
|
190
|
+
kind: "pod",
|
|
191
|
+
id: ref,
|
|
192
|
+
pid: t?.pid,
|
|
193
|
+
pids: t?.pids,
|
|
194
|
+
comm: t?.comm,
|
|
195
|
+
cmdline: t?.cmdline,
|
|
196
|
+
runtime: t?.runtime,
|
|
197
|
+
host: "local",
|
|
198
|
+
container: { runtime: "k8s", id: containerId.slice(0, 12), name, namespace: ns }
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function spawnCommand(cmdline, captureDir) {
|
|
202
|
+
const argv = splitArgs(cmdline);
|
|
203
|
+
if (argv.length === 0) throw new Error("empty command");
|
|
204
|
+
const dir = captureDir ?? process.cwd();
|
|
205
|
+
const stdoutPath = join(dir, "stdout.log");
|
|
206
|
+
const stderrPath = join(dir, "stderr.log");
|
|
207
|
+
const out = createWriteStream(stdoutPath);
|
|
208
|
+
const err = createWriteStream(stderrPath);
|
|
209
|
+
const child = spawn(argv[0], argv.slice(1), { stdio: ["ignore", "pipe", "pipe"], detached: true, env: process.env });
|
|
210
|
+
child.stdout.pipe(out);
|
|
211
|
+
child.stderr.pipe(err);
|
|
212
|
+
child.stdout.pipe(process.stderr);
|
|
213
|
+
child.stderr.pipe(process.stderr);
|
|
214
|
+
child.unref();
|
|
215
|
+
if (!child.pid) throw new Error(`failed to spawn: ${cmdline}`);
|
|
216
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
217
|
+
const info = await processInfo(child.pid);
|
|
218
|
+
const comm = info?.comm ?? basename(argv[0]);
|
|
219
|
+
return {
|
|
220
|
+
kind: "command",
|
|
221
|
+
id: cmdline,
|
|
222
|
+
pid: child.pid,
|
|
223
|
+
comm,
|
|
224
|
+
cmdline,
|
|
225
|
+
pids: await descendantPids(child.pid),
|
|
226
|
+
runtime: detectRuntime(comm, cmdline),
|
|
227
|
+
host: "local",
|
|
228
|
+
spawned: { pid: child.pid, stdout: stdoutPath, stderr: stderrPath }
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function splitArgs(s) {
|
|
232
|
+
const out = [];
|
|
233
|
+
let cur = "";
|
|
234
|
+
let quote = null;
|
|
235
|
+
let has = false;
|
|
236
|
+
for (let i = 0; i < s.length; i++) {
|
|
237
|
+
const c = s[i];
|
|
238
|
+
if (quote) {
|
|
239
|
+
if (c === quote) quote = null;
|
|
240
|
+
else if (c === "\\" && quote === '"' && i + 1 < s.length) cur += s[++i];
|
|
241
|
+
else cur += c;
|
|
242
|
+
} else if (c === '"' || c === "'") {
|
|
243
|
+
quote = c;
|
|
244
|
+
has = true;
|
|
245
|
+
} else if (/\s/.test(c)) {
|
|
246
|
+
if (has || cur) out.push(cur);
|
|
247
|
+
cur = "";
|
|
248
|
+
has = false;
|
|
249
|
+
} else if (c === "\\" && i + 1 < s.length) cur += s[++i];
|
|
250
|
+
else cur += c;
|
|
251
|
+
}
|
|
252
|
+
if (has || cur) out.push(cur);
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
async function exePath(pid) {
|
|
256
|
+
try {
|
|
257
|
+
return await readlink(`/proc/${pid}/exe`);
|
|
258
|
+
} catch {
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async function threadIds(pid) {
|
|
263
|
+
try {
|
|
264
|
+
return (await readdir(`/proc/${pid}/task`)).map(Number).filter(Number.isFinite);
|
|
265
|
+
} catch {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function targetPids(t) {
|
|
270
|
+
const set = /* @__PURE__ */ new Set();
|
|
271
|
+
if (t.pid) set.add(t.pid);
|
|
272
|
+
for (const p of t.pids ?? []) set.add(p);
|
|
273
|
+
return [...set];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export {
|
|
277
|
+
parsePs,
|
|
278
|
+
listProcesses,
|
|
279
|
+
containerIdFromCgroup,
|
|
280
|
+
processInfo,
|
|
281
|
+
descendantPids,
|
|
282
|
+
detectRuntime,
|
|
283
|
+
guessKind,
|
|
284
|
+
resolveTarget,
|
|
285
|
+
resolvePid,
|
|
286
|
+
spawnCommand,
|
|
287
|
+
splitArgs,
|
|
288
|
+
exePath,
|
|
289
|
+
threadIds,
|
|
290
|
+
targetPids
|
|
291
|
+
};
|
|
292
|
+
//# sourceMappingURL=chunk-5RAKTYW5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/target.ts"],"sourcesContent":["/**\n * Resolve pid | container name | k8s pod | command → attachable Target.\n * Also provides the cross-platform process listing used by list_processes.\n *\n * Nothing here assumes the target is local forever: the resolved Target\n * carries `host` and the container block so later hosts (ssh, k8s) can\n * fill them in with the same shape.\n */\nimport { readFile, readdir, readlink } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { spawn } from \"node:child_process\";\nimport { createWriteStream } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { execa } from \"execa\";\nimport type { Target, TargetKind } from \"./contracts.js\";\nimport { assertPid, assertSafeIdentifier } from \"./safety.js\";\nimport { platform } from \"./session.js\";\n\nexport interface ProcessInfo {\n pid: number;\n ppid: number;\n user: string;\n cpu: number;\n mem: number;\n rss_kb: number;\n comm: string;\n args: string;\n container?: string;\n}\n\n// ---------------------------------------------------------------------------\n// ps\n// ---------------------------------------------------------------------------\n\n/**\n * Parse ps output. We run ps twice because `comm` may contain spaces on macOS\n * and `args` always may: once with comm as the LAST column, once with args as\n * the last column, then join by pid. Pure.\n */\nexport function parsePs(commRaw: string, argsRaw = \"\"): ProcessInfo[] {\n const args = new Map<number, string>();\n for (const line of argsRaw.split(\"\\n\")) {\n const m = /^\\s*(\\d+)\\s+(.*)$/.exec(line);\n if (m) args.set(Number(m[1]), m[2]!.trim());\n }\n const out: ProcessInfo[] = [];\n for (const line of commRaw.split(\"\\n\")) {\n const t = line.trim();\n if (!t) continue;\n // pid ppid user cpu mem rss comm...\n const m = /^(\\d+)\\s+(\\d+)\\s+(\\S+)\\s+([\\d.]+)\\s+([\\d.]+)\\s+(\\d+)\\s+(.*)$/.exec(t);\n if (!m) continue;\n const pid = Number(m[1]);\n const a = args.get(pid) ?? \"\";\n let comm = basename(m[7]!.trim());\n // macOS truncates comm to 16 chars; prefer argv[0]'s basename when it extends comm.\n const argv0 = basename(a.split(\" \")[0] ?? \"\");\n if (argv0 && argv0.length > comm.length && argv0.startsWith(comm)) comm = argv0;\n out.push({ pid, ppid: Number(m[2]), user: m[3]!, cpu: Number(m[4]), mem: Number(m[5]), rss_kb: Number(m[6]), comm, args: a });\n }\n return out;\n}\n\nconst COMM_COL = process.platform === \"darwin\" ? \"ucomm=\" : \"comm=\";\n\nfunction basename(p: string): string {\n const i = p.lastIndexOf(\"/\");\n return i >= 0 ? p.slice(i + 1) : p;\n}\n\nexport async function listProcesses(filter?: string, limit = 100): Promise<ProcessInfo[]> {\n if (platform() === \"win32\") throw new Error(\"list_processes is not implemented on Windows yet\");\n const [r1, r2] = await Promise.all([\n execa(\"ps\", [\"-axo\", `pid=,ppid=,user=,%cpu=,%mem=,rss=,${COMM_COL}`], { reject: false, timeout: 10_000, maxBuffer: 64 * 1024 * 1024 }),\n execa(\"ps\", [\"-axo\", \"pid=,args=\"], { reject: false, timeout: 10_000, maxBuffer: 64 * 1024 * 1024 }),\n ]);\n let procs = parsePs(r1.stdout, r2.stdout);\n if (platform() === \"linux\") await annotateContainers(procs);\n if (filter) {\n const f = filter.toLowerCase();\n procs = procs.filter((p) => p.comm.toLowerCase().includes(f) || p.args.toLowerCase().includes(f) || p.user.toLowerCase().includes(f) || String(p.pid) === f);\n }\n procs.sort((a, b) => b.cpu - a.cpu || b.rss_kb - a.rss_kb);\n return procs.slice(0, limit);\n}\n\n/** Linux: read /proc/<pid>/cgroup to find a container id. */\nexport function containerIdFromCgroup(cgroup: string): string | undefined {\n const m = /(?:docker|cri-containerd|containerd|libpod|podman)[-/]([0-9a-f]{12,64})/.exec(cgroup) ?? /\\/([0-9a-f]{64})(?:\\.scope)?$/m.exec(cgroup);\n return m?.[1]?.slice(0, 12);\n}\n\nasync function annotateContainers(procs: ProcessInfo[]): Promise<void> {\n await Promise.all(\n procs.map(async (p) => {\n try {\n const cg = await readFile(`/proc/${p.pid}/cgroup`, \"utf8\");\n const id = containerIdFromCgroup(cg);\n if (id) p.container = id;\n } catch {\n /* gone or no permission */\n }\n }),\n );\n}\n\nexport async function processInfo(pid: number): Promise<ProcessInfo | undefined> {\n const [r1, r2] = await Promise.all([\n execa(\"ps\", [\"-o\", `pid=,ppid=,user=,%cpu=,%mem=,rss=,${COMM_COL}`, \"-p\", String(pid)], { reject: false, timeout: 5000 }),\n execa(\"ps\", [\"-o\", \"pid=,args=\", \"-p\", String(pid)], { reject: false, timeout: 5000 }),\n ]);\n return parsePs(r1.stdout, r2.stdout)[0];\n}\n\n/** All descendant pids of `pid` (one ps call). */\nexport async function descendantPids(pid: number): Promise<number[]> {\n const r = await execa(\"ps\", [\"-axo\", \"pid=,ppid=\"], { reject: false, timeout: 5000 });\n const children = new Map<number, number[]>();\n for (const line of r.stdout.split(\"\\n\")) {\n const m = /^\\s*(\\d+)\\s+(\\d+)/.exec(line);\n if (!m) continue;\n const c = Number(m[1]);\n const p = Number(m[2]);\n if (!children.has(p)) children.set(p, []);\n children.get(p)!.push(c);\n }\n const out: number[] = [];\n const stack = [pid];\n while (stack.length) {\n const cur = stack.pop()!;\n for (const c of children.get(cur) ?? []) {\n out.push(c);\n stack.push(c);\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime detection\n// ---------------------------------------------------------------------------\n\nexport function detectRuntime(comm: string, args: string): NonNullable<Target[\"runtime\"]> {\n const s = `${comm} ${args}`.toLowerCase();\n if (/(^|\\/|\\s)python[\\d.]*(\\s|$)|uvicorn|gunicorn|celery|flask|django|\\.py(\\s|$)/.test(s)) return \"python\";\n if (/(^|\\/|\\s)(node|nodejs|bun|deno|tsx|ts-node)(\\s|$)|\\.m?[jt]s(\\s|$)|next-server|electron/.test(s)) return \"node\";\n if (/(^|\\/|\\s)java(\\s|$)|\\.jar(\\s|$)|jvm/.test(s)) return \"java\";\n if (/(^|\\/|\\s)go(\\s|$)|\\/go\\/bin\\//.test(s)) return \"go\";\n if (comm) return \"native\";\n return \"unknown\";\n}\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\nexport interface ResolveOptions {\n kind?: TargetKind;\n /** For kind=command: directory to tee stdout/stderr into. */\n captureDir?: string;\n}\n\nexport function guessKind(target: string, kind?: TargetKind): TargetKind {\n if (kind) return kind;\n if (/^\\d+$/.test(target.trim())) return \"pid\";\n if (target.includes(\" \") || target.startsWith(\"./\") || target.startsWith(\"/\")) return \"command\";\n if (/^[a-z0-9-]+\\/[a-z0-9.-]+$/i.test(target) && !target.includes(\":\")) return \"pod\";\n return \"container\";\n}\n\nexport async function resolveTarget(input: string, opts: ResolveOptions = {}): Promise<Target> {\n const kind = guessKind(input, opts.kind);\n switch (kind) {\n case \"pid\":\n return resolvePid(Number(input.trim()));\n case \"container\":\n return resolveContainer(input.trim());\n case \"pod\":\n return resolvePod(input.trim());\n case \"command\":\n return spawnCommand(input, opts.captureDir);\n }\n}\n\nexport async function resolvePid(pid: number, extra: Partial<Target> = {}): Promise<Target> {\n assertPid(pid);\n const info = await processInfo(pid);\n if (!info) throw new Error(`no such process: ${pid}`);\n const pids = await descendantPids(pid);\n let cmdline = info.args;\n if (platform() === \"linux\") {\n try {\n cmdline = (await readFile(`/proc/${pid}/cmdline`, \"utf8\")).replace(/\\0/g, \" \").trim() || cmdline;\n } catch {\n /* keep ps args */\n }\n }\n return {\n kind: \"pid\",\n id: String(pid),\n pid,\n comm: info.comm,\n cmdline,\n pids,\n runtime: detectRuntime(info.comm, cmdline),\n host: \"local\",\n ...extra,\n };\n}\n\nasync function resolveContainer(name: string): Promise<Target> {\n assertSafeIdentifier(name, \"container name\");\n const { dockerInspectForAttach } = await import(\"./adapters/docker.js\");\n const info = await dockerInspectForAttach(name);\n if (!info) throw new Error(`container not found: ${name} (docker inspect returned nothing)`);\n if (!info.pid) throw new Error(`container ${name} is not running (no host pid)`);\n const canSeeHostPid = platform() === \"linux\" && existsSync(`/proc/${info.pid}`);\n const base: Target = {\n kind: \"container\",\n id: name,\n pid: canSeeHostPid ? info.pid : undefined,\n comm: info.comm,\n cmdline: info.cmdline,\n runtime: detectRuntime(info.comm ?? \"\", info.cmdline ?? \"\"),\n host: \"local\",\n container: { runtime: \"docker\", id: info.id, name: info.name, image: info.image },\n };\n if (canSeeHostPid) {\n try {\n base.pids = await descendantPids(info.pid);\n } catch {\n /* ignore */\n }\n }\n return base;\n}\n\nasync function resolvePod(ref: string): Promise<Target> {\n assertSafeIdentifier(ref, \"pod reference\");\n const [ns, name] = ref.includes(\"/\") ? ref.split(\"/\", 2) : [\"default\", ref];\n // v0: resolve a pod to a local container only when the kubelet runs on this host (kind/minikube/k3s).\n const r = await execa(\"kubectl\", [\"get\", \"pod\", name!, \"-n\", ns!, \"-o\", \"jsonpath={.status.containerStatuses[0].containerID}\"], { reject: false, timeout: 10_000 });\n if (r.exitCode !== 0) throw new Error(`kubectl get pod failed: ${r.stderr.trim() || \"kubectl not available\"}`);\n const containerId = r.stdout.trim().replace(/^[a-z-]+:\\/\\//, \"\");\n if (!containerId) throw new Error(`pod ${ref} has no running container`);\n const t = await resolveContainer(containerId.slice(0, 12)).catch(() => undefined);\n return {\n kind: \"pod\",\n id: ref,\n pid: t?.pid,\n pids: t?.pids,\n comm: t?.comm,\n cmdline: t?.cmdline,\n runtime: t?.runtime,\n host: \"local\",\n container: { runtime: \"k8s\", id: containerId.slice(0, 12), name: name!, namespace: ns! },\n };\n}\n\n/** Spawn a command under capture; stdout/stderr are tee'd to files in captureDir. */\nexport async function spawnCommand(cmdline: string, captureDir?: string): Promise<Target & { spawned: { pid: number; stdout: string; stderr: string } }> {\n const argv = splitArgs(cmdline);\n if (argv.length === 0) throw new Error(\"empty command\");\n const dir = captureDir ?? process.cwd();\n const stdoutPath = join(dir, \"stdout.log\");\n const stderrPath = join(dir, \"stderr.log\");\n const out = createWriteStream(stdoutPath);\n const err = createWriteStream(stderrPath);\n const child = spawn(argv[0]!, argv.slice(1), { stdio: [\"ignore\", \"pipe\", \"pipe\"], detached: true, env: process.env });\n child.stdout.pipe(out);\n child.stderr.pipe(err);\n child.stdout.pipe(process.stderr);\n child.stderr.pipe(process.stderr);\n child.unref();\n if (!child.pid) throw new Error(`failed to spawn: ${cmdline}`);\n // Give it a moment to fail fast (bad binary, immediate crash).\n await new Promise((r) => setTimeout(r, 500));\n const info = await processInfo(child.pid);\n const comm = info?.comm ?? basename(argv[0]!);\n return {\n kind: \"command\",\n id: cmdline,\n pid: child.pid,\n comm,\n cmdline,\n pids: await descendantPids(child.pid),\n runtime: detectRuntime(comm, cmdline),\n host: \"local\",\n spawned: { pid: child.pid, stdout: stdoutPath, stderr: stderrPath },\n };\n}\n\n/** Minimal shell-ish argv splitter supporting quotes. No expansion, no shell. */\nexport function splitArgs(s: string): string[] {\n const out: string[] = [];\n let cur = \"\";\n let quote: string | null = null;\n let has = false;\n for (let i = 0; i < s.length; i++) {\n const c = s[i]!;\n if (quote) {\n if (c === quote) quote = null;\n else if (c === \"\\\\\" && quote === '\"' && i + 1 < s.length) cur += s[++i];\n else cur += c;\n } else if (c === '\"' || c === \"'\") {\n quote = c;\n has = true;\n } else if (/\\s/.test(c)) {\n if (has || cur) out.push(cur);\n cur = \"\";\n has = false;\n } else if (c === \"\\\\\" && i + 1 < s.length) cur += s[++i];\n else cur += c;\n }\n if (has || cur) out.push(cur);\n return out;\n}\n\n/** Linux: resolve the executable path of a pid via /proc. */\nexport async function exePath(pid: number): Promise<string | undefined> {\n try {\n return await readlink(`/proc/${pid}/exe`);\n } catch {\n return undefined;\n }\n}\n\n/** Linux: list thread ids of a pid. */\nexport async function threadIds(pid: number): Promise<number[]> {\n try {\n return (await readdir(`/proc/${pid}/task`)).map(Number).filter(Number.isFinite);\n } catch {\n return [];\n }\n}\n\n/** Primary pid plus descendants, for scoping adapters. */\nexport function targetPids(t: Target): number[] {\n const set = new Set<number>();\n if (t.pid) set.add(t.pid);\n for (const p of t.pids ?? []) set.add(p);\n return [...set];\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/hook.ts
|
|
4
|
+
var TRIGGERS = [
|
|
5
|
+
{ id: "port_busy", pattern: "EADDRINUSE|address already in use|port (\\d+) is (already )?in use|bind\\(\\): Address already in use", flags: "i", scope: "both", hint: "run `debuganything port <port>` (or inspect_sockets) to see which pid holds the port; never kill it blindly" },
|
|
6
|
+
{ id: "conn_refused", pattern: "ECONNREFUSED|connection refused|ECONNRESET|socket hang up|ETIMEDOUT|connect timed out|EHOSTUNREACH", flags: "i", scope: "output", hint: "attach Debug Anything to the client and run inspect_sockets / trace_syscalls (network) to see which remote fails and how" },
|
|
7
|
+
{ id: "fd_exhaustion", pattern: "EMFILE|too many open files|ENFILE", flags: "i", scope: "both", hint: "attach Debug Anything and run inspect_files (fd growth, deleted-but-open) and inspect_sockets (CLOSE_WAIT pileups)" },
|
|
8
|
+
{ id: "oom", pattern: "OOMKilled|Out of memory|oom-kill|exit(ed)? (code|status)[ =:]*137|Killed process \\d+|JavaScript heap out of memory|MemoryError|Cannot allocate memory", flags: "i", scope: "both", hint: "attach Debug Anything: container_info (OOM/limits) and snapshot_memory twice around load, then diff_runs to name the retainer" },
|
|
9
|
+
{ id: "timeout", pattern: "timed? ?out after|Timeout - Async callback|Test timeout of|exceeded timeout|context deadline exceeded|TimeoutError", flags: "i", scope: "output", hint: "if the process under test is still running, attach Debug Anything and run dump_stacks (waiting on a lock? a socket?) before changing timeouts" },
|
|
10
|
+
{ id: "hang_words", pattern: "\\b(hangs?|hanging|stuck|frozen|freezes|deadlock(ed)?|not responding|never (returns|finishes|completes))\\b", flags: "i", scope: "prompt", hint: "attach Debug Anything and run dump_stacks first \u2014 do not theorize about the hang from the code" },
|
|
11
|
+
{ id: "slow_words", pattern: "\\b(slow|slower|latency|p99|p95|takes forever|sluggish|cpu (is )?(pegged|at 100)|100% cpu)\\b", flags: "i", scope: "prompt", hint: "attach Debug Anything and run profile_cpu then trace_syscalls (file,network) \u2014 measure before optimizing" },
|
|
12
|
+
{ id: "leak_words", pattern: "\\b(memory (leak|keeps growing|grows|usage grows)|leaking|rss (grows|keeps)|heap (grows|keeps growing)|out of memory)\\b", flags: "i", scope: "prompt", hint: "attach Debug Anything, snapshot_memory, exercise the target, snapshot_memory again, diff_runs" },
|
|
13
|
+
{ id: "crash_words", pattern: "\\b(crashed|crashes|keeps restarting|restart loop|CrashLoopBackOff|segfault|SIGSEGV|core dumped|exit code 1[34][0-9])\\b", flags: "i", scope: "both", hint: "attach Debug Anything to the container/pid and run container_info (exit code, OOM, restarts, log tail)" },
|
|
14
|
+
{ id: "segv", pattern: "Segmentation fault|SIGSEGV|SIGABRT|SIGBUS|core dumped|fatal error: unexpected signal", flags: "i", scope: "output", hint: "if it restarted, attach Debug Anything and dump_stacks/profile_cpu to catch the lead-up; hand the symbol to a step debugger or a static-analysis tool" }
|
|
15
|
+
];
|
|
16
|
+
function detectTriggers(text, scope) {
|
|
17
|
+
const hits = [];
|
|
18
|
+
for (const t of TRIGGERS) {
|
|
19
|
+
if (t.scope !== "both" && t.scope !== scope) continue;
|
|
20
|
+
const m = new RegExp(t.pattern, t.flags).exec(text);
|
|
21
|
+
if (m) hits.push({ id: t.id, hint: t.hint, match: m[0].slice(0, 80) });
|
|
22
|
+
}
|
|
23
|
+
return hits;
|
|
24
|
+
}
|
|
25
|
+
function renderNudge(hits, scope) {
|
|
26
|
+
if (!hits.length) return "";
|
|
27
|
+
const where = scope === "output" ? "The last command's output" : "The user's message";
|
|
28
|
+
const lines = hits.slice(0, 3).map((h) => `- ${h.id} (\u201C${h.match}\u201D): ${h.hint}`);
|
|
29
|
+
return [
|
|
30
|
+
`[Debug Anything] ${where} shows a runtime problem signal. Debug Anything is installed: observe before you theorize.`,
|
|
31
|
+
...lines,
|
|
32
|
+
"Protocol: readiness (once per session) \u2192 attach <pid|container|command> \u2192 the tool above \u2192 detach. Cite evidence ids (e.g. dump_stacks#2/e3) in your answer; capture_traffic needs the user's consent. Skip this if the user explicitly said not to attach to anything."
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
var HOOK_SCRIPT = `#!/usr/bin/env node
|
|
36
|
+
// Debug Anything proactive hook for Claude Code. Installed by \`debuganything setup\`; remove the hooks entries in
|
|
37
|
+
// ~/.claude/settings.json (or run \`debuganything setup --no-hooks\`) to disable. Reads the hook event on stdin,
|
|
38
|
+
// prints additional context when command output or the prompt shows a runtime-problem signal.
|
|
39
|
+
const TRIGGERS = ${JSON.stringify(TRIGGERS.map((t) => ({ id: t.id, pattern: t.pattern, flags: t.flags, scope: t.scope, hint: t.hint })))};
|
|
40
|
+
let raw = "";
|
|
41
|
+
process.stdin.setEncoding("utf8");
|
|
42
|
+
process.stdin.on("data", (c) => (raw += c));
|
|
43
|
+
process.stdin.on("end", () => {
|
|
44
|
+
let ev;
|
|
45
|
+
try { ev = JSON.parse(raw); } catch { return; }
|
|
46
|
+
const event = ev.hook_event_name || "";
|
|
47
|
+
let text = "";
|
|
48
|
+
let scope = "output";
|
|
49
|
+
if (event === "UserPromptSubmit") { text = String(ev.prompt || ""); scope = "prompt"; }
|
|
50
|
+
else if (event === "PostToolUse") {
|
|
51
|
+
if (ev.tool_name !== "Bash") return;
|
|
52
|
+
const r = ev.tool_response || {};
|
|
53
|
+
text = [r.stdout, r.stderr, r.output, typeof r === "string" ? r : ""].filter(Boolean).join("\\n");
|
|
54
|
+
const cmd = String((ev.tool_input || {}).command || "");
|
|
55
|
+
if (/\\b(debuganything|debugany)\\b/.test(cmd)) return; // don't nudge about Debug Anything's own output
|
|
56
|
+
} else return;
|
|
57
|
+
if (!text || text.length > 400000) return;
|
|
58
|
+
const hits = [];
|
|
59
|
+
for (const t of TRIGGERS) {
|
|
60
|
+
if (t.scope !== "both" && t.scope !== scope) continue;
|
|
61
|
+
const m = new RegExp(t.pattern, t.flags).exec(text);
|
|
62
|
+
if (m) hits.push({ id: t.id, hint: t.hint, match: m[0].slice(0, 80) });
|
|
63
|
+
}
|
|
64
|
+
if (!hits.length) return;
|
|
65
|
+
const where = scope === "output" ? "The last command's output" : "The user's message";
|
|
66
|
+
const lines = hits.slice(0, 3).map((h) => "- " + h.id + " (\\u201c" + h.match + "\\u201d): " + h.hint);
|
|
67
|
+
const additionalContext = ["[Debug Anything] " + where + " shows a runtime problem signal. Debug Anything is installed: observe before you theorize.", ...lines,
|
|
68
|
+
"Protocol: readiness (once per session) \\u2192 attach <pid|container|command> \\u2192 the tool above \\u2192 detach. Cite evidence ids (e.g. dump_stacks#2/e3) in your answer; capture_traffic needs the user's consent. Skip this if the user explicitly said not to attach to anything."].join("\\n");
|
|
69
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext } }) + "\\n");
|
|
70
|
+
});
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
TRIGGERS,
|
|
75
|
+
detectTriggers,
|
|
76
|
+
renderNudge,
|
|
77
|
+
HOOK_SCRIPT
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=chunk-7MMXFUUN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hook.ts"],"sourcesContent":["/**\n * Proactive triggers: signals in command output or in the user's prompt that\n * mean \"a running system is misbehaving — observe it with Debug Anything before theorizing\".\n *\n * Used two ways:\n * - `detectTriggers()` in-process (tests, `debuganything hook` subcommand).\n * - `HOOK_SCRIPT`, a dependency-free Node script written by `debuganything setup` to\n * ~/.debuganything/hooks/debuganything-hook.mjs and registered as a Claude Code hook\n * (PostToolUse on Bash + UserPromptSubmit). It must start in ~40 ms, so it\n * never loads the Debug Anything package itself.\n */\n\nexport interface Trigger {\n id: string;\n /** Regex source (string so it can be embedded in the standalone script). */\n pattern: string;\n flags: string;\n /** Where it applies. */\n scope: \"output\" | \"prompt\" | \"both\";\n hint: string;\n}\n\nexport const TRIGGERS: Trigger[] = [\n { id: \"port_busy\", pattern: \"EADDRINUSE|address already in use|port (\\\\d+) is (already )?in use|bind\\\\(\\\\): Address already in use\", flags: \"i\", scope: \"both\", hint: \"run `debuganything port <port>` (or inspect_sockets) to see which pid holds the port; never kill it blindly\" },\n { id: \"conn_refused\", pattern: \"ECONNREFUSED|connection refused|ECONNRESET|socket hang up|ETIMEDOUT|connect timed out|EHOSTUNREACH\", flags: \"i\", scope: \"output\", hint: \"attach Debug Anything to the client and run inspect_sockets / trace_syscalls (network) to see which remote fails and how\" },\n { id: \"fd_exhaustion\", pattern: \"EMFILE|too many open files|ENFILE\", flags: \"i\", scope: \"both\", hint: \"attach Debug Anything and run inspect_files (fd growth, deleted-but-open) and inspect_sockets (CLOSE_WAIT pileups)\" },\n { id: \"oom\", pattern: \"OOMKilled|Out of memory|oom-kill|exit(ed)? (code|status)[ =:]*137|Killed process \\\\d+|JavaScript heap out of memory|MemoryError|Cannot allocate memory\", flags: \"i\", scope: \"both\", hint: \"attach Debug Anything: container_info (OOM/limits) and snapshot_memory twice around load, then diff_runs to name the retainer\" },\n { id: \"timeout\", pattern: \"timed? ?out after|Timeout - Async callback|Test timeout of|exceeded timeout|context deadline exceeded|TimeoutError\", flags: \"i\", scope: \"output\", hint: \"if the process under test is still running, attach Debug Anything and run dump_stacks (waiting on a lock? a socket?) before changing timeouts\" },\n { id: \"hang_words\", pattern: \"\\\\b(hangs?|hanging|stuck|frozen|freezes|deadlock(ed)?|not responding|never (returns|finishes|completes))\\\\b\", flags: \"i\", scope: \"prompt\", hint: \"attach Debug Anything and run dump_stacks first — do not theorize about the hang from the code\" },\n { id: \"slow_words\", pattern: \"\\\\b(slow|slower|latency|p99|p95|takes forever|sluggish|cpu (is )?(pegged|at 100)|100% cpu)\\\\b\", flags: \"i\", scope: \"prompt\", hint: \"attach Debug Anything and run profile_cpu then trace_syscalls (file,network) — measure before optimizing\" },\n { id: \"leak_words\", pattern: \"\\\\b(memory (leak|keeps growing|grows|usage grows)|leaking|rss (grows|keeps)|heap (grows|keeps growing)|out of memory)\\\\b\", flags: \"i\", scope: \"prompt\", hint: \"attach Debug Anything, snapshot_memory, exercise the target, snapshot_memory again, diff_runs\" },\n { id: \"crash_words\", pattern: \"\\\\b(crashed|crashes|keeps restarting|restart loop|CrashLoopBackOff|segfault|SIGSEGV|core dumped|exit code 1[34][0-9])\\\\b\", flags: \"i\", scope: \"both\", hint: \"attach Debug Anything to the container/pid and run container_info (exit code, OOM, restarts, log tail)\" },\n { id: \"segv\", pattern: \"Segmentation fault|SIGSEGV|SIGABRT|SIGBUS|core dumped|fatal error: unexpected signal\", flags: \"i\", scope: \"output\", hint: \"if it restarted, attach Debug Anything and dump_stacks/profile_cpu to catch the lead-up; hand the symbol to a step debugger or a static-analysis tool\" },\n];\n\nexport interface TriggerHit {\n id: string;\n hint: string;\n match: string;\n}\n\n/** Scan text for trigger patterns. Pure. */\nexport function detectTriggers(text: string, scope: \"output\" | \"prompt\"): TriggerHit[] {\n const hits: TriggerHit[] = [];\n for (const t of TRIGGERS) {\n if (t.scope !== \"both\" && t.scope !== scope) continue;\n const m = new RegExp(t.pattern, t.flags).exec(text);\n if (m) hits.push({ id: t.id, hint: t.hint, match: m[0].slice(0, 80) });\n }\n return hits;\n}\n\n/** The nudge injected as additional context for the agent. Pure. */\nexport function renderNudge(hits: TriggerHit[], scope: \"output\" | \"prompt\"): string {\n if (!hits.length) return \"\";\n const where = scope === \"output\" ? \"The last command's output\" : \"The user's message\";\n const lines = hits.slice(0, 3).map((h) => `- ${h.id} (“${h.match}”): ${h.hint}`);\n return [\n `[Debug Anything] ${where} shows a runtime problem signal. Debug Anything is installed: observe before you theorize.`,\n ...lines,\n \"Protocol: readiness (once per session) → attach <pid|container|command> → the tool above → detach. Cite evidence ids (e.g. dump_stacks#2/e3) in your answer; capture_traffic needs the user's consent. Skip this if the user explicitly said not to attach to anything.\",\n ].join(\"\\n\");\n}\n\n/** Standalone hook script (no dependencies) written by `debuganything setup`. Keep in sync with TRIGGERS. */\nexport const HOOK_SCRIPT = `#!/usr/bin/env node\n// Debug Anything proactive hook for Claude Code. Installed by \\`debuganything setup\\`; remove the hooks entries in\n// ~/.claude/settings.json (or run \\`debuganything setup --no-hooks\\`) to disable. Reads the hook event on stdin,\n// prints additional context when command output or the prompt shows a runtime-problem signal.\nconst TRIGGERS = ${JSON.stringify(TRIGGERS.map((t) => ({ id: t.id, pattern: t.pattern, flags: t.flags, scope: t.scope, hint: t.hint })))};\nlet raw = \"\";\nprocess.stdin.setEncoding(\"utf8\");\nprocess.stdin.on(\"data\", (c) => (raw += c));\nprocess.stdin.on(\"end\", () => {\n let ev;\n try { ev = JSON.parse(raw); } catch { return; }\n const event = ev.hook_event_name || \"\";\n let text = \"\";\n let scope = \"output\";\n if (event === \"UserPromptSubmit\") { text = String(ev.prompt || \"\"); scope = \"prompt\"; }\n else if (event === \"PostToolUse\") {\n if (ev.tool_name !== \"Bash\") return;\n const r = ev.tool_response || {};\n text = [r.stdout, r.stderr, r.output, typeof r === \"string\" ? r : \"\"].filter(Boolean).join(\"\\\\n\");\n const cmd = String((ev.tool_input || {}).command || \"\");\n if (/\\\\b(debuganything|debugany)\\\\b/.test(cmd)) return; // don't nudge about Debug Anything's own output\n } else return;\n if (!text || text.length > 400000) return;\n const hits = [];\n for (const t of TRIGGERS) {\n if (t.scope !== \"both\" && t.scope !== scope) continue;\n const m = new RegExp(t.pattern, t.flags).exec(text);\n if (m) hits.push({ id: t.id, hint: t.hint, match: m[0].slice(0, 80) });\n }\n if (!hits.length) return;\n const where = scope === \"output\" ? \"The last command's output\" : \"The user's message\";\n const lines = hits.slice(0, 3).map((h) => \"- \" + h.id + \" (\\\\u201c\" + h.match + \"\\\\u201d): \" + h.hint);\n const additionalContext = [\"[Debug Anything] \" + where + \" shows a runtime problem signal. Debug Anything is installed: observe before you theorize.\", ...lines,\n \"Protocol: readiness (once per session) \\\\u2192 attach <pid|container|command> \\\\u2192 the tool above \\\\u2192 detach. Cite evidence ids (e.g. dump_stacks#2/e3) in your answer; capture_traffic needs the user's consent. Skip this if the user explicitly said not to attach to anything.\"].join(\"\\\\n\");\n process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext } }) + \"\\\\n\");\n});\n`;\n"],"mappings":";;;AAsBO,IAAM,WAAsB;AAAA,EACjC,EAAE,IAAI,aAAa,SAAS,yGAAyG,OAAO,KAAK,OAAO,QAAQ,MAAM,8GAA8G;AAAA,EACpR,EAAE,IAAI,gBAAgB,SAAS,sGAAsG,OAAO,KAAK,OAAO,UAAU,MAAM,2HAA2H;AAAA,EACnS,EAAE,IAAI,iBAAiB,SAAS,qCAAqC,OAAO,KAAK,OAAO,QAAQ,MAAM,qHAAqH;AAAA,EAC3N,EAAE,IAAI,OAAO,SAAS,0JAA0J,OAAO,KAAK,OAAO,QAAQ,MAAM,gIAAgI;AAAA,EACjV,EAAE,IAAI,WAAW,SAAS,sHAAsH,OAAO,KAAK,OAAO,UAAU,MAAM,gJAAgJ;AAAA,EACnU,EAAE,IAAI,cAAc,SAAS,+GAA+G,OAAO,KAAK,OAAO,UAAU,MAAM,sGAAiG;AAAA,EAChR,EAAE,IAAI,cAAc,SAAS,iGAAiG,OAAO,KAAK,OAAO,UAAU,MAAM,gHAA2G;AAAA,EAC5Q,EAAE,IAAI,cAAc,SAAS,4HAA4H,OAAO,KAAK,OAAO,UAAU,MAAM,gGAAgG;AAAA,EAC5R,EAAE,IAAI,eAAe,SAAS,4HAA4H,OAAO,KAAK,OAAO,QAAQ,MAAM,yGAAyG;AAAA,EACpS,EAAE,IAAI,QAAQ,SAAS,wFAAwF,OAAO,KAAK,OAAO,UAAU,MAAM,wJAAwJ;AAC5S;AASO,SAAS,eAAe,MAAc,OAA0C;AACrF,QAAM,OAAqB,CAAC;AAC5B,aAAW,KAAK,UAAU;AACxB,QAAI,EAAE,UAAU,UAAU,EAAE,UAAU,MAAO;AAC7C,UAAM,IAAI,IAAI,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,IAAI;AAClD,QAAI,EAAG,MAAK,KAAK,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,MAAM,OAAO,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,EAAE,CAAC;AAAA,EACvE;AACA,SAAO;AACT;AAGO,SAAS,YAAY,MAAoB,OAAoC;AAClF,MAAI,CAAC,KAAK,OAAQ,QAAO;AACzB,QAAM,QAAQ,UAAU,WAAW,8BAA8B;AACjE,QAAM,QAAQ,KAAK,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,KAAK,EAAE,EAAE,WAAM,EAAE,KAAK,YAAO,EAAE,IAAI,EAAE;AAC/E,SAAO;AAAA,IACL,oBAAoB,KAAK;AAAA,IACzB,GAAG;AAAA,IACH;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAGO,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA,mBAIR,KAAK,UAAU,SAAS,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,SAAS,EAAE,SAAS,OAAO,EAAE,OAAO,OAAO,EAAE,OAAO,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
|