shakers-cli 0.15.0 → 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.
- package/README.md +51 -1203
- package/bin/ai-usage.js +11 -56
- package/bin/login.js +10 -10
- package/bin/mcp.js +13 -9
- package/bin/mcpb.js +1 -1
- package/bin/onboarding.js +3 -2
- package/bin/register.js +17 -4
- package/bin/report-html.js +2 -2
- package/bin/shakers-cli.js +352 -0
- package/bin/shakers.js +20 -344
- package/bin/share.js +1 -1
- package/bin/superadmin.js +5 -5
- package/bin/version.js +3 -0
- package/package.json +12 -3
- package/src/agent-category.js +1 -1
- package/src/agent-evaluation.js +3 -3
- package/src/agent-org-chart.js +5 -5
- package/src/agent-synthesis.js +3 -3
- package/src/agent-usage.js +1 -1
- package/src/agents-client.js +4 -4
- package/src/ai.js +1 -1
- package/src/ansi.js +1 -1
- package/src/auth-client.js +18 -4
- package/src/auth-method-picker.js +1 -1
- package/src/auth-methods.js +1 -1
- package/src/auth-session-store.js +11 -8
- package/src/authorship-mismatch.js +1 -1
- package/src/authorship.js +2 -2
- package/src/automations-detector.js +1 -1
- package/src/backend-request.js +3 -3
- package/src/brand-ansi.js +1 -1
- package/src/browser-tools-detector.js +1 -1
- package/src/certify-disclaimer.js +1 -1
- package/src/certify-remediation-prompt.js +1 -1
- package/src/cli-args.js +2 -2
- package/src/command-audit.js +8 -5
- package/src/config-dir.js +1 -1
- package/src/config.js +112 -48
- package/src/consent-flow.js +4 -4
- package/src/consent-skip.js +1 -1
- package/src/detection-evidence.js +2 -2
- package/src/detection-noise.js +1 -1
- package/src/detectors.js +1 -1
- package/src/device-login.js +12 -1
- package/src/email-verification.js +3 -3
- package/src/env-paths.js +1 -1
- package/src/find-projects-client.js +12 -0
- package/src/find-projects-flow.js +1 -13
- package/src/git-activity.js +1 -1
- package/src/graph-certs.js +3 -3
- package/src/graph-scan.js +9 -9
- package/src/i18n.js +124 -124
- package/src/interactive-select.js +1 -1
- package/src/interview-client.js +6 -1
- package/src/interview-transcript.js +1 -1
- package/src/legal-notice.js +1 -1
- package/src/maturity.js +6 -6
- package/src/mcp-ai-usage-tool.js +1 -1
- package/src/mcp-auth-form.js +1 -1
- package/src/mcp-detector.js +3 -3
- package/src/mcp-device-login-tools.js +2 -1
- package/src/mcp-find-projects-tools.js +1 -2
- package/src/mcp-onboarding-tools.js +4 -4
- package/src/mcp-prompts.js +2 -2
- package/src/mcp-register-tools.js +1 -0
- package/src/mcp-server.js +5 -1
- package/src/mcp-signup-client.js +2 -2
- package/src/mcp-signup-tools.js +26 -2
- package/src/memory-structure-detector.js +1 -1
- package/src/model-call-record.js +2 -2
- package/src/onboarding-flow.js +16 -16
- package/src/onboarding-funnel.js +110 -17
- package/src/open-file.js +1 -1
- package/src/osc-link.js +1 -1
- package/src/portfolio-client.js +11 -1
- package/src/portfolio-git-info.js +1 -1
- package/src/profile-flow.js +1 -1
- package/src/profile-switch.js +5 -12
- package/src/render-certification.js +10 -10
- package/src/render-html.js +6 -6
- package/src/render-sheet.js +10 -10
- package/src/render-terminal.js +6 -6
- package/src/repo-scope.js +132 -0
- package/src/report-gating.js +1 -1
- package/src/report-store.js +6 -6
- package/src/roadmap-content.js +5 -5
- package/src/roadmap-personalization.js +3 -3
- package/src/roadmap-prompt.js +2 -2
- package/src/scan-exclusions.js +1 -1
- package/src/scanner.js +14 -14
- package/src/secret-prompt.js +1 -1
- package/src/session-gate.js +1 -1
- package/src/session-refresh.js +33 -3
- package/src/session-scan.js +1 -1
- package/src/shakers-wordmark.js +1 -1
- package/src/share-card.js +2 -2
- package/src/share-certification.js +4 -4
- package/src/share-consent-state.js +7 -7
- package/src/share-payload.js +7 -7
- package/src/share.js +7 -7
- package/src/signup-client.js +1 -1
- package/src/signup-copy.js +1 -1
- package/src/skills-client.js +1 -1
- package/src/start-add-agents.js +2 -2
- package/src/start-add-portfolio.js +3 -13
- package/src/start-add-skills.js +2 -2
- package/src/stdin-ask.js +3 -3
- package/src/strip-internal-comments.js +1 -1
- package/src/tech-detector.js +2 -2
- package/src/templates/report-sheet.html +11 -13
- package/src/terminal-agents.js +8 -8
- package/src/terminal-format.js +3 -3
- package/src/terminal-sections.js +11 -11
- package/src/tier-engine.js +3 -3
- package/src/tier-projection.js +2 -2
- package/src/transcript-turns.js +1 -1
- package/src/typewriter.js +1 -1
- package/src/usage-model-calls.js +6 -6
- package/src/usage-oneshot-commands.js +5 -5
- package/src/usage-repo-scope.js +11 -129
- package/src/usage-run-steps.js +32 -184
- package/src/usage-scan.js +235 -0
- 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 {
|
|
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({
|
|
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,
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/bin/report-html.js
CHANGED
|
@@ -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
|
|
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
|
|
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`);
|