toolhome 0.6.12 → 0.6.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.
Files changed (54) hide show
  1. package/README.md +24 -1
  2. package/README.zh.md +24 -1
  3. package/dist/server/cli/adopt.d.ts +43 -0
  4. package/dist/server/cli/adopt.d.ts.map +1 -0
  5. package/dist/server/cli/adopt.js +170 -0
  6. package/dist/server/cli/adopt.js.map +1 -0
  7. package/dist/server/cli/main.js +126 -3
  8. package/dist/server/cli/main.js.map +1 -1
  9. package/dist/server/cli/migrate.d.ts +161 -0
  10. package/dist/server/cli/migrate.d.ts.map +1 -0
  11. package/dist/server/cli/migrate.js +405 -0
  12. package/dist/server/cli/migrate.js.map +1 -0
  13. package/dist/server/cli-plane/cli-service.d.ts +17 -0
  14. package/dist/server/cli-plane/cli-service.d.ts.map +1 -1
  15. package/dist/server/cli-plane/cli-service.js +48 -0
  16. package/dist/server/cli-plane/cli-service.js.map +1 -1
  17. package/dist/server/cli-plane/state.d.ts +29 -0
  18. package/dist/server/cli-plane/state.d.ts.map +1 -0
  19. package/dist/server/cli-plane/state.js +103 -0
  20. package/dist/server/cli-plane/state.js.map +1 -0
  21. package/dist/server/control/client.d.ts +10 -0
  22. package/dist/server/control/client.d.ts.map +1 -1
  23. package/dist/server/control/client.js +40 -2
  24. package/dist/server/control/client.js.map +1 -1
  25. package/dist/server/control/control-api.d.ts.map +1 -1
  26. package/dist/server/control/control-api.js +30 -1
  27. package/dist/server/control/control-api.js.map +1 -1
  28. package/dist/server/control/control-service.d.ts +94 -38
  29. package/dist/server/control/control-service.d.ts.map +1 -1
  30. package/dist/server/control/control-service.js +341 -24
  31. package/dist/server/control/control-service.js.map +1 -1
  32. package/dist/server/control/openapi.d.ts.map +1 -1
  33. package/dist/server/control/openapi.js +14 -1
  34. package/dist/server/control/openapi.js.map +1 -1
  35. package/dist/server/data-plane/gateway-server.d.ts.map +1 -1
  36. package/dist/server/data-plane/gateway-server.js +72 -31
  37. package/dist/server/data-plane/gateway-server.js.map +1 -1
  38. package/dist/server/market/catalog.d.ts +39 -0
  39. package/dist/server/market/catalog.d.ts.map +1 -1
  40. package/dist/server/market/catalog.js +38 -1
  41. package/dist/server/market/catalog.js.map +1 -1
  42. package/dist/server/market/market-service.d.ts +1 -0
  43. package/dist/server/market/market-service.d.ts.map +1 -1
  44. package/dist/server/node/local-gateway.d.ts.map +1 -1
  45. package/dist/server/node/local-gateway.js +20 -1
  46. package/dist/server/node/local-gateway.js.map +1 -1
  47. package/dist/server/upstream/oauth-service.d.ts +22 -0
  48. package/dist/server/upstream/oauth-service.d.ts.map +1 -1
  49. package/dist/server/upstream/oauth-service.js +26 -0
  50. package/dist/server/upstream/oauth-service.js.map +1 -1
  51. package/package.json +1 -1
  52. package/skills/toolhome/SKILL.md +46 -0
  53. package/skills/toolhome/references/cli-reference.md +98 -0
  54. package/skills/toolhome/references/troubleshooting.md +83 -0
@@ -126,11 +126,32 @@ toolhome market update <id> # update to the catalog pin (keeps
126
126
  toolhome config export <file> # redacted export
127
127
  toolhome config export <file> --include-secrets # full backup (0600)
128
128
  toolhome config import <file> # restore (atomic transaction)
129
+ toolhome config import <file> --mode merge [--upsert] [--dry-run]
130
+ # merge keeps what the target already has
129
131
  toolhome config import-harness <file> [--preview] [--upsert]
130
132
  # import a Claude Desktop / Cursor mcpServers JSON;
131
133
  # secrets become encrypted credentials (never echoed)
132
134
  ```
133
135
 
136
+ An export covers `credentials`, `servers` (including `nodeId` placement), `clis` (the hosted
137
+ CLI plane) and `installations` (Market provenance, reported but not written by the import).
138
+ `--mode create` (default) aborts when the target already has a slug and lists the conflicts;
139
+ `--mode merge` reuses credentials by name, skips records the target already holds, and updates
140
+ them only with `--upsert`. Credentials whose name matches but whose payload differs are a 409
141
+ unless `--upsert` is given, so a re-run is a no-op rather than a silent overwrite.
142
+ `--dry-run` returns the same report without writing. The response lists per-item status:
143
+
144
+ ```json
145
+ {
146
+ "mode": "merge", "upsert": false, "dryRun": false,
147
+ "credentials": [{ "name": "Azure SP", "status": "reused" }],
148
+ "servers": [{ "slug": "firecrawl", "status": "created" }],
149
+ "clis": [{ "slug": "gh-cli", "status": "skipped", "changes": [] }],
150
+ "installations": [{ "entryId": "gh-cli", "targetType": "cli", "targetSlug": "gh-cli" }],
151
+ "counts": { "created": 2, "reused": 1, "updated": 0, "skipped": 1, "conflict": 0 }
152
+ }
153
+ ```
154
+
134
155
  Harness import accepts `{ "mcpServers": { name: { command, args, env } | { url, headers } } }`.
135
156
  Secret-looking env keys (password/token/secret/key/authorization) and `Authorization` headers
136
157
  are stored as encrypted credentials; everything else lands in the server transport.
@@ -201,6 +222,39 @@ toolhome cli exec <slug> --stdin-file ./input.txt -- <args...>
201
222
  toolhome cli exec <slug> --stdin-file - -- <args...>
202
223
  ```
203
224
 
225
+ ### Moving a login into ToolHome
226
+
227
+ A platform CLI keeps its login where it likes: `gh` in the macOS keychain, `az` in a token
228
+ cache, others in a container volume on the target. Each catalog entry declares how its state
229
+ moves, and `adopt` follows that declaration.
230
+
231
+ ```bash
232
+ toolhome cli adopt gh-cli # one entry, against the configured instance
233
+ toolhome cli adopt gh-cli --to https://cloud.example.com --to-key tch_ctl_... [--dry-run]
234
+ toolhome cli sync --to https://cloud.example.com --to-key tch_ctl_... [--dry-run]
235
+ # every installed CLI entry on the target
236
+ ```
237
+
238
+ Outcomes per entry: `adopted` (credential handed over and accepted), `already-authenticated`,
239
+ `credential-only` (the entry authenticates from injected credential values, which travel with
240
+ a configuration export), `manual` (the login cannot move — the entry says what to run on the
241
+ target), `undeclared`, `failed`. The extracted secret is written to the target's `exec` stdin
242
+ and is never printed, logged or returned.
243
+
244
+ ### Moving a state volume
245
+
246
+ ```bash
247
+ toolhome cli export-state gh-cli --volume toolhome-gh-cli-state > gh-state.json
248
+ toolhome cli import-state gh-cli gh-state.json --volume toolhome-gh-cli-state
249
+ ```
250
+
251
+ Both go through the control API (`GET|POST /api/v1/clis/{id}/state?volume=<name>`) with the
252
+ archive as base64. Only a `docker`-mode record with a declared, writable volume of that name
253
+ is accepted; archives are capped at 64 MiB. **The archive contains the CLI's credentials.**
254
+ The gateway needs the host Docker socket (already required for `executionMode: docker`) and
255
+ pulls `alpine:3.20` on first use. Tools such as `offen/docker-volume-backup` cover the same
256
+ ground if you prefer to move volumes yourself.
257
+
204
258
  `--stdin <text>` and `--stdin-file <path|->` are mutually exclusive. Put platform arguments after `--`; timeout is in milliseconds and cannot exceed the record's limit. Human output streams stdout/stderr; `--output json` emits NDJSON frames and an outcome. A failed or timed-out remote execution exits locally with code 1. Ctrl-C cancels the stream.
205
259
 
206
260
  ```bash
@@ -252,6 +306,50 @@ Semantics:
252
306
  - **Audit**: every exec appends a `cli.exec` event (slug, argv, exit code, duration) visible
253
307
  via `toolhome events` / `GET /api/v1/events`.
254
308
 
309
+ ## migrate
310
+
311
+ ```bash
312
+ toolhome migrate --to https://cloud.example.com --to-key tch_ctl_... [options]
313
+ ```
314
+
315
+ Copies this instance into another one. The source is whatever the command already resolves
316
+ (`--url`/`--key`, `TOOLHOME_URL`/`TOOLHOME_CONTROL_KEY`, or `~/.config/toolhome/config.json`);
317
+ the target is `--to`/`--to-key`. Both keys must be admin-scoped.
318
+
319
+ | Option | Effect |
320
+ | --- | --- |
321
+ | `--planes <mcp,cli>` | Which planes to move (default both) |
322
+ | `--dry-run` | Report what would change without writing to the target |
323
+ | `--with-state` | Also move each hosted CLI's state volume (contains credentials) |
324
+ | `--reset-oauth-clients` | Clear OAuth client registrations that point at the source origin |
325
+ | `--issue-access-key <name>` | Issue an MCP Access Key on the target and print the harness snippet |
326
+
327
+ Order is deliberate: Market entries are **reinstalled first** (each one provisions its own
328
+ credential, record and installation record at its pinned version, using values harvested from
329
+ the exported credential), then the configuration import runs in `merge` mode and skips what
330
+ the Market already created. Reinstalling needs the entry's values; `--set`-style values are
331
+ harvested from `env` variables, `bearer` tokens, `api-key` values and `headers` entries. An
332
+ OAuth entry has no harvestable value, so it is reported as needing authorization on the target.
333
+
334
+ The report ends with what still needs a human: every `access-key` (stored as a digest, so it
335
+ cannot be copied), every hosted CLI that reports no login state, every Market entry that needs
336
+ a secret or failed, and every OAuth credential whose registration was left in place.
337
+
338
+ ```json
339
+ {
340
+ "dryRun": false,
341
+ "source": { "servers": 2, "clis": 1, "credentials": 1, "installations": 1 },
342
+ "target": { "servers": 0, "clis": 0, "credentials": 0 },
343
+ "reinstalls": [{ "entryId": "gh-cli", "plane": "cli", "status": "installed" }],
344
+ "imported": { "counts": { "created": 2, "skipped": 1 } },
345
+ "stateTransfers": [{ "slug": "gh-cli", "volume": "toolhome-gh-cli-state", "status": "moved", "bytes": 412 }],
346
+ "verification": { "clis": [{ "slug": "gh-cli", "installed": true, "version": "gh 2.97.0", "loggedIn": true }], "mcpOk": true },
347
+ "oauthRegistrationsReset": [],
348
+ "accessKey": null,
349
+ "manual": ["Access keys are stored as digests and cannot be moved: …"]
350
+ }
351
+ ```
352
+
255
353
  ## global options
256
354
 
257
355
  ```
@@ -132,6 +132,89 @@ toolhome server refresh <server-id>
132
132
 
133
133
  **Fix**: Update to the latest version: `npm install -g toolhome@latest`.
134
134
 
135
+ ## The MCP Client Times Out on `toolhome-local` (70s), Then Never Recovers
136
+
137
+ **Symptom**: the client reports `MCP server 'toolhome-local' timed out after 70s`. The remote
138
+ `toolhome` entry (same instance) keeps working, and `toolhome mcp stdio` started by hand works
139
+ too.
140
+
141
+ **Cause**: `toolhome-local` is a stdio entry — `toolhome mcp stdio` — so a timeout means the
142
+ launcher's startup did not finish, not that the instance is down. Startup makes one request
143
+ (`GET /api/v1/servers`) plus one per node-placed server (`/runtime`). Whatever the transport
144
+ reason — a proxy switching nodes, a fake-IP mapping going stale, a stalled VPN, a brief network
145
+ drop — the client has already given up at 70s and **does not retry**, so the session stays
146
+ broken until the client restarts the server. The client's captured stderr is the evidence:
147
+
148
+ ```bash
149
+ cat ~/.grok/logs/mcp/toolhome-local.stderr.log # e.g. "cannot reach https://… : ETIMEDOUT"
150
+ ```
151
+
152
+ **Check the order that isolates it**:
153
+
154
+ ```bash
155
+ toolhome status # control plane reachable with this config? (env overrides the file)
156
+ toolhome node status # the local mirror's view of this machine's servers
157
+ toolhome mcp stdio # by hand: it must print `serving node "<label>" over stdio`
158
+ ```
159
+
160
+ Read the error text: `ENOTFOUND`/`EAI_AGAIN` is DNS, `ECONNREFUSED` is nothing listening,
161
+ `ETIMEDOUT` is a stalled path, and a plain 401/403 means the control key, not the network.
162
+
163
+ **On a host with a TUN-mode proxy** (Clash, Surge, sing-box), DNS returns a fake IP from the
164
+ proxy's pool and every connection is tunnelled, which is the usual reason a path that works one
165
+ minute stalls the next. Node's `fetch` ignores `HTTP_PROXY`/`HTTPS_PROXY` unless
166
+ `NODE_USE_ENV_PROXY=1` is set, so the launcher uses the TUN path even when the proxy's HTTP port
167
+ works for `curl`. To make the launcher use the proxy port that is already proven working:
168
+
169
+ ```bash
170
+ NODE_USE_ENV_PROXY=1 HTTP_PROXY=http://127.0.0.1:10808 HTTPS_PROXY=http://127.0.0.1:10808 \
171
+ NO_PROXY=localhost,127.0.0.1 toolhome mcp stdio
172
+ ```
173
+
174
+ **If a launcher is wedged**, it holds the local mirror while the client is no longer using it:
175
+
176
+ ```bash
177
+ ps -ax -o pid,command | grep 'toolhome mcp stdio' # a stuck one shows a high CPU time
178
+ kill -9 <pid> # the client respawns it on demand
179
+ ```
180
+
181
+ The mirror is a cache: deleting `~/.config/toolhome/node.sqlite*` while no launcher runs is safe
182
+ and forces a clean rebuild on the next start.
183
+
184
+ ## A Migrated Hosted CLI Reports `loggedIn: false`
185
+
186
+ **Symptom**: after `toolhome migrate`, `toolhome cli status gh-cli` on the target says the CLI is
187
+ installed but not logged in.
188
+
189
+ **Cause**: a platform CLI keeps its login where it chooses, and none of those places is the CLI
190
+ record. `gh` stores its token in the macOS keychain by default — `~/.config/gh/hosts.yml`
191
+ contains `user`, `git_protocol` and an empty `users:` entry but no token — so copying that file
192
+ moves nothing. `az` keeps a token cache under `/root/.azure` inside its state volume. Others log
193
+ in by device flow and write state inside the gateway.
194
+
195
+ **Fix**: run `toolhome cli adopt <slug>` from the machine that is already logged in (it extracts
196
+ with the CLI's own command, e.g. `gh auth token`, and hands the value to the target's `exec`
197
+ stdin), or migrate with `--with-state` to move the whole state volume. `toolhome cli adopt <slug>
198
+ --dry-run` prints what it would do without touching anything. Entries whose login cannot move
199
+ report `manual` and name the command to run on the target; `lark-cli` is one of those. Access
200
+ keys cannot move at all (only their digests are stored), so re-issue one with
201
+ `toolhome access-key create <name>` and re-point the harnesses.
202
+
203
+ ## OAuth Upstream Fails After a Migration
204
+
205
+ **Symptom**: an MCP server that worked on the old instance answers `credential_authorization_required`
206
+ or "Invalid client" on the new one.
207
+
208
+ **Cause**: the OAuth client registration is bound to the instance that created it — the client
209
+ metadata URL and the callback both carry `TOOLHOME_PUBLIC_URL` and the credential id
210
+ (`/oauth/upstream/client/<id>`, `/oauth/upstream/callback/<id>`). A copied credential therefore
211
+ holds a registration the authorization server cannot match. Refresh tokens still work (the
212
+ refresh grant sends no `redirect_uri`), but a new authorization needs a new registration.
213
+
214
+ **Fix**: re-run the migration with `--reset-oauth-clients`, or call
215
+ `POST /api/v1/credentials/{id}/reset-oauth-registration`, then
216
+ `toolhome credential authorize <name>` on the new instance.
217
+
135
218
  ## Aggregate `/mcp` Only Shows `search` and `exec`
136
219
 
137
220
  **Cause**: Compact tool exposure is enabled. `compact` intentionally replaces the full tool list with the `search`/`exec` discovery pair.