@getpawl/setup 1.0.1 → 1.1.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/dist/index.js +439 -13
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -5,13 +5,28 @@
|
|
|
5
5
|
var import_node_fs = require("fs");
|
|
6
6
|
var import_node_path = require("path");
|
|
7
7
|
function main() {
|
|
8
|
-
const
|
|
9
|
-
if (!
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const arg = process.argv[2];
|
|
9
|
+
if (!arg) {
|
|
10
|
+
printUsage();
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
if (arg === "init") {
|
|
14
|
+
const key = process.argv[3];
|
|
15
|
+
pawlInit(key);
|
|
16
|
+
} else {
|
|
17
|
+
legacySetup(arg);
|
|
14
18
|
}
|
|
19
|
+
}
|
|
20
|
+
function printUsage() {
|
|
21
|
+
console.log(`
|
|
22
|
+
Usage:
|
|
23
|
+
pawl init [PROJECT_KEY] Initialize Pawl in this repo
|
|
24
|
+
pawl-setup <PROJECT_KEY> Legacy setup (still supported)
|
|
25
|
+
|
|
26
|
+
Get your project key at: https://pawl.dev/settings
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
function legacySetup(encoded) {
|
|
15
30
|
let config;
|
|
16
31
|
try {
|
|
17
32
|
const decoded = Buffer.from(encoded, "base64").toString("utf-8");
|
|
@@ -38,6 +53,411 @@ function main() {
|
|
|
38
53
|
console.log(" Created: .agentmap/.env, .agentmap/sync.sh, .agentmap/parse-cc-session.js");
|
|
39
54
|
console.log(" Updated: .claude/settings.json, CLAUDE.md, .gitignore\n");
|
|
40
55
|
}
|
|
56
|
+
function pawlInit(key) {
|
|
57
|
+
const cwd = process.cwd();
|
|
58
|
+
const detected = detectAgents(cwd);
|
|
59
|
+
migrateIfNeeded(cwd, detected.hasAgentMapDir);
|
|
60
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.join)(cwd, ".pawl"), { recursive: true });
|
|
61
|
+
if (key) {
|
|
62
|
+
let config;
|
|
63
|
+
try {
|
|
64
|
+
const decoded = Buffer.from(key, "base64").toString("utf-8");
|
|
65
|
+
config = JSON.parse(decoded);
|
|
66
|
+
} catch {
|
|
67
|
+
console.error("Error: Invalid project key \u2014 could not decode.");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
writePawlEnvFile(cwd, config);
|
|
71
|
+
}
|
|
72
|
+
writePawlSyncScript(cwd);
|
|
73
|
+
copyParserScript(cwd, ".pawl");
|
|
74
|
+
if (detected.hasCC) {
|
|
75
|
+
mergeClaudeSettings(cwd, ".pawl");
|
|
76
|
+
}
|
|
77
|
+
if (detected.hasGemini) {
|
|
78
|
+
mergeGeminiSettings(cwd);
|
|
79
|
+
}
|
|
80
|
+
writeGitHook(cwd, detected.hasGitDir);
|
|
81
|
+
writePawlClaudeMd(cwd);
|
|
82
|
+
writeAgentsMd(cwd);
|
|
83
|
+
updateGitignore(cwd);
|
|
84
|
+
printSummary(detected, !!key);
|
|
85
|
+
}
|
|
86
|
+
function detectAgents(cwd) {
|
|
87
|
+
return {
|
|
88
|
+
hasCC: (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".claude")),
|
|
89
|
+
hasGemini: (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".gemini")),
|
|
90
|
+
hasAgentMapDir: (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".agentmap")),
|
|
91
|
+
hasGitDir: (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".git"))
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function migrateIfNeeded(cwd, hasAgentMapDir) {
|
|
95
|
+
if (!hasAgentMapDir) return;
|
|
96
|
+
if ((0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".pawl"))) return;
|
|
97
|
+
console.log(" Migrating .agentmap/ \u2192 .pawl/...");
|
|
98
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.join)(cwd, ".pawl"), { recursive: true });
|
|
99
|
+
const filesToCopy = [".env", "sync.sh", "parse-cc-session.js", "context.md", "progress.md"];
|
|
100
|
+
for (const file of filesToCopy) {
|
|
101
|
+
const src = (0, import_node_path.join)(cwd, ".agentmap", file);
|
|
102
|
+
if ((0, import_node_fs.existsSync)(src)) {
|
|
103
|
+
(0, import_node_fs.copyFileSync)(src, (0, import_node_path.join)(cwd, ".pawl", file));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const specsDir = (0, import_node_path.join)(cwd, ".agentmap", "specs");
|
|
107
|
+
if ((0, import_node_fs.existsSync)(specsDir)) {
|
|
108
|
+
(0, import_node_fs.cpSync)(specsDir, (0, import_node_path.join)(cwd, ".pawl", "specs"), { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
const envPath = (0, import_node_path.join)(cwd, ".pawl", ".env");
|
|
111
|
+
if ((0, import_node_fs.existsSync)(envPath)) {
|
|
112
|
+
let envContent = (0, import_node_fs.readFileSync)(envPath, "utf-8");
|
|
113
|
+
envContent = envContent.replace(/AGENTMAP_/g, "PAWL_");
|
|
114
|
+
(0, import_node_fs.writeFileSync)(envPath, envContent, "utf-8");
|
|
115
|
+
}
|
|
116
|
+
console.log(" .agentmap/ preserved \u2014 safe to delete manually when ready.\n");
|
|
117
|
+
}
|
|
118
|
+
function writePawlEnvFile(cwd, config) {
|
|
119
|
+
const content = [
|
|
120
|
+
`PAWL_API_KEY=${config.apiKey}`,
|
|
121
|
+
`PAWL_PROJECT_ID=${config.projectId}`,
|
|
122
|
+
`PAWL_API_URL=${config.apiUrl}`,
|
|
123
|
+
""
|
|
124
|
+
].join("\n");
|
|
125
|
+
(0, import_node_fs.writeFileSync)((0, import_node_path.join)(cwd, ".pawl", ".env"), content, "utf-8");
|
|
126
|
+
}
|
|
127
|
+
function writePawlSyncScript(cwd) {
|
|
128
|
+
const script = `#!/usr/bin/env bash
|
|
129
|
+
set -euo pipefail
|
|
130
|
+
|
|
131
|
+
# Pawl sync script \u2014 generated by pawl init
|
|
132
|
+
# Do not edit manually; re-run pawl init to update.
|
|
133
|
+
|
|
134
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
135
|
+
|
|
136
|
+
# Source env only if it exists (pawl init without key skips .env)
|
|
137
|
+
if [ -f "$SCRIPT_DIR/.env" ]; then
|
|
138
|
+
source "$SCRIPT_DIR/.env"
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
BASE_URL="\${PAWL_API_URL:-}/api/projects/\${PAWL_PROJECT_ID:-}"
|
|
142
|
+
|
|
143
|
+
# \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
|
|
144
|
+
pull() {
|
|
145
|
+
if [ -z "\${PAWL_API_KEY:-}" ]; then
|
|
146
|
+
echo "Warning: PAWL_API_KEY not set \u2014 sync disabled" >&2
|
|
147
|
+
if [ -f "$SCRIPT_DIR/context.md" ]; then
|
|
148
|
+
cat "$SCRIPT_DIR/context.md"
|
|
149
|
+
fi
|
|
150
|
+
return 0
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# Recovery: if tracked-files exist from a previous incomplete session, push first
|
|
154
|
+
if [ -f "$SCRIPT_DIR/.tracked-files" ]; then
|
|
155
|
+
push >/dev/null 2>&1
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Ensure specs directory exists
|
|
159
|
+
mkdir -p "$SCRIPT_DIR/specs"
|
|
160
|
+
|
|
161
|
+
# \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
|
|
162
|
+
RESPONSE=$(curl -sf -X GET "$BASE_URL/context/files" \\
|
|
163
|
+
-H "Authorization: Bearer $PAWL_API_KEY" \\
|
|
164
|
+
-H "Accept: application/json" 2>/dev/null) || true
|
|
165
|
+
|
|
166
|
+
if [ -n "$RESPONSE" ] && echo "$RESPONSE" | jq -e '.files' >/dev/null 2>&1; then
|
|
167
|
+
echo "$RESPONSE" | jq -r '.files[] | @base64' | while read -r encoded; do
|
|
168
|
+
FILE_PATH=$(echo "$encoded" | base64 -d | jq -r '.path')
|
|
169
|
+
FILE_CONTENT=$(echo "$encoded" | base64 -d | jq -r '.content')
|
|
170
|
+
|
|
171
|
+
# Ensure parent directory exists
|
|
172
|
+
mkdir -p "$SCRIPT_DIR/$(dirname "$FILE_PATH")"
|
|
173
|
+
echo "$FILE_CONTENT" > "$SCRIPT_DIR/$FILE_PATH"
|
|
174
|
+
done
|
|
175
|
+
|
|
176
|
+
# Output context.md to stdout (for SessionStart hook)
|
|
177
|
+
if [ -f "$SCRIPT_DIR/context.md" ]; then
|
|
178
|
+
cat "$SCRIPT_DIR/context.md"
|
|
179
|
+
fi
|
|
180
|
+
return 0
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# \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
|
|
184
|
+
RESPONSE=$(curl -sf -X GET "$BASE_URL/context" \\
|
|
185
|
+
-H "Authorization: Bearer $PAWL_API_KEY" \\
|
|
186
|
+
-H "Accept: application/json" 2>/dev/null) || true
|
|
187
|
+
|
|
188
|
+
if [ -n "$RESPONSE" ] && echo "$RESPONSE" | jq -e '.formatted_context' >/dev/null 2>&1; then
|
|
189
|
+
echo "$RESPONSE" | jq -r '.formatted_context // empty' > "$SCRIPT_DIR/context.md"
|
|
190
|
+
cat "$SCRIPT_DIR/context.md"
|
|
191
|
+
return 0
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# \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
|
|
195
|
+
if [ -f "$SCRIPT_DIR/context.md" ]; then
|
|
196
|
+
echo "# (cached \u2014 API unreachable)" | cat - "$SCRIPT_DIR/context.md"
|
|
197
|
+
return 0
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
echo "Warning: Pawl context unavailable" >&2
|
|
201
|
+
return 1
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# \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
|
|
205
|
+
push() {
|
|
206
|
+
if [ -z "\${PAWL_API_KEY:-}" ]; then
|
|
207
|
+
return 0
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
LAST_SHA=""
|
|
211
|
+
if [ -f "$SCRIPT_DIR/.last-sync-sha" ]; then
|
|
212
|
+
LAST_SHA=$(cat "$SCRIPT_DIR/.last-sync-sha")
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
CURRENT_SHA=$(git rev-parse HEAD 2>/dev/null || echo "")
|
|
216
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
217
|
+
|
|
218
|
+
# Compute diff
|
|
219
|
+
if [ -n "$LAST_SHA" ]; then
|
|
220
|
+
DIFF=$(git diff "$LAST_SHA"..HEAD 2>/dev/null || echo "")
|
|
221
|
+
else
|
|
222
|
+
DIFF=$(git diff HEAD~1..HEAD 2>/dev/null || echo "")
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# Collect tracked files (dedup)
|
|
226
|
+
FILES_CHANGED="[]"
|
|
227
|
+
if [ -f "$SCRIPT_DIR/.tracked-files" ]; then
|
|
228
|
+
FILES_CHANGED=$(sort -u "$SCRIPT_DIR/.tracked-files" | jq -R . | jq -s .)
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
# Commit info
|
|
232
|
+
COMMIT_MSG=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "")
|
|
233
|
+
|
|
234
|
+
# Session ID from Claude Code (if available)
|
|
235
|
+
SESSION_ID="\${CLAUDE_SESSION_ID:-}"
|
|
236
|
+
|
|
237
|
+
# Repo root for CC session lookup
|
|
238
|
+
REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
239
|
+
|
|
240
|
+
# Parse CC session data locally (if node + parser available)
|
|
241
|
+
CC_SESSION="{}"
|
|
242
|
+
if command -v node &> /dev/null; then
|
|
243
|
+
PARSER="$SCRIPT_DIR/parse-cc-session.js"
|
|
244
|
+
if [ -f "$PARSER" ]; then
|
|
245
|
+
CC_SESSION=$(node "$PARSER" "$REPO_PATH" 2>/dev/null || echo "{}")
|
|
246
|
+
fi
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
# Extract cc_tasks from parser output
|
|
250
|
+
CC_TASKS=$(echo "$CC_SESSION" | jq -c '.tasks // []')
|
|
251
|
+
|
|
252
|
+
PAYLOAD=$(jq -n \\
|
|
253
|
+
--arg diff "$DIFF" \\
|
|
254
|
+
--arg branch "$BRANCH" \\
|
|
255
|
+
--arg commit_sha "$CURRENT_SHA" \\
|
|
256
|
+
--arg commit_message "$COMMIT_MSG" \\
|
|
257
|
+
--arg session_id "$SESSION_ID" \\
|
|
258
|
+
--arg last_sync_sha "$LAST_SHA" \\
|
|
259
|
+
--arg repo_path "$REPO_PATH" \\
|
|
260
|
+
--argjson files_changed "$FILES_CHANGED" \\
|
|
261
|
+
--argjson cc_session "$CC_SESSION" \\
|
|
262
|
+
--argjson cc_tasks "$CC_TASKS" \\
|
|
263
|
+
'{
|
|
264
|
+
diff: $diff,
|
|
265
|
+
branch: $branch,
|
|
266
|
+
commit_sha: $commit_sha,
|
|
267
|
+
commit_message: $commit_message,
|
|
268
|
+
session_id: $session_id,
|
|
269
|
+
last_sync_sha: $last_sync_sha,
|
|
270
|
+
repo_path: $repo_path,
|
|
271
|
+
files_changed: $files_changed,
|
|
272
|
+
cc_session: $cc_session,
|
|
273
|
+
cc_tasks: $cc_tasks
|
|
274
|
+
}')
|
|
275
|
+
|
|
276
|
+
RESPONSE=$(curl -sf -X POST "$BASE_URL/sync" \\
|
|
277
|
+
-H "Authorization: Bearer $PAWL_API_KEY" \\
|
|
278
|
+
-H "Content-Type: application/json" \\
|
|
279
|
+
-d "$PAYLOAD")
|
|
280
|
+
|
|
281
|
+
echo "$RESPONSE" | jq .
|
|
282
|
+
|
|
283
|
+
# Update last sync SHA
|
|
284
|
+
echo "$CURRENT_SHA" > "$SCRIPT_DIR/.last-sync-sha"
|
|
285
|
+
|
|
286
|
+
# Cleanup tracked files
|
|
287
|
+
rm -f "$SCRIPT_DIR/.tracked-files"
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
# \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
|
|
291
|
+
case "\${1:-}" in
|
|
292
|
+
pull) pull ;;
|
|
293
|
+
push) push ;;
|
|
294
|
+
*)
|
|
295
|
+
echo "Usage: sync.sh [pull|push]"
|
|
296
|
+
exit 1
|
|
297
|
+
;;
|
|
298
|
+
esac
|
|
299
|
+
`;
|
|
300
|
+
const scriptPath = (0, import_node_path.join)(cwd, ".pawl", "sync.sh");
|
|
301
|
+
(0, import_node_fs.writeFileSync)(scriptPath, script, "utf-8");
|
|
302
|
+
(0, import_node_fs.chmodSync)(scriptPath, 493);
|
|
303
|
+
}
|
|
304
|
+
function mergeGeminiSettings(cwd) {
|
|
305
|
+
const geminiDir = (0, import_node_path.join)(cwd, ".gemini");
|
|
306
|
+
(0, import_node_fs.mkdirSync)(geminiDir, { recursive: true });
|
|
307
|
+
const settingsPath = (0, import_node_path.join)(geminiDir, "settings.json");
|
|
308
|
+
let settings = {};
|
|
309
|
+
if ((0, import_node_fs.existsSync)(settingsPath)) {
|
|
310
|
+
try {
|
|
311
|
+
settings = JSON.parse((0, import_node_fs.readFileSync)(settingsPath, "utf-8"));
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const hooksToInstall = {
|
|
316
|
+
SessionStart: { command: ".pawl/sync.sh pull" },
|
|
317
|
+
AfterAgentLoop: { command: ".pawl/sync.sh push" }
|
|
318
|
+
};
|
|
319
|
+
const hooksObj = settings.hooks || {};
|
|
320
|
+
for (const [event, entry] of Object.entries(hooksToInstall)) {
|
|
321
|
+
const existing = hooksObj[event] || [];
|
|
322
|
+
const alreadyExists = existing.some((e) => e.command === entry.command);
|
|
323
|
+
if (!alreadyExists) {
|
|
324
|
+
existing.push(entry);
|
|
325
|
+
}
|
|
326
|
+
hooksObj[event] = existing;
|
|
327
|
+
}
|
|
328
|
+
settings.hooks = hooksObj;
|
|
329
|
+
(0, import_node_fs.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
330
|
+
}
|
|
331
|
+
function writeGitHook(cwd, hasGitDir) {
|
|
332
|
+
if (!hasGitDir) return;
|
|
333
|
+
const hooksDir = (0, import_node_path.join)(cwd, ".git", "hooks");
|
|
334
|
+
(0, import_node_fs.mkdirSync)(hooksDir, { recursive: true });
|
|
335
|
+
const hookPath = (0, import_node_path.join)(hooksDir, "post-commit");
|
|
336
|
+
const hookBlock = `#!/bin/sh
|
|
337
|
+
# Pawl sync hook \u2014 fires after every commit
|
|
338
|
+
# Provides universal sync fallback for agents without lifecycle hooks (Codex, etc.)
|
|
339
|
+
if [ -f ".pawl/sync.sh" ]; then
|
|
340
|
+
.pawl/sync.sh push
|
|
341
|
+
fi`;
|
|
342
|
+
if (!(0, import_node_fs.existsSync)(hookPath)) {
|
|
343
|
+
(0, import_node_fs.writeFileSync)(hookPath, hookBlock + "\n", "utf-8");
|
|
344
|
+
(0, import_node_fs.chmodSync)(hookPath, 493);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const content = (0, import_node_fs.readFileSync)(hookPath, "utf-8");
|
|
348
|
+
if (content.includes("Pawl sync hook")) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
(0, import_node_fs.writeFileSync)(hookPath, content.trimEnd() + "\n\n" + hookBlock + "\n", "utf-8");
|
|
352
|
+
(0, import_node_fs.chmodSync)(hookPath, 493);
|
|
353
|
+
}
|
|
354
|
+
function writePawlClaudeMd(cwd) {
|
|
355
|
+
const claudeMdPath = (0, import_node_path.join)(cwd, "CLAUDE.md");
|
|
356
|
+
const PAWL_START = "<!-- pawl:start -->";
|
|
357
|
+
const PAWL_END = "<!-- pawl:end -->";
|
|
358
|
+
const AGENTMAP_START = "<!-- agentmap:start -->";
|
|
359
|
+
const AGENTMAP_END = "<!-- agentmap:end -->";
|
|
360
|
+
const block = [
|
|
361
|
+
PAWL_START,
|
|
362
|
+
"# Pawl Context",
|
|
363
|
+
"",
|
|
364
|
+
"This project uses Pawl for spec management and AI session tracking.",
|
|
365
|
+
"",
|
|
366
|
+
"Context files are in `.pawl/`:",
|
|
367
|
+
"- `.pawl/context.md` \u2014 project index, spec map, health",
|
|
368
|
+
"- `.pawl/specs/` \u2014 individual spec files (one per feature area)",
|
|
369
|
+
"- `.pawl/progress.md` \u2014 last session summary",
|
|
370
|
+
"",
|
|
371
|
+
"Read `.pawl/context.md` at session start, then load relevant specs from `.pawl/specs/` based on the task.",
|
|
372
|
+
PAWL_END
|
|
373
|
+
].join("\n");
|
|
374
|
+
if (!(0, import_node_fs.existsSync)(claudeMdPath)) {
|
|
375
|
+
(0, import_node_fs.writeFileSync)(claudeMdPath, block + "\n", "utf-8");
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
let content = (0, import_node_fs.readFileSync)(claudeMdPath, "utf-8");
|
|
379
|
+
let startIdx = content.indexOf(PAWL_START);
|
|
380
|
+
let endIdx = content.indexOf(PAWL_END);
|
|
381
|
+
let endMarkerLen = PAWL_END.length;
|
|
382
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
383
|
+
startIdx = content.indexOf(AGENTMAP_START);
|
|
384
|
+
endIdx = content.indexOf(AGENTMAP_END);
|
|
385
|
+
endMarkerLen = AGENTMAP_END.length;
|
|
386
|
+
}
|
|
387
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
388
|
+
content = content.slice(0, startIdx) + block + content.slice(endIdx + endMarkerLen);
|
|
389
|
+
} else {
|
|
390
|
+
content = content.trimEnd() + "\n\n" + block + "\n";
|
|
391
|
+
}
|
|
392
|
+
(0, import_node_fs.writeFileSync)(claudeMdPath, content, "utf-8");
|
|
393
|
+
}
|
|
394
|
+
function writeAgentsMd(cwd) {
|
|
395
|
+
const agentsMdPath = (0, import_node_path.join)(cwd, "AGENTS.md");
|
|
396
|
+
const START_MARKER = "<!-- pawl:start -->";
|
|
397
|
+
const END_MARKER = "<!-- pawl:end -->";
|
|
398
|
+
const block = [
|
|
399
|
+
START_MARKER,
|
|
400
|
+
"## Pawl Project Context",
|
|
401
|
+
"",
|
|
402
|
+
"This project uses Pawl for spec management and AI session tracking.",
|
|
403
|
+
"Structured context files are available at:",
|
|
404
|
+
"",
|
|
405
|
+
"- `.pawl/context.md` \u2014 project index, spec map, health overview",
|
|
406
|
+
"- `.pawl/specs/` \u2014 individual spec files (one per feature area)",
|
|
407
|
+
"- `.pawl/progress.md` \u2014 last session summary, recent decisions",
|
|
408
|
+
"",
|
|
409
|
+
"**Start here**: Read `.pawl/context.md` first, then load relevant",
|
|
410
|
+
"spec files from `.pawl/specs/` based on the task at hand.",
|
|
411
|
+
END_MARKER
|
|
412
|
+
].join("\n");
|
|
413
|
+
if (!(0, import_node_fs.existsSync)(agentsMdPath)) {
|
|
414
|
+
(0, import_node_fs.writeFileSync)(agentsMdPath, block + "\n", "utf-8");
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
let content = (0, import_node_fs.readFileSync)(agentsMdPath, "utf-8");
|
|
418
|
+
const startIdx = content.indexOf(START_MARKER);
|
|
419
|
+
const endIdx = content.indexOf(END_MARKER);
|
|
420
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
421
|
+
content = content.slice(0, startIdx) + block + content.slice(endIdx + END_MARKER.length);
|
|
422
|
+
} else {
|
|
423
|
+
content = content.trimEnd() + "\n\n" + block + "\n";
|
|
424
|
+
}
|
|
425
|
+
(0, import_node_fs.writeFileSync)(agentsMdPath, content, "utf-8");
|
|
426
|
+
}
|
|
427
|
+
function printSummary(detected, hasKey) {
|
|
428
|
+
console.log("\n Pawl initialized.\n");
|
|
429
|
+
console.log(" Detected agents:");
|
|
430
|
+
if (detected.hasCC) {
|
|
431
|
+
console.log(" Claude Code \u2713 hooks installed (.claude/settings.json)");
|
|
432
|
+
}
|
|
433
|
+
if (detected.hasGemini) {
|
|
434
|
+
console.log(" Gemini CLI \u2713 hooks installed (.gemini/settings.json)");
|
|
435
|
+
}
|
|
436
|
+
console.log(" Codex / other no lifecycle hooks \u2014 git post-commit fallback active");
|
|
437
|
+
console.log("\n Files written:");
|
|
438
|
+
console.log(" .pawl/sync.sh");
|
|
439
|
+
console.log(" .pawl/parse-cc-session.js");
|
|
440
|
+
console.log(" CLAUDE.md (pawl context block updated)");
|
|
441
|
+
console.log(" AGENTS.md (pawl context block \u2014 readable by Codex, Kiro, and others)");
|
|
442
|
+
if (detected.hasGitDir) {
|
|
443
|
+
console.log(" .git/hooks/post-commit (universal sync fallback)");
|
|
444
|
+
}
|
|
445
|
+
console.log(" .gitignore (updated)");
|
|
446
|
+
if (detected.hasGitDir) {
|
|
447
|
+
console.log("\n Note: .git/hooks/post-commit is per-machine \u2014 not committed to the repo.");
|
|
448
|
+
console.log(" Each team member should run: pawl init");
|
|
449
|
+
} else {
|
|
450
|
+
console.log("\n Note: git repo not detected \u2014 post-commit hook skipped.");
|
|
451
|
+
}
|
|
452
|
+
if (!hasKey) {
|
|
453
|
+
console.log("\n Sync is not yet connected to the Pawl dashboard.");
|
|
454
|
+
console.log(" To enable: pawl init <YOUR_PROJECT_KEY>");
|
|
455
|
+
console.log(" Get your key at: https://pawl.dev/settings");
|
|
456
|
+
} else {
|
|
457
|
+
console.log("\n .pawl/.env written \u2014 sync is connected.");
|
|
458
|
+
}
|
|
459
|
+
console.log("");
|
|
460
|
+
}
|
|
41
461
|
function writeEnvFile(cwd, config) {
|
|
42
462
|
const dir = (0, import_node_path.join)(cwd, ".agentmap");
|
|
43
463
|
(0, import_node_fs.mkdirSync)(dir, { recursive: true });
|
|
@@ -212,14 +632,14 @@ esac
|
|
|
212
632
|
(0, import_node_fs.writeFileSync)(scriptPath, script, "utf-8");
|
|
213
633
|
(0, import_node_fs.chmodSync)(scriptPath, 493);
|
|
214
634
|
}
|
|
215
|
-
function copyParserScript(cwd) {
|
|
635
|
+
function copyParserScript(cwd, destDir = ".agentmap") {
|
|
216
636
|
const src = (0, import_node_path.join)(__dirname, "parse-cc-session.js");
|
|
217
|
-
const dest = (0, import_node_path.join)(cwd,
|
|
637
|
+
const dest = (0, import_node_path.join)(cwd, destDir, "parse-cc-session.js");
|
|
218
638
|
if ((0, import_node_fs.existsSync)(src)) {
|
|
219
639
|
(0, import_node_fs.copyFileSync)(src, dest);
|
|
220
640
|
}
|
|
221
641
|
}
|
|
222
|
-
function mergeClaudeSettings(cwd) {
|
|
642
|
+
function mergeClaudeSettings(cwd, basePath = ".agentmap") {
|
|
223
643
|
const claudeDir = (0, import_node_path.join)(cwd, ".claude");
|
|
224
644
|
(0, import_node_fs.mkdirSync)(claudeDir, { recursive: true });
|
|
225
645
|
const settingsPath = (0, import_node_path.join)(claudeDir, "settings.json");
|
|
@@ -234,7 +654,7 @@ function mergeClaudeSettings(cwd) {
|
|
|
234
654
|
SessionStart: [
|
|
235
655
|
{
|
|
236
656
|
hooks: [
|
|
237
|
-
{ type: "command", command:
|
|
657
|
+
{ type: "command", command: `${basePath}/sync.sh pull` }
|
|
238
658
|
]
|
|
239
659
|
}
|
|
240
660
|
],
|
|
@@ -244,7 +664,7 @@ function mergeClaudeSettings(cwd) {
|
|
|
244
664
|
hooks: [
|
|
245
665
|
{
|
|
246
666
|
type: "command",
|
|
247
|
-
command:
|
|
667
|
+
command: `jq -r '.tool_input.file_path' >> ${basePath}/.tracked-files`
|
|
248
668
|
}
|
|
249
669
|
]
|
|
250
670
|
}
|
|
@@ -252,7 +672,7 @@ function mergeClaudeSettings(cwd) {
|
|
|
252
672
|
Stop: [
|
|
253
673
|
{
|
|
254
674
|
hooks: [
|
|
255
|
-
{ type: "command", command:
|
|
675
|
+
{ type: "command", command: `${basePath}/sync.sh push` }
|
|
256
676
|
]
|
|
257
677
|
}
|
|
258
678
|
]
|
|
@@ -320,7 +740,13 @@ function updateGitignore(cwd) {
|
|
|
320
740
|
".agentmap/.last-sync-sha",
|
|
321
741
|
".agentmap/context.md",
|
|
322
742
|
".agentmap/specs/",
|
|
323
|
-
".agentmap/progress.md"
|
|
743
|
+
".agentmap/progress.md",
|
|
744
|
+
".pawl/.env",
|
|
745
|
+
".pawl/.tracked-files",
|
|
746
|
+
".pawl/.last-sync-sha",
|
|
747
|
+
".pawl/context.md",
|
|
748
|
+
".pawl/specs/",
|
|
749
|
+
".pawl/progress.md"
|
|
324
750
|
];
|
|
325
751
|
const missing = toAdd.filter((line) => !lines.includes(line));
|
|
326
752
|
if (missing.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getpawl/setup",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "One-shot setup for Pawl + Claude Code hooks",
|
|
6
6
|
"bin": {
|
|
7
|
+
"pawl": "./dist/index.js",
|
|
7
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
|
},
|