shakers-cli 0.0.0-stage → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/LICENSE +59 -0
  2. package/README.md +1233 -2
  3. package/bin/add-agent.js +88 -0
  4. package/bin/add-project.js +88 -0
  5. package/bin/add-role.js +61 -0
  6. package/bin/add-skill.js +88 -0
  7. package/bin/ai-usage.js +99 -0
  8. package/bin/alma.js +99 -0
  9. package/bin/applications.js +69 -0
  10. package/bin/availability.js +70 -0
  11. package/bin/certifications.js +69 -0
  12. package/bin/certify.js +73 -0
  13. package/bin/change-role.js +61 -0
  14. package/bin/config.js +124 -0
  15. package/bin/experiences.js +69 -0
  16. package/bin/find-projects.js +89 -0
  17. package/bin/invitations.js +69 -0
  18. package/bin/lang.js +70 -0
  19. package/bin/login.js +216 -0
  20. package/bin/mcp.js +124 -0
  21. package/bin/mcpb.js +5 -0
  22. package/bin/me.js +69 -0
  23. package/bin/onboarding.js +134 -0
  24. package/bin/portfolios.js +69 -0
  25. package/bin/rate.js +70 -0
  26. package/bin/register.js +337 -0
  27. package/bin/report-html.js +134 -0
  28. package/bin/save.js +77 -0
  29. package/bin/shakers.js +363 -0
  30. package/bin/share.js +72 -0
  31. package/bin/show-project.js +72 -0
  32. package/bin/socials.js +70 -0
  33. package/bin/superadmin.js +351 -0
  34. package/bin/update-check-worker.js +70 -0
  35. package/bin/update.js +111 -0
  36. package/package.json +40 -4
  37. package/src/agent-category.js +13 -0
  38. package/src/agent-evaluation.js +177 -0
  39. package/src/agent-org-chart.js +355 -0
  40. package/src/agent-synthesis.js +132 -0
  41. package/src/agent-usage.js +118 -0
  42. package/src/agents-client.js +165 -0
  43. package/src/ai-fluency-cell.js +72 -0
  44. package/src/ai-profile-client.js +45 -0
  45. package/src/ai-profile-preview.js +154 -0
  46. package/src/ai.js +66 -0
  47. package/src/alma-client.js +123 -0
  48. package/src/alma-flow.js +32 -0
  49. package/src/alma-repo-context.js +47 -0
  50. package/src/alma-shell.js +161 -0
  51. package/src/ansi.js +62 -0
  52. package/src/applications-client.js +79 -0
  53. package/src/applications-flow.js +58 -0
  54. package/src/auth-client.js +144 -0
  55. package/src/auth-device-client.js +118 -0
  56. package/src/auth-method-picker.js +44 -0
  57. package/src/auth-methods.js +35 -0
  58. package/src/auth-session-store.js +215 -0
  59. package/src/authorship-mismatch.js +62 -0
  60. package/src/authorship.js +204 -0
  61. package/src/automations-detector.js +170 -0
  62. package/src/availability-client.js +93 -0
  63. package/src/availability-flow.js +124 -0
  64. package/src/backend-request.js +191 -0
  65. package/src/blind-approval-extract.js +48 -0
  66. package/src/brand-ansi.js +14 -0
  67. package/src/brand-svg.js +9 -0
  68. package/src/browser-tools-detector.js +21 -0
  69. package/src/build-next-level.js +56 -0
  70. package/src/certifications-client.js +89 -0
  71. package/src/certifications-flow.js +75 -0
  72. package/src/certify-dimension-client.js +163 -0
  73. package/src/certify-dimension-flow.js +135 -0
  74. package/src/certify-disclaimer.js +51 -0
  75. package/src/certify-remediation-prompt.js +23 -0
  76. package/src/cli-args.js +83 -0
  77. package/src/command-audit.js +105 -0
  78. package/src/config-dir.js +120 -0
  79. package/src/config.js +877 -0
  80. package/src/consent-flow.js +115 -0
  81. package/src/consent-skip.js +27 -0
  82. package/src/cv-file.js +62 -0
  83. package/src/cv-text.js +92 -0
  84. package/src/decisions-extract.js +43 -0
  85. package/src/detect-repos.js +145 -0
  86. package/src/detected-tools.js +15 -0
  87. package/src/detection-evidence.js +105 -0
  88. package/src/detection-noise.js +132 -0
  89. package/src/detectors.js +221 -0
  90. package/src/device-auth-store.js +100 -0
  91. package/src/device-login.js +120 -0
  92. package/src/email-verification.js +152 -0
  93. package/src/env-paths.js +30 -0
  94. package/src/find-projects-cache.js +89 -0
  95. package/src/find-projects-client.js +148 -0
  96. package/src/find-projects-flow.js +164 -0
  97. package/src/git-activity.js +124 -0
  98. package/src/git-log-prefetch.js +53 -0
  99. package/src/graph-certs.js +58 -0
  100. package/src/graph-generator.js +230 -0
  101. package/src/graph-scan.js +239 -0
  102. package/src/i18n.js +3341 -0
  103. package/src/inter-fonts.js +6 -0
  104. package/src/interactive-select.js +183 -0
  105. package/src/interview-client.js +226 -0
  106. package/src/interview-transcript.js +56 -0
  107. package/src/inventory-client.js +90 -0
  108. package/src/invitations-client.js +66 -0
  109. package/src/invitations-flow.js +50 -0
  110. package/src/lang-client.js +147 -0
  111. package/src/lang-codes.js +14 -0
  112. package/src/lang-flow.js +85 -0
  113. package/src/legal-notice.js +53 -0
  114. package/src/locale.js +72 -0
  115. package/src/markdown-ansi.js +50 -0
  116. package/src/maturity.js +111 -0
  117. package/src/mcp-ai-usage-result-tool.js +111 -0
  118. package/src/mcp-ai-usage-tool.js +401 -0
  119. package/src/mcp-alma-tools.js +95 -0
  120. package/src/mcp-applications-tools.js +44 -0
  121. package/src/mcp-auth-form.js +480 -0
  122. package/src/mcp-availability-tools.js +45 -0
  123. package/src/mcp-certifications-tools.js +44 -0
  124. package/src/mcp-certify-dimension-tools.js +66 -0
  125. package/src/mcp-choice.js +95 -0
  126. package/src/mcp-detector.js +223 -0
  127. package/src/mcp-device-login-tools.js +177 -0
  128. package/src/mcp-find-projects-tools.js +69 -0
  129. package/src/mcp-install.js +275 -0
  130. package/src/mcp-instructions.js +27 -0
  131. package/src/mcp-invitations-tools.js +44 -0
  132. package/src/mcp-lang-tools.js +41 -0
  133. package/src/mcp-onboarding-tools.js +379 -0
  134. package/src/mcp-portfolios-tools.js +50 -0
  135. package/src/mcp-profile-tools.js +48 -0
  136. package/src/mcp-prompts.js +73 -0
  137. package/src/mcp-rate-tools.js +44 -0
  138. package/src/mcp-register-tools.js +441 -0
  139. package/src/mcp-report-tools.js +105 -0
  140. package/src/mcp-role-tools.js +372 -0
  141. package/src/mcp-saved-positions-tools.js +59 -0
  142. package/src/mcp-server.js +290 -0
  143. package/src/mcp-show-project-tools.js +52 -0
  144. package/src/mcp-signup-client.js +118 -0
  145. package/src/mcp-signup-tools.js +1171 -0
  146. package/src/mcp-socials-tools.js +41 -0
  147. package/src/memory-structure-detector.js +108 -0
  148. package/src/model-call-record.js +198 -0
  149. package/src/onboarding-availability.js +26 -0
  150. package/src/onboarding-client.js +374 -0
  151. package/src/onboarding-flow.js +1162 -0
  152. package/src/onboarding-funnel.js +272 -0
  153. package/src/onboarding-livekit-client.js +102 -0
  154. package/src/onboarding-status-client.js +57 -0
  155. package/src/open-file.js +32 -0
  156. package/src/osc-link.js +16 -0
  157. package/src/portfolio-client.js +164 -0
  158. package/src/portfolio-git-info.js +61 -0
  159. package/src/portfolios-client.js +85 -0
  160. package/src/portfolios-flow.js +79 -0
  161. package/src/profile-client.js +133 -0
  162. package/src/profile-flow.js +115 -0
  163. package/src/profile-switch.js +46 -0
  164. package/src/prompt-select.js +66 -0
  165. package/src/rate-client.js +81 -0
  166. package/src/rate-flow.js +137 -0
  167. package/src/register-context.js +145 -0
  168. package/src/render-ai-fluency.js +266 -0
  169. package/src/render-certification.js +177 -0
  170. package/src/render-html.js +140 -0
  171. package/src/render-report-text.js +27 -0
  172. package/src/render-sheet.js +261 -0
  173. package/src/render-terminal.js +109 -0
  174. package/src/render-usage-report-text.js +155 -0
  175. package/src/repo-context.js +215 -0
  176. package/src/report-gating.js +49 -0
  177. package/src/report-store.js +226 -0
  178. package/src/report-theme.js +177 -0
  179. package/src/report-view-client.js +50 -0
  180. package/src/reveal-output.js +29 -0
  181. package/src/roadmap-content.js +453 -0
  182. package/src/roadmap-personalization.js +132 -0
  183. package/src/roadmap-prompt.js +154 -0
  184. package/src/roles-client.js +112 -0
  185. package/src/roles-flow.js +192 -0
  186. package/src/sanitize-network-text.js +36 -0
  187. package/src/saved-positions-client.js +71 -0
  188. package/src/saved-positions-flow.js +50 -0
  189. package/src/scan-exclusions.js +50 -0
  190. package/src/scanner.js +624 -0
  191. package/src/scope-signals.js +148 -0
  192. package/src/secret-prompt.js +72 -0
  193. package/src/session-gate.js +30 -0
  194. package/src/session-normalizers.js +174 -0
  195. package/src/session-refresh.js +51 -0
  196. package/src/session-scan.js +475 -0
  197. package/src/session-signals.js +36 -0
  198. package/src/shakers-wordmark.js +17 -0
  199. package/src/share-card.js +362 -0
  200. package/src/share-certification.js +111 -0
  201. package/src/share-consent-state.js +157 -0
  202. package/src/share-payload.js +300 -0
  203. package/src/share.js +185 -0
  204. package/src/show-project-client.js +58 -0
  205. package/src/show-project-flow.js +119 -0
  206. package/src/signup-client.js +103 -0
  207. package/src/signup-copy.js +450 -0
  208. package/src/signup-language.js +21 -0
  209. package/src/skills-client.js +102 -0
  210. package/src/socials-client.js +91 -0
  211. package/src/socials-flow.js +83 -0
  212. package/src/specificity-extract.js +41 -0
  213. package/src/start-add-agents.js +306 -0
  214. package/src/start-add-portfolio.js +388 -0
  215. package/src/start-add-skills.js +260 -0
  216. package/src/stdin-ask.js +129 -0
  217. package/src/steering-extract.js +43 -0
  218. package/src/strip-internal-comments.js +45 -0
  219. package/src/tech-detector.js +295 -0
  220. package/src/templates/report-sheet.html +572 -0
  221. package/src/terminal-agents.js +207 -0
  222. package/src/terminal-format.js +85 -0
  223. package/src/terminal-progress.js +106 -0
  224. package/src/terminal-sections.js +219 -0
  225. package/src/tier-analysis.js +126 -0
  226. package/src/tier-engine.js +114 -0
  227. package/src/tier-projection.js +117 -0
  228. package/src/transcript-turns.js +49 -0
  229. package/src/translations-client.js +84 -0
  230. package/src/typewriter.js +154 -0
  231. package/src/update-flow.js +223 -0
  232. package/src/update-notifier.js +113 -0
  233. package/src/usage-model-calls.js +119 -0
  234. package/src/usage-oneshot-commands.js +180 -0
  235. package/src/usage-repo-scope.js +162 -0
  236. package/src/usage-run-steps.js +354 -0
  237. package/src/work-streams.js +79 -0
package/bin/login.js ADDED
@@ -0,0 +1,216 @@
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 { getAuthTokenEndpoint, getDeviceAuthorizeEndpoint, getDeviceTokenEndpoint, getCompleteRegistrationEndpoint } = require('../src/config');
8
+ const { runDeviceLogin, appendProviderParam } = require('../src/device-login');
9
+ const { buildRegistrationContext } = require('../src/signup-client');
10
+ const { openPath } = require('../src/open-file');
11
+ const {
12
+ saveAuthSession,
13
+ clearAuthSession,
14
+ loadAuthSession,
15
+ sessionStatus,
16
+ } = require('../src/auth-session-store');
17
+ const { normalizeEmail, loadConsentState, clearConsentState } = require('../src/share');
18
+ const { createStdinAsk } = require('../src/stdin-ask');
19
+ const { chooseAuthMethod } = require('../src/auth-method-picker');
20
+
21
+ // Minimal flag parse: --email, --lang, --google, --linkedin, --provider.
22
+ function parseArgs(argv) {
23
+ const opts = { email: null, lang: null, google: false, linkedin: false, provider: null };
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const a = argv[i];
26
+ if (a === '--email') opts.email = argv[++i];
27
+ else if (a.startsWith('--email=')) opts.email = a.slice('--email='.length);
28
+ else if (a === '--google') opts.google = true;
29
+ else if (a === '--linkedin') opts.linkedin = true;
30
+ else if (a === '--provider' && argv[i + 1] === 'google') { opts.google = true; opts.provider = 'google'; i++; }
31
+ else if (a === '--provider=google') { opts.google = true; opts.provider = 'google'; }
32
+ else if (a === '--provider' && argv[i + 1] === 'linkedin') { opts.linkedin = true; opts.provider = 'linkedin'; i++; }
33
+ else if (a === '--provider=linkedin') { opts.linkedin = true; opts.provider = 'linkedin'; }
34
+ else if (a === '--provider' && argv[i + 1] === 'email') { opts.provider = 'email'; i++; }
35
+ else if (a === '--provider=email') opts.provider = 'email';
36
+ else if (a === '--lang' && (argv[i + 1] === 'es' || argv[i + 1] === 'en')) opts.lang = argv[++i];
37
+ else if (a === '--lang=es') opts.lang = 'es';
38
+ else if (a === '--lang=en') opts.lang = 'en';
39
+ }
40
+ return opts;
41
+ }
42
+
43
+ function hasMethodFlag(opts) {
44
+ return opts.google || opts.linkedin || opts.provider === 'email' || !!opts.email;
45
+ }
46
+
47
+ // The interactive method picker for `login`.
48
+ async function chooseLoginMethod({ ask, stdinIsTTY, c, input, output }) {
49
+ return chooseAuthMethod({ flow: 'login', ask, stdinIsTTY, catalog: c, input, output });
50
+ }
51
+
52
+ async function run(
53
+ argv = process.argv.slice(2),
54
+ { ask: injectedAsk = null, openBrowser = undefined, stdinIsTTY: ttyOverride = undefined, input = undefined, output = undefined, sleep = undefined } = {},
55
+ ) {
56
+ const opts = parseArgs(argv);
57
+ const lang = opts.lang || detectReportLang();
58
+ const c = getCatalog(lang).login;
59
+
60
+ // Every method signs in through the real web device flow (RFC 8628): the
61
+ // Talent reuses an existing web session or signs in on the `/cli-login` page,
62
+ // so no password is ever read in the terminal. Email now travels the same
63
+ // path Google/LinkedIn already use; only the `provider` query differs.
64
+ let method = null;
65
+ if (opts.google) method = 'google';
66
+ else if (opts.linkedin) method = 'linkedin';
67
+ else if (opts.provider === 'email' || !!opts.email) method = 'email';
68
+
69
+ if (!method) {
70
+ const stdinIsTTY = ttyOverride !== undefined ? ttyOverride : !!process.stdin.isTTY;
71
+ // ADR-044: an ACTIVE session is not silently overwritten, and the picker never shows.
72
+ if (sessionStatus(loadAuthSession()) === 'active') {
73
+ (output || process.stdout).write(`\n ${c.alreadyLoggedIn}\n\n`);
74
+ return;
75
+ }
76
+ if (stdinIsTTY) {
77
+ // ADR-041/046 — the picker offers Email, Google and LinkedIn.
78
+ const pickerAsk = injectedAsk || createStdinAsk();
79
+ try {
80
+ method = await chooseLoginMethod({ ask: pickerAsk, stdinIsTTY, c, input, output });
81
+ } finally {
82
+ if (!injectedAsk) pickerAsk.close();
83
+ }
84
+ if (method === null) return; // esc/ctrl-c: cancelled, nothing sent
85
+ } else {
86
+ method = 'email'; // non-interactive default (matches the picker's own non-TTY default)
87
+ }
88
+ }
89
+
90
+ return runDeviceFlow(method, c, { openBrowser, sleep, lang, output });
91
+ }
92
+
93
+ // Shared success path: persist the session (atomic 0600 store) and report.
94
+ // `output` lets a caller (and tests) redirect the human-facing lines; errors stay on stderr.
95
+ function persistAndReport(result, c, email = null, output = process.stdout) {
96
+ if (email) {
97
+ const prior = loadConsentState();
98
+ if (prior && prior.email && normalizeEmail(prior.email) !== normalizeEmail(email)) {
99
+ clearConsentState();
100
+ }
101
+ }
102
+ try {
103
+ // `cookie`/`cookieExpiresAt` are present for the email (better-auth) flow and
104
+ // absent for Google (auth-works JWT) — saveAuthSession picks the right model.
105
+ saveAuthSession({
106
+ accessToken: result.accessToken,
107
+ expiresAt: result.expiresAt,
108
+ accessTokenExpiresAt: result.expiresAt,
109
+ cookie: result.cookie || null,
110
+ cookieExpiresAt: result.cookieExpiresAt || null,
111
+ email,
112
+ hubAccessToken: result.hubAccessToken || null,
113
+ });
114
+ } catch {
115
+ process.stderr.write(`\n ${c.errorPersist}\n\n`);
116
+ process.exitCode = 1;
117
+ return false;
118
+ }
119
+ output.write(`\n ${c.success}\n`);
120
+ if (result.expiresAt) output.write(` ${c.expiresAt(result.expiresAt)}\n`);
121
+ output.write('\n');
122
+ return true;
123
+ }
124
+
125
+ // `login` for one method — one device flow (RFC 8628); only the `provider` query
126
+ // on the `/cli-login` URL differs (google | linkedin | email). Email reuses an
127
+ // existing web session or signs in on the page, with no password in the terminal.
128
+ async function runDeviceFlow(provider, c, { openBrowser = openPath, sleep = undefined, lang = 'en', output = process.stdout } = {}) {
129
+ const authorizeEndpoint = getDeviceAuthorizeEndpoint();
130
+ const tokenEndpoint = getDeviceTokenEndpoint();
131
+ const authTokenEndpoint = getAuthTokenEndpoint();
132
+ if (!authorizeEndpoint || !tokenEndpoint || !authTokenEndpoint) {
133
+ process.stderr.write(`\n ${c.errorNoEndpoint}\n\n`);
134
+ process.exitCode = 1;
135
+ return;
136
+ }
137
+ // A new account also needs complete-registration before the JWT (freelanceType is a transient default).
138
+ const completeRegistrationEndpoint = getCompleteRegistrationEndpoint();
139
+ const registrationContext = buildRegistrationContext({ preferredLanguage: lang, newsletterConsent: false, freelanceType: 'POTENTIAL_FREELANCE' });
140
+ const providerName = provider === 'linkedin' ? c.methodLinkedin : provider === 'email' ? c.methodEmail : c.methodGoogle;
141
+
142
+ // An ACTIVE session is not silently overwritten.
143
+ const status = sessionStatus(loadAuthSession());
144
+ if (status === 'active') {
145
+ output.write(`\n ${c.alreadyLoggedIn}\n\n`);
146
+ return;
147
+ }
148
+ const intro = status === 'expired'
149
+ ? c.sessionExpiredIntro
150
+ : (provider === 'email' ? c.emailDeviceIntro : c.deviceIntro(providerName));
151
+ output.write(`\n ${intro}\n`);
152
+
153
+ const result = await runDeviceLogin(
154
+ { authorizeEndpoint, tokenEndpoint, authTokenEndpoint, completeRegistrationEndpoint, registrationContext },
155
+ {
156
+ ...(sleep ? { sleep } : {}),
157
+ onPrompt: ({ verificationUriComplete, userCode }) => {
158
+ const url = appendProviderParam(verificationUriComplete, provider);
159
+ output.write(`\n ${c.deviceVisit}\n ${url}\n\n ${c.deviceCodeLabel(userCode)}\n\n ${c.deviceWaiting}\n`);
160
+ if (provider === 'email') output.write(` ${c.deviceApproveHint}\n`);
161
+ try { openBrowser(url); } catch { /* the printed URL covers it */ }
162
+ },
163
+ },
164
+ );
165
+ handleDeviceResult(result, c, output);
166
+ }
167
+
168
+ function handleDeviceResult(result, c, output = process.stdout) {
169
+ if (result.ok) {
170
+ // Map the device-flow JWT to the shared persist shape (no cookie).
171
+ return void persistAndReport(
172
+ { accessToken: result.token, hubAccessToken: result.token, email: result.email || null },
173
+ c,
174
+ result.email || null,
175
+ output,
176
+ );
177
+ }
178
+
179
+ const messageFor = {
180
+ expired: c.deviceErrorExpired,
181
+ denied: c.deviceErrorDenied,
182
+ 'authorize-failed': c.deviceErrorGeneric,
183
+ 'poll-failed': c.deviceErrorGeneric,
184
+ 'token-exchange-failed': c.errorUpstream,
185
+ 'complete-registration-failed': c.errorUpstream,
186
+ upstream: 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.deviceErrorGeneric,
192
+ 'http-error': c.deviceErrorGeneric,
193
+ 'local-error': c.deviceErrorGeneric,
194
+ };
195
+ process.stderr.write(`\n ${messageFor[result.reason] || c.deviceErrorGeneric}\n\n`);
196
+ process.exitCode = 1;
197
+ }
198
+
199
+ // `logout` — local only (no endpoint, no network): forget the stored session.
200
+ async function runLogout(argv = process.argv.slice(2), { output = process.stdout } = {}) {
201
+ const opts = parseArgs(argv);
202
+ const lang = opts.lang || detectReportLang();
203
+ const c = getCatalog(lang).login;
204
+ const had = loadAuthSession() !== null;
205
+ clearAuthSession();
206
+ clearConsentState();
207
+ output.write(`\n ${had ? c.loggedOut : c.notLoggedIn}\n\n`);
208
+ }
209
+
210
+ module.exports = { run, runLogout, chooseLoginMethod, parseArgs, hasMethodFlag };
211
+
212
+ if (require.main === module) {
213
+ const argv = process.argv.slice(2);
214
+ if (argv[0] === 'logout') runLogout(argv.slice(1));
215
+ else run(argv);
216
+ }
package/bin/mcp.js ADDED
@@ -0,0 +1,124 @@
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 { makeAlmaTools } = require('../src/mcp-alma-tools');
18
+ const { makeLangTools } = require('../src/mcp-lang-tools');
19
+ const { makeSocialsTools } = require('../src/mcp-socials-tools');
20
+ const { makePortfolioTools } = require('../src/mcp-portfolios-tools');
21
+ const { makeOnboardingTools } = require('../src/mcp-onboarding-tools');
22
+ const { makeRoleTools } = require('../src/mcp-role-tools');
23
+ const { makeDeviceLoginTools } = require('../src/mcp-device-login-tools');
24
+ const { makeRegisterTools } = require('../src/mcp-register-tools');
25
+ const { makeSignupTools } = require('../src/mcp-signup-tools');
26
+ const { detectFlowLang } = require('../src/i18n');
27
+ const { makeReportTools } = require('../src/mcp-report-tools');
28
+ const { makePrompts } = require('../src/mcp-prompts');
29
+ const { buildServerInstructions } = require('../src/mcp-instructions');
30
+ const { ensureFreshSession } = require('../src/session-refresh');
31
+ const { createFunnel } = require('../src/onboarding-funnel');
32
+
33
+ let VERSION = '';
34
+ try {
35
+ VERSION = require('../package.json').version || '';
36
+ } catch {
37
+ VERSION = '';
38
+ }
39
+
40
+ const HELP = `\n shakers mcp — local MCP server (stdio)\n\n`
41
+ + ` Exposes the AI-usage evaluation as an MCP tool over stdin/stdout so a\n`
42
+ + ` desktop AI app (Claude / ChatGPT Desktop) can run it on your machine.\n\n`
43
+ + ` Usage: shakers mcp\n\n`
44
+ + ` This command speaks JSON-RPC on stdio; it is meant to be launched by an\n`
45
+ + ` MCP client, not run by hand.\n`
46
+ + ` Tools: ai_usage, ai_usage_result, logout, social_signin_start, social_signin_poll,\n`
47
+ + ` list_addable, add_skill, add_agent,\n`
48
+ + ` add_project, signup_start, signup_email, signup_draft, signup_create_account,\n`
49
+ + ` signup_status, update_existing_profile, import_profile, save_profile_details, open_web,\n`
50
+ + ` open_linkedin_profile,\n`
51
+ + ` suggest_register_context, read_cv, onboarding_interview_start,\n`
52
+ + ` onboarding_interview_turn, onboarding_interview_complete,\n`
53
+ + ` repeat_onboarding_interview, onboarding_finish,\n`
54
+ + ` list_available_roles, add_role, list_my_roles, set_main_role,\n`
55
+ + ` report, share,\n`
56
+ + ` list_certifiable_dimensions, find_projects, show_project,\n`
57
+ + ` save_project, unsave_project, list_invitations,\n`
58
+ + ` list_applications, get_availability,\n`
59
+ + ` list_certifications, get_profile, get_rate, get_languages,\n`
60
+ + ` list_socials, list_experiences, list_portfolios, alma_chat, alma_decision.\n\n`;
61
+
62
+ // The LOCAL (filesystem/machine) tools — the only ones the .mcpb bundle exposes; the
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']);
65
+
66
+ // `localOnly` builds the scan-only server for the Claude .mcpb bundle, reusing the SAME
67
+ // tool registry (just filtered) — no duplicate registration.
68
+ function buildServer({ localOnly = false, funnel = createFunnel({ surface: 'mcp' }) } = {}) {
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()];
71
+ const tools = localOnly ? allTools.filter((t) => LOCAL_ONLY_TOOL_NAMES.has(t.name)) : allTools;
72
+ // Slash-command prompts, kept to the tools actually registered in this mode.
73
+ const prompts = makePrompts(new Set(tools.map((t) => t.name)), lang);
74
+ return createMcpServer({
75
+ tools,
76
+ prompts,
77
+ serverInfo: { name: localOnly ? 'Shakers (local scan)' : 'Shakers', version: VERSION || '0.0.0' },
78
+ instructions: localOnly ? null : (client) => buildServerInstructions(client),
79
+ // Long-lived server: refresh the email session's hub JWT from the stored
80
+ // cookie before each tool call so a stale (~15-min) JWT never reaches a tool.
81
+ beforeCall: () => ensureFreshSession(process.env),
82
+ onInitialize: ({ clientInfo }) => funnel.setMcpClient(clientInfo.name),
83
+ });
84
+ }
85
+
86
+ function prepareProcess(env = process.env) {
87
+ require('../src/config').sanitizeStaleBakedEndpoints(env);
88
+ require('../src/backend-request').setClientIdentity('mcp');
89
+ }
90
+
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
+ }
97
+ if (argv.includes('--help') || argv.includes('-h')) {
98
+ process.stdout.write(HELP);
99
+ return;
100
+ }
101
+
102
+ prepareProcess();
103
+
104
+ // `--local` = the scan-only server shipped in the Claude .mcpb bundle.
105
+ const server = buildServer({ localOnly: argv.includes('--local') });
106
+ const rl = server.start({
107
+ input: process.stdin,
108
+ output: process.stdout,
109
+ onError: (e) => {
110
+ process.stderr.write(` [mcp] ${e && e.message ? e.message : String(e)}\n`);
111
+ },
112
+ });
113
+
114
+ await new Promise((resolve) => {
115
+ rl.on('close', resolve);
116
+ process.stdin.on('end', () => rl.close());
117
+ });
118
+ }
119
+
120
+ module.exports = { run, buildServer, buildServerInstructions, prepareProcess };
121
+
122
+ if (require.main === module) {
123
+ run();
124
+ }
package/bin/mcpb.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
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();
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
+ }