@hasna/skills 0.1.72 → 0.2.1
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 +29419 -18305
- package/bin/mcp.js +13798 -3053
- package/bin/migrate.js +114 -62
- package/bin/server.js +3517 -3015
- package/bin/worker.js +1788 -1513
- package/dist/admin-contract.d.ts +887 -5832
- package/dist/admin-contract.js +13914 -3611
- package/dist/cli/commands/install.d.ts +16 -0
- package/dist/cli/commands/publish.d.ts +49 -0
- package/dist/cli/commands/registry.d.ts +5 -0
- package/dist/index.js +14654 -4123
- package/dist/lib/app-home.d.ts +27 -1
- package/dist/lib/feedback.d.ts +6 -0
- package/dist/lib/installer.d.ts +2 -0
- package/dist/lib/portable-skills-files.d.ts +9 -0
- package/dist/lib/portable-skills.d.ts +2 -2
- 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/sdk/index.js +20030 -9222
- package/dist/sdk/runs.d.ts +19 -101
- package/dist/server/app.d.ts +3 -0
- package/dist/server/artifact-storage.d.ts +38 -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 +31 -3
- 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 +62 -61
- package/migrations/postgres/0006_skill_versions.sql +30 -0
- package/migrations/sqlite/0006_skill_versions.sql +17 -0
- package/package.json +2 -3
package/dist/storage.js
CHANGED
|
@@ -56,11 +56,11 @@ import {
|
|
|
56
56
|
statSync as statSync2,
|
|
57
57
|
writeFileSync as writeFileSync3
|
|
58
58
|
} from "fs";
|
|
59
|
-
import { dirname as dirname2, join as
|
|
59
|
+
import { dirname as dirname2, join as join4, normalize, relative, sep } from "path";
|
|
60
60
|
|
|
61
61
|
// src/lib/config.ts
|
|
62
62
|
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
63
|
-
import { join as
|
|
63
|
+
import { join as join2, dirname } from "path";
|
|
64
64
|
|
|
65
65
|
// src/lib/retired-settings.ts
|
|
66
66
|
var RETIRED_ENV_SUFFIXES = ["_STORAGE_MODE", "_DEPLOYMENT_MODE", "_CLOUD_MODE"];
|
|
@@ -110,94 +110,87 @@ function assertNoRetiredConfigKeys(config, source) {
|
|
|
110
110
|
|
|
111
111
|
// src/lib/app-home.ts
|
|
112
112
|
import { existsSync } from "fs";
|
|
113
|
-
import { homedir as homedir2 } from "os";
|
|
114
|
-
import { join as join2, resolve } from "path";
|
|
115
|
-
|
|
116
|
-
// ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
|
|
117
113
|
import { homedir } from "os";
|
|
118
|
-
import { join } from "path";
|
|
119
|
-
|
|
114
|
+
import { join, resolve } from "path";
|
|
115
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
116
|
+
import { join as pathsResolverJoin } from "path";
|
|
117
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
120
118
|
config: "HASNA_CONFIG_HOME",
|
|
121
119
|
data: "HASNA_DATA_HOME",
|
|
122
120
|
state: "HASNA_STATE_HOME",
|
|
123
121
|
cache: "HASNA_CACHE_HOME"
|
|
124
122
|
};
|
|
125
|
-
var
|
|
126
|
-
function
|
|
123
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
124
|
+
function pathsResolverAssertApp(app) {
|
|
127
125
|
if (typeof app !== "string" || app.length === 0) {
|
|
128
126
|
throw new TypeError("paths: app must be a non-empty string");
|
|
129
127
|
}
|
|
130
|
-
if (!
|
|
128
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
131
129
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
132
130
|
}
|
|
133
131
|
}
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
139
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
140
|
-
}
|
|
141
|
-
function isMacOS(platform) {
|
|
142
|
-
return platform === "darwin";
|
|
132
|
+
function pathsResolverAssertKind(kind) {
|
|
133
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
134
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
135
|
+
}
|
|
143
136
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
137
|
+
function pathsResolverBaseDir(kind, options) {
|
|
138
|
+
pathsResolverAssertKind(kind);
|
|
139
|
+
const env = options.env ?? process.env;
|
|
140
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
141
|
+
if (typeof override === "string" && override.length > 0)
|
|
147
142
|
return override;
|
|
148
|
-
const home = options.home ??
|
|
143
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
149
144
|
const platform = options.platform ?? process.platform;
|
|
150
|
-
if (
|
|
145
|
+
if (platform === "darwin") {
|
|
151
146
|
switch (kind) {
|
|
152
147
|
case "config":
|
|
153
148
|
case "data":
|
|
154
|
-
return
|
|
149
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
155
150
|
case "cache":
|
|
156
|
-
return
|
|
151
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
157
152
|
case "state":
|
|
158
|
-
return
|
|
153
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
159
154
|
}
|
|
160
155
|
}
|
|
161
156
|
switch (kind) {
|
|
162
157
|
case "config":
|
|
163
|
-
return
|
|
158
|
+
return pathsResolverJoin(home, ".config", "hasna");
|
|
164
159
|
case "data":
|
|
165
|
-
return
|
|
160
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
166
161
|
case "state":
|
|
167
|
-
return
|
|
162
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
168
163
|
case "cache":
|
|
169
|
-
return
|
|
164
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
170
165
|
}
|
|
171
166
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
const appSegment = options.internal === true ?
|
|
175
|
-
return
|
|
167
|
+
function pathsResolverResolve(kind, options) {
|
|
168
|
+
pathsResolverAssertApp(options.app);
|
|
169
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
170
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
176
171
|
}
|
|
177
172
|
function dataDir(options) {
|
|
178
|
-
return
|
|
173
|
+
return pathsResolverResolve("data", options);
|
|
179
174
|
}
|
|
180
|
-
|
|
181
|
-
// src/lib/app-home.ts
|
|
182
175
|
var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
|
|
183
176
|
var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
|
|
184
177
|
var SKILLS_HOME_ENV = "SKILLS_HOME";
|
|
185
178
|
var DEFAULT_SQLITE_FILENAME = "server.db";
|
|
186
179
|
var GLOBAL_CONFIG_FILENAME = "config.json";
|
|
187
180
|
function effectiveHome() {
|
|
188
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
181
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
|
|
189
182
|
}
|
|
190
183
|
function legacyDataRoot() {
|
|
191
|
-
return
|
|
184
|
+
return join(effectiveHome(), ".hasna", "skills");
|
|
192
185
|
}
|
|
193
|
-
function resolverDataRoot(home = effectiveHome()) {
|
|
194
|
-
return dataDir({ app: "skills", home });
|
|
186
|
+
function resolverDataRoot(home = effectiveHome(), env) {
|
|
187
|
+
return dataDir({ app: "skills", home, env });
|
|
195
188
|
}
|
|
196
189
|
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
197
190
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
198
191
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
199
192
|
return true;
|
|
200
|
-
return existsSync(
|
|
193
|
+
return existsSync(join(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync(join(resolved, GLOBAL_CONFIG_FILENAME));
|
|
201
194
|
}
|
|
202
195
|
function exactDataRoot() {
|
|
203
196
|
for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
|
|
@@ -221,8 +214,16 @@ function getDataRoot() {
|
|
|
221
214
|
return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
|
|
222
215
|
}
|
|
223
216
|
function skillsDataRootForHome(home) {
|
|
224
|
-
const
|
|
225
|
-
|
|
217
|
+
const isOwnHome = resolve(home) === resolve(effectiveHome()) || resolve(home) === resolve(homedir());
|
|
218
|
+
if (isOwnHome) {
|
|
219
|
+
const exact = exactDataRoot();
|
|
220
|
+
if (exact)
|
|
221
|
+
return exact;
|
|
222
|
+
const resolved2 = resolverDataRoot(home);
|
|
223
|
+
return adoptResolverDataRoot(resolved2) ? resolve(resolved2) : resolve(join(home, ".hasna", "skills"));
|
|
224
|
+
}
|
|
225
|
+
const resolved = resolverDataRoot(home, {});
|
|
226
|
+
return adoptResolverDataRoot(resolved, {}) ? resolve(resolved) : resolve(join(home, ".hasna", "skills"));
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
// src/lib/config.ts
|
|
@@ -243,8 +244,8 @@ function mergeDirectoryContents(sourceDir, targetDir) {
|
|
|
243
244
|
return;
|
|
244
245
|
mkdirSync(targetDir, { recursive: true });
|
|
245
246
|
for (const entry of readdirSync(sourceDir)) {
|
|
246
|
-
const sourcePath =
|
|
247
|
-
const targetPath =
|
|
247
|
+
const sourcePath = join2(sourceDir, entry);
|
|
248
|
+
const targetPath = join2(targetDir, entry);
|
|
248
249
|
try {
|
|
249
250
|
const sourceStat = statSync(sourcePath);
|
|
250
251
|
if (sourceStat.isDirectory()) {
|
|
@@ -280,7 +281,7 @@ var INSTALLED_SKILLS_DIRNAME = "installed";
|
|
|
280
281
|
var SKILLS_CACHE_DIRNAME = "skills";
|
|
281
282
|
var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
|
|
282
283
|
function isOwnerLayoutMigrated(appDir) {
|
|
283
|
-
return existsSync2(
|
|
284
|
+
return existsSync2(join2(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
|
|
284
285
|
}
|
|
285
286
|
function getDataDir() {
|
|
286
287
|
const root = getDataRoot();
|
|
@@ -290,23 +291,23 @@ function getDataDir() {
|
|
|
290
291
|
if (hasOperatorOverride())
|
|
291
292
|
return root;
|
|
292
293
|
const home = effectiveHome();
|
|
293
|
-
const oldDir =
|
|
294
|
-
const oldConfigFile =
|
|
294
|
+
const oldDir = join2(home, ".skills");
|
|
295
|
+
const oldConfigFile = join2(home, ".skillsrc");
|
|
295
296
|
try {
|
|
296
297
|
mergeDirectoryContents(oldDir, root);
|
|
297
298
|
} catch {}
|
|
298
|
-
if (existsSync2(oldConfigFile) && !existsSync2(
|
|
299
|
+
if (existsSync2(oldConfigFile) && !existsSync2(join2(root, "config.json"))) {
|
|
299
300
|
try {
|
|
300
|
-
copyFileSync(oldConfigFile,
|
|
301
|
+
copyFileSync(oldConfigFile, join2(root, "config.json"));
|
|
301
302
|
} catch {}
|
|
302
303
|
}
|
|
303
304
|
return root;
|
|
304
305
|
}
|
|
305
306
|
function getConfigPath(scope) {
|
|
306
307
|
if (scope === "global") {
|
|
307
|
-
return
|
|
308
|
+
return join2(getDataDir(), "config.json");
|
|
308
309
|
}
|
|
309
|
-
return
|
|
310
|
+
return join2(process.cwd(), "skills.config.json");
|
|
310
311
|
}
|
|
311
312
|
function readConfigFile(path) {
|
|
312
313
|
if (!existsSync2(path))
|
|
@@ -391,7 +392,7 @@ function unsetConfig(key, scope = "project") {
|
|
|
391
392
|
|
|
392
393
|
// src/lib/project-state.ts
|
|
393
394
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
394
|
-
import { join as
|
|
395
|
+
import { join as join3 } from "path";
|
|
395
396
|
|
|
396
397
|
// src/lib/utils.ts
|
|
397
398
|
function normalizeSkillName(name) {
|
|
@@ -413,10 +414,10 @@ var SKILLS_PROJECT_DIR = ".skills";
|
|
|
413
414
|
var PROJECT_CONFIG_FILE = "project.json";
|
|
414
415
|
var DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
415
416
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
416
|
-
return
|
|
417
|
+
return join3(targetDir, SKILLS_PROJECT_DIR);
|
|
417
418
|
}
|
|
418
419
|
function getProjectConfigPath(targetDir = process.cwd()) {
|
|
419
|
-
return
|
|
420
|
+
return join3(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
|
|
420
421
|
}
|
|
421
422
|
function loadProjectConfig(targetDir = process.cwd()) {
|
|
422
423
|
const path = getProjectConfigPath(targetDir);
|
|
@@ -673,7 +674,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
673
674
|
local: {
|
|
674
675
|
dataDir: getDataDir(),
|
|
675
676
|
projectStateDir: getProjectStateDir(targetDir),
|
|
676
|
-
feedbackDbPath:
|
|
677
|
+
feedbackDbPath: join4(getDataDir(), "skills.db")
|
|
677
678
|
},
|
|
678
679
|
remote: {
|
|
679
680
|
databaseConfigured: Boolean(config.databaseUrl),
|
|
@@ -1030,7 +1031,7 @@ function parsePositiveInteger(value) {
|
|
|
1030
1031
|
function walkFiles(dir) {
|
|
1031
1032
|
const files = [];
|
|
1032
1033
|
for (const entry of readdirSync2(dir)) {
|
|
1033
|
-
const full =
|
|
1034
|
+
const full = join4(dir, entry);
|
|
1034
1035
|
const stats = statSync2(full);
|
|
1035
1036
|
if (stats.isDirectory())
|
|
1036
1037
|
files.push(...walkFiles(full));
|
|
@@ -1047,7 +1048,7 @@ function resolveSnapshotPath(targetDir, snapshotPath) {
|
|
|
1047
1048
|
if (!toPosix(normalizedPath).startsWith(".skills/")) {
|
|
1048
1049
|
throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
|
|
1049
1050
|
}
|
|
1050
|
-
return
|
|
1051
|
+
return join4(targetDir, normalizedPath);
|
|
1051
1052
|
}
|
|
1052
1053
|
function normalizeS3Prefix(prefix) {
|
|
1053
1054
|
return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- Immutable skill versions (hasna/apps#1630).
|
|
2
|
+
--
|
|
3
|
+
-- skills_registry keeps ONE row per (org, slug): the current revision. Every re-publish
|
|
4
|
+
-- replaced it and purged the previous bundle, so there was no history. This table records
|
|
5
|
+
-- each published name@version once: the content-addressed bundle it points at, the file
|
|
6
|
+
-- manifest and provenance the client sent, and where the version-addressed copy lives.
|
|
7
|
+
--
|
|
8
|
+
-- Rows are never updated. A publish of an existing (org, slug, version) with a different
|
|
9
|
+
-- bundle digest is refused (409 SKILL_VERSION_EXISTS); the same digest is idempotent.
|
|
10
|
+
-- bundle_sha256 references skills_bundles(org_id, sha256) softly, like skills_registry
|
|
11
|
+
-- does; orphan collection must consult this table so a version's bundle outlives the
|
|
12
|
+
-- registry row that first published it.
|
|
13
|
+
CREATE TABLE IF NOT EXISTS skills_versions (
|
|
14
|
+
org_id text NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
15
|
+
slug text NOT NULL,
|
|
16
|
+
version text NOT NULL,
|
|
17
|
+
bundle_sha256 text NOT NULL,
|
|
18
|
+
bundle_byte_size integer NOT NULL,
|
|
19
|
+
-- 'db' when the bytes live in skills_bundles.body_blob, 's3' when a version-addressed
|
|
20
|
+
-- object (<prefix>/skills/<org>/<slug>/<version>/bundle.tar.gz) was written.
|
|
21
|
+
storage_kind text NOT NULL DEFAULT 'db',
|
|
22
|
+
storage_key text,
|
|
23
|
+
manifest_json jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
24
|
+
published_by_user_id text REFERENCES users(id) ON DELETE SET NULL,
|
|
25
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
26
|
+
PRIMARY KEY (org_id, slug, version)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE INDEX IF NOT EXISTS skills_versions_org_slug_created_idx ON skills_versions (org_id, slug, created_at DESC);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS skills_versions_bundle_idx ON skills_versions (org_id, bundle_sha256);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Immutable skill versions (hasna/apps#1630). SQLite twin of migrations/postgres/0006.
|
|
2
|
+
CREATE TABLE IF NOT EXISTS skills_versions (
|
|
3
|
+
org_id text NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
4
|
+
slug text NOT NULL,
|
|
5
|
+
version text NOT NULL,
|
|
6
|
+
bundle_sha256 text NOT NULL,
|
|
7
|
+
bundle_byte_size integer NOT NULL,
|
|
8
|
+
storage_kind text NOT NULL DEFAULT 'db',
|
|
9
|
+
storage_key text,
|
|
10
|
+
manifest_json text NOT NULL DEFAULT '{}',
|
|
11
|
+
published_by_user_id text REFERENCES users(id) ON DELETE SET NULL,
|
|
12
|
+
created_at text NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
13
|
+
PRIMARY KEY (org_id, slug, version)
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX IF NOT EXISTS skills_versions_org_slug_created_idx ON skills_versions (org_id, slug, created_at DESC);
|
|
17
|
+
CREATE INDEX IF NOT EXISTS skills_versions_bundle_idx ON skills_versions (org_id, bundle_sha256);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/skills",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Skills library for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"author": "Hasna",
|
|
81
81
|
"license": "Apache-2.0",
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@types/bun": "
|
|
83
|
+
"@types/bun": "1.3.14",
|
|
84
84
|
"@types/node": "25.2.3",
|
|
85
85
|
"@types/react": "^18.2.0",
|
|
86
86
|
"bun-types": "1.3.14",
|
|
@@ -92,7 +92,6 @@
|
|
|
92
92
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
93
93
|
"@aws-sdk/client-s3": "^3.1079.0",
|
|
94
94
|
"@hasna/events": "0.1.16",
|
|
95
|
-
"@hasna/paths": "0.1.0",
|
|
96
95
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
97
96
|
"chalk": "^5.3.0",
|
|
98
97
|
"commander": "^12.1.0",
|