@hasna/skills 0.1.71 → 0.1.72
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/bin/index.js +639 -858
- package/bin/mcp.js +342 -232
- package/bin/migrate.js +149 -40
- package/bin/server.js +212 -100
- package/bin/worker.js +151 -45
- package/dist/index.js +461 -331
- package/dist/lib/app-home.d.ts +85 -0
- package/dist/lib/config.d.ts +9 -10
- package/dist/lib/station-hydrate.d.ts +2 -0
- package/dist/lib/station-snapshot.d.ts +1 -1
- package/dist/sdk/index.js +518 -630
- package/dist/storage.js +155 -43
- package/package.json +2 -1
package/dist/storage.js
CHANGED
|
@@ -49,19 +49,18 @@ var __require = import.meta.require;
|
|
|
49
49
|
// src/lib/native-storage.ts
|
|
50
50
|
import { createHash, createHmac } from "crypto";
|
|
51
51
|
import {
|
|
52
|
-
existsSync as
|
|
52
|
+
existsSync as existsSync4,
|
|
53
53
|
mkdirSync as mkdirSync3,
|
|
54
54
|
readFileSync as readFileSync3,
|
|
55
55
|
readdirSync as readdirSync2,
|
|
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 join5, normalize, relative, sep } from "path";
|
|
60
60
|
|
|
61
61
|
// src/lib/config.ts
|
|
62
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
63
|
-
import { join, dirname } from "path";
|
|
64
|
-
import { homedir } from "os";
|
|
62
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
63
|
+
import { join as join3, dirname } from "path";
|
|
65
64
|
|
|
66
65
|
// src/lib/retired-settings.ts
|
|
67
66
|
var RETIRED_ENV_SUFFIXES = ["_STORAGE_MODE", "_DEPLOYMENT_MODE", "_CLOUD_MODE"];
|
|
@@ -109,6 +108,123 @@ function assertNoRetiredConfigKeys(config, source) {
|
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
110
|
|
|
111
|
+
// src/lib/app-home.ts
|
|
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
|
+
import { homedir } from "os";
|
|
118
|
+
import { join } from "path";
|
|
119
|
+
var KIND_ENV = {
|
|
120
|
+
config: "HASNA_CONFIG_HOME",
|
|
121
|
+
data: "HASNA_DATA_HOME",
|
|
122
|
+
state: "HASNA_STATE_HOME",
|
|
123
|
+
cache: "HASNA_CACHE_HOME"
|
|
124
|
+
};
|
|
125
|
+
var APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
126
|
+
function assertApp(app) {
|
|
127
|
+
if (typeof app !== "string" || app.length === 0) {
|
|
128
|
+
throw new TypeError("paths: app must be a non-empty string");
|
|
129
|
+
}
|
|
130
|
+
if (!APP_SLUG_RE.test(app)) {
|
|
131
|
+
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function envOf(options) {
|
|
135
|
+
return options.env ?? process.env;
|
|
136
|
+
}
|
|
137
|
+
function envValue(options, kind) {
|
|
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";
|
|
143
|
+
}
|
|
144
|
+
function baseDir(kind, options) {
|
|
145
|
+
const override = envValue(options, kind);
|
|
146
|
+
if (override)
|
|
147
|
+
return override;
|
|
148
|
+
const home = options.home ?? homedir();
|
|
149
|
+
const platform = options.platform ?? process.platform;
|
|
150
|
+
if (isMacOS(platform)) {
|
|
151
|
+
switch (kind) {
|
|
152
|
+
case "config":
|
|
153
|
+
case "data":
|
|
154
|
+
return join(home, "Library", "Application Support", "Hasna");
|
|
155
|
+
case "cache":
|
|
156
|
+
return join(home, "Library", "Caches", "Hasna");
|
|
157
|
+
case "state":
|
|
158
|
+
return join(home, "Library", "Logs", "Hasna");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
switch (kind) {
|
|
162
|
+
case "config":
|
|
163
|
+
return join(home, ".config", "hasna");
|
|
164
|
+
case "data":
|
|
165
|
+
return join(home, ".local", "share", "hasna");
|
|
166
|
+
case "state":
|
|
167
|
+
return join(home, ".local", "state", "hasna");
|
|
168
|
+
case "cache":
|
|
169
|
+
return join(home, ".cache", "hasna");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function resolvePath(kind, options) {
|
|
173
|
+
assertApp(options.app);
|
|
174
|
+
const appSegment = options.internal === true ? join("internal", options.app) : options.app;
|
|
175
|
+
return join(baseDir(kind, options), appSegment);
|
|
176
|
+
}
|
|
177
|
+
function dataDir(options) {
|
|
178
|
+
return resolvePath("data", options);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/lib/app-home.ts
|
|
182
|
+
var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
|
|
183
|
+
var HASNA_SKILLS_HOME_ENV = "HASNA_SKILLS_HOME";
|
|
184
|
+
var SKILLS_HOME_ENV = "SKILLS_HOME";
|
|
185
|
+
var DEFAULT_SQLITE_FILENAME = "server.db";
|
|
186
|
+
var GLOBAL_CONFIG_FILENAME = "config.json";
|
|
187
|
+
function effectiveHome() {
|
|
188
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir2() || "/tmp";
|
|
189
|
+
}
|
|
190
|
+
function legacyDataRoot() {
|
|
191
|
+
return join2(effectiveHome(), ".hasna", "skills");
|
|
192
|
+
}
|
|
193
|
+
function resolverDataRoot(home = effectiveHome()) {
|
|
194
|
+
return dataDir({ app: "skills", home });
|
|
195
|
+
}
|
|
196
|
+
function adoptResolverDataRoot(resolved, env = process.env) {
|
|
197
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
198
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
199
|
+
return true;
|
|
200
|
+
return existsSync(join2(resolved, DEFAULT_SQLITE_FILENAME)) || existsSync(join2(resolved, GLOBAL_CONFIG_FILENAME));
|
|
201
|
+
}
|
|
202
|
+
function exactDataRoot() {
|
|
203
|
+
for (const key of [DATA_DIR_ENV, HASNA_SKILLS_HOME_ENV, SKILLS_HOME_ENV]) {
|
|
204
|
+
const dir = process.env[key]?.trim();
|
|
205
|
+
if (dir)
|
|
206
|
+
return resolve(dir);
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
function hasExactOverride(env = process.env) {
|
|
211
|
+
return Boolean(env[DATA_DIR_ENV]?.trim()) || Boolean(env[HASNA_SKILLS_HOME_ENV]?.trim()) || Boolean(env[SKILLS_HOME_ENV]?.trim());
|
|
212
|
+
}
|
|
213
|
+
function hasOperatorOverride(env = process.env) {
|
|
214
|
+
return hasExactOverride(env) || Boolean(env.HASNA_DATA_HOME?.trim());
|
|
215
|
+
}
|
|
216
|
+
function getDataRoot() {
|
|
217
|
+
const exact = exactDataRoot();
|
|
218
|
+
if (exact)
|
|
219
|
+
return exact;
|
|
220
|
+
const resolved = resolverDataRoot();
|
|
221
|
+
return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
|
|
222
|
+
}
|
|
223
|
+
function skillsDataRootForHome(home) {
|
|
224
|
+
const resolved = resolverDataRoot(home);
|
|
225
|
+
return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(join2(home, ".hasna", "skills"));
|
|
226
|
+
}
|
|
227
|
+
|
|
112
228
|
// src/lib/config.ts
|
|
113
229
|
var ENUM_KEYS = {
|
|
114
230
|
defaultAgent: ["claude", "codex", "gemini", "pi", "opencode", "all"],
|
|
@@ -123,19 +239,19 @@ function allowedValues(key) {
|
|
|
123
239
|
return ENUM_KEYS[key];
|
|
124
240
|
}
|
|
125
241
|
function mergeDirectoryContents(sourceDir, targetDir) {
|
|
126
|
-
if (!
|
|
242
|
+
if (!existsSync2(sourceDir))
|
|
127
243
|
return;
|
|
128
244
|
mkdirSync(targetDir, { recursive: true });
|
|
129
245
|
for (const entry of readdirSync(sourceDir)) {
|
|
130
|
-
const sourcePath =
|
|
131
|
-
const targetPath =
|
|
246
|
+
const sourcePath = join3(sourceDir, entry);
|
|
247
|
+
const targetPath = join3(targetDir, entry);
|
|
132
248
|
try {
|
|
133
249
|
const sourceStat = statSync(sourcePath);
|
|
134
250
|
if (sourceStat.isDirectory()) {
|
|
135
251
|
mergeDirectoryContents(sourcePath, targetPath);
|
|
136
252
|
continue;
|
|
137
253
|
}
|
|
138
|
-
if (!
|
|
254
|
+
if (!existsSync2(targetPath))
|
|
139
255
|
copyFileSync(sourcePath, targetPath);
|
|
140
256
|
} catch {}
|
|
141
257
|
}
|
|
@@ -160,44 +276,40 @@ function normalizeConfigValue(key, value) {
|
|
|
160
276
|
return value.trim() ? value : undefined;
|
|
161
277
|
return;
|
|
162
278
|
}
|
|
163
|
-
var DATA_DIR_ENV = "HASNA_SKILLS_DIR";
|
|
164
279
|
var INSTALLED_SKILLS_DIRNAME = "installed";
|
|
165
280
|
var SKILLS_CACHE_DIRNAME = "skills";
|
|
166
281
|
var LAYOUT_MIGRATION_RECORD = ".layout-migration.json";
|
|
167
282
|
function isOwnerLayoutMigrated(appDir) {
|
|
168
|
-
return
|
|
283
|
+
return existsSync2(join3(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
|
|
169
284
|
}
|
|
170
285
|
function getDataDir() {
|
|
171
|
-
const
|
|
172
|
-
if (override) {
|
|
173
|
-
try {
|
|
174
|
-
mkdirSync(override, { recursive: true });
|
|
175
|
-
} catch {}
|
|
176
|
-
return override;
|
|
177
|
-
}
|
|
178
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
179
|
-
const newDir = join(home, ".hasna", "skills");
|
|
180
|
-
const oldDir = join(home, ".skills");
|
|
181
|
-
const oldConfigFile = join(home, ".skillsrc");
|
|
182
|
-
mkdirSync(newDir, { recursive: true });
|
|
286
|
+
const root = getDataRoot();
|
|
183
287
|
try {
|
|
184
|
-
|
|
288
|
+
mkdirSync(root, { recursive: true });
|
|
185
289
|
} catch {}
|
|
186
|
-
if (
|
|
290
|
+
if (hasOperatorOverride())
|
|
291
|
+
return root;
|
|
292
|
+
const home = effectiveHome();
|
|
293
|
+
const oldDir = join3(home, ".skills");
|
|
294
|
+
const oldConfigFile = join3(home, ".skillsrc");
|
|
295
|
+
try {
|
|
296
|
+
mergeDirectoryContents(oldDir, root);
|
|
297
|
+
} catch {}
|
|
298
|
+
if (existsSync2(oldConfigFile) && !existsSync2(join3(root, "config.json"))) {
|
|
187
299
|
try {
|
|
188
|
-
copyFileSync(oldConfigFile,
|
|
300
|
+
copyFileSync(oldConfigFile, join3(root, "config.json"));
|
|
189
301
|
} catch {}
|
|
190
302
|
}
|
|
191
|
-
return
|
|
303
|
+
return root;
|
|
192
304
|
}
|
|
193
305
|
function getConfigPath(scope) {
|
|
194
306
|
if (scope === "global") {
|
|
195
|
-
return
|
|
307
|
+
return join3(getDataDir(), "config.json");
|
|
196
308
|
}
|
|
197
|
-
return
|
|
309
|
+
return join3(process.cwd(), "skills.config.json");
|
|
198
310
|
}
|
|
199
311
|
function readConfigFile(path) {
|
|
200
|
-
if (!
|
|
312
|
+
if (!existsSync2(path))
|
|
201
313
|
return {};
|
|
202
314
|
let parsed;
|
|
203
315
|
try {
|
|
@@ -233,7 +345,7 @@ function saveConfig(key, value, scope = "project") {
|
|
|
233
345
|
}
|
|
234
346
|
const filePath = getConfigPath(scope);
|
|
235
347
|
let existing = {};
|
|
236
|
-
if (
|
|
348
|
+
if (existsSync2(filePath)) {
|
|
237
349
|
try {
|
|
238
350
|
existing = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
239
351
|
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
@@ -244,7 +356,7 @@ function saveConfig(key, value, scope = "project") {
|
|
|
244
356
|
}
|
|
245
357
|
} else {
|
|
246
358
|
const dir = dirname(filePath);
|
|
247
|
-
if (!
|
|
359
|
+
if (!existsSync2(dir)) {
|
|
248
360
|
mkdirSync(dir, { recursive: true });
|
|
249
361
|
}
|
|
250
362
|
}
|
|
@@ -258,7 +370,7 @@ function unsetConfig(key, scope = "project") {
|
|
|
258
370
|
throw new Error(`Unknown config key: ${key}. Valid keys: ${validKeys().join(", ")}`);
|
|
259
371
|
}
|
|
260
372
|
const filePath = getConfigPath(scope);
|
|
261
|
-
if (!
|
|
373
|
+
if (!existsSync2(filePath))
|
|
262
374
|
return false;
|
|
263
375
|
let existing;
|
|
264
376
|
try {
|
|
@@ -278,8 +390,8 @@ function unsetConfig(key, scope = "project") {
|
|
|
278
390
|
}
|
|
279
391
|
|
|
280
392
|
// src/lib/project-state.ts
|
|
281
|
-
import { existsSync as
|
|
282
|
-
import { join as
|
|
393
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
394
|
+
import { join as join4 } from "path";
|
|
283
395
|
|
|
284
396
|
// src/lib/utils.ts
|
|
285
397
|
function normalizeSkillName(name) {
|
|
@@ -301,14 +413,14 @@ var SKILLS_PROJECT_DIR = ".skills";
|
|
|
301
413
|
var PROJECT_CONFIG_FILE = "project.json";
|
|
302
414
|
var DEFAULT_EXPORT_DIR = ".skills/exports";
|
|
303
415
|
function getProjectStateDir(targetDir = process.cwd()) {
|
|
304
|
-
return
|
|
416
|
+
return join4(targetDir, SKILLS_PROJECT_DIR);
|
|
305
417
|
}
|
|
306
418
|
function getProjectConfigPath(targetDir = process.cwd()) {
|
|
307
|
-
return
|
|
419
|
+
return join4(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
|
|
308
420
|
}
|
|
309
421
|
function loadProjectConfig(targetDir = process.cwd()) {
|
|
310
422
|
const path = getProjectConfigPath(targetDir);
|
|
311
|
-
if (!
|
|
423
|
+
if (!existsSync3(path))
|
|
312
424
|
return null;
|
|
313
425
|
try {
|
|
314
426
|
return normalizeProjectConfig(JSON.parse(readFileSync2(path, "utf-8")));
|
|
@@ -561,7 +673,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
561
673
|
local: {
|
|
562
674
|
dataDir: getDataDir(),
|
|
563
675
|
projectStateDir: getProjectStateDir(targetDir),
|
|
564
|
-
feedbackDbPath:
|
|
676
|
+
feedbackDbPath: join5(getDataDir(), "skills.db")
|
|
565
677
|
},
|
|
566
678
|
remote: {
|
|
567
679
|
databaseConfigured: Boolean(config.databaseUrl),
|
|
@@ -584,7 +696,7 @@ function getStorageStatus(options = {}) {
|
|
|
584
696
|
function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
585
697
|
const projectStateDir = getProjectStateDir(targetDir);
|
|
586
698
|
const files = [];
|
|
587
|
-
if (
|
|
699
|
+
if (existsSync4(projectStateDir)) {
|
|
588
700
|
for (const filePath of walkFiles(projectStateDir)) {
|
|
589
701
|
const bytes = readFileSync3(filePath);
|
|
590
702
|
const relativePath = toPosix(relative(targetDir, filePath));
|
|
@@ -611,7 +723,7 @@ function importSkillsLocalSnapshot(snapshot, targetDir = process.cwd(), options
|
|
|
611
723
|
continue;
|
|
612
724
|
}
|
|
613
725
|
const absolutePath = resolveSnapshotPath(targetDir, file.path);
|
|
614
|
-
if (
|
|
726
|
+
if (existsSync4(absolutePath) && !options.overwrite) {
|
|
615
727
|
skipped += 1;
|
|
616
728
|
continue;
|
|
617
729
|
}
|
|
@@ -918,7 +1030,7 @@ function parsePositiveInteger(value) {
|
|
|
918
1030
|
function walkFiles(dir) {
|
|
919
1031
|
const files = [];
|
|
920
1032
|
for (const entry of readdirSync2(dir)) {
|
|
921
|
-
const full =
|
|
1033
|
+
const full = join5(dir, entry);
|
|
922
1034
|
const stats = statSync2(full);
|
|
923
1035
|
if (stats.isDirectory())
|
|
924
1036
|
files.push(...walkFiles(full));
|
|
@@ -935,7 +1047,7 @@ function resolveSnapshotPath(targetDir, snapshotPath) {
|
|
|
935
1047
|
if (!toPosix(normalizedPath).startsWith(".skills/")) {
|
|
936
1048
|
throw new Error(`Snapshot path must stay inside .skills: ${snapshotPath}`);
|
|
937
1049
|
}
|
|
938
|
-
return
|
|
1050
|
+
return join5(targetDir, normalizedPath);
|
|
939
1051
|
}
|
|
940
1052
|
function normalizeS3Prefix(prefix) {
|
|
941
1053
|
return (prefix ?? "").trim().replace(/^\/+|\/+$/g, "");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/skills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.72",
|
|
4
4
|
"description": "Skills library for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -92,6 +92,7 @@
|
|
|
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",
|
|
95
96
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
96
97
|
"chalk": "^5.3.0",
|
|
97
98
|
"commander": "^12.1.0",
|