@hasna/skills 0.3.0 → 0.5.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.
Files changed (55) hide show
  1. package/README.md +269 -14
  2. package/bin/index.js +7835 -5440
  3. package/bin/mcp.js +2040 -589
  4. package/bin/migrate.js +148 -40
  5. package/bin/server.js +66 -87
  6. package/bin/worker.js +42 -75
  7. package/dist/admin-contract.d.ts +37 -19
  8. package/dist/admin-contract.js +1 -1
  9. package/dist/cli/cli.test-utils.d.ts +10 -8
  10. package/dist/cli/commands/customer-profile.d.ts +2 -0
  11. package/dist/cli/commands/customer-verification.d.ts +5 -0
  12. package/dist/cli/commands/remote-account.d.ts +7 -0
  13. package/dist/cli/commands/tool-primitives.d.ts +1 -1
  14. package/dist/cli/commands/workspace-member-mutations.d.ts +2 -0
  15. package/dist/cli/commands/workspace-members.d.ts +2 -0
  16. package/dist/cli/env-assignment.d.ts +9 -0
  17. package/dist/index.d.ts +7 -2
  18. package/dist/index.js +1369 -349
  19. package/dist/lib/agent-sync.d.ts +13 -8
  20. package/dist/lib/api-url.d.ts +4 -3
  21. package/dist/lib/app-home.d.ts +0 -1
  22. package/dist/lib/auth-store.d.ts +1 -1
  23. package/dist/lib/client-types.d.ts +75 -0
  24. package/dist/lib/credential-state.d.ts +12 -0
  25. package/dist/lib/fleet-credentials.d.ts +49 -17
  26. package/dist/lib/home-adoption.d.ts +2 -0
  27. package/dist/lib/home-census.d.ts +3 -1
  28. package/dist/lib/instance-credentials.d.ts +13 -0
  29. package/dist/lib/local-opt-in.d.ts +24 -0
  30. package/dist/lib/mcp-contracts.d.ts +4 -0
  31. package/dist/lib/portable-skills-files.d.ts +10 -2
  32. package/dist/lib/portable-skills-types.d.ts +2 -0
  33. package/dist/lib/read-access.d.ts +83 -0
  34. package/dist/lib/remote-account.d.ts +42 -0
  35. package/dist/lib/remote-auth.d.ts +46 -0
  36. package/dist/lib/remote-client.d.ts +90 -6
  37. package/dist/lib/remote-customer-operations.d.ts +106 -0
  38. package/dist/lib/remote-files.d.ts +21 -0
  39. package/dist/lib/remote-profile.d.ts +26 -0
  40. package/dist/lib/remote-registry.d.ts +7 -3
  41. package/dist/lib/remote-workspace.d.ts +76 -0
  42. package/dist/lib/run-routing.d.ts +1 -0
  43. package/dist/lib/run-state.d.ts +3 -0
  44. package/dist/lib/skillinfo.d.ts +1 -1
  45. package/dist/mcp/helpers.d.ts +22 -0
  46. package/dist/mcp/index.d.ts +16 -0
  47. package/dist/mcp/remote-customer-tools.d.ts +2 -0
  48. package/dist/sdk/governance-store.d.ts +1 -0
  49. package/dist/sdk/index.d.ts +8 -1
  50. package/dist/sdk/index.js +1994 -416
  51. package/dist/sdk/outputs.d.ts +0 -11
  52. package/dist/sdk/runs.d.ts +5 -5
  53. package/dist/storage.js +6 -40
  54. package/docs/skill-standard.md +30 -2
  55. package/package.json +7 -6
package/README.md CHANGED
@@ -11,7 +11,7 @@ Skills library for AI coding agents — discover, pin, and run reusable capabili
11
11
  bun install -g @hasna/skills
12
12
  ```
13
13
 
14
- Requires [Bun](https://bun.sh/) 1.0+.
14
+ Requires [Bun](https://bun.sh/) 1.3+.
15
15
 
16
16
  ## Quick Start
17
17
 
@@ -124,7 +124,7 @@ The unprefixed `SKILLS_API_KEY` and `SKILLS_API_URL` spellings are still accepte
124
124
  as silent aliases one rung below the canonical names, for one release. Use the
125
125
  `HASNA_`-prefixed names. `SKILL_API_KEY` (singular) is no longer read at all.
126
126
 
127
- **Three outcomes, and no fourth:**
127
+ **Fail closed, and the local run is a deliberate choice:**
128
128
 
129
129
  - a credential resolves → **hosted**, against the configured URL or the gateway.
130
130
  A credential that resolves but cannot produce a usable key — a deliberate
@@ -133,14 +133,39 @@ as silent aliases one rung below the canonical names, for one release. Use the
133
133
  - no credential but a URL is configured → **loud failure**, exit non-zero. There
134
134
  is no local fallback: answering from the bundled corpus while authentication is
135
135
  unconfigured is a false green;
136
- - neither **local**. Skills ships its corpus, so running on this machine is a
137
- real mode; it prints one line on stderr saying so.
136
+ - neither a credential nor a URL, and no opt-in **loud failure**, exit
137
+ non-zero. Running on this machine is no longer the silence that follows a
138
+ missing credential: an unconfigured install fails closed, opening no local
139
+ database and emitting no local-fallback event, and the error names the way
140
+ out and where the credential should live. This holds on every data surface:
141
+ `list`, `search`, `categories`, `tags`, `info`, `show`, `docs`, `requires`
142
+ and the bare `skills` listing all exit 1; `skills-mcp` exits 1 at startup
143
+ before answering `initialize` or binding a port, and each MCP data tool
144
+ answers `AUTH_REQUIRED` on its own;
145
+ - the explicit local opt-in → **local**. Skills ships its corpus, so running on
146
+ this machine is a real mode — but it must be asked for:
147
+ `HASNA_SKILLS_LOCAL=1` (alias `SKILLS_LOCAL=1`). It prints one line saying
148
+ "local mode" on stderr. A configured environment always outranks the opt-in:
149
+ with an authority or credential in the environment, `HASNA_SKILLS_LOCAL` is
150
+ ignored and the run goes (or fails) hosted.
138
151
 
139
152
  The retired locations are not read: `auth.json` (in either the app directory or
140
153
  the legacy `~/.skills/`), the old fleet-env and per-machine cloud env folders
141
154
  under `~/.hasna`, and the XDG config directory. `~/.hasna` is a closed namespace
142
155
  of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
143
156
 
157
+ ### Routing environment variables
158
+
159
+ | Variable | Meaning |
160
+ |---|---|
161
+ | `HASNA_SKILLS_API_KEY` | The API key (tier 5 of the ladder). The silent alias `SKILLS_API_KEY` is accepted for one release. |
162
+ | `HASNA_SKILLS_API_URL` | The Skills API origin (HTTPS, or loopback HTTP). The silent alias `SKILLS_API_URL` is accepted for one release. |
163
+ | `HASNA_SKILLS_LOCAL` | Explicit unhosted opt-in: run on this machine against the bundled corpus when no authority is configured. Any non-blank value (`1`). Alias `SKILLS_LOCAL`. Ignored whenever an authority or credential variable IS set. |
164
+ | `HASNA_SKILLS_API_KEY_OVERRIDE` | Deliberate tier-2 key that outranks every store. |
165
+ | `HASNA_SKILLS_API_KEY_REF` | Deliberate tier-2 vault-item pointer (resolved through `@hasna/secrets`). |
166
+ | `HASNA_PROFILE` | Selects an isolated `credentials-<profile>` file (tier 1). |
167
+ | `HASNA_STATION` | The Keychain account for tier 3; falls back to `hostname -s`, then `$USER`. |
168
+
144
169
  ## CLI Commands
145
170
 
146
171
  | Command | Alias | Description |
@@ -151,7 +176,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
151
176
  | `skills unpin <name>` | | Remove a project pin |
152
177
  | `skills pins list` | | List pinned skills |
153
178
  | `skills setup --api-url <url>` | | Point the CLI at a Skills API origin for remote runs |
154
- | `skills setup` | | Show whether an API origin is configured; with none, skills run on this machine |
179
+ | `skills setup` | | Show whether an API origin is configured; with none, running on this machine requires `HASNA_SKILLS_LOCAL=1` |
155
180
  | `skills setup agents` | | Register the Skills MCP server with all supported agents |
156
181
  | `skills list` | `ls` | List available skills (filter with `-c`, `--pinned`, `-t`, `--brief`) |
157
182
  | `skills search <query>` | `s` | Search by name, description, or tags |
@@ -168,6 +193,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
168
193
  | `skills categories` | | List all categories with skill counts |
169
194
  | `skills tags` | | List all unique tags with occurrence counts |
170
195
  | `skills doctor` | | Check env vars, system deps, and pinned skill health |
196
+ | `skills env-check [name]` | `check-env` | Show required environment variables; `--set KEY=VALUE` updates the project's `.env` |
171
197
  | `skills test [name]` | | Test skill readiness (env, system, npm deps) |
172
198
  | `skills outdated` | | Compare pinned vs registry versions |
173
199
  | `skills auth login --api-key <key>` | | Verify and store a Skills API key |
@@ -175,7 +201,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
175
201
  | `skills billing status` | | Show server account plan and balance |
176
202
  | `skills billing checkout` | | Create a checkout session when billing is enabled |
177
203
  | `skills billing portal` | | Create a customer portal session when billing is enabled |
178
- | `skills credits buy <amount>` | | Create a credit-pack checkout session when billing is enabled |
204
+ | `skills credits buy <pack-id>` | | Create a credit-pack checkout session when billing is enabled |
179
205
  | `skills setup-info` | | Version, pinned skills, agent configs, paths |
180
206
  | `skills export` | | Export pinned skills as JSON |
181
207
  | `skills import <file>` | | Pin skills from a JSON export |
@@ -186,6 +212,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
186
212
  | `skills create <name>` | | Scaffold a new custom skill directory |
187
213
  | `skills sync --to claude` | | Disabled by design; use `skills mcp --register <agent|all>` |
188
214
  | `skills sync --from claude` | | Disabled by design; agent skill folders are not used |
215
+ | `skills sync [names...] --check --for <agent> --source <path>` | `render` | Read-only drift census for the selected corpus, skills and agent; explicit source overrides `SKILLS_SOURCE`, then the installed cache. Unknown selections or drift exit nonzero. Without selectors, check all existing agent homes. |
189
216
  | `skills sync --station <id>` | | Per-station snapshot mode: snapshot the installed skill homes into `resources/<station>/skills` with a v3 sync-manifest (dry-run by default; `--populate` writes) |
190
217
  | `skills hydrate --station <id>` | | Restore the canonical corpus cache from a reviewed per-station snapshot (dry-run by default; `--apply` writes) |
191
218
  | `skills validate <name>` | | Check a skill's directory structure |
@@ -198,6 +225,30 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
198
225
  | `skills self-update` | | Update this package to the latest version |
199
226
  | `skills completion <shell>` | | Generate shell completions (bash, zsh, fish) |
200
227
 
228
+ `self-update` asks the same Bun executable for its global bin directory after a
229
+ successful installation. It reports success only when the `skills` command on
230
+ your PATH resolves to that installed command and exits successfully with one
231
+ semantic version. If discovery or verification fails, it exits nonzero and
232
+ explains that installation may already have completed. It does not automatically
233
+ reinstall or change your PATH.
234
+
235
+ ### Local environment assignments
236
+
237
+ `skills env-check --set 'KEY=value'` writes one literal value to the current
238
+ project's `.env`. Keys must match `[A-Za-z_][A-Za-z0-9_]*`. Empty values and `=`
239
+ within a value are supported; output confirms the key and path without printing
240
+ the value. Quote the shell argument when it contains shell metacharacters.
241
+
242
+ The writer preserves unrelated lines, comments, line endings and existing file
243
+ permissions. New files use mode `0600`. Symlinks and special files are rejected.
244
+ Values containing control characters, newlines, all three quote delimiters, an
245
+ odd trailing backslash, a backslash immediately before a final dollar, or a
246
+ backslash together with both a single quote and a backtick are rejected because
247
+ literal Bun dotenv serialization is not supported
248
+ for those cases. Multiline or ambiguous existing assignments also
249
+ require manual editing; refusal leaves the file unchanged. Literal round trips
250
+ are tested with Bun 1.3.14 and 1.4.0.
251
+
201
252
  ### Common Options
202
253
 
203
254
  - `--json` — Output as JSON (pipeable)
@@ -255,6 +306,19 @@ Stable command shapes:
255
306
  served by the API and never ships in this package.
256
307
  - Config and schedules: `config * --json` and `schedule * --json` return
257
308
  machine-readable status objects.
309
+ `schedule run` exits 1 if any item fails, in human and JSON output. JSON
310
+ `results[].attempted` identifies items handed to the local executor; `ran`
311
+ counts those attempts, including failures. Missing skills, routing refusals,
312
+ and unsupported hosted scheduling leave that occurrence due and its history
313
+ unchanged. Local attempts record success/error and advance the schedule.
314
+ If history cannot be saved, the item reports `executionStatus` and
315
+ `historyError`; inspect its effects before retrying. Other due items still run.
316
+ No-due and `--dry-run` remain successful without consuming occurrences.
317
+ With `schedule run --json`, child stdout and stderr stream to stderr while
318
+ stdout contains only the command's JSON result. Human-mode child output is
319
+ unchanged. Programmatic `runSkill` callers can select `stdio: "stderr"` to
320
+ stream both child output channels to stderr without buffering; the existing
321
+ default/inherit and pipe modes retain their behavior.
258
322
  - Storage: `storage status --json` returns local `.skills` paths and optional
259
323
  repo-native remote readiness; `storage sync-plan --json` returns a no-network
260
324
  snapshot plan.
@@ -314,6 +378,16 @@ skills show my-skill
314
378
  skills port ./existing-skill
315
379
  ```
316
380
 
381
+ New and imported local skill names use hyphens: `MyHTTPTool` becomes
382
+ `my-http-tool`, and `my_tool.v2` becomes `my-tool-v2`. Existing installed names
383
+ and declared command names remain readable without renaming. Already joined
384
+ lowercase words are not split automatically; supply `--name my-skill` when importing.
385
+ An import updates name declarations in its copied files; original source files stay unchanged.
386
+
387
+ Single-folder `port` / `add` reports validation after importing. If validation
388
+ fails, both human and JSON output exit with status 1; the imported folder remains
389
+ available for correction. Check `valid` and `issues` in JSON before using it.
390
+
317
391
  The scaffold includes `SKILL.md`, `skill.json`, `AGENTS.md`, `package.json`,
318
392
  `tsconfig.json`, and `src/index.ts`. `AGENTS.md` is written for coding agents:
319
393
  after `skills new my-skill`, an agent can open that file, implement the skill,
@@ -366,16 +440,100 @@ skills mcp --register all # Register with all supported agents
366
440
 
367
441
  ## Skills API
368
442
 
443
+ Use a named profile for each independently operated instance. A commercial Skills instance
444
+ and an internal instance have separate credentials, accounts,
445
+ credits and data. Selecting one does not change another profile or the fleet
446
+ resolver's existing defaults.
447
+
369
448
  ```bash
370
- skills setup --api-url https://skills.example.com # only for your own instance
371
- skills auth login --api-key "$HASNA_SKILLS_API_KEY"
372
- skills billing status
449
+ # Configure the commercial instance before signing in.
450
+ skills --profile customer setup --api-url https://skills.example.com/api/v1 --json
451
+ skills --profile customer auth signup --email you@example.com --json
452
+ skills --profile customer auth login --email you@example.com --code <CODE> --json
453
+ skills --profile customer auth whoami --json
454
+ skills --profile customer capabilities --json
455
+ skills --profile customer list --remote --json
456
+
457
+ # Quote without spending. Put CLI flags before the skill name.
458
+ skills --profile customer quote --json blog-article --topic "Your topic"
459
+ skills --profile customer run --remote --yes --wait --json --idempotency-key article-001 blog-article --topic "Your topic"
460
+ skills --profile customer runs status <run-id> --json
461
+ skills --profile customer runs logs <run-id> --json
462
+ skills --profile customer runs artifacts <run-id> --json
463
+ skills --profile customer exports download <run-id> --json
464
+
465
+ skills --profile customer billing status --json
466
+ skills --profile customer billing usage --json
467
+ skills --profile customer billing invoices --json
468
+ skills --profile customer credits packs --json
469
+ skills --profile customer credits buy <pack-id> --json
470
+ skills --profile customer billing portal --json
471
+ skills --profile customer auth keys list --email you@example.com --code <FRESH-CODE> --json
472
+ # Request a fresh OTP, then create a separately scoped key (shown once).
473
+ skills --profile customer auth signup --email you@example.com --json
474
+ skills --profile customer auth keys create automation --email you@example.com --code <CODE> --scope runs:read --json
475
+ skills --profile customer auth logout --json
373
476
  ```
374
477
 
375
- Account, run, log, artifact, and optional billing commands use the configured
376
- Skills API. The public package stores only local configuration
377
- and CLI credentials. Artifacts can be stored in S3 when `HASNA_SKILLS_S3_BUCKET`
378
- is configured.
478
+ An origin, a full `/api/v1` base and a base with a path prefix normalize to the
479
+ same routes. `HASNA_PROFILE=customer` selects the same profile as `--profile`.
480
+ `HASNA_SKILLS_API_URL` (or the compatible `SKILLS_API_URL`) is an explicit URL
481
+ override, not permission to send a saved key to a different instance. Stored
482
+ keys retain their original instance binding; sign in to a separate profile to
483
+ switch instances. `HASNA_HOME` / `HASNA_CONFIG_HOME` isolate credential state;
484
+ `HASNA_SKILLS_DIR` separately isolates corpus/configuration data. They do not
485
+ require changing `HOME`.
486
+
487
+ A paid remote run requires explicit approval. Interactive runs ask before
488
+ submission; JSON and other noninteractive runs require `--yes`. The approved
489
+ quote becomes the server-enforced credit ceiling. A changed price above that
490
+ ceiling fails before admission. A compatible server must advertise bounded
491
+ approval; older or unsupported APIs return errors, not local results. Reuse the
492
+ same idempotency key only for an identical submission to safely recover an
493
+ interrupted response. `runs cancel` and `runs resume` call the server's lifecycle
494
+ operations and can be refused when the current state does not allow them.
495
+
496
+ `run --remote --file input.txt ...` declares upload hashes before admission and
497
+ uploads bytes without forwarding the account key to storage. Failed uploads
498
+ request cancellation. Upload support must be advertised by the server.
499
+ Downloads verify authenticated size and SHA-256 metadata before writing files;
500
+ existing files and unsafe paths are refused. CLI/SDK downloads are bounded to
501
+ 64 MiB; MCP inline artifacts and input files are limited to 1 MiB. Listing, creating and revoking API keys require fresh email OTP reauthentication; an existing
502
+ API key cannot grant new key authority. Checkout
503
+ commands return external links; payment confirmation remains in the browser.
504
+
505
+ The MCP server uses the same account, quote, run and artifact client. Agent
506
+ hosts must launch `skills-mcp --stdio`; the standalone default is loopback HTTP.
507
+ Configure its environment with the selected `HASNA_PROFILE` and isolated state
508
+ paths. `run_skill` accepts `remote:true`, `maxCredits`, `idempotency_key`, and
509
+ optional inline `files:[{name,base64,contentType}]`. An omitted ceiling permits
510
+ only free execution. `quote_skill` never submits a run.
511
+
512
+ ```ts
513
+ import { RemoteSkillsAuthClient, createRemoteSkillsClient } from "@hasna/skills/sdk";
514
+
515
+ // Auth transports never write credentials; the embedding application owns storage.
516
+ const auth = new RemoteSkillsAuthClient("https://skills.example.com/api/v1");
517
+ await auth.requestCode("you@example.com");
518
+ // await auth.verifyCode("you@example.com", code);
519
+
520
+ // Uses the same selected profile and credential binding as CLI/MCP.
521
+ const client = await createRemoteSkillsClient(process.env);
522
+ if (!client) throw new Error("Configure an instance and sign in first");
523
+ const quote = await client.quoteRun("blog-article", {}, ["--topic", "Your topic"]);
524
+ // Obtain explicit user approval of quote.pricing.costCents before this call.
525
+ const run = await client.submitQuotedRun("blog-article", {}, ["--topic", "Your topic"], {
526
+ maxCredits: quote.pricing.costCents,
527
+ idempotencyKey: "article-001",
528
+ });
529
+ ```
530
+
531
+ `submitRun` remains a low-level compatibility transport. New paid integrations
532
+ should use `submitQuotedRun` or `submitQuotedRunWithFiles` so capability and
533
+ approval checks run before submission. Credit counts are integers; `maxCostCents`
534
+ is a legacy spelling for the same credit ceiling. Missing billing capabilities
535
+ on an internal instance are explicit unsupported responses; this package does
536
+ not add a billing engine to the OSS server.
379
537
 
380
538
  ### Server database
381
539
 
@@ -516,7 +674,7 @@ skills/ # Public skill contracts and local OSS skills
516
674
  |---|---|---|
517
675
  | Catalog skills | 86 | `SKILLS.length` (`src/lib/registry-data/`) |
518
676
  | Categories | 17 | `CATEGORIES` (`src/lib/registry-types.ts`) |
519
- | MCP tools | 37 | `tools/list` against a live `buildServer()` |
677
+ | MCP tools | 61 | `tools/list` against a live `buildServer()` |
520
678
 
521
679
  Every number in this table is re-derived from the source tree on each test run by
522
680
  `src/lib/readme-derived-counts.test.ts`, so a drifted figure fails a test rather
@@ -634,3 +792,100 @@ hand.
634
792
  ## License
635
793
 
636
794
  Apache-2.0 — see [LICENSE](LICENSE)
795
+
796
+ ### Account and workspace names on a compatible server
797
+
798
+ `skills account update --display-name "Ana" --email you@example.com` and
799
+ `skills workspace update --name "Studio" --email you@example.com` request a
800
+ fresh verification email and prompt for the code without showing its digits.
801
+ The current API selection still determines the server. These commands require
802
+ that server to support the additive customer-name routes; they do not enable
803
+ membership, billing or operator administration.
804
+
805
+ For automation, first request a fresh code through `skills auth login --email you@example.com --json`,
806
+ then pipe the code from your secure input source to the same command with
807
+ `--code-stdin --json`. Do not put verification codes in command arguments or
808
+ shell history. JSON/noninteractive updates require `--code-stdin`. Cancelling
809
+ an interactive prompt exits with status130 before verification.
810
+
811
+ `RemoteSkillsAuthClient.updateProfile(email, code, { displayName })` and
812
+ `updateCurrentWorkspace(email, code, { name })` use a fresh session without
813
+ replacing saved API keys or profile selection. `RemoteSkillsClient` exposes the
814
+ same name operations for an explicitly supplied interactive session. Servers
815
+ retain the final permission checks; ordinary API keys may be refused. MCP tools
816
+ `update_account_profile` and `update_workspace_name` accept a name, email and
817
+ fresh verification code, and return only the safe updated projection.
818
+
819
+ ## Admin user-list authority
820
+
821
+ The `./admin-contract` user-list response retains a global identity whose default
822
+ workspace membership is absent or revoked. Its required `role` is `null` in that
823
+ case, and its `organizationId` remains the default workspace pointer. Null grants
824
+ no workspace authority. Consumers must handle it explicitly; an organization
825
+ filter lists active members of that workspace. Role assignment still requires a
826
+ concrete role and targets the default membership; a missing default membership
827
+ does not select a different workspace automatically. Active organization rosters
828
+ and role-mutation responses continue to require non-null roles.
829
+
830
+ ## Current workspace roster on a compatible server
831
+
832
+ `skills workspace members --email you@example.com` requests fresh email
833
+ verification and reads one page of the current workspace roster. The selected
834
+ server requires a current owner/admin session; API keys and support
835
+ impersonation do not grant roster access. It never replaces saved credentials
836
+ or changes the selected profile.
837
+
838
+ For noninteractive use, request a code with the existing auth flow, then supply
839
+ it on stdin (never as a command argument):
840
+
841
+ ```sh
842
+ skills workspace members --email you@example.com --code-stdin --limit 25 --json
843
+ ```
844
+
845
+ JSON includes `organizationId`, `members`, and required `nextCursor` (null on the
846
+ last page). Pass a returned cursor unchanged with `--cursor` to read the next
847
+ page; limits are 1–100 with server default 50. Timestamps retain the server's
848
+ microsecond strings. Human output also includes a continuation cursor when one
849
+ exists. Empty pages are distinct from denied, unsupported or malformed responses,
850
+ which fail the command.
851
+
852
+ SDK callers with an authorized customer session can use
853
+ `RemoteSkillsClient.listWorkspaceMembers({ limit, cursor })`. For fresh email
854
+ verification, use `RemoteSkillsAuthClient.listWorkspaceMembers(email, code,
855
+ options)`. The MCP tool `list_workspace_members` takes `email`, `code`, and
856
+ optional `limit`/`cursor` and calls the same fresh-auth client.
857
+
858
+ ## Change a current workspace membership
859
+
860
+ On a compatible server, use the exact `membershipId` and role from the roster:
861
+
862
+ ```sh
863
+ skills workspace member role <membership-id> --role viewer --expected-role member --email you@example.com --code-stdin --json
864
+ skills workspace member remove <membership-id> --expected-role viewer --email you@example.com --code-stdin --json
865
+ ```
866
+
867
+ Both commands use fresh verification and leave saved credentials and profiles
868
+ unchanged. Without `--code-stdin`, an interactive terminal requests and prompts
869
+ for a new code. JSON and noninteractive calls require a previously requested
870
+ code on stdin. The server enforces current owner/admin policy. Owners can change
871
+ roles while retaining an owner; admins can manage another member/viewer within
872
+ those two roles. Self-removal is unavailable.
873
+
874
+ `RemoteSkillsClient.setWorkspaceMemberRole(membershipId, { role, expectedRole })`
875
+ and `.removeWorkspaceMember(membershipId, { expectedRole })` accept an explicit
876
+ customer session. The corresponding `RemoteSkillsAuthClient` methods take
877
+ `email, code` before those arguments and obtain an ephemeral session. MCP tools
878
+ `set_workspace_member_role` and `remove_workspace_member` use the same client,
879
+ with required `membershipId`, `expectedRole`, `email`, `code`, and `role` for a
880
+ role change. API keys and support impersonation do not grant this authority.
881
+
882
+ A role result contains `{ organizationId, member, changed }`; removal contains
883
+ `{ organizationId, membershipId, removed: true, alreadyRemoved }`. Exact server
884
+ timestamps remain strings. An authorized already-applied role returns
885
+ `changed: false`; retrying removal of the same tombstone returns
886
+ `alreadyRemoved: true`. A later replacement membership has a different ID.
887
+ Stale expected-role conflicts require a roster refresh before another action.
888
+ The client never refreshes, changes the precondition, or retries automatically.
889
+ Known refusals expose a fixed message and code through `RemoteWorkspaceMemberError`;
890
+ unsupported routes remain errors. Invitations, workspace switching and leaving
891
+ your own workspace are separate capabilities.