shakers-cli 0.17.0 → 0.19.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/README.md +15 -5
- package/bin/add-agent.js +12 -6
- package/bin/ai-usage.js +2 -1
- package/bin/apply.js +66 -0
- package/bin/command-audit-worker.js +19 -0
- package/bin/login.js +4 -8
- package/bin/mcp.js +32 -19
- package/bin/register.js +10 -29
- package/bin/shakers-cli.js +21 -12
- package/package.json +1 -1
- package/src/agents-client.js +1 -0
- package/src/ai-usage-review.js +5 -3
- package/src/apply-client.js +164 -0
- package/src/apply-flow.js +105 -0
- package/src/auth-client.js +7 -107
- package/src/auth-session-store.js +6 -12
- package/src/backend-request.js +1 -24
- package/src/cli-skill.js +77 -0
- package/src/command-audit.js +58 -5
- package/src/config.js +15 -16
- package/src/consent-flow.js +7 -2
- package/src/detect-repos.js +4 -8
- package/src/detected-agents.js +40 -0
- package/src/detectors.js +23 -0
- package/src/device-auth-store.js +2 -2
- package/src/device-login.js +11 -9
- package/src/env-paths.js +9 -1
- package/src/i18n.js +258 -88
- package/src/mcp-ai-usage-result-tool.js +6 -4
- package/src/mcp-ai-usage-tool.js +73 -47
- package/src/mcp-apply-tools.js +152 -0
- package/src/mcp-choice.js +171 -34
- package/src/mcp-detector.js +26 -2
- package/src/mcp-device-login-tools.js +40 -8
- package/src/mcp-install.js +15 -11
- package/src/mcp-instructions.js +43 -17
- package/src/mcp-onboarding-tools.js +90 -3
- package/src/mcp-prompts.js +6 -4
- package/src/mcp-register-tools.js +25 -14
- package/src/mcp-role-tools.js +35 -17
- package/src/mcp-server-name.js +7 -0
- package/src/mcp-server.js +35 -7
- package/src/mcp-signup-client.js +3 -6
- package/src/mcp-signup-flow.js +389 -0
- package/src/mcp-signup-tools.js +237 -126
- package/src/onboarding-flow.js +30 -125
- package/src/opencode-sessions.js +92 -0
- package/src/profile-agents.js +26 -0
- package/src/saved-positions-flow.js +3 -3
- package/src/scanner.js +104 -12
- package/src/session-gate.js +1 -4
- package/src/session-normalizers.js +114 -8
- package/src/session-refresh.js +5 -16
- package/src/session-scan.js +167 -22
- package/src/share-payload.js +4 -1
- package/src/share.js +2 -2
- package/src/signup-client.js +12 -39
- package/src/signup-copy.js +506 -68
- package/src/skill-install.js +102 -0
- package/src/start-add-agents.js +106 -3
- package/src/tier-engine.js +14 -2
- package/src/trust-narration.js +32 -0
- package/src/usage-run-steps.js +16 -4
- package/src/usage-scan.js +3 -0
- package/src/brand-svg.js +0 -9
- package/src/inter-fonts.js +0 -6
- package/src/mcp-auth-form.js +0 -484
package/src/command-audit.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const crypto = require('crypto');
|
|
3
4
|
const os = require('os');
|
|
4
5
|
const path = require('path');
|
|
6
|
+
const { spawn } = require('child_process');
|
|
5
7
|
const { scrubSecrets } = require('./agent-synthesis');
|
|
6
8
|
const { loadAuthSession, decodeJwtPayload } = require('./auth-session-store');
|
|
7
9
|
const { getCertsBase } = require('./config');
|
|
@@ -13,6 +15,13 @@ const AUDIT_TIMEOUT_MS = 1500;
|
|
|
13
15
|
// Capped at 200 chars AFTER scrubbing (mirrors MAX_COMMAND_LEN in session-scan.js) so a secret straddling the cut cannot leak.
|
|
14
16
|
const MAX_ARGS_LEN = 200;
|
|
15
17
|
|
|
18
|
+
const WORKER = path.join(__dirname, '..', 'bin', 'command-audit-worker.js');
|
|
19
|
+
const PAYLOAD_VAR = 'SHAKERS_CLI_AUDIT_PAYLOAD';
|
|
20
|
+
|
|
21
|
+
function auditDisabled(env) {
|
|
22
|
+
return !!env && env.SHAKERS_CLI_NO_AUDIT === '1';
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
function cliVersion() {
|
|
17
26
|
try {
|
|
18
27
|
return require(path.join(__dirname, '..', 'package.json')).version || null;
|
|
@@ -28,6 +37,16 @@ function redactArgs(argv) {
|
|
|
28
37
|
return scrubbed || null;
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
// `apply` takes only the position ref: anything typed after it could be the talent's message, so it never reaches the log.
|
|
41
|
+
function withoutFreeText(argv) {
|
|
42
|
+
if (!Array.isArray(argv)) return argv;
|
|
43
|
+
const words = argv.filter((a) => typeof a === 'string' && !a.startsWith('-')).map((w) => w.toLowerCase());
|
|
44
|
+
const at = words[0] === 'apply' ? 0 : words[0] === 'projects' && words[1] === 'apply' ? 1 : -1;
|
|
45
|
+
if (at === -1) return argv;
|
|
46
|
+
const kept = argv.filter((a) => typeof a === 'string' && !a.startsWith('-'));
|
|
47
|
+
return kept.slice(0, at + 2);
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
// Anonymous runs carry only a best-effort machine hint; authenticated runs identify via the Bearer instead.
|
|
32
51
|
function machineHint() {
|
|
33
52
|
let osUser = null;
|
|
@@ -52,7 +71,7 @@ function jwtExpired(token, now) {
|
|
|
52
71
|
}
|
|
53
72
|
|
|
54
73
|
// Builds the record + headers; returns null when there is no endpoint to post to.
|
|
55
|
-
function buildAudit(command, argv, env) {
|
|
74
|
+
function buildAudit(command, argv, env, id = null) {
|
|
56
75
|
let base;
|
|
57
76
|
try {
|
|
58
77
|
base = getCertsBase(env);
|
|
@@ -66,8 +85,9 @@ function buildAudit(command, argv, env) {
|
|
|
66
85
|
const token = stored && !jwtExpired(stored, Date.now()) ? stored : null;
|
|
67
86
|
|
|
68
87
|
const body = {
|
|
88
|
+
...(id ? { id } : {}),
|
|
69
89
|
command: String(command || 'unknown').slice(0, 100),
|
|
70
|
-
args: redactArgs(argv),
|
|
90
|
+
args: redactArgs(withoutFreeText(argv)),
|
|
71
91
|
cliVersion: cliVersion(),
|
|
72
92
|
clientTimestamp: new Date().toISOString(),
|
|
73
93
|
};
|
|
@@ -85,9 +105,10 @@ function buildAudit(command, argv, env) {
|
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
// Emits ONE fire-and-forget audit record; never throws or rejects. Returns the `.catch`-guarded promise so tests can await it (production does not).
|
|
88
|
-
function emitCommandAudit({ command, argv = [], env = process.env } = {}) {
|
|
108
|
+
function emitCommandAudit({ command, argv = [], env = process.env, id = null } = {}) {
|
|
89
109
|
try {
|
|
90
|
-
|
|
110
|
+
if (auditDisabled(env)) return Promise.resolve();
|
|
111
|
+
const audit = buildAudit(command, argv, env, id);
|
|
91
112
|
if (!audit) return Promise.resolve();
|
|
92
113
|
return postJsonWithTimeout(
|
|
93
114
|
audit.endpoint,
|
|
@@ -105,4 +126,36 @@ function emitCommandAudit({ command, argv = [], env = process.env } = {}) {
|
|
|
105
126
|
}
|
|
106
127
|
}
|
|
107
128
|
|
|
108
|
-
|
|
129
|
+
async function logCommandRun({ command, argv = [], env = process.env } = {}) {
|
|
130
|
+
if (auditDisabled(env)) return null;
|
|
131
|
+
const id = crypto.randomUUID();
|
|
132
|
+
await emitCommandAudit({ command, argv, env, id });
|
|
133
|
+
return id;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let runId = null;
|
|
137
|
+
|
|
138
|
+
function startCommandAudit({ command, argv = [], env = process.env } = {}) {
|
|
139
|
+
if (auditDisabled(env)) return null;
|
|
140
|
+
const id = crypto.randomUUID();
|
|
141
|
+
try {
|
|
142
|
+
const child = spawn(process.execPath, [WORKER], {
|
|
143
|
+
detached: true,
|
|
144
|
+
stdio: 'ignore',
|
|
145
|
+
windowsHide: true,
|
|
146
|
+
env: { ...env, [PAYLOAD_VAR]: JSON.stringify({ id, command, args: redactArgs(withoutFreeText(argv)) }) },
|
|
147
|
+
});
|
|
148
|
+
child.on('error', () => {});
|
|
149
|
+
child.unref();
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
runId = id;
|
|
154
|
+
return id;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function currentRunId() {
|
|
158
|
+
return runId;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { emitCommandAudit, startCommandAudit, logCommandRun, currentRunId, redactArgs, withoutFreeText, buildAudit, PAYLOAD_VAR };
|
package/src/config.js
CHANGED
|
@@ -300,14 +300,6 @@ function getAgentEvaluationEndpoint(env = process.env) {
|
|
|
300
300
|
return deriveFromIngest(env, 'agent-evaluation');
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
// Login endpoint — better-auth email sign-in: `POST {hubBase}/auth/sign-in/email` with `{email,password}`.
|
|
304
|
-
function getLoginEndpoint(env = process.env) {
|
|
305
|
-
const explicit = readEnv(env, 'LOGIN_ENDPOINT');
|
|
306
|
-
if (explicit && explicit.trim()) return explicit.trim();
|
|
307
|
-
return deriveFromHub(env, 'auth/sign-in/email');
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// better-auth JWT endpoint: `GET {hubBase}/auth/token` with the session cookie → `{token}` (ES256/JWKS, ~15-min TTL).
|
|
311
303
|
function getSignOutEndpoint(env = process.env) {
|
|
312
304
|
return deriveFromHub(env, 'auth/sign-out');
|
|
313
305
|
}
|
|
@@ -421,6 +413,19 @@ function getUnsavePositionEndpoint(positionId, env = process.env) {
|
|
|
421
413
|
return deriveFromHub(env, `works/talent/saved-positions/${encodeURIComponent(positionId)}`);
|
|
422
414
|
}
|
|
423
415
|
|
|
416
|
+
function getPositionApplicationEndpoint(positionId, env = process.env) {
|
|
417
|
+
const explicit = readEnv(env, 'POSITION_APPLICATION_ENDPOINT');
|
|
418
|
+
if (explicit && explicit.trim()) return explicit.trim();
|
|
419
|
+
if (positionId == null || typeof positionId === 'object') return null;
|
|
420
|
+
return deriveFromHub(env, `works/talent/positions/${encodeURIComponent(positionId)}/application`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function getCandidaturesEndpoint(env = process.env) {
|
|
424
|
+
const explicit = readEnv(env, 'CANDIDATURES_ENDPOINT');
|
|
425
|
+
if (explicit && explicit.trim()) return explicit.trim();
|
|
426
|
+
return deriveFromHub(env, 'works/talent/candidatures');
|
|
427
|
+
}
|
|
428
|
+
|
|
424
429
|
function getReceivedInvitationsEndpoint(env = process.env) {
|
|
425
430
|
const explicit = readEnv(env, 'RECEIVED_INVITATIONS_ENDPOINT');
|
|
426
431
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
@@ -589,12 +594,6 @@ function deriveFromHub(env, relativePath) {
|
|
|
589
594
|
return `${base}/${relativePath}`;
|
|
590
595
|
}
|
|
591
596
|
|
|
592
|
-
function getSignUpEndpoint(env = process.env) {
|
|
593
|
-
const explicit = readEnv(env, 'SIGNUP_ENDPOINT');
|
|
594
|
-
if (explicit && explicit.trim()) return explicit.trim();
|
|
595
|
-
return deriveFromHub(env, 'auth/sign-up/email');
|
|
596
|
-
}
|
|
597
|
-
|
|
598
597
|
function getImportProfileEndpoint(env = process.env) {
|
|
599
598
|
const explicit = readEnv(env, 'IMPORT_PROFILE_ENDPOINT');
|
|
600
599
|
if (explicit && explicit.trim()) return explicit.trim();
|
|
@@ -832,7 +831,6 @@ module.exports = {
|
|
|
832
831
|
getSynthesisEndpoint,
|
|
833
832
|
getRoadmapEndpoint,
|
|
834
833
|
getAgentEvaluationEndpoint,
|
|
835
|
-
getLoginEndpoint,
|
|
836
834
|
getAuthTokenEndpoint,
|
|
837
835
|
getSignOutEndpoint,
|
|
838
836
|
getCompleteRegistrationEndpoint,
|
|
@@ -852,6 +850,8 @@ module.exports = {
|
|
|
852
850
|
// Marketplace (show-project / save-project / unsave-project / invitations).
|
|
853
851
|
getPositionDetailEndpoint,
|
|
854
852
|
getSavePositionEndpoint,
|
|
853
|
+
getPositionApplicationEndpoint,
|
|
854
|
+
getCandidaturesEndpoint,
|
|
855
855
|
getUnsavePositionEndpoint,
|
|
856
856
|
getReceivedInvitationsEndpoint,
|
|
857
857
|
getMyCandidaturesEndpoint,
|
|
@@ -884,7 +884,6 @@ module.exports = {
|
|
|
884
884
|
getCertsBase,
|
|
885
885
|
getHubBase,
|
|
886
886
|
sanitizeStaleBakedEndpoints,
|
|
887
|
-
getSignUpEndpoint,
|
|
888
887
|
getImportProfileEndpoint,
|
|
889
888
|
getMcpSignupEndpoint,
|
|
890
889
|
getMyImportStatusEndpoint,
|
package/src/consent-flow.js
CHANGED
|
@@ -15,9 +15,12 @@ const MAX_ATTEMPTS = 5;
|
|
|
15
15
|
const YES_RE = /^(y|yes|s|si|sí)$/i;
|
|
16
16
|
const NO_RE = /^(n|no)$/i;
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
const UNANSWERED = Symbol('unanswered');
|
|
19
|
+
|
|
20
|
+
async function askYesNo(ask, question, catalog, { defaultNo = false, defaultYes = false, blankUnanswered = false } = {}) {
|
|
19
21
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
20
22
|
const raw = String(await ask(question)).trim();
|
|
23
|
+
if (blankUnanswered && raw === '') return UNANSWERED;
|
|
21
24
|
if (defaultNo && raw === '') return false;
|
|
22
25
|
if (defaultYes && raw === '') return true;
|
|
23
26
|
if (YES_RE.test(raw)) return true;
|
|
@@ -56,6 +59,7 @@ async function runConsentPrompt({
|
|
|
56
59
|
sessionEmail = null,
|
|
57
60
|
drainBeforeAsk = false,
|
|
58
61
|
profile = 'talent',
|
|
62
|
+
requireExplicitAnswer = false,
|
|
59
63
|
}) {
|
|
60
64
|
const c = catalog.consent;
|
|
61
65
|
// Presentation only: framed by src/legal-notice.js on a
|
|
@@ -69,7 +73,8 @@ async function runConsentPrompt({
|
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
// defaultNo: a blank Enter means "do not save" (legal requirement).
|
|
72
|
-
const accepted = await askYesNo(ask, c.persistQuestion, catalog, { defaultNo:
|
|
76
|
+
const accepted = await askYesNo(ask, c.persistQuestion, catalog, { defaultNo: !requireExplicitAnswer, blankUnanswered: requireExplicitAnswer });
|
|
77
|
+
if (accepted === UNANSWERED) return 'unanswered';
|
|
73
78
|
if (accepted === null) {
|
|
74
79
|
process.stdout.write(` ${c.notObtained}\n\n`);
|
|
75
80
|
return null;
|
package/src/detect-repos.js
CHANGED
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { execFileSync } = require('child_process');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const {
|
|
6
|
-
toolSources,
|
|
7
|
-
listSessionFiles,
|
|
8
|
-
readSessionObjects,
|
|
9
|
-
resolveFileCwd,
|
|
10
|
-
} = require('./session-scan');
|
|
5
|
+
const { toolSources, sourceSessions, resolveFileCwd } = require('./session-scan');
|
|
11
6
|
const { normalizeRemote } = require('./authorship');
|
|
12
7
|
|
|
13
8
|
const GIT_TIMEOUT_MS = 15000;
|
|
@@ -37,8 +32,9 @@ function sessionCwdCounts(env = process.env) {
|
|
|
37
32
|
const counts = new Map();
|
|
38
33
|
let unassigned = 0;
|
|
39
34
|
for (const source of toolSources(env)) {
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
if (source.repoLevel) continue;
|
|
36
|
+
for (const { objects } of sourceSessions(source)) {
|
|
37
|
+
const cwd = resolveFileCwd(objects);
|
|
42
38
|
if (cwd === null) {
|
|
43
39
|
unassigned += 1;
|
|
44
40
|
continue;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { normalizeAgentName } = require('./tier-engine');
|
|
4
|
+
const { scrubSecrets } = require('./agent-synthesis');
|
|
5
|
+
const { requestDeclareAgent } = require('./agents-client');
|
|
6
|
+
|
|
7
|
+
function detectedAgentsFromReport(report) {
|
|
8
|
+
const synthesis = report && report.agentSynthesis && Array.isArray(report.agentSynthesis.agents) ? report.agentSynthesis.agents : [];
|
|
9
|
+
const whatItDoesByName = new Map(synthesis.filter((a) => a && a.name).map((a) => [a.name, a.whatItDoes]));
|
|
10
|
+
const agents = report && Array.isArray(report.agents) ? report.agents : [];
|
|
11
|
+
return agents
|
|
12
|
+
.filter((a) => a && typeof a.name === 'string' && a.name)
|
|
13
|
+
.map((a) => ({ name: a.name, whatItDoes: whatItDoesByName.get(a.name) || null }));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function newDetectedAgents(candidates, profileAgents) {
|
|
17
|
+
const taken = new Set((Array.isArray(profileAgents) ? profileAgents : []).map((a) => normalizeAgentName(a && a.name)));
|
|
18
|
+
const fresh = [];
|
|
19
|
+
for (const agent of Array.isArray(candidates) ? candidates : []) {
|
|
20
|
+
const key = normalizeAgentName(agent && agent.name);
|
|
21
|
+
if (!key || taken.has(key)) continue;
|
|
22
|
+
taken.add(key);
|
|
23
|
+
fresh.push({ name: agent.name.trim(), whatItDoes: typeof agent.whatItDoes === 'string' && agent.whatItDoes ? agent.whatItDoes : null });
|
|
24
|
+
}
|
|
25
|
+
return fresh;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function declareDetectedAgents(agents, { accessToken, hubAccessToken }, { endpoint, declare = requestDeclareAgent } = {}) {
|
|
29
|
+
const added = [];
|
|
30
|
+
const failed = [];
|
|
31
|
+
for (const agent of agents) {
|
|
32
|
+
const whatItDoes = agent.whatItDoes ? scrubSecrets(agent.whatItDoes) : undefined;
|
|
33
|
+
const result = await declare({ name: agent.name, whatItDoes, accessToken, hubAccessToken }, { endpoint });
|
|
34
|
+
if (result && result.ok) added.push(agent);
|
|
35
|
+
else failed.push({ agent, reason: (result && result.reason) || 'declare-failed' });
|
|
36
|
+
}
|
|
37
|
+
return { added, failed };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { detectedAgentsFromReport, newDetectedAgents, declareDetectedAgents };
|
package/src/detectors.js
CHANGED
|
@@ -115,6 +115,29 @@ const detectors = [
|
|
|
115
115
|
{ type: 'bin', name: 'codex' },
|
|
116
116
|
],
|
|
117
117
|
},
|
|
118
|
+
{
|
|
119
|
+
id: 'pi',
|
|
120
|
+
name: 'Pi',
|
|
121
|
+
vendor: 'Earendil Works',
|
|
122
|
+
category: CATEGORIES.AGENTIC_CLI,
|
|
123
|
+
signals: [
|
|
124
|
+
{ type: 'projectPath', path: '.pi' },
|
|
125
|
+
{ type: 'homePath', path: '.pi/agent' },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'opencode',
|
|
130
|
+
name: 'OpenCode',
|
|
131
|
+
vendor: 'OpenCode',
|
|
132
|
+
category: CATEGORIES.AGENTIC_CLI,
|
|
133
|
+
signals: [
|
|
134
|
+
{ type: 'projectPath', path: 'opencode.json' },
|
|
135
|
+
{ type: 'projectPath', path: '.opencode' },
|
|
136
|
+
{ type: 'homePath', path: '.config/opencode' },
|
|
137
|
+
{ type: 'homePath', path: '.local/share/opencode' },
|
|
138
|
+
{ type: 'bin', name: 'opencode' },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
118
141
|
{
|
|
119
142
|
id: 'cody',
|
|
120
143
|
name: 'Cody',
|
package/src/device-auth-store.js
CHANGED
|
@@ -26,7 +26,7 @@ function savePendingDevice(pending, env = process.env, { fs: fsImpl = fs } = {})
|
|
|
26
26
|
interval: typeof pending.interval === 'number' && pending.interval > 0 ? Math.floor(pending.interval) : 5,
|
|
27
27
|
expiresAt: typeof pending.expiresAt === 'string' && pending.expiresAt ? pending.expiresAt : null,
|
|
28
28
|
mode: pending.mode === 'register' ? 'register' : 'login',
|
|
29
|
-
provider: pending.provider === 'linkedin'
|
|
29
|
+
provider: pending.provider === 'linkedin' || pending.provider === 'email' ? pending.provider : 'google',
|
|
30
30
|
registerContext: pending.registerContext && typeof pending.registerContext === 'object' ? pending.registerContext : null,
|
|
31
31
|
email: typeof pending.email === 'string' && pending.email ? pending.email : null,
|
|
32
32
|
version: DEVICE_FILE_VERSION,
|
|
@@ -75,7 +75,7 @@ function loadPendingDevice(env = process.env, { fs: fsImpl = fs } = {}) {
|
|
|
75
75
|
interval: typeof parsed.interval === 'number' && parsed.interval > 0 ? Math.floor(parsed.interval) : 5,
|
|
76
76
|
expiresAt: typeof parsed.expiresAt === 'string' && parsed.expiresAt ? parsed.expiresAt : null,
|
|
77
77
|
mode: parsed.mode === 'register' ? 'register' : 'login',
|
|
78
|
-
provider: parsed.provider === 'linkedin'
|
|
78
|
+
provider: parsed.provider === 'linkedin' || parsed.provider === 'email' ? parsed.provider : 'google',
|
|
79
79
|
registerContext: parsed.registerContext && typeof parsed.registerContext === 'object' ? parsed.registerContext : null,
|
|
80
80
|
email: typeof parsed.email === 'string' && parsed.email ? parsed.email : null,
|
|
81
81
|
};
|
package/src/device-login.js
CHANGED
|
@@ -10,15 +10,16 @@ function defaultSleep(ms) {
|
|
|
10
10
|
return new Promise((resolve) => { setTimeout(resolve, ms); });
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (!url || !
|
|
13
|
+
function withDeviceParams(url, params = {}) {
|
|
14
|
+
const entries = Object.entries(params).filter(([, v]) => typeof v === 'string' && v);
|
|
15
|
+
if (!url || !entries.length) return url;
|
|
16
16
|
try {
|
|
17
17
|
const u = new URL(url);
|
|
18
|
-
u.searchParams.set(
|
|
18
|
+
for (const [k, v] of entries) u.searchParams.set(k, v);
|
|
19
19
|
return u.href;
|
|
20
20
|
} catch {
|
|
21
|
-
|
|
21
|
+
const query = entries.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
|
|
22
|
+
return `${url}${url.includes('?') ? '&' : '?'}${query}`;
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -32,7 +33,7 @@ async function pollDeviceToken({ deviceCode, tokenEndpoint, clientId, timeoutMs
|
|
|
32
33
|
|
|
33
34
|
// ORDER: a NEW user needs complete-registration BEFORE GET /auth/token (else the Hub 500s: no domain user).
|
|
34
35
|
async function finalizeDeviceSession(
|
|
35
|
-
{ sessionToken, isNewUser, authTokenEndpoint, completeRegistrationEndpoint = null, registrationContext = null, claimCode = null, timeoutMs } = {},
|
|
36
|
+
{ sessionToken, isNewUser, authTokenEndpoint, completeRegistrationEndpoint = null, registrationContext = null, consent = null, claimCode = null, timeoutMs } = {},
|
|
36
37
|
{
|
|
37
38
|
requestCompleteRegistration = require('./signup-client').requestCompleteRegistration,
|
|
38
39
|
requestApiToken: apiToken = requestApiToken,
|
|
@@ -42,7 +43,7 @@ async function finalizeDeviceSession(
|
|
|
42
43
|
|
|
43
44
|
let claimed = false;
|
|
44
45
|
if (isNewUser && completeRegistrationEndpoint) {
|
|
45
|
-
const done = await requestCompleteRegistration(registrationContext || {}, { endpoint: completeRegistrationEndpoint, bearerToken: sessionToken, claimCode, timeoutMs });
|
|
46
|
+
const done = await requestCompleteRegistration(registrationContext || {}, { endpoint: completeRegistrationEndpoint, bearerToken: sessionToken, claimCode, consent, timeoutMs });
|
|
46
47
|
if (!done.ok) return { ok: false, reason: done.reason || 'complete-registration-failed' };
|
|
47
48
|
claimed = done.claimed === true;
|
|
48
49
|
}
|
|
@@ -53,7 +54,7 @@ async function finalizeDeviceSession(
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
async function runDeviceLogin(
|
|
56
|
-
{ authorizeEndpoint, tokenEndpoint, authTokenEndpoint, completeRegistrationEndpoint = null, registrationContext = null, clientId, scope, timeoutMs } = {},
|
|
57
|
+
{ authorizeEndpoint, tokenEndpoint, authTokenEndpoint, completeRegistrationEndpoint = null, registrationContext = null, consent = null, clientId, scope, timeoutMs } = {},
|
|
57
58
|
{
|
|
58
59
|
onPrompt = null,
|
|
59
60
|
onWaiting = null,
|
|
@@ -92,6 +93,7 @@ async function runDeviceLogin(
|
|
|
92
93
|
authTokenEndpoint,
|
|
93
94
|
completeRegistrationEndpoint,
|
|
94
95
|
registrationContext,
|
|
96
|
+
consent,
|
|
95
97
|
timeoutMs,
|
|
96
98
|
});
|
|
97
99
|
if (!fin.ok) return { ok: false, reason: fin.reason };
|
|
@@ -124,7 +126,7 @@ module.exports = {
|
|
|
124
126
|
pollDeviceToken,
|
|
125
127
|
finalizeDeviceSession,
|
|
126
128
|
runDeviceLogin,
|
|
127
|
-
|
|
129
|
+
withDeviceParams,
|
|
128
130
|
defaultSleep,
|
|
129
131
|
SLOW_DOWN_STEP_SECONDS,
|
|
130
132
|
DEFAULT_MAX_WAIT_MS,
|
package/src/env-paths.js
CHANGED
|
@@ -13,6 +13,14 @@ function getHomeDir(env = process.env) {
|
|
|
13
13
|
return readEnv(env, 'HOME_DIR') || os.homedir();
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function codexHome(env = process.env) {
|
|
17
|
+
return env.CODEX_HOME || require('path').join(getHomeDir(env), '.codex');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function piAgentDir(env = process.env) {
|
|
21
|
+
return env.PI_CODING_AGENT_DIR ? expandHome(env.PI_CODING_AGENT_DIR, env) : require('path').join(getHomeDir(env), '.pi', 'agent');
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
// `~/Documents/cv.pdf` -> absolute, so a model can pass the short form it was shown.
|
|
17
25
|
function expandHome(file, env = process.env) {
|
|
18
26
|
if (typeof file !== 'string') return file;
|
|
@@ -27,4 +35,4 @@ function tildify(file, env = process.env) {
|
|
|
27
35
|
return typeof file === 'string' && file.startsWith(`${home}/`) ? `~/${file.slice(home.length + 1)}` : file;
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
module.exports = { getHomeDir, readEnv, expandHome, tildify };
|
|
38
|
+
module.exports = { getHomeDir, codexHome, piAgentDir, readEnv, expandHome, tildify };
|