shakers-cli 0.15.0 → 0.17.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 +56 -1203
- package/bin/ai-usage.js +32 -58
- package/bin/login.js +10 -10
- package/bin/mcp.js +17 -12
- package/bin/mcpb.js +1 -1
- package/bin/onboarding.js +3 -2
- package/bin/register.js +19 -5
- 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/account-marker.js +40 -0
- package/src/account-trace.js +23 -0
- 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-usage-review.js +101 -0
- 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 +18 -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 +5 -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 +114 -48
- package/src/consent-flow.js +7 -6
- 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 +148 -138
- 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-result-tool.js +34 -12
- package/src/mcp-ai-usage-tool.js +282 -163
- package/src/mcp-auth-form.js +7 -3
- 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-install.js +279 -44
- package/src/mcp-instructions.js +1 -1
- package/src/mcp-onboarding-tools.js +4 -4
- package/src/mcp-prompts.js +4 -4
- package/src/mcp-register-tools.js +1 -0
- package/src/mcp-role-tools.js +24 -1
- package/src/mcp-server.js +5 -1
- package/src/mcp-signup-client.js +2 -2
- package/src/mcp-signup-tools.js +39 -9
- package/src/memory-structure-detector.js +1 -1
- package/src/model-call-record.js +2 -2
- package/src/onboarding-flow.js +63 -42
- package/src/onboarding-funnel.js +153 -20
- 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-report-text.js +1 -1
- 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 +5 -3
- package/src/report-store.js +13 -8
- package/src/roadmap-content.js +5 -5
- package/src/roadmap-personalization.js +3 -3
- package/src/roadmap-prompt.js +2 -2
- package/src/roles-flow.js +8 -5
- 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 +9 -9
- 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 +177 -2
- 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 +16 -4
- 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 +82 -181
- package/src/usage-scan.js +235 -0
- package/src/work-streams.js +1 -1
package/bin/ai-usage.js
CHANGED
|
@@ -1,74 +1,23 @@
|
|
|
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 {
|
|
11
|
-
const {
|
|
12
|
-
const { AUTHORSHIP_LOG_ARGS } = require('../src/authorship');
|
|
7
|
+
const { createLazyStdinAsk } = require('../src/stdin-ask');
|
|
8
|
+
const { computeUsage } = require('../src/usage-scan');
|
|
9
|
+
const { hasAccountTrace } = require('../src/account-trace');
|
|
13
10
|
const {
|
|
14
11
|
resolveRepoScope,
|
|
15
12
|
machineWideToplevels,
|
|
16
13
|
promptScope,
|
|
17
14
|
} = require('../src/usage-repo-scope');
|
|
18
15
|
const {
|
|
19
|
-
performScan,
|
|
20
16
|
prepareIdentityAndUsage,
|
|
21
17
|
resolveAiConsent,
|
|
22
|
-
runAggregation,
|
|
23
|
-
runModelCalls,
|
|
24
18
|
renderAndFinalize,
|
|
25
19
|
} = require('../src/usage-run-steps');
|
|
26
20
|
|
|
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
21
|
// Shared by the dispatcher and register flow: an injected `ask` is caller-owned, `bypassThrottle` is register-only.
|
|
73
22
|
async function run(argv = process.argv.slice(2), { ask: injectedAsk = null, bypassThrottle = false, inRegistration = false } = {}) {
|
|
74
23
|
const opts = parseArgs(argv);
|
|
@@ -85,13 +34,38 @@ async function run(argv = process.argv.slice(2), { ask: injectedAsk = null, bypa
|
|
|
85
34
|
// One-shot commands (consent/endpoint config): act immediately, do NOT scan.
|
|
86
35
|
if (await handleOneShotCommand({ opts, catalog, injectedAsk })) return;
|
|
87
36
|
|
|
88
|
-
|
|
37
|
+
if (opts.roadmap && hasAccountTrace()) {
|
|
38
|
+
process.stdout.write(`\n ${catalog.cli.roadmapNotForAccounts}\n\n`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
89
41
|
|
|
90
|
-
|
|
91
|
-
|
|
42
|
+
const ask = injectedAsk || createLazyStdinAsk();
|
|
43
|
+
try {
|
|
44
|
+
const { report, maturity, root, gate, repos } = await computeUsage({
|
|
45
|
+
opts,
|
|
46
|
+
catalog,
|
|
47
|
+
lang,
|
|
48
|
+
inRegistration,
|
|
49
|
+
prepareIdentity: prepareIdentityAndUsage,
|
|
50
|
+
resolveConsent: (args) => resolveAiConsent({ ...args, injectedAsk: ask }),
|
|
51
|
+
resolveScope: async (args) => {
|
|
52
|
+
if (injectedAsk) return resolveRepoScope({ ...args, injectedAsk });
|
|
53
|
+
ask.suspend();
|
|
54
|
+
try {
|
|
55
|
+
return await resolveRepoScope(args);
|
|
56
|
+
} finally {
|
|
57
|
+
ask.resume();
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await renderAndFinalize({ report, maturity, lang, opts, root, gate, catalog, bypassThrottle, repos, injectedAsk: ask });
|
|
63
|
+
} finally {
|
|
64
|
+
if (!injectedAsk) ask.close();
|
|
65
|
+
}
|
|
92
66
|
}
|
|
93
67
|
|
|
94
|
-
module.exports = { run,
|
|
68
|
+
module.exports = { run, resolveRepoScope, machineWideToplevels, promptScope };
|
|
95
69
|
|
|
96
70
|
// Auto-run only when executed directly, not when require()'d by the dispatcher.
|
|
97
71
|
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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const { createMcpServer } = require('../src/mcp-server');
|
|
5
|
-
const {
|
|
5
|
+
const { makeAiUsageTools } = require('../src/mcp-ai-usage-tool');
|
|
6
6
|
const { makeAiUsageResultTool } = require('../src/mcp-ai-usage-result-tool');
|
|
7
7
|
const { makeCertifyDimensionTools } = require('../src/mcp-certify-dimension-tools');
|
|
8
8
|
const { makeFindProjectsTools } = require('../src/mcp-find-projects-tools');
|
|
@@ -43,7 +43,7 @@ const HELP = `\n shakers mcp — local MCP server (stdio)\n\n`
|
|
|
43
43
|
+ ` Usage: shakers mcp\n\n`
|
|
44
44
|
+ ` This command speaks JSON-RPC on stdio; it is meant to be launched by an\n`
|
|
45
45
|
+ ` MCP client, not run by hand.\n`
|
|
46
|
-
+ ` Tools: ai_usage, ai_usage_result, logout, social_signin_start, social_signin_poll,\n`
|
|
46
|
+
+ ` Tools: ai_usage, ai_usage_upload, ai_usage_result, logout, social_signin_start, social_signin_poll,\n`
|
|
47
47
|
+ ` list_addable, add_skill, add_agent,\n`
|
|
48
48
|
+ ` add_project, signup_start, signup_email, signup_draft, signup_create_account,\n`
|
|
49
49
|
+ ` signup_status, update_existing_profile, import_profile, save_profile_details, open_web,\n`
|
|
@@ -61,13 +61,14 @@ const HELP = `\n shakers mcp — local MCP server (stdio)\n\n`
|
|
|
61
61
|
|
|
62
62
|
// The LOCAL (filesystem/machine) tools — the only ones the .mcpb bundle exposes; the
|
|
63
63
|
// server-backed tools belong to the remote connector.
|
|
64
|
-
const LOCAL_ONLY_TOOL_NAMES = new Set(['ai_usage', 'ai_usage_result', 'read_cv', 'suggest_register_context']);
|
|
64
|
+
const LOCAL_ONLY_TOOL_NAMES = new Set(['ai_usage', 'ai_usage_upload', 'ai_usage_result', 'read_cv', 'suggest_register_context']);
|
|
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
|
|
70
|
+
const aiUsage = makeAiUsageTools();
|
|
71
|
+
const allTools = [aiUsage.usage, aiUsage.upload, 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
72
|
const tools = localOnly ? allTools.filter((t) => LOCAL_ONLY_TOOL_NAMES.has(t.name)) : allTools;
|
|
72
73
|
// Slash-command prompts, kept to the tools actually registered in this mode.
|
|
73
74
|
const prompts = makePrompts(new Set(tools.map((t) => t.name)), lang);
|
|
@@ -79,6 +80,7 @@ function buildServer({ localOnly = false, funnel = createFunnel({ surface: 'mcp'
|
|
|
79
80
|
// Long-lived server: refresh the email session's hub JWT from the stored
|
|
80
81
|
// cookie before each tool call so a stale (~15-min) JWT never reaches a tool.
|
|
81
82
|
beforeCall: () => ensureFreshSession(process.env),
|
|
83
|
+
afterCall: () => { funnel.flush(); },
|
|
82
84
|
onInitialize: ({ clientInfo }) => funnel.setMcpClient(clientInfo.name),
|
|
83
85
|
});
|
|
84
86
|
}
|
|
@@ -88,21 +90,23 @@ function prepareProcess(env = process.env) {
|
|
|
88
90
|
require('../src/backend-request').setClientIdentity('mcp');
|
|
89
91
|
}
|
|
90
92
|
|
|
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
|
-
}
|
|
93
|
+
async function run(argv = process.argv.slice(2), { makeInterviewClient = null } = {}) {
|
|
97
94
|
if (argv.includes('--help') || argv.includes('-h')) {
|
|
98
95
|
process.stdout.write(HELP);
|
|
99
96
|
return;
|
|
100
97
|
}
|
|
101
98
|
|
|
99
|
+
const problem = require('../src/config').envProfileProblem(process.env);
|
|
100
|
+
if (problem) {
|
|
101
|
+
process.stderr.write(` [mcp] ${problem}\n`);
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
102
105
|
prepareProcess();
|
|
103
106
|
|
|
104
107
|
// `--local` = the scan-only server shipped in the Claude .mcpb bundle.
|
|
105
|
-
const
|
|
108
|
+
const funnel = createFunnel({ surface: 'mcp' });
|
|
109
|
+
const server = buildServer({ localOnly: argv.includes('--local'), funnel, makeInterviewClient });
|
|
106
110
|
const rl = server.start({
|
|
107
111
|
input: process.stdin,
|
|
108
112
|
output: process.stdout,
|
|
@@ -115,6 +119,7 @@ async function run(argv = process.argv.slice(2)) {
|
|
|
115
119
|
rl.on('close', resolve);
|
|
116
120
|
process.stdin.on('end', () => rl.close());
|
|
117
121
|
});
|
|
122
|
+
await funnel.settle();
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
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');
|
|
@@ -121,9 +122,10 @@ function makeIo({ ask, lang, out, acceptDisclaimer, stdinIsTTY = false }) {
|
|
|
121
122
|
}
|
|
122
123
|
return { fullTimeSelected: true, fullTimeAmount, fullTimeCurrency, partTimeSelected, partTimeAmount, partTimeCurrency };
|
|
123
124
|
},
|
|
124
|
-
askAvailability: async (c) => {
|
|
125
|
+
askAvailability: async (c, { onLocation = () => {} } = {}) => {
|
|
125
126
|
const available = isYes(await ask(` ${c.availableAsk} `));
|
|
126
127
|
const monthlyHours = await promptSelect({ ask, stdinIsTTY, out: selectOut, items: MONTHLY_HOURS, header: ` ${c.monthlyHoursAsk}` });
|
|
128
|
+
onLocation();
|
|
127
129
|
const workModes = await promptMultiSelect({
|
|
128
130
|
ask,
|
|
129
131
|
stdinIsTTY,
|
|
@@ -305,7 +307,20 @@ async function run(
|
|
|
305
307
|
return;
|
|
306
308
|
}
|
|
307
309
|
|
|
308
|
-
|
|
310
|
+
// Injected deps (tests) bring their own funnel or none.
|
|
311
|
+
const registrationFunnel = funnel || (deps ? undefined : createFunnel({ surface: 'cli' }));
|
|
312
|
+
let interrupted = false;
|
|
313
|
+
const onInterrupt = () => {
|
|
314
|
+
if (interrupted) process.exit(130);
|
|
315
|
+
interrupted = true;
|
|
316
|
+
process.stdout.write('\n');
|
|
317
|
+
const settled = registrationFunnel ? registrationFunnel.settle() : Promise.resolve();
|
|
318
|
+
settled.then(() => {
|
|
319
|
+
ask.close();
|
|
320
|
+
process.exit(130);
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
const ask = injectedAsk || createStdinAsk({ onInterrupt });
|
|
309
324
|
try {
|
|
310
325
|
// Method choice (Email + Google) from the shared auth-methods registry; a
|
|
311
326
|
// valid method flag skips the picker.
|
|
@@ -317,9 +332,7 @@ async function run(
|
|
|
317
332
|
const io = makeIo({ ask, lang, out: write, acceptDisclaimer: o.acceptDisclaimer, stdinIsTTY });
|
|
318
333
|
// At a terminal the profile opens in the browser at the end; piped runs, tests and --no-open only print it.
|
|
319
334
|
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 });
|
|
335
|
+
const result = await runOnboarding(io, deps || makeDeps({ makeInterviewClient: createInterviewClient }), { method, openBrowser, onAuthUrl: io.onGoogleAuthUrl, openProfile, funnel: registrationFunnel });
|
|
323
336
|
if (!result.ok) {
|
|
324
337
|
process.stderr.write(`\n ${red(`register: ${result.reason}${result.step ? ` (${result.step})` : ''}`)}\n\n`);
|
|
325
338
|
process.exitCode = 1;
|
|
@@ -327,6 +340,7 @@ async function run(
|
|
|
327
340
|
return result;
|
|
328
341
|
} finally {
|
|
329
342
|
if (!injectedAsk) ask.close();
|
|
343
|
+
if (registrationFunnel) await registrationFunnel.settle();
|
|
330
344
|
}
|
|
331
345
|
}
|
|
332
346
|
|
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`);
|