overlord-cli 4.17.0 → 4.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/bin/_cli/attach.mjs +5 -4
- package/bin/_cli/auth.mjs +117 -35
- package/bin/_cli/credentials.mjs +271 -53
- package/bin/_cli/launcher.mjs +20 -6
- package/bin/_cli/new-ticket.mjs +7 -7
- package/bin/_cli/protocol.mjs +79 -42
- package/bin/_cli/ticket.mjs +2 -2
- package/bin/_cli/tickets.mjs +5 -4
- package/package.json +1 -1
package/bin/_cli/attach.mjs
CHANGED
|
@@ -58,11 +58,11 @@ function statusColor(status) {
|
|
|
58
58
|
|
|
59
59
|
// ─── API ──────────────────────────────────────────────────────────────────────
|
|
60
60
|
|
|
61
|
-
async function searchTickets(platformUrl,
|
|
61
|
+
async function searchTickets(platformUrl, bearerToken, localSecret, organizationId, query) {
|
|
62
62
|
const res = await fetch(`${platformUrl}/api/protocol/search-tickets`, {
|
|
63
63
|
method: 'POST',
|
|
64
64
|
headers: {
|
|
65
|
-
...buildAuthHeaders(
|
|
65
|
+
...buildAuthHeaders(bearerToken, localSecret, organizationId),
|
|
66
66
|
'Content-Type': 'application/json'
|
|
67
67
|
},
|
|
68
68
|
body: JSON.stringify({
|
|
@@ -287,7 +287,7 @@ function runInteractivePrompt({ label, items = [], search, prefix = '' }) {
|
|
|
287
287
|
export async function runAttachCommand(args) {
|
|
288
288
|
const [ticketIdArg, agentArg] = args;
|
|
289
289
|
|
|
290
|
-
const { platformUrl,
|
|
290
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
291
291
|
|
|
292
292
|
// ── Phase 1: Ticket selection ──────────────────────────────────────────────
|
|
293
293
|
|
|
@@ -301,7 +301,8 @@ export async function runAttachCommand(args) {
|
|
|
301
301
|
|
|
302
302
|
const selectedTicket = await runInteractivePrompt({
|
|
303
303
|
label: 'Search tickets',
|
|
304
|
-
search: nextQuery =>
|
|
304
|
+
search: nextQuery =>
|
|
305
|
+
searchTickets(platformUrl, bearerToken, localSecret, organizationId, nextQuery)
|
|
305
306
|
});
|
|
306
307
|
|
|
307
308
|
if (!selectedTicket) {
|
package/bin/_cli/auth.mjs
CHANGED
|
@@ -240,23 +240,6 @@ async function exchangeCodeForSupabaseTokens(supabaseUrl, clientId, code, codeVe
|
|
|
240
240
|
return readJsonOrThrow(res, 'Token exchange', supabaseUrl);
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
async function exchangeForAgentToken(platformUrl, supabaseAccessToken, localSecret) {
|
|
244
|
-
const res = await fetch(`${platformUrl}/api/auth/token`, {
|
|
245
|
-
method: 'POST',
|
|
246
|
-
headers: {
|
|
247
|
-
...buildAuthHeaders('', localSecret),
|
|
248
|
-
Authorization: `Bearer ${supabaseAccessToken}`
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
if (!res.ok) {
|
|
253
|
-
const text = await res.text();
|
|
254
|
-
throw new Error(`Agent token exchange failed (${res.status}): ${snippet(text)}`);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return readJsonOrThrow(res, 'Agent token exchange', platformUrl);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
243
|
function openBrowser(url) {
|
|
261
244
|
try {
|
|
262
245
|
const platform = process.platform;
|
|
@@ -331,6 +314,8 @@ export async function authLoginViaDeviceFlow(
|
|
|
331
314
|
logger.log('\n');
|
|
332
315
|
return {
|
|
333
316
|
access_token: result.access_token,
|
|
317
|
+
access_token_expires_at: result.access_token_expires_at ?? null,
|
|
318
|
+
refresh_token: result.refresh_token,
|
|
334
319
|
platform_url: result.platform_url ?? platformUrl
|
|
335
320
|
};
|
|
336
321
|
}
|
|
@@ -403,19 +388,88 @@ export async function authLoginViaOAuthLoopback(platformUrl, localSecret) {
|
|
|
403
388
|
redirectUri
|
|
404
389
|
);
|
|
405
390
|
|
|
406
|
-
// 8. Exchange Supabase access token → Overlord agent_token
|
|
407
|
-
const agentTokenData = await exchangeForAgentToken(
|
|
408
|
-
resolvedPlatformUrl,
|
|
409
|
-
supabaseTokens.access_token,
|
|
410
|
-
localSecret
|
|
411
|
-
);
|
|
412
|
-
|
|
413
391
|
return {
|
|
414
|
-
access_token:
|
|
415
|
-
|
|
392
|
+
access_token: supabaseTokens.access_token,
|
|
393
|
+
access_token_expires_at:
|
|
394
|
+
typeof supabaseTokens.expires_in === 'number' && supabaseTokens.expires_in > 0
|
|
395
|
+
? new Date(Date.now() + supabaseTokens.expires_in * 1000).toISOString()
|
|
396
|
+
: null,
|
|
397
|
+
refresh_token: supabaseTokens.refresh_token,
|
|
398
|
+
platform_url: resolvedPlatformUrl
|
|
416
399
|
};
|
|
417
400
|
}
|
|
418
401
|
|
|
402
|
+
async function fetchOrganizations(platformUrl, accessToken, localSecret) {
|
|
403
|
+
const res = await fetch(`${platformUrl}/api/auth/organizations`, {
|
|
404
|
+
headers: {
|
|
405
|
+
...buildAuthHeaders('', localSecret),
|
|
406
|
+
Authorization: `Bearer ${accessToken}`
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
if (!res.ok) {
|
|
411
|
+
const text = await res.text().catch(() => '');
|
|
412
|
+
throw new Error(`Failed to load organizations (${res.status}): ${snippet(text)}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const data = await readJsonOrThrow(res, 'Organizations', platformUrl);
|
|
416
|
+
return Array.isArray(data.organizations) ? data.organizations : [];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async function promptForOrganization(organizations, preselectedId = null) {
|
|
420
|
+
if (!organizations.length) {
|
|
421
|
+
throw new Error('No organizations found. Please complete onboarding first.');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (preselectedId !== null) {
|
|
425
|
+
const match = organizations.find(org => org.id === preselectedId);
|
|
426
|
+
if (!match) {
|
|
427
|
+
throw new Error(
|
|
428
|
+
`Organization ${preselectedId} is not available to this account. ` +
|
|
429
|
+
`Available: ${organizations.map(o => o.id).join(', ')}`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
return match;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (organizations.length === 1) {
|
|
436
|
+
return organizations[0];
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!process.stdin.isTTY) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
'Multiple organizations available but stdin is not a TTY. ' +
|
|
442
|
+
'Pass --organization-id <id> to select non-interactively.'
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const rl = (await import('node:readline')).createInterface({
|
|
447
|
+
input: process.stdin,
|
|
448
|
+
output: process.stdout
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
for (;;) {
|
|
453
|
+
console.log('\nOrganizations');
|
|
454
|
+
organizations.forEach((organization, index) => {
|
|
455
|
+
console.log(` ${index + 1}. ${organization.name} (${organization.id})`);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const answer = await new Promise(resolve => {
|
|
459
|
+
rl.question('\nSelect an organization by number: ', resolve);
|
|
460
|
+
});
|
|
461
|
+
const selected = Number.parseInt(String(answer).trim(), 10);
|
|
462
|
+
if (Number.isFinite(selected) && selected >= 1 && selected <= organizations.length) {
|
|
463
|
+
return organizations[selected - 1];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
console.log(`Enter a number between 1 and ${organizations.length}.`);
|
|
467
|
+
}
|
|
468
|
+
} finally {
|
|
469
|
+
rl.close();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
419
473
|
// ---------------------------------------------------------------------------
|
|
420
474
|
// Public auth commands
|
|
421
475
|
// ---------------------------------------------------------------------------
|
|
@@ -424,7 +478,19 @@ export function resolveLoginPlatformUrl(runtime = null) {
|
|
|
424
478
|
return process.env.OVERLORD_URL ?? runtime?.platform_url ?? getDefaultOverlordUrl();
|
|
425
479
|
}
|
|
426
480
|
|
|
427
|
-
|
|
481
|
+
function parseOrganizationFlag(args) {
|
|
482
|
+
const index = args.findIndex(arg => arg === '--organization-id' || arg.startsWith('--organization-id='));
|
|
483
|
+
if (index === -1) return null;
|
|
484
|
+
const raw = args[index].includes('=') ? args[index].split('=')[1] : args[index + 1];
|
|
485
|
+
const parsed = Number.parseInt(String(raw ?? ''), 10);
|
|
486
|
+
if (!Number.isFinite(parsed)) {
|
|
487
|
+
throw new Error('--organization-id must be a numeric id');
|
|
488
|
+
}
|
|
489
|
+
return parsed;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export async function authLogin(args = []) {
|
|
493
|
+
const preselectedOrganizationId = parseOrganizationFlag(args);
|
|
428
494
|
const platformUrl = resolveLoginPlatformUrl();
|
|
429
495
|
const runtime = loadRuntime(platformUrl);
|
|
430
496
|
const localSecret = runtime?.local_secret ?? process.env.OVERLORD_LOCAL_SECRET ?? '';
|
|
@@ -454,16 +520,27 @@ export async function authLogin() {
|
|
|
454
520
|
}
|
|
455
521
|
}
|
|
456
522
|
|
|
523
|
+
const resolvedPlatformUrl = credentials.platform_url ?? platformUrl;
|
|
524
|
+
const organizations = await fetchOrganizations(
|
|
525
|
+
resolvedPlatformUrl,
|
|
526
|
+
credentials.access_token,
|
|
527
|
+
localSecret
|
|
528
|
+
);
|
|
529
|
+
const selectedOrganization = await promptForOrganization(organizations, preselectedOrganizationId);
|
|
530
|
+
|
|
457
531
|
saveCredentials({
|
|
458
532
|
access_token: credentials.access_token,
|
|
459
|
-
|
|
533
|
+
access_token_expires_at: credentials.access_token_expires_at ?? undefined,
|
|
534
|
+
refresh_token: credentials.refresh_token,
|
|
535
|
+
organization_id: selectedOrganization.id,
|
|
536
|
+
platform_url: resolvedPlatformUrl
|
|
460
537
|
});
|
|
461
538
|
|
|
462
539
|
console.log('Logged in successfully!');
|
|
463
540
|
}
|
|
464
541
|
|
|
465
|
-
function printVerboseAuthStatus() {
|
|
466
|
-
const status = getAuthStatus();
|
|
542
|
+
async function printVerboseAuthStatus() {
|
|
543
|
+
const status = await getAuthStatus();
|
|
467
544
|
if (!status.isLoggedIn) {
|
|
468
545
|
console.log('Not logged in. Run: ovld auth login');
|
|
469
546
|
} else {
|
|
@@ -473,6 +550,11 @@ function printVerboseAuthStatus() {
|
|
|
473
550
|
console.log(` Platform source: ${status.platformUrlSource}`);
|
|
474
551
|
console.log(` Token source: ${status.tokenSource}`);
|
|
475
552
|
console.log(` Token present: ${status.tokenPresent ? 'yes' : 'no'}`);
|
|
553
|
+
console.log(` Auth mode: ${status.authMode}`);
|
|
554
|
+
console.log(` Organization ID: ${status.organizationId ?? 'none'}`);
|
|
555
|
+
if (status.error) {
|
|
556
|
+
console.log(` Error: ${status.error}`);
|
|
557
|
+
}
|
|
476
558
|
console.log(` Local secret: ${status.hasLocalSecret ? 'yes' : 'no'}`);
|
|
477
559
|
console.log(` credentials.json: ${status.credentialsFileExists ? 'present' : 'missing'}`);
|
|
478
560
|
console.log(
|
|
@@ -480,9 +562,9 @@ function printVerboseAuthStatus() {
|
|
|
480
562
|
);
|
|
481
563
|
}
|
|
482
564
|
|
|
483
|
-
export function authStatus(args = []) {
|
|
565
|
+
export async function authStatus(args = []) {
|
|
484
566
|
if (args.includes('--verbose') || args.includes('-v')) {
|
|
485
|
-
printVerboseAuthStatus();
|
|
567
|
+
await printVerboseAuthStatus();
|
|
486
568
|
return;
|
|
487
569
|
}
|
|
488
570
|
|
|
@@ -510,7 +592,7 @@ export function authRepair() {
|
|
|
510
592
|
} else {
|
|
511
593
|
console.log(`Credentials not repaired: ${result.reason}`);
|
|
512
594
|
}
|
|
513
|
-
printVerboseAuthStatus();
|
|
595
|
+
void printVerboseAuthStatus();
|
|
514
596
|
}
|
|
515
597
|
|
|
516
598
|
export async function runAuthCommand(subcommand, args = []) {
|
|
@@ -527,12 +609,12 @@ Subcommands:
|
|
|
527
609
|
}
|
|
528
610
|
|
|
529
611
|
if (subcommand === 'login') {
|
|
530
|
-
await authLogin();
|
|
612
|
+
await authLogin(args);
|
|
531
613
|
return;
|
|
532
614
|
}
|
|
533
615
|
|
|
534
616
|
if (subcommand === 'status') {
|
|
535
|
-
authStatus(args);
|
|
617
|
+
await authStatus(args);
|
|
536
618
|
return;
|
|
537
619
|
}
|
|
538
620
|
|
package/bin/_cli/credentials.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
/* global process, URL */
|
|
3
|
+
/* global Buffer, fetch, process, URL, URLSearchParams */
|
|
4
4
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import os from 'node:os';
|
|
@@ -16,7 +16,15 @@ const LOCAL_DEV_OVERLORD_URL = 'http://localhost:3000';
|
|
|
16
16
|
const LOCAL_SECRET_HEADER = 'X-Overlord-Local-Secret';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* @typedef {{
|
|
19
|
+
* @typedef {{
|
|
20
|
+
* access_token?: string,
|
|
21
|
+
* access_token_expires_at?: string,
|
|
22
|
+
* refresh_token?: string,
|
|
23
|
+
* organization_id?: number | null,
|
|
24
|
+
* platform_url: string,
|
|
25
|
+
* user_email?: string,
|
|
26
|
+
* legacy_agent_token?: string
|
|
27
|
+
* }} Credentials
|
|
20
28
|
*/
|
|
21
29
|
|
|
22
30
|
function ensureCredentialsDir() {
|
|
@@ -53,44 +61,74 @@ function writeJsonFileAtomic(filePath, data) {
|
|
|
53
61
|
fs.chmodSync(filePath, 0o600);
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
function parseStoredCredentialsData(parsed, {
|
|
64
|
+
function parseStoredCredentialsData(parsed, { requireAuthData = false } = {}) {
|
|
57
65
|
if (!parsed || typeof parsed !== 'object') return null;
|
|
58
66
|
|
|
59
|
-
const accessToken = normalizeAgentToken(parsed.access_token);
|
|
60
67
|
const platformUrl = typeof parsed.platform_url === 'string' ? parsed.platform_url.trim() : '';
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
const refreshToken =
|
|
69
|
+
typeof parsed.refresh_token === 'string'
|
|
70
|
+
? parsed.refresh_token.trim()
|
|
71
|
+
: typeof parsed.supabase_refresh_token === 'string'
|
|
72
|
+
? parsed.supabase_refresh_token.trim()
|
|
73
|
+
: '';
|
|
74
|
+
const accessToken = typeof parsed.access_token === 'string' ? parsed.access_token.trim() : '';
|
|
75
|
+
const accessTokenExpiresAt =
|
|
76
|
+
typeof parsed.access_token_expires_at === 'string'
|
|
77
|
+
? parsed.access_token_expires_at.trim()
|
|
78
|
+
: '';
|
|
79
|
+
const organizationId =
|
|
80
|
+
typeof parsed.organization_id === 'number' && Number.isFinite(parsed.organization_id)
|
|
81
|
+
? parsed.organization_id
|
|
82
|
+
: null;
|
|
83
|
+
const legacyAgentToken = accessToken && !refreshToken ? accessToken : '';
|
|
84
|
+
|
|
85
|
+
if (!platformUrl) return null;
|
|
86
|
+
if (requireAuthData && !refreshToken && !legacyAgentToken) return null;
|
|
63
87
|
|
|
64
88
|
return {
|
|
65
|
-
access_token: accessToken,
|
|
66
89
|
platform_url: platformUrl,
|
|
90
|
+
...(refreshToken ? { refresh_token: refreshToken } : {}),
|
|
91
|
+
...(accessToken ? { access_token: accessToken } : {}),
|
|
92
|
+
...(accessTokenExpiresAt ? { access_token_expires_at: accessTokenExpiresAt } : {}),
|
|
93
|
+
...(organizationId ? { organization_id: organizationId } : {}),
|
|
67
94
|
...(typeof parsed.user_email === 'string' && parsed.user_email.trim()
|
|
68
95
|
? { user_email: parsed.user_email.trim() }
|
|
69
|
-
: {})
|
|
96
|
+
: {}),
|
|
97
|
+
...(legacyAgentToken ? { legacy_agent_token: legacyAgentToken } : {})
|
|
70
98
|
};
|
|
71
99
|
}
|
|
72
100
|
|
|
73
101
|
function normalizeCredentialsForSave(data) {
|
|
74
|
-
const parsed = parseStoredCredentialsData(data, {
|
|
102
|
+
const parsed = parseStoredCredentialsData(data, { requireAuthData: true });
|
|
75
103
|
if (!parsed) return null;
|
|
76
104
|
|
|
77
105
|
const platformUrl = normalizePlatformUrl(parsed.platform_url);
|
|
78
106
|
if (!platformUrl) return null;
|
|
79
107
|
|
|
80
108
|
return {
|
|
81
|
-
|
|
82
|
-
|
|
109
|
+
platform_url: platformUrl,
|
|
110
|
+
...(parsed.refresh_token ? { refresh_token: parsed.refresh_token } : {}),
|
|
111
|
+
...(parsed.access_token ? { access_token: parsed.access_token } : {}),
|
|
112
|
+
...(parsed.access_token_expires_at
|
|
113
|
+
? { access_token_expires_at: parsed.access_token_expires_at }
|
|
114
|
+
: {}),
|
|
115
|
+
...(parsed.organization_id ? { organization_id: parsed.organization_id } : {}),
|
|
116
|
+
...(parsed.user_email ? { user_email: parsed.user_email } : {})
|
|
83
117
|
};
|
|
84
118
|
}
|
|
85
119
|
|
|
86
120
|
/** @returns {Credentials | null} */
|
|
87
121
|
export function loadCredentials() {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
);
|
|
122
|
+
const cliCredentials = parseStoredCredentialsData(readJsonFile(CREDENTIALS_FILE), {
|
|
123
|
+
requireAuthData: true
|
|
124
|
+
});
|
|
125
|
+
const electronCredentials = parseStoredCredentialsData(readJsonFile(ELECTRON_CREDENTIALS_FILE), {
|
|
126
|
+
requireAuthData: true
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (cliCredentials?.refresh_token) return cliCredentials;
|
|
130
|
+
if (electronCredentials?.refresh_token) return electronCredentials;
|
|
131
|
+
return cliCredentials ?? electronCredentials;
|
|
94
132
|
}
|
|
95
133
|
|
|
96
134
|
/** @param {Credentials} data */
|
|
@@ -100,16 +138,23 @@ export function saveCredentials(data) {
|
|
|
100
138
|
throw new Error('Cannot save empty Overlord credentials.');
|
|
101
139
|
}
|
|
102
140
|
|
|
103
|
-
|
|
141
|
+
const sharedCredentials = { ...credentials, updated_at: new Date().toISOString() };
|
|
142
|
+
writeJsonFileAtomic(CREDENTIALS_FILE, sharedCredentials);
|
|
104
143
|
|
|
105
|
-
// `electron-credentials.json` is now the shared desktop/CLI credential record.
|
|
106
|
-
// Preserve Electron-only encrypted fields when CLI login refreshes the agent token.
|
|
107
144
|
const existingElectronCredentials = readJsonFile(ELECTRON_CREDENTIALS_FILE);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
145
|
+
if (existingElectronCredentials && typeof existingElectronCredentials === 'object') {
|
|
146
|
+
const electronPayload = { ...existingElectronCredentials };
|
|
147
|
+
electronPayload.updated_at = sharedCredentials.updated_at;
|
|
148
|
+
if (credentials.platform_url) electronPayload.platform_url = credentials.platform_url;
|
|
149
|
+
if (credentials.access_token_expires_at) {
|
|
150
|
+
electronPayload.access_token_expires_at = credentials.access_token_expires_at;
|
|
151
|
+
}
|
|
152
|
+
if (credentials.organization_id) electronPayload.organization_id = credentials.organization_id;
|
|
153
|
+
if (credentials.user_email) electronPayload.user_email = credentials.user_email;
|
|
154
|
+
delete electronPayload.legacy_agent_token;
|
|
155
|
+
delete electronPayload.supabase_refresh_token;
|
|
156
|
+
writeJsonFileAtomic(ELECTRON_CREDENTIALS_FILE, electronPayload);
|
|
157
|
+
}
|
|
113
158
|
}
|
|
114
159
|
|
|
115
160
|
export function clearCredentials() {
|
|
@@ -123,13 +168,16 @@ export function clearCredentials() {
|
|
|
123
168
|
}
|
|
124
169
|
|
|
125
170
|
function getCredentialFileSource() {
|
|
171
|
+
const cliCredentials = parseStoredCredentialsData(readJsonFile(CREDENTIALS_FILE), {
|
|
172
|
+
requireAuthData: true
|
|
173
|
+
});
|
|
126
174
|
const electronCredentials = parseStoredCredentialsData(readJsonFile(ELECTRON_CREDENTIALS_FILE), {
|
|
127
|
-
|
|
175
|
+
requireAuthData: true
|
|
128
176
|
});
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
const cliCredentials = parseStoredCredentialsData(readJsonFile(CREDENTIALS_FILE));
|
|
177
|
+
if (cliCredentials?.refresh_token) return 'credentials.json';
|
|
178
|
+
if (electronCredentials?.refresh_token) return 'electron-credentials.json';
|
|
132
179
|
if (cliCredentials) return 'credentials.json';
|
|
180
|
+
if (electronCredentials) return 'electron-credentials.json';
|
|
133
181
|
|
|
134
182
|
return 'none';
|
|
135
183
|
}
|
|
@@ -261,6 +309,88 @@ function isRunningPid(pid) {
|
|
|
261
309
|
}
|
|
262
310
|
}
|
|
263
311
|
|
|
312
|
+
function decodeJwtExpiry(accessToken) {
|
|
313
|
+
try {
|
|
314
|
+
const payload = JSON.parse(Buffer.from(accessToken.split('.')[1] ?? '', 'base64url').toString('utf8'));
|
|
315
|
+
return typeof payload.exp === 'number' ? payload.exp : null;
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function computeAccessTokenExpiry(data) {
|
|
322
|
+
const expiresIn = Number.parseInt(String(data?.expires_in ?? ''), 10);
|
|
323
|
+
if (Number.isFinite(expiresIn) && expiresIn > 0) {
|
|
324
|
+
return new Date(Date.now() + expiresIn * 1000).toISOString();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const jwtExp = typeof data?.access_token === 'string' ? decodeJwtExpiry(data.access_token) : null;
|
|
328
|
+
return jwtExp ? new Date(jwtExp * 1000).toISOString() : null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function resolveAccessTokenExpiry(credentials) {
|
|
332
|
+
if (!credentials?.access_token) return null;
|
|
333
|
+
if (credentials.access_token_expires_at) {
|
|
334
|
+
const parsed = Date.parse(credentials.access_token_expires_at);
|
|
335
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
336
|
+
}
|
|
337
|
+
const jwtExp = decodeJwtExpiry(credentials.access_token);
|
|
338
|
+
return jwtExp ? jwtExp * 1000 : null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function isAccessTokenFresh(credentials) {
|
|
342
|
+
const expiresAt = resolveAccessTokenExpiry(credentials);
|
|
343
|
+
if (expiresAt === null) return false;
|
|
344
|
+
return expiresAt - Date.now() > 60_000;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const authConfigCache = new Map();
|
|
348
|
+
|
|
349
|
+
async function fetchAuthConfig(platformUrl, localSecret) {
|
|
350
|
+
if (authConfigCache.has(platformUrl)) return authConfigCache.get(platformUrl);
|
|
351
|
+
const res = await fetch(`${platformUrl}/api/auth/config`, {
|
|
352
|
+
headers: buildAuthHeaders('', localSecret)
|
|
353
|
+
});
|
|
354
|
+
if (!res.ok) {
|
|
355
|
+
throw new Error(`Failed to fetch auth config (${res.status}).`);
|
|
356
|
+
}
|
|
357
|
+
const data = await res.json();
|
|
358
|
+
authConfigCache.set(platformUrl, data);
|
|
359
|
+
return data;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function refreshOAuthAccessToken(platformUrl, refreshToken, localSecret) {
|
|
363
|
+
const config = await fetchAuthConfig(platformUrl, localSecret);
|
|
364
|
+
const clientId = config.cli_client_id ?? config.electron_client_id;
|
|
365
|
+
const supabaseUrl = config.supabase_url;
|
|
366
|
+
|
|
367
|
+
if (!supabaseUrl || !clientId) {
|
|
368
|
+
throw new Error('OAuth is not configured for Overlord CLI auth.');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const res = await fetch(`${supabaseUrl}/auth/v1/oauth/token`, {
|
|
372
|
+
method: 'POST',
|
|
373
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
374
|
+
body: new URLSearchParams({
|
|
375
|
+
grant_type: 'refresh_token',
|
|
376
|
+
refresh_token: refreshToken,
|
|
377
|
+
client_id: clientId
|
|
378
|
+
})
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (!res.ok) {
|
|
382
|
+
const text = await res.text().catch(() => '');
|
|
383
|
+
throw new Error(`OAuth token refresh failed (${res.status}): ${text}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const data = await res.json();
|
|
387
|
+
return {
|
|
388
|
+
access_token: data.access_token,
|
|
389
|
+
refresh_token: data.refresh_token,
|
|
390
|
+
access_token_expires_at: computeAccessTokenExpiry(data)
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
264
394
|
/** @returns {{ platform_url?: string, local_secret?: string } | null} */
|
|
265
395
|
export function loadRuntime(targetUrl) {
|
|
266
396
|
if (targetUrl) {
|
|
@@ -284,9 +414,10 @@ export function loadRuntime(targetUrl) {
|
|
|
284
414
|
/**
|
|
285
415
|
* @param {string} token
|
|
286
416
|
* @param {string} [localSecret]
|
|
417
|
+
* @param {number | null | undefined} [organizationId]
|
|
287
418
|
* @returns {Record<string, string>}
|
|
288
419
|
*/
|
|
289
|
-
export function buildAuthHeaders(token, localSecret) {
|
|
420
|
+
export function buildAuthHeaders(token, localSecret, organizationId) {
|
|
290
421
|
const headers = {};
|
|
291
422
|
|
|
292
423
|
if (token) {
|
|
@@ -297,6 +428,10 @@ export function buildAuthHeaders(token, localSecret) {
|
|
|
297
428
|
headers[LOCAL_SECRET_HEADER] = localSecret;
|
|
298
429
|
}
|
|
299
430
|
|
|
431
|
+
if (organizationId && Number.isFinite(organizationId)) {
|
|
432
|
+
headers['x-organization-id'] = String(organizationId);
|
|
433
|
+
}
|
|
434
|
+
|
|
300
435
|
return headers;
|
|
301
436
|
}
|
|
302
437
|
|
|
@@ -310,23 +445,17 @@ function isLocalDevCli() {
|
|
|
310
445
|
}
|
|
311
446
|
|
|
312
447
|
/**
|
|
313
|
-
* Resolve the
|
|
314
|
-
*
|
|
448
|
+
* Resolve the Overlord auth session from env vars or shared credentials.
|
|
449
|
+
* Refreshes OAuth access tokens when possible.
|
|
315
450
|
*/
|
|
316
|
-
export function resolveAuth() {
|
|
451
|
+
export async function resolveAuth() {
|
|
317
452
|
const creds = loadCredentials();
|
|
318
453
|
const overlordUrlFromEnv = normalizePlatformUrl(process.env.OVERLORD_URL);
|
|
319
454
|
const overlordUrlFromCreds = normalizeStoredPlatformUrl(creds?.platform_url);
|
|
320
455
|
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
: null;
|
|
456
|
+
const platformUrl = overlordUrlFromEnv ?? overlordUrlFromCreds ?? getDefaultOverlordUrl();
|
|
457
|
+
const runtime = isLocalhostUrl(platformUrl) ? loadRuntime(platformUrl) : null;
|
|
324
458
|
const runtimeOverlordUrl = runtime?.platform_url;
|
|
325
|
-
|
|
326
|
-
const platformUrl =
|
|
327
|
-
overlordUrlFromEnv ??
|
|
328
|
-
overlordUrlFromCreds ??
|
|
329
|
-
getDefaultOverlordUrl();
|
|
330
459
|
const localSecret =
|
|
331
460
|
runtime &&
|
|
332
461
|
runtime.local_secret &&
|
|
@@ -336,25 +465,111 @@ export function resolveAuth() {
|
|
|
336
465
|
? runtime.local_secret
|
|
337
466
|
: '';
|
|
338
467
|
|
|
468
|
+
const envAgentToken = normalizeAgentToken(process.env.AGENT_TOKEN);
|
|
469
|
+
if (envAgentToken) {
|
|
470
|
+
return {
|
|
471
|
+
platformUrl,
|
|
472
|
+
bearerToken: envAgentToken,
|
|
473
|
+
localSecret,
|
|
474
|
+
organizationId:
|
|
475
|
+
typeof process.env.OVERLORD_ORGANIZATION_ID === 'string'
|
|
476
|
+
? Number.parseInt(process.env.OVERLORD_ORGANIZATION_ID, 10)
|
|
477
|
+
: null,
|
|
478
|
+
authMode: 'legacy_agent_token'
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (!creds) {
|
|
483
|
+
return {
|
|
484
|
+
platformUrl,
|
|
485
|
+
bearerToken: 'overlord-local-dev-token',
|
|
486
|
+
localSecret,
|
|
487
|
+
organizationId: null,
|
|
488
|
+
authMode: 'local_fallback'
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (creds.refresh_token) {
|
|
493
|
+
let nextCredentials = creds;
|
|
494
|
+
if (!isAccessTokenFresh(creds)) {
|
|
495
|
+
try {
|
|
496
|
+
const refreshed = await refreshOAuthAccessToken(platformUrl, creds.refresh_token, localSecret);
|
|
497
|
+
nextCredentials = {
|
|
498
|
+
...creds,
|
|
499
|
+
access_token: refreshed.access_token,
|
|
500
|
+
access_token_expires_at: refreshed.access_token_expires_at,
|
|
501
|
+
refresh_token: refreshed.refresh_token || creds.refresh_token
|
|
502
|
+
};
|
|
503
|
+
saveCredentials(nextCredentials);
|
|
504
|
+
} catch (refreshError) {
|
|
505
|
+
if (!creds.access_token) throw refreshError;
|
|
506
|
+
// Transient refresh failure — keep the existing access token and let the server
|
|
507
|
+
// reject it if it's truly expired/revoked.
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (!Number.isFinite(nextCredentials.organization_id)) {
|
|
512
|
+
throw new Error('Overlord login is missing an organization selection. Run `ovld auth login` again.');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (!nextCredentials.access_token) {
|
|
516
|
+
throw new Error('No OAuth access token is available. Run `ovld auth login` again.');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
platformUrl,
|
|
521
|
+
bearerToken: nextCredentials.access_token,
|
|
522
|
+
localSecret,
|
|
523
|
+
organizationId: nextCredentials.organization_id,
|
|
524
|
+
authMode: 'oauth'
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (creds.legacy_agent_token) {
|
|
529
|
+
return {
|
|
530
|
+
platformUrl,
|
|
531
|
+
bearerToken: creds.legacy_agent_token,
|
|
532
|
+
localSecret,
|
|
533
|
+
organizationId: creds.organization_id ?? null,
|
|
534
|
+
authMode: 'legacy_agent_token'
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
339
538
|
return {
|
|
340
539
|
platformUrl,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
localSecret
|
|
540
|
+
bearerToken: 'overlord-local-dev-token',
|
|
541
|
+
localSecret,
|
|
542
|
+
organizationId: null,
|
|
543
|
+
authMode: 'local_fallback'
|
|
346
544
|
};
|
|
347
545
|
}
|
|
348
546
|
|
|
349
|
-
export function getAuthStatus() {
|
|
547
|
+
export async function getAuthStatus() {
|
|
350
548
|
const creds = loadCredentials();
|
|
351
|
-
|
|
549
|
+
let resolved;
|
|
550
|
+
let error = null;
|
|
551
|
+
try {
|
|
552
|
+
resolved = await resolveAuth();
|
|
553
|
+
} catch (resolveError) {
|
|
554
|
+
error = resolveError instanceof Error ? resolveError.message : String(resolveError);
|
|
555
|
+
resolved = {
|
|
556
|
+
platformUrl:
|
|
557
|
+
normalizePlatformUrl(process.env.OVERLORD_URL) ??
|
|
558
|
+
normalizeStoredPlatformUrl(creds?.platform_url) ??
|
|
559
|
+
getDefaultOverlordUrl(),
|
|
560
|
+
localSecret: '',
|
|
561
|
+
organizationId: creds?.organization_id ?? null,
|
|
562
|
+
authMode: 'error'
|
|
563
|
+
};
|
|
564
|
+
}
|
|
352
565
|
|
|
353
566
|
let tokenSource = 'fallback';
|
|
354
567
|
if (normalizeAgentToken(process.env.AGENT_TOKEN)) {
|
|
355
568
|
tokenSource = 'AGENT_TOKEN';
|
|
356
|
-
} else if (
|
|
569
|
+
} else if (creds?.refresh_token) {
|
|
357
570
|
tokenSource = getCredentialFileSource();
|
|
571
|
+
} else if (creds?.legacy_agent_token) {
|
|
572
|
+
tokenSource = `${getCredentialFileSource()} (legacy)`;
|
|
358
573
|
}
|
|
359
574
|
|
|
360
575
|
let platformUrlSource = 'default';
|
|
@@ -371,6 +586,9 @@ export function getAuthStatus() {
|
|
|
371
586
|
tokenPresent: tokenSource !== 'fallback',
|
|
372
587
|
tokenSource,
|
|
373
588
|
hasLocalSecret: Boolean(resolved.localSecret),
|
|
589
|
+
organizationId: resolved.organizationId ?? null,
|
|
590
|
+
authMode: resolved.authMode,
|
|
591
|
+
error,
|
|
374
592
|
credentialsFileExists: fileExists(CREDENTIALS_FILE),
|
|
375
593
|
electronCredentialsFileExists: fileExists(ELECTRON_CREDENTIALS_FILE)
|
|
376
594
|
};
|
|
@@ -378,12 +596,12 @@ export function getAuthStatus() {
|
|
|
378
596
|
|
|
379
597
|
export function repairCredentials() {
|
|
380
598
|
const creds = loadCredentials();
|
|
381
|
-
if (!creds
|
|
599
|
+
if (!creds) {
|
|
382
600
|
ensureCredentialsDir();
|
|
383
601
|
return {
|
|
384
602
|
repaired: false,
|
|
385
|
-
reason: 'No valid stored credentials
|
|
386
|
-
status:
|
|
603
|
+
reason: 'No valid stored credentials were found.',
|
|
604
|
+
status: null
|
|
387
605
|
};
|
|
388
606
|
}
|
|
389
607
|
|
|
@@ -391,7 +609,7 @@ export function repairCredentials() {
|
|
|
391
609
|
|
|
392
610
|
return {
|
|
393
611
|
repaired: true,
|
|
394
|
-
status:
|
|
612
|
+
status: null
|
|
395
613
|
};
|
|
396
614
|
}
|
|
397
615
|
|
package/bin/_cli/launcher.mjs
CHANGED
|
@@ -43,7 +43,7 @@ function getInstructionMode(agent) {
|
|
|
43
43
|
return 'legacy';
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
async function fetchContext(platformUrl,
|
|
46
|
+
async function fetchContext(platformUrl, bearerToken, localSecret, organizationId, ticketId, agent) {
|
|
47
47
|
const params = new URLSearchParams({
|
|
48
48
|
context: 'cli',
|
|
49
49
|
agent,
|
|
@@ -51,7 +51,7 @@ async function fetchContext(platformUrl, agentToken, localSecret, ticketId, agen
|
|
|
51
51
|
});
|
|
52
52
|
const url = `${platformUrl}/api/protocol/context/${ticketId}?${params.toString()}`;
|
|
53
53
|
const response = await fetch(url, {
|
|
54
|
-
headers: buildAuthHeaders(
|
|
54
|
+
headers: buildAuthHeaders(bearerToken, localSecret, organizationId)
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
if (!response.ok) {
|
|
@@ -116,8 +116,15 @@ async function runAgent(agent, mode = 'run') {
|
|
|
116
116
|
process.exit(1);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
const { platformUrl,
|
|
120
|
-
const context = await fetchContext(
|
|
119
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
120
|
+
const context = await fetchContext(
|
|
121
|
+
platformUrl,
|
|
122
|
+
bearerToken,
|
|
123
|
+
localSecret,
|
|
124
|
+
organizationId,
|
|
125
|
+
ticketId,
|
|
126
|
+
agent
|
|
127
|
+
);
|
|
121
128
|
|
|
122
129
|
const childEnv = { ...process.env, AGENT_IDENTIFIER: agentIdentifierMap[agent] };
|
|
123
130
|
|
|
@@ -199,8 +206,15 @@ async function printContext() {
|
|
|
199
206
|
process.exit(1);
|
|
200
207
|
}
|
|
201
208
|
|
|
202
|
-
const { platformUrl,
|
|
203
|
-
const context = await fetchContext(
|
|
209
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
210
|
+
const context = await fetchContext(
|
|
211
|
+
platformUrl,
|
|
212
|
+
bearerToken,
|
|
213
|
+
localSecret,
|
|
214
|
+
organizationId,
|
|
215
|
+
ticketId,
|
|
216
|
+
'claude'
|
|
217
|
+
);
|
|
204
218
|
process.stdout.write(context);
|
|
205
219
|
}
|
|
206
220
|
|
package/bin/_cli/new-ticket.mjs
CHANGED
|
@@ -131,9 +131,9 @@ async function promptForSelection({ items, label, prompt, renderItem }) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
async function fetchProjects(platformUrl,
|
|
134
|
+
async function fetchProjects(platformUrl, bearerToken, localSecret, organizationId) {
|
|
135
135
|
const res = await fetch(`${platformUrl}/api/protocol/projects`, {
|
|
136
|
-
headers: buildAuthHeaders(
|
|
136
|
+
headers: buildAuthHeaders(bearerToken, localSecret, organizationId)
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
const data = await res.json().catch(() => ({}));
|
|
@@ -146,11 +146,11 @@ async function fetchProjects(platformUrl, agentToken, localSecret) {
|
|
|
146
146
|
return Array.isArray(data.projects) ? sortProjects(data.projects) : [];
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
async function createTicket(platformUrl,
|
|
149
|
+
async function createTicket(platformUrl, bearerToken, localSecret, organizationId, body) {
|
|
150
150
|
const res = await fetch(`${platformUrl}/api/protocol/tickets`, {
|
|
151
151
|
method: 'POST',
|
|
152
152
|
headers: {
|
|
153
|
-
...buildAuthHeaders(
|
|
153
|
+
...buildAuthHeaders(bearerToken, localSecret, organizationId),
|
|
154
154
|
'Content-Type': 'application/json'
|
|
155
155
|
},
|
|
156
156
|
body: JSON.stringify(body)
|
|
@@ -224,8 +224,8 @@ async function runTicketCreationFlow(args, { commandName, launchAgent }) {
|
|
|
224
224
|
const objective = String(flags.objective ?? positionals.join(' ')).trim();
|
|
225
225
|
ensureObjective(commandName, objective);
|
|
226
226
|
|
|
227
|
-
const { platformUrl,
|
|
228
|
-
const projects = await fetchProjects(platformUrl,
|
|
227
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
228
|
+
const projects = await fetchProjects(platformUrl, bearerToken, localSecret, organizationId);
|
|
229
229
|
|
|
230
230
|
if (!projects.length) {
|
|
231
231
|
throw new Error('No projects available. Create a project first.');
|
|
@@ -253,7 +253,7 @@ async function runTicketCreationFlow(args, { commandName, launchAgent }) {
|
|
|
253
253
|
const modelIdentifier = resolveTicketCreationModelIdentifier(flags);
|
|
254
254
|
const ticketDelegate = resolveTicketCreationDelegate(flags, selectedAgent, modelIdentifier);
|
|
255
255
|
|
|
256
|
-
const ticket = await createTicket(platformUrl,
|
|
256
|
+
const ticket = await createTicket(platformUrl, bearerToken, localSecret, organizationId, {
|
|
257
257
|
objective,
|
|
258
258
|
title: String(flags.title ?? ''),
|
|
259
259
|
acceptanceCriteria: String(flags['acceptance-criteria'] ?? ''),
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -91,7 +91,15 @@ function resolveProtocolMetadata(flags = {}, base = {}) {
|
|
|
91
91
|
*/
|
|
92
92
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
93
93
|
|
|
94
|
-
async function apiPost(
|
|
94
|
+
async function apiPost(
|
|
95
|
+
platformUrl,
|
|
96
|
+
token,
|
|
97
|
+
localSecret,
|
|
98
|
+
organizationId,
|
|
99
|
+
path,
|
|
100
|
+
body,
|
|
101
|
+
timeoutMs = DEFAULT_TIMEOUT_MS
|
|
102
|
+
) {
|
|
95
103
|
const requestUrl = `${platformUrl}${path}`;
|
|
96
104
|
const requestStart = Date.now();
|
|
97
105
|
let res;
|
|
@@ -99,7 +107,7 @@ async function apiPost(platformUrl, token, localSecret, path, body, timeoutMs =
|
|
|
99
107
|
res = await fetch(requestUrl, {
|
|
100
108
|
method: 'POST',
|
|
101
109
|
headers: {
|
|
102
|
-
...buildAuthHeaders(token, localSecret),
|
|
110
|
+
...buildAuthHeaders(token, localSecret, organizationId),
|
|
103
111
|
'Content-Type': 'application/json'
|
|
104
112
|
},
|
|
105
113
|
body: JSON.stringify(body),
|
|
@@ -150,8 +158,8 @@ async function apiPost(platformUrl, token, localSecret, path, body, timeoutMs =
|
|
|
150
158
|
if (res.status === 401) {
|
|
151
159
|
throw new Error(
|
|
152
160
|
`Authentication failed (401): ${data.error ?? 'Invalid or missing token.'}\n` +
|
|
153
|
-
`IMPORTANT: Stop all work immediately. Your
|
|
154
|
-
`The user should
|
|
161
|
+
`IMPORTANT: Stop all work immediately. Your Overlord auth session is invalid, expired, or missing required scope.\n` +
|
|
162
|
+
`The user should sign in again with Overlord Desktop or \`ovld auth login\`.\n` +
|
|
155
163
|
`Ask the user if they would like to proceed without submitting updates to Overlord.`
|
|
156
164
|
);
|
|
157
165
|
}
|
|
@@ -474,7 +482,7 @@ function resolveExternalSessionId(flags) {
|
|
|
474
482
|
async function protocolAttach(args) {
|
|
475
483
|
const flags = parseFlags(args);
|
|
476
484
|
const ticketId = requireFlag(flags, 'ticket-id', 'TICKET_ID');
|
|
477
|
-
const { platformUrl,
|
|
485
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
478
486
|
const timeoutMs = resolveTimeout(flags);
|
|
479
487
|
|
|
480
488
|
const externalSessionId = resolveExternalSessionId(flags);
|
|
@@ -489,8 +497,9 @@ async function protocolAttach(args) {
|
|
|
489
497
|
|
|
490
498
|
const data = await apiPost(
|
|
491
499
|
platformUrl,
|
|
492
|
-
|
|
500
|
+
bearerToken,
|
|
493
501
|
localSecret,
|
|
502
|
+
organizationId,
|
|
494
503
|
'/api/protocol/attach',
|
|
495
504
|
body,
|
|
496
505
|
timeoutMs
|
|
@@ -519,7 +528,7 @@ async function protocolUpdate(args) {
|
|
|
519
528
|
? readTextFile(String(flags['summary-file']), '--summary-file')
|
|
520
529
|
: requireFlag(flags, 'summary', undefined);
|
|
521
530
|
|
|
522
|
-
const { platformUrl,
|
|
531
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
523
532
|
const timeoutMs = resolveTimeout(flags);
|
|
524
533
|
const changeRationales = await resolveChangeRationales(flags);
|
|
525
534
|
const externalSessionId = resolveExternalSessionId(flags);
|
|
@@ -545,8 +554,9 @@ async function protocolUpdate(args) {
|
|
|
545
554
|
|
|
546
555
|
const data = await apiPost(
|
|
547
556
|
platformUrl,
|
|
548
|
-
|
|
557
|
+
bearerToken,
|
|
549
558
|
localSecret,
|
|
559
|
+
organizationId,
|
|
550
560
|
'/api/protocol/update',
|
|
551
561
|
body,
|
|
552
562
|
timeoutMs
|
|
@@ -571,7 +581,7 @@ async function protocolRecordChangeRationales(args) {
|
|
|
571
581
|
);
|
|
572
582
|
}
|
|
573
583
|
|
|
574
|
-
const { platformUrl,
|
|
584
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
575
585
|
const timeoutMs = resolveTimeout(flags);
|
|
576
586
|
|
|
577
587
|
const body = {
|
|
@@ -588,8 +598,9 @@ async function protocolRecordChangeRationales(args) {
|
|
|
588
598
|
|
|
589
599
|
const data = await apiPost(
|
|
590
600
|
platformUrl,
|
|
591
|
-
|
|
601
|
+
bearerToken,
|
|
592
602
|
localSecret,
|
|
603
|
+
organizationId,
|
|
593
604
|
'/api/protocol/change-rationales',
|
|
594
605
|
body,
|
|
595
606
|
timeoutMs
|
|
@@ -610,7 +621,7 @@ async function protocolAsk(args) {
|
|
|
610
621
|
? readTextFile(String(flags['question-file']), '--question-file')
|
|
611
622
|
: requireFlag(flags, 'question', undefined);
|
|
612
623
|
|
|
613
|
-
const { platformUrl,
|
|
624
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
614
625
|
const timeoutMs = resolveTimeout(flags);
|
|
615
626
|
|
|
616
627
|
const body = {
|
|
@@ -621,7 +632,15 @@ async function protocolAsk(args) {
|
|
|
621
632
|
...(flags['payload-json'] ? { payload: parseJsonFlag('--payload-json', flags['payload-json']) } : {})
|
|
622
633
|
};
|
|
623
634
|
|
|
624
|
-
const data = await apiPost(
|
|
635
|
+
const data = await apiPost(
|
|
636
|
+
platformUrl,
|
|
637
|
+
bearerToken,
|
|
638
|
+
localSecret,
|
|
639
|
+
organizationId,
|
|
640
|
+
'/api/protocol/ask',
|
|
641
|
+
body,
|
|
642
|
+
timeoutMs
|
|
643
|
+
);
|
|
625
644
|
console.log(JSON.stringify(data, null, 2));
|
|
626
645
|
}
|
|
627
646
|
|
|
@@ -636,13 +655,14 @@ async function protocolPermissionRequest(args) {
|
|
|
636
655
|
? await readJsonFileOrStdin(String(flags['payload-file']), '--payload-file')
|
|
637
656
|
: {};
|
|
638
657
|
|
|
639
|
-
const { platformUrl,
|
|
658
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
640
659
|
const timeoutMs = resolveTimeout(flags);
|
|
641
660
|
|
|
642
661
|
const data = await apiPost(
|
|
643
662
|
platformUrl,
|
|
644
|
-
|
|
663
|
+
bearerToken,
|
|
645
664
|
localSecret,
|
|
665
|
+
organizationId,
|
|
646
666
|
`/api/protocol/permission-request?ticketId=${encodeURIComponent(ticketId)}`,
|
|
647
667
|
payload,
|
|
648
668
|
timeoutMs
|
|
@@ -660,7 +680,7 @@ async function protocolReadContext(args) {
|
|
|
660
680
|
if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
|
|
661
681
|
if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
|
|
662
682
|
|
|
663
|
-
const { platformUrl,
|
|
683
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
664
684
|
const timeoutMs = resolveTimeout(flags);
|
|
665
685
|
|
|
666
686
|
const body = {
|
|
@@ -672,8 +692,9 @@ async function protocolReadContext(args) {
|
|
|
672
692
|
|
|
673
693
|
const data = await apiPost(
|
|
674
694
|
platformUrl,
|
|
675
|
-
|
|
695
|
+
bearerToken,
|
|
676
696
|
localSecret,
|
|
697
|
+
organizationId,
|
|
677
698
|
'/api/protocol/read-context',
|
|
678
699
|
body,
|
|
679
700
|
timeoutMs
|
|
@@ -703,7 +724,7 @@ async function protocolWriteContext(args) {
|
|
|
703
724
|
value = String(flags.value);
|
|
704
725
|
}
|
|
705
726
|
|
|
706
|
-
const { platformUrl,
|
|
727
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
707
728
|
const timeoutMs = resolveTimeout(flags);
|
|
708
729
|
|
|
709
730
|
const body = {
|
|
@@ -716,8 +737,9 @@ async function protocolWriteContext(args) {
|
|
|
716
737
|
|
|
717
738
|
const data = await apiPost(
|
|
718
739
|
platformUrl,
|
|
719
|
-
|
|
740
|
+
bearerToken,
|
|
720
741
|
localSecret,
|
|
742
|
+
organizationId,
|
|
721
743
|
'/api/protocol/write-context',
|
|
722
744
|
body,
|
|
723
745
|
timeoutMs
|
|
@@ -742,7 +764,7 @@ async function protocolDeliver(args) {
|
|
|
742
764
|
? readTextFile(String(flags['summary-file']), '--summary-file')
|
|
743
765
|
: requireFlag(flags, 'summary', undefined));
|
|
744
766
|
|
|
745
|
-
const { platformUrl,
|
|
767
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
746
768
|
const timeoutMs = resolveTimeout(flags);
|
|
747
769
|
|
|
748
770
|
let artifacts = deliverPayload?.artifacts ?? [];
|
|
@@ -775,8 +797,9 @@ async function protocolDeliver(args) {
|
|
|
775
797
|
|
|
776
798
|
const data = await apiPost(
|
|
777
799
|
platformUrl,
|
|
778
|
-
|
|
800
|
+
bearerToken,
|
|
779
801
|
localSecret,
|
|
802
|
+
organizationId,
|
|
780
803
|
'/api/protocol/deliver',
|
|
781
804
|
body,
|
|
782
805
|
timeoutMs
|
|
@@ -795,7 +818,7 @@ async function protocolArtifactPrepareUpload(args) {
|
|
|
795
818
|
if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
|
|
796
819
|
const fileName = requireFlag(flags, 'file-name', undefined);
|
|
797
820
|
|
|
798
|
-
const { platformUrl,
|
|
821
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
799
822
|
const timeoutMs = resolveTimeout(flags);
|
|
800
823
|
|
|
801
824
|
const body = {
|
|
@@ -811,8 +834,9 @@ async function protocolArtifactPrepareUpload(args) {
|
|
|
811
834
|
|
|
812
835
|
const data = await apiPost(
|
|
813
836
|
platformUrl,
|
|
814
|
-
|
|
837
|
+
bearerToken,
|
|
815
838
|
localSecret,
|
|
839
|
+
organizationId,
|
|
816
840
|
'/api/protocol/artifacts/prepare-upload',
|
|
817
841
|
body,
|
|
818
842
|
timeoutMs
|
|
@@ -828,7 +852,7 @@ async function protocolArtifactFinalizeUpload(args) {
|
|
|
828
852
|
const storagePath = requireFlag(flags, 'storage-path', undefined);
|
|
829
853
|
const label = requireFlag(flags, 'label', undefined);
|
|
830
854
|
|
|
831
|
-
const { platformUrl,
|
|
855
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
832
856
|
const timeoutMs = resolveTimeout(flags);
|
|
833
857
|
|
|
834
858
|
const body = {
|
|
@@ -844,8 +868,9 @@ async function protocolArtifactFinalizeUpload(args) {
|
|
|
844
868
|
|
|
845
869
|
const data = await apiPost(
|
|
846
870
|
platformUrl,
|
|
847
|
-
|
|
871
|
+
bearerToken,
|
|
848
872
|
localSecret,
|
|
873
|
+
organizationId,
|
|
849
874
|
'/api/protocol/artifacts/finalize-upload',
|
|
850
875
|
body,
|
|
851
876
|
timeoutMs
|
|
@@ -862,7 +887,7 @@ async function protocolArtifactGetDownloadUrl(args) {
|
|
|
862
887
|
throw new Error('--artifact-id or --storage-path is required');
|
|
863
888
|
}
|
|
864
889
|
|
|
865
|
-
const { platformUrl,
|
|
890
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
866
891
|
const timeoutMs = resolveTimeout(flags);
|
|
867
892
|
|
|
868
893
|
const body = {
|
|
@@ -875,8 +900,9 @@ async function protocolArtifactGetDownloadUrl(args) {
|
|
|
875
900
|
|
|
876
901
|
const data = await apiPost(
|
|
877
902
|
platformUrl,
|
|
878
|
-
|
|
903
|
+
bearerToken,
|
|
879
904
|
localSecret,
|
|
905
|
+
organizationId,
|
|
880
906
|
'/api/protocol/artifacts/get-download-url',
|
|
881
907
|
body,
|
|
882
908
|
timeoutMs
|
|
@@ -900,13 +926,14 @@ async function protocolArtifactUploadFile(args) {
|
|
|
900
926
|
const fileStats = await stat(filePath);
|
|
901
927
|
const fileBytes = await readFile(filePath);
|
|
902
928
|
|
|
903
|
-
const { platformUrl,
|
|
929
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
904
930
|
const timeoutMs = resolveTimeout(flags);
|
|
905
931
|
|
|
906
932
|
const prepared = await apiPost(
|
|
907
933
|
platformUrl,
|
|
908
|
-
|
|
934
|
+
bearerToken,
|
|
909
935
|
localSecret,
|
|
936
|
+
organizationId,
|
|
910
937
|
'/api/protocol/artifacts/prepare-upload',
|
|
911
938
|
{
|
|
912
939
|
sessionKey,
|
|
@@ -931,8 +958,9 @@ async function protocolArtifactUploadFile(args) {
|
|
|
931
958
|
|
|
932
959
|
const finalized = await apiPost(
|
|
933
960
|
platformUrl,
|
|
934
|
-
|
|
961
|
+
bearerToken,
|
|
935
962
|
localSecret,
|
|
963
|
+
organizationId,
|
|
936
964
|
'/api/protocol/artifacts/finalize-upload',
|
|
937
965
|
{
|
|
938
966
|
sessionKey,
|
|
@@ -956,15 +984,16 @@ async function protocolArtifactUploadFile(args) {
|
|
|
956
984
|
|
|
957
985
|
async function protocolDiscoverProject(args) {
|
|
958
986
|
const flags = parseFlags(args);
|
|
959
|
-
const { platformUrl,
|
|
987
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
960
988
|
const timeoutMs = resolveTimeout(flags);
|
|
961
989
|
|
|
962
990
|
const workingDirectory = String(flags['working-directory'] ?? process.cwd());
|
|
963
991
|
|
|
964
992
|
const data = await apiPost(
|
|
965
993
|
platformUrl,
|
|
966
|
-
|
|
994
|
+
bearerToken,
|
|
967
995
|
localSecret,
|
|
996
|
+
organizationId,
|
|
968
997
|
'/api/protocol/discover-project',
|
|
969
998
|
{ workingDirectory },
|
|
970
999
|
timeoutMs
|
|
@@ -983,7 +1012,7 @@ async function protocolDiscoverProject(args) {
|
|
|
983
1012
|
async function protocolConnect(args) {
|
|
984
1013
|
const flags = parseFlags(args);
|
|
985
1014
|
const ticketId = requireFlag(flags, 'ticket-id', 'TICKET_ID');
|
|
986
|
-
const { platformUrl,
|
|
1015
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
987
1016
|
const timeoutMs = resolveTimeout(flags);
|
|
988
1017
|
|
|
989
1018
|
const body = {
|
|
@@ -995,8 +1024,9 @@ async function protocolConnect(args) {
|
|
|
995
1024
|
|
|
996
1025
|
const data = await apiPost(
|
|
997
1026
|
platformUrl,
|
|
998
|
-
|
|
1027
|
+
bearerToken,
|
|
999
1028
|
localSecret,
|
|
1029
|
+
organizationId,
|
|
1000
1030
|
'/api/protocol/connect',
|
|
1001
1031
|
body,
|
|
1002
1032
|
timeoutMs
|
|
@@ -1017,15 +1047,16 @@ async function protocolConnect(args) {
|
|
|
1017
1047
|
async function protocolLoadContext(args) {
|
|
1018
1048
|
const flags = parseFlags(args);
|
|
1019
1049
|
const ticketId = requireFlag(flags, 'ticket-id', 'TICKET_ID');
|
|
1020
|
-
const { platformUrl,
|
|
1050
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
1021
1051
|
const timeoutMs = resolveTimeout(flags);
|
|
1022
1052
|
|
|
1023
1053
|
const body = { ticketId };
|
|
1024
1054
|
|
|
1025
1055
|
const data = await apiPost(
|
|
1026
1056
|
platformUrl,
|
|
1027
|
-
|
|
1057
|
+
bearerToken,
|
|
1028
1058
|
localSecret,
|
|
1059
|
+
organizationId,
|
|
1029
1060
|
'/api/protocol/load-context',
|
|
1030
1061
|
body,
|
|
1031
1062
|
timeoutMs
|
|
@@ -1040,7 +1071,7 @@ async function protocolLoadContext(args) {
|
|
|
1040
1071
|
async function protocolSpawn(args) {
|
|
1041
1072
|
const flags = parseFlags(args);
|
|
1042
1073
|
const objective = requireFlag(flags, 'objective', undefined);
|
|
1043
|
-
const { platformUrl,
|
|
1074
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
1044
1075
|
const timeoutMs = resolveTimeout(flags);
|
|
1045
1076
|
const agentIdentifier = resolveProtocolAgentIdentifier(flags);
|
|
1046
1077
|
const modelIdentifier = resolveProtocolModelIdentifier(flags);
|
|
@@ -1072,8 +1103,9 @@ async function protocolSpawn(args) {
|
|
|
1072
1103
|
|
|
1073
1104
|
const data = await apiPost(
|
|
1074
1105
|
platformUrl,
|
|
1075
|
-
|
|
1106
|
+
bearerToken,
|
|
1076
1107
|
localSecret,
|
|
1108
|
+
organizationId,
|
|
1077
1109
|
'/api/protocol/spawn',
|
|
1078
1110
|
body,
|
|
1079
1111
|
timeoutMs
|
|
@@ -1099,7 +1131,7 @@ async function protocolCreateTicket(args) {
|
|
|
1099
1131
|
const flags = parseFlags(args);
|
|
1100
1132
|
const { sessionKey, ticketId } = resolveSessionFlags(flags);
|
|
1101
1133
|
const objective = requireFlag(flags, 'objective', undefined);
|
|
1102
|
-
const { platformUrl,
|
|
1134
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
1103
1135
|
const timeoutMs = resolveTimeout(flags);
|
|
1104
1136
|
const agentIdentifier = resolveProtocolAgentIdentifier(flags);
|
|
1105
1137
|
const modelIdentifier = resolveProtocolModelIdentifier(flags);
|
|
@@ -1122,8 +1154,9 @@ async function protocolCreateTicket(args) {
|
|
|
1122
1154
|
|
|
1123
1155
|
const data = await apiPost(
|
|
1124
1156
|
platformUrl,
|
|
1125
|
-
|
|
1157
|
+
bearerToken,
|
|
1126
1158
|
localSecret,
|
|
1159
|
+
organizationId,
|
|
1127
1160
|
'/api/protocol/create-ticket',
|
|
1128
1161
|
body,
|
|
1129
1162
|
timeoutMs
|
|
@@ -1159,8 +1192,9 @@ async function protocolCreateTicket(args) {
|
|
|
1159
1192
|
|
|
1160
1193
|
const data = await apiPost(
|
|
1161
1194
|
platformUrl,
|
|
1162
|
-
|
|
1195
|
+
bearerToken,
|
|
1163
1196
|
localSecret,
|
|
1197
|
+
organizationId,
|
|
1164
1198
|
'/api/protocol/tickets',
|
|
1165
1199
|
standaloneBody,
|
|
1166
1200
|
timeoutMs
|
|
@@ -1173,7 +1207,7 @@ async function protocolCreateTicket(args) {
|
|
|
1173
1207
|
// ---------------------------------------------------------------------------
|
|
1174
1208
|
|
|
1175
1209
|
async function protocolAuthStatus() {
|
|
1176
|
-
const status = getAuthStatus();
|
|
1210
|
+
const status = await getAuthStatus();
|
|
1177
1211
|
|
|
1178
1212
|
console.log(
|
|
1179
1213
|
JSON.stringify(
|
|
@@ -1185,6 +1219,9 @@ async function protocolAuthStatus() {
|
|
|
1185
1219
|
platformUrlSource: status.platformUrlSource,
|
|
1186
1220
|
tokenSource: status.tokenSource,
|
|
1187
1221
|
tokenPresent: status.tokenPresent,
|
|
1222
|
+
organizationId: status.organizationId,
|
|
1223
|
+
authMode: status.authMode,
|
|
1224
|
+
error: status.error,
|
|
1188
1225
|
hasLocalSecret: status.hasLocalSecret,
|
|
1189
1226
|
credentialsFileExists: status.credentialsFileExists,
|
|
1190
1227
|
electronCredentialsFileExists: status.electronCredentialsFileExists
|
|
@@ -1243,7 +1280,7 @@ Subcommands:
|
|
|
1243
1280
|
Environment fallback:
|
|
1244
1281
|
--session-key <- SESSION_KEY
|
|
1245
1282
|
--ticket-id <- TICKET_ID
|
|
1246
|
-
auth/host <- OVERLORD_URL, AGENT_TOKEN, or shared credentials from ovld auth/Desktop login
|
|
1283
|
+
auth/host <- OVERLORD_URL, optional legacy AGENT_TOKEN, or shared OAuth credentials from ovld auth/Desktop login
|
|
1247
1284
|
--timeout <- OVERLORD_TIMEOUT
|
|
1248
1285
|
|
|
1249
1286
|
Common flags:
|
package/bin/_cli/ticket.mjs
CHANGED
|
@@ -15,11 +15,11 @@ export async function ticketContext(ticketId) {
|
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const { platformUrl,
|
|
18
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
19
19
|
|
|
20
20
|
const url = `${platformUrl}/api/protocol/context/${ticketId}`;
|
|
21
21
|
const res = await fetch(url, {
|
|
22
|
-
headers: buildAuthHeaders(
|
|
22
|
+
headers: buildAuthHeaders(bearerToken, localSecret, organizationId)
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
if (!res.ok) {
|
package/bin/_cli/tickets.mjs
CHANGED
|
@@ -33,11 +33,11 @@ function parseFlags(args, knownFlags) {
|
|
|
33
33
|
return result;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
async function apiPost(url, token, localSecret, body) {
|
|
36
|
+
async function apiPost(url, token, localSecret, organizationId, body) {
|
|
37
37
|
const res = await fetch(url, {
|
|
38
38
|
method: 'POST',
|
|
39
39
|
headers: {
|
|
40
|
-
...buildAuthHeaders(token, localSecret),
|
|
40
|
+
...buildAuthHeaders(token, localSecret, organizationId),
|
|
41
41
|
'Content-Type': 'application/json'
|
|
42
42
|
},
|
|
43
43
|
body: JSON.stringify(body)
|
|
@@ -59,7 +59,7 @@ export async function ticketsCreate(args) {
|
|
|
59
59
|
export async function ticketsList(args) {
|
|
60
60
|
const flags = parseFlags(args, ['status', 'include-completed']);
|
|
61
61
|
|
|
62
|
-
const { platformUrl,
|
|
62
|
+
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
63
63
|
|
|
64
64
|
const body = {
|
|
65
65
|
includeCompleted: flags['include-completed'] !== false,
|
|
@@ -68,8 +68,9 @@ export async function ticketsList(args) {
|
|
|
68
68
|
|
|
69
69
|
const data = await apiPost(
|
|
70
70
|
`${platformUrl}/api/protocol/list-tickets`,
|
|
71
|
-
|
|
71
|
+
bearerToken,
|
|
72
72
|
localSecret,
|
|
73
|
+
organizationId,
|
|
73
74
|
body
|
|
74
75
|
);
|
|
75
76
|
|