@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/bin/migrate.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/server/migrate.ts
|
|
5
5
|
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync3 } from "fs";
|
|
6
|
-
import { join as
|
|
6
|
+
import { join as join6 } from "path";
|
|
7
7
|
|
|
8
8
|
// src/lib/retired-settings.ts
|
|
9
9
|
var RETIRED_ENV_SUFFIXES = ["_STORAGE_MODE", "_DEPLOYMENT_MODE", "_CLOUD_MODE"];
|
|
@@ -56,6 +56,7 @@ function resolveServerConfig(env = process.env) {
|
|
|
56
56
|
port,
|
|
57
57
|
databaseUrl: env[DATABASE_URL_ENV] || env.DATABASE_URL || undefined,
|
|
58
58
|
bootstrapApiKey: env.HASNA_SKILLS_BOOTSTRAP_API_KEY || undefined,
|
|
59
|
+
seedBundledCorpus: (env.HASNA_SKILLS_SEED_BUNDLED_CORPUS ?? "1") !== "0",
|
|
59
60
|
artifactBucket: env.HASNA_SKILLS_S3_BUCKET || env.SKILLS_S3_BUCKET || undefined,
|
|
60
61
|
artifactPrefix: normalizePrefix(env.HASNA_SKILLS_S3_PREFIX || env.SKILLS_S3_PREFIX || "skills/artifacts"),
|
|
61
62
|
inlineWorker: env.HASNA_SKILLS_INLINE_WORKER === "1",
|
|
@@ -81,103 +82,96 @@ function normalizePrefix(value) {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
// src/server/database-url.ts
|
|
84
|
-
import { isAbsolute, join as
|
|
85
|
+
import { isAbsolute, join as join3 } from "path";
|
|
85
86
|
import { fileURLToPath } from "url";
|
|
86
87
|
|
|
87
88
|
// src/lib/config.ts
|
|
88
89
|
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
89
|
-
import { join as
|
|
90
|
+
import { join as join2, dirname } from "path";
|
|
90
91
|
|
|
91
92
|
// src/lib/app-home.ts
|
|
92
93
|
import { existsSync } from "fs";
|
|
93
|
-
import { homedir as homedir2 } from "os";
|
|
94
|
-
import { join as join2, resolve } from "path";
|
|
95
|
-
|
|
96
|
-
// ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
|
|
97
94
|
import { homedir } from "os";
|
|
98
|
-
import { join } from "path";
|
|
99
|
-
|
|
95
|
+
import { join, resolve } from "path";
|
|
96
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
97
|
+
import { join as pathsResolverJoin } from "path";
|
|
98
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
100
99
|
config: "HASNA_CONFIG_HOME",
|
|
101
100
|
data: "HASNA_DATA_HOME",
|
|
102
101
|
state: "HASNA_STATE_HOME",
|
|
103
102
|
cache: "HASNA_CACHE_HOME"
|
|
104
103
|
};
|
|
105
|
-
var
|
|
106
|
-
function
|
|
104
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
105
|
+
function pathsResolverAssertApp(app) {
|
|
107
106
|
if (typeof app !== "string" || app.length === 0) {
|
|
108
107
|
throw new TypeError("paths: app must be a non-empty string");
|
|
109
108
|
}
|
|
110
|
-
if (!
|
|
109
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
111
110
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
119
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
120
|
-
}
|
|
121
|
-
function isMacOS(platform) {
|
|
122
|
-
return platform === "darwin";
|
|
113
|
+
function pathsResolverAssertKind(kind) {
|
|
114
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
115
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
116
|
+
}
|
|
123
117
|
}
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
function pathsResolverBaseDir(kind, options) {
|
|
119
|
+
pathsResolverAssertKind(kind);
|
|
120
|
+
const env = options.env ?? process.env;
|
|
121
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
122
|
+
if (typeof override === "string" && override.length > 0)
|
|
127
123
|
return override;
|
|
128
|
-
const home = options.home ??
|
|
124
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
129
125
|
const platform = options.platform ?? process.platform;
|
|
130
|
-
if (
|
|
126
|
+
if (platform === "darwin") {
|
|
131
127
|
switch (kind) {
|
|
132
128
|
case "config":
|
|
133
129
|
case "data":
|
|
134
|
-
return
|
|
130
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
135
131
|
case "cache":
|
|
136
|
-
return
|
|
132
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
137
133
|
case "state":
|
|
138
|
-
return
|
|
134
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
139
135
|
}
|
|
140
136
|
}
|
|
141
137
|
switch (kind) {
|
|
142
138
|
case "config":
|
|
143
|
-
return
|
|
139
|
+
return pathsResolverJoin(home, ".config", "hasna");
|
|
144
140
|
case "data":
|
|
145
|
-
return
|
|
141
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
146
142
|
case "state":
|
|
147
|
-
return
|
|
143
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
148
144
|
case "cache":
|
|
149
|
-
return
|
|
145
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
150
146
|
}
|
|
151
147
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
const appSegment = options.internal === true ?
|
|
155
|
-
return
|
|
148
|
+
function pathsResolverResolve(kind, options) {
|
|
149
|
+
pathsResolverAssertApp(options.app);
|
|
150
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
151
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
156
152
|
}
|
|
157
153
|
function dataDir(options) {
|
|
158
|
-
return
|
|
154
|
+
return pathsResolverResolve("data", options);
|
|
159
155
|
}
|
|
160
|
-
|
|
161
|
-
// src/lib/app-home.ts
|
|
162
156
|
var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
|
|
163
157
|
var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
|
|
164
158
|
var SKILLS_HOME_ENV = "SKILLS_HOME";
|
|
165
159
|
var DEFAULT_SQLITE_FILENAME = "server.db";
|
|
166
160
|
var GLOBAL_CONFIG_FILENAME = "config.json";
|
|
167
161
|
function effectiveHome() {
|
|
168
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
162
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
|
|
169
163
|
}
|
|
170
164
|
function legacyDataRoot() {
|
|
171
|
-
return
|
|
165
|
+
return join(effectiveHome(), ".hasna", "skills");
|
|
172
166
|
}
|
|
173
|
-
function resolverDataRoot(home = effectiveHome()) {
|
|
174
|
-
return dataDir({ app: "skills", home });
|
|
167
|
+
function resolverDataRoot(home = effectiveHome(), env) {
|
|
168
|
+
return dataDir({ app: "skills", home, env });
|
|
175
169
|
}
|
|
176
170
|
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
177
171
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
178
172
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
179
173
|
return true;
|
|
180
|
-
return existsSync(
|
|
174
|
+
return existsSync(join(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync(join(resolved, GLOBAL_CONFIG_FILENAME));
|
|
181
175
|
}
|
|
182
176
|
function exactDataRoot() {
|
|
183
177
|
for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
|
|
@@ -206,8 +200,8 @@ function mergeDirectoryContents(sourceDir, targetDir) {
|
|
|
206
200
|
return;
|
|
207
201
|
mkdirSync(targetDir, { recursive: true });
|
|
208
202
|
for (const entry of readdirSync(sourceDir)) {
|
|
209
|
-
const sourcePath =
|
|
210
|
-
const targetPath =
|
|
203
|
+
const sourcePath = join2(sourceDir, entry);
|
|
204
|
+
const targetPath = join2(targetDir, entry);
|
|
211
205
|
try {
|
|
212
206
|
const sourceStat = statSync(sourcePath);
|
|
213
207
|
if (sourceStat.isDirectory()) {
|
|
@@ -227,14 +221,14 @@ function getDataDir() {
|
|
|
227
221
|
if (hasOperatorOverride())
|
|
228
222
|
return root;
|
|
229
223
|
const home = effectiveHome();
|
|
230
|
-
const oldDir =
|
|
231
|
-
const oldConfigFile =
|
|
224
|
+
const oldDir = join2(home, ".skills");
|
|
225
|
+
const oldConfigFile = join2(home, ".skillsrc");
|
|
232
226
|
try {
|
|
233
227
|
mergeDirectoryContents(oldDir, root);
|
|
234
228
|
} catch {}
|
|
235
|
-
if (existsSync2(oldConfigFile) && !existsSync2(
|
|
229
|
+
if (existsSync2(oldConfigFile) && !existsSync2(join2(root, "config.json"))) {
|
|
236
230
|
try {
|
|
237
|
-
copyFileSync(oldConfigFile,
|
|
231
|
+
copyFileSync(oldConfigFile, join2(root, "config.json"));
|
|
238
232
|
} catch {}
|
|
239
233
|
}
|
|
240
234
|
return root;
|
|
@@ -248,7 +242,7 @@ var SQLITE_SCHEMES = new Set(["sqlite", "sqlite3", "file"]);
|
|
|
248
242
|
var MEMORY_SCHEMES = new Set(["memory"]);
|
|
249
243
|
var SQLITE_EXTENSIONS = [".db", ".sqlite", ".sqlite3", ".db3"];
|
|
250
244
|
function defaultSqlitePath() {
|
|
251
|
-
return
|
|
245
|
+
return join3(getDataDir(), DEFAULT_SQLITE_FILENAME2);
|
|
252
246
|
}
|
|
253
247
|
function resolveDatabaseTarget(raw) {
|
|
254
248
|
const value = raw?.trim();
|
|
@@ -275,7 +269,7 @@ function resolveDatabaseTarget(raw) {
|
|
|
275
269
|
throw new Error(`unsupported database scheme "${scheme}:". Supported: postgres://, postgresql://, sqlite:, file:, ` + `an absolute or relative path to a .db/.sqlite file, ":memory:", or "memory:" (non-durable, tests only). ` + `Leave the setting empty to use the default SQLite database at ${defaultSqlitePath()}.`);
|
|
276
270
|
}
|
|
277
271
|
if (looksLikeSqlitePath(value)) {
|
|
278
|
-
const path = isAbsolute(value) ? value :
|
|
272
|
+
const path = isAbsolute(value) ? value : join3(process.cwd(), value);
|
|
279
273
|
return { kind: "sqlite", path, durable: true, label: `sqlite (${path})` };
|
|
280
274
|
}
|
|
281
275
|
throw new Error(`could not resolve a database backend from "${value}". Use postgres://\u2026, sqlite:\u2026, an absolute or ` + `relative path ending in ${SQLITE_EXTENSIONS.join("/")}, ":memory:", or leave it empty for the ` + `default SQLite database at ${defaultSqlitePath()}.`);
|
|
@@ -299,7 +293,7 @@ function sqlitePathFromUrl(value, scheme) {
|
|
|
299
293
|
}
|
|
300
294
|
return rest.slice(2).replace(/^\/\/+/, "/");
|
|
301
295
|
}
|
|
302
|
-
return isAbsolute(rest) ? rest :
|
|
296
|
+
return isAbsolute(rest) ? rest : join3(process.cwd(), rest);
|
|
303
297
|
}
|
|
304
298
|
function looksLikeSqlitePath(value) {
|
|
305
299
|
if (value.includes("/"))
|
|
@@ -309,15 +303,15 @@ function looksLikeSqlitePath(value) {
|
|
|
309
303
|
|
|
310
304
|
// src/server/migrations-dir.ts
|
|
311
305
|
import { existsSync as existsSync3 } from "fs";
|
|
312
|
-
import { dirname as dirname2, join as
|
|
306
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
313
307
|
var MIGRATION_DIALECTS = ["postgres", "sqlite"];
|
|
314
308
|
var MAX_WALK_UP = 6;
|
|
315
309
|
function findMigrationsRoot(startDirs = defaultStartDirs()) {
|
|
316
310
|
for (const start of startDirs) {
|
|
317
311
|
let dir = start;
|
|
318
312
|
for (let level = 0;level < MAX_WALK_UP; level += 1) {
|
|
319
|
-
const candidate =
|
|
320
|
-
if (MIGRATION_DIALECTS.some((dialect) => existsSync3(
|
|
313
|
+
const candidate = join4(dir, "migrations");
|
|
314
|
+
if (MIGRATION_DIALECTS.some((dialect) => existsSync3(join4(candidate, dialect))))
|
|
321
315
|
return candidate;
|
|
322
316
|
const parent = dirname2(dir);
|
|
323
317
|
if (parent === dir)
|
|
@@ -331,7 +325,7 @@ function resolveMigrationsDir(dialect, root = findMigrationsRoot()) {
|
|
|
331
325
|
if (!root) {
|
|
332
326
|
throw new Error(`could not locate the migrations directory for dialect "${dialect}". ` + `Expected a migrations/${dialect}/ folder alongside the package root.`);
|
|
333
327
|
}
|
|
334
|
-
const dir =
|
|
328
|
+
const dir = join4(root, dialect);
|
|
335
329
|
if (!existsSync3(dir)) {
|
|
336
330
|
throw new Error(`migrations directory not found: ${dir}`);
|
|
337
331
|
}
|
|
@@ -349,7 +343,7 @@ function defaultStartDirs() {
|
|
|
349
343
|
import { Database } from "bun:sqlite";
|
|
350
344
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
351
345
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
352
|
-
import { dirname as dirname3, join as
|
|
346
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
353
347
|
|
|
354
348
|
// src/server/auth.ts
|
|
355
349
|
import { createHash } from "crypto";
|
|
@@ -641,6 +635,20 @@ function dateString(value) {
|
|
|
641
635
|
return value.toISOString();
|
|
642
636
|
return String(value);
|
|
643
637
|
}
|
|
638
|
+
function rowToSkillVersion(row) {
|
|
639
|
+
return {
|
|
640
|
+
orgId: String(row.org_id),
|
|
641
|
+
slug: String(row.slug),
|
|
642
|
+
version: String(row.version),
|
|
643
|
+
bundleSha256: String(row.bundle_sha256),
|
|
644
|
+
bundleByteSize: Number(row.bundle_byte_size),
|
|
645
|
+
storageKind: String(row.storage_kind ?? "db"),
|
|
646
|
+
...typeof row.storage_key === "string" ? { storageKey: row.storage_key } : {},
|
|
647
|
+
manifest: parseJsonObject(row.manifest_json),
|
|
648
|
+
...typeof row.published_by_user_id === "string" ? { publishedByUserId: row.published_by_user_id } : {},
|
|
649
|
+
createdAt: dateString(row.created_at)
|
|
650
|
+
};
|
|
651
|
+
}
|
|
644
652
|
|
|
645
653
|
// src/server/types.ts
|
|
646
654
|
class StaleLeaseGenerationError extends Error {
|
|
@@ -671,6 +679,21 @@ class SkillRevisionConflictError extends Error {
|
|
|
671
679
|
}
|
|
672
680
|
}
|
|
673
681
|
|
|
682
|
+
class SkillVersionExistsError extends Error {
|
|
683
|
+
slug;
|
|
684
|
+
version;
|
|
685
|
+
existingSha256;
|
|
686
|
+
attemptedSha256;
|
|
687
|
+
constructor(slug, version, existingSha256, attemptedSha256) {
|
|
688
|
+
super(`version conflict for '${slug}@${version}': already published with bundle ${existingSha256}, ` + `refusing to overwrite it with ${attemptedSha256}. Publish a new version instead.`);
|
|
689
|
+
this.slug = slug;
|
|
690
|
+
this.version = version;
|
|
691
|
+
this.existingSha256 = existingSha256;
|
|
692
|
+
this.attemptedSha256 = attemptedSha256;
|
|
693
|
+
this.name = "SkillVersionExistsError";
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
674
697
|
// src/lib/revision.ts
|
|
675
698
|
import { createHash as createHash2 } from "crypto";
|
|
676
699
|
function revisionIdOf(content) {
|
|
@@ -966,6 +989,13 @@ class SqliteSkillsStore {
|
|
|
966
989
|
throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, previousRevisionId);
|
|
967
990
|
}
|
|
968
991
|
const carriedSha = input.bundle?.sha256 ?? previousSha;
|
|
992
|
+
if (input.version && carriedSha) {
|
|
993
|
+
const existingVersion = this.get("SELECT bundle_sha256 FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [orgId, input.slug, input.version]);
|
|
994
|
+
const existingSha = typeof existingVersion?.bundle_sha256 === "string" ? existingVersion.bundle_sha256 : null;
|
|
995
|
+
if (existingSha && existingSha !== carriedSha) {
|
|
996
|
+
throw new SkillVersionExistsError(input.slug, input.version, existingSha, carriedSha);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
969
999
|
const carriedSize = input.bundle?.byteSize ?? (previous?.bundle_byte_size == null ? null : Number(previous.bundle_byte_size));
|
|
970
1000
|
const revisionId = revisionIdOfRecord({
|
|
971
1001
|
slug: input.slug,
|
|
@@ -1053,6 +1083,21 @@ class SqliteSkillsStore {
|
|
|
1053
1083
|
const currentId = typeof current?.revision_id === "string" ? current.revision_id : null;
|
|
1054
1084
|
throw new SkillRevisionConflictError(input.slug, input.expectedRevisionId, currentId);
|
|
1055
1085
|
}
|
|
1086
|
+
if (input.version && carriedSha) {
|
|
1087
|
+
this.db.run(`INSERT OR IGNORE INTO skills_versions (org_id, slug, version, bundle_sha256, bundle_byte_size, storage_kind, storage_key, manifest_json, published_by_user_id, created_at)
|
|
1088
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1089
|
+
orgId,
|
|
1090
|
+
input.slug,
|
|
1091
|
+
input.version,
|
|
1092
|
+
carriedSha,
|
|
1093
|
+
carriedSize ?? 0,
|
|
1094
|
+
input.versionStorage?.storageKind ?? "db",
|
|
1095
|
+
input.versionStorage?.storageKey ?? null,
|
|
1096
|
+
JSON.stringify(input.versionManifest ?? {}),
|
|
1097
|
+
input.principal.userId ?? null,
|
|
1098
|
+
now
|
|
1099
|
+
]);
|
|
1100
|
+
}
|
|
1056
1101
|
if (previousSha && input.bundle && previousSha !== input.bundle.sha256)
|
|
1057
1102
|
this.collectOrphanBundle(orgId, previousSha);
|
|
1058
1103
|
this.db.run("DELETE FROM skills_tags WHERE org_id = ? AND slug = ?", [orgId, input.slug]);
|
|
@@ -1160,6 +1205,13 @@ class SqliteSkillsStore {
|
|
|
1160
1205
|
const row = this.get("SELECT * FROM skills_bundles WHERE org_id = ? AND sha256 = ? LIMIT 1", [principal.orgId, sha256]);
|
|
1161
1206
|
return row ? rowToSkillBundle(row) : null;
|
|
1162
1207
|
}
|
|
1208
|
+
async listSkillVersions(principal, slug) {
|
|
1209
|
+
return this.all("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? ORDER BY created_at DESC, version DESC", [principal.orgId, slug]).map(rowToSkillVersion);
|
|
1210
|
+
}
|
|
1211
|
+
async getSkillVersion(principal, slug, version) {
|
|
1212
|
+
const row = this.get("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [principal.orgId, slug, version]);
|
|
1213
|
+
return row ? rowToSkillVersion(row) : null;
|
|
1214
|
+
}
|
|
1163
1215
|
async pinSkill(principal, slug, metadata = {}) {
|
|
1164
1216
|
const row = this.get(`INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
|
|
1165
1217
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -1199,7 +1251,7 @@ class SqliteSkillsStore {
|
|
|
1199
1251
|
return this.all("SELECT slug FROM skills_registry WHERE org_id = ? AND tombstoned_at IS NULL ORDER BY slug ASC", [principal.orgId]).map((row) => String(row.slug));
|
|
1200
1252
|
}
|
|
1201
1253
|
collectOrphanBundle(orgId, sha256) {
|
|
1202
|
-
const referenced = this.get("SELECT 1 AS present FROM skills_registry WHERE org_id = ? AND bundle_sha256 = ? LIMIT 1", [orgId, sha256]);
|
|
1254
|
+
const referenced = this.get("SELECT 1 AS present FROM skills_registry WHERE org_id = ? AND bundle_sha256 = ? LIMIT 1", [orgId, sha256]) ?? this.get("SELECT 1 AS present FROM skills_versions WHERE org_id = ? AND bundle_sha256 = ? LIMIT 1", [orgId, sha256]);
|
|
1203
1255
|
if (referenced)
|
|
1204
1256
|
return;
|
|
1205
1257
|
this.db.run("DELETE FROM skills_bundles WHERE org_id = ? AND sha256 = ?", [orgId, sha256]);
|
|
@@ -1228,7 +1280,7 @@ function applySqliteMigrations(db, migrationsDir = resolveMigrationsDir("sqlite"
|
|
|
1228
1280
|
const appliedNow = [];
|
|
1229
1281
|
for (const file of files) {
|
|
1230
1282
|
const version = file.replace(/\.sql$/, "");
|
|
1231
|
-
const text = readFileSync2(
|
|
1283
|
+
const text = readFileSync2(join5(migrationsDir, file), "utf8");
|
|
1232
1284
|
const apply = db.transaction(() => {
|
|
1233
1285
|
const already = db.query("SELECT 1 AS present FROM schema_migrations WHERE version = ? LIMIT 1").get(version);
|
|
1234
1286
|
if (already)
|
|
@@ -1317,7 +1369,7 @@ async function runPostgresMigrations(databaseUrl, migrationsDir) {
|
|
|
1317
1369
|
const version = file.replace(/\.sql$/, "");
|
|
1318
1370
|
if (applied.has(version))
|
|
1319
1371
|
continue;
|
|
1320
|
-
const sqlText = readFileSync3(
|
|
1372
|
+
const sqlText = readFileSync3(join6(migrationsDir, file), "utf8");
|
|
1321
1373
|
await sql.unsafe("BEGIN");
|
|
1322
1374
|
try {
|
|
1323
1375
|
await sql.unsafe(sqlText);
|