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.
Files changed (218) hide show
  1. package/LICENSE +59 -0
  2. package/README.md +1175 -0
  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 +81 -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 +120 -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 +283 -0
  20. package/bin/mcp.js +98 -0
  21. package/bin/me.js +69 -0
  22. package/bin/onboarding.js +134 -0
  23. package/bin/portfolios.js +69 -0
  24. package/bin/rate.js +70 -0
  25. package/bin/register.js +325 -0
  26. package/bin/report-html.js +134 -0
  27. package/bin/save.js +77 -0
  28. package/bin/shakers.js +344 -0
  29. package/bin/share.js +72 -0
  30. package/bin/show-project.js +72 -0
  31. package/bin/socials.js +70 -0
  32. package/bin/superadmin.js +351 -0
  33. package/package.json +40 -0
  34. package/src/agent-category.js +13 -0
  35. package/src/agent-evaluation.js +177 -0
  36. package/src/agent-org-chart.js +355 -0
  37. package/src/agent-synthesis.js +132 -0
  38. package/src/agent-usage.js +118 -0
  39. package/src/agents-client.js +165 -0
  40. package/src/ai-fluency-cell.js +72 -0
  41. package/src/ai-profile-client.js +45 -0
  42. package/src/ai-profile-preview.js +154 -0
  43. package/src/ai.js +66 -0
  44. package/src/alma-client.js +123 -0
  45. package/src/alma-flow.js +32 -0
  46. package/src/alma-repo-context.js +47 -0
  47. package/src/alma-shell.js +161 -0
  48. package/src/ansi.js +62 -0
  49. package/src/applications-client.js +79 -0
  50. package/src/applications-flow.js +58 -0
  51. package/src/auth-client.js +144 -0
  52. package/src/auth-device-client.js +118 -0
  53. package/src/auth-method-picker.js +44 -0
  54. package/src/auth-methods.js +35 -0
  55. package/src/auth-session-store.js +214 -0
  56. package/src/authorship.js +203 -0
  57. package/src/automations-detector.js +170 -0
  58. package/src/availability-client.js +92 -0
  59. package/src/availability-flow.js +105 -0
  60. package/src/backend-request.js +170 -0
  61. package/src/blind-approval-extract.js +48 -0
  62. package/src/brand-ansi.js +14 -0
  63. package/src/brand-svg.js +9 -0
  64. package/src/browser-tools-detector.js +21 -0
  65. package/src/build-next-level.js +56 -0
  66. package/src/certifications-client.js +89 -0
  67. package/src/certifications-flow.js +75 -0
  68. package/src/certify-dimension-client.js +217 -0
  69. package/src/certify-dimension-flow.js +193 -0
  70. package/src/certify-disclaimer.js +51 -0
  71. package/src/certify-remediation-prompt.js +23 -0
  72. package/src/cli-args.js +83 -0
  73. package/src/config-dir.js +120 -0
  74. package/src/config.js +778 -0
  75. package/src/consent-flow.js +115 -0
  76. package/src/consent-skip.js +27 -0
  77. package/src/decisions-extract.js +43 -0
  78. package/src/detect-repos.js +145 -0
  79. package/src/detected-tools.js +15 -0
  80. package/src/detection-evidence.js +105 -0
  81. package/src/detection-noise.js +132 -0
  82. package/src/detectors.js +221 -0
  83. package/src/device-auth-store.js +100 -0
  84. package/src/device-login.js +118 -0
  85. package/src/email-verification.js +152 -0
  86. package/src/env-paths.js +16 -0
  87. package/src/find-projects-cache.js +89 -0
  88. package/src/find-projects-client.js +138 -0
  89. package/src/find-projects-flow.js +164 -0
  90. package/src/git-activity.js +123 -0
  91. package/src/graph-certs.js +58 -0
  92. package/src/graph-generator.js +230 -0
  93. package/src/graph-scan.js +239 -0
  94. package/src/i18n.js +3264 -0
  95. package/src/inter-fonts.js +6 -0
  96. package/src/interactive-select.js +183 -0
  97. package/src/interview-client.js +226 -0
  98. package/src/interview-transcript.js +56 -0
  99. package/src/inventory-client.js +90 -0
  100. package/src/invitations-client.js +66 -0
  101. package/src/invitations-flow.js +50 -0
  102. package/src/lang-client.js +147 -0
  103. package/src/lang-codes.js +14 -0
  104. package/src/lang-flow.js +85 -0
  105. package/src/legal-notice.js +53 -0
  106. package/src/locale.js +51 -0
  107. package/src/markdown-ansi.js +50 -0
  108. package/src/maturity.js +111 -0
  109. package/src/mcp-ai-usage-result-tool.js +109 -0
  110. package/src/mcp-ai-usage-tool.js +333 -0
  111. package/src/mcp-alma-tools.js +91 -0
  112. package/src/mcp-applications-tools.js +42 -0
  113. package/src/mcp-ask-tools.js +50 -0
  114. package/src/mcp-auth-form.js +282 -0
  115. package/src/mcp-availability-tools.js +43 -0
  116. package/src/mcp-certifications-tools.js +42 -0
  117. package/src/mcp-certify-dimension-tools.js +58 -0
  118. package/src/mcp-detector.js +223 -0
  119. package/src/mcp-device-login-tools.js +173 -0
  120. package/src/mcp-find-projects-tools.js +67 -0
  121. package/src/mcp-install.js +245 -0
  122. package/src/mcp-invitations-tools.js +42 -0
  123. package/src/mcp-lang-tools.js +39 -0
  124. package/src/mcp-onboarding-tools.js +388 -0
  125. package/src/mcp-portfolios-tools.js +46 -0
  126. package/src/mcp-profile-tools.js +46 -0
  127. package/src/mcp-rate-tools.js +42 -0
  128. package/src/mcp-register-tools.js +517 -0
  129. package/src/mcp-report-tools.js +101 -0
  130. package/src/mcp-role-tools.js +112 -0
  131. package/src/mcp-saved-positions-tools.js +55 -0
  132. package/src/mcp-server.js +170 -0
  133. package/src/mcp-show-project-tools.js +50 -0
  134. package/src/mcp-socials-tools.js +39 -0
  135. package/src/memory-structure-detector.js +108 -0
  136. package/src/model-call-record.js +198 -0
  137. package/src/onboarding-availability.js +26 -0
  138. package/src/onboarding-client.js +364 -0
  139. package/src/onboarding-flow.js +1078 -0
  140. package/src/onboarding-livekit-client.js +102 -0
  141. package/src/onboarding-status-client.js +42 -0
  142. package/src/open-file.js +32 -0
  143. package/src/osc-link.js +16 -0
  144. package/src/portfolio-client.js +164 -0
  145. package/src/portfolio-git-info.js +61 -0
  146. package/src/portfolios-client.js +85 -0
  147. package/src/portfolios-flow.js +79 -0
  148. package/src/profile-client.js +125 -0
  149. package/src/profile-flow.js +115 -0
  150. package/src/profile-switch.js +46 -0
  151. package/src/prompt-select.js +29 -0
  152. package/src/rate-client.js +82 -0
  153. package/src/rate-flow.js +137 -0
  154. package/src/render-ai-fluency.js +266 -0
  155. package/src/render-certification.js +177 -0
  156. package/src/render-html.js +140 -0
  157. package/src/render-report-text.js +27 -0
  158. package/src/render-sheet.js +261 -0
  159. package/src/render-terminal.js +109 -0
  160. package/src/render-usage-report-text.js +155 -0
  161. package/src/repo-context.js +215 -0
  162. package/src/report-gating.js +49 -0
  163. package/src/report-store.js +226 -0
  164. package/src/report-theme.js +177 -0
  165. package/src/report-view-client.js +50 -0
  166. package/src/reveal-output.js +29 -0
  167. package/src/roadmap-content.js +453 -0
  168. package/src/roadmap-personalization.js +132 -0
  169. package/src/roadmap-prompt.js +154 -0
  170. package/src/roles-client.js +114 -0
  171. package/src/roles-flow.js +192 -0
  172. package/src/sanitize-network-text.js +36 -0
  173. package/src/saved-positions-client.js +71 -0
  174. package/src/saved-positions-flow.js +50 -0
  175. package/src/scan-exclusions.js +50 -0
  176. package/src/scanner.js +612 -0
  177. package/src/scope-signals.js +148 -0
  178. package/src/secret-prompt.js +72 -0
  179. package/src/session-gate.js +30 -0
  180. package/src/session-refresh.js +51 -0
  181. package/src/session-scan.js +336 -0
  182. package/src/session-signals.js +36 -0
  183. package/src/shakers-wordmark.js +17 -0
  184. package/src/share-card.js +362 -0
  185. package/src/share-certification.js +111 -0
  186. package/src/share-consent-state.js +157 -0
  187. package/src/share-payload.js +300 -0
  188. package/src/share.js +184 -0
  189. package/src/show-project-client.js +58 -0
  190. package/src/show-project-flow.js +119 -0
  191. package/src/signup-client.js +91 -0
  192. package/src/skills-client.js +102 -0
  193. package/src/socials-client.js +91 -0
  194. package/src/socials-flow.js +83 -0
  195. package/src/specificity-extract.js +41 -0
  196. package/src/start-add-agents.js +306 -0
  197. package/src/start-add-portfolio.js +388 -0
  198. package/src/start-add-skills.js +260 -0
  199. package/src/stdin-ask.js +129 -0
  200. package/src/steering-extract.js +43 -0
  201. package/src/strip-internal-comments.js +45 -0
  202. package/src/tech-detector.js +295 -0
  203. package/src/templates/report-sheet.html +572 -0
  204. package/src/terminal-agents.js +199 -0
  205. package/src/terminal-format.js +85 -0
  206. package/src/terminal-progress.js +106 -0
  207. package/src/terminal-sections.js +219 -0
  208. package/src/tier-analysis.js +126 -0
  209. package/src/tier-engine.js +114 -0
  210. package/src/tier-projection.js +117 -0
  211. package/src/transcript-turns.js +49 -0
  212. package/src/translations-client.js +84 -0
  213. package/src/typewriter.js +154 -0
  214. package/src/usage-model-calls.js +119 -0
  215. package/src/usage-oneshot-commands.js +180 -0
  216. package/src/usage-repo-scope.js +162 -0
  217. package/src/usage-run-steps.js +338 -0
  218. package/src/work-streams.js +74 -0
@@ -0,0 +1,198 @@
1
+ 'use strict';
2
+
3
+ // What happened with each model call, written down (talents-ai-score, issue 109).
4
+
5
+ // The three keys.
6
+ const MODEL_CALL = Object.freeze({
7
+ SYNTHESIS: 'agentSynthesis',
8
+ EVALUATION: 'agentEvaluation',
9
+ ROADMAP: 'roadmapPersonalization',
10
+ });
11
+
12
+ const REASON = Object.freeze({
13
+ // ok:true
14
+ OK: 'ok',
15
+ // ok:true AND INCOMPLETE (2026-08-03, from a real incident).
16
+ OK_PARTIAL: 'ok-partial',
17
+ // ok:null — never attempted, and NOT a failure
18
+ NO_AGENTS: 'no-agents',
19
+ NO_ENDPOINT: 'no-endpoint-configured',
20
+ MAX_TIER: 'max-tier',
21
+ NOT_APPLICABLE: 'not-applicable',
22
+ NO_CONSENT: 'no-consent',
23
+ // ok:false — attempted, and something was lost
24
+ TIMEOUT: 'timeout',
25
+ NETWORK: 'network-error',
26
+ INVALID_URL: 'invalid-endpoint-url',
27
+ HTTP: 'http-error',
28
+ MALFORMED_JSON: 'malformed-json',
29
+ UNEXPECTED_SHAPE: 'unexpected-shape',
30
+ LOCAL_ERROR: 'local-error',
31
+ });
32
+
33
+ // The reasons that mean the call produced usable data (`ok: true`).
34
+ const SUCCESS_REASONS = new Set([REASON.OK, REASON.OK_PARTIAL]);
35
+
36
+ // "Didn't apply" — the reasons that produce `ok: null`.
37
+ const SKIP_REASONS = new Set([
38
+ REASON.NO_AGENTS,
39
+ REASON.NO_ENDPOINT,
40
+ REASON.MAX_TIER,
41
+ REASON.NOT_APPLICABLE,
42
+ REASON.NO_CONSENT,
43
+ ]);
44
+
45
+ const FAILURE_REASONS = new Set([
46
+ REASON.TIMEOUT,
47
+ REASON.NETWORK,
48
+ REASON.INVALID_URL,
49
+ REASON.HTTP,
50
+ REASON.MALFORMED_JSON,
51
+ REASON.UNEXPECTED_SHAPE,
52
+ REASON.LOCAL_ERROR,
53
+ ]);
54
+
55
+ // The rejection `.kind` tags `src/backend-request.js` attaches, mapped onto this vocabulary.
56
+ function classifyChainError(e) {
57
+ const kind = e && e.kind;
58
+ if (kind === 'timeout') return REASON.TIMEOUT;
59
+ if (kind === 'network-error') return REASON.NETWORK;
60
+ if (kind === 'invalid-url') return REASON.INVALID_URL;
61
+ if (kind === 'no-endpoint') return REASON.NO_ENDPOINT;
62
+ return REASON.LOCAL_ERROR;
63
+ }
64
+
65
+ // The canonical record.
66
+ function record({ attemptedAt, ok, reason, status = null, backend = null, elapsedMs = null, timeoutMs = null, hops = [], omitted = [] }) {
67
+ return {
68
+ attemptedAt: attemptedAt || null,
69
+ ok,
70
+ reason,
71
+ status: typeof status === 'number' ? status : null,
72
+ backend: backend || null,
73
+ elapsedMs: typeof elapsedMs === 'number' ? elapsedMs : null,
74
+ timeoutMs: typeof timeoutMs === 'number' ? timeoutMs : null,
75
+ hops: Array.isArray(hops) ? hops : [],
76
+ // What the call did NOT cover, by name.
77
+ omitted: Array.isArray(omitted) ? omitted.filter((n) => typeof n === 'string' && n) : [],
78
+ };
79
+ }
80
+
81
+ // A call that never went out.
82
+ function skipped(reason) {
83
+ return record({ attemptedAt: null, ok: null, reason: reason || REASON.NOT_APPLICABLE });
84
+ }
85
+
86
+ // A failure that happened BEFORE the wire (building the request threw: a file that can't be read, a parse that blew up).
87
+ function failedLocally(reason = REASON.LOCAL_ERROR) {
88
+ return record({ attemptedAt: new Date().toISOString(), ok: false, reason });
89
+ }
90
+
91
+ // The one-shot reporter each client module uses.
92
+ function reporterFor(onOutcome, { timeoutMs = null } = {}) {
93
+ const attemptedAt = new Date().toISOString();
94
+ const startedHr = process.hrtime.bigint();
95
+ const hops = [];
96
+ let settled = false;
97
+
98
+ const emit = (built) => {
99
+ if (settled) return null;
100
+ settled = true;
101
+ if (typeof onOutcome === 'function') onOutcome(built);
102
+ return built;
103
+ };
104
+ const elapsed = () => Number((process.hrtime.bigint() - startedHr) / 1000000n);
105
+
106
+ return {
107
+ // Handed to `requestBackend` as `trace`.
108
+ hops,
109
+ skip(reason) {
110
+ return emit(skipped(reason));
111
+ },
112
+ fail(reason, { status = null, backend = null } = {}) {
113
+ return emit(record({ attemptedAt, ok: false, reason, status, backend, elapsedMs: elapsed(), timeoutMs, hops }));
114
+ },
115
+ failFromError(e) {
116
+ return this.fail(classifyChainError(e));
117
+ },
118
+ // `omitted` non-empty flips the reason to `ok-partial` and NOT `ok` — the one decision that stops a partial answer from being recorded as a clean run.
119
+ succeed({ status = null, backend = null, omitted = [] } = {}) {
120
+ const left = Array.isArray(omitted) ? omitted.filter((n) => typeof n === 'string' && n) : [];
121
+ return emit(record({
122
+ attemptedAt,
123
+ ok: true,
124
+ reason: left.length ? REASON.OK_PARTIAL : REASON.OK,
125
+ status,
126
+ backend,
127
+ elapsedMs: elapsed(),
128
+ timeoutMs,
129
+ hops,
130
+ omitted: left,
131
+ }));
132
+ },
133
+ };
134
+ }
135
+
136
+ // Attaches a record to the report under its call key.
137
+ function attachModelCall(report, key, built) {
138
+ if (!report || typeof report !== 'object' || !key) return built;
139
+ if (!report.modelCalls || typeof report.modelCalls !== 'object') report.modelCalls = {};
140
+ report.modelCalls[key] = built;
141
+ return built;
142
+ }
143
+
144
+ function modelCall(report, key) {
145
+ const calls = report && report.modelCalls;
146
+ return calls && typeof calls === 'object' ? calls[key] || null : null;
147
+ }
148
+
149
+ // The two predicates the render layer is allowed to ask.
150
+ function callFailed(report, key) {
151
+ const c = modelCall(report, key);
152
+ return !!c && c.ok === false;
153
+ }
154
+
155
+ function callSucceeded(report, key) {
156
+ const c = modelCall(report, key);
157
+ return !!c && c.ok === true;
158
+ }
159
+
160
+ // The call worked AND left something out.
161
+ function callPartial(report, key) {
162
+ const c = modelCall(report, key);
163
+ return !!c && c.ok === true && Array.isArray(c.omitted) && c.omitted.length > 0;
164
+ }
165
+
166
+ // THE CALL LOST TIME TO A DEADLINE — and this is NOT `reason === TIMEOUT` (issue 116, found by measuring, not by reading).
167
+ function callTimedOut(report, key) {
168
+ const c = modelCall(report, key);
169
+ if (!c || c.ok !== false) return false;
170
+ if (c.reason === REASON.TIMEOUT) return true;
171
+ return Array.isArray(c.hops) && c.hops.some((h) => h && h.kind === REASON.TIMEOUT);
172
+ }
173
+
174
+ // The names the call left out, for the surface that names them. `[]` when the run
175
+ // was complete, so a caller never has to null-check before iterating.
176
+ function omittedByCall(report, key) {
177
+ const c = modelCall(report, key);
178
+ return c && Array.isArray(c.omitted) ? c.omitted : [];
179
+ }
180
+
181
+ module.exports = {
182
+ MODEL_CALL,
183
+ REASON,
184
+ SKIP_REASONS,
185
+ FAILURE_REASONS,
186
+ SUCCESS_REASONS,
187
+ classifyChainError,
188
+ skipped,
189
+ failedLocally,
190
+ reporterFor,
191
+ attachModelCall,
192
+ modelCall,
193
+ callFailed,
194
+ callSucceeded,
195
+ callPartial,
196
+ callTimedOut,
197
+ omittedByCall,
198
+ };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const { loadAuthSession, sessionStatus } = require('./auth-session-store');
4
+ const { getOnboardingStatusEndpoint } = require('./config');
5
+ const { requestOnboardingStatus } = require('./onboarding-status-client');
6
+
7
+ async function checkOnboardingCompleted({
8
+ env = process.env,
9
+ session = null,
10
+ loadSession = loadAuthSession,
11
+ endpointGetter = getOnboardingStatusEndpoint,
12
+ fetchStatus = requestOnboardingStatus,
13
+ timeoutMs,
14
+ } = {}) {
15
+ const active = session || loadSession();
16
+ if (sessionStatus(active) !== 'active') return false;
17
+ const endpoint = endpointGetter(env);
18
+ if (!endpoint) return false;
19
+ const res = await fetchStatus(
20
+ { accessToken: active.accessToken },
21
+ timeoutMs ? { endpoint, timeoutMs } : { endpoint },
22
+ );
23
+ return res.ok ? res.completed === true : false;
24
+ }
25
+
26
+ module.exports = { checkOnboardingCompleted };
@@ -0,0 +1,364 @@
1
+ 'use strict';
2
+
3
+ const { postJsonWithTimeout } = require('./backend-request');
4
+ const { WORKS_CODE_TO_REASON } = require('./skills-client');
5
+ const { toLowerLang, toUpperLang } = require('./lang-codes');
6
+
7
+ const DEFAULT_TIMEOUT_MS = 20000;
8
+ const IMPORT_KICKOFF_TIMEOUT_MS = 90000;
9
+
10
+ function parseJson(raw) {
11
+ try {
12
+ return raw ? JSON.parse(raw) : null;
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ function hubHeaders({ hubAccessToken } = {}) {
19
+ const headers = {};
20
+ if (typeof hubAccessToken === 'string' && hubAccessToken) headers.Authorization = `Bearer ${hubAccessToken}`;
21
+ return headers;
22
+ }
23
+
24
+ function bearerHeaders({ accessToken } = {}) {
25
+ const headers = {};
26
+ if (typeof accessToken === 'string' && accessToken) headers.Authorization = `Bearer ${accessToken}`;
27
+ return headers;
28
+ }
29
+
30
+ function reasonForError(status, raw) {
31
+ const body = parseJson(raw);
32
+ const code = body && typeof body.code === 'string' ? body.code : null;
33
+ return (code && WORKS_CODE_TO_REASON[code]) || `http-${status}`;
34
+ }
35
+
36
+ function envelopeData(raw) {
37
+ const json = parseJson(raw);
38
+ if (!json || typeof json !== 'object' || json.status !== 'OK') return undefined;
39
+ return json.data;
40
+ }
41
+
42
+ function buildImportSources({ linkedinUrl, cvPath, githubUrl, websiteUrl, language } = {}) {
43
+ const sources = { language: toLowerLang(language) };
44
+ if (typeof linkedinUrl === 'string' && linkedinUrl) sources.linkedinUrl = linkedinUrl;
45
+ if (typeof cvPath === 'string' && cvPath) sources.cvPath = cvPath;
46
+ if (typeof githubUrl === 'string' && githubUrl) sources.githubUrl = githubUrl;
47
+ if (typeof websiteUrl === 'string' && websiteUrl) sources.websiteUrl = websiteUrl;
48
+ return sources;
49
+ }
50
+
51
+ async function requestImportProfile(
52
+ { linkedinUrl, cvPath, githubUrl, websiteUrl, language, accessToken, hubAccessToken } = {},
53
+ { endpoint, timeoutMs = IMPORT_KICKOFF_TIMEOUT_MS } = {},
54
+ ) {
55
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
56
+ if (!linkedinUrl) return { ok: false, reason: 'no-linkedin' };
57
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
58
+
59
+ const body = buildImportSources({ linkedinUrl, cvPath, githubUrl, websiteUrl, language });
60
+ let res;
61
+ try {
62
+ res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'POST', null, hubHeaders({ hubAccessToken }));
63
+ } catch (e) {
64
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
65
+ }
66
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
67
+
68
+ const data = envelopeData(res.raw);
69
+ if (data === undefined) return { ok: false, reason: 'bad-response' };
70
+ const jobId = data && typeof data === 'object' && (data.jobId || data.importId || data.id) || null;
71
+ const state = data && typeof data === 'object' && typeof data.state === 'string' ? data.state : 'running';
72
+ return { ok: true, jobId, state };
73
+ }
74
+
75
+ async function requestImportStatus(
76
+ { jobId, accessToken, hubAccessToken } = {},
77
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
78
+ ) {
79
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
80
+ if (!jobId) return { ok: false, reason: 'no-job' };
81
+
82
+ const url = `${endpoint.replace(/\/+$/, '')}/${encodeURIComponent(jobId)}`;
83
+ let res;
84
+ try {
85
+ res = await postJsonWithTimeout(url, null, timeoutMs, 'GET', null, hubHeaders({ hubAccessToken }));
86
+ } catch (e) {
87
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
88
+ }
89
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
90
+
91
+ const data = envelopeData(res.raw);
92
+ if (data === undefined || !data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
93
+ const state = typeof data.state === 'string' ? data.state : 'running';
94
+ return { ok: true, state };
95
+ }
96
+
97
+ async function requestSetProfessionalDetails(
98
+ { currentEmploymentStatus, freelanceIntent, changeMotivators, freelanceType, accessToken, hubAccessToken } = {},
99
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
100
+ ) {
101
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
102
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
103
+
104
+ const body = {};
105
+ if (typeof currentEmploymentStatus === 'string' && currentEmploymentStatus) body.currentEmploymentStatus = currentEmploymentStatus;
106
+ if (typeof freelanceIntent === 'string' && freelanceIntent) body.freelanceIntent = freelanceIntent;
107
+ if (typeof changeMotivators === 'string' && changeMotivators) body.changeMotivators = changeMotivators;
108
+ // freelanceType derived from the work situation (the single place it is asked now).
109
+ if (typeof freelanceType === 'string' && freelanceType) body.freelanceType = freelanceType;
110
+ let res;
111
+ try {
112
+ res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'PATCH', null, hubHeaders({ hubAccessToken }));
113
+ } catch (e) {
114
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
115
+ }
116
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
117
+ if (envelopeData(res.raw) === undefined) return { ok: false, reason: 'bad-response' };
118
+ return { ok: true };
119
+ }
120
+
121
+ async function requestSetPricingRate(
122
+ { pricing, accessToken, hubAccessToken } = {},
123
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
124
+ ) {
125
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
126
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
127
+ if (!pricing || typeof pricing !== 'object') return { ok: false, reason: 'no-pricing' };
128
+
129
+ let res;
130
+ try {
131
+ res = await postJsonWithTimeout(endpoint, pricing, timeoutMs, 'PUT', null, hubHeaders({ hubAccessToken }));
132
+ } catch (e) {
133
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
134
+ }
135
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
136
+ if (envelopeData(res.raw) === undefined) return { ok: false, reason: 'bad-response' };
137
+ return { ok: true };
138
+ }
139
+
140
+ async function requestSetAvailability(
141
+ { availability, accessToken, hubAccessToken } = {},
142
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
143
+ ) {
144
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
145
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
146
+ if (!availability || typeof availability !== 'object') return { ok: false, reason: 'no-availability' };
147
+
148
+ let res;
149
+ try {
150
+ res = await postJsonWithTimeout(endpoint, availability, timeoutMs, 'PUT', null, hubHeaders({ hubAccessToken }));
151
+ } catch (e) {
152
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
153
+ }
154
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
155
+ if (envelopeData(res.raw) === undefined) return { ok: false, reason: 'bad-response' };
156
+ return { ok: true };
157
+ }
158
+
159
+ async function requestLanguagesCatalog(
160
+ { hubAccessToken } = {},
161
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
162
+ ) {
163
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
164
+
165
+ let res;
166
+ try {
167
+ res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'GET', null, hubHeaders({ hubAccessToken }));
168
+ } catch (e) {
169
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
170
+ }
171
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
172
+
173
+ const data = envelopeData(res.raw);
174
+ if (!Array.isArray(data)) return { ok: false, reason: 'bad-response' };
175
+ return { ok: true, languages: data };
176
+ }
177
+
178
+ async function requestSetLanguages(
179
+ { languages, accessToken, hubAccessToken } = {},
180
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
181
+ ) {
182
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
183
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
184
+ if (!Array.isArray(languages) || languages.length === 0) return { ok: false, reason: 'no-languages' };
185
+
186
+ let res;
187
+ try {
188
+ res = await postJsonWithTimeout(endpoint, { languages }, timeoutMs, 'PATCH', null, hubHeaders({ hubAccessToken }));
189
+ } catch (e) {
190
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
191
+ }
192
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
193
+ if (envelopeData(res.raw) === undefined) return { ok: false, reason: 'bad-response' };
194
+ return { ok: true };
195
+ }
196
+
197
+ async function requestSetTalentPhone(
198
+ { telephoneCode, telephoneNumber, accessToken, hubAccessToken } = {},
199
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
200
+ ) {
201
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
202
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
203
+
204
+ const body = { telephoneCode, telephoneNumber };
205
+ let res;
206
+ try {
207
+ res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'PATCH', null, hubHeaders({ hubAccessToken }));
208
+ } catch (e) {
209
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
210
+ }
211
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
212
+ if (envelopeData(res.raw) === undefined) return { ok: false, reason: 'bad-response' };
213
+ return { ok: true };
214
+ }
215
+
216
+ async function requestCompleteOnboarding(
217
+ { accessToken, hubAccessToken } = {},
218
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
219
+ ) {
220
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
221
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
222
+
223
+ let res;
224
+ try {
225
+ res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'POST', null, hubHeaders({ hubAccessToken }));
226
+ } catch (e) {
227
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
228
+ }
229
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
230
+
231
+ const data = envelopeData(res.raw);
232
+ if (data === undefined) return { ok: false, reason: 'bad-response' };
233
+ const d = data && typeof data === 'object' ? data : {};
234
+ return {
235
+ ok: true,
236
+ onboardingStatus: typeof d.onboardingStatus === 'string' ? d.onboardingStatus : null,
237
+ registrationLevel: typeof d.registrationLevel === 'string' ? d.registrationLevel : null,
238
+ completedProfilePercentage: typeof d.completedProfilePercentage === 'number' ? d.completedProfilePercentage : null,
239
+ };
240
+ }
241
+
242
+ // PATCH /interviews/onboarding/restart (no body) — overwrites the taken onboarding interview in-place so it can be re-run.
243
+ async function requestRestartOnboardingInterview(
244
+ { accessToken } = {},
245
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
246
+ ) {
247
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
248
+ let res;
249
+ try {
250
+ res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'PATCH', null, bearerHeaders({ accessToken }));
251
+ } catch (e) {
252
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
253
+ }
254
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw), status: res.status };
255
+
256
+ const data = envelopeData(res.raw);
257
+ const d = data && typeof data === 'object' ? data : {};
258
+ return { ok: true, interviewId: d.interviewId || null, language: d.language || null };
259
+ }
260
+
261
+ async function requestCreateOnboardingInterview(
262
+ { candidateId, accessToken } = {},
263
+ { base, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
264
+ ) {
265
+ if (!base) return { ok: false, reason: 'no-endpoint' };
266
+ if (!candidateId) return { ok: false, reason: 'no-candidate' };
267
+
268
+ const url = `${base.replace(/\/+$/, '')}/onboarding`;
269
+ let res;
270
+ try {
271
+ res = await postJsonWithTimeout(url, { candidateId }, timeoutMs, 'POST', null, bearerHeaders({ accessToken }));
272
+ } catch (e) {
273
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
274
+ }
275
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
276
+
277
+ const data = envelopeData(res.raw);
278
+ if (data === undefined || !data || typeof data !== 'object' || !data.interviewId) return { ok: false, reason: 'bad-response' };
279
+ return { ok: true, interviewId: data.interviewId, created: data.created === true, language: data.language || null };
280
+ }
281
+
282
+ async function requestStartTextSession(
283
+ { interviewId, language, accessToken } = {},
284
+ { base, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
285
+ ) {
286
+ if (!base) return { ok: false, reason: 'no-endpoint' };
287
+ if (!interviewId) return { ok: false, reason: 'no-interview' };
288
+
289
+ const url = `${base.replace(/\/+$/, '')}/${encodeURIComponent(interviewId)}/text-session`;
290
+ const body = { language: toUpperLang(language) };
291
+ let res;
292
+ try {
293
+ res = await postJsonWithTimeout(url, body, timeoutMs, 'POST', null, bearerHeaders({ accessToken }));
294
+ } catch (e) {
295
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
296
+ }
297
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
298
+
299
+ const data = envelopeData(res.raw);
300
+ if (data === undefined || !data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
301
+ return { ok: true, interviewId: data.interviewId || interviewId, greeting: typeof data.greeting === 'string' ? data.greeting : '' };
302
+ }
303
+
304
+ async function requestOnboardingTurn(
305
+ { interviewId, message, accessToken } = {},
306
+ { base, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
307
+ ) {
308
+ if (!base) return { ok: false, reason: 'no-endpoint' };
309
+ if (!interviewId) return { ok: false, reason: 'no-interview' };
310
+ if (typeof message !== 'string' || !message) return { ok: false, reason: 'no-message' };
311
+
312
+ const url = `${base.replace(/\/+$/, '')}/${encodeURIComponent(interviewId)}/text-session/turns`;
313
+ let res;
314
+ try {
315
+ res = await postJsonWithTimeout(url, { message }, timeoutMs, 'POST', null, bearerHeaders({ accessToken }));
316
+ } catch (e) {
317
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
318
+ }
319
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
320
+
321
+ const data = envelopeData(res.raw);
322
+ if (data === undefined || !data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
323
+ return { ok: true, response: typeof data.response === 'string' ? data.response : '', ended: data.ended === true };
324
+ }
325
+
326
+ async function requestCompleteTextSession(
327
+ { interviewId, accessToken } = {},
328
+ { base, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
329
+ ) {
330
+ if (!base) return { ok: false, reason: 'no-endpoint' };
331
+ if (!interviewId) return { ok: false, reason: 'no-interview' };
332
+
333
+ const url = `${base.replace(/\/+$/, '')}/${encodeURIComponent(interviewId)}/text-session/complete`;
334
+ let res;
335
+ try {
336
+ res = await postJsonWithTimeout(url, null, timeoutMs, 'POST', null, bearerHeaders({ accessToken }));
337
+ } catch (e) {
338
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
339
+ }
340
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
341
+
342
+ const data = envelopeData(res.raw);
343
+ if (data === undefined || !data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
344
+ return { ok: true, state: typeof data.state === 'string' ? data.state : null };
345
+ }
346
+
347
+ module.exports = {
348
+ requestImportProfile,
349
+ requestImportStatus,
350
+ requestSetProfessionalDetails,
351
+ requestSetPricingRate,
352
+ requestSetAvailability,
353
+ requestLanguagesCatalog,
354
+ requestSetLanguages,
355
+ requestSetTalentPhone,
356
+ requestCompleteOnboarding,
357
+ requestRestartOnboardingInterview,
358
+ requestCreateOnboardingInterview,
359
+ requestStartTextSession,
360
+ requestOnboardingTurn,
361
+ requestCompleteTextSession,
362
+ buildImportSources,
363
+ reasonForError,
364
+ };