@usepowerplant/cli 0.4.1 → 0.4.3

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.
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ // Local discovery only. The collector validates paths and capture policy
3
+ // before opening transcripts. Never persist prompts, email, or full input.
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { Buffer } from "node:buffer";
6
+ import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+
10
+ const MAX_INPUT_BYTES = 128 * 1024;
11
+ let chunks = [];
12
+ let bytes = 0;
13
+ process.stdin.on("error", () => {});
14
+ process.stdin.on("data", (chunk) => {
15
+ bytes += chunk.length;
16
+ if (bytes <= MAX_INPUT_BYTES) chunks.push(chunk);
17
+ else chunks = [];
18
+ });
19
+ process.stdin.on("end", () => {
20
+ const dir = path.join(os.homedir(), ".config", "powerplant");
21
+ try {
22
+ if (bytes <= MAX_INPUT_BYTES) {
23
+ const input = JSON.parse(Buffer.concat(chunks).toString("utf8"));
24
+ if (
25
+ ["sessionStart", "sessionEnd", "stop"].includes(input?.hook_event_name) &&
26
+ typeof input.conversation_id === "string" &&
27
+ /^[a-zA-Z0-9_-]{1,200}$/.test(input.conversation_id)
28
+ ) {
29
+ const roots =
30
+ Array.isArray(input.workspace_roots) &&
31
+ input.workspace_roots.length <= 8 &&
32
+ input.workspace_roots.every(
33
+ (p) => typeof p === "string" && p.length <= 4096 && path.isAbsolute(p)
34
+ )
35
+ ? input.workspace_roots
36
+ : [];
37
+ const record = {
38
+ sessionId: input.conversation_id,
39
+ transcriptPath:
40
+ typeof input.transcript_path === "string" &&
41
+ input.transcript_path.length <= 4096 &&
42
+ path.isAbsolute(input.transcript_path)
43
+ ? input.transcript_path
44
+ : null,
45
+ workspaceRoots: roots,
46
+ ...(typeof input.model === "string" &&
47
+ input.model.length <= 256 && { model: input.model }),
48
+ };
49
+ const records = path.join(dir, "cursor-transcripts");
50
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
51
+ mkdirSync(records, { recursive: true, mode: 0o700 });
52
+ const file = path.join(
53
+ records,
54
+ `${createHash("sha256").update(record.sessionId).digest("hex")}.json`
55
+ );
56
+ const temporary = `${file}.${randomUUID()}.tmp`;
57
+ try {
58
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
59
+ writeFileSync(temporary, JSON.stringify(record), { mode: 0o600, flag: "wx" });
60
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
61
+ renameSync(temporary, file);
62
+ } finally {
63
+ rmSync(temporary, { force: true });
64
+ }
65
+ }
66
+ }
67
+ } catch {
68
+ // Discovery failure must never stop the provider. Existing capture remains.
69
+ }
70
+ try {
71
+ // Publish after the pointer so the watcher sees a complete discovery record.
72
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
73
+ mkdirSync(dir, { recursive: true });
74
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
75
+ writeFileSync(path.join(dir, "session-change"), `${Date.now()}\n`, { mode: 0o600 });
76
+ } catch {
77
+ // No output to the agent, even if the disk is unavailable.
78
+ }
79
+ });
@@ -0,0 +1,21 @@
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usepowerplant/cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },