@hasna/skills 0.2.1 → 0.4.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 (37) hide show
  1. package/README.md +192 -44
  2. package/bin/index.js +15778 -24369
  3. package/bin/mcp.js +4802 -13678
  4. package/bin/migrate.js +5 -0
  5. package/bin/server.js +380 -214
  6. package/bin/worker.js +365 -199
  7. package/dist/admin-contract.d.ts +5861 -916
  8. package/dist/admin-contract.js +3611 -13914
  9. package/dist/cli/cli.test-utils.d.ts +14 -0
  10. package/dist/cli/commands/remote-account.d.ts +7 -0
  11. package/dist/index.d.ts +3 -0
  12. package/dist/index.js +5543 -14354
  13. package/dist/lib/api-url.d.ts +32 -23
  14. package/dist/lib/auth-store.d.ts +111 -42
  15. package/dist/lib/config.d.ts +19 -21
  16. package/dist/lib/feedback.d.ts +8 -3
  17. package/dist/lib/fleet-credentials.d.ts +252 -0
  18. package/dist/lib/instance-credentials-race.fixture.d.ts +1 -0
  19. package/dist/lib/instance-credentials.d.ts +13 -0
  20. package/dist/lib/mcp-contracts.d.ts +4 -0
  21. package/dist/lib/portable-skills-files.d.ts +4 -0
  22. package/dist/lib/portable-skills-types.d.ts +2 -0
  23. package/dist/lib/remote-account.d.ts +42 -0
  24. package/dist/lib/remote-auth.d.ts +32 -0
  25. package/dist/lib/remote-client.d.ts +75 -10
  26. package/dist/lib/remote-customer-operations.d.ts +106 -0
  27. package/dist/lib/remote-files.d.ts +21 -0
  28. package/dist/lib/remote-registry.d.ts +31 -11
  29. package/dist/lib/run-routing.d.ts +7 -4
  30. package/dist/lib/run-state.d.ts +3 -0
  31. package/dist/lib/vendor-host-policy.d.ts +31 -0
  32. package/dist/mcp/remote-customer-tools.d.ts +2 -0
  33. package/dist/sdk/index.d.ts +10 -0
  34. package/dist/sdk/index.js +6251 -14621
  35. package/dist/sdk/runs.d.ts +101 -19
  36. package/dist/storage.js +9 -14
  37. package/package.json +4 -3
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
 
@@ -19,11 +19,12 @@ Requires [Bun](https://bun.sh/) 1.0+.
19
19
  # Browse skills interactively
20
20
  skills
21
21
 
22
- # Point the CLI at a Skills API server for server-owned (premium) skill runs
23
- skills setup --api-url https://skills.example.com
24
- skills auth login --api-key "$SKILLS_API_KEY"
22
+ # Sign in. With a credential and no URL, the CLI talks to the fleet gateway;
23
+ # point it at your own instance first if you run one.
24
+ skills setup --api-url https://skills.example.com # only for your own instance
25
+ skills auth login --api-key "$HASNA_SKILLS_API_KEY"
25
26
 
26
- # With no API URL configured, skills simply run on this machine
27
+ # With no credential and no URL, skills simply run on this machine
27
28
  skills list
28
29
 
29
30
  # Optionally pin a skill preference in this project
@@ -54,21 +55,16 @@ create local run metadata, and then expose status and artifact commands. They
54
55
  do not fall back to bundled local execution when auth is missing or the server
55
56
  runtime is unavailable.
56
57
 
57
- Routing is config-driven and local is the default: a run is sent to the API
58
- only when an origin is configured (`apiUrl` or `$SKILLS_API_URL`), a credential
59
- is present (`SKILLS_API_KEY` or the auth store), and the skill carries the
60
- server-owned marker. Every other skill runs on this machine, whether or not an
61
- API is configured. No skill in the OSS catalog is server-owned today; the
58
+ Routing is credential-driven and local is the default: a run is sent to the API
59
+ only when a credential resolves (see **Credentials** below) and the skill carries
60
+ the server-owned marker. Every other skill runs on this machine, whether or not
61
+ a credential exists. No skill in the OSS catalog is server-owned today; the
62
62
  marker arrives with skills synced from a Skills API deployment. A server-owned
63
- skill run without the origin or the credential fails closed with an error
64
- naming the missing setup — it never silently runs locally.
65
-
66
- Use `SKILLS_API_KEY` or `skills auth login --api-key` for server-side
67
- execution:
63
+ skill run without a credential fails closed with an error naming the missing
64
+ setup — it never silently runs locally.
68
65
 
69
66
  ```bash
70
- skills setup --api-url https://skills.example.com
71
- skills auth login --api-key "$SKILLS_API_KEY"
67
+ skills auth login --api-key "$HASNA_SKILLS_API_KEY"
72
68
  skills run <server-owned-skill> --brief "minimal geometric owl mark"
73
69
  skills runs status <run-id>
74
70
  skills exports download <run-id>
@@ -78,11 +74,73 @@ Browser/device-code and email-code login commands are retained for compatible
78
74
  deployments. A Skills deployment can bootstrap with a provisioned API key via
79
75
  `skills auth login --api-key`.
80
76
 
81
- `SKILLS_API_KEY` is the Skills API credential. It is not a provider
77
+ `HASNA_SKILLS_API_KEY` is the Skills API credential. It is not a provider
82
78
  credential. Provider keys such as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, or
83
79
  `GEMINI_API_KEY` remain supported only for free/local OSS skills whose
84
80
  requirements explicitly document local provider use.
85
81
 
82
+ ## Credentials
83
+
84
+ The credential and the service address are resolved by the shared client in
85
+ [`@hasna/contracts`](https://www.npmjs.com/package/@hasna/contracts), the same
86
+ ladder every Hasna CLI uses. Nothing here is resolved twice, and this package
87
+ keeps no credential store of its own.
88
+
89
+ **The credential, in precedence order, resolved fresh on every call:**
90
+
91
+ | # | Tier | Where |
92
+ |---|------|-------|
93
+ | 1 | Explicit argument | `--api-key`, `--profile` |
94
+ | 2 | Deliberate env pointer | `HASNA_SKILLS_API_KEY_OVERRIDE`, `HASNA_PROFILE`, `HASNA_SKILLS_API_KEY_REF` (a secrets-vault item key, never a value) |
95
+ | 3 | macOS Keychain | generic-password item `hasna.credentials.skills.api-key`, account `$HASNA_STATION`, else `hostname -s`, else `$USER` |
96
+ | 4 | Disk | `~/.hasna/skills/config/credentials` (mode 0400/0600; `HASNA_HOME` and `HASNA_CONFIG_HOME` relocate it; XDG is never consulted) |
97
+ | 5 | Environment | `HASNA_SKILLS_API_KEY` — a legitimate tier, and deliberately *below* disk |
98
+
99
+ Tier 5 sits below disk on purpose. A wrapper that injects `HASNA_SKILLS_API_KEY`
100
+ into one child process re-reads its store every time and cannot go stale; a shell
101
+ `export` can, and after a key rotation the file on disk is the correct one.
102
+
103
+ `skills auth login` writes tier 4. A tier an operator set on purpose (1 and 2)
104
+ never falls through to another identity: if it cannot be honoured, the command
105
+ fails rather than acting as a different principal.
106
+
107
+ `HASNA_SKILLS_API_KEY_REF` names a *vault item*, not a key, so it resolves in two
108
+ steps: the item is fetched through the `@hasna/secrets` SDK on each call, which
109
+ means a rotated item is picked up without a restart — and which means the SDK has
110
+ to be installed in the process. Every way that fetch can fail (SDK absent, vault
111
+ unreachable, item missing or empty) is terminal and exits non-zero; a pointer
112
+ never falls through to another tier, and never to the local corpus.
113
+
114
+ **The service address, in the same shape:**
115
+
116
+ `HASNA_SKILLS_API_URL` → the Keychain item `hasna.credentials.skills.api-url` →
117
+ `~/.hasna/skills/config/credentials` → the fleet gateway
118
+ `https://api.hasna.com/skills`. The gateway default applies only once a
119
+ credential has resolved, so an install with no credential names no host at all.
120
+ `skills setup --api-url <origin>` writes the credentials file; the address is
121
+ per-user, never per-project.
122
+
123
+ The unprefixed `SKILLS_API_KEY` and `SKILLS_API_URL` spellings are still accepted
124
+ as silent aliases one rung below the canonical names, for one release. Use the
125
+ `HASNA_`-prefixed names. `SKILL_API_KEY` (singular) is no longer read at all.
126
+
127
+ **Three outcomes, and no fourth:**
128
+
129
+ - a credential resolves → **hosted**, against the configured URL or the gateway.
130
+ A credential that resolves but cannot produce a usable key — a deliberate
131
+ selection that cannot be honoured, a vault pointer whose item is missing, any
132
+ tier that yields a blank value — is a **loud failure** too, never a demotion;
133
+ - no credential but a URL is configured → **loud failure**, exit non-zero. There
134
+ is no local fallback: answering from the bundled corpus while authentication is
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.
138
+
139
+ The retired locations are not read: `auth.json` (in either the app directory or
140
+ the legacy `~/.skills/`), the old fleet-env and per-machine cloud env folders
141
+ under `~/.hasna`, and the XDG config directory. `~/.hasna` is a closed namespace
142
+ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
143
+
86
144
  ## CLI Commands
87
145
 
88
146
  | Command | Alias | Description |
@@ -117,12 +175,12 @@ requirements explicitly document local provider use.
117
175
  | `skills billing status` | | Show server account plan and balance |
118
176
  | `skills billing checkout` | | Create a checkout session when billing is enabled |
119
177
  | `skills billing portal` | | Create a customer portal session when billing is enabled |
120
- | `skills credits buy <amount>` | | Create a credit-pack checkout session when billing is enabled |
178
+ | `skills credits buy <pack-id>` | | Create a credit-pack checkout session when billing is enabled |
121
179
  | `skills setup-info` | | Version, pinned skills, agent configs, paths |
122
180
  | `skills export` | | Export pinned skills as JSON |
123
181
  | `skills import <file>` | | Pin skills from a JSON export |
124
182
  | `skills config set <key> <value>` | | Set default agent, scope, output format, or API origin |
125
- | `skills config unset <key>` | | Remove a configuration value (`skills config unset apiUrl` returns to running on this machine) |
183
+ | `skills config unset <key>` | | Remove a configuration value (`skills config unset apiUrl` clears the stored service address and returns to running on this machine) |
126
184
  | `skills new <name>` | `scaffold` | Scaffold a portable skill under `~/.hasna/skills/installed/<name>` |
127
185
  | `skills port <path>` | `add` | Import an existing skill folder into the portable standard |
128
186
  | `skills create <name>` | | Scaffold a new custom skill directory |
@@ -146,7 +204,7 @@ requirements explicitly document local provider use.
146
204
  - `--brief` — One-line format
147
205
  - `--limit <n>` — Cap human rows where supported; use `--limit all` or `--limit 0` for every row
148
206
  - `--cursor <n>` — Continue human-output pagination from a numeric offset
149
- - `--remote` — Read browse/search data from `SKILLS_API_URL` or `config apiUrl`
207
+ - `--remote` — Read browse/search data from the configured Skills instance (see **Credentials**)
150
208
  - `--dry-run` — Preview without applying changes
151
209
  - `--verbose` — Debug logging globally; richer human discovery rows where supported
152
210
  - `--no-color` — Disable ANSI colors
@@ -206,15 +264,16 @@ Stable command shapes:
206
264
  ## Remote Registry
207
265
 
208
266
  The npm package ships no bundled skill corpus. Discovery reads the local corpus
209
- cache (`~/.hasna/skills/installed`, filled by `skills pull`) and, when an API
210
- base URL is set, the server's registry. This is not a mode you select: pointing
211
- browse/search commands at a server's registry is one fact, an API base URL being
212
- set. To set it:
267
+ cache (`~/.hasna/skills/installed`, filled by `skills pull`) and, when a
268
+ credential resolves, the server's registry. This is not a mode you select:
269
+ whether browse/search commands read a server's registry is one fact, whether a
270
+ credential resolves (see [Credentials](#credentials)). To point at your own
271
+ instance:
213
272
 
214
273
  ```bash
215
- export SKILLS_API_URL=https://your-server.example
216
- # or persist it:
217
- skills config set apiUrl https://your-server.example
274
+ export HASNA_SKILLS_API_URL=https://your-server.example
275
+ # or persist it in the credentials file the shared ladder reads:
276
+ skills setup --api-url https://your-server.example
218
277
  # and to stop using it:
219
278
  skills config unset apiUrl
220
279
 
@@ -228,8 +287,9 @@ If the URL is an origin such as `https://your-server.example`, the CLI requests
228
287
  `/api/v1/skills`. If it already ends in `/api` or `/api/v1`, the CLI appends
229
288
  `/skills`.
230
289
 
231
- Authenticated registry listing and premium server-side execution use
232
- `SKILLS_API_KEY` or the credential saved by `skills auth login --api-key`.
290
+ Authenticated registry listing and premium server-side execution use whichever
291
+ credential the ladder resolves most often the one saved by
292
+ `skills auth login --api-key`.
233
293
 
234
294
  The typed `RemoteSkillsClient` also exposes pin, tag, and cursor-based
235
295
  incremental-sync methods (`listPins`/`pin`/`unpin`, `listTags`/`skillsByTag`,
@@ -306,16 +366,100 @@ skills mcp --register all # Register with all supported agents
306
366
 
307
367
  ## Skills API
308
368
 
369
+ Use a named profile for each independently operated instance. A commercial Skills instance
370
+ and an internal instance have separate credentials, accounts,
371
+ credits and data. Selecting one does not change another profile or the fleet
372
+ resolver's existing defaults.
373
+
309
374
  ```bash
310
- skills setup --api-url https://skills.example.com
311
- skills auth login --api-key "$SKILLS_API_KEY"
312
- skills billing status
375
+ # Configure the commercial instance before signing in.
376
+ skills --profile customer setup --api-url https://skills.example.com/api/v1 --json
377
+ skills --profile customer auth signup --email you@example.com --json
378
+ skills --profile customer auth login --email you@example.com --code <CODE> --json
379
+ skills --profile customer auth whoami --json
380
+ skills --profile customer capabilities --json
381
+ skills --profile customer list --remote --json
382
+
383
+ # Quote without spending. Put CLI flags before the skill name.
384
+ skills --profile customer quote --json blog-article --topic "Your topic"
385
+ skills --profile customer run --remote --yes --wait --json --idempotency-key article-001 blog-article --topic "Your topic"
386
+ skills --profile customer runs status <run-id> --json
387
+ skills --profile customer runs logs <run-id> --json
388
+ skills --profile customer runs artifacts <run-id> --json
389
+ skills --profile customer exports download <run-id> --json
390
+
391
+ skills --profile customer billing status --json
392
+ skills --profile customer billing usage --json
393
+ skills --profile customer billing invoices --json
394
+ skills --profile customer credits packs --json
395
+ skills --profile customer credits buy <pack-id> --json
396
+ skills --profile customer billing portal --json
397
+ skills --profile customer auth keys list --email you@example.com --code <FRESH-CODE> --json
398
+ # Request a fresh OTP, then create a separately scoped key (shown once).
399
+ skills --profile customer auth signup --email you@example.com --json
400
+ skills --profile customer auth keys create automation --email you@example.com --code <CODE> --scope runs:read --json
401
+ skills --profile customer auth logout --json
313
402
  ```
314
403
 
315
- Account, run, log, artifact, and optional billing commands use the configured
316
- Skills API. The public package stores only local configuration
317
- and CLI credentials. Artifacts can be stored in S3 when `HASNA_SKILLS_S3_BUCKET`
318
- is configured.
404
+ An origin, a full `/api/v1` base and a base with a path prefix normalize to the
405
+ same routes. `HASNA_PROFILE=customer` selects the same profile as `--profile`.
406
+ `HASNA_SKILLS_API_URL` (or the compatible `SKILLS_API_URL`) is an explicit URL
407
+ override, not permission to send a saved key to a different instance. Stored
408
+ keys retain their original instance binding; sign in to a separate profile to
409
+ switch instances. `HASNA_HOME` / `HASNA_CONFIG_HOME` isolate credential state;
410
+ `HASNA_SKILLS_DIR` separately isolates corpus/configuration data. They do not
411
+ require changing `HOME`.
412
+
413
+ A paid remote run requires explicit approval. Interactive runs ask before
414
+ submission; JSON and other noninteractive runs require `--yes`. The approved
415
+ quote becomes the server-enforced credit ceiling. A changed price above that
416
+ ceiling fails before admission. A compatible server must advertise bounded
417
+ approval; older or unsupported APIs return errors, not local results. Reuse the
418
+ same idempotency key only for an identical submission to safely recover an
419
+ interrupted response. `runs cancel` and `runs resume` call the server's lifecycle
420
+ operations and can be refused when the current state does not allow them.
421
+
422
+ `run --remote --file input.txt ...` declares upload hashes before admission and
423
+ uploads bytes without forwarding the account key to storage. Failed uploads
424
+ request cancellation. Upload support must be advertised by the server.
425
+ Downloads verify authenticated size and SHA-256 metadata before writing files;
426
+ existing files and unsafe paths are refused. CLI/SDK downloads are bounded to
427
+ 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
428
+ API key cannot grant new key authority. Checkout
429
+ commands return external links; payment confirmation remains in the browser.
430
+
431
+ The MCP server uses the same account, quote, run and artifact client. Agent
432
+ hosts must launch `skills-mcp --stdio`; the standalone default is loopback HTTP.
433
+ Configure its environment with the selected `HASNA_PROFILE` and isolated state
434
+ paths. `run_skill` accepts `remote:true`, `maxCredits`, `idempotency_key`, and
435
+ optional inline `files:[{name,base64,contentType}]`. An omitted ceiling permits
436
+ only free execution. `quote_skill` never submits a run.
437
+
438
+ ```ts
439
+ import { RemoteSkillsAuthClient, createRemoteSkillsClient } from "@hasna/skills/sdk";
440
+
441
+ // Auth transports never write credentials; the embedding application owns storage.
442
+ const auth = new RemoteSkillsAuthClient("https://skills.example.com/api/v1");
443
+ await auth.requestCode("you@example.com");
444
+ // await auth.verifyCode("you@example.com", code);
445
+
446
+ // Uses the same selected profile and credential binding as CLI/MCP.
447
+ const client = await createRemoteSkillsClient(process.env);
448
+ if (!client) throw new Error("Configure an instance and sign in first");
449
+ const quote = await client.quoteRun("blog-article", {}, ["--topic", "Your topic"]);
450
+ // Obtain explicit user approval of quote.pricing.costCents before this call.
451
+ const run = await client.submitQuotedRun("blog-article", {}, ["--topic", "Your topic"], {
452
+ maxCredits: quote.pricing.costCents,
453
+ idempotencyKey: "article-001",
454
+ });
455
+ ```
456
+
457
+ `submitRun` remains a low-level compatibility transport. New paid integrations
458
+ should use `submitQuotedRun` or `submitQuotedRunWithFiles` so capability and
459
+ approval checks run before submission. Credit counts are integers; `maxCostCents`
460
+ is a legacy spelling for the same credit ceiling. Missing billing capabilities
461
+ on an internal instance are explicit unsupported responses; this package does
462
+ not add a billing engine to the OSS server.
319
463
 
320
464
  ### Server database
321
465
 
@@ -324,11 +468,15 @@ different product: the schema, the organization scoping, and the run lifecycle a
324
468
  identical either way.
325
469
 
326
470
  ```bash
327
- skills-server # SQLite at ~/.hasna/skills/server.db
328
- HASNA_SKILLS_DATABASE_URL=<path>/server.db skills-server
329
- HASNA_SKILLS_DATABASE_URL=postgres://user:CHANGEME@host/skills skills-server
471
+ skills-serve # SQLite at ~/.hasna/skills/server.db
472
+ HASNA_SKILLS_DATABASE_URL=<path>/server.db skills-serve
473
+ HASNA_SKILLS_DATABASE_URL=postgres://user:CHANGEME@host/skills skills-serve
330
474
  ```
331
475
 
476
+ > **Bin naming:** the canonical server bin is `skills-serve`; `skills-server` remains
477
+ > installed as a deprecated alias for one release (same entrypoint). `skills-worker`
478
+ > and `skills-migrate` are additional documented surfaces.
479
+
332
480
  | `HASNA_SKILLS_DATABASE_URL` | Backend | Survives restart |
333
481
  | --- | --- | --- |
334
482
  | *(unset)* | SQLite at `<data dir>/server.db` | yes |
@@ -368,8 +516,8 @@ SQLite file, so it stays usable as a deploy gate.
368
516
 
369
517
  `HASNA_SKILLS_DATABASE_URL` and `DATABASE_URL` are server-only. CLI, MCP, and SDK
370
518
  clients never read them and never open a database connection: a client reaches
371
- the cloud only through `SKILLS_API_URL` plus an API key (the `apiKey` stored by
372
- `skills auth`, or `SKILLS_API_KEY`). The one exception is the repo-native storage
519
+ the cloud only through the resolved API URL plus the resolved API key (see
520
+ [Credentials](#credentials)). The one exception is the repo-native storage
373
521
  sync under [Storage Boundary](#storage-boundary), an operator tool that
374
522
  intentionally reads the same variables.
375
523
 
@@ -387,7 +535,7 @@ Three things the server will not do:
387
535
  against the same file — nothing more. A container without a persistent volume gets a
388
536
  database in its own ephemeral layer, and two replicas each get their *own* database
389
537
  rather than sharing one. Multi-replica and container deployments want Postgres; both
390
- `skills-server` and `skills-worker` print the database they opened on startup, so a
538
+ `skills-serve` and `skills-worker` print the database they opened on startup, so a
391
539
  split-brain SQLite setup shows up as two different paths in the logs.
392
540
 
393
541
  <a id="storage-boundary"></a>
@@ -452,7 +600,7 @@ skills/ # Public skill contracts and local OSS skills
452
600
  |---|---|---|
453
601
  | Catalog skills | 86 | `SKILLS.length` (`src/lib/registry-data/`) |
454
602
  | Categories | 17 | `CATEGORIES` (`src/lib/registry-types.ts`) |
455
- | MCP tools | 37 | `tools/list` against a live `buildServer()` |
603
+ | MCP tools | 56 | `tools/list` against a live `buildServer()` |
456
604
 
457
605
  Every number in this table is re-derived from the source tree on each test run by
458
606
  `src/lib/readme-derived-counts.test.ts`, so a drifted figure fails a test rather