@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/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 existsSync3,
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 join3, normalize, relative, sep } from "path";
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 (!existsSync(sourceDir))
242
+ if (!existsSync2(sourceDir))
127
243
  return;
128
244
  mkdirSync(targetDir, { recursive: true });
129
245
  for (const entry of readdirSync(sourceDir)) {
130
- const sourcePath = join(sourceDir, entry);
131
- const targetPath = join(targetDir, entry);
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 (!existsSync(targetPath))
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 existsSync(join(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
283
+ return existsSync2(join3(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD));
169
284
  }
170
285
  function getDataDir() {
171
- const override = process.env[DATA_DIR_ENV];
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
- mergeDirectoryContents(oldDir, newDir);
288
+ mkdirSync(root, { recursive: true });
185
289
  } catch {}
186
- if (existsSync(oldConfigFile) && !existsSync(join(newDir, "config.json"))) {
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, join(newDir, "config.json"));
300
+ copyFileSync(oldConfigFile, join3(root, "config.json"));
189
301
  } catch {}
190
302
  }
191
- return newDir;
303
+ return root;
192
304
  }
193
305
  function getConfigPath(scope) {
194
306
  if (scope === "global") {
195
- return join(getDataDir(), "config.json");
307
+ return join3(getDataDir(), "config.json");
196
308
  }
197
- return join(process.cwd(), "skills.config.json");
309
+ return join3(process.cwd(), "skills.config.json");
198
310
  }
199
311
  function readConfigFile(path) {
200
- if (!existsSync(path))
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 (existsSync(filePath)) {
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 (!existsSync(dir)) {
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 (!existsSync(filePath))
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 existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
282
- import { join as join2 } from "path";
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 join2(targetDir, SKILLS_PROJECT_DIR);
416
+ return join4(targetDir, SKILLS_PROJECT_DIR);
305
417
  }
306
418
  function getProjectConfigPath(targetDir = process.cwd()) {
307
- return join2(getProjectStateDir(targetDir), PROJECT_CONFIG_FILE);
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 (!existsSync2(path))
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: join3(getDataDir(), "skills.db")
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 (existsSync3(projectStateDir)) {
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 (existsSync3(absolutePath) && !options.overwrite) {
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 = join3(dir, entry);
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 join3(targetDir, normalizedPath);
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.71",
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",