@hasna/skills 0.1.64 → 0.1.66

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.
@@ -96,7 +96,7 @@ export interface SyncSkillsOptions {
96
96
  * package root (which contains `skills/`). Takes precedence over
97
97
  * $SKILLS_SOURCE, which takes precedence over the installed corpus cache.
98
98
  * Agent-workflow skills are NOT part of the public repo corpus anymore — they
99
- * moved to the private per-station store (hasna-internal/fleet-resources) and
99
+ * moved to the private per-station store (fleet-resources) and
100
100
  * reach sync through the installed cache — so a package root resolves to
101
101
  * `skills/` only.
102
102
  */
@@ -118,7 +118,7 @@ export interface SyncSkillsResult {
118
118
  * resolved through `resolveCorpusRoot` so a migrated owner layout
119
119
  * (~/.hasna/skills/skills/) is read in preference to `installed/`. This cache is
120
120
  * also where private agent-workflow skills arrive from the per-station store
121
- * (hasna-internal/fleet-resources) and get synced into agent folders.
121
+ * (fleet-resources) and get synced into agent folders.
122
122
  *
123
123
  * A missing explicit source is an error, not a fallback to "nothing": the whole point
124
124
  * of zero-corpus is that sync must not silently sync an empty corpus because the
@@ -87,6 +87,34 @@ export declare const SKILLS_STORAGE_FALLBACK_ENV: {
87
87
  readonly syncBatchSize: "SKILLS_SYNC_BATCH_SIZE";
88
88
  readonly dryRun: "SKILLS_SYNC_DRY_RUN";
89
89
  };
90
+ /**
91
+ * Compatibility contract for the `./storage` subpath (`@hasna/skills/storage`).
92
+ *
93
+ * Consumers — including private SaaS embedders — compile against this subpath.
94
+ * `version` is the contract version: it MUST be bumped on every breaking
95
+ * removal or rename of a listed member. `values` are the runtime exports
96
+ * (functions, classes, constants) and `types` the type-only exports the
97
+ * subpath guarantees; `src/storage-boundary.test.ts` imports the subpath and
98
+ * fails if a listed member is no longer exported, so a removal cannot land
99
+ * silently.
100
+ *
101
+ * The retirement this contract exists to make unrepeatable: in 0.1.61 the
102
+ * storage-mode label functions, their mode type, and the storage-mode env
103
+ * variables were removed from both the main entrypoint and this subpath (see
104
+ * the CHANGELOG entry for 0.1.61, "the deployment 'mode' concept is gone").
105
+ * Nothing replaced them with a successor — the mode concept is gone and must
106
+ * not be reintroduced. The replacement for the old question ("which mode am I
107
+ * in?") is configuration-derived status: call
108
+ * `getSkillsNativeStorageStatus()` (aliases `getSkillsStorageStatus` /
109
+ * `getStorageStatus`) and read `remote.databaseConfigured` /
110
+ * `remote.s3Configured`. On-box SQLite and files are always present; Postgres
111
+ * and S3 are used when, and only when, their variables are set.
112
+ */
113
+ export declare const storageCapabilities: {
114
+ readonly version: 1;
115
+ readonly values: readonly ["SKILLS_NATIVE_STORAGE_ENV", "SKILLS_NATIVE_STORAGE_FALLBACK_ENV", "SKILLS_STORAGE_ENV", "SKILLS_STORAGE_FALLBACK_ENV", "SKILLS_STORAGE_TABLES", "STORAGE_TABLES", "SkillsPostgresSyncStore", "SkillsS3ObjectStore", "buildSkillsS3ObjectUrl", "createSkillsPostgresSyncStore", "createSkillsS3ObjectStore", "createSkillsSnapshotSyncRecord", "exportSkillsLocalSnapshot", "getSkillsNativeStorageStatus", "getSkillsStorageDatabaseEnv", "getSkillsStorageDatabaseUrl", "getSkillsStorageStatus", "getStorageDatabaseEnv", "getStorageDatabaseUrl", "getStorageStatus", "importSkillsLocalSnapshot", "planSkillsS3SnapshotUpload", "resolveSkillsNativeStorageConfig", "resolveStorageConfig", "signSkillsAwsV4Request", "skillsPostgresSyncSchemaSql", "storageCapabilities", "uploadSkillsSnapshotFilesToS3"];
116
+ readonly types: readonly ["AwsCredentials", "SignSkillsAwsV4RequestOptions", "SkillsFetch", "SkillsLocalSnapshot", "SkillsNativeStorageConfig", "SkillsNativeStorageStatus", "SkillsPostgresQueryClient", "SkillsS3ObjectStoreOptions", "SkillsS3PutObjectOptions", "SkillsS3SnapshotPlanEntry", "SkillsS3StoredObject", "SkillsSnapshotFile", "SkillsStorageTable", "SkillsSyncRecord"];
117
+ };
90
118
  export declare function resolveSkillsNativeStorageConfig(env?: Record<string, string | undefined>): SkillsNativeStorageConfig;
91
119
  export declare function resolveStorageConfig(env?: Record<string, string | undefined>): SkillsNativeStorageConfig;
92
120
  export declare function getSkillsStorageDatabaseEnv(env?: Record<string, string | undefined>): string;
@@ -98,5 +98,19 @@ export interface WriteCorpusSkillInput {
98
98
  * skill — but removes nothing else, so a re-pull never destroys sibling files.
99
99
  */
100
100
  export declare function writeCorpusSkill(input: WriteCorpusSkillInput, options?: PortableSkillOptions): PortableSkillWriteResult;
101
+ /**
102
+ * Atomically replace the corpus entry for a metadata-only pull (todos b4d956a3):
103
+ * stage SKILL.md and skill.json in a sibling directory, then rename into place — the
104
+ * existing entry is moved aside first and removed only after the staged tree is in
105
+ * position. A failure at any point leaves either the old entry or nothing — never a
106
+ * partial skill, mirroring installBundleAtomically on the bundle path.
107
+ *
108
+ * The manifest construction is identical to writeCorpusSkill; what differs is that no
109
+ * write ever lands in the live target before the swap. The direct-overwrite order of
110
+ * writeCorpusSkill (SKILL.md, then skill.json) can destroy the prior good copy when a
111
+ * mid-write failure (ENOSPC, EISDIR, crash) hits between the two writes, leaving a
112
+ * truncated or mismatched SKILL.md/skill.json pair that loadRegistry then serves.
113
+ */
114
+ export declare function installCorpusSkillAtomically(input: WriteCorpusSkillInput, options?: PortableSkillOptions): PortableSkillWriteResult;
101
115
  export declare function validatePortableSkillDirectory(name: string, skillPath: string): SkillValidationResult;
102
116
  export declare function runPortableSkill(name: string, args: string[], options?: PortableSkillRunOptions): Promise<PortableSkillRunResult>;
@@ -20,6 +20,15 @@ export interface SkillMeta {
20
20
  kind?: SkillKind;
21
21
  availability?: SkillAvailabilityMetadata;
22
22
  source?: SkillSource;
23
+ /**
24
+ * Execution is server-owned per the published-skill contract: the skill's
25
+ * package.json declares `skills.runtime: "hosted"` or `skills.source:
26
+ * "remote" | "private-hosted"` (see isHostedMetadataPackage in
27
+ * hosted-skill-set.ts). A server-owned skill is submitted to the configured
28
+ * Skills API and never falls back to local execution (see resolveRunRouting
29
+ * in run-routing.ts). Absent means local execution is the contract.
30
+ */
31
+ serverOwned?: boolean;
23
32
  }
24
33
  export interface SkillAvailabilityMetadata {
25
34
  status: "available" | "unavailable";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * run-routing.ts — where a skill run executes: on this machine, or on the
3
+ * configured Skills API.
4
+ *
5
+ * One resolver serves every run surface (the CLI `skills run` path, the MCP
6
+ * `run_skill` tool, and the `skills schedule run` surface) so they cannot drift
7
+ * apart.
8
+ *
9
+ * A skill run is REMOTE only when all three hold:
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`);
14
+ * 3. the skill carries the server-owned marker from its published contract
15
+ * (`skills.runtime: "hosted"` or `skills.source: "remote" |
16
+ * "private-hosted"` in the skill's `package.json` — see
17
+ * isHostedMetadataPackage in hosted-skill-set.ts).
18
+ *
19
+ * Otherwise the run is LOCAL, and local stays the default: an unconfigured
20
+ * install never sends anything anywhere.
21
+ *
22
+ * A server-owned skill never falls back to local execution. Without the origin
23
+ * or the credential the resolver fails closed with an error naming the exact
24
+ * setup command, per the product brief: "Premium or server-executed skills
25
+ * fail closed without API credentials and do not fall back to bundled local
26
+ * execution."
27
+ *
28
+ * Premium-catalog and pricing metadata is server-side and never ships in this
29
+ * package; the resolver concerns routing only.
30
+ */
31
+ import type { SkillMeta } from "./registry-types.js";
32
+ export type RunRoutingErrorCode = "REMOTE_REQUIRES_ORIGIN" | "REMOTE_REQUIRES_CREDENTIAL";
33
+ export type RunRouting = {
34
+ route: "remote";
35
+ apiKey: string;
36
+ } | {
37
+ route: "local";
38
+ } | {
39
+ route: "error";
40
+ code: RunRoutingErrorCode;
41
+ error: string;
42
+ };
43
+ /**
44
+ * Is execution of this skill server-owned per its published contract?
45
+ * The marker is the skill contract's declaration; absent means local
46
+ * execution is the contract.
47
+ */
48
+ export declare function isServerOwnedSkill(skill: Pick<SkillMeta, "serverOwned">): boolean;
49
+ /**
50
+ * Decide the route for one skill run from injectable inputs, so tests can mock
51
+ * the origin and the credential without touching the ambient environment.
52
+ */
53
+ export declare function resolveRunRouting(skill: Pick<SkillMeta, "name" | "serverOwned">, apiKey: string | null | undefined, apiUrl: string | undefined): RunRouting;
54
+ /**
55
+ * Resolve the route against the ambient configuration and credential store.
56
+ * The returned `apiKey` on the remote route is the same value the resolver
57
+ * validated, so the caller never re-reads (and cannot diverge from) the
58
+ * credential the decision was made with.
59
+ */
60
+ export declare function resolveConfiguredRunRouting(skill: Pick<SkillMeta, "name" | "serverOwned">): RunRouting;