@caravo/cli 0.2.0 → 0.2.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.
package/README.md CHANGED
@@ -29,7 +29,7 @@ caravo review EXECUTION_ID --rating 5 --comment "Great quality"
29
29
  # Upvote an existing review
30
30
  caravo upvote REVIEW_ID --exec EXECUTION_ID
31
31
 
32
- # Manage favorites (requires API key)
32
+ # Manage favorites (works with or without API key)
33
33
  caravo fav list
34
34
  caravo fav add fal-ai/flux/schnell
35
35
  caravo fav rm fal-ai/flux/schnell
package/dist/cli.js CHANGED
@@ -22,6 +22,7 @@ Commands:
22
22
  request --title <t> --desc <d>
23
23
  Submit a tool request
24
24
  request-upvote <req-id> Upvote a tool request
25
+ login Connect your Caravo account via browser (saves API key)
25
26
  wallet Show wallet + balance info
26
27
  fetch [METHOD] <url> Raw x402 HTTP request
27
28
 
@@ -169,11 +170,16 @@ function parseArgs(argv) {
169
170
  else if (!arg.startsWith("-")) {
170
171
  args.positional.push(arg);
171
172
  }
173
+ else {
174
+ // Unknown flag
175
+ process.stderr.write(`[caravo] unknown option: ${arg}\n`);
176
+ process.exit(1);
177
+ }
172
178
  i++;
173
179
  }
174
180
  return args;
175
181
  }
176
- const VERSION = "0.2.0";
182
+ const VERSION = "0.2.2";
177
183
  async function main() {
178
184
  const args = parseArgs(process.argv.slice(2));
179
185
  if (args.version) {
@@ -218,8 +224,14 @@ async function main() {
218
224
  break;
219
225
  }
220
226
  case "exec": {
221
- const { run } = await import("./commands/exec.js");
222
- await run(args.positional[0], args.data, auth, args.compact);
227
+ if (args.dryRun) {
228
+ const { runDryRun } = await import("./commands/exec.js");
229
+ await runDryRun(args.positional[0], args.data, auth, args.compact);
230
+ }
231
+ else {
232
+ const { run } = await import("./commands/exec.js");
233
+ await run(args.positional[0], args.data, auth, args.compact);
234
+ }
223
235
  break;
224
236
  }
225
237
  case "dry-run": {
@@ -271,6 +283,11 @@ async function main() {
271
283
  await runReqUpvote(args.positional[0], args.exec, auth, args.compact);
272
284
  break;
273
285
  }
286
+ case "login": {
287
+ const { runLogin } = await import("./commands/login.js");
288
+ await runLogin(auth.baseUrl);
289
+ break;
290
+ }
274
291
  case "wallet": {
275
292
  const { run } = await import("./commands/wallet-cmd.js");
276
293
  await run(auth, args.compact);
@@ -26,7 +26,18 @@ function localList(compact) {
26
26
  const data = { data: favorites.map((id) => ({ id })), total: favorites.length };
27
27
  outputJson(data, compact);
28
28
  }
29
- function localAdd(toolId) {
29
+ async function localAdd(toolId, baseUrl) {
30
+ // Verify tool exists on server before adding
31
+ try {
32
+ const r = await fetch(`${baseUrl}/api/tools/${toolId}`);
33
+ if (r.status === 404) {
34
+ log(`Tool not found: ${toolId}`);
35
+ process.exit(1);
36
+ }
37
+ }
38
+ catch {
39
+ // Network error — allow offline add
40
+ }
30
41
  const favs = readLocal();
31
42
  if (favs.favorites.includes(toolId)) {
32
43
  log(`${toolId} is already in favorites`);
@@ -90,7 +101,7 @@ export async function run(sub, toolId, auth, compact) {
90
101
  }
91
102
  // No API key → local mode
92
103
  log("[local mode — set CARAVO_API_KEY to sync with server]");
93
- return runLocal(sub, toolId, compact);
104
+ return runLocal(sub, toolId, compact, auth.baseUrl);
94
105
  }
95
106
  async function runServer(sub, toolId, auth, compact) {
96
107
  switch (sub) {
@@ -132,7 +143,7 @@ async function runServer(sub, toolId, auth, compact) {
132
143
  process.exit(1);
133
144
  }
134
145
  }
135
- function runLocal(sub, toolId, compact) {
146
+ async function runLocal(sub, toolId, compact, baseUrl) {
136
147
  switch (sub) {
137
148
  case "list":
138
149
  localList(compact);
@@ -147,7 +158,7 @@ function runLocal(sub, toolId, compact) {
147
158
  log(err);
148
159
  process.exit(1);
149
160
  }
150
- localAdd(toolId);
161
+ await localAdd(toolId, baseUrl);
151
162
  break;
152
163
  }
153
164
  case "rm": {
@@ -0,0 +1,67 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import { exec } from "child_process";
5
+ const CONFIG_DIR = join(homedir(), ".caravo");
6
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
7
+ function loadConfig() {
8
+ try {
9
+ if (!existsSync(CONFIG_FILE))
10
+ return {};
11
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
12
+ }
13
+ catch {
14
+ return {};
15
+ }
16
+ }
17
+ function saveConfig(data) {
18
+ mkdirSync(CONFIG_DIR, { recursive: true });
19
+ writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
20
+ }
21
+ function openBrowser(url) {
22
+ const opener = process.platform === "darwin"
23
+ ? `open "${url}"`
24
+ : process.platform === "win32"
25
+ ? `start "" "${url}"`
26
+ : `xdg-open "${url}"`;
27
+ exec(opener);
28
+ }
29
+ export async function runLogin(baseUrl) {
30
+ // 1. Create session
31
+ process.stdout.write("Opening browser for login...\n");
32
+ const initRes = await fetch(`${baseUrl}/api/auth/mcp-session`, {
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/json" },
35
+ });
36
+ if (!initRes.ok) {
37
+ process.stderr.write(`[caravo] login: server error ${initRes.status}\n`);
38
+ process.exit(1);
39
+ }
40
+ const { token, url } = (await initRes.json());
41
+ // 2. Open browser
42
+ openBrowser(url);
43
+ process.stdout.write(`\nOpened: ${url}\n\nWaiting for login (5 min timeout)...\n`);
44
+ // 3. Poll every 2s
45
+ const deadline = Date.now() + 5 * 60 * 1000;
46
+ while (Date.now() < deadline) {
47
+ await new Promise((r) => setTimeout(r, 2000));
48
+ const pollRes = await fetch(`${baseUrl}/api/auth/mcp-session?token=${encodeURIComponent(token)}`);
49
+ const poll = (await pollRes.json());
50
+ if (poll.status === "completed" && poll.api_key) {
51
+ // 4. Save to config
52
+ const existing = loadConfig();
53
+ saveConfig({ ...existing, api_key: poll.api_key });
54
+ process.stdout.write(`\n✓ Logged in! API key saved to ${CONFIG_FILE}\n\n`);
55
+ process.stdout.write(`To use it immediately (this shell session):\n export CARAVO_API_KEY=${poll.api_key}\n\n`);
56
+ process.stdout.write(`To make it permanent, add to your shell profile (~/.zshrc / ~/.bashrc):\n export CARAVO_API_KEY=${poll.api_key}\n`);
57
+ return;
58
+ }
59
+ if (poll.status === "expired") {
60
+ process.stderr.write("[caravo] login: session expired. Run `caravo login` again.\n");
61
+ process.exit(1);
62
+ }
63
+ process.stdout.write(".");
64
+ }
65
+ process.stderr.write("\n[caravo] login: timed out after 5 minutes.\n");
66
+ process.exit(1);
67
+ }
package/dist/lib/auth.js CHANGED
@@ -1,7 +1,23 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
1
4
  import { loadOrCreateWallet } from "../wallet.js";
2
5
  const DEFAULT_BASE_URL = "https://caravo.ai";
6
+ const CONFIG_FILE = join(homedir(), ".caravo", "config.json");
7
+ function readConfigApiKey() {
8
+ try {
9
+ if (!existsSync(CONFIG_FILE))
10
+ return undefined;
11
+ const c = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
12
+ return typeof c.api_key === "string" ? c.api_key : undefined;
13
+ }
14
+ catch {
15
+ return undefined;
16
+ }
17
+ }
3
18
  export function resolveAuth(args) {
4
- const apiKey = args.apiKey || process.env.CARAVO_API_KEY;
19
+ // Priority: explicit --api-key flag → CARAVO_API_KEY env → ~/.caravo/config.json
20
+ const apiKey = args.apiKey || process.env.CARAVO_API_KEY || readConfigApiKey();
5
21
  const baseUrl = args.baseUrl || process.env.CARAVO_URL || DEFAULT_BASE_URL;
6
22
  let cached;
7
23
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caravo/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Caravo CLI — search, execute, and review tools with API key or x402 USDC payments",
5
5
  "type": "module",
6
6
  "bin": {