@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 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 (`ah_pat_`), then select its workspace:
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
@@ -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;AAyJxD,eAAO,MAAM,KAAK,s3aAkIjB,CAAA;AA8pFD;;;;;;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"}
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?.apiKey?.startsWith('ah_pat_'));
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.startsWith('ah_pat_')) &&
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 = getFlag(flags, 'api-key') ??
1272
- (explicitProfile || stored?.workspaceId || stored?.apiKey?.startsWith('ah_pat_')
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
- return {
1278
- configPath, config, profile, accessToken: auth.apiKey,
1279
- workspaceMfaToken: undefined, baseUrl: auth.baseUrl,
1280
- selectedWorkspaceId: auth.workspaceId, personal: true,
1281
- client: createWorkspaceClient({ accessToken: auth.apiKey, baseUrl: auth.baseUrl, workspaceId: auth.workspaceId }),
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.apiKey.startsWith('ah_pat_'));
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 personal = apiKey.startsWith('ah_pat_');
1511
- const account = personal
1512
- ? await createWorkspaceClient({ accessToken: apiKey, baseUrl }).list()
1513
- : undefined;
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?.apiKey?.startsWith('ah_pat_');
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') ??
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assethub/cli",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Command line interface for AssetHub.",
5
5
  "type": "module",
6
6
  "bin": {