@antasphere/slideless 0.2.2 → 0.2.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.
Files changed (2) hide show
  1. package/dist/bin.js +237 -167
  2. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -3042,7 +3042,7 @@ var {
3042
3042
  Help
3043
3043
  } = import_index.default;
3044
3044
 
3045
- // ../../node_modules/.pnpm/@antasphere+cli-core@0.2.0/node_modules/@antasphere/cli-core/dist/config.js
3045
+ // ../../node_modules/.pnpm/@antasphere+cli-core@0.4.0/node_modules/@antasphere/cli-core/dist/config.js
3046
3046
  import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3047
3047
  import { dirname, join } from "node:path";
3048
3048
  var HUB_TOOL = "hub";
@@ -3109,50 +3109,43 @@ function redactKey(key) {
3109
3109
  return "****";
3110
3110
  return `${key.slice(0, 13)}\u2026${key.slice(-4)}`;
3111
3111
  }
3112
- function lookupWorkspaceKey(profile, workspaceId) {
3113
- if (!profile)
3114
- return void 0;
3115
- const entry = profile.workspaceKeys?.[workspaceId];
3116
- if (entry?.apiKey)
3117
- return entry.apiKey;
3118
- if (profile.apiKey && profile.workspaceId === workspaceId)
3119
- return profile.apiKey;
3120
- return void 0;
3112
+ function lookupConnectKey(profile, hubProfileName) {
3113
+ return profile?.connectKeys?.[hubProfileName]?.apiKey;
3121
3114
  }
3122
- function storeWorkspaceKey(env, tool, profileName, workspaceId, entry) {
3115
+ function storeConnectKey(env, tool, profileName, hubProfileName, entry) {
3123
3116
  const config = loadConfig(env, tool);
3124
3117
  const profile = config.profiles[profileName] ?? {};
3125
3118
  config.profiles[profileName] = {
3126
3119
  ...profile,
3127
- workspaceKeys: { ...profile.workspaceKeys, [workspaceId]: entry }
3120
+ connectKeys: { ...profile.connectKeys, [hubProfileName]: entry }
3128
3121
  };
3129
3122
  if (!config.activeProfile)
3130
3123
  config.activeProfile = profileName;
3131
3124
  return saveConfig(env, tool, config);
3132
3125
  }
3133
- function removeWorkspaceKey(env, tool, profileName, workspaceId) {
3126
+ function removeConnectKey(env, tool, profileName, hubProfileName) {
3134
3127
  const config = loadConfig(env, tool);
3135
3128
  const profile = config.profiles[profileName];
3136
- if (!profile?.workspaceKeys || !(workspaceId in profile.workspaceKeys))
3129
+ if (!profile?.connectKeys || !(hubProfileName in profile.connectKeys))
3137
3130
  return false;
3138
- const { [workspaceId]: _dropped, ...rest } = profile.workspaceKeys;
3131
+ const { [hubProfileName]: _dropped, ...rest } = profile.connectKeys;
3139
3132
  if (Object.keys(rest).length === 0) {
3140
- const { workspaceKeys: _empty, ...bare } = profile;
3133
+ const { connectKeys: _empty, ...bare } = profile;
3141
3134
  config.profiles[profileName] = bare;
3142
3135
  } else {
3143
- config.profiles[profileName] = { ...profile, workspaceKeys: rest };
3136
+ config.profiles[profileName] = { ...profile, connectKeys: rest };
3144
3137
  }
3145
3138
  saveConfig(env, tool, config);
3146
3139
  return true;
3147
3140
  }
3148
3141
 
3149
- // ../../node_modules/.pnpm/@antasphere+cli-core@0.2.0/node_modules/@antasphere/cli-core/dist/io.js
3142
+ // ../../node_modules/.pnpm/@antasphere+cli-core@0.4.0/node_modules/@antasphere/cli-core/dist/io.js
3150
3143
  function printJson(io, value) {
3151
3144
  io.out.write(`${JSON.stringify(value, null, 2)}
3152
3145
  `);
3153
3146
  }
3154
3147
 
3155
- // ../../node_modules/.pnpm/@antasphere+cli-core@0.2.0/node_modules/@antasphere/cli-core/dist/resolve.js
3148
+ // ../../node_modules/.pnpm/@antasphere+cli-core@0.4.0/node_modules/@antasphere/cli-core/dist/resolve.js
3156
3149
  var CliUsageError = class extends Error {
3157
3150
  };
3158
3151
  var DEFAULT_PROFILE = "default";
@@ -3179,7 +3172,7 @@ function resolveApiKey(opts) {
3179
3172
  return opts.flag ?? opts.env[opts.envVar] ?? opts.profile?.apiKey;
3180
3173
  }
3181
3174
 
3182
- // ../../node_modules/.pnpm/@antasphere+cli-core@0.2.0/node_modules/@antasphere/cli-core/dist/auth.js
3175
+ // ../../node_modules/.pnpm/@antasphere+cli-core@0.4.0/node_modules/@antasphere/cli-core/dist/auth.js
3183
3176
  var CliAuthError = class extends Error {
3184
3177
  status;
3185
3178
  code;
@@ -3234,7 +3227,7 @@ var CliAuthClient = class {
3234
3227
  }
3235
3228
  };
3236
3229
 
3237
- // ../../node_modules/.pnpm/@antasphere+cli-core@0.2.0/node_modules/@antasphere/cli-core/dist/connect.js
3230
+ // ../../node_modules/.pnpm/@antasphere+cli-core@0.4.0/node_modules/@antasphere/cli-core/dist/connect.js
3238
3231
  var CliConnectError = class extends CliAuthError {
3239
3232
  stage;
3240
3233
  constructor(stage, status, code, message, details) {
@@ -3268,19 +3261,27 @@ var CliConnectClient = class {
3268
3261
  toolToken(hubApiKey, body) {
3269
3262
  return this.call("hub", `${this.hubBaseUrl}/api/v1/sso/tool-token`, body, hubApiKey);
3270
3263
  }
3271
- /** Step 2 — the tool verifies the JWT (its only credential) and mints its key. */
3272
- cliConnect(token) {
3273
- return this.call("tool", `${this.toolBaseUrl}/api/v1/sso/cli-connect`, { token });
3264
+ /**
3265
+ * Step 2 — the tool verifies the JWT (its only credential) and mints its
3266
+ * key. `hubRefreshToken` is the SAME response's offline grant, relayed so
3267
+ * the tool can store it (a grant-less connect is refused for users with
3268
+ * none already stored — `hub_grant_missing`).
3269
+ */
3270
+ cliConnect(token, hubRefreshToken) {
3271
+ return this.call("tool", `${this.toolBaseUrl}/api/v1/sso/cli-connect`, {
3272
+ token,
3273
+ ...hubRefreshToken ? { hubRefreshToken } : {}
3274
+ });
3274
3275
  }
3275
- /** The full two-call exchange. The hub key goes to the hub ONLY. */
3276
+ /** The full two-call exchange. The hub key goes to the hub ONLY; the
3277
+ * offline grant goes to the TOOL only — neither is ever persisted here. */
3276
3278
  async exchange(hubApiKey, body) {
3277
3279
  const minted = await this.toolToken(hubApiKey, body);
3278
- const connected = await this.cliConnect(minted.token);
3280
+ const connected = await this.cliConnect(minted.token, minted.hubRefreshToken);
3279
3281
  return {
3280
3282
  key: connected.key,
3281
3283
  apiKey: connected.apiKey,
3282
3284
  user: connected.user,
3283
- hubWorkspaceId: minted.workspaceId,
3284
3285
  toolWorkspaceId: connected.workspaceId
3285
3286
  };
3286
3287
  }
@@ -3299,16 +3300,106 @@ var CliConnectClient = class {
3299
3300
  }
3300
3301
  };
3301
3302
 
3302
- // ../../node_modules/.pnpm/@antasphere+cli-core@0.2.0/node_modules/@antasphere/cli-core/dist/org.js
3303
- function getActiveWorkspace(env, opts = {}) {
3304
- const { profile } = resolveProfile(loadConfig(env, HUB_TOOL), opts.profile);
3305
- return profile?.activeWorkspaceId ?? profile?.workspaceId;
3303
+ // ../../node_modules/.pnpm/@antasphere+cli-core@0.4.0/node_modules/@antasphere/cli-core/dist/connect-on-demand.js
3304
+ async function probeToolInstance(toolBaseUrl, fetchImpl) {
3305
+ const f = fetchImpl ?? globalThis.fetch.bind(globalThis);
3306
+ try {
3307
+ const res = await f(`${toolBaseUrl.replace(/\/+$/, "")}/api/v1/instance`, {
3308
+ method: "GET",
3309
+ // Discovery is served cacheable for CLI/MCP clients, but the probe
3310
+ // must see live state (mirrors the tool SDKs' discovery call).
3311
+ cache: "no-store"
3312
+ });
3313
+ if (!res.ok)
3314
+ return null;
3315
+ const info = await res.json().catch(() => null);
3316
+ const methods = info?.auth?.methods;
3317
+ if (!Array.isArray(methods))
3318
+ return null;
3319
+ const authMethods = methods.filter((m) => typeof m === "string");
3320
+ const edition = typeof info?.edition === "string" ? info.edition : void 0;
3321
+ return { cloud: authMethods.includes("antasphere") || edition === "cloud", edition, authMethods };
3322
+ } catch {
3323
+ return null;
3324
+ }
3325
+ }
3326
+ function activeHubProfile(env) {
3327
+ return resolveProfile(loadConfig(env, HUB_TOOL), void 0);
3328
+ }
3329
+ function defaultMissingHubLogin(tool) {
3330
+ const envVar = `${tool.replace(/-/g, "_").toUpperCase()}_API_KEY`;
3331
+ return `This ${tool} instance signs in through the Antasphere hub. Run \`antasphere login\` once, then retry \u2014 or pass --api-key / set ${envVar}.`;
3306
3332
  }
3307
- function resolveTargetWorkspace(env, opts = {}) {
3308
- return opts.override ?? getActiveWorkspace(env, opts);
3333
+ var DEFAULT_HUB_LOGIN_REJECTED = "The stored Antasphere hub credential was rejected \u2014 run `antasphere login` again, then retry.";
3334
+ function defaultConnected(facts) {
3335
+ return `Connected to ${facts.toolBaseUrl} as ${facts.email} via Antasphere` + (facts.stored ? "" : ` \u2014 key NOT cached: profile '${facts.profileName}' points at ${facts.profileBaseUrl}`) + ".\n";
3336
+ }
3337
+ async function connectOnDemand(opts) {
3338
+ assertToolNamespace(opts.tool);
3339
+ const toolBaseUrl = opts.toolBaseUrl.replace(/\/+$/, "");
3340
+ const messages = opts.messages ?? {};
3341
+ const profileName = opts.profileName ?? DEFAULT_PROFILE;
3342
+ const hub = activeHubProfile(opts.env);
3343
+ const existing = loadConfig(opts.env, opts.tool).profiles[profileName];
3344
+ const pinnedHere = existing?.baseUrl?.replace(/\/+$/, "") === toolBaseUrl;
3345
+ if (pinnedHere && hub.name) {
3346
+ const cachedKey = lookupConnectKey(existing, hub.name);
3347
+ if (cachedKey)
3348
+ return { outcome: "connected", key: cachedKey, cached: true };
3349
+ }
3350
+ const probe = await probeToolInstance(toolBaseUrl, opts.fetch);
3351
+ if (!probe?.cloud)
3352
+ return { outcome: "not_cloud" };
3353
+ const hubKey = hub.profile?.apiKey;
3354
+ const hubBaseUrl = hub.profile?.baseUrl;
3355
+ if (!hubKey || !hubBaseUrl || !hub.name) {
3356
+ throw new CliUsageError(messages.missingHubLogin ?? defaultMissingHubLogin(opts.tool));
3357
+ }
3358
+ const resource = opts.resource ?? `${toolBaseUrl}/mcp`;
3359
+ const connect = new CliConnectClient({
3360
+ hubBaseUrl,
3361
+ toolBaseUrl,
3362
+ ...opts.fetch ? { fetch: opts.fetch } : {}
3363
+ });
3364
+ const body = { resource };
3365
+ let result;
3366
+ try {
3367
+ result = await connect.exchange(hubKey, body);
3368
+ } catch (e) {
3369
+ if (e instanceof CliConnectError && e.needsReExchange) {
3370
+ result = await connect.exchange(hubKey, body);
3371
+ } else if (e instanceof CliConnectError && e.needsHubLogin) {
3372
+ throw new CliUsageError(messages.hubLoginRejected ?? DEFAULT_HUB_LOGIN_REJECTED);
3373
+ } else {
3374
+ throw e;
3375
+ }
3376
+ }
3377
+ const stored = !existing?.baseUrl || pinnedHere;
3378
+ if (stored) {
3379
+ storeConnectKey(opts.env, opts.tool, profileName, hub.name, {
3380
+ apiKey: result.key,
3381
+ email: result.user.email,
3382
+ mintedAt: (/* @__PURE__ */ new Date()).toISOString()
3383
+ });
3384
+ if (!existing?.baseUrl) {
3385
+ const cfg = loadConfig(opts.env, opts.tool);
3386
+ cfg.profiles[profileName] = { ...cfg.profiles[profileName], baseUrl: toolBaseUrl };
3387
+ saveConfig(opts.env, opts.tool, cfg);
3388
+ }
3389
+ }
3390
+ const facts = {
3391
+ toolBaseUrl,
3392
+ email: result.user.email,
3393
+ stored,
3394
+ profileName,
3395
+ // `stored` is false only when the profile names another instance.
3396
+ ...stored ? {} : { profileBaseUrl: existing?.baseUrl }
3397
+ };
3398
+ opts.notify?.((messages.connected ?? defaultConnected)(facts));
3399
+ return { outcome: "connected", key: result.key, result, cached: false, stored };
3309
3400
  }
3310
3401
 
3311
- // ../../node_modules/.pnpm/@antasphere+cli-core@0.2.0/node_modules/@antasphere/cli-core/dist/migrate.js
3402
+ // ../../node_modules/.pnpm/@antasphere+cli-core@0.4.0/node_modules/@antasphere/cli-core/dist/migrate.js
3312
3403
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
3313
3404
  import { join as join2 } from "node:path";
3314
3405
  function migrateLegacyConfig(env, spec) {
@@ -3426,6 +3517,14 @@ var PlatformClient = class {
3426
3517
  me() {
3427
3518
  return this.request("GET", "/me");
3428
3519
  }
3520
+ /**
3521
+ * CLOUD EDITION only (404 on oss); sessions only (machines 403): record
3522
+ * the explicit first-run-welcome dismissal — flips /me's
3523
+ * `firstRunPending` to false, permanently. Idempotent.
3524
+ */
3525
+ dismissOnboarding() {
3526
+ return this.request("POST", "/me/onboarding/dismiss");
3527
+ }
3429
3528
  /** Better Auth session probe — null when signed out. */
3430
3529
  async session() {
3431
3530
  return this.request("GET", "/auth/get-session");
@@ -3448,6 +3547,15 @@ var PlatformClient = class {
3448
3547
  cliAuthComplete(req) {
3449
3548
  return this.request("POST", "/cli/auth/complete", req);
3450
3549
  }
3550
+ /**
3551
+ * Revoke the PRESENTING API key (CLI logout). Self-revocation only: the
3552
+ * route names no key, so the credential can kill exactly itself. Requires
3553
+ * an API-key credential with presentations:write (sessions and OAuth
3554
+ * bearers are refused — the dashboard is their key-management surface).
3555
+ */
3556
+ cliAuthRevoke() {
3557
+ return this.request("DELETE", "/cli/auth/key");
3558
+ }
3451
3559
  /**
3452
3560
  * Public, CLOUD EDITION only (404 on oss): exchange a hub-minted 120 s
3453
3561
  * connect JWT (the hub's /sso/tool-token response) for an `slk_` API key
@@ -3457,6 +3565,17 @@ var PlatformClient = class {
3457
3565
  ssoCliConnect(req) {
3458
3566
  return this.request("POST", "/sso/cli-connect", req);
3459
3567
  }
3568
+ /**
3569
+ * CLOUD EDITION only (404 on oss); sessions only (machines 403 — the
3570
+ * path is unlisted in the scope allowlist): single logout. The server
3571
+ * revokes the local session and clears the SSO hint cookie in this very
3572
+ * response; `url` is the hub end-session URL the browser must then VISIT
3573
+ * to end the hub anchor session (null = local signout only — go straight
3574
+ * to /login?signed_out=1).
3575
+ */
3576
+ ssoLogout() {
3577
+ return this.request("POST", "/sso/logout");
3578
+ }
3460
3579
  // ── Members ───────────────────────────────────────────────────────────────
3461
3580
  members(params = {}) {
3462
3581
  return this.request("GET", this.pathWithQuery("/members", params));
@@ -3529,15 +3648,6 @@ var PlatformClient = class {
3529
3648
  acceptInvitation(req) {
3530
3649
  return this.request("POST", "/invitations/accept", req);
3531
3650
  }
3532
- // ── OAuth consent ─────────────────────────────────────────────────────────
3533
- /**
3534
- * Park the workspace the upcoming OAuth consent should bind (ADR 014).
3535
- * Sessions only; the consent page calls it right before approving when
3536
- * the user picked a non-default workspace.
3537
- */
3538
- oauthConsentWorkspace(workspaceId) {
3539
- return this.request("POST", "/oauth/consent-workspace", { workspaceId });
3540
- }
3541
3651
  // ── Audit ─────────────────────────────────────────────────────────────────
3542
3652
  audit(params = {}) {
3543
3653
  return this.request("GET", this.pathWithQuery("/audit", params));
@@ -3768,11 +3878,8 @@ function saveConfig2(env, config) {
3768
3878
  function clearConfig2(env) {
3769
3879
  clearConfig(env, TOOL);
3770
3880
  }
3771
- function storeWorkspaceKey2(env, profileName, workspaceId, entry) {
3772
- return storeWorkspaceKey(env, TOOL, profileName, workspaceId, entry);
3773
- }
3774
- function removeWorkspaceKey2(env, profileName, workspaceId) {
3775
- return removeWorkspaceKey(env, TOOL, profileName, workspaceId);
3881
+ function removeConnectKey2(env, profileName, hubProfileName) {
3882
+ return removeConnectKey(env, TOOL, profileName, hubProfileName);
3776
3883
  }
3777
3884
 
3778
3885
  // src/context.ts
@@ -3794,19 +3901,15 @@ function resolveContext(cmd, io) {
3794
3901
  missingMessage: "No instance configured. Pass --api-url <url>, set SLIDELESS_URL, or sign in once with `slideless auth login-request --api-url <url> --email <you>` to save a profile."
3795
3902
  });
3796
3903
  let apiKey = resolveApiKey({ flag: opts.apiKey, env: io.env, envVar: "SLIDELESS_API_KEY", profile });
3797
- let org;
3798
- if (!apiKey) {
3799
- org = resolveTargetWorkspace(io.env, opts.org ? { override: opts.org } : {});
3800
- if (org && profile?.baseUrl?.replace(/\/+$/, "") === baseUrl) {
3801
- apiKey = lookupWorkspaceKey(profile, org);
3802
- }
3904
+ if (!apiKey && profile?.baseUrl?.replace(/\/+$/, "") === baseUrl) {
3905
+ const hub = activeHubProfile(io.env);
3906
+ if (hub.name) apiKey = lookupConnectKey(profile, hub.name);
3803
3907
  }
3804
3908
  const fetchImpl = io.fetch ?? globalThis.fetch.bind(globalThis);
3805
3909
  return {
3806
3910
  client: new PlatformClient({ baseUrl, ...apiKey ? { apiKey } : {}, fetch: fetchImpl }),
3807
3911
  baseUrl,
3808
3912
  apiKey,
3809
- org,
3810
3913
  json: Boolean(opts.json),
3811
3914
  io,
3812
3915
  profileName,
@@ -3814,74 +3917,28 @@ function resolveContext(cmd, io) {
3814
3917
  };
3815
3918
  }
3816
3919
  var NO_KEY_MESSAGE = "An API key is required. Sign in (`slideless auth login-request --email <you>`), paste one (`slideless login`), pass --api-key, or set SLIDELESS_API_KEY.";
3920
+ var MISSING_HUB_LOGIN_MESSAGE = "This Slideless instance signs in through the Antasphere hub. Run `antasphere login` once, then retry \u2014 or pass --api-key <slk_\u2026> / set SLIDELESS_API_KEY.";
3817
3921
  async function requireApiKey(ctx) {
3818
3922
  if (ctx.apiKey) return ctx.apiKey;
3819
- const key = await connectViaHub(ctx);
3820
- if (!key) throw new CliUsageError(NO_KEY_MESSAGE);
3821
- return key;
3822
- }
3823
- async function connectViaHub(ctx) {
3824
3923
  const { io } = ctx;
3825
- let cloud = false;
3826
- try {
3827
- const info = await ctx.client.instance();
3828
- cloud = info.auth.methods.includes("antasphere") || info.edition === "cloud";
3829
- } catch {
3830
- return void 0;
3831
- }
3832
- if (!cloud) return void 0;
3833
- const { profile: hubProfile } = resolveProfile(loadConfig(io.env, HUB_TOOL), void 0);
3834
- const hubKey = hubProfile?.apiKey;
3835
- const hubBaseUrl = hubProfile?.baseUrl;
3836
- if (!hubKey || !hubBaseUrl) {
3837
- throw new CliUsageError(
3838
- "This Slideless instance signs in through the Antasphere hub. Run `antasphere login` once, then retry \u2014 or pass --api-key <slk_\u2026> / set SLIDELESS_API_KEY."
3839
- );
3840
- }
3841
- const resource = `${ctx.baseUrl}/mcp`;
3842
- const connect = new CliConnectClient({
3843
- hubBaseUrl,
3924
+ const outcome = await connectOnDemand({
3925
+ tool: "slideless",
3844
3926
  toolBaseUrl: ctx.baseUrl,
3845
- ...io.fetch ? { fetch: io.fetch } : {}
3927
+ ...ctx.profileName !== void 0 ? { profileName: ctx.profileName } : {},
3928
+ env: io.env,
3929
+ // Thread the injected fetch so the probe + exchange stay on the test
3930
+ // harness wire (and any proxying the runner set up).
3931
+ ...io.fetch ? { fetch: io.fetch } : {},
3932
+ notify: (line) => io.err.write(line),
3933
+ messages: { missingHubLogin: MISSING_HUB_LOGIN_MESSAGE }
3846
3934
  });
3847
- const body = { resource, ...ctx.org ? { workspaceId: ctx.org } : {} };
3848
- let result;
3849
- try {
3850
- result = await connect.exchange(hubKey, body);
3851
- } catch (e) {
3852
- if (e instanceof CliConnectError && e.needsReExchange) {
3853
- result = await connect.exchange(hubKey, body);
3854
- } else if (e instanceof CliConnectError && e.needsHubLogin) {
3855
- throw new CliUsageError(
3856
- "The stored Antasphere hub credential was rejected \u2014 run `antasphere login` again, then retry."
3857
- );
3858
- } else {
3859
- throw e;
3860
- }
3861
- }
3862
- const profileName = ctx.profileName ?? DEFAULT_PROFILE;
3863
- const existing = ctx.config.profiles[profileName];
3864
- const cacheable = !existing?.baseUrl || existing.baseUrl.replace(/\/+$/, "") === ctx.baseUrl;
3865
- if (cacheable) {
3866
- storeWorkspaceKey2(io.env, profileName, result.hubWorkspaceId, {
3867
- apiKey: result.key,
3868
- toolWorkspaceId: result.toolWorkspaceId,
3869
- email: result.user.email,
3870
- mintedAt: (/* @__PURE__ */ new Date()).toISOString()
3871
- });
3872
- if (!existing?.baseUrl) {
3873
- const cfg = loadConfig2(io.env);
3874
- cfg.profiles[profileName] = { ...cfg.profiles[profileName], baseUrl: ctx.baseUrl };
3875
- saveConfig2(io.env, cfg);
3876
- }
3935
+ if (outcome.outcome === "not_cloud") {
3936
+ throw new CliUsageError(NO_KEY_MESSAGE);
3877
3937
  }
3878
- io.err.write(
3879
- `Connected to ${ctx.baseUrl} as ${result.user.email} via Antasphere (org ${result.hubWorkspaceId})` + (cacheable ? "" : ` \u2014 key NOT cached: profile '${profileName}' points at ${existing?.baseUrl}`) + ".\n"
3880
- );
3881
- ctx.apiKey = result.key;
3938
+ ctx.apiKey = outcome.key;
3882
3939
  const fetchImpl = io.fetch ?? globalThis.fetch.bind(globalThis);
3883
- ctx.client = new PlatformClient({ baseUrl: ctx.baseUrl, apiKey: result.key, fetch: fetchImpl });
3884
- return result.key;
3940
+ ctx.client = new PlatformClient({ baseUrl: ctx.baseUrl, apiKey: outcome.key, fetch: fetchImpl });
3941
+ return outcome.key;
3885
3942
  }
3886
3943
  function table(rows) {
3887
3944
  if (rows.length === 0) return "";
@@ -3917,10 +3974,26 @@ async function readLineFromStdin() {
3917
3974
  for await (const chunk of process.stdin) chunks.push(chunk);
3918
3975
  return Buffer.concat(chunks).toString("utf8").split("\n")[0]?.trim() ?? "";
3919
3976
  }
3977
+ async function refuseOtpLoginOnCloud(baseUrl, io) {
3978
+ let cloud = false;
3979
+ try {
3980
+ const fetchImpl = io.fetch ?? globalThis.fetch.bind(globalThis);
3981
+ const info = await new PlatformClient({ baseUrl, fetch: fetchImpl }).instance();
3982
+ cloud = info.auth.methods.includes("antasphere") || info.edition === "cloud";
3983
+ } catch {
3984
+ return;
3985
+ }
3986
+ if (cloud) {
3987
+ throw new CliUsageError(
3988
+ `${baseUrl} is an Antasphere-cloud instance \u2014 it signs in at the hub, not with its own email codes. Run \`antasphere login\` once; \`slideless\` then connects automatically (or pass --api-key <slk_\u2026> / set SLIDELESS_API_KEY).`
3989
+ );
3990
+ }
3991
+ }
3920
3992
  function registerAuthCommands(program2, io) {
3921
3993
  const auth = program2.command("auth").description("Sign in over email OTP (mints an API key)");
3922
3994
  auth.command("login-request").description("Email a 6-digit sign-in code (existing accounts only \u2014 sign-up stays closed)").requiredOption("--email <email>", "account email on the instance").action(async (opts, cmd) => {
3923
3995
  const baseUrl = resolveAuthUrl(cmd, io);
3996
+ await refuseOtpLoginOnCloud(baseUrl, io);
3924
3997
  const client = new CliAuthClient({ baseUrl, ...io.fetch ? { fetch: io.fetch } : {} });
3925
3998
  const result = await client.request({ email: opts.email });
3926
3999
  const json = Boolean(cmd.optsWithGlobals().json);
@@ -3939,6 +4012,7 @@ Complete with: slideless auth login-complete --email ${opts.email} --code <code>
3939
4012
  async (opts, cmd) => {
3940
4013
  const globals = cmd.optsWithGlobals();
3941
4014
  const baseUrl = resolveAuthUrl(cmd, io);
4015
+ await refuseOtpLoginOnCloud(baseUrl, io);
3942
4016
  const client = new CliAuthClient({ baseUrl, ...io.fetch ? { fetch: io.fetch } : {} });
3943
4017
  const result = await client.complete({
3944
4018
  email: opts.email,
@@ -3988,7 +4062,7 @@ API key "${result.apiKey.name}" (${redactKey(result.key)}) saved to profile "${p
3988
4062
  );
3989
4063
  });
3990
4064
  program2.command("logout").description(
3991
- "Forget the stored API key of the active (or named) profile; --org logs one connected hub org out instead"
4065
+ "Forget the stored API key of the active (or named) profile; a hub-connected profile disconnects from Antasphere (revokes + evicts the cached user-scoped key)"
3992
4066
  ).action(async (_opts, cmd) => {
3993
4067
  const globals = cmd.optsWithGlobals();
3994
4068
  const config2 = loadConfig2(io.env);
@@ -3997,49 +4071,46 @@ API key "${result.apiKey.name}" (${redactKey(result.key)}) saved to profile "${p
3997
4071
  if (!profileName || !profile) {
3998
4072
  throw new CliUsageError("No profile to log out of.");
3999
4073
  }
4000
- const hubKeys = profile.workspaceKeys ?? {};
4001
- const perOrg = globals.org !== void 0 || !profile.apiKey && Object.keys(hubKeys).length > 0;
4002
- if (perOrg) {
4003
- const org = globals.org ?? resolveTargetWorkspace(io.env, {});
4004
- if (!org) {
4005
- throw new CliUsageError(
4006
- "Pass --org <id> to say which hub org to log out of (or set one with `antasphere org use`)."
4007
- );
4008
- }
4009
- const entry = hubKeys[org];
4010
- if (!entry) {
4011
- const cached = Object.keys(hubKeys).join(", ") || "(none)";
4012
- throw new CliUsageError(
4013
- `No connected key for hub org ${org} on profile "${profileName}". Connected orgs: ${cached}.`
4014
- );
4015
- }
4074
+ const connectKeys = profile.connectKeys ?? {};
4075
+ const slots = Object.entries(connectKeys);
4076
+ if (!profile.apiKey && slots.length > 0) {
4016
4077
  const rawUrl = profile.baseUrl;
4017
- let revoked = false;
4018
- if (rawUrl) {
4019
- const baseUrl = rawUrl.replace(/\/+$/, "");
4020
- try {
4021
- const client = new CliAuthClient({ baseUrl, ...io.fetch ? { fetch: io.fetch } : {} });
4022
- await client.revoke(entry.apiKey);
4023
- revoked = true;
4024
- } catch (e) {
4025
- if (e instanceof CliAuthError && e.status === 401) {
4026
- io.err.write("The cached key was already unusable \u2014 evicting it anyway.\n");
4027
- } else if (e instanceof CliAuthError && e.status === 403) {
4028
- io.err.write(
4029
- `This instance refused the self-revoke (${e.code}) \u2014 the key STAYS VALID server-side; revoke it from the dashboard. Evicting the cached copy.
4078
+ let revoked = true;
4079
+ for (const [slot, entry] of slots) {
4080
+ if (rawUrl) {
4081
+ const baseUrl = rawUrl.replace(/\/+$/, "");
4082
+ try {
4083
+ const client = new CliAuthClient({ baseUrl, ...io.fetch ? { fetch: io.fetch } : {} });
4084
+ await client.revoke(entry.apiKey);
4085
+ } catch (e) {
4086
+ revoked = false;
4087
+ if (e instanceof CliAuthError && e.status === 401) {
4088
+ io.err.write("The cached key was already unusable \u2014 evicting it anyway.\n");
4089
+ } else if (e instanceof CliAuthError && e.status === 403) {
4090
+ io.err.write(
4091
+ `This instance refused the self-revoke (${e.code}) \u2014 the key STAYS VALID server-side; revoke it from the dashboard. Evicting the cached copy.
4030
4092
  `
4031
- );
4032
- } else {
4033
- throw e;
4093
+ );
4094
+ } else {
4095
+ throw e;
4096
+ }
4034
4097
  }
4098
+ } else {
4099
+ revoked = false;
4100
+ io.err.write("No instance URL known for this profile \u2014 evicting the cached key locally only.\n");
4035
4101
  }
4036
- } else {
4037
- io.err.write("No instance URL known for this profile \u2014 evicting the cached key locally only.\n");
4102
+ removeConnectKey2(io.env, profileName, slot);
4103
+ }
4104
+ if (globals.json) {
4105
+ return printJson(io, {
4106
+ profile: profileName,
4107
+ hubProfiles: slots.map(([slot]) => slot),
4108
+ revoked,
4109
+ evicted: true
4110
+ });
4038
4111
  }
4039
- removeWorkspaceKey2(io.env, profileName, org);
4040
- if (globals.json) return printJson(io, { profile: profileName, org, revoked, evicted: true });
4041
4112
  io.out.write(
4042
- `Logged out of hub org ${org} on profile "${profileName}"${revoked ? " (key revoked server-side)" : ""}.
4113
+ `Disconnected from Antasphere on profile "${profileName}"${revoked ? " (key revoked server-side)" : ""}.
4043
4114
  `
4044
4115
  );
4045
4116
  return;
@@ -4061,8 +4132,8 @@ API key "${result.apiKey.name}" (${redactKey(result.key)}) saved to profile "${p
4061
4132
  io.out.write(
4062
4133
  `${me.user.name} <${me.user.email}>
4063
4134
  instance: ${ctx.baseUrl}
4064
- workspace: ${me.workspace.name}
4065
- role: ${me.role} (via ${me.via})
4135
+ workspace: ${me.workspace?.name ?? "(none)"}
4136
+ role: ${me.role ?? "(none)"} (via ${me.via})
4066
4137
  scopes: ${me.scopes ? me.scopes.join(", ") : "full (session)"}
4067
4138
  ` + (me.via === "api_key" ? ` key expires: ${me.apiKeyExpiresAt ?? "never"}
4068
4139
  ` : "")
@@ -4103,7 +4174,9 @@ API key "${result.apiKey.name}" (${redactKey(result.key)}) saved to profile "${p
4103
4174
  {
4104
4175
  baseUrl: p.baseUrl ?? null,
4105
4176
  apiKey: p.apiKey ? redactKey(p.apiKey) : null,
4106
- hubOrgs: Object.keys(p.workspaceKeys ?? {})
4177
+ // Hub profiles this tool profile is connected through
4178
+ // (one user-scoped key each, valid for every org).
4179
+ hubProfiles: Object.keys(p.connectKeys ?? {})
4107
4180
  }
4108
4181
  ];
4109
4182
  })
@@ -4120,12 +4193,12 @@ API key "${result.apiKey.name}" (${redactKey(result.key)}) saved to profile "${p
4120
4193
  table(
4121
4194
  names.map((n) => {
4122
4195
  const p = config2.profiles[n];
4123
- const hubOrgs = Object.keys(p.workspaceKeys ?? {});
4196
+ const hubProfiles = Object.keys(p.connectKeys ?? {});
4124
4197
  return [
4125
4198
  config2.activeProfile === n ? "*" : " ",
4126
4199
  n,
4127
4200
  p.baseUrl ?? "(no url)",
4128
- p.apiKey ? redactKey(p.apiKey) : hubOrgs.length > 0 ? `(hub: ${hubOrgs.join(", ")})` : "(no key)"
4201
+ p.apiKey ? redactKey(p.apiKey) : hubProfiles.length > 0 ? `(hub: ${hubProfiles.join(", ")})` : "(no key)"
4129
4202
  ];
4130
4203
  })
4131
4204
  )
@@ -4145,9 +4218,9 @@ API key "${result.apiKey.name}" (${redactKey(result.key)}) saved to profile "${p
4145
4218
  {
4146
4219
  baseUrl: p.baseUrl ?? null,
4147
4220
  apiKey: p.apiKey ? redactKey(p.apiKey) : null,
4148
- // Hub-connected orgs: cached exchange keys, redacted like the rest.
4149
- workspaceKeys: Object.fromEntries(
4150
- Object.entries(p.workspaceKeys ?? {}).map(([org, entry]) => [org, redactKey(entry.apiKey)])
4221
+ // Hub-connected keys (one per hub profile), redacted like the rest.
4222
+ connectKeys: Object.fromEntries(
4223
+ Object.entries(p.connectKeys ?? {}).map(([slot, entry]) => [slot, redactKey(entry.apiKey)])
4151
4224
  )
4152
4225
  }
4153
4226
  ])
@@ -5084,13 +5157,10 @@ function registerCompletionCommand(program2, io) {
5084
5157
  }
5085
5158
 
5086
5159
  // src/index.ts
5087
- var VERSION = "0.2.2";
5160
+ var VERSION = "0.2.4";
5088
5161
  function buildProgram(io) {
5089
5162
  const program2 = new Command();
5090
- program2.name("slideless").description("Command-line client for a Slideless instance (push, share, pull, preview)").version(VERSION).option("--api-url <url>", "instance base URL (or SLIDELESS_URL / profile baseUrl)").option("--url <url>", "alias of --api-url").option("--api-key <key>", "API key (or SLIDELESS_API_KEY / profile apiKey)").option(
5091
- "--org <id>",
5092
- "hub org to connect as on a cloud instance (default: `antasphere org use`; ignored when a direct API key resolves)"
5093
- ).option("--profile <name>", "use this saved profile instead of the active one").option("--json", "machine-readable JSON output", false);
5163
+ program2.name("slideless").description("Command-line client for a Slideless instance (push, share, pull, preview)").version(VERSION).option("--api-url <url>", "instance base URL (or SLIDELESS_URL / profile baseUrl)").option("--url <url>", "alias of --api-url").option("--api-key <key>", "API key (or SLIDELESS_API_KEY / profile apiKey)").option("--profile <name>", "use this saved profile instead of the active one").option("--json", "machine-readable JSON output", false);
5094
5164
  registerAuthCommands(program2, io);
5095
5165
  registerDeckCommands(program2, io);
5096
5166
  registerContentCommands(program2, io);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antasphere/slideless",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "The Slideless CLI — push, share, pull, and preview decks on a Slideless instance",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "test": "vitest run --passWithNoTests"
31
31
  },
32
32
  "devDependencies": {
33
- "@antasphere/cli-core": "^0.2.0",
33
+ "@antasphere/cli-core": "^0.4.0",
34
34
  "@slideless/contract": "workspace:*",
35
35
  "@slideless/sdk": "workspace:*",
36
36
  "@types/node": "^22.15.0",