@caphub/cli 0.1.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.
Files changed (3) hide show
  1. package/README.md +41 -0
  2. package/bin/caphub.js +261 -0
  3. package/package.json +27 -0
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # `@caphub/cli`
2
+
3
+ Root CLI for Caphub capabilities.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @caphub/cli
9
+ ```
10
+
11
+ Or run without install:
12
+
13
+ ```bash
14
+ npx @caphub/cli help
15
+ ```
16
+
17
+ ## Auth
18
+
19
+ Store your API key once:
20
+
21
+ ```bash
22
+ caphub auth login --api-key csk_live_...
23
+ ```
24
+
25
+ ## Discovery
26
+
27
+ ```bash
28
+ caphub help
29
+ caphub capabilities
30
+ caphub help search
31
+ ```
32
+
33
+ ## Run capabilities
34
+
35
+ ```bash
36
+ caphub search '{"queries":["site:github.com awesome ai agents"]}'
37
+ ```
38
+
39
+ ```bash
40
+ caphub search-ideas '{"queries":["best robot vacuum"]}'
41
+ ```
package/bin/caphub.js ADDED
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import os from "node:os";
5
+ import { dirname, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, "..", "package.json"), "utf8"));
10
+ const DEFAULT_API_URL = "https://api.caphub.io";
11
+ const CONFIG_DIR = resolve(os.homedir(), ".config", "caphub");
12
+ const CONFIG_PATH = resolve(CONFIG_DIR, "config.json");
13
+
14
+ const ROOT_HELP = `caphub
15
+
16
+ purpose: root CLI for Caphub agent capabilities
17
+ auth: CAPHUB_API_KEY env or ${CONFIG_PATH}
18
+ api: ${DEFAULT_API_URL}
19
+
20
+ commands:
21
+ help explain the platform and command layout
22
+ help <capability> show capability-specific help from the API
23
+ capabilities list live capabilities with short descriptions
24
+ auth login save api key locally for future runs
25
+ auth whoami verify the current api key against the API
26
+ auth logout remove stored api key from local config
27
+ <capability> <json> run a capability directly, e.g. search or search-ideas
28
+
29
+ examples:
30
+ caphub capabilities
31
+ caphub auth login --api-key csk_live_...
32
+ caphub help search
33
+ caphub search '{"queries":["site:github.com awesome ai agents"]}'
34
+ `;
35
+
36
+ function readConfig() {
37
+ try {
38
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
39
+ } catch {
40
+ return {};
41
+ }
42
+ }
43
+
44
+ function writeConfig(config) {
45
+ mkdirSync(CONFIG_DIR, { recursive: true });
46
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf8");
47
+ }
48
+
49
+ function getApiUrl() {
50
+ const config = readConfig();
51
+ return (
52
+ process.env.CAPHUB_API_URL ||
53
+ config.api_url ||
54
+ DEFAULT_API_URL
55
+ ).replace(/\/+$/, "");
56
+ }
57
+
58
+ function getApiKey() {
59
+ const config = readConfig();
60
+ return (
61
+ process.env.CAPHUB_API_KEY ||
62
+ config.api_key ||
63
+ ""
64
+ ).trim();
65
+ }
66
+
67
+ function fail(message) {
68
+ process.stderr.write(`${message}\n`);
69
+ process.exit(1);
70
+ }
71
+
72
+ function readStdin() {
73
+ return new Promise((resolveInput) => {
74
+ let data = "";
75
+ process.stdin.setEncoding("utf8");
76
+ process.stdin.on("data", (chunk) => (data += chunk));
77
+ process.stdin.on("end", () => resolveInput(data));
78
+ });
79
+ }
80
+
81
+ async function fetchJson(url, { method = "GET", body, apiKey = "" } = {}) {
82
+ const headers = { "Content-Type": "application/json" };
83
+ if (apiKey) headers["X-API-Key"] = apiKey;
84
+
85
+ const resp = await fetch(url, {
86
+ method,
87
+ headers,
88
+ ...(body ? { body: JSON.stringify(body) } : {}),
89
+ });
90
+
91
+ const text = await resp.text();
92
+ let data;
93
+ try {
94
+ data = text ? JSON.parse(text) : {};
95
+ } catch {
96
+ throw new Error(`non-JSON response from ${url} (HTTP ${resp.status})`);
97
+ }
98
+
99
+ if (!resp.ok) {
100
+ throw new Error(data?.error || `HTTP ${resp.status}`);
101
+ }
102
+
103
+ return data;
104
+ }
105
+
106
+ function parseFlag(args, name) {
107
+ const idx = args.indexOf(name);
108
+ if (idx === -1) return "";
109
+ return args[idx + 1] || "";
110
+ }
111
+
112
+ function printCapabilities(payload) {
113
+ const lines = ["caphub capabilities", ""];
114
+ for (const capability of payload.capabilities || []) {
115
+ lines.push(`/${capability.capability} - ${capability.purpose}`);
116
+ if (capability.credits) lines.push(` credits: ${capability.credits}`);
117
+ if (capability.limits?.max_queries_per_request) lines.push(` max queries: ${capability.limits.max_queries_per_request}`);
118
+ if (capability.endpoint) lines.push(` endpoint: ${capability.endpoint}`);
119
+ lines.push("");
120
+ }
121
+ process.stdout.write(lines.join("\n"));
122
+ }
123
+
124
+ function printCapabilityHelp(payload) {
125
+ const lines = [
126
+ payload.capability,
127
+ "",
128
+ `purpose: ${payload.purpose}`,
129
+ `endpoint: ${payload.endpoint}`,
130
+ `method: ${payload.method}`,
131
+ payload.credits ? `credits: ${payload.credits}` : null,
132
+ payload.limits?.max_queries_per_request ? `limits: max ${payload.limits.max_queries_per_request} queries per request` : null,
133
+ `auth: ${payload.auth?.env || "CAPHUB_API_KEY"} or ${CONFIG_PATH}`,
134
+ "",
135
+ "input:",
136
+ JSON.stringify(payload.input_contract, null, 2),
137
+ "",
138
+ "output:",
139
+ JSON.stringify(payload.output_contract, null, 2),
140
+ ].filter(Boolean);
141
+ process.stdout.write(`${lines.join("\n")}\n`);
142
+ }
143
+
144
+ async function commandHelp(args) {
145
+ const apiUrl = getApiUrl();
146
+ const capability = args[0];
147
+ if (!capability) {
148
+ process.stdout.write(ROOT_HELP);
149
+ return;
150
+ }
151
+
152
+ const payload = await fetchJson(`${apiUrl}/v1/${capability}/help`);
153
+ printCapabilityHelp(payload);
154
+ }
155
+
156
+ async function commandCapabilities(args) {
157
+ const apiUrl = getApiUrl();
158
+ const payload = await fetchJson(`${apiUrl}/v1/help`);
159
+ if (args.includes("--json")) {
160
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
161
+ return;
162
+ }
163
+ printCapabilities(payload);
164
+ }
165
+
166
+ async function commandAuth(args) {
167
+ const sub = args[0];
168
+ const config = readConfig();
169
+
170
+ if (sub === "login") {
171
+ const apiKey = parseFlag(args, "--api-key") || getApiKey();
172
+ const apiUrl = parseFlag(args, "--api-url") || getApiUrl();
173
+ if (!apiKey) fail("Error: auth login requires --api-key or CAPHUB_API_KEY.");
174
+ writeConfig({
175
+ ...config,
176
+ api_key: apiKey,
177
+ api_url: apiUrl,
178
+ });
179
+ process.stdout.write(`${JSON.stringify({ ok: true, config_path: CONFIG_PATH, api_url: apiUrl }, null, 2)}\n`);
180
+ return;
181
+ }
182
+
183
+ if (sub === "whoami") {
184
+ const apiKey = getApiKey();
185
+ if (!apiKey) fail(`Error: no api key configured. Use 'caphub auth login --api-key ...' or set ${CONFIG_PATH}.`);
186
+ const payload = await fetchJson(`${getApiUrl()}/v1/me`, { apiKey });
187
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
188
+ return;
189
+ }
190
+
191
+ if (sub === "logout") {
192
+ const next = { ...config };
193
+ delete next.api_key;
194
+ writeConfig(next);
195
+ process.stdout.write(`${JSON.stringify({ ok: true, config_path: CONFIG_PATH }, null, 2)}\n`);
196
+ return;
197
+ }
198
+
199
+ fail("Error: auth commands are: login, whoami, logout.");
200
+ }
201
+
202
+ async function commandCapability(capability, args) {
203
+ const apiKey = getApiKey();
204
+ if (!apiKey) fail(`Error: CAPHUB_API_KEY is required, or set api_key in ${CONFIG_PATH}.`);
205
+
206
+ if (args[0] === "--help" || args[0] === "help") {
207
+ await commandHelp([capability]);
208
+ return;
209
+ }
210
+
211
+ const arg = args[0];
212
+ const rawInput = arg ?? (process.stdin.isTTY ? "" : await readStdin());
213
+ if (!rawInput.trim()) {
214
+ fail(`Error: input JSON is required. Run 'caphub help ${capability}' for the contract.`);
215
+ }
216
+
217
+ let body;
218
+ try {
219
+ body = JSON.parse(rawInput);
220
+ } catch {
221
+ fail("Error: input must be valid JSON.");
222
+ }
223
+
224
+ if (!("function" in body)) body.function = capability;
225
+
226
+ const payload = await fetchJson(`${getApiUrl()}/v1/${capability}`, {
227
+ method: "POST",
228
+ apiKey,
229
+ body,
230
+ });
231
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
232
+ }
233
+
234
+ async function main() {
235
+ const args = process.argv.slice(2);
236
+ const cmd = args[0];
237
+
238
+ if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
239
+ await commandHelp(args.slice(1));
240
+ return;
241
+ }
242
+
243
+ if (cmd === "--version" || cmd === "-v" || cmd === "version") {
244
+ process.stdout.write(`${pkg.version}\n`);
245
+ return;
246
+ }
247
+
248
+ if (cmd === "capabilities") {
249
+ await commandCapabilities(args.slice(1));
250
+ return;
251
+ }
252
+
253
+ if (cmd === "auth") {
254
+ await commandAuth(args.slice(1));
255
+ return;
256
+ }
257
+
258
+ await commandCapability(cmd, args.slice(1));
259
+ }
260
+
261
+ await main().catch((error) => fail(`Error: ${error.message}`));
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@caphub/cli",
3
+ "version": "0.1.0",
4
+ "description": "Root CLI for Caphub agent capabilities",
5
+ "type": "module",
6
+ "bin": {
7
+ "caphub": "bin/caphub.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "README.md"
12
+ ],
13
+ "keywords": [
14
+ "agents",
15
+ "cli",
16
+ "caphub",
17
+ "search",
18
+ "automation"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "license": "UNLICENSED",
24
+ "engines": {
25
+ "node": ">=18"
26
+ }
27
+ }