shakers-cli-beta 0.10.4 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/certify.js +1 -1
- package/bin/login.js +44 -111
- package/bin/shakers.js +25 -6
- package/bin/update-check-worker.js +70 -0
- package/bin/update.js +111 -0
- package/package.json +1 -1
- package/src/agents-client.js +1 -1
- package/src/ai-profile-client.js +1 -1
- package/src/applications-client.js +1 -1
- package/src/availability-client.js +1 -1
- package/src/certifications-client.js +1 -1
- package/src/certify-dimension-client.js +0 -79
- package/src/certify-dimension-flow.js +24 -105
- package/src/command-audit.js +105 -0
- package/src/config.js +31 -22
- package/src/find-projects-client.js +1 -1
- package/src/i18n.js +92 -78
- package/src/lang-client.js +1 -1
- package/src/mcp-device-login-tools.js +3 -3
- package/src/mcp-install.js +2 -1
- package/src/mcp-instructions.js +2 -2
- package/src/mcp-onboarding-tools.js +10 -16
- package/src/mcp-role-tools.js +13 -5
- package/src/mcp-signup-client.js +1 -1
- package/src/mcp-signup-tools.js +76 -18
- package/src/profile-client.js +1 -4
- package/src/rate-client.js +1 -2
- package/src/roles-client.js +0 -4
- package/src/saved-positions-client.js +1 -1
- package/src/show-project-client.js +1 -1
- package/src/signup-copy.js +34 -12
- package/src/update-flow.js +223 -0
- package/src/update-notifier.js +113 -0
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
const { postJsonWithTimeout } = require('./backend-request');
|
|
6
6
|
const { reasonForError } = require('./onboarding-client');
|
|
7
|
-
const { toUpperLang } = require('./lang-codes');
|
|
8
7
|
|
|
9
8
|
const DEFAULT_TIMEOUT_MS = 20000;
|
|
10
9
|
|
|
@@ -154,87 +153,9 @@ async function discoverOfferableDimensions(deps = {}, { accessToken, hubAccessTo
|
|
|
154
153
|
return { ok: true, mainRole: mine.mainRole, offerable, unmatched };
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
async function requestCreateCertificationInterview(
|
|
158
|
-
{ dimensionKey, languageCode, accessToken } = {},
|
|
159
|
-
{ endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
|
160
|
-
) {
|
|
161
|
-
if (!endpoint) return { ok: false, reason: 'no-endpoint' };
|
|
162
|
-
if (!dimensionKey) return { ok: false, reason: 'no-dimension' };
|
|
163
|
-
const body = { dimensionKey };
|
|
164
|
-
if (languageCode) body.languageCode = toUpperLang(languageCode);
|
|
165
|
-
let res;
|
|
166
|
-
try {
|
|
167
|
-
res = await postJsonWithTimeout(endpoint, body, timeoutMs, 'POST', null, bearerHeaders(accessToken));
|
|
168
|
-
} catch (e) {
|
|
169
|
-
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
170
|
-
}
|
|
171
|
-
if (res.status < 200 || res.status >= 300) {
|
|
172
|
-
const reason = reasonForError(res.status, res.raw);
|
|
173
|
-
if (reason !== 'dimension-on-cooldown') return { ok: false, reason };
|
|
174
|
-
// certs puts the date in the message only: "... available on 2026-10-28T14:50:38.265Z".
|
|
175
|
-
const available = String(res.raw || '').match(/available on (\d{4}-\d{2}-\d{2}T[\d:.]+Z)/);
|
|
176
|
-
return { ok: false, reason, availableOn: available ? available[1] : null };
|
|
177
|
-
}
|
|
178
|
-
const data = payloadOf(res.raw);
|
|
179
|
-
const interviewId = pick(data || {}, ['interviewId', 'id']);
|
|
180
|
-
if (!interviewId) return { ok: false, reason: 'bad-response' };
|
|
181
|
-
return { ok: true, interviewId, language: pick(data || {}, ['language', 'languageCode']) || null };
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Poll the verdict; a 2xx/404/425/202 with no band yet resolves `ready:false` (retriable).
|
|
185
|
-
async function requestCertificationReport(
|
|
186
|
-
{ interviewId, accessToken } = {},
|
|
187
|
-
{ base, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
|
188
|
-
) {
|
|
189
|
-
if (!base) return { ok: false, reason: 'no-endpoint' };
|
|
190
|
-
if (!interviewId) return { ok: false, reason: 'no-interview' };
|
|
191
|
-
const url = `${base.replace(/\/+$/, '')}/${encodeURIComponent(interviewId)}/report`;
|
|
192
|
-
let res;
|
|
193
|
-
try {
|
|
194
|
-
res = await postJsonWithTimeout(url, null, timeoutMs, 'GET', null, bearerHeaders(accessToken));
|
|
195
|
-
} catch (e) {
|
|
196
|
-
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
197
|
-
}
|
|
198
|
-
if (res.status === 404 || res.status === 425 || res.status === 202) return { ok: true, ready: false, report: null };
|
|
199
|
-
if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
|
|
200
|
-
const data = payloadOf(res.raw);
|
|
201
|
-
if (data === undefined || data === null || typeof data !== 'object') return { ok: true, ready: false, report: null };
|
|
202
|
-
const band = pick(data, ['band', 'level', 'combinedLevel']);
|
|
203
|
-
// A report with too little evidence for a level has band null; it is still finished.
|
|
204
|
-
if (!band && !pick(data, ['evaluationId'])) return { ok: true, ready: false, report: null };
|
|
205
|
-
return { ok: true, ready: true, report: data };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// The case the web preview shows before starting (works `get-dimension-case`): the exercise statement, else the
|
|
209
|
-
// version's brief. `case: null` for a template with neither.
|
|
210
|
-
async function requestDimensionCase(
|
|
211
|
-
{ dimensionKey, accessToken } = {},
|
|
212
|
-
{ base, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
|
213
|
-
) {
|
|
214
|
-
if (!base) return { ok: false, reason: 'no-endpoint' };
|
|
215
|
-
if (!dimensionKey) return { ok: false, reason: 'no-dimension' };
|
|
216
|
-
const path = String(dimensionKey).split('/').map(encodeURIComponent).join('/');
|
|
217
|
-
const url = `${base.replace(/\/+$/, '')}/dimension/${path}/case`;
|
|
218
|
-
let res;
|
|
219
|
-
try {
|
|
220
|
-
res = await postJsonWithTimeout(url, null, timeoutMs, 'GET', null, bearerHeaders(accessToken));
|
|
221
|
-
} catch (e) {
|
|
222
|
-
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
223
|
-
}
|
|
224
|
-
if (res.status < 200 || res.status >= 300) return { ok: false, reason: reasonForError(res.status, res.raw) };
|
|
225
|
-
const data = payloadOf(res.raw);
|
|
226
|
-
if (!data || typeof data !== 'object') return { ok: false, reason: 'bad-response' };
|
|
227
|
-
const text = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
|
|
228
|
-
const brief = text(data.statement) || text(data.candidateBrief);
|
|
229
|
-
return { ok: true, case: brief ? { brief, testType: text(data.testType) } : null };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
156
|
module.exports = {
|
|
233
157
|
requestMyCertifications,
|
|
234
158
|
requestTemplatesByDimension,
|
|
235
|
-
requestCreateCertificationInterview,
|
|
236
|
-
requestCertificationReport,
|
|
237
|
-
requestDimensionCase,
|
|
238
159
|
composeDimensionKey,
|
|
239
160
|
composeOfferableDimensions,
|
|
240
161
|
discoverOfferableDimensions,
|
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// `certify` dimension flow: discover -> pick ->
|
|
3
|
+
// `certify` dimension flow: discover -> pick -> hand over the web deep link. The interview runs on the web.
|
|
4
4
|
|
|
5
5
|
const { runInteractiveMultiSelect } = require('./interactive-select');
|
|
6
6
|
|
|
7
7
|
function makeCertifyDimensionDeps(overrides = {}) {
|
|
8
8
|
const config = require('./config');
|
|
9
9
|
const client = require('./certify-dimension-client');
|
|
10
|
-
const onboardingFlow = require('./onboarding-flow');
|
|
11
10
|
return {
|
|
12
|
-
|
|
13
|
-
// `{certsBase}/interviews` — shared base for LiveKit start/complete + the report poll.
|
|
14
|
-
getInterviewsBase: config.getOnboardingInterviewsEndpoint,
|
|
11
|
+
getTalentProfileUrl: config.getTalentProfileUrl,
|
|
15
12
|
discoverOfferableDimensions: (opts) => client.discoverOfferableDimensions({}, opts),
|
|
16
|
-
|
|
17
|
-
requestCertificationReport: client.requestCertificationReport,
|
|
18
|
-
requestDimensionCase: client.requestDimensionCase,
|
|
19
|
-
conductLivekitInterview: onboardingFlow.conductLivekitInterview,
|
|
20
|
-
onboardingDeps: onboardingFlow.makeDeps(),
|
|
21
|
-
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
13
|
+
buildWebLink,
|
|
22
14
|
...overrides,
|
|
23
15
|
};
|
|
24
16
|
}
|
|
25
17
|
|
|
18
|
+
// The web route validates the decoded `dimension` param against `^[^/]+\/[^/]+$`,
|
|
19
|
+
// so the separator slash stays literal and each segment is url-encoded.
|
|
20
|
+
function buildWebLink(profileUrl, { clusterId, slug } = {}) {
|
|
21
|
+
if (!profileUrl || !clusterId || !slug) return null;
|
|
22
|
+
let origin;
|
|
23
|
+
try {
|
|
24
|
+
origin = new URL(profileUrl).origin;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return `${origin}/certifications?dimension=${encodeURIComponent(clusterId)}/${encodeURIComponent(slug)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
// Numbered fallback when `ask` can't release stdin for raw mode.
|
|
27
32
|
async function askNumberedChoice(ask, promptText, count) {
|
|
28
33
|
const raw = (await ask(promptText)).trim();
|
|
@@ -62,53 +67,6 @@ function resolveDimensionArg(arg, items) {
|
|
|
62
67
|
return items.find((d) => d.dimensionKey === raw || d.slug === raw) || undefined;
|
|
63
68
|
}
|
|
64
69
|
|
|
65
|
-
// The evaluation lands one to two minutes after the interview ends, four when the queue is busy.
|
|
66
|
-
async function pollReport(deps, { interviewId, accessToken }, { base, attempts = 60, intervalMs = 5000 } = {}) {
|
|
67
|
-
for (let i = 0; i < attempts; i++) {
|
|
68
|
-
const res = await deps.requestCertificationReport({ interviewId, accessToken }, { base });
|
|
69
|
-
if (!res.ok) return { ok: false, reason: res.reason };
|
|
70
|
-
if (res.ready) return { ok: true, report: res.report };
|
|
71
|
-
if (i < attempts - 1) await deps.sleep(intervalMs);
|
|
72
|
-
}
|
|
73
|
-
return { ok: false, reason: 'timeout' };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function formatDay(iso, lang) {
|
|
77
|
-
const d = iso ? new Date(iso) : null;
|
|
78
|
-
if (!d || Number.isNaN(d.getTime())) return null;
|
|
79
|
-
return d.toLocaleDateString(lang === 'en' ? 'en-GB' : 'es-ES', { day: 'numeric', month: 'long', year: 'numeric' });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function renderVerdict(report, cd) {
|
|
83
|
-
const lines = [];
|
|
84
|
-
const band = report.band || report.level || report.combinedLevel;
|
|
85
|
-
const dimension = report.dimensionName || report.dimension || report.dimensionKey;
|
|
86
|
-
if (dimension) lines.push(cd.verdictDimension(dimension));
|
|
87
|
-
if (band) lines.push(cd.verdictBand(band));
|
|
88
|
-
if (typeof report.certified === 'boolean') lines.push(report.certified ? cd.verdictCertified : cd.verdictNotCertified);
|
|
89
|
-
const summary = report.summary || report.rationale || report.feedback;
|
|
90
|
-
if (typeof summary === 'string' && summary.trim()) lines.push(summary.trim());
|
|
91
|
-
const areas = Array.isArray(report.areas) ? report.areas : null;
|
|
92
|
-
if (areas && areas.length) {
|
|
93
|
-
for (const a of areas) {
|
|
94
|
-
const name = a && (a.name || a.area || a.tag);
|
|
95
|
-
const note = a && (a.note || a.comment);
|
|
96
|
-
if (name) lines.push(`- ${name}${note ? `: ${note}` : ''}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return lines.join('\n ');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// The statement is Markdown; the terminal shows it without the emphasis markers.
|
|
103
|
-
function renderCase(dimensionCase, cd) {
|
|
104
|
-
const brief = dimensionCase.brief.replace(/(\*\*|__)(.+?)\1/g, '$2');
|
|
105
|
-
const expectations = cd.caseExpectations[dimensionCase.testType];
|
|
106
|
-
const lines = brief.split('\n');
|
|
107
|
-
if (expectations) lines.push('', cd.caseExpectationsTitle, ...Object.values(expectations).map((e) => `- ${e}`));
|
|
108
|
-
lines.push('', cd.caseBeforeStart);
|
|
109
|
-
return lines.join('\n ');
|
|
110
|
-
}
|
|
111
|
-
|
|
112
70
|
async function runCertifyDimension({ io, ask, stdinIsTTY, session, lang, catalog, opts = {}, deps = makeCertifyDimensionDeps() }) {
|
|
113
71
|
const cd = catalog.certifyDimension;
|
|
114
72
|
const accessToken = session ? session.accessToken : null;
|
|
@@ -157,60 +115,21 @@ async function runCertifyDimension({ io, ask, stdinIsTTY, session, lang, catalog
|
|
|
157
115
|
}
|
|
158
116
|
}
|
|
159
117
|
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
{ endpoint: createEndpoint },
|
|
165
|
-
));
|
|
166
|
-
if (!created.ok) {
|
|
167
|
-
io.error(created.reason === 'dimension-on-cooldown'
|
|
168
|
-
? cd.onCooldown(formatDay(created.availableOn, lang))
|
|
169
|
-
: cd.createFailed(created.reason));
|
|
170
|
-
return { ok: false, reason: created.reason, step: 'create' };
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// A failed read leaves the run as it was before the case existed, like the web preview does.
|
|
174
|
-
const caseRead = await deps.requestDimensionCase(
|
|
175
|
-
{ dimensionKey: chosen.dimensionKey, accessToken },
|
|
176
|
-
{ base: deps.getInterviewsBase() },
|
|
177
|
-
);
|
|
178
|
-
if (caseRead.ok && caseRead.case) {
|
|
179
|
-
io.section(cd.caseTitle);
|
|
180
|
-
io.notify(renderCase(caseRead.case, cd));
|
|
181
|
-
if (stdinIsTTY) await ask(` ${cd.caseReadyPrompt}`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const interview = await deps.conductLivekitInterview(io, deps.onboardingDeps, {
|
|
185
|
-
interviewId: created.interviewId,
|
|
186
|
-
language: lang,
|
|
187
|
-
plain: true,
|
|
188
|
-
});
|
|
189
|
-
if (!interview.ok) {
|
|
190
|
-
io.warn(interview.reason === 'needs-web' ? cd.needsWeb : cd.interviewUnavailable(interview.reason));
|
|
191
|
-
return { ok: false, reason: interview.reason, step: 'interview' };
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const reportBase = deps.getInterviewsBase();
|
|
195
|
-
const polled = await io.withProgress(cd.reportPolling, () =>
|
|
196
|
-
pollReport(deps, { interviewId: created.interviewId, accessToken }, { base: reportBase }));
|
|
197
|
-
if (!polled.ok) {
|
|
198
|
-
io.warn(polled.reason === 'timeout' ? cd.reportTimeout : cd.reportFailed(polled.reason));
|
|
199
|
-
return { ok: true, completed: 'interview', verdict: null };
|
|
118
|
+
const link = deps.buildWebLink(deps.getTalentProfileUrl(), { clusterId: chosen.clusterId, slug: chosen.slug });
|
|
119
|
+
if (!link) {
|
|
120
|
+
io.warn(cd.webHandoffNoLink);
|
|
121
|
+
return { ok: true, handoff: null, dimension: chosen.dimensionKey };
|
|
200
122
|
}
|
|
201
123
|
|
|
202
|
-
io.
|
|
203
|
-
io.notify(
|
|
204
|
-
|
|
205
|
-
return { ok: true, completed: 'interview', verdict: polled.report };
|
|
124
|
+
io.notify(cd.webHandoff);
|
|
125
|
+
io.notify(cd.webHandoffLink(link));
|
|
126
|
+
return { ok: true, handoff: link, dimension: chosen.dimensionKey };
|
|
206
127
|
}
|
|
207
128
|
|
|
208
129
|
module.exports = {
|
|
209
130
|
makeCertifyDimensionDeps,
|
|
210
131
|
runCertifyDimension,
|
|
132
|
+
buildWebLink,
|
|
211
133
|
chooseDimension,
|
|
212
134
|
resolveDimensionArg,
|
|
213
|
-
pollReport,
|
|
214
|
-
renderVerdict,
|
|
215
|
-
renderCase,
|
|
216
135
|
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { scrubSecrets } = require('./agent-synthesis');
|
|
6
|
+
const { loadAuthSession } = require('./auth-session-store');
|
|
7
|
+
const { getCertsBase } = require('./config');
|
|
8
|
+
const { postJsonWithTimeout } = require('./backend-request');
|
|
9
|
+
|
|
10
|
+
// Fire-and-forget: short timeout, every error swallowed, so a logging failure never blocks or fails a command.
|
|
11
|
+
const AUDIT_TIMEOUT_MS = 1500;
|
|
12
|
+
|
|
13
|
+
// Capped at 200 chars AFTER scrubbing (mirrors MAX_COMMAND_LEN in session-scan.js) so a secret straddling the cut cannot leak.
|
|
14
|
+
const MAX_ARGS_LEN = 200;
|
|
15
|
+
|
|
16
|
+
function cliVersion() {
|
|
17
|
+
try {
|
|
18
|
+
return require(path.join(__dirname, '..', 'package.json')).version || null;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Scrub THEN cap: scrubSecrets can GROW text, so the cap is measured on the scrubbed string that goes on the wire.
|
|
25
|
+
function redactArgs(argv) {
|
|
26
|
+
if (!Array.isArray(argv) || argv.length === 0) return null;
|
|
27
|
+
const scrubbed = scrubSecrets(argv.join(' ')).trim().slice(0, MAX_ARGS_LEN);
|
|
28
|
+
return scrubbed || null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Anonymous runs carry only a best-effort machine hint; authenticated runs identify via the Bearer instead.
|
|
32
|
+
function machineHint() {
|
|
33
|
+
let osUser = null;
|
|
34
|
+
try {
|
|
35
|
+
const info = os.userInfo();
|
|
36
|
+
osUser = info && typeof info.username === 'string' ? info.username : null;
|
|
37
|
+
} catch {
|
|
38
|
+
osUser = null;
|
|
39
|
+
}
|
|
40
|
+
let hostname = null;
|
|
41
|
+
try {
|
|
42
|
+
hostname = os.hostname() || null;
|
|
43
|
+
} catch {
|
|
44
|
+
hostname = null;
|
|
45
|
+
}
|
|
46
|
+
return { osUser, hostname };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Builds the record + headers; returns null when there is no endpoint to post to.
|
|
50
|
+
function buildAudit(command, argv, env) {
|
|
51
|
+
let base;
|
|
52
|
+
try {
|
|
53
|
+
base = getCertsBase(env);
|
|
54
|
+
} catch {
|
|
55
|
+
base = null;
|
|
56
|
+
}
|
|
57
|
+
if (!base) return null;
|
|
58
|
+
|
|
59
|
+
const session = loadAuthSession(env);
|
|
60
|
+
const token =
|
|
61
|
+
session && (session.accessToken || session.hubAccessToken)
|
|
62
|
+
? session.accessToken || session.hubAccessToken
|
|
63
|
+
: null;
|
|
64
|
+
|
|
65
|
+
const body = {
|
|
66
|
+
command: String(command || 'unknown').slice(0, 100),
|
|
67
|
+
args: redactArgs(argv),
|
|
68
|
+
cliVersion: cliVersion(),
|
|
69
|
+
clientTimestamp: new Date().toISOString(),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Machine hint only when anonymous; identity comes from the token otherwise.
|
|
73
|
+
if (!token) {
|
|
74
|
+
const { osUser, hostname } = machineHint();
|
|
75
|
+
body.osUser = osUser;
|
|
76
|
+
body.hostname = hostname;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const headers = token ? { Authorization: `Bearer ${token}` } : null;
|
|
80
|
+
const endpoint = `${base.replace(/\/+$/, '')}/cli/command-log`;
|
|
81
|
+
return { endpoint, body, headers };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Emits ONE fire-and-forget audit record; never throws or rejects. Returns the `.catch`-guarded promise so tests can await it (production does not).
|
|
85
|
+
function emitCommandAudit({ command, argv = [], env = process.env } = {}) {
|
|
86
|
+
try {
|
|
87
|
+
const audit = buildAudit(command, argv, env);
|
|
88
|
+
if (!audit) return Promise.resolve();
|
|
89
|
+
return postJsonWithTimeout(
|
|
90
|
+
audit.endpoint,
|
|
91
|
+
audit.body,
|
|
92
|
+
AUDIT_TIMEOUT_MS,
|
|
93
|
+
'POST',
|
|
94
|
+
null,
|
|
95
|
+
audit.headers,
|
|
96
|
+
).then(
|
|
97
|
+
() => {},
|
|
98
|
+
() => {},
|
|
99
|
+
);
|
|
100
|
+
} catch {
|
|
101
|
+
return Promise.resolve();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { emitCommandAudit, redactArgs, buildAudit };
|
package/src/config.js
CHANGED
|
@@ -264,7 +264,7 @@ function getAuthTokenEndpoint(env = process.env) {
|
|
|
264
264
|
function getCompleteRegistrationEndpoint(env = process.env) {
|
|
265
265
|
const explicit = readEnv(env, 'COMPLETE_REGISTRATION_ENDPOINT');
|
|
266
266
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
267
|
-
return deriveFromHub(env, 'works/auth/complete-registration');
|
|
267
|
+
return deriveFromHub(env, 'works/talent/auth/complete-registration');
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
// Google via device flow (RFC 8628): the Hub broker owns all Google config; the CLI holds no client_id.
|
|
@@ -287,7 +287,7 @@ function getDeviceTokenEndpoint(env = process.env) {
|
|
|
287
287
|
function getMyCertificationsEndpoint(env = process.env) {
|
|
288
288
|
const explicit = readEnv(env, 'MY_CERTIFICATIONS_ENDPOINT');
|
|
289
289
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
290
|
-
return deriveFromHub(env, 'works/certifications/me');
|
|
290
|
+
return deriveFromHub(env, 'works/talent/certifications/me');
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
// Role/cluster endpoints (hub, talent Bearer). available-roles = the catalog the
|
|
@@ -296,19 +296,19 @@ function getMyCertificationsEndpoint(env = process.env) {
|
|
|
296
296
|
function getAvailableRolesEndpoint(env = process.env) {
|
|
297
297
|
const explicit = readEnv(env, 'AVAILABLE_ROLES_ENDPOINT');
|
|
298
298
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
299
|
-
return deriveFromHub(env, 'works/certifications/me/available-roles');
|
|
299
|
+
return deriveFromHub(env, 'works/talent/certifications/me/available-roles');
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
function getSetMainRoleEndpoint(env = process.env) {
|
|
303
303
|
const explicit = readEnv(env, 'SET_MAIN_ROLE_ENDPOINT');
|
|
304
304
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
305
|
-
return deriveFromHub(env, 'works/certifications/me/main-role');
|
|
305
|
+
return deriveFromHub(env, 'works/talent/certifications/me/main-role');
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
function getAssignedClustersEndpoint(env = process.env) {
|
|
309
309
|
const explicit = readEnv(env, 'ASSIGNED_CLUSTERS_ENDPOINT');
|
|
310
310
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
311
|
-
return deriveFromHub(env, 'works/
|
|
311
|
+
return deriveFromHub(env, 'works/talent/me/work-details/assigned-clusters');
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
function getTemplatesByDimensionEndpoint(env = process.env) {
|
|
@@ -326,12 +326,20 @@ function getMyAssessmentsEndpoint(env = process.env) {
|
|
|
326
326
|
return base ? `${base}/users/me/assessments` : null;
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
+
// `certify` creates its dimension interview here (certs, talent Bearer).
|
|
330
|
+
function getCertificationInterviewsEndpoint(env = process.env) {
|
|
331
|
+
const explicit = readEnv(env, 'CERTIFICATION_INTERVIEWS_ENDPOINT');
|
|
332
|
+
if (explicit && explicit.trim()) return explicit.trim();
|
|
333
|
+
const base = getCertsBase(env);
|
|
334
|
+
return base ? `${base}/users/me/certification-interviews` : null;
|
|
335
|
+
}
|
|
336
|
+
|
|
329
337
|
// Find Positions list (hub `works/positions/find`, talent Bearer). Serves the
|
|
330
338
|
// All/Recommended/Saved tabs via `criteria.savedByMe`; ordered by match desc.
|
|
331
339
|
function getFindPositionsEndpoint(env = process.env) {
|
|
332
340
|
const explicit = readEnv(env, 'FIND_POSITIONS_ENDPOINT');
|
|
333
341
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
334
|
-
return deriveFromHub(env, 'works/positions/find');
|
|
342
|
+
return deriveFromHub(env, 'works/talent/positions/find');
|
|
335
343
|
}
|
|
336
344
|
|
|
337
345
|
// Marketplace read/write (hub, talent Bearer). The id-taking getters return null
|
|
@@ -340,20 +348,20 @@ function getPositionDetailEndpoint(positionId, env = process.env) {
|
|
|
340
348
|
const explicit = readEnv(env, 'POSITION_DETAIL_ENDPOINT');
|
|
341
349
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
342
350
|
if (positionId == null || typeof positionId === 'object') return null;
|
|
343
|
-
return deriveFromHub(env, `works/positions/${encodeURIComponent(positionId)}/detail`);
|
|
351
|
+
return deriveFromHub(env, `works/talent/positions/${encodeURIComponent(positionId)}/detail`);
|
|
344
352
|
}
|
|
345
353
|
|
|
346
354
|
function getSavePositionEndpoint(env = process.env) {
|
|
347
355
|
const explicit = readEnv(env, 'SAVE_POSITION_ENDPOINT');
|
|
348
356
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
349
|
-
return deriveFromHub(env, 'works/saved-positions');
|
|
357
|
+
return deriveFromHub(env, 'works/talent/saved-positions');
|
|
350
358
|
}
|
|
351
359
|
|
|
352
360
|
function getUnsavePositionEndpoint(positionId, env = process.env) {
|
|
353
361
|
const explicit = readEnv(env, 'UNSAVE_POSITION_ENDPOINT');
|
|
354
362
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
355
363
|
if (positionId == null || typeof positionId === 'object') return null;
|
|
356
|
-
return deriveFromHub(env, `works/saved-positions/${encodeURIComponent(positionId)}`);
|
|
364
|
+
return deriveFromHub(env, `works/talent/saved-positions/${encodeURIComponent(positionId)}`);
|
|
357
365
|
}
|
|
358
366
|
|
|
359
367
|
function getReceivedInvitationsEndpoint(env = process.env) {
|
|
@@ -365,7 +373,7 @@ function getReceivedInvitationsEndpoint(env = process.env) {
|
|
|
365
373
|
function getMyCandidaturesEndpoint(env = process.env) {
|
|
366
374
|
const explicit = readEnv(env, 'MY_CANDIDATURES_ENDPOINT');
|
|
367
375
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
368
|
-
return deriveFromHub(env, 'works/candidatures/me');
|
|
376
|
+
return deriveFromHub(env, 'works/talent/candidatures/me');
|
|
369
377
|
}
|
|
370
378
|
|
|
371
379
|
function getMeAvailabilityEndpoint(env = process.env) {
|
|
@@ -377,13 +385,13 @@ function getMeAvailabilityEndpoint(env = process.env) {
|
|
|
377
385
|
function getMeProfileEndpoint(env = process.env) {
|
|
378
386
|
const explicit = readEnv(env, 'ME_PROFILE_ENDPOINT');
|
|
379
387
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
380
|
-
return deriveFromHub(env, 'works/me/profile');
|
|
388
|
+
return deriveFromHub(env, 'works/talent/me/profile-summary');
|
|
381
389
|
}
|
|
382
390
|
|
|
383
391
|
function getTalentMeProfileEndpoint(env = process.env) {
|
|
384
392
|
const explicit = readEnv(env, 'TALENT_ME_PROFILE_ENDPOINT');
|
|
385
393
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
386
|
-
return deriveFromHub(env, 'works/
|
|
394
|
+
return deriveFromHub(env, 'works/talent/me/profile');
|
|
387
395
|
}
|
|
388
396
|
|
|
389
397
|
// Alma (the AI assistant) is a SEPARATE service — NOT derived from hubBase.
|
|
@@ -444,7 +452,7 @@ function getUsageReportEndpoint(env = process.env) {
|
|
|
444
452
|
function getAiProfileEndpoint(env = process.env) {
|
|
445
453
|
const explicit = readEnv(env, 'AI_PROFILE_ENDPOINT');
|
|
446
454
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
447
|
-
return deriveFromHub(env, 'works/me/ai-profile');
|
|
455
|
+
return deriveFromHub(env, 'works/talent/me/ai-profile');
|
|
448
456
|
}
|
|
449
457
|
|
|
450
458
|
// Evidence emission endpoint for the `cli_scan` instrument (talents-ai-score Phase 2).
|
|
@@ -476,7 +484,7 @@ function getPortfolioDraftDescriptionEndpoint(env = process.env) {
|
|
|
476
484
|
function getPortfolioUpdateEndpoint(env = process.env) {
|
|
477
485
|
const explicit = readEnv(env, 'PORTFOLIO_UPDATE_ENDPOINT');
|
|
478
486
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
479
|
-
return deriveFromHub(env, 'works/portfolios');
|
|
487
|
+
return deriveFromHub(env, 'works/talent/portfolios');
|
|
480
488
|
}
|
|
481
489
|
|
|
482
490
|
function getAgentsDeclareEndpoint(env = process.env) {
|
|
@@ -502,7 +510,7 @@ function getAgentPortfoliosEndpoint(agentId, env = process.env) {
|
|
|
502
510
|
// Null on missing/non-scalar id: no bogus URL, and keeps config.test.js's `fn(env)`
|
|
503
511
|
// getter sweep honest when the env object lands in `agentId`.
|
|
504
512
|
if (agentId == null || typeof agentId === 'object') return null;
|
|
505
|
-
return deriveFromHub(env, `works/me/agents/${encodeURIComponent(agentId)}/portfolios`);
|
|
513
|
+
return deriveFromHub(env, `works/talent/me/agents/${encodeURIComponent(agentId)}/portfolios`);
|
|
506
514
|
}
|
|
507
515
|
|
|
508
516
|
// Onboarding endpoints (evaluation-mcp, ADR-011..015/024 — the register flow).
|
|
@@ -533,7 +541,7 @@ function getSignUpEndpoint(env = process.env) {
|
|
|
533
541
|
function getImportProfileEndpoint(env = process.env) {
|
|
534
542
|
const explicit = readEnv(env, 'IMPORT_PROFILE_ENDPOINT');
|
|
535
543
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
536
|
-
return deriveFromHub(env, 'works/me/import-profile');
|
|
544
|
+
return deriveFromHub(env, 'works/talent/me/import-profile');
|
|
537
545
|
}
|
|
538
546
|
|
|
539
547
|
// MCP sign-up (ticket 39540): public UNREGISTERED + claim code, its import status, and the one-time web login.
|
|
@@ -542,7 +550,7 @@ function getMcpSignupEndpoint(env = process.env) {
|
|
|
542
550
|
}
|
|
543
551
|
|
|
544
552
|
function getMyImportStatusEndpoint(env = process.env) {
|
|
545
|
-
return deriveFromHub(env, 'works/me/import-profile/status');
|
|
553
|
+
return deriveFromHub(env, 'works/talent/me/import-profile/status');
|
|
546
554
|
}
|
|
547
555
|
|
|
548
556
|
function getOneTimeTokenEndpoint(env = process.env) {
|
|
@@ -564,25 +572,25 @@ function getOneTimeLoginUrl(token, env = process.env) {
|
|
|
564
572
|
function getProfessionalDetailsEndpoint(env = process.env) {
|
|
565
573
|
const explicit = readEnv(env, 'PROFESSIONAL_DETAILS_ENDPOINT');
|
|
566
574
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
567
|
-
return deriveFromHub(env, 'works/me/professional-details');
|
|
575
|
+
return deriveFromHub(env, 'works/talent/me/professional-details');
|
|
568
576
|
}
|
|
569
577
|
|
|
570
578
|
function getPricingRateEndpoint(env = process.env) {
|
|
571
579
|
const explicit = readEnv(env, 'PRICING_RATE_ENDPOINT');
|
|
572
580
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
573
|
-
return deriveFromHub(env, 'works/
|
|
581
|
+
return deriveFromHub(env, 'works/talent/me/work-details/pricing-rate');
|
|
574
582
|
}
|
|
575
583
|
|
|
576
584
|
function getSetAvailabilityEndpoint(env = process.env) {
|
|
577
585
|
const explicit = readEnv(env, 'AVAILABILITY_ENDPOINT');
|
|
578
586
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
579
|
-
return deriveFromHub(env, 'works/
|
|
587
|
+
return deriveFromHub(env, 'works/talent/me/work-details/availability');
|
|
580
588
|
}
|
|
581
589
|
|
|
582
590
|
function getLanguagesEndpoint(env = process.env) {
|
|
583
591
|
const explicit = readEnv(env, 'LANGUAGES_ENDPOINT');
|
|
584
592
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
585
|
-
return deriveFromHub(env, 'works/
|
|
593
|
+
return deriveFromHub(env, 'works/talent/me/work-details/languages');
|
|
586
594
|
}
|
|
587
595
|
|
|
588
596
|
function getLanguagesCatalogEndpoint(env = process.env) {
|
|
@@ -616,7 +624,7 @@ function getTranslationsEndpoint(env = process.env) {
|
|
|
616
624
|
function getTalentMeEndpoint(env = process.env) {
|
|
617
625
|
const explicit = readEnv(env, 'TALENT_ME_ENDPOINT');
|
|
618
626
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
619
|
-
return deriveFromHub(env, 'works/me');
|
|
627
|
+
return deriveFromHub(env, 'works/users/me');
|
|
620
628
|
}
|
|
621
629
|
|
|
622
630
|
function getOnboardingInterviewsEndpoint(env = process.env) {
|
|
@@ -758,6 +766,7 @@ module.exports = {
|
|
|
758
766
|
getAssignedClustersEndpoint,
|
|
759
767
|
getTemplatesByDimensionEndpoint,
|
|
760
768
|
getMyAssessmentsEndpoint,
|
|
769
|
+
getCertificationInterviewsEndpoint,
|
|
761
770
|
// Find Positions list (`find-projects` command / `find_projects` MCP tool).
|
|
762
771
|
getFindPositionsEndpoint,
|
|
763
772
|
// Marketplace (show-project / save-project / unsave-project / invitations).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Find Positions list client
|
|
3
|
+
// Find Positions list client. Each
|
|
4
4
|
// item carries the parent Project's id/name — a "project" here is the Position's
|
|
5
5
|
// parent, not a separate list. Pagination (limit/offset) and filters map to the
|
|
6
6
|
// endpoint's `criteria`; the server orders by match desc and returns the total.
|