@hasna/skills 0.2.0 → 0.3.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.
@@ -120,11 +120,11 @@ export declare class RemoteSkillsClient {
120
120
  * instance serves no bundle for this skill (the metadata-only fallback path).
121
121
  */
122
122
  getBundle(slug: string, version?: string): Promise<Response | null>;
123
- /** List the pins the instance holds for this principal. */
124
123
  /** Every published version of a slug, newest first (hasna/apps#1630). */
125
124
  listSkillVersions(slug: string): Promise<RemoteSkillVersion[]>;
126
125
  /** One version's manifest, or null when the slug@version was never published. */
127
126
  getSkillVersion(slug: string, version: string): Promise<RemoteSkillVersion | null>;
127
+ /** List the pins the instance holds for this principal. */
128
128
  listPins(): Promise<RemotePin[]>;
129
129
  /**
130
130
  * Pin a skill on the instance (upsert — pinning again refreshes it). The
@@ -154,16 +154,26 @@ export declare class RemoteSkillsClient {
154
154
  limit?: number;
155
155
  }): Promise<UpdatedSincePage>;
156
156
  }
157
- export declare function createRemoteSkillsClient(): RemoteSkillsClient | null;
157
+ /**
158
+ * The client for the configured instance, or null when this install runs on
159
+ * this machine (no credential and no authority).
160
+ *
161
+ * A configured authority with no credential does NOT return null: the shared
162
+ * ladder throws, so the caller fails loudly instead of quietly reading the
163
+ * bundled corpus while authentication is unconfigured.
164
+ *
165
+ * ASYNC because the credential ladder is: a vault pointer
166
+ * (`HASNA_SKILLS_API_KEY_REF`) is completed through the secrets vault before a
167
+ * client is built, so this never hands `RemoteSkillsClient` an empty key to put
168
+ * behind `Authorization: Bearer `.
169
+ */
170
+ export declare function createRemoteSkillsClient(env?: Record<string, string | undefined>): Promise<RemoteSkillsClient | null>;
158
171
  /**
159
172
  * Write-free client resolution for read-only paths (e.g. `sync --dry-run`).
160
173
  *
161
- * createRemoteSkillsClient() resolves the stored credential through
162
- * getAuthFilePath() and the stored origin through loadConfig() both route
163
- * through getDataDir(), which WRITES (mkdirs the app dir, merges legacy ~/.skills
164
- * content, copies the legacy config). A dry run must resolve the same client a
165
- * real run would without performing any of that: the credential and the origin
166
- * are read from the same files at the same computed paths, and the MissingApiUrl
167
- * failure mode is preserved (a key with no origin still fails loudly).
174
+ * Identical to createRemoteSkillsClient() now that resolution is the shared
175
+ * ladder, which reads the Keychain and the credentials file per call and writes
176
+ * nothing. Kept as a separate name so read-only callers keep reading as
177
+ * read-only, and so the distinction survives if a write ever creeps back in.
168
178
  */
169
- export declare function createRemoteSkillsClientReadOnly(): RemoteSkillsClient | null;
179
+ export declare function createRemoteSkillsClientReadOnly(env?: Record<string, string | undefined>): Promise<RemoteSkillsClient | null>;
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Remote registry client.
3
3
  *
4
- * Local registry behavior remains the default. These helpers are opt-in and
5
- * read from SKILLS_API_URL or config.apiUrl so services can expose
6
- * a compatible registry API without hard-coding deployment details upstream.
4
+ * Local registry behavior remains the default. These helpers are opt-in: the
5
+ * authority and the credential both come from the shared fleet ladder
6
+ * (lib/fleet-credentials.ts), so a service can expose a compatible registry API
7
+ * without this package hard-coding anything about where it is deployed.
7
8
  */
8
- import { type SkillsConfig } from "./config.js";
9
9
  import type { SkillMeta } from "./registry.js";
10
10
  export interface RemoteRegistryOptions {
11
11
  apiUrl?: string;
@@ -14,7 +14,25 @@ export interface RemoteRegistryOptions {
14
14
  authToken?: string | null;
15
15
  fetchImpl?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
16
16
  }
17
- export declare function getConfiguredApiUrl(config?: SkillsConfig, env?: Record<string, string | undefined>): string | undefined;
17
+ export declare function getConfiguredApiUrl(env?: Record<string, string | undefined>): string | undefined;
18
+ /**
19
+ * Compose one Skills API request URL from an authority and an endpoint.
20
+ *
21
+ * The Skills server serves its API under `/api/v1`, so a bare authority gets
22
+ * `/api/v1` appended — the SAME composition `RemoteSkillsClient` performs
23
+ * (`${origin}/api/v1/...`). The two sites must agree: they are handed the same
24
+ * origin by the same resolver.
25
+ *
26
+ * A trailing `/skills` is only stripped when the API prefix precedes it
27
+ * (`.../api/skills`, `.../api/v1/skills`), i.e. when an operator pasted the
28
+ * full collection base that this package's own error messages print. A BARE
29
+ * trailing `/skills` is NOT a collection: the default fleet authority is
30
+ * `https://api.hasna.com/skills`, where `/skills` is the gateway's per-app PATH
31
+ * PREFIX. Treating that as "the base already names the collection" collapsed
32
+ * every remote read onto the gateway app root — which answers 404 — so a
33
+ * correctly credentialled install on the default authority could not run
34
+ * `skills list` at all, on the plain merge path as well as `--remote`.
35
+ */
18
36
  export declare function buildSkillsApiUrl(apiUrl: string, endpoint?: string): string;
19
37
  export declare function parseRemoteRegistryPayload(payload: unknown): SkillMeta[];
20
38
  export declare function parseRemoteSkillPayload(payload: unknown): SkillMeta;
@@ -27,12 +45,14 @@ export declare function loadRemoteRegistry(options?: RemoteRegistryOptions): Pro
27
45
  * origin sees the folder UNION cloud in the plain `list`/`search` path, while
28
46
  * every other install keeps today's exact local behavior.
29
47
  *
30
- * - No origin configured -> the local list is returned unchanged and no
31
- * request is attempted. An unconfigured install must stay byte-identical
32
- * to the pre-merge output.
33
- * - Origin configured but no credential -> the local list is returned
34
- * unchanged and nothing throws. Auth-missing must never crash a read.
35
- * - Origin + credential -> the remote registry is fetched and merged under
48
+ * - Nothing configured (no credential, no authority) -> the local list is
49
+ * returned unchanged and no request is attempted. An install running on
50
+ * this machine must stay byte-identical to the pre-merge output.
51
+ * - An authority configured with NO credential -> this throws, from the
52
+ * shared ladder. It used to return the local half silently, which is the
53
+ * false green the 2026-09-04 ruling removes: an operator who pointed this
54
+ * CLI at an instance and lost the key was shown a healthy local listing.
55
+ * - Credential (+ authority, else the fleet gateway) -> the remote registry is fetched and merged under
36
56
  * the precedence in registry-merge.ts (custom > extension > private >
37
57
  * private-hosted > remote > upstream > official), remote rows tagged
38
58
  * `source: "remote"`.
@@ -8,9 +8,11 @@
8
8
  *
9
9
  * A skill run is REMOTE only when all three hold:
10
10
  *
11
- * 1. an origin is configured (config `apiUrl` or `$SKILLS_API_URL`);
12
- * 2. a credential is present (`$SKILLS_API_KEY` / `$SKILL_API_KEY` or the
13
- * auth store written by `skills auth login`);
11
+ * 1. a credential resolves on the fleet ladder (an argument, an env pointer,
12
+ * the macOS Keychain, `~/.hasna/skills/config/credentials`, or
13
+ * `$HASNA_SKILLS_API_KEY`) see lib/fleet-credentials.ts;
14
+ * 2. an authority follows from it (`$HASNA_SKILLS_API_URL`, the Keychain
15
+ * `api-url` item, the credentials file, else the fleet gateway);
14
16
  * 3. the skill carries the server-owned marker from its published contract
15
17
  * (`skills.runtime: "hosted"` or `skills.source: "remote" |
16
18
  * "private-hosted"` in the skill's `package.json` — see
@@ -57,4 +59,4 @@ export declare function resolveRunRouting(skill: Pick<SkillMeta, "name" | "serve
57
59
  * validated, so the caller never re-reads (and cannot diverge from) the
58
60
  * credential the decision was made with.
59
61
  */
60
- export declare function resolveConfiguredRunRouting(skill: Pick<SkillMeta, "name" | "serverOwned">): RunRouting;
62
+ export declare function resolveConfiguredRunRouting(skill: Pick<SkillMeta, "name" | "serverOwned">, env?: Record<string, string | undefined>): Promise<RunRouting>;
@@ -29,10 +29,41 @@ export declare const APPROVED_CODE_HOSTS: readonly {
29
29
  domain: string;
30
30
  reason: string;
31
31
  }[];
32
+ /**
33
+ * The public fleet gateway's host.
34
+ *
35
+ * Every Hasna CLI reaches its service through one gateway origin, path-prefixed
36
+ * by app, and since the credential-ladder ruling of 2026-09-04 (hasna/apps#1720,
37
+ * #1668) that address is the DEFAULT authority in the shared @hasna/contracts
38
+ * client — so the string ships inside the bundle, on a domain this policy calls
39
+ * vendor-controlled. It is listed in VENDOR_HOST_URL_EXCEPTIONS below.
40
+ *
41
+ * R1 IS NOT WEAKENED BY THIS, because R1 was never about the hostname. It says
42
+ * an UNCONFIGURED install must not produce a URL, and that still holds exactly:
43
+ * the shared resolver composes the gateway only AFTER a credential has resolved
44
+ * (an argument, an env pointer, the macOS Keychain, the credentials file, or
45
+ * the API-key variable). With no credential there is no URL — which is what
46
+ * `unconfigured-client-boundary.test.ts` asserts, against the resolvers rather
47
+ * than against a hostname, so that test needed no weakening either.
48
+ *
49
+ * The exception is scoped to exact strings on this one host. Every other
50
+ * endpoint on a vendor domain still fails, including other paths on this one.
51
+ *
52
+ * Typed as `string` rather than left to inference on purpose: a literal type
53
+ * would print the host into the emitted `.d.ts`, which ships, and the packed
54
+ * vendor-domain scan reads shipped bytes without caring that they are types.
55
+ */
56
+ export declare const FLEET_GATEWAY_HOST: string;
32
57
  /**
33
58
  * Exact URLs that may appear despite being on a vendor domain. Matched on the
34
59
  * full URL, never on the domain, so a real endpoint on the same domain still
35
60
  * fails. Keep minimal and justified.
61
+ *
62
+ * An entry whose path is empty is allowed ONLY for {@link FLEET_GATEWAY_HOST}:
63
+ * the shared client holds the gateway ORIGIN as a constant and appends the app
64
+ * slug at runtime, so the origin and its one-slash folded form are the literals
65
+ * that actually appear in the bundles. Every other vendor host must name an
66
+ * exact endpoint, so a bare vendor origin can never be excepted.
36
67
  */
37
68
  export declare const VENDOR_HOST_URL_EXCEPTIONS: readonly {
38
69
  url: string;
@@ -9,7 +9,13 @@
9
9
  * implementation with the interface as the contract; nothing here duplicates
10
10
  * business logic. The SaaS control plane imports this package directly and
11
11
  * never spawns the server binaries.
12
+ *
13
+ * The client credential seam is re-exported too, so an SDK consumer resolves the
14
+ * Skills authority and credential exactly the way the CLI and the MCP server do
15
+ * — the shared @hasna/contracts ladder — instead of reading an environment
16
+ * variable of its own. See lib/fleet-credentials.ts.
12
17
  */
18
+ export { MissingSkillsFleetError, SkillsFleetCredentialError, SKILLS_API_KEY_ENV, SKILLS_API_URL_ENV, SKILLS_APP, configuredSkillsApiUrl, noticeLocalSkillsMode, normalizeSkillsApiOrigin, requireSkillsApiOrigin, requireSkillsFleet, resolveSkillsApiOrigin, resolveSkillsFleet, skillsCredentialFilePath, skillsCredentialFiles, skillsCredentialOrReason, type HostedSkillsFleet, type LocalSkillsFleet, type SkillsFleet, type SkillsFleetErrorCode, type SkillsFleetOptions, } from "../lib/fleet-credentials.js";
13
19
  export * from "./server.js";
14
20
  export * from "./registry.js";
15
21
  export * from "./runs.js";