@gravity-rail/cli 0.8.0 → 0.11.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/README.md +146 -80
- package/dist/{account-verify-phone-TC73QIIE.js → account-verify-phone-7YQXKU3X.js} +4 -4
- package/dist/{chunk-L5YCPUAM.js → chunk-GRLUFHMI.js} +63 -131
- package/dist/chunk-GRLUFHMI.js.map +1 -0
- package/dist/{chunk-H62EC2CF.js → chunk-SIV2WU2I.js} +307 -54
- package/dist/chunk-SIV2WU2I.js.map +1 -0
- package/dist/{chunk-7VZ4JLAJ.js → chunk-WZVMWN3S.js} +2 -1
- package/dist/{chunk-7VZ4JLAJ.js.map → chunk-WZVMWN3S.js.map} +1 -1
- package/dist/cli-client-context-C5l8lg7h.d.ts +130 -0
- package/dist/cli-utils.d.ts +65 -4
- package/dist/cli-utils.js +3 -1
- package/dist/{errors-VK4QO3QZ.js → errors-YHGVIDPO.js} +2 -2
- package/dist/gr.js +9425 -3203
- package/dist/gr.js.map +1 -1
- package/dist/lib.d.ts +77 -9
- package/dist/lib.js +3346 -860
- package/dist/lib.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-H62EC2CF.js.map +0 -1
- package/dist/chunk-L5YCPUAM.js.map +0 -1
- package/dist/cli-client-context-9KRUt6O-.d.ts +0 -98
- /package/dist/{account-verify-phone-TC73QIIE.js.map → account-verify-phone-7YQXKU3X.js.map} +0 -0
- /package/dist/{errors-VK4QO3QZ.js.map → errors-YHGVIDPO.js.map} +0 -0
|
@@ -44,7 +44,7 @@ function detectWorktreePorts() {
|
|
|
44
44
|
for (const match of content.matchAll(/-\s*'(\d+):(\d+)'/g)) {
|
|
45
45
|
const host = parseInt(match[1], 10);
|
|
46
46
|
const container = parseInt(match[2], 10);
|
|
47
|
-
if (container ===
|
|
47
|
+
if (container === 443 || container === 5054) apiPort = host;
|
|
48
48
|
if (container === 3e3) frontendPort = host;
|
|
49
49
|
}
|
|
50
50
|
return { apiPort, frontendPort };
|
|
@@ -86,9 +86,20 @@ function getEnvironmentConfig(env) {
|
|
|
86
86
|
// src/cli-utils/auth.ts
|
|
87
87
|
import { createServer } from "http";
|
|
88
88
|
import { createHash, randomBytes } from "crypto";
|
|
89
|
-
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync, chmodSync, unlinkSync } from "fs";
|
|
90
|
-
import { join as join2 } from "path";
|
|
91
89
|
import { exec } from "child_process";
|
|
90
|
+
|
|
91
|
+
// src/cli-utils/credentials-manager.ts
|
|
92
|
+
import {
|
|
93
|
+
readFileSync as readFileSync2,
|
|
94
|
+
writeFileSync,
|
|
95
|
+
existsSync as existsSync2,
|
|
96
|
+
mkdirSync,
|
|
97
|
+
chmodSync,
|
|
98
|
+
unlinkSync,
|
|
99
|
+
renameSync
|
|
100
|
+
} from "fs";
|
|
101
|
+
import { join as join2, resolve as resolve2, relative, isAbsolute } from "path";
|
|
102
|
+
var DEFAULT_PROFILE = "default";
|
|
92
103
|
var CredentialsManager = class {
|
|
93
104
|
cacheDir;
|
|
94
105
|
apiUrl;
|
|
@@ -96,73 +107,303 @@ var CredentialsManager = class {
|
|
|
96
107
|
this.cacheDir = join2(rootDir, ".gr-cli");
|
|
97
108
|
this.apiUrl = apiUrl;
|
|
98
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Validate a profile name. Allows the legacy realm path form
|
|
112
|
+
* `workspace/member` so `acme-demo/acme-admin` works as a profile name.
|
|
113
|
+
* Rejects path traversal and absolute paths.
|
|
114
|
+
*/
|
|
115
|
+
validateProfileName(profile) {
|
|
116
|
+
if (!profile) {
|
|
117
|
+
throw new Error("Invalid profile name: (empty).");
|
|
118
|
+
}
|
|
119
|
+
if (profile.includes("..") || profile.startsWith("/") || profile.endsWith("/")) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Invalid profile name: ${profile}. Do not use ".." or leading/trailing "/".`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (!/^[a-zA-Z0-9_.-]+(?:\/[a-zA-Z0-9_.-]+)?$/.test(profile)) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Invalid profile name: ${profile}. Use letters, digits, '_', '-', '.', or workspace/member form (one slash).`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Resolve which profile to use for this invocation.
|
|
132
|
+
* Precedence: explicit flag → GRAVITY_RAIL_PROFILE → stored current → "default".
|
|
133
|
+
*/
|
|
134
|
+
resolveProfileName(explicit, env) {
|
|
135
|
+
if (explicit) {
|
|
136
|
+
this.validateProfileName(explicit);
|
|
137
|
+
return explicit;
|
|
138
|
+
}
|
|
139
|
+
const fromEnv = process.env.GRAVITY_RAIL_PROFILE;
|
|
140
|
+
if (fromEnv) {
|
|
141
|
+
this.validateProfileName(fromEnv);
|
|
142
|
+
return fromEnv;
|
|
143
|
+
}
|
|
144
|
+
if (env) {
|
|
145
|
+
return this.getCurrentProfile(env);
|
|
146
|
+
}
|
|
147
|
+
return DEFAULT_PROFILE;
|
|
148
|
+
}
|
|
149
|
+
getCurrentProfile(env) {
|
|
150
|
+
const parsed = this.readStore(env);
|
|
151
|
+
if (!parsed) return DEFAULT_PROFILE;
|
|
152
|
+
return parsed.store.currentProfile || DEFAULT_PROFILE;
|
|
153
|
+
}
|
|
154
|
+
setCurrentProfile(env, profile) {
|
|
155
|
+
this.validateProfileName(profile);
|
|
156
|
+
const store = this.readStore(env)?.store ?? this.emptyStore(profile);
|
|
157
|
+
if (!store.profiles[profile]) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Profile "${profile}" has no stored credentials for ${env}. Run: gr auth login --name ${profile} (or gr auth import-dev for local tokens)`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
store.currentProfile = profile;
|
|
163
|
+
this.writeStore(env, store);
|
|
164
|
+
}
|
|
165
|
+
listProfiles(env) {
|
|
166
|
+
const parsed = this.readStore(env);
|
|
167
|
+
if (!parsed) return [];
|
|
168
|
+
const store = parsed.store;
|
|
169
|
+
const current = store.currentProfile || DEFAULT_PROFILE;
|
|
170
|
+
return Object.keys(store.profiles).sort().map((name) => {
|
|
171
|
+
const creds = store.profiles[name];
|
|
172
|
+
return {
|
|
173
|
+
name,
|
|
174
|
+
current: name === current,
|
|
175
|
+
hasAccessToken: Boolean(creds.accessToken || creds.refreshToken),
|
|
176
|
+
expiresAt: creds.expiresAt,
|
|
177
|
+
workspaceUuid: creds.workspaceUuid
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
}
|
|
99
181
|
getCredentialsPath(env) {
|
|
182
|
+
if (!/^[a-zA-Z0-9_:.-]+$/.test(env)) {
|
|
183
|
+
throw new Error(`Invalid environment name: ${env}`);
|
|
184
|
+
}
|
|
185
|
+
const candidate = this.buildCredentialsPath(env);
|
|
186
|
+
const resolvedDir = resolve2(this.cacheDir);
|
|
187
|
+
const rel = relative(resolvedDir, resolve2(candidate));
|
|
188
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
189
|
+
throw new Error(`Invalid environment name: ${env}`);
|
|
190
|
+
}
|
|
191
|
+
return candidate;
|
|
192
|
+
}
|
|
193
|
+
buildCredentialsPath(env) {
|
|
100
194
|
if (this.apiUrl) {
|
|
101
195
|
try {
|
|
102
196
|
const url = new URL(this.apiUrl);
|
|
103
|
-
|
|
104
|
-
|
|
197
|
+
const port = url.port;
|
|
198
|
+
if (port && port !== "80" && port !== "443") {
|
|
199
|
+
const isCanonicalLocalDev = url.hostname === "localhost" && port === "5050" || url.hostname.endsWith("gravityrail.test") && port === "5054";
|
|
200
|
+
if (!isCanonicalLocalDev) {
|
|
201
|
+
return join2(this.cacheDir, `credentials-${env}-${port}.json`);
|
|
202
|
+
}
|
|
105
203
|
}
|
|
106
204
|
} catch {
|
|
107
205
|
}
|
|
108
206
|
}
|
|
109
207
|
return join2(this.cacheDir, `credentials-${env}.json`);
|
|
110
208
|
}
|
|
111
|
-
|
|
209
|
+
emptyStore(currentProfile = DEFAULT_PROFILE) {
|
|
210
|
+
return { version: 2, currentProfile, profiles: {} };
|
|
211
|
+
}
|
|
212
|
+
isCredentialShape(value) {
|
|
213
|
+
if (!value || typeof value !== "object") return false;
|
|
214
|
+
const v = value;
|
|
215
|
+
const hasOAuth = typeof v.clientId === "string" && typeof v.clientSecret === "string";
|
|
216
|
+
const hasToken = typeof v.accessToken === "string" && v.accessToken.length > 0;
|
|
217
|
+
return hasOAuth || hasToken;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Read and normalize the on-disk store (migrating legacy formats in memory).
|
|
221
|
+
* `wasLegacy` is true when the file was not already v2 so callers can rewrite once.
|
|
222
|
+
*/
|
|
223
|
+
readStore(env) {
|
|
112
224
|
const credsPath = this.getCredentialsPath(env);
|
|
113
225
|
if (!existsSync2(credsPath)) {
|
|
114
226
|
return null;
|
|
115
227
|
}
|
|
116
228
|
try {
|
|
117
229
|
const raw = JSON.parse(readFileSync2(credsPath, "utf-8"));
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this.save(env, creds);
|
|
124
|
-
} else {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
if (creds.expiresAt) {
|
|
128
|
-
const expiresAt = new Date(creds.expiresAt);
|
|
129
|
-
if (expiresAt <= /* @__PURE__ */ new Date()) {
|
|
130
|
-
if (creds.refreshToken) {
|
|
131
|
-
return creds;
|
|
230
|
+
if (raw.version === 2 && raw.profiles && typeof raw.profiles === "object") {
|
|
231
|
+
const profiles = {};
|
|
232
|
+
for (const [name, value] of Object.entries(raw.profiles)) {
|
|
233
|
+
if (this.isCredentialShape(value)) {
|
|
234
|
+
profiles[name] = value;
|
|
132
235
|
}
|
|
133
|
-
return null;
|
|
134
236
|
}
|
|
237
|
+
const current = typeof raw.currentProfile === "string" && profiles[raw.currentProfile] ? raw.currentProfile : Object.keys(profiles)[0] || DEFAULT_PROFILE;
|
|
238
|
+
return {
|
|
239
|
+
store: { version: 2, currentProfile: current, profiles },
|
|
240
|
+
wasLegacy: false
|
|
241
|
+
};
|
|
135
242
|
}
|
|
136
|
-
|
|
243
|
+
if (this.isCredentialShape(raw)) {
|
|
244
|
+
return {
|
|
245
|
+
store: {
|
|
246
|
+
version: 2,
|
|
247
|
+
currentProfile: DEFAULT_PROFILE,
|
|
248
|
+
profiles: { [DEFAULT_PROFILE]: raw }
|
|
249
|
+
},
|
|
250
|
+
wasLegacy: true
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const legacyDefault = raw.default;
|
|
254
|
+
if (this.isCredentialShape(legacyDefault)) {
|
|
255
|
+
return {
|
|
256
|
+
store: {
|
|
257
|
+
version: 2,
|
|
258
|
+
currentProfile: DEFAULT_PROFILE,
|
|
259
|
+
profiles: { [DEFAULT_PROFILE]: legacyDefault }
|
|
260
|
+
},
|
|
261
|
+
wasLegacy: true
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
137
265
|
} catch {
|
|
138
266
|
return null;
|
|
139
267
|
}
|
|
140
268
|
}
|
|
141
|
-
|
|
269
|
+
ensureCacheDir() {
|
|
142
270
|
if (!existsSync2(this.cacheDir)) {
|
|
143
|
-
mkdirSync(this.cacheDir, { recursive: true });
|
|
271
|
+
mkdirSync(this.cacheDir, { recursive: true, mode: 448 });
|
|
272
|
+
try {
|
|
273
|
+
chmodSync(this.cacheDir, 448);
|
|
274
|
+
} catch {
|
|
275
|
+
}
|
|
144
276
|
}
|
|
277
|
+
}
|
|
278
|
+
writeStore(env, store) {
|
|
279
|
+
this.ensureCacheDir();
|
|
145
280
|
const credsPath = this.getCredentialsPath(env);
|
|
146
|
-
|
|
281
|
+
const tmpPath = `${credsPath}.tmp`;
|
|
282
|
+
writeFileSync(tmpPath, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
|
|
283
|
+
try {
|
|
284
|
+
chmodSync(tmpPath, 384);
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
renameSync(tmpPath, credsPath);
|
|
147
288
|
try {
|
|
148
289
|
chmodSync(credsPath, 384);
|
|
149
290
|
} catch {
|
|
150
291
|
}
|
|
151
292
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
293
|
+
filterExpired(creds) {
|
|
294
|
+
if (creds.expiresAt) {
|
|
295
|
+
const expiresAt = new Date(creds.expiresAt);
|
|
296
|
+
if (expiresAt <= /* @__PURE__ */ new Date()) {
|
|
297
|
+
if (creds.refreshToken) {
|
|
298
|
+
return creds;
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return creds;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Load credentials for an environment + profile.
|
|
307
|
+
* When `profile` is omitted, uses the resolved current profile for `env`.
|
|
308
|
+
*/
|
|
309
|
+
load(env, profile) {
|
|
310
|
+
const parsed = this.readStore(env);
|
|
311
|
+
if (!parsed) return null;
|
|
312
|
+
const { store, wasLegacy } = parsed;
|
|
313
|
+
const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;
|
|
314
|
+
if (profile) this.validateProfileName(profile);
|
|
315
|
+
const creds = store.profiles[name];
|
|
316
|
+
if (!creds) return null;
|
|
317
|
+
if (wasLegacy) {
|
|
318
|
+
this.writeStore(env, store);
|
|
319
|
+
}
|
|
320
|
+
return this.filterExpired(creds);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Save credentials for an environment + profile.
|
|
324
|
+
* When `profile` is omitted, uses the current profile (or "default").
|
|
325
|
+
* Sets currentProfile to the saved profile when the store was empty.
|
|
326
|
+
*/
|
|
327
|
+
save(env, creds, profile) {
|
|
328
|
+
if (profile) this.validateProfileName(profile);
|
|
329
|
+
const store = this.readStore(env)?.store ?? this.emptyStore();
|
|
330
|
+
const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;
|
|
331
|
+
if (profile) {
|
|
332
|
+
store.profiles[name] = creds;
|
|
333
|
+
if (!store.profiles[store.currentProfile] || Object.keys(store.profiles).length === 1) {
|
|
334
|
+
store.currentProfile = name;
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
store.profiles[name] = creds;
|
|
338
|
+
if (!store.currentProfile) {
|
|
339
|
+
store.currentProfile = name;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
this.writeStore(env, store);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Bulk-save many profiles in one write (used by dev-realms import).
|
|
346
|
+
* Does not change currentProfile unless the store was empty — then uses
|
|
347
|
+
* `preferCurrent` if provided and present, else `superadmin`, else first.
|
|
348
|
+
*/
|
|
349
|
+
saveMany(env, profiles, preferCurrent) {
|
|
350
|
+
const names = Object.keys(profiles);
|
|
351
|
+
for (const name of names) {
|
|
352
|
+
this.validateProfileName(name);
|
|
353
|
+
}
|
|
354
|
+
const store = this.readStore(env)?.store ?? this.emptyStore();
|
|
355
|
+
const wasEmpty = Object.keys(store.profiles).length === 0;
|
|
356
|
+
for (const [name, creds] of Object.entries(profiles)) {
|
|
357
|
+
store.profiles[name] = creds;
|
|
358
|
+
}
|
|
359
|
+
if (wasEmpty) {
|
|
360
|
+
if (preferCurrent && store.profiles[preferCurrent]) {
|
|
361
|
+
store.currentProfile = preferCurrent;
|
|
362
|
+
} else if (store.profiles.superadmin) {
|
|
363
|
+
store.currentProfile = "superadmin";
|
|
364
|
+
} else {
|
|
365
|
+
store.currentProfile = names.sort()[0] ?? DEFAULT_PROFILE;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
this.writeStore(env, store);
|
|
369
|
+
return names.length;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Clear one profile (default: current profile). Deletes the file when no profiles remain.
|
|
373
|
+
* Pass a profile name to clear a specific account without touching others.
|
|
374
|
+
*/
|
|
375
|
+
clear(env, profile) {
|
|
376
|
+
const parsed = this.readStore(env);
|
|
377
|
+
if (!parsed) return;
|
|
378
|
+
const store = parsed.store;
|
|
379
|
+
const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;
|
|
380
|
+
if (profile) this.validateProfileName(profile);
|
|
381
|
+
delete store.profiles[name];
|
|
382
|
+
if (Object.keys(store.profiles).length === 0) {
|
|
383
|
+
const credsPath = this.getCredentialsPath(env);
|
|
384
|
+
if (existsSync2(credsPath)) {
|
|
385
|
+
try {
|
|
386
|
+
unlinkSync(credsPath);
|
|
387
|
+
} catch {
|
|
388
|
+
}
|
|
389
|
+
}
|
|
155
390
|
return;
|
|
156
391
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
} catch {
|
|
392
|
+
if (store.currentProfile === name) {
|
|
393
|
+
store.currentProfile = Object.keys(store.profiles).sort()[0];
|
|
160
394
|
}
|
|
395
|
+
this.writeStore(env, store);
|
|
161
396
|
}
|
|
162
397
|
getCacheDir() {
|
|
163
398
|
return this.cacheDir;
|
|
164
399
|
}
|
|
400
|
+
/** Absolute path of the credentials file for `env` (for docs / `auth list`). */
|
|
401
|
+
getCredentialsFilePath(env) {
|
|
402
|
+
return this.getCredentialsPath(env);
|
|
403
|
+
}
|
|
165
404
|
};
|
|
405
|
+
|
|
406
|
+
// src/cli-utils/auth.ts
|
|
166
407
|
function stderr(msg) {
|
|
167
408
|
process.stderr.write(msg + "\n");
|
|
168
409
|
}
|
|
@@ -218,7 +459,7 @@ function openBrowser(url) {
|
|
|
218
459
|
});
|
|
219
460
|
}
|
|
220
461
|
function startCallbackServer() {
|
|
221
|
-
return new Promise((
|
|
462
|
+
return new Promise((resolve3, reject) => {
|
|
222
463
|
let authCode = null;
|
|
223
464
|
let error = null;
|
|
224
465
|
let receivedState = null;
|
|
@@ -260,7 +501,7 @@ function startCallbackServer() {
|
|
|
260
501
|
</html>
|
|
261
502
|
`);
|
|
262
503
|
server.close();
|
|
263
|
-
|
|
504
|
+
resolve3({ code: authCode, state: receivedState });
|
|
264
505
|
} else {
|
|
265
506
|
res.writeHead(400);
|
|
266
507
|
res.end("Missing code parameter");
|
|
@@ -302,25 +543,31 @@ async function dynamicClientRegistration(envConfig, clientName = "Gravity CLI")
|
|
|
302
543
|
return await response.json();
|
|
303
544
|
}
|
|
304
545
|
async function performOAuthFlow(options) {
|
|
305
|
-
const { env, envConfig, credentialsManager, clientName = "Gravity CLI" } = options;
|
|
546
|
+
const { env, envConfig, credentialsManager, clientName = "Gravity CLI", profile } = options;
|
|
547
|
+
const profileName = credentialsManager.resolveProfileName(profile, env);
|
|
306
548
|
let hasRetried = false;
|
|
307
549
|
while (true) {
|
|
308
|
-
let creds = credentialsManager.load(env);
|
|
309
|
-
if (!creds || !creds.clientId) {
|
|
550
|
+
let creds = credentialsManager.load(env, profileName);
|
|
551
|
+
if (!creds || !creds.clientId || !creds.clientSecret) {
|
|
310
552
|
const registration = await dynamicClientRegistration(envConfig, clientName);
|
|
311
553
|
creds = {
|
|
312
554
|
clientId: registration.client_id,
|
|
313
555
|
clientSecret: registration.client_secret
|
|
314
556
|
};
|
|
315
|
-
credentialsManager.save(env, creds);
|
|
557
|
+
credentialsManager.save(env, creds, profileName);
|
|
316
558
|
} else {
|
|
317
559
|
stderr(" Using cached client credentials");
|
|
318
560
|
}
|
|
561
|
+
if (!creds.clientId || !creds.clientSecret) {
|
|
562
|
+
throw new Error("OAuth client credentials missing after registration");
|
|
563
|
+
}
|
|
564
|
+
const clientId = creds.clientId;
|
|
565
|
+
const clientSecret = creds.clientSecret;
|
|
319
566
|
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
320
567
|
const state = randomBytes(32).toString("base64url");
|
|
321
568
|
const authParams = new URLSearchParams({
|
|
322
569
|
response_type: "code",
|
|
323
|
-
client_id:
|
|
570
|
+
client_id: clientId,
|
|
324
571
|
redirect_uri: REDIRECT_URI,
|
|
325
572
|
scope: "workspace:admin",
|
|
326
573
|
state,
|
|
@@ -330,6 +577,7 @@ async function performOAuthFlow(options) {
|
|
|
330
577
|
const authUrl = `${envConfig.apiUrl}/api/v2/oauth/authorize?${authParams}`;
|
|
331
578
|
const callbackPromise = startCallbackServer();
|
|
332
579
|
stderr("\n Opening browser for authorization...");
|
|
580
|
+
stderr(` Profile: ${profileName}`);
|
|
333
581
|
stderr(` If browser doesn't open, visit:
|
|
334
582
|
${authUrl}
|
|
335
583
|
`);
|
|
@@ -343,7 +591,7 @@ async function performOAuthFlow(options) {
|
|
|
343
591
|
const msg = err instanceof Error ? err.message : String(err);
|
|
344
592
|
if (!hasRetried && isOAuthError(msg, "invalid_client")) {
|
|
345
593
|
stderr(" Cached client credentials expired, re-registering...");
|
|
346
|
-
credentialsManager.clear(env);
|
|
594
|
+
credentialsManager.clear(env, profileName);
|
|
347
595
|
hasRetried = true;
|
|
348
596
|
continue;
|
|
349
597
|
}
|
|
@@ -355,7 +603,7 @@ async function performOAuthFlow(options) {
|
|
|
355
603
|
stderr(" Authorization code received!");
|
|
356
604
|
stderr(" Exchanging code for tokens...");
|
|
357
605
|
const tokenUrl = `${envConfig.apiUrl}/api/v2/oauth/token`;
|
|
358
|
-
const authHeader = Buffer.from(`${
|
|
606
|
+
const authHeader = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
359
607
|
const tokenResponse = await fetch(tokenUrl, {
|
|
360
608
|
method: "POST",
|
|
361
609
|
headers: {
|
|
@@ -373,7 +621,7 @@ async function performOAuthFlow(options) {
|
|
|
373
621
|
const body = await tokenResponse.text();
|
|
374
622
|
if (!hasRetried && isOAuthError(body, "invalid_client")) {
|
|
375
623
|
stderr(" Cached client credentials expired, re-registering...");
|
|
376
|
-
credentialsManager.clear(env);
|
|
624
|
+
credentialsManager.clear(env, profileName);
|
|
377
625
|
hasRetried = true;
|
|
378
626
|
continue;
|
|
379
627
|
}
|
|
@@ -384,16 +632,18 @@ async function performOAuthFlow(options) {
|
|
|
384
632
|
creds.accessToken = accessToken;
|
|
385
633
|
creds.refreshToken = refreshToken ?? creds.refreshToken;
|
|
386
634
|
creds.expiresAt = new Date(Date.now() + expiresIn * 1e3).toISOString();
|
|
387
|
-
credentialsManager.save(env, creds);
|
|
635
|
+
credentialsManager.save(env, creds, profileName);
|
|
636
|
+
credentialsManager.setCurrentProfile(env, profileName);
|
|
388
637
|
stderr(" Authentication successful!");
|
|
389
638
|
return { accessToken, credentials: creds };
|
|
390
639
|
}
|
|
391
640
|
}
|
|
392
|
-
async function refreshAccessToken(envConfig, creds, credentialsManager, env) {
|
|
641
|
+
async function refreshAccessToken(envConfig, creds, credentialsManager, env, profile) {
|
|
393
642
|
if (!creds.refreshToken || !creds.clientId || !creds.clientSecret) {
|
|
394
643
|
stderr(" Cannot refresh: missing credentials (re-login required)");
|
|
395
644
|
return null;
|
|
396
645
|
}
|
|
646
|
+
const profileName = credentialsManager.resolveProfileName(profile, env);
|
|
397
647
|
const tokenUrl = `${envConfig.apiUrl}/api/v2/oauth/token`;
|
|
398
648
|
const authHeader = Buffer.from(`${creds.clientId}:${creds.clientSecret}`).toString("base64");
|
|
399
649
|
try {
|
|
@@ -416,7 +666,7 @@ async function refreshAccessToken(envConfig, creds, credentialsManager, env) {
|
|
|
416
666
|
} else {
|
|
417
667
|
stderr(` Token refresh failed (${response.status}), clearing credentials`);
|
|
418
668
|
}
|
|
419
|
-
credentialsManager.clear(env);
|
|
669
|
+
credentialsManager.clear(env, profileName);
|
|
420
670
|
return null;
|
|
421
671
|
}
|
|
422
672
|
const tokenData = await response.json();
|
|
@@ -427,18 +677,18 @@ async function refreshAccessToken(envConfig, creds, credentialsManager, env) {
|
|
|
427
677
|
refreshToken: refreshToken ?? creds.refreshToken,
|
|
428
678
|
expiresAt: new Date(Date.now() + expiresIn * 1e3).toISOString()
|
|
429
679
|
};
|
|
430
|
-
credentialsManager.save(env, updated);
|
|
680
|
+
credentialsManager.save(env, updated, profileName);
|
|
431
681
|
return updated;
|
|
432
682
|
} catch (err) {
|
|
433
683
|
stderr(` Token refresh error: ${err instanceof Error ? err.message : "unknown"}`);
|
|
434
|
-
credentialsManager.clear(env);
|
|
684
|
+
credentialsManager.clear(env, profileName);
|
|
435
685
|
return null;
|
|
436
686
|
}
|
|
437
687
|
}
|
|
438
688
|
|
|
439
689
|
// src/cli-utils/ui.ts
|
|
440
690
|
import * as readline from "readline";
|
|
441
|
-
var NEW_ITEM_MARKER = Symbol("NEW_ITEM");
|
|
691
|
+
var NEW_ITEM_MARKER = /* @__PURE__ */ Symbol("NEW_ITEM");
|
|
442
692
|
async function selectFromList(items, options = {}) {
|
|
443
693
|
const {
|
|
444
694
|
title = "Select an item",
|
|
@@ -455,7 +705,7 @@ async function selectFromList(items, options = {}) {
|
|
|
455
705
|
return { item: null, isNew: false };
|
|
456
706
|
}
|
|
457
707
|
const displayItems = allowNew ? [NEW_ITEM_MARKER, ...items] : items;
|
|
458
|
-
return new Promise((
|
|
708
|
+
return new Promise((resolve3) => {
|
|
459
709
|
let selectedIndex = allowNew ? 1 : 0;
|
|
460
710
|
let filterText = "";
|
|
461
711
|
const getFilteredItems = () => {
|
|
@@ -538,16 +788,16 @@ async function selectFromList(items, options = {}) {
|
|
|
538
788
|
if (filtered.length > 0 && selectedIndex < filtered.length) {
|
|
539
789
|
const selected = filtered[selectedIndex];
|
|
540
790
|
if (selected === NEW_ITEM_MARKER) {
|
|
541
|
-
|
|
791
|
+
resolve3({ item: null, isNew: true });
|
|
542
792
|
} else {
|
|
543
|
-
|
|
793
|
+
resolve3({ item: selected, isNew: false });
|
|
544
794
|
}
|
|
545
795
|
} else {
|
|
546
|
-
|
|
796
|
+
resolve3({ item: null, isNew: false });
|
|
547
797
|
}
|
|
548
798
|
} else if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
549
799
|
cleanup();
|
|
550
|
-
|
|
800
|
+
resolve3({ item: null, isNew: false });
|
|
551
801
|
} else if (key.name === "backspace") {
|
|
552
802
|
filterText = filterText.slice(0, -1);
|
|
553
803
|
selectedIndex = allowNew ? 0 : 0;
|
|
@@ -678,7 +928,8 @@ var AGUIClient = class {
|
|
|
678
928
|
this.appContext = options.appContext;
|
|
679
929
|
}
|
|
680
930
|
inferenceStatusUrl() {
|
|
681
|
-
|
|
931
|
+
const noChat = typeof this.chatId === "number" ? this.chatId <= 0 : this.chatId.length === 0;
|
|
932
|
+
if (!this.workspaceUuid || noChat) return null;
|
|
682
933
|
return `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/inference/status`;
|
|
683
934
|
}
|
|
684
935
|
async waitForInferenceInactive(maxWaitMs = 6e4) {
|
|
@@ -721,6 +972,7 @@ var AGUIClient = class {
|
|
|
721
972
|
...this.appContext ? { appContext: this.appContext } : {},
|
|
722
973
|
...request.resumeValue !== void 0 ? { resumeValue: request.resumeValue } : {},
|
|
723
974
|
...request.resumeInterruptId ? { resumeInterruptId: request.resumeInterruptId } : {},
|
|
975
|
+
...request.resume !== void 0 ? { resume: request.resume } : {},
|
|
724
976
|
...options?.autoApprove ? { context: { autoApprove: true } } : {}
|
|
725
977
|
});
|
|
726
978
|
if (this.debug) {
|
|
@@ -814,6 +1066,7 @@ export {
|
|
|
814
1066
|
CALLBACK_PATH,
|
|
815
1067
|
REDIRECT_URI,
|
|
816
1068
|
getEnvironmentConfig,
|
|
1069
|
+
DEFAULT_PROFILE,
|
|
817
1070
|
CredentialsManager,
|
|
818
1071
|
generatePKCE,
|
|
819
1072
|
openBrowser,
|
|
@@ -832,4 +1085,4 @@ export {
|
|
|
832
1085
|
getTextDelta,
|
|
833
1086
|
getErrorMessage
|
|
834
1087
|
};
|
|
835
|
-
//# sourceMappingURL=chunk-
|
|
1088
|
+
//# sourceMappingURL=chunk-SIV2WU2I.js.map
|