@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/bin/mcp.js CHANGED
@@ -6510,7 +6510,7 @@ var require_dist = __commonJS((exports, module) => {
6510
6510
  });
6511
6511
 
6512
6512
  // src/lib/config.ts
6513
- import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "fs";
6513
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
6514
6514
  import { join, dirname } from "path";
6515
6515
  import { homedir } from "os";
6516
6516
  function validKeys() {
@@ -6521,6 +6521,24 @@ function allowedValues(key) {
6521
6521
  return MODE_VALUES;
6522
6522
  return ENUM_KEYS[key];
6523
6523
  }
6524
+ function mergeDirectoryContents(sourceDir, targetDir) {
6525
+ if (!existsSync(sourceDir))
6526
+ return;
6527
+ mkdirSync(targetDir, { recursive: true });
6528
+ for (const entry of readdirSync(sourceDir)) {
6529
+ const sourcePath = join(sourceDir, entry);
6530
+ const targetPath = join(targetDir, entry);
6531
+ try {
6532
+ const sourceStat = statSync(sourcePath);
6533
+ if (sourceStat.isDirectory()) {
6534
+ mergeDirectoryContents(sourcePath, targetPath);
6535
+ continue;
6536
+ }
6537
+ if (!existsSync(targetPath))
6538
+ copyFileSync(sourcePath, targetPath);
6539
+ } catch {}
6540
+ }
6541
+ }
6524
6542
  function normalizeConfigValue(key, value) {
6525
6543
  if (typeof value !== "string")
6526
6544
  return;
@@ -6544,14 +6562,17 @@ function normalizeConfigValue(key, value) {
6544
6562
  function getDataDir() {
6545
6563
  const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
6546
6564
  const newDir = join(home, ".hasna", "skills");
6565
+ const oldDir = join(home, ".skills");
6547
6566
  const oldConfigFile = join(home, ".skillsrc");
6567
+ mkdirSync(newDir, { recursive: true });
6568
+ try {
6569
+ mergeDirectoryContents(oldDir, newDir);
6570
+ } catch {}
6548
6571
  if (existsSync(oldConfigFile) && !existsSync(join(newDir, "config.json"))) {
6549
- mkdirSync(newDir, { recursive: true });
6550
6572
  try {
6551
6573
  copyFileSync(oldConfigFile, join(newDir, "config.json"));
6552
6574
  } catch {}
6553
6575
  }
6554
- mkdirSync(newDir, { recursive: true });
6555
6576
  return newDir;
6556
6577
  }
6557
6578
  function getConfigPath(scope) {
@@ -6625,7 +6646,8 @@ var init_skill_aliases = __esm(() => {
6625
6646
  "pdf-reader": "read-pdf",
6626
6647
  "generate-image": "image",
6627
6648
  "image-generator": "image",
6628
- "create-blog-article": "blog-article"
6649
+ "create-blog-article": "blog-article",
6650
+ "skill-diff": "diff-viewer"
6629
6651
  };
6630
6652
  });
6631
6653
 
@@ -7042,7 +7064,7 @@ var init_pricing = __esm(() => {
7042
7064
  { 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" },
7043
7065
  { slug: "generate-book-cover", displayName: "Book Cover", tier: "premium", costCents: 20, providers: ["gpt-image-2"], description: "Professional book cover design from title and genre" },
7044
7066
  { slug: "remove-background", displayName: "Remove Background", tier: "premium", costCents: 10, providers: ["gemini-3-pro"], description: "AI-powered background removal from images" },
7045
- { slug: "transcript", displayName: "Transcript", tier: "premium", costCents: 10, providers: ["whisper"], description: "Audio/video transcription with timestamps" },
7067
+ { slug: "transcript", displayName: "Transcript", tier: "premium", costCents: 10, providers: ["openai", "elevenlabs", "deepgram", "hosted"], description: "Audio/video transcription with timestamps, diarization, and URL support" },
7046
7068
  { slug: "webcrawling", displayName: "Web Crawling", tier: "premium", costCents: 5, providers: ["firecrawl"], description: "Structured web page crawling and extraction" },
7047
7069
  { slug: "browse", displayName: "Browse", tier: "premium", costCents: 5, providers: ["browser"], description: "Web browsing and page interaction" },
7048
7070
  { slug: "read-pdf", displayName: "Read PDF", tier: "premium", costCents: 5, providers: ["cerebras"], description: "Hosted PDF extraction and structured content analysis" },
@@ -18269,7 +18291,7 @@ function finalize(ctx, schema) {
18269
18291
  result.$schema = "http://json-schema.org/draft-07/schema#";
18270
18292
  } else if (ctx.target === "draft-04") {
18271
18293
  result.$schema = "http://json-schema.org/draft-04/schema#";
18272
- } else if (ctx.target === "openapi-3.0") {}
18294
+ } else if (ctx.target === "openapi-3.0") {} else {}
18273
18295
  if (ctx.external?.uri) {
18274
18296
  const id = ctx.external.registry.get(schema)?.id;
18275
18297
  if (!id)
@@ -18517,7 +18539,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
18517
18539
  if (val === undefined) {
18518
18540
  if (ctx.unrepresentable === "throw") {
18519
18541
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
18520
- }
18542
+ } else {}
18521
18543
  } else if (typeof val === "bigint") {
18522
18544
  if (ctx.unrepresentable === "throw") {
18523
18545
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -21797,7 +21819,7 @@ class StdioServerTransport {
21797
21819
  // package.json
21798
21820
  var package_default = {
21799
21821
  name: "@hasna/skills",
21800
- version: "0.1.46",
21822
+ version: "0.1.48",
21801
21823
  description: "Skills library for AI coding agents",
21802
21824
  type: "module",
21803
21825
  bin: {
@@ -21866,7 +21888,7 @@ var package_default = {
21866
21888
  typescript: "^5"
21867
21889
  },
21868
21890
  dependencies: {
21869
- "@hasna/events": "^0.1.3",
21891
+ "@hasna/events": "^0.1.7",
21870
21892
  "@modelcontextprotocol/sdk": "^1.26.0",
21871
21893
  chalk: "^5.3.0",
21872
21894
  commander: "^12.1.0",
@@ -29480,7 +29502,7 @@ var EMPTY_COMPLETION_RESULT = {
29480
29502
 
29481
29503
  // src/lib/registry.ts
29482
29504
  init_config();
29483
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
29505
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync4 } from "fs";
29484
29506
  import { join as join4 } from "path";
29485
29507
 
29486
29508
  // src/lib/portable-skills.ts
@@ -29491,16 +29513,16 @@ import {
29491
29513
  lstatSync as lstatSync2,
29492
29514
  mkdirSync as mkdirSync2,
29493
29515
  readFileSync as readFileSync3,
29494
- readdirSync as readdirSync2,
29516
+ readdirSync as readdirSync3,
29495
29517
  rmSync,
29496
- statSync as statSync2,
29518
+ statSync as statSync3,
29497
29519
  writeFileSync as writeFileSync2
29498
29520
  } from "fs";
29499
29521
  import { basename, dirname as dirname2, isAbsolute as isAbsolute2, join as join3, normalize as normalize2, relative } from "path";
29500
29522
 
29501
29523
  // src/lib/skill-validation.ts
29502
29524
  init_pricing();
29503
- import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
29525
+ import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
29504
29526
  import { isAbsolute, join as join2, normalize } from "path";
29505
29527
  var DOC_FILES = ["SKILL.md", "README.md", "CLAUDE.md"];
29506
29528
  var RESERVED_SKILL_ENTRIES = new Set([
@@ -29656,7 +29678,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
29656
29678
  if (!VALID_BIN_COMMAND.test(bareName)) {
29657
29679
  add(issues, "skill.name_invalid", `Skill name '${bareName}' must use lowercase letters, numbers, dots, underscores, or hyphens`);
29658
29680
  }
29659
- for (const entry of readdirSync(skillPath).sort()) {
29681
+ for (const entry of readdirSync2(skillPath).sort()) {
29660
29682
  const entryPath = join2(skillPath, entry);
29661
29683
  if (RESERVED_SKILL_ENTRIES.has(entry)) {
29662
29684
  add(issues, "skill.reserved_file", `Reserved file '${entry}' is not allowed in skill packages`);
@@ -29771,7 +29793,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
29771
29793
  const targetPath = join2(skillPath, target);
29772
29794
  if (!existsSync2(targetPath)) {
29773
29795
  add(warnings, "package.bin_target_missing", `package.json bin '${command}' target '${target}' is not present before build`);
29774
- } else if (statSync(targetPath).isDirectory()) {
29796
+ } else if (statSync2(targetPath).isDirectory()) {
29775
29797
  add(issues, "package.bin_target_directory", `package.json bin '${command}' target '${target}' must point to a file, not a directory`);
29776
29798
  }
29777
29799
  }
@@ -29794,7 +29816,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
29794
29816
  add(issues, "skill.src_index_missing", "Missing src/index.ts or src/index.js");
29795
29817
  } else {
29796
29818
  const indexPath = existsSync2(join2(srcDir, "index.ts")) ? join2(srcDir, "index.ts") : join2(srcDir, "index.js");
29797
- const size = statSync(indexPath).size;
29819
+ const size = statSync2(indexPath).size;
29798
29820
  if (size < 50)
29799
29821
  add(warnings, "skill.src_index_minimal", `Source entry point is very small (${size}B)`);
29800
29822
  }
@@ -29861,7 +29883,7 @@ function findPortableSkill(name, options = {}) {
29861
29883
  return null;
29862
29884
  }
29863
29885
  const path = getPortableSkillPath(normalized, options);
29864
- if (!existsSync3(path) || !statSync2(path).isDirectory())
29886
+ if (!existsSync3(path) || !statSync3(path).isDirectory())
29865
29887
  return null;
29866
29888
  try {
29867
29889
  return summarizePortableSkill(path, normalized);
@@ -29874,7 +29896,7 @@ function listPortableSkills(options = {}) {
29874
29896
  if (!existsSync3(root))
29875
29897
  return [];
29876
29898
  const skills = [];
29877
- for (const entry of readdirSync2(root).sort()) {
29899
+ for (const entry of readdirSync3(root).sort()) {
29878
29900
  if (entry.startsWith(".") || DATA_DIR_NON_SKILL_ENTRIES.has(entry))
29879
29901
  continue;
29880
29902
  const path = join3(root, entry);
@@ -29950,7 +29972,7 @@ function scaffoldPortableSkill(name, options = {}) {
29950
29972
  }
29951
29973
  function portPortableSkill(sourcePath, options = {}) {
29952
29974
  const absoluteSource = normalize2(sourcePath);
29953
- if (!existsSync3(absoluteSource) || !statSync2(absoluteSource).isDirectory()) {
29975
+ if (!existsSync3(absoluteSource) || !statSync3(absoluteSource).isDirectory()) {
29954
29976
  throw new Error(`Skill source directory not found: ${sourcePath}`);
29955
29977
  }
29956
29978
  const inferred = readPortableSkillManifest(absoluteSource, basename(absoluteSource));
@@ -30019,7 +30041,7 @@ function validatePortableSkillDirectory(name, skillPath) {
30019
30041
  const entryPath = join3(skillPath, command.entry);
30020
30042
  if (!existsSync3(entryPath))
30021
30043
  add2(issues, "portable.command_entry_missing", `Command '${command.name}' entry '${command.entry}' is missing`);
30022
- else if (statSync2(entryPath).isDirectory())
30044
+ else if (statSync3(entryPath).isDirectory())
30023
30045
  add2(issues, "portable.command_entry_directory", `Command '${command.name}' entry '${command.entry}' must be a file`);
30024
30046
  }
30025
30047
  }
@@ -30387,7 +30409,7 @@ function displayName(name) {
30387
30409
  }
30388
30410
  function safeIsDirectory(path) {
30389
30411
  try {
30390
- return statSync2(path).isDirectory();
30412
+ return statSync3(path).isDirectory();
30391
30413
  } catch {
30392
30414
  return false;
30393
30415
  }
@@ -31601,9 +31623,9 @@ var MEDIA_PROCESSING_SKILLS = [
31601
31623
  {
31602
31624
  name: "transcript",
31603
31625
  displayName: "Transcript",
31604
- description: "Generate transcripts from audio and video files with timestamps",
31626
+ description: "Transcribe audio, video, and media URLs with OpenAI GPT-4o, ElevenLabs Scribe v2, DeepGram, or hosted runtime",
31605
31627
  category: "Media Processing",
31606
- tags: ["transcript", "audio", "video", "speech-to-text"]
31628
+ tags: ["transcript", "audio", "video", "speech-to-text", "diarization", "youtube"]
31607
31629
  },
31608
31630
  {
31609
31631
  name: "video-cut-suggester",
@@ -32383,7 +32405,7 @@ function discoverSkillsInDir(dir) {
32383
32405
  return [];
32384
32406
  const result = [];
32385
32407
  try {
32386
- const entries = readdirSync3(dir, { withFileTypes: true });
32408
+ const entries = readdirSync4(dir, { withFileTypes: true });
32387
32409
  for (const entry of entries) {
32388
32410
  if (!entry.isDirectory())
32389
32411
  continue;
@@ -33047,18 +33069,29 @@ var runOutputSchema = objectSchema({
33047
33069
  exitCode: { type: "number", description: "Process exit code for local runs." },
33048
33070
  skill: stringSchema("Canonical skill slug."),
33049
33071
  remote: { type: "boolean", description: "Whether the skill was submitted to the hosted runtime." },
33050
- stdout: stringSchema("Captured stdout for local runs."),
33051
- stderr: stringSchema("Captured stderr for local runs."),
33072
+ stdoutPreview: objectSchema({
33073
+ text: stringSchema("Truncated stdout preview."),
33074
+ length: { type: "number" },
33075
+ truncated: { type: "boolean" }
33076
+ }, [], "Default compact stdout preview."),
33077
+ stderrPreview: objectSchema({
33078
+ text: stringSchema("Truncated stderr preview."),
33079
+ length: { type: "number" },
33080
+ truncated: { type: "boolean" }
33081
+ }, [], "Default compact stderr preview."),
33082
+ stdout: stringSchema("Captured stdout for local runs when detail:true is requested."),
33083
+ stderr: stringSchema("Captured stderr for local runs when detail:true is requested."),
33052
33084
  id: stringSchema("Remote run id when submitted remotely."),
33053
33085
  localRunId: stringSchema("Local run metadata id."),
33054
33086
  status: stringSchema("Run lifecycle status."),
33055
33087
  pricing: pricingSchema,
33056
- remoteRun: objectSchema({}, [], "Normalized hosted remote run contract.", true),
33057
- run: objectSchema({}, [], "Local run metadata.", true),
33088
+ remoteRun: objectSchema({}, [], "Compact hosted remote run summary by default; full contract when detail:true is requested.", true),
33089
+ run: objectSchema({}, [], "Compact local run metadata by default; full metadata when detail:true is requested.", true),
33058
33090
  nextActions: objectSchema({
33059
33091
  poll: stringSchema("Command to poll run status."),
33060
33092
  download: stringSchema("Command to download artifacts.")
33061
- })
33093
+ }),
33094
+ detailHint: stringSchema("How to request the complete payload.")
33062
33095
  }, [], "Skill run result.");
33063
33096
  var toolContracts = [
33064
33097
  {
@@ -33106,7 +33139,7 @@ var toolContracts = [
33106
33139
  {
33107
33140
  name: "list_skills",
33108
33141
  title: "List Skills",
33109
- description: "List skills from the basic or full registry profile.",
33142
+ description: "List skills from the basic or full registry profile. Returns a compact paged envelope by default.",
33110
33143
  params: ["category?", "profile?", "detail?", "limit?", "offset?"],
33111
33144
  category: "discovery",
33112
33145
  sideEffects: "none",
@@ -33122,7 +33155,11 @@ var toolContracts = [
33122
33155
  skills: arraySchema(skillSummarySchema),
33123
33156
  total: { type: "number" },
33124
33157
  offset: { type: "number" },
33125
- limit: { type: "number" }
33158
+ limit: { type: "number" },
33159
+ nextOffset: { type: "number" },
33160
+ hasMore: { type: "boolean" },
33161
+ nextArguments: objectSchema({}, [], "Arguments for the next page.", true),
33162
+ detailHint: stringSchema("How to request fuller skill objects.")
33126
33163
  })
33127
33164
  },
33128
33165
  {
@@ -33143,7 +33180,7 @@ var toolContracts = [
33143
33180
  {
33144
33181
  name: "search_skills",
33145
33182
  title: "Search Skills",
33146
- description: "Search skills by name, description, or tags.",
33183
+ description: "Search skills by name, description, or tags. Returns a compact paged envelope by default.",
33147
33184
  params: ["query", "profile?", "detail?", "limit?", "offset?"],
33148
33185
  category: "discovery",
33149
33186
  sideEffects: "none",
@@ -33155,7 +33192,16 @@ var toolContracts = [
33155
33192
  limit: { type: "number", minimum: 0 },
33156
33193
  offset: { type: "number", minimum: 0 }
33157
33194
  }, ["query"]),
33158
- outputSchema: objectSchema({ skills: arraySchema(skillSummarySchema) })
33195
+ outputSchema: objectSchema({
33196
+ skills: arraySchema(skillSummarySchema),
33197
+ total: { type: "number" },
33198
+ offset: { type: "number" },
33199
+ limit: { type: "number" },
33200
+ nextOffset: { type: "number" },
33201
+ hasMore: { type: "boolean" },
33202
+ nextArguments: objectSchema({}, [], "Arguments for the next page.", true),
33203
+ detailHint: stringSchema("How to request fuller skill objects.")
33204
+ })
33159
33205
  },
33160
33206
  {
33161
33207
  name: "get_skill_info",
@@ -33272,8 +33318,8 @@ var toolContracts = [
33272
33318
  {
33273
33319
  name: "run_skill",
33274
33320
  title: "Run Skill",
33275
- description: "Run a skill locally or through a configured remote runner.",
33276
- params: ["name", "input?", "args?", "approved?"],
33321
+ 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.",
33322
+ params: ["name", "input?", "args?", "approved?", "detail?"],
33277
33323
  category: "execution",
33278
33324
  sideEffects: "local-process-or-remote-run",
33279
33325
  stable: true,
@@ -33281,28 +33327,33 @@ var toolContracts = [
33281
33327
  name: skillNameInput,
33282
33328
  input: runInputSchema,
33283
33329
  args: runArgsSchema,
33284
- approved: paidRunApprovalSchema
33330
+ approved: paidRunApprovalSchema,
33331
+ detail: { type: "boolean", default: false, description: "Return full stdout/stderr, remote run, and local run metadata." }
33285
33332
  }, ["name"]),
33286
33333
  outputSchema: runOutputSchema
33287
33334
  },
33288
33335
  {
33289
33336
  name: "get_run_status",
33290
33337
  title: "Get Run Status",
33291
- description: "Fetch remote run status and next actions.",
33292
- params: ["run_id"],
33338
+ description: "Fetch remote run status and next actions. Returns a compact status summary by default; pass detail:true for the complete remote run payload.",
33339
+ params: ["run_id", "detail?"],
33293
33340
  category: "execution",
33294
33341
  sideEffects: "none",
33295
33342
  stable: true,
33296
- inputSchema: objectSchema({ run_id: stringSchema("Remote or local run id.") }, ["run_id"]),
33343
+ inputSchema: objectSchema({
33344
+ run_id: stringSchema("Remote or local run id."),
33345
+ detail: { type: "boolean", default: false, description: "Return the complete remote run payload." }
33346
+ }, ["run_id"]),
33297
33347
  outputSchema: objectSchema({
33298
33348
  contractVersion: { type: "number", description: "Remote run payload contract version." },
33299
33349
  runId: stringSchema("Remote run id."),
33300
33350
  localRunId: stringSchema("Local run id."),
33301
- run: objectSchema({}, [], "Normalized remote run status.", true),
33351
+ run: objectSchema({}, [], "Compact remote run status by default; full status when detail:true is requested.", true),
33302
33352
  nextActions: objectSchema({
33303
33353
  poll: stringSchema("Command to poll run status."),
33304
33354
  download: stringSchema("Command to download artifacts.")
33305
- })
33355
+ }),
33356
+ detailHint: stringSchema("How to request the complete payload.")
33306
33357
  })
33307
33358
  },
33308
33359
  {
@@ -33397,13 +33448,25 @@ var toolContracts = [
33397
33448
  {
33398
33449
  name: "list_schedules",
33399
33450
  title: "List Schedules",
33400
- description: "List scheduled skill runs.",
33401
- params: [],
33451
+ description: "List scheduled skill runs as a compact paged envelope.",
33452
+ params: ["limit?", "offset?"],
33402
33453
  category: "scheduling",
33403
33454
  sideEffects: "none",
33404
33455
  stable: true,
33405
- inputSchema: objectSchema(),
33406
- outputSchema: arraySchema(objectSchema({}, [], "Schedule record.", true))
33456
+ inputSchema: objectSchema({
33457
+ limit: { type: "number", minimum: 0 },
33458
+ offset: { type: "number", minimum: 0 }
33459
+ }),
33460
+ outputSchema: objectSchema({
33461
+ schedules: arraySchema(objectSchema({}, [], "Compact schedule record.", true)),
33462
+ total: { type: "number" },
33463
+ offset: { type: "number" },
33464
+ limit: { type: "number" },
33465
+ nextOffset: { type: "number" },
33466
+ hasMore: { type: "boolean" },
33467
+ nextArguments: objectSchema({}, [], "Arguments for the next page.", true),
33468
+ detailHint: stringSchema("How to request complete schedule details.")
33469
+ })
33407
33470
  },
33408
33471
  {
33409
33472
  name: "remove_schedule",
@@ -33810,6 +33873,90 @@ function resolveDiscoveryPricing(skill) {
33810
33873
  return skill.pricing && typeof skill.pricing.formattedCost === "string" ? skill.pricing : getPublicSkillPricing(skill.name);
33811
33874
  }
33812
33875
 
33876
+ // src/lib/compact-output.ts
33877
+ var DEFAULT_MCP_LIMIT = 25;
33878
+ var MAX_PAGE_LIMIT = 200;
33879
+ var DEFAULT_PREVIEW_CHARS = 600;
33880
+ function truncateText(value, maxChars = 96) {
33881
+ const text = String(value ?? "").replace(/\s+/g, " ").trim();
33882
+ if (text.length <= maxChars)
33883
+ return text;
33884
+ return `${text.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
33885
+ }
33886
+ function previewText(value, maxChars = DEFAULT_PREVIEW_CHARS) {
33887
+ const text = String(value ?? "");
33888
+ return {
33889
+ text: text.length > maxChars ? `${text.slice(0, Math.max(0, maxChars - 3))}...` : text,
33890
+ length: text.length,
33891
+ truncated: text.length > maxChars
33892
+ };
33893
+ }
33894
+ function parsePageLimit(value, fallback, options = {}) {
33895
+ const max = options.max ?? MAX_PAGE_LIMIT;
33896
+ if (typeof value === "string" && options.allowAll && value.trim().toLowerCase() === "all") {
33897
+ return Number.POSITIVE_INFINITY;
33898
+ }
33899
+ const parsed = typeof value === "number" ? value : Number.parseInt(value ?? "", 10);
33900
+ if (!Number.isFinite(parsed))
33901
+ return fallback;
33902
+ if (options.allowAll && parsed === 0)
33903
+ return Number.POSITIVE_INFINITY;
33904
+ if (parsed <= 0)
33905
+ return fallback;
33906
+ return Math.min(parsed, max);
33907
+ }
33908
+ function parsePageOffset(value, fallback = 0) {
33909
+ const parsed = typeof value === "number" ? value : Number.parseInt(value ?? "", 10);
33910
+ if (!Number.isFinite(parsed) || parsed < 0)
33911
+ return fallback;
33912
+ return parsed;
33913
+ }
33914
+ function paginate(items, options) {
33915
+ const offset = Math.min(parsePageOffset(options.offset), items.length);
33916
+ const effectiveLimit = Number.isFinite(options.limit) ? Math.max(0, Math.min(options.limit, MAX_PAGE_LIMIT)) : Math.max(0, items.length - offset);
33917
+ const pageItems = items.slice(offset, offset + effectiveLimit);
33918
+ const nextOffset = offset + pageItems.length < items.length ? offset + pageItems.length : null;
33919
+ return {
33920
+ items: pageItems,
33921
+ total: items.length,
33922
+ offset,
33923
+ limit: effectiveLimit,
33924
+ hasMore: nextOffset !== null,
33925
+ nextOffset
33926
+ };
33927
+ }
33928
+ function compactRunRecord(run) {
33929
+ if (!run || typeof run !== "object")
33930
+ return {};
33931
+ return {
33932
+ id: run.id,
33933
+ skill: run.skill,
33934
+ status: run.status,
33935
+ startedAt: run.startedAt,
33936
+ ...run.completedAt ? { completedAt: run.completedAt } : {},
33937
+ ...run.remoteRunId ? { remoteRunId: run.remoteRunId } : {},
33938
+ ...run.costCents !== undefined ? { costCents: run.costCents } : {},
33939
+ ...run.error ? { error: truncateText(run.error, 240) } : {},
33940
+ artifactCount: Array.isArray(run.artifacts) ? run.artifacts.length : 0,
33941
+ paths: run.paths
33942
+ };
33943
+ }
33944
+ function compactRemoteRun(run) {
33945
+ if (!run || typeof run !== "object")
33946
+ return {};
33947
+ return {
33948
+ ...run.contractVersion !== undefined ? { contractVersion: run.contractVersion } : {},
33949
+ id: run.id,
33950
+ skill: run.skill ?? run.requestedSlug,
33951
+ status: run.status,
33952
+ ...run.correlationId ? { correlationId: run.correlationId } : {},
33953
+ ...run.createdAt ? { createdAt: run.createdAt } : {},
33954
+ ...run.startedAt ? { startedAt: run.startedAt } : {},
33955
+ ...run.completedAt ? { completedAt: run.completedAt } : {},
33956
+ ...run.error || run.errorMessage ? { error: truncateText(run.error ?? run.errorMessage, 240) } : {}
33957
+ };
33958
+ }
33959
+
33813
33960
  // src/mcp/helpers.ts
33814
33961
  function stripNulls(obj) {
33815
33962
  return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== undefined && !(Array.isArray(v) && v.length === 0)));
@@ -33858,7 +34005,7 @@ var TOOL_DESCRIPTIONS = getMcpToolDescriptions();
33858
34005
  function registerDiscoveryTools(server) {
33859
34006
  server.registerTool("list_skills", {
33860
34007
  title: "List Skills",
33861
- description: "List skills with public pricing. Defaults to the clean basic profile to avoid context overflow. Set profile:'all' for the full registry. Returns {name,category,pricing} by default; detail:true for full public objects. Supports limit/offset pagination.",
34008
+ description: "List skills with public pricing. Defaults to a compact paged response from the basic profile to avoid context overflow. Set profile:'all' for the full registry, detail:true for full public objects, and use limit/offset to page.",
33862
34009
  inputSchema: {
33863
34010
  category: exports_external.string().optional(),
33864
34011
  profile: exports_external.enum(["basic", "all"]).optional(),
@@ -33870,16 +34017,20 @@ function registerDiscoveryTools(server) {
33870
34017
  const selectedProfile = profile || "basic";
33871
34018
  const skills = category ? loadRegistryProfile(selectedProfile).filter((s) => s.category === category) : loadRegistryProfile(selectedProfile);
33872
34019
  const mapped = detail ? skills.map(getPublicSkillDiscovery) : skills.map(getCompactSkillDiscovery);
33873
- if (limit !== undefined || offset !== undefined) {
33874
- const start = offset || 0;
33875
- const sliced = limit !== undefined ? mapped.slice(start, start + limit) : mapped.slice(start);
33876
- return {
33877
- content: [{ type: "text", text: JSON.stringify({ skills: sliced, total: mapped.length, offset: start, limit: limit ?? null }) }]
33878
- };
33879
- }
33880
- return {
33881
- content: [{ type: "text", text: JSON.stringify(mapped) }]
33882
- };
34020
+ const page = paginate(mapped, {
34021
+ limit: parsePageLimit(limit, DEFAULT_MCP_LIMIT, { max: 100 }),
34022
+ offset: parsePageOffset(offset)
34023
+ });
34024
+ return mcpJson({
34025
+ skills: page.items,
34026
+ total: page.total,
34027
+ offset: page.offset,
34028
+ limit: page.limit,
34029
+ nextOffset: page.nextOffset,
34030
+ hasMore: page.hasMore,
34031
+ nextArguments: page.hasMore ? { profile: selectedProfile, category, detail: Boolean(detail), limit: page.limit, offset: page.nextOffset } : null,
34032
+ detailHint: detail ? undefined : "Set detail:true for full public skill objects, or call get_skill_info for one skill."
34033
+ });
33883
34034
  });
33884
34035
  server.registerTool("list_pinned_skills", {
33885
34036
  title: "List Pinned Skills",
@@ -33896,7 +34047,7 @@ function registerDiscoveryTools(server) {
33896
34047
  });
33897
34048
  server.registerTool("search_skills", {
33898
34049
  title: "Search Skills",
33899
- description: "Search skills by name, description, or tags. Defaults to the clean basic profile; set profile:'all' for the full registry. Returns compact list with pricing by default. Supports limit/offset pagination.",
34050
+ description: "Search skills by name, description, or tags. Defaults to a compact paged response from the basic profile; set profile:'all' for the full registry and detail:true for full public objects.",
33900
34051
  inputSchema: {
33901
34052
  query: exports_external.string(),
33902
34053
  profile: exports_external.enum(["basic", "all"]).optional(),
@@ -33912,16 +34063,20 @@ function registerDiscoveryTools(server) {
33912
34063
  if (!cached2)
33913
34064
  cacheSet(cacheKey, results);
33914
34065
  const out = detail ? results.map(getPublicSkillDiscovery) : results.map(getCompactSkillDiscovery);
33915
- if (limit !== undefined || offset !== undefined) {
33916
- const start = offset || 0;
33917
- const sliced = limit !== undefined ? out.slice(start, start + limit) : out.slice(start);
33918
- return {
33919
- content: [{ type: "text", text: JSON.stringify({ skills: sliced, total: out.length, offset: start, limit: limit ?? null }) }]
33920
- };
33921
- }
33922
- return {
33923
- content: [{ type: "text", text: JSON.stringify(out) }]
33924
- };
34066
+ const page = paginate(out, {
34067
+ limit: parsePageLimit(limit, DEFAULT_MCP_LIMIT, { max: 100 }),
34068
+ offset: parsePageOffset(offset)
34069
+ });
34070
+ return mcpJson({
34071
+ skills: page.items,
34072
+ total: page.total,
34073
+ offset: page.offset,
34074
+ limit: page.limit,
34075
+ nextOffset: page.nextOffset,
34076
+ hasMore: page.hasMore,
34077
+ nextArguments: page.hasMore ? { query, profile: selectedProfile, detail: Boolean(detail), limit: page.limit, offset: page.nextOffset } : null,
34078
+ detailHint: detail ? undefined : "Set detail:true for full public skill objects, or call get_skill_info for one skill."
34079
+ });
33925
34080
  });
33926
34081
  server.registerTool("get_skill_info", {
33927
34082
  title: "Get Skill Info",
@@ -33966,12 +34121,12 @@ function registerDiscoveryTools(server) {
33966
34121
  }
33967
34122
 
33968
34123
  // src/mcp/operation-tools.ts
33969
- import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
34124
+ import { existsSync as existsSync10, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
33970
34125
  import { join as join10 } from "path";
33971
34126
 
33972
34127
  // src/lib/run-state.ts
33973
34128
  import { createHash, randomBytes } from "crypto";
33974
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync8, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
34129
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
33975
34130
  import { extname, join as join8, relative as relative2 } from "path";
33976
34131
  function createSkillRun(params, targetDir = process.cwd()) {
33977
34132
  const now = new Date;
@@ -34045,7 +34200,7 @@ function findSkillRun(runId, targetDir = process.cwd()) {
34045
34200
  const runsRoot = join8(getProjectStateDir(targetDir), "runs");
34046
34201
  if (!existsSync8(runsRoot))
34047
34202
  return null;
34048
- for (const day of readdirSync4(runsRoot)) {
34203
+ for (const day of readdirSync5(runsRoot)) {
34049
34204
  const record3 = readRunRecord(join8(runsRoot, day, runId));
34050
34205
  if (record3)
34051
34206
  return record3;
@@ -34065,7 +34220,7 @@ function collectRunArtifacts(context) {
34065
34220
  return [];
34066
34221
  const artifacts = [];
34067
34222
  for (const path of walkFiles(context.exportDir)) {
34068
- const stat = statSync3(path);
34223
+ const stat = statSync4(path);
34069
34224
  const bytes = readFileSync8(path);
34070
34225
  artifacts.push({
34071
34226
  path: toProjectRelative(context.targetDir, path),
@@ -34088,9 +34243,9 @@ function readRunRecord(runDir) {
34088
34243
  }
34089
34244
  function walkFiles(dir) {
34090
34245
  const files = [];
34091
- for (const entry of readdirSync4(dir)) {
34246
+ for (const entry of readdirSync5(dir)) {
34092
34247
  const full = join8(dir, entry);
34093
- if (statSync3(full).isDirectory())
34248
+ if (statSync4(full).isDirectory())
34094
34249
  files.push(...walkFiles(full));
34095
34250
  else
34096
34251
  files.push(full);
@@ -34362,9 +34517,10 @@ function registerOperationTools(server) {
34362
34517
  name: exports_external.string(),
34363
34518
  input: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
34364
34519
  args: exports_external.array(exports_external.string()).optional(),
34365
- approved: exports_external.boolean().optional()
34520
+ approved: exports_external.boolean().optional(),
34521
+ detail: exports_external.boolean().optional()
34366
34522
  }
34367
- }, async ({ name, input, args, approved }) => {
34523
+ }, async ({ name, input, args, approved, detail }) => {
34368
34524
  const skill = getSkill(name);
34369
34525
  if (!skill) {
34370
34526
  return mcpError("SKILL_NOT_FOUND", `Skill '${name}' not found`, findSimilarSkills(name));
@@ -34430,7 +34586,7 @@ function registerOperationTools(server) {
34430
34586
  });
34431
34587
  writeRunLogs(runContext, "", "");
34432
34588
  const remoteRunId = typeof run.id === "string" ? run.id : undefined;
34433
- return mcpJson({
34589
+ const payload2 = {
34434
34590
  contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
34435
34591
  id: run.id,
34436
34592
  localRunId: localRun2.id,
@@ -34441,7 +34597,8 @@ function registerOperationTools(server) {
34441
34597
  remoteRun: run,
34442
34598
  run: localRun2,
34443
34599
  nextActions: remoteRunNextActions(remoteRunId)
34444
- });
34600
+ };
34601
+ return mcpJson(detail ? payload2 : compactRunToolPayload(payload2, "Call run_skill again with detail:true for full remote/local run records."));
34445
34602
  } catch (err) {
34446
34603
  const error48 = `Hosted skill ${skillName} requires hosted access: ${err.message}`;
34447
34604
  writeRunLogs(runContext, "", error48 + `
@@ -34463,23 +34620,23 @@ function registerOperationTools(server) {
34463
34620
  status: result.exitCode === 0 ? "completed" : "failed",
34464
34621
  error: result.error
34465
34622
  });
34623
+ const payload = { exitCode: result.exitCode, skill: skillName, stdout: result.stdout, stderr: result.stderr, run: localRun };
34466
34624
  if (result.error) {
34467
34625
  return {
34468
- content: [{ type: "text", text: JSON.stringify({ exitCode: result.exitCode, error: result.error, run: localRun }, null, 2) }],
34626
+ content: [{ type: "text", text: JSON.stringify(detail ? { ...payload, error: result.error } : compactRunToolPayload({ ...payload, error: result.error }, "Call run_skill again with detail:true for full stdout/stderr and run metadata.")) }],
34469
34627
  isError: true
34470
34628
  };
34471
34629
  }
34472
- return {
34473
- content: [{ type: "text", text: JSON.stringify({ exitCode: result.exitCode, skill: skillName, stdout: result.stdout, stderr: result.stderr, run: localRun }, null, 2) }]
34474
- };
34630
+ return mcpJson(detail ? payload : compactRunToolPayload(payload, "Call run_skill again with detail:true for full stdout/stderr and run metadata."));
34475
34631
  });
34476
34632
  server.registerTool("get_run_status", {
34477
34633
  title: "Get Run Status",
34478
34634
  description: "Fetch remote run status. Accepts a remote run id or a local run id linked to a remote run.",
34479
34635
  inputSchema: {
34480
- run_id: exports_external.string()
34636
+ run_id: exports_external.string(),
34637
+ detail: exports_external.boolean().optional()
34481
34638
  }
34482
- }, async ({ run_id }) => {
34639
+ }, async ({ run_id, detail }) => {
34483
34640
  const { getApiKey: getApiKey2 } = await Promise.resolve().then(() => (init_auth_store(), exports_auth_store));
34484
34641
  const apiKey = getApiKey2();
34485
34642
  if (!apiKey) {
@@ -34496,12 +34653,20 @@ function registerOperationTools(server) {
34496
34653
  const run = await client.getRun(remoteRunId);
34497
34654
  if (!run)
34498
34655
  return mcpError("RUN_NOT_FOUND", `Remote run '${remoteRunId}' not found`);
34499
- return mcpJson({
34656
+ const payload = {
34500
34657
  contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
34501
34658
  runId: remoteRunId,
34502
34659
  ...localRun ? { localRunId: localRun.id } : {},
34503
34660
  run,
34504
34661
  nextActions: remoteRunNextActions(remoteRunId)
34662
+ };
34663
+ return mcpJson(detail ? payload : {
34664
+ contractVersion: payload.contractVersion,
34665
+ runId: payload.runId,
34666
+ ...localRun ? { localRunId: localRun.id } : {},
34667
+ run: compactRemoteRun(run),
34668
+ nextActions: payload.nextActions,
34669
+ detailHint: "Call get_run_status with detail:true for the complete remote run payload."
34505
34670
  });
34506
34671
  } catch (err) {
34507
34672
  return mcpError("SKILLS_MD_ERROR", err.message);
@@ -34573,9 +34738,9 @@ function registerOperationTools(server) {
34573
34738
  let skillCount = 0;
34574
34739
  if (exists) {
34575
34740
  try {
34576
- skillCount = readdirSync5(agentSkillsPath).filter((f) => {
34741
+ skillCount = readdirSync6(agentSkillsPath).filter((f) => {
34577
34742
  const full = join10(agentSkillsPath, f);
34578
- return !f.startsWith(".") && statSync4(full).isDirectory();
34743
+ return !f.startsWith(".") && statSync5(full).isDirectory();
34579
34744
  }).length;
34580
34745
  } catch {}
34581
34746
  }
@@ -34593,6 +34758,32 @@ function registerOperationTools(server) {
34593
34758
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
34594
34759
  });
34595
34760
  }
34761
+ function compactRunToolPayload(payload, detailHint) {
34762
+ const stdout = previewText(payload.stdout ?? "");
34763
+ const stderr = previewText(payload.stderr ?? "");
34764
+ return {
34765
+ ...payload.contractVersion !== undefined ? { contractVersion: payload.contractVersion } : {},
34766
+ ...payload.id !== undefined ? { id: payload.id } : {},
34767
+ ...payload.localRunId !== undefined ? { localRunId: payload.localRunId } : {},
34768
+ ...payload.exitCode !== undefined ? { exitCode: payload.exitCode } : {},
34769
+ skill: payload.skill,
34770
+ ...payload.status !== undefined ? { status: payload.status } : {},
34771
+ ...payload.remote !== undefined ? { remote: payload.remote } : {},
34772
+ ...payload.correlationId !== undefined ? { correlationId: payload.correlationId } : {},
34773
+ ...payload.pricing !== undefined ? { pricing: payload.pricing } : {},
34774
+ ...payload.error !== undefined ? { error: payload.error } : {},
34775
+ ...payload.remoteRun !== undefined ? { remoteRun: compactRemoteRun(payload.remoteRun) } : {},
34776
+ run: compactRunRecord(payload.run),
34777
+ stdoutPreview: stdout,
34778
+ stderrPreview: stderr,
34779
+ stdoutChars: stdout.length,
34780
+ stderrChars: stderr.length,
34781
+ stdoutTruncated: stdout.truncated,
34782
+ stderrTruncated: stderr.truncated,
34783
+ ...payload.nextActions !== undefined ? { nextActions: payload.nextActions } : {},
34784
+ detailHint
34785
+ };
34786
+ }
34596
34787
 
34597
34788
  // src/lib/feedback.ts
34598
34789
  import { existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
@@ -34951,11 +35142,37 @@ function registerScheduleTools(server) {
34951
35142
  });
34952
35143
  server.registerTool("list_schedules", {
34953
35144
  title: "List Schedules",
34954
- description: "List all scheduled skill runs.",
34955
- inputSchema: {}
34956
- }, async () => {
35145
+ description: "List scheduled skill runs as a compact paged response. Use limit/offset for pagination.",
35146
+ inputSchema: {
35147
+ limit: exports_external.number().optional(),
35148
+ offset: exports_external.number().optional()
35149
+ }
35150
+ }, async ({ limit, offset }) => {
34957
35151
  const schedules = listSchedules();
34958
- return { content: [{ type: "text", text: JSON.stringify(schedules, null, 2) }] };
35152
+ const page = paginate(schedules, {
35153
+ limit: parsePageLimit(limit, DEFAULT_MCP_LIMIT, { max: 100 }),
35154
+ offset: parsePageOffset(offset)
35155
+ });
35156
+ return mcpJson({
35157
+ schedules: page.items.map((schedule) => ({
35158
+ id: schedule.id,
35159
+ name: schedule.name,
35160
+ skill: schedule.skill,
35161
+ cron: schedule.cron,
35162
+ enabled: schedule.enabled,
35163
+ lastRun: schedule.lastRun,
35164
+ lastRunStatus: schedule.lastRunStatus,
35165
+ nextRun: schedule.nextRun,
35166
+ argCount: schedule.args?.length ?? 0
35167
+ })),
35168
+ total: page.total,
35169
+ offset: page.offset,
35170
+ limit: page.limit,
35171
+ nextOffset: page.nextOffset,
35172
+ hasMore: page.hasMore,
35173
+ nextArguments: page.hasMore ? { limit: page.limit, offset: page.nextOffset } : null,
35174
+ detailHint: "Use schedule state files or future schedule detail commands for complete schedule records."
35175
+ });
34959
35176
  });
34960
35177
  server.registerTool("remove_schedule", {
34961
35178
  title: "Remove Schedule",
@@ -35011,8 +35228,8 @@ import {
35011
35228
  existsSync as existsSync13,
35012
35229
  mkdirSync as mkdirSync8,
35013
35230
  readFileSync as readFileSync11,
35014
- readdirSync as readdirSync6,
35015
- statSync as statSync5,
35231
+ readdirSync as readdirSync7,
35232
+ statSync as statSync6,
35016
35233
  writeFileSync as writeFileSync7
35017
35234
  } from "fs";
35018
35235
  import { dirname as dirname5, join as join13, normalize as normalize3, relative as relative3, sep } from "path";
@@ -35206,9 +35423,9 @@ function parsePositiveInteger(value) {
35206
35423
  }
35207
35424
  function walkFiles2(dir) {
35208
35425
  const files = [];
35209
- for (const entry of readdirSync6(dir)) {
35426
+ for (const entry of readdirSync7(dir)) {
35210
35427
  const full = join13(dir, entry);
35211
- const stats = statSync5(full);
35428
+ const stats = statSync6(full);
35212
35429
  if (stats.isDirectory())
35213
35430
  files.push(...walkFiles2(full));
35214
35431
  else