@getpawl/setup 1.0.1 → 1.1.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.
Files changed (2) hide show
  1. package/dist/index.js +559 -14
  2. package/package.json +5 -3
package/dist/index.js CHANGED
@@ -4,14 +4,145 @@
4
4
  // src/index.ts
5
5
  var import_node_fs = require("fs");
6
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
- );
7
+ var import_node_child_process = require("child_process");
8
+ var DEFAULT_API_URL = "https://agentmap-api.onrender.com";
9
+ async function main() {
10
+ const arg = process.argv[2];
11
+ if (!arg) {
12
+ printUsage();
13
+ process.exit(0);
14
+ }
15
+ if (arg === "sync") {
16
+ pawlSync(process.argv[3]);
17
+ } else if (arg === "connect") {
18
+ await pawlConnect();
19
+ } else if (arg === "init") {
20
+ const key = process.argv[3];
21
+ pawlInit(key);
22
+ } else {
23
+ legacySetup(arg);
24
+ }
25
+ }
26
+ function printUsage() {
27
+ console.log(`
28
+ Usage:
29
+ pawl init Initialize Pawl in this repo
30
+ pawl connect Link this repo to a Pawl project (opens browser)
31
+ pawl sync [--pull] Sync with Pawl dashboard (push by default)
32
+ pawl-setup <PROJECT_KEY> Legacy setup (still supported)
33
+
34
+ Dashboard: https://pawl.dev
35
+ `);
36
+ }
37
+ function pawlSync(flag) {
38
+ const cwd = process.cwd();
39
+ const syncScript = (0, import_node_path.join)(cwd, ".pawl", "sync.sh");
40
+ if (!(0, import_node_fs.existsSync)(syncScript)) {
41
+ console.error("Error: .pawl/sync.sh not found \u2014 run `pawl init` first.");
42
+ process.exit(1);
43
+ }
44
+ const envFile = (0, import_node_path.join)(cwd, ".pawl", ".env");
45
+ if (!(0, import_node_fs.existsSync)(envFile)) {
46
+ console.error("Error: .pawl/.env not found \u2014 run `pawl connect` to link your project.");
47
+ process.exit(1);
48
+ }
49
+ const mode = flag === "--pull" ? "pull" : "push";
50
+ (0, import_node_child_process.execSync)(`.pawl/sync.sh ${mode}`, { stdio: "inherit", cwd });
51
+ }
52
+ function generateCode() {
53
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
54
+ let code = "";
55
+ for (let i = 0; i < 6; i++) {
56
+ code += chars[Math.floor(Math.random() * chars.length)];
57
+ }
58
+ return code;
59
+ }
60
+ function getRepoName() {
61
+ try {
62
+ const remote = (0, import_node_child_process.execSync)("git remote get-url origin", { encoding: "utf-8" }).trim();
63
+ const match = remote.match(/\/([^/]+?)(?:\.git)?$/);
64
+ if (match) return match[1];
65
+ } catch {
66
+ }
67
+ return (0, import_node_path.basename)(process.cwd());
68
+ }
69
+ function openBrowser(url) {
70
+ try {
71
+ const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
72
+ (0, import_node_child_process.execSync)(cmd, { stdio: "ignore" });
73
+ } catch {
74
+ console.log(` Open this URL in your browser:
75
+ ${url}`);
76
+ }
77
+ }
78
+ function sleep(ms) {
79
+ return new Promise((resolve) => setTimeout(resolve, ms));
80
+ }
81
+ async function pollForKey(apiUrl, code) {
82
+ const deadline = Date.now() + 5 * 60 * 1e3;
83
+ const interval = 2e3;
84
+ while (Date.now() < deadline) {
85
+ try {
86
+ const res = await fetch(`${apiUrl}/api/cli/connect/poll?code=${code}`);
87
+ if (!res.ok) {
88
+ await sleep(interval);
89
+ continue;
90
+ }
91
+ const data = await res.json();
92
+ if (data.status === "ready") {
93
+ return {
94
+ apiKey: data.apiKey,
95
+ projectId: data.projectId,
96
+ apiUrl: data.apiUrl
97
+ };
98
+ }
99
+ if (data.status === "expired") {
100
+ return null;
101
+ }
102
+ } catch {
103
+ }
104
+ await sleep(interval);
105
+ }
106
+ return null;
107
+ }
108
+ async function pawlConnect() {
109
+ const cwd = process.cwd();
110
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".pawl"))) {
111
+ console.error("Error: .pawl/ not found \u2014 run `pawl init` first.");
112
+ process.exit(1);
113
+ }
114
+ const apiUrl = DEFAULT_API_URL;
115
+ const code = generateCode();
116
+ const repoName = getRepoName();
117
+ const startRes = await fetch(`${apiUrl}/api/cli/connect/start`, {
118
+ method: "POST",
119
+ headers: { "Content-Type": "application/json" },
120
+ body: JSON.stringify({ code, repoName })
121
+ });
122
+ if (!startRes.ok) {
123
+ console.error("Error: Could not start connect session. Is the API reachable?");
13
124
  process.exit(1);
14
125
  }
126
+ const frontendUrl = apiUrl.replace("-api", "").replace(":3001", ":3002");
127
+ const connectUrl = `${frontendUrl}/connect?code=${code}&repo=${encodeURIComponent(repoName)}`;
128
+ console.log(`
129
+ Opening browser to link "${repoName}" to a Pawl project...
130
+ `);
131
+ openBrowser(connectUrl);
132
+ console.log(" Waiting for you to select a project in the browser...");
133
+ console.log(" (Press Ctrl+C to cancel)\n");
134
+ const result = await pollForKey(apiUrl, code);
135
+ if (!result) {
136
+ console.error(" Session expired or cancelled. Run `pawl connect` to try again.");
137
+ process.exit(1);
138
+ }
139
+ (0, import_node_fs.mkdirSync)((0, import_node_path.join)(cwd, ".pawl"), { recursive: true });
140
+ writePawlEnvFile(cwd, result);
141
+ console.log(" Connected! .pawl/.env written.\n");
142
+ console.log(` Project: ${result.apiUrl}/projects/${result.projectId}`);
143
+ console.log(" Run `pawl sync` to push your first session.\n");
144
+ }
145
+ function legacySetup(encoded) {
15
146
  let config;
16
147
  try {
17
148
  const decoded = Buffer.from(encoded, "base64").toString("utf-8");
@@ -38,6 +169,411 @@ function main() {
38
169
  console.log(" Created: .agentmap/.env, .agentmap/sync.sh, .agentmap/parse-cc-session.js");
39
170
  console.log(" Updated: .claude/settings.json, CLAUDE.md, .gitignore\n");
40
171
  }
172
+ function pawlInit(key) {
173
+ const cwd = process.cwd();
174
+ const detected = detectAgents(cwd);
175
+ migrateIfNeeded(cwd, detected.hasAgentMapDir);
176
+ (0, import_node_fs.mkdirSync)((0, import_node_path.join)(cwd, ".pawl"), { recursive: true });
177
+ if (key) {
178
+ let config;
179
+ try {
180
+ const decoded = Buffer.from(key, "base64").toString("utf-8");
181
+ config = JSON.parse(decoded);
182
+ } catch {
183
+ console.error("Error: Invalid project key \u2014 could not decode.");
184
+ process.exit(1);
185
+ }
186
+ writePawlEnvFile(cwd, config);
187
+ }
188
+ writePawlSyncScript(cwd);
189
+ copyParserScript(cwd, ".pawl");
190
+ if (detected.hasCC) {
191
+ mergeClaudeSettings(cwd, ".pawl");
192
+ }
193
+ if (detected.hasGemini) {
194
+ mergeGeminiSettings(cwd);
195
+ }
196
+ writeGitHook(cwd, detected.hasGitDir);
197
+ writePawlClaudeMd(cwd);
198
+ writeAgentsMd(cwd);
199
+ updateGitignore(cwd);
200
+ printSummary(detected, !!key);
201
+ }
202
+ function detectAgents(cwd) {
203
+ return {
204
+ hasCC: (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".claude")),
205
+ hasGemini: (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".gemini")),
206
+ hasAgentMapDir: (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".agentmap")),
207
+ hasGitDir: (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".git"))
208
+ };
209
+ }
210
+ function migrateIfNeeded(cwd, hasAgentMapDir) {
211
+ if (!hasAgentMapDir) return;
212
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".pawl"))) return;
213
+ console.log(" Migrating .agentmap/ \u2192 .pawl/...");
214
+ (0, import_node_fs.mkdirSync)((0, import_node_path.join)(cwd, ".pawl"), { recursive: true });
215
+ const filesToCopy = [".env", "sync.sh", "parse-cc-session.js", "context.md", "progress.md"];
216
+ for (const file of filesToCopy) {
217
+ const src = (0, import_node_path.join)(cwd, ".agentmap", file);
218
+ if ((0, import_node_fs.existsSync)(src)) {
219
+ (0, import_node_fs.copyFileSync)(src, (0, import_node_path.join)(cwd, ".pawl", file));
220
+ }
221
+ }
222
+ const specsDir = (0, import_node_path.join)(cwd, ".agentmap", "specs");
223
+ if ((0, import_node_fs.existsSync)(specsDir)) {
224
+ (0, import_node_fs.cpSync)(specsDir, (0, import_node_path.join)(cwd, ".pawl", "specs"), { recursive: true });
225
+ }
226
+ const envPath = (0, import_node_path.join)(cwd, ".pawl", ".env");
227
+ if ((0, import_node_fs.existsSync)(envPath)) {
228
+ let envContent = (0, import_node_fs.readFileSync)(envPath, "utf-8");
229
+ envContent = envContent.replace(/AGENTMAP_/g, "PAWL_");
230
+ (0, import_node_fs.writeFileSync)(envPath, envContent, "utf-8");
231
+ }
232
+ console.log(" .agentmap/ preserved \u2014 safe to delete manually when ready.\n");
233
+ }
234
+ function writePawlEnvFile(cwd, config) {
235
+ const content = [
236
+ `PAWL_API_KEY=${config.apiKey}`,
237
+ `PAWL_PROJECT_ID=${config.projectId}`,
238
+ `PAWL_API_URL=${config.apiUrl}`,
239
+ ""
240
+ ].join("\n");
241
+ (0, import_node_fs.writeFileSync)((0, import_node_path.join)(cwd, ".pawl", ".env"), content, "utf-8");
242
+ }
243
+ function writePawlSyncScript(cwd) {
244
+ const script = `#!/usr/bin/env bash
245
+ set -euo pipefail
246
+
247
+ # Pawl sync script \u2014 generated by pawl init
248
+ # Do not edit manually; re-run pawl init to update.
249
+
250
+ SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
251
+
252
+ # Source env only if it exists (pawl init without key skips .env)
253
+ if [ -f "$SCRIPT_DIR/.env" ]; then
254
+ source "$SCRIPT_DIR/.env"
255
+ fi
256
+
257
+ BASE_URL="\${PAWL_API_URL:-}/api/projects/\${PAWL_PROJECT_ID:-}"
258
+
259
+ # \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
260
+ pull() {
261
+ if [ -z "\${PAWL_API_KEY:-}" ]; then
262
+ echo "Warning: PAWL_API_KEY not set \u2014 sync disabled" >&2
263
+ if [ -f "$SCRIPT_DIR/context.md" ]; then
264
+ cat "$SCRIPT_DIR/context.md"
265
+ fi
266
+ return 0
267
+ fi
268
+
269
+ # Recovery: if tracked-files exist from a previous incomplete session, push first
270
+ if [ -f "$SCRIPT_DIR/.tracked-files" ]; then
271
+ push >/dev/null 2>&1
272
+ fi
273
+
274
+ # Ensure specs directory exists
275
+ mkdir -p "$SCRIPT_DIR/specs"
276
+
277
+ # \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
278
+ RESPONSE=$(curl -sf -X GET "$BASE_URL/context/files" \\
279
+ -H "Authorization: Bearer $PAWL_API_KEY" \\
280
+ -H "Accept: application/json" 2>/dev/null) || true
281
+
282
+ if [ -n "$RESPONSE" ] && echo "$RESPONSE" | jq -e '.files' >/dev/null 2>&1; then
283
+ echo "$RESPONSE" | jq -r '.files[] | @base64' | while read -r encoded; do
284
+ FILE_PATH=$(echo "$encoded" | base64 -d | jq -r '.path')
285
+ FILE_CONTENT=$(echo "$encoded" | base64 -d | jq -r '.content')
286
+
287
+ # Ensure parent directory exists
288
+ mkdir -p "$SCRIPT_DIR/$(dirname "$FILE_PATH")"
289
+ echo "$FILE_CONTENT" > "$SCRIPT_DIR/$FILE_PATH"
290
+ done
291
+
292
+ # Output context.md to stdout (for SessionStart hook)
293
+ if [ -f "$SCRIPT_DIR/context.md" ]; then
294
+ cat "$SCRIPT_DIR/context.md"
295
+ fi
296
+ return 0
297
+ fi
298
+
299
+ # \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
300
+ RESPONSE=$(curl -sf -X GET "$BASE_URL/context" \\
301
+ -H "Authorization: Bearer $PAWL_API_KEY" \\
302
+ -H "Accept: application/json" 2>/dev/null) || true
303
+
304
+ if [ -n "$RESPONSE" ] && echo "$RESPONSE" | jq -e '.formatted_context' >/dev/null 2>&1; then
305
+ echo "$RESPONSE" | jq -r '.formatted_context // empty' > "$SCRIPT_DIR/context.md"
306
+ cat "$SCRIPT_DIR/context.md"
307
+ return 0
308
+ fi
309
+
310
+ # \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
311
+ if [ -f "$SCRIPT_DIR/context.md" ]; then
312
+ echo "# (cached \u2014 API unreachable)" | cat - "$SCRIPT_DIR/context.md"
313
+ return 0
314
+ fi
315
+
316
+ echo "Warning: Pawl context unavailable" >&2
317
+ return 1
318
+ }
319
+
320
+ # \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
321
+ push() {
322
+ if [ -z "\${PAWL_API_KEY:-}" ]; then
323
+ return 0
324
+ fi
325
+
326
+ LAST_SHA=""
327
+ if [ -f "$SCRIPT_DIR/.last-sync-sha" ]; then
328
+ LAST_SHA=$(cat "$SCRIPT_DIR/.last-sync-sha")
329
+ fi
330
+
331
+ CURRENT_SHA=$(git rev-parse HEAD 2>/dev/null || echo "")
332
+ BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
333
+
334
+ # Compute diff
335
+ if [ -n "$LAST_SHA" ]; then
336
+ DIFF=$(git diff "$LAST_SHA"..HEAD 2>/dev/null || echo "")
337
+ else
338
+ DIFF=$(git diff HEAD~1..HEAD 2>/dev/null || echo "")
339
+ fi
340
+
341
+ # Collect tracked files (dedup)
342
+ FILES_CHANGED="[]"
343
+ if [ -f "$SCRIPT_DIR/.tracked-files" ]; then
344
+ FILES_CHANGED=$(sort -u "$SCRIPT_DIR/.tracked-files" | jq -R . | jq -s .)
345
+ fi
346
+
347
+ # Commit info
348
+ COMMIT_MSG=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "")
349
+
350
+ # Session ID from Claude Code (if available)
351
+ SESSION_ID="\${CLAUDE_SESSION_ID:-}"
352
+
353
+ # Repo root for CC session lookup
354
+ REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
355
+
356
+ # Parse CC session data locally (if node + parser available)
357
+ CC_SESSION="{}"
358
+ if command -v node &> /dev/null; then
359
+ PARSER="$SCRIPT_DIR/parse-cc-session.js"
360
+ if [ -f "$PARSER" ]; then
361
+ CC_SESSION=$(node "$PARSER" "$REPO_PATH" 2>/dev/null || echo "{}")
362
+ fi
363
+ fi
364
+
365
+ # Extract cc_tasks from parser output
366
+ CC_TASKS=$(echo "$CC_SESSION" | jq -c '.tasks // []')
367
+
368
+ PAYLOAD=$(jq -n \\
369
+ --arg diff "$DIFF" \\
370
+ --arg branch "$BRANCH" \\
371
+ --arg commit_sha "$CURRENT_SHA" \\
372
+ --arg commit_message "$COMMIT_MSG" \\
373
+ --arg session_id "$SESSION_ID" \\
374
+ --arg last_sync_sha "$LAST_SHA" \\
375
+ --arg repo_path "$REPO_PATH" \\
376
+ --argjson files_changed "$FILES_CHANGED" \\
377
+ --argjson cc_session "$CC_SESSION" \\
378
+ --argjson cc_tasks "$CC_TASKS" \\
379
+ '{
380
+ diff: $diff,
381
+ branch: $branch,
382
+ commit_sha: $commit_sha,
383
+ commit_message: $commit_message,
384
+ session_id: $session_id,
385
+ last_sync_sha: $last_sync_sha,
386
+ repo_path: $repo_path,
387
+ files_changed: $files_changed,
388
+ cc_session: $cc_session,
389
+ cc_tasks: $cc_tasks
390
+ }')
391
+
392
+ RESPONSE=$(curl -sf -X POST "$BASE_URL/sync" \\
393
+ -H "Authorization: Bearer $PAWL_API_KEY" \\
394
+ -H "Content-Type: application/json" \\
395
+ -d "$PAYLOAD")
396
+
397
+ echo "$RESPONSE" | jq .
398
+
399
+ # Update last sync SHA
400
+ echo "$CURRENT_SHA" > "$SCRIPT_DIR/.last-sync-sha"
401
+
402
+ # Cleanup tracked files
403
+ rm -f "$SCRIPT_DIR/.tracked-files"
404
+ }
405
+
406
+ # \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
407
+ case "\${1:-}" in
408
+ pull) pull ;;
409
+ push) push ;;
410
+ *)
411
+ echo "Usage: sync.sh [pull|push]"
412
+ exit 1
413
+ ;;
414
+ esac
415
+ `;
416
+ const scriptPath = (0, import_node_path.join)(cwd, ".pawl", "sync.sh");
417
+ (0, import_node_fs.writeFileSync)(scriptPath, script, "utf-8");
418
+ (0, import_node_fs.chmodSync)(scriptPath, 493);
419
+ }
420
+ function mergeGeminiSettings(cwd) {
421
+ const geminiDir = (0, import_node_path.join)(cwd, ".gemini");
422
+ (0, import_node_fs.mkdirSync)(geminiDir, { recursive: true });
423
+ const settingsPath = (0, import_node_path.join)(geminiDir, "settings.json");
424
+ let settings = {};
425
+ if ((0, import_node_fs.existsSync)(settingsPath)) {
426
+ try {
427
+ settings = JSON.parse((0, import_node_fs.readFileSync)(settingsPath, "utf-8"));
428
+ } catch {
429
+ }
430
+ }
431
+ const hooksToInstall = {
432
+ SessionStart: { command: ".pawl/sync.sh pull" },
433
+ AfterAgentLoop: { command: ".pawl/sync.sh push" }
434
+ };
435
+ const hooksObj = settings.hooks || {};
436
+ for (const [event, entry] of Object.entries(hooksToInstall)) {
437
+ const existing = hooksObj[event] || [];
438
+ const alreadyExists = existing.some((e) => e.command === entry.command);
439
+ if (!alreadyExists) {
440
+ existing.push(entry);
441
+ }
442
+ hooksObj[event] = existing;
443
+ }
444
+ settings.hooks = hooksObj;
445
+ (0, import_node_fs.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
446
+ }
447
+ function writeGitHook(cwd, hasGitDir) {
448
+ if (!hasGitDir) return;
449
+ const hooksDir = (0, import_node_path.join)(cwd, ".git", "hooks");
450
+ (0, import_node_fs.mkdirSync)(hooksDir, { recursive: true });
451
+ const hookPath = (0, import_node_path.join)(hooksDir, "post-commit");
452
+ const hookBlock = `#!/bin/sh
453
+ # Pawl sync hook \u2014 fires after every commit
454
+ # Provides universal sync fallback for agents without lifecycle hooks (Codex, etc.)
455
+ if [ -f ".pawl/sync.sh" ]; then
456
+ .pawl/sync.sh push
457
+ fi`;
458
+ if (!(0, import_node_fs.existsSync)(hookPath)) {
459
+ (0, import_node_fs.writeFileSync)(hookPath, hookBlock + "\n", "utf-8");
460
+ (0, import_node_fs.chmodSync)(hookPath, 493);
461
+ return;
462
+ }
463
+ const content = (0, import_node_fs.readFileSync)(hookPath, "utf-8");
464
+ if (content.includes("Pawl sync hook")) {
465
+ return;
466
+ }
467
+ (0, import_node_fs.writeFileSync)(hookPath, content.trimEnd() + "\n\n" + hookBlock + "\n", "utf-8");
468
+ (0, import_node_fs.chmodSync)(hookPath, 493);
469
+ }
470
+ function writePawlClaudeMd(cwd) {
471
+ const claudeMdPath = (0, import_node_path.join)(cwd, "CLAUDE.md");
472
+ const PAWL_START = "<!-- pawl:start -->";
473
+ const PAWL_END = "<!-- pawl:end -->";
474
+ const AGENTMAP_START = "<!-- agentmap:start -->";
475
+ const AGENTMAP_END = "<!-- agentmap:end -->";
476
+ const block = [
477
+ PAWL_START,
478
+ "# Pawl Context",
479
+ "",
480
+ "This project uses Pawl for spec management and AI session tracking.",
481
+ "",
482
+ "Context files are in `.pawl/`:",
483
+ "- `.pawl/context.md` \u2014 project index, spec map, health",
484
+ "- `.pawl/specs/` \u2014 individual spec files (one per feature area)",
485
+ "- `.pawl/progress.md` \u2014 last session summary",
486
+ "",
487
+ "Read `.pawl/context.md` at session start, then load relevant specs from `.pawl/specs/` based on the task.",
488
+ PAWL_END
489
+ ].join("\n");
490
+ if (!(0, import_node_fs.existsSync)(claudeMdPath)) {
491
+ (0, import_node_fs.writeFileSync)(claudeMdPath, block + "\n", "utf-8");
492
+ return;
493
+ }
494
+ let content = (0, import_node_fs.readFileSync)(claudeMdPath, "utf-8");
495
+ let startIdx = content.indexOf(PAWL_START);
496
+ let endIdx = content.indexOf(PAWL_END);
497
+ let endMarkerLen = PAWL_END.length;
498
+ if (startIdx === -1 || endIdx === -1) {
499
+ startIdx = content.indexOf(AGENTMAP_START);
500
+ endIdx = content.indexOf(AGENTMAP_END);
501
+ endMarkerLen = AGENTMAP_END.length;
502
+ }
503
+ if (startIdx !== -1 && endIdx !== -1) {
504
+ content = content.slice(0, startIdx) + block + content.slice(endIdx + endMarkerLen);
505
+ } else {
506
+ content = content.trimEnd() + "\n\n" + block + "\n";
507
+ }
508
+ (0, import_node_fs.writeFileSync)(claudeMdPath, content, "utf-8");
509
+ }
510
+ function writeAgentsMd(cwd) {
511
+ const agentsMdPath = (0, import_node_path.join)(cwd, "AGENTS.md");
512
+ const START_MARKER = "<!-- pawl:start -->";
513
+ const END_MARKER = "<!-- pawl:end -->";
514
+ const block = [
515
+ START_MARKER,
516
+ "## Pawl Project Context",
517
+ "",
518
+ "This project uses Pawl for spec management and AI session tracking.",
519
+ "Structured context files are available at:",
520
+ "",
521
+ "- `.pawl/context.md` \u2014 project index, spec map, health overview",
522
+ "- `.pawl/specs/` \u2014 individual spec files (one per feature area)",
523
+ "- `.pawl/progress.md` \u2014 last session summary, recent decisions",
524
+ "",
525
+ "**Start here**: Read `.pawl/context.md` first, then load relevant",
526
+ "spec files from `.pawl/specs/` based on the task at hand.",
527
+ END_MARKER
528
+ ].join("\n");
529
+ if (!(0, import_node_fs.existsSync)(agentsMdPath)) {
530
+ (0, import_node_fs.writeFileSync)(agentsMdPath, block + "\n", "utf-8");
531
+ return;
532
+ }
533
+ let content = (0, import_node_fs.readFileSync)(agentsMdPath, "utf-8");
534
+ const startIdx = content.indexOf(START_MARKER);
535
+ const endIdx = content.indexOf(END_MARKER);
536
+ if (startIdx !== -1 && endIdx !== -1) {
537
+ content = content.slice(0, startIdx) + block + content.slice(endIdx + END_MARKER.length);
538
+ } else {
539
+ content = content.trimEnd() + "\n\n" + block + "\n";
540
+ }
541
+ (0, import_node_fs.writeFileSync)(agentsMdPath, content, "utf-8");
542
+ }
543
+ function printSummary(detected, hasKey) {
544
+ console.log("\n Pawl initialized.\n");
545
+ console.log(" Detected agents:");
546
+ if (detected.hasCC) {
547
+ console.log(" Claude Code \u2713 hooks installed (.claude/settings.json)");
548
+ }
549
+ if (detected.hasGemini) {
550
+ console.log(" Gemini CLI \u2713 hooks installed (.gemini/settings.json)");
551
+ }
552
+ console.log(" Codex / other no lifecycle hooks \u2014 git post-commit fallback active");
553
+ console.log("\n Files written:");
554
+ console.log(" .pawl/sync.sh");
555
+ console.log(" .pawl/parse-cc-session.js");
556
+ console.log(" CLAUDE.md (pawl context block updated)");
557
+ console.log(" AGENTS.md (pawl context block \u2014 readable by Codex, Kiro, and others)");
558
+ if (detected.hasGitDir) {
559
+ console.log(" .git/hooks/post-commit (universal sync fallback)");
560
+ }
561
+ console.log(" .gitignore (updated)");
562
+ if (detected.hasGitDir) {
563
+ console.log("\n Note: .git/hooks/post-commit is per-machine \u2014 not committed to the repo.");
564
+ console.log(" Each team member should run: pawl init");
565
+ } else {
566
+ console.log("\n Note: git repo not detected \u2014 post-commit hook skipped.");
567
+ }
568
+ if (!hasKey) {
569
+ console.log("\n Sync is not yet connected to the Pawl dashboard.");
570
+ console.log(" To enable: pawl init <YOUR_PROJECT_KEY>");
571
+ console.log(" Get your key at: https://pawl.dev/settings");
572
+ } else {
573
+ console.log("\n .pawl/.env written \u2014 sync is connected.");
574
+ }
575
+ console.log("");
576
+ }
41
577
  function writeEnvFile(cwd, config) {
42
578
  const dir = (0, import_node_path.join)(cwd, ".agentmap");
43
579
  (0, import_node_fs.mkdirSync)(dir, { recursive: true });
@@ -212,14 +748,14 @@ esac
212
748
  (0, import_node_fs.writeFileSync)(scriptPath, script, "utf-8");
213
749
  (0, import_node_fs.chmodSync)(scriptPath, 493);
214
750
  }
215
- function copyParserScript(cwd) {
751
+ function copyParserScript(cwd, destDir = ".agentmap") {
216
752
  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");
753
+ const dest = (0, import_node_path.join)(cwd, destDir, "parse-cc-session.js");
218
754
  if ((0, import_node_fs.existsSync)(src)) {
219
755
  (0, import_node_fs.copyFileSync)(src, dest);
220
756
  }
221
757
  }
222
- function mergeClaudeSettings(cwd) {
758
+ function mergeClaudeSettings(cwd, basePath = ".agentmap") {
223
759
  const claudeDir = (0, import_node_path.join)(cwd, ".claude");
224
760
  (0, import_node_fs.mkdirSync)(claudeDir, { recursive: true });
225
761
  const settingsPath = (0, import_node_path.join)(claudeDir, "settings.json");
@@ -234,7 +770,7 @@ function mergeClaudeSettings(cwd) {
234
770
  SessionStart: [
235
771
  {
236
772
  hooks: [
237
- { type: "command", command: ".agentmap/sync.sh pull" }
773
+ { type: "command", command: `${basePath}/sync.sh pull` }
238
774
  ]
239
775
  }
240
776
  ],
@@ -244,7 +780,7 @@ function mergeClaudeSettings(cwd) {
244
780
  hooks: [
245
781
  {
246
782
  type: "command",
247
- command: "jq -r '.tool_input.file_path' >> .agentmap/.tracked-files"
783
+ command: `jq -r '.tool_input.file_path' >> ${basePath}/.tracked-files`
248
784
  }
249
785
  ]
250
786
  }
@@ -252,7 +788,7 @@ function mergeClaudeSettings(cwd) {
252
788
  Stop: [
253
789
  {
254
790
  hooks: [
255
- { type: "command", command: ".agentmap/sync.sh push" }
791
+ { type: "command", command: `${basePath}/sync.sh push` }
256
792
  ]
257
793
  }
258
794
  ]
@@ -320,7 +856,13 @@ function updateGitignore(cwd) {
320
856
  ".agentmap/.last-sync-sha",
321
857
  ".agentmap/context.md",
322
858
  ".agentmap/specs/",
323
- ".agentmap/progress.md"
859
+ ".agentmap/progress.md",
860
+ ".pawl/.env",
861
+ ".pawl/.tracked-files",
862
+ ".pawl/.last-sync-sha",
863
+ ".pawl/context.md",
864
+ ".pawl/specs/",
865
+ ".pawl/progress.md"
324
866
  ];
325
867
  const missing = toAdd.filter((line) => !lines.includes(line));
326
868
  if (missing.length > 0) {
@@ -333,4 +875,7 @@ function updateGitignore(cwd) {
333
875
  (0, import_node_fs.writeFileSync)(gitignorePath, newContent, "utf-8");
334
876
  }
335
877
  }
336
- main();
878
+ main().catch((err) => {
879
+ console.error(err.message || err);
880
+ process.exit(1);
881
+ });
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@getpawl/setup",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "type": "commonjs",
5
5
  "description": "One-shot setup for Pawl + Claude Code hooks",
6
6
  "bin": {
7
- "pawl-setup": "./dist/index.js"
7
+ "pawl": "dist/index.js",
8
+ "pawl-setup": "dist/index.js"
8
9
  },
9
10
  "scripts": {
10
11
  "build": "tsup",
12
+ "test": "node --test test/e2e.test.mjs",
11
13
  "lint": "tsc --noEmit",
12
14
  "prepublishOnly": "pnpm build"
13
15
  },
@@ -20,7 +22,7 @@
20
22
  },
21
23
  "repository": {
22
24
  "type": "git",
23
- "url": "https://github.com/0xfishbone/agentMap.git",
25
+ "url": "git+https://github.com/0xfishbone/agentMap.git",
24
26
  "directory": "packages/setup"
25
27
  },
26
28
  "devDependencies": {