@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.
- package/dist/index.js +559 -14
- 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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
|
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": {
|