@oml/cli 0.14.2 → 0.14.4

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/src/auth/auth.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  DEFAULT_SUPABASE_ANON_KEY,
11
11
  DEFAULT_SUPABASE_URL
12
12
  } from './constants.js';
13
+
13
14
  const GITHUB_DEVICE_CODE_URL = 'https://github.com/login/device/code';
14
15
  const GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token';
15
16
  const GITHUB_USER_URL = 'https://api.github.com/user';
@@ -17,20 +18,40 @@ const DEFAULT_GITHUB_CLIENT_ID = 'Ov23liQkHYczdOAvHp5P';
17
18
  const API_BASE_URL_ENV = 'OML_PLATFORM_API_URL';
18
19
  const SUPABASE_URL_ENV = 'OML_SUPABASE_URL';
19
20
  const SUPABASE_ANON_KEY_ENV = 'OML_SUPABASE_ANON_KEY';
21
+ const API_KEY_ENV = 'OML_PLATFORM_API_KEY';
22
+ const KEYCHAIN_SERVICE = 'oml-code';
23
+ const ACCESS_TOKEN_KEY = 'oml.cli.access_token';
24
+ const REFRESH_TOKEN_KEY = 'oml.cli.refresh_token';
25
+ const EXPIRES_AT_KEY = 'oml.cli.expires_at';
26
+ const PROFILE_PATH = path.join(os.homedir(), '.oml', 'cli-profile.json');
27
+ const LOCK_PATH = path.join(os.homedir(), '.oml', 'credentials.lock');
28
+ const SESSION_EXPIRATION_LEEWAY_MS = 20_000;
29
+ const LOCK_TIMEOUT_MS = 5_000;
30
+ const LOCK_POLL_INTERVAL_MS = 200;
31
+
32
+ type KeytarModule = {
33
+ getPassword(service: string, account: string): Promise<string | null>;
34
+ setPassword(service: string, account: string, password: string): Promise<void>;
35
+ deletePassword(service: string, account: string): Promise<boolean>;
36
+ };
37
+
38
+ let keytarModulePromise: Promise<KeytarModule> | undefined;
20
39
 
21
40
  type Provider = 'github';
22
41
 
23
- type StoredSession = {
42
+ type StoredProfile = {
24
43
  provider: Provider;
25
44
  userId: string;
26
45
  userLabel?: string;
27
46
  email: string | null;
28
47
  tier?: string;
48
+ signedInAt: string;
49
+ };
50
+
51
+ type StoredCredential = {
29
52
  accessToken: string;
30
53
  refreshToken: string;
31
- tokenType: string;
32
- expiresIn: number;
33
- signedInAt: string;
54
+ expiresAtMs: number;
34
55
  };
35
56
 
36
57
  type LoginOptions = {
@@ -43,134 +64,222 @@ export type OmlCliServerAuthSnapshot = {
43
64
  };
44
65
 
45
66
  export class OmlCliAuthService {
67
+ async login(_options: LoginOptions): Promise<void> {
68
+ const apiKey = process.env[API_KEY_ENV]?.trim();
69
+ if (apiKey) {
70
+ console.error(chalk.yellow(
71
+ 'OML_PLATFORM_API_KEY is set and will take precedence over stored OAuth credentials. ' +
72
+ 'Unset it to use interactive CLI login.'
73
+ ));
74
+ console.error('Run `oml whoami` to inspect the currently effective authentication mode.');
75
+ return;
76
+ }
46
77
 
47
- async login(options: LoginOptions): Promise<void> {
48
- const session = await this.authenticate();
49
- await writeSession(session, true);
78
+ const existing = await this.tryGetValidSnapshot();
79
+ if (existing) {
80
+ const profile = await readProfile();
81
+ console.error(chalk.green(
82
+ `Already signed in as ${profile?.userLabel ?? profile?.email ?? profile?.userId ?? 'current user'}.`
83
+ ));
84
+ return;
85
+ }
86
+
87
+ const session = await authenticateWithGitHub();
88
+ await writeCredential({
89
+ accessToken: session.accessToken,
90
+ refreshToken: session.refreshToken,
91
+ expiresAtMs: session.expiresAtMs,
92
+ });
93
+ await writeProfile({
94
+ provider: session.provider,
95
+ userId: session.userId,
96
+ userLabel: session.userLabel,
97
+ email: session.email,
98
+ tier: session.tier,
99
+ signedInAt: new Date().toISOString(),
100
+ });
50
101
  const summary = session.userLabel ?? session.email ?? 'signed-in user';
51
102
  console.error(chalk.green(`Signed in as ${summary} via ${session.provider}.`));
52
103
  }
53
104
 
54
105
  async logout(): Promise<void> {
55
- await deleteSession();
56
- console.error(chalk.green('Signed out.'));
106
+ const activeServers = await listActiveServers();
107
+ await deleteCredential();
108
+ await deleteProfile();
109
+ if (activeServers.length > 0) {
110
+ console.error(chalk.yellow('Stored OAuth credentials were cleared. Running servers were not stopped:'));
111
+ for (const server of activeServers) {
112
+ console.error(`- ${server.workspaceRoot ?? '(unknown workspace)'} on port ${server.port} (pid ${server.pid})`);
113
+ }
114
+ if (process.env[API_KEY_ENV]?.trim()) {
115
+ console.error(chalk.yellow('OML_PLATFORM_API_KEY is still set, so future starts may use API-key auth.'));
116
+ }
117
+ return;
118
+ }
119
+ console.error(chalk.green('Stored OAuth credentials were cleared.'));
120
+ if (process.env[API_KEY_ENV]?.trim()) {
121
+ console.error(chalk.yellow('OML_PLATFORM_API_KEY is still set, so future starts may use API-key auth.'));
122
+ }
57
123
  }
58
124
 
59
125
  async whoami(): Promise<void> {
60
- const session = await readSession();
61
- if (!session) {
126
+ const apiKey = process.env[API_KEY_ENV]?.trim();
127
+ if (apiKey) {
128
+ console.error('Auth mode: api_key');
129
+ console.error('Account: resolved by OML Platform from OML_PLATFORM_API_KEY');
130
+ const profile = await readProfile();
131
+ if (profile) {
132
+ console.error(`Stored OAuth user: ${profile.userLabel ?? profile.email ?? profile.userId}`);
133
+ }
134
+ return;
135
+ }
136
+
137
+ const credential = await readCredential();
138
+ const profile = await readProfile();
139
+ if (!credential || !profile) {
62
140
  console.error(chalk.yellow('Not signed in.'));
63
141
  return;
64
142
  }
65
- console.error(`Provider: ${session.provider}`);
66
- console.error(`User ID: ${session.userId}`);
67
- console.error(`User label: ${session.userLabel ?? '(not set)'}`);
68
- console.error(`Email: ${session.email ?? '(not set)'}`);
69
- console.error(`Tier: ${session.tier ?? '(not set)'}`);
70
- console.error(`Signed in at: ${session.signedInAt}`);
143
+ console.error('Auth mode: oauth');
144
+ console.error(`Provider: ${profile.provider}`);
145
+ console.error(`User ID: ${profile.userId}`);
146
+ console.error(`User label: ${profile.userLabel ?? '(not set)'}`);
147
+ console.error(`Email: ${profile.email ?? '(not set)'}`);
148
+ console.error(`Tier: ${profile.tier ?? '(not set)'}`);
149
+ console.error(`Signed in at: ${profile.signedInAt}`);
150
+ console.error(`Access token expires at: ${new Date(credential.expiresAtMs).toISOString()}`);
71
151
  }
72
152
 
73
153
  async ensureAuthenticated(operationName: string): Promise<void> {
74
- const session = await readSession();
154
+ if (process.env[API_KEY_ENV]?.trim()) {
155
+ return;
156
+ }
157
+ const session = await readCredential();
75
158
  if (!session) {
76
159
  throw new Error(`${operationName} requires authentication. Run 'oml login' first.`);
77
160
  }
78
161
  }
79
162
 
80
163
  async getAccessToken(): Promise<string> {
81
- const session = await readSession();
82
- if (!session?.accessToken) {
83
- throw new Error('OML CLI authentication is required. Run \'oml login\' first.');
84
- }
164
+ const session = await this.getServerAuthSnapshot();
85
165
  return session.accessToken;
86
166
  }
87
167
 
88
168
  async refreshAccessToken(): Promise<string> {
89
- const session = await readSession();
90
- if (!session?.refreshToken) {
91
- throw new Error('OML CLI authentication is required. Run \'oml login\' first.');
92
- }
93
-
94
- let refreshed;
95
- try {
96
- refreshed = await refreshSupabaseAccessToken(
97
- resolveSupabaseUrl(),
98
- resolveSupabaseAnonKey(),
99
- session.refreshToken
100
- );
101
- } catch {
102
- throw new Error('Authentication refresh failed. Check your network connection or sign in again with \'oml login\'.');
103
- }
104
-
105
- const updatedSession: StoredSession = {
106
- ...session,
107
- accessToken: refreshed.access_token,
108
- refreshToken: refreshed.refresh_token,
109
- tokenType: refreshed.token_type,
110
- expiresIn: refreshed.expires_in,
111
- email: refreshed.email ?? session.email,
112
- signedInAt: new Date().toISOString(),
113
- };
114
-
115
- await writeSession(updatedSession);
116
- return updatedSession.accessToken;
169
+ const refreshed = await this.refreshCredential();
170
+ return refreshed.accessToken;
117
171
  }
118
172
 
119
173
  async getServerAuthSnapshot(): Promise<OmlCliServerAuthSnapshot> {
120
- const session = await readSession();
174
+ const session = await readCredential();
121
175
  if (!session?.accessToken) {
122
176
  throw new Error('OML CLI authentication is required. Run \'oml login\' first.');
123
177
  }
124
- const signedInAtMs = Date.parse(session.signedInAt);
125
- const expiresAtMs = Number.isFinite(signedInAtMs)
126
- ? signedInAtMs + (session.expiresIn * 1000)
127
- : undefined;
128
- if (expiresAtMs !== undefined && Date.now() + 20_000 >= expiresAtMs) {
129
- await this.refreshAccessToken();
130
- return await this.getServerAuthSnapshot();
178
+ if (Date.now() + SESSION_EXPIRATION_LEEWAY_MS >= session.expiresAtMs) {
179
+ const refreshed = await this.refreshCredential();
180
+ return {
181
+ accessToken: refreshed.accessToken,
182
+ refreshToken: refreshed.refreshToken,
183
+ expiresAtMs: refreshed.expiresAtMs,
184
+ };
131
185
  }
132
186
  return {
133
187
  accessToken: session.accessToken,
134
188
  refreshToken: session.refreshToken,
135
- expiresAtMs,
189
+ expiresAtMs: session.expiresAtMs,
136
190
  };
137
191
  }
138
192
 
139
- async storeRefreshedTokens(accessToken: string, refreshToken: string, expiresAtMs: number): Promise<void> {
140
- const session = await readSession();
141
- if (!session) {
142
- return;
193
+ private async tryGetValidSnapshot(): Promise<OmlCliServerAuthSnapshot | null> {
194
+ try {
195
+ return await this.getServerAuthSnapshot();
196
+ } catch {
197
+ return null;
143
198
  }
144
- const expiresIn = Math.max(0, Math.round((expiresAtMs - Date.now()) / 1000));
145
- const updatedSession: StoredSession = {
146
- ...session,
147
- accessToken,
148
- refreshToken,
149
- expiresIn,
150
- signedInAt: new Date().toISOString(),
151
- };
152
- await writeSession(updatedSession);
153
199
  }
154
200
 
155
- private async authenticate(): Promise<StoredSession> {
156
- return authenticateWithGitHub();
201
+ private async refreshCredential(): Promise<StoredCredential> {
202
+ const session = await readCredential();
203
+ if (!session?.refreshToken) {
204
+ throw new Error('OML CLI authentication is required. Run \'oml login\' first.');
205
+ }
206
+
207
+ const lock = await acquireCredentialLock();
208
+ try {
209
+ const latest = await readCredential();
210
+ if (latest && Date.now() + SESSION_EXPIRATION_LEEWAY_MS < latest.expiresAtMs) {
211
+ return latest;
212
+ }
213
+ const source = latest ?? session;
214
+ const refreshed = await refreshSupabaseAccessToken(
215
+ resolveSupabaseUrl(),
216
+ resolveSupabaseAnonKey(),
217
+ source.refreshToken
218
+ );
219
+ const updatedSession: StoredCredential = {
220
+ accessToken: refreshed.access_token,
221
+ refreshToken: refreshed.refresh_token,
222
+ expiresAtMs: Date.now() + (refreshed.expires_in * 1000),
223
+ };
224
+ await writeCredential(updatedSession);
225
+ const profile = await readProfile();
226
+ if (profile) {
227
+ await writeProfile({
228
+ ...profile,
229
+ email: refreshed.email ?? profile.email,
230
+ });
231
+ }
232
+ return updatedSession;
233
+ } catch (error) {
234
+ if (isUnauthorizedError(error)) {
235
+ await deleteCredential();
236
+ await deleteProfile();
237
+ throw new Error('Authentication refresh failed because the stored credential was revoked. Run \'oml login\' again.');
238
+ }
239
+ throw new Error('Authentication refresh failed. Check your network connection or sign in again with \'oml login\'.');
240
+ } finally {
241
+ await lock.release();
242
+ }
243
+ }
244
+ }
245
+
246
+ async function readCredential(): Promise<StoredCredential | undefined> {
247
+ const keytar = await getKeytarModule();
248
+ const [accessToken, refreshToken, expiresAtRaw] = await Promise.all([
249
+ keytar.getPassword(KEYCHAIN_SERVICE, ACCESS_TOKEN_KEY),
250
+ keytar.getPassword(KEYCHAIN_SERVICE, REFRESH_TOKEN_KEY),
251
+ keytar.getPassword(KEYCHAIN_SERVICE, EXPIRES_AT_KEY),
252
+ ]);
253
+ const expiresAtMs = Number(expiresAtRaw ?? NaN);
254
+ if (!accessToken || !refreshToken || !Number.isFinite(expiresAtMs)) {
255
+ return undefined;
157
256
  }
257
+ return { accessToken, refreshToken, expiresAtMs };
158
258
  }
159
259
 
160
- async function readSession(): Promise<StoredSession | undefined> {
260
+ async function writeCredential(credential: StoredCredential): Promise<void> {
261
+ const keytar = await getKeytarModule();
262
+ await Promise.all([
263
+ keytar.setPassword(KEYCHAIN_SERVICE, ACCESS_TOKEN_KEY, credential.accessToken),
264
+ keytar.setPassword(KEYCHAIN_SERVICE, REFRESH_TOKEN_KEY, credential.refreshToken),
265
+ keytar.setPassword(KEYCHAIN_SERVICE, EXPIRES_AT_KEY, String(credential.expiresAtMs)),
266
+ ]);
267
+ }
268
+
269
+ async function deleteCredential(): Promise<void> {
270
+ const keytar = await getKeytarModule();
271
+ await Promise.all([
272
+ keytar.deletePassword(KEYCHAIN_SERVICE, ACCESS_TOKEN_KEY),
273
+ keytar.deletePassword(KEYCHAIN_SERVICE, REFRESH_TOKEN_KEY),
274
+ keytar.deletePassword(KEYCHAIN_SERVICE, EXPIRES_AT_KEY),
275
+ ]);
276
+ }
277
+
278
+ async function readProfile(): Promise<StoredProfile | undefined> {
161
279
  try {
162
- const content = await fs.readFile(getSessionPath(), 'utf-8');
163
- const data = JSON.parse(content) as Partial<StoredSession>;
164
- if (!data.userId || !data.provider || !data.signedInAt) {
165
- return undefined;
166
- }
167
- if (!data.accessToken) {
168
- return undefined;
169
- }
170
- if (!data.refreshToken || !data.tokenType || typeof data.expiresIn !== 'number') {
171
- return undefined;
172
- }
173
- if (data.provider !== 'github') {
280
+ const content = await fs.readFile(PROFILE_PATH, 'utf-8');
281
+ const data = JSON.parse(content) as Partial<StoredProfile>;
282
+ if (!data.provider || !data.userId || !data.signedInAt) {
174
283
  return undefined;
175
284
  }
176
285
  return {
@@ -179,34 +288,21 @@ async function readSession(): Promise<StoredSession | undefined> {
179
288
  userLabel: data.userLabel,
180
289
  email: data.email ?? null,
181
290
  tier: data.tier,
182
- accessToken: data.accessToken,
183
- refreshToken: data.refreshToken,
184
- tokenType: data.tokenType,
185
- expiresIn: data.expiresIn,
186
- signedInAt: data.signedInAt
291
+ signedInAt: data.signedInAt,
187
292
  };
188
293
  } catch {
189
294
  return undefined;
190
295
  }
191
296
  }
192
297
 
193
- async function writeSession(session: StoredSession, warnAboutPlaintext = false): Promise<void> {
194
- const sessionPath = getSessionPath();
195
- await fs.mkdir(path.dirname(sessionPath), { recursive: true });
196
- await fs.writeFile(sessionPath, `${JSON.stringify(session, null, 2)}\n`, 'utf-8');
197
- if (warnAboutPlaintext) {
198
- process.stderr.write(
199
- chalk.yellow(
200
- `[oml] Warning: credentials stored in plaintext at ${sessionPath}. ` +
201
- `A system keychain is not available in this environment.\n`
202
- )
203
- );
204
- }
298
+ async function writeProfile(profile: StoredProfile): Promise<void> {
299
+ await fs.mkdir(path.dirname(PROFILE_PATH), { recursive: true });
300
+ await fs.writeFile(PROFILE_PATH, `${JSON.stringify(profile, null, 2)}\n`, 'utf-8');
205
301
  }
206
302
 
207
- async function deleteSession(): Promise<void> {
303
+ async function deleteProfile(): Promise<void> {
208
304
  try {
209
- await fs.unlink(getSessionPath());
305
+ await fs.unlink(PROFILE_PATH);
210
306
  } catch (error) {
211
307
  if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
212
308
  throw error;
@@ -214,11 +310,16 @@ async function deleteSession(): Promise<void> {
214
310
  }
215
311
  }
216
312
 
217
- function getSessionPath(): string {
218
- return path.join(os.homedir(), '.oml', 'auth.json');
219
- }
220
-
221
- async function authenticateWithGitHub(): Promise<StoredSession> {
313
+ async function authenticateWithGitHub(): Promise<{
314
+ provider: Provider;
315
+ userId: string;
316
+ userLabel?: string;
317
+ email: string | null;
318
+ tier?: string;
319
+ accessToken: string;
320
+ refreshToken: string;
321
+ expiresAtMs: number;
322
+ }> {
222
323
  const clientId = resolveClientId();
223
324
  const params = new URLSearchParams({
224
325
  client_id: clientId,
@@ -266,9 +367,7 @@ async function authenticateWithGitHub(): Promise<StoredSession> {
266
367
  tier: platformSession.tier,
267
368
  accessToken: platformSession.access_token,
268
369
  refreshToken: platformSession.refresh_token,
269
- tokenType: platformSession.token_type,
270
- expiresIn: platformSession.expires_in,
271
- signedInAt: new Date().toISOString()
370
+ expiresAtMs: Date.now() + platformSession.expires_in * 1000,
272
371
  };
273
372
  }
274
373
 
@@ -323,6 +422,59 @@ async function pollForGitHubAccessToken(clientId: string, device: GitHubDeviceCo
323
422
  }
324
423
  }
325
424
 
425
+ async function acquireCredentialLock(): Promise<{ release: () => Promise<void> }> {
426
+ await fs.mkdir(path.dirname(LOCK_PATH), { recursive: true });
427
+ const startedAt = Date.now();
428
+ while (Date.now() - startedAt < LOCK_TIMEOUT_MS) {
429
+ try {
430
+ const handle = await fs.open(LOCK_PATH, 'wx');
431
+ return {
432
+ release: async () => {
433
+ await handle.close();
434
+ await fs.rm(LOCK_PATH, { force: true });
435
+ },
436
+ };
437
+ } catch (error) {
438
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {
439
+ throw error;
440
+ }
441
+ await delay(LOCK_POLL_INTERVAL_MS);
442
+ }
443
+ }
444
+ throw new Error('Timed out waiting for the CLI credential lock.');
445
+ }
446
+
447
+ async function listActiveServers(): Promise<Array<{ pid: number; port: number; workspaceRoot?: string }>> {
448
+ const baseDir = path.join(os.homedir(), '.oml', 'workspaces');
449
+ try {
450
+ const workspaceDirs = await fs.readdir(baseDir);
451
+ const active: Array<{ pid: number; port: number; workspaceRoot?: string }> = [];
452
+ for (const workspaceDir of workspaceDirs) {
453
+ const lockFile = path.join(baseDir, workspaceDir, 'server.lock');
454
+ try {
455
+ const raw = await fs.readFile(lockFile, 'utf-8');
456
+ const parsed = JSON.parse(raw) as { pid?: unknown; port?: unknown; workspaceRoot?: unknown };
457
+ const pid = Number(parsed.pid);
458
+ const port = Number(parsed.port);
459
+ if (!Number.isFinite(pid) || !Number.isFinite(port)) {
460
+ continue;
461
+ }
462
+ process.kill(Math.trunc(pid), 0);
463
+ active.push({
464
+ pid: Math.trunc(pid),
465
+ port: Math.trunc(port),
466
+ workspaceRoot: typeof parsed.workspaceRoot === 'string' ? parsed.workspaceRoot : undefined,
467
+ });
468
+ } catch {
469
+ // ignore malformed or stale lock entries
470
+ }
471
+ }
472
+ return active;
473
+ } catch {
474
+ return [];
475
+ }
476
+ }
477
+
326
478
  function resolveClientId(): string {
327
479
  const configured = process.env.OML_AUTH_GITHUB_CLIENT_ID?.trim() || DEFAULT_GITHUB_CLIENT_ID;
328
480
  if (configured) {
@@ -349,6 +501,32 @@ function delay(ms: number): Promise<void> {
349
501
  return new Promise((resolve) => setTimeout(resolve, ms));
350
502
  }
351
503
 
504
+ function isUnauthorizedError(error: unknown): boolean {
505
+ const message = error instanceof Error ? error.message : String(error);
506
+ return /\b401\b/.test(message);
507
+ }
508
+
509
+ async function getKeytarModule(): Promise<KeytarModule> {
510
+ if (!keytarModulePromise) {
511
+ keytarModulePromise = import('keytar')
512
+ .then((loaded) => loaded.default as KeytarModule)
513
+ .catch((error: unknown) => {
514
+ keytarModulePromise = undefined;
515
+ throw new Error(formatKeytarLoadError(error));
516
+ });
517
+ }
518
+ return keytarModulePromise;
519
+ }
520
+
521
+ function formatKeytarLoadError(error: unknown): string {
522
+ const message = error instanceof Error ? error.message : String(error);
523
+ if (/libsecret|dlopen|ERR_DLOPEN_FAILED/i.test(message)) {
524
+ return 'Secure credential storage is unavailable (missing system keychain runtime, e.g. libsecret on Linux). ' +
525
+ 'Install the OS keychain runtime or set OML_PLATFORM_API_KEY for non-interactive mode.';
526
+ }
527
+ return `Secure credential storage is unavailable: ${message}`;
528
+ }
529
+
352
530
  type GitHubDeviceCodeResponse = {
353
531
  device_code?: string;
354
532
  user_code?: string;
@@ -363,6 +541,5 @@ type GitHubTokenResponse = {
363
541
  };
364
542
 
365
543
  type GitHubUserResponse = {
366
- id?: number;
367
544
  login?: string;
368
545
  };
@@ -8,7 +8,7 @@
8
8
  * commands to execute.
9
9
  */
10
10
 
11
- import { OmlClient, FileStorageAdapter, installNodeShutdownHandlers, exchangeApiToken } from '@oml/platform';
11
+ import { OmlClient, FileStorageAdapter, installNodeShutdownHandlers } from '@oml/platform';
12
12
  import type { OmlClientConfig } from '@oml/platform';
13
13
  import type { NodeShutdownHandle } from '@oml/platform';
14
14
  import chalk from 'chalk';
@@ -16,7 +16,6 @@ import { DEFAULT_API_BASE_URL } from './constants.js';
16
16
  import { OmlCliAuthService } from './auth.js';
17
17
  const API_BASE_URL_ENV = 'OML_PLATFORM_API_URL';
18
18
  const API_KEY_ENV = 'OML_PLATFORM_API_KEY';
19
- const OIDC_TOKEN_ENV = 'OML_CI_TOKEN';
20
19
 
21
20
  let client: OmlClient | null = null;
22
21
  let shutdownHandle: NodeShutdownHandle | null = null;
@@ -37,24 +36,6 @@ export async function initializePlatform(
37
36
  const resolvedApiBaseUrl = process.env[API_BASE_URL_ENV]?.trim() || apiBaseUrl;
38
37
 
39
38
  const apiKey = process.env[API_KEY_ENV]?.trim();
40
- let apiKeyAccessToken: string | undefined;
41
- let apiKeyAccessTokenExpiresAtMs = 0;
42
-
43
- const getApiKeyAccessToken = async (): Promise<string> => {
44
- const refreshLeewayMs = 20_000;
45
- if (apiKeyAccessToken && Date.now() + refreshLeewayMs < apiKeyAccessTokenExpiresAtMs) {
46
- return apiKeyAccessToken;
47
- }
48
- if (!apiKey) {
49
- throw new Error('OML API key is missing.');
50
- }
51
- const oidcToken = process.env[OIDC_TOKEN_ENV]?.trim() || undefined;
52
- const exchanged = await exchangeApiToken(resolvedApiBaseUrl, apiKey, oidcToken);
53
- apiKeyAccessToken = exchanged.accessToken;
54
- apiKeyAccessTokenExpiresAtMs = Date.now() + (Math.max(1, exchanged.expiresIn) * 1000);
55
- return apiKeyAccessToken;
56
- };
57
-
58
39
  const config: OmlClientConfig = {
59
40
  apiBaseUrl: resolvedApiBaseUrl,
60
41
  tool: 'oml-cli',
@@ -62,21 +43,14 @@ export async function initializePlatform(
62
43
  auth: apiKey
63
44
  ? {
64
45
  method: 'oauth',
65
- getToken: () => getApiKeyAccessToken(),
66
- refreshToken: () => getApiKeyAccessToken(),
46
+ getToken: async () => apiKey,
47
+ refreshToken: async () => apiKey,
67
48
  }
68
49
  : {
69
50
  method: 'oauth',
70
51
  getToken: () => authService.getAccessToken(),
71
52
  refreshToken: () => authService.refreshAccessToken(),
72
53
  },
73
- onConcurrencyLimit: (info) => {
74
- console.error(chalk.yellow(
75
- `OML Platform: concurrent session limit reached `
76
- + `(${info.active_sessions}/${info.max_sessions}). `
77
- + `Close another instance or upgrade your plan.`
78
- ));
79
- },
80
54
  onAuthError: (error) => {
81
55
  console.error(chalk.red(
82
56
  `OML Platform: authentication error — ${toGenericPlatformErrorMessage(error)}`
@@ -97,7 +71,7 @@ export async function initializePlatform(
97
71
 
98
72
  /**
99
73
  * Dispose the platform client. Flushes any buffered telemetry
100
- * and ends the session. Call at the end of CLI execution.
74
+ * and flushes buffered telemetry. Call at the end of CLI execution.
101
75
  */
102
76
  export async function disposePlatform(): Promise<void> {
103
77
  if (shutdownHandle) {