@topolo/cli 0.4.4 → 0.4.6
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 +146 -34
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -75,6 +75,7 @@ var APP_API_URLS = {
|
|
|
75
75
|
auth: "https://auth.topolo.app",
|
|
76
76
|
backup: "https://backup.topolo.app",
|
|
77
77
|
books: "https://books.topolo.app",
|
|
78
|
+
brand: "https://brand.topolo.app",
|
|
78
79
|
bugfix: "https://bugfix.topolo.app",
|
|
79
80
|
bytes: "https://bytes.topolo.app",
|
|
80
81
|
calendar: "https://calendar.topolo.app",
|
|
@@ -255,6 +256,38 @@ var APPLICATIONS = {
|
|
|
255
256
|
}
|
|
256
257
|
]
|
|
257
258
|
},
|
|
259
|
+
"brand": {
|
|
260
|
+
"id": "brand",
|
|
261
|
+
"appDir": "TopoloBrand",
|
|
262
|
+
"name": "topolo-brand",
|
|
263
|
+
"packageName": "@topolo/topolo-brand",
|
|
264
|
+
"productionUrl": "https://brand.topolo.app",
|
|
265
|
+
"services": [
|
|
266
|
+
"brand"
|
|
267
|
+
],
|
|
268
|
+
"deployTargets": [
|
|
269
|
+
{
|
|
270
|
+
"name": "topolo-brand",
|
|
271
|
+
"kind": "worker",
|
|
272
|
+
"apiUrl": "https://brand.topolo.app",
|
|
273
|
+
"domains": [
|
|
274
|
+
"brand.topolo.app"
|
|
275
|
+
],
|
|
276
|
+
"routes": [
|
|
277
|
+
"brand.topolo.app"
|
|
278
|
+
],
|
|
279
|
+
"appId": "brand"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"name": "topolo-brand-api",
|
|
283
|
+
"kind": "worker",
|
|
284
|
+
"apiUrl": null,
|
|
285
|
+
"domains": [],
|
|
286
|
+
"routes": [],
|
|
287
|
+
"appId": null
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
},
|
|
258
291
|
"bugfix": {
|
|
259
292
|
"id": "bugfix",
|
|
260
293
|
"appDir": "TopoloBugFix",
|
|
@@ -2369,6 +2402,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2369
2402
|
requireConfirmForWrites;
|
|
2370
2403
|
timeoutMs;
|
|
2371
2404
|
fetchImpl;
|
|
2405
|
+
credentialProvider;
|
|
2406
|
+
credentialLoadInFlight = null;
|
|
2372
2407
|
refreshCredential;
|
|
2373
2408
|
refreshInFlight = null;
|
|
2374
2409
|
debug;
|
|
@@ -2396,6 +2431,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2396
2431
|
this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
|
|
2397
2432
|
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
2398
2433
|
this.fetchImpl = options.fetch ?? fetch;
|
|
2434
|
+
this.credentialProvider = options.credentialProvider;
|
|
2399
2435
|
this.refreshCredential = options.refreshCredential;
|
|
2400
2436
|
this.debug = options.debug;
|
|
2401
2437
|
}
|
|
@@ -2418,6 +2454,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2418
2454
|
`Mutating ${method} requests require { confirm: true }. This guardrail prevents agents from issuing writes without an explicit human-in-the-loop acknowledgement.`
|
|
2419
2455
|
);
|
|
2420
2456
|
}
|
|
2457
|
+
await this.loadCurrentCredential();
|
|
2421
2458
|
const resolvedService = opts.baseUrl ? { serviceKey: normalizeLookupKey(opts.service), baseUrl: requireHttpOrigin(opts.baseUrl) } : await this.resolveRequestService(opts.service);
|
|
2422
2459
|
const baseUrl = resolvedService.baseUrl;
|
|
2423
2460
|
const url = new URL(opts.path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
@@ -2529,6 +2566,19 @@ var TopoloClient = class _TopoloClient {
|
|
|
2529
2566
|
});
|
|
2530
2567
|
return parsed;
|
|
2531
2568
|
}
|
|
2569
|
+
async loadCurrentCredential() {
|
|
2570
|
+
if (!this.credentialProvider) return;
|
|
2571
|
+
this.credentialLoadInFlight ??= this.credentialProvider().then((credential) => {
|
|
2572
|
+
if (!credential) {
|
|
2573
|
+
throw new TopoloAuthError("The configured Topolo credential is no longer available.");
|
|
2574
|
+
}
|
|
2575
|
+
this.credential = credential;
|
|
2576
|
+
return credential;
|
|
2577
|
+
}).finally(() => {
|
|
2578
|
+
this.credentialLoadInFlight = null;
|
|
2579
|
+
});
|
|
2580
|
+
await this.credentialLoadInFlight;
|
|
2581
|
+
}
|
|
2532
2582
|
async refreshRejectedAccessToken(attemptedAccessToken) {
|
|
2533
2583
|
if (this.credential.kind === "access_token" && this.credential.accessToken !== attemptedAccessToken) {
|
|
2534
2584
|
return this.credential;
|
|
@@ -3197,6 +3247,7 @@ function normalizeActionCatalogEntry(item) {
|
|
|
3197
3247
|
permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
|
|
3198
3248
|
requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
|
|
3199
3249
|
authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
|
|
3250
|
+
allowedServices: stringArray(a.allowed_services ?? a.allowedServices),
|
|
3200
3251
|
inputSchema: plainObject(a.input_schema ?? a.inputSchema),
|
|
3201
3252
|
outputSchema: plainObject(a.output_schema ?? a.outputSchema),
|
|
3202
3253
|
agentContract: plainObject(a.agent_contract ?? a.agentContract),
|
|
@@ -3218,7 +3269,7 @@ function pickAgentAccess(value) {
|
|
|
3218
3269
|
return value === "auto" || value === "confirm" || value === "off" ? value : null;
|
|
3219
3270
|
}
|
|
3220
3271
|
function pickAuthorizationMode(value) {
|
|
3221
|
-
return value === "runtime"
|
|
3272
|
+
return value === "runtime" || value === "public" || value === "service" ? value : "static";
|
|
3222
3273
|
}
|
|
3223
3274
|
function plainObject(value) {
|
|
3224
3275
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -3312,6 +3363,7 @@ var CrmModule = class {
|
|
|
3312
3363
|
constructor(client) {
|
|
3313
3364
|
this.client = client;
|
|
3314
3365
|
}
|
|
3366
|
+
client;
|
|
3315
3367
|
async listContacts(options = {}) {
|
|
3316
3368
|
const res = await this.client.request({
|
|
3317
3369
|
service: "crm",
|
|
@@ -3357,6 +3409,7 @@ var IdentityModule = class {
|
|
|
3357
3409
|
constructor(client) {
|
|
3358
3410
|
this.client = client;
|
|
3359
3411
|
}
|
|
3412
|
+
client;
|
|
3360
3413
|
/** Returns the resolved user, organization, and permission set for the
|
|
3361
3414
|
* current credential. Equivalent to `TopoloClient.introspect()` — exposed
|
|
3362
3415
|
* on this module for symmetry with the other domain modules. */
|
|
@@ -3489,6 +3542,7 @@ var APP_API_URLS2 = {
|
|
|
3489
3542
|
auth: "https://auth.topolo.app",
|
|
3490
3543
|
backup: "https://backup.topolo.app",
|
|
3491
3544
|
books: "https://books.topolo.app",
|
|
3545
|
+
brand: "https://brand.topolo.app",
|
|
3492
3546
|
bugfix: "https://bugfix.topolo.app",
|
|
3493
3547
|
bytes: "https://bytes.topolo.app",
|
|
3494
3548
|
calendar: "https://calendar.topolo.app",
|
|
@@ -3817,6 +3871,46 @@ async function refreshCredentialIfNeeded(environment = selectedEnvironment(), op
|
|
|
3817
3871
|
return cred;
|
|
3818
3872
|
}
|
|
3819
3873
|
}
|
|
3874
|
+
async function refreshContextCredentialIfNeeded(environment, key, options = {}) {
|
|
3875
|
+
const config = await readConfig();
|
|
3876
|
+
const profile = profileForEnvironment(config, environment);
|
|
3877
|
+
const context = profile.contexts?.[key];
|
|
3878
|
+
const credential = context?.credential;
|
|
3879
|
+
if (!credential || credential.kind !== "access_token") return credential ?? null;
|
|
3880
|
+
const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
|
|
3881
|
+
if (!credential.refreshToken || !oauthClientId) return credential;
|
|
3882
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3883
|
+
if (!options.force && credential.expiresAt && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
|
|
3884
|
+
try {
|
|
3885
|
+
const oauth = new TopoloOAuth2({
|
|
3886
|
+
serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
|
|
3887
|
+
});
|
|
3888
|
+
const tokens = await oauth.refreshToken({
|
|
3889
|
+
clientId: oauthClientId,
|
|
3890
|
+
refreshToken: credential.refreshToken
|
|
3891
|
+
});
|
|
3892
|
+
const refreshed = {
|
|
3893
|
+
kind: "access_token",
|
|
3894
|
+
accessToken: tokens.access_token,
|
|
3895
|
+
...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
|
|
3896
|
+
expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
3897
|
+
};
|
|
3898
|
+
await writeConfig(writeProfileConfig(config, environment, {
|
|
3899
|
+
...profile.activeContext === key ? { credential: refreshed } : {},
|
|
3900
|
+
contexts: {
|
|
3901
|
+
...profile.contexts ?? {},
|
|
3902
|
+
[key]: {
|
|
3903
|
+
...context,
|
|
3904
|
+
credential: refreshed,
|
|
3905
|
+
oauthClientId
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
}));
|
|
3909
|
+
return refreshed;
|
|
3910
|
+
} catch {
|
|
3911
|
+
return credential;
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3820
3914
|
function resolveAgentName(configAgentName) {
|
|
3821
3915
|
return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
|
|
3822
3916
|
}
|
|
@@ -5924,6 +6018,7 @@ function packageJson(application, target, options) {
|
|
|
5924
6018
|
version: "0.1.0",
|
|
5925
6019
|
private: true,
|
|
5926
6020
|
type: "module",
|
|
6021
|
+
...target === "platform" ? { packageManager: "pnpm@10.17.1" } : {},
|
|
5927
6022
|
scripts,
|
|
5928
6023
|
dependencies,
|
|
5929
6024
|
devDependencies
|
|
@@ -5942,7 +6037,7 @@ function topoloPackageVersion(packageName, target) {
|
|
|
5942
6037
|
"@topolo/login": "^0.1.0",
|
|
5943
6038
|
"@topolo-io/app-funnel": "^0.1.6",
|
|
5944
6039
|
"@topolo-io/app-launcher": "^0.1.15",
|
|
5945
|
-
"@topolo-io/app-shell": "^0.
|
|
6040
|
+
"@topolo-io/app-shell": "^0.8.4",
|
|
5946
6041
|
"@topolo-io/auth-client": "^1.2.1",
|
|
5947
6042
|
"@topolo-io/auth-middleware": "^0.4.0",
|
|
5948
6043
|
"@topolo-io/error-middleware": "^0.6.0",
|
|
@@ -7419,6 +7514,7 @@ function platformWorkerTs(application, options) {
|
|
|
7419
7514
|
"import { withSecurityHeaders } from '@topolo-io/security-headers';",
|
|
7420
7515
|
"import { withErrorReporting } from '@topolo-io/error-middleware';",
|
|
7421
7516
|
"import { SCAFFOLD_APP_NAME, SCAFFOLD_SERVICE_SLUG, resolveRuntimeAppId } from './app-identity';",
|
|
7517
|
+
"import { topoloActionContracts } from '../../../topolo.action-contracts.mjs';",
|
|
7422
7518
|
...options.resources.workflow ? ["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';"] : [],
|
|
7423
7519
|
"",
|
|
7424
7520
|
"export interface Env {",
|
|
@@ -7447,6 +7543,7 @@ function platformWorkerTs(application, options) {
|
|
|
7447
7543
|
" // so rt.verifyAuth() targets the correct Auth service per deploy.",
|
|
7448
7544
|
" authBaseUrl: (env) => env.AUTH_BASE_URL,",
|
|
7449
7545
|
" authGateway: { enabled: false },",
|
|
7546
|
+
" actionContracts: topoloActionContracts,",
|
|
7450
7547
|
"});",
|
|
7451
7548
|
"",
|
|
7452
7549
|
"async function handleRequest(",
|
|
@@ -7476,9 +7573,10 @@ function platformWorkerTs(application, options) {
|
|
|
7476
7573
|
" }, { status: 200 });",
|
|
7477
7574
|
" }",
|
|
7478
7575
|
"",
|
|
7576
|
+
" const auth = await rt.verifyAuth({ appId });",
|
|
7577
|
+
" if (!auth.ok) return rt.authError(auth);",
|
|
7578
|
+
"",
|
|
7479
7579
|
" if (url.pathname === '/api/widget') {",
|
|
7480
|
-
" const auth = await rt.verifyAuth({ appId });",
|
|
7481
|
-
" if (!auth.ok) return rt.authError(auth);",
|
|
7482
7580
|
" return rt.json(createTopoloWidgetResponse({",
|
|
7483
7581
|
" appId,",
|
|
7484
7582
|
" serviceName: SCAFFOLD_APP_NAME,",
|
|
@@ -7486,12 +7584,6 @@ function platformWorkerTs(application, options) {
|
|
|
7486
7584
|
" }), { status: 200, headers: { 'Cache-Control': 'public, max-age=15' } });",
|
|
7487
7585
|
" }",
|
|
7488
7586
|
"",
|
|
7489
|
-
" const auth = await rt.verifyAuth({ appId });",
|
|
7490
|
-
"",
|
|
7491
|
-
" if (!auth.ok) {",
|
|
7492
|
-
" return rt.authError(auth);",
|
|
7493
|
-
" }",
|
|
7494
|
-
"",
|
|
7495
7587
|
" if (url.pathname === '/api/bootstrap') {",
|
|
7496
7588
|
...bootstrapBody,
|
|
7497
7589
|
" }",
|
|
@@ -7850,8 +7942,8 @@ function topoloAppManifestJson(application, options) {
|
|
|
7850
7942
|
},
|
|
7851
7943
|
compatibilityFlags: ["nodejs_compat"],
|
|
7852
7944
|
buildCommand: {
|
|
7853
|
-
staging: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us
|
|
7854
|
-
production: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app
|
|
7945
|
+
staging: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us pnpm run build",
|
|
7946
|
+
production: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app pnpm run build"
|
|
7855
7947
|
},
|
|
7856
7948
|
healthcheckPath: options.api ? "/api/health" : "/",
|
|
7857
7949
|
...options.api ? {
|
|
@@ -7909,12 +8001,14 @@ function topoloActionContractsMjs() {
|
|
|
7909
8001
|
" name: 'widget.get',",
|
|
7910
8002
|
" method: 'GET',",
|
|
7911
8003
|
" path: '/api/widget',",
|
|
8004
|
+
" permission: 'workspace:read',",
|
|
7912
8005
|
" input: emptyInput,",
|
|
7913
8006
|
" },",
|
|
7914
8007
|
" 'workspace.bootstrap': {",
|
|
7915
8008
|
" name: 'workspace.bootstrap',",
|
|
7916
8009
|
" method: 'GET',",
|
|
7917
8010
|
" path: '/api/bootstrap',",
|
|
8011
|
+
" permission: 'workspace:read',",
|
|
7918
8012
|
" input: emptyInput,",
|
|
7919
8013
|
" },",
|
|
7920
8014
|
"};",
|
|
@@ -8105,7 +8199,7 @@ function cloudcontrolJson(application, options) {
|
|
|
8105
8199
|
cloudflare_account_id: STAGING_CLOUDFLARE_ACCOUNT_ID,
|
|
8106
8200
|
config_path: "apps/web/wrangler.toml",
|
|
8107
8201
|
workdir: "apps/web",
|
|
8108
|
-
build_command: "DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us
|
|
8202
|
+
build_command: "cd ../.. && DEPLOY_ENVIRONMENT=staging VITE_TOPOLO_AUTH_URL=https://auth.stg.topolo.us pnpm run build",
|
|
8109
8203
|
output_dir: null,
|
|
8110
8204
|
production_branch: "staging",
|
|
8111
8205
|
healthcheck_path: "/api/health",
|
|
@@ -8138,7 +8232,7 @@ function cloudcontrolJson(application, options) {
|
|
|
8138
8232
|
cloudflare_account_id: CLOUDFLARE_ACCOUNT_ID,
|
|
8139
8233
|
config_path: "apps/web/wrangler.toml",
|
|
8140
8234
|
workdir: "apps/web",
|
|
8141
|
-
build_command: "DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app
|
|
8235
|
+
build_command: "cd ../.. && DEPLOY_ENVIRONMENT=production VITE_TOPOLO_AUTH_URL=https://auth.topolo.app pnpm run build",
|
|
8142
8236
|
output_dir: null,
|
|
8143
8237
|
production_branch: "main",
|
|
8144
8238
|
healthcheck_path: "/api/health",
|
|
@@ -8206,17 +8300,17 @@ function cloudcontrolJson(application, options) {
|
|
|
8206
8300
|
ci_targets: [
|
|
8207
8301
|
{
|
|
8208
8302
|
name: "typecheck",
|
|
8209
|
-
command: "
|
|
8303
|
+
command: "pnpm run typecheck",
|
|
8210
8304
|
required: true
|
|
8211
8305
|
},
|
|
8212
8306
|
{
|
|
8213
8307
|
name: "test",
|
|
8214
|
-
command: "
|
|
8308
|
+
command: "pnpm run test",
|
|
8215
8309
|
required: true
|
|
8216
8310
|
},
|
|
8217
8311
|
{
|
|
8218
8312
|
name: "build",
|
|
8219
|
-
command: "
|
|
8313
|
+
command: "pnpm run build",
|
|
8220
8314
|
required: true
|
|
8221
8315
|
}
|
|
8222
8316
|
],
|
|
@@ -11181,6 +11275,10 @@ function hasSameAccessToken(left, right) {
|
|
|
11181
11275
|
const rightCredential = right;
|
|
11182
11276
|
return leftCredential?.kind === "access_token" && rightCredential?.kind === "access_token" && leftCredential.accessToken === rightCredential.accessToken;
|
|
11183
11277
|
}
|
|
11278
|
+
function identityMatchesContext(me, context) {
|
|
11279
|
+
if (context.contextType === "personal") return me.activeType === "personal";
|
|
11280
|
+
return me.activeType === "organization" && me.activeOrgId === context.organizationId;
|
|
11281
|
+
}
|
|
11184
11282
|
function registerContextCommand(program2) {
|
|
11185
11283
|
const context = program2.command("context").description("Show and switch the active context (personal or an organization)");
|
|
11186
11284
|
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) => {
|
|
@@ -11276,24 +11374,38 @@ function registerContextCommand(program2) {
|
|
|
11276
11374
|
const key = contextKey(resolved.contextType, resolved.organizationId);
|
|
11277
11375
|
const existing = profile.contexts?.[key];
|
|
11278
11376
|
let newCredential;
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11377
|
+
if (existing?.credential && profile.activeContext !== key) {
|
|
11378
|
+
const storedCredential = await refreshContextCredentialIfNeeded(environment, key);
|
|
11379
|
+
if (storedCredential) {
|
|
11380
|
+
try {
|
|
11381
|
+
const storedIdentity = await fetchMe(storedCredential, serviceUrls);
|
|
11382
|
+
if (identityMatchesContext(storedIdentity, resolved)) {
|
|
11383
|
+
newCredential = storedCredential;
|
|
11384
|
+
}
|
|
11385
|
+
} catch {
|
|
11386
|
+
}
|
|
11387
|
+
}
|
|
11388
|
+
}
|
|
11389
|
+
if (!newCredential) {
|
|
11390
|
+
try {
|
|
11391
|
+
newCredential = await exchangeForContext(cred, serviceUrls, {
|
|
11392
|
+
type: resolved.contextType,
|
|
11393
|
+
id: resolved.organizationId
|
|
11394
|
+
});
|
|
11395
|
+
} catch (error) {
|
|
11396
|
+
if (!isAuthenticationFailure(error)) throw error;
|
|
11397
|
+
const refreshed = await refreshCredentialIfNeeded(environment, { force: true });
|
|
11398
|
+
if (!refreshed || hasSameAccessToken(cred, refreshed)) {
|
|
11399
|
+
throw new Error(
|
|
11400
|
+
`The active credential could not be refreshed. Run \`topolo --env ${environment} auth login\` to sign in again.`
|
|
11401
|
+
);
|
|
11402
|
+
}
|
|
11403
|
+
cred = refreshed;
|
|
11404
|
+
newCredential = await exchangeForContext(cred, serviceUrls, {
|
|
11405
|
+
type: resolved.contextType,
|
|
11406
|
+
id: resolved.organizationId
|
|
11407
|
+
});
|
|
11291
11408
|
}
|
|
11292
|
-
cred = refreshed;
|
|
11293
|
-
newCredential = await exchangeForContext(cred, serviceUrls, {
|
|
11294
|
-
type: resolved.contextType,
|
|
11295
|
-
id: resolved.organizationId
|
|
11296
|
-
});
|
|
11297
11409
|
}
|
|
11298
11410
|
resolved.credential = newCredential;
|
|
11299
11411
|
resolved.oauthClientId = existing?.oauthClientId ?? profile.oauthClientId;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
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": {
|