@codeharbor/agent-playbook 0.1.1 → 0.1.3

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 +4 -2
  2. package/package.json +1 -1
  3. package/src/cli.js +119 -10
package/README.md CHANGED
@@ -23,16 +23,18 @@ pnpm dlx @codeharbor/agent-playbook init --project
23
23
  - Records a metadata block in `~/.codex/config.toml`.
24
24
 
25
25
  ## Commands
26
- - `agent-playbook init [--project] [--copy] [--hooks] [--no-hooks] [--session-dir <path>] [--dry-run] [--repo <path>]`
26
+ - `agent-playbook init [--project] [--copy] [--overwrite] [--hooks] [--no-hooks] [--session-dir <path>] [--dry-run] [--repo <path>]`
27
27
  - `agent-playbook status`
28
28
  - `agent-playbook doctor`
29
- - `agent-playbook repair`
29
+ - `agent-playbook repair [--overwrite]`
30
30
  - `agent-playbook uninstall`
31
31
  - `agent-playbook session-log [--session-dir <path>]`
32
32
  - `agent-playbook self-improve`
33
33
 
34
34
  ## Notes
35
35
  - Default session logs go to repo `sessions/` if a Git root is found; otherwise `~/.claude/sessions/`.
36
+ - If skill folders already exist, you will be prompted before overwriting. Use `--overwrite` to skip the prompt.
37
+ - Session logs and self-improve entries record the agent-playbook version.
36
38
  - Hooks are merged without overwriting existing user hooks.
37
39
  - Requires Node.js 18+.
38
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeharbor/agent-playbook",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "One-click installer and workflow fixer for agent-playbook across Claude Code and Codex.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
package/src/cli.js CHANGED
@@ -48,10 +48,10 @@ function printHelp() {
48
48
  `${APP_NAME} ${VERSION}`,
49
49
  "",
50
50
  "Usage:",
51
- ` ${APP_NAME} init [--project] [--copy] [--hooks] [--no-hooks] [--session-dir <path>] [--dry-run] [--repo <path>]`,
51
+ ` ${APP_NAME} init [--project] [--copy] [--overwrite] [--hooks] [--no-hooks] [--session-dir <path>] [--dry-run] [--repo <path>]`,
52
52
  ` ${APP_NAME} status [--project] [--repo <path>]`,
53
53
  ` ${APP_NAME} doctor [--project] [--repo <path>]`,
54
- ` ${APP_NAME} repair [--project] [--repo <path>]`,
54
+ ` ${APP_NAME} repair [--project] [--overwrite] [--repo <path>]`,
55
55
  ` ${APP_NAME} uninstall [--project] [--repo <path>]`,
56
56
  "",
57
57
  "Hook commands:",
@@ -116,6 +116,7 @@ function handleInit(options, context) {
116
116
  const hooksEnabled = options.hooks !== false;
117
117
  const repoRoot = settings.repoRoot;
118
118
  const warnings = [];
119
+ const overwriteState = createOverwriteState(options);
119
120
 
120
121
  if (!settings.skillsSource) {
121
122
  if (options.repair) {
@@ -143,8 +144,8 @@ function handleInit(options, context) {
143
144
  let claudeLinks = { created: [], skipped: [] };
144
145
  let codexLinks = { created: [], skipped: [] };
145
146
  if (settings.skillsSource) {
146
- claudeLinks = linkSkills(settings.skillsSource, settings.claudeSkillsDir, options);
147
- codexLinks = linkSkills(settings.skillsSource, settings.codexSkillsDir, options);
147
+ claudeLinks = linkSkills(settings.skillsSource, settings.claudeSkillsDir, options, overwriteState);
148
+ codexLinks = linkSkills(settings.skillsSource, settings.codexSkillsDir, options, overwriteState);
148
149
  manifest.links.claude = claudeLinks.created;
149
150
  manifest.links.codex = codexLinks.created;
150
151
 
@@ -251,6 +252,7 @@ async function handleSelfImprove(options) {
251
252
  session_id: sessionId,
252
253
  cwd,
253
254
  transcript_path: transcriptPath,
255
+ agent_playbook_version: VERSION,
254
256
  hook_event: input.hook_event_name || "PostToolUse",
255
257
  tool_name: input.tool_name || "",
256
258
  tool_input: input.tool_input || "",
@@ -339,9 +341,102 @@ function resolveSkillsSource(candidates) {
339
341
  return null;
340
342
  }
341
343
 
342
- function linkSkills(sourceDir, targetDir, options) {
344
+ function createOverwriteState(options) {
345
+ return {
346
+ decision: options.overwrite === true ? true : null,
347
+ prompted: false,
348
+ nonInteractive: false,
349
+ };
350
+ }
351
+
352
+ function promptYesNo(question, defaultYes) {
353
+ const suffix = defaultYes ? "[Y/n]" : "[y/N]";
354
+ process.stdout.write(`${question} ${suffix} `);
355
+ let answer = "";
356
+ try {
357
+ answer = readLineSync().toLowerCase();
358
+ } catch (error) {
359
+ if (error && error.code === "EAGAIN") {
360
+ console.error("Warning: unable to read prompt input; skipping overwrite.");
361
+ return defaultYes;
362
+ }
363
+ throw error;
364
+ }
365
+ if (!answer) {
366
+ return defaultYes;
367
+ }
368
+ return answer === "y" || answer === "yes";
369
+ }
370
+
371
+ function readLineSync() {
372
+ const buffer = Buffer.alloc(1024);
373
+ let input = "";
374
+ const ttyPath = process.platform === "win32" ? null : "/dev/tty";
375
+ let fd = 0;
376
+ let shouldClose = false;
377
+
378
+ if (ttyPath) {
379
+ try {
380
+ fd = fs.openSync(ttyPath, "r");
381
+ shouldClose = true;
382
+ } catch (error) {
383
+ fd = 0;
384
+ }
385
+ }
386
+
387
+ while (true) {
388
+ let bytes = 0;
389
+ try {
390
+ bytes = fs.readSync(fd, buffer, 0, buffer.length, null);
391
+ } catch (error) {
392
+ if (error && error.code === "EAGAIN") {
393
+ continue;
394
+ }
395
+ throw error;
396
+ }
397
+ if (bytes <= 0) {
398
+ break;
399
+ }
400
+ input += buffer.toString("utf8", 0, bytes);
401
+ if (input.includes("\n")) {
402
+ break;
403
+ }
404
+ }
405
+ if (shouldClose) {
406
+ try {
407
+ fs.closeSync(fd);
408
+ } catch (error) {
409
+ return input.trim();
410
+ }
411
+ }
412
+ return input.trim();
413
+ }
414
+
415
+ function shouldOverwriteExisting(options, state, targetPath) {
416
+ if (options.overwrite) {
417
+ state.decision = true;
418
+ return true;
419
+ }
420
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
421
+ state.nonInteractive = true;
422
+ return false;
423
+ }
424
+ if (state.decision !== null) {
425
+ return state.decision;
426
+ }
427
+ state.prompted = true;
428
+ state.decision = promptYesNo(
429
+ `Existing skill found at ${targetPath}. Overwrite all existing skills?`,
430
+ false
431
+ );
432
+ return state.decision;
433
+ }
434
+
435
+ function linkSkills(sourceDir, targetDir, options, overwriteState) {
343
436
  const created = [];
344
437
  const skipped = [];
438
+ const overwritten = [];
439
+ const state = overwriteState || createOverwriteState(options);
345
440
  const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
346
441
 
347
442
  entries.forEach((entry) => {
@@ -364,8 +459,14 @@ function linkSkills(sourceDir, targetDir, options) {
364
459
  skipped.push({ source: skillDir, target: targetPath, reason: "already linked" });
365
460
  return;
366
461
  }
367
- skipped.push({ source: skillDir, target: targetPath, reason: "exists" });
368
- return;
462
+ if (!shouldOverwriteExisting(options, state, targetPath)) {
463
+ skipped.push({ source: skillDir, target: targetPath, reason: "exists" });
464
+ return;
465
+ }
466
+ overwritten.push({ source: skillDir, target: targetPath });
467
+ if (!options["dry-run"]) {
468
+ safeUnlink(targetPath);
469
+ }
369
470
  }
370
471
 
371
472
  if (options["dry-run"]) {
@@ -389,7 +490,7 @@ function linkSkills(sourceDir, targetDir, options) {
389
490
  }
390
491
  });
391
492
 
392
- return { created, skipped };
493
+ return { created, skipped, overwritten };
393
494
  }
394
495
 
395
496
  function ensureLocalCli(settings, context, options) {
@@ -800,6 +901,7 @@ function buildSessionSummary(insights, sessionId, cwd) {
800
901
  `**Date**: ${date}`,
801
902
  `**Duration**: unknown`,
802
903
  `**Context**: ${repoRoot}`,
904
+ `**Agent Playbook Version**: ${VERSION}`,
803
905
  "",
804
906
  "## Summary",
805
907
  "Auto-generated session log.",
@@ -907,6 +1009,12 @@ function printInitSummary(settings, hooksEnabled, options, claudeLinks, codexLin
907
1009
  console.log(`- Codex skills: ${settings.codexSkillsDir}`);
908
1010
  console.log(`- Hooks: ${hooksEnabled ? "enabled" : "disabled"}`);
909
1011
  console.log(`- Linked skills: ${claudeLinks.created.length + codexLinks.created.length}`);
1012
+ const overwrittenCount =
1013
+ (claudeLinks.overwritten ? claudeLinks.overwritten.length : 0) +
1014
+ (codexLinks.overwritten ? codexLinks.overwritten.length : 0);
1015
+ if (overwrittenCount) {
1016
+ console.log(`- Overwritten skills: ${overwrittenCount}`);
1017
+ }
910
1018
  if (claudeLinks.skipped.length || codexLinks.skipped.length) {
911
1019
  console.log("- Some skills were skipped due to existing paths.");
912
1020
  }
@@ -1064,6 +1172,7 @@ function handleRepair(options, context) {
1064
1172
  const settings = resolveSettings(options, context);
1065
1173
  const status = collectStatus(settings);
1066
1174
  const warnings = [];
1175
+ const overwriteState = createOverwriteState(options);
1067
1176
 
1068
1177
  if (!settings.skillsSource) {
1069
1178
  warnings.push("Skills directory not found; skipping skill linking.");
@@ -1094,8 +1203,8 @@ function handleRepair(options, context) {
1094
1203
  }
1095
1204
 
1096
1205
  if (settings.skillsSource) {
1097
- linkSkills(settings.skillsSource, settings.claudeSkillsDir, options);
1098
- linkSkills(settings.skillsSource, settings.codexSkillsDir, options);
1206
+ linkSkills(settings.skillsSource, settings.claudeSkillsDir, options, overwriteState);
1207
+ linkSkills(settings.skillsSource, settings.codexSkillsDir, options, overwriteState);
1099
1208
  if (!options["dry-run"]) {
1100
1209
  const manifestPath = path.join(settings.claudeSkillsDir, ".agent-playbook.json");
1101
1210
  if (!fs.existsSync(manifestPath)) {