@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
@@ -22,10 +22,14 @@ export declare function parseSkillKind(value: string | undefined): SkillKind | u
22
22
  export declare function readDeclaredSkillVersion(skillPath: string): string | undefined;
23
23
  export declare function createInstructionManifest(name: string, options: {
24
24
  description: string;
25
+ category?: string;
26
+ tags?: string[];
25
27
  }): PortableSkillManifest;
26
28
  export declare function writeInstructionSkillTemplate(skillPath: string, manifest: PortableSkillManifest): void;
27
29
  export declare function createPortableManifest(name: string, options: {
28
30
  description: string;
31
+ category?: string;
32
+ tags?: string[];
29
33
  }): PortableSkillManifest;
30
34
  export declare function writePortableSkillTemplate(skillPath: string, manifest: PortableSkillManifest): void;
31
35
  /**
@@ -98,6 +98,8 @@ export interface PortableSkillOptions {
98
98
  }
99
99
  export interface ScaffoldPortableSkillOptions extends PortableSkillOptions {
100
100
  description?: string;
101
+ category?: string;
102
+ tags?: string[];
101
103
  overwrite?: boolean;
102
104
  kind?: SkillKind;
103
105
  }
@@ -0,0 +1,42 @@
1
+ import type { RemoteInputFileDescriptor } from "./remote-files.js";
2
+ /** Optional account APIs supplied by a configured Skills server. No local prices or provider policy. */
3
+ export interface RemoteRunQuote {
4
+ skill: string;
5
+ pricing: {
6
+ costCents: number;
7
+ formattedCost: string;
8
+ [key: string]: unknown;
9
+ };
10
+ [key: string]: unknown;
11
+ }
12
+ export interface RemoteRunApproval {
13
+ /** Preferred public spelling: maximum integer credits approved by the caller. */
14
+ maxCredits?: number;
15
+ /** Maximum integer credits approved by the caller (legacy wire spelling). */
16
+ maxCostCents?: number;
17
+ idempotencyKey?: string;
18
+ inputFiles?: RemoteInputFileDescriptor[];
19
+ }
20
+ export interface RemoteCreditPack {
21
+ id: string;
22
+ credits: number;
23
+ expiresInDays?: number;
24
+ }
25
+ export declare class RemoteCreditApprovalError extends Error {
26
+ readonly requiredCredits: number;
27
+ readonly maximumCredits: number;
28
+ readonly code = "CREDIT_APPROVAL_REQUIRED";
29
+ constructor(requiredCredits: number, maximumCredits: number);
30
+ }
31
+ export declare function creditCount(value: unknown): number;
32
+ export declare function parseRemoteRunQuote(value: unknown): RemoteRunQuote;
33
+ export declare function parseRemoteCreditPacks(value: unknown): RemoteCreditPack[];
34
+ export declare function parseRemoteBillingStatus(value: unknown): {
35
+ hasPaymentMethod?: boolean | undefined;
36
+ plan?: string | undefined;
37
+ creditBalance: number;
38
+ formattedCreditBalance: string;
39
+ };
40
+ export declare function parseRemoteCheckout(value: unknown): {
41
+ url: string;
42
+ };
@@ -0,0 +1,32 @@
1
+ export declare class HostedApiError extends Error {
2
+ readonly status?: number;
3
+ readonly code?: string;
4
+ readonly detail?: string;
5
+ readonly endpoint?: string;
6
+ readonly apiUrl?: string;
7
+ constructor(message: string, options?: {
8
+ status?: number;
9
+ code?: string;
10
+ detail?: string;
11
+ endpoint?: string;
12
+ apiUrl?: string;
13
+ });
14
+ }
15
+ /** Passwordless auth transport for an explicitly selected instance. It never writes credentials. */
16
+ export declare class RemoteSkillsAuthClient {
17
+ readonly apiOrigin: string;
18
+ constructor(apiUrl: string);
19
+ requestCode(email: string): Promise<any>;
20
+ verifyCode(email: string, code: string): Promise<any>;
21
+ startDevice(): Promise<any>;
22
+ pollDevice(deviceCode: string): Promise<any>;
23
+ private sessionClient;
24
+ createApiKey(email: string, code: string, name: string, scopes?: string[]): Promise<{
25
+ [field: string]: unknown;
26
+ key: string;
27
+ }>;
28
+ listApiKeys(email: string, code: string): Promise<Record<string, unknown>[]>;
29
+ revokeApiKey(email: string, code: string, keyId: string): Promise<Record<string, unknown>>;
30
+ /** Common auth transport used by CLI login, preserving the selected instance through awaits. */
31
+ request(path: string, options?: RequestInit): Promise<any>;
32
+ }
@@ -1,4 +1,6 @@
1
1
  import { type RemoteSkillRunContract } from "./remote-run-contract.js";
2
+ import { type RemoteCreditPack, type RemoteRunApproval, type RemoteRunQuote } from "./remote-account.js";
3
+ import { type RemoteInputFile } from "./remote-files.js";
2
4
  /**
3
5
  * A server that predates this client's pin/tag/incremental-sync routes answered
4
6
  * 404/405 for them. The caller must never mistake that for "no pins" or "empty
@@ -55,6 +57,7 @@ export interface UpdatedSincePage {
55
57
  export declare class RemoteSkillsClient {
56
58
  private apiUrl;
57
59
  private apiKey;
60
+ private capabilities?;
58
61
  constructor(apiKey: string, apiUrl?: string);
59
62
  private request;
60
63
  /**
@@ -88,12 +91,64 @@ export declare class RemoteSkillsClient {
88
91
  status: number;
89
92
  body: unknown;
90
93
  }>;
91
- submitRun(slug: string, input?: Record<string, unknown>, args?: string[]): Promise<RemoteSkillRunContract>;
94
+ /** Low-level admission transport; interactive surfaces use submitQuotedRun. */
95
+ submitRun(slug: string, input?: Record<string, unknown>, args?: string[], approval?: RemoteRunApproval): Promise<RemoteSkillRunContract>;
96
+ quoteRun(slug: string, input?: Record<string, unknown>, args?: string[]): Promise<RemoteRunQuote>;
97
+ getCapabilities(): Promise<{
98
+ contractVersion: 1;
99
+ apiVersion: 1;
100
+ capabilities: string[];
101
+ billing?: {
102
+ boundedRunApproval?: boolean;
103
+ unit?: string;
104
+ };
105
+ }>;
106
+ /** Quote first and fail closed when the caller has not approved the required credits. */
107
+ submitQuotedRun(slug: string, input?: Record<string, unknown>, args?: string[], approval?: RemoteRunApproval): Promise<RemoteSkillRunContract>;
108
+ getIdentity(): Promise<Record<string, unknown>>;
109
+ listApiKeys(): Promise<Record<string, unknown>[]>;
110
+ createApiKey(name: string, scopes?: string[]): Promise<{
111
+ key: string;
112
+ [field: string]: unknown;
113
+ }>;
114
+ revokeApiKey(keyId: string): Promise<Record<string, unknown>>;
115
+ getBillingStatus(): Promise<{
116
+ hasPaymentMethod?: boolean | undefined;
117
+ plan?: string | undefined;
118
+ creditBalance: number;
119
+ formattedCreditBalance: string;
120
+ }>;
121
+ listCreditPacks(): Promise<RemoteCreditPack[]>;
122
+ createCreditCheckout(packId: string): Promise<{
123
+ url: string;
124
+ }>;
125
+ getUsage(): Promise<Record<string, unknown>[]>;
126
+ listInvoices(): Promise<Record<string, unknown>[]>;
127
+ createBillingCheckout(): Promise<{
128
+ url: string;
129
+ }>;
130
+ createBillingPortal(): Promise<{
131
+ url: string;
132
+ }>;
133
+ cancelRun(runId: string): Promise<RemoteSkillRunContract>;
134
+ resumeRun(runId: string): Promise<RemoteSkillRunContract>;
135
+ private controlRun;
136
+ private checkoutResponse;
137
+ private arrayResponse;
92
138
  getRun(runId: string): Promise<RemoteSkillRunContract | null>;
93
139
  getRunLogs(runId: string): Promise<any[]>;
94
140
  listRuns(limit?: number): Promise<any[]>;
95
141
  getRunArtifacts(runId: string): Promise<any[]>;
96
142
  downloadRunArtifact(runId: string, artifactId: string): Promise<Response>;
143
+ getVerifiedRunArtifact(runId: string, artifactId: string, maximumBytes?: number): Promise<{
144
+ id: string;
145
+ fileName: string;
146
+ bytes: Uint8Array<ArrayBufferLike>;
147
+ byteSize: number;
148
+ sha256: any;
149
+ }>;
150
+ submitQuotedRunWithFiles(slug: string, input: Record<string, unknown>, args: string[], files: RemoteInputFile[], approval?: RemoteRunApproval): Promise<RemoteSkillRunContract>;
151
+ uploadRunFiles(runId: string, files: RemoteInputFile[]): Promise<void>;
97
152
  /**
98
153
  * Publish a skill to the configured instance.
99
154
  *
@@ -154,16 +209,26 @@ export declare class RemoteSkillsClient {
154
209
  limit?: number;
155
210
  }): Promise<UpdatedSincePage>;
156
211
  }
157
- export declare function createRemoteSkillsClient(): RemoteSkillsClient | null;
212
+ /**
213
+ * The client for the configured instance, or null when this install runs on
214
+ * this machine (no credential and no authority).
215
+ *
216
+ * A configured authority with no credential does NOT return null: the shared
217
+ * ladder throws, so the caller fails loudly instead of quietly reading the
218
+ * bundled corpus while authentication is unconfigured.
219
+ *
220
+ * ASYNC because the credential ladder is: a vault pointer
221
+ * (`HASNA_SKILLS_API_KEY_REF`) is completed through the secrets vault before a
222
+ * client is built, so this never hands `RemoteSkillsClient` an empty key to put
223
+ * behind `Authorization: Bearer `.
224
+ */
225
+ export declare function createRemoteSkillsClient(env?: Record<string, string | undefined>): Promise<RemoteSkillsClient | null>;
158
226
  /**
159
227
  * Write-free client resolution for read-only paths (e.g. `sync --dry-run`).
160
228
  *
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).
229
+ * Identical to createRemoteSkillsClient() now that resolution is the shared
230
+ * ladder, which reads the Keychain and the credentials file per call and writes
231
+ * nothing. Kept as a separate name so read-only callers keep reading as
232
+ * read-only, and so the distinction survives if a write ever creeps back in.
168
233
  */
169
- export declare function createRemoteSkillsClientReadOnly(): RemoteSkillsClient | null;
234
+ export declare function createRemoteSkillsClientReadOnly(env?: Record<string, string | undefined>): Promise<RemoteSkillsClient | null>;
@@ -0,0 +1,106 @@
1
+ import type { RemoteSkillsClient } from "./remote-client.js";
2
+ /** Shared customer operation declarations for MCP registration and its discovery contract. */
3
+ export declare const REMOTE_CUSTOMER_OPERATIONS: readonly [{
4
+ readonly name: "get_account";
5
+ readonly title: "Get Account Identity";
6
+ readonly parameter: null;
7
+ readonly mutates: false;
8
+ readonly invoke: (client: RemoteSkillsClient) => Promise<Record<string, unknown>>;
9
+ }, {
10
+ readonly name: "get_server_capabilities";
11
+ readonly title: "Get Server Capabilities";
12
+ readonly parameter: null;
13
+ readonly mutates: false;
14
+ readonly invoke: (client: RemoteSkillsClient) => Promise<{
15
+ contractVersion: 1;
16
+ apiVersion: 1;
17
+ capabilities: string[];
18
+ billing?: {
19
+ boundedRunApproval?: boolean;
20
+ unit?: string;
21
+ };
22
+ }>;
23
+ }, {
24
+ readonly name: "list_remote_skills";
25
+ readonly title: "List Remote Skills";
26
+ readonly parameter: null;
27
+ readonly mutates: false;
28
+ readonly invoke: (client: RemoteSkillsClient) => Promise<any[]>;
29
+ }, {
30
+ readonly name: "get_billing_status";
31
+ readonly title: "Get Billing Status";
32
+ readonly parameter: null;
33
+ readonly mutates: false;
34
+ readonly invoke: (client: RemoteSkillsClient) => Promise<{
35
+ hasPaymentMethod?: boolean | undefined;
36
+ plan?: string | undefined;
37
+ creditBalance: number;
38
+ formattedCreditBalance: string;
39
+ }>;
40
+ }, {
41
+ readonly name: "list_credit_packs";
42
+ readonly title: "List Credit Packs";
43
+ readonly parameter: null;
44
+ readonly mutates: false;
45
+ readonly invoke: (client: RemoteSkillsClient) => Promise<import("./remote-account.js").RemoteCreditPack[]>;
46
+ }, {
47
+ readonly name: "create_credit_checkout";
48
+ readonly title: "Create Credit Checkout";
49
+ readonly parameter: "pack_id";
50
+ readonly mutates: true;
51
+ readonly invoke: (client: RemoteSkillsClient, value: string) => Promise<{
52
+ url: string;
53
+ }>;
54
+ }, {
55
+ readonly name: "get_billing_usage";
56
+ readonly title: "Get Billing Usage";
57
+ readonly parameter: null;
58
+ readonly mutates: false;
59
+ readonly invoke: (client: RemoteSkillsClient) => Promise<Record<string, unknown>[]>;
60
+ }, {
61
+ readonly name: "list_invoices";
62
+ readonly title: "List Invoices";
63
+ readonly parameter: null;
64
+ readonly mutates: false;
65
+ readonly invoke: (client: RemoteSkillsClient) => Promise<Record<string, unknown>[]>;
66
+ }, {
67
+ readonly name: "create_billing_checkout";
68
+ readonly title: "Create Billing Checkout";
69
+ readonly parameter: null;
70
+ readonly mutates: true;
71
+ readonly invoke: (client: RemoteSkillsClient) => Promise<{
72
+ url: string;
73
+ }>;
74
+ }, {
75
+ readonly name: "create_billing_portal";
76
+ readonly title: "Create Billing Portal";
77
+ readonly parameter: null;
78
+ readonly mutates: true;
79
+ readonly invoke: (client: RemoteSkillsClient) => Promise<{
80
+ url: string;
81
+ }>;
82
+ }, {
83
+ readonly name: "get_run_logs";
84
+ readonly title: "Get Run Logs";
85
+ readonly parameter: "run_id";
86
+ readonly mutates: false;
87
+ readonly invoke: (client: RemoteSkillsClient, value: string) => Promise<any[]>;
88
+ }, {
89
+ readonly name: "cancel_run";
90
+ readonly title: "Cancel Run";
91
+ readonly parameter: "run_id";
92
+ readonly mutates: true;
93
+ readonly invoke: (client: RemoteSkillsClient, value: string) => Promise<import("./remote-run-contract.js").RemoteSkillRunContract>;
94
+ }, {
95
+ readonly name: "resume_run";
96
+ readonly title: "Resume Run";
97
+ readonly parameter: "run_id";
98
+ readonly mutates: true;
99
+ readonly invoke: (client: RemoteSkillsClient, value: string) => Promise<import("./remote-run-contract.js").RemoteSkillRunContract>;
100
+ }, {
101
+ readonly name: "list_run_artifacts";
102
+ readonly title: "List Run Artifacts";
103
+ readonly parameter: "run_id";
104
+ readonly mutates: false;
105
+ readonly invoke: (client: RemoteSkillsClient, value: string) => Promise<any[]>;
106
+ }];
@@ -0,0 +1,21 @@
1
+ export declare const MAX_REMOTE_FILE_BYTES: number;
2
+ export interface RemoteInputFile {
3
+ name: string;
4
+ bytes: Uint8Array;
5
+ contentType?: string;
6
+ }
7
+ export interface RemoteInputFileDescriptor {
8
+ name: string;
9
+ sizeBytes: number;
10
+ sha256: string;
11
+ contentType: string;
12
+ }
13
+ export declare function describeRemoteFiles(files: RemoteInputFile[]): RemoteInputFileDescriptor[];
14
+ export declare function sha256(bytes: Uint8Array): string;
15
+ export declare function readBoundedResponse(response: Response, maximum: number): Promise<Uint8Array>;
16
+ /** Small inline inputs for agent transports; larger files belong in the CLI/SDK. */
17
+ export declare function decodeRemoteFiles(files: Array<{
18
+ name: string;
19
+ base64: string;
20
+ contentType?: string;
21
+ }>): RemoteInputFile[];
@@ -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
@@ -33,6 +35,7 @@ export type RunRoutingErrorCode = "REMOTE_REQUIRES_ORIGIN" | "REMOTE_REQUIRES_CR
33
35
  export type RunRouting = {
34
36
  route: "remote";
35
37
  apiKey: string;
38
+ apiOrigin: string;
36
39
  } | {
37
40
  route: "local";
38
41
  } | {
@@ -57,4 +60,4 @@ export declare function resolveRunRouting(skill: Pick<SkillMeta, "name" | "serve
57
60
  * validated, so the caller never re-reads (and cannot diverge from) the
58
61
  * credential the decision was made with.
59
62
  */
60
- export declare function resolveConfiguredRunRouting(skill: Pick<SkillMeta, "name" | "serverOwned">): RunRouting;
63
+ export declare function resolveConfiguredRunRouting(skill: Pick<SkillMeta, "name" | "serverOwned">, env?: Record<string, string | undefined>): Promise<RunRouting>;
@@ -25,6 +25,8 @@ export interface SkillRunRecord {
25
25
  durationMs?: number;
26
26
  remote: boolean;
27
27
  remoteRunId?: string;
28
+ /** Exact configured instance that owns a remote run; absent on historical records. */
29
+ remoteApiOrigin?: string;
28
30
  costCents?: number;
29
31
  error?: string;
30
32
  artifacts: SkillRunArtifact[];
@@ -47,6 +49,7 @@ export declare function createSkillRun(params: {
47
49
  prompt?: string;
48
50
  remote?: boolean;
49
51
  remoteRunId?: string;
52
+ remoteApiOrigin?: string;
50
53
  costCents?: number;
51
54
  status?: SkillRunStatus;
52
55
  }, targetDir?: string): SkillRunContext;
@@ -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;
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerRemoteCustomerTools(server: McpServer): void;
@@ -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";
@@ -24,3 +30,7 @@ export * from "./events.js";
24
30
  export * from "./spend.js";
25
31
  export * from "./offline.js";
26
32
  export * from "./execution/index.js";
33
+ export { RemoteSkillsClient, createRemoteSkillsClient, RemoteRequestError, RemoteRouteUnsupportedError } from "../lib/remote-client.js";
34
+ export { RemoteCreditApprovalError, type RemoteRunApproval, type RemoteRunQuote, type RemoteCreditPack } from "../lib/remote-account.js";
35
+ export { type RemoteInputFile, type RemoteInputFileDescriptor } from "../lib/remote-files.js";
36
+ export { RemoteSkillsAuthClient, HostedApiError } from "../lib/remote-auth.js";