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 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. Clean drafts publish to the marketplace with a 7-day retraction window. Anything a screen flags waits in your private pending queue instead.
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 that any screen flags waits in a pending queue only you can see.
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 # approve, reject, or skip each queued draft
48
- npx auxilo status # clients, hooks, queue depth, consent state
49
- npx auxilo disable # kill switch: extraction stops immediately
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
- sharedRl = readline.createInterface({ input: process.stdin, output: process.stdout });
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) { sharedRl.close(); sharedRl = null; }
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
- getRl().question(question, (answer) => resolve(answer.trim()));
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 desc, clean before flagged), with bulk modes that batch through
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: 'clean' or e.g. 'inj+sens'. */
551
+ /** Short lane/flag codes for a summary row, e.g. 'ready' or 'inj+sens'. */
526
552
  function shortFlags(row) {
527
- if (row.screens_passed) return 'clean';
528
- const map = { injection: 'inj', content_sensitivity: 'sens', near_duplicate: 'dup' };
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
- /** Print the triage summary: counts, then clean rows, then flagged rows. */
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 items = summary.items || [];
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: ${clean.length} passed every platform screen, ${flagged.length} flagged.`);
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 (clean.length > 0) {
554
- console.log(`\nCLEAN (passed every screen) - sorted by quality:`);
555
- clean.forEach((r, i) => console.log(triageLine(r, i + 1, clean.length)));
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 (flagged.length > 0) {
558
- console.log(`\nFLAGGED (a platform screen raised a signal) - review individually:`);
559
- flagged.forEach((r, i) => console.log(triageLine(r, i + 1, flagged.length)));
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-clean: bulk-approve items that passed EVERY platform screen
644
- // AND clear the quality threshold. Typed-count confirmation, no bypass. ──
645
- if (flags['approve-clean']) {
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: 'clean', minQuality });
648
- console.log(`approve-clean selection: ${sel.selected.length} of ${rows.length} pending (threshold: quality >= ${minQuality}).`);
649
- if (sel.excluded_flagged.length) console.log(` excluded ${sel.excluded_flagged.length} screen-flagged item(s) (review those individually).`);
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}). Flagged and below-threshold items stay pending.`);
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 screen-flagged items unless
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) => !r.screens_passed)) {
666
- console.log('WARNING: --include-flagged is set. This selection INCLUDES items a platform screen flagged (possible injection, sensitive content, or near-duplicates). Approving publishes them publicly.');
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} screen-flagged item(s) (pass --include-flagged to include them; safer to review those individually).`);
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; // clean-first, quality desc (server order preserved by printSummaryTable groups)
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 table first (quality desc, clean vs flagged), then rapid
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-clean bulk-approve items that passed EVERY platform
779
- screen AND quality >= threshold (default 14;
780
- tune with --min-quality N). Prints the exact
781
- list, then requires typing the count.
782
- --all bulk-approve everything EXCEPT screen-flagged
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-clean (0-20)
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 = { parseFlags, resolveBaseUrl, run };
955
+ module.exports = {
956
+ parseFlags,
957
+ resolveBaseUrl,
958
+ shortFlags,
959
+ groupSummaryRows,
960
+ printSummaryTable,
961
+ usage,
962
+ run,
963
+ };
@@ -0,0 +1,9 @@
1
+ {
2
+ "SHINGLE_FLAG_THRESHOLD": 0.6,
3
+ "UNIGRAM_FLAG_THRESHOLD": 0.6,
4
+ "TF_COSINE_FLAG_THRESHOLD": 0.75,
5
+ "TITLE_FLAG_THRESHOLD": 0.6,
6
+ "COMPOSITE_FLAG_THRESHOLD": 0.6,
7
+ "CONTENT_WEIGHT": 0.75,
8
+ "TITLE_WEIGHT": 0.25
9
+ }