@assethub/cli 0.1.20 → 0.1.21
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 +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +79 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Command line interface for AssetHub.
|
|
|
4
4
|
|
|
5
5
|
## Personal API keys
|
|
6
6
|
|
|
7
|
-
Log in once with a personal key
|
|
7
|
+
Log in once with a personal key, then select its workspace:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
assethub auth login --api-key-stdin --profile personal
|
|
@@ -19,6 +19,13 @@ Login verifies account identity through workspace discovery, including when
|
|
|
19
19
|
in the same profile with the same key. It does not issue keys or change browser
|
|
20
20
|
sessions. Pass `--cursor <nextCursor>` for another workspace list page.
|
|
21
21
|
|
|
22
|
+
CLI 0.1.21 also discovers existing `sk_` keys enabled for personal access by the
|
|
23
|
+
server. Run `workspace list --profile <saved-profile>` to discover a saved key
|
|
24
|
+
without logging in again. Verified personal identity is saved in that profile;
|
|
25
|
+
workspace selection keeps its original key. The `assethub-UUID` format is not
|
|
26
|
+
eligible for this discovery. Unregistered workspace keys retain the existing
|
|
27
|
+
user-token flow; rejected personal keys never fall back to another identity.
|
|
28
|
+
|
|
22
29
|
`--workspace` overrides the saved selection for one command, including diagnostics,
|
|
23
30
|
MCP discovery, history, and run recovery. Recovery still rejects an operation saved
|
|
24
31
|
in another workspace. Personal profiles take precedence over environment API
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA4BA,OAAO,EAIL,KAAK,wBAAwB,EAO7B,KAAK,YAAY,EAEjB,KAAK,UAAU,EAgBhB,MAAM,sBAAsB,CAAA;AAc7B,OAAO,EAAC,kBAAkB,EAAc,MAAM,gCAAgC,CAAA;AAI9E,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA4BA,OAAO,EAIL,KAAK,wBAAwB,EAO7B,KAAK,YAAY,EAEjB,KAAK,UAAU,EAgBhB,MAAM,sBAAsB,CAAA;AAc7B,OAAO,EAAC,kBAAkB,EAAc,MAAM,gCAAgC,CAAA;AAI9E,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAAA;AA2JxD,eAAO,MAAM,KAAK,s3aAkIjB,CAAA;AA4sFD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GACxC,OAAO,KAAK,KACX,wBA0BF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,KAAK,KACX,IAAI,CAAC,UAAU,EAAE,QAAQ,CAM1B,CAAA;AA6fF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,uBAAuB,GAClC,MAAM,SAAS,MAAM,EAAE,KACtB,YAAY,EAuGd,CAAA;AAgsCD,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,YAAY,EAAE,OAAO,CAAA;CACtB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,YAAY,MAAM,GAAG,SAAS,EAC9B,aAAa,MAAM,EAAE,EACrB,OAAO,KAAK,KACX,cAoBF,CAAA;AAED,KAAK,iBAAiB,GAAG,IAAI,CAC3B,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,EAC5C,SAAS,GACT,UAAU,GACV,KAAK,GACL,WAAW,GACX,QAAQ,GACR,OAAO,GACP,OAAO,GACP,UAAU,CACb,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,WAAW,MAAM,EACjB,MAAM,iBAAiB;;;;;;;;;;;;;;CAUvB,CAAA;AAEF,eAAO,MAAM,sBAAsB,GACjC,SAAS,UAAU,CAAC,OAAO,iBAAiB,CAAC,EAC7C,MAAM,iBAAiB,EACvB,SAAS,MAAM;;;;;;;;;;;;;;;;;;;;;;;CAYf,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -444,6 +444,39 @@ const readStdinBlob = async (type) => await new Promise((resolve, reject) => {
|
|
|
444
444
|
});
|
|
445
445
|
stdin.on('error', reject);
|
|
446
446
|
});
|
|
447
|
+
const isPersonalProfile = (profile) => profile?.authentication === 'personal' || Boolean(profile?.apiKey?.startsWith('ah_pat_'));
|
|
448
|
+
const personalProfileAuthentication = (apiKey, baseUrl, profile) => profile?.apiKey === apiKey && new URL(profile.baseUrl).origin === new URL(baseUrl).origin
|
|
449
|
+
? profile.authentication : undefined;
|
|
450
|
+
const canDiscoverPersonalKey = (key) => Boolean(key && (key.startsWith('ah_pat_') || /^sk_[a-f0-9]{64}$/i.test(key)));
|
|
451
|
+
const preferredApiKey = (flags, stored) => getFlag(flags, 'api-key') ??
|
|
452
|
+
(hasFlag(flags, 'profile') || stored?.workspaceId || isPersonalProfile(stored)
|
|
453
|
+
? stored?.apiKey
|
|
454
|
+
: env.ASSETHUB_API_KEY?.trim() || stored?.apiKey);
|
|
455
|
+
const personalAccount = async (auth) => {
|
|
456
|
+
const knownPersonal = isPersonalProfile(auth);
|
|
457
|
+
if (!knownPersonal && !canDiscoverPersonalKey(auth.apiKey))
|
|
458
|
+
return undefined;
|
|
459
|
+
try {
|
|
460
|
+
const account = await createWorkspaceClient({ accessToken: auth.apiKey, baseUrl: auth.baseUrl, workspaceId: auth.workspaceId }).list();
|
|
461
|
+
if (account.authentication !== 'personal')
|
|
462
|
+
throw new Error('Workspace API did not verify personal authentication');
|
|
463
|
+
return account;
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
if (!knownPersonal && error instanceof WorkspaceClientError && error.status === 401 && error.code === 'PERSONAL_KEY_NOT_REGISTERED')
|
|
467
|
+
return undefined;
|
|
468
|
+
throw error;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
const rememberPersonalProfile = async (auth, configPath, config, userId) => {
|
|
472
|
+
if (auth.source !== 'profile')
|
|
473
|
+
return;
|
|
474
|
+
config.profiles = { ...config.profiles, [auth.profile]: {
|
|
475
|
+
apiKey: auth.apiKey, authentication: 'personal', userId,
|
|
476
|
+
workspaceId: config.profiles?.[auth.profile]?.workspaceId, baseUrl: auth.baseUrl, updatedAt: new Date().toISOString(),
|
|
477
|
+
} };
|
|
478
|
+
await writeAuthConfig(configPath, config);
|
|
479
|
+
};
|
|
447
480
|
const resolveAuth = async (flags) => {
|
|
448
481
|
const explicitProfile = hasFlag(flags, 'profile') ? requireFlag(flags, 'profile') : undefined;
|
|
449
482
|
const overrideKey = hasFlag(flags, 'api-key') ? requireFlag(flags, 'api-key') : undefined;
|
|
@@ -452,7 +485,7 @@ const resolveAuth = async (flags) => {
|
|
|
452
485
|
: undefined;
|
|
453
486
|
const profile = explicitProfile ?? config?.defaultProfile ?? defaultProfileName;
|
|
454
487
|
const storedProfile = config?.profiles?.[profile];
|
|
455
|
-
const selected = Boolean(explicitProfile || storedProfile?.workspaceId || storedProfile
|
|
488
|
+
const selected = Boolean(explicitProfile || storedProfile?.workspaceId || isPersonalProfile(storedProfile));
|
|
456
489
|
if (selected && (!storedProfile || (!storedProfile.apiKey && !overrideKey)))
|
|
457
490
|
throw new Error('Selected profile has no workspace API key. Use workspace use <id> or auth login --api-key-stdin --profile <name>.');
|
|
458
491
|
const baseUrl = getFlag(flags, 'base-url') ??
|
|
@@ -462,15 +495,18 @@ const resolveAuth = async (flags) => {
|
|
|
462
495
|
if (overrideKey)
|
|
463
496
|
return { apiKey: overrideKey, baseUrl, profile, source: 'flag', workspaceId: getFlag(flags, 'workspace') };
|
|
464
497
|
if (selected && storedProfile?.apiKey) {
|
|
465
|
-
if ((storedProfile.workspaceId || storedProfile.apiKey
|
|
498
|
+
if ((storedProfile.workspaceId || isPersonalProfile(storedProfile) || canDiscoverPersonalKey(storedProfile.apiKey)) &&
|
|
466
499
|
new URL(baseUrl).origin !== new URL(storedProfile.baseUrl).origin)
|
|
467
500
|
throw new Error('Selected workspace belongs to another API origin; select a workspace for this origin or supply an explicit API key');
|
|
468
|
-
return { apiKey: storedProfile.apiKey, baseUrl, profile, source: 'profile', workspaceId: getFlag(flags, 'workspace') ?? storedProfile.workspaceId };
|
|
501
|
+
return { apiKey: storedProfile.apiKey, authentication: storedProfile.authentication, baseUrl, profile, source: 'profile', workspaceId: getFlag(flags, 'workspace') ?? storedProfile.workspaceId };
|
|
469
502
|
}
|
|
470
503
|
if (env.ASSETHUB_API_KEY?.trim())
|
|
471
504
|
return { apiKey: env.ASSETHUB_API_KEY, baseUrl, profile, source: 'env', workspaceId: getFlag(flags, 'workspace') };
|
|
472
|
-
if (storedProfile?.apiKey)
|
|
505
|
+
if (storedProfile?.apiKey) {
|
|
506
|
+
if (canDiscoverPersonalKey(storedProfile.apiKey) && new URL(baseUrl).origin !== new URL(storedProfile.baseUrl).origin)
|
|
507
|
+
throw new Error('Saved API key belongs to another API origin; supply an explicit API key for this origin');
|
|
473
508
|
return { apiKey: storedProfile.apiKey, baseUrl, profile, source: 'profile', workspaceId: getFlag(flags, 'workspace') ?? storedProfile.workspaceId };
|
|
509
|
+
}
|
|
474
510
|
throw new Error('Missing workspace API key. Use "workspace use <id>" after user login, "auth login --api-key-stdin", or ASSETHUB_API_KEY.');
|
|
475
511
|
};
|
|
476
512
|
const createContext = async (flags, args) => {
|
|
@@ -1268,18 +1304,22 @@ const workspaceAuth = async (flags, allowPersonal = false) => {
|
|
|
1268
1304
|
const explicitProfile = hasFlag(flags, 'profile') ? requireFlag(flags, 'profile') : undefined;
|
|
1269
1305
|
const profile = explicitProfile ?? config.defaultProfile ?? defaultProfileName;
|
|
1270
1306
|
const stored = config.profiles?.[profile];
|
|
1271
|
-
const preferredKey =
|
|
1272
|
-
|
|
1273
|
-
? stored?.apiKey
|
|
1274
|
-
: env.ASSETHUB_API_KEY?.trim() || stored?.apiKey);
|
|
1275
|
-
if (allowPersonal && preferredKey?.startsWith('ah_pat_')) {
|
|
1307
|
+
const preferredKey = preferredApiKey(flags, stored);
|
|
1308
|
+
if (allowPersonal && (canDiscoverPersonalKey(preferredKey) || (preferredKey === stored?.apiKey && isPersonalProfile(stored)))) {
|
|
1276
1309
|
const auth = await resolveAuth(flags);
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1310
|
+
auth.authentication = personalProfileAuthentication(auth.apiKey, auth.baseUrl, stored);
|
|
1311
|
+
const knownPersonal = isPersonalProfile(auth);
|
|
1312
|
+
const account = knownPersonal ? undefined : await personalAccount(auth);
|
|
1313
|
+
if (knownPersonal || account) {
|
|
1314
|
+
if (account)
|
|
1315
|
+
await rememberPersonalProfile(auth, configPath, config, account.userId);
|
|
1316
|
+
return {
|
|
1317
|
+
configPath, config, profile, accessToken: auth.apiKey,
|
|
1318
|
+
workspaceMfaToken: undefined, baseUrl: auth.baseUrl,
|
|
1319
|
+
selectedWorkspaceId: auth.workspaceId, personal: true,
|
|
1320
|
+
client: createWorkspaceClient({ accessToken: auth.apiKey, baseUrl: auth.baseUrl, workspaceId: auth.workspaceId }),
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1283
1323
|
}
|
|
1284
1324
|
const environmentToken = explicitProfile ? undefined : env.ASSETHUB_ACCESS_TOKEN?.trim();
|
|
1285
1325
|
const accessToken = environmentToken || stored?.accessToken;
|
|
@@ -1392,6 +1432,7 @@ const commandWorkspace = async (subcommand, positionals, flags) => {
|
|
|
1392
1432
|
throw new Error('Workspace API did not verify personal authentication');
|
|
1393
1433
|
profiles[auth.profile] = {
|
|
1394
1434
|
apiKey: auth.accessToken,
|
|
1435
|
+
authentication: 'personal',
|
|
1395
1436
|
userId: account.userId,
|
|
1396
1437
|
workspaceId,
|
|
1397
1438
|
baseUrl: auth.baseUrl,
|
|
@@ -1404,7 +1445,7 @@ const commandWorkspace = async (subcommand, positionals, flags) => {
|
|
|
1404
1445
|
const cached = Object.entries(profiles).find(([, entry]) => entry.userId === account.userId &&
|
|
1405
1446
|
entry.workspaceId === workspaceId &&
|
|
1406
1447
|
new URL(entry.baseUrl).origin === new URL(auth.baseUrl).origin &&
|
|
1407
|
-
entry.apiKey && !entry
|
|
1448
|
+
entry.apiKey && !isPersonalProfile(entry));
|
|
1408
1449
|
let apiKey;
|
|
1409
1450
|
for (const candidate of new Set([cached?.[1].apiKey, suppliedKey])) {
|
|
1410
1451
|
if (!candidate)
|
|
@@ -1507,22 +1548,19 @@ const commandAuth = async (subcommand, flags) => {
|
|
|
1507
1548
|
throw new Error('API key is empty. Pass --api-key, use --api-key-stdin, or set ASSETHUB_API_KEY.');
|
|
1508
1549
|
}
|
|
1509
1550
|
const baseUrl = getFlag(flags, 'base-url') ?? env.ASSETHUB_API_BASE_URL ?? defaultBaseUrl;
|
|
1510
|
-
const
|
|
1511
|
-
const
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
if (personal && account?.authentication !== 'personal')
|
|
1515
|
-
throw new Error('Workspace API did not verify personal authentication');
|
|
1551
|
+
const config = await readAuthConfig(configPath);
|
|
1552
|
+
const authentication = personalProfileAuthentication(apiKey, baseUrl, config.profiles?.[profile]);
|
|
1553
|
+
const account = await personalAccount({ apiKey, baseUrl, authentication });
|
|
1554
|
+
const personal = account !== undefined;
|
|
1516
1555
|
if (!personal && !hasFlag(flags, 'skip-verify')) {
|
|
1517
1556
|
const client = createAssetHubClient({ apiKey, baseUrl });
|
|
1518
1557
|
await client.v2.listModels();
|
|
1519
1558
|
}
|
|
1520
|
-
const config = await readAuthConfig(configPath);
|
|
1521
1559
|
const profiles = config.profiles ?? {};
|
|
1522
1560
|
profiles[profile] = {
|
|
1523
1561
|
apiKey,
|
|
1524
1562
|
baseUrl,
|
|
1525
|
-
...(account ? { userId: account.userId } : {}),
|
|
1563
|
+
...(account ? { authentication: 'personal', userId: account.userId } : {}),
|
|
1526
1564
|
updatedAt: new Date().toISOString(),
|
|
1527
1565
|
};
|
|
1528
1566
|
await writeAuthConfig(configPath, {
|
|
@@ -1543,9 +1581,22 @@ const commandAuth = async (subcommand, flags) => {
|
|
|
1543
1581
|
if (subcommand === 'status') {
|
|
1544
1582
|
const config = await readAuthConfig(configPath);
|
|
1545
1583
|
const current = config.profiles?.[getFlag(flags, 'profile') ?? config.defaultProfile ?? profile];
|
|
1584
|
+
let apiAuth;
|
|
1585
|
+
const preferredKey = preferredApiKey(flags, current);
|
|
1586
|
+
if (canDiscoverPersonalKey(preferredKey) || (preferredKey === current?.apiKey && isPersonalProfile(current))) {
|
|
1587
|
+
apiAuth = await resolveAuth(flags);
|
|
1588
|
+
apiAuth.authentication = personalProfileAuthentication(apiAuth.apiKey, apiAuth.baseUrl, current);
|
|
1589
|
+
const account = await personalAccount(apiAuth);
|
|
1590
|
+
if (account) {
|
|
1591
|
+
if (apiAuth.workspaceId)
|
|
1592
|
+
await createAssetHubClient(apiAuth).v2.getCapabilities();
|
|
1593
|
+
await rememberPersonalProfile(apiAuth, configPath, config, account.userId);
|
|
1594
|
+
print({ authenticated: true, authentication: 'personal', userId: account.userId, workspaceId: apiAuth.workspaceId,
|
|
1595
|
+
profile: apiAuth.profile, source: apiAuth.source, baseUrl: apiAuth.baseUrl });
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1546
1599
|
if (!hasFlag(flags, 'api-key') &&
|
|
1547
|
-
!current?.apiKey?.startsWith('ah_pat_') &&
|
|
1548
|
-
(hasFlag(flags, 'profile') || !env.ASSETHUB_API_KEY?.startsWith('ah_pat_')) &&
|
|
1549
1600
|
((!hasFlag(flags, 'profile') && env.ASSETHUB_ACCESS_TOKEN?.trim()) ||
|
|
1550
1601
|
(current?.accessToken && !current.apiKey))) {
|
|
1551
1602
|
const auth = await workspaceAuth(flags);
|
|
@@ -1560,22 +1611,12 @@ const commandAuth = async (subcommand, flags) => {
|
|
|
1560
1611
|
});
|
|
1561
1612
|
return;
|
|
1562
1613
|
}
|
|
1563
|
-
const auth = await resolveAuth(flags);
|
|
1614
|
+
const auth = apiAuth ?? await resolveAuth(flags);
|
|
1564
1615
|
const client = createAssetHubClient({
|
|
1565
1616
|
apiKey: auth.apiKey,
|
|
1566
1617
|
baseUrl: auth.baseUrl,
|
|
1567
1618
|
workspaceId: auth.workspaceId,
|
|
1568
1619
|
});
|
|
1569
|
-
if (auth.apiKey.startsWith('ah_pat_')) {
|
|
1570
|
-
const account = await createWorkspaceClient({ accessToken: auth.apiKey, baseUrl: auth.baseUrl, workspaceId: auth.workspaceId }).list();
|
|
1571
|
-
if (account.authentication !== 'personal')
|
|
1572
|
-
throw new Error('Workspace API did not verify personal authentication');
|
|
1573
|
-
if (auth.workspaceId)
|
|
1574
|
-
await client.v2.getCapabilities();
|
|
1575
|
-
print({ authenticated: true, authentication: 'personal', userId: account.userId, workspaceId: auth.workspaceId,
|
|
1576
|
-
profile: auth.profile, source: auth.source, baseUrl: auth.baseUrl });
|
|
1577
|
-
return;
|
|
1578
|
-
}
|
|
1579
1620
|
const models = await client.v2.listModels();
|
|
1580
1621
|
print({
|
|
1581
1622
|
authenticated: true,
|
|
@@ -3810,7 +3851,7 @@ const run = async () => {
|
|
|
3810
3851
|
const stored = config.profiles?.[explicitProfile ?? config.defaultProfile ?? defaultProfileName];
|
|
3811
3852
|
if (explicitProfile && !stored)
|
|
3812
3853
|
throw new Error('Selected profile does not exist');
|
|
3813
|
-
const selected = explicitProfile || stored?.workspaceId || stored
|
|
3854
|
+
const selected = explicitProfile || stored?.workspaceId || isPersonalProfile(stored);
|
|
3814
3855
|
const baseUrl = getFlag(parsed.flags, 'base-url') ??
|
|
3815
3856
|
(selected ? stored?.baseUrl : env.ASSETHUB_API_BASE_URL ?? stored?.baseUrl) ?? defaultBaseUrl;
|
|
3816
3857
|
const workspaceId = getFlag(parsed.flags, 'workspace') ??
|