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
package/src/config.js ADDED
@@ -0,0 +1,778 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { readEnv } = require('./env-paths');
6
+ const { getConfigDir } = require('./config-dir');
7
+
8
+ // Persistent CLI config file (skill-code-certification, endpoint-config task).
9
+ function configDir(env = process.env) {
10
+ return getConfigDir(env);
11
+ }
12
+
13
+ function configFilePath(env = process.env) {
14
+ return path.join(configDir(env), 'config.json');
15
+ }
16
+
17
+ function loadConfigFile(env = process.env) {
18
+ try {
19
+ const parsed = JSON.parse(fs.readFileSync(configFilePath(env), 'utf8'));
20
+ return parsed && typeof parsed === 'object' ? parsed : {};
21
+ } catch {
22
+ return {};
23
+ }
24
+ }
25
+
26
+ // DISTRIBUTION PROFILE (talents-ai-score, ADR-058 fase 1).
27
+ const VALID_PROFILES = new Set(['external', 'talent']);
28
+ const PROFILE_DEFAULT_WHEN_ABSENT = 'talent';
29
+
30
+ function getProfile(env = process.env) {
31
+ const fromFile = loadConfigFile(env).profile;
32
+ return VALID_PROFILES.has(fromFile) ? fromFile : PROFILE_DEFAULT_WHEN_ABSENT;
33
+ }
34
+
35
+ function isTalentProfile(env = process.env) {
36
+ return getProfile(env) === 'talent';
37
+ }
38
+
39
+ // config.json holds the superadmin session token (ADR-027) and consent/ endpoint data, so it must never be briefly world/group-readable.
40
+ function saveConfigFile(config, env = process.env) {
41
+ const dir = configDir(env);
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ fs.writeFileSync(configFilePath(env), JSON.stringify(config, null, 2), { mode: 0o600 });
44
+ try { fs.chmodSync(configFilePath(env), 0o600); } catch { /* e.g. Windows */ }
45
+ }
46
+
47
+ // Endpoint safety (skill-code-certification, endpoint-config task) — BOUNDED.
48
+ const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '::1']);
49
+ const SHAKERS_CLI_DOMAINS = [];
50
+
51
+ function isAllowedShakersDomain(host) {
52
+ const h = String(host || '').toLowerCase();
53
+ return SHAKERS_CLI_DOMAINS.some((domain) => h === domain || h.endsWith(`.${domain}`));
54
+ }
55
+
56
+ function validateEndpoint(value) {
57
+ const raw = typeof value === 'string' ? value.trim() : '';
58
+ if (!raw) return { ok: false, reason: 'empty' };
59
+ let u;
60
+ try {
61
+ u = new URL(raw);
62
+ } catch {
63
+ return { ok: false, reason: 'invalid-url' };
64
+ }
65
+ if (u.protocol !== 'http:' && u.protocol !== 'https:') {
66
+ return { ok: false, reason: 'bad-protocol' };
67
+ }
68
+ const host = u.hostname.replace(/^\[|\]$/g, '');
69
+ const isLocal = LOCAL_HOSTS.has(host);
70
+ if (!isLocal && u.protocol !== 'https:') {
71
+ return { ok: false, reason: 'insecure-remote' };
72
+ }
73
+ const isAllowlisted = isLocal || isAllowedShakersDomain(host);
74
+ return { ok: true, value: raw, host, isLocal, isAllowlisted };
75
+ }
76
+
77
+ // Persists the ingest endpoint into config.json after validating it.
78
+ function setIngestEndpoint(value, env = process.env, { confirmed = false } = {}) {
79
+ const v = validateEndpoint(value);
80
+ if (!v.ok) return { ok: false, reason: v.reason };
81
+ if (!v.isAllowlisted && !confirmed) return { ok: false, reason: 'needs-confirmation', host: v.host };
82
+ const config = loadConfigFile(env);
83
+ config.ingestEndpoint = v.value;
84
+ saveConfigFile(config, env);
85
+ return { ok: true, value: v.value, path: configFilePath(env) };
86
+ }
87
+
88
+ const RETIRED_FALLBACK_CONFIG_KEY = 'ingestEndpointFallback';
89
+
90
+ // Whether the persisted config still carries the retired fallback key, so `--show-endpoint` can say it is being ignored.
91
+ function hasRetiredFallbackConfig(env = process.env) {
92
+ const value = loadConfigFile(env)[RETIRED_FALLBACK_CONFIG_KEY];
93
+ return typeof value === 'string' && value.trim().length > 0;
94
+ }
95
+
96
+ // Resolves the effective ingest endpoint AND its source, for `--show-endpoint`.
97
+ function resolveIngestEndpoint(env = process.env) {
98
+ const fromEnv = readEnv(env, 'INGEST_ENDPOINT');
99
+ if (fromEnv && fromEnv.trim()) {
100
+ return { endpoint: fromEnv.trim(), source: 'env' };
101
+ }
102
+ const fromFile = loadConfigFile(env).ingestEndpoint;
103
+ if (fromFile && String(fromFile).trim()) {
104
+ const v = validateEndpoint(String(fromFile).trim());
105
+ if (v.ok) return { endpoint: v.value, source: 'config-file', path: configFilePath(env) };
106
+ return { endpoint: null, source: 'config-file-invalid', path: configFilePath(env), reason: v.reason };
107
+ }
108
+ return { endpoint: null, source: 'none' };
109
+ }
110
+
111
+ // Baked backend targets per distribution flavor (talents-ai-score, ADR-007/065).
112
+ // Only the DEFAULT bases; per-endpoint SHAKERS_CLI_* env and config.json overrides
113
+ // still win at each getter (env > config.json > baked profile).
114
+ const ENV_PROFILES = {
115
+ staging: {
116
+ certsBase: 'https://api.staging.certifications.shakersworks.com/api/v1',
117
+ hubBase: 'https://api.hub.staging.shakersworks.com/api/v1',
118
+ profileUrl: 'https://works.staging.shakersworks.com/login',
119
+ },
120
+ dev: {
121
+ certsBase: 'https://api.dev.certifications.shakersworks.com/api/v1',
122
+ hubBase: 'https://api.hub.new-works.dev.shakersworks.com/api/v1',
123
+ profileUrl: 'https://works.new-works.dev.shakersworks.com/login',
124
+ },
125
+ local: {
126
+ certsBase: 'http://localhost:3004/api/v1',
127
+ hubBase: 'http://localhost:3001/api/v1',
128
+ profileUrl: 'http://localhost:8081/login',
129
+ },
130
+ };
131
+
132
+ // Which flavor is baked: SHAKERS_CLI_ENV override wins, else derived from the
133
+ // published package name (beta -> staging, private -> dev), else dev (safe default).
134
+ const PACKAGE_NAME_ENV = { 'shakers-cli-beta': 'staging', '@shakers/shakers-cli': 'dev' };
135
+ const DEFAULT_ENV_PROFILE = 'dev';
136
+
137
+ function bakedEnvProfileName() {
138
+ try {
139
+ const name = require('../package.json').name;
140
+ if (PACKAGE_NAME_ENV[name]) return PACKAGE_NAME_ENV[name];
141
+ } catch { /* package.json unreadable — fall through to the safe default */ }
142
+ return DEFAULT_ENV_PROFILE;
143
+ }
144
+
145
+ function getEnvProfileName(env = process.env) {
146
+ const override = readEnv(env, 'ENV');
147
+ if (override && ENV_PROFILES[String(override).trim()]) return String(override).trim();
148
+ return bakedEnvProfileName();
149
+ }
150
+
151
+ function getEnvProfile(env = process.env) {
152
+ return ENV_PROFILES[getEnvProfileName(env)] || ENV_PROFILES[DEFAULT_ENV_PROFILE];
153
+ }
154
+
155
+ function explicitIngest(env = process.env) {
156
+ const value = readEnv(env, 'INGEST_ENDPOINT');
157
+ if (value && value.trim()) return value.trim();
158
+ const fromFile = loadConfigFile(env).ingestEndpoint;
159
+ if (fromFile && String(fromFile).trim()) {
160
+ const v = validateEndpoint(String(fromFile).trim());
161
+ if (!v.ok) return null;
162
+ // The ai-footprint -> shakers config migration rewrote the old hub ingest
163
+ // (`/works/ai-footprint/reports`) to `/works/usage/reports`, a route no service
164
+ // has: ingestion lives in certs. Kept, it silently sent every report and the
165
+ // agent evaluation there and both failed ("agentes sin categoria", 2026-09-25).
166
+ // Ignored, the endpoint derives from certsBase like a fresh install. The same
167
+ // goes for the loopback default install.sh used to bake into every config.
168
+ if (LEGACY_HUB_INGEST.test(v.value) || v.value === RETIRED_INSTALL_INGEST) return null;
169
+ return v.value;
170
+ }
171
+ return null;
172
+ }
173
+
174
+ const LEGACY_HUB_INGEST = /\/works\/(usage|ai-footprint)\/reports\/?$/;
175
+ const RETIRED_INSTALL_INGEST = 'http://localhost:3004/api/v1/usage/reports';
176
+
177
+ function getCertsBase(env = process.env) {
178
+ const value = readEnv(env, 'CERTS_BASE');
179
+ if (value && value.trim()) return value.trim().replace(/\/+$/, '');
180
+ const fromFile = loadConfigFile(env).certsBase;
181
+ if (fromFile && String(fromFile).trim()) {
182
+ const v = validateEndpoint(String(fromFile).trim());
183
+ if (v.ok) return v.value.replace(/\/+$/, '');
184
+ }
185
+ const ingest = explicitIngest(env);
186
+ if (ingest) return ingest.replace(/\/usage\/reports\/?$/, '').replace(/\/+$/, '');
187
+ return null;
188
+ }
189
+
190
+ function getIngestEndpoint(env = process.env) {
191
+ const explicit = explicitIngest(env);
192
+ if (explicit) return explicit;
193
+ const base = getCertsBase(env);
194
+ return base ? `${base}/usage/reports` : null;
195
+ }
196
+
197
+ // Agent-synthesis endpoint (talents-ai-score, ADR-010/ADR-011).
198
+ function getSynthesisEndpoint(env = process.env) {
199
+ const value = readEnv(env, 'SYNTHESIS_ENDPOINT');
200
+ if (value && value.trim()) return value.trim();
201
+ // Issue 111: derived as an ingest sibling when the override is unset.
202
+ return deriveFromIngest(env, 'agent-synthesis');
203
+ }
204
+
205
+ function getRoadmapEndpoint(env = process.env) {
206
+ const value = readEnv(env, 'ROADMAP_ENDPOINT');
207
+ if (value && value.trim()) return value.trim();
208
+ return deriveFromIngest(env, 'roadmap-personalize');
209
+ }
210
+
211
+ // Email-verification endpoints (ADR-006): the OTP "prove you own this email" step that gates PERSISTENCE. Sibling-of-ingest resolution (`new URL(relative, base)`); unset ingest -> null.
212
+ function deriveFromIngest(env, relativePath) {
213
+ const ingest = getIngestEndpoint(env);
214
+ if (!ingest) return null;
215
+ const base = ingest.replace(/\/+$/, '');
216
+ try {
217
+ return new URL(relativePath, base).href;
218
+ } catch {
219
+ return null;
220
+ }
221
+ }
222
+
223
+ function deriveEmailVerificationUrl(env, segment) {
224
+ return deriveFromIngest(env, `email-verification/${segment}`);
225
+ }
226
+
227
+ // Agent-evaluation endpoint (footprint agent cards — RESTORED).
228
+ function getAgentEvaluationEndpoint(env = process.env) {
229
+ const explicit = readEnv(env, 'AGENT_EVAL_ENDPOINT');
230
+ if (explicit && explicit.trim()) return explicit.trim();
231
+ return deriveFromIngest(env, 'agent-evaluation');
232
+ }
233
+
234
+ // Login endpoint — better-auth email sign-in (owner decision, supersedes ADR-031 for the email path): `POST {hubBase}/auth/sign-in/email` with `{email,password}`.
235
+ function getLoginEndpoint(env = process.env) {
236
+ const explicit = readEnv(env, 'LOGIN_ENDPOINT');
237
+ if (explicit && explicit.trim()) return explicit.trim();
238
+ return deriveFromHub(env, 'auth/sign-in/email');
239
+ }
240
+
241
+ // better-auth JWT endpoint: `GET {hubBase}/auth/token` with the session cookie → `{token}` (ES256/JWKS, ~15-min TTL).
242
+ function getAuthTokenEndpoint(env = process.env) {
243
+ const explicit = readEnv(env, 'AUTH_TOKEN_ENDPOINT');
244
+ if (explicit && explicit.trim()) return explicit.trim();
245
+ return deriveFromHub(env, 'auth/token');
246
+ }
247
+
248
+ function getCompleteRegistrationEndpoint(env = process.env) {
249
+ const explicit = readEnv(env, 'COMPLETE_REGISTRATION_ENDPOINT');
250
+ if (explicit && explicit.trim()) return explicit.trim();
251
+ return deriveFromHub(env, 'works/auth/complete-registration');
252
+ }
253
+
254
+ // Google via device flow (RFC 8628): the Hub broker owns all Google config; the CLI holds no client_id.
255
+ function getDeviceAuthorizeEndpoint(env = process.env) {
256
+ const explicit = readEnv(env, 'DEVICE_AUTHORIZE_ENDPOINT');
257
+ if (explicit && explicit.trim()) return explicit.trim();
258
+ // better-auth device plugin, mounted at /auth.
259
+ return deriveFromHub(env, 'auth/device/code');
260
+ }
261
+
262
+ function getDeviceTokenEndpoint(env = process.env) {
263
+ const explicit = readEnv(env, 'DEVICE_TOKEN_ENDPOINT');
264
+ if (explicit && explicit.trim()) return explicit.trim();
265
+ // Same Nest prefix as complete-registration (the works auth controller).
266
+ return deriveFromHub(env, 'works/auth/device/token');
267
+ }
268
+
269
+ // Dimension-certification endpoints. The LiveKit start/complete + `/interviews/:id/report`
270
+ // poll reuse `getOnboardingInterviewsEndpoint` (= `{certsBase}/interviews`).
271
+ function getMyCertificationsEndpoint(env = process.env) {
272
+ const explicit = readEnv(env, 'MY_CERTIFICATIONS_ENDPOINT');
273
+ if (explicit && explicit.trim()) return explicit.trim();
274
+ return deriveFromHub(env, 'works/certifications/me');
275
+ }
276
+
277
+ // Role/cluster endpoints (hub, talent Bearer). available-roles = the catalog the
278
+ // talent can add; main-role = set the preferred main; assigned-clusters = the
279
+ // talent's own GROWTH/RECOMMENDED roles (GET add POST, remove DELETE).
280
+ function getAvailableRolesEndpoint(env = process.env) {
281
+ const explicit = readEnv(env, 'AVAILABLE_ROLES_ENDPOINT');
282
+ if (explicit && explicit.trim()) return explicit.trim();
283
+ return deriveFromHub(env, 'works/certifications/me/available-roles');
284
+ }
285
+
286
+ function getSetMainRoleEndpoint(env = process.env) {
287
+ const explicit = readEnv(env, 'SET_MAIN_ROLE_ENDPOINT');
288
+ if (explicit && explicit.trim()) return explicit.trim();
289
+ return deriveFromHub(env, 'works/certifications/me/main-role');
290
+ }
291
+
292
+ function getAssignedClustersEndpoint(env = process.env) {
293
+ const explicit = readEnv(env, 'ASSIGNED_CLUSTERS_ENDPOINT');
294
+ if (explicit && explicit.trim()) return explicit.trim();
295
+ return deriveFromHub(env, 'works/talents/me/work-details/assigned-clusters');
296
+ }
297
+
298
+ function getTemplatesByDimensionEndpoint(env = process.env) {
299
+ const explicit = readEnv(env, 'TEMPLATES_BY_DIMENSION_ENDPOINT');
300
+ if (explicit && explicit.trim()) return explicit.trim();
301
+ const base = getCertsBase(env);
302
+ return base ? `${base}/templates/by-dimension` : null;
303
+ }
304
+
305
+ function getCertificationInterviewsEndpoint(env = process.env) {
306
+ const explicit = readEnv(env, 'CERTIFICATION_INTERVIEWS_ENDPOINT');
307
+ if (explicit && explicit.trim()) return explicit.trim();
308
+ const base = getCertsBase(env);
309
+ return base ? `${base}/users/me/certification-interviews` : null;
310
+ }
311
+
312
+ // Find Positions list (hub `works/positions/find`, talent Bearer). Serves the
313
+ // All/Recommended/Saved tabs via `criteria.savedByMe`; ordered by match desc.
314
+ function getFindPositionsEndpoint(env = process.env) {
315
+ const explicit = readEnv(env, 'FIND_POSITIONS_ENDPOINT');
316
+ if (explicit && explicit.trim()) return explicit.trim();
317
+ return deriveFromHub(env, 'works/positions/find');
318
+ }
319
+
320
+ // Marketplace read/write (hub, talent Bearer). The id-taking getters return null
321
+ // on a non-scalar first arg so config.test.js's `fn(env)` sweep stays honest.
322
+ function getPositionDetailEndpoint(positionId, env = process.env) {
323
+ const explicit = readEnv(env, 'POSITION_DETAIL_ENDPOINT');
324
+ if (explicit && explicit.trim()) return explicit.trim();
325
+ if (positionId == null || typeof positionId === 'object') return null;
326
+ return deriveFromHub(env, `works/positions/${encodeURIComponent(positionId)}/detail`);
327
+ }
328
+
329
+ function getSavePositionEndpoint(env = process.env) {
330
+ const explicit = readEnv(env, 'SAVE_POSITION_ENDPOINT');
331
+ if (explicit && explicit.trim()) return explicit.trim();
332
+ return deriveFromHub(env, 'works/saved-positions');
333
+ }
334
+
335
+ function getUnsavePositionEndpoint(positionId, env = process.env) {
336
+ const explicit = readEnv(env, 'UNSAVE_POSITION_ENDPOINT');
337
+ if (explicit && explicit.trim()) return explicit.trim();
338
+ if (positionId == null || typeof positionId === 'object') return null;
339
+ return deriveFromHub(env, `works/saved-positions/${encodeURIComponent(positionId)}`);
340
+ }
341
+
342
+ function getReceivedInvitationsEndpoint(env = process.env) {
343
+ const explicit = readEnv(env, 'RECEIVED_INVITATIONS_ENDPOINT');
344
+ if (explicit && explicit.trim()) return explicit.trim();
345
+ return deriveFromHub(env, 'works/positions/received-invitations');
346
+ }
347
+
348
+ function getMyCandidaturesEndpoint(env = process.env) {
349
+ const explicit = readEnv(env, 'MY_CANDIDATURES_ENDPOINT');
350
+ if (explicit && explicit.trim()) return explicit.trim();
351
+ return deriveFromHub(env, 'works/candidatures/me');
352
+ }
353
+
354
+ function getMeAvailabilityEndpoint(env = process.env) {
355
+ const explicit = readEnv(env, 'ME_AVAILABILITY_ENDPOINT');
356
+ if (explicit && explicit.trim()) return explicit.trim();
357
+ return deriveFromHub(env, 'works/me/availability');
358
+ }
359
+
360
+ function getMeProfileEndpoint(env = process.env) {
361
+ const explicit = readEnv(env, 'ME_PROFILE_ENDPOINT');
362
+ if (explicit && explicit.trim()) return explicit.trim();
363
+ return deriveFromHub(env, 'works/me/profile');
364
+ }
365
+
366
+ function getTalentMeProfileEndpoint(env = process.env) {
367
+ const explicit = readEnv(env, 'TALENT_ME_PROFILE_ENDPOINT');
368
+ if (explicit && explicit.trim()) return explicit.trim();
369
+ return deriveFromHub(env, 'works/talents/me/profile');
370
+ }
371
+
372
+ // Alma (the AI assistant) is a SEPARATE service — NOT derived from hubBase.
373
+ // Default to the local/dev container; override with SHAKERS_CLI_ALMA_BASE.
374
+ const DEFAULT_ALMA_BASE = 'http://127.0.0.1:5030';
375
+
376
+ function getAlmaBase(env = process.env) {
377
+ const explicit = readEnv(env, 'ALMA_BASE');
378
+ if (explicit && explicit.trim()) return explicit.trim().replace(/\/+$/, '');
379
+ return DEFAULT_ALMA_BASE;
380
+ }
381
+
382
+ function getAlmaChatEndpoint(env = process.env) {
383
+ const explicit = readEnv(env, 'ALMA_CHAT_ENDPOINT');
384
+ if (explicit && explicit.trim()) return explicit.trim();
385
+ const base = getAlmaBase(env);
386
+ return base ? `${base}/alma/chat` : null;
387
+ }
388
+
389
+ function getAlmaDecisionEndpoint(env = process.env) {
390
+ const explicit = readEnv(env, 'ALMA_DECISION_ENDPOINT');
391
+ if (explicit && explicit.trim()) return explicit.trim();
392
+ const base = getAlmaBase(env);
393
+ return base ? `${base}/alma/decision` : null;
394
+ }
395
+
396
+ function getSkillsDeclareEndpoint(env = process.env) {
397
+ const explicit = readEnv(env, 'SKILLS_DECLARE_ENDPOINT');
398
+ if (explicit && explicit.trim()) return explicit.trim();
399
+ return deriveFromIngest(env, '../works/talents/me/skills/declare');
400
+ }
401
+
402
+ function getSkillsResolveAddableEndpoint(env = process.env) {
403
+ const explicit = readEnv(env, 'SKILLS_RESOLVE_ADDABLE_ENDPOINT');
404
+ if (explicit && explicit.trim()) return explicit.trim();
405
+ return deriveFromIngest(env, '../works/talents/me/skills/resolve-addable');
406
+ }
407
+
408
+ function getSkillsResolveMatchedEndpoint(env = process.env) {
409
+ const explicit = readEnv(env, 'SKILLS_RESOLVE_MATCHED_ENDPOINT');
410
+ if (explicit && explicit.trim()) return explicit.trim();
411
+ return deriveFromIngest(env, '../works/talents/me/skills/resolve-matched');
412
+ }
413
+
414
+ function getUsageDiscoveredInventoryEndpoint(env = process.env) {
415
+ const explicit = readEnv(env, 'USAGE_DISCOVERED_INVENTORY_ENDPOINT');
416
+ if (explicit && explicit.trim()) return explicit.trim();
417
+ return deriveFromIngest(env, 'discovered-inventory');
418
+ }
419
+
420
+ function getUsageReportEndpoint(env = process.env) {
421
+ const explicit = readEnv(env, 'USAGE_REPORT_ENDPOINT');
422
+ if (explicit && explicit.trim()) return explicit.trim();
423
+ return deriveFromIngest(env, 'report');
424
+ }
425
+
426
+ // Hub "My work with AI" read model (same endpoint the front paints).
427
+ function getAiProfileEndpoint(env = process.env) {
428
+ const explicit = readEnv(env, 'AI_PROFILE_ENDPOINT');
429
+ if (explicit && explicit.trim()) return explicit.trim();
430
+ return deriveFromHub(env, 'works/me/ai-profile');
431
+ }
432
+
433
+ // Evidence emission endpoint for the `cli_scan` instrument (talents-ai-score Phase 2).
434
+ function getEvidenceCliScanEndpoint(env = process.env) {
435
+ const explicit = readEnv(env, 'EVIDENCE_CLI_SCAN_ENDPOINT');
436
+ if (explicit && explicit.trim()) return explicit.trim();
437
+ return deriveFromIngest(env, '../evidence/cli-scan');
438
+ }
439
+
440
+ // Talent-portfolio endpoints (talents-ai-score, ADR-059 — `start`'s "Add project to portfolio" route, talent-only).
441
+ function getPortfoliosDeclareEndpoint(env = process.env) {
442
+ const explicit = readEnv(env, 'PORTFOLIOS_DECLARE_ENDPOINT');
443
+ if (explicit && explicit.trim()) return explicit.trim();
444
+ return deriveFromIngest(env, '../works/talents/me/portfolios/declare');
445
+ }
446
+
447
+ function getPortfoliosListEndpoint(env = process.env) {
448
+ const explicit = readEnv(env, 'PORTFOLIOS_LIST_ENDPOINT');
449
+ if (explicit && explicit.trim()) return explicit.trim();
450
+ return deriveFromIngest(env, '../works/talents/me/portfolios');
451
+ }
452
+
453
+ function getPortfolioDraftDescriptionEndpoint(env = process.env) {
454
+ const explicit = readEnv(env, 'PORTFOLIO_DRAFT_DESCRIPTION_ENDPOINT');
455
+ if (explicit && explicit.trim()) return explicit.trim();
456
+ return deriveFromIngest(env, '../works/talents/me/portfolios/draft-description');
457
+ }
458
+
459
+ function getPortfolioUpdateEndpoint(env = process.env) {
460
+ const explicit = readEnv(env, 'PORTFOLIO_UPDATE_ENDPOINT');
461
+ if (explicit && explicit.trim()) return explicit.trim();
462
+ return deriveFromHub(env, 'works/portfolios');
463
+ }
464
+
465
+ function getAgentsDeclareEndpoint(env = process.env) {
466
+ const explicit = readEnv(env, 'AGENTS_DECLARE_ENDPOINT');
467
+ if (explicit && explicit.trim()) return explicit.trim();
468
+ return deriveFromIngest(env, '../works/talents/me/agents/declare');
469
+ }
470
+
471
+ function getAgentsListEndpoint(env = process.env) {
472
+ const explicit = readEnv(env, 'AGENTS_LIST_ENDPOINT');
473
+ if (explicit && explicit.trim()) return explicit.trim();
474
+ return deriveFromIngest(env, '../works/talents/me/agents');
475
+ }
476
+
477
+ function getAgentsDraftFieldsEndpoint(env = process.env) {
478
+ const explicit = readEnv(env, 'AGENTS_DRAFT_FIELDS_ENDPOINT');
479
+ if (explicit && explicit.trim()) return explicit.trim();
480
+ return deriveFromIngest(env, '../works/talents/me/agents/draft-fields');
481
+ }
482
+
483
+ // Agent<->experience relate endpoint (ADR-041, `start`'s "Add agent" relate step / MCP `add_agent.relatedPortfolios`).
484
+ function getAgentPortfoliosEndpoint(agentId, env = process.env) {
485
+ // Null on missing/non-scalar id: no bogus URL, and keeps config.test.js's `fn(env)`
486
+ // getter sweep honest when the env object lands in `agentId`.
487
+ if (agentId == null || typeof agentId === 'object') return null;
488
+ return deriveFromHub(env, `works/me/agents/${encodeURIComponent(agentId)}/portfolios`);
489
+ }
490
+
491
+ // Onboarding endpoints (evaluation-mcp, ADR-011..015/024 — the register flow).
492
+ function getHubBase(env = process.env) {
493
+ const explicit = readEnv(env, 'HUB_BASE');
494
+ if (explicit && explicit.trim()) return explicit.trim().replace(/\/+$/, '');
495
+ const fromFile = loadConfigFile(env).hubBase;
496
+ if (fromFile && String(fromFile).trim()) {
497
+ const v = validateEndpoint(String(fromFile).trim());
498
+ return v.ok ? v.value.replace(/\/+$/, '') : null;
499
+ }
500
+ return null;
501
+ }
502
+
503
+ function deriveFromHub(env, relativePath) {
504
+ const base = getHubBase(env);
505
+ if (!base) return null;
506
+ return `${base}/${relativePath}`;
507
+ }
508
+
509
+ function getSignUpEndpoint(env = process.env) {
510
+ const explicit = readEnv(env, 'SIGNUP_ENDPOINT');
511
+ if (explicit && explicit.trim()) return explicit.trim();
512
+ return deriveFromHub(env, 'auth/sign-up/email');
513
+ }
514
+
515
+ function getImportProfileEndpoint(env = process.env) {
516
+ const explicit = readEnv(env, 'IMPORT_PROFILE_ENDPOINT');
517
+ if (explicit && explicit.trim()) return explicit.trim();
518
+ return deriveFromHub(env, 'works/me/import-profile');
519
+ }
520
+
521
+ function getProfessionalDetailsEndpoint(env = process.env) {
522
+ const explicit = readEnv(env, 'PROFESSIONAL_DETAILS_ENDPOINT');
523
+ if (explicit && explicit.trim()) return explicit.trim();
524
+ return deriveFromHub(env, 'works/me/professional-details');
525
+ }
526
+
527
+ function getPricingRateEndpoint(env = process.env) {
528
+ const explicit = readEnv(env, 'PRICING_RATE_ENDPOINT');
529
+ if (explicit && explicit.trim()) return explicit.trim();
530
+ return deriveFromHub(env, 'works/talents/me/work-details/pricing-rate');
531
+ }
532
+
533
+ function getSetAvailabilityEndpoint(env = process.env) {
534
+ const explicit = readEnv(env, 'AVAILABILITY_ENDPOINT');
535
+ if (explicit && explicit.trim()) return explicit.trim();
536
+ return deriveFromHub(env, 'works/talents/me/work-details/availability');
537
+ }
538
+
539
+ function getLanguagesEndpoint(env = process.env) {
540
+ const explicit = readEnv(env, 'LANGUAGES_ENDPOINT');
541
+ if (explicit && explicit.trim()) return explicit.trim();
542
+ return deriveFromHub(env, 'works/talents/me/work-details/languages');
543
+ }
544
+
545
+ function getLanguagesCatalogEndpoint(env = process.env) {
546
+ const explicit = readEnv(env, 'LANGUAGES_CATALOG_ENDPOINT');
547
+ if (explicit && explicit.trim()) return explicit.trim();
548
+ return deriveFromHub(env, 'static-data/languages');
549
+ }
550
+
551
+ // Talent social links (GET + PUT upsert of the whole object).
552
+ function getMeSocialEndpoint(env = process.env) {
553
+ const explicit = readEnv(env, 'ME_SOCIAL_ENDPOINT');
554
+ if (explicit && explicit.trim()) return explicit.trim();
555
+ return deriveFromHub(env, 'works/me/social');
556
+ }
557
+
558
+ // Talent portfolio entries (GET; ?type=EXPERIENCE|PORTFOLIO — one entity, two subsets).
559
+ function getMePortfoliosEndpoint(env = process.env) {
560
+ const explicit = readEnv(env, 'ME_PORTFOLIOS_ENDPOINT');
561
+ if (explicit && explicit.trim()) return explicit.trim();
562
+ return deriveFromHub(env, 'works/me/portfolios');
563
+ }
564
+
565
+ // i18n translations (GET ?namespace=static-data): resolves static-data KEYS
566
+ // (e.g. staticDataSkillsSkill_N) to readable names per language. Bearer required.
567
+ function getTranslationsEndpoint(env = process.env) {
568
+ const explicit = readEnv(env, 'TRANSLATIONS_ENDPOINT');
569
+ if (explicit && explicit.trim()) return explicit.trim();
570
+ return deriveFromHub(env, 'translations');
571
+ }
572
+
573
+ function getTalentMeEndpoint(env = process.env) {
574
+ const explicit = readEnv(env, 'TALENT_ME_ENDPOINT');
575
+ if (explicit && explicit.trim()) return explicit.trim();
576
+ return deriveFromHub(env, 'works/me');
577
+ }
578
+
579
+ function getOnboardingInterviewsEndpoint(env = process.env) {
580
+ const explicit = readEnv(env, 'ONBOARDING_INTERVIEWS_ENDPOINT');
581
+ if (explicit && explicit.trim()) return explicit.trim();
582
+ const base = getCertsBase(env);
583
+ return base ? `${base}/interviews` : null;
584
+ }
585
+
586
+ // Overwrite-repeat of the onboarding interview (certs, talent Bearer) — resets the
587
+ // taken interview in-place so it can be re-run.
588
+ function getOnboardingRestartEndpoint(env = process.env) {
589
+ const explicit = readEnv(env, 'ONBOARDING_RESTART_ENDPOINT');
590
+ if (explicit && explicit.trim()) return explicit.trim();
591
+ const base = getCertsBase(env);
592
+ return base ? `${base}/interviews/onboarding/restart` : null;
593
+ }
594
+
595
+ function getCompleteOnboardingEndpoint(env = process.env) {
596
+ const explicit = readEnv(env, 'COMPLETE_ONBOARDING_ENDPOINT');
597
+ if (explicit && explicit.trim()) return explicit.trim();
598
+ return deriveFromHub(env, 'works/talents/me/complete-onboarding');
599
+ }
600
+
601
+ function getOnboardingStatusEndpoint(env = process.env) {
602
+ const explicit = readEnv(env, 'ONBOARDING_STATUS_ENDPOINT');
603
+ if (explicit && explicit.trim()) return explicit.trim();
604
+ const base = getCertsBase(env);
605
+ return base ? `${base}/interviews/onboarding/status` : null;
606
+ }
607
+
608
+ // Talent profile web URL (evaluation-mcp, ADR-011 — the step-5 skip fallback).
609
+ function getTalentProfileUrl(env = process.env) {
610
+ const explicit = readEnv(env, 'PROFILE_URL');
611
+ if (explicit && explicit.trim()) return explicit.trim().replace(/\/+$/, '');
612
+ const fromFile = loadConfigFile(env).profileUrl;
613
+ if (fromFile && String(fromFile).trim()) return String(fromFile).trim().replace(/\/+$/, '');
614
+ return null;
615
+ }
616
+
617
+ // First-run local bootstrap: seed config.json with the baked flavor bases (ADR-065)
618
+ // so the talent CLI/MCP works with zero exports; the flavor is resolved per-call
619
+ // (SHAKERS_CLI_ENV > package name > dev) so env overrides still take precedence.
620
+ function ensureLocalConfigDefaults(env = process.env, { force = false } = {}) {
621
+ if (!force && !isTalentProfile(env)) return false;
622
+ const defaults = getEnvProfile(env);
623
+ const config = loadConfigFile(env);
624
+ let changed = false;
625
+ for (const [key, value] of Object.entries(defaults)) {
626
+ if (!(config[key] && String(config[key]).trim())) {
627
+ config[key] = value;
628
+ changed = true;
629
+ }
630
+ }
631
+ if (changed) {
632
+ try { saveConfigFile(config, env); } catch { return false; }
633
+ }
634
+ return changed;
635
+ }
636
+
637
+ function getEmailVerificationRequestUrl(env = process.env) {
638
+ return deriveEmailVerificationUrl(env, 'request');
639
+ }
640
+
641
+ function getEmailVerificationVerifyUrl(env = process.env) {
642
+ return deriveEmailVerificationUrl(env, 'verify');
643
+ }
644
+
645
+ function getSuperadminSessionEndpoint(env = process.env) {
646
+ return deriveFromIngest(env, 'superadmin/session');
647
+ }
648
+
649
+ // Superadmin READ-ONLY certification inspection endpoint (ADR-025, NON-PROD only) — a sibling of the ingest endpoint.
650
+ function getInspectCertificationsEndpoint(env = process.env) {
651
+ return deriveFromIngest(env, 'superadmin/inspect-certifications');
652
+ }
653
+
654
+ // ADR-027 superadmin SESSION persistence.
655
+ function loadSuperadminSession(env = process.env) {
656
+ const config = loadConfigFile(env);
657
+ const s = config && typeof config === 'object' ? config.superadminSession : null;
658
+ if (!s || typeof s !== 'object' || typeof s.token !== 'string' || !s.token) return null;
659
+ if (s.expiresAt && Date.parse(s.expiresAt) <= Date.now()) return null; // expired
660
+ return { email: s.email || null, token: s.token, expiresAt: s.expiresAt || null };
661
+ }
662
+
663
+ function saveSuperadminSession(session, env = process.env) {
664
+ const config = loadConfigFile(env);
665
+ config.superadminSession = {
666
+ email: session.email || null,
667
+ token: session.token,
668
+ expiresAt: session.expiresAt || null,
669
+ };
670
+ saveConfigFile(config, env);
671
+ }
672
+
673
+ function clearSuperadminSession(env = process.env) {
674
+ const config = loadConfigFile(env);
675
+ if (config && typeof config === 'object' && 'superadminSession' in config) {
676
+ delete config.superadminSession;
677
+ saveConfigFile(config, env);
678
+ }
679
+ }
680
+
681
+ module.exports = {
682
+ getIngestEndpoint,
683
+ getSynthesisEndpoint,
684
+ getRoadmapEndpoint,
685
+ getAgentEvaluationEndpoint,
686
+ getLoginEndpoint,
687
+ getAuthTokenEndpoint,
688
+ getCompleteRegistrationEndpoint,
689
+ getDeviceAuthorizeEndpoint,
690
+ getDeviceTokenEndpoint,
691
+ // Certification-by-dimension endpoints (`certify` = dimension LiveKit interview).
692
+ getMyCertificationsEndpoint,
693
+ // Role/cluster management (`add-role` / `change-role` / onboarding main-role step).
694
+ getAvailableRolesEndpoint,
695
+ getSetMainRoleEndpoint,
696
+ getAssignedClustersEndpoint,
697
+ getTemplatesByDimensionEndpoint,
698
+ getCertificationInterviewsEndpoint,
699
+ // Find Positions list (`find-projects` command / `find_projects` MCP tool).
700
+ getFindPositionsEndpoint,
701
+ // Marketplace (show-project / save-project / unsave-project / invitations).
702
+ getPositionDetailEndpoint,
703
+ getSavePositionEndpoint,
704
+ getUnsavePositionEndpoint,
705
+ getReceivedInvitationsEndpoint,
706
+ getMyCandidaturesEndpoint,
707
+ getMeAvailabilityEndpoint,
708
+ getMeProfileEndpoint,
709
+ getTalentMeProfileEndpoint,
710
+ // Alma assistant (`ask` command / `ask` MCP tool) — separate service.
711
+ getAlmaBase,
712
+ getAlmaChatEndpoint,
713
+ getAlmaDecisionEndpoint,
714
+ getSkillsDeclareEndpoint,
715
+ getSkillsResolveAddableEndpoint,
716
+ getSkillsResolveMatchedEndpoint,
717
+ getUsageDiscoveredInventoryEndpoint,
718
+ getUsageReportEndpoint,
719
+ getAiProfileEndpoint,
720
+ // Evidence emission (talents-ai-score Phase 2, evidence-based evaluation).
721
+ getEvidenceCliScanEndpoint,
722
+ // Talent-portfolio endpoints (ADR-059).
723
+ getPortfoliosDeclareEndpoint,
724
+ getPortfoliosListEndpoint,
725
+ getPortfolioDraftDescriptionEndpoint,
726
+ getPortfolioUpdateEndpoint,
727
+ // Talent-agent endpoints (talents-ai-score Phase 2 — "Add agent to profile").
728
+ getAgentsDeclareEndpoint,
729
+ getAgentsListEndpoint,
730
+ getAgentsDraftFieldsEndpoint,
731
+ getAgentPortfoliosEndpoint,
732
+ // Onboarding / register flow (evaluation-mcp, ADR-011..015/024).
733
+ getCertsBase,
734
+ getHubBase,
735
+ ensureLocalConfigDefaults,
736
+ getSignUpEndpoint,
737
+ getImportProfileEndpoint,
738
+ getProfessionalDetailsEndpoint,
739
+ getPricingRateEndpoint,
740
+ getSetAvailabilityEndpoint,
741
+ getLanguagesEndpoint,
742
+ getLanguagesCatalogEndpoint,
743
+ getMeSocialEndpoint,
744
+ getMePortfoliosEndpoint,
745
+ getTranslationsEndpoint,
746
+ getTalentMeEndpoint,
747
+ getOnboardingInterviewsEndpoint,
748
+ getCompleteOnboardingEndpoint,
749
+ getOnboardingRestartEndpoint,
750
+ getOnboardingStatusEndpoint,
751
+ getTalentProfileUrl,
752
+ getEmailVerificationRequestUrl,
753
+ getEmailVerificationVerifyUrl,
754
+ getSuperadminSessionEndpoint,
755
+ getInspectCertificationsEndpoint,
756
+ // ADR-027 superadmin session persistence.
757
+ loadSuperadminSession,
758
+ saveSuperadminSession,
759
+ clearSuperadminSession,
760
+ // Persistent config file + endpoint safety (endpoint-config task).
761
+ configFilePath,
762
+ loadConfigFile,
763
+ saveConfigFile,
764
+ validateEndpoint,
765
+ isAllowedShakersDomain,
766
+ SHAKERS_CLI_DOMAINS,
767
+ setIngestEndpoint,
768
+ hasRetiredFallbackConfig,
769
+ resolveIngestEndpoint,
770
+ // Distribution profile (ADR-058 fase 1).
771
+ VALID_PROFILES,
772
+ getProfile,
773
+ isTalentProfile,
774
+ // Baked backend target flavor (staging | dev | local).
775
+ ENV_PROFILES,
776
+ getEnvProfileName,
777
+ getEnvProfile,
778
+ };