@caphub/cli 0.1.0 → 0.1.1

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 +121 -14
  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}
@@ -26,6 +28,19 @@ commands:
26
28
  auth logout remove stored api key from local config
27
29
  <capability> <json> run a capability directly, e.g. search or search-ideas
28
30
 
31
+ agent workflow:
32
+ 1. caphub capabilities
33
+ 2. caphub help <capability>
34
+ 3. caphub auth login --api-key csk_live_...
35
+ 4. caphub <capability> '<json>'
36
+
37
+ recovery:
38
+ no api key caphub auth login --api-key csk_live_...
39
+ invalid api key generate a new key in https://caphub.io/dashboard/
40
+ insufficient credits top up in https://caphub.io/dashboard/
41
+ bad json caphub help <capability>
42
+ capability unknown caphub capabilities
43
+
29
44
  examples:
30
45
  caphub capabilities
31
46
  caphub auth login --api-key csk_live_...
@@ -33,6 +48,15 @@ examples:
33
48
  caphub search '{"queries":["site:github.com awesome ai agents"]}'
34
49
  `;
35
50
 
51
+ class ApiError extends Error {
52
+ constructor(message, status = 0, data = null) {
53
+ super(message);
54
+ this.name = "ApiError";
55
+ this.status = status;
56
+ this.data = data;
57
+ }
58
+ }
59
+
36
60
  function readConfig() {
37
61
  try {
38
62
  return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
@@ -82,22 +106,27 @@ async function fetchJson(url, { method = "GET", body, apiKey = "" } = {}) {
82
106
  const headers = { "Content-Type": "application/json" };
83
107
  if (apiKey) headers["X-API-Key"] = apiKey;
84
108
 
85
- const resp = await fetch(url, {
86
- method,
87
- headers,
88
- ...(body ? { body: JSON.stringify(body) } : {}),
89
- });
109
+ let resp;
110
+ try {
111
+ resp = await fetch(url, {
112
+ method,
113
+ headers,
114
+ ...(body ? { body: JSON.stringify(body) } : {}),
115
+ });
116
+ } catch (error) {
117
+ throw new ApiError(`request failed: ${error.message}`, 0, null);
118
+ }
90
119
 
91
120
  const text = await resp.text();
92
121
  let data;
93
122
  try {
94
123
  data = text ? JSON.parse(text) : {};
95
124
  } catch {
96
- throw new Error(`non-JSON response from ${url} (HTTP ${resp.status})`);
125
+ throw new ApiError(`non-JSON response from ${url} (HTTP ${resp.status})`, resp.status, null);
97
126
  }
98
127
 
99
128
  if (!resp.ok) {
100
- throw new Error(data?.error || `HTTP ${resp.status}`);
129
+ throw new ApiError(data?.error || `HTTP ${resp.status}`, resp.status, data);
101
130
  }
102
131
 
103
132
  return data;
@@ -109,8 +138,67 @@ function parseFlag(args, name) {
109
138
  return args[idx + 1] || "";
110
139
  }
111
140
 
141
+ function buildRecoveryHints(error, context = {}) {
142
+ const hints = [];
143
+ const capability = context.capability || "<capability>";
144
+ const message = String(error?.message || "").toLowerCase();
145
+ const status = Number(error?.status || 0);
146
+
147
+ if (status === 401 || message.includes("missing x-api-key header")) {
148
+ hints.push("login: caphub auth login --api-key csk_live_...");
149
+ }
150
+
151
+ if (status === 403 || message.includes("invalid api key")) {
152
+ hints.push("generate a new key in https://caphub.io/dashboard/");
153
+ hints.push("login again: caphub auth login --api-key csk_live_...");
154
+ }
155
+
156
+ if (status === 402 || message.includes("insufficient credits")) {
157
+ hints.push("top up credits in https://caphub.io/dashboard/");
158
+ hints.push("check account state: caphub auth whoami");
159
+ }
160
+
161
+ if (
162
+ status === 400 ||
163
+ message.includes("queries array is required") ||
164
+ message.includes("unsupported function") ||
165
+ message.includes("input must be valid json") ||
166
+ message.includes("input json is required")
167
+ ) {
168
+ hints.push(`capability contract: caphub help ${capability}`);
169
+ }
170
+
171
+ if (message.includes("request failed:")) {
172
+ hints.push(`api url: ${getApiUrl()}`);
173
+ hints.push(`health: curl -sS ${getApiUrl()}/health`);
174
+ }
175
+
176
+ if (!hints.length) {
177
+ hints.push("discover capabilities: caphub capabilities");
178
+ hints.push(`capability contract: caphub help ${capability}`);
179
+ }
180
+
181
+ return hints;
182
+ }
183
+
184
+ function failWithHints(message, error, context = {}) {
185
+ const lines = [`Error: ${message}`];
186
+ const hints = buildRecoveryHints(error, context);
187
+ if (hints.length) {
188
+ lines.push("", "next:");
189
+ for (const hint of hints) lines.push(` - ${hint}`);
190
+ }
191
+ fail(lines.join("\n"));
192
+ }
193
+
112
194
  function printCapabilities(payload) {
113
- const lines = ["caphub capabilities", ""];
195
+ const lines = [
196
+ "caphub capabilities",
197
+ "",
198
+ "Live capabilities available through the current API.",
199
+ "Use 'caphub help <capability>' before first use.",
200
+ "",
201
+ ];
114
202
  for (const capability of payload.capabilities || []) {
115
203
  lines.push(`/${capability.capability} - ${capability.purpose}`);
116
204
  if (capability.credits) lines.push(` credits: ${capability.credits}`);
@@ -137,6 +225,15 @@ function printCapabilityHelp(payload) {
137
225
  "",
138
226
  "output:",
139
227
  JSON.stringify(payload.output_contract, null, 2),
228
+ payload.notes_for_agents?.length ? "" : null,
229
+ payload.notes_for_agents?.length ? "notes:" : null,
230
+ ...(payload.notes_for_agents || []).map((note) => `- ${note}`),
231
+ "",
232
+ "common recovery:",
233
+ "- auth missing: caphub auth login --api-key csk_live_...",
234
+ "- auth invalid: generate a new key in https://caphub.io/dashboard/",
235
+ "- low credits: top up in https://caphub.io/dashboard/",
236
+ `- bad payload: caphub help ${payload.capability}`,
140
237
  ].filter(Boolean);
141
238
  process.stdout.write(`${lines.join("\n")}\n`);
142
239
  }
@@ -170,7 +267,7 @@ async function commandAuth(args) {
170
267
  if (sub === "login") {
171
268
  const apiKey = parseFlag(args, "--api-key") || getApiKey();
172
269
  const apiUrl = parseFlag(args, "--api-url") || getApiUrl();
173
- if (!apiKey) fail("Error: auth login requires --api-key or CAPHUB_API_KEY.");
270
+ if (!apiKey) fail("Error: auth login requires --api-key or CAPHUB_API_KEY.\n\nnext:\n - caphub auth login --api-key csk_live_...");
174
271
  writeConfig({
175
272
  ...config,
176
273
  api_key: apiKey,
@@ -182,7 +279,7 @@ async function commandAuth(args) {
182
279
 
183
280
  if (sub === "whoami") {
184
281
  const apiKey = getApiKey();
185
- if (!apiKey) fail(`Error: no api key configured. Use 'caphub auth login --api-key ...' or set ${CONFIG_PATH}.`);
282
+ 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
283
  const payload = await fetchJson(`${getApiUrl()}/v1/me`, { apiKey });
187
284
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
188
285
  return;
@@ -201,7 +298,11 @@ async function commandAuth(args) {
201
298
 
202
299
  async function commandCapability(capability, args) {
203
300
  const apiKey = getApiKey();
204
- if (!apiKey) fail(`Error: CAPHUB_API_KEY is required, or set api_key in ${CONFIG_PATH}.`);
301
+ if (!apiKey) {
302
+ fail(
303
+ `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}`
304
+ );
305
+ }
205
306
 
206
307
  if (args[0] === "--help" || args[0] === "help") {
207
308
  await commandHelp([capability]);
@@ -211,14 +312,14 @@ async function commandCapability(capability, args) {
211
312
  const arg = args[0];
212
313
  const rawInput = arg ?? (process.stdin.isTTY ? "" : await readStdin());
213
314
  if (!rawInput.trim()) {
214
- fail(`Error: input JSON is required. Run 'caphub help ${capability}' for the contract.`);
315
+ fail(`Error: input JSON is required.\n\nnext:\n - caphub help ${capability}\n - then run: caphub ${capability} '<json>'`);
215
316
  }
216
317
 
217
318
  let body;
218
319
  try {
219
320
  body = JSON.parse(rawInput);
220
321
  } catch {
221
- fail("Error: input must be valid JSON.");
322
+ fail(`Error: input must be valid JSON.\n\nnext:\n - caphub help ${capability}\n - pass exactly one JSON object`);
222
323
  }
223
324
 
224
325
  if (!("function" in body)) body.function = capability;
@@ -258,4 +359,10 @@ async function main() {
258
359
  await commandCapability(cmd, args.slice(1));
259
360
  }
260
361
 
261
- await main().catch((error) => fail(`Error: ${error.message}`));
362
+ await main().catch((error) => {
363
+ const cmd = process.argv[2];
364
+ const capability = cmd && !["help", "--help", "-h", "capabilities", "auth", "--version", "-v", "version"].includes(cmd)
365
+ ? cmd
366
+ : undefined;
367
+ failWithHints(error.message || "unknown error", error, { capability });
368
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caphub/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Root CLI for Caphub agent capabilities",
5
5
  "type": "module",
6
6
  "bin": {