@skillrecordings/cli 0.17.0 → 0.18.0

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
@@ -56,8 +56,10 @@ import "./chunk-F4EM72IH.js";
56
56
  import {
57
57
  addShellIntegration,
58
58
  autoBootstrapKeychain,
59
+ getFromKeychain,
59
60
  getKeychainStatus,
60
61
  isKeychainSupported,
62
+ isOpCliAvailable,
61
63
  storeInKeychain
62
64
  } from "./chunk-7SQU7KCI.js";
63
65
  import {
@@ -74618,9 +74620,9 @@ function parseEnvContent(content) {
74618
74620
  return env;
74619
74621
  }
74620
74622
  async function decryptEnvFile(encryptedPath) {
74621
- const { existsSync: existsSync15 } = await import("fs");
74623
+ const { existsSync: existsSync17 } = await import("fs");
74622
74624
  const { readFile: readFile10 } = await import("fs/promises");
74623
- if (!existsSync15(encryptedPath)) {
74625
+ if (!existsSync17(encryptedPath)) {
74624
74626
  return {};
74625
74627
  }
74626
74628
  const ageKey = await getAgeKeyFrom1Password();
@@ -74641,8 +74643,8 @@ async function getAgeKeyFrom1Password() {
74641
74643
  return process.env.SKILL_AGE_KEY;
74642
74644
  }
74643
74645
  try {
74644
- const { getFromKeychain, storeInKeychain: storeInKeychain2, autoBootstrapKeychain: autoBootstrapKeychain2 } = await import("./keychain-IEZHT5WN.js");
74645
- const fromKeychain = getFromKeychain("age-private-key");
74646
+ const { getFromKeychain: getFromKeychain2, storeInKeychain: storeInKeychain2, autoBootstrapKeychain: autoBootstrapKeychain2 } = await import("./keychain-IEZHT5WN.js");
74647
+ const fromKeychain = getFromKeychain2("age-private-key");
74646
74648
  if (fromKeychain) return fromKeychain;
74647
74649
  let opToken = process.env.OP_SERVICE_ACCOUNT_TOKEN;
74648
74650
  if (!opToken) {
@@ -74750,7 +74752,7 @@ var ROOT_DESCRIPTIONS = {
74750
74752
  minimal: "Skill Recordings support agent CLI. Try: skill wizard, skill keys, skill front inbox, skill front triage. Use --help for details."
74751
74753
  };
74752
74754
  var FRONT_DESCRIPTIONS = {
74753
- full: "Front conversations, inboxes, tags, archival, and reporting.\n\n Start here:\n skill front inbox See unassigned conversations\n skill front inbox support List conversations in a specific inbox\n skill front triage AI-powered categorization of inbox items\n\n Investigate a conversation:\n skill front conversation <id> -m Full conversation with messages\n skill front message <id> Single message details + body\n\n Take action:\n skill front assign <id> Assign to a teammate\n skill front reply <id> Draft a reply (HITL, never auto-sends)\n skill front tag <id> Add a tag\n skill front archive <id> Archive a resolved conversation\n\n Bulk operations:\n skill front bulk-archive Archive old/spam conversations\n skill front report Volume + tag + sender forensics\n\n All commands accept --json for HATEOAS-enriched output with _links and _actions.",
74755
+ full: "Front conversations, inboxes, tags, archival, and reporting.\n\n Start here:\n skill front inbox See unassigned conversations\n skill front inbox support List conversations in a specific inbox\n skill front triage AI-powered categorization of inbox items\n\n Investigate a conversation:\n skill front conversation <id> -m Full conversation with messages\n skill front message <id> Single message details + body\n\n Take action:\n skill front assign <id> Assign to a teammate\n skill front reply <id> Draft a reply (HITL, never auto-sends)\n skill front tag <id> Add a tag\n skill front archive <id> Archive a resolved conversation\n\n Bulk operations:\n skill front bulk-archive Archive old/spam conversations\n skill front report Volume + tag + sender forensics\n\n All commands accept --json for HATEOAS-enriched output with _links and _actions.\n\n Environment: FRONT_API_TOKEN required. Run `skill doctor` to check.",
74754
74756
  abbreviated: "Front API workflows for inbox triage and conversation actions.\n\n Common:\n skill front inbox\n skill front triage\n skill front conversation <id> -m\n skill front reply <id>\n skill front archive <id>",
74755
74757
  minimal: "Front API commands (inbox, triage, assign, reply, archive)."
74756
74758
  };
@@ -74760,7 +74762,7 @@ var AUTH_DESCRIPTIONS = {
74760
74762
  minimal: "Auth status commands (auth status, auth whoami)."
74761
74763
  };
74762
74764
  var INNGEST_DESCRIPTIONS = {
74763
- full: "Inngest event and workflow commands.\n\n Debug pipeline runs:\n skill inngest runs --status failed --after 1h Recent failures\n skill inngest events --after 12h Recent events\n skill inngest investigate <run-id> Deep-dive a specific run",
74765
+ full: "Inngest event and workflow commands.\n\n Debug pipeline runs:\n skill inngest runs --status failed --after 1h Recent failures\n skill inngest events --after 12h Recent events\n skill inngest investigate <run-id> Deep-dive a specific run\n\n Environment: INNGEST_SIGNING_KEY required. Run `skill doctor` to check.",
74764
74766
  abbreviated: "Inngest events and workflow runs.\n\n Common:\n skill inngest runs --status failed --after 1h\n skill inngest events --after 12h\n skill inngest investigate <run-id>",
74765
74767
  minimal: "Inngest events and runs debugging."
74766
74768
  };
@@ -76836,7 +76838,9 @@ async function listApprovals(ctx, options) {
76836
76838
  }
76837
76839
  }
76838
76840
  function registerAxiomCommands(program3) {
76839
- const axiom = program3.command("axiom").description("Query Axiom logs and traces");
76841
+ const axiom = program3.command("axiom").description(
76842
+ "Query Axiom logs and traces.\n Environment: AXIOM_TOKEN required. Run `skill doctor` to check."
76843
+ );
76840
76844
  axiom.command("query").description("Run a raw APL query").argument("<apl>", "APL query string").option("-s, --since <time>", "Time range (e.g., 1h, 24h, 7d)", "24h").option("--json", "Output as JSON").action(async (apl, options, command) => {
76841
76845
  const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
76842
76846
  ...command.parent?.opts(),
@@ -77074,11 +77078,11 @@ Saved to ${options.output}`);
77074
77078
  }
77075
77079
  }
77076
77080
  async function toEvalite(options) {
77077
- const { readFileSync: readFileSync13 } = await import("fs");
77081
+ const { readFileSync: readFileSync14 } = await import("fs");
77078
77082
  const { ctx } = options;
77079
77083
  const outputJson = ctx.format === "json";
77080
77084
  const data2 = JSON.parse(
77081
- readFileSync13(options.input, "utf-8")
77085
+ readFileSync14(options.input, "utf-8")
77082
77086
  );
77083
77087
  const evaliteData = data2.map((d) => ({
77084
77088
  input: d.triggerMessage.body,
@@ -77882,6 +77886,170 @@ function registerDeployCommands(program3) {
77882
77886
  });
77883
77887
  }
77884
77888
 
77889
+ // src/commands/doctor.ts
77890
+ init_esm_shims();
77891
+ import { execSync as execSync3 } from "child_process";
77892
+ import { existsSync as existsSync6 } from "fs";
77893
+ import { join as join3 } from "path";
77894
+ var REQUIRED_ENV_VARS = [
77895
+ "DATABASE_URL",
77896
+ "INNGEST_SIGNING_KEY",
77897
+ "INNGEST_EVENT_KEY",
77898
+ "FRONT_API_TOKEN",
77899
+ "LINEAR_API_KEY",
77900
+ "AXIOM_TOKEN",
77901
+ "SLACK_BOT_TOKEN",
77902
+ "UPSTASH_REDIS_REST_URL",
77903
+ "UPSTASH_VECTOR_REST_URL"
77904
+ ];
77905
+ function checkEnvVars() {
77906
+ return REQUIRED_ENV_VARS.map((varName) => {
77907
+ const value = process.env[varName];
77908
+ return {
77909
+ name: varName,
77910
+ status: value ? "ok" : "warn",
77911
+ message: value ? void 0 : "Not set"
77912
+ };
77913
+ });
77914
+ }
77915
+ function checkKeychain() {
77916
+ const checks = [];
77917
+ const opAvailable = isOpCliAvailable();
77918
+ checks.push({
77919
+ name: "op CLI",
77920
+ status: opAvailable ? "ok" : "warn",
77921
+ message: opAvailable ? void 0 : "Not installed or not authenticated"
77922
+ });
77923
+ const opToken = getFromKeychain("op-service-account-token");
77924
+ checks.push({
77925
+ name: "op-service-account-token",
77926
+ status: opToken ? "ok" : "warn",
77927
+ message: opToken ? void 0 : "Not found in keychain"
77928
+ });
77929
+ const ageKey = getFromKeychain("age-private-key");
77930
+ checks.push({
77931
+ name: "age-private-key",
77932
+ status: ageKey ? "ok" : "warn",
77933
+ message: ageKey ? void 0 : "Not found in keychain"
77934
+ });
77935
+ return checks;
77936
+ }
77937
+ function checkTools() {
77938
+ const checks = [];
77939
+ try {
77940
+ execSync3("gh auth status", { stdio: "pipe", timeout: 3e3 });
77941
+ checks.push({
77942
+ name: "gh CLI",
77943
+ status: "ok"
77944
+ });
77945
+ } catch {
77946
+ checks.push({
77947
+ name: "gh CLI",
77948
+ status: "warn",
77949
+ message: "Not installed or not authenticated"
77950
+ });
77951
+ }
77952
+ return checks;
77953
+ }
77954
+ function checkWorkspace() {
77955
+ const checks = [];
77956
+ const hivePath = join3(process.cwd(), ".hive");
77957
+ const hiveExists = existsSync6(hivePath);
77958
+ checks.push({
77959
+ name: ".hive directory",
77960
+ status: hiveExists ? "ok" : "warn",
77961
+ message: hiveExists ? void 0 : "Not found in current directory"
77962
+ });
77963
+ return checks;
77964
+ }
77965
+ function runHealthChecks() {
77966
+ const categories = [
77967
+ {
77968
+ category: "Environment",
77969
+ checks: checkEnvVars()
77970
+ },
77971
+ {
77972
+ category: "Keychain",
77973
+ checks: checkKeychain()
77974
+ },
77975
+ {
77976
+ category: "Tools",
77977
+ checks: checkTools()
77978
+ },
77979
+ {
77980
+ category: "Workspace",
77981
+ checks: checkWorkspace()
77982
+ }
77983
+ ];
77984
+ let total = 0;
77985
+ let ok = 0;
77986
+ let warn = 0;
77987
+ let fail = 0;
77988
+ for (const category of categories) {
77989
+ for (const check of category.checks) {
77990
+ total++;
77991
+ if (check.status === "ok") ok++;
77992
+ else if (check.status === "warn") warn++;
77993
+ else fail++;
77994
+ }
77995
+ }
77996
+ let status;
77997
+ if (fail > 0) {
77998
+ status = "unhealthy";
77999
+ } else if (warn > 3) {
78000
+ status = "degraded";
78001
+ } else {
78002
+ status = "healthy";
78003
+ }
78004
+ return {
78005
+ status,
78006
+ categories,
78007
+ summary: { total, ok, warn, fail }
78008
+ };
78009
+ }
78010
+ function formatTextOutput(results) {
78011
+ console.log("\n\u{1FA7A} Health Check Results\n");
78012
+ for (const category of results.categories) {
78013
+ console.log(`${category.category}:`);
78014
+ for (const check of category.checks) {
78015
+ const symbol = check.status === "ok" ? "\u2713" : check.status === "warn" ? "\u26A0" : "\u2717";
78016
+ const line = check.message ? ` ${symbol} ${check.name} - ${check.message}` : ` ${symbol} ${check.name}`;
78017
+ console.log(line);
78018
+ }
78019
+ console.log("");
78020
+ }
78021
+ console.log("\u2500".repeat(60));
78022
+ console.log(
78023
+ `Summary: ${results.summary.ok}/${results.summary.total} checks passed`
78024
+ );
78025
+ if (results.summary.warn > 0) {
78026
+ console.log(`\u26A0 ${results.summary.warn} warnings`);
78027
+ }
78028
+ if (results.summary.fail > 0) {
78029
+ console.log(`\u2717 ${results.summary.fail} failures`);
78030
+ }
78031
+ console.log(`
78032
+ Overall status: ${results.status.toUpperCase()}`);
78033
+ console.log("");
78034
+ }
78035
+ function registerDoctorCommand(program3) {
78036
+ program3.command("doctor").description(
78037
+ "Run health checks on environment and tools\n\n Checks:\n - Environment variables\n - Keychain secrets\n - CLI tools (gh, op)\n - Workspace setup (.hive)\n\n Examples:\n skill doctor\n skill doctor --json"
78038
+ ).option("--json", "Output as JSON").action(async (options, command) => {
78039
+ const ctx = await createContext({
78040
+ format: options.json ? "json" : "text",
78041
+ verbose: command.optsWithGlobals().verbose,
78042
+ quiet: command.optsWithGlobals().quiet
78043
+ });
78044
+ const results = runHealthChecks();
78045
+ if (options.json || ctx.format === "json") {
78046
+ ctx.output.data(results);
78047
+ } else {
78048
+ formatTextOutput(results);
78049
+ }
78050
+ });
78051
+ }
78052
+
77885
78053
  // src/commands/eval.ts
77886
78054
  init_esm_shims();
77887
78055
  import { access, readFile as readFile2 } from "fs/promises";
@@ -79889,12 +80057,12 @@ async function scoreProduction(ctx, options) {
79889
80057
 
79890
80058
  // src/commands/eval-local/seed.ts
79891
80059
  init_esm_shims();
79892
- import { join as join4 } from "path";
80060
+ import { join as join5 } from "path";
79893
80061
  import { glob as glob3 } from "glob";
79894
80062
 
79895
80063
  // src/lib/eval-seed.ts
79896
80064
  init_esm_shims();
79897
- import { join as join3 } from "path";
80065
+ import { join as join4 } from "path";
79898
80066
  import { readFile as readFile5, readdir } from "fs/promises";
79899
80067
  import { glob as glob2 } from "glob";
79900
80068
  import matter from "gray-matter";
@@ -79940,7 +80108,7 @@ async function loadJsonFiles(dirPath) {
79940
80108
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
79941
80109
  const items = await Promise.all(
79942
80110
  jsonFiles.map(async (file) => {
79943
- const content = await readFile5(join3(dirPath, file), "utf-8");
80111
+ const content = await readFile5(join4(dirPath, file), "utf-8");
79944
80112
  return JSON.parse(content);
79945
80113
  })
79946
80114
  );
@@ -79950,7 +80118,7 @@ async function loadJsonFiles(dirPath) {
79950
80118
  }
79951
80119
  }
79952
80120
  async function loadKnowledgeFiles(basePath) {
79953
- const files = await glob2(join3(basePath, "**/*.md"));
80121
+ const files = await glob2(join4(basePath, "**/*.md"));
79954
80122
  const docs = [];
79955
80123
  for (const filePath of files) {
79956
80124
  const content = await readFile5(filePath, "utf-8");
@@ -80140,16 +80308,16 @@ async function seed(ctx, options) {
80140
80308
  await cleanQdrant();
80141
80309
  }
80142
80310
  log("\u{1F4E6} Seeding apps...");
80143
- const apps = await loadJsonFiles(join4(fixturesPath, "apps"));
80311
+ const apps = await loadJsonFiles(join5(fixturesPath, "apps"));
80144
80312
  result.apps = await seedApps(connection, apps);
80145
80313
  log("\u{1F465} Loading customer fixtures...");
80146
- const customers = await loadJsonFiles(join4(fixturesPath, "customers"));
80314
+ const customers = await loadJsonFiles(join5(fixturesPath, "customers"));
80147
80315
  result.customers = customers.length;
80148
80316
  log("\u{1F4DA} Seeding knowledge base...");
80149
- const knowledge = await loadKnowledgeFiles(join4(fixturesPath, "knowledge"));
80317
+ const knowledge = await loadKnowledgeFiles(join5(fixturesPath, "knowledge"));
80150
80318
  result.knowledge = knowledge.length;
80151
80319
  result.embeddings = await seedKnowledgeBase(knowledge, !outputJson);
80152
- const scenarioFiles = await glob3(join4(fixturesPath, "scenarios/**/*.json"));
80320
+ const scenarioFiles = await glob3(join5(fixturesPath, "scenarios/**/*.json"));
80153
80321
  result.scenarios = scenarioFiles.length;
80154
80322
  await connection.end();
80155
80323
  if (outputJson) {
@@ -80225,8 +80393,8 @@ init_esm_shims();
80225
80393
  // src/commands/eval-pipeline/run.ts
80226
80394
  init_esm_shims();
80227
80395
  import { createHash as createHash2 } from "crypto";
80228
- import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync4 } from "fs";
80229
- import { join as join5 } from "path";
80396
+ import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync4 } from "fs";
80397
+ import { join as join6 } from "path";
80230
80398
  import { readFile as readFile6 } from "fs/promises";
80231
80399
  import { glob as glob4 } from "glob";
80232
80400
 
@@ -80639,11 +80807,11 @@ function getCacheKey(scenarioId, classifySourceHash) {
80639
80807
  function getClassifySourceHash() {
80640
80808
  try {
80641
80809
  const possiblePaths = [
80642
- join5(process.cwd(), "packages/core/src/pipeline/classify.ts"),
80643
- join5(process.cwd(), "../core/src/pipeline/classify.ts")
80810
+ join6(process.cwd(), "packages/core/src/pipeline/classify.ts"),
80811
+ join6(process.cwd(), "../core/src/pipeline/classify.ts")
80644
80812
  ];
80645
80813
  for (const path of possiblePaths) {
80646
- if (existsSync6(path)) {
80814
+ if (existsSync7(path)) {
80647
80815
  const content = readFileSync5(path, "utf-8");
80648
80816
  return createHash2("md5").update(content).digest("hex");
80649
80817
  }
@@ -80653,9 +80821,9 @@ function getClassifySourceHash() {
80653
80821
  return createHash2("md5").update(Math.floor(Date.now() / 3e5).toString()).digest("hex");
80654
80822
  }
80655
80823
  function loadCachedClassify(cacheKey) {
80656
- const cachePath = join5(CACHE_DIR, `${cacheKey}.json`);
80824
+ const cachePath = join6(CACHE_DIR, `${cacheKey}.json`);
80657
80825
  try {
80658
- if (existsSync6(cachePath)) {
80826
+ if (existsSync7(cachePath)) {
80659
80827
  return JSON.parse(readFileSync5(cachePath, "utf-8"));
80660
80828
  }
80661
80829
  } catch {
@@ -80664,17 +80832,17 @@ function loadCachedClassify(cacheKey) {
80664
80832
  }
80665
80833
  function saveCachedClassify(cacheKey, result) {
80666
80834
  try {
80667
- if (!existsSync6(CACHE_DIR)) {
80835
+ if (!existsSync7(CACHE_DIR)) {
80668
80836
  mkdirSync(CACHE_DIR, { recursive: true });
80669
80837
  }
80670
- const cachePath = join5(CACHE_DIR, `${cacheKey}.json`);
80838
+ const cachePath = join6(CACHE_DIR, `${cacheKey}.json`);
80671
80839
  writeFileSync4(cachePath, JSON.stringify(result));
80672
80840
  } catch {
80673
80841
  }
80674
80842
  }
80675
80843
  function clearClassifyCache() {
80676
80844
  try {
80677
- if (existsSync6(CACHE_DIR)) {
80845
+ if (existsSync7(CACHE_DIR)) {
80678
80846
  rmSync(CACHE_DIR, { recursive: true, force: true });
80679
80847
  }
80680
80848
  } catch {
@@ -81550,7 +81718,7 @@ Latency: ${avgLatency.toFixed(0)}ms avg`);
81550
81718
 
81551
81719
  // src/commands/eval-pipeline/seed.ts
81552
81720
  init_esm_shims();
81553
- import { join as join6 } from "path";
81721
+ import { join as join7 } from "path";
81554
81722
  import { glob as glob5 } from "glob";
81555
81723
  async function seed2(ctx, options) {
81556
81724
  const fixturesPath = options.fixtures || "fixtures";
@@ -81581,20 +81749,20 @@ async function seed2(ctx, options) {
81581
81749
  await cleanQdrant();
81582
81750
  }
81583
81751
  if (!outputJson) ctx.output.message("\u{1F4E6} Seeding apps...");
81584
- const apps = await loadJsonFiles(join6(fixturesPath, "apps"));
81752
+ const apps = await loadJsonFiles(join7(fixturesPath, "apps"));
81585
81753
  result.apps = await seedApps(connection, apps);
81586
81754
  const [trustRows] = await connection.execute(
81587
81755
  "SELECT COUNT(*) as count FROM SUPPORT_trust_scores"
81588
81756
  );
81589
81757
  result.trustScores = trustRows[0].count;
81590
81758
  if (!outputJson) ctx.output.message("\u{1F465} Loading customer fixtures...");
81591
- const customers = await loadJsonFiles(join6(fixturesPath, "customers"));
81759
+ const customers = await loadJsonFiles(join7(fixturesPath, "customers"));
81592
81760
  result.customers = customers.length;
81593
81761
  if (!outputJson) ctx.output.message("\u{1F4DA} Seeding knowledge base...");
81594
- const knowledge = await loadKnowledgeFiles(join6(fixturesPath, "knowledge"));
81762
+ const knowledge = await loadKnowledgeFiles(join7(fixturesPath, "knowledge"));
81595
81763
  result.knowledge = knowledge.length;
81596
81764
  result.embeddings = await seedKnowledgeBase(knowledge, !outputJson);
81597
- const scenarioFiles = await glob5(join6(fixturesPath, "scenarios/**/*.json"));
81765
+ const scenarioFiles = await glob5(join7(fixturesPath, "scenarios/**/*.json"));
81598
81766
  result.scenarios = scenarioFiles.length;
81599
81767
  await connection.end();
81600
81768
  if (outputJson) {
@@ -81666,7 +81834,7 @@ function registerEvalPipelineCommands(program3) {
81666
81834
 
81667
81835
  // src/commands/eval-prompt.ts
81668
81836
  init_esm_shims();
81669
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
81837
+ import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
81670
81838
  import { generateText as generateText2, stepCountIs as stepCountIs2, tool as tool4 } from "ai";
81671
81839
  import { z as z5 } from "zod";
81672
81840
  var leakPatterns = [
@@ -81853,7 +82021,7 @@ async function runEval2(ctx, options) {
81853
82021
  try {
81854
82022
  let prompt = SUPPORT_AGENT_PROMPT;
81855
82023
  if (promptPath) {
81856
- if (!existsSync7(promptPath)) {
82024
+ if (!existsSync8(promptPath)) {
81857
82025
  throw new CLIError({
81858
82026
  userMessage: `Prompt file not found: ${promptPath}.`,
81859
82027
  suggestion: "Verify the prompt path and try again."
@@ -81866,7 +82034,7 @@ async function runEval2(ctx, options) {
81866
82034
  } else if (!outputJson) {
81867
82035
  ctx.output.message("Using production prompt");
81868
82036
  }
81869
- if (!existsSync7(datasetPath)) {
82037
+ if (!existsSync8(datasetPath)) {
81870
82038
  throw new CLIError({
81871
82039
  userMessage: `Dataset not found: ${datasetPath}.`,
81872
82040
  suggestion: "Provide a valid dataset file path."
@@ -81978,7 +82146,7 @@ async function comparePrompts(ctx, options) {
81978
82146
  try {
81979
82147
  const baselinePrompt = baseline ? readFileSync6(baseline, "utf-8") : SUPPORT_AGENT_PROMPT;
81980
82148
  const candidatePrompt = readFileSync6(candidate, "utf-8");
81981
- if (!existsSync7(datasetPath)) {
82149
+ if (!existsSync8(datasetPath)) {
81982
82150
  throw new CLIError({
81983
82151
  userMessage: `Dataset not found: ${datasetPath}.`,
81984
82152
  suggestion: "Provide a valid dataset file path."
@@ -82126,20 +82294,20 @@ init_esm_shims();
82126
82294
 
82127
82295
  // src/commands/faq/classify.ts
82128
82296
  init_esm_shims();
82129
- import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync7 } from "fs";
82130
- import { dirname as dirname2, join as join7, resolve as resolve3 } from "path";
82297
+ import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync7 } from "fs";
82298
+ import { dirname as dirname2, join as join8, resolve as resolve3 } from "path";
82131
82299
  import { generateObject } from "ai";
82132
82300
  import { z as z6 } from "zod";
82133
82301
  var PROJECT_ROOT = resolve3(__dirname, "../../../..");
82134
- var DEFAULT_PARQUET_PATH = join7(
82302
+ var DEFAULT_PARQUET_PATH = join8(
82135
82303
  PROJECT_ROOT,
82136
82304
  "artifacts/phase-0/embeddings/v2/conversations.parquet"
82137
82305
  );
82138
- var DEFAULT_TAXONOMY_PATH = join7(
82306
+ var DEFAULT_TAXONOMY_PATH = join8(
82139
82307
  PROJECT_ROOT,
82140
82308
  "artifacts/phase-1/llm-topics/taxonomy.json"
82141
82309
  );
82142
- var DEFAULT_OUTPUT_PATH = join7(
82310
+ var DEFAULT_OUTPUT_PATH = join8(
82143
82311
  PROJECT_ROOT,
82144
82312
  "artifacts/phase-1/llm-topics/classifications.jsonl"
82145
82313
  );
@@ -82173,7 +82341,7 @@ function createProgressReporter(ctx, total) {
82173
82341
  };
82174
82342
  }
82175
82343
  async function loadConversationsFromParquet(parquetPath) {
82176
- const { execSync: execSync3 } = await import("child_process");
82344
+ const { execSync: execSync4 } = await import("child_process");
82177
82345
  const query = `
82178
82346
  SELECT
82179
82347
  conversation_id,
@@ -82184,7 +82352,7 @@ async function loadConversationsFromParquet(parquetPath) {
82184
82352
  WHERE first_message IS NOT NULL
82185
82353
  ORDER BY conversation_id
82186
82354
  `;
82187
- const result = execSync3(`duckdb -json -c "${query.replace(/"/g, '\\"')}"`, {
82355
+ const result = execSync4(`duckdb -json -c "${query.replace(/"/g, '\\"')}"`, {
82188
82356
  encoding: "utf-8",
82189
82357
  maxBuffer: 100 * 1024 * 1024
82190
82358
  // 100MB buffer for large datasets
@@ -82194,7 +82362,7 @@ async function loadConversationsFromParquet(parquetPath) {
82194
82362
  }
82195
82363
  function loadExistingClassifications(outputPath) {
82196
82364
  const classifiedIds = /* @__PURE__ */ new Set();
82197
- if (!existsSync8(outputPath)) {
82365
+ if (!existsSync9(outputPath)) {
82198
82366
  return classifiedIds;
82199
82367
  }
82200
82368
  const content = readFileSync7(outputPath, "utf-8");
@@ -82312,7 +82480,7 @@ async function faqClassify(ctx, options) {
82312
82480
  ctx.output.data(` Dry run: ${options.dryRun ?? false}`);
82313
82481
  ctx.output.data("");
82314
82482
  }
82315
- if (!existsSync8(parquetPath)) {
82483
+ if (!existsSync9(parquetPath)) {
82316
82484
  handleFaqClassifyError(
82317
82485
  ctx,
82318
82486
  new CLIError({
@@ -82323,7 +82491,7 @@ async function faqClassify(ctx, options) {
82323
82491
  );
82324
82492
  return;
82325
82493
  }
82326
- if (!existsSync8(taxonomyPath)) {
82494
+ if (!existsSync9(taxonomyPath)) {
82327
82495
  handleFaqClassifyError(
82328
82496
  ctx,
82329
82497
  new CLIError({
@@ -82335,7 +82503,7 @@ async function faqClassify(ctx, options) {
82335
82503
  return;
82336
82504
  }
82337
82505
  const outputDir = dirname2(outputPath);
82338
- if (!existsSync8(outputDir)) {
82506
+ if (!existsSync9(outputDir)) {
82339
82507
  mkdirSync2(outputDir, { recursive: true });
82340
82508
  }
82341
82509
  if (!outputJson) ctx.output.data("\u{1F4DA} Loading taxonomy...");
@@ -82504,18 +82672,18 @@ function registerFaqClassifyCommands(program3) {
82504
82672
 
82505
82673
  // src/commands/faq/cluster.ts
82506
82674
  init_esm_shims();
82507
- import { existsSync as existsSync10 } from "fs";
82508
- import { join as join9, resolve as resolve4 } from "path";
82675
+ import { existsSync as existsSync11 } from "fs";
82676
+ import { join as join10, resolve as resolve4 } from "path";
82509
82677
 
82510
82678
  // ../core/src/faq/production-clusterer.ts
82511
82679
  init_esm_shims();
82512
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
82513
- import { join as join8 } from "path";
82680
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
82681
+ import { join as join9 } from "path";
82514
82682
  function readPhase0Assignments(phase0Path) {
82515
- const assignmentsPath = join8(phase0Path, "clusters/v1/assignments.json");
82516
- if (!existsSync9(assignmentsPath)) {
82517
- const latestPath = join8(phase0Path, "clusters/latest/assignments.json");
82518
- if (!existsSync9(latestPath)) {
82683
+ const assignmentsPath = join9(phase0Path, "clusters/v1/assignments.json");
82684
+ if (!existsSync10(assignmentsPath)) {
82685
+ const latestPath = join9(phase0Path, "clusters/latest/assignments.json");
82686
+ if (!existsSync10(latestPath)) {
82519
82687
  throw new Error(`Phase 0 assignments not found at ${assignmentsPath}`);
82520
82688
  }
82521
82689
  const content2 = readFileSync8(latestPath, "utf-8");
@@ -82525,10 +82693,10 @@ function readPhase0Assignments(phase0Path) {
82525
82693
  return JSON.parse(content);
82526
82694
  }
82527
82695
  function readPhase0Labels(phase0Path) {
82528
- const labelsPath = join8(phase0Path, "clusters/v1/labels.json");
82529
- if (!existsSync9(labelsPath)) {
82530
- const latestPath = join8(phase0Path, "clusters/latest/labels.json");
82531
- if (!existsSync9(latestPath)) {
82696
+ const labelsPath = join9(phase0Path, "clusters/v1/labels.json");
82697
+ if (!existsSync10(labelsPath)) {
82698
+ const latestPath = join9(phase0Path, "clusters/latest/labels.json");
82699
+ if (!existsSync10(latestPath)) {
82532
82700
  throw new Error(`Phase 0 labels not found at ${labelsPath}`);
82533
82701
  }
82534
82702
  const content2 = readFileSync8(latestPath, "utf-8");
@@ -82540,10 +82708,10 @@ function readPhase0Labels(phase0Path) {
82540
82708
  return parsed.clusters || [];
82541
82709
  }
82542
82710
  function readPhase0Metrics(phase0Path) {
82543
- const metricsPath = join8(phase0Path, "clusters/v1/metrics.json");
82544
- if (!existsSync9(metricsPath)) {
82545
- const latestPath = join8(phase0Path, "clusters/latest/metrics.json");
82546
- if (!existsSync9(latestPath)) {
82711
+ const metricsPath = join9(phase0Path, "clusters/v1/metrics.json");
82712
+ if (!existsSync10(metricsPath)) {
82713
+ const latestPath = join9(phase0Path, "clusters/latest/metrics.json");
82714
+ if (!existsSync10(latestPath)) {
82547
82715
  throw new Error(`Phase 0 metrics not found at ${metricsPath}`);
82548
82716
  }
82549
82717
  return JSON.parse(readFileSync8(latestPath, "utf-8"));
@@ -82663,17 +82831,17 @@ async function generateProductionClustering(options) {
82663
82831
  return result;
82664
82832
  }
82665
82833
  function writeProductionArtifacts(result, outputPath) {
82666
- const versionPath = join8(outputPath, result.version);
82667
- if (!existsSync9(versionPath)) {
82834
+ const versionPath = join9(outputPath, result.version);
82835
+ if (!existsSync10(versionPath)) {
82668
82836
  mkdirSync3(versionPath, { recursive: true });
82669
82837
  }
82670
- const resultPath = join8(versionPath, "clustering-result.json");
82838
+ const resultPath = join9(versionPath, "clustering-result.json");
82671
82839
  writeFileSync6(resultPath, JSON.stringify(result, null, 2));
82672
82840
  console.log(`\u2705 Written: ${resultPath}`);
82673
- const assignmentsPath = join8(versionPath, "assignments.json");
82841
+ const assignmentsPath = join9(versionPath, "assignments.json");
82674
82842
  writeFileSync6(assignmentsPath, JSON.stringify(result.assignments, null, 2));
82675
82843
  console.log(`\u2705 Written: ${assignmentsPath}`);
82676
- const clustersPath = join8(versionPath, "clusters.json");
82844
+ const clustersPath = join9(versionPath, "clusters.json");
82677
82845
  writeFileSync6(
82678
82846
  clustersPath,
82679
82847
  JSON.stringify(
@@ -82688,7 +82856,7 @@ function writeProductionArtifacts(result, outputPath) {
82688
82856
  )
82689
82857
  );
82690
82858
  console.log(`\u2705 Written: ${clustersPath}`);
82691
- const summaryPath = join8(versionPath, "summary.json");
82859
+ const summaryPath = join9(versionPath, "summary.json");
82692
82860
  const summary = {
82693
82861
  version: result.version,
82694
82862
  generatedAt: result.generatedAt,
@@ -82702,8 +82870,8 @@ function writeProductionArtifacts(result, outputPath) {
82702
82870
  };
82703
82871
  writeFileSync6(summaryPath, JSON.stringify(summary, null, 2));
82704
82872
  console.log(`\u2705 Written: ${summaryPath}`);
82705
- const latestPath = join8(outputPath, "latest");
82706
- if (existsSync9(latestPath)) {
82873
+ const latestPath = join9(outputPath, "latest");
82874
+ if (existsSync10(latestPath)) {
82707
82875
  const { rmSync: rmSync2 } = __require("fs");
82708
82876
  rmSync2(latestPath, { recursive: true, force: true });
82709
82877
  }
@@ -82714,8 +82882,8 @@ function writeProductionArtifacts(result, outputPath) {
82714
82882
  "clusters.json",
82715
82883
  "summary.json"
82716
82884
  ]) {
82717
- const src = join8(versionPath, file);
82718
- const dst = join8(latestPath, file);
82885
+ const src = join9(versionPath, file);
82886
+ const dst = join9(latestPath, file);
82719
82887
  writeFileSync6(dst, readFileSync8(src));
82720
82888
  }
82721
82889
  console.log(`\u2705 Updated: ${latestPath}`);
@@ -82768,25 +82936,25 @@ function displayClusteringSummary(result) {
82768
82936
 
82769
82937
  // src/commands/faq/cluster.ts
82770
82938
  var PROJECT_ROOT2 = resolve4(__dirname, "../../../..");
82771
- var DEFAULT_PHASE0_PATH = join9(PROJECT_ROOT2, "artifacts/phase-0");
82772
- var DEFAULT_OUTPUT_PATH2 = join9(PROJECT_ROOT2, "artifacts/phase-1/clustering");
82939
+ var DEFAULT_PHASE0_PATH = join10(PROJECT_ROOT2, "artifacts/phase-0");
82940
+ var DEFAULT_OUTPUT_PATH2 = join10(PROJECT_ROOT2, "artifacts/phase-1/clustering");
82773
82941
  function validatePaths(phase0Path) {
82774
- const assignmentsPath = join9(phase0Path, "clusters/v1/assignments.json");
82775
- const labelsPath = join9(phase0Path, "clusters/v1/labels.json");
82776
- const metricsPath = join9(phase0Path, "clusters/v1/metrics.json");
82777
- if (!existsSync10(assignmentsPath)) {
82942
+ const assignmentsPath = join10(phase0Path, "clusters/v1/assignments.json");
82943
+ const labelsPath = join10(phase0Path, "clusters/v1/labels.json");
82944
+ const metricsPath = join10(phase0Path, "clusters/v1/metrics.json");
82945
+ if (!existsSync11(assignmentsPath)) {
82778
82946
  throw new CLIError({
82779
82947
  userMessage: `Phase 0 assignments not found at ${assignmentsPath}.`,
82780
82948
  suggestion: "Run Phase 0 clustering first or specify the correct --phase0-path."
82781
82949
  });
82782
82950
  }
82783
- if (!existsSync10(labelsPath)) {
82951
+ if (!existsSync11(labelsPath)) {
82784
82952
  throw new CLIError({
82785
82953
  userMessage: `Phase 0 labels not found at ${labelsPath}.`,
82786
82954
  suggestion: "Verify the --phase0-path points to valid artifacts."
82787
82955
  });
82788
82956
  }
82789
- if (!existsSync10(metricsPath)) {
82957
+ if (!existsSync11(metricsPath)) {
82790
82958
  throw new CLIError({
82791
82959
  userMessage: `Phase 0 metrics not found at ${metricsPath}.`,
82792
82960
  suggestion: "Verify the --phase0-path points to valid artifacts."
@@ -82824,7 +82992,7 @@ async function faqCluster(ctx, options) {
82824
82992
  writeProductionArtifacts(result, outputPath);
82825
82993
  if (!outputJson) {
82826
82994
  ctx.output.data("\n\u2705 Production clustering complete!");
82827
- ctx.output.data(` Artifacts written to: ${join9(outputPath, version)}`);
82995
+ ctx.output.data(` Artifacts written to: ${join10(outputPath, version)}`);
82828
82996
  }
82829
82997
  } else {
82830
82998
  if (!outputJson) ctx.output.data("\n\u{1F9EA} Dry run - no artifacts written");
@@ -82867,13 +83035,13 @@ function registerFaqClusterCommands(program3) {
82867
83035
 
82868
83036
  // src/commands/faq/extract.ts
82869
83037
  init_esm_shims();
82870
- import { existsSync as existsSync12 } from "fs";
82871
- import { join as join11, resolve as resolve5 } from "path";
83038
+ import { existsSync as existsSync13 } from "fs";
83039
+ import { join as join12, resolve as resolve5 } from "path";
82872
83040
 
82873
83041
  // ../core/src/faq/extractor.ts
82874
83042
  init_esm_shims();
82875
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
82876
- import { join as join10 } from "path";
83043
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
83044
+ import { join as join11 } from "path";
82877
83045
 
82878
83046
  // ../core/src/faq/review.ts
82879
83047
  init_esm_shims();
@@ -83487,14 +83655,14 @@ async function extractFaqCandidates(options) {
83487
83655
  return result;
83488
83656
  }
83489
83657
  function writeExtractionArtifacts(result, outputPath) {
83490
- const versionPath = join10(outputPath, result.version);
83491
- if (!existsSync11(versionPath)) {
83658
+ const versionPath = join11(outputPath, result.version);
83659
+ if (!existsSync12(versionPath)) {
83492
83660
  mkdirSync4(versionPath, { recursive: true });
83493
83661
  }
83494
- const resultPath = join10(versionPath, "extraction-result.json");
83662
+ const resultPath = join11(versionPath, "extraction-result.json");
83495
83663
  writeFileSync7(resultPath, JSON.stringify(result, null, 2));
83496
83664
  console.log(`\u2705 Written: ${resultPath}`);
83497
- const candidatesPath = join10(versionPath, "candidates.json");
83665
+ const candidatesPath = join11(versionPath, "candidates.json");
83498
83666
  const candidatesData = {
83499
83667
  version: result.version,
83500
83668
  extractedAt: result.extractedAt,
@@ -83514,7 +83682,7 @@ function writeExtractionArtifacts(result, outputPath) {
83514
83682
  };
83515
83683
  writeFileSync7(candidatesPath, JSON.stringify(candidatesData, null, 2));
83516
83684
  console.log(`\u2705 Written: ${candidatesPath}`);
83517
- const statsPath = join10(versionPath, "stats.json");
83685
+ const statsPath = join11(versionPath, "stats.json");
83518
83686
  writeFileSync7(
83519
83687
  statsPath,
83520
83688
  JSON.stringify(
@@ -83528,8 +83696,8 @@ function writeExtractionArtifacts(result, outputPath) {
83528
83696
  )
83529
83697
  );
83530
83698
  console.log(`\u2705 Written: ${statsPath}`);
83531
- const latestPath = join10(outputPath, "latest");
83532
- if (existsSync11(latestPath)) {
83699
+ const latestPath = join11(outputPath, "latest");
83700
+ if (existsSync12(latestPath)) {
83533
83701
  const { rmSync: rmSync2 } = __require("fs");
83534
83702
  rmSync2(latestPath, { recursive: true, force: true });
83535
83703
  }
@@ -83539,9 +83707,9 @@ function writeExtractionArtifacts(result, outputPath) {
83539
83707
  "candidates.json",
83540
83708
  "stats.json"
83541
83709
  ]) {
83542
- const src = join10(versionPath, file);
83543
- const dst = join10(latestPath, file);
83544
- if (existsSync11(src)) {
83710
+ const src = join11(versionPath, file);
83711
+ const dst = join11(latestPath, file);
83712
+ if (existsSync12(src)) {
83545
83713
  writeFileSync7(dst, readFileSync9(src));
83546
83714
  }
83547
83715
  }
@@ -83595,24 +83763,24 @@ ${i + 1}. [${confPct}%]${golden} ${candidate.suggestedCategory}`
83595
83763
 
83596
83764
  // src/commands/faq/extract.ts
83597
83765
  var PROJECT_ROOT3 = resolve5(__dirname, "../../../..");
83598
- var DEFAULT_CLUSTERING_PATH = join11(
83766
+ var DEFAULT_CLUSTERING_PATH = join12(
83599
83767
  PROJECT_ROOT3,
83600
83768
  "artifacts/phase-1/clustering/v1/clustering-result.json"
83601
83769
  );
83602
- var DEFAULT_GOLDEN_PATH = join11(
83770
+ var DEFAULT_GOLDEN_PATH = join12(
83603
83771
  PROJECT_ROOT3,
83604
83772
  "artifacts/phase-0/golden/latest/responses.json"
83605
83773
  );
83606
- var DEFAULT_OUTPUT_PATH3 = join11(PROJECT_ROOT3, "artifacts/phase-1/extraction");
83774
+ var DEFAULT_OUTPUT_PATH3 = join12(PROJECT_ROOT3, "artifacts/phase-1/extraction");
83607
83775
  var DEFAULT_CACHE_PATH = `${process.env.HOME}/skill/data/front-cache.db`;
83608
83776
  function validatePaths2(ctx, clusteringPath, goldenPath, outputJson) {
83609
- if (!existsSync12(clusteringPath)) {
83777
+ if (!existsSync13(clusteringPath)) {
83610
83778
  throw new CLIError({
83611
83779
  userMessage: `Clustering result not found at ${clusteringPath}.`,
83612
83780
  suggestion: "Run `bun src/index.ts faq cluster` first to generate clustering."
83613
83781
  });
83614
83782
  }
83615
- if (goldenPath && !existsSync12(goldenPath)) {
83783
+ if (goldenPath && !existsSync13(goldenPath)) {
83616
83784
  if (!outputJson) {
83617
83785
  ctx.output.warn(`Golden responses not found at ${goldenPath}`);
83618
83786
  ctx.output.warn("Golden matching will be disabled.");
@@ -83641,7 +83809,7 @@ async function faqExtract(ctx, options) {
83641
83809
  ctx.output.data("");
83642
83810
  }
83643
83811
  validatePaths2(ctx, clusteringPath, goldenPath, outputJson);
83644
- if (!existsSync12(cachePath)) {
83812
+ if (!existsSync13(cachePath)) {
83645
83813
  const cliError = new CLIError({
83646
83814
  userMessage: `DuckDB cache not found at ${cachePath}.`,
83647
83815
  suggestion: "Run `bun src/index.ts front-cache sync` first to populate cache."
@@ -83663,7 +83831,7 @@ async function faqExtract(ctx, options) {
83663
83831
  }
83664
83832
  const extractionOptions = {
83665
83833
  clusteringPath,
83666
- goldenPath: existsSync12(goldenPath) ? goldenPath : void 0,
83834
+ goldenPath: existsSync13(goldenPath) ? goldenPath : void 0,
83667
83835
  source,
83668
83836
  outputPath,
83669
83837
  version,
@@ -83708,7 +83876,7 @@ async function faqExtract(ctx, options) {
83708
83876
  if (!options.dryRun) {
83709
83877
  ctx.output.data(`
83710
83878
  \u2705 Extraction complete!`);
83711
- ctx.output.data(` Artifacts written to: ${join11(outputPath, version)}`);
83879
+ ctx.output.data(` Artifacts written to: ${join12(outputPath, version)}`);
83712
83880
  if (options.pushRedis && options.app) {
83713
83881
  ctx.output.data(
83714
83882
  ` Candidates pushed to Redis queue: faq:pending:${options.app}`
@@ -92737,9 +92905,9 @@ function registerFaqMineCommands(program3) {
92737
92905
  // src/commands/faq/review.ts
92738
92906
  init_esm_shims();
92739
92907
  import { spawnSync } from "child_process";
92740
- import { existsSync as existsSync13, readFileSync as readFileSync10, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
92908
+ import { existsSync as existsSync14, readFileSync as readFileSync10, unlinkSync, writeFileSync as writeFileSync9 } from "fs";
92741
92909
  import { tmpdir } from "os";
92742
- import { join as join12 } from "path";
92910
+ import { join as join13 } from "path";
92743
92911
  import { confirm as confirm2, select as select3 } from "@inquirer/prompts";
92744
92912
  var COLORS2 = {
92745
92913
  reset: "\x1B[0m",
@@ -92800,7 +92968,7 @@ function getEditor() {
92800
92968
  }
92801
92969
  function editInEditor(ctx, question, answer) {
92802
92970
  const editor = getEditor();
92803
- const tmpFile = join12(tmpdir(), `faq-edit-${Date.now()}.md`);
92971
+ const tmpFile = join13(tmpdir(), `faq-edit-${Date.now()}.md`);
92804
92972
  const content = `# FAQ Edit
92805
92973
 
92806
92974
  ## Question
@@ -92843,7 +93011,7 @@ The sections are separated by "## Question" and "## Answer" headers.
92843
93011
  answer: editedAnswer
92844
93012
  };
92845
93013
  } finally {
92846
- if (existsSync13(tmpFile)) {
93014
+ if (existsSync14(tmpFile)) {
92847
93015
  unlinkSync(tmpFile);
92848
93016
  }
92849
93017
  }
@@ -97795,6 +97963,154 @@ function registerInvestigateCommands(inngest) {
97795
97963
  });
97796
97964
  }
97797
97965
 
97966
+ // src/commands/inngest/patterns.ts
97967
+ init_esm_shims();
97968
+ async function patterns(ctx, options) {
97969
+ const outputJson = options.json === true || ctx.format === "json";
97970
+ try {
97971
+ const client = new InngestClient({ dev: options.dev });
97972
+ const params = { limit: 100 };
97973
+ if (options.after) {
97974
+ params.received_after = parseTimeArg(options.after);
97975
+ }
97976
+ const events = outputJson ? await client.listEvents(params) : await withSpinner("Fetching events...", () => client.listEvents(params));
97977
+ const byName = {};
97978
+ const byFunction = {};
97979
+ for (const event of events.data) {
97980
+ byName[event.name] = (byName[event.name] || 0) + 1;
97981
+ if (event.name === "inngest/function.finished") {
97982
+ const data2 = event.data;
97983
+ const functionId = data2?.function_id;
97984
+ if (!functionId) continue;
97985
+ if (!byFunction[functionId]) {
97986
+ byFunction[functionId] = { success: 0, failed: 0 };
97987
+ }
97988
+ if (data2.error || data2._inngest?.status === "Failed") {
97989
+ byFunction[functionId].failed++;
97990
+ } else {
97991
+ byFunction[functionId].success++;
97992
+ }
97993
+ }
97994
+ }
97995
+ const timeRangeHours = options.after ? parseTimeWindow(options.after) : 24;
97996
+ const topEvents = Object.entries(byName).map(([name, count]) => ({
97997
+ name,
97998
+ count,
97999
+ frequency_per_hour: count / timeRangeHours
98000
+ })).sort((a, b) => b.count - a.count).slice(0, 10);
98001
+ const functionStats = {};
98002
+ for (const [functionId, stats4] of Object.entries(byFunction)) {
98003
+ const total = stats4.success + stats4.failed;
98004
+ functionStats[functionId] = {
98005
+ success: stats4.success,
98006
+ failed: stats4.failed,
98007
+ success_rate: total > 0 ? stats4.success / total : 0
98008
+ };
98009
+ }
98010
+ const output = {
98011
+ time_range: options.after || "24h",
98012
+ total_events: events.data.length,
98013
+ events_by_name: byName,
98014
+ by_function: functionStats,
98015
+ top_events: topEvents
98016
+ };
98017
+ if (outputJson) {
98018
+ ctx.output.data(output);
98019
+ } else {
98020
+ const lines = [];
98021
+ lines.push("\nEvent Patterns\n");
98022
+ lines.push(`Time range: ${output.time_range}`);
98023
+ lines.push(`Total events: ${output.total_events}
98024
+ `);
98025
+ lines.push("Top Events:");
98026
+ lines.push(
98027
+ "\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"
98028
+ );
98029
+ lines.push(
98030
+ "\u2502 Event Name \u2502 Count \u2502 Per Hour \u2502"
98031
+ );
98032
+ lines.push(
98033
+ "\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"
98034
+ );
98035
+ for (const event of topEvents) {
98036
+ const name = event.name.padEnd(38).slice(0, 38);
98037
+ const count = String(event.count).padStart(5);
98038
+ const freq = event.frequency_per_hour.toFixed(2).padStart(12);
98039
+ lines.push(`\u2502 ${name} \u2502 ${count} \u2502 ${freq} \u2502`);
98040
+ }
98041
+ lines.push(
98042
+ "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n"
98043
+ );
98044
+ if (Object.keys(functionStats).length > 0) {
98045
+ lines.push("Function Stats:");
98046
+ lines.push(
98047
+ "\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"
98048
+ );
98049
+ lines.push(
98050
+ "\u2502 Function \u2502 Success \u2502 Failed \u2502 Success % \u2502"
98051
+ );
98052
+ lines.push(
98053
+ "\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"
98054
+ );
98055
+ for (const [functionId, stats4] of Object.entries(functionStats)) {
98056
+ const name = functionId.padEnd(38).slice(0, 38);
98057
+ const success = String(stats4.success).padStart(7);
98058
+ const failed = String(stats4.failed).padStart(6);
98059
+ const rate = `${(stats4.success_rate * 100).toFixed(1)}%`.padStart(11);
98060
+ lines.push(`\u2502 ${name} \u2502 ${success} \u2502 ${failed} \u2502 ${rate} \u2502`);
98061
+ }
98062
+ lines.push(
98063
+ "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n"
98064
+ );
98065
+ }
98066
+ lines.push(
98067
+ "Use `skill inngest failures` for detailed failure analysis.\n"
98068
+ );
98069
+ ctx.output.data(lines.join("\n"));
98070
+ }
98071
+ } catch (error) {
98072
+ const cliError = error instanceof CLIError ? error : new CLIError({
98073
+ userMessage: "Failed to analyze Inngest patterns.",
98074
+ suggestion: "Verify INNGEST_SIGNING_KEY and time window.",
98075
+ cause: error
98076
+ });
98077
+ ctx.output.error(formatError(cliError));
98078
+ process.exitCode = cliError.exitCode;
98079
+ }
98080
+ }
98081
+ function parseTimeWindow(input2) {
98082
+ const match = input2.match(/^(\d+)([hmd])$/);
98083
+ if (!match) return 24;
98084
+ const [, num, unit] = match;
98085
+ const value = Number.parseInt(num ?? "24", 10);
98086
+ switch (unit) {
98087
+ case "h":
98088
+ return value;
98089
+ case "d":
98090
+ return value * 24;
98091
+ case "m":
98092
+ return value / 60;
98093
+ default:
98094
+ return 24;
98095
+ }
98096
+ }
98097
+ function registerPatternsCommand(inngest) {
98098
+ inngest.command("patterns").description(
98099
+ "Aggregate event analysis (event distribution, function success rates)"
98100
+ ).option("--after <time>", 'Time window (e.g., "2h", "1d")', "24h").option("--json", "Output as JSON").option("--dev", "Use dev server").action(async (options, command) => {
98101
+ const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
98102
+ ...command.parent?.opts(),
98103
+ ...command.opts()
98104
+ };
98105
+ const ctx = await createContext({
98106
+ format: options.json ? "json" : opts.format,
98107
+ verbose: opts.verbose,
98108
+ quiet: opts.quiet
98109
+ });
98110
+ await patterns(ctx, options);
98111
+ });
98112
+ }
98113
+
97798
98114
  // src/commands/inngest/runs.ts
97799
98115
  init_esm_shims();
97800
98116
  import { confirm as confirm4 } from "@inquirer/prompts";
@@ -97984,11 +98300,16 @@ function registerSignalCommand(inngest) {
97984
98300
 
97985
98301
  // src/commands/inngest/index.ts
97986
98302
  function registerInngestCommands(program3, usageState2) {
97987
- const inngest = program3.command("inngest").description(getInngestAdaptiveDescription(usageState2));
98303
+ const baseDescription = getInngestAdaptiveDescription(usageState2);
98304
+ const descriptionWithEnv = `${baseDescription}
98305
+
98306
+ Environment: INNGEST_SIGNING_KEY required. Run \`skill doctor\` to check.`;
98307
+ const inngest = program3.command("inngest").description(descriptionWithEnv);
97988
98308
  registerEventsCommands(inngest);
97989
98309
  registerRunsCommands(inngest);
97990
98310
  registerSignalCommand(inngest);
97991
98311
  registerInvestigateCommands(inngest);
98312
+ registerPatternsCommand(inngest);
97992
98313
  }
97993
98314
 
97994
98315
  // src/commands/kb-sync.ts
@@ -114017,7 +114338,7 @@ function registerKbCommands(program3) {
114017
114338
 
114018
114339
  // src/commands/keys/index.ts
114019
114340
  init_esm_shims();
114020
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
114341
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
114021
114342
  import { password as password2, select as select5 } from "@inquirer/prompts";
114022
114343
  import { Decrypter as Decrypter4 } from "age-encryption";
114023
114344
  var buildContext4 = async (command, json) => {
@@ -114034,7 +114355,7 @@ var buildContext4 = async (command, json) => {
114034
114355
  async function getUserConfiguredKeys() {
114035
114356
  const keyPath = getAgeKeyPath();
114036
114357
  const configPath = getEncryptedConfigPath();
114037
- if (!existsSync14(keyPath) || !existsSync14(configPath)) {
114358
+ if (!existsSync15(keyPath) || !existsSync15(configPath)) {
114038
114359
  return /* @__PURE__ */ new Set();
114039
114360
  }
114040
114361
  try {
@@ -114102,7 +114423,7 @@ async function showKeyStatus(ctx) {
114102
114423
  }
114103
114424
  async function interactiveKeySetup(ctx) {
114104
114425
  const keyPath = getAgeKeyPath();
114105
- if (!existsSync14(keyPath)) {
114426
+ if (!existsSync15(keyPath)) {
114106
114427
  ctx.output.data("\n\u{1F511} First time setup - creating your encryption key...\n");
114107
114428
  await configInitAction(ctx, { json: false });
114108
114429
  ctx.output.data("");
@@ -114195,7 +114516,7 @@ function registerKeysCommands(program3) {
114195
114516
  ).option("--json", "Output as JSON").action(async (keyValue, options, command) => {
114196
114517
  const ctx = await buildContext4(command, options.json);
114197
114518
  const keyPath = getAgeKeyPath();
114198
- if (!existsSync14(keyPath)) {
114519
+ if (!existsSync15(keyPath)) {
114199
114520
  if (process.stdin.isTTY && !options.json) {
114200
114521
  ctx.output.data(
114201
114522
  "\u{1F511} First time setup - creating your encryption key...\n"
@@ -114244,8 +114565,8 @@ function registerKeysCommands(program3) {
114244
114565
  steps.push("\u2713 OP token from env");
114245
114566
  }
114246
114567
  if (!opToken) {
114247
- const { getFromKeychain } = await import("./keychain-IEZHT5WN.js");
114248
- opToken = getFromKeychain("op-service-account-token");
114568
+ const { getFromKeychain: getFromKeychain2 } = await import("./keychain-IEZHT5WN.js");
114569
+ opToken = getFromKeychain2("op-service-account-token");
114249
114570
  if (opToken) steps.push("\u2713 OP token from keychain");
114250
114571
  }
114251
114572
  if (!opToken && status.opCliAvailable) {
@@ -114265,8 +114586,8 @@ function registerKeysCommands(program3) {
114265
114586
  steps.push("\u2713 Age key from env");
114266
114587
  }
114267
114588
  if (!ageKey) {
114268
- const { getFromKeychain } = await import("./keychain-IEZHT5WN.js");
114269
- ageKey = getFromKeychain("age-private-key");
114589
+ const { getFromKeychain: getFromKeychain2 } = await import("./keychain-IEZHT5WN.js");
114590
+ ageKey = getFromKeychain2("age-private-key");
114270
114591
  if (ageKey) steps.push("\u2713 Age key from keychain");
114271
114592
  }
114272
114593
  if (!ageKey && opToken) {
@@ -115637,6 +115958,25 @@ var PRIORITY_EMOJI2 = {
115637
115958
  3: "\u{1F7E2}",
115638
115959
  4: "\u26AA"
115639
115960
  };
115961
+ function parseRelativeTime(timeStr) {
115962
+ const match = timeStr.match(/^(\d+)(d|w|h|m)$/);
115963
+ if (!match || !match[1] || !match[2]) {
115964
+ throw new CLIError({
115965
+ userMessage: `Invalid time format: ${timeStr}`,
115966
+ suggestion: "Use format like 90d, 2w, 24h, or 3m"
115967
+ });
115968
+ }
115969
+ const value = parseInt(match[1], 10);
115970
+ const unit = match[2];
115971
+ const msMap = {
115972
+ h: 60 * 60 * 1e3,
115973
+ d: 24 * 60 * 60 * 1e3,
115974
+ w: 7 * 24 * 60 * 60 * 1e3,
115975
+ m: 30 * 24 * 60 * 60 * 1e3
115976
+ };
115977
+ const ms = value * msMap[unit];
115978
+ return new Date(Date.now() - ms);
115979
+ }
115640
115980
  async function listIssues(ctx, options = {}) {
115641
115981
  const limit2 = options.limit || 20;
115642
115982
  try {
@@ -115648,6 +115988,10 @@ async function listIssues(ctx, options = {}) {
115648
115988
  }
115649
115989
  }
115650
115990
  };
115991
+ if (options.olderThan) {
115992
+ const date = parseRelativeTime(options.olderThan);
115993
+ filter4.updatedAt = { lt: date };
115994
+ }
115651
115995
  let teamKey;
115652
115996
  if (options.team) {
115653
115997
  const teams = await client.teams();
@@ -115709,28 +116053,57 @@ async function listIssues(ctx, options = {}) {
115709
116053
  });
115710
116054
  const issues = response.nodes || [];
115711
116055
  const issuesWithDetails = await Promise.all(
115712
- issues.map(async (issue) => ({
115713
- issue,
115714
- state: await issue.state,
115715
- assignee: await issue.assignee,
115716
- team: await issue.team
115717
- }))
116056
+ issues.map(async (issue) => {
116057
+ const labels = options.export ? await issue.labels() : null;
116058
+ const comments = options.export ? await issue.comments() : null;
116059
+ return {
116060
+ issue,
116061
+ state: await issue.state,
116062
+ assignee: await issue.assignee,
116063
+ team: await issue.team,
116064
+ labels: labels?.nodes || null,
116065
+ comments: comments?.nodes || null
116066
+ };
116067
+ })
115718
116068
  );
115719
116069
  if (ctx.format === "json") {
115720
- const issueData = issuesWithDetails.map(
115721
- ({ issue, state, assignee, team }) => ({
115722
- id: issue.id,
115723
- identifier: issue.identifier,
115724
- title: issue.title,
115725
- state: state?.name || null,
115726
- stateType: state?.type || null,
115727
- priority: issue.priority,
115728
- assignee: assignee ? { id: assignee.id, name: assignee.name, email: assignee.email } : null,
115729
- team: team ? { key: team.key, name: team.name } : null,
115730
- url: issue.url,
115731
- createdAt: issue.createdAt,
115732
- updatedAt: issue.updatedAt
115733
- })
116070
+ const issueData = await Promise.all(
116071
+ issuesWithDetails.map(
116072
+ async ({ issue, state, assignee, team, labels, comments }) => {
116073
+ const baseData = {
116074
+ id: issue.id,
116075
+ identifier: issue.identifier,
116076
+ title: issue.title,
116077
+ state: state?.name || null,
116078
+ stateType: state?.type || null,
116079
+ priority: issue.priority,
116080
+ assignee: assignee ? {
116081
+ id: assignee.id,
116082
+ name: assignee.name,
116083
+ email: assignee.email
116084
+ } : null,
116085
+ team: team ? { key: team.key, name: team.name } : null,
116086
+ url: issue.url,
116087
+ createdAt: issue.createdAt,
116088
+ updatedAt: issue.updatedAt
116089
+ };
116090
+ if (options.export) {
116091
+ return {
116092
+ ...baseData,
116093
+ description: issue.description,
116094
+ labels: labels ? labels.map((l) => l.name) : [],
116095
+ comments: comments ? await Promise.all(
116096
+ comments.map(async (c) => ({
116097
+ body: c.body,
116098
+ user: (await c.user)?.name || "Unknown",
116099
+ createdAt: c.createdAt
116100
+ }))
116101
+ ) : []
116102
+ };
116103
+ }
116104
+ return baseData;
116105
+ }
116106
+ )
115734
116107
  );
115735
116108
  ctx.output.data(
115736
116109
  JSON.stringify(
@@ -115778,7 +116151,14 @@ async function listIssues(ctx, options = {}) {
115778
116151
  ctx.output.data("");
115779
116152
  return;
115780
116153
  }
115781
- for (const { issue, state, assignee, team } of issuesWithDetails) {
116154
+ for (const {
116155
+ issue,
116156
+ state,
116157
+ assignee,
116158
+ team,
116159
+ labels,
116160
+ comments
116161
+ } of issuesWithDetails) {
115782
116162
  const emoji = PRIORITY_EMOJI2[issue.priority] || "\u26AA";
115783
116163
  const assigneeName = assignee ? `@${assignee.name}` : "";
115784
116164
  const teamBadge = team ? `[${team.key}]` : "";
@@ -115789,6 +116169,19 @@ async function listIssues(ctx, options = {}) {
115789
116169
  ctx.output.data(
115790
116170
  ` Status: ${state?.name || "unknown"}${assigneeName ? ` | Assignee: ${assigneeName}` : ""}`
115791
116171
  );
116172
+ if (options.export) {
116173
+ if (issue.description) {
116174
+ ctx.output.data(` Description: ${issue.description}`);
116175
+ }
116176
+ if (labels && labels.length > 0) {
116177
+ ctx.output.data(
116178
+ ` Labels: ${labels.map((l) => l.name).join(", ")}`
116179
+ );
116180
+ }
116181
+ if (comments && comments.length > 0) {
116182
+ ctx.output.data(` Comments: ${comments.length}`);
116183
+ }
116184
+ }
115792
116185
  }
115793
116186
  ctx.output.data("");
115794
116187
  ctx.output.data(" Use `skill linear issue <ID> --json` for full details.");
@@ -116582,6 +116975,13 @@ Quick start:
116582
116975
  skill linear create "Title" Create issue
116583
116976
  skill linear search "query" Search issues
116584
116977
 
116978
+ Bulk queries:
116979
+ skill linear issues --older-than 90d Stale issues
116980
+ skill linear issues --older-than 2w --export Full data export
116981
+
116982
+ Environment:
116983
+ LINEAR_API_KEY required. Run 'skill doctor' to check.
116984
+
116585
116985
  All commands support --json for machine-readable output.
116586
116986
 
116587
116987
  \u26A0\uFE0F Write operations require personal LINEAR_API_KEY (run 'skill keys add').`
@@ -116594,8 +116994,10 @@ Examples:
116594
116994
  skill linear issues --team ENG Filter by team
116595
116995
  skill linear issues --state "In Progress" Filter by state
116596
116996
  skill linear issues --assignee me Your issues
116597
- skill linear issues --priority 0 Urgent only`
116598
- ).option("--limit <number>", "Maximum results (default: 20)", "20").option("--team <key>", "Filter by team key (e.g., ENG)").option("--state <name>", "Filter by state name").option("--assignee <email>", 'Filter by assignee (or "me")').option("--project <name>", "Filter by project name").option("--priority <0-4>", "Filter by priority (0=urgent)").option("--json", "Output as JSON with HATEOAS links").action(async (options, command) => {
116997
+ skill linear issues --priority 0 Urgent only
116998
+ skill linear issues --older-than 90d Stale issues (90+ days)
116999
+ skill linear issues --older-than 2w --export Full export`
117000
+ ).option("--limit <number>", "Maximum results (default: 20)", "20").option("--team <key>", "Filter by team key (e.g., ENG)").option("--state <name>", "Filter by state name").option("--assignee <email>", 'Filter by assignee (or "me")').option("--project <name>", "Filter by project name").option("--priority <0-4>", "Filter by priority (0=urgent)").option("--older-than <time>", "Filter by last update (90d, 2w, 24h, 3m)").option("--export", "Include full details (description, labels, comments)").option("--json", "Output as JSON with HATEOAS links").action(async (options, command) => {
116599
117001
  const ctx = await contextFromCommand2(command, options);
116600
117002
  await listIssues(ctx, {
116601
117003
  limit: parseInt(options.limit || "20", 10),
@@ -116603,7 +117005,9 @@ Examples:
116603
117005
  state: options.state,
116604
117006
  assignee: options.assignee,
116605
117007
  project: options.project,
116606
- priority: options.priority !== void 0 ? parseInt(options.priority, 10) : void 0
117008
+ priority: options.priority !== void 0 ? parseInt(options.priority, 10) : void 0,
117009
+ olderThan: options.olderThan,
117010
+ export: options.export
116607
117011
  });
116608
117012
  });
116609
117013
  linear.command("my").description(
@@ -116850,6 +117254,101 @@ Examples:
116850
117254
  });
116851
117255
  }
116852
117256
 
117257
+ // src/commands/list.ts
117258
+ init_esm_shims();
117259
+ import { existsSync as existsSync16, readFileSync as readFileSync13, readdirSync, statSync } from "fs";
117260
+ import { join as join14 } from "path";
117261
+ function discoverSkills(skillsDir) {
117262
+ if (!existsSync16(skillsDir)) {
117263
+ return [];
117264
+ }
117265
+ const skills = [];
117266
+ try {
117267
+ const entries = readdirSync(skillsDir);
117268
+ for (const entry of entries) {
117269
+ const entryPath = join14(skillsDir, entry);
117270
+ const stat = statSync(entryPath);
117271
+ if (!stat.isDirectory()) continue;
117272
+ const skillPath = join14(entryPath, "SKILL.md");
117273
+ if (!existsSync16(skillPath)) continue;
117274
+ const content = readFileSync13(skillPath, "utf8");
117275
+ const description = extractDescription(content);
117276
+ skills.push({
117277
+ name: entry,
117278
+ description,
117279
+ path: skillPath
117280
+ });
117281
+ }
117282
+ } catch {
117283
+ return [];
117284
+ }
117285
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
117286
+ }
117287
+ function extractDescription(markdown) {
117288
+ const lines = markdown.split("\n");
117289
+ let inFrontmatter = false;
117290
+ let foundHeading = false;
117291
+ const descriptionLines = [];
117292
+ for (const line of lines) {
117293
+ const trimmed = line.trim();
117294
+ if (trimmed === "---") {
117295
+ if (!inFrontmatter && !foundHeading) {
117296
+ inFrontmatter = true;
117297
+ continue;
117298
+ } else if (inFrontmatter) {
117299
+ inFrontmatter = false;
117300
+ continue;
117301
+ }
117302
+ }
117303
+ if (inFrontmatter) continue;
117304
+ if (trimmed.startsWith("# ")) {
117305
+ foundHeading = true;
117306
+ continue;
117307
+ }
117308
+ if (foundHeading && trimmed.startsWith("#")) {
117309
+ break;
117310
+ }
117311
+ if (foundHeading) {
117312
+ if (trimmed === "") {
117313
+ if (descriptionLines.length > 0) {
117314
+ break;
117315
+ }
117316
+ continue;
117317
+ }
117318
+ descriptionLines.push(trimmed);
117319
+ }
117320
+ }
117321
+ return descriptionLines.join(" ").trim();
117322
+ }
117323
+ async function listAction(options, command) {
117324
+ const ctx = await createContext({
117325
+ format: options.json ? "json" : command.optsWithGlobals().format,
117326
+ verbose: command.optsWithGlobals().verbose,
117327
+ quiet: command.optsWithGlobals().quiet
117328
+ });
117329
+ const skillsDir = join14(process.cwd(), ".claude", "skills");
117330
+ const skills = discoverSkills(skillsDir);
117331
+ if (options.json) {
117332
+ ctx.output.data({ skills });
117333
+ return;
117334
+ }
117335
+ if (skills.length === 0) {
117336
+ ctx.output.message("No skills found in .claude/skills/");
117337
+ return;
117338
+ }
117339
+ ctx.output.data(`
117340
+ \u{1F4DA} Available Skills (${skills.length})`);
117341
+ ctx.output.data("\u2500".repeat(80));
117342
+ const rows = skills.map((skill) => ({
117343
+ Name: skill.name,
117344
+ Description: skill.description || "(no description)"
117345
+ }));
117346
+ ctx.output.table(rows);
117347
+ }
117348
+ function registerListCommand(program3) {
117349
+ program3.command("list").description("List available CLI skills for programmatic discovery").option("--json", "Output as JSON").action(listAction);
117350
+ }
117351
+
116853
117352
  // src/commands/memory/index.ts
116854
117353
  init_esm_shims();
116855
117354
 
@@ -117979,16 +118478,16 @@ function registerPipelineCommands(program3) {
117979
118478
  // src/commands/plugin-sync.ts
117980
118479
  init_esm_shims();
117981
118480
  import { homedir as homedir3 } from "os";
117982
- import { dirname as dirname3, join as join13, resolve as resolve6 } from "path";
118481
+ import { dirname as dirname3, join as join15, resolve as resolve6 } from "path";
117983
118482
  import { fileURLToPath } from "url";
117984
118483
  var PLUGIN_SOURCE_DIR = resolve6(
117985
118484
  dirname3(fileURLToPath(import.meta.url)),
117986
118485
  "../../plugin"
117987
118486
  );
117988
- var PLUGIN_MANIFEST_RELATIVE = join13(".claude-plugin", "plugin.json");
118487
+ var PLUGIN_MANIFEST_RELATIVE = join15(".claude-plugin", "plugin.json");
117989
118488
  var resolveTargetDir = (global2) => {
117990
- const base = join13(homedir3(), ".claude", global2 ? "skills" : "plugins");
117991
- return join13(base, "skill-cli");
118489
+ const base = join15(homedir3(), ".claude", global2 ? "skills" : "plugins");
118490
+ return join15(base, "skill-cli");
117992
118491
  };
117993
118492
  var readManifest = async (path) => {
117994
118493
  const manifest = await readJson(path);
@@ -118017,7 +118516,7 @@ var writeResult4 = (ctx, payload) => {
118017
118516
  };
118018
118517
  async function executePluginSync(ctx, options) {
118019
118518
  try {
118020
- const sourceManifestPath = join13(PLUGIN_SOURCE_DIR, PLUGIN_MANIFEST_RELATIVE);
118519
+ const sourceManifestPath = join15(PLUGIN_SOURCE_DIR, PLUGIN_MANIFEST_RELATIVE);
118021
118520
  const sourceExists = await pathExists(sourceManifestPath);
118022
118521
  if (!sourceExists) {
118023
118522
  throw new CLIError({
@@ -118027,7 +118526,7 @@ async function executePluginSync(ctx, options) {
118027
118526
  }
118028
118527
  const sourceManifest = await readManifest(sourceManifestPath);
118029
118528
  const targetDir = resolveTargetDir(options.global);
118030
- const targetManifestPath = join13(targetDir, PLUGIN_MANIFEST_RELATIVE);
118529
+ const targetManifestPath = join15(targetDir, PLUGIN_MANIFEST_RELATIVE);
118031
118530
  const targetExists = await pathExists(targetManifestPath);
118032
118531
  if (targetExists && !options.force) {
118033
118532
  const targetManifest = await readManifest(targetManifestPath);
@@ -119270,7 +119769,7 @@ init_esm_shims();
119270
119769
  import { spawn } from "child_process";
119271
119770
  import { writeFile as writeFile7 } from "fs/promises";
119272
119771
  import { homedir as homedir4 } from "os";
119273
- import { dirname as dirname4, join as join14 } from "path";
119772
+ import { dirname as dirname4, join as join16 } from "path";
119274
119773
  var CONFIG_DIR_NAME = "skill-cli";
119275
119774
  var AUTO_UPDATE_STATE_FILE = "auto-update.json";
119276
119775
  var DEFAULT_PACKAGE = "@skillrecordings/cli";
@@ -119282,7 +119781,7 @@ var AutoUpdateStore = class {
119282
119781
  now;
119283
119782
  constructor(options = {}) {
119284
119783
  const configDir = resolveConfigDir(options.configDir);
119285
- this.filePath = join14(configDir, AUTO_UPDATE_STATE_FILE);
119784
+ this.filePath = join16(configDir, AUTO_UPDATE_STATE_FILE);
119286
119785
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
119287
119786
  }
119288
119787
  getNow() {
@@ -119310,9 +119809,9 @@ function resolveConfigDir(configDir) {
119310
119809
  if (configDir) return configDir;
119311
119810
  const xdgConfigHome = process.env.XDG_CONFIG_HOME;
119312
119811
  if (xdgConfigHome && xdgConfigHome.trim() !== "") {
119313
- return join14(xdgConfigHome, CONFIG_DIR_NAME);
119812
+ return join16(xdgConfigHome, CONFIG_DIR_NAME);
119314
119813
  }
119315
- return join14(homedir4(), ".config", CONFIG_DIR_NAME);
119814
+ return join16(homedir4(), ".config", CONFIG_DIR_NAME);
119316
119815
  }
119317
119816
  function isAutoUpdateState(value) {
119318
119817
  if (!value || typeof value !== "object") return false;
@@ -119609,13 +120108,13 @@ var writeHints = (hints, stderr) => {
119609
120108
  init_esm_shims();
119610
120109
  import { lstat, readlink, symlink } from "fs/promises";
119611
120110
  import { homedir as homedir5 } from "os";
119612
- import { dirname as dirname5, join as join15, resolve as resolve7 } from "path";
120111
+ import { dirname as dirname5, join as join17, resolve as resolve7 } from "path";
119613
120112
  import { fileURLToPath as fileURLToPath2 } from "url";
119614
120113
  var SKILL_SOURCE_DIR = resolve7(
119615
120114
  dirname5(fileURLToPath2(import.meta.url)),
119616
120115
  "../../../../.claude/skills/skill-cli"
119617
120116
  );
119618
- var SKILL_TARGET_DIR = join15(homedir5(), ".claude", "skills", "skill-cli");
120117
+ var SKILL_TARGET_DIR = join17(homedir5(), ".claude", "skills", "skill-cli");
119619
120118
  async function autoLinkSkill() {
119620
120119
  const source = SKILL_SOURCE_DIR;
119621
120120
  const target = SKILL_TARGET_DIR;
@@ -119719,16 +120218,16 @@ async function sendTelemetryEvent(event) {
119719
120218
  init_esm_shims();
119720
120219
  import { writeFile as writeFile8 } from "fs/promises";
119721
120220
  import { homedir as homedir6 } from "os";
119722
- import { dirname as dirname6, join as join16 } from "path";
120221
+ import { dirname as dirname6, join as join18 } from "path";
119723
120222
  var CONFIG_DIR_NAME2 = "skill-cli";
119724
120223
  var USAGE_FILE_NAME = "usage.json";
119725
120224
  function resolveConfigDir2(configDir) {
119726
120225
  if (configDir) return configDir;
119727
120226
  const xdgConfigHome = process.env.XDG_CONFIG_HOME;
119728
120227
  if (xdgConfigHome && xdgConfigHome.trim() !== "") {
119729
- return join16(xdgConfigHome, CONFIG_DIR_NAME2);
120228
+ return join18(xdgConfigHome, CONFIG_DIR_NAME2);
119730
120229
  }
119731
- return join16(homedir6(), ".config", CONFIG_DIR_NAME2);
120230
+ return join18(homedir6(), ".config", CONFIG_DIR_NAME2);
119732
120231
  }
119733
120232
  function createDefaultState(now) {
119734
120233
  return {
@@ -119766,7 +120265,7 @@ var UsageTracker = class {
119766
120265
  statePromise;
119767
120266
  constructor(options = {}) {
119768
120267
  const configDir = resolveConfigDir2(options.configDir);
119769
- this.filePath = join16(configDir, USAGE_FILE_NAME);
120268
+ this.filePath = join18(configDir, USAGE_FILE_NAME);
119770
120269
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
119771
120270
  }
119772
120271
  async loadState() {
@@ -120345,8 +120844,8 @@ if (!envLoaded && !process.env.DATABASE_URL) {
120345
120844
  process.env.SKIP_ENV_VALIDATION = "1";
120346
120845
  }
120347
120846
  var runtimeTarget = `bun-${process.platform}-${process.arch}`;
120348
- var buildVersion = "0.17.0".length > 0 ? "0.17.0" : "0.0.0-dev";
120349
- var buildCommit = "e664abd".length > 0 ? "e664abd" : "dev";
120847
+ var buildVersion = "0.18.0".length > 0 ? "0.18.0" : "0.0.0-dev";
120848
+ var buildCommit = "8d98b14".length > 0 ? "8d98b14" : "dev";
120350
120849
  var buildTarget = "node".length > 0 ? "node" : runtimeTarget;
120351
120850
  var isDevBuild = buildVersion.includes("dev") || buildCommit === "dev";
120352
120851
  var versionLabel = `skill v${buildVersion} (${buildCommit}) ${buildTarget}`;
@@ -120551,6 +121050,8 @@ registerKbCommands(program2);
120551
121050
  registerAuthCommands(program2, usageState);
120552
121051
  registerConfigCommands(program2);
120553
121052
  registerKeysCommands(program2);
121053
+ registerDoctorCommand(program2);
121054
+ registerListCommand(program2);
120554
121055
  registerPluginSyncCommand(program2);
120555
121056
  program2.command("mcp").description(
120556
121057
  "Start MCP server for AI coding agent integration.\n Exposes 9 Front tools over JSON-RPC stdio for Claude Code, Cursor, etc.\n Tools: inbox, conversation, message, assign, reply, tag, archive, search, report\n Usage: skill mcp (then connect your AI editor to stdin/stdout)"