@usepowerplant/cli 0.4.3 → 0.4.4
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 +69 -10
- package/dist/cli.js +10016 -4149
- package/dist/hooks/powerplant-codex-transcript.mjs +67 -0
- package/package.json +3 -3
- package/dist/hooks/powerplant-session-change.mjs +0 -21
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Discovery only. The collector owns scope, transcript reads and uploads.
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
const MAX_INPUT_BYTES = 128 * 1024;
|
|
10
|
+
let chunks = [];
|
|
11
|
+
let bytes = 0;
|
|
12
|
+
process.stdin.on("error", () => {});
|
|
13
|
+
process.stdin.on("data", (chunk) => {
|
|
14
|
+
bytes += chunk.length;
|
|
15
|
+
if (bytes <= MAX_INPUT_BYTES) chunks.push(chunk);
|
|
16
|
+
else chunks = [];
|
|
17
|
+
});
|
|
18
|
+
process.stdin.on("end", () => {
|
|
19
|
+
const dir = path.join(os.homedir(), ".config", "powerplant");
|
|
20
|
+
try {
|
|
21
|
+
if (bytes <= MAX_INPUT_BYTES) {
|
|
22
|
+
const input = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
23
|
+
if (
|
|
24
|
+
["SessionStart", "SessionEnd", "Stop"].includes(input?.hook_event_name) &&
|
|
25
|
+
typeof input.session_id === "string" &&
|
|
26
|
+
/^[a-zA-Z0-9_-]{1,200}$/.test(input.session_id)
|
|
27
|
+
) {
|
|
28
|
+
const record = {
|
|
29
|
+
sessionId: input.session_id,
|
|
30
|
+
transcriptPath:
|
|
31
|
+
typeof input.transcript_path === "string" &&
|
|
32
|
+
input.transcript_path.length <= 4096 &&
|
|
33
|
+
path.isAbsolute(input.transcript_path)
|
|
34
|
+
? input.transcript_path
|
|
35
|
+
: null,
|
|
36
|
+
};
|
|
37
|
+
const records = path.join(dir, "codex-transcripts");
|
|
38
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
39
|
+
mkdirSync(records, { recursive: true, mode: 0o700 });
|
|
40
|
+
const file = path.join(
|
|
41
|
+
records,
|
|
42
|
+
`${createHash("sha256").update(record.sessionId).digest("hex")}.json`
|
|
43
|
+
);
|
|
44
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
45
|
+
try {
|
|
46
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
47
|
+
writeFileSync(temporary, JSON.stringify(record), { mode: 0o600, flag: "wx" });
|
|
48
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
49
|
+
renameSync(temporary, file);
|
|
50
|
+
} finally {
|
|
51
|
+
rmSync(temporary, { force: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// Invalid input or unavailable disk must not interrupt the agent.
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
// Publish after the pointer; existing file scans also recover missed hooks.
|
|
60
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
61
|
+
mkdirSync(dir, { recursive: true });
|
|
62
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
63
|
+
writeFileSync(path.join(dir, "session-change"), `${Date.now()}\n`, { mode: 0o600 });
|
|
64
|
+
} catch {
|
|
65
|
+
// Never report collector failures as agent output.
|
|
66
|
+
}
|
|
67
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usepowerplant/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@powerplant/collector": "workspace:*",
|
|
30
30
|
"@powerplant/core": "workspace:*",
|
|
31
|
-
"@types/node": "^26.
|
|
31
|
+
"@types/node": "^26.5.0",
|
|
32
32
|
"esbuild": "^0.28.2",
|
|
33
33
|
"typescript": "^5.9.3",
|
|
34
34
|
"vitest": "^4.1.11",
|
|
35
|
-
"zod": "^4.4
|
|
35
|
+
"zod": "^4.5.4"
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Canonical standalone hook, bundled by packages/cli/build.mjs.
|
|
3
|
-
// Wake the local collector without inspecting or saving the hook payload.
|
|
4
|
-
// Existing readers still enforce capture scope and redact before uploading.
|
|
5
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
-
import os from "node:os";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
|
|
9
|
-
// Drain provider input without retaining it or emitting an agent response.
|
|
10
|
-
process.stdin.on("error", () => {});
|
|
11
|
-
process.stdin.resume();
|
|
12
|
-
try {
|
|
13
|
-
const dir = path.join(os.homedir(), ".config", "powerplant");
|
|
14
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
15
|
-
mkdirSync(dir, { recursive: true });
|
|
16
|
-
// A fixed-size signal coalesces events. No paths or content come from stdin.
|
|
17
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
18
|
-
writeFileSync(path.join(dir, "session-change"), `${Date.now()}\n`, { mode: 0o600 });
|
|
19
|
-
} catch {
|
|
20
|
-
// A disk failure must not disrupt the agent. Recovery scans still run.
|
|
21
|
-
}
|