auxilo-mcp 0.9.4 → 0.9.7
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 +8 -6
- package/bin/auxilo-cli.js +172 -41
- package/config/near-duplicate.json +9 -0
- package/lib/extraction-index.js +462 -0
- package/lib/hook-status.js +24 -0
- package/lib/installer.js +14 -3
- package/lib/review.js +70 -16
- package/lib/similarity.js +275 -0
- package/mcp-server.js +1 -1
- package/package.json +5 -1
- package/scripts/extract-local.js +442 -25
- package/scripts/runner.js +53 -5
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Enable extraction and a session-end hook runs when your agent finishes a session
|
|
|
33
33
|
1. The hook hands the runner the path to the session transcript.
|
|
34
34
|
2. The runner reads the transcript on your machine and scrubs it with a fail-closed secret filter: 24 patterns covering API keys, tokens, private keys, JWTs, connection strings, cookies, email addresses, phone numbers, and internal IPs. If a rescan still finds a match, the run stops and nothing is sent.
|
|
35
35
|
3. Your own model client (your claude CLI, on your subscription) drafts learnings from the scrubbed text and screens them again.
|
|
36
|
-
4.
|
|
36
|
+
4. Drafts held for review appear in three server-defined lanes: **Ready to publish**, **Needs a score**, and **Needs your eyes**. The last lane includes the server's reason so you can inspect the exception directly.
|
|
37
37
|
|
|
38
38
|
### What never leaves your machine
|
|
39
39
|
|
|
@@ -41,15 +41,17 @@ Your raw transcripts. They are read and scrubbed on your machine, and they are n
|
|
|
41
41
|
|
|
42
42
|
### How publishing works
|
|
43
43
|
|
|
44
|
-
Extraction defaults to seamless: a draft that passes every screen (secrets, sensitivity, injection, near-duplicate, quality) publishes right away, and you can retract it for 7 days. A draft
|
|
44
|
+
Extraction defaults to seamless: a draft that passes every screen (secrets, sensitivity, injection, near-duplicate, quality) publishes right away, and you can retract it for 7 days. A draft held for review waits in a pending queue only you can see, grouped by the server's Ready to publish / Needs a score / Needs your eyes verdict.
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
npx auxilo review
|
|
48
|
-
npx auxilo
|
|
49
|
-
npx auxilo
|
|
47
|
+
npx auxilo review --list # show all three lanes and each exception reason
|
|
48
|
+
npx auxilo review --approve-ready # select exactly the server's ready_to_publish lane
|
|
49
|
+
npx auxilo review # approve, reject, view, or skip one draft at a time
|
|
50
|
+
npx auxilo status # clients, hooks, queue depth, consent state
|
|
51
|
+
npx auxilo disable # kill switch: extraction stops immediately
|
|
50
52
|
```
|
|
51
53
|
|
|
52
|
-
Approve a queued draft and it goes live in the marketplace. Reject it and it stays private. Prefer approve-first for everything? Switch your account to manual mode in account settings and every draft waits for you.
|
|
54
|
+
Every bulk approval prints the exact selection and requires you to type its count. `--min-quality 16` narrows the ready lane; values below 14 explicitly reach into Needs a score and print a warning before the same counted confirmation. Approve a queued draft and it goes live in the marketplace. Reject it and it stays private. Prefer approve-first for everything? Switch your account to manual mode in account settings and every draft waits for you.
|
|
53
55
|
|
|
54
56
|
Extraction off? Your agent can still contribute in-session: tell it to submit a learning with the `auxilo_contribute` tool.
|
|
55
57
|
|
package/bin/auxilo-cli.js
CHANGED
|
@@ -32,19 +32,45 @@ const HOME = os.homedir();
|
|
|
32
32
|
// question N+1 arrives with question N's buffer and the next prompt hangs
|
|
33
33
|
// on EOF (the 0.8.1 consent step silently never completed).
|
|
34
34
|
let sharedRl = null;
|
|
35
|
+
let bufferedLines = [];
|
|
36
|
+
let lineWaiters = [];
|
|
37
|
+
let readlineEnded = false;
|
|
35
38
|
function getRl() {
|
|
36
39
|
if (!sharedRl) {
|
|
37
|
-
|
|
40
|
+
readlineEnded = false;
|
|
41
|
+
sharedRl = readline.createInterface({
|
|
42
|
+
input: process.stdin,
|
|
43
|
+
output: process.stdout,
|
|
44
|
+
terminal: !!process.stdin.isTTY,
|
|
45
|
+
});
|
|
46
|
+
sharedRl.on('line', (line) => {
|
|
47
|
+
const answer = line.trim();
|
|
48
|
+
const waiter = lineWaiters.shift();
|
|
49
|
+
if (waiter) waiter(answer);
|
|
50
|
+
else bufferedLines.push(answer);
|
|
51
|
+
});
|
|
52
|
+
sharedRl.on('close', () => {
|
|
53
|
+
readlineEnded = true;
|
|
54
|
+
while (lineWaiters.length) lineWaiters.shift()('');
|
|
55
|
+
});
|
|
38
56
|
}
|
|
39
57
|
return sharedRl;
|
|
40
58
|
}
|
|
41
59
|
function closeRl() {
|
|
42
|
-
if (sharedRl)
|
|
60
|
+
if (sharedRl) sharedRl.close();
|
|
61
|
+
sharedRl = null;
|
|
62
|
+
bufferedLines = [];
|
|
63
|
+
lineWaiters = [];
|
|
64
|
+
readlineEnded = false;
|
|
43
65
|
}
|
|
44
66
|
|
|
45
67
|
function ask(question) {
|
|
68
|
+
getRl();
|
|
69
|
+
process.stdout.write(question);
|
|
70
|
+
if (bufferedLines.length > 0) return Promise.resolve(bufferedLines.shift());
|
|
71
|
+
if (readlineEnded) return Promise.resolve('');
|
|
46
72
|
return new Promise((resolve) => {
|
|
47
|
-
|
|
73
|
+
lineWaiters.push(resolve);
|
|
48
74
|
});
|
|
49
75
|
}
|
|
50
76
|
|
|
@@ -509,7 +535,7 @@ async function cmdDisable(flags) {
|
|
|
509
535
|
// Operates ONLY on the caller's own pending learnings (account-scoped API key).
|
|
510
536
|
//
|
|
511
537
|
// Review-seamless (2026-07-18): the default view is now a triage summary table
|
|
512
|
-
// (quality
|
|
538
|
+
// (quality-desc rows grouped into the server's three lanes), with bulk modes that batch through
|
|
513
539
|
// POST /account/pending/bulk. HARD RULE for every bulk APPROVE path: print the
|
|
514
540
|
// exact list and count first, then require the operator to TYPE THE COUNT.
|
|
515
541
|
// There is no --yes bypass on any approve path; publishing always costs one
|
|
@@ -522,10 +548,18 @@ function fit(s, width) {
|
|
|
522
548
|
return str.padEnd(width);
|
|
523
549
|
}
|
|
524
550
|
|
|
525
|
-
/** Short flag codes for a summary row
|
|
551
|
+
/** Short lane/flag codes for a summary row, e.g. 'ready' or 'inj+sens'. */
|
|
526
552
|
function shortFlags(row) {
|
|
527
|
-
|
|
528
|
-
|
|
553
|
+
const resolved = review.reviewLane(row);
|
|
554
|
+
if (resolved.lane === 'ready_to_publish') return 'ready';
|
|
555
|
+
if (resolved.lane === 'needs_score') return 'score';
|
|
556
|
+
const map = {
|
|
557
|
+
injection: 'inj',
|
|
558
|
+
content_sensitivity: 'sens',
|
|
559
|
+
near_duplicate: 'dup',
|
|
560
|
+
process_advice: 'advice',
|
|
561
|
+
account_vocab: 'vocab',
|
|
562
|
+
};
|
|
529
563
|
return (row.flags || []).map((f) => map[f] || f).join('+') || 'flagged';
|
|
530
564
|
}
|
|
531
565
|
|
|
@@ -535,13 +569,41 @@ function triageLine(row, n, total) {
|
|
|
535
569
|
return ` ${String(n).padStart(String(total).length)}. q=${q} ${fit(shortFlags(row), 9)} ${fit(row.category || '', 20)} ${fit(row.title || '(no title)', 52)}`;
|
|
536
570
|
}
|
|
537
571
|
|
|
538
|
-
|
|
572
|
+
function groupSummaryRows(summary) {
|
|
573
|
+
const groups = {
|
|
574
|
+
ready_to_publish: [],
|
|
575
|
+
needs_score: [],
|
|
576
|
+
needs_your_eyes: [],
|
|
577
|
+
};
|
|
578
|
+
let versionSkew = false;
|
|
579
|
+
for (const row of summary.items || []) {
|
|
580
|
+
const resolved = review.reviewLane(row);
|
|
581
|
+
versionSkew = versionSkew || resolved.version_skew;
|
|
582
|
+
groups[resolved.lane].push(row);
|
|
583
|
+
}
|
|
584
|
+
const serverCounts = summary.counts && summary.counts.by_lane;
|
|
585
|
+
const counts = !versionSkew && serverCounts
|
|
586
|
+
? {
|
|
587
|
+
ready_to_publish: serverCounts.ready_to_publish || 0,
|
|
588
|
+
needs_score: serverCounts.needs_score || 0,
|
|
589
|
+
needs_your_eyes: serverCounts.needs_your_eyes || 0,
|
|
590
|
+
}
|
|
591
|
+
: {
|
|
592
|
+
ready_to_publish: groups.ready_to_publish.length,
|
|
593
|
+
needs_score: groups.needs_score.length,
|
|
594
|
+
needs_your_eyes: groups.needs_your_eyes.length,
|
|
595
|
+
};
|
|
596
|
+
return { groups, counts, versionSkew };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Print the triage summary in the server's three-lane order. */
|
|
539
600
|
function printSummaryTable(summary) {
|
|
540
|
-
const
|
|
541
|
-
const clean = items.filter((r) => r.screens_passed);
|
|
542
|
-
const flagged = items.filter((r) => !r.screens_passed);
|
|
601
|
+
const { groups, counts, versionSkew } = groupSummaryRows(summary);
|
|
543
602
|
|
|
544
|
-
console.log(`\n${summary.pending_count} learning(s) pending your review: ${
|
|
603
|
+
console.log(`\n${summary.pending_count} learning(s) pending your review: ${counts.ready_to_publish} ready to publish, ${counts.needs_score} need a score, ${counts.needs_your_eyes} need your eyes.`);
|
|
604
|
+
if (versionSkew) {
|
|
605
|
+
console.log('VERSION SKEW: this server did not return lane on every row; using the legacy screens-and-quality fallback.');
|
|
606
|
+
}
|
|
545
607
|
const bands = summary.counts && summary.counts.by_quality_band;
|
|
546
608
|
if (bands) {
|
|
547
609
|
console.log(`Quality bands: 18-20: ${bands['18-20'] || 0} · 14-17: ${bands['14-17'] || 0} · 10-13: ${bands['10-13'] || 0} · below 10: ${bands.below_10 || 0} · unscored: ${bands.unscored || 0}`);
|
|
@@ -550,13 +612,20 @@ function printSummaryTable(summary) {
|
|
|
550
612
|
console.log(`Near-duplicate clusters among your pending items: ${summary.near_dup_clusters.length} (review these together)`);
|
|
551
613
|
}
|
|
552
614
|
|
|
553
|
-
if (
|
|
554
|
-
console.log(`\
|
|
555
|
-
|
|
615
|
+
if (groups.ready_to_publish.length > 0) {
|
|
616
|
+
console.log(`\nREADY TO PUBLISH (${counts.ready_to_publish}) - sorted by quality:`);
|
|
617
|
+
groups.ready_to_publish.forEach((r, i) => console.log(triageLine(r, i + 1, groups.ready_to_publish.length)));
|
|
556
618
|
}
|
|
557
|
-
if (
|
|
558
|
-
console.log(`\
|
|
559
|
-
|
|
619
|
+
if (groups.needs_score.length > 0) {
|
|
620
|
+
console.log(`\nNEEDS A SCORE (${counts.needs_score}) - score or review individually:`);
|
|
621
|
+
groups.needs_score.forEach((r, i) => console.log(triageLine(r, i + 1, groups.needs_score.length)));
|
|
622
|
+
}
|
|
623
|
+
if (groups.needs_your_eyes.length > 0) {
|
|
624
|
+
console.log(`\nNEEDS YOUR EYES (${counts.needs_your_eyes}) - review individually:`);
|
|
625
|
+
groups.needs_your_eyes.forEach((r, i) => {
|
|
626
|
+
console.log(triageLine(r, i + 1, groups.needs_your_eyes.length));
|
|
627
|
+
if (r.why) console.log(` why: ${r.why}`);
|
|
628
|
+
});
|
|
560
629
|
}
|
|
561
630
|
console.log('');
|
|
562
631
|
}
|
|
@@ -640,32 +709,43 @@ async function cmdReview(flags) {
|
|
|
640
709
|
// ── --list: summary only, no mutations ────────────────────────────────────
|
|
641
710
|
if (flags.list) return;
|
|
642
711
|
|
|
643
|
-
// ── --approve-
|
|
644
|
-
//
|
|
645
|
-
|
|
712
|
+
// ── --approve-ready: consume the server's ready_to_publish verdict.
|
|
713
|
+
// --approve-clean remains a hidden compatibility alias. A stricter
|
|
714
|
+
// threshold narrows the ready lane; a lower one explicitly reaches into
|
|
715
|
+
// needs_score. Typed-count confirmation, no bypass. ─────────────────────
|
|
716
|
+
if (flags['approve-ready'] || flags['approve-clean']) {
|
|
717
|
+
if (flags['approve-clean']) {
|
|
718
|
+
console.log('Note: --approve-clean was renamed to --approve-ready; the old flag remains a compatibility alias.');
|
|
719
|
+
}
|
|
646
720
|
const minQuality = parseMinQuality(flags);
|
|
647
|
-
const sel = review.selectForBulkApprove(rows, { mode: '
|
|
648
|
-
console.log(`approve-
|
|
649
|
-
if (
|
|
721
|
+
const sel = review.selectForBulkApprove(rows, { mode: 'ready', minQuality });
|
|
722
|
+
console.log(`approve-ready selection: ${sel.selected.length} of ${rows.length} pending (threshold: quality >= ${minQuality}).`);
|
|
723
|
+
if (minQuality < review.DEFAULT_QUALITY_THRESHOLD) {
|
|
724
|
+
const approvableCount = Number.isFinite(summary.approvable_count)
|
|
725
|
+
? summary.approvable_count
|
|
726
|
+
: review.selectForBulkApprove(rows, { mode: 'ready' }).selected.length;
|
|
727
|
+
console.log(`WARNING: selection goes beyond the server's approvable verdict (approvable_count=${approvableCount}); including ${sel.included_beyond_verdict.length} items from needs_score.`);
|
|
728
|
+
}
|
|
729
|
+
if (sel.excluded_flagged.length) console.log(` excluded ${sel.excluded_flagged.length} needs_your_eyes item(s) (review those individually).`);
|
|
650
730
|
if (sel.excluded_low_quality.length) console.log(` excluded ${sel.excluded_low_quality.length} below the quality threshold.`);
|
|
651
731
|
if (sel.excluded_unscored.length) console.log(` excluded ${sel.excluded_unscored.length} with no quality score (pass --min-quality 0 to include).`);
|
|
652
732
|
if (sel.selected.length === 0) { console.log('Nothing qualifies. No changes made.'); return; }
|
|
653
733
|
|
|
654
734
|
if (!await confirmByTypedCount(sel.selected, 'APPROVE and PUBLISH')) return;
|
|
655
735
|
const totals = await runBulk({ apiKey, baseUrl, rows: sel.selected, decision: 'approve' });
|
|
656
|
-
console.log(`\nDone. Approved ${totals.approved} (already approved earlier: ${totals.idempotent}, failed: ${totals.failed}).
|
|
736
|
+
console.log(`\nDone. Approved ${totals.approved} (already approved earlier: ${totals.idempotent}, failed: ${totals.failed}). Items outside the selection stay pending.`);
|
|
657
737
|
return;
|
|
658
738
|
}
|
|
659
739
|
|
|
660
|
-
// ── --all: bulk-approve everything EXCEPT
|
|
740
|
+
// ── --all: bulk-approve everything EXCEPT needs_your_eyes items unless
|
|
661
741
|
// --include-flagged. Typed-count confirmation, no bypass. ───────────────
|
|
662
742
|
if (flags.all) {
|
|
663
743
|
const includeFlagged = flags['include-flagged'] === true;
|
|
664
744
|
const sel = review.selectForBulkApprove(rows, { mode: 'all', includeFlagged });
|
|
665
|
-
if (includeFlagged && sel.selected.some((r) =>
|
|
666
|
-
console.log('WARNING: --include-flagged is set. This selection INCLUDES items
|
|
745
|
+
if (includeFlagged && sel.selected.some((r) => review.reviewLane(r).lane === 'needs_your_eyes')) {
|
|
746
|
+
console.log('WARNING: --include-flagged is set. This selection INCLUDES needs_your_eyes items (possible injection, sensitive content, process advice, account vocabulary, or near-duplicates). Approving publishes them publicly.');
|
|
667
747
|
} else if (sel.excluded_flagged.length) {
|
|
668
|
-
console.log(`Excluding ${sel.excluded_flagged.length}
|
|
748
|
+
console.log(`Excluding ${sel.excluded_flagged.length} needs_your_eyes item(s) (pass --include-flagged to include them; safer to review those individually).`);
|
|
669
749
|
}
|
|
670
750
|
if (sel.selected.length === 0) { console.log('Nothing qualifies. No changes made.'); return; }
|
|
671
751
|
|
|
@@ -707,13 +787,14 @@ async function cmdReview(flags) {
|
|
|
707
787
|
console.log('Rapid review. Per item: [y] approve (goes live) · [n] reject (stays private) · [v] view full body · [s] skip · [q] quit\n');
|
|
708
788
|
|
|
709
789
|
let approved = 0, rejected = 0, skipped = 0;
|
|
710
|
-
const ordered = rows; //
|
|
790
|
+
const ordered = rows; // quality-desc server order; the summary above groups lanes
|
|
711
791
|
for (let i = 0; i < ordered.length; i++) {
|
|
712
792
|
const row = ordered[i];
|
|
713
793
|
console.log(triageLine(row, i + 1, ordered.length));
|
|
714
794
|
let choice = '';
|
|
715
795
|
for (;;) {
|
|
716
796
|
choice = (await ask(' [y/n/v/s/q]? ')).toLowerCase().slice(0, 1);
|
|
797
|
+
if (!choice && readlineEnded) choice = 'q';
|
|
717
798
|
if (choice === 'v') {
|
|
718
799
|
const full = bodies.get(row.id);
|
|
719
800
|
console.log(' ------------------------------------------------------------');
|
|
@@ -747,7 +828,46 @@ async function cmdReview(flags) {
|
|
|
747
828
|
|
|
748
829
|
// ─── Entry point ────────────────────────────────────────────────────────────
|
|
749
830
|
|
|
750
|
-
function usage() {
|
|
831
|
+
function usage(command) {
|
|
832
|
+
const blocks = {
|
|
833
|
+
setup: `Usage: auxilo setup [--re-auth] [--base-url <url>]
|
|
834
|
+
|
|
835
|
+
Interactively detect clients, register Auxilo, sign in, install the optional
|
|
836
|
+
extraction runner and SessionEnd hook, and record the extraction choice.`,
|
|
837
|
+
init: `Usage: auxilo init [--scope <read|earnings-read|contribute>] [--label <name>]
|
|
838
|
+
[--env-file <path>] [--save] [--json] [--no-browser]
|
|
839
|
+
[--base-url <url>]
|
|
840
|
+
|
|
841
|
+
Mint a scoped API key for CI or a second machine without running full setup.`,
|
|
842
|
+
status: `Usage: auxilo status
|
|
843
|
+
|
|
844
|
+
Show detected clients, auth, extraction mode, kill switch, SessionEnd hook,
|
|
845
|
+
last sweep, and pending queue depth.`,
|
|
846
|
+
review: `Usage: auxilo review [flags]
|
|
847
|
+
|
|
848
|
+
Render YOUR pending-review learnings in three server-defined lanes:
|
|
849
|
+
Ready to publish, Needs a score, and Needs your eyes.
|
|
850
|
+
|
|
851
|
+
Flags:
|
|
852
|
+
--list show the three-lane summary only; make no changes
|
|
853
|
+
--approve-ready select the server's ready_to_publish lane (default quality
|
|
854
|
+
floor 14; a higher --min-quality narrows it, while a lower
|
|
855
|
+
value explicitly reaches into needs_score)
|
|
856
|
+
--min-quality N quality threshold for --approve-ready (0-20)
|
|
857
|
+
--all approve everything except needs_your_eyes items
|
|
858
|
+
--include-flagged include needs_your_eyes with --all
|
|
859
|
+
--all-reject reject the whole batch [--yes for scripted incident use]
|
|
860
|
+
--base-url <url>
|
|
861
|
+
|
|
862
|
+
Every approval path prints the exact list and requires typing its count.`,
|
|
863
|
+
disable: `Usage: auxilo disable [--base-url <url>]
|
|
864
|
+
|
|
865
|
+
Disable background extraction locally and optionally revoke server consent.`,
|
|
866
|
+
};
|
|
867
|
+
if (command && blocks[command]) {
|
|
868
|
+
console.log(`\n${blocks[command]}\n`);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
751
871
|
console.log(`
|
|
752
872
|
Usage: auxilo <command>
|
|
753
873
|
|
|
@@ -771,18 +891,17 @@ Commands:
|
|
|
771
891
|
status Show install/auth/extraction status.
|
|
772
892
|
review Review YOUR pending-review learnings (from background extraction)
|
|
773
893
|
and approve/reject before anything goes public. Default: triage
|
|
774
|
-
summary
|
|
775
|
-
y/n/s review.
|
|
894
|
+
summary first in the server's Ready to publish / Needs a score /
|
|
895
|
+
Needs your eyes lanes, then rapid y/n/s review.
|
|
776
896
|
Flags:
|
|
777
897
|
--list summary table only, no changes
|
|
778
|
-
--approve-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
items (add --include-flagged to include them).
|
|
898
|
+
--approve-ready select the server's ready_to_publish lane
|
|
899
|
+
(default floor 14; tune with --min-quality N).
|
|
900
|
+
Prints the exact list, then requires its count.
|
|
901
|
+
--all bulk-approve everything EXCEPT needs_your_eyes
|
|
902
|
+
(add --include-flagged to include that lane).
|
|
784
903
|
Same typed-count confirmation.
|
|
785
|
-
--min-quality N quality threshold for --approve-
|
|
904
|
+
--min-quality N quality threshold for --approve-ready (0-20)
|
|
786
905
|
--all-reject reject the whole batch [--yes for scripted
|
|
787
906
|
incident response; rejects stay private]
|
|
788
907
|
--base-url <url>
|
|
@@ -797,6 +916,10 @@ Docs: https://auxilo.io · API: ${installer.DEFAULT_BASE_URL}
|
|
|
797
916
|
|
|
798
917
|
async function main() {
|
|
799
918
|
const cmd = process.argv[2];
|
|
919
|
+
const subcommandHelp = ['help', '--help', '-h'].includes(process.argv[3]);
|
|
920
|
+
if (['setup', 'init', 'status', 'review', 'disable'].includes(cmd) && subcommandHelp) {
|
|
921
|
+
return usage(cmd);
|
|
922
|
+
}
|
|
800
923
|
const flags = parseFlags(process.argv);
|
|
801
924
|
switch (cmd) {
|
|
802
925
|
case 'setup': return cmdSetup(flags);
|
|
@@ -829,4 +952,12 @@ if (require.main === module) {
|
|
|
829
952
|
run();
|
|
830
953
|
}
|
|
831
954
|
|
|
832
|
-
module.exports = {
|
|
955
|
+
module.exports = {
|
|
956
|
+
parseFlags,
|
|
957
|
+
resolveBaseUrl,
|
|
958
|
+
shortFlags,
|
|
959
|
+
groupSummaryRows,
|
|
960
|
+
printSummaryTable,
|
|
961
|
+
usage,
|
|
962
|
+
run,
|
|
963
|
+
};
|