@hasna/skills 0.1.71 → 0.2.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 +4 -2
- package/bin/index.js +24657 -24681
- package/bin/mcp.js +372 -243
- package/bin/migrate.js +202 -41
- package/bin/server.js +3502 -2898
- package/bin/worker.js +2187 -1806
- package/dist/cli/commands/install.d.ts +16 -0
- package/dist/cli/commands/publish.d.ts +35 -0
- package/dist/cli/commands/registry.d.ts +5 -0
- package/dist/index.js +705 -363
- package/dist/lib/app-home.d.ts +111 -0
- package/dist/lib/config.d.ts +9 -10
- package/dist/lib/feedback.d.ts +6 -0
- package/dist/lib/installer.d.ts +2 -0
- package/dist/lib/pull.d.ts +18 -1
- package/dist/lib/remote-client.d.ts +15 -1
- package/dist/lib/skill-version.d.ts +11 -0
- package/dist/lib/station-hydrate.d.ts +2 -0
- package/dist/lib/station-snapshot.d.ts +1 -1
- package/dist/sdk/index.js +5723 -5340
- package/dist/server/app.d.ts +3 -0
- package/dist/server/artifact-storage.d.ts +27 -0
- package/dist/server/config.d.ts +2 -0
- package/dist/server/rows.d.ts +2 -1
- package/dist/server/seed-bundled.d.ts +20 -0
- package/dist/server/skills-api.d.ts +18 -1
- package/dist/server/sqlite-store.d.ts +3 -1
- package/dist/server/store.d.ts +6 -1
- package/dist/server/types.d.ts +43 -0
- package/dist/storage.js +156 -43
- package/migrations/postgres/0006_skill_versions.sql +30 -0
- package/migrations/sqlite/0006_skill_versions.sql +17 -0
- package/package.json +2 -2
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills app-home resolution through the @hasna/paths resolver.
|
|
3
|
+
*
|
|
4
|
+
* Skills keeps its global config (`config.json`), auth (`auth.json`), the
|
|
5
|
+
* default SQLite database (`server.db`), the corpus cache (`skills/`), and the
|
|
6
|
+
* portable-skill tree (`installed/`, `custom/`) under one data root.
|
|
7
|
+
* Historically that root was `~/.hasna/skills`. This module resolves the root
|
|
8
|
+
* through `@hasna/paths` (XDG / macOS home layout) with a gated legacy
|
|
9
|
+
* adoption: the legacy `~/.hasna/skills` stays the effective data root until
|
|
10
|
+
* the store is physically migrated to the XDG data home (`server.db` or
|
|
11
|
+
* `config.json` present there) or the operator sets the data-kind override
|
|
12
|
+
* `HASNA_DATA_HOME`. An existing live store never becomes invisible on
|
|
13
|
+
* upgrade. The exact-app overrides win unconditionally, in this order:
|
|
14
|
+
* `HASNA_SKILLS_DIR` (the shipped override), then the wave-convention aliases
|
|
15
|
+
* `HASNA_SKILLS_HOME` and `SKILLS_HOME`.
|
|
16
|
+
*
|
|
17
|
+
* Nothing moves on disk in this phase — the package just resolves the new
|
|
18
|
+
* paths.
|
|
19
|
+
*/
|
|
20
|
+
export type PathKind = "config" | "data" | "state" | "cache";
|
|
21
|
+
export interface PathsResolverOptions {
|
|
22
|
+
app: string;
|
|
23
|
+
internal?: boolean;
|
|
24
|
+
platform?: string;
|
|
25
|
+
home?: string;
|
|
26
|
+
env?: Record<string, string | undefined>;
|
|
27
|
+
}
|
|
28
|
+
export declare function dataDir(options: PathsResolverOptions): string;
|
|
29
|
+
/** Env var that relocates the skills data directory (the shipped override). */
|
|
30
|
+
export declare const DATA_DIR_ENV = "HASNA_SKILLS_DIR";
|
|
31
|
+
/** Wave-convention exact-app home aliases, honoured after HASNA_SKILLS_DIR. */
|
|
32
|
+
export declare const HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
|
|
33
|
+
export declare const SKILLS_HOME_ENV = "SKILLS_HOME";
|
|
34
|
+
/** Filename of the default SQLite database inside the skills data directory. */
|
|
35
|
+
export declare const DEFAULT_SQLITE_FILENAME = "server.db";
|
|
36
|
+
/** Filename of the global config file inside the skills data directory. */
|
|
37
|
+
export declare const GLOBAL_CONFIG_FILENAME = "config.json";
|
|
38
|
+
/**
|
|
39
|
+
* The effective user home, mirroring the pre-existing skills resolution
|
|
40
|
+
* (`HOME` || `USERPROFILE` || `os.homedir()`). Read at call time so a runtime
|
|
41
|
+
* `HOME` reassignment (e.g. a test temp home) is honoured — `os.homedir()`
|
|
42
|
+
* snapshots `HOME` at process start and, under Bun, ignores later changes.
|
|
43
|
+
*/
|
|
44
|
+
export declare function effectiveHome(): string;
|
|
45
|
+
/** The legacy (pre-XDG) data root: `~/.hasna/skills`. */
|
|
46
|
+
export declare function legacyDataRoot(): string;
|
|
47
|
+
/**
|
|
48
|
+
* The @hasna/paths-resolved (XDG / macOS home layout) data root for skills:
|
|
49
|
+
* `~/.local/share/hasna/skills` on Linux, `~/Library/Application
|
|
50
|
+
* Support/Hasna/skills` on macOS. The home override mirrors the pre-existing
|
|
51
|
+
* `$HOME`-first resolution so the resolver follows the same home the legacy
|
|
52
|
+
* path does.
|
|
53
|
+
*
|
|
54
|
+
* `env` is forwarded to the resolver: an injected env (e.g. `{}` for a
|
|
55
|
+
* staged home mirror) suppresses the process-level `HASNA_*_HOME` overrides,
|
|
56
|
+
* which describe THIS machine's live store and must not relocate a mirror.
|
|
57
|
+
* `@hasna/paths` applies env overrides before the injected home, so without
|
|
58
|
+
* this the resolver would ignore the mirror home whenever `HASNA_DATA_HOME`
|
|
59
|
+
* is set.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolverDataRoot(home?: string, env?: Record<string, string | undefined>): string;
|
|
62
|
+
/**
|
|
63
|
+
* Whether the resolver (XDG) data root should be adopted as the effective
|
|
64
|
+
* data root. The resolver root is adopted only when the operator has set
|
|
65
|
+
* `HASNA_DATA_HOME` (the data-kind override — a deliberate opt-in to the XDG
|
|
66
|
+
* layout) or the store has already been physically migrated there (`server.db`
|
|
67
|
+
* — the default SQLite store — or `config.json` exists). A machine that only
|
|
68
|
+
* redirects another kind (e.g. cache to tmpfs) must NOT have its data home
|
|
69
|
+
* moved, and a live store at the legacy home must never become invisible on
|
|
70
|
+
* upgrade.
|
|
71
|
+
*/
|
|
72
|
+
export declare function adoptResolverDataRoot(resolved: string, env?: NodeJS.ProcessEnv): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The exact-app override root, when set: the shipped `HASNA_SKILLS_DIR` wins,
|
|
75
|
+
* then the wave-convention aliases `HASNA_SKILLS_HOME` and `SKILLS_HOME`.
|
|
76
|
+
* First non-blank override wins; a blank or whitespace-only primary must not
|
|
77
|
+
* shadow a valid secondary (nullish `??` does not fall through on `""`).
|
|
78
|
+
*/
|
|
79
|
+
export declare function exactDataRoot(): string | undefined;
|
|
80
|
+
/** Whether an exact-app override root is set (used to skip legacy migration). */
|
|
81
|
+
export declare function hasExactOverride(env?: NodeJS.ProcessEnv): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Whether the operator named a data root at all — an exact-app override or the
|
|
84
|
+
* data-kind `HASNA_DATA_HOME`. Used to decide when the legacy `~/.skills` /
|
|
85
|
+
* `~/.skillsrc` migration must be skipped: copying a stray legacy tree into an
|
|
86
|
+
* operator-chosen directory would be a surprising write.
|
|
87
|
+
*/
|
|
88
|
+
export declare function hasOperatorOverride(env?: NodeJS.ProcessEnv): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* The effective data root: an exact-app override (`HASNA_SKILLS_DIR`, then
|
|
91
|
+
* `HASNA_SKILLS_HOME` / `SKILLS_HOME`) wins unconditionally; otherwise the
|
|
92
|
+
* resolver (XDG) data root once adopted; otherwise the legacy `~/.hasna/skills`
|
|
93
|
+
* default. Write-free: callers that need the directory to exist create it.
|
|
94
|
+
*/
|
|
95
|
+
export declare function getDataRoot(): string;
|
|
96
|
+
/**
|
|
97
|
+
* The skills app data root for an explicit home root, mirroring getDataRoot()
|
|
98
|
+
* with the home injected. Used by the sync-home snapshot mapping to enumerate
|
|
99
|
+
* the skills corpus under a staged home mirror (`homesRoot`) or the real home.
|
|
100
|
+
*
|
|
101
|
+
* Process-level overrides (`HASNA_SKILLS_DIR` / `HASNA_SKILLS_HOME` /
|
|
102
|
+
* `SKILLS_HOME`, and the data-kind `HASNA_DATA_HOME`) describe THIS machine's
|
|
103
|
+
* live store. They apply only when the requested home IS the process's own
|
|
104
|
+
* effective home; a staged mirror (a different home, e.g. an rsync'd
|
|
105
|
+
* remote-station `homesRoot`) must resolve its own layout — the mirror's XDG
|
|
106
|
+
* data root once the mirror itself carries a migrated store there, else the
|
|
107
|
+
* mirror's legacy `~/.hasna/skills` — never the local process's live data
|
|
108
|
+
* root. Snapshotting a staged home with the local `HASNA_DATA_HOME` set would
|
|
109
|
+
* otherwise read live local data instead of the supplied mirror.
|
|
110
|
+
*/
|
|
111
|
+
export declare function skillsDataRootForHome(home: string): string;
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Values from the project config override global config.
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* Environment variable that relocates the skills data directory.
|
|
13
|
+
*
|
|
14
|
+
* Owned by the @hasna/paths-based app-home resolver (app-home.ts), re-exported
|
|
15
|
+
* here so every existing reader keeps agreeing on the name.
|
|
16
|
+
*/
|
|
17
|
+
export { DATA_DIR_ENV } from "./app-home.js";
|
|
11
18
|
/**
|
|
12
19
|
* There is no deployment "mode" key.
|
|
13
20
|
*
|
|
@@ -35,14 +42,6 @@ export interface SkillsConfig {
|
|
|
35
42
|
extensionsDir?: string;
|
|
36
43
|
}
|
|
37
44
|
export type ConfigScope = "global" | "project";
|
|
38
|
-
/**
|
|
39
|
-
* Environment variable that relocates the skills data directory.
|
|
40
|
-
*
|
|
41
|
-
* Exported so that every reader agrees on the name: previously the literal was
|
|
42
|
-
* duplicated in portable-skills.ts and honoured there but *not* in getDataDir(),
|
|
43
|
-
* which is what made the override only half work (see getDataDir below).
|
|
44
|
-
*/
|
|
45
|
-
export declare const DATA_DIR_ENV = "HASNA_SKILLS_DIR";
|
|
46
45
|
/**
|
|
47
46
|
* Subfolder of the data directory holding the installed skill corpus.
|
|
48
47
|
*
|
|
@@ -92,8 +91,8 @@ export declare function getDataDir(): string;
|
|
|
92
91
|
*
|
|
93
92
|
* getDataDir() itself writes: it mkdirs the app folder, merges legacy ~/.skills
|
|
94
93
|
* content and copies the legacy config file. A dry run must resolve the SAME
|
|
95
|
-
* directory a real run would use without performing any of that —
|
|
96
|
-
*
|
|
94
|
+
* directory a real run would use without performing any of that — the app-home
|
|
95
|
+
* resolver (getDataRoot) is already write-free, so this mirrors it directly.
|
|
97
96
|
*/
|
|
98
97
|
export declare function getDataDirReadOnly(): string;
|
|
99
98
|
/**
|
package/dist/lib/feedback.d.ts
CHANGED
|
@@ -20,3 +20,9 @@ export interface FeedbackResult {
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function getFeedbackDbPath(): string;
|
|
22
22
|
export declare function saveFeedback(input: FeedbackInput): FeedbackResult;
|
|
23
|
+
/**
|
|
24
|
+
* True when this install is pointed at a Skills instance: the env var, its HASNA_-prefixed
|
|
25
|
+
* alias, or the config file written by `skills setup --api-url` / `skills login`. The check
|
|
26
|
+
* never throws - a broken config file means local mode, not a crash in `skills feedback`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isApiMode(env?: Record<string, string | undefined>): boolean;
|
package/dist/lib/installer.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface InstallResult {
|
|
|
16
16
|
path?: string;
|
|
17
17
|
mode?: InstallMode;
|
|
18
18
|
source?: InstallSource;
|
|
19
|
+
/** Exact published version when the caller asked for name@version (hasna/apps#1630). */
|
|
20
|
+
version?: string;
|
|
19
21
|
}
|
|
20
22
|
export interface InstallOptions {
|
|
21
23
|
targetDir?: string;
|
package/dist/lib/pull.d.ts
CHANGED
|
@@ -25,7 +25,15 @@ export interface SkillPullClient {
|
|
|
25
25
|
* `Response` is the natural transport type here: verification reads the
|
|
26
26
|
* X-Skill-Bundle-Sha256 / X-Skill-Bundle-Signature headers off it.
|
|
27
27
|
*/
|
|
28
|
-
getBundle(slug: string): Promise<Response | null>;
|
|
28
|
+
getBundle(slug: string, version?: string): Promise<Response | null>;
|
|
29
|
+
/**
|
|
30
|
+
* The registry's record of one published version (hasna/apps#1630), or null. Optional:
|
|
31
|
+
* an exact-version pull proves the received bytes against this digest; a client that
|
|
32
|
+
* cannot answer it falls back to the bundle's own digest header.
|
|
33
|
+
*/
|
|
34
|
+
getSkillVersion?(slug: string, version: string): Promise<{
|
|
35
|
+
bundleSha256?: string;
|
|
36
|
+
} | null>;
|
|
29
37
|
}
|
|
30
38
|
export interface PullSkillsOptions extends PortableSkillOptions {
|
|
31
39
|
/** Explicit skill names to pull. Ignored when `all` is set. */
|
|
@@ -136,3 +144,12 @@ export declare function writePullMarker(dir: string, record: {
|
|
|
136
144
|
revisionId?: string;
|
|
137
145
|
source?: "pull" | "sync";
|
|
138
146
|
}): void;
|
|
147
|
+
/**
|
|
148
|
+
* Split `name@version` into its parts. A name without `@` pulls the current revision; an
|
|
149
|
+
* empty version (`name@`) is an error the caller reports. Scoped-looking names are not a
|
|
150
|
+
* concern here: skill slugs never start with `@`.
|
|
151
|
+
*/
|
|
152
|
+
export declare function splitNameVersion(raw: string): {
|
|
153
|
+
name: string;
|
|
154
|
+
version?: string;
|
|
155
|
+
};
|
|
@@ -22,6 +22,16 @@ export declare class RemoteRequestError extends Error {
|
|
|
22
22
|
* (`{ slug, pinnedAt, metadata }`). `pinnedAt`/`metadata` are server-reported
|
|
23
23
|
* and may be absent.
|
|
24
24
|
*/
|
|
25
|
+
export interface RemoteSkillVersion {
|
|
26
|
+
slug: string;
|
|
27
|
+
version: string;
|
|
28
|
+
bundleSha256: string;
|
|
29
|
+
bundleByteSize: number;
|
|
30
|
+
storageKind?: string;
|
|
31
|
+
manifest?: Record<string, unknown>;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
current?: boolean;
|
|
34
|
+
}
|
|
25
35
|
export interface RemotePin {
|
|
26
36
|
slug: string;
|
|
27
37
|
pinnedAt?: string;
|
|
@@ -109,8 +119,12 @@ export declare class RemoteSkillsClient {
|
|
|
109
119
|
* read the X-Skill-Bundle-Sha256 / X-Skill-Bundle-Signature headers, or null when the
|
|
110
120
|
* instance serves no bundle for this skill (the metadata-only fallback path).
|
|
111
121
|
*/
|
|
112
|
-
getBundle(slug: string): Promise<Response | null>;
|
|
122
|
+
getBundle(slug: string, version?: string): Promise<Response | null>;
|
|
113
123
|
/** List the pins the instance holds for this principal. */
|
|
124
|
+
/** Every published version of a slug, newest first (hasna/apps#1630). */
|
|
125
|
+
listSkillVersions(slug: string): Promise<RemoteSkillVersion[]>;
|
|
126
|
+
/** One version's manifest, or null when the slug@version was never published. */
|
|
127
|
+
getSkillVersion(slug: string, version: string): Promise<RemoteSkillVersion | null>;
|
|
114
128
|
listPins(): Promise<RemotePin[]>;
|
|
115
129
|
/**
|
|
116
130
|
* Pin a skill on the instance (upsert — pinning again refreshes it). The
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a skill version string may look like (hasna/apps#1630).
|
|
3
|
+
*
|
|
4
|
+
* The version becomes a path segment in the version-addressed object key and in the
|
|
5
|
+
* `/versions/:version` routes, so it is a closed alphabet rather than "whatever the
|
|
6
|
+
* publisher sent": no separators, no dot-only names, bounded length. Semver fits; so do
|
|
7
|
+
* date stamps and package-style prereleases.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SKILL_VERSION_PATTERN: RegExp;
|
|
10
|
+
export declare function isValidSkillVersion(value: unknown): boolean;
|
|
11
|
+
export declare const SKILL_VERSION_RULE = "1-128 characters: letters, digits, '.', '_', '+', '-'; must start with a letter or digit; no '..'";
|
|
@@ -20,6 +20,8 @@ export interface HydrationCandidate {
|
|
|
20
20
|
mtimeMs: number;
|
|
21
21
|
/** The manifest's recorded sha256 for (agent, home-relative path), if any. */
|
|
22
22
|
manifestHash: string | null;
|
|
23
|
+
/** True when the candidate's bytes hash to the manifest record (verified match). */
|
|
24
|
+
verified: boolean;
|
|
23
25
|
}
|
|
24
26
|
export interface HydrationWinnerFile {
|
|
25
27
|
withinIdent: string;
|
|
@@ -11,7 +11,7 @@ export declare const STATION_SNAPSHOT_PRODUCER: {
|
|
|
11
11
|
name: string;
|
|
12
12
|
version: string;
|
|
13
13
|
};
|
|
14
|
-
export type StationSnapshotErrorCode = "INVALID_STATION" | "SYMLINKS_REFUSED" | "CONFLICT" | "DESTINATION_ESCAPE" | "MANIFEST_UNREADABLE";
|
|
14
|
+
export type StationSnapshotErrorCode = "INVALID_STATION" | "SYMLINKS_REFUSED" | "CONFLICT" | "DESTINATION_ESCAPE" | "MANIFEST_UNREADABLE" | "MANIFEST_HASH_MISMATCH";
|
|
15
15
|
export declare class StationSnapshotError extends Error {
|
|
16
16
|
readonly code: StationSnapshotErrorCode;
|
|
17
17
|
/** Per-item lines for the CONFLICT class (printed before the summary). */
|