@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
@@ -1,4 +1,9 @@
1
+ import { type RemoteWorkspaceMembersOptions, type RemoteWorkspaceMembersPage } from "./remote-workspace.js";
2
+ import { type RemoteWorkspaceMemberErrorCode, type SetRemoteWorkspaceMemberRole, type RemoveRemoteWorkspaceMember, type RemoteWorkspaceMemberRoleResult, type RemoteWorkspaceMemberRemovalResult } from "./remote-workspace.js";
1
3
  import { type RemoteSkillRunContract } from "./remote-run-contract.js";
4
+ import { type RemoteCreditPack, type RemoteRunApproval, type RemoteRunQuote } from "./remote-account.js";
5
+ import { type RemoteInputFile } from "./remote-files.js";
6
+ import { type UpdateRemoteProfile, type UpdateRemoteWorkspace } from "./remote-profile.js";
2
7
  /**
3
8
  * A server that predates this client's pin/tag/incremental-sync routes answered
4
9
  * 404/405 for them. The caller must never mistake that for "no pins" or "empty
@@ -15,7 +20,17 @@ export declare class RemoteRouteUnsupportedError extends Error {
15
20
  export declare class RemoteRequestError extends Error {
16
21
  readonly path: string;
17
22
  readonly status: number;
18
- constructor(path: string, status: number, statusText: string);
23
+ constructor(path: string, status: number, _statusText?: string);
24
+ }
25
+ /** A recognized membership refusal, with fixed text and no server payload. */
26
+ export declare class RemoteWorkspaceMemberError extends RemoteRequestError {
27
+ readonly code: RemoteWorkspaceMemberErrorCode;
28
+ constructor(path: string, code: RemoteWorkspaceMemberErrorCode);
29
+ }
30
+ /** A recognized unavailable capability; all displayed text is client-owned. */
31
+ export declare class RemoteCapabilityUnavailableError extends RemoteRequestError {
32
+ readonly code: "SUBSCRIPTION_CHECKOUT_UNAVAILABLE";
33
+ constructor();
19
34
  }
20
35
  /**
21
36
  * A remote pin on a skill, matching the hosted-pins wire shape
@@ -55,6 +70,7 @@ export interface UpdatedSincePage {
55
70
  export declare class RemoteSkillsClient {
56
71
  private apiUrl;
57
72
  private apiKey;
73
+ private capabilities?;
58
74
  constructor(apiKey: string, apiUrl?: string);
59
75
  private request;
60
76
  /**
@@ -88,12 +104,79 @@ export declare class RemoteSkillsClient {
88
104
  status: number;
89
105
  body: unknown;
90
106
  }>;
91
- submitRun(slug: string, input?: Record<string, unknown>, args?: string[]): Promise<RemoteSkillRunContract>;
107
+ /** Low-level admission transport; interactive surfaces use submitQuotedRun. */
108
+ submitRun(slug: string, input?: Record<string, unknown>, args?: string[], approval?: RemoteRunApproval): Promise<RemoteSkillRunContract>;
109
+ quoteRun(slug: string, input?: Record<string, unknown>, args?: string[]): Promise<RemoteRunQuote>;
110
+ getCapabilities(): Promise<{
111
+ contractVersion: 1;
112
+ apiVersion: 1;
113
+ capabilities: string[];
114
+ billing?: {
115
+ boundedRunApproval?: boolean;
116
+ unit?: string;
117
+ };
118
+ }>;
119
+ /** Quote first and fail closed when the caller has not approved the required credits. */
120
+ submitQuotedRun(slug: string, input?: Record<string, unknown>, args?: string[], approval?: RemoteRunApproval): Promise<RemoteSkillRunContract>;
121
+ getIdentity(): Promise<Record<string, unknown>>;
122
+ /** Requires a customer session; API keys and support impersonation cannot edit names. */
123
+ updateProfile(input: UpdateRemoteProfile): Promise<{
124
+ user: import("./remote-profile.js").RemoteCustomerProfile;
125
+ }>;
126
+ /** Owner/admin session only; the current workspace identity and slug stay fixed. */
127
+ updateCurrentWorkspace(input: UpdateRemoteWorkspace): Promise<{
128
+ organization: import("./remote-profile.js").RemoteCurrentWorkspace;
129
+ }>;
130
+ /** Current owner/admin customer session only; the server refuses API keys and impersonation. */
131
+ listWorkspaceMembers(options?: RemoteWorkspaceMembersOptions): Promise<RemoteWorkspaceMembersPage>;
132
+ /** Exact incarnation and expected role; no refresh or retry. Server enforces current authority. */
133
+ setWorkspaceMemberRole(membershipId: string, input: SetRemoteWorkspaceMemberRole): Promise<RemoteWorkspaceMemberRoleResult>;
134
+ /** Removes only this incarnation. A successful tombstone replay is returned unchanged. */
135
+ removeWorkspaceMember(membershipId: string, input: RemoveRemoteWorkspaceMember): Promise<RemoteWorkspaceMemberRemovalResult>;
136
+ private requestWorkspaceMember;
137
+ listApiKeys(): Promise<Record<string, unknown>[]>;
138
+ createApiKey(name: string, scopes?: string[]): Promise<{
139
+ key: string;
140
+ [field: string]: unknown;
141
+ }>;
142
+ revokeApiKey(keyId: string): Promise<Record<string, unknown>>;
143
+ getBillingStatus(): Promise<{
144
+ hasPaymentMethod?: boolean | undefined;
145
+ plan?: string | undefined;
146
+ creditBalance: number;
147
+ formattedCreditBalance: string;
148
+ }>;
149
+ listCreditPacks(): Promise<RemoteCreditPack[]>;
150
+ createCreditCheckout(packId: string): Promise<{
151
+ url: string;
152
+ }>;
153
+ getUsage(): Promise<Record<string, unknown>[]>;
154
+ listInvoices(): Promise<Record<string, unknown>[]>;
155
+ createBillingCheckout(): Promise<{
156
+ url: string;
157
+ }>;
158
+ createBillingPortal(): Promise<{
159
+ url: string;
160
+ }>;
161
+ cancelRun(runId: string): Promise<RemoteSkillRunContract>;
162
+ resumeRun(runId: string): Promise<RemoteSkillRunContract>;
163
+ private controlRun;
164
+ private checkoutResponse;
165
+ private arrayResponse;
92
166
  getRun(runId: string): Promise<RemoteSkillRunContract | null>;
93
167
  getRunLogs(runId: string): Promise<any[]>;
94
168
  listRuns(limit?: number): Promise<any[]>;
95
169
  getRunArtifacts(runId: string): Promise<any[]>;
96
170
  downloadRunArtifact(runId: string, artifactId: string): Promise<Response>;
171
+ getVerifiedRunArtifact(runId: string, artifactId: string, maximumBytes?: number): Promise<{
172
+ id: string;
173
+ fileName: string;
174
+ bytes: Uint8Array<ArrayBufferLike>;
175
+ byteSize: number;
176
+ sha256: any;
177
+ }>;
178
+ submitQuotedRunWithFiles(slug: string, input: Record<string, unknown>, args: string[], files: RemoteInputFile[], approval?: RemoteRunApproval): Promise<RemoteSkillRunContract>;
179
+ uploadRunFiles(runId: string, files: RemoteInputFile[]): Promise<void>;
97
180
  /**
98
181
  * Publish a skill to the configured instance.
99
182
  *
@@ -156,11 +239,12 @@ export declare class RemoteSkillsClient {
156
239
  }
157
240
  /**
158
241
  * The client for the configured instance, or null when this install runs on
159
- * this machine (no credential and no authority).
242
+ * this machine which is now the explicit local opt-in only
243
+ * (`HASNA_SKILLS_LOCAL=1`); with no credential, no authority and no opt-in the
244
+ * shared ladder throws (fail-closed ruling), so the caller fails loudly instead
245
+ * of quietly reading the bundled corpus while authentication is unconfigured.
160
246
  *
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.
247
+ * A configured authority with no credential also throws for the same reason.
164
248
  *
165
249
  * ASYNC because the credential ladder is: a vault pointer
166
250
  * (`HASNA_SKILLS_API_KEY_REF`) is completed through the secrets vault before a
@@ -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[];
@@ -0,0 +1,26 @@
1
+ export type RemoteCustomerRole = "owner" | "admin" | "member" | "viewer";
2
+ export type RemoteCustomerProfile = {
3
+ id: string;
4
+ email: string;
5
+ displayName: string | null;
6
+ role: RemoteCustomerRole;
7
+ };
8
+ export type RemoteCurrentWorkspace = {
9
+ id: string;
10
+ slug: string;
11
+ name: string;
12
+ };
13
+ export type UpdateRemoteProfile = {
14
+ displayName: string;
15
+ };
16
+ export type UpdateRemoteWorkspace = {
17
+ name: string;
18
+ };
19
+ /** Client input checks do not grant authority; the selected server owns policy. */
20
+ export declare function customerNamePatch(input: unknown, field: "displayName" | "name"): Record<string, string>;
21
+ export declare function parseUpdatedProfile(value: unknown): {
22
+ user: RemoteCustomerProfile;
23
+ };
24
+ export declare function parseUpdatedWorkspace(value: unknown): {
25
+ organization: RemoteCurrentWorkspace;
26
+ };
@@ -45,9 +45,13 @@ export declare function loadRemoteRegistry(options?: RemoteRegistryOptions): Pro
45
45
  * origin sees the folder UNION cloud in the plain `list`/`search` path, while
46
46
  * every other install keeps today's exact local behavior.
47
47
  *
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.
48
+ * - Nothing configured, local opted in -> the local list is returned
49
+ * unchanged and no request is attempted. An install running on this
50
+ * machine must stay byte-identical to the pre-merge output.
51
+ * - Nothing configured and NO local opt-in -> this throws, from the shared
52
+ * ladder (MISSING_API_CREDENTIAL, naming `HASNA_SKILLS_LOCAL` as the
53
+ * deliberate way out): local mode is opt-in only, and an unconfigured
54
+ * install is a refusal rather than a silent local listing.
51
55
  * - An authority configured with NO credential -> this throws, from the
52
56
  * shared ladder. It used to return the local half silently, which is the
53
57
  * false green the 2026-09-04 ruling removes: an operator who pointed this
@@ -0,0 +1,76 @@
1
+ import type { RemoteCustomerRole } from "./remote-profile.js";
2
+ export type RemoteWorkspaceMember = {
3
+ membershipId: string;
4
+ userId: string;
5
+ email: string;
6
+ displayName: string | null;
7
+ role: RemoteCustomerRole;
8
+ /** Exact server timestamp, including microseconds; never rounded to Date. */
9
+ createdAt: string;
10
+ };
11
+ export type RemoteWorkspaceMembersPage = {
12
+ organizationId: string;
13
+ members: RemoteWorkspaceMember[];
14
+ nextCursor: string | null;
15
+ };
16
+ export type RemoteWorkspaceMembersOptions = {
17
+ limit?: number;
18
+ cursor?: string;
19
+ };
20
+ export type SetRemoteWorkspaceMemberRole = {
21
+ role: RemoteCustomerRole;
22
+ expectedRole: RemoteCustomerRole;
23
+ };
24
+ export type RemoveRemoteWorkspaceMember = {
25
+ expectedRole: RemoteCustomerRole;
26
+ };
27
+ export type RemoteWorkspaceMemberRoleResult = {
28
+ organizationId: string;
29
+ member: RemoteWorkspaceMember;
30
+ changed: boolean;
31
+ };
32
+ export type RemoteWorkspaceMemberRemovalResult = {
33
+ organizationId: string;
34
+ membershipId: string;
35
+ removed: true;
36
+ alreadyRemoved: boolean;
37
+ };
38
+ /** Pagination is transport input, not a workspace or authority selector. */
39
+ export declare function workspaceMembersQuery(options?: RemoteWorkspaceMembersOptions): string;
40
+ export declare class WorkspaceMemberInputError extends Error {
41
+ constructor();
42
+ }
43
+ /** Snapshot transport input before verification awaits; authority stays on the server. */
44
+ export declare function workspaceMemberRoleInput(membershipId: string, input: SetRemoteWorkspaceMemberRole): {
45
+ membershipId: string;
46
+ body: {
47
+ role: RemoteCustomerRole;
48
+ expectedRole: RemoteCustomerRole;
49
+ };
50
+ };
51
+ export declare function workspaceMemberRemovalInput(membershipId: string, input: RemoveRemoteWorkspaceMember): {
52
+ membershipId: string;
53
+ body: {
54
+ expectedRole: RemoteCustomerRole;
55
+ };
56
+ };
57
+ export declare const invalidMemberResult = "The server returned an invalid workspace member result. Refresh the roster before another action.";
58
+ export declare function parseWorkspaceMemberRoleResult(value: unknown, membershipId: string, role: RemoteCustomerRole): RemoteWorkspaceMemberRoleResult;
59
+ export declare function parseWorkspaceMemberRemovalResult(value: unknown, membershipId: string): RemoteWorkspaceMemberRemovalResult;
60
+ /** Only known code/status pairs become public messages; arbitrary server text stays private. */
61
+ export declare const workspaceMemberFailures: {
62
+ readonly INVALID_REQUEST: readonly [400, "Provide the exact membership role parameters."];
63
+ readonly ACCOUNT_UNAVAILABLE: readonly [403, "Account is unavailable."];
64
+ readonly INTERACTIVE_SESSION_REQUIRED: readonly [403, "Fresh interactive sign-in is required."];
65
+ readonly WORKSPACE_ADMIN_REQUIRED: readonly [403, "A current workspace owner or admin is required."];
66
+ readonly MEMBERSHIP_ACTION_FORBIDDEN: readonly [403, "Your current workspace role cannot perform this membership action."];
67
+ readonly MEMBERSHIP_NOT_FOUND: readonly [404, "Membership was not found in the current workspace."];
68
+ readonly SELF_REMOVAL_UNAVAILABLE: readonly [409, "Leaving your own workspace is not available through member removal."];
69
+ readonly MEMBERSHIP_ROLE_CHANGED: readonly [409, "The member role changed. Refresh the roster before another action."];
70
+ readonly LAST_OWNER_REQUIRED: readonly [409, "The workspace must retain at least one active owner."];
71
+ readonly MEMBERSHIP_BUSY: readonly [503, "Membership is busy. Refresh the roster before another action."];
72
+ };
73
+ export type RemoteWorkspaceMemberErrorCode = keyof typeof workspaceMemberFailures;
74
+ export declare function workspaceMemberFailure(value: unknown, status: number): RemoteWorkspaceMemberErrorCode | null;
75
+ /** Project the documented roster only; auth metadata and other fields stay out. */
76
+ export declare function parseWorkspaceMembersPage(value: unknown): RemoteWorkspaceMembersPage;
@@ -35,6 +35,7 @@ export type RunRoutingErrorCode = "REMOTE_REQUIRES_ORIGIN" | "REMOTE_REQUIRES_CR
35
35
  export type RunRouting = {
36
36
  route: "remote";
37
37
  apiKey: string;
38
+ apiOrigin: string;
38
39
  } | {
39
40
  route: "local";
40
41
  } | {
@@ -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;
@@ -45,7 +45,7 @@ export declare function getSkillDependencyStatus(name: string): SkillDependencyS
45
45
  */
46
46
  export declare function runSkill(name: string, args: string[], options?: {
47
47
  installed?: boolean;
48
- stdio?: "inherit" | "pipe";
48
+ stdio?: "inherit" | "pipe" | "stderr";
49
49
  env?: Record<string, string>;
50
50
  }): Promise<{
51
51
  exitCode: number;
@@ -1,3 +1,24 @@
1
+ /** The shape every tool handler returns; kept structural so registrars need no SDK type import. */
2
+ type McpToolResult = {
3
+ content: Array<{
4
+ type: "text";
5
+ text: string;
6
+ }>;
7
+ isError?: boolean;
8
+ };
9
+ /**
10
+ * Run a DATA tool behind the fleet ladder's refusal.
11
+ *
12
+ * The body is expected to go through `requireSkillsReadAccess()` /
13
+ * `getBrowseRegistry()` (lib/read-access.ts) before it reads the bundled
14
+ * catalog or the on-machine corpus. When the ladder refuses — no credential, no
15
+ * authority, no `HASNA_SKILLS_LOCAL=1` opt-in; an authority with no key; a
16
+ * deliberate selection that cannot be honoured — the refusal comes back as the
17
+ * same `AUTH_REQUIRED` result `get_run_status` already gives, never as the
18
+ * local answer. Any other failure propagates unchanged (the SDK reports it as
19
+ * a tool error with its message).
20
+ */
21
+ export declare function readSurface(body: () => Promise<McpToolResult>): Promise<McpToolResult>;
1
22
  export declare function stripNulls(obj: Record<string, unknown>): Record<string, unknown>;
2
23
  export declare function cacheGet(key: string): unknown | undefined;
3
24
  export declare function cacheSet(key: string, value: unknown): void;
@@ -24,3 +45,4 @@ export declare const TOOL_DESCRIPTIONS: Record<string, {
24
45
  description: string;
25
46
  params: string[];
26
47
  }>;
48
+ export {};
@@ -8,6 +8,22 @@
8
8
  * skills-mcp # Direct binary
9
9
  * skills-mcp --http # Streamable HTTP on 127.0.0.1:8836
10
10
  */
11
+ /**
12
+ * FAIL CLOSED AT STARTUP (owner ruling 2026-09-04, hasna/apps#1720; #1720
13
+ * validation, round 1): the MCP process resolves the fleet ladder BEFORE it
14
+ * connects any transport. With no credential, no authority and no
15
+ * `HASNA_SKILLS_LOCAL=1` opt-in it exits 1 with the ladder's one line on
16
+ * stderr — before `initialize` is answered, before an HTTP port is bound —
17
+ * mirroring the CLI, which exits 1 on the same machine. Serving the bundled
18
+ * catalog to an agent host that registered `skills-mcp` on a station without
19
+ * its credential is the false green the ruling removed. The explicit local
20
+ * opt-in starts the server and announces local mode once on stderr; a
21
+ * credential (or a vault pointer the tools complete per call) starts it hosted.
22
+ *
23
+ * Every data tool still runs the same gate per call (lib/read-access.ts), so a
24
+ * credential that disappears mid-process is refused by the next call too.
25
+ */
26
+ export declare function assertSkillsMcpConfigured(env?: Record<string, string | undefined>): void;
11
27
  /**
12
28
  * Start the Skills MCP server on stdio (newline-delimited JSON-RPC).
13
29
  *
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerRemoteCustomerTools(server: McpServer): void;
@@ -51,6 +51,7 @@ export interface GovernanceStore {
51
51
  estimatedCents: number;
52
52
  }): Promise<CreditReservation>;
53
53
  reservationsForRun(orgId: string, runId: string): Promise<CreditReservation[]>;
54
+ /** First terminal reconciliation wins; retries return that persisted state unchanged. */
54
55
  reconcileReservation(reservationId: string, actualCents: number, status: "charged" | "released"): Promise<CreditReservation | null>;
55
56
  /** Sum of cost_cents of runs created in the given calendar month (YYYY-MM), plus un-reconciled reservations. */
56
57
  monthlySpendCents(orgId: string, monthPrefix: string): Promise<number>;
@@ -15,7 +15,7 @@
15
15
  * — the shared @hasna/contracts ladder — instead of reading an environment
16
16
  * variable of its own. See lib/fleet-credentials.ts.
17
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";
18
+ export { MissingSkillsFleetError, SkillsFleetCredentialError, SKILLS_API_KEY_ENV, SKILLS_API_URL_ENV, SKILLS_APP, SKILLS_LOCAL_OPT_IN_ENV_KEYS, configuredSkillsApiUrl, isSkillsLocalOptIn, noticeLocalSkillsMode, normalizeSkillsApiOrigin, requireSkillsApiOrigin, requireSkillsFleet, resolveSkillsApiOrigin, resolveSkillsFleet, selectsSkillsLocalMode, skillsCredentialFilePath, skillsCredentialFiles, skillsCredentialOrReason, type HostedSkillsFleet, type LocalSkillsFleet, type SkillsFleet, type SkillsFleetErrorCode, type SkillsFleetOptions, } from "../lib/fleet-credentials.js";
19
19
  export * from "./server.js";
20
20
  export * from "./registry.js";
21
21
  export * from "./runs.js";
@@ -30,3 +30,10 @@ export * from "./events.js";
30
30
  export * from "./spend.js";
31
31
  export * from "./offline.js";
32
32
  export * from "./execution/index.js";
33
+ export { RemoteSkillsClient, createRemoteSkillsClient, RemoteRequestError, RemoteRouteUnsupportedError, RemoteCapabilityUnavailableError, RemoteWorkspaceMemberError } 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";
37
+ export type { RemoteWorkspaceMember, RemoteWorkspaceMembersPage, RemoteWorkspaceMembersOptions } from "../lib/remote-workspace.js";
38
+ export type { SetRemoteWorkspaceMemberRole, RemoveRemoteWorkspaceMember, RemoteWorkspaceMemberRoleResult, RemoteWorkspaceMemberRemovalResult, RemoteWorkspaceMemberErrorCode } from "../lib/remote-workspace.js";
39
+ export type { RemoteCustomerRole, RemoteCustomerProfile, RemoteCurrentWorkspace, UpdateRemoteProfile, UpdateRemoteWorkspace } from "../lib/remote-profile.js";