@topolo/cli 0.7.0 → 0.7.3
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 +9 -2
- package/dist/index.js +139 -28
- package/package.json +2 -2
- package/skills/topolo/SKILL.md +18 -2
package/README.md
CHANGED
|
@@ -153,6 +153,7 @@ and the CLI calls them through the shared SDK.
|
|
|
153
153
|
|
|
154
154
|
```bash
|
|
155
155
|
topolo actions --service mail --json
|
|
156
|
+
topolo actions --service mail --detail --json
|
|
156
157
|
topolo actions get app_topolo_mail.messages.send --json
|
|
157
158
|
topolo actions call app_topolo_mail.messages.send \
|
|
158
159
|
--data '{"mailboxId":"primary","subject":"Hello","text":"..."}' \
|
|
@@ -163,6 +164,11 @@ Use `topolo actions` before `topolo api` when an app has published a machine
|
|
|
163
164
|
contract for the operation. `topolo api` remains the escape hatch for endpoints
|
|
164
165
|
that are not yet in the action catalog.
|
|
165
166
|
|
|
167
|
+
Action search, capability discovery, validation, and planning return compact
|
|
168
|
+
agent guidance by default. Use `--detail` only when the full schemas and agent
|
|
169
|
+
contract are required. `topolo resources --service <app>` is accepted alongside
|
|
170
|
+
the positional application selector.
|
|
171
|
+
|
|
166
172
|
Workspace-scoped actions carry resource context independently from the active
|
|
167
173
|
personal/organization credential and independently from the browser session:
|
|
168
174
|
|
|
@@ -215,8 +221,9 @@ Agents should call `topolo apps requirements <app> --json` before creating or
|
|
|
215
221
|
expanding a Topolo app. The output is the versioned platform contract for app
|
|
216
222
|
metadata, canonical docs, shared auth, shared shell, service registration,
|
|
217
223
|
deployment metadata, agent surfaces, observability, and verification.
|
|
218
|
-
`topolo apps audit --json`
|
|
219
|
-
|
|
224
|
+
`topolo apps audit --json` returns compact per-app scores and a migration queue
|
|
225
|
+
count. Add `--detail` for every finding and migration item, or
|
|
226
|
+
`--local --repo-root <topolo-checkout>` to
|
|
220
227
|
include filesystem evidence from the app repo, CloudControl metadata, and
|
|
221
228
|
TopoloDocs. Add `--fail-on needs_review` for the default new-work gate, or
|
|
222
229
|
`--fail-on partial` for strict release readiness.
|
package/dist/index.js
CHANGED
|
@@ -3914,6 +3914,52 @@ function mergeSignals(a, b) {
|
|
|
3914
3914
|
b.addEventListener("abort", onAbort, { once: true });
|
|
3915
3915
|
return controller.signal;
|
|
3916
3916
|
}
|
|
3917
|
+
function compactActionCatalogEntry(action) {
|
|
3918
|
+
const required = Array.isArray(action.inputSchema["required"]) ? action.inputSchema["required"].filter((value) => typeof value === "string") : [];
|
|
3919
|
+
const examples = action.agentContract?.examples ?? [];
|
|
3920
|
+
return {
|
|
3921
|
+
actionId: action.actionId,
|
|
3922
|
+
title: action.title,
|
|
3923
|
+
description: action.description,
|
|
3924
|
+
method: action.method,
|
|
3925
|
+
readOnly: action.readOnly,
|
|
3926
|
+
requiresConfirmation: action.requiresConfirmation,
|
|
3927
|
+
agentAccess: action.agentAccess ?? (action.requiresConfirmation ? "confirm" : action.readOnly ? "auto" : "confirm"),
|
|
3928
|
+
requiredPermission: action.requiredPermission,
|
|
3929
|
+
requiredInput: required,
|
|
3930
|
+
resourceType: action.agentContract?.resource?.kind ?? null,
|
|
3931
|
+
exampleInput: examples[0]?.input ?? null,
|
|
3932
|
+
docsUrl: action.agentContract?.docsUrl ?? null
|
|
3933
|
+
};
|
|
3934
|
+
}
|
|
3935
|
+
function compactActionValidation(actionId, validation) {
|
|
3936
|
+
return { actionId, valid: validation.valid, errors: validation.errors };
|
|
3937
|
+
}
|
|
3938
|
+
function compactActionPlan(plan) {
|
|
3939
|
+
return {
|
|
3940
|
+
actionId: plan.action.actionId,
|
|
3941
|
+
valid: plan.validation.valid,
|
|
3942
|
+
errors: plan.validation.errors,
|
|
3943
|
+
executable: plan.executable,
|
|
3944
|
+
confirmationRequired: plan.confirmationRequired,
|
|
3945
|
+
confirmationProvided: plan.confirmationProvided,
|
|
3946
|
+
request: plan.request,
|
|
3947
|
+
verification: plan.verification,
|
|
3948
|
+
rollbackActionId: plan.rollbackActionId,
|
|
3949
|
+
nextActionIds: plan.nextActionIds
|
|
3950
|
+
};
|
|
3951
|
+
}
|
|
3952
|
+
function compactApplicationAudit(report) {
|
|
3953
|
+
return {
|
|
3954
|
+
version: report.version,
|
|
3955
|
+
applications: report.applications.map((audit) => ({
|
|
3956
|
+
applicationId: audit.application.id,
|
|
3957
|
+
applicationName: audit.application.name,
|
|
3958
|
+
score: audit.score
|
|
3959
|
+
})),
|
|
3960
|
+
migrationQueueCount: report.migrationQueue.length
|
|
3961
|
+
};
|
|
3962
|
+
}
|
|
3917
3963
|
var CrmModule = class {
|
|
3918
3964
|
constructor(client) {
|
|
3919
3965
|
this.client = client;
|
|
@@ -4598,6 +4644,18 @@ function emit(data, mode, pretty) {
|
|
|
4598
4644
|
process.stdout.write(pretty() + "\n");
|
|
4599
4645
|
}
|
|
4600
4646
|
}
|
|
4647
|
+
function handleOutputStreamError(err, exit = process.exit) {
|
|
4648
|
+
if (err?.code === "EPIPE") {
|
|
4649
|
+
exit(0);
|
|
4650
|
+
return;
|
|
4651
|
+
}
|
|
4652
|
+
throw err;
|
|
4653
|
+
}
|
|
4654
|
+
function installOutputErrorHandlers(stdout = process.stdout, stderr = process.stderr, exit = process.exit) {
|
|
4655
|
+
const handler = (err) => handleOutputStreamError(err, exit);
|
|
4656
|
+
stdout.on("error", handler);
|
|
4657
|
+
stderr.on("error", handler);
|
|
4658
|
+
}
|
|
4601
4659
|
function fail(err, mode) {
|
|
4602
4660
|
const payload = normalizeError(err);
|
|
4603
4661
|
if (mode === "json") {
|
|
@@ -5107,7 +5165,7 @@ function parseQueryPairs(pairs) {
|
|
|
5107
5165
|
|
|
5108
5166
|
// src/commands/actions.ts
|
|
5109
5167
|
function registerActionsCommand(program2) {
|
|
5110
|
-
const actions = program2.command("actions").description("Search permission-scoped Topolo actions for one application").option("-s, --service <service>", "App id, slug, or name (required for search)").option("-q, --query <query>", "Filter by action id, name, or title").option("--cursor <cursor>", "Continuation cursor from the previous page").option("--limit <limit>", "Page size (1-100)", "25").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts) => {
|
|
5168
|
+
const actions = program2.command("actions").description("Search permission-scoped Topolo actions for one application").option("-s, --service <service>", "App id, slug, or name (required for search)").option("-q, --query <query>", "Filter by action id, name, or title").option("--cursor <cursor>", "Continuation cursor from the previous page").option("--limit <limit>", "Page size (1-100)", "25").option("--detail", "Include full schemas and agent contracts").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts) => {
|
|
5111
5169
|
const mode = resolveOutputMode(opts.json);
|
|
5112
5170
|
try {
|
|
5113
5171
|
const topolo = await buildClient();
|
|
@@ -5121,7 +5179,8 @@ function registerActionsCommand(program2) {
|
|
|
5121
5179
|
cursor: opts.cursor,
|
|
5122
5180
|
limit: parsePageSize(opts.limit)
|
|
5123
5181
|
});
|
|
5124
|
-
|
|
5182
|
+
const output2 = opts.detail ? result : { actions: result.actions.map(compactActionCatalogEntry), page: result.page };
|
|
5183
|
+
emit(output2, mode, () => formatActionRows(result.actions, result.page.nextCursor));
|
|
5125
5184
|
} catch (err) {
|
|
5126
5185
|
fail(err, mode);
|
|
5127
5186
|
}
|
|
@@ -5181,17 +5240,18 @@ function registerActionsCommand(program2) {
|
|
|
5181
5240
|
fail(err, mode);
|
|
5182
5241
|
}
|
|
5183
5242
|
});
|
|
5184
|
-
actions.command("validate <action>").description("Validate action input locally against the published JSON Schema").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
|
|
5243
|
+
actions.command("validate <action>").description("Validate action input locally against the published JSON Schema").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--detail", "Include the full action contract").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
|
|
5185
5244
|
const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
|
|
5186
5245
|
try {
|
|
5187
5246
|
const topolo = await requiredClient();
|
|
5188
5247
|
const result = await topolo.client.validateActionInput(action, parseJsonObject(opts.data || "{}"));
|
|
5189
|
-
|
|
5248
|
+
const output2 = detailRequested(opts.detail, command) ? result : compactActionValidation(result.action.actionId, result.validation);
|
|
5249
|
+
emit(output2, mode, () => result.validation.valid ? `Valid input for ${result.action.actionId}.` : result.validation.errors.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
|
|
5190
5250
|
} catch (err) {
|
|
5191
5251
|
fail(err, mode);
|
|
5192
5252
|
}
|
|
5193
5253
|
});
|
|
5194
|
-
actions.command("plan <action>").description("Validate and preview the exact request, confirmation, verification, and rollback plan").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--resource-type <type>", "App-local resource alias, such as workspace").option("--resource-id <id>", "Resource id for the selected resource type").option("--confirm", "Include confirmation in the plan without executing it").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
|
|
5254
|
+
actions.command("plan <action>").description("Validate and preview the exact request, confirmation, verification, and rollback plan").option("-d, --data <json-or-@file>", "JSON action input or @path to a JSON file", "{}").option("--resource-type <type>", "App-local resource alias, such as workspace").option("--resource-id <id>", "Resource id for the selected resource type").option("--confirm", "Include confirmation in the plan without executing it").option("--detail", "Include the full action contract").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (action, opts, command) => {
|
|
5195
5255
|
const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
|
|
5196
5256
|
try {
|
|
5197
5257
|
const topolo = await requiredClient();
|
|
@@ -5200,7 +5260,7 @@ function registerActionsCommand(program2) {
|
|
|
5200
5260
|
confirm: opts.confirm === true,
|
|
5201
5261
|
...resource ? { resource } : {}
|
|
5202
5262
|
});
|
|
5203
|
-
emit(plan, mode, () => formatActionPlan(plan));
|
|
5263
|
+
emit(detailRequested(opts.detail, command) ? plan : compactActionPlan(plan), mode, () => formatActionPlan(plan));
|
|
5204
5264
|
} catch (err) {
|
|
5205
5265
|
fail(err, mode);
|
|
5206
5266
|
}
|
|
@@ -5220,19 +5280,65 @@ function registerActionsCommand(program2) {
|
|
|
5220
5280
|
fail(err, mode);
|
|
5221
5281
|
}
|
|
5222
5282
|
});
|
|
5223
|
-
actions.command("capabilities").description("List every published action for one application (its capability contract)").option("-s, --service <service>", "App id, slug, or name").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts, command) => {
|
|
5283
|
+
actions.command("capabilities").description("List every published action for one application (its capability contract)").option("-s, --service <service>", "App id, slug, or name").option("--summary", "Return compact capability counts without action rows").option("--detail", "Include full schemas and agent contracts").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts, command) => {
|
|
5224
5284
|
const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
|
|
5225
5285
|
try {
|
|
5226
5286
|
const topolo = await requiredClient();
|
|
5227
5287
|
const service = opts.service || command.parent?.opts().service;
|
|
5228
5288
|
if (!service?.trim()) throw new Error("--service is required for capability discovery");
|
|
5229
|
-
|
|
5230
|
-
|
|
5289
|
+
if (opts.summary && detailRequested(opts.detail, command)) {
|
|
5290
|
+
throw new Error("--summary cannot be combined with --detail");
|
|
5291
|
+
}
|
|
5292
|
+
const resolvedService = service.trim();
|
|
5293
|
+
const discovered = [];
|
|
5294
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
5295
|
+
let cursor;
|
|
5296
|
+
for (; ; ) {
|
|
5297
|
+
const page = await topolo.client.searchActions({
|
|
5298
|
+
service: resolvedService,
|
|
5299
|
+
limit: 100,
|
|
5300
|
+
...cursor ? { cursor } : {}
|
|
5301
|
+
});
|
|
5302
|
+
discovered.push(...page.actions);
|
|
5303
|
+
if (!page.page.hasMore) break;
|
|
5304
|
+
const nextCursor = page.page.nextCursor?.trim();
|
|
5305
|
+
if (!nextCursor || seenCursors.has(nextCursor)) {
|
|
5306
|
+
throw new Error("Capability discovery returned an invalid continuation cursor.");
|
|
5307
|
+
}
|
|
5308
|
+
seenCursors.add(nextCursor);
|
|
5309
|
+
cursor = nextCursor;
|
|
5310
|
+
}
|
|
5311
|
+
const result = { actions: discovered, page: { nextCursor: null, hasMore: false } };
|
|
5312
|
+
const output2 = opts.summary ? summarizeActionCapabilities(resolvedService, discovered) : detailRequested(opts.detail, command) ? result : { actions: discovered.map(compactActionCatalogEntry), page: result.page };
|
|
5313
|
+
emit(output2, mode, () => opts.summary ? formatActionCapabilitySummary(summarizeActionCapabilities(resolvedService, discovered)) : formatActionRows(discovered));
|
|
5231
5314
|
} catch (err) {
|
|
5232
5315
|
fail(err, mode);
|
|
5233
5316
|
}
|
|
5234
5317
|
});
|
|
5235
5318
|
}
|
|
5319
|
+
function summarizeActionCapabilities(service, actions) {
|
|
5320
|
+
return {
|
|
5321
|
+
service,
|
|
5322
|
+
total: actions.length,
|
|
5323
|
+
readOnly: actions.filter((action) => action.readOnly).length,
|
|
5324
|
+
mutating: actions.filter((action) => !action.readOnly).length,
|
|
5325
|
+
destructive: actions.filter((action) => action.destructive).length,
|
|
5326
|
+
confirmationRequired: actions.filter((action) => action.requiresConfirmation).length,
|
|
5327
|
+
resourceTypes: [...new Set(actions.map((action) => action.agentContract?.resource?.kind).filter(Boolean))].sort()
|
|
5328
|
+
};
|
|
5329
|
+
}
|
|
5330
|
+
function formatActionCapabilitySummary(summary) {
|
|
5331
|
+
return [
|
|
5332
|
+
`Service: ${summary.service}`,
|
|
5333
|
+
`Actions: ${summary.total} (${summary.readOnly} read-only, ${summary.mutating} mutating)`,
|
|
5334
|
+
`Destructive: ${summary.destructive}`,
|
|
5335
|
+
`Confirmation required: ${summary.confirmationRequired}`,
|
|
5336
|
+
`Resource types: ${summary.resourceTypes.join(", ") || "-"}`
|
|
5337
|
+
].join("\n");
|
|
5338
|
+
}
|
|
5339
|
+
function detailRequested(local, command) {
|
|
5340
|
+
return local === true || command.parent?.opts().detail === true;
|
|
5341
|
+
}
|
|
5236
5342
|
async function requiredClient() {
|
|
5237
5343
|
const topolo = await buildClient();
|
|
5238
5344
|
if (!topolo) throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
|
|
@@ -5336,7 +5442,7 @@ function formatActionPlan(plan) {
|
|
|
5336
5442
|
|
|
5337
5443
|
// src/commands/apps.ts
|
|
5338
5444
|
import { execSync } from "child_process";
|
|
5339
|
-
import { existsSync as
|
|
5445
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
5340
5446
|
import path6 from "path";
|
|
5341
5447
|
|
|
5342
5448
|
// src/commands/app-audit-local.ts
|
|
@@ -6639,7 +6745,7 @@ function firstPartyAppPath(appDir, _audience) {
|
|
|
6639
6745
|
}
|
|
6640
6746
|
|
|
6641
6747
|
// src/commands/app-scaffold-platform-versions.ts
|
|
6642
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
6748
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
6643
6749
|
import path2 from "path";
|
|
6644
6750
|
var versionCache = /* @__PURE__ */ new Map();
|
|
6645
6751
|
function platformPackagePin(packageName, options = {}) {
|
|
@@ -6647,7 +6753,7 @@ function platformPackagePin(packageName, options = {}) {
|
|
|
6647
6753
|
options.repoRoot ?? process.env.TOPOLO_WORKSPACE_ROOT ?? void 0,
|
|
6648
6754
|
options.cwd ?? process.cwd()
|
|
6649
6755
|
);
|
|
6650
|
-
if (!workspaceRoot) {
|
|
6756
|
+
if (!workspaceRoot || !existsSync3(path2.join(workspaceRoot, "topolo-platform", "packages"))) {
|
|
6651
6757
|
throw new Error(
|
|
6652
6758
|
`Cannot resolve a concrete version for ${packageName}: platform-target scaffolds must run inside the Topolo workspace checkout (a directory containing apps/ and system-apps/), or receive --repo-root. The fleet pinning policy requires exact ^<latest> pins, never "latest".`
|
|
6653
6759
|
);
|
|
@@ -7194,6 +7300,7 @@ function readme(application, target, options) {
|
|
|
7194
7300
|
"- `@topolo-io/app-launcher` provides shared app switching and handoff helpers.",
|
|
7195
7301
|
"- `@topolo-io/ui-kit` provides every control, table, panel, workbench, inspector, icon, transition, and bug reporter primitive used by the generated product surface.",
|
|
7196
7302
|
"- `@topolo-io/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
|
|
7303
|
+
'- Build the app data client with `authClient.createJsonClient()` (or `createAuthenticatedFetch()`) from `@topolo-io/auth-client`. It already attaches the bearer token, refreshes proactively near expiry, and refreshes-and-retries once on a 401. NEVER hand-roll an `Authorization: Bearer` header in browser code: every app that has done so forgot the 401 recovery and showed raw auth errors ("Token expired") to the user on an ordinary 15-minute expiry. The `browser-auth-refresh` fleet static guard fails any browser file that builds its own bearer header.',
|
|
7197
7304
|
"- `@topolo-io/api-key-management`, `@topolo-io/chat-widget`, `@topolo-io/notifications`, and `@topolo-io/resource-context` complete the AppShell capability baseline.",
|
|
7198
7305
|
"- Every Topolo-owned browser dependency is emitted at `latest`; product-domain packages are added only when the app actually adopts that domain.",
|
|
7199
7306
|
"- `.npmrc` routes private `@topolo-io` packages to GitHub Packages without storing credentials; public `@topolo` packages remain on npm. Local and CI environments must provide a GitHub Packages token.",
|
|
@@ -8760,7 +8867,7 @@ function developerWorkerTs(application) {
|
|
|
8760
8867
|
}
|
|
8761
8868
|
|
|
8762
8869
|
// src/commands/app-scaffold-generate.ts
|
|
8763
|
-
import { existsSync as
|
|
8870
|
+
import { existsSync as existsSync4, mkdirSync, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync } from "fs";
|
|
8764
8871
|
import { spawnSync } from "child_process";
|
|
8765
8872
|
import { tmpdir } from "os";
|
|
8766
8873
|
import path3 from "path";
|
|
@@ -8774,7 +8881,7 @@ function resolveGeneratorPath() {
|
|
|
8774
8881
|
];
|
|
8775
8882
|
for (const candidate of candidates) {
|
|
8776
8883
|
const p = decodeURIComponent(candidate.pathname);
|
|
8777
|
-
if (
|
|
8884
|
+
if (existsSync4(p)) return p;
|
|
8778
8885
|
}
|
|
8779
8886
|
throw new Error(
|
|
8780
8887
|
`topolo: could not locate generate-wrangler.mjs. A published CLI vendors it into dist/ at build time; in-repo it is read from topolo-platform/tooling/. Looked in:
|
|
@@ -11668,7 +11775,7 @@ function registerAppsCommand(program2) {
|
|
|
11668
11775
|
});
|
|
11669
11776
|
apps.command("audit [application]").description(
|
|
11670
11777
|
"Audit Topolo applications against the versioned application-build requirements."
|
|
11671
|
-
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").option("--local", "Inspect the local Topolo checkout for filesystem-backed evidence").option("--repo-root <path>", "Topolo checkout root to inspect when --local is set").option(
|
|
11778
|
+
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").option("--detail", "Include all findings and migration queue items").option("--local", "Inspect the local Topolo checkout for filesystem-backed evidence").option("--repo-root <path>", "Topolo checkout root to inspect when --local is set").option(
|
|
11672
11779
|
"--fail-on <status>",
|
|
11673
11780
|
"Exit non-zero when audit has findings at this severity or worse: missing, needs_review, partial"
|
|
11674
11781
|
).action(async (application, opts) => {
|
|
@@ -11680,8 +11787,9 @@ function registerAppsCommand(program2) {
|
|
|
11680
11787
|
client: await directoryClient()
|
|
11681
11788
|
});
|
|
11682
11789
|
const gate = opts.failOn ? evaluateApplicationAuditGate(payload, opts.failOn) : null;
|
|
11683
|
-
const
|
|
11684
|
-
|
|
11790
|
+
const compact = opts.detail ? payload : compactApplicationAudit(payload);
|
|
11791
|
+
const output2 = gate ? { ...compact, conformanceGate: gate } : compact;
|
|
11792
|
+
emit(output2, mode, () => formatAudit(gate ? { ...payload, conformanceGate: gate } : payload));
|
|
11685
11793
|
if (gate && !gate.passed) {
|
|
11686
11794
|
if (mode === "json") {
|
|
11687
11795
|
process.stderr.write(JSON.stringify({ error: gate.message, conformanceGate: gate }, null, 2) + "\n");
|
|
@@ -11824,7 +11932,7 @@ async function applicationAuditPayload(application, options = {}) {
|
|
|
11824
11932
|
async function recordApplicationEvidence(application, options = {}) {
|
|
11825
11933
|
const appId = (await resolveApplication(application, options.client ?? null)).id;
|
|
11826
11934
|
const root = path6.resolve(options.appRoot ?? process.cwd());
|
|
11827
|
-
if (!
|
|
11935
|
+
if (!existsSync5(root)) {
|
|
11828
11936
|
throw new Error(`Application root does not exist: ${root}`);
|
|
11829
11937
|
}
|
|
11830
11938
|
let commit = options.commit;
|
|
@@ -11838,7 +11946,7 @@ async function recordApplicationEvidence(application, options = {}) {
|
|
|
11838
11946
|
const evidenceDir = path6.join(root, ".topolo");
|
|
11839
11947
|
const evidencePath = path6.join(evidenceDir, "evidence.json");
|
|
11840
11948
|
let before = null;
|
|
11841
|
-
if (
|
|
11949
|
+
if (existsSync5(evidencePath)) {
|
|
11842
11950
|
try {
|
|
11843
11951
|
const raw = JSON.parse(readFileSync6(evidencePath, "utf8"));
|
|
11844
11952
|
if (raw && typeof raw === "object" && raw.version === 1) {
|
|
@@ -13110,7 +13218,7 @@ function formatCheckResult(result) {
|
|
|
13110
13218
|
|
|
13111
13219
|
// src/commands/skills.ts
|
|
13112
13220
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
13113
|
-
import { existsSync as
|
|
13221
|
+
import { existsSync as existsSync6 } from "fs";
|
|
13114
13222
|
import { mkdir as mkdir4, copyFile, readFile as readFile7, writeFile as writeFile7, rm } from "fs/promises";
|
|
13115
13223
|
import { homedir as homedir2 } from "os";
|
|
13116
13224
|
import { dirname as dirname4, join as join5, resolve as resolve3 } from "path";
|
|
@@ -13122,7 +13230,7 @@ function skillsSourceDir() {
|
|
|
13122
13230
|
const distCandidate = resolve3(here, "../skills");
|
|
13123
13231
|
const srcCandidate = resolve3(here, "../../skills");
|
|
13124
13232
|
for (const candidate of [distCandidate, srcCandidate]) {
|
|
13125
|
-
if (
|
|
13233
|
+
if (existsSync6(join5(candidate, "topolo", "SKILL.md"))) return candidate;
|
|
13126
13234
|
}
|
|
13127
13235
|
return distCandidate;
|
|
13128
13236
|
}
|
|
@@ -13167,7 +13275,7 @@ function registerViaOfficialCli(cli, mcp, agentName) {
|
|
|
13167
13275
|
async function registerClaudeViaConfig(mcp) {
|
|
13168
13276
|
const path9 = join5(homedir2(), ".claude.json");
|
|
13169
13277
|
let config = {};
|
|
13170
|
-
if (
|
|
13278
|
+
if (existsSync6(path9)) {
|
|
13171
13279
|
try {
|
|
13172
13280
|
config = JSON.parse(await readFile7(path9, "utf8"));
|
|
13173
13281
|
} catch {
|
|
@@ -13188,7 +13296,7 @@ async function registerClaudeViaConfig(mcp) {
|
|
|
13188
13296
|
async function registerCodexViaConfig(mcp) {
|
|
13189
13297
|
const path9 = join5(homedir2(), ".codex", "config.toml");
|
|
13190
13298
|
let text = "";
|
|
13191
|
-
if (
|
|
13299
|
+
if (existsSync6(path9)) text = await readFile7(path9, "utf8");
|
|
13192
13300
|
text = text.replace(
|
|
13193
13301
|
/(^|\n)\[mcp_servers\.topolo(?:\.[a-z]+)?\][^\[]*/g,
|
|
13194
13302
|
(match) => match.startsWith("\n") ? "\n" : ""
|
|
@@ -13243,7 +13351,7 @@ function registerSkillsCommand(program2) {
|
|
|
13243
13351
|
const mode = resolveOutputMode(opts.json);
|
|
13244
13352
|
try {
|
|
13245
13353
|
const sourceDir = skillsSourceDir();
|
|
13246
|
-
if (!
|
|
13354
|
+
if (!existsSync6(join5(sourceDir, "topolo", "SKILL.md"))) {
|
|
13247
13355
|
throw new Error(
|
|
13248
13356
|
"Bundled skill file not found. Reinstall the CLI: npm install -g @topolo/cli"
|
|
13249
13357
|
);
|
|
@@ -13272,7 +13380,7 @@ function registerSkillsCommand(program2) {
|
|
|
13272
13380
|
const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
|
|
13273
13381
|
return {
|
|
13274
13382
|
agent,
|
|
13275
|
-
skillInstalled:
|
|
13383
|
+
skillInstalled: existsSync6(join5(skillDir, "SKILL.md")),
|
|
13276
13384
|
skillPath: join5(skillDir, "SKILL.md"),
|
|
13277
13385
|
cliAvailable: agentBinaryExists(agent)
|
|
13278
13386
|
};
|
|
@@ -13289,7 +13397,7 @@ function registerSkillsCommand(program2) {
|
|
|
13289
13397
|
for (const agent of resolveTargets(opts)) {
|
|
13290
13398
|
const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
|
|
13291
13399
|
const skillFile = join5(skillDir, "SKILL.md");
|
|
13292
|
-
const skillRemoved =
|
|
13400
|
+
const skillRemoved = existsSync6(skillFile);
|
|
13293
13401
|
if (skillRemoved) await rm(skillFile, { force: true });
|
|
13294
13402
|
if (agentBinaryExists(agent)) {
|
|
13295
13403
|
spawnSync2(agent, ["mcp", "remove", "topolo"], { stdio: "ignore" });
|
|
@@ -13541,12 +13649,14 @@ function registerContextCommand(program2) {
|
|
|
13541
13649
|
|
|
13542
13650
|
// src/commands/resources.ts
|
|
13543
13651
|
function registerResourcesCommand(program2) {
|
|
13544
|
-
program2.command("resources
|
|
13652
|
+
program2.command("resources [application]").description("List bindable resource ids for one credential-scoped Topolo application").option("-s, --service <service>", "App id, slug, or name").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (application, opts) => {
|
|
13545
13653
|
const mode = resolveOutputMode(opts.json);
|
|
13546
13654
|
try {
|
|
13547
13655
|
const topolo = await buildClient();
|
|
13548
13656
|
if (!topolo) throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
|
|
13549
|
-
const
|
|
13657
|
+
const selector = application?.trim() || opts.service?.trim();
|
|
13658
|
+
if (!selector) throw new Error("application or --service is required");
|
|
13659
|
+
const result = await topolo.client.listBindableResources(selector);
|
|
13550
13660
|
emit(result, mode, () => formatResources(result));
|
|
13551
13661
|
} catch (err) {
|
|
13552
13662
|
fail(err, mode);
|
|
@@ -13641,6 +13751,7 @@ Workspace: ${workspace.name} (${workspace.id})${workspace.isDefault ? "\nDefault
|
|
|
13641
13751
|
}
|
|
13642
13752
|
|
|
13643
13753
|
// src/index.ts
|
|
13754
|
+
installOutputErrorHandlers();
|
|
13644
13755
|
var program = new Command();
|
|
13645
13756
|
var argv;
|
|
13646
13757
|
try {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/cli",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"topoloPublishedVersion": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
|
+
"topoloPublishedVersion": "0.7.3",
|
|
5
5
|
"description": "Command-line interface for Topolo developers, operators, and agents, with internal first-party scaffolding available under the topolo platform namespace.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
package/skills/topolo/SKILL.md
CHANGED
|
@@ -20,6 +20,22 @@ topolo skills install
|
|
|
20
20
|
|
|
21
21
|
`topolo skills install` installs this skill and registers the Topolo MCP server for Claude Code and Codex. Both use the same credential written by `topolo auth login`.
|
|
22
22
|
|
|
23
|
+
Before testing platform behavior, verify every installed agent surface against
|
|
24
|
+
the package registry. Do not diagnose behavior with stale tooling:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
topolo --version
|
|
28
|
+
npm list -g --depth=0 @topolo/cli @topolo/mcp @topolo/sdk
|
|
29
|
+
npm view @topolo/cli version
|
|
30
|
+
npm view @topolo/mcp version
|
|
31
|
+
npm view @topolo/sdk version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If any version differs, install the three latest released versions together,
|
|
35
|
+
run `topolo skills install`, and restart the MCP client before testing. Record
|
|
36
|
+
the versions used with the results. Source builds may test a candidate fix, but
|
|
37
|
+
must be labelled separately from released-tooling proof.
|
|
38
|
+
|
|
23
39
|
Production is the default. Use `--env staging` on every staging command, or set `TOPOLO_ENV=staging`. Never mix staging discovery with production execution.
|
|
24
40
|
|
|
25
41
|
## Authentication gate
|
|
@@ -153,7 +169,7 @@ For large inputs, write one schema-valid JSON object to a file and pass it witho
|
|
|
153
169
|
|
|
154
170
|
```bash
|
|
155
171
|
topolo actions validate <action-id> --data @payload.json --json
|
|
156
|
-
topolo actions plan <action-id> --data @payload.json --json
|
|
172
|
+
topolo actions plan <action-id> --data @payload.json --confirm --json
|
|
157
173
|
topolo actions call <action-id> --data @payload.json --confirm --json
|
|
158
174
|
```
|
|
159
175
|
|
|
@@ -167,7 +183,7 @@ topolo actions plan <action-id> --data '<json>' --json
|
|
|
167
183
|
|
|
168
184
|
MCP: `topolo_plan_action`.
|
|
169
185
|
|
|
170
|
-
The plan must be valid and must expose the resolved request, confirmation requirement, verification steps, and rollback when one is published.
|
|
186
|
+
The plan must be valid and must expose the resolved request, confirmation requirement, verification steps, and rollback when one is published. For a mutation that the user has already authorized, include `--confirm` in the plan so `executable` reflects the exact confirmed call; planning never executes the action. Before approval, omit `--confirm`, present the plan, and treat `executable: false` with `confirmationRequired: true` and `confirmationProvided: false` as awaiting approval rather than an invalid contract. Once approval is obtained, call the unchanged request with `--confirm` without planning it a second time. Any other `executable: false` result blocks the call.
|
|
171
187
|
|
|
172
188
|
### 6. Confirm mutations
|
|
173
189
|
|