overlord-cli 4.5.0 → 4.7.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/auth.mjs +45 -4
- package/bin/_cli/credentials.mjs +150 -9
- package/bin/_cli/index.mjs +3 -1
- package/bin/_cli/protocol.mjs +115 -7
- package/bin/_cli/setup.mjs +11 -49
- package/package.json +1 -1
- package/plugins/claude/README.md +3 -3
- package/plugins/overlord/README.md +1 -1
- package/plugins/overlord/scripts/overlord-mcp.mjs +10 -2
package/bin/_cli/auth.mjs
CHANGED
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
buildAuthHeaders,
|
|
10
10
|
clearCredentials,
|
|
11
11
|
getDefaultOverlordUrl,
|
|
12
|
+
getAuthStatus,
|
|
12
13
|
loadCredentials,
|
|
13
14
|
loadRuntime,
|
|
15
|
+
repairCredentials,
|
|
14
16
|
saveCredentials
|
|
15
17
|
} from './credentials.mjs';
|
|
16
18
|
|
|
@@ -460,7 +462,30 @@ export async function authLogin() {
|
|
|
460
462
|
console.log('Logged in successfully!');
|
|
461
463
|
}
|
|
462
464
|
|
|
463
|
-
|
|
465
|
+
function printVerboseAuthStatus() {
|
|
466
|
+
const status = getAuthStatus();
|
|
467
|
+
if (!status.isLoggedIn) {
|
|
468
|
+
console.log('Not logged in. Run: ovld auth login');
|
|
469
|
+
} else {
|
|
470
|
+
console.log('Logged in');
|
|
471
|
+
}
|
|
472
|
+
console.log(` Platform URL: ${status.platformUrl}`);
|
|
473
|
+
console.log(` Platform source: ${status.platformUrlSource}`);
|
|
474
|
+
console.log(` Token source: ${status.tokenSource}`);
|
|
475
|
+
console.log(` Token present: ${status.tokenPresent ? 'yes' : 'no'}`);
|
|
476
|
+
console.log(` Local secret: ${status.hasLocalSecret ? 'yes' : 'no'}`);
|
|
477
|
+
console.log(` credentials.json: ${status.credentialsFileExists ? 'present' : 'missing'}`);
|
|
478
|
+
console.log(
|
|
479
|
+
` electron-credentials.json: ${status.electronCredentialsFileExists ? 'present' : 'missing'}`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function authStatus(args = []) {
|
|
484
|
+
if (args.includes('--verbose') || args.includes('-v')) {
|
|
485
|
+
printVerboseAuthStatus();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
464
489
|
const creds = loadCredentials();
|
|
465
490
|
if (!creds) {
|
|
466
491
|
console.log('Not logged in. Run: ovld auth login');
|
|
@@ -478,13 +503,24 @@ export function authLogout() {
|
|
|
478
503
|
console.log('Logged out.');
|
|
479
504
|
}
|
|
480
505
|
|
|
481
|
-
export
|
|
506
|
+
export function authRepair() {
|
|
507
|
+
const result = repairCredentials();
|
|
508
|
+
if (result.repaired) {
|
|
509
|
+
console.log('Credentials repaired.');
|
|
510
|
+
} else {
|
|
511
|
+
console.log(`Credentials not repaired: ${result.reason}`);
|
|
512
|
+
}
|
|
513
|
+
printVerboseAuthStatus();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export async function runAuthCommand(subcommand, args = []) {
|
|
482
517
|
if (!subcommand || subcommand === 'help' || subcommand === '--help') {
|
|
483
518
|
console.log(`ovld auth <subcommand>
|
|
484
519
|
|
|
485
520
|
Subcommands:
|
|
486
521
|
login Authorize the CLI via browser (works locally or over SSH)
|
|
487
|
-
status Show current login status
|
|
522
|
+
status Show current login status (use --verbose for redacted diagnostics)
|
|
523
|
+
repair Mirror and chmod shared Desktop/CLI credentials when possible
|
|
488
524
|
logout Remove stored credentials
|
|
489
525
|
`);
|
|
490
526
|
return;
|
|
@@ -496,7 +532,12 @@ Subcommands:
|
|
|
496
532
|
}
|
|
497
533
|
|
|
498
534
|
if (subcommand === 'status') {
|
|
499
|
-
authStatus();
|
|
535
|
+
authStatus(args);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (subcommand === 'repair') {
|
|
540
|
+
authRepair();
|
|
500
541
|
return;
|
|
501
542
|
}
|
|
502
543
|
|
package/bin/_cli/credentials.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
|
|
10
10
|
const CREDENTIALS_DIR = path.join(os.homedir(), '.ovld');
|
|
11
11
|
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.json');
|
|
12
|
+
const ELECTRON_CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'electron-credentials.json');
|
|
12
13
|
const RUNTIME_FILE_PATTERN = /^runtime\..+\.json$/;
|
|
13
14
|
const HOSTED_OVERLORD_URL = 'https://www.ovld.ai';
|
|
14
15
|
const LOCAL_DEV_OVERLORD_URL = 'http://localhost:3000';
|
|
@@ -18,30 +19,121 @@ const LOCAL_SECRET_HEADER = 'X-Overlord-Local-Secret';
|
|
|
18
19
|
* @typedef {{ access_token: string, platform_url: string, user_email?: string }} Credentials
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
function ensureCredentialsDir() {
|
|
23
|
+
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
|
|
24
|
+
try {
|
|
25
|
+
fs.chmodSync(CREDENTIALS_DIR, 0o700);
|
|
26
|
+
} catch {
|
|
27
|
+
// Best-effort hardening for existing directories.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readJsonFile(filePath) {
|
|
23
32
|
try {
|
|
24
|
-
const raw = fs.readFileSync(
|
|
33
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
25
34
|
return JSON.parse(raw);
|
|
26
35
|
} catch {
|
|
27
36
|
return null;
|
|
28
37
|
}
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
function fileExists(filePath) {
|
|
41
|
+
try {
|
|
42
|
+
return fs.statSync(filePath).isFile();
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writeJsonFileAtomic(filePath, data) {
|
|
49
|
+
ensureCredentialsDir();
|
|
50
|
+
const tempFile = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
51
|
+
fs.writeFileSync(tempFile, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
52
|
+
fs.renameSync(tempFile, filePath);
|
|
53
|
+
fs.chmodSync(filePath, 0o600);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseStoredCredentialsData(parsed, { requireAccessToken = false } = {}) {
|
|
57
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
58
|
+
|
|
59
|
+
const accessToken = normalizeAgentToken(parsed.access_token);
|
|
60
|
+
const platformUrl = typeof parsed.platform_url === 'string' ? parsed.platform_url.trim() : '';
|
|
61
|
+
if (requireAccessToken && !accessToken) return null;
|
|
62
|
+
if (!accessToken && !platformUrl) return null;
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
access_token: accessToken,
|
|
66
|
+
platform_url: platformUrl,
|
|
67
|
+
...(typeof parsed.user_email === 'string' && parsed.user_email.trim()
|
|
68
|
+
? { user_email: parsed.user_email.trim() }
|
|
69
|
+
: {})
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizeCredentialsForSave(data) {
|
|
74
|
+
const parsed = parseStoredCredentialsData(data, { requireAccessToken: true });
|
|
75
|
+
if (!parsed) return null;
|
|
76
|
+
|
|
77
|
+
const platformUrl = normalizePlatformUrl(parsed.platform_url);
|
|
78
|
+
if (!platformUrl) return null;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
...parsed,
|
|
82
|
+
platform_url: platformUrl
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @returns {Credentials | null} */
|
|
87
|
+
export function loadCredentials() {
|
|
88
|
+
return (
|
|
89
|
+
parseStoredCredentialsData(readJsonFile(ELECTRON_CREDENTIALS_FILE), {
|
|
90
|
+
requireAccessToken: true
|
|
91
|
+
}) ??
|
|
92
|
+
parseStoredCredentialsData(readJsonFile(CREDENTIALS_FILE))
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
31
96
|
/** @param {Credentials} data */
|
|
32
97
|
export function saveCredentials(data) {
|
|
33
|
-
|
|
34
|
-
|
|
98
|
+
const credentials = normalizeCredentialsForSave(data);
|
|
99
|
+
if (!credentials) {
|
|
100
|
+
throw new Error('Cannot save empty Overlord credentials.');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
writeJsonFileAtomic(CREDENTIALS_FILE, credentials);
|
|
104
|
+
|
|
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
|
+
const existingElectronCredentials = readJsonFile(ELECTRON_CREDENTIALS_FILE);
|
|
108
|
+
const electronPayload =
|
|
109
|
+
existingElectronCredentials && typeof existingElectronCredentials === 'object'
|
|
110
|
+
? { ...existingElectronCredentials, ...credentials }
|
|
111
|
+
: credentials;
|
|
112
|
+
writeJsonFileAtomic(ELECTRON_CREDENTIALS_FILE, electronPayload);
|
|
35
113
|
}
|
|
36
114
|
|
|
37
115
|
export function clearCredentials() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
116
|
+
for (const filePath of [CREDENTIALS_FILE, ELECTRON_CREDENTIALS_FILE]) {
|
|
117
|
+
try {
|
|
118
|
+
fs.unlinkSync(filePath);
|
|
119
|
+
} catch {
|
|
120
|
+
// Already gone
|
|
121
|
+
}
|
|
42
122
|
}
|
|
43
123
|
}
|
|
44
124
|
|
|
125
|
+
function getCredentialFileSource() {
|
|
126
|
+
const electronCredentials = parseStoredCredentialsData(readJsonFile(ELECTRON_CREDENTIALS_FILE), {
|
|
127
|
+
requireAccessToken: true
|
|
128
|
+
});
|
|
129
|
+
if (electronCredentials) return 'electron-credentials.json';
|
|
130
|
+
|
|
131
|
+
const cliCredentials = parseStoredCredentialsData(readJsonFile(CREDENTIALS_FILE));
|
|
132
|
+
if (cliCredentials) return 'credentials.json';
|
|
133
|
+
|
|
134
|
+
return 'none';
|
|
135
|
+
}
|
|
136
|
+
|
|
45
137
|
function getRuntimeFilePath(targetUrl) {
|
|
46
138
|
try {
|
|
47
139
|
const parsed = new URL(targetUrl);
|
|
@@ -254,6 +346,55 @@ export function resolveAuth() {
|
|
|
254
346
|
};
|
|
255
347
|
}
|
|
256
348
|
|
|
349
|
+
export function getAuthStatus() {
|
|
350
|
+
const creds = loadCredentials();
|
|
351
|
+
const resolved = resolveAuth();
|
|
352
|
+
|
|
353
|
+
let tokenSource = 'fallback';
|
|
354
|
+
if (normalizeAgentToken(process.env.AGENT_TOKEN)) {
|
|
355
|
+
tokenSource = 'AGENT_TOKEN';
|
|
356
|
+
} else if (normalizeAgentToken(creds?.access_token)) {
|
|
357
|
+
tokenSource = getCredentialFileSource();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
let platformUrlSource = 'default';
|
|
361
|
+
if (normalizePlatformUrl(process.env.OVERLORD_URL)) {
|
|
362
|
+
platformUrlSource = 'OVERLORD_URL';
|
|
363
|
+
} else if (normalizeStoredPlatformUrl(creds?.platform_url)) {
|
|
364
|
+
platformUrlSource = getCredentialFileSource();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
isLoggedIn: tokenSource !== 'fallback',
|
|
369
|
+
platformUrl: resolved.platformUrl,
|
|
370
|
+
platformUrlSource,
|
|
371
|
+
tokenPresent: tokenSource !== 'fallback',
|
|
372
|
+
tokenSource,
|
|
373
|
+
hasLocalSecret: Boolean(resolved.localSecret),
|
|
374
|
+
credentialsFileExists: fileExists(CREDENTIALS_FILE),
|
|
375
|
+
electronCredentialsFileExists: fileExists(ELECTRON_CREDENTIALS_FILE)
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function repairCredentials() {
|
|
380
|
+
const creds = loadCredentials();
|
|
381
|
+
if (!creds || !normalizeAgentToken(creds.access_token)) {
|
|
382
|
+
ensureCredentialsDir();
|
|
383
|
+
return {
|
|
384
|
+
repaired: false,
|
|
385
|
+
reason: 'No valid stored credentials with an access token were found.',
|
|
386
|
+
status: getAuthStatus()
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
saveCredentials(creds);
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
repaired: true,
|
|
394
|
+
status: getAuthStatus()
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
257
398
|
function normalizeAgentToken(value) {
|
|
258
399
|
if (typeof value !== 'string') return '';
|
|
259
400
|
return value.trim();
|
package/bin/_cli/index.mjs
CHANGED
|
@@ -31,7 +31,7 @@ Usage:
|
|
|
31
31
|
${primaryCommand} attach [ticketId] [agent] Search tickets and launch an agent (interactive)
|
|
32
32
|
${primaryCommand} create "<objective>" Create a ticket with numbered project selection
|
|
33
33
|
${primaryCommand} prompt "<objective>" Create a ticket, then launch an agent on it
|
|
34
|
-
${primaryCommand} auth <subcommand> Login, logout, or check auth status
|
|
34
|
+
${primaryCommand} auth <subcommand> Login, logout, repair, or check auth status
|
|
35
35
|
${primaryCommand} tickets <subcommand> Create or list tickets
|
|
36
36
|
${primaryCommand} ticket <subcommand> Work with a single ticket
|
|
37
37
|
${primaryCommand} protocol <subcommand> Agent workflow commands
|
|
@@ -46,10 +46,12 @@ Usage:
|
|
|
46
46
|
|
|
47
47
|
Agents:
|
|
48
48
|
Use ${primaryCommand} protocol help for ticket lifecycle commands.
|
|
49
|
+
Key protocol commands: auth-status, discover-project, spawn, attach, connect, load-context.
|
|
49
50
|
|
|
50
51
|
Auth:
|
|
51
52
|
${primaryCommand} auth login Authorize CLI via browser
|
|
52
53
|
${primaryCommand} auth status Show login status
|
|
54
|
+
${primaryCommand} auth repair Repair shared Desktop/CLI credentials
|
|
53
55
|
${primaryCommand} auth logout Remove stored credentials
|
|
54
56
|
|
|
55
57
|
Tickets:
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import fs from 'node:fs';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
|
|
8
|
-
import { buildAuthHeaders, resolveAuth } from './credentials.mjs';
|
|
8
|
+
import { buildAuthHeaders, getAuthStatus, resolveAuth } from './credentials.mjs';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Parse simple CLI flags: --key value or --key=value
|
|
@@ -51,6 +51,36 @@ export function resolveProtocolTicketDelegate(flags = {}, agentIdentifier = '')
|
|
|
51
51
|
return resolvedAgent || null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
export function resolveProtocolModelIdentifier(flags = {}) {
|
|
55
|
+
const explicitModel = typeof flags.model === 'string' ? flags.model.trim() : '';
|
|
56
|
+
if (explicitModel) return explicitModel;
|
|
57
|
+
|
|
58
|
+
const envModel =
|
|
59
|
+
process.env.OVERLORD_MODEL_IDENTIFIER?.trim() ||
|
|
60
|
+
process.env.MODEL_IDENTIFIER?.trim() ||
|
|
61
|
+
process.env.AGENT_MODEL?.trim();
|
|
62
|
+
return envModel || null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveProtocolMetadata(flags = {}, base = {}) {
|
|
66
|
+
const metadata = { ...base };
|
|
67
|
+
|
|
68
|
+
if (flags['metadata-json']) {
|
|
69
|
+
const parsed = JSON.parse(String(flags['metadata-json']));
|
|
70
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
71
|
+
throw new Error('--metadata-json must be a JSON object');
|
|
72
|
+
}
|
|
73
|
+
Object.assign(metadata, parsed);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const modelIdentifier = resolveProtocolModelIdentifier(flags);
|
|
77
|
+
if (modelIdentifier) {
|
|
78
|
+
metadata.model = modelIdentifier;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return metadata;
|
|
82
|
+
}
|
|
83
|
+
|
|
54
84
|
/**
|
|
55
85
|
* Default request timeout in milliseconds. Overridable via --timeout flag or
|
|
56
86
|
* OVERLORD_TIMEOUT env var. A bounded timeout prevents indefinite spinner hangs
|
|
@@ -446,9 +476,7 @@ async function protocolAttach(args) {
|
|
|
446
476
|
agentIdentifier: resolveProtocolAgentIdentifier(flags),
|
|
447
477
|
connectionMethod: String(flags.method ?? 'cli'),
|
|
448
478
|
...(externalSessionId !== undefined ? { externalSessionId } : {}),
|
|
449
|
-
metadata: {
|
|
450
|
-
cwd: process.cwd()
|
|
451
|
-
}
|
|
479
|
+
metadata: resolveProtocolMetadata(flags, { cwd: process.cwd() })
|
|
452
480
|
};
|
|
453
481
|
|
|
454
482
|
const data = await apiPost(
|
|
@@ -589,6 +617,31 @@ async function protocolAsk(args) {
|
|
|
589
617
|
console.log(JSON.stringify(data, null, 2));
|
|
590
618
|
}
|
|
591
619
|
|
|
620
|
+
// ---------------------------------------------------------------------------
|
|
621
|
+
// permission-request
|
|
622
|
+
// ---------------------------------------------------------------------------
|
|
623
|
+
|
|
624
|
+
async function protocolPermissionRequest(args) {
|
|
625
|
+
const flags = parseFlags(args);
|
|
626
|
+
const ticketId = requireFlag(flags, 'ticket-id', 'TICKET_ID');
|
|
627
|
+
const payload = flags['payload-file']
|
|
628
|
+
? await readJsonFileOrStdin(String(flags['payload-file']), '--payload-file')
|
|
629
|
+
: {};
|
|
630
|
+
|
|
631
|
+
const { platformUrl, agentToken, localSecret } = resolveAuth();
|
|
632
|
+
const timeoutMs = resolveTimeout(flags);
|
|
633
|
+
|
|
634
|
+
const data = await apiPost(
|
|
635
|
+
platformUrl,
|
|
636
|
+
agentToken,
|
|
637
|
+
localSecret,
|
|
638
|
+
`/api/protocol/permission-request?ticketId=${encodeURIComponent(ticketId)}`,
|
|
639
|
+
payload,
|
|
640
|
+
timeoutMs
|
|
641
|
+
);
|
|
642
|
+
console.log(JSON.stringify(data, null, 2));
|
|
643
|
+
}
|
|
644
|
+
|
|
592
645
|
// ---------------------------------------------------------------------------
|
|
593
646
|
// read-context
|
|
594
647
|
// ---------------------------------------------------------------------------
|
|
@@ -933,7 +986,7 @@ async function protocolConnect(args) {
|
|
|
933
986
|
ticketId,
|
|
934
987
|
agentIdentifier: resolveProtocolAgentIdentifier(flags),
|
|
935
988
|
connectionMethod: String(flags.method ?? 'cli'),
|
|
936
|
-
metadata: {}
|
|
989
|
+
metadata: resolveProtocolMetadata(flags, { cwd: process.cwd() })
|
|
937
990
|
};
|
|
938
991
|
|
|
939
992
|
const data = await apiPost(
|
|
@@ -995,7 +1048,7 @@ async function protocolSpawn(args) {
|
|
|
995
1048
|
objective,
|
|
996
1049
|
agentIdentifier,
|
|
997
1050
|
connectionMethod: String(flags.method ?? 'cli'),
|
|
998
|
-
metadata: {},
|
|
1051
|
+
metadata: resolveProtocolMetadata(flags, { cwd: process.cwd() }),
|
|
999
1052
|
...(flags.title ? { title: String(flags.title) } : {}),
|
|
1000
1053
|
...(flags.priority ? { priority: String(flags.priority) } : {}),
|
|
1001
1054
|
...(flags['project-id'] ? { projectId: String(flags['project-id']) } : {}),
|
|
@@ -1029,6 +1082,34 @@ async function protocolSpawn(args) {
|
|
|
1029
1082
|
}
|
|
1030
1083
|
}
|
|
1031
1084
|
|
|
1085
|
+
// ---------------------------------------------------------------------------
|
|
1086
|
+
// auth-status (agent-friendly auth diagnostics)
|
|
1087
|
+
// ---------------------------------------------------------------------------
|
|
1088
|
+
|
|
1089
|
+
async function protocolAuthStatus() {
|
|
1090
|
+
const status = getAuthStatus();
|
|
1091
|
+
|
|
1092
|
+
console.log(
|
|
1093
|
+
JSON.stringify(
|
|
1094
|
+
{
|
|
1095
|
+
ok: status.isLoggedIn,
|
|
1096
|
+
authStatus: {
|
|
1097
|
+
isLoggedIn: status.isLoggedIn,
|
|
1098
|
+
platformUrl: status.platformUrl,
|
|
1099
|
+
platformUrlSource: status.platformUrlSource,
|
|
1100
|
+
tokenSource: status.tokenSource,
|
|
1101
|
+
tokenPresent: status.tokenPresent,
|
|
1102
|
+
hasLocalSecret: status.hasLocalSecret,
|
|
1103
|
+
credentialsFileExists: status.credentialsFileExists,
|
|
1104
|
+
electronCredentialsFileExists: status.electronCredentialsFileExists
|
|
1105
|
+
}
|
|
1106
|
+
},
|
|
1107
|
+
null,
|
|
1108
|
+
2
|
|
1109
|
+
)
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1032
1113
|
// ---------------------------------------------------------------------------
|
|
1033
1114
|
// Router
|
|
1034
1115
|
// ---------------------------------------------------------------------------
|
|
@@ -1053,6 +1134,7 @@ Project discovery:
|
|
|
1053
1134
|
Use --project-id to override automatic resolution on spawn or ticket creation.
|
|
1054
1135
|
|
|
1055
1136
|
Subcommands:
|
|
1137
|
+
auth-status Return machine-readable auth status for agent runtimes
|
|
1056
1138
|
discover-project Resolve a project from the current working directory
|
|
1057
1139
|
attach Start a ticket session and return full working context
|
|
1058
1140
|
connect Start a lightweight session without full context
|
|
@@ -1061,6 +1143,7 @@ Subcommands:
|
|
|
1061
1143
|
update Post progress, activity events, and optional change rationales
|
|
1062
1144
|
record-change-rationales Persist structured change rationales without a progress update
|
|
1063
1145
|
ask Post a blocking question and move the ticket to review
|
|
1146
|
+
permission-request Notify Overlord that the agent is requesting tool permission
|
|
1064
1147
|
read-context Read shared persistent context for this ticket
|
|
1065
1148
|
write-context Write shared persistent context for future sessions
|
|
1066
1149
|
deliver Finish work, send artifacts, and move the ticket to review
|
|
@@ -1072,7 +1155,7 @@ Subcommands:
|
|
|
1072
1155
|
Environment fallback:
|
|
1073
1156
|
--session-key <- SESSION_KEY
|
|
1074
1157
|
--ticket-id <- TICKET_ID
|
|
1075
|
-
auth/host <- OVERLORD_URL, AGENT_TOKEN
|
|
1158
|
+
auth/host <- OVERLORD_URL, AGENT_TOKEN, or shared credentials from ovld auth/Desktop login
|
|
1076
1159
|
--timeout <- OVERLORD_TIMEOUT
|
|
1077
1160
|
|
|
1078
1161
|
Common flags:
|
|
@@ -1080,8 +1163,15 @@ Common flags:
|
|
|
1080
1163
|
--ticket-id <id> Ticket id when the subcommand operates on an existing ticket
|
|
1081
1164
|
--session-key <key> Session key returned by attach/connect/spawn
|
|
1082
1165
|
--agent <identifier> Agent identifier sent to Overlord (default: AGENT_IDENTIFIER or claude-code)
|
|
1166
|
+
--model <identifier> Model identifier to snapshot on executing objectives
|
|
1083
1167
|
--method <connectionMethod> Connection method sent to Overlord (default: cli)
|
|
1084
1168
|
|
|
1169
|
+
auth-status:
|
|
1170
|
+
Purpose:
|
|
1171
|
+
Check whether the local runtime has usable Overlord credentials.
|
|
1172
|
+
Returns:
|
|
1173
|
+
JSON with ok=true|false plus authStatus fields describing token and host sources.
|
|
1174
|
+
|
|
1085
1175
|
discover-project:
|
|
1086
1176
|
Purpose:
|
|
1087
1177
|
Resolve the Overlord project that corresponds to the current (or given) working directory.
|
|
@@ -1101,8 +1191,10 @@ attach:
|
|
|
1101
1191
|
--ticket-id <id>
|
|
1102
1192
|
Optional:
|
|
1103
1193
|
--agent <identifier>
|
|
1194
|
+
--model <identifier>
|
|
1104
1195
|
--method <connectionMethod>
|
|
1105
1196
|
--external-session-id <id|null> Store the native agent thread/session id, or clear it with null
|
|
1197
|
+
--metadata-json <json> Extra session metadata object
|
|
1106
1198
|
Returns:
|
|
1107
1199
|
Full JSON including session.sessionKey, ticket, history, artifacts, sharedState, and promptContext
|
|
1108
1200
|
Notes:
|
|
@@ -1115,7 +1207,9 @@ connect:
|
|
|
1115
1207
|
--ticket-id <id>
|
|
1116
1208
|
Optional:
|
|
1117
1209
|
--agent <identifier>
|
|
1210
|
+
--model <identifier>
|
|
1118
1211
|
--method <connectionMethod>
|
|
1212
|
+
--metadata-json <json> Extra session metadata object
|
|
1119
1213
|
Returns:
|
|
1120
1214
|
Session JSON and SESSION_KEY on stderr when available
|
|
1121
1215
|
|
|
@@ -1167,6 +1261,15 @@ ask:
|
|
|
1167
1261
|
Notes:
|
|
1168
1262
|
After ask succeeds, stop working until the human responds
|
|
1169
1263
|
|
|
1264
|
+
permission-request:
|
|
1265
|
+
Purpose:
|
|
1266
|
+
Notify Overlord that the local agent runtime is requesting tool permission.
|
|
1267
|
+
This is primarily used by installed permission hooks.
|
|
1268
|
+
Required:
|
|
1269
|
+
--ticket-id <id>
|
|
1270
|
+
Optional:
|
|
1271
|
+
--payload-file <path|-> Hook JSON payload, or stdin when "-"
|
|
1272
|
+
|
|
1170
1273
|
read-context:
|
|
1171
1274
|
Purpose:
|
|
1172
1275
|
Read persistent shared context written by earlier sessions
|
|
@@ -1226,7 +1329,9 @@ spawn:
|
|
|
1226
1329
|
--parent-session-key <key>
|
|
1227
1330
|
--parent-ticket-id <id>
|
|
1228
1331
|
--agent <identifier>
|
|
1332
|
+
--model <identifier>
|
|
1229
1333
|
--method <connectionMethod>
|
|
1334
|
+
--metadata-json <json> Extra session metadata object
|
|
1230
1335
|
Returns:
|
|
1231
1336
|
New ticket/session JSON plus SESSION_KEY and TICKET_ID on stderr when available
|
|
1232
1337
|
|
|
@@ -1275,6 +1380,7 @@ artifact-upload-file:
|
|
|
1275
1380
|
--metadata-json <json>
|
|
1276
1381
|
|
|
1277
1382
|
Examples:
|
|
1383
|
+
ovld protocol auth-status
|
|
1278
1384
|
ovld protocol discover-project
|
|
1279
1385
|
ovld protocol discover-project --working-directory /path/to/repo
|
|
1280
1386
|
ovld protocol spawn --agent codex --objective "Implement feature X" # auto-resolves project from cwd
|
|
@@ -1303,6 +1409,7 @@ Examples:
|
|
|
1303
1409
|
}
|
|
1304
1410
|
|
|
1305
1411
|
if (subcommand === 'discover-project') { await protocolDiscoverProject(args); return; }
|
|
1412
|
+
if (subcommand === 'auth-status') { await protocolAuthStatus(); return; }
|
|
1306
1413
|
if (subcommand === 'attach') { await protocolAttach(args); return; }
|
|
1307
1414
|
if (subcommand === 'connect') { await protocolConnect(args); return; }
|
|
1308
1415
|
if (subcommand === 'load-context') { await protocolLoadContext(args); return; }
|
|
@@ -1314,6 +1421,7 @@ Examples:
|
|
|
1314
1421
|
if (subcommand === 'update') { await protocolUpdate(args); return; }
|
|
1315
1422
|
if (subcommand === 'record-change-rationales') { await protocolRecordChangeRationales(args); return; }
|
|
1316
1423
|
if (subcommand === 'ask') { await protocolAsk(args); return; }
|
|
1424
|
+
if (subcommand === 'permission-request') { await protocolPermissionRequest(args); return; }
|
|
1317
1425
|
if (subcommand === 'read-context') { await protocolReadContext(args); return; }
|
|
1318
1426
|
if (subcommand === 'write-context') { await protocolWriteContext(args); return; }
|
|
1319
1427
|
if (subcommand === 'deliver') { await protocolDeliver(args); return; }
|
package/bin/_cli/setup.mjs
CHANGED
|
@@ -195,13 +195,9 @@ ovld protocol artifact-upload-file --session-key <sessionKey> --ticket-id $TICKE
|
|
|
195
195
|
const PERMISSION_HOOK_SCRIPT = `#!/bin/bash
|
|
196
196
|
# Overlord PermissionRequest notification hook (managed by Overlord)
|
|
197
197
|
BODY=$(cat -)
|
|
198
|
-
if [ -n "$
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
-H "Authorization: Bearer $AGENT_TOKEN" \\
|
|
202
|
-
-H "X-Overlord-Local-Secret: $OVERLORD_LOCAL_SECRET" \\
|
|
203
|
-
-H "Content-Type: application/json" \\
|
|
204
|
-
-d "$BODY" \\
|
|
198
|
+
if [ -n "$TICKET_ID" ] && command -v ovld >/dev/null 2>&1; then
|
|
199
|
+
{ if [ -n "$BODY" ]; then printf '%s' "$BODY"; else printf '{}'; fi; } \\
|
|
200
|
+
| ovld protocol permission-request --ticket-id "$TICKET_ID" --payload-file - \\
|
|
205
201
|
>/dev/null 2>&1 &
|
|
206
202
|
disown
|
|
207
203
|
fi
|
|
@@ -823,9 +819,7 @@ function installOpenCode() {
|
|
|
823
819
|
bash: {
|
|
824
820
|
'*': 'ask',
|
|
825
821
|
...existingBash,
|
|
826
|
-
'ovld protocol *': 'allow'
|
|
827
|
-
'curl -sS -X POST *': 'allow',
|
|
828
|
-
'curl -s -X POST *': 'allow'
|
|
822
|
+
'ovld protocol *': 'allow'
|
|
829
823
|
}
|
|
830
824
|
}
|
|
831
825
|
});
|
|
@@ -904,8 +898,7 @@ function installCursor() {
|
|
|
904
898
|
const mergedAllow = Array.from(
|
|
905
899
|
new Set([
|
|
906
900
|
...asStringArray(permissions.allow),
|
|
907
|
-
'Shell(ovld protocol:*)'
|
|
908
|
-
'Shell(curl -sS -X POST:*)'
|
|
901
|
+
'Shell(ovld protocol:*)'
|
|
909
902
|
])
|
|
910
903
|
);
|
|
911
904
|
writeJsonFile(paths.settingsFile, {
|
|
@@ -939,12 +932,6 @@ function installGemini() {
|
|
|
939
932
|
'commandPrefix = "ovld protocol"',
|
|
940
933
|
'decision = "allow"',
|
|
941
934
|
'priority = 900',
|
|
942
|
-
'',
|
|
943
|
-
'[[rule]]',
|
|
944
|
-
'toolName = "run_shell_command"',
|
|
945
|
-
'commandPrefix = "curl -sS -X POST"',
|
|
946
|
-
'decision = "allow"',
|
|
947
|
-
'priority = 900',
|
|
948
935
|
''
|
|
949
936
|
].join('\n');
|
|
950
937
|
writeTextFile(paths.policyFile, policyContent);
|
|
@@ -1277,21 +1264,7 @@ function installClaudePermissions(platformUrl) {
|
|
|
1277
1264
|
if (!Array.isArray(settings.permissions.allow)) settings.permissions.allow = [];
|
|
1278
1265
|
}
|
|
1279
1266
|
|
|
1280
|
-
const
|
|
1281
|
-
'attach', 'update', 'ask', 'read-context', 'write-context', 'deliver',
|
|
1282
|
-
'create-ticket', 'list-tickets', 'record-change-rationales', 'spawn',
|
|
1283
|
-
'discover-project', 'load-context', 'artifact-upload-file', 'artifact-download-url'
|
|
1284
|
-
];
|
|
1285
|
-
|
|
1286
|
-
const entries = [];
|
|
1287
|
-
for (const endpoint of PROTOCOL_ENDPOINTS) {
|
|
1288
|
-
entries.push(`Bash(curl -s -X POST "${platformUrl}/api/protocol/${endpoint}":*)`);
|
|
1289
|
-
}
|
|
1290
|
-
entries.push(`Bash(curl -s -H 'Authorization::*)`);
|
|
1291
|
-
for (const endpoint of PROTOCOL_ENDPOINTS) {
|
|
1292
|
-
entries.push(`Bash(curl -s -X POST "$OVERLORD_URL/api/protocol/${endpoint}":*)`);
|
|
1293
|
-
}
|
|
1294
|
-
entries.push(`Bash(curl -s -H "Authorization::*)`);
|
|
1267
|
+
const entries = ['Bash(ovld protocol:*)'];
|
|
1295
1268
|
|
|
1296
1269
|
const existing = new Set(settings.permissions.allow);
|
|
1297
1270
|
const toAdd = entries.filter((e) => !existing.has(e));
|
|
@@ -1353,9 +1326,7 @@ function installOpenCodePermissions(_platformUrl) {
|
|
|
1353
1326
|
bash: {
|
|
1354
1327
|
'*': 'ask',
|
|
1355
1328
|
...existingBash,
|
|
1356
|
-
'ovld protocol *': 'allow'
|
|
1357
|
-
'curl -sS -X POST *': 'allow',
|
|
1358
|
-
'curl -s -X POST *': 'allow'
|
|
1329
|
+
'ovld protocol *': 'allow'
|
|
1359
1330
|
}
|
|
1360
1331
|
}
|
|
1361
1332
|
};
|
|
@@ -1374,21 +1345,12 @@ function installOpenCodePermissions(_platformUrl) {
|
|
|
1374
1345
|
return true;
|
|
1375
1346
|
}
|
|
1376
1347
|
|
|
1377
|
-
function installCodexPermissions(
|
|
1348
|
+
function installCodexPermissions() {
|
|
1378
1349
|
console.log(`--- Codex ---`);
|
|
1379
1350
|
console.log(' Codex does not support file-based permission configuration.');
|
|
1380
|
-
console.log(' To warm up permissions, run the following
|
|
1381
|
-
console.log(' (Codex will prompt for approval; approve
|
|
1382
|
-
|
|
1383
|
-
const PROTOCOL_ENDPOINTS = [
|
|
1384
|
-
'attach', 'update', 'ask', 'read-context', 'write-context', 'deliver',
|
|
1385
|
-
'create-ticket', 'list-tickets'
|
|
1386
|
-
];
|
|
1387
|
-
|
|
1388
|
-
for (const endpoint of PROTOCOL_ENDPOINTS) {
|
|
1389
|
-
console.log(` curl -s -X POST "${platformUrl}/api/protocol/${endpoint}" -H "Content-Type: application/json" -H "Authorization: Bearer \\$AGENT_TOKEN" -d '{}'`);
|
|
1390
|
-
}
|
|
1391
|
-
console.log(` curl -s -H "Authorization: Bearer \\$AGENT_TOKEN" "${platformUrl}/api/protocol/context/test"`);
|
|
1351
|
+
console.log(' To warm up permissions, run the following command once inside a Codex session:');
|
|
1352
|
+
console.log(' (Codex will prompt for approval; approve it to persist the prefix.)\n');
|
|
1353
|
+
console.log(' ovld protocol help');
|
|
1392
1354
|
console.log();
|
|
1393
1355
|
return true;
|
|
1394
1356
|
}
|
package/package.json
CHANGED
package/plugins/claude/README.md
CHANGED
|
@@ -6,8 +6,8 @@ Claude Code plugin that exposes the Overlord local ticket workflow to any Claude
|
|
|
6
6
|
|
|
7
7
|
- `skills/overlord-ticket/SKILL.md` — durable attach → update → ask → deliver workflow.
|
|
8
8
|
- `commands/{connect,load,spawn}.md` — slash commands for session routing and ticket creation.
|
|
9
|
-
- `hooks/hooks.json` + `scripts/permission-hook.sh` — PermissionRequest notifier that
|
|
10
|
-
- `userConfig` for `overlord_url`
|
|
9
|
+
- `hooks/hooks.json` + `scripts/permission-hook.sh` — PermissionRequest notifier that calls `ovld protocol permission-request`.
|
|
10
|
+
- `userConfig` for legacy `overlord_url` and `agent_token` installs. Current installs should authenticate with `ovld auth login` or Overlord Desktop; env vars remain optional overrides for remote shells, CI, and explicit token injection.
|
|
11
11
|
|
|
12
12
|
## Requirements
|
|
13
13
|
|
|
@@ -29,7 +29,7 @@ claude plugin marketplace add cooperativ/overlord-marketplace
|
|
|
29
29
|
claude plugin install overlord@cooperativ
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Older plugin versions prompted for `overlord_url` and `agent_token` at install time. The current hook goes through `ovld protocol`, so the CLI resolves auth from env vars or the shared `~/.ovld` credentials written by CLI/Desktop login.
|
|
33
33
|
|
|
34
34
|
## Namespaced components
|
|
35
35
|
|
|
@@ -14,7 +14,7 @@ personal marketplace entry at `~/.agents/plugins/marketplace.json`.
|
|
|
14
14
|
## Requirements
|
|
15
15
|
|
|
16
16
|
- Install the Overlord CLI so `ovld` is available on `PATH`.
|
|
17
|
-
-
|
|
17
|
+
- Authenticate with `ovld auth login` or Overlord Desktop. `OVERLORD_URL` and `AGENT_TOKEN` are optional overrides, mainly for remote shells, CI, or explicit token injection.
|
|
18
18
|
- Optionally set `OVLD_BIN` if the CLI lives at a non-standard path.
|
|
19
19
|
|
|
20
20
|
## Tool coverage
|
|
@@ -51,16 +51,20 @@ const tools = [
|
|
|
51
51
|
properties: {
|
|
52
52
|
ticket_id: { type: 'string', description: 'Target ticket ID' },
|
|
53
53
|
agent: { type: 'string' },
|
|
54
|
+
model: { type: 'string' },
|
|
54
55
|
method: { type: 'string' },
|
|
55
|
-
external_session_id: { type: ['string', 'null'] }
|
|
56
|
+
external_session_id: { type: ['string', 'null'] },
|
|
57
|
+
metadata: { type: 'object' }
|
|
56
58
|
},
|
|
57
59
|
required: ['ticket_id']
|
|
58
60
|
},
|
|
59
61
|
toCliFlags: args => ({
|
|
60
62
|
'ticket-id': args.ticket_id,
|
|
61
63
|
agent: args.agent,
|
|
64
|
+
model: args.model,
|
|
62
65
|
method: args.method,
|
|
63
|
-
'external-session-id': args.external_session_id
|
|
66
|
+
'external-session-id': args.external_session_id,
|
|
67
|
+
'metadata-json': args.metadata
|
|
64
68
|
}),
|
|
65
69
|
subcommand: 'attach'
|
|
66
70
|
},
|
|
@@ -116,6 +120,8 @@ const tools = [
|
|
|
116
120
|
parent_session_key: { type: 'string' },
|
|
117
121
|
parent_ticket_id: { type: 'string' },
|
|
118
122
|
agent: { type: 'string' },
|
|
123
|
+
model: { type: 'string' },
|
|
124
|
+
metadata: { type: 'object' },
|
|
119
125
|
method: { type: 'string' }
|
|
120
126
|
},
|
|
121
127
|
required: ['objective']
|
|
@@ -133,6 +139,8 @@ const tools = [
|
|
|
133
139
|
'parent-session-key': args.parent_session_key,
|
|
134
140
|
'parent-ticket-id': args.parent_ticket_id,
|
|
135
141
|
agent: args.agent,
|
|
142
|
+
model: args.model,
|
|
143
|
+
'metadata-json': args.metadata,
|
|
136
144
|
method: args.method
|
|
137
145
|
}),
|
|
138
146
|
subcommand: 'spawn'
|