oathbound 0.1.1 → 0.1.2

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 +1 -1
  2. package/cli.ts +43 -17
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -80,4 +80,4 @@ The content hash algorithm is deterministic: files are sorted lexicographically
80
80
 
81
81
  ## License
82
82
 
83
- Apache 2.0
83
+ Business Source License 1.1 (BUSL-1.1). The Change Date is 2028-03-04, after which the code is available under Apache 2.0.
package/cli.ts CHANGED
@@ -7,19 +7,20 @@ import { writeFileSync, readFileSync, unlinkSync, existsSync, readdirSync, statS
7
7
  import { join, relative } from 'node:path';
8
8
  import { tmpdir } from 'node:os';
9
9
 
10
- const VERSION = '0.1.1';
10
+ const VERSION = '0.1.2';
11
11
 
12
12
  // --- Supabase ---
13
13
  const SUPABASE_URL = 'https://mjnfqagwuewhgwbtrdgs.supabase.co';
14
14
  const SUPABASE_ANON_KEY = 'sb_publishable_T-rk0azNRqAMLLGCyadyhQ_ulk9685n';
15
15
 
16
- // --- ANSI ---
17
- const TEAL = '\x1b[38;2;63;168;164m'; // brand teal #3fa8a4
18
- const GREEN = '\x1b[32m';
19
- const RED = '\x1b[31m';
20
- const DIM = '\x1b[2m';
21
- const BOLD = '\x1b[1m';
22
- const RESET = '\x1b[0m';
16
+ // --- ANSI (respect NO_COLOR standard: https://no-color.org) ---
17
+ const USE_COLOR = process.env.NO_COLOR === undefined && process.stderr.isTTY;
18
+ const TEAL = USE_COLOR ? '\x1b[38;2;63;168;164m' : ''; // brand teal #3fa8a4
19
+ const GREEN = USE_COLOR ? '\x1b[32m' : '';
20
+ const RED = USE_COLOR ? '\x1b[31m' : '';
21
+ const DIM = USE_COLOR ? '\x1b[2m' : '';
22
+ const BOLD = USE_COLOR ? '\x1b[1m' : '';
23
+ const RESET = USE_COLOR ? '\x1b[0m' : '';
23
24
 
24
25
  // --- Types ---
25
26
  interface SkillRow {
@@ -67,7 +68,7 @@ function spinner(text: string): { stop: () => void } {
67
68
  return {
68
69
  stop() {
69
70
  clearInterval(interval);
70
- process.stdout.write('\r\x1b[2K');
71
+ process.stdout.write(USE_COLOR ? '\r\x1b[2K' : '\n');
71
72
  },
72
73
  };
73
74
  }
@@ -180,8 +181,14 @@ async function readStdin(): Promise<string> {
180
181
 
181
182
  // --- Verify (SessionStart hook) ---
182
183
  async function verify(): Promise<void> {
183
- const input = JSON.parse(await readStdin());
184
- const sessionId: string = input.session_id;
184
+ let input: Record<string, unknown>;
185
+ try {
186
+ input = JSON.parse(await readStdin());
187
+ } catch {
188
+ process.stderr.write('oathbound verify: invalid JSON on stdin\n');
189
+ process.exit(1);
190
+ }
191
+ const sessionId: string = input.session_id as string;
185
192
  if (!sessionId) {
186
193
  process.stderr.write('oathbound verify: no session_id in stdin\n');
187
194
  process.exit(1);
@@ -276,9 +283,15 @@ async function verify(): Promise<void> {
276
283
 
277
284
  // --- Verify --check (PreToolUse hook) ---
278
285
  async function verifyCheck(): Promise<void> {
279
- const input = JSON.parse(await readStdin());
280
- const sessionId: string = input.session_id;
281
- const skillName: string | undefined = input.tool_input?.skill;
286
+ let input: Record<string, unknown>;
287
+ try {
288
+ input = JSON.parse(await readStdin());
289
+ } catch {
290
+ process.stderr.write('oathbound verify --check: invalid JSON on stdin\n');
291
+ process.exit(1);
292
+ }
293
+ const sessionId: string = input.session_id as string;
294
+ const skillName: string | undefined = (input.tool_input as Record<string, unknown> | undefined)?.skill as string | undefined;
282
295
 
283
296
  if (!sessionId || !skillName) {
284
297
  // Can't verify — allow through (non-skill invocation or missing context)
@@ -291,7 +304,13 @@ async function verifyCheck(): Promise<void> {
291
304
  process.exit(0);
292
305
  }
293
306
 
294
- const state: SessionState = JSON.parse(readFileSync(stateFile, 'utf-8'));
307
+ let state: SessionState;
308
+ try {
309
+ state = JSON.parse(readFileSync(stateFile, 'utf-8'));
310
+ } catch {
311
+ process.stderr.write('oathbound verify --check: corrupt session state file\n');
312
+ process.exit(1);
313
+ }
295
314
 
296
315
  // Extract just the skill name (strip namespace/ prefix if present)
297
316
  const baseName = skillName.includes(':') ? skillName.split(':').pop()! : skillName;
@@ -380,7 +399,7 @@ async function pull(skillArg: string): Promise<void> {
380
399
  }
381
400
 
382
401
  const buffer = Buffer.from(await blob.arrayBuffer());
383
- const tarFile = `${name}.tar.gz`;
402
+ const tarFile = join(tmpdir(), `oathbound-${name}-${Date.now()}.tar.gz`);
384
403
 
385
404
  // 3. Hash and verify
386
405
  const verify = spinner('Verifying...');
@@ -395,7 +414,14 @@ async function pull(skillArg: string): Promise<void> {
395
414
  }
396
415
 
397
416
  // 4. Find target directory and extract
398
- const skillsDir = findSkillsDir();
417
+ let skillsDir = findSkillsDir();
418
+ if (!skillsDir.endsWith('.claude/skills') && !skillsDir.includes('.claude/skills')) {
419
+ // findSkillsDir() fell back to cwd — create .claude/skills instead of extracting into project root
420
+ skillsDir = join(process.cwd(), '.claude', 'skills');
421
+ const { mkdirSync } = await import('node:fs');
422
+ mkdirSync(skillsDir, { recursive: true });
423
+ console.log(`${DIM} Created ${skillsDir}${RESET}`);
424
+ }
399
425
  writeFileSync(tarFile, buffer);
400
426
  try {
401
427
  execFileSync('tar', ['-xf', tarFile, '-C', skillsDir], { stdio: 'pipe' });
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "oathbound",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Install verified Claude Code skills from the Oath Bound registry",
5
- "license": "Apache-2.0",
5
+ "license": "BUSL-1.1",
6
6
  "author": "Josh Anderson",
7
7
  "homepage": "https://oathbound.ai",
8
8
  "repository": {