@getpawl/setup 1.1.2 → 1.3.0
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 +62 -13
- package/dist/index.js +299 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,29 +1,78 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @getpawl/setup
|
|
2
2
|
|
|
3
|
-
One-shot setup for [
|
|
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
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
|
|
8
|
+
npm install -g @getpawl/setup
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
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 `.
|
|
16
|
-
2. Generates `.
|
|
17
|
-
3.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
-
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
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,
|
|
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";
|
|
@@ -138,7 +178,7 @@ async function pawlConnect() {
|
|
|
138
178
|
(0, import_node_fs.mkdirSync)((0, import_node_path.join)(cwd, ".pawl"), { recursive: true });
|
|
139
179
|
writePawlEnvFile(cwd, result);
|
|
140
180
|
console.log(" Connected! .pawl/.env written.\n");
|
|
141
|
-
console.log(`
|
|
181
|
+
console.log(` Dashboard: https://agentmap-mimy.onrender.com/projects/${result.projectId}`);
|
|
142
182
|
console.log(" Run `pawl sync` to push your first session.\n");
|
|
143
183
|
}
|
|
144
184
|
function legacySetup(encoded) {
|
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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");
|
|
@@ -550,7 +818,9 @@ function printSummary(detected, hasKey) {
|
|
|
550
818
|
}
|
|
551
819
|
console.log(" Codex / other no lifecycle hooks \u2014 git post-commit fallback active");
|
|
552
820
|
console.log("\n Files written:");
|
|
553
|
-
console.log(" .pawl/sync.
|
|
821
|
+
console.log(" .pawl/sync.mjs (Node.js sync \u2014 no curl/jq needed)");
|
|
822
|
+
console.log(" .pawl/track-file.mjs (Node.js file tracker)");
|
|
823
|
+
console.log(" .pawl/sync.sh (bash fallback)");
|
|
554
824
|
console.log(" .pawl/parse-cc-session.js");
|
|
555
825
|
console.log(" CLAUDE.md (pawl context block updated)");
|
|
556
826
|
console.log(" AGENTS.md (pawl context block \u2014 readable by Codex, Kiro, and others)");
|
|
@@ -769,7 +1039,7 @@ function mergeClaudeSettings(cwd, basePath = ".agentmap") {
|
|
|
769
1039
|
SessionStart: [
|
|
770
1040
|
{
|
|
771
1041
|
hooks: [
|
|
772
|
-
{ type: "command", command:
|
|
1042
|
+
{ type: "command", command: `node ${basePath}/sync.mjs pull 2>/dev/null || ${basePath}/sync.sh pull` }
|
|
773
1043
|
]
|
|
774
1044
|
}
|
|
775
1045
|
],
|
|
@@ -779,7 +1049,7 @@ function mergeClaudeSettings(cwd, basePath = ".agentmap") {
|
|
|
779
1049
|
hooks: [
|
|
780
1050
|
{
|
|
781
1051
|
type: "command",
|
|
782
|
-
command: `jq -r '.tool_input.file_path' >> ${basePath}/.tracked-files`
|
|
1052
|
+
command: `node ${basePath}/track-file.mjs 2>/dev/null || jq -r '.tool_input.file_path' >> ${basePath}/.tracked-files`
|
|
783
1053
|
}
|
|
784
1054
|
]
|
|
785
1055
|
}
|
|
@@ -787,18 +1057,30 @@ function mergeClaudeSettings(cwd, basePath = ".agentmap") {
|
|
|
787
1057
|
Stop: [
|
|
788
1058
|
{
|
|
789
1059
|
hooks: [
|
|
790
|
-
{ type: "command", command:
|
|
1060
|
+
{ type: "command", command: `node ${basePath}/sync.mjs push 2>/dev/null || ${basePath}/sync.sh push` }
|
|
791
1061
|
]
|
|
792
1062
|
}
|
|
793
1063
|
]
|
|
794
1064
|
};
|
|
1065
|
+
const oldPatterns = [
|
|
1066
|
+
`${basePath}/sync.sh pull`,
|
|
1067
|
+
`${basePath}/sync.sh push`,
|
|
1068
|
+
`jq -r '.tool_input.file_path' >> ${basePath}/.tracked-files`
|
|
1069
|
+
];
|
|
795
1070
|
const hooksObj = settings.hooks || {};
|
|
796
1071
|
for (const [event, entries] of Object.entries(hooks)) {
|
|
797
|
-
|
|
1072
|
+
let existing = hooksObj[event] || [];
|
|
798
1073
|
for (const entry of entries) {
|
|
799
|
-
const
|
|
1074
|
+
const newCmd = entry.hooks[0].command;
|
|
1075
|
+
existing = existing.map((e) => {
|
|
1076
|
+
const existingCmd = e.hooks?.[0]?.command;
|
|
1077
|
+
if (existingCmd && oldPatterns.includes(existingCmd)) {
|
|
1078
|
+
return { ...e, hooks: [{ ...e.hooks[0], command: newCmd }] };
|
|
1079
|
+
}
|
|
1080
|
+
return e;
|
|
1081
|
+
});
|
|
800
1082
|
const alreadyExists = existing.some(
|
|
801
|
-
(e) => e.hooks?.[0]?.command ===
|
|
1083
|
+
(e) => e.hooks?.[0]?.command === newCmd
|
|
802
1084
|
);
|
|
803
1085
|
if (!alreadyExists) {
|
|
804
1086
|
existing.push(entry);
|