@topolo/cli 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.
- package/dist/index.js +217 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -78,6 +78,7 @@ var APP_API_URLS = {
|
|
|
78
78
|
bugfix: "https://bugfix.topolo.app",
|
|
79
79
|
bytes: "https://bytes.topolo.app",
|
|
80
80
|
calendar: "https://calendar.topolo.app",
|
|
81
|
+
campaigns: "https://campaigns.topolo.app",
|
|
81
82
|
capacity: "https://capacity.topolo.app",
|
|
82
83
|
chat: "https://chat.topolo.app",
|
|
83
84
|
commerce: "https://commerce.topolo.app",
|
|
@@ -336,6 +337,38 @@ var APPLICATIONS = {
|
|
|
336
337
|
}
|
|
337
338
|
]
|
|
338
339
|
},
|
|
340
|
+
"campaigns": {
|
|
341
|
+
"id": "campaigns",
|
|
342
|
+
"appDir": "TopoloCampaigns",
|
|
343
|
+
"name": "topolo-campaigns",
|
|
344
|
+
"packageName": "topolo-campaigns",
|
|
345
|
+
"productionUrl": "https://campaigns.topolo.app",
|
|
346
|
+
"services": [
|
|
347
|
+
"campaigns"
|
|
348
|
+
],
|
|
349
|
+
"deployTargets": [
|
|
350
|
+
{
|
|
351
|
+
"name": "topolo-campaigns",
|
|
352
|
+
"kind": "worker",
|
|
353
|
+
"apiUrl": "https://campaigns.topolo.app",
|
|
354
|
+
"domains": [
|
|
355
|
+
"campaigns.topolo.app"
|
|
356
|
+
],
|
|
357
|
+
"routes": [
|
|
358
|
+
"campaigns.topolo.app"
|
|
359
|
+
],
|
|
360
|
+
"appId": "campaigns"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"name": "topolo-campaigns-api",
|
|
364
|
+
"kind": "worker",
|
|
365
|
+
"apiUrl": null,
|
|
366
|
+
"domains": [],
|
|
367
|
+
"routes": [],
|
|
368
|
+
"appId": null
|
|
369
|
+
}
|
|
370
|
+
]
|
|
371
|
+
},
|
|
339
372
|
"capacity": {
|
|
340
373
|
"id": "capacity",
|
|
341
374
|
"appDir": "TopoloCapacity",
|
|
@@ -2510,7 +2543,21 @@ var TopoloClient = class _TopoloClient {
|
|
|
2510
2543
|
return match;
|
|
2511
2544
|
}
|
|
2512
2545
|
async callAction(action, input2 = {}, options = {}) {
|
|
2546
|
+
const entry = await this.getAction(action);
|
|
2547
|
+
return this.executeResolvedAction(entry, input2, options);
|
|
2548
|
+
}
|
|
2549
|
+
async readAction(action, input2 = {}, options = {}) {
|
|
2513
2550
|
const entry = await this.getAction(action);
|
|
2551
|
+
const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
|
|
2552
|
+
if (entry.method !== "GET" || !entry.readOnly || entry.destructive || entry.requiresConfirmation || agentAccess !== "auto") {
|
|
2553
|
+
throw new TopoloPermissionError(
|
|
2554
|
+
`Topolo action "${entry.actionId}" is not an automatic read-only action.`,
|
|
2555
|
+
entry.requiredPermission ? [entry.requiredPermission] : []
|
|
2556
|
+
);
|
|
2557
|
+
}
|
|
2558
|
+
return this.executeResolvedAction(entry, input2, options);
|
|
2559
|
+
}
|
|
2560
|
+
async executeResolvedAction(entry, input2, options) {
|
|
2514
2561
|
const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
|
|
2515
2562
|
if (agentAccess === "off") {
|
|
2516
2563
|
throw new TopoloPermissionError(
|
|
@@ -2582,6 +2629,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2582
2629
|
const kind = payload.credentialType === "api_key" ? "api_key" : "access_token";
|
|
2583
2630
|
return {
|
|
2584
2631
|
kind,
|
|
2632
|
+
authorizationMode: normalizeCredentialAuthorizationMode(payload.authorizationMode, kind),
|
|
2585
2633
|
user: {
|
|
2586
2634
|
id: user.id,
|
|
2587
2635
|
email: user.email,
|
|
@@ -2844,6 +2892,11 @@ function objectToQuery(value) {
|
|
|
2844
2892
|
}
|
|
2845
2893
|
return Object.keys(query).length > 0 ? query : void 0;
|
|
2846
2894
|
}
|
|
2895
|
+
function normalizeCredentialAuthorizationMode(value, kind) {
|
|
2896
|
+
if (kind === "api_key") return "api_key";
|
|
2897
|
+
if (value === "context_rbac" || value === "oauth_scopes" || value === "session_rbac") return value;
|
|
2898
|
+
return "unknown";
|
|
2899
|
+
}
|
|
2847
2900
|
function describeError(body, fallback) {
|
|
2848
2901
|
if (body && typeof body === "object") {
|
|
2849
2902
|
const maybe = body;
|
|
@@ -3064,6 +3117,7 @@ var APP_API_URLS2 = {
|
|
|
3064
3117
|
bugfix: "https://bugfix.topolo.app",
|
|
3065
3118
|
bytes: "https://bytes.topolo.app",
|
|
3066
3119
|
calendar: "https://calendar.topolo.app",
|
|
3120
|
+
campaigns: "https://campaigns.topolo.app",
|
|
3067
3121
|
capacity: "https://capacity.topolo.app",
|
|
3068
3122
|
chat: "https://chat.topolo.app",
|
|
3069
3123
|
commerce: "https://commerce.topolo.app",
|
|
@@ -3388,6 +3442,46 @@ async function refreshCredentialIfNeeded(environment = selectedEnvironment(), op
|
|
|
3388
3442
|
return cred;
|
|
3389
3443
|
}
|
|
3390
3444
|
}
|
|
3445
|
+
async function refreshContextCredentialIfNeeded(environment, key, options = {}) {
|
|
3446
|
+
const config = await readConfig();
|
|
3447
|
+
const profile = profileForEnvironment(config, environment);
|
|
3448
|
+
const context = profile.contexts?.[key];
|
|
3449
|
+
const credential = context?.credential;
|
|
3450
|
+
if (!credential || credential.kind !== "access_token") return credential ?? null;
|
|
3451
|
+
const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
|
|
3452
|
+
if (!credential.refreshToken || !credential.expiresAt || !oauthClientId) return credential;
|
|
3453
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3454
|
+
if (!options.force && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
|
|
3455
|
+
try {
|
|
3456
|
+
const oauth = new TopoloOAuth2({
|
|
3457
|
+
serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
|
|
3458
|
+
});
|
|
3459
|
+
const tokens = await oauth.refreshToken({
|
|
3460
|
+
clientId: oauthClientId,
|
|
3461
|
+
refreshToken: credential.refreshToken
|
|
3462
|
+
});
|
|
3463
|
+
const refreshed = {
|
|
3464
|
+
kind: "access_token",
|
|
3465
|
+
accessToken: tokens.access_token,
|
|
3466
|
+
...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
|
|
3467
|
+
expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
3468
|
+
};
|
|
3469
|
+
await writeConfig(writeProfileConfig(config, environment, {
|
|
3470
|
+
...profile.activeContext === key ? { credential: refreshed } : {},
|
|
3471
|
+
contexts: {
|
|
3472
|
+
...profile.contexts ?? {},
|
|
3473
|
+
[key]: {
|
|
3474
|
+
...context,
|
|
3475
|
+
credential: refreshed,
|
|
3476
|
+
oauthClientId
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
}));
|
|
3480
|
+
return refreshed;
|
|
3481
|
+
} catch {
|
|
3482
|
+
return credential;
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3391
3485
|
function resolveAgentName(configAgentName) {
|
|
3392
3486
|
return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
|
|
3393
3487
|
}
|
|
@@ -3446,7 +3540,7 @@ function resolveOAuthClientId(config, environment = selectedEnvironment()) {
|
|
|
3446
3540
|
const envSpecific = process.env[`${envVarPrefix(environment)}_CLI_CLIENT_ID`];
|
|
3447
3541
|
if (envSpecific) return envSpecific;
|
|
3448
3542
|
if (process.env.TOPOLO_CLI_CLIENT_ID) return process.env.TOPOLO_CLI_CLIENT_ID;
|
|
3449
|
-
return profileForEnvironment(config, environment).oauthClientId
|
|
3543
|
+
return DEFAULT_CLI_CLIENT_IDS[environment] ?? profileForEnvironment(config, environment).oauthClientId;
|
|
3450
3544
|
}
|
|
3451
3545
|
var NON_PROD_URL_MARKERS = {
|
|
3452
3546
|
staging: [".stg.topolo.us", "topolo-staging"],
|
|
@@ -3832,7 +3926,7 @@ function stderrDebugLogger(event) {
|
|
|
3832
3926
|
|
|
3833
3927
|
// src/commands/whoami.ts
|
|
3834
3928
|
function registerWhoamiCommand(program2) {
|
|
3835
|
-
program2.command("whoami").description("Show the user, organization, and
|
|
3929
|
+
program2.command("whoami").description("Show the current user, organization, and authorization mode").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (opts) => {
|
|
3836
3930
|
const mode = resolveOutputMode(opts.json);
|
|
3837
3931
|
try {
|
|
3838
3932
|
const environment = selectedEnvironment();
|
|
@@ -3857,7 +3951,10 @@ function formatPretty(info) {
|
|
|
3857
3951
|
`Organization: ${info.organization ? `${info.organization.name} (${info.organization.slug})` : "(none)"}`
|
|
3858
3952
|
);
|
|
3859
3953
|
lines.push(`Credential: ${info.kind}`);
|
|
3860
|
-
|
|
3954
|
+
const dynamicRbac = info.authorizationMode === "context_rbac";
|
|
3955
|
+
const authorization = dynamicRbac ? "Dynamic RBAC (checked per application action)" : info.authorizationMode === "oauth_scopes" ? "Delegated OAuth scopes" : info.authorizationMode === "api_key" ? "API key scopes" : info.authorizationMode === "session_rbac" ? "Session RBAC" : "Credential scopes";
|
|
3956
|
+
lines.push(`Authorization: ${authorization}`);
|
|
3957
|
+
lines.push(`${dynamicRbac ? "Identity scopes" : "Scopes"}: ${info.user.permissions.length === 0 ? "(none)" : ""}`);
|
|
3861
3958
|
for (const p of info.user.permissions) lines.push(` - ${p}`);
|
|
3862
3959
|
return lines.join("\n");
|
|
3863
3960
|
}
|
|
@@ -5169,7 +5266,6 @@ function isFile(filePath) {
|
|
|
5169
5266
|
}
|
|
5170
5267
|
|
|
5171
5268
|
// src/commands/app-scaffold.ts
|
|
5172
|
-
import { randomBytes } from "crypto";
|
|
5173
5269
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
5174
5270
|
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
5175
5271
|
|
|
@@ -6152,12 +6248,15 @@ function platformAppTsx(application, options) {
|
|
|
6152
6248
|
" }",
|
|
6153
6249
|
"",
|
|
6154
6250
|
" if (!auth) {",
|
|
6251
|
+
" // Pre-AppRoot: appId is still resolving, so auth can't be built yet.",
|
|
6252
|
+
" // Render the SAME scaffold layout AppRoot uses below so this phase and",
|
|
6253
|
+
" // the auth-gate fallback read as one continuous loading state (the",
|
|
6254
|
+
" // scaffold resolves theme + rail width on its own to match the shell).",
|
|
6155
6255
|
" return (",
|
|
6156
6256
|
" <TopoloAppLoadingScaffold",
|
|
6157
6257
|
" appId={appId}",
|
|
6158
6258
|
" appName={APP_NAME}",
|
|
6159
6259
|
" layout={{ shell: 'topbar', content: 'workspace' }}",
|
|
6160
|
-
" subtitle={`Loading ${APP_NAME}...`}",
|
|
6161
6260
|
" />",
|
|
6162
6261
|
" );",
|
|
6163
6262
|
" }",
|
|
@@ -6198,8 +6297,6 @@ function platformAppTsx(application, options) {
|
|
|
6198
6297
|
" appId={appId}",
|
|
6199
6298
|
" appName={APP_NAME}",
|
|
6200
6299
|
" layout={{ shell: loading?.shell || 'topbar', content: loading?.content || 'workspace' }}",
|
|
6201
|
-
" title={loading?.title}",
|
|
6202
|
-
" subtitle={`Loading ${APP_NAME}...`}",
|
|
6203
6300
|
" />",
|
|
6204
6301
|
" )}",
|
|
6205
6302
|
" shell={{",
|
|
@@ -6896,7 +6993,7 @@ function platformWorkerTs(application, options) {
|
|
|
6896
6993
|
"import { createTopoloWorkerRuntime, type TopoloRuntimeContext } from '@topolo-io/worker-runtime';",
|
|
6897
6994
|
"import { withSecurityHeaders } from '@topolo-io/security-headers';",
|
|
6898
6995
|
"import { withErrorReporting } from '@topolo-io/error-middleware';",
|
|
6899
|
-
"import {
|
|
6996
|
+
"import { SCAFFOLD_APP_NAME, SCAFFOLD_SERVICE_SLUG, resolveRuntimeAppId } from './app-identity';",
|
|
6900
6997
|
...options.resources.workflow ? ["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';"] : [],
|
|
6901
6998
|
"",
|
|
6902
6999
|
"export interface Env {",
|
|
@@ -6921,12 +7018,10 @@ function platformWorkerTs(application, options) {
|
|
|
6921
7018
|
"const runtime = createTopoloWorkerRuntime<Env>({",
|
|
6922
7019
|
" appName: SCAFFOLD_APP_NAME,",
|
|
6923
7020
|
" serviceSlug: SCAFFOLD_SERVICE_SLUG,",
|
|
6924
|
-
"
|
|
7021
|
+
" // Auth verification base URL is environment-driven (staging/prod/dev),",
|
|
7022
|
+
" // so rt.verifyAuth() targets the correct Auth service per deploy.",
|
|
6925
7023
|
" authBaseUrl: (env) => env.AUTH_BASE_URL,",
|
|
6926
|
-
"
|
|
6927
|
-
" appId: SCAFFOLD_APP_ID,",
|
|
6928
|
-
" nexusGatewayUrl: (env) => env.NEXUS_GATEWAY_URL,",
|
|
6929
|
-
" },",
|
|
7024
|
+
" authGateway: { enabled: false },",
|
|
6930
7025
|
"});",
|
|
6931
7026
|
"",
|
|
6932
7027
|
"async function handleRequest(",
|
|
@@ -7348,6 +7443,7 @@ function topoloAppManifestJson(application, options) {
|
|
|
7348
7443
|
}
|
|
7349
7444
|
} : {}
|
|
7350
7445
|
};
|
|
7446
|
+
const actions = options.api ? baselineApiActions() : [];
|
|
7351
7447
|
return `${json({
|
|
7352
7448
|
$schema: "./../../topolo-platform/tooling/app-manifest-schema.json",
|
|
7353
7449
|
name: `topolo-${slug}`,
|
|
@@ -7367,10 +7463,87 @@ function topoloAppManifestJson(application, options) {
|
|
|
7367
7463
|
domain: "topolo.app"
|
|
7368
7464
|
}
|
|
7369
7465
|
},
|
|
7370
|
-
targets
|
|
7466
|
+
targets,
|
|
7467
|
+
...actions.length ? { actions } : {}
|
|
7371
7468
|
})}
|
|
7372
7469
|
`;
|
|
7373
7470
|
}
|
|
7471
|
+
function topoloActionContractsMjs() {
|
|
7472
|
+
return [
|
|
7473
|
+
"// Canonical action contracts \u2014 the source of truth for the agent/SDK/MCP",
|
|
7474
|
+
"// surface. Each entry maps 1:1 to a worker route (method + path) and validates",
|
|
7475
|
+
"// its input with zod. Add product actions here and mirror them into",
|
|
7476
|
+
"// topolo.app.json actions[]. Mirrors the normalized fleet convention.",
|
|
7477
|
+
"",
|
|
7478
|
+
"import { z } from 'zod';",
|
|
7479
|
+
"",
|
|
7480
|
+
"const emptyInput = z.object({}).strict();",
|
|
7481
|
+
"",
|
|
7482
|
+
"export const topoloActionContracts = {",
|
|
7483
|
+
" 'widget.get': {",
|
|
7484
|
+
" name: 'widget.get',",
|
|
7485
|
+
" method: 'GET',",
|
|
7486
|
+
" path: '/api/widget',",
|
|
7487
|
+
" input: emptyInput,",
|
|
7488
|
+
" },",
|
|
7489
|
+
" 'workspace.bootstrap': {",
|
|
7490
|
+
" name: 'workspace.bootstrap',",
|
|
7491
|
+
" method: 'GET',",
|
|
7492
|
+
" path: '/api/bootstrap',",
|
|
7493
|
+
" input: emptyInput,",
|
|
7494
|
+
" },",
|
|
7495
|
+
"};",
|
|
7496
|
+
"",
|
|
7497
|
+
"export const topoloActionInputSchemas = Object.fromEntries(",
|
|
7498
|
+
" Object.entries(topoloActionContracts).map(([name, contract]) => [",
|
|
7499
|
+
" name,",
|
|
7500
|
+
" z.toJSONSchema(contract.input),",
|
|
7501
|
+
" ]),",
|
|
7502
|
+
");",
|
|
7503
|
+
""
|
|
7504
|
+
].join("\n");
|
|
7505
|
+
}
|
|
7506
|
+
function baselineApiActions() {
|
|
7507
|
+
const emptyInputSchema = {
|
|
7508
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
7509
|
+
type: "object",
|
|
7510
|
+
properties: {},
|
|
7511
|
+
additionalProperties: false
|
|
7512
|
+
};
|
|
7513
|
+
const openOutputSchema = { type: "object", additionalProperties: true };
|
|
7514
|
+
return [
|
|
7515
|
+
{
|
|
7516
|
+
name: "widget.get",
|
|
7517
|
+
title: "Get widget summary",
|
|
7518
|
+
description: "Get the TopoloOne widget summary for this service.",
|
|
7519
|
+
method: "GET",
|
|
7520
|
+
path: "/api/widget",
|
|
7521
|
+
permission: "workspace:read",
|
|
7522
|
+
readOnly: true,
|
|
7523
|
+
inputSchema: emptyInputSchema,
|
|
7524
|
+
outputSchema: openOutputSchema,
|
|
7525
|
+
target: "api",
|
|
7526
|
+
destructive: false,
|
|
7527
|
+
requiresConfirmation: false,
|
|
7528
|
+
agentAccess: "auto"
|
|
7529
|
+
},
|
|
7530
|
+
{
|
|
7531
|
+
name: "workspace.bootstrap",
|
|
7532
|
+
title: "Get workspace bootstrap",
|
|
7533
|
+
description: "Get the signed workspace bootstrap payload for this service.",
|
|
7534
|
+
method: "GET",
|
|
7535
|
+
path: "/api/bootstrap",
|
|
7536
|
+
permission: "workspace:read",
|
|
7537
|
+
readOnly: true,
|
|
7538
|
+
inputSchema: emptyInputSchema,
|
|
7539
|
+
outputSchema: openOutputSchema,
|
|
7540
|
+
target: "api",
|
|
7541
|
+
destructive: false,
|
|
7542
|
+
requiresConfirmation: false,
|
|
7543
|
+
agentAccess: "auto"
|
|
7544
|
+
}
|
|
7545
|
+
];
|
|
7546
|
+
}
|
|
7374
7547
|
function cloudcontrolJson(application, options) {
|
|
7375
7548
|
const slug = toSlug(application.id);
|
|
7376
7549
|
const appPath = firstPartyAppPath(application.appDir, options.audience);
|
|
@@ -8024,9 +8197,6 @@ function envExample(application) {
|
|
|
8024
8197
|
}
|
|
8025
8198
|
|
|
8026
8199
|
// src/commands/app-scaffold.ts
|
|
8027
|
-
function generateOpaqueAppId() {
|
|
8028
|
-
return `app_${randomBytes(9).toString("base64url")}`;
|
|
8029
|
-
}
|
|
8030
8200
|
function standardScriptStub() {
|
|
8031
8201
|
return `#!/usr/bin/env python3
|
|
8032
8202
|
"""Canonical-fetching stub for .github/scripts/standard_*.py.
|
|
@@ -8122,7 +8292,7 @@ function applicationScaffoldPayload(application, options = {}) {
|
|
|
8122
8292
|
const name = options.name ?? catalogApplication?.name ?? `topolo-${slug}`;
|
|
8123
8293
|
const packageName = options.packageName ?? catalogApplication?.packageName ?? `topolo-${slug}`;
|
|
8124
8294
|
const serviceSlug = api ? target === "platform" ? `topolo-${slug}` : id : null;
|
|
8125
|
-
const defaultAppId = target === "platform" ?
|
|
8295
|
+
const defaultAppId = target === "platform" ? `app_topolo_${id}` : catalogApplication?.services[0] ?? id;
|
|
8126
8296
|
const appId = api ? options.appId ?? defaultAppId : null;
|
|
8127
8297
|
const productionUrl = browser ? options.productionUrl ?? catalogApplication?.productionUrl ?? `https://${slug}.topolo.app` : null;
|
|
8128
8298
|
const scopes = scaffoldScopes({ browser, api });
|
|
@@ -8438,6 +8608,17 @@ function scaffoldFiles(options) {
|
|
|
8438
8608
|
profile: options.payload.profile
|
|
8439
8609
|
})
|
|
8440
8610
|
},
|
|
8611
|
+
...options.api ? [
|
|
8612
|
+
{
|
|
8613
|
+
path: `${base}/topolo.action-contracts.mjs`,
|
|
8614
|
+
purpose: "Zod source of truth for the agent/SDK/MCP action catalog, mirrored into topolo.app.json actions[].",
|
|
8615
|
+
requiredSignals: [
|
|
8616
|
+
"declares topoloActionContracts with name/method/path per action",
|
|
8617
|
+
"each contract has a zod input validator, keeping the manifest actions[] honest"
|
|
8618
|
+
],
|
|
8619
|
+
content: topoloActionContractsMjs()
|
|
8620
|
+
}
|
|
8621
|
+
] : [],
|
|
8441
8622
|
{
|
|
8442
8623
|
path: `${base}/topolo.cloudcontrol.json`,
|
|
8443
8624
|
purpose: "CloudControl source of truth for deploy targets, routes, bindings, and CI metadata.",
|
|
@@ -10621,11 +10802,25 @@ function registerContextCommand(program2) {
|
|
|
10621
10802
|
}
|
|
10622
10803
|
const wantPersonal = /^(personal|me)$/i.test(target.trim());
|
|
10623
10804
|
let resolved;
|
|
10624
|
-
|
|
10805
|
+
let exchangeCredential = cred;
|
|
10806
|
+
const needle = target.trim().toLowerCase();
|
|
10807
|
+
const storedEntry = Object.entries(profile.contexts ?? {}).find(([, candidate]) => {
|
|
10808
|
+
if (wantPersonal) return candidate.contextType === "personal";
|
|
10809
|
+
return candidate.contextType === "organization" && (candidate.organizationId === target || candidate.organizationSlug?.toLowerCase() === needle || candidate.organizationName?.toLowerCase() === needle);
|
|
10810
|
+
});
|
|
10811
|
+
const storedContext = storedEntry?.[1];
|
|
10812
|
+
if (storedEntry && storedContext?.credential) {
|
|
10813
|
+
exchangeCredential = await refreshContextCredentialIfNeeded(environment, storedEntry[0]) ?? storedContext.credential;
|
|
10814
|
+
resolved = {
|
|
10815
|
+
contextType: storedContext.contextType,
|
|
10816
|
+
organizationId: storedContext.organizationId,
|
|
10817
|
+
organizationSlug: storedContext.organizationSlug,
|
|
10818
|
+
organizationName: storedContext.organizationName
|
|
10819
|
+
};
|
|
10820
|
+
} else if (wantPersonal) {
|
|
10625
10821
|
resolved = { contextType: "personal" };
|
|
10626
10822
|
} else {
|
|
10627
10823
|
const me = await fetchMe(cred, serviceUrls);
|
|
10628
|
-
const needle = target.trim().toLowerCase();
|
|
10629
10824
|
const match = me.memberships.find(
|
|
10630
10825
|
(m) => m.id === target || m.slug?.toLowerCase() === needle || m.name?.toLowerCase() === needle
|
|
10631
10826
|
);
|
|
@@ -10644,13 +10839,13 @@ function registerContextCommand(program2) {
|
|
|
10644
10839
|
}
|
|
10645
10840
|
const key = contextKey(resolved.contextType, resolved.organizationId);
|
|
10646
10841
|
const existing = profile.contexts?.[key];
|
|
10647
|
-
const newCredential = await exchangeForContext(
|
|
10842
|
+
const newCredential = await exchangeForContext(exchangeCredential, serviceUrls, {
|
|
10648
10843
|
type: resolved.contextType,
|
|
10649
10844
|
id: resolved.organizationId
|
|
10650
10845
|
});
|
|
10651
10846
|
resolved.credential = newCredential;
|
|
10652
10847
|
resolved.oauthClientId = existing?.oauthClientId ?? profile.oauthClientId;
|
|
10653
|
-
const updated = writeContextCredential(
|
|
10848
|
+
const updated = writeContextCredential(await readConfig(), environment, resolved, true);
|
|
10654
10849
|
await writeConfig(updated);
|
|
10655
10850
|
emit(
|
|
10656
10851
|
{ environment, active: key, context: { type: resolved.contextType, label: labelFor(resolved) } },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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": {
|