@hasna/skills 0.1.46 → 0.1.48

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/index.js CHANGED
@@ -15,11 +15,11 @@ var __export = (target, all) => {
15
15
  };
16
16
 
17
17
  // src/lib/registry.ts
18
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
18
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync4 } from "fs";
19
19
  import { join as join4 } from "path";
20
20
 
21
21
  // src/lib/config.ts
22
- import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "fs";
22
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
23
23
  import { join, dirname } from "path";
24
24
  import { homedir } from "os";
25
25
  var ENUM_KEYS = {
@@ -45,6 +45,24 @@ function allowedValues(key) {
45
45
  return MODE_VALUES;
46
46
  return ENUM_KEYS[key];
47
47
  }
48
+ function mergeDirectoryContents(sourceDir, targetDir) {
49
+ if (!existsSync(sourceDir))
50
+ return;
51
+ mkdirSync(targetDir, { recursive: true });
52
+ for (const entry of readdirSync(sourceDir)) {
53
+ const sourcePath = join(sourceDir, entry);
54
+ const targetPath = join(targetDir, entry);
55
+ try {
56
+ const sourceStat = statSync(sourcePath);
57
+ if (sourceStat.isDirectory()) {
58
+ mergeDirectoryContents(sourcePath, targetPath);
59
+ continue;
60
+ }
61
+ if (!existsSync(targetPath))
62
+ copyFileSync(sourcePath, targetPath);
63
+ } catch {}
64
+ }
65
+ }
48
66
  function normalizeConfigValue(key, value) {
49
67
  if (typeof value !== "string")
50
68
  return;
@@ -68,14 +86,17 @@ function normalizeConfigValue(key, value) {
68
86
  function getDataDir() {
69
87
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
70
88
  const newDir = join(home, ".hasna", "skills");
89
+ const oldDir = join(home, ".skills");
71
90
  const oldConfigFile = join(home, ".skillsrc");
91
+ mkdirSync(newDir, { recursive: true });
92
+ try {
93
+ mergeDirectoryContents(oldDir, newDir);
94
+ } catch {}
72
95
  if (existsSync(oldConfigFile) && !existsSync(join(newDir, "config.json"))) {
73
- mkdirSync(newDir, { recursive: true });
74
96
  try {
75
97
  copyFileSync(oldConfigFile, join(newDir, "config.json"));
76
98
  } catch {}
77
99
  }
78
- mkdirSync(newDir, { recursive: true });
79
100
  return newDir;
80
101
  }
81
102
  function getConfigPath(scope) {
@@ -146,15 +167,15 @@ import {
146
167
  lstatSync as lstatSync2,
147
168
  mkdirSync as mkdirSync2,
148
169
  readFileSync as readFileSync3,
149
- readdirSync as readdirSync2,
170
+ readdirSync as readdirSync3,
150
171
  rmSync,
151
- statSync as statSync2,
172
+ statSync as statSync3,
152
173
  writeFileSync as writeFileSync2
153
174
  } from "fs";
154
175
  import { basename, dirname as dirname2, isAbsolute as isAbsolute2, join as join3, normalize as normalize2, relative } from "path";
155
176
 
156
177
  // src/lib/skill-validation.ts
157
- import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
178
+ import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
158
179
  import { isAbsolute, join as join2, normalize } from "path";
159
180
 
160
181
  // src/lib/skill-aliases.ts
@@ -170,7 +191,8 @@ var SKILL_ALIASES = {
170
191
  "pdf-reader": "read-pdf",
171
192
  "generate-image": "image",
172
193
  "image-generator": "image",
173
- "create-blog-article": "blog-article"
194
+ "create-blog-article": "blog-article",
195
+ "skill-diff": "diff-viewer"
174
196
  };
175
197
  function normalizeSkillSlug(name) {
176
198
  return name.trim();
@@ -199,7 +221,7 @@ var PREMIUM_SKILLS = [
199
221
  { slug: "brand-kit", displayName: "Brand Kit", tier: "premium", costCents: 400, providers: ["hosted"], description: "Hosted brand kit with logo usage, palette, typography, brand voice, sample applications, Markdown guide, PDF guide, and SVG assets" },
200
222
  { slug: "generate-book-cover", displayName: "Book Cover", tier: "premium", costCents: 20, providers: ["gpt-image-2"], description: "Professional book cover design from title and genre" },
201
223
  { slug: "remove-background", displayName: "Remove Background", tier: "premium", costCents: 10, providers: ["gemini-3-pro"], description: "AI-powered background removal from images" },
202
- { slug: "transcript", displayName: "Transcript", tier: "premium", costCents: 10, providers: ["whisper"], description: "Audio/video transcription with timestamps" },
224
+ { slug: "transcript", displayName: "Transcript", tier: "premium", costCents: 10, providers: ["openai", "elevenlabs", "deepgram", "hosted"], description: "Audio/video transcription with timestamps, diarization, and URL support" },
203
225
  { slug: "webcrawling", displayName: "Web Crawling", tier: "premium", costCents: 5, providers: ["firecrawl"], description: "Structured web page crawling and extraction" },
204
226
  { slug: "browse", displayName: "Browse", tier: "premium", costCents: 5, providers: ["browser"], description: "Web browsing and page interaction" },
205
227
  { slug: "read-pdf", displayName: "Read PDF", tier: "premium", costCents: 5, providers: ["cerebras"], description: "Hosted PDF extraction and structured content analysis" },
@@ -787,7 +809,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
787
809
  if (!VALID_BIN_COMMAND.test(bareName)) {
788
810
  add(issues, "skill.name_invalid", `Skill name '${bareName}' must use lowercase letters, numbers, dots, underscores, or hyphens`);
789
811
  }
790
- for (const entry of readdirSync(skillPath).sort()) {
812
+ for (const entry of readdirSync2(skillPath).sort()) {
791
813
  const entryPath = join2(skillPath, entry);
792
814
  if (RESERVED_SKILL_ENTRIES.has(entry)) {
793
815
  add(issues, "skill.reserved_file", `Reserved file '${entry}' is not allowed in skill packages`);
@@ -902,7 +924,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
902
924
  const targetPath = join2(skillPath, target);
903
925
  if (!existsSync2(targetPath)) {
904
926
  add(warnings, "package.bin_target_missing", `package.json bin '${command}' target '${target}' is not present before build`);
905
- } else if (statSync(targetPath).isDirectory()) {
927
+ } else if (statSync2(targetPath).isDirectory()) {
906
928
  add(issues, "package.bin_target_directory", `package.json bin '${command}' target '${target}' must point to a file, not a directory`);
907
929
  }
908
930
  }
@@ -925,7 +947,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
925
947
  add(issues, "skill.src_index_missing", "Missing src/index.ts or src/index.js");
926
948
  } else {
927
949
  const indexPath = existsSync2(join2(srcDir, "index.ts")) ? join2(srcDir, "index.ts") : join2(srcDir, "index.js");
928
- const size = statSync(indexPath).size;
950
+ const size = statSync2(indexPath).size;
929
951
  if (size < 50)
930
952
  add(warnings, "skill.src_index_minimal", `Source entry point is very small (${size}B)`);
931
953
  }
@@ -947,9 +969,9 @@ function validateRegistryConsistency(registry, skillsDir) {
947
969
  seen.add(name);
948
970
  return false;
949
971
  })));
950
- const skillDirs = existsSync2(skillsDir) ? readdirSync(skillsDir).filter((entry) => {
972
+ const skillDirs = existsSync2(skillsDir) ? readdirSync2(skillsDir).filter((entry) => {
951
973
  const fullPath = join2(skillsDir, entry);
952
- return !entry.startsWith(".") && entry !== "_common" && statSync(fullPath).isDirectory();
974
+ return !entry.startsWith(".") && entry !== "_common" && statSync2(fullPath).isDirectory();
953
975
  }) : [];
954
976
  const directoryNames = new Set(skillDirs);
955
977
  const registryNameSet = new Set(registryNames);
@@ -1016,7 +1038,7 @@ function findPortableSkill(name, options = {}) {
1016
1038
  return null;
1017
1039
  }
1018
1040
  const path = getPortableSkillPath(normalized, options);
1019
- if (!existsSync3(path) || !statSync2(path).isDirectory())
1041
+ if (!existsSync3(path) || !statSync3(path).isDirectory())
1020
1042
  return null;
1021
1043
  try {
1022
1044
  return summarizePortableSkill(path, normalized);
@@ -1029,7 +1051,7 @@ function listPortableSkills(options = {}) {
1029
1051
  if (!existsSync3(root))
1030
1052
  return [];
1031
1053
  const skills = [];
1032
- for (const entry of readdirSync2(root).sort()) {
1054
+ for (const entry of readdirSync3(root).sort()) {
1033
1055
  if (entry.startsWith(".") || DATA_DIR_NON_SKILL_ENTRIES.has(entry))
1034
1056
  continue;
1035
1057
  const path = join3(root, entry);
@@ -1105,7 +1127,7 @@ function scaffoldPortableSkill(name, options = {}) {
1105
1127
  }
1106
1128
  function portPortableSkill(sourcePath, options = {}) {
1107
1129
  const absoluteSource = normalize2(sourcePath);
1108
- if (!existsSync3(absoluteSource) || !statSync2(absoluteSource).isDirectory()) {
1130
+ if (!existsSync3(absoluteSource) || !statSync3(absoluteSource).isDirectory()) {
1109
1131
  throw new Error(`Skill source directory not found: ${sourcePath}`);
1110
1132
  }
1111
1133
  const inferred = readPortableSkillManifest(absoluteSource, basename(absoluteSource));
@@ -1174,7 +1196,7 @@ function validatePortableSkillDirectory(name, skillPath) {
1174
1196
  const entryPath = join3(skillPath, command.entry);
1175
1197
  if (!existsSync3(entryPath))
1176
1198
  add2(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
1177
- else if (statSync2(entryPath).isDirectory())
1199
+ else if (statSync3(entryPath).isDirectory())
1178
1200
  add2(issues, "portable.command_entry_directory", `Command '${command.name}' entry '${command.entry}' must be a file`);
1179
1201
  }
1180
1202
  }
@@ -1595,7 +1617,7 @@ function displayName(name) {
1595
1617
  }
1596
1618
  function safeIsDirectory(path) {
1597
1619
  try {
1598
- return statSync2(path).isDirectory();
1620
+ return statSync3(path).isDirectory();
1599
1621
  } catch {
1600
1622
  return false;
1601
1623
  }
@@ -2806,9 +2828,9 @@ var MEDIA_PROCESSING_SKILLS = [
2806
2828
  {
2807
2829
  name: "transcript",
2808
2830
  displayName: "Transcript",
2809
- description: "Generate transcripts from audio and video files with timestamps",
2831
+ description: "Transcribe audio, video, and media URLs with OpenAI GPT-4o, ElevenLabs Scribe v2, DeepGram, or hosted runtime",
2810
2832
  category: "Media Processing",
2811
- tags: ["transcript", "audio", "video", "speech-to-text"]
2833
+ tags: ["transcript", "audio", "video", "speech-to-text", "diarization", "youtube"]
2812
2834
  },
2813
2835
  {
2814
2836
  name: "video-cut-suggester",
@@ -3591,7 +3613,7 @@ function discoverSkillsInDir(dir) {
3591
3613
  return [];
3592
3614
  const result = [];
3593
3615
  try {
3594
- const entries = readdirSync3(dir, { withFileTypes: true });
3616
+ const entries = readdirSync4(dir, { withFileTypes: true });
3595
3617
  for (const entry of entries) {
3596
3618
  if (!entry.isDirectory())
3597
3619
  continue;
@@ -4062,7 +4084,7 @@ function loadProjectConfigCompat(targetDir) {
4062
4084
  }
4063
4085
  // src/lib/run-state.ts
4064
4086
  import { createHash, randomBytes } from "crypto";
4065
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
4087
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
4066
4088
  import { extname, join as join7, relative as relative2 } from "path";
4067
4089
  function createSkillRun(params, targetDir = process.cwd()) {
4068
4090
  const now = new Date;
@@ -4137,11 +4159,11 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
4137
4159
  if (!existsSync7(runsRoot))
4138
4160
  return [];
4139
4161
  const records = [];
4140
- for (const day of readdirSync4(runsRoot).sort().reverse()) {
4162
+ for (const day of readdirSync5(runsRoot).sort().reverse()) {
4141
4163
  const dayDir = join7(runsRoot, day);
4142
- if (!statSync3(dayDir).isDirectory())
4164
+ if (!statSync4(dayDir).isDirectory())
4143
4165
  continue;
4144
- for (const runId of readdirSync4(dayDir).sort().reverse()) {
4166
+ for (const runId of readdirSync5(dayDir).sort().reverse()) {
4145
4167
  const record = readRunRecord(join7(dayDir, runId));
4146
4168
  if (record)
4147
4169
  records.push(record);
@@ -4155,7 +4177,7 @@ function findSkillRun(runId, targetDir = process.cwd()) {
4155
4177
  const runsRoot = join7(getProjectStateDir(targetDir), "runs");
4156
4178
  if (!existsSync7(runsRoot))
4157
4179
  return null;
4158
- for (const day of readdirSync4(runsRoot)) {
4180
+ for (const day of readdirSync5(runsRoot)) {
4159
4181
  const record = readRunRecord(join7(runsRoot, day, runId));
4160
4182
  if (record)
4161
4183
  return record;
@@ -4178,7 +4200,7 @@ function collectRunArtifacts(context) {
4178
4200
  return [];
4179
4201
  const artifacts = [];
4180
4202
  for (const path of walkFiles(context.exportDir)) {
4181
- const stat = statSync3(path);
4203
+ const stat = statSync4(path);
4182
4204
  const bytes = readFileSync7(path);
4183
4205
  artifacts.push({
4184
4206
  path: toProjectRelative(context.targetDir, path),
@@ -4201,9 +4223,9 @@ function readRunRecord(runDir) {
4201
4223
  }
4202
4224
  function walkFiles(dir) {
4203
4225
  const files = [];
4204
- for (const entry of readdirSync4(dir)) {
4226
+ for (const entry of readdirSync5(dir)) {
4205
4227
  const full = join7(dir, entry);
4206
- if (statSync3(full).isDirectory())
4228
+ if (statSync4(full).isDirectory())
4207
4229
  files.push(...walkFiles(full));
4208
4230
  else
4209
4231
  files.push(full);
@@ -15482,7 +15504,7 @@ function finalize(ctx, schema) {
15482
15504
  result.$schema = "http://json-schema.org/draft-07/schema#";
15483
15505
  } else if (ctx.target === "draft-04") {
15484
15506
  result.$schema = "http://json-schema.org/draft-04/schema#";
15485
- } else if (ctx.target === "openapi-3.0") {}
15507
+ } else if (ctx.target === "openapi-3.0") {} else {}
15486
15508
  if (ctx.external?.uri) {
15487
15509
  const id = ctx.external.registry.get(schema)?.id;
15488
15510
  if (!id)
@@ -15730,7 +15752,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
15730
15752
  if (val === undefined) {
15731
15753
  if (ctx.unrepresentable === "throw") {
15732
15754
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
15733
- }
15755
+ } else {}
15734
15756
  } else if (typeof val === "bigint") {
15735
15757
  if (ctx.unrepresentable === "throw") {
15736
15758
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -18811,7 +18833,7 @@ import { dirname as dirname4, relative as relative3 } from "path";
18811
18833
  // package.json
18812
18834
  var package_default = {
18813
18835
  name: "@hasna/skills",
18814
- version: "0.1.46",
18836
+ version: "0.1.48",
18815
18837
  description: "Skills library for AI coding agents",
18816
18838
  type: "module",
18817
18839
  bin: {
@@ -18880,7 +18902,7 @@ var package_default = {
18880
18902
  typescript: "^5"
18881
18903
  },
18882
18904
  dependencies: {
18883
- "@hasna/events": "^0.1.3",
18905
+ "@hasna/events": "^0.1.7",
18884
18906
  "@modelcontextprotocol/sdk": "^1.26.0",
18885
18907
  chalk: "^5.3.0",
18886
18908
  commander: "^12.1.0",
@@ -19064,18 +19086,29 @@ var runOutputSchema = objectSchema({
19064
19086
  exitCode: { type: "number", description: "Process exit code for local runs." },
19065
19087
  skill: stringSchema("Canonical skill slug."),
19066
19088
  remote: { type: "boolean", description: "Whether the skill was submitted to the hosted runtime." },
19067
- stdout: stringSchema("Captured stdout for local runs."),
19068
- stderr: stringSchema("Captured stderr for local runs."),
19089
+ stdoutPreview: objectSchema({
19090
+ text: stringSchema("Truncated stdout preview."),
19091
+ length: { type: "number" },
19092
+ truncated: { type: "boolean" }
19093
+ }, [], "Default compact stdout preview."),
19094
+ stderrPreview: objectSchema({
19095
+ text: stringSchema("Truncated stderr preview."),
19096
+ length: { type: "number" },
19097
+ truncated: { type: "boolean" }
19098
+ }, [], "Default compact stderr preview."),
19099
+ stdout: stringSchema("Captured stdout for local runs when detail:true is requested."),
19100
+ stderr: stringSchema("Captured stderr for local runs when detail:true is requested."),
19069
19101
  id: stringSchema("Remote run id when submitted remotely."),
19070
19102
  localRunId: stringSchema("Local run metadata id."),
19071
19103
  status: stringSchema("Run lifecycle status."),
19072
19104
  pricing: pricingSchema,
19073
- remoteRun: objectSchema({}, [], "Normalized hosted remote run contract.", true),
19074
- run: objectSchema({}, [], "Local run metadata.", true),
19105
+ remoteRun: objectSchema({}, [], "Compact hosted remote run summary by default; full contract when detail:true is requested.", true),
19106
+ run: objectSchema({}, [], "Compact local run metadata by default; full metadata when detail:true is requested.", true),
19075
19107
  nextActions: objectSchema({
19076
19108
  poll: stringSchema("Command to poll run status."),
19077
19109
  download: stringSchema("Command to download artifacts.")
19078
- })
19110
+ }),
19111
+ detailHint: stringSchema("How to request the complete payload.")
19079
19112
  }, [], "Skill run result.");
19080
19113
  var toolContracts = [
19081
19114
  {
@@ -19123,7 +19156,7 @@ var toolContracts = [
19123
19156
  {
19124
19157
  name: "list_skills",
19125
19158
  title: "List Skills",
19126
- description: "List skills from the basic or full registry profile.",
19159
+ description: "List skills from the basic or full registry profile. Returns a compact paged envelope by default.",
19127
19160
  params: ["category?", "profile?", "detail?", "limit?", "offset?"],
19128
19161
  category: "discovery",
19129
19162
  sideEffects: "none",
@@ -19139,7 +19172,11 @@ var toolContracts = [
19139
19172
  skills: arraySchema(skillSummarySchema),
19140
19173
  total: { type: "number" },
19141
19174
  offset: { type: "number" },
19142
- limit: { type: "number" }
19175
+ limit: { type: "number" },
19176
+ nextOffset: { type: "number" },
19177
+ hasMore: { type: "boolean" },
19178
+ nextArguments: objectSchema({}, [], "Arguments for the next page.", true),
19179
+ detailHint: stringSchema("How to request fuller skill objects.")
19143
19180
  })
19144
19181
  },
19145
19182
  {
@@ -19160,7 +19197,7 @@ var toolContracts = [
19160
19197
  {
19161
19198
  name: "search_skills",
19162
19199
  title: "Search Skills",
19163
- description: "Search skills by name, description, or tags.",
19200
+ description: "Search skills by name, description, or tags. Returns a compact paged envelope by default.",
19164
19201
  params: ["query", "profile?", "detail?", "limit?", "offset?"],
19165
19202
  category: "discovery",
19166
19203
  sideEffects: "none",
@@ -19172,7 +19209,16 @@ var toolContracts = [
19172
19209
  limit: { type: "number", minimum: 0 },
19173
19210
  offset: { type: "number", minimum: 0 }
19174
19211
  }, ["query"]),
19175
- outputSchema: objectSchema({ skills: arraySchema(skillSummarySchema) })
19212
+ outputSchema: objectSchema({
19213
+ skills: arraySchema(skillSummarySchema),
19214
+ total: { type: "number" },
19215
+ offset: { type: "number" },
19216
+ limit: { type: "number" },
19217
+ nextOffset: { type: "number" },
19218
+ hasMore: { type: "boolean" },
19219
+ nextArguments: objectSchema({}, [], "Arguments for the next page.", true),
19220
+ detailHint: stringSchema("How to request fuller skill objects.")
19221
+ })
19176
19222
  },
19177
19223
  {
19178
19224
  name: "get_skill_info",
@@ -19289,8 +19335,8 @@ var toolContracts = [
19289
19335
  {
19290
19336
  name: "run_skill",
19291
19337
  title: "Run Skill",
19292
- description: "Run a skill locally or through a configured remote runner.",
19293
- params: ["name", "input?", "args?", "approved?"],
19338
+ description: "Run a skill locally or through a configured remote runner. Returns compact stdout/stderr previews and run summaries by default; pass detail:true for full records.",
19339
+ params: ["name", "input?", "args?", "approved?", "detail?"],
19294
19340
  category: "execution",
19295
19341
  sideEffects: "local-process-or-remote-run",
19296
19342
  stable: true,
@@ -19298,28 +19344,33 @@ var toolContracts = [
19298
19344
  name: skillNameInput,
19299
19345
  input: runInputSchema,
19300
19346
  args: runArgsSchema,
19301
- approved: paidRunApprovalSchema
19347
+ approved: paidRunApprovalSchema,
19348
+ detail: { type: "boolean", default: false, description: "Return full stdout/stderr, remote run, and local run metadata." }
19302
19349
  }, ["name"]),
19303
19350
  outputSchema: runOutputSchema
19304
19351
  },
19305
19352
  {
19306
19353
  name: "get_run_status",
19307
19354
  title: "Get Run Status",
19308
- description: "Fetch remote run status and next actions.",
19309
- params: ["run_id"],
19355
+ description: "Fetch remote run status and next actions. Returns a compact status summary by default; pass detail:true for the complete remote run payload.",
19356
+ params: ["run_id", "detail?"],
19310
19357
  category: "execution",
19311
19358
  sideEffects: "none",
19312
19359
  stable: true,
19313
- inputSchema: objectSchema({ run_id: stringSchema("Remote or local run id.") }, ["run_id"]),
19360
+ inputSchema: objectSchema({
19361
+ run_id: stringSchema("Remote or local run id."),
19362
+ detail: { type: "boolean", default: false, description: "Return the complete remote run payload." }
19363
+ }, ["run_id"]),
19314
19364
  outputSchema: objectSchema({
19315
19365
  contractVersion: { type: "number", description: "Remote run payload contract version." },
19316
19366
  runId: stringSchema("Remote run id."),
19317
19367
  localRunId: stringSchema("Local run id."),
19318
- run: objectSchema({}, [], "Normalized remote run status.", true),
19368
+ run: objectSchema({}, [], "Compact remote run status by default; full status when detail:true is requested.", true),
19319
19369
  nextActions: objectSchema({
19320
19370
  poll: stringSchema("Command to poll run status."),
19321
19371
  download: stringSchema("Command to download artifacts.")
19322
- })
19372
+ }),
19373
+ detailHint: stringSchema("How to request the complete payload.")
19323
19374
  })
19324
19375
  },
19325
19376
  {
@@ -19414,13 +19465,25 @@ var toolContracts = [
19414
19465
  {
19415
19466
  name: "list_schedules",
19416
19467
  title: "List Schedules",
19417
- description: "List scheduled skill runs.",
19418
- params: [],
19468
+ description: "List scheduled skill runs as a compact paged envelope.",
19469
+ params: ["limit?", "offset?"],
19419
19470
  category: "scheduling",
19420
19471
  sideEffects: "none",
19421
19472
  stable: true,
19422
- inputSchema: objectSchema(),
19423
- outputSchema: arraySchema(objectSchema({}, [], "Schedule record.", true))
19473
+ inputSchema: objectSchema({
19474
+ limit: { type: "number", minimum: 0 },
19475
+ offset: { type: "number", minimum: 0 }
19476
+ }),
19477
+ outputSchema: objectSchema({
19478
+ schedules: arraySchema(objectSchema({}, [], "Compact schedule record.", true)),
19479
+ total: { type: "number" },
19480
+ offset: { type: "number" },
19481
+ limit: { type: "number" },
19482
+ nextOffset: { type: "number" },
19483
+ hasMore: { type: "boolean" },
19484
+ nextArguments: objectSchema({}, [], "Arguments for the next page.", true),
19485
+ detailHint: stringSchema("How to request complete schedule details.")
19486
+ })
19424
19487
  },
19425
19488
  {
19426
19489
  name: "remove_schedule",
@@ -19753,8 +19816,8 @@ import {
19753
19816
  existsSync as existsSync12,
19754
19817
  mkdirSync as mkdirSync9,
19755
19818
  readFileSync as readFileSync11,
19756
- readdirSync as readdirSync5,
19757
- statSync as statSync4,
19819
+ readdirSync as readdirSync6,
19820
+ statSync as statSync5,
19758
19821
  writeFileSync as writeFileSync8
19759
19822
  } from "fs";
19760
19823
  import { dirname as dirname6, join as join12, normalize as normalize3, relative as relative4, sep } from "path";
@@ -20216,9 +20279,9 @@ function parsePositiveInteger(value) {
20216
20279
  }
20217
20280
  function walkFiles2(dir) {
20218
20281
  const files = [];
20219
- for (const entry of readdirSync5(dir)) {
20282
+ for (const entry of readdirSync6(dir)) {
20220
20283
  const full = join12(dir, entry);
20221
- const stats = statSync4(full);
20284
+ const stats = statSync5(full);
20222
20285
  if (stats.isDirectory())
20223
20286
  files.push(...walkFiles2(full));
20224
20287
  else
@@ -0,0 +1,32 @@
1
+ export declare const DEFAULT_LIST_LIMIT = 30;
2
+ export declare const DEFAULT_SEARCH_LIMIT = 20;
3
+ export declare const DEFAULT_TAG_LIMIT = 80;
4
+ export declare const DEFAULT_MCP_LIMIT = 25;
5
+ export declare const MAX_PAGE_LIMIT = 200;
6
+ export declare const DEFAULT_PREVIEW_CHARS = 600;
7
+ export interface Page<T> {
8
+ items: T[];
9
+ total: number;
10
+ offset: number;
11
+ limit: number;
12
+ hasMore: boolean;
13
+ nextOffset: number | null;
14
+ }
15
+ export declare function truncateText(value: unknown, maxChars?: number): string;
16
+ export declare function previewText(value: unknown, maxChars?: number): {
17
+ text: string;
18
+ length: number;
19
+ truncated: boolean;
20
+ };
21
+ export declare function parsePageLimit(value: string | number | undefined, fallback: number, options?: {
22
+ max?: number;
23
+ allowAll?: boolean;
24
+ }): number;
25
+ export declare function parsePageOffset(value: string | number | undefined, fallback?: number): number;
26
+ export declare function paginate<T>(items: T[], options: {
27
+ limit: number;
28
+ offset?: number;
29
+ }): Page<T>;
30
+ export declare function showingLabel(total: number, shown: number, offset: number): string;
31
+ export declare function compactRunRecord(run: any): Record<string, unknown>;
32
+ export declare function compactRemoteRun(run: any): Record<string, unknown>;
@@ -19,7 +19,7 @@ export type ConfigScope = "global" | "project";
19
19
  /**
20
20
  * Get the data directory for skills global config/data.
21
21
  * New default: ~/.hasna/skills/
22
- * Auto-migrates from ~/.skillsrc if the new config doesn't exist yet.
22
+ * Auto-migrates from ~/.skills/ and ~/.skillsrc without deleting legacy data.
23
23
  */
24
24
  export declare function getDataDir(): string;
25
25
  /**
@@ -11,6 +11,7 @@ export declare const SKILL_ALIASES: {
11
11
  readonly "generate-image": "image";
12
12
  readonly "image-generator": "image";
13
13
  readonly "create-blog-article": "blog-article";
14
+ readonly "skill-diff": "diff-viewer";
14
15
  };
15
16
  export type SkillAlias = keyof typeof SKILL_ALIASES;
16
17
  export declare function normalizeSkillSlug(name: string): string;
package/dist/storage.js CHANGED
@@ -20,14 +20,14 @@ import {
20
20
  existsSync as existsSync3,
21
21
  mkdirSync as mkdirSync3,
22
22
  readFileSync as readFileSync3,
23
- readdirSync,
24
- statSync,
23
+ readdirSync as readdirSync2,
24
+ statSync as statSync2,
25
25
  writeFileSync as writeFileSync3
26
26
  } from "fs";
27
27
  import { dirname as dirname2, join as join3, normalize, relative, sep } from "path";
28
28
 
29
29
  // src/lib/config.ts
30
- import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "fs";
30
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
31
31
  import { join, dirname } from "path";
32
32
  import { homedir } from "os";
33
33
  var ENUM_KEYS = {
@@ -53,6 +53,24 @@ function allowedValues(key) {
53
53
  return MODE_VALUES;
54
54
  return ENUM_KEYS[key];
55
55
  }
56
+ function mergeDirectoryContents(sourceDir, targetDir) {
57
+ if (!existsSync(sourceDir))
58
+ return;
59
+ mkdirSync(targetDir, { recursive: true });
60
+ for (const entry of readdirSync(sourceDir)) {
61
+ const sourcePath = join(sourceDir, entry);
62
+ const targetPath = join(targetDir, entry);
63
+ try {
64
+ const sourceStat = statSync(sourcePath);
65
+ if (sourceStat.isDirectory()) {
66
+ mergeDirectoryContents(sourcePath, targetPath);
67
+ continue;
68
+ }
69
+ if (!existsSync(targetPath))
70
+ copyFileSync(sourcePath, targetPath);
71
+ } catch {}
72
+ }
73
+ }
56
74
  function normalizeConfigValue(key, value) {
57
75
  if (typeof value !== "string")
58
76
  return;
@@ -76,14 +94,17 @@ function normalizeConfigValue(key, value) {
76
94
  function getDataDir() {
77
95
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
78
96
  const newDir = join(home, ".hasna", "skills");
97
+ const oldDir = join(home, ".skills");
79
98
  const oldConfigFile = join(home, ".skillsrc");
99
+ mkdirSync(newDir, { recursive: true });
100
+ try {
101
+ mergeDirectoryContents(oldDir, newDir);
102
+ } catch {}
80
103
  if (existsSync(oldConfigFile) && !existsSync(join(newDir, "config.json"))) {
81
- mkdirSync(newDir, { recursive: true });
82
104
  try {
83
105
  copyFileSync(oldConfigFile, join(newDir, "config.json"));
84
106
  } catch {}
85
107
  }
86
- mkdirSync(newDir, { recursive: true });
87
108
  return newDir;
88
109
  }
89
110
  function getConfigPath(scope) {
@@ -743,9 +764,9 @@ function parsePositiveInteger(value) {
743
764
  }
744
765
  function walkFiles(dir) {
745
766
  const files = [];
746
- for (const entry of readdirSync(dir)) {
767
+ for (const entry of readdirSync2(dir)) {
747
768
  const full = join3(dir, entry);
748
- const stats = statSync(full);
769
+ const stats = statSync2(full);
749
770
  if (stats.isDirectory())
750
771
  files.push(...walkFiles(full));
751
772
  else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -69,7 +69,7 @@
69
69
  "typescript": "^5"
70
70
  },
71
71
  "dependencies": {
72
- "@hasna/events": "^0.1.3",
72
+ "@hasna/events": "^0.1.7",
73
73
  "@modelcontextprotocol/sdk": "^1.26.0",
74
74
  "chalk": "^5.3.0",
75
75
  "commander": "^12.1.0",