overlord-cli 0.2605291046.0 → 0.2605300746.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 +43 -0
- package/bin/_cli/credentials.mjs +53 -3
- package/bin/_cli/direct-launch.mjs +1 -1
- package/bin/_cli/index.mjs +1 -1
- package/bin/_cli/launcher.mjs +35 -6
- package/bin/_cli/protocol.mjs +1 -1
- package/package.json +1 -1
package/bin/_cli/auth.mjs
CHANGED
|
@@ -500,7 +500,27 @@ function parseOrganizationFlag(args) {
|
|
|
500
500
|
return parsed;
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
+
function parseTokenFlag(args) {
|
|
504
|
+
const index = args.findIndex(arg => arg === '--token' || arg.startsWith('--token='));
|
|
505
|
+
if (index === -1) return null;
|
|
506
|
+
const raw = args[index].includes('=') ? args[index].split('=')[1] : args[index + 1];
|
|
507
|
+
if (!raw || typeof raw !== 'string') {
|
|
508
|
+
throw new Error('--token requires a value (e.g. --token oat_…)');
|
|
509
|
+
}
|
|
510
|
+
const trimmed = raw.trim();
|
|
511
|
+
if (!trimmed.startsWith('oat_')) {
|
|
512
|
+
throw new Error('--token value must be an agent token starting with oat_');
|
|
513
|
+
}
|
|
514
|
+
return trimmed;
|
|
515
|
+
}
|
|
516
|
+
|
|
503
517
|
export async function authLogin(args = []) {
|
|
518
|
+
const agentToken = parseTokenFlag(args);
|
|
519
|
+
|
|
520
|
+
if (agentToken) {
|
|
521
|
+
return authLoginWithAgentToken(agentToken, args);
|
|
522
|
+
}
|
|
523
|
+
|
|
504
524
|
const preselectedOrganizationId = parseOrganizationFlag(args);
|
|
505
525
|
const storedCredentials = loadCredentials();
|
|
506
526
|
const platformUrl = resolveLoginPlatformUrl(null, storedCredentials?.platform_url ?? null);
|
|
@@ -558,6 +578,27 @@ export async function authLogin(args = []) {
|
|
|
558
578
|
}
|
|
559
579
|
}
|
|
560
580
|
|
|
581
|
+
async function authLoginWithAgentToken(agentToken, args) {
|
|
582
|
+
const preselectedOrganizationId = parseOrganizationFlag(args);
|
|
583
|
+
const storedCredentials = loadCredentials();
|
|
584
|
+
const platformUrl = resolveLoginPlatformUrl(null, storedCredentials?.platform_url ?? null);
|
|
585
|
+
|
|
586
|
+
saveCredentials({
|
|
587
|
+
agent_token: agentToken,
|
|
588
|
+
platform_url: platformUrl,
|
|
589
|
+
...(preselectedOrganizationId !== null ? { organization_id: preselectedOrganizationId } : {})
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
console.log(`Agent token saved. The CLI will use this token for all protocol commands.`);
|
|
593
|
+
if (preselectedOrganizationId !== null) {
|
|
594
|
+
console.log(` Default organization: ${preselectedOrganizationId}`);
|
|
595
|
+
} else {
|
|
596
|
+
console.log(` Organization will be derived from your token's membership.`);
|
|
597
|
+
}
|
|
598
|
+
console.log(` Platform URL: ${platformUrl}`);
|
|
599
|
+
console.log(`\nTo remove: ovld auth logout`);
|
|
600
|
+
}
|
|
601
|
+
|
|
561
602
|
async function printVerboseAuthStatus() {
|
|
562
603
|
const status = await getAuthStatus();
|
|
563
604
|
if (!status.isLoggedIn) {
|
|
@@ -638,6 +679,8 @@ export async function runAuthCommand(subcommand, args = []) {
|
|
|
638
679
|
|
|
639
680
|
Subcommands:
|
|
640
681
|
login Authorize the CLI via browser (works locally or over SSH)
|
|
682
|
+
--token <oat_…> Persist an agent token from Settings → Agents & MCP.
|
|
683
|
+
Skips the browser flow; token never expires.
|
|
641
684
|
--organization-id <id> Optional default organization override
|
|
642
685
|
(ticket-scoped commands infer organization
|
|
643
686
|
from ticket ids such as 1:899)
|
package/bin/_cli/credentials.mjs
CHANGED
|
@@ -79,19 +79,21 @@ function parseStoredCredentialsData(parsed, { requireAuthData = false } = {}) {
|
|
|
79
79
|
typeof parsed.access_token_expires_at === 'string'
|
|
80
80
|
? parsed.access_token_expires_at.trim()
|
|
81
81
|
: '';
|
|
82
|
+
const agentToken = normalizeAgentToken(parsed.agent_token);
|
|
82
83
|
const organizationId =
|
|
83
84
|
typeof parsed.organization_id === 'number' && Number.isFinite(parsed.organization_id)
|
|
84
85
|
? parsed.organization_id
|
|
85
86
|
: null;
|
|
86
87
|
|
|
87
88
|
if (!platformUrl) return null;
|
|
88
|
-
if (requireAuthData && !refreshToken) return null;
|
|
89
|
+
if (requireAuthData && !refreshToken && !agentToken) return null;
|
|
89
90
|
|
|
90
91
|
return {
|
|
91
92
|
platform_url: platformUrl,
|
|
92
93
|
...(refreshToken ? { refresh_token: refreshToken } : {}),
|
|
93
94
|
...(accessToken ? { access_token: accessToken } : {}),
|
|
94
95
|
...(accessTokenExpiresAt ? { access_token_expires_at: accessTokenExpiresAt } : {}),
|
|
96
|
+
...(agentToken ? { agent_token: agentToken } : {}),
|
|
95
97
|
...(organizationId ? { organization_id: organizationId } : {}),
|
|
96
98
|
...(typeof parsed.user_email === 'string' && parsed.user_email.trim()
|
|
97
99
|
? { user_email: parsed.user_email.trim() }
|
|
@@ -116,6 +118,7 @@ function normalizeCredentialsForSave(data) {
|
|
|
116
118
|
...(parsed.access_token_expires_at
|
|
117
119
|
? { access_token_expires_at: parsed.access_token_expires_at }
|
|
118
120
|
: {}),
|
|
121
|
+
...(parsed.agent_token ? { agent_token: parsed.agent_token } : {}),
|
|
119
122
|
...(parsed.organization_id ? { organization_id: parsed.organization_id } : {}),
|
|
120
123
|
...(parsed.user_email ? { user_email: parsed.user_email } : {}),
|
|
121
124
|
...(parsed.updated_at ? { updated_at: parsed.updated_at } : {})
|
|
@@ -181,10 +184,13 @@ function selectStoredCredentials() {
|
|
|
181
184
|
requireAuthData: true
|
|
182
185
|
})
|
|
183
186
|
}
|
|
184
|
-
].filter(candidate => candidate.credentials?.refresh_token);
|
|
187
|
+
].filter(candidate => candidate.credentials?.refresh_token || candidate.credentials?.agent_token);
|
|
185
188
|
|
|
186
189
|
if (candidates.length === 0) return null;
|
|
187
190
|
|
|
191
|
+
const agentTokenCandidate = candidates.find(c => c.credentials?.agent_token);
|
|
192
|
+
if (agentTokenCandidate) return agentTokenCandidate;
|
|
193
|
+
|
|
188
194
|
const fresh = candidates.filter(candidate => isAccessTokenFresh(candidate.credentials));
|
|
189
195
|
const pool = fresh.length > 0 ? fresh : candidates;
|
|
190
196
|
return pool.sort(
|
|
@@ -551,6 +557,27 @@ export async function resolveAuth(options = {}) {
|
|
|
551
557
|
? runtime.local_secret
|
|
552
558
|
: '';
|
|
553
559
|
|
|
560
|
+
const envAgentToken = normalizeAgentToken(process.env.OVERLORD_AGENT_TOKEN);
|
|
561
|
+
if (envAgentToken) {
|
|
562
|
+
const envOrganizationId =
|
|
563
|
+
typeof process.env.OVERLORD_ORGANIZATION_ID === 'string'
|
|
564
|
+
? Number.parseInt(process.env.OVERLORD_ORGANIZATION_ID, 10)
|
|
565
|
+
: null;
|
|
566
|
+
const resolvedOrganizationId =
|
|
567
|
+
organizationIdHint ?? (Number.isFinite(envOrganizationId) ? envOrganizationId : null);
|
|
568
|
+
|
|
569
|
+
// Agent tokens are long-lived and never need refreshing. Organization is
|
|
570
|
+
// optional — the platform derives it from the token owner's membership when
|
|
571
|
+
// no hint is supplied, so a headless CLI keeps working without Desktop.
|
|
572
|
+
return {
|
|
573
|
+
platformUrl,
|
|
574
|
+
bearerToken: envAgentToken,
|
|
575
|
+
localSecret,
|
|
576
|
+
organizationId: resolvedOrganizationId,
|
|
577
|
+
authMode: 'agent_token'
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
554
581
|
const envAccessToken = normalizeAccessToken(process.env.OVERLORD_ACCESS_TOKEN);
|
|
555
582
|
if (envAccessToken) {
|
|
556
583
|
const envOrganizationId =
|
|
@@ -575,6 +602,16 @@ export async function resolveAuth(options = {}) {
|
|
|
575
602
|
}
|
|
576
603
|
}
|
|
577
604
|
|
|
605
|
+
if (creds?.agent_token) {
|
|
606
|
+
return {
|
|
607
|
+
platformUrl,
|
|
608
|
+
bearerToken: creds.agent_token,
|
|
609
|
+
localSecret,
|
|
610
|
+
organizationId: organizationIdHint ?? creds.organization_id ?? null,
|
|
611
|
+
authMode: 'agent_token'
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
578
615
|
if (!creds) {
|
|
579
616
|
return {
|
|
580
617
|
platformUrl,
|
|
@@ -649,10 +686,15 @@ export async function getAuthStatus() {
|
|
|
649
686
|
};
|
|
650
687
|
}
|
|
651
688
|
|
|
689
|
+
const envAgentToken = normalizeAgentToken(process.env.OVERLORD_AGENT_TOKEN);
|
|
652
690
|
const envAccessToken = normalizeAccessToken(process.env.OVERLORD_ACCESS_TOKEN);
|
|
653
691
|
let tokenSource = 'fallback';
|
|
654
|
-
if (
|
|
692
|
+
if (envAgentToken) {
|
|
693
|
+
tokenSource = 'OVERLORD_AGENT_TOKEN';
|
|
694
|
+
} else if (envAccessToken && isEnvAccessTokenUsable(envAccessToken)) {
|
|
655
695
|
tokenSource = 'OVERLORD_ACCESS_TOKEN';
|
|
696
|
+
} else if (creds?.agent_token) {
|
|
697
|
+
tokenSource = getCredentialFileSource() + ' (agent token)';
|
|
656
698
|
} else if (creds?.refresh_token) {
|
|
657
699
|
tokenSource = getCredentialFileSource();
|
|
658
700
|
}
|
|
@@ -704,6 +746,14 @@ function normalizeAccessToken(value) {
|
|
|
704
746
|
return value.trim();
|
|
705
747
|
}
|
|
706
748
|
|
|
749
|
+
const AGENT_TOKEN_PREFIX = 'oat_';
|
|
750
|
+
|
|
751
|
+
function normalizeAgentToken(value) {
|
|
752
|
+
if (typeof value !== 'string') return '';
|
|
753
|
+
const trimmed = value.trim();
|
|
754
|
+
return trimmed.startsWith(AGENT_TOKEN_PREFIX) ? trimmed : '';
|
|
755
|
+
}
|
|
756
|
+
|
|
707
757
|
function isEnvAccessTokenUsable(accessToken) {
|
|
708
758
|
const expiresAt = decodeJwtExpiry(accessToken);
|
|
709
759
|
return expiresAt === null || expiresAt * 1000 - Date.now() > 60_000;
|
|
@@ -216,7 +216,7 @@ Overlord flags:
|
|
|
216
216
|
--model <identifier> Preferred model (sets the ticket delegate and is forwarded to the agent)
|
|
217
217
|
--thinking <level> Agent reasoning/effort level
|
|
218
218
|
--flag <value> Extra agent flag (repeatable)
|
|
219
|
-
--pre-command <command>
|
|
219
|
+
--pre-command <command> Shell command to run before the agent binary (e.g. ollama, agent-pod)
|
|
220
220
|
--project-id <uuid> Target project (skips working-directory resolution)
|
|
221
221
|
--personal Create the ticket in your personal project
|
|
222
222
|
--for-human Create a human-review ticket
|
package/bin/_cli/index.mjs
CHANGED
|
@@ -57,7 +57,7 @@ Agents:
|
|
|
57
57
|
Key protocol commands: auth-status, discover-project, create, prompt, attach, connect, load-context.
|
|
58
58
|
|
|
59
59
|
Auth:
|
|
60
|
-
${primaryCommand} auth login Authorize CLI via browser
|
|
60
|
+
${primaryCommand} auth login Authorize CLI via browser or --token <oat_…>
|
|
61
61
|
${primaryCommand} auth status Show login status
|
|
62
62
|
${primaryCommand} auth repair Repair shared Desktop/CLI credentials
|
|
63
63
|
${primaryCommand} auth logout Remove stored credentials
|
package/bin/_cli/launcher.mjs
CHANGED
|
@@ -137,24 +137,53 @@ const supportedAgents = ['claude', 'codex', 'cursor', 'antigravity', 'opencode',
|
|
|
137
137
|
// Re-exported under clearer names for the direct-launch dispatcher in index.mjs.
|
|
138
138
|
export { supportedAgents as BUILTIN_LAUNCH_AGENTS, agentIdentifierMap };
|
|
139
139
|
|
|
140
|
+
/** @internal Test-only overrides for launcher subprocess calls. */
|
|
141
|
+
export const launcherTestHooks = {
|
|
142
|
+
execFileSync: null,
|
|
143
|
+
shell: null,
|
|
144
|
+
platform: null
|
|
145
|
+
};
|
|
146
|
+
|
|
140
147
|
function shellQuote(value) {
|
|
141
148
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
142
149
|
}
|
|
143
150
|
|
|
151
|
+
function resolveLauncherShell() {
|
|
152
|
+
const platform = launcherTestHooks.platform ?? process.platform;
|
|
153
|
+
if (platform === 'win32') {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return launcherTestHooks.shell ?? process.env.SHELL ?? 'sh';
|
|
157
|
+
}
|
|
158
|
+
|
|
144
159
|
/**
|
|
145
160
|
* Run an agent binary, optionally routed through a user-defined pre-command
|
|
146
|
-
* (e.g. `ollama` to run claude-code through Ollama).
|
|
147
|
-
*
|
|
148
|
-
*
|
|
161
|
+
* (e.g. `ollama` to run claude-code through Ollama). On POSIX shells, execute
|
|
162
|
+
* the wrapper through the user's interactive login shell so aliases, functions,
|
|
163
|
+
* and PATH customizations are available:
|
|
164
|
+
* preCommand="agent-pod" → $SHELL -ilc 'agent-pod codex <args...>'
|
|
165
|
+
*
|
|
166
|
+
* The shell must be interactive (`-i`), not just a login shell (`-l`): zsh only
|
|
167
|
+
* sources ~/.zshrc — and bash only sources ~/.bashrc — for interactive shells.
|
|
168
|
+
* Wrappers like agent-pod are commonly installed as a shell alias in ~/.zshrc
|
|
169
|
+
* (its documented default), so a plain `-lc` login shell never sees them and
|
|
170
|
+
* fails with `command not found`.
|
|
149
171
|
*/
|
|
150
172
|
function execAgentBinary(binary, args, opts, preCommand) {
|
|
173
|
+
const exec = launcherTestHooks.execFileSync ?? execFileSync;
|
|
151
174
|
const pre = typeof preCommand === 'string' ? preCommand.trim() : '';
|
|
152
175
|
if (pre) {
|
|
176
|
+
const shell = resolveLauncherShell();
|
|
177
|
+
if (shell) {
|
|
178
|
+
const command = [pre, shellQuote(binary), ...args.map(arg => shellQuote(arg))].join(' ');
|
|
179
|
+
exec(shell, ['-ilc', command], opts);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
153
182
|
const preTokens = pre.split(/\s+/).filter(Boolean);
|
|
154
|
-
|
|
183
|
+
exec(preTokens[0], [...preTokens.slice(1), binary, ...args], opts);
|
|
155
184
|
return;
|
|
156
185
|
}
|
|
157
|
-
|
|
186
|
+
exec(binary, args, opts);
|
|
158
187
|
}
|
|
159
188
|
|
|
160
189
|
function toTomlString(value) {
|
|
@@ -292,7 +321,7 @@ Options:
|
|
|
292
321
|
--model <identifier> Preferred model identifier
|
|
293
322
|
--thinking <level> Agent reasoning/effort level
|
|
294
323
|
--flag <value> Extra agent flag (repeatable)
|
|
295
|
-
--pre-command <command>
|
|
324
|
+
--pre-command <command> Shell command to run before the agent binary (e.g. ollama, agent-pod)
|
|
296
325
|
--ssh-command <command> Launch remotely over SSH by running ovld on the target host
|
|
297
326
|
--remote-working-directory <path> Change to this path on the remote host before launch
|
|
298
327
|
--server-multiplexer <none|tmux> Wrap remote launches in tmux
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -2614,7 +2614,7 @@ Subcommands:
|
|
|
2614
2614
|
Environment fallback:
|
|
2615
2615
|
--session-key <- SESSION_KEY or auto-persisted session from last attach/connect/prompt in this working directory
|
|
2616
2616
|
--ticket-id <- TICKET_ID or auto-persisted session (human-readable ticket_id, e.g. 1:899)
|
|
2617
|
-
auth/host <- OVERLORD_URL, optional OVERLORD_ACCESS_TOKEN + OVERLORD_ORGANIZATION_ID, or shared OAuth credentials from ovld auth/Desktop login
|
|
2617
|
+
auth/host <- OVERLORD_URL, OVERLORD_AGENT_TOKEN (durable; best for headless/CI), optional OVERLORD_ACCESS_TOKEN + OVERLORD_ORGANIZATION_ID, or shared OAuth credentials from ovld auth/Desktop login
|
|
2618
2618
|
--timeout <- OVERLORD_TIMEOUT
|
|
2619
2619
|
|
|
2620
2620
|
Common flags:
|