@caravo/cli 0.1.1 → 0.2.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/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
@@ -169,11 +169,16 @@ function parseArgs(argv) {
169
169
  else if (!arg.startsWith("-")) {
170
170
  args.positional.push(arg);
171
171
  }
172
+ else {
173
+ // Unknown flag
174
+ process.stderr.write(`[caravo] unknown option: ${arg}\n`);
175
+ process.exit(1);
176
+ }
172
177
  i++;
173
178
  }
174
179
  return args;
175
180
  }
176
- const VERSION = "0.1.1";
181
+ const VERSION = "0.2.1";
177
182
  async function main() {
178
183
  const args = parseArgs(process.argv.slice(2));
179
184
  if (args.version) {
@@ -218,8 +223,14 @@ async function main() {
218
223
  break;
219
224
  }
220
225
  case "exec": {
221
- const { run } = await import("./commands/exec.js");
222
- await run(args.positional[0], args.data, auth, args.compact);
226
+ if (args.dryRun) {
227
+ const { runDryRun } = await import("./commands/exec.js");
228
+ await runDryRun(args.positional[0], args.data, auth, args.compact);
229
+ }
230
+ else {
231
+ const { run } = await import("./commands/exec.js");
232
+ await run(args.positional[0], args.data, auth, args.compact);
233
+ }
223
234
  break;
224
235
  }
225
236
  case "dry-run": {
@@ -1,10 +1,109 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
1
4
  import { apiGet, apiPost, apiDelete, validateToolId } from "../lib/api.js";
2
5
  import { outputJson, log } from "../lib/output.js";
6
+ // ─── Local favorites ────────────────────────────────────────────────────────
7
+ const FAV_DIR = join(homedir(), ".caravo");
8
+ const FAV_FILE = join(FAV_DIR, "favorites.json");
9
+ function readLocal() {
10
+ if (!existsSync(FAV_FILE))
11
+ return { version: "1.0.0", favorites: [] };
12
+ try {
13
+ const raw = JSON.parse(readFileSync(FAV_FILE, "utf-8"));
14
+ if (Array.isArray(raw.favorites))
15
+ return raw;
16
+ }
17
+ catch { }
18
+ return { version: "1.0.0", favorites: [] };
19
+ }
20
+ function writeLocal(favs) {
21
+ mkdirSync(FAV_DIR, { recursive: true });
22
+ writeFileSync(FAV_FILE, JSON.stringify(favs, null, 2) + "\n");
23
+ }
24
+ function localList(compact) {
25
+ const { favorites } = readLocal();
26
+ const data = { data: favorites.map((id) => ({ id })), total: favorites.length };
27
+ outputJson(data, compact);
28
+ }
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
+ }
41
+ const favs = readLocal();
42
+ if (favs.favorites.includes(toolId)) {
43
+ log(`${toolId} is already in favorites`);
44
+ return;
45
+ }
46
+ favs.favorites.push(toolId);
47
+ writeLocal(favs);
48
+ log(`✓ Added ${toolId} to local favorites`);
49
+ }
50
+ function localRm(toolId) {
51
+ const favs = readLocal();
52
+ const idx = favs.favorites.indexOf(toolId);
53
+ if (idx === -1) {
54
+ log(`${toolId} is not in favorites`);
55
+ return;
56
+ }
57
+ favs.favorites.splice(idx, 1);
58
+ writeLocal(favs);
59
+ log(`✓ Removed ${toolId} from local favorites`);
60
+ }
61
+ // ─── Auto-sync: merge local → server on first authenticated run ─────────────
62
+ async function autoSyncLocalToServer(auth) {
63
+ if (!existsSync(FAV_FILE))
64
+ return;
65
+ const local = readLocal();
66
+ if (local.favorites.length === 0)
67
+ return;
68
+ log(`Found ${local.favorites.length} local favorite(s), syncing to server...`);
69
+ // Get current server favorites to check connectivity and avoid duplicates
70
+ const serverData = (await apiGet("/api/favorites", auth));
71
+ if (serverData?.error || !serverData?.data) {
72
+ log("Server sync skipped (auth failed) — keeping local favorites");
73
+ return;
74
+ }
75
+ const serverIds = new Set(serverData.data.map((f) => f.id));
76
+ let added = 0;
77
+ for (const toolId of local.favorites) {
78
+ if (serverIds.has(toolId))
79
+ continue;
80
+ try {
81
+ await apiPost("/api/favorites", { tool_id: toolId }, auth);
82
+ added++;
83
+ }
84
+ catch {
85
+ // Tool may no longer exist — skip silently
86
+ }
87
+ }
88
+ // Only remove local file after successful sync
89
+ try {
90
+ unlinkSync(FAV_FILE);
91
+ }
92
+ catch { }
93
+ log(`✓ Synced ${added} new favorite(s) to server (local file removed)`);
94
+ }
95
+ // ─── Main entry ─────────────────────────────────────────────────────────────
3
96
  export async function run(sub, toolId, auth, compact) {
4
- if (auth.mode !== "apikey") {
5
- log("Favorites require an API key. Set $CARAVO_API_KEY or use --api-key.");
6
- process.exit(1);
97
+ // Authenticated → server mode (with auto-sync from local on first run)
98
+ if (auth.mode === "apikey") {
99
+ await autoSyncLocalToServer(auth);
100
+ return runServer(sub, toolId, auth, compact);
7
101
  }
102
+ // No API key → local mode
103
+ log("[local mode — set CARAVO_API_KEY to sync with server]");
104
+ return runLocal(sub, toolId, compact, auth.baseUrl);
105
+ }
106
+ async function runServer(sub, toolId, auth, compact) {
8
107
  switch (sub) {
9
108
  case "list": {
10
109
  const data = await apiGet("/api/favorites", auth);
@@ -44,3 +143,39 @@ export async function run(sub, toolId, auth, compact) {
44
143
  process.exit(1);
45
144
  }
46
145
  }
146
+ async function runLocal(sub, toolId, compact, baseUrl) {
147
+ switch (sub) {
148
+ case "list":
149
+ localList(compact);
150
+ break;
151
+ case "add": {
152
+ if (!toolId) {
153
+ log("Usage: caravo fav add <tool-id>");
154
+ process.exit(1);
155
+ }
156
+ const err = validateToolId(toolId);
157
+ if (err) {
158
+ log(err);
159
+ process.exit(1);
160
+ }
161
+ await localAdd(toolId, baseUrl);
162
+ break;
163
+ }
164
+ case "rm": {
165
+ if (!toolId) {
166
+ log("Usage: caravo fav rm <tool-id>");
167
+ process.exit(1);
168
+ }
169
+ const err = validateToolId(toolId);
170
+ if (err) {
171
+ log(err);
172
+ process.exit(1);
173
+ }
174
+ localRm(toolId);
175
+ break;
176
+ }
177
+ default:
178
+ log("Usage: caravo fav <list|add|rm> [tool-id]");
179
+ process.exit(1);
180
+ }
181
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caravo/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Caravo CLI — search, execute, and review tools with API key or x402 USDC payments",
5
5
  "type": "module",
6
6
  "bin": {