@topolo/cli 0.6.8 → 0.7.0
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 +17 -1
- package/dist/index.js +353 -173
- package/package.json +2 -2
- package/skills/codex/AGENTS.md +2 -2
- package/skills/topolo/SKILL.md +12 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ via `stdin`/`stdout`.
|
|
|
12
12
|
- First-class `production`, `staging`, `development`, and `local` environments with separate stored credentials and service URL overrides.
|
|
13
13
|
- **Per-context credentials** — hold and switch between your personal space and multiple organizations with `topolo context`.
|
|
14
14
|
- **One-command agent setup** — `topolo skills install` wires the skill + MCP into Claude Code / Codex.
|
|
15
|
-
- Commands: `auth`, `whoami`, `context`, `skills`, `apps`, `services`, credential-scoped `actions`, generic `api` passthrough, and internal `platform` scaffolding.
|
|
15
|
+
- Commands: `auth`, `whoami`, `context`, `skills`, `apps`, `services`, `workspaces`, credential-scoped `actions`, generic `api` passthrough, and internal `platform` scaffolding.
|
|
16
16
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
@@ -177,6 +177,22 @@ Both resource flags are required together. This supports concurrent agent
|
|
|
177
177
|
operations across many app-local workspaces without changing the CLI/MCP org
|
|
178
178
|
context or the user's web switcher.
|
|
179
179
|
|
|
180
|
+
### `topolo workspaces`
|
|
181
|
+
|
|
182
|
+
Discovers canonical workspaces and the caller-specific creation policy for a
|
|
183
|
+
target application. Auth owns workspace identity, while its runtime policy
|
|
184
|
+
decides access separately for each app and caller.
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
topolo workspaces list design --json
|
|
188
|
+
topolo workspaces create design --name "Campaigns" --confirm --json
|
|
189
|
+
topolo workspaces rename design <workspace-id> --name "Social campaigns" --confirm --json
|
|
190
|
+
topolo workspaces default design <workspace-id> --confirm --json
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Always read `data.policy.canCreate` and `data.policy.reason` from `list` before
|
|
194
|
+
creation. Organization ownership is not treated as permission for every app.
|
|
195
|
+
|
|
180
196
|
### `topolo apps`
|
|
181
197
|
|
|
182
198
|
Lists launchable Topolo applications available to the current credential. This
|
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);
|
|
@@ -3735,10 +3836,19 @@ function mapActionInput(action, input3) {
|
|
|
3735
3836
|
delete remaining[key];
|
|
3736
3837
|
return encodeURIComponent(String(value));
|
|
3737
3838
|
});
|
|
3738
|
-
const
|
|
3739
|
-
|
|
3740
|
-
const
|
|
3741
|
-
|
|
3839
|
+
const schemaProperties = plainObject(plainObject(action.inputSchema).properties);
|
|
3840
|
+
const schemaOwnsQuery = Object.prototype.hasOwnProperty.call(schemaProperties, "query");
|
|
3841
|
+
const schemaOwnsBody = Object.prototype.hasOwnProperty.call(schemaProperties, "body");
|
|
3842
|
+
let explicitQuery = {};
|
|
3843
|
+
if (!schemaOwnsQuery) {
|
|
3844
|
+
explicitQuery = plainObject(remaining.query);
|
|
3845
|
+
delete remaining.query;
|
|
3846
|
+
}
|
|
3847
|
+
let explicitBody;
|
|
3848
|
+
if (!schemaOwnsBody) {
|
|
3849
|
+
explicitBody = Object.prototype.hasOwnProperty.call(remaining, "body") ? remaining.body : void 0;
|
|
3850
|
+
delete remaining.body;
|
|
3851
|
+
}
|
|
3742
3852
|
if (action.method === "GET") {
|
|
3743
3853
|
return {
|
|
3744
3854
|
path: path9,
|
|
@@ -3862,6 +3972,65 @@ var IdentityModule = class {
|
|
|
3862
3972
|
return this.client.introspect();
|
|
3863
3973
|
}
|
|
3864
3974
|
};
|
|
3975
|
+
var ACTIONS = {
|
|
3976
|
+
list: "app_topolo_auth.workspaces.list",
|
|
3977
|
+
create: "app_topolo_auth.workspaces.create",
|
|
3978
|
+
rename: "app_topolo_auth.workspaces.rename",
|
|
3979
|
+
setDefault: "app_topolo_auth.workspaces.default.set"
|
|
3980
|
+
};
|
|
3981
|
+
var WorkspacesModule = class {
|
|
3982
|
+
constructor(client) {
|
|
3983
|
+
this.client = client;
|
|
3984
|
+
}
|
|
3985
|
+
client;
|
|
3986
|
+
async list(application) {
|
|
3987
|
+
const context = await this.resolveContext(application);
|
|
3988
|
+
return this.client.callAction(ACTIONS.list, {
|
|
3989
|
+
org_id: context.organizationId,
|
|
3990
|
+
app_id: context.appId
|
|
3991
|
+
});
|
|
3992
|
+
}
|
|
3993
|
+
async create(application, input3) {
|
|
3994
|
+
const current = await this.list(application);
|
|
3995
|
+
if (!current.data.policy.canCreate) {
|
|
3996
|
+
throw new TopoloSdkError(
|
|
3997
|
+
"workspace_creation_not_allowed",
|
|
3998
|
+
`Workspace creation is not allowed for ${current.data.appId}: ${current.data.policy.reason}`
|
|
3999
|
+
);
|
|
4000
|
+
}
|
|
4001
|
+
return this.client.callAction(ACTIONS.create, {
|
|
4002
|
+
organizationId: current.data.organizationId,
|
|
4003
|
+
appId: current.data.appId,
|
|
4004
|
+
name: input3.name,
|
|
4005
|
+
...input3.isDefault === void 0 ? {} : { isDefault: input3.isDefault }
|
|
4006
|
+
}, { confirm: input3.confirm === true });
|
|
4007
|
+
}
|
|
4008
|
+
async rename(application, workspaceId, name, confirm = false) {
|
|
4009
|
+
const context = await this.resolveContext(application);
|
|
4010
|
+
return this.client.callAction(ACTIONS.rename, {
|
|
4011
|
+
workspaceId,
|
|
4012
|
+
organizationId: context.organizationId,
|
|
4013
|
+
appId: context.appId,
|
|
4014
|
+
name
|
|
4015
|
+
}, { confirm });
|
|
4016
|
+
}
|
|
4017
|
+
async setDefault(application, workspaceId, confirm = false) {
|
|
4018
|
+
const context = await this.resolveContext(application);
|
|
4019
|
+
return this.client.callAction(ACTIONS.setDefault, {
|
|
4020
|
+
workspaceId,
|
|
4021
|
+
organizationId: context.organizationId,
|
|
4022
|
+
appId: context.appId
|
|
4023
|
+
}, { confirm });
|
|
4024
|
+
}
|
|
4025
|
+
async resolveContext(application) {
|
|
4026
|
+
const identity = await this.client.introspect();
|
|
4027
|
+
if (!identity.organization?.id) {
|
|
4028
|
+
throw new TopoloSdkError("organization_required", "Workspace lifecycle requires an organization-scoped credential.");
|
|
4029
|
+
}
|
|
4030
|
+
const service = await this.client.getService(application);
|
|
4031
|
+
return { organizationId: identity.organization.id, appId: service.appId };
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
3865
4034
|
var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
3866
4035
|
var TopoloOAuth = class {
|
|
3867
4036
|
baseUrl;
|
|
@@ -3945,6 +4114,7 @@ function createTopolo(options) {
|
|
|
3945
4114
|
return {
|
|
3946
4115
|
client,
|
|
3947
4116
|
identity: new IdentityModule(client),
|
|
4117
|
+
workspaces: new WorkspacesModule(client),
|
|
3948
4118
|
crm: new CrmModule(client)
|
|
3949
4119
|
};
|
|
3950
4120
|
}
|
|
@@ -4010,57 +4180,8 @@ var TopoloHttpError2 = class extends TopoloSdkError2 {
|
|
|
4010
4180
|
}
|
|
4011
4181
|
};
|
|
4012
4182
|
var APP_API_URLS2 = {
|
|
4013
|
-
agent: "https://agent.topolo.app",
|
|
4014
4183
|
auth: "https://auth.topolo.app",
|
|
4015
|
-
|
|
4016
|
-
blog: "https://blog.topolo.app",
|
|
4017
|
-
books: "https://books.topolo.app",
|
|
4018
|
-
brand: "https://brand.topolo.app",
|
|
4019
|
-
bugfix: "https://bugfix.topolo.app",
|
|
4020
|
-
bytes: "https://bytes.topolo.app",
|
|
4021
|
-
calendar: "https://calendar.topolo.app",
|
|
4022
|
-
campaigns: "https://campaigns.topolo.app",
|
|
4023
|
-
capacity: "https://capacity.topolo.app",
|
|
4024
|
-
chat: "https://chat.topolo.app",
|
|
4025
|
-
commerce: "https://commerce.topolo.app",
|
|
4026
|
-
compose: "https://compose.topolo.app",
|
|
4027
|
-
consent: "https://consent.topolo.app",
|
|
4028
|
-
crm: "https://crm.topolo.app",
|
|
4029
|
-
design: "https://design.topolo.app",
|
|
4030
|
-
developers: "https://developers.topolo.app",
|
|
4031
|
-
director: "https://director.topolo.app",
|
|
4032
|
-
feed: "https://feed.topolo.app",
|
|
4033
|
-
flow: "https://flow.topolo.app",
|
|
4034
|
-
forecast: "https://forecast.topolo.app",
|
|
4035
|
-
forms: "https://forms.topolo.app",
|
|
4036
|
-
home: "https://home.topolo.app",
|
|
4037
|
-
insights: "https://insights.topolo.app",
|
|
4038
|
-
inventory: "https://inventory.topolo.app",
|
|
4039
|
-
learn: "https://learn.topolo.app",
|
|
4040
|
-
localize: "https://localize.topolo.app",
|
|
4041
|
-
mail: "https://mail.topolo.app",
|
|
4042
|
-
mdm: "https://mdm.topolo.app",
|
|
4043
|
-
messages: "https://messages.topolo.app",
|
|
4044
|
-
nexus: "https://nexus.topolo.app",
|
|
4045
|
-
notify: "https://notify.topolo.app",
|
|
4046
|
-
observability_watch: "https://observability-watch.topolo.app",
|
|
4047
|
-
one: "https://www.topolo.app",
|
|
4048
|
-
p2p: "https://p2p.topolo.app",
|
|
4049
|
-
pay: "https://pay.topolo.app",
|
|
4050
|
-
people: "https://people.topolo.app",
|
|
4051
|
-
"quro.api": "https://api.ol0.me",
|
|
4052
|
-
"quro.redirect": "https://ol0.me",
|
|
4053
|
-
roadmapper: "https://roadmapper.topolo.app",
|
|
4054
|
-
sign: "https://sign.topolo.app",
|
|
4055
|
-
social_studio: "https://studio.topolo.app",
|
|
4056
|
-
socialize: "https://socialize.topolo.app",
|
|
4057
|
-
spaces: "https://spaces.topolo.app",
|
|
4058
|
-
status: "https://status.topolo.app",
|
|
4059
|
-
success: "https://success.topolo.app",
|
|
4060
|
-
support: "https://support.topolo.app",
|
|
4061
|
-
survey: "https://survey.topolo.app",
|
|
4062
|
-
voice: "https://voice.topolo.app",
|
|
4063
|
-
web: "https://web.topolo.app"
|
|
4184
|
+
developers: "https://developers.topolo.app"
|
|
4064
4185
|
};
|
|
4065
4186
|
var DEFAULT_APP_URLS2 = APP_API_URLS2;
|
|
4066
4187
|
function resolveServiceUrl2(service, overrides) {
|
|
@@ -6454,12 +6575,6 @@ function normalizeContainerAppId(value, surfaceModel) {
|
|
|
6454
6575
|
function normalizeApplicationId(application) {
|
|
6455
6576
|
const raw = application.trim().toLowerCase();
|
|
6456
6577
|
const normalized = raw.replace(/-/g, "_");
|
|
6457
|
-
if (Object.prototype.hasOwnProperty.call(APPLICATIONS, raw)) {
|
|
6458
|
-
return raw;
|
|
6459
|
-
}
|
|
6460
|
-
if (Object.prototype.hasOwnProperty.call(APPLICATIONS, normalized)) {
|
|
6461
|
-
return normalized;
|
|
6462
|
-
}
|
|
6463
6578
|
if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(raw) || /^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$/.test(raw)) {
|
|
6464
6579
|
return normalized;
|
|
6465
6580
|
}
|
|
@@ -9845,7 +9960,7 @@ function applicationScaffoldPayload(application, options = {}) {
|
|
|
9845
9960
|
const requestedId = normalizeApplicationId(application);
|
|
9846
9961
|
const id = target === "platform" ? normalizeFirstPartyApplicationId(requestedId) : requestedId;
|
|
9847
9962
|
const slug = toSlug(id);
|
|
9848
|
-
const catalogApplication =
|
|
9963
|
+
const catalogApplication = options.catalogApplication ?? null;
|
|
9849
9964
|
const browser = options.browser !== false;
|
|
9850
9965
|
const api = options.api ?? target === "platform";
|
|
9851
9966
|
const audience = normalizeAudience(
|
|
@@ -11481,7 +11596,6 @@ function registerApplicationReconcileCommand(parent, ownerType, dependencies = {
|
|
|
11481
11596
|
}
|
|
11482
11597
|
|
|
11483
11598
|
// src/commands/apps.ts
|
|
11484
|
-
var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
|
|
11485
11599
|
var CLOUDCONTROL_LOCAL_APPLICATION = {
|
|
11486
11600
|
id: "cloudcontrol",
|
|
11487
11601
|
appDir: "TopoloCloudControl",
|
|
@@ -11491,6 +11605,21 @@ var CLOUDCONTROL_LOCAL_APPLICATION = {
|
|
|
11491
11605
|
services: [],
|
|
11492
11606
|
deployTargets: []
|
|
11493
11607
|
};
|
|
11608
|
+
async function directoryClient() {
|
|
11609
|
+
try {
|
|
11610
|
+
const topolo = await buildClient();
|
|
11611
|
+
return topolo ? topolo.client : null;
|
|
11612
|
+
} catch {
|
|
11613
|
+
return null;
|
|
11614
|
+
}
|
|
11615
|
+
}
|
|
11616
|
+
async function resolveScaffoldCatalogApplication(application) {
|
|
11617
|
+
try {
|
|
11618
|
+
return await resolveApplication(application, await directoryClient());
|
|
11619
|
+
} catch {
|
|
11620
|
+
return null;
|
|
11621
|
+
}
|
|
11622
|
+
}
|
|
11494
11623
|
function registerAppsCommand(program2) {
|
|
11495
11624
|
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) => {
|
|
11496
11625
|
const mode = resolveOutputMode(opts.json);
|
|
@@ -11528,10 +11657,10 @@ function registerAppsCommand(program2) {
|
|
|
11528
11657
|
});
|
|
11529
11658
|
apps.command("requirements [application]").description(
|
|
11530
11659
|
"Show the versioned Topolo application-build requirements, optionally filtered to one app."
|
|
11531
|
-
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action((application, opts) => {
|
|
11660
|
+
).option("--json", "Output JSON", true).option("--no-json", "Output human-readable details").action(async (application, opts) => {
|
|
11532
11661
|
const mode = resolveOutputMode(opts.json);
|
|
11533
11662
|
try {
|
|
11534
|
-
const payload = applicationRequirementsPayload(application);
|
|
11663
|
+
const payload = await applicationRequirementsPayload(application, await directoryClient());
|
|
11535
11664
|
emit(payload, mode, () => formatRequirements(payload));
|
|
11536
11665
|
} catch (err) {
|
|
11537
11666
|
fail(err, mode);
|
|
@@ -11542,12 +11671,13 @@ function registerAppsCommand(program2) {
|
|
|
11542
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(
|
|
11543
11672
|
"--fail-on <status>",
|
|
11544
11673
|
"Exit non-zero when audit has findings at this severity or worse: missing, needs_review, partial"
|
|
11545
|
-
).action((application, opts) => {
|
|
11674
|
+
).action(async (application, opts) => {
|
|
11546
11675
|
const mode = resolveOutputMode(opts.json);
|
|
11547
11676
|
try {
|
|
11548
|
-
const payload = applicationAuditPayload(application, {
|
|
11677
|
+
const payload = await applicationAuditPayload(application, {
|
|
11549
11678
|
local: opts.local,
|
|
11550
|
-
repoRoot: opts.repoRoot
|
|
11679
|
+
repoRoot: opts.repoRoot,
|
|
11680
|
+
client: await directoryClient()
|
|
11551
11681
|
});
|
|
11552
11682
|
const gate = opts.failOn ? evaluateApplicationAuditGate(payload, opts.failOn) : null;
|
|
11553
11683
|
const output2 = gate ? { ...payload, conformanceGate: gate } : payload;
|
|
@@ -11567,10 +11697,13 @@ function registerAppsCommand(program2) {
|
|
|
11567
11697
|
});
|
|
11568
11698
|
apps.command("record-evidence <application>").description(
|
|
11569
11699
|
"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)."
|
|
11570
|
-
).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) => {
|
|
11700
|
+
).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) => {
|
|
11571
11701
|
const mode = resolveOutputMode(opts.json);
|
|
11572
11702
|
try {
|
|
11573
|
-
const payload = recordApplicationEvidence(application,
|
|
11703
|
+
const payload = await recordApplicationEvidence(application, {
|
|
11704
|
+
...opts,
|
|
11705
|
+
client: await directoryClient()
|
|
11706
|
+
});
|
|
11574
11707
|
emit(payload, mode, () => formatEvidence(payload));
|
|
11575
11708
|
} catch (err) {
|
|
11576
11709
|
fail(err, mode);
|
|
@@ -11592,6 +11725,7 @@ function registerAppsCommand(program2) {
|
|
|
11592
11725
|
try {
|
|
11593
11726
|
const api = resolveScaffoldApiOption(opts.api, opts.withApi);
|
|
11594
11727
|
const payload = applicationScaffoldPayload(application, {
|
|
11728
|
+
catalogApplication: await resolveScaffoldCatalogApplication(application),
|
|
11595
11729
|
appDir: opts.appDir,
|
|
11596
11730
|
name: opts.name,
|
|
11597
11731
|
packageName: opts.packageName,
|
|
@@ -11653,14 +11787,11 @@ function parsePageSize2(value) {
|
|
|
11653
11787
|
}
|
|
11654
11788
|
return parsed;
|
|
11655
11789
|
}
|
|
11656
|
-
function resolveApplication(application) {
|
|
11657
|
-
const
|
|
11658
|
-
|
|
11659
|
-
throw new Error(`Unknown application "${application}". ${formatKnownValues(APPLICATION_IDS, "applications")}`);
|
|
11660
|
-
}
|
|
11661
|
-
return APPLICATIONS[id];
|
|
11790
|
+
async function resolveApplication(application, client = null) {
|
|
11791
|
+
const resolved = await resolveApplicationDirectoryEntry(client, application);
|
|
11792
|
+
return resolved.application;
|
|
11662
11793
|
}
|
|
11663
|
-
function applicationRequirementsPayload(application) {
|
|
11794
|
+
async function applicationRequirementsPayload(application, client = null) {
|
|
11664
11795
|
if (!application) {
|
|
11665
11796
|
return {
|
|
11666
11797
|
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
@@ -11669,7 +11800,7 @@ function applicationRequirementsPayload(application) {
|
|
|
11669
11800
|
requirements: [...APPLICATION_REQUIREMENTS]
|
|
11670
11801
|
};
|
|
11671
11802
|
}
|
|
11672
|
-
const app = resolveApplication(application);
|
|
11803
|
+
const app = await resolveApplication(application, client);
|
|
11673
11804
|
return {
|
|
11674
11805
|
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
11675
11806
|
application: app,
|
|
@@ -11677,18 +11808,21 @@ function applicationRequirementsPayload(application) {
|
|
|
11677
11808
|
requirements: requirementsForApplication(app)
|
|
11678
11809
|
};
|
|
11679
11810
|
}
|
|
11680
|
-
function applicationAuditPayload(application, options = {}) {
|
|
11811
|
+
async function applicationAuditPayload(application, options = {}) {
|
|
11812
|
+
const client = options.client ?? null;
|
|
11681
11813
|
const requestedId = application?.trim().toLowerCase();
|
|
11682
11814
|
const report = requestedId === "cloudcontrol" && options.local ? {
|
|
11683
11815
|
version: APPLICATION_REQUIREMENTS_VERSION,
|
|
11684
11816
|
applications: [auditApplicationRequirements(CLOUDCONTROL_LOCAL_APPLICATION)],
|
|
11685
11817
|
migrationQueue: []
|
|
11686
|
-
} : application ?
|
|
11818
|
+
} : application ? auditApplicationEntries([await resolveApplication(application, client)]) : auditApplicationEntries(
|
|
11819
|
+
(await listApplicationDirectory(client)).applications.map((app) => app.application)
|
|
11820
|
+
);
|
|
11687
11821
|
if (!options.local) return report;
|
|
11688
11822
|
return enrichApplicationAuditWithLocalEvidence(report, { repoRoot: options.repoRoot });
|
|
11689
11823
|
}
|
|
11690
|
-
function recordApplicationEvidence(application, options = {}) {
|
|
11691
|
-
const appId = resolveApplication(application).id;
|
|
11824
|
+
async function recordApplicationEvidence(application, options = {}) {
|
|
11825
|
+
const appId = (await resolveApplication(application, options.client ?? null)).id;
|
|
11692
11826
|
const root = path6.resolve(options.appRoot ?? process.cwd());
|
|
11693
11827
|
if (!existsSync4(root)) {
|
|
11694
11828
|
throw new Error(`Application root does not exist: ${root}`);
|
|
@@ -11819,22 +11953,7 @@ function applicationUrl(application) {
|
|
|
11819
11953
|
return application.apiBaseUrl ?? application.launchUrl ?? application.productionUrl ?? null;
|
|
11820
11954
|
}
|
|
11821
11955
|
function displayCatalogId(entry) {
|
|
11822
|
-
|
|
11823
|
-
if (typeof value !== "string" || !value.trim()) continue;
|
|
11824
|
-
const candidates = catalogIdCandidates(value);
|
|
11825
|
-
const withoutTopolo = candidates.find((candidate) => !candidate.startsWith("topolo-"));
|
|
11826
|
-
if (withoutTopolo) return withoutTopolo;
|
|
11827
|
-
if (candidates[0]) return candidates[0];
|
|
11828
|
-
}
|
|
11829
|
-
return entry.appId;
|
|
11830
|
-
}
|
|
11831
|
-
function catalogIdCandidates(value) {
|
|
11832
|
-
const normalized = value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
11833
|
-
const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
|
|
11834
|
-
return [.../* @__PURE__ */ new Set([
|
|
11835
|
-
normalized,
|
|
11836
|
-
withoutTopolo
|
|
11837
|
-
])].filter(Boolean);
|
|
11956
|
+
return friendlyAppId(entry);
|
|
11838
11957
|
}
|
|
11839
11958
|
function formatRequirements(payload) {
|
|
11840
11959
|
const lines = [
|
|
@@ -11998,12 +12117,6 @@ function normalizeFailOn(value) {
|
|
|
11998
12117
|
}
|
|
11999
12118
|
throw new Error("Invalid --fail-on value. Use missing, needs_review, or partial.");
|
|
12000
12119
|
}
|
|
12001
|
-
function formatKnownValues(values, label) {
|
|
12002
|
-
const sorted = [...values].sort();
|
|
12003
|
-
const preview = sorted.slice(0, 25).join(", ");
|
|
12004
|
-
const suffix = sorted.length > 25 ? `, ... +${sorted.length - 25} more` : "";
|
|
12005
|
-
return `Run discovery for the full ${label} list. Known snapshot sample: ${preview}${suffix}`;
|
|
12006
|
-
}
|
|
12007
12120
|
function statusesAtOrWorse(threshold) {
|
|
12008
12121
|
if (threshold === "missing") return ["missing"];
|
|
12009
12122
|
if (threshold === "needs_review") return ["missing", "needs_review"];
|
|
@@ -12205,6 +12318,7 @@ function registerPlatformCommand(program2) {
|
|
|
12205
12318
|
}
|
|
12206
12319
|
const api = resolveScaffoldApiOption(opts.api, opts.withApi);
|
|
12207
12320
|
const payload = applicationScaffoldPayload(application, {
|
|
12321
|
+
catalogApplication: await resolveScaffoldCatalogApplication(application),
|
|
12208
12322
|
appDir: opts.appDir,
|
|
12209
12323
|
name: opts.name,
|
|
12210
12324
|
packageName: opts.packageName,
|
|
@@ -12456,15 +12570,7 @@ function isStagingUrl(value) {
|
|
|
12456
12570
|
return url.includes(".stg.topolo.us") || url.includes("localhost") || url.includes("127.0.0.1") || url.includes("topolo-staging");
|
|
12457
12571
|
}
|
|
12458
12572
|
function displayAppId(entry) {
|
|
12459
|
-
|
|
12460
|
-
if (typeof value !== "string" || !value.trim()) continue;
|
|
12461
|
-
for (const candidate of appIdCandidates(value)) {
|
|
12462
|
-
if (Object.prototype.hasOwnProperty.call(DEFAULT_APP_URLS, candidate)) {
|
|
12463
|
-
return candidate;
|
|
12464
|
-
}
|
|
12465
|
-
}
|
|
12466
|
-
}
|
|
12467
|
-
return appIdCandidates(entry.slug || entry.appId)[0] || entry.appId;
|
|
12573
|
+
return friendlyAppId(entry);
|
|
12468
12574
|
}
|
|
12469
12575
|
function defaultServiceUrl(id, entry) {
|
|
12470
12576
|
return resolveCatalogServiceUrl(entry, { serviceKey: id });
|
|
@@ -13461,6 +13567,79 @@ function formatResources(result) {
|
|
|
13461
13567
|
return lines.join("\n");
|
|
13462
13568
|
}
|
|
13463
13569
|
|
|
13570
|
+
// src/commands/workspaces.ts
|
|
13571
|
+
function registerWorkspacesCommand(program2) {
|
|
13572
|
+
const workspaces = program2.command("workspaces").description("Discover and manage caller-authorized workspaces for one Topolo application");
|
|
13573
|
+
workspaces.command("list <application>").description("List workspaces and the caller-specific creation policy for one application").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (application, opts) => {
|
|
13574
|
+
const mode = resolveOutputMode(opts.json);
|
|
13575
|
+
try {
|
|
13576
|
+
const topolo = await requiredClient2();
|
|
13577
|
+
const result = await topolo.workspaces.list(application);
|
|
13578
|
+
emit(result, mode, () => formatWorkspaceList(result));
|
|
13579
|
+
} catch (error) {
|
|
13580
|
+
fail(error, mode);
|
|
13581
|
+
}
|
|
13582
|
+
});
|
|
13583
|
+
workspaces.command("create <application>").description("Create a workspace when the target application policy permits it").requiredOption("--name <name>", "Workspace display name").option("--default", "Make the new workspace the application default").option("--confirm", "Acknowledge workspace creation").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (application, opts) => {
|
|
13584
|
+
const mode = resolveOutputMode(opts.json);
|
|
13585
|
+
try {
|
|
13586
|
+
const topolo = await requiredClient2();
|
|
13587
|
+
const result = await topolo.workspaces.create(application, {
|
|
13588
|
+
name: opts.name.trim(),
|
|
13589
|
+
isDefault: opts.default === true,
|
|
13590
|
+
confirm: opts.confirm === true
|
|
13591
|
+
});
|
|
13592
|
+
emit(result, mode, () => formatWorkspaceMutation(result));
|
|
13593
|
+
} catch (error) {
|
|
13594
|
+
fail(error, mode);
|
|
13595
|
+
}
|
|
13596
|
+
});
|
|
13597
|
+
workspaces.command("rename <application> <workspace-id>").description("Rename a caller-authorized workspace without changing its stable id").requiredOption("--name <name>", "New workspace display name").option("--confirm", "Acknowledge workspace rename").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (application, workspaceId, opts) => {
|
|
13598
|
+
const mode = resolveOutputMode(opts.json);
|
|
13599
|
+
try {
|
|
13600
|
+
const topolo = await requiredClient2();
|
|
13601
|
+
const result = await topolo.workspaces.rename(application, workspaceId, opts.name.trim(), opts.confirm === true);
|
|
13602
|
+
emit(result, mode, () => formatWorkspaceMutation(result));
|
|
13603
|
+
} catch (error) {
|
|
13604
|
+
fail(error, mode);
|
|
13605
|
+
}
|
|
13606
|
+
});
|
|
13607
|
+
workspaces.command("default <application> <workspace-id>").description("Set the default workspace for one application").option("--confirm", "Acknowledge the default-workspace change").option("--json", "Output JSON").option("--no-json", "Output human-readable text").action(async (application, workspaceId, opts) => {
|
|
13608
|
+
const mode = resolveOutputMode(opts.json);
|
|
13609
|
+
try {
|
|
13610
|
+
const topolo = await requiredClient2();
|
|
13611
|
+
const result = await topolo.workspaces.setDefault(application, workspaceId, opts.confirm === true);
|
|
13612
|
+
emit(result, mode, () => formatWorkspaceMutation(result));
|
|
13613
|
+
} catch (error) {
|
|
13614
|
+
fail(error, mode);
|
|
13615
|
+
}
|
|
13616
|
+
});
|
|
13617
|
+
}
|
|
13618
|
+
async function requiredClient2() {
|
|
13619
|
+
const topolo = await buildClient();
|
|
13620
|
+
if (!topolo) throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
|
|
13621
|
+
return topolo;
|
|
13622
|
+
}
|
|
13623
|
+
function formatWorkspaceList(result) {
|
|
13624
|
+
const { data } = result;
|
|
13625
|
+
const lines = [
|
|
13626
|
+
`Application: ${data.appId}`,
|
|
13627
|
+
`Organization: ${data.organizationId}`,
|
|
13628
|
+
`Can create: ${data.policy.canCreate ? "yes" : "no"} (${data.policy.reason})`
|
|
13629
|
+
];
|
|
13630
|
+
if (!data.workspaces.length) return [...lines, "", "No accessible workspaces."].join("\n");
|
|
13631
|
+
lines.push("", "DEFAULT ID NAME ACCESS");
|
|
13632
|
+
for (const workspace of data.workspaces) {
|
|
13633
|
+
lines.push(`${workspace.isDefault ? "yes" : "no"} ${workspace.id} ${workspace.name} ${workspace.accessLevel}`);
|
|
13634
|
+
}
|
|
13635
|
+
return lines.join("\n");
|
|
13636
|
+
}
|
|
13637
|
+
function formatWorkspaceMutation(result) {
|
|
13638
|
+
const workspace = result.data.workspace;
|
|
13639
|
+
return `${result.message}
|
|
13640
|
+
Workspace: ${workspace.name} (${workspace.id})${workspace.isDefault ? "\nDefault: yes" : ""}`;
|
|
13641
|
+
}
|
|
13642
|
+
|
|
13464
13643
|
// src/index.ts
|
|
13465
13644
|
var program = new Command();
|
|
13466
13645
|
var argv;
|
|
@@ -13492,6 +13671,7 @@ registerSeedCommands(program);
|
|
|
13492
13671
|
registerSkillsCommand(program);
|
|
13493
13672
|
registerContextCommand(program);
|
|
13494
13673
|
registerResourcesCommand(program);
|
|
13674
|
+
registerWorkspacesCommand(program);
|
|
13495
13675
|
program.parseAsync(argv).catch((err) => {
|
|
13496
13676
|
process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
|
|
13497
13677
|
`);
|
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.0",
|
|
4
|
+
"topoloPublishedVersion": "0.7.0",
|
|
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
|
@@ -72,12 +72,24 @@ topolo resources <application> --json
|
|
|
72
72
|
|
|
73
73
|
Use the returned app id or slug. Read `apiKeyResources` to learn whether calls can be scoped to aliases such as a workspace, site, mailbox, brand, or venue. Use `topolo resources` to resolve the actual credential-scoped resource ids. With an API key it returns only the key's bound resources; with OAuth it uses the published Auth resource catalog. Never assume that an organization id is a valid app resource id.
|
|
74
74
|
|
|
75
|
+
Workspace lifecycle is canonically owned by Topolo Auth but authorized against the concrete target application. Never conclude that workspace provisioning is unavailable because the target app's own action catalog does not duplicate Auth actions. Always run:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
topolo workspaces list <application> --json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Its `data.policy.canCreate` and `data.policy.reason` are the caller-specific creation decision for that application. Organization ownership does not imply permission to create workspaces for every app. When permitted, the first-class mutations are `topolo workspaces create`, `topolo workspaces rename`, and `topolo workspaces default`; each still requires confirmation and Auth performs the final runtime authorization.
|
|
82
|
+
|
|
75
83
|
MCP equivalents:
|
|
76
84
|
|
|
77
85
|
- `topolo_search_applications`
|
|
78
86
|
- `topolo_get_application`
|
|
79
87
|
- `topolo_get_resource_types`
|
|
80
88
|
- `topolo_list_resources`
|
|
89
|
+
- `topolo_list_workspaces`
|
|
90
|
+
- `topolo_create_workspace`
|
|
91
|
+
- `topolo_rename_workspace`
|
|
92
|
+
- `topolo_set_default_workspace`
|
|
81
93
|
|
|
82
94
|
### 2. Discover credential-scoped actions
|
|
83
95
|
|