@topolo/cli 0.13.13 → 0.13.15
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 +153 -0
- package/agent-packs/templates/career/skills/topolo/SKILL.md +7 -0
- package/agent-packs/templates/clients/skills/topolo/SKILL.md +7 -0
- package/agent-packs/templates/creative/skills/topolo/SKILL.md +7 -0
- package/agent-packs/templates/founder/skills/topolo/SKILL.md +7 -0
- package/agent-packs/templates/learning/skills/topolo/SKILL.md +7 -0
- package/agent-packs/templates/personal/skills/topolo/SKILL.md +7 -0
- package/agent-packs/templates/research/skills/topolo/SKILL.md +7 -0
- package/agent-packs/templates/ventures/skills/topolo/SKILL.md +7 -0
- package/dist/index.js +1398 -135
- package/package.json +3 -3
- package/skills/topolo/SKILL.md +7 -0
package/README.md
CHANGED
|
@@ -114,6 +114,88 @@ The local installer does not edit JetBrains AI Assistant settings or web-only
|
|
|
114
114
|
connectors because those are managed through their own UI or remote connector
|
|
115
115
|
configuration. Use the generated native MCP entry as the import source there.
|
|
116
116
|
|
|
117
|
+
### Shared local MCP hub (opt-in)
|
|
118
|
+
|
|
119
|
+
Every stdio registration starts its own Node process in every agent thread, so
|
|
120
|
+
N threads x M pinned identities means N x M MCP processes. The shared hub runs
|
|
121
|
+
**one** loopback process that serves every identity at
|
|
122
|
+
`http://127.0.0.1:<port>/mcp/<name>`; Claude Code and Codex connect to it by URL.
|
|
123
|
+
Plain `topolo setup` keeps the stdio path; `--shared` is opt-in.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
topolo --env production setup --shared --name topolo-acme --context org:org_acme
|
|
127
|
+
topolo --env staging setup --shared --name topolo-acme-stg --context org:org_acme
|
|
128
|
+
topolo setup --shared --import-stdio # move every existing stdio topolo/topolo-* entry onto the hub
|
|
129
|
+
topolo doctor --shared # hub, token, unit, registrations, then MCP whoami per identity
|
|
130
|
+
topolo mcp serve [--port 7337] # what the keep-alive unit runs; foreground for debugging
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`setup --shared`:
|
|
134
|
+
|
|
135
|
+
1. Adds the identity (`{name, env, context}`, exactly what a stdio entry pins
|
|
136
|
+
with `TOPOLO_ENV` + `TOPOLO_CONTEXT`; omit `--context` only for the default
|
|
137
|
+
`topolo` identity that follows the active context) to `mcp-hub.json` next to
|
|
138
|
+
the credential store (`~/.config/topolo/mcp-hub.json`, or the directory of
|
|
139
|
+
`TOPOLO_CONFIG_PATH`). `--import-stdio` converts existing stdio entries and
|
|
140
|
+
skips ones the hub cannot serve (private `TOPOLO_CONFIG_PATH` profiles,
|
|
141
|
+
agent installations, invalid or conflicting definitions).
|
|
142
|
+
2. Creates `mcp-hub.token` (256-bit, mode 0600) once and reuses it.
|
|
143
|
+
3. Registers **every** hub identity with Claude Code and Codex at user scope,
|
|
144
|
+
replacing same-name stdio entries. `--project` is refused so the token can
|
|
145
|
+
never land in a repository file. Other hosts stay on stdio.
|
|
146
|
+
4. Installs a keep-alive unit running the exact Node binary and CLI entry that
|
|
147
|
+
ran setup (`--no-service` to skip): `~/Library/LaunchAgents/io.topolo.mcp-hub.plist`
|
|
148
|
+
(launchd, `KeepAlive`, logs to `~/Library/Logs/topolo-mcp-hub.log`) or
|
|
149
|
+
`~/.config/systemd/user/topolo-mcp-hub.service` (`Restart=always`). Only
|
|
150
|
+
`TOPOLO_CONFIG_PATH`/`XDG_CONFIG_HOME` are passed through; PATH and secrets
|
|
151
|
+
are not. With `--agent-home` the unit is written there but not loaded.
|
|
152
|
+
|
|
153
|
+
Exact entries written (token shown as `<token>`):
|
|
154
|
+
|
|
155
|
+
```jsonc
|
|
156
|
+
// ~/.claude.json (written 0600)
|
|
157
|
+
"mcpServers": {
|
|
158
|
+
"topolo-acme": {
|
|
159
|
+
"type": "http",
|
|
160
|
+
"url": "http://127.0.0.1:7337/mcp/topolo-acme",
|
|
161
|
+
"headers": { "Authorization": "Bearer <token>", "X-Topolo-Agent": "claude-code" }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```toml
|
|
167
|
+
# ~/.codex/config.toml (written 0600)
|
|
168
|
+
[mcp_servers.topolo-acme]
|
|
169
|
+
url = "http://127.0.0.1:7337/mcp/topolo-acme"
|
|
170
|
+
http_headers = { "Authorization" = "Bearer <token>", "X-Topolo-Agent" = "codex" }
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Codex streamable-HTTP servers accept `url`, `bearer_token_env_var`,
|
|
174
|
+
`http_headers` and `env_http_headers`; an inline `bearer_token` is rejected.
|
|
175
|
+
`http_headers` is used so the hub works when Codex is launched from a GUI with
|
|
176
|
+
no shell environment. To keep the token out of `config.toml`, replace the
|
|
177
|
+
`Authorization` header with `bearer_token_env_var = "TOPOLO_MCP_HUB_TOKEN"` and
|
|
178
|
+
export that variable (contents of `mcp-hub.token`) into Codex's environment.
|
|
179
|
+
|
|
180
|
+
Hub behaviour and security:
|
|
181
|
+
|
|
182
|
+
- Binds `127.0.0.1` only; there is no host option.
|
|
183
|
+
- Refuses any request whose `Host` is not `127.0.0.1`, `localhost` or `[::1]`
|
|
184
|
+
on the hub port, or that carries a non-loopback `Origin` (DNS rebinding and
|
|
185
|
+
browser cross-origin calls), before checking the bearer token
|
|
186
|
+
(constant-time). Only then is `/mcp/<name>` routed; `GET /health` lists
|
|
187
|
+
identity names.
|
|
188
|
+
- Each identity has its own handler (`@topolo/mcp/local`) that resolves and
|
|
189
|
+
refreshes its credential from the local store exactly as a pinned stdio MCP
|
|
190
|
+
does. Process-wide `TOPOLO_API_KEY`/`TOPOLO_ACCESS_TOKEN`/`TOPOLO_CONTEXT`,
|
|
191
|
+
request headers and other identities are never consulted; the hub token is
|
|
192
|
+
stripped before the request reaches an identity.
|
|
193
|
+
- Stateless Streamable HTTP (no session ids), so restarts never strand a client.
|
|
194
|
+
- `mcp-hub.json` is re-read when it changes; adding an identity needs no restart.
|
|
195
|
+
- Logs carry method, path, status and duration only.
|
|
196
|
+
- Rotating the token: delete `mcp-hub.token`, rerun `topolo setup --shared`,
|
|
197
|
+
then restart the agents.
|
|
198
|
+
|
|
117
199
|
## Cross-org isolation
|
|
118
200
|
|
|
119
201
|
The CLI **never accepts an `orgId` argument**. Every command is scoped to the
|
|
@@ -224,6 +306,77 @@ Both resource flags are required together. This supports concurrent agent
|
|
|
224
306
|
operations across many app-local workspaces without changing the CLI/MCP org
|
|
225
307
|
context or the user's web switcher.
|
|
226
308
|
|
|
309
|
+
### `topolo credentials write-custody`
|
|
310
|
+
|
|
311
|
+
Local CLI and stdio MCP replace credentials in action results with opaque
|
|
312
|
+
`topolo-secret:<uuid>` references held in the OS protected store (macOS
|
|
313
|
+
Keychain, Linux Secret Service, Windows DPAPI). Both use the same store service
|
|
314
|
+
and the same binding (environment, verified principal, organization and API key
|
|
315
|
+
identity), so a reference returned by a stdio MCP call can be used by the CLI
|
|
316
|
+
pinned to the same identity and environment. References expire after 30 minutes.
|
|
317
|
+
|
|
318
|
+
When a service such as the release controller needs a scoped API key in a
|
|
319
|
+
custody file, write it straight from the reference so no person or agent sees
|
|
320
|
+
the plaintext:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
export TOPOLO_CONTEXT=org:<organizationId>
|
|
324
|
+
created=$(topolo --env production actions call app_topolo_auth.api_keys.create \
|
|
325
|
+
--data '{"appId":"<appId>","name":"<label>","scopes":["<scope>"],"expiresInDays":90}' \
|
|
326
|
+
--confirm --detail --json) # data.key.secret is a topolo-secret reference
|
|
327
|
+
topolo --env production credentials write-custody "$(jq -r .data.key.secret <<<"$created")" \
|
|
328
|
+
--api-key-id "$(jq -r .data.key.id <<<"$created")" --app <appId> \
|
|
329
|
+
--out /absolute/custody/dir/production --confirm --json
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
The command:
|
|
333
|
+
|
|
334
|
+
1. Requires `--confirm`, an absolute `--out`, `--app` and `--api-key-id`.
|
|
335
|
+
Keep one key per environment in its own subdirectory (for example
|
|
336
|
+
`.../catalog-publication-credentials/<environment>/`). When the last path
|
|
337
|
+
component is an environment name it must equal the credential's environment
|
|
338
|
+
(`custody_environment_mismatch` otherwise), and `custody.json.environment`
|
|
339
|
+
is always the environment the credential, protected store and Auth calls
|
|
340
|
+
are bound to.
|
|
341
|
+
2. Creates `--out` (and any missing parents) with mode 0700, or accepts an existing 0700 directory owned
|
|
342
|
+
by the current user. Every path component must be a real directory (no
|
|
343
|
+
symbolic links); parents must be owned by root or the current user and must
|
|
344
|
+
not be group- or world-writable (root-owned sticky directories such as
|
|
345
|
+
`/tmp` are allowed).
|
|
346
|
+
3. Refuses to overwrite `api-key.json` or `custody.json` without `--replace`,
|
|
347
|
+
and refuses symbolic-link or non-regular destinations even with it.
|
|
348
|
+
4. Reads the key's metadata from Auth `app_topolo_auth.api_keys.list` with the
|
|
349
|
+
pinned credential and requires the same organization, the `--app`
|
|
350
|
+
application, status `active` and an unexpired key.
|
|
351
|
+
5. Resolves the reference through the SDK protected store with its binding
|
|
352
|
+
checks, then verifies the key against Auth introspection: it must be that
|
|
353
|
+
API key id, application, organization and scope set.
|
|
354
|
+
6. Writes, each 0600 through a temporary file and rename in the same directory:
|
|
355
|
+
- `api-key.json`: `{"schemaVersion":1,"apiKey":"<secret>"}`
|
|
356
|
+
- `custody.json`: `{schemaVersion, appId, scopes, organizationId, environment,
|
|
357
|
+
apiKeyId, expiresAt, keyDigest, createdAt}`. `expiresAt` and `createdAt`
|
|
358
|
+
are Auth's values unchanged (epoch seconds, `expiresAt` null for no
|
|
359
|
+
expiry); `keyDigest` is `sha256:<hex>` of the secret.
|
|
360
|
+
7. Deletes the local protected reference, like `actions release-secret`. This
|
|
361
|
+
does **not** revoke the upstream key; use `app_topolo_auth.api_keys.revoke`.
|
|
362
|
+
|
|
363
|
+
Output (JSON or text) carries only paths, metadata and the digest. Refusals use
|
|
364
|
+
stable `error.code` values: `custody_confirmation_required`,
|
|
365
|
+
`custody_invalid_input`, `custody_path_not_absolute`, `custody_environment_mismatch`, `custody_platform_unsupported`,
|
|
366
|
+
`custody_path_symlink`, `custody_path_not_directory`, `custody_path_not_regular`,
|
|
367
|
+
`custody_parent_insecure`, `custody_dir_insecure`, `custody_file_exists`,
|
|
368
|
+
`custody_organization_required`, `custody_metadata_unavailable`,
|
|
369
|
+
`custody_key_not_found`, `custody_app_mismatch`, `custody_organization_mismatch`,
|
|
370
|
+
`custody_key_inactive`, `custody_key_expired`, `custody_secret_unsupported_type`,
|
|
371
|
+
`custody_key_verification_failed`, `custody_key_mismatch`,
|
|
372
|
+
`custody_metadata_mismatch`, `custody_reference_release_failed` and
|
|
373
|
+
`custody_error_withheld`, plus the protected store's `invalid_action_secret_reference`,
|
|
374
|
+
`action_secret_unavailable` (unknown, or bound to another principal,
|
|
375
|
+
organization, API key or environment), `action_secret_expired`,
|
|
376
|
+
`action_secret_invalid` and `action_secret_identity_required`. A refusal before
|
|
377
|
+
step 6 writes no custody file and keeps the reference (step 2 may already have
|
|
378
|
+
created the empty 0700 directory).
|
|
379
|
+
|
|
227
380
|
### `topolo workspaces`
|
|
228
381
|
|
|
229
382
|
Discovers canonical workspaces and the caller-specific creation policy for a
|
|
@@ -270,6 +270,13 @@ Local deletion does not revoke the upstream credential; use its published
|
|
|
270
270
|
revocation action separately when required. Other SDK/remote hosts keep
|
|
271
271
|
redaction unless they explicitly provide a protected store.
|
|
272
272
|
|
|
273
|
+
The only supported way to put a referenced API key on disk is an
|
|
274
|
+
operator-approved custody file:
|
|
275
|
+
`topolo credentials write-custody <reference> --out <absolute-dir> --app <appId> --api-key-id <id> --confirm --json`
|
|
276
|
+
verifies the key against Auth, writes 0600 `api-key.json` and `custody.json`
|
|
277
|
+
(metadata and a SHA-256 digest only) into a 0700 directory, prints no secret,
|
|
278
|
+
and deletes the local reference without revoking the upstream key.
|
|
279
|
+
|
|
273
280
|
## Generic public API escape hatch
|
|
274
281
|
|
|
275
282
|
Only when public docs provide a complete contract:
|
|
@@ -270,6 +270,13 @@ Local deletion does not revoke the upstream credential; use its published
|
|
|
270
270
|
revocation action separately when required. Other SDK/remote hosts keep
|
|
271
271
|
redaction unless they explicitly provide a protected store.
|
|
272
272
|
|
|
273
|
+
The only supported way to put a referenced API key on disk is an
|
|
274
|
+
operator-approved custody file:
|
|
275
|
+
`topolo credentials write-custody <reference> --out <absolute-dir> --app <appId> --api-key-id <id> --confirm --json`
|
|
276
|
+
verifies the key against Auth, writes 0600 `api-key.json` and `custody.json`
|
|
277
|
+
(metadata and a SHA-256 digest only) into a 0700 directory, prints no secret,
|
|
278
|
+
and deletes the local reference without revoking the upstream key.
|
|
279
|
+
|
|
273
280
|
## Generic public API escape hatch
|
|
274
281
|
|
|
275
282
|
Only when public docs provide a complete contract:
|
|
@@ -270,6 +270,13 @@ Local deletion does not revoke the upstream credential; use its published
|
|
|
270
270
|
revocation action separately when required. Other SDK/remote hosts keep
|
|
271
271
|
redaction unless they explicitly provide a protected store.
|
|
272
272
|
|
|
273
|
+
The only supported way to put a referenced API key on disk is an
|
|
274
|
+
operator-approved custody file:
|
|
275
|
+
`topolo credentials write-custody <reference> --out <absolute-dir> --app <appId> --api-key-id <id> --confirm --json`
|
|
276
|
+
verifies the key against Auth, writes 0600 `api-key.json` and `custody.json`
|
|
277
|
+
(metadata and a SHA-256 digest only) into a 0700 directory, prints no secret,
|
|
278
|
+
and deletes the local reference without revoking the upstream key.
|
|
279
|
+
|
|
273
280
|
## Generic public API escape hatch
|
|
274
281
|
|
|
275
282
|
Only when public docs provide a complete contract:
|
|
@@ -270,6 +270,13 @@ Local deletion does not revoke the upstream credential; use its published
|
|
|
270
270
|
revocation action separately when required. Other SDK/remote hosts keep
|
|
271
271
|
redaction unless they explicitly provide a protected store.
|
|
272
272
|
|
|
273
|
+
The only supported way to put a referenced API key on disk is an
|
|
274
|
+
operator-approved custody file:
|
|
275
|
+
`topolo credentials write-custody <reference> --out <absolute-dir> --app <appId> --api-key-id <id> --confirm --json`
|
|
276
|
+
verifies the key against Auth, writes 0600 `api-key.json` and `custody.json`
|
|
277
|
+
(metadata and a SHA-256 digest only) into a 0700 directory, prints no secret,
|
|
278
|
+
and deletes the local reference without revoking the upstream key.
|
|
279
|
+
|
|
273
280
|
## Generic public API escape hatch
|
|
274
281
|
|
|
275
282
|
Only when public docs provide a complete contract:
|
|
@@ -270,6 +270,13 @@ Local deletion does not revoke the upstream credential; use its published
|
|
|
270
270
|
revocation action separately when required. Other SDK/remote hosts keep
|
|
271
271
|
redaction unless they explicitly provide a protected store.
|
|
272
272
|
|
|
273
|
+
The only supported way to put a referenced API key on disk is an
|
|
274
|
+
operator-approved custody file:
|
|
275
|
+
`topolo credentials write-custody <reference> --out <absolute-dir> --app <appId> --api-key-id <id> --confirm --json`
|
|
276
|
+
verifies the key against Auth, writes 0600 `api-key.json` and `custody.json`
|
|
277
|
+
(metadata and a SHA-256 digest only) into a 0700 directory, prints no secret,
|
|
278
|
+
and deletes the local reference without revoking the upstream key.
|
|
279
|
+
|
|
273
280
|
## Generic public API escape hatch
|
|
274
281
|
|
|
275
282
|
Only when public docs provide a complete contract:
|
|
@@ -270,6 +270,13 @@ Local deletion does not revoke the upstream credential; use its published
|
|
|
270
270
|
revocation action separately when required. Other SDK/remote hosts keep
|
|
271
271
|
redaction unless they explicitly provide a protected store.
|
|
272
272
|
|
|
273
|
+
The only supported way to put a referenced API key on disk is an
|
|
274
|
+
operator-approved custody file:
|
|
275
|
+
`topolo credentials write-custody <reference> --out <absolute-dir> --app <appId> --api-key-id <id> --confirm --json`
|
|
276
|
+
verifies the key against Auth, writes 0600 `api-key.json` and `custody.json`
|
|
277
|
+
(metadata and a SHA-256 digest only) into a 0700 directory, prints no secret,
|
|
278
|
+
and deletes the local reference without revoking the upstream key.
|
|
279
|
+
|
|
273
280
|
## Generic public API escape hatch
|
|
274
281
|
|
|
275
282
|
Only when public docs provide a complete contract:
|
|
@@ -270,6 +270,13 @@ Local deletion does not revoke the upstream credential; use its published
|
|
|
270
270
|
revocation action separately when required. Other SDK/remote hosts keep
|
|
271
271
|
redaction unless they explicitly provide a protected store.
|
|
272
272
|
|
|
273
|
+
The only supported way to put a referenced API key on disk is an
|
|
274
|
+
operator-approved custody file:
|
|
275
|
+
`topolo credentials write-custody <reference> --out <absolute-dir> --app <appId> --api-key-id <id> --confirm --json`
|
|
276
|
+
verifies the key against Auth, writes 0600 `api-key.json` and `custody.json`
|
|
277
|
+
(metadata and a SHA-256 digest only) into a 0700 directory, prints no secret,
|
|
278
|
+
and deletes the local reference without revoking the upstream key.
|
|
279
|
+
|
|
273
280
|
## Generic public API escape hatch
|
|
274
281
|
|
|
275
282
|
Only when public docs provide a complete contract:
|
|
@@ -270,6 +270,13 @@ Local deletion does not revoke the upstream credential; use its published
|
|
|
270
270
|
revocation action separately when required. Other SDK/remote hosts keep
|
|
271
271
|
redaction unless they explicitly provide a protected store.
|
|
272
272
|
|
|
273
|
+
The only supported way to put a referenced API key on disk is an
|
|
274
|
+
operator-approved custody file:
|
|
275
|
+
`topolo credentials write-custody <reference> --out <absolute-dir> --app <appId> --api-key-id <id> --confirm --json`
|
|
276
|
+
verifies the key against Auth, writes 0600 `api-key.json` and `custody.json`
|
|
277
|
+
(metadata and a SHA-256 digest only) into a 0700 directory, prints no secret,
|
|
278
|
+
and deletes the local reference without revoking the upstream key.
|
|
279
|
+
|
|
273
280
|
## Generic public API escape hatch
|
|
274
281
|
|
|
275
282
|
Only when public docs provide a complete contract:
|