oathbound 0.15.1 → 0.16.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.
Files changed (2) hide show
  1. package/dist/cli.cjs +311 -156
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -14157,6 +14157,7 @@ __export(exports_cli, {
14157
14157
  mergeClaudeSettings: () => mergeClaudeSettings,
14158
14158
  isNewer: () => isNewer,
14159
14159
  installDevDependency: () => installDevDependency,
14160
+ findSkillsDirs: () => findSkillsDirs,
14160
14161
  addPrepareScript: () => addPrepareScript
14161
14162
  });
14162
14163
  module.exports = __toCommonJS(exports_cli);
@@ -16478,9 +16479,9 @@ if (shouldShowDeprecationWarning())
16478
16479
 
16479
16480
  // cli.ts
16480
16481
  var import_node_crypto2 = require("node:crypto");
16481
- var import_node_child_process2 = require("node:child_process");
16482
- var import_node_fs8 = require("node:fs");
16483
- var import_node_path8 = require("node:path");
16482
+ var import_node_child_process3 = require("node:child_process");
16483
+ var import_node_fs9 = require("node:fs");
16484
+ var import_node_path9 = require("node:path");
16484
16485
  var import_node_os4 = require("node:os");
16485
16486
 
16486
16487
  // node_modules/@clack/core/dist/index.mjs
@@ -17094,9 +17095,9 @@ function usage(exitCode = 1) {
17094
17095
  ${BOLD}oathbound${RESET} — install, verify, and publish skills & agents
17095
17096
 
17096
17097
  ${DIM}Usage:${RESET}
17097
- oathbound init ${DIM}Setup wizard configure project${RESET}
17098
- oathbound pull <namespace/skill-name[@version]>
17099
- oathbound install <namespace/skill-name[@version]>
17098
+ oathbound init [--global|--local] ${DIM}Setup wizard (default: global + local)${RESET}
17099
+ oathbound pull <namespace/skill-name[@version]> [--global]
17100
+ oathbound install <namespace/skill-name[@version]> [--global]
17100
17101
  oathbound push [path] [--private] ${DIM}Publish a skill to the registry${RESET}
17101
17102
  oathbound search [query] ${DIM}Search skills in the registry${RESET}
17102
17103
  oathbound list ${DIM}List all public skills${RESET}
@@ -17256,8 +17257,9 @@ function hasOathboundHooks(settings) {
17256
17257
  }
17257
17258
  return false;
17258
17259
  }
17259
- function mergeClaudeSettings() {
17260
- const claudeDir = import_node_path.join(process.cwd(), ".claude");
17260
+ function mergeClaudeSettings(targetDir) {
17261
+ const baseDir = targetDir ?? process.cwd();
17262
+ const claudeDir = import_node_path.join(baseDir, ".claude");
17261
17263
  const settingsPath = import_node_path.join(claudeDir, "settings.json");
17262
17264
  if (!import_node_fs.existsSync(settingsPath)) {
17263
17265
  import_node_fs.mkdirSync(claudeDir, { recursive: true });
@@ -17380,8 +17382,8 @@ function compareSemver(a, b) {
17380
17382
  }
17381
17383
 
17382
17384
  // verify.ts
17383
- var import_node_fs4 = require("node:fs");
17384
- var import_node_path4 = require("node:path");
17385
+ var import_node_fs5 = require("node:fs");
17386
+ var import_node_path5 = require("node:path");
17385
17387
  var import_node_os2 = require("node:os");
17386
17388
  var import_yaml = __toESM(require_dist(), 1);
17387
17389
 
@@ -17424,9 +17426,66 @@ function hashSkillDir(skillDir) {
17424
17426
  return contentHash(files);
17425
17427
  }
17426
17428
 
17429
+ // project-setup.ts
17430
+ var import_node_child_process = require("node:child_process");
17431
+ var import_node_fs4 = require("node:fs");
17432
+ var import_node_path4 = require("node:path");
17433
+ function detectPackageManager() {
17434
+ if (import_node_fs4.existsSync(import_node_path4.join(process.cwd(), "bun.lockb")) || import_node_fs4.existsSync(import_node_path4.join(process.cwd(), "bun.lock")))
17435
+ return "bun";
17436
+ if (import_node_fs4.existsSync(import_node_path4.join(process.cwd(), "pnpm-lock.yaml")))
17437
+ return "pnpm";
17438
+ if (import_node_fs4.existsSync(import_node_path4.join(process.cwd(), "yarn.lock")))
17439
+ return "yarn";
17440
+ return "npm";
17441
+ }
17442
+ function installDevDependency() {
17443
+ const pkgPath = import_node_path4.join(process.cwd(), "package.json");
17444
+ if (!import_node_fs4.existsSync(pkgPath))
17445
+ return "no-package-json";
17446
+ try {
17447
+ const pkg = JSON.parse(import_node_fs4.readFileSync(pkgPath, "utf-8"));
17448
+ if (pkg.devDependencies?.oathbound || pkg.dependencies?.oathbound)
17449
+ return "skipped";
17450
+ } catch {}
17451
+ const pm = detectPackageManager();
17452
+ const cmds = {
17453
+ bun: ["bun", ["add", "--dev", "oathbound"]],
17454
+ pnpm: ["pnpm", ["add", "--save-dev", "oathbound"]],
17455
+ yarn: ["yarn", ["add", "--dev", "oathbound"]],
17456
+ npm: ["npm", ["install", "--save-dev", "oathbound"]]
17457
+ };
17458
+ const [bin, args] = cmds[pm];
17459
+ try {
17460
+ import_node_child_process.execFileSync(bin, args, { stdio: "pipe", cwd: process.cwd() });
17461
+ return "installed";
17462
+ } catch {
17463
+ return "failed";
17464
+ }
17465
+ }
17466
+ function addPrepareScript() {
17467
+ const pkgPath = import_node_path4.join(process.cwd(), "package.json");
17468
+ if (!import_node_fs4.existsSync(pkgPath))
17469
+ return "skipped";
17470
+ let pkg;
17471
+ try {
17472
+ pkg = JSON.parse(import_node_fs4.readFileSync(pkgPath, "utf-8"));
17473
+ } catch {
17474
+ return "skipped";
17475
+ }
17476
+ const prepare = pkg.scripts?.prepare ?? "";
17477
+ if (prepare.includes("oathbound setup"))
17478
+ return "skipped";
17479
+ const newPrepare = prepare ? `${prepare} && oathbound setup` : "oathbound setup";
17480
+ pkg.scripts = { ...pkg.scripts ?? {}, prepare: newPrepare };
17481
+ import_node_fs4.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + `
17482
+ `);
17483
+ return prepare ? "appended" : "added";
17484
+ }
17485
+
17427
17486
  // verify.ts
17428
17487
  function sessionStatePath(sessionId) {
17429
- return import_node_path4.join(import_node_os2.tmpdir(), `oathbound-${sessionId}.json`);
17488
+ return import_node_path5.join(import_node_os2.tmpdir(), `oathbound-${sessionId}.json`);
17430
17489
  }
17431
17490
  async function readStdin() {
17432
17491
  const chunks = [];
@@ -17435,45 +17494,113 @@ async function readStdin() {
17435
17494
  }
17436
17495
  return Buffer.concat(chunks).toString("utf-8");
17437
17496
  }
17438
- function findSkillsDir() {
17497
+ function resolveLocalSkillsDir() {
17439
17498
  const cwd = process.cwd();
17440
17499
  const normalized = cwd.replace(/\/+$/, "");
17441
17500
  if (normalized.endsWith(".claude/skills"))
17442
17501
  return cwd;
17443
17502
  if (normalized.endsWith(".claude")) {
17444
- const skills = import_node_path4.join(cwd, "skills");
17445
- if (import_node_fs4.existsSync(skills))
17503
+ const skills = import_node_path5.join(cwd, "skills");
17504
+ if (import_node_fs5.existsSync(skills))
17446
17505
  return skills;
17447
17506
  }
17448
- const direct = import_node_path4.join(cwd, ".claude", "skills");
17449
- if (import_node_fs4.existsSync(direct))
17507
+ const direct = import_node_path5.join(cwd, ".claude", "skills");
17508
+ if (import_node_fs5.existsSync(direct))
17450
17509
  return direct;
17451
17510
  const SKIP = new Set(["node_modules", ".git", "dist", "build", ".next"]);
17452
17511
  function search(dir, depth) {
17453
17512
  if (depth <= 0)
17454
17513
  return null;
17455
17514
  try {
17456
- const entries = import_node_fs4.readdirSync(dir, { withFileTypes: true });
17515
+ const entries = import_node_fs5.readdirSync(dir, { withFileTypes: true });
17457
17516
  for (const entry of entries) {
17458
17517
  if (!entry.isDirectory() || SKIP.has(entry.name))
17459
17518
  continue;
17460
17519
  if (entry.name === ".claude") {
17461
- const skills = import_node_path4.join(dir, ".claude", "skills");
17462
- if (import_node_fs4.existsSync(skills))
17520
+ const skills = import_node_path5.join(dir, ".claude", "skills");
17521
+ if (import_node_fs5.existsSync(skills))
17463
17522
  return skills;
17464
17523
  }
17465
17524
  }
17466
17525
  for (const entry of entries) {
17467
17526
  if (!entry.isDirectory() || SKIP.has(entry.name) || entry.name.startsWith("."))
17468
17527
  continue;
17469
- const result = search(import_node_path4.join(dir, entry.name), depth - 1);
17528
+ const result = search(import_node_path5.join(dir, entry.name), depth - 1);
17470
17529
  if (result)
17471
17530
  return result;
17472
17531
  }
17473
17532
  } catch {}
17474
17533
  return null;
17475
17534
  }
17476
- return search(cwd, 5) ?? cwd;
17535
+ return search(cwd, 5);
17536
+ }
17537
+ function hasSkillSubdirs(dir) {
17538
+ try {
17539
+ const entries = import_node_fs5.readdirSync(dir, { withFileTypes: true });
17540
+ return entries.some((e) => e.isDirectory() && !e.name.startsWith("."));
17541
+ } catch {
17542
+ return false;
17543
+ }
17544
+ }
17545
+ function findSkillsDirs() {
17546
+ const results = [];
17547
+ const globalDir = import_node_path5.join(import_node_os2.homedir(), ".claude", "skills");
17548
+ if (import_node_fs5.existsSync(globalDir) && hasSkillSubdirs(globalDir)) {
17549
+ results.push({ path: globalDir, source: "global" });
17550
+ }
17551
+ const localDir = resolveLocalSkillsDir();
17552
+ if (localDir && hasSkillSubdirs(localDir)) {
17553
+ results.push({ path: localDir, source: "local" });
17554
+ }
17555
+ return results;
17556
+ }
17557
+ function propagateToProject() {
17558
+ try {
17559
+ const cwd = process.cwd();
17560
+ const localSkills = import_node_path5.join(cwd, ".claude", "skills");
17561
+ if (!import_node_fs5.existsSync(localSkills))
17562
+ return;
17563
+ if (!hasSkillSubdirs(localSkills))
17564
+ return;
17565
+ if (import_node_fs5.existsSync(import_node_path5.join(cwd, ".oathbound.jsonc")))
17566
+ return;
17567
+ const pkgPath = import_node_path5.join(cwd, "package.json");
17568
+ if (!import_node_fs5.existsSync(pkgPath))
17569
+ return;
17570
+ writeOathboundConfig("warn");
17571
+ const raw = import_node_fs5.readFileSync(pkgPath, "utf-8");
17572
+ const pkg = JSON.parse(raw);
17573
+ if (!pkg.devDependencies?.oathbound && !pkg.dependencies?.oathbound) {
17574
+ pkg.devDependencies = { ...pkg.devDependencies ?? {}, oathbound: "latest" };
17575
+ const indent = raw.match(/^(\s+)/m)?.[1]?.length ?? 2;
17576
+ import_node_fs5.writeFileSync(pkgPath, JSON.stringify(pkg, null, indent) + `
17577
+ `);
17578
+ }
17579
+ addPrepareScript();
17580
+ mergeClaudeSettings();
17581
+ process.stderr.write(`
17582
+ Oathbound: configured project for team verification.
17583
+ `);
17584
+ process.stderr.write(` Created .oathbound.jsonc
17585
+ `);
17586
+ process.stderr.write(` Added oathbound to devDependencies
17587
+ `);
17588
+ process.stderr.write(` Added prepare script to package.json
17589
+
17590
+ `);
17591
+ process.stderr.write(` Commit these changes to protect your team:
17592
+ `);
17593
+ process.stderr.write(` git add .oathbound.jsonc package.json .claude/settings.json
17594
+
17595
+ `);
17596
+ process.stderr.write(` Then run: npm install (or bun/pnpm install)
17597
+
17598
+ `);
17599
+ } catch (err) {
17600
+ const msg = err instanceof Error ? err.message : "Unknown error";
17601
+ process.stderr.write(`oathbound: auto-propagation warning: ${msg}
17602
+ `);
17603
+ }
17477
17604
  }
17478
17605
  function skillNameFromPath(filePath) {
17479
17606
  const marker = ".claude/skills/";
@@ -17516,30 +17643,34 @@ ${TEAL}${BOLD}⬡ oathbound${RESET} ${YELLOW}⚠ Warning:${RESET} skill ${BOLD}"
17516
17643
  `);
17517
17644
  process.exit(0);
17518
17645
  }
17519
- function isExternalSkillAccess(toolName, toolInput, skillsDir, baseName) {
17520
- const resolvedSkillsDir = import_node_path4.resolve(skillsDir);
17646
+ function isExternalSkillAccess(toolName, toolInput, knownDirs, baseName) {
17647
+ const resolvedDirs = knownDirs.map((d) => import_node_path5.resolve(d));
17648
+ const isUnderKnownDir = (p) => {
17649
+ const rp = import_node_path5.resolve(p);
17650
+ return resolvedDirs.some((d) => rp.startsWith(d));
17651
+ };
17521
17652
  if (toolName === "Read") {
17522
17653
  const p = String(toolInput.file_path ?? "");
17523
- if (p && !import_node_path4.resolve(p).startsWith(resolvedSkillsDir))
17654
+ if (p && !isUnderKnownDir(p))
17524
17655
  return true;
17525
17656
  }
17526
17657
  if (toolName === "Glob" || toolName === "Grep") {
17527
17658
  const p = String(toolInput.path ?? "");
17528
- if (p && !import_node_path4.resolve(p).startsWith(resolvedSkillsDir))
17659
+ if (p && !isUnderKnownDir(p))
17529
17660
  return true;
17530
17661
  }
17531
17662
  if (toolName === "Bash") {
17532
17663
  const cmd = String(toolInput.command ?? "");
17533
- if (cmd.includes("/.claude/skills/" + baseName) && !cmd.includes(resolvedSkillsDir))
17664
+ if (cmd.includes("/.claude/skills/" + baseName) && !resolvedDirs.some((d) => cmd.includes(d)))
17534
17665
  return true;
17535
17666
  }
17536
17667
  return false;
17537
17668
  }
17538
17669
  function parseSkillVersion(skillDir) {
17539
- const skillMdPath = import_node_path4.join(skillDir, "SKILL.md");
17540
- if (!import_node_fs4.existsSync(skillMdPath))
17670
+ const skillMdPath = import_node_path5.join(skillDir, "SKILL.md");
17671
+ if (!import_node_fs5.existsSync(skillMdPath))
17541
17672
  return null;
17542
- const content = import_node_fs4.readFileSync(skillMdPath, "utf-8");
17673
+ const content = import_node_fs5.readFileSync(skillMdPath, "utf-8");
17543
17674
  const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
17544
17675
  if (!match)
17545
17676
  return null;
@@ -17569,28 +17700,32 @@ async function verify(supabaseUrl, supabaseAnonKey) {
17569
17700
  `);
17570
17701
  process.exit(1);
17571
17702
  }
17572
- const skillsDir = findSkillsDir();
17573
- if (!skillsDir.endsWith(".claude/skills") && !skillsDir.includes(".claude/skills")) {
17703
+ const skillsDirs = findSkillsDirs();
17704
+ propagateToProject();
17705
+ if (skillsDirs.length === 0) {
17574
17706
  const state2 = { verified: {}, rejected: [], ok: true };
17575
- import_node_fs4.writeFileSync(sessionStatePath(sessionId), JSON.stringify(state2));
17576
- console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: "Oathbound: no .claude/skills/ directory found — nothing to verify." } }));
17707
+ import_node_fs5.writeFileSync(sessionStatePath(sessionId), JSON.stringify(state2));
17708
+ console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: "Oathbound: no skills found — nothing to verify." } }));
17577
17709
  process.exit(0);
17578
17710
  }
17579
- const entries = import_node_fs4.readdirSync(skillsDir, { withFileTypes: true });
17580
- const skillDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith("."));
17581
- if (skillDirs.length === 0) {
17711
+ const localSkills = {};
17712
+ for (const { path: dir } of skillsDirs) {
17713
+ const entries = import_node_fs5.readdirSync(dir, { withFileTypes: true });
17714
+ for (const e of entries.filter((e2) => e2.isDirectory() && !e2.name.startsWith("."))) {
17715
+ const fullPath = import_node_path5.join(dir, e.name);
17716
+ localSkills[e.name] = {
17717
+ hash: hashSkillDir(fullPath),
17718
+ version: parseSkillVersion(fullPath) ?? "1.0.0",
17719
+ dir: fullPath
17720
+ };
17721
+ }
17722
+ }
17723
+ if (Object.keys(localSkills).length === 0) {
17582
17724
  const state2 = { verified: {}, rejected: [], ok: true };
17583
- import_node_fs4.writeFileSync(sessionStatePath(sessionId), JSON.stringify(state2));
17725
+ import_node_fs5.writeFileSync(sessionStatePath(sessionId), JSON.stringify(state2));
17584
17726
  console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: "Oathbound: no skills installed — nothing to verify." } }));
17585
17727
  process.exit(0);
17586
17728
  }
17587
- const localSkills = {};
17588
- for (const dir of skillDirs) {
17589
- const fullPath = import_node_path4.join(skillsDir, dir.name);
17590
- const hash = hashSkillDir(fullPath);
17591
- const version3 = parseSkillVersion(fullPath) ?? "1.0.0";
17592
- localSkills[dir.name] = { hash, version: version3 };
17593
- }
17594
17729
  const config = readOathboundConfig();
17595
17730
  const enforcement = config?.enforcement ?? "warn";
17596
17731
  const supabase = createClient(supabaseUrl, supabaseAnonKey);
@@ -17651,8 +17786,12 @@ async function verify(supabaseUrl, supabaseAnonKey) {
17651
17786
  }
17652
17787
  }
17653
17788
  const ok = rejected.length === 0;
17654
- const state = { verified, rejected, ok };
17655
- import_node_fs4.writeFileSync(sessionStatePath(sessionId), JSON.stringify(state));
17789
+ const skillDirsMap = {};
17790
+ for (const [name, { dir }] of Object.entries(localSkills)) {
17791
+ skillDirsMap[name] = dir;
17792
+ }
17793
+ const state = { verified, rejected, ok, skillDirs: skillDirsMap };
17794
+ import_node_fs5.writeFileSync(sessionStatePath(sessionId), JSON.stringify(state));
17656
17795
  if (ok && warnings.length === 0) {
17657
17796
  const names = Object.keys(verified).join(", ");
17658
17797
  console.log(JSON.stringify({
@@ -17726,23 +17865,42 @@ async function verifyCheck() {
17726
17865
  process.exit(0);
17727
17866
  const config = readOathboundConfig();
17728
17867
  const enforcement = config?.enforcement ?? "warn";
17729
- const skillsDir = findSkillsDir();
17730
- if (isExternalSkillAccess(toolName, toolInput, skillsDir, baseName)) {
17731
- process.exit(0);
17732
- }
17733
17868
  const stateFile = sessionStatePath(sessionId);
17734
- if (!import_node_fs4.existsSync(stateFile))
17869
+ if (!import_node_fs5.existsSync(stateFile))
17735
17870
  process.exit(0);
17736
17871
  let state;
17737
17872
  try {
17738
- state = JSON.parse(import_node_fs4.readFileSync(stateFile, "utf-8"));
17873
+ state = JSON.parse(import_node_fs5.readFileSync(stateFile, "utf-8"));
17739
17874
  } catch {
17740
17875
  process.stderr.write(`oathbound verify --check: corrupt session state file
17741
17876
  `);
17742
17877
  process.exit(1);
17743
17878
  }
17744
- const skillDir = import_node_path4.join(skillsDir, baseName);
17745
- if (!import_node_fs4.existsSync(skillDir) || !import_node_fs4.statSync(skillDir).isDirectory()) {
17879
+ let skillDir = null;
17880
+ if (state.skillDirs?.[baseName]) {
17881
+ skillDir = state.skillDirs[baseName];
17882
+ } else {
17883
+ for (const { path: dir } of findSkillsDirs()) {
17884
+ const candidate = import_node_path5.join(dir, baseName);
17885
+ if (import_node_fs5.existsSync(candidate) && import_node_fs5.statSync(candidate).isDirectory()) {
17886
+ skillDir = candidate;
17887
+ }
17888
+ }
17889
+ }
17890
+ const knownDirs = [];
17891
+ if (state.skillDirs) {
17892
+ const parentDirs = new Set;
17893
+ for (const d of Object.values(state.skillDirs)) {
17894
+ parentDirs.add(import_node_path5.join(d, ".."));
17895
+ }
17896
+ knownDirs.push(...parentDirs);
17897
+ } else {
17898
+ knownDirs.push(...findSkillsDirs().map((e) => e.path));
17899
+ }
17900
+ if (knownDirs.length > 0 && isExternalSkillAccess(toolName, toolInput, knownDirs, baseName)) {
17901
+ process.exit(0);
17902
+ }
17903
+ if (!skillDir || !import_node_fs5.existsSync(skillDir) || !import_node_fs5.statSync(skillDir).isDirectory()) {
17746
17904
  if (enforcement === "warn") {
17747
17905
  warnSkill(baseName, "not installed locally");
17748
17906
  } else {
@@ -17771,38 +17929,38 @@ async function verifyCheck() {
17771
17929
  }
17772
17930
 
17773
17931
  // auth.ts
17774
- var import_node_child_process = require("node:child_process");
17775
- var import_node_fs5 = require("node:fs");
17932
+ var import_node_child_process2 = require("node:child_process");
17933
+ var import_node_fs6 = require("node:fs");
17776
17934
  var import_node_http = require("node:http");
17777
- var import_node_path5 = require("node:path");
17935
+ var import_node_path6 = require("node:path");
17778
17936
  var import_node_os3 = require("node:os");
17779
17937
  var SUPABASE_URL = "https://mjnfqagwuewhgwbtrdgs.supabase.co";
17780
17938
  var SUPABASE_ANON_KEY = "sb_publishable_T-rk0azNRqAMLLGCyadyhQ_ulk9685n";
17781
17939
  var API_BASE = process.env.OATHBOUND_API_URL ?? "https://www.oathbound.ai";
17782
- var AUTH_DIR = import_node_path5.join(import_node_os3.homedir(), ".oathbound");
17783
- var AUTH_FILE = import_node_path5.join(AUTH_DIR, "auth.json");
17940
+ var AUTH_DIR = import_node_path6.join(import_node_os3.homedir(), ".oathbound");
17941
+ var AUTH_FILE = import_node_path6.join(AUTH_DIR, "auth.json");
17784
17942
  function saveSession(session) {
17785
- import_node_fs5.mkdirSync(AUTH_DIR, { recursive: true, mode: 448 });
17786
- import_node_fs5.writeFileSync(AUTH_FILE, JSON.stringify(session, null, 2), { mode: 384 });
17943
+ import_node_fs6.mkdirSync(AUTH_DIR, { recursive: true, mode: 448 });
17944
+ import_node_fs6.writeFileSync(AUTH_FILE, JSON.stringify(session, null, 2), { mode: 384 });
17787
17945
  }
17788
17946
  function loadSession() {
17789
- if (!import_node_fs5.existsSync(AUTH_FILE))
17947
+ if (!import_node_fs6.existsSync(AUTH_FILE))
17790
17948
  return null;
17791
17949
  try {
17792
- return JSON.parse(import_node_fs5.readFileSync(AUTH_FILE, "utf-8"));
17950
+ return JSON.parse(import_node_fs6.readFileSync(AUTH_FILE, "utf-8"));
17793
17951
  } catch {
17794
17952
  return null;
17795
17953
  }
17796
17954
  }
17797
17955
  function clearSession() {
17798
- if (import_node_fs5.existsSync(AUTH_FILE))
17799
- import_node_fs5.unlinkSync(AUTH_FILE);
17956
+ if (import_node_fs6.existsSync(AUTH_FILE))
17957
+ import_node_fs6.unlinkSync(AUTH_FILE);
17800
17958
  }
17801
17959
  function openBrowser(url) {
17802
17960
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
17803
17961
  const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
17804
17962
  try {
17805
- import_node_child_process.spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
17963
+ import_node_child_process2.spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
17806
17964
  } catch {}
17807
17965
  }
17808
17966
  var SUCCESS_HTML = `<!DOCTYPE html>
@@ -17936,16 +18094,16 @@ ${BRAND}`);
17936
18094
  }
17937
18095
 
17938
18096
  // push.ts
17939
- var import_node_fs6 = require("node:fs");
17940
- var import_node_path6 = require("node:path");
18097
+ var import_node_fs7 = require("node:fs");
18098
+ var import_node_path7 = require("node:path");
17941
18099
  var import_yaml2 = __toESM(require_dist(), 1);
17942
18100
  var API_BASE2 = process.env.OATHBOUND_API_URL ?? "https://www.oathbound.ai";
17943
18101
  async function push(pathArg, options) {
17944
18102
  Wt2(BRAND);
17945
18103
  const skillDir = resolveSkillDir(pathArg);
17946
18104
  console.log(`${DIM} directory: ${skillDir}${RESET}`);
17947
- if (!import_node_fs6.existsSync(import_node_path6.join(skillDir, "SKILL.md"))) {
17948
- fail("No SKILL.md found", `Expected at ${import_node_path6.join(skillDir, "SKILL.md")}`);
18105
+ if (!import_node_fs7.existsSync(import_node_path7.join(skillDir, "SKILL.md"))) {
18106
+ fail("No SKILL.md found", `Expected at ${import_node_path7.join(skillDir, "SKILL.md")}`);
17949
18107
  }
17950
18108
  const rawFiles = collectFiles(skillDir);
17951
18109
  const skillMdFile = rawFiles.find((f) => f.path === "SKILL.md");
@@ -18017,13 +18175,13 @@ async function push(pathArg, options) {
18017
18175
  }
18018
18176
  function resolveSkillDir(pathArg) {
18019
18177
  if (pathArg) {
18020
- const resolved = import_node_path6.resolve(pathArg);
18021
- if (!import_node_fs6.existsSync(resolved) || !import_node_fs6.statSync(resolved).isDirectory()) {
18178
+ const resolved = import_node_path7.resolve(pathArg);
18179
+ if (!import_node_fs7.existsSync(resolved) || !import_node_fs7.statSync(resolved).isDirectory()) {
18022
18180
  fail("Invalid path", `${resolved} is not a directory`);
18023
18181
  }
18024
18182
  return resolved;
18025
18183
  }
18026
- if (import_node_fs6.existsSync(import_node_path6.join(process.cwd(), "SKILL.md"))) {
18184
+ if (import_node_fs7.existsSync(import_node_path7.join(process.cwd(), "SKILL.md"))) {
18027
18185
  return process.cwd();
18028
18186
  }
18029
18187
  fail("No skill directory found", "Run from within a skill directory or pass a path: oathbound push ./my-skill");
@@ -18146,8 +18304,8 @@ ${BRAND} ${TEAL}${showing}${RESET}
18146
18304
  }
18147
18305
 
18148
18306
  // agent-push.ts
18149
- var import_node_fs7 = require("node:fs");
18150
- var import_node_path7 = require("node:path");
18307
+ var import_node_fs8 = require("node:fs");
18308
+ var import_node_path8 = require("node:path");
18151
18309
  var import_yaml3 = __toESM(require_dist(), 1);
18152
18310
  var API_BASE4 = process.env.OATHBOUND_API_URL ?? "https://www.oathbound.ai";
18153
18311
  function parseAgentFrontmatter(content) {
@@ -18160,11 +18318,11 @@ function parseAgentFrontmatter(content) {
18160
18318
  }
18161
18319
  function resolveAgentFile(pathArg) {
18162
18320
  if (pathArg) {
18163
- const resolved = import_node_path7.resolve(pathArg);
18164
- if (!import_node_fs7.existsSync(resolved)) {
18321
+ const resolved = import_node_path8.resolve(pathArg);
18322
+ if (!import_node_fs8.existsSync(resolved)) {
18165
18323
  fail("File not found", resolved);
18166
18324
  }
18167
- if (import_node_fs7.statSync(resolved).isDirectory()) {
18325
+ if (import_node_fs8.statSync(resolved).isDirectory()) {
18168
18326
  return findAgentInDir(resolved);
18169
18327
  }
18170
18328
  if (!resolved.endsWith(".md")) {
@@ -18175,11 +18333,11 @@ function resolveAgentFile(pathArg) {
18175
18333
  return findAgentInDir(process.cwd());
18176
18334
  }
18177
18335
  function findAgentInDir(dir) {
18178
- const mdFiles = import_node_fs7.readdirSync(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => import_node_path7.join(dir, f));
18336
+ const mdFiles = import_node_fs8.readdirSync(dir).filter((f) => f.endsWith(".md") && !f.startsWith(".")).map((f) => import_node_path8.join(dir, f));
18179
18337
  const agents = [];
18180
18338
  for (const file of mdFiles) {
18181
18339
  try {
18182
- const content = import_node_fs7.readFileSync(file, "utf-8");
18340
+ const content = import_node_fs8.readFileSync(file, "utf-8");
18183
18341
  const { meta, body } = parseAgentFrontmatter(content);
18184
18342
  if (meta.name && meta.description && body.trim()) {
18185
18343
  agents.push(file);
@@ -18197,7 +18355,7 @@ function findAgentInDir(dir) {
18197
18355
  async function agentPush(pathArg, options) {
18198
18356
  Wt2(BRAND);
18199
18357
  const agentFile = resolveAgentFile(pathArg);
18200
- const content = import_node_fs7.readFileSync(agentFile, "utf-8");
18358
+ const content = import_node_fs8.readFileSync(agentFile, "utf-8");
18201
18359
  const { meta, body } = parseAgentFrontmatter(content);
18202
18360
  const name = String(meta.name ?? "");
18203
18361
  const description = String(meta.description ?? "");
@@ -18386,7 +18544,7 @@ ${BRAND} ${TEAL}${showing}${RESET}
18386
18544
  }
18387
18545
  }
18388
18546
  // cli.ts
18389
- var VERSION = "0.15.1";
18547
+ var VERSION = "0.16.0";
18390
18548
  var SUPABASE_URL2 = "https://mjnfqagwuewhgwbtrdgs.supabase.co";
18391
18549
  var SUPABASE_ANON_KEY2 = "sb_publishable_T-rk0azNRqAMLLGCyadyhQ_ulk9685n";
18392
18550
  var API_BASE6 = process.env.OATHBOUND_API_URL ?? "https://www.oathbound.ai";
@@ -18407,41 +18565,8 @@ function parseSkillArg(arg) {
18407
18565
  return null;
18408
18566
  return { namespace: arg.slice(0, slash), name, version: vStr };
18409
18567
  }
18410
- function detectPackageManager() {
18411
- if (import_node_fs8.existsSync(import_node_path8.join(process.cwd(), "bun.lockb")) || import_node_fs8.existsSync(import_node_path8.join(process.cwd(), "bun.lock")))
18412
- return "bun";
18413
- if (import_node_fs8.existsSync(import_node_path8.join(process.cwd(), "pnpm-lock.yaml")))
18414
- return "pnpm";
18415
- if (import_node_fs8.existsSync(import_node_path8.join(process.cwd(), "yarn.lock")))
18416
- return "yarn";
18417
- return "npm";
18418
- }
18419
- function installDevDependency() {
18420
- const pkgPath = import_node_path8.join(process.cwd(), "package.json");
18421
- if (!import_node_fs8.existsSync(pkgPath))
18422
- return "no-package-json";
18423
- try {
18424
- const pkg = JSON.parse(import_node_fs8.readFileSync(pkgPath, "utf-8"));
18425
- if (pkg.devDependencies?.oathbound || pkg.dependencies?.oathbound)
18426
- return "skipped";
18427
- } catch {}
18428
- const pm = detectPackageManager();
18429
- const cmds = {
18430
- bun: ["bun", ["add", "--dev", "oathbound"]],
18431
- pnpm: ["pnpm", ["add", "--save-dev", "oathbound"]],
18432
- yarn: ["yarn", ["add", "--dev", "oathbound"]],
18433
- npm: ["npm", ["install", "--save-dev", "oathbound"]]
18434
- };
18435
- const [bin, args] = cmds[pm];
18436
- try {
18437
- import_node_child_process2.execFileSync(bin, args, { stdio: "pipe", cwd: process.cwd() });
18438
- return "installed";
18439
- } catch {
18440
- return "failed";
18441
- }
18442
- }
18443
18568
  function setup() {
18444
- if (!import_node_fs8.existsSync(import_node_path8.join(process.cwd(), ".oathbound.jsonc")))
18569
+ if (!import_node_fs9.existsSync(import_node_path9.join(process.cwd(), ".oathbound.jsonc")))
18445
18570
  return;
18446
18571
  const result = mergeClaudeSettings();
18447
18572
  if (result === "malformed") {
@@ -18450,27 +18575,31 @@ function setup() {
18450
18575
  process.exit(1);
18451
18576
  }
18452
18577
  }
18453
- function addPrepareScript() {
18454
- const pkgPath = import_node_path8.join(process.cwd(), "package.json");
18455
- if (!import_node_fs8.existsSync(pkgPath))
18456
- return "skipped";
18457
- let pkg;
18458
- try {
18459
- pkg = JSON.parse(import_node_fs8.readFileSync(pkgPath, "utf-8"));
18460
- } catch {
18461
- return "skipped";
18462
- }
18463
- const prepare = pkg.scripts?.prepare ?? "";
18464
- if (prepare.includes("oathbound setup"))
18465
- return "skipped";
18466
- const newPrepare = prepare ? `${prepare} && oathbound setup` : "oathbound setup";
18467
- pkg.scripts = { ...pkg.scripts ?? {}, prepare: newPrepare };
18468
- import_node_fs8.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + `
18578
+ function initGlobal() {
18579
+ const home = import_node_os4.homedir();
18580
+ const mergeResult = mergeClaudeSettings(home);
18581
+ switch (mergeResult) {
18582
+ case "created":
18583
+ process.stderr.write(`${GREEN} ✓ Created ~/.claude/settings.json with hooks${RESET}
18469
18584
  `);
18470
- return prepare ? "appended" : "added";
18585
+ break;
18586
+ case "merged":
18587
+ process.stderr.write(`${GREEN} ✓ Added hooks to ~/.claude/settings.json${RESET}
18588
+ `);
18589
+ break;
18590
+ case "skipped":
18591
+ process.stderr.write(`${DIM} ~/.claude/settings.json already has oathbound hooks — skipped${RESET}
18592
+ `);
18593
+ break;
18594
+ case "malformed":
18595
+ process.stderr.write(`${RED} ✗ ~/.claude/settings.json is malformed JSON — skipped${RESET}
18596
+ `);
18597
+ process.stderr.write(`${RED} Please fix the file manually and re-run oathbound init --global${RESET}
18598
+ `);
18599
+ break;
18600
+ }
18471
18601
  }
18472
- async function init() {
18473
- Wt2(BRAND);
18602
+ async function initLocal() {
18474
18603
  const level = "warn";
18475
18604
  let installResult = installDevDependency();
18476
18605
  if (installResult === "no-package-json") {
@@ -18481,8 +18610,8 @@ async function init() {
18481
18610
  Nt("Please run `npx oathbound init` inside of the folder where you want to run Claude Code. Oathbound currently needs an NPM package in order to run.");
18482
18611
  process.exit(1);
18483
18612
  }
18484
- const dirName = import_node_path8.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^[._]+/, "").replace(/-+/g, "-") || "project";
18485
- import_node_fs8.writeFileSync(import_node_path8.join(process.cwd(), "package.json"), JSON.stringify({
18613
+ const dirName = import_node_path9.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/^[._]+/, "").replace(/-+/g, "-") || "project";
18614
+ import_node_fs9.writeFileSync(import_node_path9.join(process.cwd(), "package.json"), JSON.stringify({
18486
18615
  name: dirName,
18487
18616
  private: true,
18488
18617
  scripts: { prepare: "oathbound setup" }
@@ -18544,9 +18673,20 @@ async function init() {
18544
18673
  `);
18545
18674
  break;
18546
18675
  }
18676
+ }
18677
+ async function init(options = {}) {
18678
+ Wt2(BRAND);
18679
+ if (options.global && !options.local) {
18680
+ initGlobal();
18681
+ } else if (options.local && !options.global) {
18682
+ await initLocal();
18683
+ } else {
18684
+ initGlobal();
18685
+ await initLocal();
18686
+ }
18547
18687
  Gt(`\uD83C\uDF89 Oath Bound set up complete!`);
18548
18688
  }
18549
- async function pull(skillArg) {
18689
+ async function pull(skillArg, options = {}) {
18550
18690
  const parsed = parseSkillArg(skillArg);
18551
18691
  if (!parsed)
18552
18692
  usage();
@@ -18574,7 +18714,7 @@ ${BRAND} ${TEAL}↓ Pulling ${fullName}${version3 ? `@${version3}` : ""}...${RES
18574
18714
  fail("Download failed", downloadError?.message ?? "Unknown storage error");
18575
18715
  }
18576
18716
  const buffer = Buffer.from(await blob.arrayBuffer());
18577
- const tarFile = import_node_path8.join(import_node_os4.tmpdir(), `oathbound-${name}-${Date.now()}.tar.gz`);
18717
+ const tarFile = import_node_path9.join(import_node_os4.tmpdir(), `oathbound-${name}-${Date.now()}.tar.gz`);
18578
18718
  const verifySpinner = spinner("Verifying...");
18579
18719
  const hash = import_node_crypto2.createHash("sha256").update(buffer).digest("hex");
18580
18720
  verifySpinner.stop();
@@ -18583,21 +18723,30 @@ ${BRAND} ${TEAL}↓ Pulling ${fullName}${version3 ? `@${version3}` : ""}...${RES
18583
18723
  console.log(`${RED} expected: ${skill.tar_hash}${RESET}`);
18584
18724
  fail("Verification failed", `Downloaded file does not match expected hash for ${fullName}`);
18585
18725
  }
18586
- let skillsDir = findSkillsDir();
18587
- if (!skillsDir.endsWith(".claude/skills") && !skillsDir.includes(".claude/skills")) {
18588
- skillsDir = import_node_path8.join(process.cwd(), ".claude", "skills");
18589
- import_node_fs8.mkdirSync(skillsDir, { recursive: true });
18590
- console.log(`${DIM} Created ${skillsDir}${RESET}`);
18726
+ let skillsDir;
18727
+ if (options.global) {
18728
+ skillsDir = import_node_path9.join(import_node_os4.homedir(), ".claude", "skills");
18729
+ import_node_fs9.mkdirSync(skillsDir, { recursive: true });
18730
+ } else {
18731
+ const dirs = findSkillsDirs();
18732
+ const localEntry = dirs.find((d) => d.source === "local");
18733
+ if (localEntry) {
18734
+ skillsDir = localEntry.path;
18735
+ } else {
18736
+ skillsDir = import_node_path9.join(process.cwd(), ".claude", "skills");
18737
+ import_node_fs9.mkdirSync(skillsDir, { recursive: true });
18738
+ console.log(`${DIM} Created ${skillsDir}${RESET}`);
18739
+ }
18591
18740
  }
18592
- import_node_fs8.writeFileSync(tarFile, buffer);
18741
+ import_node_fs9.writeFileSync(tarFile, buffer);
18593
18742
  try {
18594
- import_node_child_process2.execFileSync("tar", ["-xf", tarFile, "-C", skillsDir], { stdio: "pipe" });
18743
+ import_node_child_process3.execFileSync("tar", ["-xf", tarFile, "-C", skillsDir], { stdio: "pipe" });
18595
18744
  } catch (e) {
18596
- import_node_fs8.unlinkSync(tarFile);
18745
+ import_node_fs9.unlinkSync(tarFile);
18597
18746
  const msg = e instanceof Error ? e.message : "Unknown error";
18598
18747
  fail("Extraction failed", msg);
18599
18748
  }
18600
- import_node_fs8.unlinkSync(tarFile);
18749
+ import_node_fs9.unlinkSync(tarFile);
18601
18750
  try {
18602
18751
  const trackRes = await fetch(`${API_BASE6}/api/downloads`, {
18603
18752
  method: "POST",
@@ -18611,7 +18760,7 @@ ${BRAND} ${TEAL}↓ Pulling ${fullName}${version3 ? `@${version3}` : ""}...${RES
18611
18760
  } catch {}
18612
18761
  console.log(`${BOLD}${GREEN} ✓ Skill verified${RESET}`);
18613
18762
  console.log(`${DIM} ${fullName} v${skill.version}${RESET}`);
18614
- console.log(`${DIM} → ${import_node_path8.join(skillsDir, name)}${RESET}`);
18763
+ console.log(`${DIM} → ${import_node_path9.join(skillsDir, name)}${RESET}`);
18615
18764
  }
18616
18765
  async function agentPull(agentArg) {
18617
18766
  const parsed = parseSkillArg(agentArg);
@@ -18652,9 +18801,9 @@ ${BRAND} ${TEAL}↓ Pulling agent ${fullName}${version3 ? `@${version3}` : ""}..
18652
18801
  if (name.includes("/") || name.includes("\\") || name.includes("..")) {
18653
18802
  fail("Invalid agent name", `Name "${name}" contains path traversal characters`);
18654
18803
  }
18655
- const agentsDir = import_node_path8.join(process.cwd(), ".claude", "agents");
18656
- import_node_fs8.mkdirSync(agentsDir, { recursive: true });
18657
- const targetPath = import_node_path8.join(agentsDir, `${name}.md`);
18804
+ const agentsDir = import_node_path9.join(process.cwd(), ".claude", "agents");
18805
+ import_node_fs9.mkdirSync(agentsDir, { recursive: true });
18806
+ const targetPath = import_node_path9.join(agentsDir, `${name}.md`);
18658
18807
  if (!targetPath.startsWith(agentsDir)) {
18659
18808
  fail("Invalid agent name", `Resolved path escapes agents directory`);
18660
18809
  }
@@ -18682,7 +18831,7 @@ ${YELLOW}${BOLD} ⚠ This agent defines MCP servers (external connections):${RES
18682
18831
  fail("Aborted", "Agent not installed");
18683
18832
  }
18684
18833
  }
18685
- import_node_fs8.writeFileSync(targetPath, content);
18834
+ import_node_fs9.writeFileSync(targetPath, content);
18686
18835
  try {
18687
18836
  const trackRes = await fetch(`${API_BASE6}/api/downloads`, {
18688
18837
  method: "POST",
@@ -18737,7 +18886,11 @@ if (require.main == module) {
18737
18886
  }
18738
18887
  }
18739
18888
  if (subcommand === "init") {
18740
- await init().catch((err) => {
18889
+ const initArgs = args.slice(1);
18890
+ await init({
18891
+ global: initArgs.includes("--global"),
18892
+ local: initArgs.includes("--local")
18893
+ }).catch((err) => {
18741
18894
  const msg = err instanceof Error ? err.message : "Unknown error";
18742
18895
  fail("Init failed", msg);
18743
18896
  });
@@ -18788,11 +18941,13 @@ if (require.main == module) {
18788
18941
  });
18789
18942
  } else {
18790
18943
  const PULL_ALIASES = new Set(["pull", "i", "install"]);
18791
- const skillArg = args[1];
18944
+ const pullArgs = args.slice(1);
18945
+ const isGlobalPull = pullArgs.includes("--global");
18946
+ const skillArg = pullArgs.find((a) => !a.startsWith("--"));
18792
18947
  if (!subcommand || !PULL_ALIASES.has(subcommand) || !skillArg) {
18793
18948
  usage();
18794
18949
  }
18795
- await pull(skillArg).catch((err) => {
18950
+ await pull(skillArg, { global: isGlobalPull }).catch((err) => {
18796
18951
  const msg = err instanceof Error ? err.message : "Unknown error";
18797
18952
  fail("Unexpected error", msg);
18798
18953
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oathbound",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "Install verified Claude Code skills and agents from the Oath Bound registry",
5
5
  "license": "MIT",
6
6
  "author": "Josh Anderson",