@caphub/cli 0.1.0 → 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 (2) hide show
  1. package/bin/caphub.js +212 -16
  2. package/package.json +1 -1
package/bin/caphub.js CHANGED
@@ -13,6 +13,8 @@ const CONFIG_PATH = resolve(CONFIG_DIR, "config.json");
13
13
 
14
14
  const ROOT_HELP = `caphub
15
15
 
16
+ Caphub is hosted infrastructure for agent-ready capabilities such as search and query expansion.
17
+
16
18
  purpose: root CLI for Caphub agent capabilities
17
19
  auth: CAPHUB_API_KEY env or ${CONFIG_PATH}
18
20
  api: ${DEFAULT_API_URL}
@@ -21,18 +23,43 @@ commands:
21
23
  help explain the platform and command layout
22
24
  help <capability> show capability-specific help from the API
23
25
  capabilities list live capabilities with short descriptions
24
- auth login save api key locally for future runs
26
+ auth show current login state
27
+ auth login website login flow; stores api key locally after approval
25
28
  auth whoami verify the current api key against the API
26
29
  auth logout remove stored api key from local config
27
30
  <capability> <json> run a capability directly, e.g. search or search-ideas
28
31
 
32
+ agent workflow:
33
+ 1. caphub capabilities
34
+ 2. caphub help <capability>
35
+ 3. caphub auth login
36
+ 4. caphub <capability> '<json>'
37
+
38
+ recovery:
39
+ no api key caphub auth login
40
+ invalid api key generate a new key in https://caphub.io/dashboard/
41
+ insufficient credits top up in https://caphub.io/dashboard/
42
+ bad json caphub help <capability>
43
+ capability unknown caphub capabilities
44
+
29
45
  examples:
46
+ caphub auth
30
47
  caphub capabilities
48
+ caphub auth login
31
49
  caphub auth login --api-key csk_live_...
32
50
  caphub help search
33
51
  caphub search '{"queries":["site:github.com awesome ai agents"]}'
34
52
  `;
35
53
 
54
+ class ApiError extends Error {
55
+ constructor(message, status = 0, data = null) {
56
+ super(message);
57
+ this.name = "ApiError";
58
+ this.status = status;
59
+ this.data = data;
60
+ }
61
+ }
62
+
36
63
  function readConfig() {
37
64
  try {
38
65
  return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
@@ -82,22 +109,27 @@ async function fetchJson(url, { method = "GET", body, apiKey = "" } = {}) {
82
109
  const headers = { "Content-Type": "application/json" };
83
110
  if (apiKey) headers["X-API-Key"] = apiKey;
84
111
 
85
- const resp = await fetch(url, {
86
- method,
87
- headers,
88
- ...(body ? { body: JSON.stringify(body) } : {}),
89
- });
112
+ let resp;
113
+ try {
114
+ resp = await fetch(url, {
115
+ method,
116
+ headers,
117
+ ...(body ? { body: JSON.stringify(body) } : {}),
118
+ });
119
+ } catch (error) {
120
+ throw new ApiError(`request failed: ${error.message}`, 0, null);
121
+ }
90
122
 
91
123
  const text = await resp.text();
92
124
  let data;
93
125
  try {
94
126
  data = text ? JSON.parse(text) : {};
95
127
  } catch {
96
- throw new Error(`non-JSON response from ${url} (HTTP ${resp.status})`);
128
+ throw new ApiError(`non-JSON response from ${url} (HTTP ${resp.status})`, resp.status, null);
97
129
  }
98
130
 
99
131
  if (!resp.ok) {
100
- throw new Error(data?.error || `HTTP ${resp.status}`);
132
+ throw new ApiError(data?.error || `HTTP ${resp.status}`, resp.status, data);
101
133
  }
102
134
 
103
135
  return data;
@@ -109,8 +141,71 @@ function parseFlag(args, name) {
109
141
  return args[idx + 1] || "";
110
142
  }
111
143
 
144
+ function buildRecoveryHints(error, context = {}) {
145
+ const hints = [];
146
+ const capability = context.capability || "<capability>";
147
+ const message = String(error?.message || "").toLowerCase();
148
+ const status = Number(error?.status || 0);
149
+
150
+ if (status === 401 || message.includes("missing x-api-key header")) {
151
+ hints.push("login: caphub auth login");
152
+ }
153
+
154
+ if (status === 403 || message.includes("invalid api key")) {
155
+ hints.push("generate a new key in https://caphub.io/dashboard/");
156
+ hints.push("login again: caphub auth login --api-key csk_live_...");
157
+ }
158
+
159
+ if (status === 402 || message.includes("insufficient credits")) {
160
+ hints.push("top up credits in https://caphub.io/dashboard/");
161
+ hints.push("check account state: caphub auth whoami");
162
+ }
163
+
164
+ if (
165
+ status === 400 ||
166
+ message.includes("queries array is required") ||
167
+ message.includes("unsupported function") ||
168
+ message.includes("input must be valid json") ||
169
+ message.includes("input json is required")
170
+ ) {
171
+ hints.push(`capability contract: caphub help ${capability}`);
172
+ }
173
+
174
+ if (message.includes("request failed:")) {
175
+ hints.push(`api url: ${getApiUrl()}`);
176
+ hints.push(`health: curl -sS ${getApiUrl()}/health`);
177
+ }
178
+
179
+ if (!hints.length) {
180
+ hints.push("discover capabilities: caphub capabilities");
181
+ hints.push(`capability contract: caphub help ${capability}`);
182
+ }
183
+
184
+ return hints;
185
+ }
186
+
187
+ function failWithHints(message, error, context = {}) {
188
+ const lines = [`Error: ${message}`];
189
+ const hints = buildRecoveryHints(error, context);
190
+ if (hints.length) {
191
+ lines.push("", "next:");
192
+ for (const hint of hints) lines.push(` - ${hint}`);
193
+ }
194
+ fail(lines.join("\n"));
195
+ }
196
+
197
+ function sleep(ms) {
198
+ return new Promise((resolveWait) => setTimeout(resolveWait, ms));
199
+ }
200
+
112
201
  function printCapabilities(payload) {
113
- const lines = ["caphub capabilities", ""];
202
+ const lines = [
203
+ "caphub capabilities",
204
+ "",
205
+ "Live capabilities available through the current API.",
206
+ "Use 'caphub help <capability>' before first use.",
207
+ "",
208
+ ];
114
209
  for (const capability of payload.capabilities || []) {
115
210
  lines.push(`/${capability.capability} - ${capability.purpose}`);
116
211
  if (capability.credits) lines.push(` credits: ${capability.credits}`);
@@ -137,6 +232,15 @@ function printCapabilityHelp(payload) {
137
232
  "",
138
233
  "output:",
139
234
  JSON.stringify(payload.output_contract, null, 2),
235
+ payload.notes_for_agents?.length ? "" : null,
236
+ payload.notes_for_agents?.length ? "notes:" : null,
237
+ ...(payload.notes_for_agents || []).map((note) => `- ${note}`),
238
+ "",
239
+ "common recovery:",
240
+ "- auth missing: caphub auth login",
241
+ "- auth invalid: generate a new key in https://caphub.io/dashboard/",
242
+ "- low credits: top up in https://caphub.io/dashboard/",
243
+ `- bad payload: caphub help ${payload.capability}`,
140
244
  ].filter(Boolean);
141
245
  process.stdout.write(`${lines.join("\n")}\n`);
142
246
  }
@@ -167,10 +271,92 @@ async function commandAuth(args) {
167
271
  const sub = args[0];
168
272
  const config = readConfig();
169
273
 
274
+ if (!sub) {
275
+ const apiKey = getApiKey();
276
+ if (!apiKey) {
277
+ process.stdout.write([
278
+ "caphub auth",
279
+ "",
280
+ "status: not logged in",
281
+ "",
282
+ "next:",
283
+ " - caphub auth login",
284
+ " - or for headless/cloud agents: caphub auth login --api-key csk_live_...",
285
+ "",
286
+ ].join("\n"));
287
+ return;
288
+ }
289
+
290
+ try {
291
+ const payload = await fetchJson(`${getApiUrl()}/v1/me`, { apiKey });
292
+ process.stdout.write([
293
+ "caphub auth",
294
+ "",
295
+ "status: logged in",
296
+ `email: ${payload.user.email}`,
297
+ `user_id: ${payload.user.id}`,
298
+ `credits_remaining: ${payload.total_usage.credits_remaining}`,
299
+ `total_credits_used: ${payload.total_usage.total_credits_used}`,
300
+ "",
301
+ "next:",
302
+ " - caphub capabilities",
303
+ " - caphub help search",
304
+ "",
305
+ ].join("\n"));
306
+ return;
307
+ } catch (error) {
308
+ failWithHints("stored credentials are not valid", error, { capability: "search" });
309
+ }
310
+ }
311
+
170
312
  if (sub === "login") {
171
- const apiKey = parseFlag(args, "--api-key") || getApiKey();
313
+ const explicitApiKey = parseFlag(args, "--api-key");
314
+ if (!explicitApiKey && !args.includes("--api-key")) {
315
+ const apiUrl = getApiUrl();
316
+ const started = await fetchJson(`${apiUrl}/v1/auth/cli/start`, { method: "POST", body: {} });
317
+ process.stdout.write([
318
+ "caphub auth login",
319
+ "",
320
+ "Open this URL in your browser and finish sign-in:",
321
+ started.approval_url,
322
+ "",
323
+ `code: ${started.code}`,
324
+ `expires_in_seconds: ${started.expires_in_seconds}`,
325
+ "",
326
+ "Waiting for website approval...",
327
+ "",
328
+ ].join("\n"));
329
+
330
+ const deadline = Date.now() + Number(started.expires_in_seconds || 600) * 1000;
331
+ const intervalMs = Number(started.poll_interval_seconds || 2) * 1000;
332
+ while (Date.now() < deadline) {
333
+ const polled = await fetchJson(`${apiUrl}/v1/auth/cli/poll`, {
334
+ method: "POST",
335
+ body: {
336
+ session_id: started.session_id,
337
+ poll_token: started.poll_token,
338
+ },
339
+ });
340
+
341
+ if (polled.status === "approved" && polled.api_key) {
342
+ writeConfig({
343
+ ...config,
344
+ api_key: polled.api_key,
345
+ api_url: apiUrl,
346
+ });
347
+ process.stdout.write(`${JSON.stringify({ ok: true, config_path: CONFIG_PATH, api_url: apiUrl }, null, 2)}\n`);
348
+ return;
349
+ }
350
+
351
+ await sleep(intervalMs);
352
+ }
353
+
354
+ fail("Error: login approval timed out.\n\nnext:\n - rerun: caphub auth login\n - or open https://caphub.io/dashboard/");
355
+ }
356
+
357
+ const apiKey = explicitApiKey || getApiKey();
172
358
  const apiUrl = parseFlag(args, "--api-url") || getApiUrl();
173
- if (!apiKey) fail("Error: auth login requires --api-key or CAPHUB_API_KEY.");
359
+ if (!apiKey) fail("Error: auth login requires --api-key or CAPHUB_API_KEY.\n\nnext:\n - caphub auth login --api-key csk_live_...");
174
360
  writeConfig({
175
361
  ...config,
176
362
  api_key: apiKey,
@@ -182,7 +368,7 @@ async function commandAuth(args) {
182
368
 
183
369
  if (sub === "whoami") {
184
370
  const apiKey = getApiKey();
185
- if (!apiKey) fail(`Error: no api key configured. Use 'caphub auth login --api-key ...' or set ${CONFIG_PATH}.`);
371
+ if (!apiKey) fail(`Error: no api key configured.\n\nnext:\n - caphub auth login --api-key csk_live_...\n - or set api_key in ${CONFIG_PATH}`);
186
372
  const payload = await fetchJson(`${getApiUrl()}/v1/me`, { apiKey });
187
373
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
188
374
  return;
@@ -201,7 +387,11 @@ async function commandAuth(args) {
201
387
 
202
388
  async function commandCapability(capability, args) {
203
389
  const apiKey = getApiKey();
204
- if (!apiKey) fail(`Error: CAPHUB_API_KEY is required, or set api_key in ${CONFIG_PATH}.`);
390
+ if (!apiKey) {
391
+ fail(
392
+ `Error: no api key configured for capability "${capability}".\n\nnext:\n - caphub auth login --api-key csk_live_...\n - then retry: caphub ${capability} '<json>'\n - contract: caphub help ${capability}`
393
+ );
394
+ }
205
395
 
206
396
  if (args[0] === "--help" || args[0] === "help") {
207
397
  await commandHelp([capability]);
@@ -211,14 +401,14 @@ async function commandCapability(capability, args) {
211
401
  const arg = args[0];
212
402
  const rawInput = arg ?? (process.stdin.isTTY ? "" : await readStdin());
213
403
  if (!rawInput.trim()) {
214
- fail(`Error: input JSON is required. Run 'caphub help ${capability}' for the contract.`);
404
+ fail(`Error: input JSON is required.\n\nnext:\n - caphub help ${capability}\n - then run: caphub ${capability} '<json>'`);
215
405
  }
216
406
 
217
407
  let body;
218
408
  try {
219
409
  body = JSON.parse(rawInput);
220
410
  } catch {
221
- fail("Error: input must be valid JSON.");
411
+ fail(`Error: input must be valid JSON.\n\nnext:\n - caphub help ${capability}\n - pass exactly one JSON object`);
222
412
  }
223
413
 
224
414
  if (!("function" in body)) body.function = capability;
@@ -258,4 +448,10 @@ async function main() {
258
448
  await commandCapability(cmd, args.slice(1));
259
449
  }
260
450
 
261
- await main().catch((error) => fail(`Error: ${error.message}`));
451
+ await main().catch((error) => {
452
+ const cmd = process.argv[2];
453
+ const capability = cmd && !["help", "--help", "-h", "capabilities", "auth", "--version", "-v", "version"].includes(cmd)
454
+ ? cmd
455
+ : undefined;
456
+ failWithHints(error.message || "unknown error", error, { capability });
457
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caphub/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Root CLI for Caphub agent capabilities",
5
5
  "type": "module",
6
6
  "bin": {