overlord-cli 0.2605291450.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 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)
@@ -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 (envAccessToken && isEnvAccessTokenUsable(envAccessToken)) {
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;
@@ -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
@@ -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:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2605291450.0",
3
+ "version": "0.2605300746.0",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "homepage": "https://www.ovld.ai/docs/",
6
6
  "type": "module",