@topolo/cli 0.6.10 → 0.7.1
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 +289 -196
- package/package.json +2 -2
- package/skills/codex/AGENTS.md +2 -2
- package/skills/topolo/SKILL.md +16 -0
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
|
@@ -110,57 +110,8 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
110
110
|
}
|
|
111
111
|
};
|
|
112
112
|
var APP_API_URLS = {
|
|
113
|
-
agent: "https://agent.topolo.app",
|
|
114
113
|
auth: "https://auth.topolo.app",
|
|
115
|
-
|
|
116
|
-
blog: "https://blog.topolo.app",
|
|
117
|
-
books: "https://books.topolo.app",
|
|
118
|
-
brand: "https://brand.topolo.app",
|
|
119
|
-
bugfix: "https://bugfix.topolo.app",
|
|
120
|
-
bytes: "https://bytes.topolo.app",
|
|
121
|
-
calendar: "https://calendar.topolo.app",
|
|
122
|
-
campaigns: "https://campaigns.topolo.app",
|
|
123
|
-
capacity: "https://capacity.topolo.app",
|
|
124
|
-
chat: "https://chat.topolo.app",
|
|
125
|
-
commerce: "https://commerce.topolo.app",
|
|
126
|
-
compose: "https://compose.topolo.app",
|
|
127
|
-
consent: "https://consent.topolo.app",
|
|
128
|
-
crm: "https://crm.topolo.app",
|
|
129
|
-
design: "https://design.topolo.app",
|
|
130
|
-
developers: "https://developers.topolo.app",
|
|
131
|
-
director: "https://director.topolo.app",
|
|
132
|
-
feed: "https://feed.topolo.app",
|
|
133
|
-
flow: "https://flow.topolo.app",
|
|
134
|
-
forecast: "https://forecast.topolo.app",
|
|
135
|
-
forms: "https://forms.topolo.app",
|
|
136
|
-
home: "https://home.topolo.app",
|
|
137
|
-
insights: "https://insights.topolo.app",
|
|
138
|
-
inventory: "https://inventory.topolo.app",
|
|
139
|
-
learn: "https://learn.topolo.app",
|
|
140
|
-
localize: "https://localize.topolo.app",
|
|
141
|
-
mail: "https://mail.topolo.app",
|
|
142
|
-
mdm: "https://mdm.topolo.app",
|
|
143
|
-
messages: "https://messages.topolo.app",
|
|
144
|
-
nexus: "https://nexus.topolo.app",
|
|
145
|
-
notify: "https://notify.topolo.app",
|
|
146
|
-
observability_watch: "https://observability-watch.topolo.app",
|
|
147
|
-
one: "https://www.topolo.app",
|
|
148
|
-
p2p: "https://p2p.topolo.app",
|
|
149
|
-
pay: "https://pay.topolo.app",
|
|
150
|
-
people: "https://people.topolo.app",
|
|
151
|
-
"quro.api": "https://api.ol0.me",
|
|
152
|
-
"quro.redirect": "https://ol0.me",
|
|
153
|
-
roadmapper: "https://roadmapper.topolo.app",
|
|
154
|
-
sign: "https://sign.topolo.app",
|
|
155
|
-
social_studio: "https://studio.topolo.app",
|
|
156
|
-
socialize: "https://socialize.topolo.app",
|
|
157
|
-
spaces: "https://spaces.topolo.app",
|
|
158
|
-
status: "https://status.topolo.app",
|
|
159
|
-
success: "https://success.topolo.app",
|
|
160
|
-
support: "https://support.topolo.app",
|
|
161
|
-
survey: "https://survey.topolo.app",
|
|
162
|
-
voice: "https://voice.topolo.app",
|
|
163
|
-
web: "https://web.topolo.app"
|
|
114
|
+
developers: "https://developers.topolo.app"
|
|
164
115
|
};
|
|
165
116
|
var APPLICATIONS = {
|
|
166
117
|
"admin": {
|
|
@@ -1886,7 +1837,145 @@ var APPLICATIONS = {
|
|
|
1886
1837
|
]
|
|
1887
1838
|
}
|
|
1888
1839
|
};
|
|
1889
|
-
|
|
1840
|
+
function friendlyAppId(entry) {
|
|
1841
|
+
for (const value of [entry.slug, entry.name, entry.appId]) {
|
|
1842
|
+
if (typeof value !== "string" || !value.trim()) continue;
|
|
1843
|
+
const normalized = value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
1844
|
+
const withoutPrefix = normalized.replace(/^(?:app[_-])?topolo[-_]+/, "");
|
|
1845
|
+
if (withoutPrefix) return withoutPrefix;
|
|
1846
|
+
}
|
|
1847
|
+
return entry.appId;
|
|
1848
|
+
}
|
|
1849
|
+
function idVariants(value) {
|
|
1850
|
+
const normalized = value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
1851
|
+
return [.../* @__PURE__ */ new Set([
|
|
1852
|
+
normalized,
|
|
1853
|
+
normalized.replace(/-/g, "_"),
|
|
1854
|
+
normalized.replace(/_/g, "-")
|
|
1855
|
+
])].filter(Boolean);
|
|
1856
|
+
}
|
|
1857
|
+
var STATIC_APPLICATIONS = APPLICATIONS;
|
|
1858
|
+
function staticApplicationForId(id) {
|
|
1859
|
+
for (const variant of idVariants(id)) {
|
|
1860
|
+
const entry = STATIC_APPLICATIONS[variant];
|
|
1861
|
+
if (entry) return entry;
|
|
1862
|
+
}
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
function applicationEntryFromCatalog(live) {
|
|
1866
|
+
const id = friendlyAppId(live);
|
|
1867
|
+
return {
|
|
1868
|
+
id,
|
|
1869
|
+
appDir: "-",
|
|
1870
|
+
name: live.name ?? live.slug ?? live.appId,
|
|
1871
|
+
packageName: null,
|
|
1872
|
+
productionUrl: live.launchUrl ?? live.apiBaseUrl ?? null,
|
|
1873
|
+
services: live.apiBaseUrl ? [id] : [],
|
|
1874
|
+
deployTargets: []
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
var DIRECTORY_PAGE_LIMIT = 100;
|
|
1878
|
+
var DIRECTORY_MAX_PAGES = 100;
|
|
1879
|
+
function staticApplicationDirectory() {
|
|
1880
|
+
const applications = Object.entries(STATIC_APPLICATIONS).map(([id, application]) => ({
|
|
1881
|
+
id,
|
|
1882
|
+
application,
|
|
1883
|
+
source: "static",
|
|
1884
|
+
live: null
|
|
1885
|
+
})).sort((left, right) => left.id.localeCompare(right.id));
|
|
1886
|
+
return { source: "static_fallback", applications };
|
|
1887
|
+
}
|
|
1888
|
+
function directoryApplicationFromLive(live) {
|
|
1889
|
+
const id = friendlyAppId(live);
|
|
1890
|
+
return {
|
|
1891
|
+
id,
|
|
1892
|
+
// Prefer the static snapshot's richer repo/deploy metadata when the live
|
|
1893
|
+
// registry confirms the app; synthesize from the live entry otherwise.
|
|
1894
|
+
application: staticApplicationForId(id) ?? applicationEntryFromCatalog(live),
|
|
1895
|
+
source: "live",
|
|
1896
|
+
live
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
async function listApplicationDirectory(client) {
|
|
1900
|
+
if (!client) return staticApplicationDirectory();
|
|
1901
|
+
let services;
|
|
1902
|
+
try {
|
|
1903
|
+
services = [];
|
|
1904
|
+
let cursor;
|
|
1905
|
+
for (let page = 0; page < DIRECTORY_MAX_PAGES; page += 1) {
|
|
1906
|
+
const result = await client.searchServices({
|
|
1907
|
+
...cursor ? { cursor } : {},
|
|
1908
|
+
limit: DIRECTORY_PAGE_LIMIT
|
|
1909
|
+
});
|
|
1910
|
+
services.push(...result.services);
|
|
1911
|
+
if (!result.page.nextCursor) break;
|
|
1912
|
+
cursor = result.page.nextCursor;
|
|
1913
|
+
}
|
|
1914
|
+
} catch {
|
|
1915
|
+
return staticApplicationDirectory();
|
|
1916
|
+
}
|
|
1917
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1918
|
+
for (const live of services) {
|
|
1919
|
+
const entry = directoryApplicationFromLive(live);
|
|
1920
|
+
if (!seen.has(entry.id)) seen.set(entry.id, entry);
|
|
1921
|
+
}
|
|
1922
|
+
return {
|
|
1923
|
+
source: "live",
|
|
1924
|
+
applications: [...seen.values()].sort((left, right) => left.id.localeCompare(right.id))
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
function matchesDirectoryApplication(app, needle) {
|
|
1928
|
+
const candidates = new Set(idVariants(needle));
|
|
1929
|
+
const keys = [
|
|
1930
|
+
app.id,
|
|
1931
|
+
app.application.id,
|
|
1932
|
+
app.application.name,
|
|
1933
|
+
app.live?.appId,
|
|
1934
|
+
app.live?.slug,
|
|
1935
|
+
app.live?.name
|
|
1936
|
+
];
|
|
1937
|
+
for (const key of keys) {
|
|
1938
|
+
if (typeof key !== "string" || !key.trim()) continue;
|
|
1939
|
+
for (const variant of idVariants(key)) {
|
|
1940
|
+
if (candidates.has(variant)) return true;
|
|
1941
|
+
}
|
|
1942
|
+
for (const variant of idVariants(friendlyAppId({ appId: key }))) {
|
|
1943
|
+
if (candidates.has(variant)) return true;
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
return false;
|
|
1947
|
+
}
|
|
1948
|
+
function formatDirectoryApplicationIds(directory) {
|
|
1949
|
+
const ids = directory.applications.map((app) => app.id).sort();
|
|
1950
|
+
const preview = ids.slice(0, 50).join(", ");
|
|
1951
|
+
const suffix = ids.length > 50 ? `, ... +${ids.length - 50} more` : "";
|
|
1952
|
+
const label = directory.source === "live" ? "Applications available to this credential" : "Applications in the bundled static snapshot (live registry unreachable)";
|
|
1953
|
+
return `${label}: ${preview}${suffix}`;
|
|
1954
|
+
}
|
|
1955
|
+
async function resolveApplicationDirectoryEntry(client, application) {
|
|
1956
|
+
const needle = typeof application === "string" ? application.trim() : "";
|
|
1957
|
+
if (!needle) {
|
|
1958
|
+
throw new TopoloSdkError("unknown_application", "application is required");
|
|
1959
|
+
}
|
|
1960
|
+
if (client) {
|
|
1961
|
+
let live = null;
|
|
1962
|
+
try {
|
|
1963
|
+
live = await client.getService(needle);
|
|
1964
|
+
} catch {
|
|
1965
|
+
live = null;
|
|
1966
|
+
}
|
|
1967
|
+
if (live) return directoryApplicationFromLive(live);
|
|
1968
|
+
}
|
|
1969
|
+
const directory = await listApplicationDirectory(client);
|
|
1970
|
+
const match = directory.applications.find((app) => matchesDirectoryApplication(app, needle));
|
|
1971
|
+
if (match) return match;
|
|
1972
|
+
throw new TopoloSdkError(
|
|
1973
|
+
"unknown_application",
|
|
1974
|
+
`Unknown application "${application}". ${formatDirectoryApplicationIds(directory)}`
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1977
|
+
var STATIC_APPLICATIONS2 = APPLICATIONS;
|
|
1978
|
+
var APPLICATION_REQUIREMENTS_VERSION = "2026-08-11.1";
|
|
1890
1979
|
var APPLICATION_REQUIREMENTS = [
|
|
1891
1980
|
{
|
|
1892
1981
|
id: "platform-metadata",
|
|
@@ -1896,10 +1985,10 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1896
1985
|
evidence: [
|
|
1897
1986
|
"topolo.cloudcontrol.json exists for every deployed app surface",
|
|
1898
1987
|
"package.json names the package or app when the repo ships code",
|
|
1899
|
-
"The
|
|
1988
|
+
"The live platform registry exposes the app through topolo apps and MCP discovery"
|
|
1900
1989
|
],
|
|
1901
1990
|
implementation: [
|
|
1902
|
-
"
|
|
1991
|
+
"Register the app in the live platform registry (Auth service catalog) before exposing it to agents",
|
|
1903
1992
|
"Keep production URLs, Worker names, Pages projects, routes, domains, and bindings current in CloudControl metadata",
|
|
1904
1993
|
"Do not leave a new Topolo* app directory without an explicit application ID"
|
|
1905
1994
|
]
|
|
@@ -1974,7 +2063,7 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1974
2063
|
implementation: [
|
|
1975
2064
|
"Add production Worker api_url metadata and sdk_app_id for every agent-callable API target",
|
|
1976
2065
|
"Mark browser, documentation, and runtime-only Worker targets with agent_callable:false",
|
|
1977
|
-
"Expose stable app IDs through
|
|
2066
|
+
"Expose stable app IDs through live catalog discovery",
|
|
1978
2067
|
"Keep typed SDK/CLI/MCP surfaces in sync when an API contract stabilizes"
|
|
1979
2068
|
]
|
|
1980
2069
|
},
|
|
@@ -2094,15 +2183,24 @@ function applicationRequirementScopes(application) {
|
|
|
2094
2183
|
}
|
|
2095
2184
|
return Array.from(scopes);
|
|
2096
2185
|
}
|
|
2186
|
+
function fallbackApplication(id) {
|
|
2187
|
+
const entry = STATIC_APPLICATIONS2[id];
|
|
2188
|
+
if (!entry) {
|
|
2189
|
+
throw new Error(
|
|
2190
|
+
`Unknown application "${id}" in the static fallback snapshot. Resolve applications from the live registry (resolveApplicationDirectoryEntry) and pass the entry directly.`
|
|
2191
|
+
);
|
|
2192
|
+
}
|
|
2193
|
+
return entry;
|
|
2194
|
+
}
|
|
2097
2195
|
function requirementsForApplication(applicationOrId) {
|
|
2098
|
-
const application = typeof applicationOrId === "string" ?
|
|
2196
|
+
const application = typeof applicationOrId === "string" ? fallbackApplication(applicationOrId) : applicationOrId;
|
|
2099
2197
|
const scopes = new Set(applicationRequirementScopes(application));
|
|
2100
2198
|
return APPLICATION_REQUIREMENTS.filter(
|
|
2101
2199
|
(requirement) => requirement.appliesTo.some((scope) => scopes.has(scope))
|
|
2102
2200
|
);
|
|
2103
2201
|
}
|
|
2104
2202
|
function auditApplicationRequirements(applicationOrId) {
|
|
2105
|
-
const application = typeof applicationOrId === "string" ?
|
|
2203
|
+
const application = typeof applicationOrId === "string" ? fallbackApplication(applicationOrId) : applicationOrId;
|
|
2106
2204
|
const scopes = applicationRequirementScopes(application);
|
|
2107
2205
|
const findings = requirementsForApplication(application).map(
|
|
2108
2206
|
(requirement) => evaluateRequirement(application, requirement)
|
|
@@ -2115,10 +2213,12 @@ function auditApplicationRequirements(applicationOrId) {
|
|
|
2115
2213
|
findings
|
|
2116
2214
|
};
|
|
2117
2215
|
}
|
|
2118
|
-
function
|
|
2119
|
-
|
|
2120
|
-
(
|
|
2216
|
+
function auditApplicationEntries(applications) {
|
|
2217
|
+
return buildAuditReport(
|
|
2218
|
+
applications.map((application) => auditApplicationRequirements(application))
|
|
2121
2219
|
);
|
|
2220
|
+
}
|
|
2221
|
+
function buildAuditReport(applications) {
|
|
2122
2222
|
const migrationQueue = applications.flatMap(
|
|
2123
2223
|
(audit) => audit.findings.filter((finding2) => finding2.status !== "met").map((finding2) => ({
|
|
2124
2224
|
applicationId: audit.application.id,
|
|
@@ -3421,8 +3521,9 @@ var TopoloClient = class _TopoloClient {
|
|
|
3421
3521
|
});
|
|
3422
3522
|
}
|
|
3423
3523
|
/**
|
|
3424
|
-
*
|
|
3425
|
-
*
|
|
3524
|
+
* @deprecated Static fallback snapshot only. Use `searchServices()` (the
|
|
3525
|
+
* live credential-scoped registry) for anything that decides behavior; this
|
|
3526
|
+
* exists for offline packaging audits when the registry is unreachable.
|
|
3426
3527
|
*/
|
|
3427
3528
|
listBundledServices() {
|
|
3428
3529
|
const apps = Object.values(APPLICATIONS);
|
|
@@ -3813,6 +3914,52 @@ function mergeSignals(a, b) {
|
|
|
3813
3914
|
b.addEventListener("abort", onAbort, { once: true });
|
|
3814
3915
|
return controller.signal;
|
|
3815
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
|
+
}
|
|
3816
3963
|
var CrmModule = class {
|
|
3817
3964
|
constructor(client) {
|
|
3818
3965
|
this.client = client;
|
|
@@ -4079,57 +4226,8 @@ var TopoloHttpError2 = class extends TopoloSdkError2 {
|
|
|
4079
4226
|
}
|
|
4080
4227
|
};
|
|
4081
4228
|
var APP_API_URLS2 = {
|
|
4082
|
-
agent: "https://agent.topolo.app",
|
|
4083
4229
|
auth: "https://auth.topolo.app",
|
|
4084
|
-
|
|
4085
|
-
blog: "https://blog.topolo.app",
|
|
4086
|
-
books: "https://books.topolo.app",
|
|
4087
|
-
brand: "https://brand.topolo.app",
|
|
4088
|
-
bugfix: "https://bugfix.topolo.app",
|
|
4089
|
-
bytes: "https://bytes.topolo.app",
|
|
4090
|
-
calendar: "https://calendar.topolo.app",
|
|
4091
|
-
campaigns: "https://campaigns.topolo.app",
|
|
4092
|
-
capacity: "https://capacity.topolo.app",
|
|
4093
|
-
chat: "https://chat.topolo.app",
|
|
4094
|
-
commerce: "https://commerce.topolo.app",
|
|
4095
|
-
compose: "https://compose.topolo.app",
|
|
4096
|
-
consent: "https://consent.topolo.app",
|
|
4097
|
-
crm: "https://crm.topolo.app",
|
|
4098
|
-
design: "https://design.topolo.app",
|
|
4099
|
-
developers: "https://developers.topolo.app",
|
|
4100
|
-
director: "https://director.topolo.app",
|
|
4101
|
-
feed: "https://feed.topolo.app",
|
|
4102
|
-
flow: "https://flow.topolo.app",
|
|
4103
|
-
forecast: "https://forecast.topolo.app",
|
|
4104
|
-
forms: "https://forms.topolo.app",
|
|
4105
|
-
home: "https://home.topolo.app",
|
|
4106
|
-
insights: "https://insights.topolo.app",
|
|
4107
|
-
inventory: "https://inventory.topolo.app",
|
|
4108
|
-
learn: "https://learn.topolo.app",
|
|
4109
|
-
localize: "https://localize.topolo.app",
|
|
4110
|
-
mail: "https://mail.topolo.app",
|
|
4111
|
-
mdm: "https://mdm.topolo.app",
|
|
4112
|
-
messages: "https://messages.topolo.app",
|
|
4113
|
-
nexus: "https://nexus.topolo.app",
|
|
4114
|
-
notify: "https://notify.topolo.app",
|
|
4115
|
-
observability_watch: "https://observability-watch.topolo.app",
|
|
4116
|
-
one: "https://www.topolo.app",
|
|
4117
|
-
p2p: "https://p2p.topolo.app",
|
|
4118
|
-
pay: "https://pay.topolo.app",
|
|
4119
|
-
people: "https://people.topolo.app",
|
|
4120
|
-
"quro.api": "https://api.ol0.me",
|
|
4121
|
-
"quro.redirect": "https://ol0.me",
|
|
4122
|
-
roadmapper: "https://roadmapper.topolo.app",
|
|
4123
|
-
sign: "https://sign.topolo.app",
|
|
4124
|
-
social_studio: "https://studio.topolo.app",
|
|
4125
|
-
socialize: "https://socialize.topolo.app",
|
|
4126
|
-
spaces: "https://spaces.topolo.app",
|
|
4127
|
-
status: "https://status.topolo.app",
|
|
4128
|
-
success: "https://success.topolo.app",
|
|
4129
|
-
support: "https://support.topolo.app",
|
|
4130
|
-
survey: "https://survey.topolo.app",
|
|
4131
|
-
voice: "https://voice.topolo.app",
|
|
4132
|
-
web: "https://web.topolo.app"
|
|
4230
|
+
developers: "https://developers.topolo.app"
|
|
4133
4231
|
};
|
|
4134
4232
|
var DEFAULT_APP_URLS2 = APP_API_URLS2;
|
|
4135
4233
|
function resolveServiceUrl2(service, overrides) {
|
|
@@ -5055,7 +5153,7 @@ function parseQueryPairs(pairs) {
|
|
|
5055
5153
|
|
|
5056
5154
|
// src/commands/actions.ts
|
|
5057
5155
|
function registerActionsCommand(program2) {
|
|
5058
|
-
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) => {
|
|
5156
|
+
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) => {
|
|
5059
5157
|
const mode = resolveOutputMode(opts.json);
|
|
5060
5158
|
try {
|
|
5061
5159
|
const topolo = await buildClient();
|
|
@@ -5069,7 +5167,8 @@ function registerActionsCommand(program2) {
|
|
|
5069
5167
|
cursor: opts.cursor,
|
|
5070
5168
|
limit: parsePageSize(opts.limit)
|
|
5071
5169
|
});
|
|
5072
|
-
|
|
5170
|
+
const output2 = opts.detail ? result : { actions: result.actions.map(compactActionCatalogEntry), page: result.page };
|
|
5171
|
+
emit(output2, mode, () => formatActionRows(result.actions, result.page.nextCursor));
|
|
5073
5172
|
} catch (err) {
|
|
5074
5173
|
fail(err, mode);
|
|
5075
5174
|
}
|
|
@@ -5129,17 +5228,18 @@ function registerActionsCommand(program2) {
|
|
|
5129
5228
|
fail(err, mode);
|
|
5130
5229
|
}
|
|
5131
5230
|
});
|
|
5132
|
-
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) => {
|
|
5231
|
+
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) => {
|
|
5133
5232
|
const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
|
|
5134
5233
|
try {
|
|
5135
5234
|
const topolo = await requiredClient();
|
|
5136
5235
|
const result = await topolo.client.validateActionInput(action, parseJsonObject(opts.data || "{}"));
|
|
5137
|
-
|
|
5236
|
+
const output2 = detailRequested(opts.detail, command) ? result : compactActionValidation(result.action.actionId, result.validation);
|
|
5237
|
+
emit(output2, mode, () => result.validation.valid ? `Valid input for ${result.action.actionId}.` : result.validation.errors.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
|
|
5138
5238
|
} catch (err) {
|
|
5139
5239
|
fail(err, mode);
|
|
5140
5240
|
}
|
|
5141
5241
|
});
|
|
5142
|
-
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) => {
|
|
5242
|
+
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) => {
|
|
5143
5243
|
const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
|
|
5144
5244
|
try {
|
|
5145
5245
|
const topolo = await requiredClient();
|
|
@@ -5148,7 +5248,7 @@ function registerActionsCommand(program2) {
|
|
|
5148
5248
|
confirm: opts.confirm === true,
|
|
5149
5249
|
...resource ? { resource } : {}
|
|
5150
5250
|
});
|
|
5151
|
-
emit(plan, mode, () => formatActionPlan(plan));
|
|
5251
|
+
emit(detailRequested(opts.detail, command) ? plan : compactActionPlan(plan), mode, () => formatActionPlan(plan));
|
|
5152
5252
|
} catch (err) {
|
|
5153
5253
|
fail(err, mode);
|
|
5154
5254
|
}
|
|
@@ -5168,19 +5268,23 @@ function registerActionsCommand(program2) {
|
|
|
5168
5268
|
fail(err, mode);
|
|
5169
5269
|
}
|
|
5170
5270
|
});
|
|
5171
|
-
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) => {
|
|
5271
|
+
actions.command("capabilities").description("List every published action for one application (its capability contract)").option("-s, --service <service>", "App id, slug, or name").option("--detail", "Include full schemas and agent contracts").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts, command) => {
|
|
5172
5272
|
const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
|
|
5173
5273
|
try {
|
|
5174
5274
|
const topolo = await requiredClient();
|
|
5175
5275
|
const service = opts.service || command.parent?.opts().service;
|
|
5176
5276
|
if (!service?.trim()) throw new Error("--service is required for capability discovery");
|
|
5177
5277
|
const result = await topolo.client.searchActions({ service: service.trim(), limit: 100 });
|
|
5178
|
-
|
|
5278
|
+
const output2 = detailRequested(opts.detail, command) ? result : { actions: result.actions.map(compactActionCatalogEntry), page: result.page };
|
|
5279
|
+
emit(output2, mode, () => formatActionRows(result.actions, result.page.nextCursor));
|
|
5179
5280
|
} catch (err) {
|
|
5180
5281
|
fail(err, mode);
|
|
5181
5282
|
}
|
|
5182
5283
|
});
|
|
5183
5284
|
}
|
|
5285
|
+
function detailRequested(local, command) {
|
|
5286
|
+
return local === true || command.parent?.opts().detail === true;
|
|
5287
|
+
}
|
|
5184
5288
|
async function requiredClient() {
|
|
5185
5289
|
const topolo = await buildClient();
|
|
5186
5290
|
if (!topolo) throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
|
|
@@ -5284,7 +5388,7 @@ function formatActionPlan(plan) {
|
|
|
5284
5388
|
|
|
5285
5389
|
// src/commands/apps.ts
|
|
5286
5390
|
import { execSync } from "child_process";
|
|
5287
|
-
import { existsSync as
|
|
5391
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
5288
5392
|
import path6 from "path";
|
|
5289
5393
|
|
|
5290
5394
|
// src/commands/app-audit-local.ts
|
|
@@ -6523,12 +6627,6 @@ function normalizeContainerAppId(value, surfaceModel) {
|
|
|
6523
6627
|
function normalizeApplicationId(application) {
|
|
6524
6628
|
const raw = application.trim().toLowerCase();
|
|
6525
6629
|
const normalized = raw.replace(/-/g, "_");
|
|
6526
|
-
if (Object.prototype.hasOwnProperty.call(APPLICATIONS, raw)) {
|
|
6527
|
-
return raw;
|
|
6528
|
-
}
|
|
6529
|
-
if (Object.prototype.hasOwnProperty.call(APPLICATIONS, normalized)) {
|
|
6530
|
-
return normalized;
|
|
6531
|
-
}
|
|
6532
6630
|
if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(raw) || /^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$/.test(raw)) {
|
|
6533
6631
|
return normalized;
|
|
6534
6632
|
}
|
|
@@ -6593,7 +6691,7 @@ function firstPartyAppPath(appDir, _audience) {
|
|
|
6593
6691
|
}
|
|
6594
6692
|
|
|
6595
6693
|
// src/commands/app-scaffold-platform-versions.ts
|
|
6596
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
6694
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
6597
6695
|
import path2 from "path";
|
|
6598
6696
|
var versionCache = /* @__PURE__ */ new Map();
|
|
6599
6697
|
function platformPackagePin(packageName, options = {}) {
|
|
@@ -6601,7 +6699,7 @@ function platformPackagePin(packageName, options = {}) {
|
|
|
6601
6699
|
options.repoRoot ?? process.env.TOPOLO_WORKSPACE_ROOT ?? void 0,
|
|
6602
6700
|
options.cwd ?? process.cwd()
|
|
6603
6701
|
);
|
|
6604
|
-
if (!workspaceRoot) {
|
|
6702
|
+
if (!workspaceRoot || !existsSync3(path2.join(workspaceRoot, "topolo-platform", "packages"))) {
|
|
6605
6703
|
throw new Error(
|
|
6606
6704
|
`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".`
|
|
6607
6705
|
);
|
|
@@ -7148,6 +7246,7 @@ function readme(application, target, options) {
|
|
|
7148
7246
|
"- `@topolo-io/app-launcher` provides shared app switching and handoff helpers.",
|
|
7149
7247
|
"- `@topolo-io/ui-kit` provides every control, table, panel, workbench, inspector, icon, transition, and bug reporter primitive used by the generated product surface.",
|
|
7150
7248
|
"- `@topolo-io/auth-client` owns browser auth, callback exchange, token refresh, and logout.",
|
|
7249
|
+
'- 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.',
|
|
7151
7250
|
"- `@topolo-io/api-key-management`, `@topolo-io/chat-widget`, `@topolo-io/notifications`, and `@topolo-io/resource-context` complete the AppShell capability baseline.",
|
|
7152
7251
|
"- Every Topolo-owned browser dependency is emitted at `latest`; product-domain packages are added only when the app actually adopts that domain.",
|
|
7153
7252
|
"- `.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.",
|
|
@@ -8714,7 +8813,7 @@ function developerWorkerTs(application) {
|
|
|
8714
8813
|
}
|
|
8715
8814
|
|
|
8716
8815
|
// src/commands/app-scaffold-generate.ts
|
|
8717
|
-
import { existsSync as
|
|
8816
|
+
import { existsSync as existsSync4, mkdirSync, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync } from "fs";
|
|
8718
8817
|
import { spawnSync } from "child_process";
|
|
8719
8818
|
import { tmpdir } from "os";
|
|
8720
8819
|
import path3 from "path";
|
|
@@ -8728,7 +8827,7 @@ function resolveGeneratorPath() {
|
|
|
8728
8827
|
];
|
|
8729
8828
|
for (const candidate of candidates) {
|
|
8730
8829
|
const p = decodeURIComponent(candidate.pathname);
|
|
8731
|
-
if (
|
|
8830
|
+
if (existsSync4(p)) return p;
|
|
8732
8831
|
}
|
|
8733
8832
|
throw new Error(
|
|
8734
8833
|
`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:
|
|
@@ -9914,7 +10013,7 @@ function applicationScaffoldPayload(application, options = {}) {
|
|
|
9914
10013
|
const requestedId = normalizeApplicationId(application);
|
|
9915
10014
|
const id = target === "platform" ? normalizeFirstPartyApplicationId(requestedId) : requestedId;
|
|
9916
10015
|
const slug = toSlug(id);
|
|
9917
|
-
const catalogApplication =
|
|
10016
|
+
const catalogApplication = options.catalogApplication ?? null;
|
|
9918
10017
|
const browser = options.browser !== false;
|
|
9919
10018
|
const api = options.api ?? target === "platform";
|
|
9920
10019
|
const audience = normalizeAudience(
|
|
@@ -11550,7 +11649,6 @@ function registerApplicationReconcileCommand(parent, ownerType, dependencies = {
|
|
|
11550
11649
|
}
|
|
11551
11650
|
|
|
11552
11651
|
// src/commands/apps.ts
|
|
11553
|
-
var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
|
|
11554
11652
|
var CLOUDCONTROL_LOCAL_APPLICATION = {
|
|
11555
11653
|
id: "cloudcontrol",
|
|
11556
11654
|
appDir: "TopoloCloudControl",
|
|
@@ -11560,6 +11658,21 @@ var CLOUDCONTROL_LOCAL_APPLICATION = {
|
|
|
11560
11658
|
services: [],
|
|
11561
11659
|
deployTargets: []
|
|
11562
11660
|
};
|
|
11661
|
+
async function directoryClient() {
|
|
11662
|
+
try {
|
|
11663
|
+
const topolo = await buildClient();
|
|
11664
|
+
return topolo ? topolo.client : null;
|
|
11665
|
+
} catch {
|
|
11666
|
+
return null;
|
|
11667
|
+
}
|
|
11668
|
+
}
|
|
11669
|
+
async function resolveScaffoldCatalogApplication(application) {
|
|
11670
|
+
try {
|
|
11671
|
+
return await resolveApplication(application, await directoryClient());
|
|
11672
|
+
} catch {
|
|
11673
|
+
return null;
|
|
11674
|
+
}
|
|
11675
|
+
}
|
|
11563
11676
|
function registerAppsCommand(program2) {
|
|
11564
11677
|
const apps = program2.command("apps").description("Search Topolo platform applications available to the current credential").option("-q, --query <query>", "Filter by app id, slug, or name").option("--cursor <cursor>", "Continuation cursor from the previous page").option("--limit <limit>", "Page size (1-100)", "25").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action(async (opts) => {
|
|
11565
11678
|
const mode = resolveOutputMode(opts.json);
|
|
@@ -11597,10 +11710,10 @@ function registerAppsCommand(program2) {
|
|
|
11597
11710
|
});
|
|
11598
11711
|
apps.command("requirements [application]").description(
|
|
11599
11712
|
"Show the versioned Topolo application-build requirements, optionally filtered to one app."
|
|
11600
|
-
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action((application, opts) => {
|
|
11713
|
+
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action(async (application, opts) => {
|
|
11601
11714
|
const mode = resolveOutputMode(opts.json);
|
|
11602
11715
|
try {
|
|
11603
|
-
const payload = applicationRequirementsPayload(application);
|
|
11716
|
+
const payload = await applicationRequirementsPayload(application, await directoryClient());
|
|
11604
11717
|
emit(payload, mode, () => formatRequirements(payload));
|
|
11605
11718
|
} catch (err) {
|
|
11606
11719
|
fail(err, mode);
|
|
@@ -11608,19 +11721,21 @@ function registerAppsCommand(program2) {
|
|
|
11608
11721
|
});
|
|
11609
11722
|
apps.command("audit [application]").description(
|
|
11610
11723
|
"Audit Topolo applications against the versioned application-build requirements."
|
|
11611
|
-
).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(
|
|
11724
|
+
).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(
|
|
11612
11725
|
"--fail-on <status>",
|
|
11613
11726
|
"Exit non-zero when audit has findings at this severity or worse: missing, needs_review, partial"
|
|
11614
|
-
).action((application, opts) => {
|
|
11727
|
+
).action(async (application, opts) => {
|
|
11615
11728
|
const mode = resolveOutputMode(opts.json);
|
|
11616
11729
|
try {
|
|
11617
|
-
const payload = applicationAuditPayload(application, {
|
|
11730
|
+
const payload = await applicationAuditPayload(application, {
|
|
11618
11731
|
local: opts.local,
|
|
11619
|
-
repoRoot: opts.repoRoot
|
|
11732
|
+
repoRoot: opts.repoRoot,
|
|
11733
|
+
client: await directoryClient()
|
|
11620
11734
|
});
|
|
11621
11735
|
const gate = opts.failOn ? evaluateApplicationAuditGate(payload, opts.failOn) : null;
|
|
11622
|
-
const
|
|
11623
|
-
|
|
11736
|
+
const compact = opts.detail ? payload : compactApplicationAudit(payload);
|
|
11737
|
+
const output2 = gate ? { ...compact, conformanceGate: gate } : compact;
|
|
11738
|
+
emit(output2, mode, () => formatAudit(gate ? { ...payload, conformanceGate: gate } : payload));
|
|
11624
11739
|
if (gate && !gate.passed) {
|
|
11625
11740
|
if (mode === "json") {
|
|
11626
11741
|
process.stderr.write(JSON.stringify({ error: gate.message, conformanceGate: gate }, null, 2) + "\n");
|
|
@@ -11636,10 +11751,13 @@ function registerAppsCommand(program2) {
|
|
|
11636
11751
|
});
|
|
11637
11752
|
apps.command("record-evidence <application>").description(
|
|
11638
11753
|
"Stamp <repo>/.topolo/evidence.json with the latest CI gate results so the audit can flip verification-gates / observability-and-audit / canonical-docs from partial to met when the recorded run is fresh (< 24h)."
|
|
11639
|
-
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable summary").option("--app-root <path>", "Path to the application repo root (defaults to cwd)").option("--build", "Mark the latest build as successful").option("--typecheck", "Mark the latest typecheck as successful").option("--test", "Mark the latest test run as successful").option("--docs-validate", "Mark the latest TopoloDocs npm run validate as successful").option("--smoke <url>", "Mark the latest live smoke as successful against this URL").option("--commit <sha>", "Override the commit hash (defaults to git HEAD)").action((application, opts) => {
|
|
11754
|
+
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable summary").option("--app-root <path>", "Path to the application repo root (defaults to cwd)").option("--build", "Mark the latest build as successful").option("--typecheck", "Mark the latest typecheck as successful").option("--test", "Mark the latest test run as successful").option("--docs-validate", "Mark the latest TopoloDocs npm run validate as successful").option("--smoke <url>", "Mark the latest live smoke as successful against this URL").option("--commit <sha>", "Override the commit hash (defaults to git HEAD)").action(async (application, opts) => {
|
|
11640
11755
|
const mode = resolveOutputMode(opts.json);
|
|
11641
11756
|
try {
|
|
11642
|
-
const payload = recordApplicationEvidence(application,
|
|
11757
|
+
const payload = await recordApplicationEvidence(application, {
|
|
11758
|
+
...opts,
|
|
11759
|
+
client: await directoryClient()
|
|
11760
|
+
});
|
|
11643
11761
|
emit(payload, mode, () => formatEvidence(payload));
|
|
11644
11762
|
} catch (err) {
|
|
11645
11763
|
fail(err, mode);
|
|
@@ -11661,6 +11779,7 @@ function registerAppsCommand(program2) {
|
|
|
11661
11779
|
try {
|
|
11662
11780
|
const api = resolveScaffoldApiOption(opts.api, opts.withApi);
|
|
11663
11781
|
const payload = applicationScaffoldPayload(application, {
|
|
11782
|
+
catalogApplication: await resolveScaffoldCatalogApplication(application),
|
|
11664
11783
|
appDir: opts.appDir,
|
|
11665
11784
|
name: opts.name,
|
|
11666
11785
|
packageName: opts.packageName,
|
|
@@ -11722,14 +11841,11 @@ function parsePageSize2(value) {
|
|
|
11722
11841
|
}
|
|
11723
11842
|
return parsed;
|
|
11724
11843
|
}
|
|
11725
|
-
function resolveApplication(application) {
|
|
11726
|
-
const
|
|
11727
|
-
|
|
11728
|
-
throw new Error(`Unknown application "${application}". ${formatKnownValues(APPLICATION_IDS, "applications")}`);
|
|
11729
|
-
}
|
|
11730
|
-
return APPLICATIONS[id];
|
|
11844
|
+
async function resolveApplication(application, client = null) {
|
|
11845
|
+
const resolved = await resolveApplicationDirectoryEntry(client, application);
|
|
11846
|
+
return resolved.application;
|
|
11731
11847
|
}
|
|
11732
|
-
function applicationRequirementsPayload(application) {
|
|
11848
|
+
async function applicationRequirementsPayload(application, client = null) {
|
|
11733
11849
|
if (!application) {
|
|
11734
11850
|
return {
|
|
11735
11851
|
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
@@ -11738,7 +11854,7 @@ function applicationRequirementsPayload(application) {
|
|
|
11738
11854
|
requirements: [...APPLICATION_REQUIREMENTS]
|
|
11739
11855
|
};
|
|
11740
11856
|
}
|
|
11741
|
-
const app = resolveApplication(application);
|
|
11857
|
+
const app = await resolveApplication(application, client);
|
|
11742
11858
|
return {
|
|
11743
11859
|
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
11744
11860
|
application: app,
|
|
@@ -11746,20 +11862,23 @@ function applicationRequirementsPayload(application) {
|
|
|
11746
11862
|
requirements: requirementsForApplication(app)
|
|
11747
11863
|
};
|
|
11748
11864
|
}
|
|
11749
|
-
function applicationAuditPayload(application, options = {}) {
|
|
11865
|
+
async function applicationAuditPayload(application, options = {}) {
|
|
11866
|
+
const client = options.client ?? null;
|
|
11750
11867
|
const requestedId = application?.trim().toLowerCase();
|
|
11751
11868
|
const report = requestedId === "cloudcontrol" && options.local ? {
|
|
11752
11869
|
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
11753
11870
|
applications: [auditApplicationRequirements(CLOUDCONTROL_LOCAL_APPLICATION)],
|
|
11754
11871
|
migrationQueue: []
|
|
11755
|
-
} : application ?
|
|
11872
|
+
} : application ? auditApplicationEntries([await resolveApplication(application, client)]) : auditApplicationEntries(
|
|
11873
|
+
(await listApplicationDirectory(client)).applications.map((app) => app.application)
|
|
11874
|
+
);
|
|
11756
11875
|
if (!options.local) return report;
|
|
11757
11876
|
return enrichApplicationAuditWithLocalEvidence(report, { repoRoot: options.repoRoot });
|
|
11758
11877
|
}
|
|
11759
|
-
function recordApplicationEvidence(application, options = {}) {
|
|
11760
|
-
const appId = resolveApplication(application).id;
|
|
11878
|
+
async function recordApplicationEvidence(application, options = {}) {
|
|
11879
|
+
const appId = (await resolveApplication(application, options.client ?? null)).id;
|
|
11761
11880
|
const root = path6.resolve(options.appRoot ?? process.cwd());
|
|
11762
|
-
if (!
|
|
11881
|
+
if (!existsSync5(root)) {
|
|
11763
11882
|
throw new Error(`Application root does not exist: ${root}`);
|
|
11764
11883
|
}
|
|
11765
11884
|
let commit = options.commit;
|
|
@@ -11773,7 +11892,7 @@ function recordApplicationEvidence(application, options = {}) {
|
|
|
11773
11892
|
const evidenceDir = path6.join(root, ".topolo");
|
|
11774
11893
|
const evidencePath = path6.join(evidenceDir, "evidence.json");
|
|
11775
11894
|
let before = null;
|
|
11776
|
-
if (
|
|
11895
|
+
if (existsSync5(evidencePath)) {
|
|
11777
11896
|
try {
|
|
11778
11897
|
const raw = JSON.parse(readFileSync6(evidencePath, "utf8"));
|
|
11779
11898
|
if (raw && typeof raw === "object" && raw.version === 1) {
|
|
@@ -11888,22 +12007,7 @@ function applicationUrl(application) {
|
|
|
11888
12007
|
return application.apiBaseUrl ?? application.launchUrl ?? application.productionUrl ?? null;
|
|
11889
12008
|
}
|
|
11890
12009
|
function displayCatalogId(entry) {
|
|
11891
|
-
|
|
11892
|
-
if (typeof value !== "string" || !value.trim()) continue;
|
|
11893
|
-
const candidates = catalogIdCandidates(value);
|
|
11894
|
-
const withoutTopolo = candidates.find((candidate) => !candidate.startsWith("topolo-"));
|
|
11895
|
-
if (withoutTopolo) return withoutTopolo;
|
|
11896
|
-
if (candidates[0]) return candidates[0];
|
|
11897
|
-
}
|
|
11898
|
-
return entry.appId;
|
|
11899
|
-
}
|
|
11900
|
-
function catalogIdCandidates(value) {
|
|
11901
|
-
const normalized = value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
11902
|
-
const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
|
|
11903
|
-
return [.../* @__PURE__ */ new Set([
|
|
11904
|
-
normalized,
|
|
11905
|
-
withoutTopolo
|
|
11906
|
-
])].filter(Boolean);
|
|
12010
|
+
return friendlyAppId(entry);
|
|
11907
12011
|
}
|
|
11908
12012
|
function formatRequirements(payload) {
|
|
11909
12013
|
const lines = [
|
|
@@ -12067,12 +12171,6 @@ function normalizeFailOn(value) {
|
|
|
12067
12171
|
}
|
|
12068
12172
|
throw new Error("Invalid --fail-on value. Use missing, needs_review, or partial.");
|
|
12069
12173
|
}
|
|
12070
|
-
function formatKnownValues(values, label) {
|
|
12071
|
-
const sorted = [...values].sort();
|
|
12072
|
-
const preview = sorted.slice(0, 25).join(", ");
|
|
12073
|
-
const suffix = sorted.length > 25 ? `, ... +${sorted.length - 25} more` : "";
|
|
12074
|
-
return `Run discovery for the full ${label} list. Known snapshot sample: ${preview}${suffix}`;
|
|
12075
|
-
}
|
|
12076
12174
|
function statusesAtOrWorse(threshold) {
|
|
12077
12175
|
if (threshold === "missing") return ["missing"];
|
|
12078
12176
|
if (threshold === "needs_review") return ["missing", "needs_review"];
|
|
@@ -12274,6 +12372,7 @@ function registerPlatformCommand(program2) {
|
|
|
12274
12372
|
}
|
|
12275
12373
|
const api = resolveScaffoldApiOption(opts.api, opts.withApi);
|
|
12276
12374
|
const payload = applicationScaffoldPayload(application, {
|
|
12375
|
+
catalogApplication: await resolveScaffoldCatalogApplication(application),
|
|
12277
12376
|
appDir: opts.appDir,
|
|
12278
12377
|
name: opts.name,
|
|
12279
12378
|
packageName: opts.packageName,
|
|
@@ -12525,15 +12624,7 @@ function isStagingUrl(value) {
|
|
|
12525
12624
|
return url.includes(".stg.topolo.us") || url.includes("localhost") || url.includes("127.0.0.1") || url.includes("topolo-staging");
|
|
12526
12625
|
}
|
|
12527
12626
|
function displayAppId(entry) {
|
|
12528
|
-
|
|
12529
|
-
if (typeof value !== "string" || !value.trim()) continue;
|
|
12530
|
-
for (const candidate of appIdCandidates(value)) {
|
|
12531
|
-
if (Object.prototype.hasOwnProperty.call(DEFAULT_APP_URLS, candidate)) {
|
|
12532
|
-
return candidate;
|
|
12533
|
-
}
|
|
12534
|
-
}
|
|
12535
|
-
}
|
|
12536
|
-
return appIdCandidates(entry.slug || entry.appId)[0] || entry.appId;
|
|
12627
|
+
return friendlyAppId(entry);
|
|
12537
12628
|
}
|
|
12538
12629
|
function defaultServiceUrl(id, entry) {
|
|
12539
12630
|
return resolveCatalogServiceUrl(entry, { serviceKey: id });
|
|
@@ -13073,7 +13164,7 @@ function formatCheckResult(result) {
|
|
|
13073
13164
|
|
|
13074
13165
|
// src/commands/skills.ts
|
|
13075
13166
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
13076
|
-
import { existsSync as
|
|
13167
|
+
import { existsSync as existsSync6 } from "fs";
|
|
13077
13168
|
import { mkdir as mkdir4, copyFile, readFile as readFile7, writeFile as writeFile7, rm } from "fs/promises";
|
|
13078
13169
|
import { homedir as homedir2 } from "os";
|
|
13079
13170
|
import { dirname as dirname4, join as join5, resolve as resolve3 } from "path";
|
|
@@ -13085,7 +13176,7 @@ function skillsSourceDir() {
|
|
|
13085
13176
|
const distCandidate = resolve3(here, "../skills");
|
|
13086
13177
|
const srcCandidate = resolve3(here, "../../skills");
|
|
13087
13178
|
for (const candidate of [distCandidate, srcCandidate]) {
|
|
13088
|
-
if (
|
|
13179
|
+
if (existsSync6(join5(candidate, "topolo", "SKILL.md"))) return candidate;
|
|
13089
13180
|
}
|
|
13090
13181
|
return distCandidate;
|
|
13091
13182
|
}
|
|
@@ -13130,7 +13221,7 @@ function registerViaOfficialCli(cli, mcp, agentName) {
|
|
|
13130
13221
|
async function registerClaudeViaConfig(mcp) {
|
|
13131
13222
|
const path9 = join5(homedir2(), ".claude.json");
|
|
13132
13223
|
let config = {};
|
|
13133
|
-
if (
|
|
13224
|
+
if (existsSync6(path9)) {
|
|
13134
13225
|
try {
|
|
13135
13226
|
config = JSON.parse(await readFile7(path9, "utf8"));
|
|
13136
13227
|
} catch {
|
|
@@ -13151,7 +13242,7 @@ async function registerClaudeViaConfig(mcp) {
|
|
|
13151
13242
|
async function registerCodexViaConfig(mcp) {
|
|
13152
13243
|
const path9 = join5(homedir2(), ".codex", "config.toml");
|
|
13153
13244
|
let text = "";
|
|
13154
|
-
if (
|
|
13245
|
+
if (existsSync6(path9)) text = await readFile7(path9, "utf8");
|
|
13155
13246
|
text = text.replace(
|
|
13156
13247
|
/(^|\n)\[mcp_servers\.topolo(?:\.[a-z]+)?\][^\[]*/g,
|
|
13157
13248
|
(match) => match.startsWith("\n") ? "\n" : ""
|
|
@@ -13206,7 +13297,7 @@ function registerSkillsCommand(program2) {
|
|
|
13206
13297
|
const mode = resolveOutputMode(opts.json);
|
|
13207
13298
|
try {
|
|
13208
13299
|
const sourceDir = skillsSourceDir();
|
|
13209
|
-
if (!
|
|
13300
|
+
if (!existsSync6(join5(sourceDir, "topolo", "SKILL.md"))) {
|
|
13210
13301
|
throw new Error(
|
|
13211
13302
|
"Bundled skill file not found. Reinstall the CLI: npm install -g @topolo/cli"
|
|
13212
13303
|
);
|
|
@@ -13235,7 +13326,7 @@ function registerSkillsCommand(program2) {
|
|
|
13235
13326
|
const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
|
|
13236
13327
|
return {
|
|
13237
13328
|
agent,
|
|
13238
|
-
skillInstalled:
|
|
13329
|
+
skillInstalled: existsSync6(join5(skillDir, "SKILL.md")),
|
|
13239
13330
|
skillPath: join5(skillDir, "SKILL.md"),
|
|
13240
13331
|
cliAvailable: agentBinaryExists(agent)
|
|
13241
13332
|
};
|
|
@@ -13252,7 +13343,7 @@ function registerSkillsCommand(program2) {
|
|
|
13252
13343
|
for (const agent of resolveTargets(opts)) {
|
|
13253
13344
|
const skillDir = agent === "claude" ? claudeSkillDir() : codexSkillDir();
|
|
13254
13345
|
const skillFile = join5(skillDir, "SKILL.md");
|
|
13255
|
-
const skillRemoved =
|
|
13346
|
+
const skillRemoved = existsSync6(skillFile);
|
|
13256
13347
|
if (skillRemoved) await rm(skillFile, { force: true });
|
|
13257
13348
|
if (agentBinaryExists(agent)) {
|
|
13258
13349
|
spawnSync2(agent, ["mcp", "remove", "topolo"], { stdio: "ignore" });
|
|
@@ -13504,12 +13595,14 @@ function registerContextCommand(program2) {
|
|
|
13504
13595
|
|
|
13505
13596
|
// src/commands/resources.ts
|
|
13506
13597
|
function registerResourcesCommand(program2) {
|
|
13507
|
-
program2.command("resources
|
|
13598
|
+
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) => {
|
|
13508
13599
|
const mode = resolveOutputMode(opts.json);
|
|
13509
13600
|
try {
|
|
13510
13601
|
const topolo = await buildClient();
|
|
13511
13602
|
if (!topolo) throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
|
|
13512
|
-
const
|
|
13603
|
+
const selector = application?.trim() || opts.service?.trim();
|
|
13604
|
+
if (!selector) throw new Error("application or --service is required");
|
|
13605
|
+
const result = await topolo.client.listBindableResources(selector);
|
|
13513
13606
|
emit(result, mode, () => formatResources(result));
|
|
13514
13607
|
} catch (err) {
|
|
13515
13608
|
fail(err, mode);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"topoloPublishedVersion": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"topoloPublishedVersion": "0.7.1",
|
|
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/codex/AGENTS.md
CHANGED
|
@@ -4,7 +4,7 @@ This file teaches an agent how to interact with the Topolo platform via the `top
|
|
|
4
4
|
|
|
5
5
|
## What the platform is
|
|
6
6
|
|
|
7
|
-
Topolo is a multi-app SaaS platform. The CLI and MCP server both use `@topolo/sdk`;
|
|
7
|
+
Topolo is a multi-app SaaS platform. The CLI and MCP server both use `@topolo/sdk`; every application resolves from the live credential-scoped registry, so newly registered apps and API/technical services work immediately — there is no bundled application catalog to rely on or wait for.
|
|
8
8
|
|
|
9
9
|
- Use `topolo apps --json` or the MCP `topolo_search_applications` tool to discover platform applications, including browser-only Worker apps.
|
|
10
10
|
- Use `topolo apps requirements <application_id> --json` or the MCP `topolo_list_application_requirements` tool before creating or expanding a Topolo app.
|
|
@@ -12,7 +12,7 @@ Topolo is a multi-app SaaS platform. The CLI and MCP server both use `@topolo/sd
|
|
|
12
12
|
- Use `topolo services --json` or the MCP `topolo_search_applications` tool to discover callable app ids and effective URLs.
|
|
13
13
|
- Use `topolo actions --service <app> --json` or MCP `topolo_search_actions` to discover callable operations granted to the current credential; invoke automatic reads with `topolo_read_action` and confirmation-gated operations with `topolo_call_action`.
|
|
14
14
|
- Use `topolo actions capabilities`, `actions examples`, `actions validate`, and `actions plan` (or their MCP equivalents) to obtain schemas, examples, verification, and rollback before execution.
|
|
15
|
-
- The generic `api` command and MCP `topolo_api_call` tool can reach
|
|
15
|
+
- The generic `api` command and MCP `topolo_api_call` tool can reach any live Auth-catalog application or API/technical service when no action contract exists yet; discover ids with `topolo apps --json` first.
|
|
16
16
|
|
|
17
17
|
Every call is scoped to the organization embedded in the credential. There is no `--org` flag anywhere — attempting to target another org will fail.
|
|
18
18
|
|
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
|