@getpawl/setup 1.0.0 → 1.0.1

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 ADDED
@@ -0,0 +1,30 @@
1
+ # @fixhbone/agentmap-setup
2
+
3
+ One-shot setup for [AgentMap](https://github.com/0xfishbone/agentMap) + Claude Code hooks. Connects any repo to your AgentMap project so spec context is automatically synced during Claude Code sessions.
4
+
5
+ ## Usage
6
+
7
+ ```sh
8
+ npx @fixhbone/agentmap-setup <PROJECT_KEY>
9
+ ```
10
+
11
+ Get your `<PROJECT_KEY>` from the AgentMap web dashboard under **Settings > Setup**.
12
+
13
+ ## What it does
14
+
15
+ 1. Creates `.agentmap/.env` with API credentials
16
+ 2. Generates `.agentmap/sync.sh` — pull context + push diffs
17
+ 3. Configures `.claude/settings.json` with three lifecycle hooks:
18
+ - **SessionStart** — pulls latest project context
19
+ - **PostToolUse** (Write/Edit) — tracks modified files
20
+ - **Stop** — pushes session diff to AgentMap
21
+ 4. Adds sensitive paths to `.gitignore`
22
+
23
+ ## Requirements
24
+
25
+ - Node.js >= 18
26
+ - [Claude Code](https://claude.ai/claude-code) CLI installed
27
+
28
+ ## License
29
+
30
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/index.ts
5
+ var import_node_fs = require("fs");
6
+ var import_node_path = require("path");
7
+ function main() {
8
+ const encoded = process.argv[2];
9
+ if (!encoded) {
10
+ console.error(
11
+ "Usage: npx @getpawl/setup <PROJECT_KEY>\n\nGet your project key from the Pawl settings page."
12
+ );
13
+ process.exit(1);
14
+ }
15
+ let config;
16
+ try {
17
+ const decoded = Buffer.from(encoded, "base64").toString("utf-8");
18
+ config = JSON.parse(decoded);
19
+ } catch {
20
+ console.error("Error: Invalid project key \u2014 could not decode.");
21
+ process.exit(1);
22
+ }
23
+ if (!config.apiKey || !config.projectId || !config.apiUrl) {
24
+ console.error(
25
+ "Error: Project key is missing required fields (apiKey, projectId, apiUrl)."
26
+ );
27
+ process.exit(1);
28
+ }
29
+ const cwd = process.cwd();
30
+ writeEnvFile(cwd, config);
31
+ writeSyncScript(cwd, config);
32
+ copyParserScript(cwd);
33
+ mergeClaudeSettings(cwd);
34
+ writeClaudeMd(cwd);
35
+ updateGitignore(cwd);
36
+ console.log("\n Pawl setup complete!\n");
37
+ console.log(` Project: ${config.apiUrl}/projects/${config.projectId}`);
38
+ console.log(" Created: .agentmap/.env, .agentmap/sync.sh, .agentmap/parse-cc-session.js");
39
+ console.log(" Updated: .claude/settings.json, CLAUDE.md, .gitignore\n");
40
+ }
41
+ function writeEnvFile(cwd, config) {
42
+ const dir = (0, import_node_path.join)(cwd, ".agentmap");
43
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
44
+ const content = [
45
+ `AGENTMAP_API_KEY=${config.apiKey}`,
46
+ `AGENTMAP_PROJECT_ID=${config.projectId}`,
47
+ `AGENTMAP_API_URL=${config.apiUrl}`,
48
+ ""
49
+ ].join("\n");
50
+ (0, import_node_fs.writeFileSync)((0, import_node_path.join)(dir, ".env"), content, "utf-8");
51
+ }
52
+ function writeSyncScript(cwd, config) {
53
+ const dir = (0, import_node_path.join)(cwd, ".agentmap");
54
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
55
+ const script = `#!/usr/bin/env bash
56
+ set -euo pipefail
57
+
58
+ # Pawl sync script \u2014 generated by pawl-setup
59
+ # Do not edit manually; re-run npx @getpawl/setup to update.
60
+
61
+ SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
62
+ source "$SCRIPT_DIR/.env"
63
+
64
+ BASE_URL="$AGENTMAP_API_URL/api/projects/$AGENTMAP_PROJECT_ID"
65
+
66
+ # \u2500\u2500 pull mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
67
+ pull() {
68
+ # Recovery: if tracked-files exist from a previous incomplete session, push first
69
+ if [ -f "$SCRIPT_DIR/.tracked-files" ]; then
70
+ push >/dev/null 2>&1
71
+ fi
72
+
73
+ # Ensure specs directory exists
74
+ mkdir -p "$SCRIPT_DIR/specs"
75
+
76
+ # \u2500\u2500 Level 1: New file-based endpoint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
77
+ RESPONSE=$(curl -sf -X GET "$BASE_URL/context/files" \\
78
+ -H "Authorization: Bearer $AGENTMAP_API_KEY" \\
79
+ -H "Accept: application/json" 2>/dev/null) || true
80
+
81
+ if [ -n "$RESPONSE" ] && echo "$RESPONSE" | jq -e '.files' >/dev/null 2>&1; then
82
+ echo "$RESPONSE" | jq -r '.files[] | @base64' | while read -r encoded; do
83
+ FILE_PATH=$(echo "$encoded" | base64 -d | jq -r '.path')
84
+ FILE_CONTENT=$(echo "$encoded" | base64 -d | jq -r '.content')
85
+
86
+ # Ensure parent directory exists
87
+ mkdir -p "$SCRIPT_DIR/$(dirname "$FILE_PATH")"
88
+ echo "$FILE_CONTENT" > "$SCRIPT_DIR/$FILE_PATH"
89
+ done
90
+
91
+ # Output context.md to stdout (for SessionStart hook)
92
+ if [ -f "$SCRIPT_DIR/context.md" ]; then
93
+ cat "$SCRIPT_DIR/context.md"
94
+ fi
95
+ return 0
96
+ fi
97
+
98
+ # \u2500\u2500 Level 2: Legacy monolithic endpoint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
99
+ RESPONSE=$(curl -sf -X GET "$BASE_URL/context" \\
100
+ -H "Authorization: Bearer $AGENTMAP_API_KEY" \\
101
+ -H "Accept: application/json" 2>/dev/null) || true
102
+
103
+ if [ -n "$RESPONSE" ] && echo "$RESPONSE" | jq -e '.formatted_context' >/dev/null 2>&1; then
104
+ echo "$RESPONSE" | jq -r '.formatted_context // empty' > "$SCRIPT_DIR/context.md"
105
+ cat "$SCRIPT_DIR/context.md"
106
+ return 0
107
+ fi
108
+
109
+ # \u2500\u2500 Level 3: Cached file fallback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
110
+ if [ -f "$SCRIPT_DIR/context.md" ]; then
111
+ echo "# (cached \u2014 API unreachable)" | cat - "$SCRIPT_DIR/context.md"
112
+ return 0
113
+ fi
114
+
115
+ echo "Warning: Pawl context unavailable" >&2
116
+ return 1
117
+ }
118
+
119
+ # \u2500\u2500 push mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
120
+ push() {
121
+ LAST_SHA=""
122
+ if [ -f "$SCRIPT_DIR/.last-sync-sha" ]; then
123
+ LAST_SHA=$(cat "$SCRIPT_DIR/.last-sync-sha")
124
+ fi
125
+
126
+ CURRENT_SHA=$(git rev-parse HEAD 2>/dev/null || echo "")
127
+ BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
128
+
129
+ # Compute diff
130
+ if [ -n "$LAST_SHA" ]; then
131
+ DIFF=$(git diff "$LAST_SHA"..HEAD 2>/dev/null || echo "")
132
+ else
133
+ DIFF=$(git diff HEAD~1..HEAD 2>/dev/null || echo "")
134
+ fi
135
+
136
+ # Collect tracked files (dedup)
137
+ FILES_CHANGED="[]"
138
+ if [ -f "$SCRIPT_DIR/.tracked-files" ]; then
139
+ FILES_CHANGED=$(sort -u "$SCRIPT_DIR/.tracked-files" | jq -R . | jq -s .)
140
+ fi
141
+
142
+ # Commit info
143
+ COMMIT_MSG=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "")
144
+
145
+ # Session ID from Claude Code (if available)
146
+ SESSION_ID="\${CLAUDE_SESSION_ID:-}"
147
+
148
+ # Repo root for CC session lookup
149
+ REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
150
+
151
+ # Parse CC session data locally (if node + parser available)
152
+ CC_SESSION="{}"
153
+ if command -v node &> /dev/null; then
154
+ PARSER="$SCRIPT_DIR/parse-cc-session.js"
155
+ if [ -f "$PARSER" ]; then
156
+ CC_SESSION=$(node "$PARSER" "$REPO_PATH" 2>/dev/null || echo "{}")
157
+ fi
158
+ fi
159
+
160
+ # Extract cc_tasks from parser output (P14-02)
161
+ CC_TASKS=$(echo "$CC_SESSION" | jq -c '.tasks // []')
162
+
163
+ PAYLOAD=$(jq -n \\
164
+ --arg diff "$DIFF" \\
165
+ --arg branch "$BRANCH" \\
166
+ --arg commit_sha "$CURRENT_SHA" \\
167
+ --arg commit_message "$COMMIT_MSG" \\
168
+ --arg session_id "$SESSION_ID" \\
169
+ --arg last_sync_sha "$LAST_SHA" \\
170
+ --arg repo_path "$REPO_PATH" \\
171
+ --argjson files_changed "$FILES_CHANGED" \\
172
+ --argjson cc_session "$CC_SESSION" \\
173
+ --argjson cc_tasks "$CC_TASKS" \\
174
+ '{
175
+ diff: $diff,
176
+ branch: $branch,
177
+ commit_sha: $commit_sha,
178
+ commit_message: $commit_message,
179
+ session_id: $session_id,
180
+ last_sync_sha: $last_sync_sha,
181
+ repo_path: $repo_path,
182
+ files_changed: $files_changed,
183
+ cc_session: $cc_session,
184
+ cc_tasks: $cc_tasks
185
+ }')
186
+
187
+ RESPONSE=$(curl -sf -X POST "$BASE_URL/sync" \\
188
+ -H "Authorization: Bearer $AGENTMAP_API_KEY" \\
189
+ -H "Content-Type: application/json" \\
190
+ -d "$PAYLOAD")
191
+
192
+ echo "$RESPONSE" | jq .
193
+
194
+ # Update last sync SHA
195
+ echo "$CURRENT_SHA" > "$SCRIPT_DIR/.last-sync-sha"
196
+
197
+ # Cleanup tracked files
198
+ rm -f "$SCRIPT_DIR/.tracked-files"
199
+ }
200
+
201
+ # \u2500\u2500 dispatch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
202
+ case "\${1:-}" in
203
+ pull) pull ;;
204
+ push) push ;;
205
+ *)
206
+ echo "Usage: sync.sh [pull|push]"
207
+ exit 1
208
+ ;;
209
+ esac
210
+ `;
211
+ const scriptPath = (0, import_node_path.join)(dir, "sync.sh");
212
+ (0, import_node_fs.writeFileSync)(scriptPath, script, "utf-8");
213
+ (0, import_node_fs.chmodSync)(scriptPath, 493);
214
+ }
215
+ function copyParserScript(cwd) {
216
+ const src = (0, import_node_path.join)(__dirname, "parse-cc-session.js");
217
+ const dest = (0, import_node_path.join)(cwd, ".agentmap", "parse-cc-session.js");
218
+ if ((0, import_node_fs.existsSync)(src)) {
219
+ (0, import_node_fs.copyFileSync)(src, dest);
220
+ }
221
+ }
222
+ function mergeClaudeSettings(cwd) {
223
+ const claudeDir = (0, import_node_path.join)(cwd, ".claude");
224
+ (0, import_node_fs.mkdirSync)(claudeDir, { recursive: true });
225
+ const settingsPath = (0, import_node_path.join)(claudeDir, "settings.json");
226
+ let settings = {};
227
+ if ((0, import_node_fs.existsSync)(settingsPath)) {
228
+ try {
229
+ settings = JSON.parse((0, import_node_fs.readFileSync)(settingsPath, "utf-8"));
230
+ } catch {
231
+ }
232
+ }
233
+ const hooks = {
234
+ SessionStart: [
235
+ {
236
+ hooks: [
237
+ { type: "command", command: ".agentmap/sync.sh pull" }
238
+ ]
239
+ }
240
+ ],
241
+ PostToolUse: [
242
+ {
243
+ matcher: "Write|Edit",
244
+ hooks: [
245
+ {
246
+ type: "command",
247
+ command: "jq -r '.tool_input.file_path' >> .agentmap/.tracked-files"
248
+ }
249
+ ]
250
+ }
251
+ ],
252
+ Stop: [
253
+ {
254
+ hooks: [
255
+ { type: "command", command: ".agentmap/sync.sh push" }
256
+ ]
257
+ }
258
+ ]
259
+ };
260
+ const hooksObj = settings.hooks || {};
261
+ for (const [event, entries] of Object.entries(hooks)) {
262
+ const existing = hooksObj[event] || [];
263
+ for (const entry of entries) {
264
+ const cmd = entry.hooks[0].command;
265
+ const alreadyExists = existing.some(
266
+ (e) => e.hooks?.[0]?.command === cmd
267
+ );
268
+ if (!alreadyExists) {
269
+ existing.push(entry);
270
+ }
271
+ }
272
+ hooksObj[event] = existing;
273
+ }
274
+ settings.hooks = hooksObj;
275
+ (0, import_node_fs.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
276
+ }
277
+ function writeClaudeMd(cwd) {
278
+ const claudeMdPath = (0, import_node_path.join)(cwd, "CLAUDE.md");
279
+ const START_MARKER = "<!-- agentmap:start -->";
280
+ const END_MARKER = "<!-- agentmap:end -->";
281
+ const block = [
282
+ START_MARKER,
283
+ "# Pawl Context",
284
+ "",
285
+ "This project uses Pawl for spec management. Context files are in `.agentmap/`.",
286
+ "",
287
+ "## Context Files",
288
+ "- `.agentmap/context.md` \u2014 Project index, spec map, work items",
289
+ "- `.agentmap/specs/*.md` \u2014 Individual spec files with vision, implementation, dependencies",
290
+ "- `.agentmap/progress.md` \u2014 Last session summary, project health, recent decisions",
291
+ "",
292
+ "Read these files at the start of a session for full project context.",
293
+ "When asked about a spec, read the corresponding file in `.agentmap/specs/`.",
294
+ END_MARKER
295
+ ].join("\n");
296
+ if (!(0, import_node_fs.existsSync)(claudeMdPath)) {
297
+ (0, import_node_fs.writeFileSync)(claudeMdPath, block + "\n", "utf-8");
298
+ return;
299
+ }
300
+ let content = (0, import_node_fs.readFileSync)(claudeMdPath, "utf-8");
301
+ const startIdx = content.indexOf(START_MARKER);
302
+ const endIdx = content.indexOf(END_MARKER);
303
+ if (startIdx !== -1 && endIdx !== -1) {
304
+ content = content.slice(0, startIdx) + block + content.slice(endIdx + END_MARKER.length);
305
+ } else {
306
+ content = content.trimEnd() + "\n\n" + block + "\n";
307
+ }
308
+ (0, import_node_fs.writeFileSync)(claudeMdPath, content, "utf-8");
309
+ }
310
+ function updateGitignore(cwd) {
311
+ const gitignorePath = (0, import_node_path.join)(cwd, ".gitignore");
312
+ let content = "";
313
+ if ((0, import_node_fs.existsSync)(gitignorePath)) {
314
+ content = (0, import_node_fs.readFileSync)(gitignorePath, "utf-8");
315
+ }
316
+ const lines = content.split("\n");
317
+ const toAdd = [
318
+ ".agentmap/.env",
319
+ ".agentmap/.tracked-files",
320
+ ".agentmap/.last-sync-sha",
321
+ ".agentmap/context.md",
322
+ ".agentmap/specs/",
323
+ ".agentmap/progress.md"
324
+ ];
325
+ const missing = toAdd.filter((line) => !lines.includes(line));
326
+ if (missing.length > 0) {
327
+ const section = [];
328
+ if (!lines.includes("# pawl")) {
329
+ section.push("", "# pawl");
330
+ }
331
+ section.push(...missing);
332
+ const newContent = content.trimEnd() + "\n" + section.join("\n") + "\n";
333
+ (0, import_node_fs.writeFileSync)(gitignorePath, newContent, "utf-8");
334
+ }
335
+ }
336
+ main();
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/parse-cc-session.ts
26
+ var import_node_fs = require("fs");
27
+ var import_node_readline = require("readline");
28
+ var import_node_os = require("os");
29
+ var import_node_path = __toESM(require("path"));
30
+ var CC_MODEL_PRICING = {
31
+ "claude-opus-4-6": { input: 15, output: 75, cacheRead: 1.5 },
32
+ "claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheRead: 0.3 },
33
+ "claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3 },
34
+ "claude-haiku-4-5-20251001": { input: 0.8, output: 4, cacheRead: 0.08 }
35
+ };
36
+ function getProjectDir(repoPath) {
37
+ const encoded = "-" + repoPath.slice(1).replace(/\//g, "-");
38
+ const projectDir = import_node_path.default.join((0, import_node_os.homedir)(), ".claude", "projects", encoded);
39
+ try {
40
+ const s = (0, import_node_fs.statSync)(projectDir);
41
+ if (s.isDirectory()) return projectDir;
42
+ } catch {
43
+ }
44
+ return null;
45
+ }
46
+ async function parseSessionFile(filePath) {
47
+ try {
48
+ const rl = (0, import_node_readline.createInterface)({
49
+ input: (0, import_node_fs.createReadStream)(filePath),
50
+ crlfDelay: Infinity
51
+ });
52
+ let model = "";
53
+ let firstTimestamp = "";
54
+ const seenMessageIds = /* @__PURE__ */ new Set();
55
+ const filesRead = /* @__PURE__ */ new Set();
56
+ const filesWritten = /* @__PURE__ */ new Set();
57
+ const tasks = /* @__PURE__ */ new Map();
58
+ const pendingCreates = /* @__PURE__ */ new Map();
59
+ let currentTaskId = null;
60
+ let totalInputTokens = 0;
61
+ let totalOutputTokens = 0;
62
+ let cacheReadTokens = 0;
63
+ let cacheWriteTokens = 0;
64
+ for await (const line of rl) {
65
+ let parsed;
66
+ try {
67
+ parsed = JSON.parse(line);
68
+ } catch {
69
+ continue;
70
+ }
71
+ if (!firstTimestamp && parsed.timestamp) {
72
+ firstTimestamp = parsed.timestamp;
73
+ }
74
+ if (parsed.type === "assistant" && parsed.message) {
75
+ const msg = parsed.message;
76
+ const msgId = msg.id;
77
+ if (!model && msg.model) model = msg.model;
78
+ if (msgId && !seenMessageIds.has(msgId)) {
79
+ seenMessageIds.add(msgId);
80
+ const u = msg.usage;
81
+ if (u) {
82
+ totalInputTokens += u.input_tokens ?? 0;
83
+ totalOutputTokens += u.output_tokens ?? 0;
84
+ cacheReadTokens += u.cache_read_input_tokens ?? 0;
85
+ cacheWriteTokens += u.cache_creation_input_tokens ?? 0;
86
+ }
87
+ }
88
+ const content = msg.content;
89
+ if (Array.isArray(content)) {
90
+ for (const block of content) {
91
+ if (block.type !== "tool_use") continue;
92
+ const fp = block.input?.file_path;
93
+ if (fp) {
94
+ if (block.name === "Read") {
95
+ filesRead.add(fp);
96
+ } else if (block.name === "Write" || block.name === "Edit") {
97
+ filesWritten.add(fp);
98
+ }
99
+ }
100
+ if (block.name === "TaskCreate") {
101
+ pendingCreates.set(block.id, {
102
+ subject: block.input?.subject || "",
103
+ description: block.input?.description || ""
104
+ });
105
+ }
106
+ if (block.name === "TaskUpdate") {
107
+ const taskId = block.input?.taskId;
108
+ const task = taskId ? tasks.get(taskId) : void 0;
109
+ if (task && block.input?.status) {
110
+ task.status = block.input.status;
111
+ if (block.input.status === "in_progress")
112
+ currentTaskId = taskId;
113
+ if (block.input.status === "completed" || block.input.status === "deleted") {
114
+ if (currentTaskId === taskId) currentTaskId = null;
115
+ }
116
+ }
117
+ }
118
+ if (currentTaskId && (block.name === "Write" || block.name === "Edit") && fp) {
119
+ tasks.get(currentTaskId)?.filesAssociated.add(fp);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ if (parsed.type === "user" && parsed.toolUseResult?.task?.id) {
125
+ const tr = parsed.toolUseResult;
126
+ const content = parsed.message?.content;
127
+ const toolUseId = Array.isArray(content) ? content.find((b) => b.type === "tool_result")?.tool_use_id : void 0;
128
+ const pending = toolUseId ? pendingCreates.get(toolUseId) : void 0;
129
+ tasks.set(tr.task.id, {
130
+ id: tr.task.id,
131
+ subject: tr.task.subject || pending?.subject || "",
132
+ description: pending?.description || "",
133
+ status: "pending",
134
+ filesAssociated: /* @__PURE__ */ new Set()
135
+ });
136
+ if (toolUseId) pendingCreates.delete(toolUseId);
137
+ }
138
+ }
139
+ if (totalInputTokens === 0 && totalOutputTokens === 0) return null;
140
+ const pricing = CC_MODEL_PRICING[model] ?? {
141
+ input: 3,
142
+ output: 15,
143
+ cacheRead: 0.3
144
+ };
145
+ const realCost = (totalInputTokens * pricing.input + totalOutputTokens * pricing.output + cacheReadTokens * pricing.cacheRead + cacheWriteTokens * pricing.input) / 1e6;
146
+ let sessionDurationMs = 0;
147
+ if (firstTimestamp) {
148
+ try {
149
+ const mtime = (0, import_node_fs.statSync)(filePath).mtime.getTime();
150
+ const start = new Date(firstTimestamp).getTime();
151
+ if (mtime > start) sessionDurationMs = mtime - start;
152
+ } catch {
153
+ }
154
+ }
155
+ return {
156
+ filesRead: [...filesRead],
157
+ filesWritten: [...filesWritten],
158
+ totalInputTokens,
159
+ totalOutputTokens,
160
+ cacheReadTokens,
161
+ cacheWriteTokens,
162
+ model,
163
+ realCost,
164
+ sessionDurationMs,
165
+ tasks: Array.from(tasks.values()).map((t) => ({
166
+ id: t.id,
167
+ subject: t.subject,
168
+ description: t.description,
169
+ status: t.status,
170
+ filesAssociated: [...t.filesAssociated]
171
+ }))
172
+ };
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+ async function getMostRecentSession(repoPath) {
178
+ const projectDir = getProjectDir(repoPath);
179
+ if (!projectDir) return null;
180
+ let entries;
181
+ try {
182
+ entries = (0, import_node_fs.readdirSync)(projectDir);
183
+ } catch {
184
+ return null;
185
+ }
186
+ const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
187
+ if (jsonlFiles.length === 0) return null;
188
+ const withStats = jsonlFiles.map((f) => {
189
+ const fp = import_node_path.default.join(projectDir, f);
190
+ try {
191
+ const s = (0, import_node_fs.statSync)(fp);
192
+ return { file: f, mtime: s.mtime.getTime(), path: fp };
193
+ } catch {
194
+ return null;
195
+ }
196
+ }).filter((x) => x !== null);
197
+ if (withStats.length === 0) return null;
198
+ withStats.sort((a, b) => b.mtime - a.mtime);
199
+ return parseSessionFile(withStats[0].path);
200
+ }
201
+ async function main() {
202
+ const repoPath = process.argv[2];
203
+ if (!repoPath) {
204
+ process.stdout.write("{}");
205
+ process.exit(0);
206
+ }
207
+ try {
208
+ const result = await getMostRecentSession(repoPath);
209
+ process.stdout.write(JSON.stringify(result ?? {}));
210
+ } catch {
211
+ process.stdout.write("{}");
212
+ }
213
+ }
214
+ main();
package/package.json CHANGED
@@ -1,13 +1,34 @@
1
1
  {
2
2
  "name": "@getpawl/setup",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "index.js",
3
+ "version": "1.0.1",
4
+ "type": "commonjs",
5
+ "description": "One-shot setup for Pawl + Claude Code hooks",
6
+ "bin": {
7
+ "pawl-setup": "./dist/index.js"
8
+ },
6
9
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
10
+ "build": "tsup",
11
+ "lint": "tsc --noEmit",
12
+ "prepublishOnly": "pnpm build"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/0xfishbone/agentMap.git",
24
+ "directory": "packages/setup"
25
+ },
26
+ "devDependencies": {
27
+ "tsup": "^8.4.0",
28
+ "typescript": "^5.7.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=18"
8
32
  },
9
- "keywords": [],
10
- "author": "",
11
- "license": "ISC",
12
- "type": "commonjs"
33
+ "license": "MIT"
13
34
  }