auxilo-mcp 0.8.1 → 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/bin/auxilo-cli.js +30 -8
- package/lib/installer.js +65 -19
- package/lib/sensitivity-filter.js +7 -1
- package/mcp-server.js +20 -7
- package/package.json +1 -1
package/bin/auxilo-cli.js
CHANGED
|
@@ -26,13 +26,25 @@ const HOME = os.homedir();
|
|
|
26
26
|
|
|
27
27
|
// ─── Prompt helpers ─────────────────────────────────────────────────────────
|
|
28
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
|
+
|
|
29
45
|
function ask(question) {
|
|
30
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
31
46
|
return new Promise((resolve) => {
|
|
32
|
-
|
|
33
|
-
rl.close();
|
|
34
|
-
resolve(answer.trim());
|
|
35
|
-
});
|
|
47
|
+
getRl().question(question, (answer) => resolve(answer.trim()));
|
|
36
48
|
});
|
|
37
49
|
}
|
|
38
50
|
|
|
@@ -415,11 +427,21 @@ async function main() {
|
|
|
415
427
|
}
|
|
416
428
|
}
|
|
417
429
|
|
|
418
|
-
|
|
419
|
-
|
|
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();
|
|
420
438
|
console.error(`auxilo: ${err.message}`);
|
|
421
439
|
process.exit(1);
|
|
422
440
|
});
|
|
423
441
|
}
|
|
424
442
|
|
|
425
|
-
|
|
443
|
+
if (require.main === module) {
|
|
444
|
+
run();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
module.exports = { parseFlags, resolveBaseUrl, run };
|
package/lib/installer.js
CHANGED
|
@@ -246,8 +246,11 @@ async function deviceLogin(opts = {}) {
|
|
|
246
246
|
});
|
|
247
247
|
if (!res.ok) throw new Error(`Device code request failed (HTTP ${res.status})`);
|
|
248
248
|
const device = await res.json();
|
|
249
|
-
|
|
249
|
+
// A-1: device_code is the secret polling credential; user_code is the human
|
|
250
|
+
// code shown on the verification page only.
|
|
251
|
+
const { user_code: userCode, device_code: deviceCode, verification_url: verificationUrl } = device;
|
|
250
252
|
if (!userCode) throw new Error('Device code request failed: no user_code in response');
|
|
253
|
+
if (!deviceCode) throw new Error('Device code request failed: no device_code in response');
|
|
251
254
|
|
|
252
255
|
// Server returns interval in seconds (default 5 per server.js /auth/device).
|
|
253
256
|
const intervalMs = (device.interval || 5) * 1000;
|
|
@@ -259,7 +262,7 @@ async function deviceLogin(opts = {}) {
|
|
|
259
262
|
await sleep(intervalMs);
|
|
260
263
|
let status;
|
|
261
264
|
try {
|
|
262
|
-
const poll = await fetchImpl(`${baseUrl}/auth/device/status?
|
|
265
|
+
const poll = await fetchImpl(`${baseUrl}/auth/device/status?device_code=${encodeURIComponent(deviceCode)}`);
|
|
263
266
|
status = await poll.json();
|
|
264
267
|
} catch {
|
|
265
268
|
continue; // transient network error during poll — keep polling
|
|
@@ -338,7 +341,10 @@ LOG="${auxiloDir}/extract.log"
|
|
|
338
341
|
# P1-13 lesson: do NOT set AUXILO_EXTRACTING here — runner.js's own recursion
|
|
339
342
|
# guard would trip and silently no-op every run. The runner sets it itself
|
|
340
343
|
# for child processes.
|
|
341
|
-
|
|
344
|
+
# LW-17: stdout goes to /dev/null — runner.js log() already appends every
|
|
345
|
+
# line to extract.log itself; redirecting stdout there too double-wrote each
|
|
346
|
+
# line and the daily digest double-counted. stderr still captured for crashes.
|
|
347
|
+
nohup /usr/bin/env node "$RUNNER" --transcript "$transcript_path" > /dev/null 2>> "$LOG" &
|
|
342
348
|
|
|
343
349
|
exit 0
|
|
344
350
|
`;
|
|
@@ -385,14 +391,27 @@ function installRunner(homeDir, opts = {}) {
|
|
|
385
391
|
|
|
386
392
|
// ─── Claude Code hook registration (spec §LW-12 step 4) ─────────────────────
|
|
387
393
|
|
|
394
|
+
/** True when an inner hook object is an Auxilo extraction command. */
|
|
395
|
+
function isAuxiloCommandHook(h) {
|
|
396
|
+
return h && typeof h === 'object' && typeof h.command === 'string' &&
|
|
397
|
+
h.command.includes('auxilo-extract');
|
|
398
|
+
}
|
|
399
|
+
|
|
388
400
|
/**
|
|
389
401
|
* Patch <home>/.claude/settings.json hooks.SessionEnd[] to contain exactly one
|
|
390
|
-
* Auxilo entry: <home>/.auxilo/bin/auxilo-extract.sh
|
|
402
|
+
* Auxilo entry: <home>/.auxilo/bin/auxilo-extract.sh, in Claude Code's
|
|
403
|
+
* STRUCTURED form ({ hooks: [{ type: 'command', command }] }).
|
|
404
|
+
*
|
|
405
|
+
* LW-17: 0.8.1 appended the command as a bare string — Claude Code silently
|
|
406
|
+
* ignores string entries, so the hook NEVER fired. It also only removed
|
|
407
|
+
* legacy entries that were strings, missing the pre-LW-12 structured entry
|
|
408
|
+
* (~/.claude/hooks/auxilo-extract.sh nested inside a matcher group).
|
|
391
409
|
*
|
|
392
|
-
* Idempotent.
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
* Malformed settings.json throws (B15) — never
|
|
410
|
+
* Idempotent. Removes every Auxilo reference (bare strings, and command
|
|
411
|
+
* objects inside matcher groups — preserving any non-Auxilo commands sharing
|
|
412
|
+
* the group), then appends one canonical structured entry. Non-Auxilo entries
|
|
413
|
+
* are preserved untouched. Malformed settings.json throws (B15) — never
|
|
414
|
+
* overwritten.
|
|
396
415
|
*
|
|
397
416
|
* @returns {{ changed: boolean, hookCmd: string, removedLegacy: string[] }}
|
|
398
417
|
*/
|
|
@@ -406,21 +425,44 @@ function registerClaudeCodeHook(homeDir) {
|
|
|
406
425
|
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
407
426
|
|
|
408
427
|
const before = settings.hooks.SessionEnd;
|
|
409
|
-
const removedLegacy =
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
428
|
+
const removedLegacy = [];
|
|
429
|
+
const kept = [];
|
|
430
|
+
|
|
431
|
+
for (const entry of before) {
|
|
432
|
+
if (typeof entry === 'string' && entry.includes('auxilo-extract')) {
|
|
433
|
+
removedLegacy.push(entry); // includes 0.8.1's dead bare-string entry
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (entry && typeof entry === 'object' && Array.isArray(entry.hooks)) {
|
|
437
|
+
const auxilo = entry.hooks.filter(isAuxiloCommandHook);
|
|
438
|
+
if (auxilo.length > 0) {
|
|
439
|
+
removedLegacy.push(...auxilo.map((h) => h.command));
|
|
440
|
+
const rest = entry.hooks.filter((h) => !isAuxiloCommandHook(h));
|
|
441
|
+
if (rest.length > 0) kept.push({ ...entry, hooks: rest });
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
kept.push(entry);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const canonical = { hooks: [{ type: 'command', command: hookCmd }] };
|
|
449
|
+
|
|
450
|
+
// Idempotency: unchanged iff the only Auxilo reference found was already
|
|
451
|
+
// the canonical command in a structured single-command entry.
|
|
452
|
+
const alreadyExact =
|
|
453
|
+
removedLegacy.length === 1 && removedLegacy[0] === hookCmd &&
|
|
454
|
+
before.some((e) =>
|
|
455
|
+
e && typeof e === 'object' && Array.isArray(e.hooks) &&
|
|
456
|
+
e.hooks.length === 1 && isAuxiloCommandHook(e.hooks[0]) &&
|
|
457
|
+
e.hooks[0].command === hookCmd && e.hooks[0].type === 'command'
|
|
458
|
+
);
|
|
417
459
|
if (alreadyExact) {
|
|
418
460
|
return { changed: false, hookCmd, removedLegacy: [] };
|
|
419
461
|
}
|
|
420
462
|
|
|
421
|
-
settings.hooks.SessionEnd = [...kept,
|
|
463
|
+
settings.hooks.SessionEnd = [...kept, canonical];
|
|
422
464
|
writeJsonAtomic(settingsPath, settings);
|
|
423
|
-
return { changed: true, hookCmd, removedLegacy };
|
|
465
|
+
return { changed: true, hookCmd, removedLegacy: removedLegacy.filter((c) => c !== hookCmd) };
|
|
424
466
|
}
|
|
425
467
|
|
|
426
468
|
// ─── Consent (spec §LW-12 step 5; server.js POST /extract/consent) ──────────
|
|
@@ -541,8 +583,12 @@ async function getStatus(homeDir, opts = {}) {
|
|
|
541
583
|
const settings = JSON.parse(
|
|
542
584
|
fs.readFileSync(path.join(homeDir, '.claude', 'settings.json'), 'utf-8')
|
|
543
585
|
);
|
|
586
|
+
// LW-17: only STRUCTURED entries count — Claude Code silently ignores
|
|
587
|
+
// bare-string entries (the 0.8.1 dead-hook bug), so reporting one as
|
|
588
|
+
// "registered" would mask exactly the failure this status exists to catch.
|
|
544
589
|
hookRegistered = Array.isArray(settings.hooks && settings.hooks.SessionEnd) &&
|
|
545
|
-
settings.hooks.SessionEnd.some((h) =>
|
|
590
|
+
settings.hooks.SessionEnd.some((h) =>
|
|
591
|
+
h && typeof h === 'object' && Array.isArray(h.hooks) && h.hooks.some(isAuxiloCommandHook));
|
|
546
592
|
} catch { /* no settings */ }
|
|
547
593
|
|
|
548
594
|
// 5. Last extraction + pending queue (ledger conventions from scripts/runner.js)
|
|
@@ -108,7 +108,13 @@ const PATTERNS = [
|
|
|
108
108
|
// ── P2.1a new patterns (§7.6 — 10 additions) ─────────────────────────────
|
|
109
109
|
{
|
|
110
110
|
name: 'email_address',
|
|
111
|
-
|
|
111
|
+
// Linearly-bounded form (D-1 ReDoS fix). The previous
|
|
112
|
+
// /[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}/ backtracked O(n^2) on a long run
|
|
113
|
+
// of dash/digit chars with no valid TLD, pinning the single-process event
|
|
114
|
+
// loop for minutes. Each quantifier here is explicitly bounded and the
|
|
115
|
+
// domain labels do not overlap with the dot separators, so there is no
|
|
116
|
+
// catastrophic-backtracking path.
|
|
117
|
+
regex: /[a-z0-9._%+\-]{1,64}@[a-z0-9](?:[a-z0-9\-]{0,253}[a-z0-9])?(?:\.[a-z]{2,24})+/gi,
|
|
112
118
|
description: 'Email address',
|
|
113
119
|
},
|
|
114
120
|
{
|
package/mcp-server.js
CHANGED
|
@@ -16,7 +16,9 @@ let credentials = {};
|
|
|
16
16
|
try {
|
|
17
17
|
credentials = JSON.parse(fs.readFileSync(CRED_PATH, 'utf8'));
|
|
18
18
|
} catch { /* no credentials file — unauthenticated mode */ }
|
|
19
|
-
|
|
19
|
+
// LW-17: default was a long-dead Conway sandbox URL — fresh installs without
|
|
20
|
+
// credentials.json pointed every tool call at it.
|
|
21
|
+
const AUXILO_BASE = credentials.base_url || 'https://auxilo.io';
|
|
20
22
|
|
|
21
23
|
function baseHeaders(extra = {}) {
|
|
22
24
|
const headers = { 'Content-Type': 'application/json', ...extra };
|
|
@@ -27,7 +29,7 @@ function baseHeaders(extra = {}) {
|
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
const server = new Server(
|
|
30
|
-
{ name: 'auxilo', version: '0.
|
|
32
|
+
{ name: 'auxilo', version: '0.8.2' },
|
|
31
33
|
{
|
|
32
34
|
capabilities: { tools: {} },
|
|
33
35
|
instructions: `You are connected to Auxilo, a knowledge marketplace where AI agents buy and sell operational learnings.
|
|
@@ -542,9 +544,18 @@ function text(obj) {
|
|
|
542
544
|
return { content: [{ type: 'text', text: JSON.stringify(obj, null, 2) }] };
|
|
543
545
|
}
|
|
544
546
|
|
|
545
|
-
// ─── CLI
|
|
546
|
-
//
|
|
547
|
-
|
|
547
|
+
// ─── CLI delegation (LW-17) ────────────────────────────────────────────────────
|
|
548
|
+
// `npx auxilo-mcp setup|status|review|disable` runs the full turnkey CLI.
|
|
549
|
+
// npx resolves PACKAGE names, not bin aliases — the documented `npx auxilo
|
|
550
|
+
// setup` 404s until the `auxilo` npm package name is claimed, so the package
|
|
551
|
+
// bin must handle these commands itself.
|
|
552
|
+
if (['setup', 'status', 'review', 'disable'].includes(process.argv[2])) {
|
|
553
|
+
require('./bin/auxilo-cli.js').run();
|
|
554
|
+
return; // module-level return in CommonJS stops the MCP server from starting
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ─── CLI: legacy setup (Change 4, pre-LW-12) — now handled by delegation above ──
|
|
558
|
+
if (process.argv[2] === '__legacy_setup_unreachable__') {
|
|
548
559
|
console.log('Note: `auxilo-mcp setup` is superseded by `npx auxilo setup` (interactive');
|
|
549
560
|
console.log('install: MCP registration + login + background extraction). Continuing with');
|
|
550
561
|
console.log('legacy MCP-registration-only setup...\n');
|
|
@@ -622,7 +633,9 @@ if (process.argv[2] === 'login') {
|
|
|
622
633
|
process.exit(1);
|
|
623
634
|
}
|
|
624
635
|
const deviceData = await deviceResp.json();
|
|
625
|
-
|
|
636
|
+
// A-1: device_code is the secret polling credential; user_code is only the
|
|
637
|
+
// human code shown on the verification page.
|
|
638
|
+
const { user_code, device_code, verification_url } = deviceData;
|
|
626
639
|
|
|
627
640
|
// 2. Display code and URL
|
|
628
641
|
console.log(`\nYour device code: ${user_code}`);
|
|
@@ -639,7 +652,7 @@ if (process.argv[2] === 'login') {
|
|
|
639
652
|
for (let i = 0; i < maxAttempts; i++) {
|
|
640
653
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
641
654
|
try {
|
|
642
|
-
const statusResp = await fetch(`${AUXILO_BASE}/auth/device/status?
|
|
655
|
+
const statusResp = await fetch(`${AUXILO_BASE}/auth/device/status?device_code=${encodeURIComponent(device_code)}`);
|
|
643
656
|
const statusData = await statusResp.json();
|
|
644
657
|
|
|
645
658
|
if (statusData.status === 'authorized') {
|