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 +49 -14
- package/bin/auxilo-cli.js +425 -0
- package/lib/installer.js +601 -0
- package/lib/review.js +124 -0
- package/lib/sensitivity-filter.js +382 -0
- package/mcp-server.js +454 -48
- package/package.json +22 -5
- 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
|
@@ -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://
|
|
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://
|
|
21
|
+
curl https://api.auxilo.io/categories
|
|
22
22
|
|
|
23
23
|
# Free — marketplace stats
|
|
24
|
-
curl https://
|
|
24
|
+
curl https://api.auxilo.io/knowledge/stats
|
|
25
25
|
|
|
26
26
|
# Free — submit a learning
|
|
27
|
-
curl -X POST https://
|
|
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
|
-
#
|
|
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)
|
|
@@ -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** (
|
|
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` |
|
|
130
|
-
| GET | `/skill/:id` |
|
|
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` |
|
|
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 };
|