zombie-vibe 0.1.4 → 0.1.6
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 +2 -2
- package/package.json +17 -4
- package/runtime/hook.mjs +17 -2
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 the
|
|
3
|
+
Zombie Pulse installs deterministic prompt-submission hooks for Codex, Claude Code, Cursor, Windsurf, and Grok. The hook records both the user's local ISO timestamp and an unambiguous UTC audit timestamp, the IANA timezone when available, the local timezone offset, the editor/AI name, the session, and prompt length. It immediately discards the prompt text.
|
|
4
4
|
|
|
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.
|
|
5
|
+
Events live in a private persistent queue at `~/.zombie-vibe/events.jsonl`. Each buffered event stores `occurred_at_local`, `occurred_at_utc`, timezone metadata, 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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zombie-vibe",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Install privacy-first prompt timing hooks for Codex, Claude Code, Cursor, Windsurf, and Grok.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,9 +19,22 @@
|
|
|
19
19
|
"pack:check": "npm pack --dry-run",
|
|
20
20
|
"publish:npm": "bash scripts/publish-npm.sh"
|
|
21
21
|
},
|
|
22
|
-
"keywords": [
|
|
22
|
+
"keywords": [
|
|
23
|
+
"codex",
|
|
24
|
+
"claude-code",
|
|
25
|
+
"cursor",
|
|
26
|
+
"windsurf",
|
|
27
|
+
"grok",
|
|
28
|
+
"hooks",
|
|
29
|
+
"developer-wellness"
|
|
30
|
+
],
|
|
23
31
|
"license": "MIT",
|
|
24
|
-
"engines": {
|
|
25
|
-
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/gunjchhetri/zombie-vibe-skills.git"
|
|
38
|
+
},
|
|
26
39
|
"homepage": "https://codersarena.xyz/#integrations"
|
|
27
40
|
}
|
package/runtime/hook.mjs
CHANGED
|
@@ -10,6 +10,17 @@ const queuePath = join(dataDir, "events.jsonl");
|
|
|
10
10
|
const lockPath = join(dataDir, "events.lock");
|
|
11
11
|
const aiNames = { codex: "Codex", claude: "Claude", cursor: "Cursor", windsurf: "Windsurf", grok: "Grok" };
|
|
12
12
|
|
|
13
|
+
function localTimestamp(date, offsetMinutes = date.getTimezoneOffset()) {
|
|
14
|
+
const local = new Date(date.getTime() - offsetMinutes * 60_000).toISOString().slice(0, -1);
|
|
15
|
+
const absolute = Math.abs(offsetMinutes);
|
|
16
|
+
const sign = offsetMinutes <= 0 ? "+" : "-";
|
|
17
|
+
return `${local}${sign}${String(Math.floor(absolute / 60)).padStart(2, "0")}:${String(absolute % 60).padStart(2, "0")}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function timezoneName() {
|
|
21
|
+
try { return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined; } catch { return undefined; }
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
function arg(name, args = process.argv.slice(2)) {
|
|
14
25
|
const index = args.indexOf(name);
|
|
15
26
|
return index >= 0 ? args[index + 1] : undefined;
|
|
@@ -26,21 +37,25 @@ export function normalizeBufferedEvent(event) {
|
|
|
26
37
|
if (!parsed || Number.isNaN(parsed.getTime())) return event;
|
|
27
38
|
const { occurred_at: _legacyOccurredAt, ...rest } = event;
|
|
28
39
|
const agent = String(event?.agent || "unknown").toLowerCase().slice(0, 40);
|
|
29
|
-
|
|
40
|
+
const offset = Number.isInteger(event?.timezone_offset_minutes) ? event.timezone_offset_minutes : parsed.getTimezoneOffset();
|
|
41
|
+
return { ...rest, occurred_at_utc: parsed.toISOString(), occurred_at_local: event?.occurred_at_local || localTimestamp(parsed, offset), timezone_offset_minutes: offset, timezone_name: event?.timezone_name || timezoneName(), agent, ai_name: String(event?.ai_name || aiNames[agent] || agent).slice(0, 40) };
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
export function eventFromPayload(payload, agent = "unknown", now = new Date()) {
|
|
33
45
|
const prompt = promptFrom(payload);
|
|
34
46
|
const session = payload?.session_id ?? payload?.sessionId ?? payload?.conversation_id ?? payload?.trajectory_id ?? `anonymous-${randomBytes(6).toString("hex")}`;
|
|
35
47
|
const normalizedAgent = String(agent).trim().toLowerCase().slice(0, 40) || "unknown";
|
|
48
|
+
const timezoneOffsetMinutes = now.getTimezoneOffset();
|
|
36
49
|
return {
|
|
37
50
|
id: randomBytes(12).toString("base64url"),
|
|
38
51
|
occurred_at_utc: now.toISOString(),
|
|
52
|
+
occurred_at_local: localTimestamp(now, timezoneOffsetMinutes),
|
|
39
53
|
agent: normalizedAgent,
|
|
40
54
|
ai_name: aiNames[normalizedAgent] || normalizedAgent,
|
|
41
55
|
session_id: String(session).slice(0, 160),
|
|
42
56
|
prompt_length: [...prompt].length,
|
|
43
|
-
timezone_offset_minutes:
|
|
57
|
+
timezone_offset_minutes: timezoneOffsetMinutes,
|
|
58
|
+
timezone_name: timezoneName(),
|
|
44
59
|
};
|
|
45
60
|
}
|
|
46
61
|
|