auxilo-mcp 0.9.2 → 0.9.3

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 CHANGED
@@ -106,7 +106,9 @@ const CONSENT_TEXT = `
106
106
  for everything) is available in your account settings. You earn 70%
107
107
  of sales.
108
108
  You can stop any time with \`auxilo disable\` (local kill-switch) and review
109
- every run in ~/.auxilo/extract.log. Saying No installs the MCP server only.
109
+ every run in ~/.auxilo/extract.log. Saying No installs the MCP server only
110
+ no session-end capture hook is written into any client config unless you say
111
+ Yes, and any capture hooks left by an earlier install are removed.
110
112
  `;
111
113
 
112
114
  async function cmdSetup(flags) {
@@ -194,7 +196,11 @@ async function cmdSetup(flags) {
194
196
  }
195
197
  }
196
198
 
197
- // 4. Runner install + Claude Code hook --------------------------------------
199
+ // 4. Runner install + review notice -----------------------------------------
200
+ // UC-1a (GOV-3): NO capture hook is written into any third-party client
201
+ // config here — only files under our own ~/.auxilo/bin, plus the
202
+ // SessionStart review notice (a count-only account-status surface, not a
203
+ // capture hook). Capture hooks are written ONLY after consent=Yes (step 5).
198
204
  console.log('');
199
205
  try {
200
206
  const { binRoot } = installer.installRunner(HOME);
@@ -206,54 +212,95 @@ async function cmdSetup(flags) {
206
212
 
207
213
  const claudeCode = chosen.find((c) => c.id === 'claude-code');
208
214
  if (claudeCode) {
215
+ // LW-18: SessionStart held-count notice ("N learnings held for your
216
+ // review") — fail-silent, ≤1 per 4h, count-only. Installed regardless of
217
+ // extraction consent: the review queue also holds MCP contributions.
209
218
  try {
210
- const hook = installer.registerClaudeCodeHook(HOME);
211
- if (hook.changed) {
212
- console.log(` ✓ Claude Code SessionEnd hook registered (${hook.hookCmd})`);
213
- if (hook.removedLegacy.length > 0) {
214
- console.log(` (replaced legacy hook entr${hook.removedLegacy.length === 1 ? 'y' : 'ies'}: ${hook.removedLegacy.join(', ')})`);
215
- }
216
- } else {
217
- console.log(' ✓ Claude Code SessionEnd hook already registered (no changes)');
218
- }
219
+ const notice = installer.registerClaudeCodeSessionStartNotice(HOME);
220
+ console.log(notice.changed
221
+ ? ` ✓ Claude Code SessionStart review notice registered (${notice.hookCmd})`
222
+ : ' ✓ Claude Code SessionStart review notice already registered (no changes)');
219
223
  } catch (err) {
220
- console.error(` ✗ Hook registration SKIPPED — ${err.message}`);
224
+ console.error(` ✗ Review notice SKIPPED — ${err.message}`);
221
225
  }
222
226
  }
223
227
 
224
- // UC-1: capture hooks for every other chosen client that supports one.
225
- try {
226
- for (const r of installer.installCaptureHooks(HOME, chosen)) {
227
- if (r.error) {
228
- console.error(` ✗ ${r.name}: SKIPPED — ${r.error}`);
229
- } else if (r.changed) {
230
- console.log(` ✓ ${r.name}: capture hook registered (${r.event})`);
231
- if (r.notes) console.log(` ${r.notes}`);
232
- } else {
233
- console.log(` ✓ ${r.name}: capture hook already registered`);
234
- if (r.notes) console.log(` ${r.notes}`);
235
- }
236
- }
237
- } catch (err) {
238
- console.error(` ✗ Capture hooks SKIPPED — ${err.message}`);
239
- }
240
-
241
- // 5. Explicit consent (default No) ------------------------------------------
228
+ // 5. Explicit consent (default No), THEN capture hooks ----------------------
242
229
  console.log(CONSENT_TEXT);
243
230
  const consented = await askYesNo(' Enable background extraction?', false);
231
+ let extractionArmed = false;
244
232
  if (consented) {
245
233
  try {
246
234
  await installer.recordConsent({ action: 'grant', apiKey: creds.api_key, baseUrl });
247
235
  installer.enableSentinel(HOME);
236
+ extractionArmed = true;
248
237
  console.log(' ✓ Consent recorded; background extraction ENABLED (~/.auxilo/autonomous-enabled)');
249
238
  } catch (err) {
250
239
  console.error(` ✗ Could not record consent on server: ${err.message}`);
251
- console.error(' Background extraction NOT enabled (no sentinel created). Re-run `auxilo setup` to retry.');
240
+ console.error(' Background extraction NOT enabled (no sentinel created, no hooks written). Re-run `auxilo setup` to retry.');
252
241
  }
253
242
  } else {
254
243
  console.log(' ✓ Background extraction left OFF (MCP-only install). Enable later by re-running `auxilo setup`.');
255
244
  }
256
245
 
246
+ if (extractionArmed) {
247
+ // Consent recorded — NOW write the capture hooks (UC-1a ordering).
248
+ if (claudeCode) {
249
+ try {
250
+ const hook = installer.registerClaudeCodeHook(HOME);
251
+ if (hook.changed) {
252
+ console.log(` ✓ Claude Code SessionEnd hook registered (${hook.hookCmd})`);
253
+ if (hook.removedLegacy.length > 0) {
254
+ console.log(` (replaced legacy hook entr${hook.removedLegacy.length === 1 ? 'y' : 'ies'}: ${hook.removedLegacy.join(', ')})`);
255
+ }
256
+ } else {
257
+ console.log(' ✓ Claude Code SessionEnd hook already registered (no changes)');
258
+ }
259
+ } catch (err) {
260
+ console.error(` ✗ Hook registration SKIPPED — ${err.message}`);
261
+ }
262
+ }
263
+
264
+ // UC-1: capture hooks for every other chosen client that supports one.
265
+ try {
266
+ for (const r of installer.installCaptureHooks(HOME, chosen)) {
267
+ if (r.error) {
268
+ console.error(` ✗ ${r.name}: SKIPPED — ${r.error}`);
269
+ } else if (r.changed) {
270
+ console.log(` ✓ ${r.name}: capture hook registered (${r.event})`);
271
+ if (r.notes) console.log(` ${r.notes}`);
272
+ } else {
273
+ console.log(` ✓ ${r.name}: capture hook already registered`);
274
+ if (r.notes) console.log(` ${r.notes}`);
275
+ }
276
+ }
277
+ } catch (err) {
278
+ console.error(` ✗ Capture hooks SKIPPED — ${err.message}`);
279
+ }
280
+ } else {
281
+ // UC-1a: consent not granted (No, or consent record failed) — ensure NO
282
+ // capture-hook artifact remains in any client config, including ones a
283
+ // pre-UC-1a install wrote before the consent step.
284
+ try {
285
+ const removedHook = installer.removeClaudeCodeHook(HOME);
286
+ if (removedHook.changed) {
287
+ console.log(` ✓ Removed pre-existing Claude Code SessionEnd capture hook (${removedHook.removed.join(', ')})`);
288
+ }
289
+ } catch (err) {
290
+ console.error(` ✗ Claude Code hook cleanup SKIPPED — ${err.message}`);
291
+ }
292
+ try {
293
+ const allClients = installer.detectClients(HOME); // clean beyond the chosen set
294
+ for (const r of installer.removeCaptureHooks(HOME, allClients)) {
295
+ if (r.error) console.error(` ✗ ${r.name}: capture-hook cleanup SKIPPED — ${r.error}`);
296
+ else if (r.changed) console.log(` ✓ ${r.name}: removed pre-existing capture hook`);
297
+ }
298
+ } catch (err) {
299
+ console.error(` ✗ Capture-hook cleanup SKIPPED — ${err.message}`);
300
+ }
301
+ console.log(' ✓ No capture hooks are present in any client config.');
302
+ }
303
+
257
304
  // 6. Rules-file snippet (UC-0 agent-prompted contribution; opt-in, default No)
258
305
  const rulesClients = chosen.filter((c) => c.rulesPath);
259
306
  if (rulesClients.length > 0) {
@@ -284,6 +331,119 @@ async function cmdSetup(flags) {
284
331
  console.log('\nDone. Check `npx auxilo status` any time. Restart your client(s) to pick up the MCP server.\n');
285
332
  }
286
333
 
334
+ // ─── auxilo init (NF-3, Wave 3.4) ───────────────────────────────────────────
335
+ //
336
+ // Non-interactive builder onboarding: mint a SCOPED key for CI or a second
337
+ // machine WITHOUT re-running full setup (no client detection, no hooks, no
338
+ // consent flow, and — unless --save — no touch of ~/.auxilo/credentials.json).
339
+ // ZERO prompts by design: every input is a flag with a default, so piped/
340
+ // scripted stdin can never hang (LW-17 lesson class; init never opens the
341
+ // shared readline at all). The only human step is the device-code approval
342
+ // in a browser, which can happen on any machine.
343
+
344
+ const INIT_SCOPES = ['read', 'earnings-read', 'contribute'];
345
+
346
+ async function cmdInit(flags) {
347
+ const baseUrl = resolveBaseUrl(flags);
348
+
349
+ const scope = flags.scope === undefined ? 'contribute' : String(flags.scope);
350
+ if (!INIT_SCOPES.includes(scope)) {
351
+ console.error(`Invalid --scope '${scope}'. Must be one of: ${INIT_SCOPES.join(', ')}.`);
352
+ console.error('(admin keys cannot be created via the API or device flow.)');
353
+ process.exit(1);
354
+ }
355
+ const label = (flags.label === undefined || flags.label === true
356
+ ? `init-${os.hostname()}`
357
+ : String(flags.label)).trim().slice(0, 50);
358
+
359
+ if (!flags.json) {
360
+ console.log('\nAuxilo init — mint a scoped API key');
361
+ console.log('===================================');
362
+ console.log(`Server: ${baseUrl}`);
363
+ console.log(`Scope: ${scope} Label: ${label}\n`);
364
+ }
365
+
366
+ let result;
367
+ try {
368
+ result = await installer.deviceLogin({
369
+ baseUrl,
370
+ scope,
371
+ label,
372
+ onCode: (code, url) => {
373
+ if (!flags.json) {
374
+ console.log(` Your device code: ${code}`);
375
+ console.log(` Approve at: ${url}`);
376
+ console.log(' Waiting for authorization (Ctrl-C to abort)...\n');
377
+ } else {
378
+ // --json keeps stdout machine-readable; the human-step info goes to stderr.
379
+ console.error(`device_code=${code} approve_at=${url}`);
380
+ }
381
+ if (!flags['no-browser']) openBrowser(url);
382
+ },
383
+ });
384
+ } catch (err) {
385
+ console.error(`✗ init failed: ${err.message}`);
386
+ process.exit(1);
387
+ }
388
+
389
+ // Pre-Wave-3.4 servers ignore the requested scope and mint 'contribute'
390
+ // (and don't echo). Detect and WARN — never silently mislabel a credential.
391
+ const grantedScope = result.scope || 'contribute';
392
+ const grantedLabel = result.label || label;
393
+ if (grantedScope !== scope) {
394
+ console.error(` ! Server granted scope '${grantedScope}' (requested '${scope}') — older server version. The key below carries '${grantedScope}'.`);
395
+ }
396
+
397
+ if (flags['env-file']) {
398
+ const envPath = String(flags['env-file']);
399
+ try {
400
+ const w = installer.writeEnvFile(envPath, { api_key: result.api_key, base_url: baseUrl });
401
+ if (!flags.json) {
402
+ console.log(` ✓ ${w.created ? 'Created' : 'Updated'} ${w.path} (AUXILO_API_KEY, mode 0600)`);
403
+ }
404
+ } catch (err) {
405
+ // The key was already minted — surface it rather than losing it.
406
+ console.error(` ✗ Could not write ${envPath}: ${err.message}`);
407
+ console.error(' Your key (shown once — store it now):');
408
+ console.error(` ${result.api_key}`);
409
+ process.exit(1);
410
+ }
411
+ }
412
+
413
+ if (flags.save) {
414
+ installer.writeCredentials(HOME, {
415
+ api_key: result.api_key,
416
+ base_url: baseUrl,
417
+ email: result.email,
418
+ account_id: result.account_id,
419
+ });
420
+ if (!flags.json) console.log(' ✓ Saved to ~/.auxilo/credentials.json (mode 0600) — this machine now uses this key.');
421
+ }
422
+
423
+ if (flags.json) {
424
+ console.log(JSON.stringify({
425
+ api_key: result.api_key,
426
+ scope: grantedScope,
427
+ label: grantedLabel,
428
+ account_id: result.account_id,
429
+ email: result.email,
430
+ base_url: baseUrl,
431
+ env_file: flags['env-file'] ? String(flags['env-file']) : null,
432
+ saved_credentials: !!flags.save,
433
+ }));
434
+ return;
435
+ }
436
+
437
+ console.log(` ✓ Key minted for ${result.email || result.account_id} (scope: ${grantedScope}, label: ${grantedLabel})`);
438
+ if (!flags['env-file']) {
439
+ console.log('\n Your API key (shown ONCE — store it now, e.g. in your CI secret store):');
440
+ console.log(` ${result.api_key}\n`);
441
+ console.log(' Use it as the X-API-Key header, or write it to an env file next time with --env-file <path>.');
442
+ }
443
+ console.log(' Rotate later: POST /account/api-keys/rotate with this key (self-rotate).');
444
+ console.log(' Revoke later: from the dashboard, or DELETE /account/api-keys/:label with this key.\n');
445
+ }
446
+
287
447
  // ─── auxilo status ──────────────────────────────────────────────────────────
288
448
 
289
449
  async function cmdStatus() {
@@ -595,6 +755,19 @@ Commands:
595
755
  setup Interactive install: client detection, MCP registration,
596
756
  device-code login, extraction runner + hook, consent.
597
757
  Flags: --re-auth, --base-url <url>
758
+ init Mint a SCOPED API key for CI or a second machine — no full setup,
759
+ no prompts (device-code approval in a browser is the only human
760
+ step). Does NOT touch ~/.auxilo/credentials.json unless --save.
761
+ Flags:
762
+ --scope <s> read | earnings-read | contribute (default:
763
+ contribute; admin is never issuable)
764
+ --label <name> key label (default: init-<hostname>)
765
+ --env-file <path> write AUXILO_API_KEY to an env file (0600,
766
+ updated in place) instead of printing the key
767
+ --save also write ~/.auxilo/credentials.json
768
+ --json machine-readable result on stdout
769
+ --no-browser don't try to open the approval URL
770
+ --base-url <url>
598
771
  status Show install/auth/extraction status.
599
772
  review Review YOUR pending-review learnings (from background extraction)
600
773
  and approve/reject before anything goes public. Default: triage
@@ -627,6 +800,7 @@ async function main() {
627
800
  const flags = parseFlags(process.argv);
628
801
  switch (cmd) {
629
802
  case 'setup': return cmdSetup(flags);
803
+ case 'init': return cmdInit(flags);
630
804
  case 'status': return cmdStatus(flags);
631
805
  case 'review': return cmdReview(flags);
632
806
  case 'disable': return cmdDisable(flags);