shakers-cli-beta 0.8.0-beta.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/LICENSE +59 -0
- package/README.md +1175 -0
- package/bin/add-agent.js +88 -0
- package/bin/add-project.js +88 -0
- package/bin/add-role.js +61 -0
- package/bin/add-skill.js +88 -0
- package/bin/ai-usage.js +81 -0
- package/bin/alma.js +99 -0
- package/bin/applications.js +69 -0
- package/bin/availability.js +70 -0
- package/bin/certifications.js +69 -0
- package/bin/certify.js +73 -0
- package/bin/change-role.js +61 -0
- package/bin/config.js +120 -0
- package/bin/experiences.js +69 -0
- package/bin/find-projects.js +89 -0
- package/bin/invitations.js +69 -0
- package/bin/lang.js +70 -0
- package/bin/login.js +283 -0
- package/bin/mcp.js +98 -0
- package/bin/me.js +69 -0
- package/bin/onboarding.js +134 -0
- package/bin/portfolios.js +69 -0
- package/bin/rate.js +70 -0
- package/bin/register.js +325 -0
- package/bin/report-html.js +134 -0
- package/bin/save.js +77 -0
- package/bin/shakers.js +344 -0
- package/bin/share.js +72 -0
- package/bin/show-project.js +72 -0
- package/bin/socials.js +70 -0
- package/bin/superadmin.js +351 -0
- package/package.json +40 -0
- package/src/agent-category.js +13 -0
- package/src/agent-evaluation.js +177 -0
- package/src/agent-org-chart.js +355 -0
- package/src/agent-synthesis.js +132 -0
- package/src/agent-usage.js +118 -0
- package/src/agents-client.js +165 -0
- package/src/ai-fluency-cell.js +72 -0
- package/src/ai-profile-client.js +45 -0
- package/src/ai-profile-preview.js +154 -0
- package/src/ai.js +66 -0
- package/src/alma-client.js +123 -0
- package/src/alma-flow.js +32 -0
- package/src/alma-repo-context.js +47 -0
- package/src/alma-shell.js +161 -0
- package/src/ansi.js +62 -0
- package/src/applications-client.js +79 -0
- package/src/applications-flow.js +58 -0
- package/src/auth-client.js +144 -0
- package/src/auth-device-client.js +118 -0
- package/src/auth-method-picker.js +44 -0
- package/src/auth-methods.js +35 -0
- package/src/auth-session-store.js +214 -0
- package/src/authorship.js +203 -0
- package/src/automations-detector.js +170 -0
- package/src/availability-client.js +92 -0
- package/src/availability-flow.js +105 -0
- package/src/backend-request.js +170 -0
- package/src/blind-approval-extract.js +48 -0
- package/src/brand-ansi.js +14 -0
- package/src/brand-svg.js +9 -0
- package/src/browser-tools-detector.js +21 -0
- package/src/build-next-level.js +56 -0
- package/src/certifications-client.js +89 -0
- package/src/certifications-flow.js +75 -0
- package/src/certify-dimension-client.js +217 -0
- package/src/certify-dimension-flow.js +193 -0
- package/src/certify-disclaimer.js +51 -0
- package/src/certify-remediation-prompt.js +23 -0
- package/src/cli-args.js +83 -0
- package/src/config-dir.js +120 -0
- package/src/config.js +778 -0
- package/src/consent-flow.js +115 -0
- package/src/consent-skip.js +27 -0
- package/src/decisions-extract.js +43 -0
- package/src/detect-repos.js +145 -0
- package/src/detected-tools.js +15 -0
- package/src/detection-evidence.js +105 -0
- package/src/detection-noise.js +132 -0
- package/src/detectors.js +221 -0
- package/src/device-auth-store.js +100 -0
- package/src/device-login.js +118 -0
- package/src/email-verification.js +152 -0
- package/src/env-paths.js +16 -0
- package/src/find-projects-cache.js +89 -0
- package/src/find-projects-client.js +138 -0
- package/src/find-projects-flow.js +164 -0
- package/src/git-activity.js +123 -0
- package/src/graph-certs.js +58 -0
- package/src/graph-generator.js +230 -0
- package/src/graph-scan.js +239 -0
- package/src/i18n.js +3264 -0
- package/src/inter-fonts.js +6 -0
- package/src/interactive-select.js +183 -0
- package/src/interview-client.js +226 -0
- package/src/interview-transcript.js +56 -0
- package/src/inventory-client.js +90 -0
- package/src/invitations-client.js +66 -0
- package/src/invitations-flow.js +50 -0
- package/src/lang-client.js +147 -0
- package/src/lang-codes.js +14 -0
- package/src/lang-flow.js +85 -0
- package/src/legal-notice.js +53 -0
- package/src/locale.js +51 -0
- package/src/markdown-ansi.js +50 -0
- package/src/maturity.js +111 -0
- package/src/mcp-ai-usage-result-tool.js +109 -0
- package/src/mcp-ai-usage-tool.js +333 -0
- package/src/mcp-alma-tools.js +91 -0
- package/src/mcp-applications-tools.js +42 -0
- package/src/mcp-ask-tools.js +50 -0
- package/src/mcp-auth-form.js +282 -0
- package/src/mcp-availability-tools.js +43 -0
- package/src/mcp-certifications-tools.js +42 -0
- package/src/mcp-certify-dimension-tools.js +58 -0
- package/src/mcp-detector.js +223 -0
- package/src/mcp-device-login-tools.js +173 -0
- package/src/mcp-find-projects-tools.js +67 -0
- package/src/mcp-install.js +245 -0
- package/src/mcp-invitations-tools.js +42 -0
- package/src/mcp-lang-tools.js +39 -0
- package/src/mcp-onboarding-tools.js +388 -0
- package/src/mcp-portfolios-tools.js +46 -0
- package/src/mcp-profile-tools.js +46 -0
- package/src/mcp-rate-tools.js +42 -0
- package/src/mcp-register-tools.js +517 -0
- package/src/mcp-report-tools.js +101 -0
- package/src/mcp-role-tools.js +112 -0
- package/src/mcp-saved-positions-tools.js +55 -0
- package/src/mcp-server.js +170 -0
- package/src/mcp-show-project-tools.js +50 -0
- package/src/mcp-socials-tools.js +39 -0
- package/src/memory-structure-detector.js +108 -0
- package/src/model-call-record.js +198 -0
- package/src/onboarding-availability.js +26 -0
- package/src/onboarding-client.js +364 -0
- package/src/onboarding-flow.js +1078 -0
- package/src/onboarding-livekit-client.js +102 -0
- package/src/onboarding-status-client.js +42 -0
- package/src/open-file.js +32 -0
- package/src/osc-link.js +16 -0
- package/src/portfolio-client.js +164 -0
- package/src/portfolio-git-info.js +61 -0
- package/src/portfolios-client.js +85 -0
- package/src/portfolios-flow.js +79 -0
- package/src/profile-client.js +125 -0
- package/src/profile-flow.js +115 -0
- package/src/profile-switch.js +46 -0
- package/src/prompt-select.js +29 -0
- package/src/rate-client.js +82 -0
- package/src/rate-flow.js +137 -0
- package/src/render-ai-fluency.js +266 -0
- package/src/render-certification.js +177 -0
- package/src/render-html.js +140 -0
- package/src/render-report-text.js +27 -0
- package/src/render-sheet.js +261 -0
- package/src/render-terminal.js +109 -0
- package/src/render-usage-report-text.js +155 -0
- package/src/repo-context.js +215 -0
- package/src/report-gating.js +49 -0
- package/src/report-store.js +226 -0
- package/src/report-theme.js +177 -0
- package/src/report-view-client.js +50 -0
- package/src/reveal-output.js +29 -0
- package/src/roadmap-content.js +453 -0
- package/src/roadmap-personalization.js +132 -0
- package/src/roadmap-prompt.js +154 -0
- package/src/roles-client.js +114 -0
- package/src/roles-flow.js +192 -0
- package/src/sanitize-network-text.js +36 -0
- package/src/saved-positions-client.js +71 -0
- package/src/saved-positions-flow.js +50 -0
- package/src/scan-exclusions.js +50 -0
- package/src/scanner.js +612 -0
- package/src/scope-signals.js +148 -0
- package/src/secret-prompt.js +72 -0
- package/src/session-gate.js +30 -0
- package/src/session-refresh.js +51 -0
- package/src/session-scan.js +336 -0
- package/src/session-signals.js +36 -0
- package/src/shakers-wordmark.js +17 -0
- package/src/share-card.js +362 -0
- package/src/share-certification.js +111 -0
- package/src/share-consent-state.js +157 -0
- package/src/share-payload.js +300 -0
- package/src/share.js +184 -0
- package/src/show-project-client.js +58 -0
- package/src/show-project-flow.js +119 -0
- package/src/signup-client.js +91 -0
- package/src/skills-client.js +102 -0
- package/src/socials-client.js +91 -0
- package/src/socials-flow.js +83 -0
- package/src/specificity-extract.js +41 -0
- package/src/start-add-agents.js +306 -0
- package/src/start-add-portfolio.js +388 -0
- package/src/start-add-skills.js +260 -0
- package/src/stdin-ask.js +129 -0
- package/src/steering-extract.js +43 -0
- package/src/strip-internal-comments.js +45 -0
- package/src/tech-detector.js +295 -0
- package/src/templates/report-sheet.html +572 -0
- package/src/terminal-agents.js +199 -0
- package/src/terminal-format.js +85 -0
- package/src/terminal-progress.js +106 -0
- package/src/terminal-sections.js +219 -0
- package/src/tier-analysis.js +126 -0
- package/src/tier-engine.js +114 -0
- package/src/tier-projection.js +117 -0
- package/src/transcript-turns.js +49 -0
- package/src/translations-client.js +84 -0
- package/src/typewriter.js +154 -0
- package/src/usage-model-calls.js +119 -0
- package/src/usage-oneshot-commands.js +180 -0
- package/src/usage-repo-scope.js +162 -0
- package/src/usage-run-steps.js +338 -0
- package/src/work-streams.js +74 -0
package/bin/login.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// `login` / `logout` — a registered Talent identifies themselves to the CLI (talents-ai-score, issue 122 / ADR-044).
|
|
5
|
+
|
|
6
|
+
const { detectReportLang, getCatalog } = require('../src/i18n');
|
|
7
|
+
const { getLoginEndpoint, getAuthTokenEndpoint, getDeviceAuthorizeEndpoint, getDeviceTokenEndpoint, getCompleteRegistrationEndpoint } = require('../src/config');
|
|
8
|
+
const { requestLogin } = require('../src/auth-client');
|
|
9
|
+
const { runDeviceLogin, appendProviderParam } = require('../src/device-login');
|
|
10
|
+
const { buildRegistrationContext } = require('../src/signup-client');
|
|
11
|
+
const { openPath } = require('../src/open-file');
|
|
12
|
+
const {
|
|
13
|
+
saveAuthSession,
|
|
14
|
+
clearAuthSession,
|
|
15
|
+
loadAuthSession,
|
|
16
|
+
sessionStatus,
|
|
17
|
+
} = require('../src/auth-session-store');
|
|
18
|
+
const { isValidEmail, normalizeEmail, loadConsentState, clearConsentState } = require('../src/share');
|
|
19
|
+
const { createStdinAsk } = require('../src/stdin-ask');
|
|
20
|
+
const { chooseAuthMethod } = require('../src/auth-method-picker');
|
|
21
|
+
|
|
22
|
+
// Minimal flag parse: --email, --lang, --google, --linkedin, --provider.
|
|
23
|
+
function parseArgs(argv) {
|
|
24
|
+
const opts = { email: null, lang: null, google: false, linkedin: false, provider: null };
|
|
25
|
+
for (let i = 0; i < argv.length; i++) {
|
|
26
|
+
const a = argv[i];
|
|
27
|
+
if (a === '--email') opts.email = argv[++i];
|
|
28
|
+
else if (a.startsWith('--email=')) opts.email = a.slice('--email='.length);
|
|
29
|
+
else if (a === '--google') opts.google = true;
|
|
30
|
+
else if (a === '--linkedin') opts.linkedin = true;
|
|
31
|
+
else if (a === '--provider' && argv[i + 1] === 'google') { opts.google = true; opts.provider = 'google'; i++; }
|
|
32
|
+
else if (a === '--provider=google') { opts.google = true; opts.provider = 'google'; }
|
|
33
|
+
else if (a === '--provider' && argv[i + 1] === 'linkedin') { opts.linkedin = true; opts.provider = 'linkedin'; i++; }
|
|
34
|
+
else if (a === '--provider=linkedin') { opts.linkedin = true; opts.provider = 'linkedin'; }
|
|
35
|
+
else if (a === '--provider' && argv[i + 1] === 'email') { opts.provider = 'email'; i++; }
|
|
36
|
+
else if (a === '--provider=email') opts.provider = 'email';
|
|
37
|
+
else if (a === '--lang' && (argv[i + 1] === 'es' || argv[i + 1] === 'en')) opts.lang = argv[++i];
|
|
38
|
+
else if (a === '--lang=es') opts.lang = 'es';
|
|
39
|
+
else if (a === '--lang=en') opts.lang = 'en';
|
|
40
|
+
}
|
|
41
|
+
return opts;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hasMethodFlag(opts) {
|
|
45
|
+
return opts.google || opts.linkedin || opts.provider === 'email' || !!opts.email;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The interactive method picker for `login`.
|
|
49
|
+
async function chooseLoginMethod({ ask, stdinIsTTY, c, input, output }) {
|
|
50
|
+
return chooseAuthMethod({ flow: 'login', ask, stdinIsTTY, catalog: c, input, output });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The SECRET prompt (issue 102) — falls back to the ordinary line read on a pipe, which `ask()` still services.
|
|
54
|
+
async function promptSecret(ask, label) {
|
|
55
|
+
const read = typeof ask.secret === 'function' ? ask.secret.bind(ask) : ask;
|
|
56
|
+
return String(await read(label)).trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function resolveEmail(opts, c, ask) {
|
|
60
|
+
if (opts.email) return String(opts.email).trim();
|
|
61
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
62
|
+
const raw = String(await ask(c.emailPrompt)).trim();
|
|
63
|
+
if (raw === '') return null; // EOF / empty line / cancel
|
|
64
|
+
if (isValidEmail(raw)) return raw;
|
|
65
|
+
process.stdout.write(` ${c.emailInvalid}\n`);
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function run(
|
|
71
|
+
argv = process.argv.slice(2),
|
|
72
|
+
{ ask: injectedAsk = null, openBrowser = undefined, stdinIsTTY: ttyOverride = undefined, input = undefined, output = undefined, sleep = undefined } = {},
|
|
73
|
+
) {
|
|
74
|
+
const opts = parseArgs(argv);
|
|
75
|
+
const lang = opts.lang || detectReportLang();
|
|
76
|
+
const c = getCatalog(lang).login;
|
|
77
|
+
|
|
78
|
+
if (opts.google) return runSocial('google', c, { openBrowser, sleep, lang });
|
|
79
|
+
if (opts.linkedin) return runSocial('linkedin', c, { openBrowser, sleep, lang });
|
|
80
|
+
|
|
81
|
+
// No method flag: offer the picker (ADR-041/046 — Email and Google only, LinkedIn stays parked).
|
|
82
|
+
if (!hasMethodFlag(opts)) {
|
|
83
|
+
const stdinIsTTY = ttyOverride !== undefined ? ttyOverride : !!process.stdin.isTTY;
|
|
84
|
+
if (stdinIsTTY) {
|
|
85
|
+
if (sessionStatus(loadAuthSession()) === 'active') {
|
|
86
|
+
process.stdout.write(`\n ${c.alreadyLoggedIn}\n\n`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const pickerAsk = injectedAsk || createStdinAsk();
|
|
90
|
+
let method;
|
|
91
|
+
try {
|
|
92
|
+
method = await chooseLoginMethod({ ask: pickerAsk, stdinIsTTY, c, input, output });
|
|
93
|
+
} finally {
|
|
94
|
+
if (!injectedAsk) pickerAsk.close();
|
|
95
|
+
}
|
|
96
|
+
if (method === null) return; // esc/ctrl-c: cancelled, nothing sent
|
|
97
|
+
if (method === 'google' || method === 'linkedin') return runSocial(method, c, { openBrowser, sleep, lang });
|
|
98
|
+
// method === 'email' falls through to the unchanged flow below.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const endpoint = getLoginEndpoint();
|
|
103
|
+
if (!endpoint) {
|
|
104
|
+
process.stderr.write(`\n ${c.errorNoEndpoint}\n\n`);
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ADR-044: an ACTIVE session is not silently overwritten — the Talent is told, and pointed at `logout` to switch accounts.
|
|
110
|
+
const status = sessionStatus(loadAuthSession());
|
|
111
|
+
if (status === 'active') {
|
|
112
|
+
process.stdout.write(`\n ${c.alreadyLoggedIn}\n\n`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
process.stdout.write(`\n ${status === 'expired' ? c.sessionExpiredIntro : c.intro}\n`);
|
|
116
|
+
|
|
117
|
+
const ask = injectedAsk || createStdinAsk();
|
|
118
|
+
try {
|
|
119
|
+
const email = await resolveEmail(opts, c, ask);
|
|
120
|
+
if (!email || !isValidEmail(email)) {
|
|
121
|
+
process.stderr.write(`\n ${c.needInput}\n\n`);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const password = await promptSecret(ask, c.passwordPrompt);
|
|
126
|
+
if (!password) {
|
|
127
|
+
process.stderr.write(`\n ${c.needInput}\n\n`);
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const normalizedEmail = normalizeEmail(email);
|
|
133
|
+
// better-auth email login: sign-in captures the session cookie, then a hub
|
|
134
|
+
// JWT is minted from it (auth-client.js). `endpoint` is the sign-in URL.
|
|
135
|
+
const result = await requestLogin(
|
|
136
|
+
{ email: normalizedEmail, password },
|
|
137
|
+
{ signInEndpoint: endpoint, tokenEndpoint: getAuthTokenEndpoint() },
|
|
138
|
+
);
|
|
139
|
+
handleResult(result, c, normalizedEmail);
|
|
140
|
+
} finally {
|
|
141
|
+
if (!injectedAsk) ask.close();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Shared success path: persist the session (atomic 0600 store) and report.
|
|
146
|
+
function persistAndReport(result, c, email = null) {
|
|
147
|
+
if (email) {
|
|
148
|
+
const prior = loadConsentState();
|
|
149
|
+
if (prior && prior.email && normalizeEmail(prior.email) !== normalizeEmail(email)) {
|
|
150
|
+
clearConsentState();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
// `cookie`/`cookieExpiresAt` are present for the email (better-auth) flow and
|
|
155
|
+
// absent for Google (auth-works JWT) — saveAuthSession picks the right model.
|
|
156
|
+
saveAuthSession({
|
|
157
|
+
accessToken: result.accessToken,
|
|
158
|
+
expiresAt: result.expiresAt,
|
|
159
|
+
accessTokenExpiresAt: result.expiresAt,
|
|
160
|
+
cookie: result.cookie || null,
|
|
161
|
+
cookieExpiresAt: result.cookieExpiresAt || null,
|
|
162
|
+
email,
|
|
163
|
+
hubAccessToken: result.hubAccessToken || null,
|
|
164
|
+
});
|
|
165
|
+
} catch {
|
|
166
|
+
process.stderr.write(`\n ${c.errorPersist}\n\n`);
|
|
167
|
+
process.exitCode = 1;
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
process.stdout.write(`\n ${c.success}\n`);
|
|
171
|
+
if (result.expiresAt) process.stdout.write(` ${c.expiresAt(result.expiresAt)}\n`);
|
|
172
|
+
process.stdout.write('\n');
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// `typedEmail` is what the CALLER typed/passed as `--email` for THIS attempt.
|
|
177
|
+
function handleResult(result, c, typedEmail = null) {
|
|
178
|
+
if (result.ok) return void persistAndReport(result, c, result.email || typedEmail);
|
|
179
|
+
|
|
180
|
+
// One message per contract reason (ADR-044) — each names the cause (issue 109).
|
|
181
|
+
const messageFor = {
|
|
182
|
+
'invalid-credentials': c.errorInvalidCredentials,
|
|
183
|
+
'no-email-password': c.errorNoEmailPassword,
|
|
184
|
+
upstream: c.errorUpstream,
|
|
185
|
+
// Creds were good but the hub JWT could not be minted from the cookie.
|
|
186
|
+
'token-fetch-failed': c.errorUpstream,
|
|
187
|
+
timeout: c.errorUnreachable,
|
|
188
|
+
'network-error': c.errorUnreachable,
|
|
189
|
+
'invalid-url': c.errorConfig,
|
|
190
|
+
'no-endpoint': c.errorNoEndpoint,
|
|
191
|
+
'bad-response': c.errorGeneric,
|
|
192
|
+
'http-error': c.errorGeneric,
|
|
193
|
+
'local-error': c.errorGeneric,
|
|
194
|
+
};
|
|
195
|
+
process.stderr.write(`\n ${messageFor[result.reason] || c.errorGeneric}\n\n`);
|
|
196
|
+
process.exitCode = 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// `login --google` / `--linkedin` — one device flow (RFC 8628); only the `provider` query on the URL differs.
|
|
200
|
+
async function runSocial(provider, c, { openBrowser = openPath, sleep = undefined, lang = 'en' } = {}) {
|
|
201
|
+
const authorizeEndpoint = getDeviceAuthorizeEndpoint();
|
|
202
|
+
const tokenEndpoint = getDeviceTokenEndpoint();
|
|
203
|
+
const authTokenEndpoint = getAuthTokenEndpoint();
|
|
204
|
+
if (!authorizeEndpoint || !tokenEndpoint || !authTokenEndpoint) {
|
|
205
|
+
process.stderr.write(`\n ${c.errorNoEndpoint}\n\n`);
|
|
206
|
+
process.exitCode = 1;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// A new social account also needs complete-registration before the JWT (freelanceType is a transient default).
|
|
210
|
+
const completeRegistrationEndpoint = getCompleteRegistrationEndpoint();
|
|
211
|
+
const registrationContext = buildRegistrationContext({ preferredLanguage: lang, newsletterConsent: false, freelanceType: 'POTENTIAL_FREELANCE' });
|
|
212
|
+
const providerName = provider === 'linkedin' ? c.methodLinkedin : c.methodGoogle;
|
|
213
|
+
|
|
214
|
+
// Same as email: an ACTIVE session is not silently overwritten.
|
|
215
|
+
const status = sessionStatus(loadAuthSession());
|
|
216
|
+
if (status === 'active') {
|
|
217
|
+
process.stdout.write(`\n ${c.alreadyLoggedIn}\n\n`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
process.stdout.write(`\n ${status === 'expired' ? c.sessionExpiredIntro : c.deviceIntro(providerName)}\n`);
|
|
221
|
+
|
|
222
|
+
const result = await runDeviceLogin(
|
|
223
|
+
{ authorizeEndpoint, tokenEndpoint, authTokenEndpoint, completeRegistrationEndpoint, registrationContext },
|
|
224
|
+
{
|
|
225
|
+
...(sleep ? { sleep } : {}),
|
|
226
|
+
onPrompt: ({ verificationUriComplete, userCode }) => {
|
|
227
|
+
const url = appendProviderParam(verificationUriComplete, provider);
|
|
228
|
+
process.stdout.write(`\n ${c.deviceVisit}\n ${url}\n\n ${c.deviceCodeLabel(userCode)}\n\n ${c.deviceWaiting}\n`);
|
|
229
|
+
try { openBrowser(url); } catch { /* the printed URL covers it */ }
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
handleSocialResult(result, c);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function handleSocialResult(result, c) {
|
|
237
|
+
if (result.ok) {
|
|
238
|
+
// Map the device-flow JWT to the shared persist shape (no cookie).
|
|
239
|
+
return void persistAndReport(
|
|
240
|
+
{ accessToken: result.token, hubAccessToken: result.token, email: result.email || null },
|
|
241
|
+
c,
|
|
242
|
+
result.email || null,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const messageFor = {
|
|
247
|
+
expired: c.deviceErrorExpired,
|
|
248
|
+
denied: c.deviceErrorDenied,
|
|
249
|
+
'authorize-failed': c.deviceErrorGeneric,
|
|
250
|
+
'poll-failed': c.deviceErrorGeneric,
|
|
251
|
+
'token-exchange-failed': c.errorUpstream,
|
|
252
|
+
'complete-registration-failed': c.errorUpstream,
|
|
253
|
+
upstream: c.errorUpstream,
|
|
254
|
+
timeout: c.errorUnreachable,
|
|
255
|
+
'network-error': c.errorUnreachable,
|
|
256
|
+
'invalid-url': c.errorConfig,
|
|
257
|
+
'no-endpoint': c.errorNoEndpoint,
|
|
258
|
+
'bad-response': c.deviceErrorGeneric,
|
|
259
|
+
'http-error': c.deviceErrorGeneric,
|
|
260
|
+
'local-error': c.deviceErrorGeneric,
|
|
261
|
+
};
|
|
262
|
+
process.stderr.write(`\n ${messageFor[result.reason] || c.deviceErrorGeneric}\n\n`);
|
|
263
|
+
process.exitCode = 1;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// `logout` — local only (no endpoint, no network): forget the stored session.
|
|
267
|
+
async function runLogout(argv = process.argv.slice(2)) {
|
|
268
|
+
const opts = parseArgs(argv);
|
|
269
|
+
const lang = opts.lang || detectReportLang();
|
|
270
|
+
const c = getCatalog(lang).login;
|
|
271
|
+
const had = loadAuthSession() !== null;
|
|
272
|
+
clearAuthSession();
|
|
273
|
+
clearConsentState();
|
|
274
|
+
process.stdout.write(`\n ${had ? c.loggedOut : c.notLoggedIn}\n\n`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
module.exports = { run, runLogout, chooseLoginMethod, parseArgs, hasMethodFlag };
|
|
278
|
+
|
|
279
|
+
if (require.main === module) {
|
|
280
|
+
const argv = process.argv.slice(2);
|
|
281
|
+
if (argv[0] === 'logout') runLogout(argv.slice(1));
|
|
282
|
+
else run(argv);
|
|
283
|
+
}
|
package/bin/mcp.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { createMcpServer } = require('../src/mcp-server');
|
|
5
|
+
const { makeAiUsageTool } = require('../src/mcp-ai-usage-tool');
|
|
6
|
+
const { makeAiUsageResultTool } = require('../src/mcp-ai-usage-result-tool');
|
|
7
|
+
const { makeCertifyDimensionTools } = require('../src/mcp-certify-dimension-tools');
|
|
8
|
+
const { makeFindProjectsTools } = require('../src/mcp-find-projects-tools');
|
|
9
|
+
const { makeShowProjectTools } = require('../src/mcp-show-project-tools');
|
|
10
|
+
const { makeSavedPositionsTools } = require('../src/mcp-saved-positions-tools');
|
|
11
|
+
const { makeInvitationsTools } = require('../src/mcp-invitations-tools');
|
|
12
|
+
const { makeApplicationsTools } = require('../src/mcp-applications-tools');
|
|
13
|
+
const { makeAvailabilityTools } = require('../src/mcp-availability-tools');
|
|
14
|
+
const { makeCertificationsTools } = require('../src/mcp-certifications-tools');
|
|
15
|
+
const { makeProfileTools } = require('../src/mcp-profile-tools');
|
|
16
|
+
const { makeRateTools } = require('../src/mcp-rate-tools');
|
|
17
|
+
const { makeAskTools } = require('../src/mcp-ask-tools');
|
|
18
|
+
const { makeAlmaTools } = require('../src/mcp-alma-tools');
|
|
19
|
+
const { makeLangTools } = require('../src/mcp-lang-tools');
|
|
20
|
+
const { makeSocialsTools } = require('../src/mcp-socials-tools');
|
|
21
|
+
const { makePortfolioTools } = require('../src/mcp-portfolios-tools');
|
|
22
|
+
const { makeOnboardingTools } = require('../src/mcp-onboarding-tools');
|
|
23
|
+
const { makeRoleTools } = require('../src/mcp-role-tools');
|
|
24
|
+
const { makeDeviceLoginTools } = require('../src/mcp-device-login-tools');
|
|
25
|
+
const { makeRegisterTools } = require('../src/mcp-register-tools');
|
|
26
|
+
const { detectFlowLang } = require('../src/i18n');
|
|
27
|
+
const { makeReportTools } = require('../src/mcp-report-tools');
|
|
28
|
+
const { ensureFreshSession } = require('../src/session-refresh');
|
|
29
|
+
|
|
30
|
+
let VERSION = '';
|
|
31
|
+
try {
|
|
32
|
+
VERSION = require('../package.json').version || '';
|
|
33
|
+
} catch {
|
|
34
|
+
VERSION = '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const HELP = `\n shakers mcp — local MCP server (stdio)\n\n`
|
|
38
|
+
+ ` Exposes the AI-usage evaluation as an MCP tool over stdin/stdout so a\n`
|
|
39
|
+
+ ` desktop AI app (Claude / ChatGPT Desktop) can run it on your machine.\n\n`
|
|
40
|
+
+ ` Usage: shakers mcp\n\n`
|
|
41
|
+
+ ` This command speaks JSON-RPC on stdio; it is meant to be launched by an\n`
|
|
42
|
+
+ ` MCP client, not run by hand.\n`
|
|
43
|
+
+ ` Tools: ai_usage, ai_usage_result, login, logout, social_signin_start, social_signin_poll,\n`
|
|
44
|
+
+ ` list_addable, add_skill, add_agent,\n`
|
|
45
|
+
+ ` add_project, register, register_status, onboarding_interview_start,\n`
|
|
46
|
+
+ ` onboarding_interview_turn, onboarding_interview_complete,\n`
|
|
47
|
+
+ ` repeat_onboarding_interview, onboarding_finish,\n`
|
|
48
|
+
+ ` list_available_roles, add_role, list_my_roles, set_main_role,\n`
|
|
49
|
+
+ ` report, share,\n`
|
|
50
|
+
+ ` list_certifiable_dimensions, find_projects, show_project,\n`
|
|
51
|
+
+ ` save_project, unsave_project, list_invitations,\n`
|
|
52
|
+
+ ` list_applications, get_availability,\n`
|
|
53
|
+
+ ` list_certifications, get_profile, get_rate, get_languages,\n`
|
|
54
|
+
+ ` list_socials, list_experiences, list_portfolios, ask.\n\n`;
|
|
55
|
+
|
|
56
|
+
function buildServer() {
|
|
57
|
+
return createMcpServer({
|
|
58
|
+
tools: [makeAiUsageTool(), makeAiUsageResultTool(), ...makeOnboardingTools(), ...makeRoleTools(), ...makeDeviceLoginTools(), ...makeRegisterTools({ lang: detectFlowLang() }), ...makeReportTools(), ...makeCertifyDimensionTools(), ...makeFindProjectsTools(), ...makeShowProjectTools(), ...makeSavedPositionsTools(), ...makeInvitationsTools(), ...makeApplicationsTools(), ...makeAvailabilityTools(), ...makeCertificationsTools(), ...makeProfileTools(), ...makeRateTools(), ...makeLangTools(), ...makeSocialsTools(), ...makePortfolioTools(), ...makeAskTools(), ...makeAlmaTools()],
|
|
59
|
+
serverInfo: { name: 'Shakers', version: VERSION || '0.0.0' },
|
|
60
|
+
// Long-lived server: refresh the email session's hub JWT from the stored
|
|
61
|
+
// cookie before each tool call so a stale (~15-min) JWT never reaches a tool.
|
|
62
|
+
beforeCall: () => ensureFreshSession(process.env),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function run(argv = process.argv.slice(2)) {
|
|
67
|
+
// The installer's last step; not a command the Talent needs to know.
|
|
68
|
+
if (argv[0] === 'install') {
|
|
69
|
+
await require('../src/mcp-install').runMcpInstall();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
73
|
+
process.stdout.write(HELP);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
require('../src/config').ensureLocalConfigDefaults(process.env, { force: true });
|
|
78
|
+
|
|
79
|
+
const server = buildServer();
|
|
80
|
+
const rl = server.start({
|
|
81
|
+
input: process.stdin,
|
|
82
|
+
output: process.stdout,
|
|
83
|
+
onError: (e) => {
|
|
84
|
+
process.stderr.write(` [mcp] ${e && e.message ? e.message : String(e)}\n`);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await new Promise((resolve) => {
|
|
89
|
+
rl.on('close', resolve);
|
|
90
|
+
process.stdin.on('end', () => rl.close());
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { run, buildServer };
|
|
95
|
+
|
|
96
|
+
if (require.main === module) {
|
|
97
|
+
run();
|
|
98
|
+
}
|
package/bin/me.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// `me` (alias `profile`) — talent summary: role + rate + "My work with AI".
|
|
5
|
+
|
|
6
|
+
const { detectReportLang, getCatalog } = require('../src/i18n');
|
|
7
|
+
const { loadAuthSession, sessionStatus } = require('../src/auth-session-store');
|
|
8
|
+
const { createStdinAsk } = require('../src/stdin-ask');
|
|
9
|
+
const { makeIo } = require('./register');
|
|
10
|
+
const { makeRevealSink } = require('../src/reveal-output');
|
|
11
|
+
const { runProfile, makeProfileDeps } = require('../src/profile-flow');
|
|
12
|
+
|
|
13
|
+
function parseMeArgs(argv = []) {
|
|
14
|
+
const o = { lang: null, help: false, json: false };
|
|
15
|
+
for (let i = 0; i < argv.length; i++) {
|
|
16
|
+
const a = argv[i];
|
|
17
|
+
if (a === '--help' || a === '-h') o.help = true;
|
|
18
|
+
else if (a === '--lang') { const v = argv[++i]; o.lang = v === 'es' || v === 'en' ? v : null; }
|
|
19
|
+
else if (a.startsWith('--lang=')) { const v = a.slice(7); o.lang = v === 'es' || v === 'en' ? v : null; }
|
|
20
|
+
else if (a === '--json') o.json = true;
|
|
21
|
+
}
|
|
22
|
+
return o;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function run(
|
|
26
|
+
argv = process.argv.slice(2),
|
|
27
|
+
{ ask: injectedAsk = null, stdinIsTTY: ttyOverride = undefined, deps = null, session: injectedSession = null, out = null } = {},
|
|
28
|
+
) {
|
|
29
|
+
const opts = parseMeArgs(argv);
|
|
30
|
+
const lang = opts.lang || detectReportLang();
|
|
31
|
+
const catalog = getCatalog(lang);
|
|
32
|
+
const fp = catalog.profile;
|
|
33
|
+
const write = out || ((s) => process.stdout.write(s));
|
|
34
|
+
|
|
35
|
+
if (opts.help) {
|
|
36
|
+
write(fp.help + '\n');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const session = injectedSession || loadAuthSession();
|
|
41
|
+
if (sessionStatus(session) !== 'active') {
|
|
42
|
+
process.stderr.write(`\n ${fp.loginRequired}\n\n`);
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const stdinIsTTY = ttyOverride !== undefined ? ttyOverride : !!process.stdin.isTTY;
|
|
48
|
+
const ask = injectedAsk || createStdinAsk();
|
|
49
|
+
const sink = makeRevealSink({ json: opts.json, interactive: false, out, env: process.env });
|
|
50
|
+
try {
|
|
51
|
+
const io = makeIo({ ask, lang, out: sink.write, stdinIsTTY });
|
|
52
|
+
const result = await runProfile({
|
|
53
|
+
io, rawOut: sink.write, session, catalog,
|
|
54
|
+
opts: { json: opts.json },
|
|
55
|
+
deps: deps || makeProfileDeps(),
|
|
56
|
+
});
|
|
57
|
+
await sink.finish();
|
|
58
|
+
if (result && result.ok === false) process.exitCode = 1;
|
|
59
|
+
return result;
|
|
60
|
+
} finally {
|
|
61
|
+
if (!injectedAsk) ask.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { run, parseMeArgs };
|
|
66
|
+
|
|
67
|
+
if (require.main === module) {
|
|
68
|
+
run();
|
|
69
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { detectFlowLang } = require('../src/i18n');
|
|
5
|
+
const { loadAuthSession, sessionStatus } = require('../src/auth-session-store');
|
|
6
|
+
const { createStdinAsk } = require('../src/stdin-ask');
|
|
7
|
+
const { runOnboardingInterview, runRepeatOnboardingInterview, makeDeps } = require('../src/onboarding-flow');
|
|
8
|
+
const { checkOnboardingCompleted } = require('../src/onboarding-availability');
|
|
9
|
+
const { makeIo } = require('./register');
|
|
10
|
+
const { c: fmt } = require('../src/terminal-format');
|
|
11
|
+
const red = (s) => `${fmt.danger}${s}${fmt.reset}`;
|
|
12
|
+
const yellow = (s) => `${fmt.warning}${s}${fmt.reset}`;
|
|
13
|
+
|
|
14
|
+
const VALID_LANGS = new Set(['es', 'en']);
|
|
15
|
+
|
|
16
|
+
const COPY = {
|
|
17
|
+
es: {
|
|
18
|
+
help: 'onboarding — solo la entrevista de onboarding de Shakers (requiere sesion). Uso: shakers onboarding [repeat] [--repeat] [--yes] [--lang es|en] [--accept-disclaimer]\n\n repeat / --repeat Repetir la entrevista SOBREESCRIBIENDO la anterior (pide confirmacion; --yes para confirmar sin TTY).',
|
|
19
|
+
gate: '"onboarding" requiere una sesion activa.\n Falta: iniciar sesion. Ejecuta primero: shakers login',
|
|
20
|
+
missingAccept: 'Modo no interactivo: falta --accept-disclaimer.\n onboarding es una entrevista interactiva.\n Reejecuta con --accept-disclaimer, aportando las respuestas por stdin.',
|
|
21
|
+
alreadyPrompt: 'Ya completaste la entrevista de onboarding. ¿Quieres repetirla? (s/N)',
|
|
22
|
+
alreadyNeedFlag: 'Ya completaste la entrevista de onboarding.\n Reejecutarla es opcional; si de verdad quieres repetirla, reejecuta con --confirm-repeat.',
|
|
23
|
+
alreadyAbort: 'De acuerdo, no se repite la entrevista de onboarding.',
|
|
24
|
+
},
|
|
25
|
+
en: {
|
|
26
|
+
help: 'onboarding — the Shakers onboarding interview only (requires a session). Usage: shakers onboarding [repeat] [--repeat] [--yes] [--lang es|en] [--accept-disclaimer]\n\n repeat / --repeat Repeat the interview OVERWRITING the previous one (asks for confirmation; --yes to confirm without a TTY).',
|
|
27
|
+
gate: '"onboarding" requires an active session.\n Missing: sign-in. Run `shakers login` first.',
|
|
28
|
+
missingAccept: 'Non-interactive mode: --accept-disclaimer is missing.\n onboarding is an interactive interview.\n Re-run with --accept-disclaimer, feeding the answers over stdin.',
|
|
29
|
+
alreadyPrompt: 'You have already completed the onboarding interview. Do you want to repeat it? (y/N)',
|
|
30
|
+
alreadyNeedFlag: 'You have already completed the onboarding interview.\n Re-running it is optional; if you really want to repeat it, re-run with --confirm-repeat.',
|
|
31
|
+
alreadyAbort: 'OK, not repeating the onboarding interview.',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function isYes(raw) {
|
|
36
|
+
const s = String(raw || '').trim().toLowerCase();
|
|
37
|
+
return s === 's' || s === 'si' || s === 'sí' || s === 'y' || s === 'yes';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseArgs(argv) {
|
|
41
|
+
const o = { lang: null, help: false, acceptDisclaimer: false, confirmRepeat: false, repeat: false, yes: false };
|
|
42
|
+
for (let i = 0; i < argv.length; i++) {
|
|
43
|
+
const a = argv[i];
|
|
44
|
+
if (a === '--lang') o.lang = VALID_LANGS.has(argv[++i]) ? argv[i] : null;
|
|
45
|
+
else if (a.startsWith('--lang=')) { const v = a.slice(7); o.lang = VALID_LANGS.has(v) ? v : null; }
|
|
46
|
+
else if (a === '--accept-disclaimer') o.acceptDisclaimer = true;
|
|
47
|
+
else if (a === '--confirm-repeat') o.confirmRepeat = true;
|
|
48
|
+
else if (a === '--repeat' || a === 'repeat') o.repeat = true; // `shakers onboarding repeat` == --repeat
|
|
49
|
+
else if (a === '--yes' || a === '-y') o.yes = true;
|
|
50
|
+
else if (a === '--help' || a === '-h') o.help = true;
|
|
51
|
+
}
|
|
52
|
+
return o;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function run(
|
|
56
|
+
argv = process.argv.slice(2),
|
|
57
|
+
{ ask: injectedAsk = null, stdinIsTTY: ttyOverride = undefined, deps = null, out = null, session: injectedSession = null, checkCompleted = checkOnboardingCompleted } = {},
|
|
58
|
+
) {
|
|
59
|
+
const o = parseArgs(argv);
|
|
60
|
+
const lang = o.lang || detectFlowLang();
|
|
61
|
+
const c = COPY[lang] || COPY.en;
|
|
62
|
+
const write = out || ((s) => process.stdout.write(s));
|
|
63
|
+
|
|
64
|
+
if (o.help) {
|
|
65
|
+
write(`\n ${c.help}\n\n`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const session = injectedSession || loadAuthSession();
|
|
70
|
+
if (sessionStatus(session) !== 'active') {
|
|
71
|
+
process.stderr.write(`\n ${red(c.gate)}\n\n`);
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const stdinIsTTY = ttyOverride !== undefined ? ttyOverride : !!process.stdin.isTTY;
|
|
77
|
+
|
|
78
|
+
let completed = false;
|
|
79
|
+
try {
|
|
80
|
+
completed = await checkCompleted({ session });
|
|
81
|
+
} catch {
|
|
82
|
+
completed = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const ask = injectedAsk || createStdinAsk();
|
|
86
|
+
try {
|
|
87
|
+
// Explicit overwrite-repeat: its own warning + confirmation, bypassing the legacy already-completed gate.
|
|
88
|
+
if (o.repeat) {
|
|
89
|
+
const io = makeIo({ ask, lang, out: write, acceptDisclaimer: o.acceptDisclaimer, stdinIsTTY });
|
|
90
|
+
const result = await runRepeatOnboardingInterview(io, deps || makeDeps(), { confirmed: o.yes });
|
|
91
|
+
if (result && result.ok === false) {
|
|
92
|
+
process.stderr.write(`\n ${red(`onboarding: ${result.reason}`)}\n\n`);
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (completed && !o.confirmRepeat) {
|
|
99
|
+
const canPrompt = stdinIsTTY || !!injectedAsk;
|
|
100
|
+
if (!canPrompt) {
|
|
101
|
+
process.stderr.write(`\n ${yellow(c.alreadyNeedFlag)}\n\n`);
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const answer = await ask(` ${c.alreadyPrompt} `);
|
|
106
|
+
if (!isYes(answer)) {
|
|
107
|
+
write(`\n ${c.alreadyAbort}\n\n`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!stdinIsTTY && !o.acceptDisclaimer && !injectedAsk) {
|
|
113
|
+
process.stderr.write(`\n ${red(c.missingAccept)}\n\n`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const io = makeIo({ ask, lang, out: write, acceptDisclaimer: o.acceptDisclaimer, stdinIsTTY });
|
|
119
|
+
const result = await runOnboardingInterview(io, deps || makeDeps());
|
|
120
|
+
if (!result.ok) {
|
|
121
|
+
process.stderr.write(`\n ${red(`onboarding: ${result.reason}${result.step ? ` (${result.step})` : ''}`)}\n\n`);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
} finally {
|
|
126
|
+
if (!injectedAsk) ask.close();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { run, parseArgs };
|
|
131
|
+
|
|
132
|
+
if (require.main === module) {
|
|
133
|
+
run();
|
|
134
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// `portfolios` — the talent's portfolio showcase pieces (portfolios type=PORTFOLIO).
|
|
5
|
+
|
|
6
|
+
const { detectReportLang, getCatalog } = require('../src/i18n');
|
|
7
|
+
const { loadAuthSession, sessionStatus } = require('../src/auth-session-store');
|
|
8
|
+
const { createStdinAsk } = require('../src/stdin-ask');
|
|
9
|
+
const { makeIo } = require('./register');
|
|
10
|
+
const { makeRevealSink } = require('../src/reveal-output');
|
|
11
|
+
const { runPortfolioList, makePortfoliosDeps } = require('../src/portfolios-flow');
|
|
12
|
+
|
|
13
|
+
function parseArgs(argv = []) {
|
|
14
|
+
const o = { lang: null, help: false, json: false };
|
|
15
|
+
for (let i = 0; i < argv.length; i++) {
|
|
16
|
+
const a = argv[i];
|
|
17
|
+
if (a === '--help' || a === '-h') o.help = true;
|
|
18
|
+
else if (a === '--lang') { const v = argv[++i]; o.lang = v === 'es' || v === 'en' ? v : null; }
|
|
19
|
+
else if (a.startsWith('--lang=')) { const v = a.slice(7); o.lang = v === 'es' || v === 'en' ? v : null; }
|
|
20
|
+
else if (a === '--json') o.json = true;
|
|
21
|
+
}
|
|
22
|
+
return o;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function run(
|
|
26
|
+
argv = process.argv.slice(2),
|
|
27
|
+
{ ask: injectedAsk = null, stdinIsTTY: ttyOverride = undefined, deps = null, session: injectedSession = null, out = null } = {},
|
|
28
|
+
) {
|
|
29
|
+
const opts = parseArgs(argv);
|
|
30
|
+
const lang = opts.lang || detectReportLang();
|
|
31
|
+
const catalog = getCatalog(lang);
|
|
32
|
+
const fp = catalog.portfolios;
|
|
33
|
+
const write = out || ((s) => process.stdout.write(s));
|
|
34
|
+
|
|
35
|
+
if (opts.help) {
|
|
36
|
+
write(fp.help + '\n');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const session = injectedSession || loadAuthSession();
|
|
41
|
+
if (sessionStatus(session) !== 'active') {
|
|
42
|
+
process.stderr.write(`\n ${fp.loginRequired}\n\n`);
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const stdinIsTTY = ttyOverride !== undefined ? ttyOverride : !!process.stdin.isTTY;
|
|
48
|
+
const ask = injectedAsk || createStdinAsk();
|
|
49
|
+
const sink = makeRevealSink({ json: opts.json, out, env: process.env });
|
|
50
|
+
try {
|
|
51
|
+
const io = makeIo({ ask, lang, out: sink.write, stdinIsTTY });
|
|
52
|
+
const result = await runPortfolioList({
|
|
53
|
+
io, rawOut: sink.write, lang, session, catalog, kind: 'portfolios',
|
|
54
|
+
opts: { json: opts.json },
|
|
55
|
+
deps: deps || makePortfoliosDeps(),
|
|
56
|
+
});
|
|
57
|
+
await sink.finish();
|
|
58
|
+
if (result && result.ok === false) process.exitCode = 1;
|
|
59
|
+
return result;
|
|
60
|
+
} finally {
|
|
61
|
+
if (!injectedAsk) ask.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { run, parseArgs };
|
|
66
|
+
|
|
67
|
+
if (require.main === module) {
|
|
68
|
+
run();
|
|
69
|
+
}
|