@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.
- package/bin/caphub.js +121 -14
- 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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
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
|
|
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 = [
|
|
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
|
|
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)
|
|
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
|
|
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(
|
|
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) =>
|
|
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
|
+
});
|