zombie-vibe 0.1.2 → 0.1.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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # Zombie Vibe Hooks
2
2
 
3
- Zombie Pulse installs deterministic prompt-submission hooks for Codex, Claude Code, Cursor, Windsurf, and Grok. The hook records when a prompt was submitted, the editor and session, the local timezone offset, and prompt length. It immediately discards the prompt text.
3
+ Zombie Pulse installs deterministic prompt-submission hooks for Codex, Claude Code, Cursor, Windsurf, and Grok. The hook records the submission time as an explicit UTC ISO timestamp, the editor/AI name, the session, the local timezone offset, and prompt length. It immediately discards the prompt text.
4
4
 
5
- Events live in a private persistent queue at `~/.zombie-vibe/events.jsonl`. When the queue reaches 10 events, the oldest 10 are sent to the Zombie Vibe API. Failed uploads stay queued for a later prompt or manual flush.
5
+ Events live in a private persistent queue at `~/.zombie-vibe/events.jsonl`. Each buffered event stores `occurred_at_utc`, the machine-readable `agent`, and a display-ready `ai_name`; older events are upgraded automatically when the queue is next read. When the queue reaches 10 events, the oldest 10 are sent to the Zombie Vibe API. Failed uploads stay queued for a later prompt or manual flush.
6
6
 
7
7
  ## Install
8
8
 
@@ -24,6 +24,10 @@ npx zombie-vibe install --editor grok --api-key zp_live_YOUR_KEY
24
24
 
25
25
  The installer copies a stable runtime to `~/.zombie-vibe/hook.mjs`, stores the key in a mode-`0600` config file, merges the hook into the editor's existing JSON, and creates a backup before every edit. Restart an open editor after installation.
26
26
 
27
+ ### Codex plugin trust
28
+
29
+ The npm installer and the optional Zombie Pulse Codex plugin are separate setup paths. If you also install the plugin, open Codex Plugins (or run `/plugins` in Codex CLI), install Zombie Pulse from a configured marketplace, review and trust its hook commands, then enable it. Start a new Codex session after enabling it so the plugin and hook configuration reload.
30
+
27
31
  The same `npx` commands work from Windows PowerShell or Command Prompt with Node.js 20 or newer. On Windows, the files are placed under `%USERPROFILE%\.zombie-vibe` and the installer generates Windows-compatible hook commands automatically. The `chmod 600` permission step applies only to manual installation on macOS/Linux; Windows uses the normal user-profile permissions.
28
32
 
29
33
  ## Hook locations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zombie-vibe",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Install privacy-first prompt timing hooks for Codex, Claude Code, Cursor, Windsurf, and Grok.",
5
5
  "type": "module",
6
6
  "bin": {
package/runtime/hook.mjs CHANGED
@@ -8,6 +8,7 @@ const dataDir = join(homedir(), ".zombie-vibe");
8
8
  const configPath = join(dataDir, "config.json");
9
9
  const queuePath = join(dataDir, "events.jsonl");
10
10
  const lockPath = join(dataDir, "events.lock");
11
+ const aiNames = { codex: "Codex", claude: "Claude", cursor: "Cursor", windsurf: "Windsurf", grok: "Grok" };
11
12
 
12
13
  function arg(name, args = process.argv.slice(2)) {
13
14
  const index = args.indexOf(name);
@@ -19,13 +20,24 @@ function promptFrom(payload) {
19
20
  return typeof candidate === "string" ? candidate : JSON.stringify(candidate || "");
20
21
  }
21
22
 
23
+ export function normalizeBufferedEvent(event) {
24
+ const source = event?.occurred_at_utc ?? event?.occurred_at;
25
+ const parsed = source ? new Date(source) : undefined;
26
+ if (!parsed || Number.isNaN(parsed.getTime())) return event;
27
+ const { occurred_at: _legacyOccurredAt, ...rest } = event;
28
+ const agent = String(event?.agent || "unknown").toLowerCase().slice(0, 40);
29
+ return { ...rest, occurred_at_utc: parsed.toISOString(), agent, ai_name: String(event?.ai_name || aiNames[agent] || agent).slice(0, 40) };
30
+ }
31
+
22
32
  export function eventFromPayload(payload, agent = "unknown", now = new Date()) {
23
33
  const prompt = promptFrom(payload);
24
34
  const session = payload?.session_id ?? payload?.sessionId ?? payload?.conversation_id ?? payload?.trajectory_id ?? `anonymous-${randomBytes(6).toString("hex")}`;
35
+ const normalizedAgent = String(agent).trim().toLowerCase().slice(0, 40) || "unknown";
25
36
  return {
26
37
  id: randomBytes(12).toString("base64url"),
27
- occurred_at: now.toISOString(),
28
- agent: String(agent).slice(0, 40),
38
+ occurred_at_utc: now.toISOString(),
39
+ agent: normalizedAgent,
40
+ ai_name: aiNames[normalizedAgent] || normalizedAgent,
29
41
  session_id: String(session).slice(0, 160),
30
42
  prompt_length: [...prompt].length,
31
43
  timezone_offset_minutes: now.getTimezoneOffset(),
@@ -51,7 +63,7 @@ async function withQueue(operation) {
51
63
  const handle = await lock();
52
64
  try {
53
65
  let events = [];
54
- try { events = (await readFile(queuePath, "utf8")).split("\n").filter(Boolean).map((line) => JSON.parse(line)); } catch (error) {
66
+ try { events = (await readFile(queuePath, "utf8")).split("\n").filter(Boolean).map((line) => normalizeBufferedEvent(JSON.parse(line))); } catch (error) {
55
67
  if (error?.code !== "ENOENT") throw error;
56
68
  }
57
69
  const next = await operation(events);
@@ -69,7 +81,7 @@ async function sendBatch(config, events) {
69
81
  if (!config.apiKey || !config.apiUrl) return false;
70
82
  const response = await fetch(config.apiUrl, {
71
83
  method: "POST",
72
- headers: { authorization: `Bearer ${config.apiKey}`, "content-type": "application/json", "user-agent": "zombie-vibe-hook/0.1.0" },
84
+ headers: { authorization: `Bearer ${config.apiKey}`, "content-type": "application/json", "user-agent": "zombie-vibe-hook/0.1.4" },
73
85
  body: JSON.stringify({ events }),
74
86
  signal: AbortSignal.timeout(3000),
75
87
  });