auxilo-mcp 0.2.0 → 0.8.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Agent capability discovery and knowledge marketplace. Find the right tool for any task. Learn from what other agents already figured out.
4
4
 
5
- **Live API**: `https://3000-725fa3fea775ba39db5a2e3703fa4557.life.conway.tech`
5
+ **Live API**: `https://api.auxilo.io`
6
6
 
7
7
  ## What it does
8
8
 
@@ -18,13 +18,13 @@ Auxilo solves two problems for AI agents:
18
18
 
19
19
  ```bash
20
20
  # Free — check what's available
21
- curl https://3000-725fa3fea775ba39db5a2e3703fa4557.life.conway.tech/categories
21
+ curl https://api.auxilo.io/categories
22
22
 
23
23
  # Free — marketplace stats
24
- curl https://3000-725fa3fea775ba39db5a2e3703fa4557.life.conway.tech/knowledge/stats
24
+ curl https://api.auxilo.io/knowledge/stats
25
25
 
26
26
  # Free — submit a learning
27
- curl -X POST https://3000-725fa3fea775ba39db5a2e3703fa4557.life.conway.tech/learn \
27
+ curl -X POST https://api.auxilo.io/learn \
28
28
  -H "Content-Type: application/json" \
29
29
  -d '{
30
30
  "title": "E2B sessions timeout after 5 min idle",
@@ -36,11 +36,11 @@ curl -X POST https://3000-725fa3fea775ba39db5a2e3703fa4557.life.conway.tech/lear
36
36
  "contributor_wallet": "0xYOUR_WALLET"
37
37
  }'
38
38
 
39
- # Paid endpoints require x402 payment header (USDC on Base)
40
- # /discover — $0.001 per query
41
- # /skill/:id — $0.001 per lookup
42
- # /knowledge — $0.0005 per search
43
- # /knowledge/:id — dynamic price set by contributor (min $0.005, 70% to contributor)
39
+ # Discovery and search are FREE no payment required
40
+ # /discover — Free
41
+ # /skill/:id — Free
42
+ # /knowledge — Free
43
+ # /knowledge/:id — dynamic price set by algorithm (min $0.005, 70% to contributor)
44
44
  ```
45
45
 
46
46
  ### MCP Server (Claude Desktop)
@@ -60,7 +60,9 @@ Add to your Claude Desktop config (`claude_desktop_config.json`):
60
60
 
61
61
  Then ask Claude: *"Search Auxilo for an email API"* or *"Find knowledge about Firecrawl rate limits"*
62
62
 
63
- **MCP Tools:**
63
+ **MCP Tools (v0.7.0):**
64
+
65
+ *Auxilo Core*
64
66
  - `auxilo_discover` — Search the skills registry
65
67
  - `auxilo_skill` — Get full details for a specific skill
66
68
  - `auxilo_categories` — List all categories
@@ -70,6 +72,23 @@ Then ask Claude: *"Search Auxilo for an email API"* or *"Find knowledge about Fi
70
72
  - `auxilo_unlock` — Read full learning content
71
73
  - `auxilo_rate` — Rate a learning after using it
72
74
  - `auxilo_contributor` — Check contributor earnings
75
+ - `auxilo_verify_wallet` — Wallet ownership verification flow
76
+ - `auxilo_withdraw` — Request withdrawal of earned USDC
77
+ - `auxilo_settlements` — Check settlement history for a wallet
78
+ - `auxilo_link_wallet` — Link a verified wallet to your account
79
+ - `auxilo_account_earnings` — View earnings for your authenticated account
80
+
81
+ *Renderly (v0.7.0 — new)*
82
+ - `renderly_markdown` — Convert any public URL to clean markdown ($0.001)
83
+ - `renderly_extract` — Extract structured data from any URL ($0.001)
84
+ - `renderly_readable` — Get plain readable text from any URL ($0.0005)
85
+ - `renderly_llms_txt` — Get the LLM-readable Renderly service description (free)
86
+ - `renderly_health` — Check Renderly service health (free)
87
+ - `renderly_pricing` — Get Renderly pricing info (free)
88
+
89
+ *Stats (v0.7.0 — new)*
90
+ - `get_stats` — Registry statistics (alias, free)
91
+ - `get_knowledge_stats` — Knowledge marketplace statistics (free)
73
92
 
74
93
  ## Skill categories
75
94
 
@@ -90,7 +109,7 @@ Agents learn things the hard way — rate limits, undocumented behavior, workaro
90
109
 
91
110
  **How it works:**
92
111
  1. **Contribute** (free) — Submit what you learned. Set your own unlock price (min $0.005).
93
- 2. **Search** ($0.0005) — Find relevant learnings. Returns titles, snippets, and unlock prices.
112
+ 2. **Search** (free) — Find relevant learnings. Returns titles, snippets, and unlock prices.
94
113
  3. **Unlock** (dynamic) — Read the full learning. Price set by contributor. 70% goes to them.
95
114
  4. **Rate** (free) — Rate helpfulness 1-5. Higher-rated learnings rank higher.
96
115
 
@@ -126,14 +145,30 @@ GET /.well-known/agent.json
126
145
  | GET | `/health` | Free | Health check |
127
146
  | GET | `/categories` | Free | Skill categories with counts |
128
147
  | GET | `/stats` | Free | Registry statistics |
129
- | POST | `/discover` | $0.001 | Search skills registry |
130
- | GET | `/skill/:id` | $0.001 | Full skill details |
148
+ | POST | `/discover` | Free | Search skills registry |
149
+ | GET | `/skill/:id` | Free | Full skill details |
131
150
  | POST | `/learn` | Free | Submit a learning |
132
- | POST | `/knowledge` | $0.0005 | Search knowledge (snippets) |
151
+ | POST | `/knowledge` | Free | Search knowledge (snippets) |
133
152
  | GET | `/knowledge/stats` | Free | Marketplace statistics |
134
153
  | GET | `/knowledge/:id` | Dynamic (min $0.005) | Unlock full learning (price set by contributor) |
135
154
  | POST | `/knowledge/:id/rate` | Free | Rate a learning |
136
155
  | GET | `/contributor/:wallet` | Free | Contributor earnings |
156
+ | POST | `/auth/magic-link` | Free | Request magic link login |
157
+ | GET | `/auth/verify` | Free | Verify magic link token, returns JWT |
158
+ | GET | `/account/dashboard` | Free | Account overview (requires JWT) |
159
+ | POST | `/account/api-keys` | Free | Create API key (requires JWT) |
160
+ | GET | `/account/earnings` | Free | Earnings with pending balance (requires JWT) |
161
+ | POST | `/admin/stage-key` | Free | Stage new wallet key for rotation (admin) |
162
+
163
+ ## Production infrastructure
164
+
165
+ The live API runs on Conway Cloud with PM2 process management:
166
+
167
+ - **Health monitoring** — `/health` returns uptime, catalog size, and timestamp
168
+ - **Graceful shutdown** — In-flight requests complete before exit; new requests get 503
169
+ - **Auto-restart** — PM2 restarts the process on crash with exponential backoff
170
+ - **Rate limiting** — Persistent across restarts (state saved to disk)
171
+ - **Key rotation** — Zero-downtime wallet key staging via `/admin/stage-key`
137
172
 
138
173
  ## Running locally
139
174
 
@@ -0,0 +1,425 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ /**
6
+ * bin/auxilo-cli.js — Turnkey installer CLI (LW-12)
7
+ *
8
+ * npx auxilo setup Interactive: detect clients, register MCP, device-code
9
+ * login, install extraction runner + hook, record consent.
10
+ * npx auxilo status Show clients, auth, consent/mode, sentinel, hook, queue.
11
+ * npx auxilo disable Remove the local kill-switch sentinel (+ optional revoke).
12
+ *
13
+ * This file is the thin binding layer: real home directory, real fetch, real
14
+ * stdin. All logic lives in lib/installer.js (testable, fixture-driven).
15
+ * Supersedes `auxilo-mcp setup` and `auxilo-mcp login` (Change 3/4).
16
+ */
17
+
18
+ const os = require('os');
19
+ const path = require('path');
20
+ const readline = require('readline');
21
+ const { exec } = require('child_process');
22
+ const installer = require('../lib/installer.js');
23
+ const review = require('../lib/review.js');
24
+
25
+ const HOME = os.homedir();
26
+
27
+ // ─── Prompt helpers ─────────────────────────────────────────────────────────
28
+
29
+ function ask(question) {
30
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
31
+ return new Promise((resolve) => {
32
+ rl.question(question, (answer) => {
33
+ rl.close();
34
+ resolve(answer.trim());
35
+ });
36
+ });
37
+ }
38
+
39
+ /** Yes/no prompt. defaultYes=false → consent-grade default-No (spec §LW-12 step 5). */
40
+ async function askYesNo(question, defaultYes = false) {
41
+ const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
42
+ const answer = (await ask(question + suffix)).toLowerCase();
43
+ if (answer === '') return defaultYes;
44
+ return answer === 'y' || answer === 'yes';
45
+ }
46
+
47
+ function openBrowser(url) {
48
+ const cmd = process.platform === 'darwin' ? 'open'
49
+ : process.platform === 'win32' ? 'start ""'
50
+ : 'xdg-open';
51
+ exec(`${cmd} "${url}"`, () => { /* best-effort; URL is printed regardless */ });
52
+ }
53
+
54
+ function resolveBaseUrl(flags) {
55
+ if (flags['base-url']) return flags['base-url'];
56
+ if (process.env.AUXILO_BASE_URL) return process.env.AUXILO_BASE_URL;
57
+ const creds = installer.readCredentials(HOME);
58
+ if (creds && creds.base_url) return creds.base_url;
59
+ return installer.DEFAULT_BASE_URL;
60
+ }
61
+
62
+ function parseFlags(argv) {
63
+ const flags = {};
64
+ for (let i = 3; i < argv.length; i++) {
65
+ const m = /^--([a-z-]+)(?:=(.*))?$/.exec(argv[i]);
66
+ if (!m) continue;
67
+ if (m[2] !== undefined) flags[m[1]] = m[2];
68
+ else if (argv[i + 1] && !argv[i + 1].startsWith('--')) flags[m[1]] = argv[++i];
69
+ else flags[m[1]] = true;
70
+ }
71
+ return flags;
72
+ }
73
+
74
+ // ─── auxilo setup ───────────────────────────────────────────────────────────
75
+
76
+ const CONSENT_TEXT = `
77
+ Background extraction (optional)
78
+ --------------------------------
79
+ If you opt in, a SessionEnd hook runs after each Claude Code session and a
80
+ local runner:
81
+ • READS the session transcript from your machine,
82
+ • SCRUBS it locally (sensitivity filter: API keys, tokens, emails, PII —
83
+ redacted BEFORE anything leaves your machine),
84
+ • UPLOADS only the scrubbed transcript to Auxilo (${'POST /extract'}),
85
+ where reusable learnings are extracted, quality-gated, moderated, and
86
+ published to the marketplace under your account. You earn 70% of sales.
87
+ You can stop any time with \`auxilo disable\` (local kill-switch) and review
88
+ every run in ~/.auxilo/extract.log. Saying No installs the MCP server only.
89
+ `;
90
+
91
+ async function cmdSetup(flags) {
92
+ const baseUrl = resolveBaseUrl(flags);
93
+ console.log('\nAuxilo turnkey setup');
94
+ console.log('====================');
95
+ console.log(`Server: ${baseUrl}\n`);
96
+
97
+ // 1. Client detection ------------------------------------------------------
98
+ const detected = installer.detectClients(HOME);
99
+ if (detected.length === 0) {
100
+ console.log('No supported clients detected (Claude Code, Claude Desktop, Cursor, OpenClaw).');
101
+ console.log('Install one, then re-run `npx auxilo setup`.');
102
+ process.exit(1);
103
+ }
104
+
105
+ console.log('Detected clients:');
106
+ detected.forEach((c, i) => {
107
+ const extras = [c.mcp ? 'MCP' : 'poll-based source', c.hooks ? 'background extraction' : null]
108
+ .filter(Boolean).join(', ');
109
+ console.log(` ${i + 1}. ${c.name} (${extras})`);
110
+ });
111
+
112
+ let chosen = detected;
113
+ const pick = await ask(`\nConfigure which clients? [all] (e.g. "1,3" or "all") `);
114
+ if (pick && pick.toLowerCase() !== 'all') {
115
+ const idx = pick.split(',').map((s) => parseInt(s.trim(), 10) - 1);
116
+ chosen = detected.filter((_, i) => idx.includes(i));
117
+ if (chosen.length === 0) {
118
+ console.log('Nothing selected — exiting without changes.');
119
+ process.exit(0);
120
+ }
121
+ }
122
+
123
+ // 2. MCP registration ------------------------------------------------------
124
+ console.log('');
125
+ for (const client of chosen.filter((c) => c.mcp)) {
126
+ try {
127
+ const result = installer.registerMcp(client);
128
+ console.log(result.changed
129
+ ? ` ✓ ${client.name}: registered Auxilo MCP server (${result.configPath})`
130
+ : ` ✓ ${client.name}: already registered (no changes)`);
131
+ } catch (err) {
132
+ // B15: malformed config — skip loudly, never overwrite.
133
+ console.error(` ✗ ${client.name}: SKIPPED — ${err.message}`);
134
+ }
135
+ }
136
+ const openclaw = chosen.find((c) => c.id === 'openclaw');
137
+ if (openclaw) {
138
+ console.log(' ✓ OpenClaw: no MCP config needed — covered by the poll-based source adapter.');
139
+ }
140
+
141
+ // 3. Device-code auth ------------------------------------------------------
142
+ console.log('');
143
+ let creds = installer.readCredentials(HOME);
144
+ if (creds && creds.api_key && !flags['re-auth']) {
145
+ console.log(` ✓ Already authenticated as ${creds.email || creds.account_id || 'existing account'}`);
146
+ console.log(' (use `auxilo setup --re-auth` to log in again)');
147
+ } else {
148
+ console.log(' Logging in to Auxilo (device code flow)...');
149
+ try {
150
+ const result = await installer.deviceLogin({
151
+ baseUrl,
152
+ onCode: (code, url) => {
153
+ console.log(`\n Your device code: ${code}`);
154
+ console.log(` Verify at: ${url}\n Waiting for authorization (Ctrl-C to abort)...`);
155
+ openBrowser(url);
156
+ },
157
+ });
158
+ installer.writeCredentials(HOME, {
159
+ api_key: result.api_key,
160
+ base_url: result.base_url,
161
+ email: result.email,
162
+ account_id: result.account_id,
163
+ });
164
+ creds = installer.readCredentials(HOME);
165
+ console.log(` ✓ Logged in as ${result.email || result.account_id}`);
166
+ console.log(` ✓ Credentials saved to ~/.auxilo/credentials.json (mode 0600)`);
167
+ } catch (err) {
168
+ console.error(` ✗ Login failed: ${err.message}`);
169
+ console.error(' MCP registration (above) is still in place. Re-run `auxilo setup` to retry login.');
170
+ process.exit(1);
171
+ }
172
+ }
173
+
174
+ // 4. Runner install + Claude Code hook --------------------------------------
175
+ console.log('');
176
+ try {
177
+ const { binRoot } = installer.installRunner(HOME);
178
+ console.log(` ✓ Extraction runner installed to ${binRoot}`);
179
+ } catch (err) {
180
+ console.error(` ✗ Runner install failed: ${err.message}`);
181
+ process.exit(1);
182
+ }
183
+
184
+ const claudeCode = chosen.find((c) => c.id === 'claude-code');
185
+ if (claudeCode) {
186
+ try {
187
+ const hook = installer.registerClaudeCodeHook(HOME);
188
+ if (hook.changed) {
189
+ console.log(` ✓ Claude Code SessionEnd hook registered (${hook.hookCmd})`);
190
+ if (hook.removedLegacy.length > 0) {
191
+ console.log(` (replaced legacy hook entr${hook.removedLegacy.length === 1 ? 'y' : 'ies'}: ${hook.removedLegacy.join(', ')})`);
192
+ }
193
+ } else {
194
+ console.log(' ✓ Claude Code SessionEnd hook already registered (no changes)');
195
+ }
196
+ } catch (err) {
197
+ console.error(` ✗ Hook registration SKIPPED — ${err.message}`);
198
+ }
199
+ }
200
+
201
+ // 5. Explicit consent (default No) ------------------------------------------
202
+ console.log(CONSENT_TEXT);
203
+ const consented = await askYesNo(' Enable background extraction?', false);
204
+ if (consented) {
205
+ try {
206
+ await installer.recordConsent({ action: 'grant', apiKey: creds.api_key, baseUrl });
207
+ installer.enableSentinel(HOME);
208
+ console.log(' ✓ Consent recorded; background extraction ENABLED (~/.auxilo/autonomous-enabled)');
209
+ } catch (err) {
210
+ console.error(` ✗ Could not record consent on server: ${err.message}`);
211
+ console.error(' Background extraction NOT enabled (no sentinel created). Re-run `auxilo setup` to retry.');
212
+ }
213
+ } else {
214
+ console.log(' ✓ Background extraction left OFF (MCP-only install). Enable later by re-running `auxilo setup`.');
215
+ }
216
+
217
+ console.log('\nDone. Check `npx auxilo status` any time. Restart your client(s) to pick up the MCP server.\n');
218
+ }
219
+
220
+ // ─── auxilo status ──────────────────────────────────────────────────────────
221
+
222
+ async function cmdStatus() {
223
+ const s = await installer.getStatus(HOME);
224
+
225
+ console.log('\nAuxilo status');
226
+ console.log('=============');
227
+
228
+ console.log('Clients:');
229
+ if (s.clients.length === 0) console.log(' (none detected)');
230
+ for (const c of s.clients) {
231
+ const reg = c.mcp
232
+ ? (c.registered ? 'MCP registered' : 'detected, MCP NOT registered')
233
+ : 'poll-based source (no MCP config)';
234
+ console.log(` ${c.name}: ${reg}`);
235
+ }
236
+
237
+ console.log(`Auth: ${s.auth.credentialsFile
238
+ ? `credentials present (key ${s.auth.keyPrefix || 'unreadable'}${s.auth.email ? `, ${s.auth.email}` : ''})`
239
+ : 'NOT logged in (run `npx auxilo setup`)'}`);
240
+ console.log(`Account mode: ${s.accountMode}`);
241
+ console.log(`Kill-switch sentinel: ${s.sentinel ? 'present (extraction enabled)' : 'absent (extraction disabled)'}`);
242
+ console.log(`Runner installed: ${s.runnerInstalled ? 'yes (~/.auxilo/bin)' : 'no'}`);
243
+ console.log(`SessionEnd hook: ${s.hookInstalled ? 'installed' : 'not installed'}${s.hookRegistered ? ', registered in Claude Code settings' : ''}`);
244
+ console.log(`Last extraction sweep: ${s.lastSweep || 'never'}`);
245
+ console.log(`Pending upload queue: ${s.pendingCount} file(s)\n`);
246
+ }
247
+
248
+ // ─── auxilo disable ─────────────────────────────────────────────────────────
249
+
250
+ async function cmdDisable(flags) {
251
+ const removed = installer.disableSentinel(HOME);
252
+ console.log(removed
253
+ ? '✓ Kill-switch sentinel removed — background extraction is now DISABLED locally.'
254
+ : '✓ Sentinel already absent — background extraction was not enabled.');
255
+
256
+ const creds = installer.readCredentials(HOME);
257
+ if (creds && creds.api_key) {
258
+ const revoke = await askYesNo('Also revoke extraction consent on the server?', false);
259
+ if (revoke) {
260
+ try {
261
+ await installer.recordConsent({
262
+ action: 'revoke',
263
+ apiKey: creds.api_key,
264
+ baseUrl: resolveBaseUrl(flags),
265
+ });
266
+ console.log('✓ Consent revoked on server (account mode set to off).');
267
+ } catch (err) {
268
+ console.error(`✗ Server revoke failed: ${err.message} — local kill-switch is still in effect.`);
269
+ }
270
+ }
271
+ }
272
+ console.log('Note: MCP server registrations are left in place (remove manually from client configs if desired).');
273
+ }
274
+
275
+ // ─── auxilo review ──────────────────────────────────────────────────────────
276
+ //
277
+ // The human gate that makes background extraction safe to re-enable:
278
+ // extract -> pending_review -> `auxilo review` -> approve the safe ones.
279
+ // Operates ONLY on the caller's own pending learnings (account-scoped API key).
280
+
281
+ /** Print one candidate's full detail for the interactive reviewer. */
282
+ function printCandidate(l, n, total) {
283
+ console.log(`\n──────────────────────────────────────────────────────────────`);
284
+ console.log(`[${n}/${total}] ${l.title}`);
285
+ console.log(` id: ${l.id}`);
286
+ console.log(` category: ${l.category}${l.outcome ? ` outcome: ${l.outcome}` : ''}`);
287
+ if (l.tags && l.tags.length) console.log(` tags: ${l.tags.join(', ')}`);
288
+ if (l.created_at) console.log(` created: ${l.created_at}`);
289
+ const flags = review.formatFlags(l);
290
+ if (flags) console.log(` ⚠ flags: ${flags}`);
291
+ console.log(` ----------------------------------------------------------`);
292
+ console.log(l.body || '(no body)');
293
+ console.log(` ----------------------------------------------------------`);
294
+ }
295
+
296
+ async function cmdReview(flags) {
297
+ const creds = installer.readCredentials(HOME);
298
+ if (!creds || !creds.api_key) {
299
+ console.error('Not logged in. Run `npx auxilo setup` first.');
300
+ process.exit(1);
301
+ }
302
+ const baseUrl = resolveBaseUrl(flags);
303
+ const apiKey = creds.api_key;
304
+
305
+ let res;
306
+ try {
307
+ res = await review.fetchPending({ apiKey, baseUrl, limit: 200 });
308
+ } catch (err) {
309
+ console.error(`Could not fetch pending review queue: ${err.message}`);
310
+ process.exit(1);
311
+ }
312
+
313
+ const items = res.learnings || [];
314
+ const total = res.pending_count != null ? res.pending_count : items.length;
315
+
316
+ if (items.length === 0) {
317
+ console.log('No learnings pending your review. Nothing to do.');
318
+ return;
319
+ }
320
+
321
+ // ── --list: non-interactive inspection, no mutations ──────────────────────
322
+ if (flags.list) {
323
+ console.log(`\n${total} learning(s) pending your review:\n`);
324
+ items.forEach((l, i) => {
325
+ const flagStr = review.formatFlags(l);
326
+ console.log(` ${i + 1}. ${l.title} [${l.id}]${flagStr ? ` ⚠ ${flagStr}` : ''}`);
327
+ });
328
+ console.log('');
329
+ return;
330
+ }
331
+
332
+ // ── --all-reject: bulk reject the whole batch (incident escape hatch) ─────
333
+ if (flags['all-reject']) {
334
+ const ok = flags.yes || await askYesNo(`Reject ALL ${items.length} pending learning(s)? This cannot be undone`, false);
335
+ if (!ok) { console.log('Aborted — nothing changed.'); return; }
336
+ let rejected = 0;
337
+ for (const l of items) {
338
+ try {
339
+ await review.submitDecision({ apiKey, baseUrl, id: l.id, decision: 'reject' });
340
+ rejected += 1;
341
+ console.log(` ✗ rejected (${rejected}/${items.length}): ${l.title}`);
342
+ } catch (err) {
343
+ console.error(` ! failed to reject ${l.id}: ${err.message}`);
344
+ }
345
+ }
346
+ console.log(`\nDone. Rejected ${rejected} of ${items.length}.`);
347
+ return;
348
+ }
349
+
350
+ // ── interactive (default) ─────────────────────────────────────────────────
351
+ console.log(`\n${total} learning(s) pending your review.`);
352
+ console.log('For each: [a]pprove (go live) · [r]eject (stays private) · [s]kip · [q]uit\n');
353
+
354
+ let approved = 0, rejected = 0, skipped = 0, seen = 0;
355
+ for (const l of items) {
356
+ seen += 1;
357
+ printCandidate(l, seen, items.length);
358
+ let choice = '';
359
+ while (!['a', 'r', 's', 'q'].includes(choice)) {
360
+ choice = (await ask(' approve / reject / skip / quit [a/r/s/q]? ')).toLowerCase().slice(0, 1);
361
+ }
362
+ if (choice === 'q') { console.log(' Stopping. Remaining items left pending.'); break; }
363
+ if (choice === 's') { skipped += 1; console.log(' → skipped (still pending)'); continue; }
364
+ try {
365
+ if (choice === 'a') {
366
+ await review.submitDecision({ apiKey, baseUrl, id: l.id, decision: 'approve' });
367
+ approved += 1; console.log(' ✓ approved — now live');
368
+ } else {
369
+ const reason = await ask(' reason (optional, Enter to skip): ');
370
+ await review.submitDecision({ apiKey, baseUrl, id: l.id, decision: 'reject', reason: reason || undefined });
371
+ rejected += 1; console.log(' ✗ rejected — stays private');
372
+ }
373
+ } catch (err) {
374
+ console.error(` ! decision failed: ${err.message} (item left pending)`);
375
+ }
376
+ }
377
+
378
+ console.log(`\nReview complete: approved ${approved}, rejected ${rejected}, skipped ${skipped} of ${items.length}.`);
379
+ }
380
+
381
+ // ─── Entry point ────────────────────────────────────────────────────────────
382
+
383
+ function usage() {
384
+ console.log(`
385
+ Usage: auxilo <command>
386
+
387
+ Commands:
388
+ setup Interactive install: client detection, MCP registration,
389
+ device-code login, extraction runner + hook, consent.
390
+ Flags: --re-auth, --base-url <url>
391
+ status Show install/auth/extraction status.
392
+ review Review YOUR pending-review learnings (from background extraction)
393
+ and approve/reject each before it goes public.
394
+ Flags: --list (inspect only), --all-reject [--yes], --base-url <url>
395
+ disable Turn off background extraction (local kill-switch; optional
396
+ server-side consent revoke).
397
+
398
+ Docs: https://auxilo.io · API: ${installer.DEFAULT_BASE_URL}
399
+ `);
400
+ }
401
+
402
+ async function main() {
403
+ const cmd = process.argv[2];
404
+ const flags = parseFlags(process.argv);
405
+ switch (cmd) {
406
+ case 'setup': return cmdSetup(flags);
407
+ case 'status': return cmdStatus(flags);
408
+ case 'review': return cmdReview(flags);
409
+ case 'disable': return cmdDisable(flags);
410
+ case 'help': case '--help': case '-h': case undefined: return usage();
411
+ default:
412
+ console.error(`Unknown command: ${cmd}`);
413
+ usage();
414
+ process.exit(1);
415
+ }
416
+ }
417
+
418
+ if (require.main === module) {
419
+ main().catch((err) => {
420
+ console.error(`auxilo: ${err.message}`);
421
+ process.exit(1);
422
+ });
423
+ }
424
+
425
+ module.exports = { parseFlags, resolveBaseUrl };