auxilo-mcp 0.7.0 → 0.8.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 +9 -9
- package/bin/auxilo-cli.js +447 -0
- package/lib/installer.js +647 -0
- package/lib/review.js +124 -0
- package/lib/sensitivity-filter.js +388 -0
- package/mcp-server.js +230 -64
- package/package.json +16 -4
- package/prompts/auxilo-learning-claude-code.md +250 -0
- package/prompts/auxilo-learning-generic.md +327 -0
- package/prompts/auxilo-learning-schema.json +98 -0
- package/scripts/hooks/auxilo-extract.sh +49 -0
- package/scripts/runner.js +843 -0
- package/scripts/sources/claude-code.js +182 -0
- package/scripts/sources/openclaw.js +171 -0
- package/scripts/sources/source.interface.js +85 -0
package/README.md
CHANGED
|
@@ -36,11 +36,11 @@ curl -X POST https://api.auxilo.io/learn \
|
|
|
36
36
|
"contributor_wallet": "0xYOUR_WALLET"
|
|
37
37
|
}'
|
|
38
38
|
|
|
39
|
-
#
|
|
40
|
-
# /discover —
|
|
41
|
-
# /skill/:id —
|
|
42
|
-
# /knowledge —
|
|
43
|
-
# /knowledge/:id — dynamic price set by
|
|
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)
|
|
@@ -109,7 +109,7 @@ Agents learn things the hard way — rate limits, undocumented behavior, workaro
|
|
|
109
109
|
|
|
110
110
|
**How it works:**
|
|
111
111
|
1. **Contribute** (free) — Submit what you learned. Set your own unlock price (min $0.005).
|
|
112
|
-
2. **Search** (
|
|
112
|
+
2. **Search** (free) — Find relevant learnings. Returns titles, snippets, and unlock prices.
|
|
113
113
|
3. **Unlock** (dynamic) — Read the full learning. Price set by contributor. 70% goes to them.
|
|
114
114
|
4. **Rate** (free) — Rate helpfulness 1-5. Higher-rated learnings rank higher.
|
|
115
115
|
|
|
@@ -145,10 +145,10 @@ GET /.well-known/agent.json
|
|
|
145
145
|
| GET | `/health` | Free | Health check |
|
|
146
146
|
| GET | `/categories` | Free | Skill categories with counts |
|
|
147
147
|
| GET | `/stats` | Free | Registry statistics |
|
|
148
|
-
| POST | `/discover` |
|
|
149
|
-
| GET | `/skill/:id` |
|
|
148
|
+
| POST | `/discover` | Free | Search skills registry |
|
|
149
|
+
| GET | `/skill/:id` | Free | Full skill details |
|
|
150
150
|
| POST | `/learn` | Free | Submit a learning |
|
|
151
|
-
| POST | `/knowledge` |
|
|
151
|
+
| POST | `/knowledge` | Free | Search knowledge (snippets) |
|
|
152
152
|
| GET | `/knowledge/stats` | Free | Marketplace statistics |
|
|
153
153
|
| GET | `/knowledge/:id` | Dynamic (min $0.005) | Unlock full learning (price set by contributor) |
|
|
154
154
|
| POST | `/knowledge/:id/rate` | Free | Rate a learning |
|
|
@@ -0,0 +1,447 @@
|
|
|
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
|
+
// LW-17: ONE shared readline interface for the whole run. Creating a fresh
|
|
30
|
+
// interface per question loses any input readline already buffered when the
|
|
31
|
+
// previous interface closed — with piped/scripted stdin the answer to
|
|
32
|
+
// question N+1 arrives with question N's buffer and the next prompt hangs
|
|
33
|
+
// on EOF (the 0.8.1 consent step silently never completed).
|
|
34
|
+
let sharedRl = null;
|
|
35
|
+
function getRl() {
|
|
36
|
+
if (!sharedRl) {
|
|
37
|
+
sharedRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
38
|
+
}
|
|
39
|
+
return sharedRl;
|
|
40
|
+
}
|
|
41
|
+
function closeRl() {
|
|
42
|
+
if (sharedRl) { sharedRl.close(); sharedRl = null; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ask(question) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
getRl().question(question, (answer) => resolve(answer.trim()));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Yes/no prompt. defaultYes=false → consent-grade default-No (spec §LW-12 step 5). */
|
|
52
|
+
async function askYesNo(question, defaultYes = false) {
|
|
53
|
+
const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] ';
|
|
54
|
+
const answer = (await ask(question + suffix)).toLowerCase();
|
|
55
|
+
if (answer === '') return defaultYes;
|
|
56
|
+
return answer === 'y' || answer === 'yes';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function openBrowser(url) {
|
|
60
|
+
const cmd = process.platform === 'darwin' ? 'open'
|
|
61
|
+
: process.platform === 'win32' ? 'start ""'
|
|
62
|
+
: 'xdg-open';
|
|
63
|
+
exec(`${cmd} "${url}"`, () => { /* best-effort; URL is printed regardless */ });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveBaseUrl(flags) {
|
|
67
|
+
if (flags['base-url']) return flags['base-url'];
|
|
68
|
+
if (process.env.AUXILO_BASE_URL) return process.env.AUXILO_BASE_URL;
|
|
69
|
+
const creds = installer.readCredentials(HOME);
|
|
70
|
+
if (creds && creds.base_url) return creds.base_url;
|
|
71
|
+
return installer.DEFAULT_BASE_URL;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseFlags(argv) {
|
|
75
|
+
const flags = {};
|
|
76
|
+
for (let i = 3; i < argv.length; i++) {
|
|
77
|
+
const m = /^--([a-z-]+)(?:=(.*))?$/.exec(argv[i]);
|
|
78
|
+
if (!m) continue;
|
|
79
|
+
if (m[2] !== undefined) flags[m[1]] = m[2];
|
|
80
|
+
else if (argv[i + 1] && !argv[i + 1].startsWith('--')) flags[m[1]] = argv[++i];
|
|
81
|
+
else flags[m[1]] = true;
|
|
82
|
+
}
|
|
83
|
+
return flags;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── auxilo setup ───────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
const CONSENT_TEXT = `
|
|
89
|
+
Background extraction (optional)
|
|
90
|
+
--------------------------------
|
|
91
|
+
If you opt in, a SessionEnd hook runs after each Claude Code session and a
|
|
92
|
+
local runner:
|
|
93
|
+
• READS the session transcript from your machine,
|
|
94
|
+
• SCRUBS it locally (sensitivity filter: API keys, tokens, emails, PII —
|
|
95
|
+
redacted BEFORE anything leaves your machine),
|
|
96
|
+
• UPLOADS only the scrubbed transcript to Auxilo (${'POST /extract'}),
|
|
97
|
+
where reusable learnings are extracted, quality-gated, moderated, and
|
|
98
|
+
published to the marketplace under your account. You earn 70% of sales.
|
|
99
|
+
You can stop any time with \`auxilo disable\` (local kill-switch) and review
|
|
100
|
+
every run in ~/.auxilo/extract.log. Saying No installs the MCP server only.
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
async function cmdSetup(flags) {
|
|
104
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
105
|
+
console.log('\nAuxilo turnkey setup');
|
|
106
|
+
console.log('====================');
|
|
107
|
+
console.log(`Server: ${baseUrl}\n`);
|
|
108
|
+
|
|
109
|
+
// 1. Client detection ------------------------------------------------------
|
|
110
|
+
const detected = installer.detectClients(HOME);
|
|
111
|
+
if (detected.length === 0) {
|
|
112
|
+
console.log('No supported clients detected (Claude Code, Claude Desktop, Cursor, OpenClaw).');
|
|
113
|
+
console.log('Install one, then re-run `npx auxilo setup`.');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log('Detected clients:');
|
|
118
|
+
detected.forEach((c, i) => {
|
|
119
|
+
const extras = [c.mcp ? 'MCP' : 'poll-based source', c.hooks ? 'background extraction' : null]
|
|
120
|
+
.filter(Boolean).join(', ');
|
|
121
|
+
console.log(` ${i + 1}. ${c.name} (${extras})`);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
let chosen = detected;
|
|
125
|
+
const pick = await ask(`\nConfigure which clients? [all] (e.g. "1,3" or "all") `);
|
|
126
|
+
if (pick && pick.toLowerCase() !== 'all') {
|
|
127
|
+
const idx = pick.split(',').map((s) => parseInt(s.trim(), 10) - 1);
|
|
128
|
+
chosen = detected.filter((_, i) => idx.includes(i));
|
|
129
|
+
if (chosen.length === 0) {
|
|
130
|
+
console.log('Nothing selected — exiting without changes.');
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 2. MCP registration ------------------------------------------------------
|
|
136
|
+
console.log('');
|
|
137
|
+
for (const client of chosen.filter((c) => c.mcp)) {
|
|
138
|
+
try {
|
|
139
|
+
const result = installer.registerMcp(client);
|
|
140
|
+
console.log(result.changed
|
|
141
|
+
? ` ✓ ${client.name}: registered Auxilo MCP server (${result.configPath})`
|
|
142
|
+
: ` ✓ ${client.name}: already registered (no changes)`);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
// B15: malformed config — skip loudly, never overwrite.
|
|
145
|
+
console.error(` ✗ ${client.name}: SKIPPED — ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const openclaw = chosen.find((c) => c.id === 'openclaw');
|
|
149
|
+
if (openclaw) {
|
|
150
|
+
console.log(' ✓ OpenClaw: no MCP config needed — covered by the poll-based source adapter.');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 3. Device-code auth ------------------------------------------------------
|
|
154
|
+
console.log('');
|
|
155
|
+
let creds = installer.readCredentials(HOME);
|
|
156
|
+
if (creds && creds.api_key && !flags['re-auth']) {
|
|
157
|
+
console.log(` ✓ Already authenticated as ${creds.email || creds.account_id || 'existing account'}`);
|
|
158
|
+
console.log(' (use `auxilo setup --re-auth` to log in again)');
|
|
159
|
+
} else {
|
|
160
|
+
console.log(' Logging in to Auxilo (device code flow)...');
|
|
161
|
+
try {
|
|
162
|
+
const result = await installer.deviceLogin({
|
|
163
|
+
baseUrl,
|
|
164
|
+
onCode: (code, url) => {
|
|
165
|
+
console.log(`\n Your device code: ${code}`);
|
|
166
|
+
console.log(` Verify at: ${url}\n Waiting for authorization (Ctrl-C to abort)...`);
|
|
167
|
+
openBrowser(url);
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
installer.writeCredentials(HOME, {
|
|
171
|
+
api_key: result.api_key,
|
|
172
|
+
base_url: result.base_url,
|
|
173
|
+
email: result.email,
|
|
174
|
+
account_id: result.account_id,
|
|
175
|
+
});
|
|
176
|
+
creds = installer.readCredentials(HOME);
|
|
177
|
+
console.log(` ✓ Logged in as ${result.email || result.account_id}`);
|
|
178
|
+
console.log(` ✓ Credentials saved to ~/.auxilo/credentials.json (mode 0600)`);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
console.error(` ✗ Login failed: ${err.message}`);
|
|
181
|
+
console.error(' MCP registration (above) is still in place. Re-run `auxilo setup` to retry login.');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 4. Runner install + Claude Code hook --------------------------------------
|
|
187
|
+
console.log('');
|
|
188
|
+
try {
|
|
189
|
+
const { binRoot } = installer.installRunner(HOME);
|
|
190
|
+
console.log(` ✓ Extraction runner installed to ${binRoot}`);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
console.error(` ✗ Runner install failed: ${err.message}`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const claudeCode = chosen.find((c) => c.id === 'claude-code');
|
|
197
|
+
if (claudeCode) {
|
|
198
|
+
try {
|
|
199
|
+
const hook = installer.registerClaudeCodeHook(HOME);
|
|
200
|
+
if (hook.changed) {
|
|
201
|
+
console.log(` ✓ Claude Code SessionEnd hook registered (${hook.hookCmd})`);
|
|
202
|
+
if (hook.removedLegacy.length > 0) {
|
|
203
|
+
console.log(` (replaced legacy hook entr${hook.removedLegacy.length === 1 ? 'y' : 'ies'}: ${hook.removedLegacy.join(', ')})`);
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
console.log(' ✓ Claude Code SessionEnd hook already registered (no changes)');
|
|
207
|
+
}
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.error(` ✗ Hook registration SKIPPED — ${err.message}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 5. Explicit consent (default No) ------------------------------------------
|
|
214
|
+
console.log(CONSENT_TEXT);
|
|
215
|
+
const consented = await askYesNo(' Enable background extraction?', false);
|
|
216
|
+
if (consented) {
|
|
217
|
+
try {
|
|
218
|
+
await installer.recordConsent({ action: 'grant', apiKey: creds.api_key, baseUrl });
|
|
219
|
+
installer.enableSentinel(HOME);
|
|
220
|
+
console.log(' ✓ Consent recorded; background extraction ENABLED (~/.auxilo/autonomous-enabled)');
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error(` ✗ Could not record consent on server: ${err.message}`);
|
|
223
|
+
console.error(' Background extraction NOT enabled (no sentinel created). Re-run `auxilo setup` to retry.');
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
console.log(' ✓ Background extraction left OFF (MCP-only install). Enable later by re-running `auxilo setup`.');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log('\nDone. Check `npx auxilo status` any time. Restart your client(s) to pick up the MCP server.\n');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ─── auxilo status ──────────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
async function cmdStatus() {
|
|
235
|
+
const s = await installer.getStatus(HOME);
|
|
236
|
+
|
|
237
|
+
console.log('\nAuxilo status');
|
|
238
|
+
console.log('=============');
|
|
239
|
+
|
|
240
|
+
console.log('Clients:');
|
|
241
|
+
if (s.clients.length === 0) console.log(' (none detected)');
|
|
242
|
+
for (const c of s.clients) {
|
|
243
|
+
const reg = c.mcp
|
|
244
|
+
? (c.registered ? 'MCP registered' : 'detected, MCP NOT registered')
|
|
245
|
+
: 'poll-based source (no MCP config)';
|
|
246
|
+
console.log(` ${c.name}: ${reg}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log(`Auth: ${s.auth.credentialsFile
|
|
250
|
+
? `credentials present (key ${s.auth.keyPrefix || 'unreadable'}${s.auth.email ? `, ${s.auth.email}` : ''})`
|
|
251
|
+
: 'NOT logged in (run `npx auxilo setup`)'}`);
|
|
252
|
+
console.log(`Account mode: ${s.accountMode}`);
|
|
253
|
+
console.log(`Kill-switch sentinel: ${s.sentinel ? 'present (extraction enabled)' : 'absent (extraction disabled)'}`);
|
|
254
|
+
console.log(`Runner installed: ${s.runnerInstalled ? 'yes (~/.auxilo/bin)' : 'no'}`);
|
|
255
|
+
console.log(`SessionEnd hook: ${s.hookInstalled ? 'installed' : 'not installed'}${s.hookRegistered ? ', registered in Claude Code settings' : ''}`);
|
|
256
|
+
console.log(`Last extraction sweep: ${s.lastSweep || 'never'}`);
|
|
257
|
+
console.log(`Pending upload queue: ${s.pendingCount} file(s)\n`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ─── auxilo disable ─────────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
async function cmdDisable(flags) {
|
|
263
|
+
const removed = installer.disableSentinel(HOME);
|
|
264
|
+
console.log(removed
|
|
265
|
+
? '✓ Kill-switch sentinel removed — background extraction is now DISABLED locally.'
|
|
266
|
+
: '✓ Sentinel already absent — background extraction was not enabled.');
|
|
267
|
+
|
|
268
|
+
const creds = installer.readCredentials(HOME);
|
|
269
|
+
if (creds && creds.api_key) {
|
|
270
|
+
const revoke = await askYesNo('Also revoke extraction consent on the server?', false);
|
|
271
|
+
if (revoke) {
|
|
272
|
+
try {
|
|
273
|
+
await installer.recordConsent({
|
|
274
|
+
action: 'revoke',
|
|
275
|
+
apiKey: creds.api_key,
|
|
276
|
+
baseUrl: resolveBaseUrl(flags),
|
|
277
|
+
});
|
|
278
|
+
console.log('✓ Consent revoked on server (account mode set to off).');
|
|
279
|
+
} catch (err) {
|
|
280
|
+
console.error(`✗ Server revoke failed: ${err.message} — local kill-switch is still in effect.`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
console.log('Note: MCP server registrations are left in place (remove manually from client configs if desired).');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ─── auxilo review ──────────────────────────────────────────────────────────
|
|
288
|
+
//
|
|
289
|
+
// The human gate that makes background extraction safe to re-enable:
|
|
290
|
+
// extract -> pending_review -> `auxilo review` -> approve the safe ones.
|
|
291
|
+
// Operates ONLY on the caller's own pending learnings (account-scoped API key).
|
|
292
|
+
|
|
293
|
+
/** Print one candidate's full detail for the interactive reviewer. */
|
|
294
|
+
function printCandidate(l, n, total) {
|
|
295
|
+
console.log(`\n──────────────────────────────────────────────────────────────`);
|
|
296
|
+
console.log(`[${n}/${total}] ${l.title}`);
|
|
297
|
+
console.log(` id: ${l.id}`);
|
|
298
|
+
console.log(` category: ${l.category}${l.outcome ? ` outcome: ${l.outcome}` : ''}`);
|
|
299
|
+
if (l.tags && l.tags.length) console.log(` tags: ${l.tags.join(', ')}`);
|
|
300
|
+
if (l.created_at) console.log(` created: ${l.created_at}`);
|
|
301
|
+
const flags = review.formatFlags(l);
|
|
302
|
+
if (flags) console.log(` ⚠ flags: ${flags}`);
|
|
303
|
+
console.log(` ----------------------------------------------------------`);
|
|
304
|
+
console.log(l.body || '(no body)');
|
|
305
|
+
console.log(` ----------------------------------------------------------`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function cmdReview(flags) {
|
|
309
|
+
const creds = installer.readCredentials(HOME);
|
|
310
|
+
if (!creds || !creds.api_key) {
|
|
311
|
+
console.error('Not logged in. Run `npx auxilo setup` first.');
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
const baseUrl = resolveBaseUrl(flags);
|
|
315
|
+
const apiKey = creds.api_key;
|
|
316
|
+
|
|
317
|
+
let res;
|
|
318
|
+
try {
|
|
319
|
+
res = await review.fetchPending({ apiKey, baseUrl, limit: 200 });
|
|
320
|
+
} catch (err) {
|
|
321
|
+
console.error(`Could not fetch pending review queue: ${err.message}`);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const items = res.learnings || [];
|
|
326
|
+
const total = res.pending_count != null ? res.pending_count : items.length;
|
|
327
|
+
|
|
328
|
+
if (items.length === 0) {
|
|
329
|
+
console.log('No learnings pending your review. Nothing to do.');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── --list: non-interactive inspection, no mutations ──────────────────────
|
|
334
|
+
if (flags.list) {
|
|
335
|
+
console.log(`\n${total} learning(s) pending your review:\n`);
|
|
336
|
+
items.forEach((l, i) => {
|
|
337
|
+
const flagStr = review.formatFlags(l);
|
|
338
|
+
console.log(` ${i + 1}. ${l.title} [${l.id}]${flagStr ? ` ⚠ ${flagStr}` : ''}`);
|
|
339
|
+
});
|
|
340
|
+
console.log('');
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ── --all-reject: bulk reject the whole batch (incident escape hatch) ─────
|
|
345
|
+
if (flags['all-reject']) {
|
|
346
|
+
const ok = flags.yes || await askYesNo(`Reject ALL ${items.length} pending learning(s)? This cannot be undone`, false);
|
|
347
|
+
if (!ok) { console.log('Aborted — nothing changed.'); return; }
|
|
348
|
+
let rejected = 0;
|
|
349
|
+
for (const l of items) {
|
|
350
|
+
try {
|
|
351
|
+
await review.submitDecision({ apiKey, baseUrl, id: l.id, decision: 'reject' });
|
|
352
|
+
rejected += 1;
|
|
353
|
+
console.log(` ✗ rejected (${rejected}/${items.length}): ${l.title}`);
|
|
354
|
+
} catch (err) {
|
|
355
|
+
console.error(` ! failed to reject ${l.id}: ${err.message}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
console.log(`\nDone. Rejected ${rejected} of ${items.length}.`);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ── interactive (default) ─────────────────────────────────────────────────
|
|
363
|
+
console.log(`\n${total} learning(s) pending your review.`);
|
|
364
|
+
console.log('For each: [a]pprove (go live) · [r]eject (stays private) · [s]kip · [q]uit\n');
|
|
365
|
+
|
|
366
|
+
let approved = 0, rejected = 0, skipped = 0, seen = 0;
|
|
367
|
+
for (const l of items) {
|
|
368
|
+
seen += 1;
|
|
369
|
+
printCandidate(l, seen, items.length);
|
|
370
|
+
let choice = '';
|
|
371
|
+
while (!['a', 'r', 's', 'q'].includes(choice)) {
|
|
372
|
+
choice = (await ask(' approve / reject / skip / quit [a/r/s/q]? ')).toLowerCase().slice(0, 1);
|
|
373
|
+
}
|
|
374
|
+
if (choice === 'q') { console.log(' Stopping. Remaining items left pending.'); break; }
|
|
375
|
+
if (choice === 's') { skipped += 1; console.log(' → skipped (still pending)'); continue; }
|
|
376
|
+
try {
|
|
377
|
+
if (choice === 'a') {
|
|
378
|
+
await review.submitDecision({ apiKey, baseUrl, id: l.id, decision: 'approve' });
|
|
379
|
+
approved += 1; console.log(' ✓ approved — now live');
|
|
380
|
+
} else {
|
|
381
|
+
const reason = await ask(' reason (optional, Enter to skip): ');
|
|
382
|
+
await review.submitDecision({ apiKey, baseUrl, id: l.id, decision: 'reject', reason: reason || undefined });
|
|
383
|
+
rejected += 1; console.log(' ✗ rejected — stays private');
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
console.error(` ! decision failed: ${err.message} (item left pending)`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
console.log(`\nReview complete: approved ${approved}, rejected ${rejected}, skipped ${skipped} of ${items.length}.`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ─── Entry point ────────────────────────────────────────────────────────────
|
|
394
|
+
|
|
395
|
+
function usage() {
|
|
396
|
+
console.log(`
|
|
397
|
+
Usage: auxilo <command>
|
|
398
|
+
|
|
399
|
+
Commands:
|
|
400
|
+
setup Interactive install: client detection, MCP registration,
|
|
401
|
+
device-code login, extraction runner + hook, consent.
|
|
402
|
+
Flags: --re-auth, --base-url <url>
|
|
403
|
+
status Show install/auth/extraction status.
|
|
404
|
+
review Review YOUR pending-review learnings (from background extraction)
|
|
405
|
+
and approve/reject each before it goes public.
|
|
406
|
+
Flags: --list (inspect only), --all-reject [--yes], --base-url <url>
|
|
407
|
+
disable Turn off background extraction (local kill-switch; optional
|
|
408
|
+
server-side consent revoke).
|
|
409
|
+
|
|
410
|
+
Docs: https://auxilo.io · API: ${installer.DEFAULT_BASE_URL}
|
|
411
|
+
`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function main() {
|
|
415
|
+
const cmd = process.argv[2];
|
|
416
|
+
const flags = parseFlags(process.argv);
|
|
417
|
+
switch (cmd) {
|
|
418
|
+
case 'setup': return cmdSetup(flags);
|
|
419
|
+
case 'status': return cmdStatus(flags);
|
|
420
|
+
case 'review': return cmdReview(flags);
|
|
421
|
+
case 'disable': return cmdDisable(flags);
|
|
422
|
+
case 'help': case '--help': case '-h': case undefined: return usage();
|
|
423
|
+
default:
|
|
424
|
+
console.error(`Unknown command: ${cmd}`);
|
|
425
|
+
usage();
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Run the CLI: used by require.main below AND by mcp-server.js delegation
|
|
431
|
+
* (LW-17: `npx auxilo-mcp setup` — the `auxilo` bin alias is unreachable via
|
|
432
|
+
* npx until the `auxilo` npm package name is claimed). */
|
|
433
|
+
function run() {
|
|
434
|
+
main().then(() => {
|
|
435
|
+
closeRl(); // an open readline interface keeps the process alive
|
|
436
|
+
}).catch((err) => {
|
|
437
|
+
closeRl();
|
|
438
|
+
console.error(`auxilo: ${err.message}`);
|
|
439
|
+
process.exit(1);
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (require.main === module) {
|
|
444
|
+
run();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
module.exports = { parseFlags, resolveBaseUrl, run };
|