@getpawl/setup 1.1.3 → 1.3.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 (3) hide show
  1. package/README.md +62 -13
  2. package/dist/index.js +300 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,29 +1,78 @@
1
- # @fixhbone/agentmap-setup
1
+ # @getpawl/setup
2
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.
3
+ One-shot setup for [Pawl](https://github.com/0xfishbone/agentMap) connects any repo to your Pawl project so spec context is automatically synced during AI coding sessions.
4
4
 
5
- ## Usage
5
+ ## Install
6
6
 
7
7
  ```sh
8
- npx @fixhbone/agentmap-setup <PROJECT_KEY>
8
+ npm install -g @getpawl/setup
9
9
  ```
10
10
 
11
- Get your `<PROJECT_KEY>` from the AgentMap web dashboard under **Settings > Setup**.
11
+ Then the `pawl` command will be available globally (`pawl sync`, `pawl connect`, `pawl migrate`).
12
+
13
+ ## Quick Start
14
+
15
+ ### 1. Initialize
16
+
17
+ ```sh
18
+ npx @getpawl/setup init
19
+ ```
20
+
21
+ Creates `.pawl/` directory with sync scripts, configures Claude Code hooks, and sets up git post-commit hook.
22
+
23
+ ### 2. Connect
24
+
25
+ ```sh
26
+ npx @getpawl/setup connect
27
+ ```
28
+
29
+ Opens your browser to link this repo to a Pawl project. Writes `.pawl/.env` automatically — no manual key copy-paste.
30
+
31
+ ### 3. Sync
32
+
33
+ ```sh
34
+ pawl sync # push current session to dashboard
35
+ pawl sync --pull # pull latest context from dashboard
36
+ ```
37
+
38
+ Sync happens automatically via hooks. Use these commands for manual sync or agents without lifecycle hooks (Codex, etc.).
39
+
40
+ ## Legacy Setup
41
+
42
+ Existing users can still use the one-step flow:
43
+
44
+ ```sh
45
+ npx @getpawl/setup <PROJECT_KEY>
46
+ ```
47
+
48
+ Get your `<PROJECT_KEY>` from the Pawl dashboard under **Settings > Setup**.
12
49
 
13
50
  ## What it does
14
51
 
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`
52
+ 1. Creates `.pawl/.env` with API credentials
53
+ 2. Generates `.pawl/sync.mjs` — Node.js sync script (no curl/jq needed)
54
+ 3. Generates `.pawl/track-file.mjs` Node.js file tracker (replaces jq in PostToolUse hook)
55
+ 4. Generates `.pawl/sync.sh` bash fallback for environments without Node.js
56
+ 5. Copies `.pawl/parse-cc-session.js`extracts token usage and tasks from CC sessions
57
+ 6. Configures `.claude/settings.json` with lifecycle hooks (SessionStart, PostToolUse, Stop)
58
+ 7. Configures `.gemini/settings.json` with lifecycle hooks (if Gemini CLI detected)
59
+ 8. Installs `.git/hooks/post-commit` for universal agent support
60
+ 9. Updates `CLAUDE.md` and `AGENTS.md` with Pawl context blocks
61
+ 10. Adds sensitive paths to `.gitignore`
62
+
63
+ ## Supported Agents
64
+
65
+ | Agent | Hook Method |
66
+ |-------|-------------|
67
+ | Claude Code | `.claude/settings.json` lifecycle hooks |
68
+ | Gemini CLI | `.gemini/settings.json` lifecycle hooks |
69
+ | Codex / Others | `git post-commit` hook fallback |
22
70
 
23
71
  ## Requirements
24
72
 
25
73
  - Node.js >= 18
26
- - [Claude Code](https://claude.ai/claude-code) CLI installed
74
+ - Git repository
75
+ - `curl` and `jq` are **no longer required** — `pawl init` generates Node.js sync scripts that use native `fetch()`. Bash scripts are kept as a fallback.
27
76
 
28
77
  ## License
29
78
 
package/dist/index.js CHANGED
@@ -19,6 +19,8 @@ async function main() {
19
19
  } else if (arg === "init") {
20
20
  const key = process.argv[3];
21
21
  pawlInit(key);
22
+ } else if (arg === "migrate") {
23
+ pawlMigrate();
22
24
  } else {
23
25
  legacySetup(arg);
24
26
  }
@@ -29,6 +31,7 @@ Usage:
29
31
  pawl init Initialize Pawl in this repo
30
32
  pawl connect Link this repo to a Pawl project (opens browser)
31
33
  pawl sync [--pull] Sync with Pawl dashboard (push by default)
34
+ pawl migrate Migrate .pawl/sync.sh from AGENTMAP_* to PAWL_* vars
32
35
  pawl-setup <PROJECT_KEY> Legacy setup (still supported)
33
36
 
34
37
  Dashboard: https://pawl.dev
@@ -36,9 +39,10 @@ Dashboard: https://pawl.dev
36
39
  }
37
40
  function pawlSync(flag) {
38
41
  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
+ const syncMjs = (0, import_node_path.join)(cwd, ".pawl", "sync.mjs");
43
+ const syncSh = (0, import_node_path.join)(cwd, ".pawl", "sync.sh");
44
+ if (!(0, import_node_fs.existsSync)(syncMjs) && !(0, import_node_fs.existsSync)(syncSh)) {
45
+ console.error("Error: .pawl/sync.mjs not found \u2014 run `pawl init` first.");
42
46
  process.exit(1);
43
47
  }
44
48
  const envFile = (0, import_node_path.join)(cwd, ".pawl", ".env");
@@ -47,7 +51,43 @@ function pawlSync(flag) {
47
51
  process.exit(1);
48
52
  }
49
53
  const mode = flag === "--pull" ? "pull" : "push";
50
- (0, import_node_child_process.execSync)(`.pawl/sync.sh ${mode}`, { stdio: "inherit", cwd });
54
+ if ((0, import_node_fs.existsSync)(syncMjs)) {
55
+ (0, import_node_child_process.execSync)(`node .pawl/sync.mjs ${mode}`, { stdio: "inherit", cwd });
56
+ } else {
57
+ (0, import_node_child_process.execSync)(`.pawl/sync.sh ${mode}`, { stdio: "inherit", cwd });
58
+ }
59
+ }
60
+ function pawlMigrate() {
61
+ const cwd = process.cwd();
62
+ const syncPath = (0, import_node_path.join)(cwd, ".pawl", "sync.sh");
63
+ if (!(0, import_node_fs.existsSync)(syncPath)) {
64
+ console.log(" Nothing to migrate \u2014 run `pawl init` first.");
65
+ process.exit(0);
66
+ }
67
+ let content = (0, import_node_fs.readFileSync)(syncPath, "utf-8");
68
+ if (!content.includes("AGENTMAP_")) {
69
+ console.log(" Already up to date.");
70
+ process.exit(0);
71
+ }
72
+ content = content.replace(/AGENTMAP_API_URL/g, "PAWL_API_URL").replace(/AGENTMAP_PROJECT_ID/g, "PAWL_PROJECT_ID").replace(/AGENTMAP_API_KEY/g, "PAWL_API_KEY");
73
+ (0, import_node_fs.writeFileSync)(syncPath, content, "utf-8");
74
+ const envPath = (0, import_node_path.join)(cwd, ".pawl", ".env");
75
+ if ((0, import_node_fs.existsSync)(envPath)) {
76
+ let envContent = (0, import_node_fs.readFileSync)(envPath, "utf-8");
77
+ if (envContent.includes("AGENTMAP_")) {
78
+ envContent = envContent.replace(/AGENTMAP_API_URL/g, "PAWL_API_URL").replace(/AGENTMAP_PROJECT_ID/g, "PAWL_PROJECT_ID").replace(/AGENTMAP_API_KEY/g, "PAWL_API_KEY");
79
+ (0, import_node_fs.writeFileSync)(envPath, envContent, "utf-8");
80
+ console.log(" .pawl/.env also migrated.");
81
+ }
82
+ }
83
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".pawl", "sync.mjs"))) {
84
+ writePawlSyncMjs(cwd);
85
+ }
86
+ if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".pawl", "track-file.mjs"))) {
87
+ writeTrackFileScript(cwd);
88
+ }
89
+ mergeClaudeSettings(cwd, ".pawl");
90
+ console.log(" Migration complete \u2014 .pawl/sync.sh updated to PAWL_* vars.");
51
91
  }
52
92
  function generateCode() {
53
93
  const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -185,6 +225,8 @@ function pawlInit(key) {
185
225
  writePawlEnvFile(cwd, config);
186
226
  }
187
227
  writePawlSyncScript(cwd);
228
+ writePawlSyncMjs(cwd);
229
+ writeTrackFileScript(cwd);
188
230
  copyParserScript(cwd, ".pawl");
189
231
  if (detected.hasCC) {
190
232
  mergeClaudeSettings(cwd, ".pawl");
@@ -416,6 +458,211 @@ esac
416
458
  (0, import_node_fs.writeFileSync)(scriptPath, script, "utf-8");
417
459
  (0, import_node_fs.chmodSync)(scriptPath, 493);
418
460
  }
461
+ function writePawlSyncMjs(cwd) {
462
+ const script = `#!/usr/bin/env node
463
+ // Pawl sync script (Node.js) \u2014 generated by pawl init
464
+ // Do not edit manually; re-run pawl init to update.
465
+
466
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from 'node:fs';
467
+ import { execSync } from 'node:child_process';
468
+ import { dirname, join } from 'node:path';
469
+ import { fileURLToPath } from 'node:url';
470
+
471
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
472
+
473
+ // Parse .env file (no dotenv dependency)
474
+ function loadEnv() {
475
+ const envPath = join(SCRIPT_DIR, '.env');
476
+ if (!existsSync(envPath)) return {};
477
+ const env = {};
478
+ for (const line of readFileSync(envPath, 'utf-8').split('\\n')) {
479
+ const trimmed = line.trim();
480
+ if (!trimmed || trimmed.startsWith('#')) continue;
481
+ const eq = trimmed.indexOf('=');
482
+ if (eq === -1) continue;
483
+ env[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
484
+ }
485
+ return env;
486
+ }
487
+
488
+ const env = loadEnv();
489
+ const API_KEY = env.PAWL_API_KEY || '';
490
+ const PROJECT_ID = env.PAWL_PROJECT_ID || '';
491
+ const API_URL = env.PAWL_API_URL || '';
492
+ const BASE_URL = \`\${API_URL}/api/projects/\${PROJECT_ID}\`;
493
+
494
+ async function pull() {
495
+ if (!API_KEY) {
496
+ process.stderr.write('Warning: PAWL_API_KEY not set \u2014 sync disabled\\n');
497
+ const cached = join(SCRIPT_DIR, 'context.md');
498
+ if (existsSync(cached)) process.stdout.write(readFileSync(cached, 'utf-8'));
499
+ return;
500
+ }
501
+
502
+ // Recovery: if tracked-files exist from a previous incomplete session, push first
503
+ if (existsSync(join(SCRIPT_DIR, '.tracked-files'))) {
504
+ try { await push(); } catch {}
505
+ }
506
+
507
+ mkdirSync(join(SCRIPT_DIR, 'specs'), { recursive: true });
508
+
509
+ // Level 1: File-based endpoint
510
+ try {
511
+ const res = await fetch(\`\${BASE_URL}/context/files\`, {
512
+ headers: { 'Authorization': \`Bearer \${API_KEY}\`, 'Accept': 'application/json' },
513
+ });
514
+ if (res.ok) {
515
+ const data = await res.json();
516
+ if (data.files) {
517
+ for (const file of data.files) {
518
+ const filePath = join(SCRIPT_DIR, file.path);
519
+ mkdirSync(dirname(filePath), { recursive: true });
520
+ writeFileSync(filePath, file.content, 'utf-8');
521
+ }
522
+ const ctxPath = join(SCRIPT_DIR, 'context.md');
523
+ if (existsSync(ctxPath)) process.stdout.write(readFileSync(ctxPath, 'utf-8'));
524
+ return;
525
+ }
526
+ }
527
+ } catch {}
528
+
529
+ // Level 2: Legacy monolithic endpoint
530
+ try {
531
+ const res = await fetch(\`\${BASE_URL}/context\`, {
532
+ headers: { 'Authorization': \`Bearer \${API_KEY}\`, 'Accept': 'application/json' },
533
+ });
534
+ if (res.ok) {
535
+ const data = await res.json();
536
+ if (data.formatted_context) {
537
+ writeFileSync(join(SCRIPT_DIR, 'context.md'), data.formatted_context, 'utf-8');
538
+ process.stdout.write(data.formatted_context);
539
+ return;
540
+ }
541
+ }
542
+ } catch {}
543
+
544
+ // Level 3: Cached fallback
545
+ const cached = join(SCRIPT_DIR, 'context.md');
546
+ if (existsSync(cached)) {
547
+ process.stdout.write('# (cached \u2014 API unreachable)\\n' + readFileSync(cached, 'utf-8'));
548
+ return;
549
+ }
550
+
551
+ process.stderr.write('Warning: Pawl context unavailable\\n');
552
+ process.exitCode = 1;
553
+ }
554
+
555
+ async function push() {
556
+ if (!API_KEY) return;
557
+
558
+ let lastSha = '';
559
+ const lastShaPath = join(SCRIPT_DIR, '.last-sync-sha');
560
+ if (existsSync(lastShaPath)) lastSha = readFileSync(lastShaPath, 'utf-8').trim();
561
+
562
+ const git = (cmd) => { try { return execSync(cmd, { encoding: 'utf-8' }).trim(); } catch { return ''; } };
563
+
564
+ const currentSha = git('git rev-parse HEAD');
565
+ const branch = git('git rev-parse --abbrev-ref HEAD') || 'unknown';
566
+
567
+ let diff = '';
568
+ if (lastSha) {
569
+ diff = git(\`git diff \${lastSha}..HEAD\`);
570
+ } else {
571
+ diff = git('git diff HEAD~1..HEAD');
572
+ }
573
+
574
+ // Cap diff at 500K
575
+ if (diff.length > 500000) diff = diff.slice(0, 500000);
576
+
577
+ // Collect tracked files
578
+ let filesChanged = [];
579
+ const trackedPath = join(SCRIPT_DIR, '.tracked-files');
580
+ if (existsSync(trackedPath)) {
581
+ filesChanged = [...new Set(readFileSync(trackedPath, 'utf-8').split('\\n').filter(Boolean))];
582
+ }
583
+
584
+ const commitMessage = git('git log -1 --pretty=format:"%s"');
585
+ const sessionId = process.env.CLAUDE_SESSION_ID || '';
586
+ const repoPath = git('git rev-parse --show-toplevel') || process.cwd();
587
+
588
+ // Parse CC session data
589
+ let ccSession = {};
590
+ const parserPath = join(SCRIPT_DIR, 'parse-cc-session.js');
591
+ if (existsSync(parserPath)) {
592
+ try {
593
+ const raw = execSync(\`node "\${parserPath}" "\${repoPath}"\`, { encoding: 'utf-8' });
594
+ ccSession = JSON.parse(raw);
595
+ } catch {}
596
+ }
597
+
598
+ const ccTasks = ccSession.tasks || [];
599
+
600
+ const payload = {
601
+ diff,
602
+ branch,
603
+ commit_sha: currentSha,
604
+ commit_message: commitMessage,
605
+ session_id: sessionId,
606
+ last_sync_sha: lastSha,
607
+ repo_path: repoPath,
608
+ files_changed: filesChanged,
609
+ cc_session: ccSession,
610
+ cc_tasks: ccTasks,
611
+ };
612
+
613
+ try {
614
+ const res = await fetch(\`\${BASE_URL}/sync\`, {
615
+ method: 'POST',
616
+ headers: {
617
+ 'Authorization': \`Bearer \${API_KEY}\`,
618
+ 'Content-Type': 'application/json',
619
+ },
620
+ body: JSON.stringify(payload),
621
+ });
622
+ if (res.ok) {
623
+ const data = await res.json();
624
+ process.stdout.write(JSON.stringify(data, null, 2) + '\\n');
625
+ }
626
+ } catch {}
627
+
628
+ writeFileSync(lastShaPath, currentSha, 'utf-8');
629
+ if (existsSync(trackedPath)) unlinkSync(trackedPath);
630
+ }
631
+
632
+ // Dispatch
633
+ const mode = process.argv[2];
634
+ if (mode === 'pull') {
635
+ pull().catch(() => { process.exitCode = 1; });
636
+ } else if (mode === 'push') {
637
+ push().catch(() => { process.exitCode = 1; });
638
+ } else {
639
+ process.stderr.write('Usage: sync.mjs [pull|push]\\n');
640
+ process.exitCode = 1;
641
+ }
642
+ `;
643
+ const scriptPath = (0, import_node_path.join)(cwd, ".pawl", "sync.mjs");
644
+ (0, import_node_fs.writeFileSync)(scriptPath, script, "utf-8");
645
+ }
646
+ function writeTrackFileScript(cwd) {
647
+ const script = `// Pawl file tracker \u2014 reads tool JSON from stdin, appends file_path to .tracked-files
648
+ import { appendFileSync } from 'node:fs';
649
+ import { dirname, join } from 'node:path';
650
+ import { fileURLToPath } from 'node:url';
651
+ let d = '';
652
+ process.stdin.on('data', c => d += c);
653
+ process.stdin.on('end', () => {
654
+ try {
655
+ const p = JSON.parse(d).tool_input.file_path;
656
+ if (p) {
657
+ const dir = dirname(fileURLToPath(import.meta.url));
658
+ appendFileSync(join(dir, '.tracked-files'), p + '\\n');
659
+ }
660
+ } catch {}
661
+ });
662
+ `;
663
+ const scriptPath = (0, import_node_path.join)(cwd, ".pawl", "track-file.mjs");
664
+ (0, import_node_fs.writeFileSync)(scriptPath, script, "utf-8");
665
+ }
419
666
  function mergeGeminiSettings(cwd) {
420
667
  const geminiDir = (0, import_node_path.join)(cwd, ".gemini");
421
668
  (0, import_node_fs.mkdirSync)(geminiDir, { recursive: true });
@@ -428,12 +675,19 @@ function mergeGeminiSettings(cwd) {
428
675
  }
429
676
  }
430
677
  const hooksToInstall = {
431
- SessionStart: { command: ".pawl/sync.sh pull" },
432
- AfterAgentLoop: { command: ".pawl/sync.sh push" }
678
+ SessionStart: { command: "node .pawl/sync.mjs pull 2>/dev/null || .pawl/sync.sh pull" },
679
+ AfterAgentLoop: { command: "node .pawl/sync.mjs push 2>/dev/null || .pawl/sync.sh push" }
433
680
  };
681
+ const oldCommands = [".pawl/sync.sh pull", ".pawl/sync.sh push"];
434
682
  const hooksObj = settings.hooks || {};
435
683
  for (const [event, entry] of Object.entries(hooksToInstall)) {
436
- const existing = hooksObj[event] || [];
684
+ let existing = hooksObj[event] || [];
685
+ existing = existing.map((e) => {
686
+ if (oldCommands.includes(e.command)) {
687
+ return { ...e, command: entry.command };
688
+ }
689
+ return e;
690
+ });
437
691
  const alreadyExists = existing.some((e) => e.command === entry.command);
438
692
  if (!alreadyExists) {
439
693
  existing.push(entry);
@@ -451,7 +705,9 @@ function writeGitHook(cwd, hasGitDir) {
451
705
  const hookBlock = `#!/bin/sh
452
706
  # Pawl sync hook \u2014 fires after every commit
453
707
  # Provides universal sync fallback for agents without lifecycle hooks (Codex, etc.)
454
- if [ -f ".pawl/sync.sh" ]; then
708
+ if command -v node >/dev/null 2>&1 && [ -f ".pawl/sync.mjs" ]; then
709
+ node .pawl/sync.mjs push
710
+ elif [ -f ".pawl/sync.sh" ]; then
455
711
  .pawl/sync.sh push
456
712
  fi`;
457
713
  if (!(0, import_node_fs.existsSync)(hookPath)) {
@@ -459,8 +715,20 @@ fi`;
459
715
  (0, import_node_fs.chmodSync)(hookPath, 493);
460
716
  return;
461
717
  }
462
- const content = (0, import_node_fs.readFileSync)(hookPath, "utf-8");
718
+ let content = (0, import_node_fs.readFileSync)(hookPath, "utf-8");
463
719
  if (content.includes("Pawl sync hook")) {
720
+ const markerStart = content.indexOf("# Pawl sync hook");
721
+ if (markerStart !== -1) {
722
+ let blockStart = markerStart;
723
+ const before = content.lastIndexOf("\n", markerStart - 1);
724
+ const fiIdx = content.indexOf("\nfi", markerStart);
725
+ if (fiIdx !== -1) {
726
+ const blockEnd = fiIdx + "\nfi".length;
727
+ content = content.slice(0, blockStart) + hookBlock.slice(hookBlock.indexOf("# Pawl sync hook")) + content.slice(blockEnd);
728
+ (0, import_node_fs.writeFileSync)(hookPath, content, "utf-8");
729
+ (0, import_node_fs.chmodSync)(hookPath, 493);
730
+ }
731
+ }
464
732
  return;
465
733
  }
466
734
  (0, import_node_fs.writeFileSync)(hookPath, content.trimEnd() + "\n\n" + hookBlock + "\n", "utf-8");
@@ -482,6 +750,7 @@ function writePawlClaudeMd(cwd) {
482
750
  "- `.pawl/context.md` \u2014 project index, spec map, health",
483
751
  "- `.pawl/specs/` \u2014 individual spec files (one per feature area)",
484
752
  "- `.pawl/progress.md` \u2014 last session summary",
753
+ "- `.pawl/decisions.json` \u2014 architectural decisions log (read before starting work)",
485
754
  "",
486
755
  "Read `.pawl/context.md` at session start, then load relevant specs from `.pawl/specs/` based on the task.",
487
756
  PAWL_END
@@ -520,6 +789,7 @@ function writeAgentsMd(cwd) {
520
789
  "- `.pawl/context.md` \u2014 project index, spec map, health overview",
521
790
  "- `.pawl/specs/` \u2014 individual spec files (one per feature area)",
522
791
  "- `.pawl/progress.md` \u2014 last session summary, recent decisions",
792
+ "- `.pawl/decisions.json` \u2014 architectural decisions log, read before starting work",
523
793
  "",
524
794
  "**Start here**: Read `.pawl/context.md` first, then load relevant",
525
795
  "spec files from `.pawl/specs/` based on the task at hand.",
@@ -550,7 +820,9 @@ function printSummary(detected, hasKey) {
550
820
  }
551
821
  console.log(" Codex / other no lifecycle hooks \u2014 git post-commit fallback active");
552
822
  console.log("\n Files written:");
553
- console.log(" .pawl/sync.sh");
823
+ console.log(" .pawl/sync.mjs (Node.js sync \u2014 no curl/jq needed)");
824
+ console.log(" .pawl/track-file.mjs (Node.js file tracker)");
825
+ console.log(" .pawl/sync.sh (bash fallback)");
554
826
  console.log(" .pawl/parse-cc-session.js");
555
827
  console.log(" CLAUDE.md (pawl context block updated)");
556
828
  console.log(" AGENTS.md (pawl context block \u2014 readable by Codex, Kiro, and others)");
@@ -769,7 +1041,7 @@ function mergeClaudeSettings(cwd, basePath = ".agentmap") {
769
1041
  SessionStart: [
770
1042
  {
771
1043
  hooks: [
772
- { type: "command", command: `${basePath}/sync.sh pull` }
1044
+ { type: "command", command: `node ${basePath}/sync.mjs pull 2>/dev/null || ${basePath}/sync.sh pull` }
773
1045
  ]
774
1046
  }
775
1047
  ],
@@ -779,7 +1051,7 @@ function mergeClaudeSettings(cwd, basePath = ".agentmap") {
779
1051
  hooks: [
780
1052
  {
781
1053
  type: "command",
782
- command: `jq -r '.tool_input.file_path' >> ${basePath}/.tracked-files`
1054
+ command: `node ${basePath}/track-file.mjs 2>/dev/null || jq -r '.tool_input.file_path' >> ${basePath}/.tracked-files`
783
1055
  }
784
1056
  ]
785
1057
  }
@@ -787,18 +1059,30 @@ function mergeClaudeSettings(cwd, basePath = ".agentmap") {
787
1059
  Stop: [
788
1060
  {
789
1061
  hooks: [
790
- { type: "command", command: `${basePath}/sync.sh push` }
1062
+ { type: "command", command: `node ${basePath}/sync.mjs push 2>/dev/null || ${basePath}/sync.sh push` }
791
1063
  ]
792
1064
  }
793
1065
  ]
794
1066
  };
1067
+ const oldPatterns = [
1068
+ `${basePath}/sync.sh pull`,
1069
+ `${basePath}/sync.sh push`,
1070
+ `jq -r '.tool_input.file_path' >> ${basePath}/.tracked-files`
1071
+ ];
795
1072
  const hooksObj = settings.hooks || {};
796
1073
  for (const [event, entries] of Object.entries(hooks)) {
797
- const existing = hooksObj[event] || [];
1074
+ let existing = hooksObj[event] || [];
798
1075
  for (const entry of entries) {
799
- const cmd = entry.hooks[0].command;
1076
+ const newCmd = entry.hooks[0].command;
1077
+ existing = existing.map((e) => {
1078
+ const existingCmd = e.hooks?.[0]?.command;
1079
+ if (existingCmd && oldPatterns.includes(existingCmd)) {
1080
+ return { ...e, hooks: [{ ...e.hooks[0], command: newCmd }] };
1081
+ }
1082
+ return e;
1083
+ });
800
1084
  const alreadyExists = existing.some(
801
- (e) => e.hooks?.[0]?.command === cmd
1085
+ (e) => e.hooks?.[0]?.command === newCmd
802
1086
  );
803
1087
  if (!alreadyExists) {
804
1088
  existing.push(entry);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpawl/setup",
3
- "version": "1.1.3",
3
+ "version": "1.3.1",
4
4
  "type": "commonjs",
5
5
  "description": "One-shot setup for Pawl + Claude Code hooks",
6
6
  "bin": {