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
@@ -0,0 +1,374 @@
1
+ 'use strict';
2
+
3
+ const { postJsonWithTimeout, postMultipartWithTimeout } = require('./backend-request');
4
+ const { WORKS_CODE_TO_REASON } = require('./skills-client');
5
+ const { toLowerLang, toUpperLang } = require('./lang-codes');
6
+ const { readLocalCv } = require('./cv-file');
7
+
8
+ const DEFAULT_TIMEOUT_MS = 20000;
9
+ const IMPORT_KICKOFF_TIMEOUT_MS = 90000;
10
+
11
+ function parseJson(raw) {
12
+ try {
13
+ return raw ? JSON.parse(raw) : null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
19
+ function hubHeaders({ hubAccessToken } = {}) {
20
+ const headers = {};
21
+ if (typeof hubAccessToken === 'string' && hubAccessToken) headers.Authorization = `Bearer ${hubAccessToken}`;
22
+ return headers;
23
+ }
24
+
25
+ function bearerHeaders({ accessToken } = {}) {
26
+ const headers = {};
27
+ if (typeof accessToken === 'string' && accessToken) headers.Authorization = `Bearer ${accessToken}`;
28
+ return headers;
29
+ }
30
+
31
+ function reasonForError(status, raw) {
32
+ const body = parseJson(raw);
33
+ const code = body && typeof body.code === 'string' ? body.code : null;
34
+ return (code && WORKS_CODE_TO_REASON[code]) || `http-${status}`;
35
+ }
36
+
37
+ function envelopeData(raw) {
38
+ const json = parseJson(raw);
39
+ if (!json || typeof json !== 'object' || json.status !== 'OK') return undefined;
40
+ return json.data;
41
+ }
42
+
43
+ function buildImportSources({ linkedinUrl, cvPath, githubUrl, websiteUrl, language, userQuery, fillEmptyOnly } = {}) {
44
+ const sources = { language: toLowerLang(language) };
45
+ if (typeof linkedinUrl === 'string' && linkedinUrl) sources.linkedinUrl = linkedinUrl;
46
+ if (typeof cvPath === 'string' && cvPath) sources.cvPath = cvPath;
47
+ if (typeof githubUrl === 'string' && githubUrl) sources.githubUrl = githubUrl;
48
+ if (typeof websiteUrl === 'string' && websiteUrl) sources.websiteUrl = websiteUrl;
49
+ if (typeof userQuery === 'string' && userQuery) sources.userQuery = userQuery;
50
+ if (fillEmptyOnly === true) sources.fillEmptyOnly = true;
51
+ return sources;
52
+ }
53
+
54
+ // Hub answers import-profile synchronously with a per-source report; null when the AI omitted it.
55
+ function importReportFrom(data) {
56
+ const report = data && typeof data === 'object' ? data.importReport : null;
57
+ if (!report || !Array.isArray(report.sources)) return null;
58
+ return {
59
+ outcome: typeof report.outcome === 'string' ? report.outcome : null,
60
+ sources: report.sources
61
+ .filter((src) => src && typeof src.source === 'string')
62
+ .map((src) => ({
63
+ source: src.source,
64
+ status: typeof src.status === 'string' ? src.status : null,
65
+ ...(typeof src.code === 'string' ? { code: src.code } : {}),
66
+ ...(typeof src.retryable === 'boolean' ? { retryable: src.retryable } : {}),
67
+ })),
68
+ };
69
+ }
70
+
71
+ // The CV formats hub accepts on import-profile.
72
+ function readCvUpload(input) {
73
+ return readLocalCv(input, { extensions: ['.pdf', '.doc', '.docx'], badFormat: 'cv-bad-format' });
74
+ }
75
+
76
+ async function requestImportProfile(
77
+ { linkedinUrl, cvPath, githubUrl, websiteUrl, language, userQuery, fillEmptyOnly, accessToken, hubAccessToken } = {},
78
+ { endpoint, timeoutMs = IMPORT_KICKOFF_TIMEOUT_MS } = {},
79
+ ) {
80
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
81
+ if (!linkedinUrl && !cvPath && !githubUrl && !websiteUrl) return { ok: false, reason: 'no-source' };
82
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
83
+
84
+ const body = buildImportSources({ linkedinUrl, cvPath, githubUrl, websiteUrl, language, userQuery, fillEmptyOnly });
85
+ let res;
86
+ try {
87
+ if (body.cvPath) {
88
+ // Hub reads the CV as an uploaded `cv` file (multipart), never as a local
89
+ // path: a path on the talent's machine means nothing to the server.
90
+ const cv = readCvUpload(body.cvPath);
91
+ if (!cv.ok) return cv;
92
+ const { cvPath: _localPath, ...fields } = body;
93
+ const form = { ...fields, fillEmptyOnly: fields.fillEmptyOnly ? 'true' : undefined };
94
+ res = await postMultipartWithTimeout(endpoint, { ...form, cv: cv.file }, timeoutMs, 'POST', hubHeaders({ hubAccessToken }));
95
+ } else {
96
+ res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'POST', null, hubHeaders({ hubAccessToken }));
97
+ }
98
+ } catch (e) {
99
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
100
+ }
101
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
102
+
103
+ const data = envelopeData(res.raw);
104
+ if (data === undefined) return { ok: false, reason: 'bad-response' };
105
+ return { ok: true, report: importReportFrom(data) };
106
+ }
107
+
108
+ async function requestSetProfessionalDetails(
109
+ { currentEmploymentStatus, freelanceIntent, changeMotivators, freelanceType, accessToken, hubAccessToken } = {},
110
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
111
+ ) {
112
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
113
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
114
+
115
+ const body = {};
116
+ if (typeof currentEmploymentStatus === 'string' && currentEmploymentStatus) body.currentEmploymentStatus = currentEmploymentStatus;
117
+ if (typeof freelanceIntent === 'string' && freelanceIntent) body.freelanceIntent = freelanceIntent;
118
+ if (typeof changeMotivators === 'string' && changeMotivators) body.changeMotivators = changeMotivators;
119
+ // freelanceType derived from the work situation (the single place it is asked now).
120
+ if (typeof freelanceType === 'string' && freelanceType) body.freelanceType = freelanceType;
121
+ let res;
122
+ try {
123
+ res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'PATCH', null, hubHeaders({ hubAccessToken }));
124
+ } catch (e) {
125
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
126
+ }
127
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
128
+ if (envelopeData(res.raw) === undefined) return { ok: false, reason: 'bad-response' };
129
+ return { ok: true };
130
+ }
131
+
132
+ async function requestSetPricingRate(
133
+ { pricing, accessToken, hubAccessToken } = {},
134
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
135
+ ) {
136
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
137
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
138
+ if (!pricing || typeof pricing !== 'object') return { ok: false, reason: 'no-pricing' };
139
+
140
+ let res;
141
+ try {
142
+ res = await postJsonWithTimeout(endpoint, pricing, timeoutMs, 'PUT', null, hubHeaders({ hubAccessToken }));
143
+ } catch (e) {
144
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
145
+ }
146
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
147
+ if (envelopeData(res.raw) === undefined) return { ok: false, reason: 'bad-response' };
148
+ return { ok: true };
149
+ }
150
+
151
+ async function requestSetAvailability(
152
+ { availability, accessToken, hubAccessToken } = {},
153
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
154
+ ) {
155
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
156
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
157
+ if (!availability || typeof availability !== 'object') return { ok: false, reason: 'no-availability' };
158
+
159
+ let res;
160
+ try {
161
+ res = await postJsonWithTimeout(endpoint, availability, timeoutMs, 'PUT', null, hubHeaders({ hubAccessToken }));
162
+ } catch (e) {
163
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
164
+ }
165
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
166
+ if (envelopeData(res.raw) === undefined) return { ok: false, reason: 'bad-response' };
167
+ return { ok: true };
168
+ }
169
+
170
+ async function requestLanguagesCatalog(
171
+ { hubAccessToken } = {},
172
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
173
+ ) {
174
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
175
+
176
+ let res;
177
+ try {
178
+ res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'GET', null, hubHeaders({ hubAccessToken }));
179
+ } catch (e) {
180
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
181
+ }
182
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
183
+
184
+ const data = envelopeData(res.raw);
185
+ if (!Array.isArray(data)) return { ok: false, reason: 'bad-response' };
186
+ return { ok: true, languages: data };
187
+ }
188
+
189
+ async function requestSetLanguages(
190
+ { languages, accessToken, hubAccessToken } = {},
191
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
192
+ ) {
193
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
194
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
195
+ if (!Array.isArray(languages) || languages.length === 0) return { ok: false, reason: 'no-languages' };
196
+
197
+ let res;
198
+ try {
199
+ res = await postJsonWithTimeout(endpoint, { languages }, timeoutMs, 'PATCH', null, hubHeaders({ hubAccessToken }));
200
+ } catch (e) {
201
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
202
+ }
203
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
204
+ if (envelopeData(res.raw) === undefined) return { ok: false, reason: 'bad-response' };
205
+ return { ok: true };
206
+ }
207
+
208
+ async function requestSetTalentPhone(
209
+ { telephoneCode, telephoneNumber, accessToken, hubAccessToken } = {},
210
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
211
+ ) {
212
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
213
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
214
+
215
+ const body = { telephoneCode, telephoneNumber };
216
+ let res;
217
+ try {
218
+ res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'PATCH', null, hubHeaders({ hubAccessToken }));
219
+ } catch (e) {
220
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
221
+ }
222
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
223
+ if (envelopeData(res.raw) === undefined) return { ok: false, reason: 'bad-response' };
224
+ return { ok: true };
225
+ }
226
+
227
+ async function requestCompleteOnboarding(
228
+ { accessToken, hubAccessToken } = {},
229
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
230
+ ) {
231
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
232
+ if (!hubAccessToken) return { ok: false, reason: 'no-hub-token' };
233
+
234
+ let res;
235
+ try {
236
+ res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'POST', null, hubHeaders({ hubAccessToken }));
237
+ } catch (e) {
238
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
239
+ }
240
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
241
+
242
+ const data = envelopeData(res.raw);
243
+ if (data === undefined) return { ok: false, reason: 'bad-response' };
244
+ const d = data && typeof data === 'object' ? data : {};
245
+ return {
246
+ ok: true,
247
+ onboardingStatus: typeof d.onboardingStatus === 'string' ? d.onboardingStatus : null,
248
+ registrationLevel: typeof d.registrationLevel === 'string' ? d.registrationLevel : null,
249
+ completedProfilePercentage: typeof d.completedProfilePercentage === 'number' ? d.completedProfilePercentage : null,
250
+ };
251
+ }
252
+
253
+ // PATCH /interviews/onboarding/restart (no body) — overwrites the taken onboarding interview in-place so it can be re-run.
254
+ async function requestRestartOnboardingInterview(
255
+ { accessToken } = {},
256
+ { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
257
+ ) {
258
+ if (!endpoint) return { ok: false, reason: 'no-endpoint' };
259
+ let res;
260
+ try {
261
+ res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'PATCH', null, bearerHeaders({ accessToken }));
262
+ } catch (e) {
263
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
264
+ }
265
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw), status: res.status };
266
+
267
+ const data = envelopeData(res.raw);
268
+ const d = data && typeof data === 'object' ? data : {};
269
+ return { ok: true, interviewId: d.interviewId || null, language: d.language || null };
270
+ }
271
+
272
+ async function requestCreateOnboardingInterview(
273
+ { candidateId, accessToken } = {},
274
+ { base, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
275
+ ) {
276
+ if (!base) return { ok: false, reason: 'no-endpoint' };
277
+ if (!candidateId) return { ok: false, reason: 'no-candidate' };
278
+
279
+ const url = `${base.replace(/\/+$/, '')}/onboarding`;
280
+ let res;
281
+ try {
282
+ res = await postJsonWithTimeout(url, { candidateId }, timeoutMs, 'POST', null, bearerHeaders({ accessToken }));
283
+ } catch (e) {
284
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
285
+ }
286
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
287
+
288
+ const data = envelopeData(res.raw);
289
+ if (data === undefined || !data || typeof data !== 'object' || !data.interviewId) return { ok: false, reason: 'bad-response' };
290
+ return { ok: true, interviewId: data.interviewId, created: data.created === true, language: data.language || null };
291
+ }
292
+
293
+ async function requestStartTextSession(
294
+ { interviewId, language, accessToken } = {},
295
+ { base, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
296
+ ) {
297
+ if (!base) return { ok: false, reason: 'no-endpoint' };
298
+ if (!interviewId) return { ok: false, reason: 'no-interview' };
299
+
300
+ const url = `${base.replace(/\/+$/, '')}/${encodeURIComponent(interviewId)}/text-session`;
301
+ const body = { language: toUpperLang(language) };
302
+ let res;
303
+ try {
304
+ res = await postJsonWithTimeout(url, body, timeoutMs, 'POST', null, bearerHeaders({ accessToken }));
305
+ } catch (e) {
306
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
307
+ }
308
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
309
+
310
+ const data = envelopeData(res.raw);
311
+ if (data === undefined || !data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
312
+ return { ok: true, interviewId: data.interviewId || interviewId, greeting: typeof data.greeting === 'string' ? data.greeting : '' };
313
+ }
314
+
315
+ async function requestOnboardingTurn(
316
+ { interviewId, message, accessToken } = {},
317
+ { base, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
318
+ ) {
319
+ if (!base) return { ok: false, reason: 'no-endpoint' };
320
+ if (!interviewId) return { ok: false, reason: 'no-interview' };
321
+ if (typeof message !== 'string' || !message) return { ok: false, reason: 'no-message' };
322
+
323
+ const url = `${base.replace(/\/+$/, '')}/${encodeURIComponent(interviewId)}/text-session/turns`;
324
+ let res;
325
+ try {
326
+ res = await postJsonWithTimeout(url, { message }, timeoutMs, 'POST', null, bearerHeaders({ accessToken }));
327
+ } catch (e) {
328
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
329
+ }
330
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
331
+
332
+ const data = envelopeData(res.raw);
333
+ if (data === undefined || !data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
334
+ return { ok: true, response: typeof data.response === 'string' ? data.response : '', ended: data.ended === true };
335
+ }
336
+
337
+ async function requestCompleteTextSession(
338
+ { interviewId, accessToken } = {},
339
+ { base, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
340
+ ) {
341
+ if (!base) return { ok: false, reason: 'no-endpoint' };
342
+ if (!interviewId) return { ok: false, reason: 'no-interview' };
343
+
344
+ const url = `${base.replace(/\/+$/, '')}/${encodeURIComponent(interviewId)}/text-session/complete`;
345
+ let res;
346
+ try {
347
+ res = await postJsonWithTimeout(url, null, timeoutMs, 'POST', null, bearerHeaders({ accessToken }));
348
+ } catch (e) {
349
+ return { ok: false, reason: (e && e.kind) || 'network-error' };
350
+ }
351
+ if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
352
+
353
+ const data = envelopeData(res.raw);
354
+ if (data === undefined || !data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
355
+ return { ok: true, state: typeof data.state === 'string' ? data.state : null };
356
+ }
357
+
358
+ module.exports = {
359
+ requestImportProfile,
360
+ requestSetProfessionalDetails,
361
+ requestSetPricingRate,
362
+ requestSetAvailability,
363
+ requestLanguagesCatalog,
364
+ requestSetLanguages,
365
+ requestSetTalentPhone,
366
+ requestCompleteOnboarding,
367
+ requestRestartOnboardingInterview,
368
+ requestCreateOnboardingInterview,
369
+ requestStartTextSession,
370
+ requestOnboardingTurn,
371
+ requestCompleteTextSession,
372
+ buildImportSources,
373
+ reasonForError,
374
+ };