@topolo/cli 0.6.7 → 0.6.10

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 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
@@ -754,7 +754,9 @@ var APPLICATIONS = {
754
754
  "domains": [
755
755
  "developers.topolo.app"
756
756
  ],
757
- "routes": [],
757
+ "routes": [
758
+ "developers.topolo.app"
759
+ ],
758
760
  "appId": "developers"
759
761
  },
760
762
  {
@@ -1442,7 +1444,9 @@ var APPLICATIONS = {
1442
1444
  "domains": [
1443
1445
  "people.topolo.app"
1444
1446
  ],
1445
- "routes": [],
1447
+ "routes": [
1448
+ "people.topolo.app"
1449
+ ],
1446
1450
  "appId": "people"
1447
1451
  },
1448
1452
  {
@@ -1521,7 +1525,9 @@ var APPLICATIONS = {
1521
1525
  "domains": [
1522
1526
  "roadmapper.topolo.app"
1523
1527
  ],
1524
- "routes": [],
1528
+ "routes": [
1529
+ "roadmapper.topolo.app"
1530
+ ],
1525
1531
  "appId": "roadmapper"
1526
1532
  },
1527
1533
  {
@@ -1551,7 +1557,9 @@ var APPLICATIONS = {
1551
1557
  "domains": [
1552
1558
  "sign.topolo.app"
1553
1559
  ],
1554
- "routes": [],
1560
+ "routes": [
1561
+ "sign.topolo.app"
1562
+ ],
1555
1563
  "appId": "sign"
1556
1564
  },
1557
1565
  {
@@ -1665,7 +1673,9 @@ var APPLICATIONS = {
1665
1673
  "domains": [
1666
1674
  "spaces.topolo.app"
1667
1675
  ],
1668
- "routes": [],
1676
+ "routes": [
1677
+ "spaces.topolo.app"
1678
+ ],
1669
1679
  "appId": "spaces"
1670
1680
  },
1671
1681
  {
@@ -3725,10 +3735,19 @@ function mapActionInput(action, input3) {
3725
3735
  delete remaining[key];
3726
3736
  return encodeURIComponent(String(value));
3727
3737
  });
3728
- const explicitQuery = plainObject(remaining.query);
3729
- delete remaining.query;
3730
- const explicitBody = Object.prototype.hasOwnProperty.call(remaining, "body") ? remaining.body : void 0;
3731
- delete remaining.body;
3738
+ const schemaProperties = plainObject(plainObject(action.inputSchema).properties);
3739
+ const schemaOwnsQuery = Object.prototype.hasOwnProperty.call(schemaProperties, "query");
3740
+ const schemaOwnsBody = Object.prototype.hasOwnProperty.call(schemaProperties, "body");
3741
+ let explicitQuery = {};
3742
+ if (!schemaOwnsQuery) {
3743
+ explicitQuery = plainObject(remaining.query);
3744
+ delete remaining.query;
3745
+ }
3746
+ let explicitBody;
3747
+ if (!schemaOwnsBody) {
3748
+ explicitBody = Object.prototype.hasOwnProperty.call(remaining, "body") ? remaining.body : void 0;
3749
+ delete remaining.body;
3750
+ }
3732
3751
  if (action.method === "GET") {
3733
3752
  return {
3734
3753
  path: path9,
@@ -3852,6 +3871,65 @@ var IdentityModule = class {
3852
3871
  return this.client.introspect();
3853
3872
  }
3854
3873
  };
3874
+ var ACTIONS = {
3875
+ list: "app_topolo_auth.workspaces.list",
3876
+ create: "app_topolo_auth.workspaces.create",
3877
+ rename: "app_topolo_auth.workspaces.rename",
3878
+ setDefault: "app_topolo_auth.workspaces.default.set"
3879
+ };
3880
+ var WorkspacesModule = class {
3881
+ constructor(client) {
3882
+ this.client = client;
3883
+ }
3884
+ client;
3885
+ async list(application) {
3886
+ const context = await this.resolveContext(application);
3887
+ return this.client.callAction(ACTIONS.list, {
3888
+ org_id: context.organizationId,
3889
+ app_id: context.appId
3890
+ });
3891
+ }
3892
+ async create(application, input3) {
3893
+ const current = await this.list(application);
3894
+ if (!current.data.policy.canCreate) {
3895
+ throw new TopoloSdkError(
3896
+ "workspace_creation_not_allowed",
3897
+ `Workspace creation is not allowed for ${current.data.appId}: ${current.data.policy.reason}`
3898
+ );
3899
+ }
3900
+ return this.client.callAction(ACTIONS.create, {
3901
+ organizationId: current.data.organizationId,
3902
+ appId: current.data.appId,
3903
+ name: input3.name,
3904
+ ...input3.isDefault === void 0 ? {} : { isDefault: input3.isDefault }
3905
+ }, { confirm: input3.confirm === true });
3906
+ }
3907
+ async rename(application, workspaceId, name, confirm = false) {
3908
+ const context = await this.resolveContext(application);
3909
+ return this.client.callAction(ACTIONS.rename, {
3910
+ workspaceId,
3911
+ organizationId: context.organizationId,
3912
+ appId: context.appId,
3913
+ name
3914
+ }, { confirm });
3915
+ }
3916
+ async setDefault(application, workspaceId, confirm = false) {
3917
+ const context = await this.resolveContext(application);
3918
+ return this.client.callAction(ACTIONS.setDefault, {
3919
+ workspaceId,
3920
+ organizationId: context.organizationId,
3921
+ appId: context.appId
3922
+ }, { confirm });
3923
+ }
3924
+ async resolveContext(application) {
3925
+ const identity = await this.client.introspect();
3926
+ if (!identity.organization?.id) {
3927
+ throw new TopoloSdkError("organization_required", "Workspace lifecycle requires an organization-scoped credential.");
3928
+ }
3929
+ const service = await this.client.getService(application);
3930
+ return { organizationId: identity.organization.id, appId: service.appId };
3931
+ }
3932
+ };
3855
3933
  var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
3856
3934
  var TopoloOAuth = class {
3857
3935
  baseUrl;
@@ -3935,6 +4013,7 @@ function createTopolo(options) {
3935
4013
  return {
3936
4014
  client,
3937
4015
  identity: new IdentityModule(client),
4016
+ workspaces: new WorkspacesModule(client),
3938
4017
  crm: new CrmModule(client)
3939
4018
  };
3940
4019
  }
@@ -5089,13 +5168,13 @@ function registerActionsCommand(program2) {
5089
5168
  fail(err, mode);
5090
5169
  }
5091
5170
  });
5092
- actions.command("capabilities").description("Discover capability and schema actions for one application").option("-s, --service <service>", "App id, slug, or name").option("--json", "Output JSON").option("--no-json", "Output human-readable table").action(async (opts, command) => {
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) => {
5093
5172
  const mode = resolveOutputMode(opts.json ?? command.parent?.opts().json);
5094
5173
  try {
5095
5174
  const topolo = await requiredClient();
5096
5175
  const service = opts.service || command.parent?.opts().service;
5097
5176
  if (!service?.trim()) throw new Error("--service is required for capability discovery");
5098
- const result = await topolo.client.searchActions({ service: service.trim(), query: "capabilities", limit: 100 });
5177
+ const result = await topolo.client.searchActions({ service: service.trim(), limit: 100 });
5099
5178
  emit(result, mode, () => formatActionRows(result.actions, result.page.nextCursor));
5100
5179
  } catch (err) {
5101
5180
  fail(err, mode);
@@ -13451,6 +13530,79 @@ function formatResources(result) {
13451
13530
  return lines.join("\n");
13452
13531
  }
13453
13532
 
13533
+ // src/commands/workspaces.ts
13534
+ function registerWorkspacesCommand(program2) {
13535
+ const workspaces = program2.command("workspaces").description("Discover and manage caller-authorized workspaces for one Topolo application");
13536
+ 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) => {
13537
+ const mode = resolveOutputMode(opts.json);
13538
+ try {
13539
+ const topolo = await requiredClient2();
13540
+ const result = await topolo.workspaces.list(application);
13541
+ emit(result, mode, () => formatWorkspaceList(result));
13542
+ } catch (error) {
13543
+ fail(error, mode);
13544
+ }
13545
+ });
13546
+ 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) => {
13547
+ const mode = resolveOutputMode(opts.json);
13548
+ try {
13549
+ const topolo = await requiredClient2();
13550
+ const result = await topolo.workspaces.create(application, {
13551
+ name: opts.name.trim(),
13552
+ isDefault: opts.default === true,
13553
+ confirm: opts.confirm === true
13554
+ });
13555
+ emit(result, mode, () => formatWorkspaceMutation(result));
13556
+ } catch (error) {
13557
+ fail(error, mode);
13558
+ }
13559
+ });
13560
+ 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) => {
13561
+ const mode = resolveOutputMode(opts.json);
13562
+ try {
13563
+ const topolo = await requiredClient2();
13564
+ const result = await topolo.workspaces.rename(application, workspaceId, opts.name.trim(), opts.confirm === true);
13565
+ emit(result, mode, () => formatWorkspaceMutation(result));
13566
+ } catch (error) {
13567
+ fail(error, mode);
13568
+ }
13569
+ });
13570
+ 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) => {
13571
+ const mode = resolveOutputMode(opts.json);
13572
+ try {
13573
+ const topolo = await requiredClient2();
13574
+ const result = await topolo.workspaces.setDefault(application, workspaceId, opts.confirm === true);
13575
+ emit(result, mode, () => formatWorkspaceMutation(result));
13576
+ } catch (error) {
13577
+ fail(error, mode);
13578
+ }
13579
+ });
13580
+ }
13581
+ async function requiredClient2() {
13582
+ const topolo = await buildClient();
13583
+ if (!topolo) throw new Error("No credential configured. Run `topolo auth login` or set TOPOLO_API_KEY.");
13584
+ return topolo;
13585
+ }
13586
+ function formatWorkspaceList(result) {
13587
+ const { data } = result;
13588
+ const lines = [
13589
+ `Application: ${data.appId}`,
13590
+ `Organization: ${data.organizationId}`,
13591
+ `Can create: ${data.policy.canCreate ? "yes" : "no"} (${data.policy.reason})`
13592
+ ];
13593
+ if (!data.workspaces.length) return [...lines, "", "No accessible workspaces."].join("\n");
13594
+ lines.push("", "DEFAULT ID NAME ACCESS");
13595
+ for (const workspace of data.workspaces) {
13596
+ lines.push(`${workspace.isDefault ? "yes" : "no"} ${workspace.id} ${workspace.name} ${workspace.accessLevel}`);
13597
+ }
13598
+ return lines.join("\n");
13599
+ }
13600
+ function formatWorkspaceMutation(result) {
13601
+ const workspace = result.data.workspace;
13602
+ return `${result.message}
13603
+ Workspace: ${workspace.name} (${workspace.id})${workspace.isDefault ? "\nDefault: yes" : ""}`;
13604
+ }
13605
+
13454
13606
  // src/index.ts
13455
13607
  var program = new Command();
13456
13608
  var argv;
@@ -13482,6 +13634,7 @@ registerSeedCommands(program);
13482
13634
  registerSkillsCommand(program);
13483
13635
  registerContextCommand(program);
13484
13636
  registerResourcesCommand(program);
13637
+ registerWorkspacesCommand(program);
13485
13638
  program.parseAsync(argv).catch((err) => {
13486
13639
  process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
13487
13640
  `);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
- "version": "0.6.7",
4
- "topoloPublishedVersion": "0.6.7",
3
+ "version": "0.6.10",
4
+ "topoloPublishedVersion": "0.6.10",
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": {
@@ -11,20 +11,6 @@
11
11
  "dist",
12
12
  "skills"
13
13
  ],
14
- "dependencies": {
15
- "commander": "^12.1.0"
16
- },
17
- "devDependencies": {
18
- "@topolo/sdk": "file:../topolo-sdk",
19
- "@types/node": "^20.12.0",
20
- "@vitest/coverage-v8": "3.2.6",
21
- "tsup": "^8.0.0",
22
- "typescript": "^5.4.0",
23
- "vitest": "3.2.6"
24
- },
25
- "engines": {
26
- "node": ">=20"
27
- },
28
14
  "scripts": {
29
15
  "build:sdk": "python3 ../../tooling/with-resource-lock.py ../topolo-sdk -- pnpm --dir ../topolo-sdk build",
30
16
  "prebuild": "pnpm run build:sdk",
@@ -38,5 +24,19 @@
38
24
  "test:watch": "vitest",
39
25
  "precoverage": "pnpm run build:sdk",
40
26
  "coverage": "vitest run --coverage"
27
+ },
28
+ "dependencies": {
29
+ "commander": "^12.1.0"
30
+ },
31
+ "devDependencies": {
32
+ "@topolo/sdk": "file:../topolo-sdk",
33
+ "@types/node": "^20.12.0",
34
+ "@vitest/coverage-v8": "3.2.6",
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.4.0",
37
+ "vitest": "3.2.6"
38
+ },
39
+ "engines": {
40
+ "node": ">=20"
41
41
  }
42
- }
42
+ }
@@ -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