shakers-cli 0.14.2 → 0.16.0

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.
Files changed (123) hide show
  1. package/README.md +51 -1203
  2. package/bin/ai-usage.js +11 -56
  3. package/bin/login.js +10 -10
  4. package/bin/mcp.js +13 -9
  5. package/bin/mcpb.js +1 -1
  6. package/bin/onboarding.js +3 -2
  7. package/bin/register.js +17 -4
  8. package/bin/report-html.js +2 -2
  9. package/bin/shakers-cli.js +352 -0
  10. package/bin/shakers.js +20 -344
  11. package/bin/share.js +1 -1
  12. package/bin/superadmin.js +5 -5
  13. package/bin/version.js +3 -0
  14. package/package.json +12 -3
  15. package/src/agent-category.js +1 -1
  16. package/src/agent-evaluation.js +3 -3
  17. package/src/agent-org-chart.js +5 -5
  18. package/src/agent-synthesis.js +3 -3
  19. package/src/agent-usage.js +1 -1
  20. package/src/agents-client.js +4 -4
  21. package/src/ai.js +1 -1
  22. package/src/ansi.js +1 -1
  23. package/src/auth-client.js +18 -4
  24. package/src/auth-method-picker.js +1 -1
  25. package/src/auth-methods.js +1 -1
  26. package/src/auth-session-store.js +11 -8
  27. package/src/authorship-mismatch.js +1 -1
  28. package/src/authorship.js +2 -2
  29. package/src/automations-detector.js +1 -1
  30. package/src/backend-request.js +20 -3
  31. package/src/brand-ansi.js +1 -1
  32. package/src/browser-tools-detector.js +1 -1
  33. package/src/certify-disclaimer.js +1 -1
  34. package/src/certify-remediation-prompt.js +1 -1
  35. package/src/cli-args.js +2 -2
  36. package/src/command-audit.js +8 -5
  37. package/src/config-dir.js +1 -1
  38. package/src/config.js +112 -48
  39. package/src/consent-flow.js +4 -4
  40. package/src/consent-skip.js +1 -1
  41. package/src/detection-evidence.js +2 -2
  42. package/src/detection-noise.js +1 -1
  43. package/src/detectors.js +1 -1
  44. package/src/device-login.js +12 -1
  45. package/src/email-verification.js +3 -3
  46. package/src/env-paths.js +1 -1
  47. package/src/find-projects-client.js +12 -0
  48. package/src/find-projects-flow.js +1 -13
  49. package/src/git-activity.js +1 -1
  50. package/src/graph-certs.js +3 -3
  51. package/src/graph-scan.js +9 -9
  52. package/src/i18n.js +124 -124
  53. package/src/interactive-select.js +1 -1
  54. package/src/interview-client.js +6 -1
  55. package/src/interview-transcript.js +1 -1
  56. package/src/legal-notice.js +1 -1
  57. package/src/maturity.js +6 -6
  58. package/src/mcp-ai-usage-tool.js +1 -1
  59. package/src/mcp-auth-form.js +1 -1
  60. package/src/mcp-detector.js +3 -3
  61. package/src/mcp-device-login-tools.js +2 -1
  62. package/src/mcp-find-projects-tools.js +1 -2
  63. package/src/mcp-onboarding-tools.js +4 -4
  64. package/src/mcp-prompts.js +2 -2
  65. package/src/mcp-register-tools.js +1 -0
  66. package/src/mcp-server.js +5 -1
  67. package/src/mcp-signup-client.js +2 -2
  68. package/src/mcp-signup-tools.js +26 -2
  69. package/src/memory-structure-detector.js +1 -1
  70. package/src/model-call-record.js +2 -2
  71. package/src/onboarding-flow.js +16 -16
  72. package/src/onboarding-funnel.js +110 -17
  73. package/src/open-file.js +1 -1
  74. package/src/osc-link.js +1 -1
  75. package/src/portfolio-client.js +11 -1
  76. package/src/portfolio-git-info.js +1 -1
  77. package/src/profile-flow.js +1 -1
  78. package/src/profile-switch.js +5 -12
  79. package/src/render-certification.js +10 -10
  80. package/src/render-html.js +6 -6
  81. package/src/render-sheet.js +10 -10
  82. package/src/render-terminal.js +6 -6
  83. package/src/repo-scope.js +132 -0
  84. package/src/report-gating.js +1 -1
  85. package/src/report-store.js +6 -6
  86. package/src/roadmap-content.js +5 -5
  87. package/src/roadmap-personalization.js +3 -3
  88. package/src/roadmap-prompt.js +2 -2
  89. package/src/scan-exclusions.js +1 -1
  90. package/src/scanner.js +14 -14
  91. package/src/secret-prompt.js +1 -1
  92. package/src/session-gate.js +1 -1
  93. package/src/session-refresh.js +33 -3
  94. package/src/session-scan.js +1 -1
  95. package/src/shakers-wordmark.js +1 -1
  96. package/src/share-card.js +2 -2
  97. package/src/share-certification.js +4 -4
  98. package/src/share-consent-state.js +7 -7
  99. package/src/share-payload.js +7 -7
  100. package/src/share.js +7 -7
  101. package/src/signup-client.js +1 -1
  102. package/src/signup-copy.js +1 -1
  103. package/src/skills-client.js +1 -1
  104. package/src/start-add-agents.js +2 -2
  105. package/src/start-add-portfolio.js +3 -13
  106. package/src/start-add-skills.js +2 -2
  107. package/src/stdin-ask.js +3 -3
  108. package/src/strip-internal-comments.js +1 -1
  109. package/src/tech-detector.js +2 -2
  110. package/src/templates/report-sheet.html +11 -13
  111. package/src/terminal-agents.js +8 -8
  112. package/src/terminal-format.js +3 -3
  113. package/src/terminal-sections.js +11 -11
  114. package/src/tier-engine.js +3 -3
  115. package/src/tier-projection.js +2 -2
  116. package/src/transcript-turns.js +1 -1
  117. package/src/typewriter.js +1 -1
  118. package/src/usage-model-calls.js +6 -6
  119. package/src/usage-oneshot-commands.js +5 -5
  120. package/src/usage-repo-scope.js +11 -129
  121. package/src/usage-run-steps.js +32 -184
  122. package/src/usage-scan.js +235 -0
  123. package/src/work-streams.js +1 -1
package/bin/ai-usage.js CHANGED
@@ -1,74 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const { classify } = require('../src/maturity');
5
4
  const { detectReportLang, getCatalog } = require('../src/i18n');
6
5
  const { parseArgs } = require('../src/cli-args');
7
- const { loadConsentState } = require('../src/share');
8
6
  const { handleOneShotCommand } = require('../src/usage-oneshot-commands');
9
- const { prefetchGitLogs } = require('../src/git-log-prefetch');
10
- const { GIT_ACTIVITY_LOG_ARGS } = require('../src/git-activity');
11
- const { WORK_STREAMS_LOG_ARGS } = require('../src/work-streams');
12
- const { AUTHORSHIP_LOG_ARGS } = require('../src/authorship');
7
+ const { computeUsage } = require('../src/usage-scan');
13
8
  const {
14
9
  resolveRepoScope,
15
10
  machineWideToplevels,
16
11
  promptScope,
17
12
  } = require('../src/usage-repo-scope');
18
13
  const {
19
- performScan,
20
14
  prepareIdentityAndUsage,
21
15
  resolveAiConsent,
22
- runAggregation,
23
- runModelCalls,
24
16
  renderAndFinalize,
25
17
  } = require('../src/usage-run-steps');
26
18
 
27
- // Steps 1-6 of a run: scan, scope, aggregate, classify; nothing is rendered or sent.
28
- async function computeUsage({ opts, catalog, lang, injectedAsk = null, inRegistration = false }) {
29
- // 1. Local scan + classify (always computed and shown, ADR-011/013).
30
- const { report, root, rootDir } = performScan({ opts, catalog });
31
-
32
- // 2. Identity gate + local agent-usage signal. Roadmap only for anonymous runs
33
- // (gate.showRoadmap = !loggedIn) AND never during register/onboarding.
34
- const { gate: identityGate } = prepareIdentityAndUsage({ report, catalog, quiet: opts.json === true });
35
- const gate = (inRegistration && identityGate.showRoadmap)
36
- ? { ...identityGate, showRoadmap: false }
37
- : identityGate;
38
-
39
- // 3. Consent gate before any AI/egress call (ADR-051); a decline still renders.
40
- const aiConsentGranted = await resolveAiConsent({ opts, catalog, gate, injectedAsk });
41
-
42
- // 4. Repo scoping (exclude client work).
43
- const scope = await resolveRepoScope({ opts, cwd: rootDir, injectedAsk, catalog });
44
- const consentEmail = (loadConsentState() || {}).email || null;
45
- const agentRoots = scope.toplevels;
46
-
47
- // 5. Aggregate scoped signals + rebuild scan/org-chart over the selection, re-classify.
48
- await prefetchGitLogs(agentRoots, [GIT_ACTIVITY_LOG_ARGS, WORK_STREAMS_LOG_ARGS, AUTHORSHIP_LOG_ARGS]);
49
- runAggregation({ report, scope, consentEmail, agentRoots, root, catalog });
50
- const maturity = classify(report);
51
- const hasAgents = Array.isArray(report.agents) && report.agents.length > 0;
52
- report.analyzedRepos =
53
- scope.mode === 'all'
54
- ? null
55
- : scope.toplevels.map((tl) => tl.split(/[/\\]/).pop() || tl);
56
-
57
- // 6. The three ephemeral model calls, serialized and gated by consent (ADR-051).
58
- await runModelCalls({
59
- report, maturity, aiConsentGranted, opts, gate, hasAgents, agentRoots, rootDir, lang, catalog,
60
- });
61
- return { report, maturity, root, gate };
62
- }
63
-
64
- // The MCP's scan: same steps, returned instead of printed, since stdout is its JSON-RPC channel.
65
- async function scanUsage(argv) {
66
- const opts = parseArgs(argv);
67
- const lang = opts.lang || detectReportLang();
68
- const { report, maturity } = await computeUsage({ opts, catalog: getCatalog(lang), lang });
69
- return { report, maturity };
70
- }
71
-
72
19
  // Shared by the dispatcher and register flow: an injected `ask` is caller-owned, `bypassThrottle` is register-only.
73
20
  async function run(argv = process.argv.slice(2), { ask: injectedAsk = null, bypassThrottle = false, inRegistration = false } = {}) {
74
21
  const opts = parseArgs(argv);
@@ -85,13 +32,21 @@ async function run(argv = process.argv.slice(2), { ask: injectedAsk = null, bypa
85
32
  // One-shot commands (consent/endpoint config): act immediately, do NOT scan.
86
33
  if (await handleOneShotCommand({ opts, catalog, injectedAsk })) return;
87
34
 
88
- const { report, maturity, root, gate } = await computeUsage({ opts, catalog, lang, injectedAsk, inRegistration });
35
+ const { report, maturity, root, gate } = await computeUsage({
36
+ opts,
37
+ catalog,
38
+ lang,
39
+ inRegistration,
40
+ prepareIdentity: prepareIdentityAndUsage,
41
+ resolveConsent: (args) => resolveAiConsent({ ...args, injectedAsk }),
42
+ resolveScope: (args) => resolveRepoScope({ ...args, injectedAsk }),
43
+ });
89
44
 
90
45
  // 7. Render (or emit --json), persist, optional --build-next-level, auto-share.
91
46
  await renderAndFinalize({ report, maturity, lang, opts, root, gate, catalog, bypassThrottle });
92
47
  }
93
48
 
94
- module.exports = { run, scanUsage, resolveRepoScope, machineWideToplevels, promptScope };
49
+ module.exports = { run, resolveRepoScope, machineWideToplevels, promptScope };
95
50
 
96
51
  // Auto-run only when executed directly, not when require()'d by the dispatcher.
97
52
  if (require.main === module) {
package/bin/login.js CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- // `login` / `logout` — a registered Talent identifies themselves to the CLI (talents-ai-score, issue 122 / ADR-044).
4
+ // `login` / `logout` — a registered Talent identifies themselves to the CLI.
5
5
 
6
6
  const { detectReportLang, getCatalog } = require('../src/i18n');
7
7
  const { getAuthTokenEndpoint, getDeviceAuthorizeEndpoint, getDeviceTokenEndpoint, getCompleteRegistrationEndpoint } = require('../src/config');
8
- const { runDeviceLogin, appendProviderParam } = require('../src/device-login');
8
+ const { runDeviceLogin, appendProviderParam, deviceSessionRecord } = require('../src/device-login');
9
9
  const { buildRegistrationContext } = require('../src/signup-client');
10
10
  const { openPath } = require('../src/open-file');
11
+ const { endSession } = require('../src/session-refresh');
11
12
  const {
12
13
  saveAuthSession,
13
- clearAuthSession,
14
14
  loadAuthSession,
15
15
  sessionStatus,
16
16
  } = require('../src/auth-session-store');
@@ -68,13 +68,13 @@ async function run(
68
68
 
69
69
  if (!method) {
70
70
  const stdinIsTTY = ttyOverride !== undefined ? ttyOverride : !!process.stdin.isTTY;
71
- // ADR-044: an ACTIVE session is not silently overwritten, and the picker never shows.
71
+ // An ACTIVE session is not silently overwritten, and the picker never shows.
72
72
  if (sessionStatus(loadAuthSession()) === 'active') {
73
73
  (output || process.stdout).write(`\n ${c.alreadyLoggedIn}\n\n`);
74
74
  return;
75
75
  }
76
76
  if (stdinIsTTY) {
77
- // ADR-041/046 — the picker offers Email, Google and LinkedIn.
77
+ // The picker offers Email, Google and LinkedIn.
78
78
  const pickerAsk = injectedAsk || createStdinAsk();
79
79
  try {
80
80
  method = await chooseLoginMethod({ ask: pickerAsk, stdinIsTTY, c, input, output });
@@ -108,6 +108,8 @@ function persistAndReport(result, c, email = null, output = process.stdout) {
108
108
  accessTokenExpiresAt: result.expiresAt,
109
109
  cookie: result.cookie || null,
110
110
  cookieExpiresAt: result.cookieExpiresAt || null,
111
+ sessionToken: result.sessionToken || null,
112
+ sessionExpiresAt: result.sessionExpiresAt || null,
111
113
  email,
112
114
  hubAccessToken: result.hubAccessToken || null,
113
115
  });
@@ -167,9 +169,8 @@ async function runDeviceFlow(provider, c, { openBrowser = openPath, sleep = unde
167
169
 
168
170
  function handleDeviceResult(result, c, output = process.stdout) {
169
171
  if (result.ok) {
170
- // Map the device-flow JWT to the shared persist shape (no cookie).
171
172
  return void persistAndReport(
172
- { accessToken: result.token, hubAccessToken: result.token, email: result.email || null },
173
+ deviceSessionRecord(result),
173
174
  c,
174
175
  result.email || null,
175
176
  output,
@@ -196,13 +197,12 @@ function handleDeviceResult(result, c, output = process.stdout) {
196
197
  process.exitCode = 1;
197
198
  }
198
199
 
199
- // `logout` — local only (no endpoint, no network): forget the stored session.
200
+ // `logout`: revoke the hub session (best-effort) and forget the stored one.
200
201
  async function runLogout(argv = process.argv.slice(2), { output = process.stdout } = {}) {
201
202
  const opts = parseArgs(argv);
202
203
  const lang = opts.lang || detectReportLang();
203
204
  const c = getCatalog(lang).login;
204
- const had = loadAuthSession() !== null;
205
- clearAuthSession();
205
+ const had = await endSession(process.env);
206
206
  clearConsentState();
207
207
  output.write(`\n ${had ? c.loggedOut : c.notLoggedIn}\n\n`);
208
208
  }
package/bin/mcp.js CHANGED
@@ -65,9 +65,9 @@ const LOCAL_ONLY_TOOL_NAMES = new Set(['ai_usage', 'ai_usage_result', 'read_cv',
65
65
 
66
66
  // `localOnly` builds the scan-only server for the Claude .mcpb bundle, reusing the SAME
67
67
  // tool registry (just filtered) — no duplicate registration.
68
- function buildServer({ localOnly = false, funnel = createFunnel({ surface: 'mcp' }) } = {}) {
68
+ function buildServer({ localOnly = false, funnel = createFunnel({ surface: 'mcp' }), makeInterviewClient = null } = {}) {
69
69
  const lang = detectFlowLang();
70
- const allTools = [makeAiUsageTool(), makeAiUsageResultTool(), ...makeOnboardingTools(), ...makeRoleTools({ lang, funnel }), ...makeDeviceLoginTools(), ...makeSignupTools({ lang, funnel }), ...makeRegisterTools({ lang, funnel }), ...makeReportTools(), ...makeCertifyDimensionTools(), ...makeFindProjectsTools(), ...makeShowProjectTools(), ...makeSavedPositionsTools(), ...makeInvitationsTools(), ...makeApplicationsTools(), ...makeAvailabilityTools(), ...makeCertificationsTools(), ...makeProfileTools(), ...makeRateTools(), ...makeLangTools(), ...makeSocialsTools(), ...makePortfolioTools(), ...makeAlmaTools()];
70
+ const allTools = [makeAiUsageTool(), makeAiUsageResultTool(), ...makeOnboardingTools(), ...makeRoleTools({ lang, funnel }), ...makeDeviceLoginTools(), ...makeSignupTools({ lang, funnel }), ...makeRegisterTools({ lang, funnel, flowDeps: { makeInterviewClient } }), ...makeReportTools(), ...makeCertifyDimensionTools(), ...makeFindProjectsTools(), ...makeShowProjectTools(), ...makeSavedPositionsTools(), ...makeInvitationsTools(), ...makeApplicationsTools(), ...makeAvailabilityTools(), ...makeCertificationsTools(), ...makeProfileTools(), ...makeRateTools(), ...makeLangTools(), ...makeSocialsTools(), ...makePortfolioTools(), ...makeAlmaTools()];
71
71
  const tools = localOnly ? allTools.filter((t) => LOCAL_ONLY_TOOL_NAMES.has(t.name)) : allTools;
72
72
  // Slash-command prompts, kept to the tools actually registered in this mode.
73
73
  const prompts = makePrompts(new Set(tools.map((t) => t.name)), lang);
@@ -79,6 +79,7 @@ function buildServer({ localOnly = false, funnel = createFunnel({ surface: 'mcp'
79
79
  // Long-lived server: refresh the email session's hub JWT from the stored
80
80
  // cookie before each tool call so a stale (~15-min) JWT never reaches a tool.
81
81
  beforeCall: () => ensureFreshSession(process.env),
82
+ afterCall: () => { funnel.flush(); },
82
83
  onInitialize: ({ clientInfo }) => funnel.setMcpClient(clientInfo.name),
83
84
  });
84
85
  }
@@ -88,21 +89,23 @@ function prepareProcess(env = process.env) {
88
89
  require('../src/backend-request').setClientIdentity('mcp');
89
90
  }
90
91
 
91
- async function run(argv = process.argv.slice(2)) {
92
- // The installer's last step; not a command the Talent needs to know.
93
- if (argv[0] === 'install') {
94
- await require('../src/mcp-install').runMcpInstall();
95
- return;
96
- }
92
+ async function run(argv = process.argv.slice(2), { makeInterviewClient = null } = {}) {
97
93
  if (argv.includes('--help') || argv.includes('-h')) {
98
94
  process.stdout.write(HELP);
99
95
  return;
100
96
  }
101
97
 
98
+ const problem = require('../src/config').envProfileProblem(process.env);
99
+ if (problem) {
100
+ process.stderr.write(` [mcp] ${problem}\n`);
101
+ process.exitCode = 1;
102
+ return;
103
+ }
102
104
  prepareProcess();
103
105
 
104
106
  // `--local` = the scan-only server shipped in the Claude .mcpb bundle.
105
- const server = buildServer({ localOnly: argv.includes('--local') });
107
+ const funnel = createFunnel({ surface: 'mcp' });
108
+ const server = buildServer({ localOnly: argv.includes('--local'), funnel, makeInterviewClient });
106
109
  const rl = server.start({
107
110
  input: process.stdin,
108
111
  output: process.stdout,
@@ -115,6 +118,7 @@ async function run(argv = process.argv.slice(2)) {
115
118
  rl.on('close', resolve);
116
119
  process.stdin.on('end', () => rl.close());
117
120
  });
121
+ await funnel.settle();
118
122
  }
119
123
 
120
124
  module.exports = { run, buildServer, buildServerInstructions, prepareProcess };
package/bin/mcpb.js CHANGED
@@ -2,4 +2,4 @@
2
2
  'use strict';
3
3
 
4
4
  // Claude Desktop's built-in Node imports this entry from its own host, so it starts without a require.main check.
5
- require('./mcp').run();
5
+ require('./mcp').run(process.argv.slice(2), { makeInterviewClient: require('../src/interview-client').createInterviewClient });
package/bin/onboarding.js CHANGED
@@ -5,6 +5,7 @@ const { detectFlowLang } = require('../src/i18n');
5
5
  const { loadAuthSession, sessionStatus } = require('../src/auth-session-store');
6
6
  const { createStdinAsk } = require('../src/stdin-ask');
7
7
  const { runOnboardingInterview, runRepeatOnboardingInterview, makeDeps } = require('../src/onboarding-flow');
8
+ const { createInterviewClient } = require('../src/interview-client');
8
9
  const { checkOnboardingCompleted } = require('../src/onboarding-availability');
9
10
  const { makeIo } = require('./register');
10
11
  const { c: fmt } = require('../src/terminal-format');
@@ -87,7 +88,7 @@ async function run(
87
88
  // Explicit overwrite-repeat: its own warning + confirmation, bypassing the legacy already-completed gate.
88
89
  if (o.repeat) {
89
90
  const io = makeIo({ ask, lang, out: write, acceptDisclaimer: o.acceptDisclaimer, stdinIsTTY });
90
- const result = await runRepeatOnboardingInterview(io, deps || makeDeps(), { confirmed: o.yes });
91
+ const result = await runRepeatOnboardingInterview(io, deps || makeDeps({ makeInterviewClient: createInterviewClient }), { confirmed: o.yes });
91
92
  if (result && result.ok === false) {
92
93
  process.stderr.write(`\n ${red(`onboarding: ${result.reason}`)}\n\n`);
93
94
  process.exitCode = 1;
@@ -116,7 +117,7 @@ async function run(
116
117
  }
117
118
 
118
119
  const io = makeIo({ ask, lang, out: write, acceptDisclaimer: o.acceptDisclaimer, stdinIsTTY });
119
- const result = await runOnboardingInterview(io, deps || makeDeps());
120
+ const result = await runOnboardingInterview(io, deps || makeDeps({ makeInterviewClient: createInterviewClient }));
120
121
  if (!result.ok) {
121
122
  process.stderr.write(`\n ${red(`onboarding: ${result.reason}${result.step ? ` (${result.step})` : ''}`)}\n\n`);
122
123
  process.exitCode = 1;
package/bin/register.js CHANGED
@@ -11,6 +11,7 @@ const {
11
11
  WORK_SITUATIONS, EMPLOYMENT_PARTICIPATIONS, FREELANCE_OPINIONS, CHANGE_MOTIVATORS, WORK_MODES,
12
12
  LANGUAGE_CODES, LANGUAGE_LEVELS, showsFreelanceOpinion, showsChangeMotivators,
13
13
  } = require('../src/onboarding-flow');
14
+ const { createInterviewClient } = require('../src/interview-client');
14
15
  const { promptSelect, promptMultiSelect } = require('../src/prompt-select');
15
16
  const { chooseAuthMethod } = require('../src/auth-method-picker');
16
17
  const { methodIdsForFlow } = require('../src/auth-methods');
@@ -305,7 +306,20 @@ async function run(
305
306
  return;
306
307
  }
307
308
 
308
- const ask = injectedAsk || createStdinAsk();
309
+ // Injected deps (tests) bring their own funnel or none.
310
+ const registrationFunnel = funnel || (deps ? undefined : createFunnel({ surface: 'cli' }));
311
+ let interrupted = false;
312
+ const onInterrupt = () => {
313
+ if (interrupted) process.exit(130);
314
+ interrupted = true;
315
+ process.stdout.write('\n');
316
+ const settled = registrationFunnel ? registrationFunnel.settle() : Promise.resolve();
317
+ settled.then(() => {
318
+ ask.close();
319
+ process.exit(130);
320
+ });
321
+ };
322
+ const ask = injectedAsk || createStdinAsk({ onInterrupt });
309
323
  try {
310
324
  // Method choice (Email + Google) from the shared auth-methods registry; a
311
325
  // valid method flag skips the picker.
@@ -317,9 +331,7 @@ async function run(
317
331
  const io = makeIo({ ask, lang, out: write, acceptDisclaimer: o.acceptDisclaimer, stdinIsTTY });
318
332
  // At a terminal the profile opens in the browser at the end; piped runs, tests and --no-open only print it.
319
333
  const openProfile = stdinIsTTY && !o.noOpen ? (openBrowser || openPath) : undefined;
320
- // Injected deps (tests) bring their own funnel or none.
321
- const registrationFunnel = funnel || (deps ? undefined : createFunnel({ surface: 'cli' }));
322
- const result = await runOnboarding(io, deps || makeDeps(), { method, openBrowser, onAuthUrl: io.onGoogleAuthUrl, openProfile, funnel: registrationFunnel });
334
+ const result = await runOnboarding(io, deps || makeDeps({ makeInterviewClient: createInterviewClient }), { method, openBrowser, onAuthUrl: io.onGoogleAuthUrl, openProfile, funnel: registrationFunnel });
323
335
  if (!result.ok) {
324
336
  process.stderr.write(`\n ${red(`register: ${result.reason}${result.step ? ` (${result.step})` : ''}`)}\n\n`);
325
337
  process.exitCode = 1;
@@ -327,6 +339,7 @@ async function run(
327
339
  return result;
328
340
  } finally {
329
341
  if (!injectedAsk) ask.close();
342
+ if (registrationFunnel) await registrationFunnel.settle();
330
343
  }
331
344
  }
332
345
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- // `report` — materializes and OPENS the shareable, cumulative HTML report for the current project (ADR-016).
4
+ // `report` — materializes and OPENS the shareable, cumulative HTML report for the current project.
5
5
 
6
6
  const { detectReportLang, getCatalog } = require('../src/i18n');
7
7
  const { materializeProjectReport } = require('../src/report-store');
@@ -119,7 +119,7 @@ async function run(argv = process.argv.slice(2), { ask } = {}) { // eslint-disab
119
119
  const opened = opts.open ? openPath(result.htmlPath) : false;
120
120
 
121
121
  // Clear "the report was UPDATED" notice (fires once per run — this is the sole
122
- // report write point, ADR-016). OSC 8: clickable file:// link where supported.
122
+ // report write point). OSC 8: clickable file:// link where supported.
123
123
  const updatedLabel = lang === 'es' ? 'Report actualizado' : 'Report updated';
124
124
  process.stdout.write(`\n ✓ ${updatedLabel} · ${oscLink(result.fileUrl)}\n`);
125
125
  if (opened) process.stdout.write(` ${r.opening}\n`);