@topolo/cli 0.4.3 → 0.4.5

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/index.js +91 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { platform as platform2 } from "os";
9
9
  import { createInterface } from "readline/promises";
10
10
  import { stdin as input } from "process";
11
11
 
12
- // ../../../topolo-platform/packages/topolo-sdk/dist/index.js
12
+ // ../topolo-sdk/dist/index.js
13
13
  function applyAuthHeaders(headers, credential) {
14
14
  if (credential.kind === "api_key") {
15
15
  headers.set("X-Api-Key", credential.apiKey);
@@ -3312,7 +3312,6 @@ var CrmModule = class {
3312
3312
  constructor(client) {
3313
3313
  this.client = client;
3314
3314
  }
3315
- client;
3316
3315
  async listContacts(options = {}) {
3317
3316
  const res = await this.client.request({
3318
3317
  service: "crm",
@@ -3358,7 +3357,6 @@ var IdentityModule = class {
3358
3357
  constructor(client) {
3359
3358
  this.client = client;
3360
3359
  }
3361
- client;
3362
3360
  /** Returns the resolved user, organization, and permission set for the
3363
3361
  * current credential. Equivalent to `TopoloClient.introspect()` — exposed
3364
3362
  * on this module for symmetry with the other domain modules. */
@@ -3453,7 +3451,7 @@ function createTopolo(options) {
3453
3451
  };
3454
3452
  }
3455
3453
 
3456
- // ../../../topolo-platform/packages/topolo-sdk/dist/node/credential-store.js
3454
+ // ../topolo-sdk/dist/node/credential-store.js
3457
3455
  import { mkdir, readFile, writeFile, chmod } from "fs/promises";
3458
3456
  import { existsSync } from "fs";
3459
3457
  import { homedir, platform } from "os";
@@ -3783,9 +3781,9 @@ async function refreshCredentialIfNeeded(environment = selectedEnvironment(), op
3783
3781
  const profile = profileForEnvironment(config, environment);
3784
3782
  const cred = profile.credential;
3785
3783
  if (!cred || cred.kind !== "access_token") return cred ?? null;
3786
- if (!cred.refreshToken || !cred.expiresAt || !profile.oauthClientId) return cred;
3784
+ if (!cred.refreshToken || !profile.oauthClientId) return cred;
3787
3785
  const now = Math.floor(Date.now() / 1e3);
3788
- if (!options.force && cred.expiresAt - REFRESH_SKEW_SECONDS > now) return cred;
3786
+ if (!options.force && cred.expiresAt && cred.expiresAt - REFRESH_SKEW_SECONDS > now) return cred;
3789
3787
  try {
3790
3788
  const oauth = new TopoloOAuth2({
3791
3789
  serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
@@ -3819,6 +3817,46 @@ async function refreshCredentialIfNeeded(environment = selectedEnvironment(), op
3819
3817
  return cred;
3820
3818
  }
3821
3819
  }
3820
+ async function refreshContextCredentialIfNeeded(environment, key, options = {}) {
3821
+ const config = await readConfig();
3822
+ const profile = profileForEnvironment(config, environment);
3823
+ const context = profile.contexts?.[key];
3824
+ const credential = context?.credential;
3825
+ if (!credential || credential.kind !== "access_token") return credential ?? null;
3826
+ const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
3827
+ if (!credential.refreshToken || !oauthClientId) return credential;
3828
+ const now = Math.floor(Date.now() / 1e3);
3829
+ if (!options.force && credential.expiresAt && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
3830
+ try {
3831
+ const oauth = new TopoloOAuth2({
3832
+ serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
3833
+ });
3834
+ const tokens = await oauth.refreshToken({
3835
+ clientId: oauthClientId,
3836
+ refreshToken: credential.refreshToken
3837
+ });
3838
+ const refreshed = {
3839
+ kind: "access_token",
3840
+ accessToken: tokens.access_token,
3841
+ ...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
3842
+ expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
3843
+ };
3844
+ await writeConfig(writeProfileConfig(config, environment, {
3845
+ ...profile.activeContext === key ? { credential: refreshed } : {},
3846
+ contexts: {
3847
+ ...profile.contexts ?? {},
3848
+ [key]: {
3849
+ ...context,
3850
+ credential: refreshed,
3851
+ oauthClientId
3852
+ }
3853
+ }
3854
+ }));
3855
+ return refreshed;
3856
+ } catch {
3857
+ return credential;
3858
+ }
3859
+ }
3822
3860
  function resolveAgentName(configAgentName) {
3823
3861
  return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
3824
3862
  }
@@ -11175,6 +11213,18 @@ function labelFor(ctx) {
11175
11213
  if (ctx.contextType === "personal") return "Personal";
11176
11214
  return ctx.organizationName || ctx.organizationSlug || ctx.organizationId || "organization";
11177
11215
  }
11216
+ function isAuthenticationFailure(error) {
11217
+ return error instanceof Error && error.code === "auth_error";
11218
+ }
11219
+ function hasSameAccessToken(left, right) {
11220
+ const leftCredential = left;
11221
+ const rightCredential = right;
11222
+ return leftCredential?.kind === "access_token" && rightCredential?.kind === "access_token" && leftCredential.accessToken === rightCredential.accessToken;
11223
+ }
11224
+ function identityMatchesContext(me, context) {
11225
+ if (context.contextType === "personal") return me.activeType === "personal";
11226
+ return me.activeType === "organization" && me.activeOrgId === context.organizationId;
11227
+ }
11178
11228
  function registerContextCommand(program2) {
11179
11229
  const context = program2.command("context").description("Show and switch the active context (personal or an organization)");
11180
11230
  context.command("ls", { isDefault: true }).alias("list").description("List the contexts you can act in and which is active").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (opts) => {
@@ -11228,16 +11278,10 @@ function registerContextCommand(program2) {
11228
11278
  const config = await readConfig();
11229
11279
  const profile = profileForEnvironment(config, environment);
11230
11280
  const serviceUrls = resolvedProfileConfig(config, environment).serviceUrls;
11231
- const priorCredential = profile.credential ?? (profile.activeContext ? profile.contexts?.[profile.activeContext]?.credential : void 0);
11232
- const cred = await refreshCredentialIfNeeded(environment, { force: true });
11281
+ let cred = await refreshCredentialIfNeeded(environment);
11233
11282
  if (!cred) {
11234
11283
  throw new Error("Not signed in. Run `topolo auth login` first.");
11235
11284
  }
11236
- if (priorCredential?.kind === "access_token" && priorCredential.refreshToken && cred.kind === "access_token" && cred.accessToken === priorCredential.accessToken) {
11237
- throw new Error(
11238
- `The active credential could not be refreshed. Run \`topolo --env ${environment} auth login\` to sign in again.`
11239
- );
11240
- }
11241
11285
  const wantPersonal = /^(personal|me)$/i.test(target.trim());
11242
11286
  let resolved;
11243
11287
  const needle = target.trim().toLowerCase();
@@ -11275,10 +11319,40 @@ function registerContextCommand(program2) {
11275
11319
  }
11276
11320
  const key = contextKey(resolved.contextType, resolved.organizationId);
11277
11321
  const existing = profile.contexts?.[key];
11278
- const newCredential = await exchangeForContext(cred, serviceUrls, {
11279
- type: resolved.contextType,
11280
- id: resolved.organizationId
11281
- });
11322
+ let newCredential;
11323
+ if (existing?.credential && profile.activeContext !== key) {
11324
+ const storedCredential = await refreshContextCredentialIfNeeded(environment, key);
11325
+ if (storedCredential) {
11326
+ try {
11327
+ const storedIdentity = await fetchMe(storedCredential, serviceUrls);
11328
+ if (identityMatchesContext(storedIdentity, resolved)) {
11329
+ newCredential = storedCredential;
11330
+ }
11331
+ } catch {
11332
+ }
11333
+ }
11334
+ }
11335
+ if (!newCredential) {
11336
+ try {
11337
+ newCredential = await exchangeForContext(cred, serviceUrls, {
11338
+ type: resolved.contextType,
11339
+ id: resolved.organizationId
11340
+ });
11341
+ } catch (error) {
11342
+ if (!isAuthenticationFailure(error)) throw error;
11343
+ const refreshed = await refreshCredentialIfNeeded(environment, { force: true });
11344
+ if (!refreshed || hasSameAccessToken(cred, refreshed)) {
11345
+ throw new Error(
11346
+ `The active credential could not be refreshed. Run \`topolo --env ${environment} auth login\` to sign in again.`
11347
+ );
11348
+ }
11349
+ cred = refreshed;
11350
+ newCredential = await exchangeForContext(cred, serviceUrls, {
11351
+ type: resolved.contextType,
11352
+ id: resolved.organizationId
11353
+ });
11354
+ }
11355
+ }
11282
11356
  resolved.credential = newCredential;
11283
11357
  resolved.oauthClientId = existing?.oauthClientId ?? profile.oauthClientId;
11284
11358
  const updated = writeContextCredential(await readConfig(), environment, resolved, true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Command-line interface for Topolo developers, operators, and agents, with internal first-party scaffolding available under the topolo platform namespace.",
5
5
  "type": "module",
6
6
  "bin": {