oathbound 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/agent-search.ts CHANGED
@@ -6,6 +6,7 @@ export interface AgentSearchOptions {
6
6
  query?: string;
7
7
  namespace?: string;
8
8
  sparse?: boolean;
9
+ sort?: 'downloads';
9
10
  limit?: number;
10
11
  offset?: number;
11
12
  }
@@ -21,6 +22,9 @@ export function parseAgentSearchArgs(args: string[]): AgentSearchOptions {
21
22
  opts.namespace = args[++i];
22
23
  } else if (arg === '--sparse' || arg === '-s') {
23
24
  opts.sparse = true;
25
+ } else if (arg === '--sort') {
26
+ const val = args[++i];
27
+ if (val === 'downloads') opts.sort = 'downloads';
24
28
  } else if (arg === '--limit') {
25
29
  opts.limit = parseInt(args[++i], 10);
26
30
  } else if (arg === '--offset') {
@@ -53,6 +57,7 @@ interface AgentResult {
53
57
  permission_mode?: string | null;
54
58
  effort?: string | null;
55
59
  author?: AgentAuthor;
60
+ download_count?: number;
56
61
  }
57
62
 
58
63
  interface AgentSearchResponse {
@@ -69,6 +74,7 @@ export async function agentSearch(opts: AgentSearchOptions): Promise<void> {
69
74
  if (opts.query) params.set('q', opts.query);
70
75
  if (opts.namespace) params.set('namespace', opts.namespace);
71
76
  if (opts.sparse) params.set('sparse', 'true');
77
+ if (opts.sort) params.set('sort', opts.sort);
72
78
  if (opts.limit != null) params.set('limit', String(opts.limit));
73
79
  if (opts.offset != null) params.set('offset', String(opts.offset));
74
80
 
@@ -135,6 +141,7 @@ export async function agentSearch(opts: AgentSearchOptions): Promise<void> {
135
141
  parts.push(`by ${name}${agent.author.verified ? ' ✓' : ''}`);
136
142
  }
137
143
  if (agent.license) parts.push(agent.license);
144
+ if (agent.download_count != null) parts.push(`↓ ${agent.download_count}`);
138
145
  if (agent.model) parts.push(`model: ${agent.model}`);
139
146
  if (agent.permission_mode) parts.push(`mode: ${agent.permission_mode}`);
140
147
  if (agent.effort) parts.push(`effort: ${agent.effort}`);
package/cli.ts CHANGED
@@ -29,14 +29,16 @@ export { stripJsoncComments, writeOathboundConfig, mergeClaudeSettings, type Mer
29
29
  export { isNewer } from './update';
30
30
  export { installDevDependency, type InstallResult, setup, addPrepareScript, type PrepareResult, addTrustedDependency, type TrustedDepResult };
31
31
 
32
- const VERSION = '0.13.1';
32
+ const VERSION = '0.14.0';
33
33
 
34
34
  // --- Supabase ---
35
35
  const SUPABASE_URL = 'https://mjnfqagwuewhgwbtrdgs.supabase.co';
36
36
  const SUPABASE_ANON_KEY = 'sb_publishable_T-rk0azNRqAMLLGCyadyhQ_ulk9685n';
37
+ const API_BASE = process.env.OATHBOUND_API_URL ?? 'https://www.oathbound.ai';
37
38
 
38
39
  // --- Types ---
39
40
  interface SkillRow {
41
+ id: string;
40
42
  name: string;
41
43
  namespace: string;
42
44
  version: string;
@@ -279,7 +281,7 @@ async function pull(skillArg: string): Promise<void> {
279
281
  if (version !== null) {
280
282
  const { data, error } = await supabase
281
283
  .from('skills')
282
- .select('name, namespace, version, tar_hash, storage_path')
284
+ .select('id, name, namespace, version, tar_hash, storage_path')
283
285
  .eq('namespace', namespace)
284
286
  .eq('name', name)
285
287
  .eq('version', version)
@@ -293,7 +295,7 @@ async function pull(skillArg: string): Promise<void> {
293
295
  // Fetch all versions, pick highest via semver comparison
294
296
  const { data, error } = await supabase
295
297
  .from('skills')
296
- .select('name, namespace, version, tar_hash, storage_path')
298
+ .select('id, name, namespace, version, tar_hash, storage_path')
297
299
  .eq('namespace', namespace)
298
300
  .eq('name', name);
299
301
 
@@ -346,7 +348,21 @@ async function pull(skillArg: string): Promise<void> {
346
348
  }
347
349
  unlinkSync(tarFile);
348
350
 
349
- // 5. Success
351
+ // 5. Record download (non-fatal)
352
+ try {
353
+ const trackRes = await fetch(`${API_BASE}/api/downloads`, {
354
+ method: 'POST',
355
+ headers: { 'Content-Type': 'application/json' },
356
+ body: JSON.stringify({ skill_id: skill.id, version: skill.version }),
357
+ });
358
+ if (!trackRes.ok) {
359
+ process.stderr.write(`${DIM} [warn] download tracking failed (${trackRes.status})${RESET}\n`);
360
+ }
361
+ } catch {
362
+ // Network error — non-fatal
363
+ }
364
+
365
+ // 6. Success
350
366
  console.log(`${BOLD}${GREEN} ✓ Skill verified${RESET}`);
351
367
  console.log(`${DIM} ${fullName} v${skill.version}${RESET}`);
352
368
  console.log(`${DIM} → ${join(skillsDir, name)}${RESET}`);
@@ -354,6 +370,7 @@ async function pull(skillArg: string): Promise<void> {
354
370
 
355
371
  // --- Agent types ---
356
372
  interface AgentRow {
373
+ id: string;
357
374
  name: string;
358
375
  namespace: string;
359
376
  version: string;
@@ -379,7 +396,7 @@ async function agentPull(agentArg: string): Promise<void> {
379
396
  if (version !== null) {
380
397
  const { data, error } = await supabase
381
398
  .from('agents')
382
- .select('name, namespace, version, content_hash, storage_path, config')
399
+ .select('id, name, namespace, version, content_hash, storage_path, config')
383
400
  .eq('namespace', namespace)
384
401
  .eq('name', name)
385
402
  .eq('version', version)
@@ -392,7 +409,7 @@ async function agentPull(agentArg: string): Promise<void> {
392
409
  } else {
393
410
  const { data, error } = await supabase
394
411
  .from('agents')
395
- .select('name, namespace, version, content_hash, storage_path, config')
412
+ .select('id, name, namespace, version, content_hash, storage_path, config')
396
413
  .eq('namespace', namespace)
397
414
  .eq('name', name);
398
415
 
@@ -466,6 +483,20 @@ async function agentPull(agentArg: string): Promise<void> {
466
483
  // Write agent file
467
484
  writeFileSync(targetPath, content);
468
485
 
486
+ // Record download (non-fatal)
487
+ try {
488
+ const trackRes = await fetch(`${API_BASE}/api/downloads`, {
489
+ method: 'POST',
490
+ headers: { 'Content-Type': 'application/json' },
491
+ body: JSON.stringify({ agent_id: agent.id, version: agent.version }),
492
+ });
493
+ if (!trackRes.ok) {
494
+ process.stderr.write(`${DIM} [warn] download tracking failed (${trackRes.status})${RESET}\n`);
495
+ }
496
+ } catch {
497
+ // Network error — non-fatal
498
+ }
499
+
469
500
  console.log(`${BOLD}${GREEN} ✓ Agent verified${RESET}`);
470
501
  console.log(`${DIM} ${fullName} v${agent.version}${RESET}`);
471
502
  console.log(`${DIM} → ${targetPath}${RESET}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oathbound",
3
- "version": "0.13.1",
3
+ "version": "0.14.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",
package/search.ts CHANGED
@@ -6,6 +6,7 @@ export interface SearchOptions {
6
6
  query?: string;
7
7
  namespace?: string;
8
8
  sparse?: boolean;
9
+ sort?: 'downloads';
9
10
  limit?: number;
10
11
  offset?: number;
11
12
  }
@@ -21,6 +22,9 @@ export function parseSearchArgs(args: string[]): SearchOptions {
21
22
  opts.namespace = args[++i];
22
23
  } else if (arg === '--sparse' || arg === '-s') {
23
24
  opts.sparse = true;
25
+ } else if (arg === '--sort') {
26
+ const val = args[++i];
27
+ if (val === 'downloads') opts.sort = 'downloads';
24
28
  } else if (arg === '--limit') {
25
29
  opts.limit = parseInt(args[++i], 10);
26
30
  } else if (arg === '--offset') {
@@ -50,6 +54,7 @@ interface SkillResult {
50
54
  visibility?: string;
51
55
  author?: SkillAuthor;
52
56
  audit_status?: 'passed' | 'failed' | 'none';
57
+ download_count?: number;
53
58
  }
54
59
 
55
60
  interface SearchResponse {
@@ -66,6 +71,7 @@ export async function search(opts: SearchOptions): Promise<void> {
66
71
  if (opts.query) params.set('q', opts.query);
67
72
  if (opts.namespace) params.set('namespace', opts.namespace);
68
73
  if (opts.sparse) params.set('sparse', 'true');
74
+ if (opts.sort) params.set('sort', opts.sort);
69
75
  if (opts.limit != null) params.set('limit', String(opts.limit));
70
76
  if (opts.offset != null) params.set('offset', String(opts.offset));
71
77
 
@@ -132,6 +138,7 @@ export async function search(opts: SearchOptions): Promise<void> {
132
138
  parts.push(`by ${name}${skill.author.verified ? ' ✓' : ''}`);
133
139
  }
134
140
  if (skill.license) parts.push(skill.license);
141
+ if (skill.download_count != null) parts.push(`↓ ${skill.download_count}`);
135
142
  if (skill.audit_status && skill.audit_status !== 'none') {
136
143
  parts.push(skill.audit_status === 'passed' ? `${GREEN}audited${RESET}` : 'audit failed');
137
144
  }