@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.
- package/README.md +269 -14
- package/bin/index.js +7835 -5440
- package/bin/mcp.js +2040 -589
- package/bin/migrate.js +148 -40
- package/bin/server.js +66 -87
- package/bin/worker.js +42 -75
- package/dist/admin-contract.d.ts +37 -19
- package/dist/admin-contract.js +1 -1
- package/dist/cli/cli.test-utils.d.ts +10 -8
- package/dist/cli/commands/customer-profile.d.ts +2 -0
- package/dist/cli/commands/customer-verification.d.ts +5 -0
- package/dist/cli/commands/remote-account.d.ts +7 -0
- package/dist/cli/commands/tool-primitives.d.ts +1 -1
- package/dist/cli/commands/workspace-member-mutations.d.ts +2 -0
- package/dist/cli/commands/workspace-members.d.ts +2 -0
- package/dist/cli/env-assignment.d.ts +9 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +1369 -349
- package/dist/lib/agent-sync.d.ts +13 -8
- package/dist/lib/api-url.d.ts +4 -3
- package/dist/lib/app-home.d.ts +0 -1
- package/dist/lib/auth-store.d.ts +1 -1
- package/dist/lib/client-types.d.ts +75 -0
- package/dist/lib/credential-state.d.ts +12 -0
- package/dist/lib/fleet-credentials.d.ts +49 -17
- package/dist/lib/home-adoption.d.ts +2 -0
- package/dist/lib/home-census.d.ts +3 -1
- package/dist/lib/instance-credentials.d.ts +13 -0
- package/dist/lib/local-opt-in.d.ts +24 -0
- package/dist/lib/mcp-contracts.d.ts +4 -0
- package/dist/lib/portable-skills-files.d.ts +10 -2
- package/dist/lib/portable-skills-types.d.ts +2 -0
- package/dist/lib/read-access.d.ts +83 -0
- package/dist/lib/remote-account.d.ts +42 -0
- package/dist/lib/remote-auth.d.ts +46 -0
- package/dist/lib/remote-client.d.ts +90 -6
- package/dist/lib/remote-customer-operations.d.ts +106 -0
- package/dist/lib/remote-files.d.ts +21 -0
- package/dist/lib/remote-profile.d.ts +26 -0
- package/dist/lib/remote-registry.d.ts +7 -3
- package/dist/lib/remote-workspace.d.ts +76 -0
- package/dist/lib/run-routing.d.ts +1 -0
- package/dist/lib/run-state.d.ts +3 -0
- package/dist/lib/skillinfo.d.ts +1 -1
- package/dist/mcp/helpers.d.ts +22 -0
- package/dist/mcp/index.d.ts +16 -0
- package/dist/mcp/remote-customer-tools.d.ts +2 -0
- package/dist/sdk/governance-store.d.ts +1 -0
- package/dist/sdk/index.d.ts +8 -1
- package/dist/sdk/index.js +1994 -416
- package/dist/sdk/outputs.d.ts +0 -11
- package/dist/sdk/runs.d.ts +5 -5
- package/dist/storage.js +6 -40
- package/docs/skill-standard.md +30 -2
- package/package.json +7 -6
package/dist/lib/agent-sync.d.ts
CHANGED
|
@@ -29,9 +29,9 @@ export declare const SYNC_AGENTS: readonly SyncAgent[];
|
|
|
29
29
|
*/
|
|
30
30
|
export declare const SKILLS_SOURCE_ENV = "SKILLS_SOURCE";
|
|
31
31
|
/**
|
|
32
|
-
* Ownership marker written beside every SKILL.md this tool syncs. Its
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* Ownership marker written beside every SKILL.md this tool syncs. Its exact managedBy
|
|
33
|
+
* value tells a re-sync which directories this tool owns; a foreign or malformed
|
|
34
|
+
* marker grants no authority. A hidden sidecar rather than a frontmatter field so the SKILL.md the
|
|
35
35
|
* agent loads stays exactly the adapted document and nothing else.
|
|
36
36
|
*/
|
|
37
37
|
export declare const SYNC_MARKER_FILE = ".hasna-skills.json";
|
|
@@ -42,6 +42,10 @@ export interface SyncMarker {
|
|
|
42
42
|
source: string;
|
|
43
43
|
syncedAt: string;
|
|
44
44
|
}
|
|
45
|
+
/** Internal shared ownership predicate; not exported by the public package entrypoint. */
|
|
46
|
+
export declare function isSkillsOwnershipMarker(marker: unknown): marker is Record<string, unknown>;
|
|
47
|
+
/** Read only a regular ownership sidecar. */
|
|
48
|
+
export declare function hasSkillsOwnershipMarker(dir: string): boolean;
|
|
45
49
|
export declare function isSyncAgent(value: string): value is SyncAgent;
|
|
46
50
|
export declare function resolveSyncAgents(arg?: string): SyncAgent[];
|
|
47
51
|
/** The global skills directory for an agent, honouring a test-supplied home. */
|
|
@@ -142,12 +146,12 @@ export interface WriteManagedAgentSkillParams {
|
|
|
142
146
|
/**
|
|
143
147
|
* Write one skill into one agent's global folder, non-clobbering.
|
|
144
148
|
*
|
|
145
|
-
* A directory this tool has written before carries
|
|
149
|
+
* A directory this tool has written before carries our exact ownership marker and is replaced with
|
|
146
150
|
* an exact mirror — except that a managed home holding full content is never silently
|
|
147
151
|
* replaced with an executable pointer stub (that would be data loss; it is refused
|
|
148
|
-
* unless `force` is passed). A directory with a SKILL.md but no marker is
|
|
149
|
-
*
|
|
150
|
-
*
|
|
152
|
+
* unless `force` is passed). A directory with a SKILL.md but no valid Skills marker is
|
|
153
|
+
* skipped unless `force` explicitly adopts it. Any other pre-existing unmanaged
|
|
154
|
+
* directory is always left untouched. A fresh directory is created.
|
|
151
155
|
*/
|
|
152
156
|
export declare function writeManagedAgentSkill(params: WriteManagedAgentSkillParams): AgentSyncAction;
|
|
153
157
|
export interface ManagedDirWriteResult {
|
|
@@ -173,6 +177,7 @@ export interface ManagedDirWriteOptions {
|
|
|
173
177
|
export declare function writeManagedSkillDir(dir: string, skillMd: string, options: ManagedDirWriteOptions): ManagedDirWriteResult;
|
|
174
178
|
/**
|
|
175
179
|
* Remove a skill this tool synced from an agent folder. Refuses to delete a directory it
|
|
176
|
-
* did not write (no
|
|
180
|
+
* did not write (no valid Skills ownership marker). Foreign or malformed markers
|
|
181
|
+
* grant no deletion authority, and removal has no force override.
|
|
177
182
|
*/
|
|
178
183
|
export declare function removeManagedAgentSkill(skill: string, agent: SyncAgent, homeDir?: string): boolean;
|
package/dist/lib/api-url.d.ts
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* thin reading of `@hasna/contracts/client`. It exists only so the two failure
|
|
6
6
|
* MODES stay named:
|
|
7
7
|
*
|
|
8
|
-
* - Read paths fail closed: `resolveApiUrl()` returns `undefined`
|
|
9
|
-
*
|
|
10
|
-
* registry.
|
|
8
|
+
* - Read paths fail closed: `resolveApiUrl()` returns `undefined` only under
|
|
9
|
+
* the explicit local opt-in (`HASNA_SKILLS_LOCAL=1`, alias `SKILLS_LOCAL=1`),
|
|
10
|
+
* and the caller keeps working against the bundled local registry. Without
|
|
11
|
+
* the opt-in, an install with nothing configured is a refusal, not a URL.
|
|
11
12
|
* - Auth and write paths fail loudly: `requireApiUrl()` throws an error naming
|
|
12
13
|
* the missing configuration.
|
|
13
14
|
*
|
package/dist/lib/app-home.d.ts
CHANGED
package/dist/lib/auth-store.d.ts
CHANGED
|
@@ -87,7 +87,7 @@ export declare function getAuthConfigReadOnly(env?: Env, options?: SkillsFleetOp
|
|
|
87
87
|
* Returns the file it wrote, so the CLI can name the real path rather than a
|
|
88
88
|
* path it assumed.
|
|
89
89
|
*/
|
|
90
|
-
export declare function saveAuthConfig(config: StoredAuthConfig, env?: Env): string;
|
|
90
|
+
export declare function saveAuthConfig(config: StoredAuthConfig, env?: Env, authenticatedOrigin?: string): string;
|
|
91
91
|
/** Store (or clear, with null) the API URL beside the credential. */
|
|
92
92
|
export declare function saveApiUrl(apiUrl: string | null, env?: Env): string;
|
|
93
93
|
/** The API URL recorded in the credentials file, or null. */
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The @hasna/contracts client types that cross this package's published
|
|
3
|
+
* boundary, spelled locally.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS. `bun build --target bun` inlines @hasna/contracts into
|
|
6
|
+
* every shipped bundle, so the runtime never needs it installed alongside
|
|
7
|
+
* @hasna/skills and it is correctly a build-time devDependency. `tsc
|
|
8
|
+
* --emitDeclarationOnly` inlines nothing: the moment a PUBLIC declaration names
|
|
9
|
+
* a type from `@hasna/contracts/client`, the published `.d.ts` imports it, and
|
|
10
|
+
* a consumer who installed only this package's declared runtime dependencies
|
|
11
|
+
* fails type-checking (7 x TS2307 before #1782 fixed the same shape in
|
|
12
|
+
* @hasna/secrets).
|
|
13
|
+
*
|
|
14
|
+
* This file is NOT the vendored resolver returning: it has no imports, no
|
|
15
|
+
* runtime statement, no tier, no Keychain read, no URL ladder — only the
|
|
16
|
+
* shapes, copied from the @hasna/contracts/@1.0.2 declarations. Its sibling
|
|
17
|
+
* `client-types.test.ts` asserts each one is mutually assignable with the real
|
|
18
|
+
* declaration, in the direction it actually crosses, so a drifted shape fails
|
|
19
|
+
* `tsc` in the same build step that emits the declarations it protects.
|
|
20
|
+
*
|
|
21
|
+
* `src/lib/fleet-credentials.ts` keeps importing the resolver and its VALUE
|
|
22
|
+
* exports from @hasna/contracts/client (inlined at runtime); only the types it
|
|
23
|
+
* re-exports or names in its own public signatures come from here.
|
|
24
|
+
*
|
|
25
|
+
* Do not add an import. Do not add a value. This file must stay a leaf.
|
|
26
|
+
*/
|
|
27
|
+
/** Which link of the chain supplied the credential. */
|
|
28
|
+
export type CredentialTier = "argument" | "override" | "pointer" | "profile" | "keychain" | "disk" | "env";
|
|
29
|
+
export interface ResolvedCredential {
|
|
30
|
+
/** The secret. Property access and destructuring work; it is never enumerated or serialized. */
|
|
31
|
+
readonly apiKey: string;
|
|
32
|
+
readonly tier: CredentialTier;
|
|
33
|
+
/** Where it came from: an env key NAME, an absolute file path, or a Keychain item reference. Never a value. */
|
|
34
|
+
readonly source: string;
|
|
35
|
+
/** True for tiers an operator sets on purpose. These never fall through. */
|
|
36
|
+
readonly deliberate: boolean;
|
|
37
|
+
/** When tier === "pointer", the vault ITEM KEY to resolve at request time. Never a credential value. */
|
|
38
|
+
readonly pointerVaultKey?: string;
|
|
39
|
+
/** The disk paths consulted before this credential was chosen. */
|
|
40
|
+
readonly diskCandidates: readonly string[];
|
|
41
|
+
/** Human-readable advisory. Never contains key material. */
|
|
42
|
+
readonly warning: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** The captured outcome of one `security` invocation. `stdout` IS the secret; it is never logged. */
|
|
45
|
+
export interface KeychainCommandResult {
|
|
46
|
+
/** Exit status; null when the tool could not be started or was killed. */
|
|
47
|
+
status: number | null;
|
|
48
|
+
stdout: string;
|
|
49
|
+
stderr: string;
|
|
50
|
+
}
|
|
51
|
+
/** Runs `/usr/bin/security` with the given argv — no shell. Injected by tests. */
|
|
52
|
+
export type KeychainCommandRunner = (argv: readonly string[]) => KeychainCommandResult;
|
|
53
|
+
/** Tier 3 controls. Every field is optional; production callers pass nothing. */
|
|
54
|
+
export interface KeychainTierOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Whether the Keychain is consulted for a caller-built env object. The tier
|
|
57
|
+
* is AMBIENT: by default it runs only for the live `process.env`. Injecting
|
|
58
|
+
* `run` implies `true`.
|
|
59
|
+
*/
|
|
60
|
+
enabled?: boolean;
|
|
61
|
+
/** Defaults to `process.platform`; the tier exists only on `"darwin"`. */
|
|
62
|
+
platform?: string;
|
|
63
|
+
/** The machine's host name, used as the account when `HASNA_STATION` is unset. */
|
|
64
|
+
hostname?: () => string;
|
|
65
|
+
/** The `security` runner. Defaults to spawning `/usr/bin/security` by argv. */
|
|
66
|
+
run?: KeychainCommandRunner;
|
|
67
|
+
}
|
|
68
|
+
export interface CredentialChainOptions {
|
|
69
|
+
/** Tier 1: an explicit key, e.g. from `--api-key`. */
|
|
70
|
+
apiKey?: string;
|
|
71
|
+
/** Tier 1: an explicit profile name, e.g. from `--profile`. Beats `HASNA_PROFILE`. */
|
|
72
|
+
profile?: string;
|
|
73
|
+
/** Tier 3: Keychain controls — a fake `security` runner in tests, an opt-out on CI. */
|
|
74
|
+
keychain?: KeychainTierOptions;
|
|
75
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CredentialState {
|
|
2
|
+
/** `misconfigured` is the ladder's refusal carried as data; `error` says why. */
|
|
3
|
+
mode: "hosted" | "local" | "misconfigured";
|
|
4
|
+
apiUrl: string | null;
|
|
5
|
+
apiUrlSource: string | null;
|
|
6
|
+
apiKeySource: string | null;
|
|
7
|
+
apiKeyTier: string | null;
|
|
8
|
+
credentialsFile: string | null;
|
|
9
|
+
credentialsFileMode: string | null;
|
|
10
|
+
error: string | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function describeCredentialState(): CredentialState;
|
|
@@ -36,8 +36,14 @@
|
|
|
36
36
|
* are not read anywhere else in this package. `SKILL_API_KEY` (singular) is
|
|
37
37
|
* gone: it shadowed nothing canonical and was never documented.
|
|
38
38
|
*
|
|
39
|
-
*
|
|
39
|
+
* THIRD: the local run is opt-in only (owner ruling 2026-09-04, hasna/apps#1720;
|
|
40
|
+
* class-patch order 2026-09-06). `HASNA_SKILLS_LOCAL=1` (alias `SKILLS_LOCAL=1`)
|
|
41
|
+
* selects the on-machine run when the ENVIRONMENT configures no authority; it is
|
|
42
|
+
* answered before the resolver runs, so opting in never reads the Keychain or
|
|
43
|
+
* the credentials file. A configured environment always outranks it. The
|
|
44
|
+
* outcomes, and no fourth:
|
|
40
45
|
*
|
|
46
|
+
* - the local opt-in selected → LOCAL, announced once on stderr.
|
|
41
47
|
* - a credential resolves → HOSTED. The authority is the configured
|
|
42
48
|
* URL, else the fleet gateway. A credential
|
|
43
49
|
* that cannot produce a usable key — a
|
|
@@ -51,14 +57,18 @@
|
|
|
51
57
|
* There is no local fallback here: serving
|
|
52
58
|
* local results while authentication is
|
|
53
59
|
* unconfigured is a false green.
|
|
54
|
-
* - neither a credential nor a URL
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
60
|
+
* - neither a credential nor a URL, and no opt-in
|
|
61
|
+
* → LOUD failure, exit non-zero, no SQLite,
|
|
62
|
+
* no *-local-fallback event. Running on
|
|
63
|
+
* this machine is a deliberate choice now,
|
|
64
|
+
* not the silence that follows a missing
|
|
65
|
+
* credential.
|
|
58
66
|
*/
|
|
59
|
-
import {
|
|
67
|
+
import type { CredentialChainOptions, CredentialTier, KeychainTierOptions, ResolvedCredential } from "./client-types.js";
|
|
60
68
|
/** The app slug: the Keychain service, the `~/.hasna/<app>` folder, the gateway path. */
|
|
61
69
|
export declare const SKILLS_APP = "skills";
|
|
70
|
+
/** The deliberate unhosted opt-in env names. Re-exported for the surfaces that have to name them. */
|
|
71
|
+
export { SKILLS_LOCAL_OPT_IN_ENV_KEYS, isSkillsLocalOptIn, selectsSkillsLocalMode } from "./local-opt-in.js";
|
|
62
72
|
type Env = Record<string, string | undefined>;
|
|
63
73
|
/** `HASNA_SKILLS_API_URL`, then the accepted `SKILLS_API_URL` alias. */
|
|
64
74
|
export declare const SKILLS_API_URL_ENV_KEYS: readonly string[];
|
|
@@ -114,7 +124,7 @@ export interface LocalSkillsFleet {
|
|
|
114
124
|
}
|
|
115
125
|
export type SkillsFleet = HostedSkillsFleet | LocalSkillsFleet;
|
|
116
126
|
/** Machine-readable reasons a hosted resolution was refused. */
|
|
117
|
-
export type SkillsFleetErrorCode = "MISSING_API_CREDENTIAL" | "INVALID_API_URL";
|
|
127
|
+
export type SkillsFleetErrorCode = "MISSING_API_CREDENTIAL" | "INVALID_API_URL" | "INSTANCE_CREDENTIAL_MISMATCH";
|
|
118
128
|
/**
|
|
119
129
|
* A configured install could not produce a usable hosted client.
|
|
120
130
|
*
|
|
@@ -129,6 +139,14 @@ export declare class SkillsFleetCredentialError extends Error {
|
|
|
129
139
|
readonly code: SkillsFleetErrorCode;
|
|
130
140
|
constructor(message: string, code?: SkillsFleetErrorCode);
|
|
131
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* True for this package's own refusal, across bundle boundaries.
|
|
144
|
+
*
|
|
145
|
+
* Exported for the surfaces that turn the refusal into data (an MCP tool's
|
|
146
|
+
* `AUTH_REQUIRED` result, a CLI handler's one-line stderr exit) so they match
|
|
147
|
+
* on the error's NAME rather than on a class identity a bundle may not share.
|
|
148
|
+
*/
|
|
149
|
+
export declare function isSkillsFleetCredentialError(error: unknown): error is SkillsFleetCredentialError;
|
|
132
150
|
/**
|
|
133
151
|
* Normalize a configured Skills authority to the origin the client dials.
|
|
134
152
|
*
|
|
@@ -151,7 +169,7 @@ export interface ConfiguredSkillsApiUrl {
|
|
|
151
169
|
* default apply for a credentialled install and what keeps an install with no
|
|
152
170
|
* credential from naming a host at all.
|
|
153
171
|
*/
|
|
154
|
-
export declare function configuredSkillsApiUrl(env?: Env, keychain?: KeychainTierOptions): ConfiguredSkillsApiUrl | null;
|
|
172
|
+
export declare function configuredSkillsApiUrl(env?: Env, keychain?: KeychainTierOptions, profile?: string): ConfiguredSkillsApiUrl | null;
|
|
155
173
|
/** The credential file paths consulted, for a message that has to name them. */
|
|
156
174
|
export declare function skillsCredentialFiles(env?: Env): string[];
|
|
157
175
|
/**
|
|
@@ -164,10 +182,12 @@ export declare function skillsCredentialFilePath(env?: Env): string;
|
|
|
164
182
|
/**
|
|
165
183
|
* Say — once per process, on stderr — that this install is running locally.
|
|
166
184
|
*
|
|
167
|
-
* Local mode is legitimate for Skills: the corpus ships in the package. It is
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
185
|
+
* Local mode is legitimate for Skills: the corpus ships in the package. It is a
|
|
186
|
+
* deliberate choice now, though: it is reachable only through the explicit
|
|
187
|
+
* opt-in (`HASNA_SKILLS_LOCAL=1`), and it is still announced, because "no
|
|
188
|
+
* credential resolved" and "deliberately offline" look identical in the output
|
|
189
|
+
* otherwise, and the first one is usually a misconfiguration the operator wants
|
|
190
|
+
* to hear about.
|
|
171
191
|
*/
|
|
172
192
|
export declare function noticeLocalSkillsMode(write?: (line: string) => void): void;
|
|
173
193
|
/** Test seam: forget that the local-mode line was printed. */
|
|
@@ -193,22 +213,29 @@ export declare function resolveSkillsFleet(env?: Env, options?: SkillsFleetOptio
|
|
|
193
213
|
* when a credential is configured and cannot be produced — never a fallback.
|
|
194
214
|
*/
|
|
195
215
|
export declare function resolveSkillsApiKey(env?: Env, options?: SkillsFleetOptions): Promise<string | null>;
|
|
216
|
+
/** Resolve the URL and credential once, including any asynchronous vault lookup. */
|
|
217
|
+
export declare function resolveSkillsConnection(env?: Env, options?: SkillsFleetOptions): Promise<(HostedSkillsFleet & {
|
|
218
|
+
apiKey: string;
|
|
219
|
+
}) | null>;
|
|
196
220
|
/** The usable API key, or throw naming what is missing. Use on every send path. */
|
|
197
221
|
export declare function requireSkillsApiKey(action?: string, env?: Env, options?: SkillsFleetOptions): Promise<string>;
|
|
198
222
|
/**
|
|
199
223
|
* The credential for a surface that reports refusals as data (an MCP tool, a
|
|
200
224
|
* `--json` command) rather than as an exception.
|
|
201
225
|
*
|
|
202
|
-
* `reason` is the ladder's own message when
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
226
|
+
* `reason` is the ladder's own message when a hosted resolution is refused — an
|
|
227
|
+
* authority with no key, or an unconfigured install without the local opt-in
|
|
228
|
+
* (fail-closed ruling) — a refusal carried as a value, NOT a fallback: the
|
|
229
|
+
* caller must still refuse. It is null only when the explicit local opt-in
|
|
230
|
+
* selected the on-machine run, the ordinary "not signed in" case.
|
|
206
231
|
*/
|
|
207
232
|
export declare function skillsCredentialOrReason(env?: Env, options?: SkillsFleetOptions): Promise<{
|
|
208
233
|
apiKey: string;
|
|
234
|
+
apiOrigin: string;
|
|
209
235
|
reason: null;
|
|
210
236
|
} | {
|
|
211
237
|
apiKey: null;
|
|
238
|
+
apiOrigin: null;
|
|
212
239
|
reason: string | null;
|
|
213
240
|
}>;
|
|
214
241
|
/**
|
|
@@ -219,6 +246,12 @@ export declare function skillsCredentialOrReason(env?: Env, options?: SkillsFlee
|
|
|
219
246
|
* Keychain, credentials file), else the authority a resolved credential implies.
|
|
220
247
|
* With neither, this returns null and the caller fails loudly: R1 still holds,
|
|
221
248
|
* an install that named no service does not get to send an email address to one.
|
|
249
|
+
*
|
|
250
|
+
* The fail-closed refusal an unconfigured install now raises on every DATA
|
|
251
|
+
* surface is swallowed here and reported as plain "no authority": for a flow
|
|
252
|
+
* whose purpose is to acquire a credential, "nothing is configured" and "local
|
|
253
|
+
* mode is opted in" have the same two-step way out (configure an origin, then
|
|
254
|
+
* sign in).
|
|
222
255
|
*/
|
|
223
256
|
export declare function resolveSkillsApiOrigin(env?: Env, options?: SkillsFleetOptions): {
|
|
224
257
|
origin: string;
|
|
@@ -243,4 +276,3 @@ export declare class MissingSkillsFleetError extends Error {
|
|
|
243
276
|
readonly code = "MISSING_API_URL";
|
|
244
277
|
constructor(action?: string);
|
|
245
278
|
}
|
|
246
|
-
export {};
|
|
@@ -30,6 +30,8 @@ export interface AdoptionScan {
|
|
|
30
30
|
}
|
|
31
31
|
export interface AdoptionOptions extends PortableSkillOptions {
|
|
32
32
|
agents?: SyncAgent[];
|
|
33
|
+
/** Optional selected home/corpus names; omitted means all names. */
|
|
34
|
+
names?: string[];
|
|
33
35
|
/** Write markers and the conflicts ledger. Without it, scan only. */
|
|
34
36
|
apply?: boolean;
|
|
35
37
|
}
|
|
@@ -23,4 +23,6 @@ export interface DriftCensus {
|
|
|
23
23
|
managed: number;
|
|
24
24
|
clean: boolean;
|
|
25
25
|
}
|
|
26
|
-
export declare function censusHomeDrift(options?: AdoptionOptions
|
|
26
|
+
export declare function censusHomeDrift(options?: AdoptionOptions & {
|
|
27
|
+
names?: string[];
|
|
28
|
+
}): DriftCensus;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const SKILLS_BOUND_API_URL = "HASNA_SKILLS_BOUND_API_URL";
|
|
2
|
+
type Env = Record<string, string | undefined>;
|
|
3
|
+
/** Match the shared resolver's profile syntax before using its path helper. */
|
|
4
|
+
export declare function selectedSkillsProfile(env: Env, explicit?: string): string | null;
|
|
5
|
+
export declare function skillsProfileCredentialFiles(env: Env, explicit?: string): string[];
|
|
6
|
+
/** Fence the released credential reader and this package's routing reads together. */
|
|
7
|
+
export declare function captureSkillsCredentialFiles(files: string[]): () => void;
|
|
8
|
+
/** Read only known non-secret routing fields, never export an API key or an arbitrary field. */
|
|
9
|
+
export declare function readSkillsInstanceMetadata(file: string): {
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
binding?: string;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The deliberate unhosted opt-in, canonical name first. */
|
|
2
|
+
export declare const SKILLS_LOCAL_OPT_IN_ENV_KEYS: readonly ["HASNA_SKILLS_LOCAL", "SKILLS_LOCAL"];
|
|
3
|
+
export type SkillsLocalOptInEnv = Record<string, string | undefined>;
|
|
4
|
+
/** True when the operator deliberately asked for the unhosted on-machine run. */
|
|
5
|
+
export declare function isSkillsLocalOptIn(env?: SkillsLocalOptInEnv): boolean;
|
|
6
|
+
/** Every env name that can configure a Skills authority or credential, resolver-derived. */
|
|
7
|
+
export declare function skillsAuthorityEnvKeys(): string[];
|
|
8
|
+
/**
|
|
9
|
+
* Does the ENVIRONMENT itself configure a Skills authority or credential?
|
|
10
|
+
*
|
|
11
|
+
* Deliberately narrower than "does a credential resolve": answering it must not
|
|
12
|
+
* touch the Keychain or the filesystem, because doing so would defeat the
|
|
13
|
+
* isolation the opt-in short-circuit exists to provide. It reads the env
|
|
14
|
+
* dictionary and nothing else.
|
|
15
|
+
*
|
|
16
|
+
* A DECLARED-BUT-BLANK variable counts as absent HERE — a blank has always been
|
|
17
|
+
* this package's spelling for "not configured", and helpers in the wild blank
|
|
18
|
+
* rather than delete. It is NOT absent once we do go hosted: the resolver
|
|
19
|
+
* refuses a blank loudly rather than falling through to another identity, which
|
|
20
|
+
* is the behaviour that matters at that point.
|
|
21
|
+
*/
|
|
22
|
+
export declare function hasSkillsEnvAuthorityIntent(env?: SkillsLocalOptInEnv): boolean;
|
|
23
|
+
/** True when this environment should be served by the on-box local run. */
|
|
24
|
+
export declare function selectsSkillsLocalMode(env?: SkillsLocalOptInEnv): boolean;
|
|
@@ -9,6 +9,10 @@ export interface JsonSchemaObject {
|
|
|
9
9
|
default?: unknown;
|
|
10
10
|
format?: string;
|
|
11
11
|
minimum?: number;
|
|
12
|
+
maximum?: number;
|
|
13
|
+
pattern?: string;
|
|
14
|
+
maxItems?: number;
|
|
15
|
+
maxLength?: number;
|
|
12
16
|
items?: JsonSchemaObject;
|
|
13
17
|
properties?: Record<string, JsonSchemaObject>;
|
|
14
18
|
required?: string[];
|
|
@@ -9,7 +9,11 @@ export declare function defaultRuntimeContract(entrypoint?: string): PortableSki
|
|
|
9
9
|
/** Provenance defaults; content_hash is filled at write time over the bundle. */
|
|
10
10
|
export declare function defaultProvenance(sourceCommit?: string): PortableSkillProvenance;
|
|
11
11
|
export declare function normalizePortableSkillName(name: string): string;
|
|
12
|
+
/** New local identities use hyphens; legacy lookups and command names keep their grammar. */
|
|
13
|
+
export declare function normalizeNewPortableSkillName(name: string): string;
|
|
12
14
|
export declare function readPortableSkillManifest(skillPath: string, fallbackName?: string): PortableSkillManifest;
|
|
15
|
+
/** Import needs the original spelling before the legacy reader discards case boundaries. */
|
|
16
|
+
export declare function readPortableSkillManifestForImport(skillPath: string): PortableSkillManifest;
|
|
13
17
|
export declare function parseSkillKind(value: string | undefined): SkillKind | undefined;
|
|
14
18
|
/**
|
|
15
19
|
* The version a skill EXPLICITLY declares, or undefined when none of the sources do:
|
|
@@ -22,10 +26,14 @@ export declare function parseSkillKind(value: string | undefined): SkillKind | u
|
|
|
22
26
|
export declare function readDeclaredSkillVersion(skillPath: string): string | undefined;
|
|
23
27
|
export declare function createInstructionManifest(name: string, options: {
|
|
24
28
|
description: string;
|
|
29
|
+
category?: string;
|
|
30
|
+
tags?: string[];
|
|
25
31
|
}): PortableSkillManifest;
|
|
26
32
|
export declare function writeInstructionSkillTemplate(skillPath: string, manifest: PortableSkillManifest): void;
|
|
27
33
|
export declare function createPortableManifest(name: string, options: {
|
|
28
34
|
description: string;
|
|
35
|
+
category?: string;
|
|
36
|
+
tags?: string[];
|
|
29
37
|
}): PortableSkillManifest;
|
|
30
38
|
export declare function writePortableSkillTemplate(skillPath: string, manifest: PortableSkillManifest): void;
|
|
31
39
|
/**
|
|
@@ -42,8 +50,8 @@ export declare function ensurePortableSkillFiles(skillPath: string, manifest: Po
|
|
|
42
50
|
/**
|
|
43
51
|
* Instruction (prose) skills are consumed by agent renderers/MCP docs, not run
|
|
44
52
|
* locally, so `port` must never fabricate executable stubs (package.json, bin,
|
|
45
|
-
* src/index.ts, tsconfig.json, AGENTS.md).
|
|
46
|
-
*
|
|
53
|
+
* src/index.ts, tsconfig.json, AGENTS.md). Apart from aligning declared names
|
|
54
|
+
* after an import rename, it preserves the copied prose and metadata.
|
|
47
55
|
*/
|
|
48
56
|
export declare function ensureInstructionSkillFiles(skillPath: string, manifest: PortableSkillManifest): PortableSkillManifest;
|
|
49
57
|
export declare function copySkillDirectory(source: string, destination: string): void;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gate every DATA surface runs before it answers from the bundled corpus,
|
|
3
|
+
* and the one registry those surfaces show.
|
|
4
|
+
*
|
|
5
|
+
* `skills list` has resolved through the fleet ladder since the fail-closed
|
|
6
|
+
* ruling (owner directive 2026-09-04, hasna/apps#1720): with no credential, no
|
|
7
|
+
* authority and no `HASNA_SKILLS_LOCAL=1` opt-in it exits 1 naming the refusal,
|
|
8
|
+
* opens nothing and serves nothing. `skills info` / `docs` / `requires`, the
|
|
9
|
+
* bare non-TTY listing and the MCP discovery tools (`list_skills`,
|
|
10
|
+
* `search_skills`, `get_skill_info`, `get_skill_docs`, `list_categories`,
|
|
11
|
+
* `list_tags`, `get_requirements`) did NOT — they read `loadRegistry()` and
|
|
12
|
+
* answered from the bundled catalog plus `~/.hasna/skills/installed` at exit 0
|
|
13
|
+
* with no notice, on the very machine `skills list` had just refused (#1720
|
|
14
|
+
* validation, round 1). Two surfaces that disagree about whether an install is
|
|
15
|
+
* configured is the false green the ruling removed, so they now share this one
|
|
16
|
+
* gate with the browsing commands.
|
|
17
|
+
*
|
|
18
|
+
* - refused (nothing configured, no opt-in; an authority with no key; a
|
|
19
|
+
* deliberate selection that cannot be honoured) → `SkillsFleetCredentialError`
|
|
20
|
+
* from the shared ladder, carried to the caller unchanged: the CLI prints
|
|
21
|
+
* its one line and exits 1, an MCP tool answers `AUTH_REQUIRED`.
|
|
22
|
+
* - the explicit local opt-in → `{ mode: "local" }`; the ladder has announced
|
|
23
|
+
* local mode once on stderr, and the caller serves the on-machine answer.
|
|
24
|
+
* - a credential resolves → `{ mode: "hosted", apiOrigin }`. The key is
|
|
25
|
+
* deliberately NOT returned here: a surface that only reads local data has
|
|
26
|
+
* no business holding it, and the surfaces that send it resolve it again
|
|
27
|
+
* at request time (`remoteRequestHeaders`, per call, completing a vault
|
|
28
|
+
* pointer if that is the tier).
|
|
29
|
+
*
|
|
30
|
+
* Resolved fresh on every call — the resolver contract — so a credential that
|
|
31
|
+
* appears, rotates or disappears mid-process is honoured by the next call.
|
|
32
|
+
*/
|
|
33
|
+
import { type SkillsFleetOptions } from "./fleet-credentials.js";
|
|
34
|
+
import { type SkillMeta } from "./registry.js";
|
|
35
|
+
type Env = Record<string, string | undefined>;
|
|
36
|
+
/** Where a read surface stands, once the ladder has let it through. Never carries a key. */
|
|
37
|
+
export type SkillsReadAccess = {
|
|
38
|
+
mode: "hosted";
|
|
39
|
+
apiOrigin: string;
|
|
40
|
+
} | {
|
|
41
|
+
mode: "local";
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Run the fail-closed routing preamble for a read surface.
|
|
45
|
+
*
|
|
46
|
+
* Throws {@link SkillsFleetCredentialError} when the ladder refuses; the
|
|
47
|
+
* caller must let that refusal out (as a non-zero exit or a structured error),
|
|
48
|
+
* never swallow it into a local answer.
|
|
49
|
+
*/
|
|
50
|
+
export declare function requireSkillsReadAccess(env?: Env, options?: SkillsFleetOptions): Promise<SkillsReadAccess>;
|
|
51
|
+
/**
|
|
52
|
+
* The registry a browsing surface should show — the CLI's `list` / `search` /
|
|
53
|
+
* `categories` / `tags` and the MCP discovery tools, from ONE implementation.
|
|
54
|
+
*
|
|
55
|
+
* The default read path is folder UNION cloud: whenever the install is pointed
|
|
56
|
+
* at a hosted instance (a resolved credential, and HASNA_SKILLS_API_URL for your
|
|
57
|
+
* own instance) the authenticated remote registry joins the local listing even
|
|
58
|
+
* without `--remote`. The explicit local opt-in keeps today's exact local
|
|
59
|
+
* output; an unconfigured or auth-missing install is a refusal, thrown from the
|
|
60
|
+
* shared ladder (fail-closed R1 — see mergeRemoteRegistry()).
|
|
61
|
+
*
|
|
62
|
+
* `--remote` used to REPLACE the local registry: `skills list --remote` returned exactly
|
|
63
|
+
* what the instance served and nothing else, so the bundled corpus and every skill the
|
|
64
|
+
* operator had written locally disappeared from the listing the moment they pointed the
|
|
65
|
+
* CLI at their own server. It now MERGES, under the precedence documented in
|
|
66
|
+
* src/lib/registry-merge.ts: custom > extension > local > remote > official, "whichever
|
|
67
|
+
* copy this machine would actually use wins the listing".
|
|
68
|
+
*
|
|
69
|
+
* The profile (`all` vs the curated basic set) applies to the local half only. The
|
|
70
|
+
* instance's skills are never filtered by it: the basic profile is a hand-written list of
|
|
71
|
+
* ten bundled names, so applying it to remote entries would drop every published skill
|
|
72
|
+
* from `skills list --remote` - the same disappearance this change exists to fix.
|
|
73
|
+
*
|
|
74
|
+
* A remote failure is still fatal. An explicit `--remote` request (and a configured,
|
|
75
|
+
* authenticated default read) that fails surfaces a clear error, and silently returning
|
|
76
|
+
* the local half of a merge the user asked to include the remote half in would report
|
|
77
|
+
* success for a listing that is missing entries.
|
|
78
|
+
*/
|
|
79
|
+
export declare function getBrowseRegistry(options?: {
|
|
80
|
+
all?: boolean;
|
|
81
|
+
remote?: boolean;
|
|
82
|
+
}): Promise<SkillMeta[]>;
|
|
83
|
+
export {};
|
|
@@ -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,46 @@
|
|
|
1
|
+
import { type RemoteWorkspaceMembersOptions } from "./remote-workspace.js";
|
|
2
|
+
import { type SetRemoteWorkspaceMemberRole, type RemoveRemoteWorkspaceMember } from "./remote-workspace.js";
|
|
3
|
+
import { type UpdateRemoteProfile, type UpdateRemoteWorkspace } from "./remote-profile.js";
|
|
4
|
+
export declare class HostedApiError extends Error {
|
|
5
|
+
readonly status?: number;
|
|
6
|
+
readonly code?: string;
|
|
7
|
+
readonly detail?: string;
|
|
8
|
+
readonly endpoint?: string;
|
|
9
|
+
readonly apiUrl?: string;
|
|
10
|
+
constructor(message: string, options?: {
|
|
11
|
+
status?: number;
|
|
12
|
+
code?: string;
|
|
13
|
+
detail?: string;
|
|
14
|
+
endpoint?: string;
|
|
15
|
+
apiUrl?: string;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Passwordless auth transport for an explicitly selected instance. It never writes credentials. */
|
|
19
|
+
export declare class RemoteSkillsAuthClient {
|
|
20
|
+
readonly apiOrigin: string;
|
|
21
|
+
constructor(apiUrl: string);
|
|
22
|
+
requestCode(email: string): Promise<any>;
|
|
23
|
+
verifyCode(email: string, code: string): Promise<any>;
|
|
24
|
+
startDevice(): Promise<any>;
|
|
25
|
+
pollDevice(deviceCode: string): Promise<any>;
|
|
26
|
+
private sessionClient;
|
|
27
|
+
createApiKey(email: string, code: string, name: string, scopes?: string[]): Promise<{
|
|
28
|
+
[field: string]: unknown;
|
|
29
|
+
key: string;
|
|
30
|
+
}>;
|
|
31
|
+
listApiKeys(email: string, code: string): Promise<Record<string, unknown>[]>;
|
|
32
|
+
revokeApiKey(email: string, code: string, keyId: string): Promise<Record<string, unknown>>;
|
|
33
|
+
/** Reauthentication is ephemeral: it never replaces a saved key or profile. */
|
|
34
|
+
updateProfile(email: string, code: string, input: UpdateRemoteProfile): Promise<{
|
|
35
|
+
user: import("./remote-profile.js").RemoteCustomerProfile;
|
|
36
|
+
}>;
|
|
37
|
+
updateCurrentWorkspace(email: string, code: string, input: UpdateRemoteWorkspace): Promise<{
|
|
38
|
+
organization: import("./remote-profile.js").RemoteCurrentWorkspace;
|
|
39
|
+
}>;
|
|
40
|
+
/** Fresh owner/admin session; no saved credential or profile is replaced. */
|
|
41
|
+
listWorkspaceMembers(email: string, code: string, options?: RemoteWorkspaceMembersOptions): Promise<import("./remote-workspace.js").RemoteWorkspaceMembersPage>;
|
|
42
|
+
setWorkspaceMemberRole(email: string, code: string, membershipId: string, input: SetRemoteWorkspaceMemberRole): Promise<import("./remote-workspace.js").RemoteWorkspaceMemberRoleResult>;
|
|
43
|
+
removeWorkspaceMember(email: string, code: string, membershipId: string, input: RemoveRemoteWorkspaceMember): Promise<import("./remote-workspace.js").RemoteWorkspaceMemberRemovalResult>;
|
|
44
|
+
/** Common auth transport used by CLI login, preserving the selected instance through awaits. */
|
|
45
|
+
request(path: string, options?: RequestInit): Promise<any>;
|
|
46
|
+
}
|