@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 +1 -1
- package/dist/cli.js +14 -3
- package/dist/commands/fav.js +138 -3
- package/package.json +1 -1
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 (
|
|
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.
|
|
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
|
-
|
|
222
|
-
|
|
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": {
|
package/dist/commands/fav.js
CHANGED
|
@@ -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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
}
|