@amigo-ai/platform-sdk 0.9.3 → 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/dist/core/device-code.js +411 -0
- package/dist/core/device-code.js.map +1 -0
- package/dist/index.cjs +387 -0
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +387 -0
- package/dist/index.mjs.map +4 -4
- package/dist/types/core/device-code.d.ts +134 -0
- package/dist/types/core/device-code.d.ts.map +1 -0
- package/dist/types/generated/api.d.ts +71 -3
- package/dist/types/generated/api.d.ts.map +1 -1
- package/dist/types/index.d.cts +2 -0
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/resources/functions.d.ts.map +1 -1
- package/dist/types/resources/metrics.d.ts.map +1 -1
- package/dist/types/resources/settings.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -37,14 +37,21 @@ __export(index_exports, {
|
|
|
37
37
|
ConfigurationError: () => ConfigurationError,
|
|
38
38
|
ConflictError: () => ConflictError,
|
|
39
39
|
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
40
|
+
DeviceCodeDeniedError: () => DeviceCodeDeniedError,
|
|
41
|
+
DeviceCodeExpiredError: () => DeviceCodeExpiredError,
|
|
42
|
+
FileTokenStorage: () => FileTokenStorage,
|
|
43
|
+
LoginCancelledError: () => LoginCancelledError,
|
|
44
|
+
MemoryTokenStorage: () => MemoryTokenStorage,
|
|
40
45
|
NetworkError: () => NetworkError,
|
|
41
46
|
NotFoundError: () => NotFoundError,
|
|
42
47
|
ParseError: () => ParseError,
|
|
43
48
|
PermissionError: () => PermissionError,
|
|
44
49
|
RateLimitError: () => RateLimitError,
|
|
50
|
+
RefreshTokenExpiredError: () => RefreshTokenExpiredError,
|
|
45
51
|
RequestTimeoutError: () => RequestTimeoutError,
|
|
46
52
|
ServerError: () => ServerError,
|
|
47
53
|
ServiceUnavailableError: () => ServiceUnavailableError,
|
|
54
|
+
TokenManager: () => TokenManager,
|
|
48
55
|
ValidationError: () => ValidationError,
|
|
49
56
|
WebhookVerificationError: () => WebhookVerificationError,
|
|
50
57
|
actionId: () => actionId,
|
|
@@ -57,6 +64,9 @@ __export(index_exports, {
|
|
|
57
64
|
entityId: () => entityId,
|
|
58
65
|
eventId: () => eventId,
|
|
59
66
|
extractRequestId: () => extractRequestId,
|
|
67
|
+
formatDeviceCodeInstructions: () => formatDeviceCodeInstructions,
|
|
68
|
+
formatDeviceCodeLink: () => formatDeviceCodeLink,
|
|
69
|
+
formatWorkspaceList: () => formatWorkspaceList,
|
|
60
70
|
functionId: () => functionId,
|
|
61
71
|
integrationId: () => integrationId,
|
|
62
72
|
isAmigoError: () => isAmigoError,
|
|
@@ -64,6 +74,8 @@ __export(index_exports, {
|
|
|
64
74
|
isNotFoundError: () => isNotFoundError,
|
|
65
75
|
isRateLimitError: () => isRateLimitError,
|
|
66
76
|
isRequestTimeoutError: () => isRequestTimeoutError,
|
|
77
|
+
loginWithDeviceCode: () => loginWithDeviceCode,
|
|
78
|
+
openBrowser: () => openBrowser,
|
|
67
79
|
paginate: () => paginate,
|
|
68
80
|
parseRateLimitHeaders: () => parseRateLimitHeaders,
|
|
69
81
|
parseWebhookEvent: () => parseWebhookEvent,
|
|
@@ -3143,6 +3155,381 @@ function toCryptoBuffer(bytes) {
|
|
|
3143
3155
|
return copy.buffer;
|
|
3144
3156
|
}
|
|
3145
3157
|
|
|
3158
|
+
// src/core/device-code.ts
|
|
3159
|
+
var DeviceCodeExpiredError = class extends AmigoError {
|
|
3160
|
+
constructor(message = "Device code expired. Please restart the login flow.") {
|
|
3161
|
+
super(message, { errorCode: "device_code_expired" });
|
|
3162
|
+
}
|
|
3163
|
+
};
|
|
3164
|
+
var DeviceCodeDeniedError = class extends AmigoError {
|
|
3165
|
+
constructor(message = "Authorization request was denied.") {
|
|
3166
|
+
super(message, { errorCode: "device_code_denied" });
|
|
3167
|
+
}
|
|
3168
|
+
};
|
|
3169
|
+
var RefreshTokenExpiredError = class extends AuthenticationError {
|
|
3170
|
+
constructor(message = "Refresh token expired. Please log in again.") {
|
|
3171
|
+
super(message, { errorCode: "refresh_token_expired" });
|
|
3172
|
+
}
|
|
3173
|
+
};
|
|
3174
|
+
var LoginCancelledError = class extends AmigoError {
|
|
3175
|
+
constructor() {
|
|
3176
|
+
super("Login cancelled", { errorCode: "login_cancelled" });
|
|
3177
|
+
}
|
|
3178
|
+
};
|
|
3179
|
+
var DEFAULT_IDENTITY_URL = "https://identity.platform.amigo.ai";
|
|
3180
|
+
async function identityPost(baseUrl, path, body, fetchFn) {
|
|
3181
|
+
try {
|
|
3182
|
+
return await fetchFn(`${baseUrl}${path}`, {
|
|
3183
|
+
method: "POST",
|
|
3184
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3185
|
+
body: body.toString(),
|
|
3186
|
+
redirect: "manual"
|
|
3187
|
+
});
|
|
3188
|
+
} catch (err) {
|
|
3189
|
+
throw new NetworkError("Network error contacting identity service", err);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
async function requestDeviceCode(baseUrl, params, fetchFn) {
|
|
3193
|
+
const body = new URLSearchParams();
|
|
3194
|
+
if (params.clientDescription) body.set("client_description", params.clientDescription);
|
|
3195
|
+
if (params.scope) body.set("scope", params.scope);
|
|
3196
|
+
const res = await identityPost(baseUrl, "/device/code", body, fetchFn);
|
|
3197
|
+
if (res.status === 429) {
|
|
3198
|
+
const retryAfter = parseInt(res.headers.get("Retry-After") ?? "", 10);
|
|
3199
|
+
throw new RateLimitError("Rate limited", { retryAfter: isNaN(retryAfter) ? void 0 : retryAfter });
|
|
3200
|
+
}
|
|
3201
|
+
if (!res.ok) {
|
|
3202
|
+
const err = await res.json().catch(() => ({}));
|
|
3203
|
+
throw new AmigoError(err.error_description ?? `Identity error (${res.status})`, {
|
|
3204
|
+
statusCode: res.status,
|
|
3205
|
+
errorCode: err.error
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
return await res.json();
|
|
3209
|
+
}
|
|
3210
|
+
async function pollDeviceCode(baseUrl, deviceCode, scope, fetchFn) {
|
|
3211
|
+
const body = new URLSearchParams({ grant_type: "device_code", device_code: deviceCode });
|
|
3212
|
+
if (scope) body.set("scope", scope);
|
|
3213
|
+
const res = await identityPost(baseUrl, "/token", body, fetchFn);
|
|
3214
|
+
if (res.status === 300)
|
|
3215
|
+
return { type: "multi_workspace", data: await res.json() };
|
|
3216
|
+
if (res.status === 200) return { type: "token", data: await res.json() };
|
|
3217
|
+
if (res.status === 400) {
|
|
3218
|
+
const err = await res.json().catch(() => ({ error: "unknown" }));
|
|
3219
|
+
if (err.error === "authorization_pending") return { type: "pending" };
|
|
3220
|
+
if (err.error === "slow_down") return { type: "slow_down" };
|
|
3221
|
+
throw new AmigoError(err.error_description ?? err.error ?? `Identity error (400)`, {
|
|
3222
|
+
statusCode: 400,
|
|
3223
|
+
errorCode: err.error
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3226
|
+
throw new AmigoError(`Identity error (${res.status})`, { statusCode: res.status });
|
|
3227
|
+
}
|
|
3228
|
+
async function doRefreshToken(baseUrl, params, fetchFn) {
|
|
3229
|
+
const body = new URLSearchParams({
|
|
3230
|
+
grant_type: "refresh_token",
|
|
3231
|
+
refresh_token: params.refreshToken
|
|
3232
|
+
});
|
|
3233
|
+
if (params.workspaceId) body.set("workspace_id", params.workspaceId);
|
|
3234
|
+
if (params.scope) body.set("scope", params.scope);
|
|
3235
|
+
const res = await identityPost(baseUrl, "/token", body, fetchFn);
|
|
3236
|
+
if (!res.ok) {
|
|
3237
|
+
const err = await res.json().catch(() => ({}));
|
|
3238
|
+
throw new AmigoError(err.error_description ?? `Identity error (${res.status})`, {
|
|
3239
|
+
statusCode: res.status,
|
|
3240
|
+
errorCode: err.error
|
|
3241
|
+
});
|
|
3242
|
+
}
|
|
3243
|
+
return await res.json();
|
|
3244
|
+
}
|
|
3245
|
+
function decodeJwtPayload(jwt) {
|
|
3246
|
+
try {
|
|
3247
|
+
const parts = jwt.split(".");
|
|
3248
|
+
if (parts.length !== 3 || !parts[1]) return null;
|
|
3249
|
+
const payload = Buffer.from(parts[1], "base64url").toString();
|
|
3250
|
+
return JSON.parse(payload);
|
|
3251
|
+
} catch {
|
|
3252
|
+
return null;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
function sleep2(ms, signal) {
|
|
3256
|
+
return new Promise((resolve, reject) => {
|
|
3257
|
+
if (signal?.aborted) {
|
|
3258
|
+
reject(new LoginCancelledError());
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
3261
|
+
const timer = setTimeout(resolve, ms);
|
|
3262
|
+
signal?.addEventListener(
|
|
3263
|
+
"abort",
|
|
3264
|
+
() => {
|
|
3265
|
+
clearTimeout(timer);
|
|
3266
|
+
reject(new LoginCancelledError());
|
|
3267
|
+
},
|
|
3268
|
+
{ once: true }
|
|
3269
|
+
);
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3272
|
+
function toAuthResult(token, workspaceIdOverride) {
|
|
3273
|
+
const claims = decodeJwtPayload(token.access_token);
|
|
3274
|
+
const workspaceId2 = workspaceIdOverride ?? claims?.workspace_id ?? "";
|
|
3275
|
+
if (!workspaceId2) {
|
|
3276
|
+
throw new AmigoError("Token does not contain a workspace_id claim", {
|
|
3277
|
+
errorCode: "missing_workspace"
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
return {
|
|
3281
|
+
accessToken: token.access_token,
|
|
3282
|
+
refreshToken: token.refresh_token ?? "",
|
|
3283
|
+
workspaceId: workspaceId2,
|
|
3284
|
+
expiresAt: claims?.exp ?? (token.expires_in ? Math.floor(Date.now() / 1e3) + token.expires_in : Math.floor(Date.now() / 1e3) + 900),
|
|
3285
|
+
scope: token.scope
|
|
3286
|
+
};
|
|
3287
|
+
}
|
|
3288
|
+
async function loginWithDeviceCode(options) {
|
|
3289
|
+
const baseUrl = (options.identityBaseUrl ?? DEFAULT_IDENTITY_URL).replace(/\/+$/, "");
|
|
3290
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
3291
|
+
const issuance = await requestDeviceCode(
|
|
3292
|
+
baseUrl,
|
|
3293
|
+
{ clientDescription: options.clientDescription, scope: options.scope },
|
|
3294
|
+
fetchFn
|
|
3295
|
+
);
|
|
3296
|
+
await options.onCode(issuance);
|
|
3297
|
+
let interval = issuance.interval * 1e3;
|
|
3298
|
+
const deadline = Date.now() + issuance.expires_in * 1e3;
|
|
3299
|
+
while (Date.now() < deadline) {
|
|
3300
|
+
if (options.signal?.aborted) throw new LoginCancelledError();
|
|
3301
|
+
await sleep2(interval, options.signal);
|
|
3302
|
+
if (options.signal?.aborted) throw new LoginCancelledError();
|
|
3303
|
+
options.onStatus?.("polling");
|
|
3304
|
+
try {
|
|
3305
|
+
const result = await pollDeviceCode(baseUrl, issuance.device_code, options.scope, fetchFn);
|
|
3306
|
+
if (result.type === "pending") {
|
|
3307
|
+
options.onStatus?.("authorization_pending");
|
|
3308
|
+
continue;
|
|
3309
|
+
}
|
|
3310
|
+
if (result.type === "slow_down") {
|
|
3311
|
+
interval += 5e3;
|
|
3312
|
+
options.onStatus?.("slow_down");
|
|
3313
|
+
continue;
|
|
3314
|
+
}
|
|
3315
|
+
options.onStatus?.("approved");
|
|
3316
|
+
if (result.type === "token") {
|
|
3317
|
+
return toAuthResult(result.data);
|
|
3318
|
+
}
|
|
3319
|
+
const workspaceId2 = await options.onWorkspaceRequired(result.data.workspaces);
|
|
3320
|
+
if (!result.data.refresh_token) {
|
|
3321
|
+
throw new AmigoError("Multi-workspace response missing refresh_token", {
|
|
3322
|
+
errorCode: "server_error"
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
const tokenResponse = await doRefreshToken(
|
|
3326
|
+
baseUrl,
|
|
3327
|
+
{ refreshToken: result.data.refresh_token, workspaceId: workspaceId2, scope: options.scope },
|
|
3328
|
+
fetchFn
|
|
3329
|
+
);
|
|
3330
|
+
return toAuthResult(tokenResponse, workspaceId2);
|
|
3331
|
+
} catch (err) {
|
|
3332
|
+
if (err instanceof AmigoError && err.errorCode === "expired_token") {
|
|
3333
|
+
options.onStatus?.("expired");
|
|
3334
|
+
throw new DeviceCodeExpiredError();
|
|
3335
|
+
}
|
|
3336
|
+
if (err instanceof AmigoError && err.errorCode === "access_denied") {
|
|
3337
|
+
options.onStatus?.("denied");
|
|
3338
|
+
throw new DeviceCodeDeniedError();
|
|
3339
|
+
}
|
|
3340
|
+
throw err;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
options.onStatus?.("expired");
|
|
3344
|
+
throw new DeviceCodeExpiredError();
|
|
3345
|
+
}
|
|
3346
|
+
var REFRESH_BUFFER_SECONDS = 60;
|
|
3347
|
+
var TokenManager = class {
|
|
3348
|
+
_storage;
|
|
3349
|
+
_baseUrl;
|
|
3350
|
+
_fetch;
|
|
3351
|
+
_cached = null;
|
|
3352
|
+
_refreshPromise = null;
|
|
3353
|
+
constructor(config = {}) {
|
|
3354
|
+
this._storage = config.storage ?? new FileTokenStorage();
|
|
3355
|
+
this._baseUrl = (config.identityBaseUrl ?? DEFAULT_IDENTITY_URL).replace(/\/+$/, "");
|
|
3356
|
+
this._fetch = config.fetch ?? globalThis.fetch;
|
|
3357
|
+
}
|
|
3358
|
+
async store(result) {
|
|
3359
|
+
const creds = {
|
|
3360
|
+
access_token: result.accessToken,
|
|
3361
|
+
refresh_token: result.refreshToken,
|
|
3362
|
+
workspace_id: result.workspaceId,
|
|
3363
|
+
expires_at: result.expiresAt,
|
|
3364
|
+
scope: result.scope
|
|
3365
|
+
};
|
|
3366
|
+
this._cached = creds;
|
|
3367
|
+
await this._storage.save(creds);
|
|
3368
|
+
}
|
|
3369
|
+
async getAccessToken() {
|
|
3370
|
+
const creds = await this._loadCached();
|
|
3371
|
+
if (!creds) return null;
|
|
3372
|
+
if (creds.expires_at - Math.floor(Date.now() / 1e3) >= REFRESH_BUFFER_SECONDS) {
|
|
3373
|
+
return { token: creds.access_token, workspaceId: creds.workspace_id };
|
|
3374
|
+
}
|
|
3375
|
+
const refreshed = await this._refresh(creds);
|
|
3376
|
+
return { token: refreshed.access_token, workspaceId: refreshed.workspace_id };
|
|
3377
|
+
}
|
|
3378
|
+
async hasCredentials() {
|
|
3379
|
+
return await this._loadCached() !== null;
|
|
3380
|
+
}
|
|
3381
|
+
async clear() {
|
|
3382
|
+
this._cached = null;
|
|
3383
|
+
this._refreshPromise = null;
|
|
3384
|
+
await this._storage.clear();
|
|
3385
|
+
}
|
|
3386
|
+
async _loadCached() {
|
|
3387
|
+
if (this._cached) return this._cached;
|
|
3388
|
+
const loaded = await this._storage.load();
|
|
3389
|
+
if (loaded) this._cached = loaded;
|
|
3390
|
+
return loaded;
|
|
3391
|
+
}
|
|
3392
|
+
async _refresh(current) {
|
|
3393
|
+
if (this._refreshPromise) return this._refreshPromise;
|
|
3394
|
+
this._refreshPromise = this._doRefresh(current).finally(() => {
|
|
3395
|
+
this._refreshPromise = null;
|
|
3396
|
+
});
|
|
3397
|
+
return this._refreshPromise;
|
|
3398
|
+
}
|
|
3399
|
+
async _doRefresh(current) {
|
|
3400
|
+
if (!current.refresh_token) throw new RefreshTokenExpiredError("No refresh token available");
|
|
3401
|
+
try {
|
|
3402
|
+
const response = await doRefreshToken(
|
|
3403
|
+
this._baseUrl,
|
|
3404
|
+
{ refreshToken: current.refresh_token, workspaceId: current.workspace_id, scope: current.scope },
|
|
3405
|
+
this._fetch
|
|
3406
|
+
);
|
|
3407
|
+
const claims = decodeJwtPayload(response.access_token);
|
|
3408
|
+
const refreshed = {
|
|
3409
|
+
access_token: response.access_token,
|
|
3410
|
+
refresh_token: response.refresh_token ?? current.refresh_token,
|
|
3411
|
+
workspace_id: current.workspace_id,
|
|
3412
|
+
expires_at: claims?.exp ?? Math.floor(Date.now() / 1e3) + response.expires_in,
|
|
3413
|
+
scope: response.scope ?? current.scope
|
|
3414
|
+
};
|
|
3415
|
+
this._cached = refreshed;
|
|
3416
|
+
await this._storage.save(refreshed);
|
|
3417
|
+
return refreshed;
|
|
3418
|
+
} catch (err) {
|
|
3419
|
+
if (err instanceof AmigoError && (err.statusCode === 401 || err.errorCode === "invalid_grant")) {
|
|
3420
|
+
await this.clear();
|
|
3421
|
+
throw new RefreshTokenExpiredError();
|
|
3422
|
+
}
|
|
3423
|
+
throw err;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
};
|
|
3427
|
+
var FileTokenStorage = class {
|
|
3428
|
+
_explicitPath;
|
|
3429
|
+
_resolvedPath;
|
|
3430
|
+
constructor(filePath) {
|
|
3431
|
+
this._explicitPath = filePath;
|
|
3432
|
+
}
|
|
3433
|
+
async _filePath() {
|
|
3434
|
+
if (this._explicitPath) return this._explicitPath;
|
|
3435
|
+
if (this._resolvedPath) return this._resolvedPath;
|
|
3436
|
+
const os = await import("node:os");
|
|
3437
|
+
const path = await import("node:path");
|
|
3438
|
+
this._resolvedPath = path.join(os.homedir(), ".amigo", "credentials.json");
|
|
3439
|
+
return this._resolvedPath;
|
|
3440
|
+
}
|
|
3441
|
+
async load() {
|
|
3442
|
+
const fs = await import("node:fs/promises");
|
|
3443
|
+
const filePath = await this._filePath();
|
|
3444
|
+
try {
|
|
3445
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
3446
|
+
const data = JSON.parse(raw);
|
|
3447
|
+
if (typeof data.access_token === "string" && typeof data.refresh_token === "string" && typeof data.workspace_id === "string" && typeof data.expires_at === "number") {
|
|
3448
|
+
return data;
|
|
3449
|
+
}
|
|
3450
|
+
return null;
|
|
3451
|
+
} catch (err) {
|
|
3452
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return null;
|
|
3453
|
+
throw err;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
async save(credentials) {
|
|
3457
|
+
const fs = await import("node:fs/promises");
|
|
3458
|
+
const path = await import("node:path");
|
|
3459
|
+
const filePath = await this._filePath();
|
|
3460
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true, mode: 448 });
|
|
3461
|
+
await fs.writeFile(filePath, JSON.stringify(credentials, null, 2) + "\n", { mode: 384 });
|
|
3462
|
+
await fs.chmod(filePath, 384);
|
|
3463
|
+
}
|
|
3464
|
+
async clear() {
|
|
3465
|
+
const fs = await import("node:fs/promises");
|
|
3466
|
+
const filePath = await this._filePath();
|
|
3467
|
+
try {
|
|
3468
|
+
await fs.unlink(filePath);
|
|
3469
|
+
} catch (err) {
|
|
3470
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return;
|
|
3471
|
+
throw err;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3475
|
+
var MemoryTokenStorage = class {
|
|
3476
|
+
_credentials = null;
|
|
3477
|
+
async load() {
|
|
3478
|
+
return this._credentials;
|
|
3479
|
+
}
|
|
3480
|
+
async save(credentials) {
|
|
3481
|
+
this._credentials = { ...credentials };
|
|
3482
|
+
}
|
|
3483
|
+
async clear() {
|
|
3484
|
+
this._credentials = null;
|
|
3485
|
+
}
|
|
3486
|
+
};
|
|
3487
|
+
function formatDeviceCodeInstructions(issuance) {
|
|
3488
|
+
return [
|
|
3489
|
+
"",
|
|
3490
|
+
" To sign in, open your browser and visit:",
|
|
3491
|
+
"",
|
|
3492
|
+
` ${issuance.verification_uri}`,
|
|
3493
|
+
"",
|
|
3494
|
+
" Then enter this code:",
|
|
3495
|
+
"",
|
|
3496
|
+
` ${issuance.user_code}`,
|
|
3497
|
+
"",
|
|
3498
|
+
` This code expires in ${Math.floor(issuance.expires_in / 60)} minutes.`,
|
|
3499
|
+
""
|
|
3500
|
+
].join("\n");
|
|
3501
|
+
}
|
|
3502
|
+
function formatDeviceCodeLink(issuance) {
|
|
3503
|
+
return `\x1B]8;;${issuance.verification_uri_complete}\x07Open browser to approve\x1B]8;;\x07`;
|
|
3504
|
+
}
|
|
3505
|
+
async function openBrowser(url) {
|
|
3506
|
+
const { spawn } = await import("node:child_process");
|
|
3507
|
+
const openers = {
|
|
3508
|
+
darwin: { cmd: "open", args: [url] },
|
|
3509
|
+
win32: { cmd: "cmd", args: ["/c", "start", "", url] },
|
|
3510
|
+
linux: { cmd: "xdg-open", args: [url] }
|
|
3511
|
+
};
|
|
3512
|
+
const opener = openers[process.platform];
|
|
3513
|
+
if (!opener) return false;
|
|
3514
|
+
return new Promise((resolve) => {
|
|
3515
|
+
const child = spawn(opener.cmd, opener.args, { stdio: "ignore", shell: false, detached: true });
|
|
3516
|
+
child.on("error", () => resolve(false));
|
|
3517
|
+
child.on("close", (code) => resolve(code === 0));
|
|
3518
|
+
child.unref();
|
|
3519
|
+
});
|
|
3520
|
+
}
|
|
3521
|
+
function formatWorkspaceList(workspaces) {
|
|
3522
|
+
const lines = ["", " Available workspaces:", ""];
|
|
3523
|
+
workspaces.forEach((ws, i) => {
|
|
3524
|
+
const name = ws.name ?? ws.workspace_id;
|
|
3525
|
+
const role = ws.role ? ` (${ws.role})` : "";
|
|
3526
|
+
lines.push(` ${i + 1}. ${name}${role}`);
|
|
3527
|
+
if (ws.name) lines.push(` ${ws.workspace_id}`);
|
|
3528
|
+
});
|
|
3529
|
+
lines.push("");
|
|
3530
|
+
return lines.join("\n");
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3146
3533
|
// src/index.ts
|
|
3147
3534
|
var DEFAULT_BASE_URL = "https://api.platform.amigo.ai";
|
|
3148
3535
|
var AmigoClient = class _AmigoClient {
|