get-tbd 0.1.29 → 0.1.30

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/bin.mjs CHANGED
@@ -14067,7 +14067,7 @@ function serializeIssue(issue) {
14067
14067
  * Package version, derived from git at build time.
14068
14068
  * Format: X.Y.Z for releases, X.Y.Z-dev.N.hash for dev builds.
14069
14069
  */
14070
- const VERSION$1 = "0.1.29";
14070
+ const VERSION$1 = "0.1.30";
14071
14071
 
14072
14072
  //#endregion
14073
14073
  //#region src/cli/lib/version.ts
@@ -97685,6 +97685,10 @@ const CHARS_PER_TOKEN = 3.5;
97685
97685
  * WHEN TO BUMP THE FORMAT VERSION:
97686
97686
  * - Bump when changes REQUIRE migration (deleting files, changing formats, moving files)
97687
97687
  * - **Bump when changing config schema** (adding, removing, or modifying fields)
97688
+ * - **Bump when the shape of a generated agent-integration surface changes** (e.g. the
97689
+ * managed AGENTS.md block). This same format is stamped there via
97690
+ * AGENT_INTEGRATION_FORMAT (integration-paths.ts), so there is ONE format code across
97691
+ * all tbd-managed surfaces.
97688
97692
  * - Do NOT bump for additive changes that don't affect config.yml (new directories, etc.)
97689
97693
  *
97690
97694
  * HOW TO ADD A NEW FORMAT VERSION:
@@ -104232,9 +104236,10 @@ function renderFooter(suggestions, colors) {
104232
104236
  /**
104233
104237
  * Centralized path constants and utilities for coding agent integrations.
104234
104238
  *
104235
- * IMPORTANT: All tbd integration files (hooks, settings, skills) are installed
104236
- * to PROJECT-LOCAL directories (.claude/, AGENTS.md) ONLY. We do NOT install to
104237
- * global/user directories (~/.claude/).
104239
+ * IMPORTANT: All tbd integration files (skills, hooks, settings, scripts) are
104240
+ * installed to PROJECT-LOCAL directories (.agents/, .claude/, .codex/,
104241
+ * scripts/agent/, AGENTS.md) ONLY. We do NOT install to global/user directories
104242
+ * (~/.claude/, ~/.codex/, ~/.agents/).
104238
104243
  *
104239
104244
  * This file defines all path constants in one place to:
104240
104245
  * 1. Ensure consistency across the codebase
@@ -104242,6 +104247,19 @@ function renderFooter(suggestions, colors) {
104242
104247
  * 3. Simplify future changes to path conventions
104243
104248
  */
104244
104249
  /**
104250
+ * Format version stamped into generated agent integration artifacts (e.g. the
104251
+ * AGENTS.md managed block's begin marker: `... format=f03 surface=...`).
104252
+ *
104253
+ * UNIFIED with the `.tbd/` directory format (`tbd_format`): there is one format
104254
+ * code for all tbd-managed surfaces, sourced from `tbd-format.ts` (the single
104255
+ * source of truth). Bump `CURRENT_FORMAT` there when any managed surface — config
104256
+ * schema OR a generated agent surface — changes shape. A marked AGENTS.md block
104257
+ * with no `format=` field predates this and is treated as `f01`; a running tbd
104258
+ * that finds a HIGHER format than it knows refuses to overwrite it and tells the
104259
+ * user to upgrade tbd.
104260
+ */
104261
+ const AGENT_INTEGRATION_FORMAT = CURRENT_FORMAT;
104262
+ /**
104245
104263
  * Relative path to Claude Code settings file from project root.
104246
104264
  * This is where hooks are configured.
104247
104265
  */
@@ -104275,11 +104293,33 @@ const TBD_CLOSING_REMINDER_REL = ".claude/hooks/tbd-closing-reminder.sh";
104275
104293
  */
104276
104294
  const GH_CLI_SCRIPT_REL = ".claude/scripts/ensure-gh-cli.sh";
104277
104295
  /**
104296
+ * Canonical portable project Agent Skill, scanned by Codex, Gemini CLI, Cursor,
104297
+ * GitHub Copilot, Amp, OpenCode, pi, and other Agent Skills clients.
104298
+ */
104299
+ const AGENTS_SKILL_REL = ".agents/skills/tbd/SKILL.md";
104300
+ /**
104301
+ * Repository distribution copy of the skill, for skills.sh-style installers
104302
+ * (`npx skills add`) and direct GitHub browsing.
104303
+ */
104304
+ const SKILLS_DIST_REL = "skills/tbd/SKILL.md";
104305
+ /**
104278
104306
  * Relative path to AGENTS.md file from project root.
104279
104307
  * Used by Codex, Factory.ai, Cursor (v1.6+), and other compatible tools.
104280
104308
  */
104281
104309
  const AGENTS_MD_REL = "AGENTS.md";
104282
104310
  /**
104311
+ * Codex project-local config/hook directory.
104312
+ */
104313
+ const CODEX_DIR_REL = ".codex";
104314
+ /**
104315
+ * Codex project-local hooks file (Claude-compatible event schema).
104316
+ */
104317
+ const CODEX_HOOKS_REL = ".codex/hooks.json";
104318
+ /**
104319
+ * Codex project-local config; may also carry an inline `[hooks]` table.
104320
+ */
104321
+ const CODEX_CONFIG_REL = ".codex/config.toml";
104322
+ /**
104283
104323
  * Global Claude Code directory in user's home.
104284
104324
  * Used ONLY for detecting if Claude Code is installed (for agent detection).
104285
104325
  * All installations are project-local.
@@ -104313,6 +104353,31 @@ function getAgentsMdPath(projectRoot) {
104313
104353
  return join(projectRoot, AGENTS_MD_REL);
104314
104354
  }
104315
104355
  /**
104356
+ * Get the three SKILL.md targets: the portable Agent Skills install, the Claude
104357
+ * Code mirror, and the committed distribution copy.
104358
+ *
104359
+ * @param projectRoot - The project root directory (containing .tbd/)
104360
+ */
104361
+ function getAgentSkillPaths(projectRoot) {
104362
+ return {
104363
+ portable: join(projectRoot, AGENTS_SKILL_REL),
104364
+ claudeMirror: join(projectRoot, CLAUDE_SKILL_REL),
104365
+ distribution: join(projectRoot, SKILLS_DIST_REL)
104366
+ };
104367
+ }
104368
+ /**
104369
+ * Get project-local Codex config/hook paths.
104370
+ *
104371
+ * @param projectRoot - The project root directory
104372
+ */
104373
+ function getCodexPaths(projectRoot) {
104374
+ return {
104375
+ dir: join(projectRoot, CODEX_DIR_REL),
104376
+ hooks: join(projectRoot, CODEX_HOOKS_REL),
104377
+ config: join(projectRoot, CODEX_CONFIG_REL)
104378
+ };
104379
+ }
104380
+ /**
104316
104381
  * Display path for Claude Code settings in status/doctor output.
104317
104382
  */
104318
104383
  const CLAUDE_SETTINGS_DISPLAY = "./.claude/settings.json";
@@ -104320,6 +104385,14 @@ const CLAUDE_SETTINGS_DISPLAY = "./.claude/settings.json";
104320
104385
  * Display path for AGENTS.md in status/doctor output.
104321
104386
  */
104322
104387
  const AGENTS_MD_DISPLAY = "./AGENTS.md";
104388
+ /**
104389
+ * Display path for the portable Agent Skill in status/doctor output.
104390
+ */
104391
+ const AGENTS_SKILL_DISPLAY = "./.agents/skills/tbd/SKILL.md";
104392
+ /**
104393
+ * Display path for the Codex hooks file in status/doctor output.
104394
+ */
104395
+ const CODEX_HOOKS_DISPLAY = "./.codex/hooks.json";
104323
104396
 
104324
104397
  //#endregion
104325
104398
  //#region src/cli/commands/status.ts
@@ -104357,10 +104430,14 @@ var StatusHandler = class extends BaseCommand {
104357
104430
  worktree_healthy: null,
104358
104431
  workspaces: [],
104359
104432
  integrations: {
104433
+ portable_skill: false,
104434
+ portable_skill_path: AGENTS_SKILL_DISPLAY,
104360
104435
  claude_code: false,
104361
104436
  claude_code_path: CLAUDE_SETTINGS_DISPLAY,
104362
104437
  codex: false,
104363
- codex_path: AGENTS_MD_DISPLAY
104438
+ codex_path: AGENTS_MD_DISPLAY,
104439
+ codex_hooks: false,
104440
+ codex_hooks_path: CODEX_HOOKS_DISPLAY
104364
104441
  }
104365
104442
  };
104366
104443
  const gitInfo = await this.checkGitRepo();
@@ -104428,11 +104505,23 @@ var StatusHandler = class extends BaseCommand {
104428
104505
  const claudePaths = getClaudePaths(projectRoot);
104429
104506
  const agentsPath = getAgentsMdPath(projectRoot);
104430
104507
  const result = {
104508
+ portable_skill: false,
104509
+ portable_skill_path: AGENTS_SKILL_DISPLAY,
104431
104510
  claude_code: false,
104432
104511
  claude_code_path: CLAUDE_SETTINGS_DISPLAY,
104433
104512
  codex: false,
104434
- codex_path: AGENTS_MD_DISPLAY
104513
+ codex_path: AGENTS_MD_DISPLAY,
104514
+ codex_hooks: false,
104515
+ codex_hooks_path: CODEX_HOOKS_DISPLAY
104435
104516
  };
104517
+ try {
104518
+ await access(getAgentSkillPaths(projectRoot).portable);
104519
+ result.portable_skill = true;
104520
+ } catch {}
104521
+ try {
104522
+ await access(getCodexPaths(projectRoot).hooks);
104523
+ result.codex_hooks = true;
104524
+ } catch {}
104436
104525
  try {
104437
104526
  await access(claudePaths.settings);
104438
104527
  const content = await readFile(claudePaths.settings, "utf-8");
@@ -104481,15 +104570,28 @@ var StatusHandler = class extends BaseCommand {
104481
104570
  remote: data.remote,
104482
104571
  displayPrefix: data.display_prefix
104483
104572
  }, colors);
104484
- if (renderIntegrationsSection([{
104485
- name: "Claude Code hooks",
104486
- installed: data.integrations.claude_code,
104487
- path: data.integrations.claude_code_path
104488
- }, {
104489
- name: "Codex AGENTS.md",
104490
- installed: data.integrations.codex,
104491
- path: data.integrations.codex_path
104492
- }], colors)) {
104573
+ if (renderIntegrationsSection([
104574
+ {
104575
+ name: "Portable Agent Skill",
104576
+ installed: data.integrations.portable_skill,
104577
+ path: data.integrations.portable_skill_path
104578
+ },
104579
+ {
104580
+ name: "Claude Code hooks",
104581
+ installed: data.integrations.claude_code,
104582
+ path: data.integrations.claude_code_path
104583
+ },
104584
+ {
104585
+ name: "Codex AGENTS.md",
104586
+ installed: data.integrations.codex,
104587
+ path: data.integrations.codex_path
104588
+ },
104589
+ {
104590
+ name: "Codex hooks",
104591
+ installed: data.integrations.codex_hooks,
104592
+ path: data.integrations.codex_hooks_path
104593
+ }
104594
+ ], colors)) {
104493
104595
  console.log("");
104494
104596
  console.log(`Run ${colors.bold("tbd setup auto")} to configure detected agents`);
104495
104597
  }
@@ -104829,8 +104931,10 @@ var DoctorHandler = class extends BaseCommand {
104829
104931
  healthChecks.push(await this.checkCloneScenarios());
104830
104932
  healthChecks.push(await this.checkSyncConsistency());
104831
104933
  const integrationChecks = [];
104934
+ integrationChecks.push(await this.checkPortableSkill());
104832
104935
  integrationChecks.push(await this.checkClaudeSkill());
104833
104936
  integrationChecks.push(await this.checkCodexAgents());
104937
+ integrationChecks.push(await this.checkCodexHooks());
104834
104938
  const allChecks = [...healthChecks, ...integrationChecks];
104835
104939
  const allOk = allChecks.every((c) => c.status === "ok");
104836
104940
  const hasFixable = allChecks.some((c) => c.fixable && c.status !== "ok");
@@ -105294,6 +105398,25 @@ var DoctorHandler = class extends BaseCommand {
105294
105398
  suggestion: "Run: tbd doctor --fix to create missing mappings"
105295
105399
  };
105296
105400
  }
105401
+ async checkPortableSkill() {
105402
+ const { portable } = getAgentSkillPaths(this.cwd);
105403
+ try {
105404
+ await access(portable);
105405
+ return {
105406
+ name: "Portable Agent Skill",
105407
+ status: "ok",
105408
+ path: AGENTS_SKILL_REL
105409
+ };
105410
+ } catch {
105411
+ return {
105412
+ name: "Portable Agent Skill",
105413
+ status: "warn",
105414
+ message: "not installed",
105415
+ path: AGENTS_SKILL_REL,
105416
+ suggestion: "Run: tbd setup --auto"
105417
+ };
105418
+ }
105419
+ }
105297
105420
  async checkClaudeSkill() {
105298
105421
  const claudePaths = getClaudePaths(this.cwd);
105299
105422
  try {
@@ -105313,6 +105436,25 @@ var DoctorHandler = class extends BaseCommand {
105313
105436
  };
105314
105437
  }
105315
105438
  }
105439
+ async checkCodexHooks() {
105440
+ const codexPaths = getCodexPaths(this.cwd);
105441
+ try {
105442
+ await access(codexPaths.hooks);
105443
+ return {
105444
+ name: "Codex hooks",
105445
+ status: "ok",
105446
+ path: CODEX_HOOKS_REL
105447
+ };
105448
+ } catch {
105449
+ return {
105450
+ name: "Codex hooks",
105451
+ status: "warn",
105452
+ message: "not installed",
105453
+ path: CODEX_HOOKS_REL,
105454
+ suggestion: "Run: tbd setup --auto"
105455
+ };
105456
+ }
105457
+ }
105316
105458
  async checkCodexAgents() {
105317
105459
  const agentsPath = getAgentsMdPath(this.cwd);
105318
105460
  try {
@@ -108323,16 +108465,89 @@ async function getShortcutDirectory(quiet = false) {
108323
108465
  return generateShortcutDirectory(shortcuts, guidelines);
108324
108466
  }
108325
108467
  /**
108326
- * Get the tbd section content for AGENTS.md (Codex integration).
108327
- * Loads from SKILL.md, strips frontmatter, and wraps in TBD INTEGRATION markers.
108468
+ * DO NOT EDIT marker inserted after the frontmatter of every generated SKILL.md.
108469
+ * Formatted to match flowmark output.
108470
+ */
108471
+ const SKILL_DO_NOT_EDIT_MARKER = "<!-- DO NOT EDIT: Generated by tbd setup.\nRun 'tbd setup' to update.\n-->";
108472
+ /**
108473
+ * Build the full generated SKILL.md payload: the bundled skill content with the
108474
+ * shortcut/guideline directory appended and a DO NOT EDIT marker after the
108475
+ * frontmatter. This is the single source for every skill surface (the portable
108476
+ * .agents/skills install and the .claude/skills mirror) so they stay identical.
108328
108477
  *
108329
- * @param quiet - If true, suppress auto-sync output (default: false)
108478
+ * @param quiet - If true, suppress auto-sync output.
108330
108479
  */
108331
- async function getCodexTbdSection(quiet = false) {
108332
- let content = stripFrontmatter(await loadSkillContent());
108480
+ async function buildSkillPayload(quiet = false) {
108481
+ let skillContent = await loadSkillContent();
108333
108482
  const directory = await getShortcutDirectory(quiet);
108334
- if (directory) content = content.trimEnd() + "\n\n" + directory + "\n";
108335
- return `<!-- BEGIN TBD INTEGRATION -->\n${content}<!-- END TBD INTEGRATION -->\n`;
108483
+ if (directory) skillContent = skillContent.trimEnd() + "\n\n" + directory;
108484
+ skillContent = insertAfterFrontmatter(skillContent, SKILL_DO_NOT_EDIT_MARKER);
108485
+ return skillContent.trimEnd() + "\n";
108486
+ }
108487
+ /**
108488
+ * Write a generated SKILL.md payload to a target path, creating parent dirs.
108489
+ */
108490
+ async function writeSkillFile(targetPath, payload) {
108491
+ await mkdir(dirname(targetPath), { recursive: true });
108492
+ await writeFile(targetPath, payload);
108493
+ }
108494
+ /**
108495
+ * AGENTS.md managed-block markers. `CODEX_BEGIN_MARKER` is the stable PREFIX of
108496
+ * the begin line — the metadata (`format=fNN surface=agents-md`) follows it on the
108497
+ * same line, e.g. `<!-- BEGIN TBD INTEGRATION format=f02 surface=agents-md -->`.
108498
+ * Cleanup/upgrade code matches on this prefix so it finds both legacy
108499
+ * (`<!-- BEGIN TBD INTEGRATION -->`) and current marked blocks.
108500
+ */
108501
+ const CODEX_BEGIN_MARKER = "<!-- BEGIN TBD INTEGRATION";
108502
+ const CODEX_END_MARKER = "<!-- END TBD INTEGRATION -->";
108503
+ /** The full begin marker line for newly generated blocks. */
108504
+ const CODEX_BEGIN_LINE = `${CODEX_BEGIN_MARKER} format=${AGENT_INTEGRATION_FORMAT} surface=agents-md -->`;
108505
+ /** Numeric value of an `fNN` format string, for ordering comparisons. */
108506
+ function formatToNumber(format) {
108507
+ return Number.parseInt(format.replace(/^f/, ""), 10);
108508
+ }
108509
+ /**
108510
+ * Read the integration format (`fNN`) stamped in a generated artifact's begin
108511
+ * marker. Returns `f01` for a legacy marked block with no `format=` field, or
108512
+ * null when there is no tbd-managed block at all.
108513
+ */
108514
+ function parseIntegrationFormat(content) {
108515
+ const match = /format=f(\d+)/.exec(content);
108516
+ if (match?.[1]) return `f${match[1]}`;
108517
+ return content.includes(CODEX_BEGIN_MARKER) ? "f01" : null;
108518
+ }
108519
+ /**
108520
+ * Forward-compatibility guard. If a generated artifact was written by a NEWER
108521
+ * tbd than this one understands, refuse to rewrite it and tell the user to
108522
+ * upgrade tbd — overwriting would downgrade a newer managed format. This is what
108523
+ * makes pinning safe on a team: an older tbd fails loudly instead of clobbering.
108524
+ */
108525
+ function assertNotNewerFormat(content, artifact) {
108526
+ const format = parseIntegrationFormat(content);
108527
+ if (format !== null && formatToNumber(format) > formatToNumber(AGENT_INTEGRATION_FORMAT)) throw new CLIError(`${artifact} was generated by a newer tbd (integration format ${format}; this tbd supports up to ${AGENT_INTEGRATION_FORMAT}).\nUpgrade tbd to manage it: npm install -g get-tbd@latest`);
108528
+ }
108529
+ /**
108530
+ * Get the tbd managed block for AGENTS.md.
108531
+ *
108532
+ * This is a COMPACT always-on bootstrap, not a copy of the full skill: it names
108533
+ * tbd, states the operating rule, and points to the CLI commands that provide
108534
+ * progressive disclosure (`tbd prime`, `tbd skill`, `tbd shortcut/guidelines
108535
+ * --list`). The full skill body lives in the SKILL.md surfaces, not here, so the
108536
+ * block stays well under the AGENTS.md prompt-budget guidance.
108537
+ */
108538
+ function getCodexTbdSection() {
108539
+ return `${CODEX_BEGIN_LINE}\n## tbd
108540
+
108541
+ This repository uses **tbd** for git-native issue tracking (beads), spec-driven
108542
+ planning, and on-demand engineering guidelines.
108543
+ As the agent, you operate tbd on the user’s behalf — translate their requests into tbd
108544
+ actions rather than telling them to run commands.
108545
+
108546
+ - Run \`tbd prime\` to load current project state and the full tbd workflow.
108547
+ - Run \`tbd skill\` for the complete reusable tbd skill instructions.
108548
+ - Run \`tbd shortcut --list\` and \`tbd guidelines --list\` for on-demand resources.
108549
+ - Track all work as beads: \`tbd create\`, \`tbd ready\`, \`tbd close\`, and \`tbd sync\`.
108550
+ \n${CODEX_END_MARKER}\n`;
108336
108551
  }
108337
108552
  /**
108338
108553
  * Script to ensure tbd CLI is installed and run tbd prime.
@@ -108344,82 +108559,32 @@ async function getCodexTbdSection(quiet = false) {
108344
108559
  * tbd-session.sh --brief # Ensure tbd + run tbd prime --brief (for PreCompact)
108345
108560
  */
108346
108561
  const TBD_SESSION_SCRIPT = `#!/bin/bash
108347
- # Ensure tbd CLI is installed and run tbd prime for Claude Code sessions
108348
- # Installed by: tbd setup --auto
108349
- # This script runs on SessionStart and PreCompact
108350
-
108351
- # Get npm global bin directory (if npm is available)
108352
- NPM_GLOBAL_BIN=""
108353
- if command -v npm &> /dev/null; then
108354
- NPM_PREFIX=$(npm config get prefix 2>/dev/null)
108355
- if [ -n "$NPM_PREFIX" ] && [ -d "$NPM_PREFIX/bin" ]; then
108356
- NPM_GLOBAL_BIN="$NPM_PREFIX/bin"
108357
- fi
108358
- fi
108562
+ # Ensure the tbd CLI is available and run \`tbd prime\`.
108563
+ # Installed by: tbd setup --auto. Runs on SessionStart and PreCompact.
108564
+ #
108565
+ # Local-first, then a VERSION-PINNED zero-install fallback. Pinning is both a
108566
+ # supply-chain control (an unpinned runner re-resolves to latest on every run
108567
+ # and bypasses any cool-off) and a consistency control (every teammate and agent
108568
+ # runs the same tbd version).
108359
108569
 
108360
- # Add common binary locations to PATH (persists for entire script)
108361
- # Include npm global bin if found
108362
- export PATH="$NPM_GLOBAL_BIN:$HOME/.local/bin:$HOME/bin:/usr/local/bin:$PATH"
108570
+ # Prefer common local bin locations.
108571
+ export PATH="$HOME/.local/bin:$HOME/bin:/usr/local/bin:$PATH"
108363
108572
 
108364
- # Function to ensure tbd is available
108365
- ensure_tbd() {
108366
- # Check if tbd is already installed
108367
- if command -v tbd &> /dev/null; then
108368
- return 0
108369
- fi
108370
-
108371
- echo "[tbd] CLI not found, installing..."
108372
-
108373
- # Try npm first (most common for Node.js tools)
108374
- if command -v npm &> /dev/null; then
108375
- echo "[tbd] Installing via npm..."
108376
- npm install -g get-tbd 2>/dev/null || {
108377
- # If global install fails (permissions), try local install
108378
- echo "[tbd] Global npm install failed, trying user install..."
108379
- mkdir -p ~/.local/bin
108380
- npm install --prefix ~/.local get-tbd
108381
- # Create symlink if needed
108382
- if [ -f ~/.local/node_modules/.bin/tbd ]; then
108383
- ln -sf ~/.local/node_modules/.bin/tbd ~/.local/bin/tbd
108384
- fi
108385
- }
108386
- elif command -v pnpm &> /dev/null; then
108387
- echo "[tbd] Installing via pnpm..."
108388
- pnpm add -g get-tbd
108389
- elif command -v yarn &> /dev/null; then
108390
- echo "[tbd] Installing via yarn..."
108391
- yarn global add get-tbd
108392
- else
108393
- echo "[tbd] ERROR: No package manager found (npm, pnpm, or yarn required)"
108394
- echo "[tbd] Please install Node.js and npm, then run: npm install -g get-tbd"
108395
- return 1
108396
- fi
108397
-
108398
- # Verify installation
108399
- if command -v tbd &> /dev/null; then
108400
- echo "[tbd] Successfully installed to $(which tbd)"
108401
- return 0
108402
- else
108403
- echo "[tbd] WARNING: tbd installed but not found in PATH"
108404
- echo "[tbd] Checking common locations..."
108405
- # Try to find and add to path (include npm global bin)
108406
- for dir in "$NPM_GLOBAL_BIN" ~/.local/bin ~/.local/node_modules/.bin /usr/local/bin; do
108407
- if [ -n "$dir" ] && [ -x "$dir/tbd" ]; then
108408
- export PATH="$dir:$PATH"
108409
- echo "[tbd] Found at $dir/tbd"
108410
- return 0
108411
- fi
108412
- done
108413
- echo "[tbd] Could not locate tbd after installation"
108414
- return 1
108415
- fi
108416
- }
108573
+ # Local-first: use tbd if it is already on PATH.
108574
+ if command -v tbd &> /dev/null; then
108575
+ tbd prime "$@"
108576
+ exit $?
108577
+ fi
108417
108578
 
108418
- # Main
108419
- ensure_tbd || exit 1
108579
+ # Pinned zero-install fallback. Never use an unpinned runner here.
108580
+ if command -v npx &> /dev/null; then
108581
+ npx --yes get-tbd@${VERSION} prime "$@"
108582
+ exit $?
108583
+ fi
108420
108584
 
108421
- # Run tbd prime with any passed arguments (e.g., --brief for PreCompact)
108422
- tbd prime "$@"
108585
+ echo "[tbd] tbd CLI not found and npx is unavailable."
108586
+ echo "[tbd] Install it with: npm install -g get-tbd@${VERSION}"
108587
+ exit 1
108423
108588
  `;
108424
108589
  /**
108425
108590
  * Claude Code session hooks configuration.
@@ -108509,22 +108674,14 @@ async function loadBundledScript(name) {
108509
108674
  throw new Error(`Bundled script not found: ${name}`);
108510
108675
  }
108511
108676
  /**
108512
- * AGENTS.md integration markers for Codex/Factory.ai
108513
- * Content is now generated dynamically from SKILL.md via getCodexTbdSection()
108514
- */
108515
- const CODEX_BEGIN_MARKER = "<!-- BEGIN TBD INTEGRATION -->";
108516
- const CODEX_END_MARKER = "<!-- END TBD INTEGRATION -->";
108517
- /**
108518
108677
  * Generate a new AGENTS.md file with tbd integration.
108519
- *
108520
- * @param quiet - If true, suppress auto-sync output (default: false)
108521
108678
  */
108522
- async function getCodexNewAgentsFile(quiet = false) {
108679
+ function getCodexNewAgentsFile() {
108523
108680
  return `# Project Instructions for AI Agents
108524
108681
 
108525
108682
  This file provides instructions and context for AI coding agents working on this project.
108526
108683
 
108527
- ${await getCodexTbdSection(quiet)}
108684
+ ${getCodexTbdSection()}
108528
108685
  ## Build & Test
108529
108686
 
108530
108687
  _Add your build and test commands here_
@@ -108545,6 +108702,54 @@ _Add your project-specific conventions here_
108545
108702
  `;
108546
108703
  }
108547
108704
  /**
108705
+ * Codex project-local script paths (relative to repo root). Codex hooks
108706
+ * reference ONLY these `.codex/` paths, never `.claude/`, so Codex setup stays
108707
+ * independent of Claude Code setup.
108708
+ */
108709
+ const CODEX_SESSION_SCRIPT_REL = ".codex/tbd-session.sh";
108710
+ const CODEX_CLOSING_REMINDER_REL = ".codex/tbd-closing-reminder.sh";
108711
+ const CODEX_GH_CLI_SCRIPT_REL = ".codex/ensure-gh-cli.sh";
108712
+ /**
108713
+ * Build the Codex hooks.json content. Codex uses the same lifecycle event
108714
+ * schema as Claude Code (command handlers), so tbd's hooks map almost 1:1:
108715
+ * SessionStart and PreCompact run `tbd prime`, PostToolUse reminds about sync
108716
+ * after `git push`, and (when enabled) a second SessionStart entry ensures gh.
108717
+ */
108718
+ function getCodexHooksConfig(useGhCli) {
108719
+ const sessionStart = [{
108720
+ matcher: "",
108721
+ hooks: [{
108722
+ type: "command",
108723
+ command: `bash ${CODEX_SESSION_SCRIPT_REL}`
108724
+ }]
108725
+ }];
108726
+ if (useGhCli) sessionStart.push({
108727
+ matcher: "",
108728
+ hooks: [{
108729
+ type: "command",
108730
+ command: `bash ${CODEX_GH_CLI_SCRIPT_REL}`,
108731
+ timeout: 120
108732
+ }]
108733
+ });
108734
+ return { hooks: {
108735
+ SessionStart: sessionStart,
108736
+ PreCompact: [{
108737
+ matcher: "",
108738
+ hooks: [{
108739
+ type: "command",
108740
+ command: `bash ${CODEX_SESSION_SCRIPT_REL} --brief`
108741
+ }]
108742
+ }],
108743
+ PostToolUse: [{
108744
+ matcher: "Bash",
108745
+ hooks: [{
108746
+ type: "command",
108747
+ command: `bash ${CODEX_CLOSING_REMINDER_REL}`
108748
+ }]
108749
+ }]
108750
+ } };
108751
+ }
108752
+ /**
108548
108753
  * Legacy script patterns to clean up from .claude/scripts/
108549
108754
  * These were used in older versions of tbd before hooks moved to `tbd prime`
108550
108755
  */
@@ -108805,13 +109010,7 @@ var SetupClaudeHandler = class extends BaseCommand {
108805
109010
  await writeFile(claudePaths.closingReminder, TBD_CLOSE_PROTOCOL_SCRIPT);
108806
109011
  await chmod(claudePaths.closingReminder, 493);
108807
109012
  this.output.success("Installed sync reminder hook script");
108808
- await mkdir(dirname(skillPath), { recursive: true });
108809
- let skillContent = await loadSkillContent();
108810
- const directory = await getShortcutDirectory(this.ctx.quiet);
108811
- if (directory) skillContent = skillContent.trimEnd() + "\n\n" + directory;
108812
- skillContent = insertAfterFrontmatter(skillContent, "<!-- DO NOT EDIT: Generated by tbd setup.\nRun 'tbd setup' to update.\n-->");
108813
- skillContent = skillContent.trimEnd() + "\n";
108814
- await writeFile(skillPath, skillContent);
109013
+ await writeSkillFile(skillPath, await buildSkillPayload(this.ctx.quiet));
108815
109014
  this.output.success("Installed skill file");
108816
109015
  this.output.info(` ${skillPath}`);
108817
109016
  this.output.info("");
@@ -108844,16 +109043,93 @@ var SetupCodexHandler = class extends BaseCommand {
108844
109043
  this.projectDir = dir;
108845
109044
  }
108846
109045
  async run(options) {
108847
- const agentsPath = join(this.projectDir ?? process.cwd(), "AGENTS.md");
109046
+ const cwd = this.projectDir ?? process.cwd();
109047
+ const agentsPath = join(cwd, "AGENTS.md");
108848
109048
  if (options.check) {
108849
109049
  await this.checkCodexSetup(agentsPath);
108850
109050
  return;
108851
109051
  }
108852
109052
  if (options.remove) {
108853
109053
  await this.removeCodexSection(agentsPath);
109054
+ await this.removeCodexHooks(cwd);
108854
109055
  return;
108855
109056
  }
108856
109057
  await this.installCodexSection(agentsPath);
109058
+ await this.installCodexHooks(cwd);
109059
+ }
109060
+ /**
109061
+ * Read the use_gh_cli setting; defaults to true (so fresh setup installs it).
109062
+ */
109063
+ async getUseGhCliSetting(cwd) {
109064
+ try {
109065
+ const tbdRoot = await findTbdRoot(cwd);
109066
+ if (!tbdRoot) return true;
109067
+ return (await readConfig(tbdRoot)).settings.use_gh_cli ?? true;
109068
+ } catch {
109069
+ return true;
109070
+ }
109071
+ }
109072
+ /**
109073
+ * Install Codex lifecycle hooks: writes .codex/ scripts and a .codex/hooks.json
109074
+ * (merged idempotently with any user hooks). Scripts reuse the same bodies as
109075
+ * the Claude install but live under .codex/ so Codex never references .claude/.
109076
+ */
109077
+ async installCodexHooks(cwd) {
109078
+ if (this.checkDryRun("Would install Codex hooks", { path: "./.codex/hooks.json" })) return;
109079
+ const codexPaths = getCodexPaths(cwd);
109080
+ await mkdir(codexPaths.dir, { recursive: true });
109081
+ const useGhCli = await this.getUseGhCliSetting(cwd);
109082
+ await writeFile(join(cwd, CODEX_SESSION_SCRIPT_REL), TBD_SESSION_SCRIPT);
109083
+ await chmod(join(cwd, CODEX_SESSION_SCRIPT_REL), 493);
109084
+ await writeFile(join(cwd, CODEX_CLOSING_REMINDER_REL), TBD_CLOSE_PROTOCOL_SCRIPT);
109085
+ await chmod(join(cwd, CODEX_CLOSING_REMINDER_REL), 493);
109086
+ if (useGhCli) {
109087
+ const ghScriptContent = await loadBundledScript("ensure-gh-cli.sh");
109088
+ await writeFile(join(cwd, CODEX_GH_CLI_SCRIPT_REL), ghScriptContent);
109089
+ await chmod(join(cwd, CODEX_GH_CLI_SCRIPT_REL), 493);
109090
+ } else await rm(join(cwd, CODEX_GH_CLI_SCRIPT_REL), { force: true });
109091
+ let existing = {};
109092
+ try {
109093
+ existing = JSON.parse(await readFile(codexPaths.hooks, "utf-8"));
109094
+ } catch {}
109095
+ const isTbdOwned = (entry) => {
109096
+ return (entry.hooks ?? []).some((h) => h.command?.includes(".codex/"));
109097
+ };
109098
+ const merged = { ...existing.hooks ?? {} };
109099
+ const tbdHooks = getCodexHooksConfig(useGhCli).hooks;
109100
+ for (const [event, entries] of Object.entries(tbdHooks)) merged[event] = [...(merged[event] ?? []).filter((e) => !isTbdOwned(e)), ...entries];
109101
+ await writeFile(codexPaths.hooks, JSON.stringify({
109102
+ ...existing,
109103
+ hooks: merged
109104
+ }, null, 2) + "\n");
109105
+ this.output.success("Installed Codex hooks (.codex/hooks.json)");
109106
+ }
109107
+ /**
109108
+ * Remove tbd-owned Codex hook entries and scripts, preserving user hooks.
109109
+ */
109110
+ async removeCodexHooks(cwd) {
109111
+ const codexPaths = getCodexPaths(cwd);
109112
+ try {
109113
+ const existing = JSON.parse(await readFile(codexPaths.hooks, "utf-8"));
109114
+ const hooks = existing.hooks ?? {};
109115
+ for (const event of Object.keys(hooks)) {
109116
+ const kept = (hooks[event] ?? []).filter((entry) => {
109117
+ return !(entry.hooks ?? []).some((h) => h.command?.includes(".codex/"));
109118
+ });
109119
+ if (kept.length === 0) delete hooks[event];
109120
+ else hooks[event] = kept;
109121
+ }
109122
+ if (Object.keys(hooks).length === 0) await rm(codexPaths.hooks, { force: true });
109123
+ else await writeFile(codexPaths.hooks, JSON.stringify({
109124
+ ...existing,
109125
+ hooks
109126
+ }, null, 2) + "\n");
109127
+ } catch {}
109128
+ for (const rel of [
109129
+ CODEX_SESSION_SCRIPT_REL,
109130
+ CODEX_CLOSING_REMINDER_REL,
109131
+ CODEX_GH_CLI_SCRIPT_REL
109132
+ ]) await rm(join(cwd, rel), { force: true });
108857
109133
  }
108858
109134
  async checkCodexSetup(agentsPath) {
108859
109135
  const agentsRelPath = "./AGENTS.md";
@@ -108938,8 +109214,9 @@ var SetupCodexHandler = class extends BaseCommand {
108938
109214
  existingContent = await readFile(agentsPath, "utf-8");
108939
109215
  } catch {}
108940
109216
  let newContent;
108941
- const tbdSection = await getCodexTbdSection(this.ctx.quiet);
109217
+ const tbdSection = getCodexTbdSection();
108942
109218
  if (existingContent) if (existingContent.includes(CODEX_BEGIN_MARKER)) {
109219
+ assertNotNewerFormat(existingContent, "AGENTS.md");
108943
109220
  newContent = this.updatetbdSection(existingContent, tbdSection);
108944
109221
  await writeFile(agentsPath, newContent);
108945
109222
  this.output.success("Updated existing tbd section in AGENTS.md");
@@ -108949,7 +109226,7 @@ var SetupCodexHandler = class extends BaseCommand {
108949
109226
  this.output.success("Added tbd section to existing AGENTS.md");
108950
109227
  }
108951
109228
  else {
108952
- await writeFile(agentsPath, await getCodexNewAgentsFile(this.ctx.quiet));
109229
+ await writeFile(agentsPath, getCodexNewAgentsFile());
108953
109230
  this.output.success("Created new AGENTS.md with tbd integration");
108954
109231
  }
108955
109232
  this.output.info(` File: ${agentsPath}`);
@@ -109319,9 +109596,11 @@ var SetupAutoHandler = class extends BaseCommand {
109319
109596
  console.log(colors.dim(`Cleaned up legacy ${parts.join(" and ")}`));
109320
109597
  }
109321
109598
  await this.syncDocs(cwd);
109322
- const claudeResult = await this.setupClaudeIfDetected(cwd);
109599
+ const targeting = this.resolveTargeting();
109600
+ await this.installPortableSkill(cwd);
109601
+ const claudeResult = await this.setupClaudeIfDetected(cwd, targeting.claude);
109323
109602
  results.push(claudeResult);
109324
- const codexResult = await this.setupCodexIfDetected(cwd);
109603
+ const codexResult = await this.setupCodexIfDetected(cwd, targeting.codex);
109325
109604
  results.push(codexResult);
109326
109605
  const installed = results.filter((r) => r.installed && !r.alreadyInstalled);
109327
109606
  const alreadyInstalled = results.filter((r) => r.alreadyInstalled);
@@ -109364,16 +109643,51 @@ var SetupAutoHandler = class extends BaseCommand {
109364
109643
  if (result.pruned.length > 0) console.log(colors.dim(`Pruned ${result.pruned.length} stale config entry/entries`));
109365
109644
  if (result.errors.length > 0) for (const { path, error } of result.errors) console.log(colors.warn(`Warning: ${path}: ${error}`));
109366
109645
  }
109367
- async setupClaudeIfDetected(cwd) {
109646
+ /**
109647
+ * Write the canonical portable Agent Skill to .agents/skills/tbd/SKILL.md.
109648
+ * Runs for every initialized repo, independent of agent detection, so the
109649
+ * skill is portable across Codex, Gemini CLI, Cursor, and other clients.
109650
+ */
109651
+ async installPortableSkill(cwd) {
109652
+ const colors = this.output.getColors();
109653
+ if (this.checkDryRun("Would install portable Agent Skill", { path: AGENTS_SKILL_DISPLAY })) return;
109654
+ const { portable } = getAgentSkillPaths(cwd);
109655
+ await writeSkillFile(portable, await buildSkillPayload(this.ctx.quiet));
109656
+ console.log(` ${colors.success("✓")} Portable Agent Skill (${AGENTS_SKILL_DISPLAY})`);
109657
+ }
109658
+ /**
109659
+ * Resolve which agent surfaces to install from the explicit targeting flags.
109660
+ * `--all`/`--claude`/`--codex` force surfaces on (and suppress auto-detection
109661
+ * of untargeted surfaces); `--skip-claude`/`--skip-codex` force them off; with
109662
+ * no targeting flag each surface falls back to detection-based auto behavior.
109663
+ */
109664
+ resolveTargeting() {
109665
+ const opts = this.cmd.optsWithGlobals();
109666
+ const all = opts.all === true;
109667
+ const anyPositive = all || opts.claude === true || opts.codex === true;
109668
+ const resolve = (on, skip) => {
109669
+ if (skip === true) return "off";
109670
+ if (on === true || all) return "on";
109671
+ return anyPositive ? "off" : "auto";
109672
+ };
109673
+ return {
109674
+ claude: resolve(opts.claude, opts.skipClaude),
109675
+ codex: resolve(opts.codex, opts.skipCodex)
109676
+ };
109677
+ }
109678
+ async setupClaudeIfDetected(cwd, mode) {
109368
109679
  const result = {
109369
109680
  name: "Claude Code",
109370
109681
  detected: false,
109371
109682
  installed: false,
109372
109683
  alreadyInstalled: false
109373
109684
  };
109374
- const hasClaudeDir = await pathExists(GLOBAL_CLAUDE_DIR);
109375
- const hasClaudeEnv = Object.keys(process.env).some((k) => k.startsWith("CLAUDE_"));
109376
- if (!hasClaudeDir && !hasClaudeEnv) return result;
109685
+ if (mode === "off") return result;
109686
+ if (mode === "auto") {
109687
+ const hasClaudeDir = await pathExists(GLOBAL_CLAUDE_DIR);
109688
+ const hasClaudeEnv = Object.keys(process.env).some((k) => k.startsWith("CLAUDE_"));
109689
+ if (!hasClaudeDir && !hasClaudeEnv) return result;
109690
+ }
109377
109691
  result.detected = true;
109378
109692
  const claudePaths = getClaudePaths(cwd);
109379
109693
  try {
@@ -109393,17 +109707,20 @@ var SetupAutoHandler = class extends BaseCommand {
109393
109707
  }
109394
109708
  return result;
109395
109709
  }
109396
- async setupCodexIfDetected(cwd) {
109710
+ async setupCodexIfDetected(cwd, mode) {
109397
109711
  const result = {
109398
109712
  name: "Codex/AGENTS.md",
109399
109713
  detected: false,
109400
109714
  installed: false,
109401
109715
  alreadyInstalled: false
109402
109716
  };
109717
+ if (mode === "off") return result;
109403
109718
  const agentsPath = getAgentsMdPath(cwd);
109404
109719
  const hasAgentsMd = await pathExists(agentsPath);
109405
- const hasCodexEnv = Object.keys(process.env).some((k) => k.startsWith("CODEX_"));
109406
- if (!hasAgentsMd && !hasCodexEnv) return result;
109720
+ if (mode === "auto") {
109721
+ const hasCodexEnv = Object.keys(process.env).some((k) => k.startsWith("CODEX_"));
109722
+ if (!hasAgentsMd && !hasCodexEnv) return result;
109723
+ }
109407
109724
  result.detected = true;
109408
109725
  if (hasAgentsMd) {
109409
109726
  if ((await readFile(agentsPath, "utf-8")).includes("BEGIN TBD INTEGRATION")) result.alreadyInstalled = true;
@@ -109414,12 +109731,13 @@ var SetupAutoHandler = class extends BaseCommand {
109414
109731
  await handler.run({});
109415
109732
  result.installed = true;
109416
109733
  } catch (error) {
109734
+ if (error instanceof CLIError) throw error;
109417
109735
  result.error = error.message;
109418
109736
  }
109419
109737
  return result;
109420
109738
  }
109421
109739
  };
109422
- const setupCommand = new Command("setup").description("Configure tbd integration with editors and tools").option("--auto", "Non-interactive mode with smart defaults (for agents/scripts)").option("--interactive", "Interactive mode with prompts (for humans)").option("--from-beads", "Migrate from Beads to tbd").option("--prefix <name>", "Project prefix for issue IDs (required for fresh setup)").option("--force", "Allow non-recommended prefix format (not 2-8 alphabetic)").option("--no-gh-cli", "Disable automatic GitHub CLI installation hook").action(async (options, command) => {
109740
+ const setupCommand = new Command("setup").description("Configure tbd integration with editors and tools").option("--auto", "Non-interactive mode with smart defaults (for agents/scripts)").option("--interactive", "Interactive mode with prompts (for humans)").option("--from-beads", "Migrate from Beads to tbd").option("--prefix <name>", "Project prefix for issue IDs (required for fresh setup)").option("--force", "Allow non-recommended prefix format (not 2-8 alphabetic)").option("--no-gh-cli", "Disable automatic GitHub CLI installation hook").option("--all", "Install every supported agent surface (Claude + Codex)").option("--claude", "Install the Claude Code surface (skill mirror + hooks)").option("--codex", "Install the Codex surface (AGENTS.md block + .codex hooks)").option("--skip-claude", "Skip the Claude Code surface even if detected").option("--skip-codex", "Skip the Codex surface even if detected").action(async (options, command) => {
109423
109741
  if (options.auto || options.interactive) {
109424
109742
  await new SetupDefaultHandler(command).run(options);
109425
109743
  return;