@skillrecordings/cli 0.11.1 → 0.12.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
@@ -16,7 +16,7 @@ import {
16
16
  getApp,
17
17
  getOutcomeHistory,
18
18
  getRedis
19
- } from "./chunk-AFZNB55D.js";
19
+ } from "./chunk-RA5G6CYF.js";
20
20
  import "./chunk-F4EM72IH.js";
21
21
  import {
22
22
  DEFAULT_CATEGORY_TAG_MAPPING,
@@ -39,7 +39,7 @@ import {
39
39
  runPipeline,
40
40
  validate,
41
41
  validateSync
42
- } from "./chunk-JKFLTZHH.js";
42
+ } from "./chunk-FT5L3SDS.js";
43
43
  import "./chunk-KEV3QKXP.js";
44
44
  import "./chunk-HK3PEWFD.js";
45
45
  import "./chunk-WYKL32C3.js";
@@ -61,7 +61,7 @@ import {
61
61
  gte,
62
62
  or,
63
63
  sql
64
- } from "./chunk-TBO2ZHJH.js";
64
+ } from "./chunk-V34YUISF.js";
65
65
  import "./chunk-ZNF7XD2S.js";
66
66
  import {
67
67
  upsertVector
@@ -19837,8 +19837,8 @@ var require_snapshot_recorder = __commonJS({
19837
19837
  "../../node_modules/.bun/undici@7.20.0/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
19838
19838
  "use strict";
19839
19839
  init_esm_shims();
19840
- var { writeFile: writeFile7, readFile: readFile10, mkdir: mkdir2 } = __require("fs/promises");
19841
- var { dirname: dirname4, resolve: resolve7 } = __require("path");
19840
+ var { writeFile: writeFile9, readFile: readFile10, mkdir: mkdir2 } = __require("fs/promises");
19841
+ var { dirname: dirname6, resolve: resolve7 } = __require("path");
19842
19842
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
19843
19843
  var { InvalidArgumentError, UndiciError } = require_errors();
19844
19844
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
@@ -20069,12 +20069,12 @@ var require_snapshot_recorder = __commonJS({
20069
20069
  throw new InvalidArgumentError("Snapshot path is required");
20070
20070
  }
20071
20071
  const resolvedPath = resolve7(path);
20072
- await mkdir2(dirname4(resolvedPath), { recursive: true });
20072
+ await mkdir2(dirname6(resolvedPath), { recursive: true });
20073
20073
  const data2 = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
20074
20074
  hash,
20075
20075
  snapshot
20076
20076
  }));
20077
- await writeFile7(resolvedPath, JSON.stringify(data2, null, 2), { flush: true });
20077
+ await writeFile9(resolvedPath, JSON.stringify(data2, null, 2), { flush: true });
20078
20078
  }
20079
20079
  /**
20080
20080
  * Clears all recorded snapshots
@@ -74544,6 +74544,63 @@ import { Command as Command4 } from "commander";
74544
74544
  // src/commands/auth/index.ts
74545
74545
  init_esm_shims();
74546
74546
 
74547
+ // src/core/adaptive-help.ts
74548
+ init_esm_shims();
74549
+ var ABBREVIATED_THRESHOLD = 2;
74550
+ var MINIMAL_THRESHOLD = 5;
74551
+ var ROOT_DESCRIPTIONS = {
74552
+ full: "Skill Recordings support agent CLI \u2014 triage, investigate, and manage customer conversations.\n\n Getting Started:\n 1. skill auth setup Configure 1Password secrets (Front API token, DB, etc.)\n 2. skill auth status Verify your credentials are working\n 3. skill front inbox See what needs attention right now\n\n Common Workflows:\n Triage inbox skill front inbox \u2192 skill front triage\n Investigate ticket skill front conversation <id> --messages\n Bulk cleanup skill front bulk-archive --older-than 30d\n Generate report skill front report --inbox support\n Check deploys skill deploys\n\n For AI Agents (Claude Code, MCP):\n skill mcp Start JSON-RPC server with 9 Front tools\n skill plugin sync Install the Claude Code plugin\n All commands support --json for structured, HATEOAS-enriched output",
74553
+ abbreviated: "Skill Recordings support agent CLI \u2014 triage and investigate support conversations.\n\n Start here:\n skill auth setup Configure 1Password + secrets\n skill front inbox See what needs attention\n skill front triage Auto-categorize conversations\n\n Common:\n skill front conversation <id> --messages\n skill front reply <id>\n skill deploys\n skill mcp\n",
74554
+ minimal: "Skill Recordings support agent CLI. Try: skill front inbox, skill front triage, skill auth status. Use --help for details."
74555
+ };
74556
+ var FRONT_DESCRIPTIONS = {
74557
+ full: "Front conversations, inboxes, tags, archival, and reporting.\n\n Prerequisites:\n FRONT_API_TOKEN must be set. Run: skill auth setup\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.",
74558
+ 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>\n\n Requires FRONT_API_TOKEN (skill auth setup).",
74559
+ minimal: "Front API commands (inbox, triage, assign, reply, archive). Requires FRONT_API_TOKEN."
74560
+ };
74561
+ var AUTH_DESCRIPTIONS = {
74562
+ full: "Manage CLI authentication and secrets.\n\n First time? Start here:\n skill auth setup Interactive wizard \u2014 connects to 1Password vault,\n configures FRONT_API_TOKEN, DATABASE_URL, and more.\n Requires: `op` CLI installed (brew install 1password-cli)\n\n Check your setup:\n skill auth status Shows which secrets are configured and which are missing\n skill auth whoami Verify your 1Password service account identity\n\n All Skill Recordings employees have access to the Support vault in 1Password.\n The setup wizard handles everything \u2014 no manual token pasting needed.",
74563
+ abbreviated: "Manage CLI authentication and 1Password secrets.\n\n Start:\n skill auth setup\n skill auth status\n skill auth whoami\n\n Requires the 1Password CLI (`op`).",
74564
+ minimal: "Auth and 1Password setup commands."
74565
+ };
74566
+ var INNGEST_DESCRIPTIONS = {
74567
+ 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 Requires: INNGEST_SIGNING_KEY, INNGEST_EVENT_KEY in env",
74568
+ 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>\n\n Requires INNGEST_SIGNING_KEY, INNGEST_EVENT_KEY.",
74569
+ minimal: "Inngest events and runs debugging. Requires INNGEST_SIGNING_KEY and INNGEST_EVENT_KEY."
74570
+ };
74571
+ var GROUP_DESCRIPTIONS = {
74572
+ root: ROOT_DESCRIPTIONS,
74573
+ front: FRONT_DESCRIPTIONS,
74574
+ auth: AUTH_DESCRIPTIONS,
74575
+ inngest: INNGEST_DESCRIPTIONS
74576
+ };
74577
+ var getGroupCommandCount = (state, group) => {
74578
+ if (group === "root") return state.totalRuns;
74579
+ const prefix = `${group}.`;
74580
+ let total = 0;
74581
+ for (const [name, entry] of Object.entries(state.commands)) {
74582
+ if (name === group || name.startsWith(prefix)) {
74583
+ total += entry.count;
74584
+ }
74585
+ }
74586
+ return total;
74587
+ };
74588
+ var resolveProficiencyLevel = (state, group) => {
74589
+ if (!state) return "full";
74590
+ const count = getGroupCommandCount(state, group);
74591
+ if (count >= MINIMAL_THRESHOLD) return "minimal";
74592
+ if (count >= ABBREVIATED_THRESHOLD) return "abbreviated";
74593
+ return "full";
74594
+ };
74595
+ var getAdaptiveDescription = (group, state) => {
74596
+ const level = resolveProficiencyLevel(state, group);
74597
+ return GROUP_DESCRIPTIONS[group][level];
74598
+ };
74599
+ var getRootAdaptiveDescription = (state) => getAdaptiveDescription("root", state);
74600
+ var getFrontAdaptiveDescription = (state) => getAdaptiveDescription("front", state);
74601
+ var getAuthAdaptiveDescription = (state) => getAdaptiveDescription("auth", state);
74602
+ var getInngestAdaptiveDescription = (state) => getAdaptiveDescription("inngest", state);
74603
+
74547
74604
  // src/core/context.ts
74548
74605
  init_esm_shims();
74549
74606
 
@@ -75582,8 +75639,8 @@ var buildContext = async (command, json) => {
75582
75639
  quiet: opts.quiet
75583
75640
  });
75584
75641
  };
75585
- function registerAuthCommands(program3) {
75586
- const auth = program3.command("auth").description("Manage CLI auth status");
75642
+ function registerAuthCommands(program3, usageState2) {
75643
+ const auth = program3.command("auth").description(getAuthAdaptiveDescription(usageState2));
75587
75644
  auth.command("status").description("Show active auth provider and secret availability").option("--json", "Output as JSON").action(async (options, command) => {
75588
75645
  const ctx = await buildContext(command, options.json);
75589
75646
  await statusAction(ctx, options);
@@ -80749,7 +80806,7 @@ async function runValidateEval(ctx, scenarios, options) {
80749
80806
  return results;
80750
80807
  }
80751
80808
  async function runE2EEval(ctx, scenarios, options) {
80752
- const { runPipeline: runPipeline2 } = await import("./pipeline-P3CLQVOL.js");
80809
+ const { runPipeline: runPipeline2 } = await import("./pipeline-MJVBAX5W.js");
80753
80810
  const concurrency = options.parallel || 1;
80754
80811
  let completed = 0;
80755
80812
  const outputJson = options.outputJson ?? false;
@@ -96087,8 +96144,8 @@ async function getTeammate(ctx, id, options) {
96087
96144
  process.exitCode = cliError.exitCode;
96088
96145
  }
96089
96146
  }
96090
- function registerFrontCommands(program3) {
96091
- const front = program3.command("front").description("Front conversations, inboxes, tags, archival, and reporting");
96147
+ function registerFrontCommands(program3, usageState2) {
96148
+ const front = program3.command("front").description(getFrontAdaptiveDescription(usageState2));
96092
96149
  front.command("message").description("Get a message by ID (body, author, recipients, attachments)").argument("<id>", "Message ID (e.g., msg_xxx)").option("--json", "Output as JSON").action(
96093
96150
  async (id, options, command) => {
96094
96151
  const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
@@ -97377,8 +97434,8 @@ function registerSignalCommand(inngest) {
97377
97434
  }
97378
97435
 
97379
97436
  // src/commands/inngest/index.ts
97380
- function registerInngestCommands(program3) {
97381
- const inngest = program3.command("inngest").description("Inngest API commands");
97437
+ function registerInngestCommands(program3, usageState2) {
97438
+ const inngest = program3.command("inngest").description(getInngestAdaptiveDescription(usageState2));
97382
97439
  registerEventsCommands(inngest);
97383
97440
  registerRunsCommands(inngest);
97384
97441
  registerSignalCommand(inngest);
@@ -114427,7 +114484,7 @@ var handlePipelineError = (ctx, error, message, suggestion = "Verify inputs and
114427
114484
  async function runPipelineCommand(ctx, opts) {
114428
114485
  const outputJson = opts.json === true || ctx.format === "json";
114429
114486
  try {
114430
- const { runPipeline: runPipeline2 } = await import("./pipeline-P3CLQVOL.js");
114487
+ const { runPipeline: runPipeline2 } = await import("./pipeline-MJVBAX5W.js");
114431
114488
  const result = await runPipeline2({
114432
114489
  message: {
114433
114490
  subject: opts.subject,
@@ -115844,6 +115901,529 @@ async function wizard(ctx, options = {}) {
115844
115901
  }
115845
115902
  }
115846
115903
 
115904
+ // src/core/auto-update.ts
115905
+ init_esm_shims();
115906
+ import { spawn } from "child_process";
115907
+ import { writeFile as writeFile7 } from "fs/promises";
115908
+ import { homedir as homedir2 } from "os";
115909
+ import { dirname as dirname4, join as join12 } from "path";
115910
+ var CONFIG_DIR_NAME = "skill-cli";
115911
+ var AUTO_UPDATE_STATE_FILE = "auto-update.json";
115912
+ var DEFAULT_PACKAGE = "@skillrecordings/cli";
115913
+ var REGISTRY_BASE = "https://registry.npmjs.org/";
115914
+ var CHECK_THROTTLE_MS = 60 * 60 * 1e3;
115915
+ var UPDATE_THROTTLE_MS = 24 * 60 * 60 * 1e3;
115916
+ var AutoUpdateStore = class {
115917
+ filePath;
115918
+ now;
115919
+ constructor(options = {}) {
115920
+ const configDir = resolveConfigDir(options.configDir);
115921
+ this.filePath = join12(configDir, AUTO_UPDATE_STATE_FILE);
115922
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
115923
+ }
115924
+ getNow() {
115925
+ return this.now();
115926
+ }
115927
+ async load() {
115928
+ try {
115929
+ if (await pathExists(this.filePath)) {
115930
+ const data2 = await readJson(this.filePath);
115931
+ if (isAutoUpdateState(data2)) return data2;
115932
+ }
115933
+ } catch {
115934
+ }
115935
+ return {};
115936
+ }
115937
+ async save(state) {
115938
+ try {
115939
+ await ensureDir(dirname4(this.filePath));
115940
+ await writeFile7(this.filePath, JSON.stringify(state, null, 2), "utf-8");
115941
+ } catch {
115942
+ }
115943
+ }
115944
+ };
115945
+ function resolveConfigDir(configDir) {
115946
+ if (configDir) return configDir;
115947
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME;
115948
+ if (xdgConfigHome && xdgConfigHome.trim() !== "") {
115949
+ return join12(xdgConfigHome, CONFIG_DIR_NAME);
115950
+ }
115951
+ return join12(homedir2(), ".config", CONFIG_DIR_NAME);
115952
+ }
115953
+ function isAutoUpdateState(value) {
115954
+ if (!value || typeof value !== "object") return false;
115955
+ const state = value;
115956
+ if (state.lastCheckAt !== void 0 && typeof state.lastCheckAt !== "string") {
115957
+ return false;
115958
+ }
115959
+ if (state.lastUpdateAt !== void 0 && typeof state.lastUpdateAt !== "string") {
115960
+ return false;
115961
+ }
115962
+ if (state.lastKnownVersion !== void 0 && typeof state.lastKnownVersion !== "string") {
115963
+ return false;
115964
+ }
115965
+ return true;
115966
+ }
115967
+ function isAutoUpdateDisabled() {
115968
+ return process.env.SKILL_NO_AUTO_UPDATE === "1";
115969
+ }
115970
+ function isWithinWindow(timestamp, now, windowMs) {
115971
+ if (!timestamp) return false;
115972
+ const parsed = Date.parse(timestamp);
115973
+ if (Number.isNaN(parsed)) return false;
115974
+ return now.getTime() - parsed < windowMs;
115975
+ }
115976
+ function normalizePackageName(name) {
115977
+ if (name.startsWith("@")) {
115978
+ return name.replace("/", "%2F");
115979
+ }
115980
+ return name;
115981
+ }
115982
+ function parseSemver(version) {
115983
+ const trimmed = version.trim().replace(/^v/, "");
115984
+ if (!trimmed) return null;
115985
+ const [core, prerelease] = trimmed.split("-", 2);
115986
+ if (!core) return null;
115987
+ const parts = core.split(".");
115988
+ if (parts.length < 3) return null;
115989
+ const major = Number.parseInt(parts[0] ?? "", 10);
115990
+ const minor = Number.parseInt(parts[1] ?? "", 10);
115991
+ const patch = Number.parseInt(parts[2] ?? "", 10);
115992
+ if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) {
115993
+ return null;
115994
+ }
115995
+ return { major, minor, patch, prerelease };
115996
+ }
115997
+ function compareSemver(a, b) {
115998
+ const parsedA = parseSemver(a);
115999
+ const parsedB = parseSemver(b);
116000
+ if (!parsedA || !parsedB) return 0;
116001
+ if (parsedA.major !== parsedB.major) {
116002
+ return parsedA.major > parsedB.major ? 1 : -1;
116003
+ }
116004
+ if (parsedA.minor !== parsedB.minor) {
116005
+ return parsedA.minor > parsedB.minor ? 1 : -1;
116006
+ }
116007
+ if (parsedA.patch !== parsedB.patch) {
116008
+ return parsedA.patch > parsedB.patch ? 1 : -1;
116009
+ }
116010
+ if (parsedA.prerelease && !parsedB.prerelease) return -1;
116011
+ if (!parsedA.prerelease && parsedB.prerelease) return 1;
116012
+ if (parsedA.prerelease && parsedB.prerelease) {
116013
+ if (parsedA.prerelease === parsedB.prerelease) return 0;
116014
+ return parsedA.prerelease > parsedB.prerelease ? 1 : -1;
116015
+ }
116016
+ return 0;
116017
+ }
116018
+ function resolvePackageManager(userAgent) {
116019
+ const ua = userAgent ?? process.env.npm_config_user_agent ?? "";
116020
+ if (ua.startsWith("bun")) return "bun";
116021
+ if (ua.includes("bun")) return "bun";
116022
+ if (ua.startsWith("npm")) return "npm";
116023
+ return "npm";
116024
+ }
116025
+ async function checkForUpdate(options) {
116026
+ const packageName = options.packageName ?? DEFAULT_PACKAGE;
116027
+ const fetchFn = options.fetchFn ?? fetch;
116028
+ const store2 = new AutoUpdateStore({
116029
+ configDir: options.configDir,
116030
+ now: options.now
116031
+ });
116032
+ const now = store2.getNow();
116033
+ const state = await store2.load();
116034
+ if (isWithinWindow(state.lastCheckAt, now, CHECK_THROTTLE_MS)) {
116035
+ const latest = state.lastKnownVersion;
116036
+ const updateAvailable2 = latest ? compareSemver(latest, options.currentVersion) > 0 : false;
116037
+ return { updateAvailable: updateAvailable2, latestVersion: latest, checked: false };
116038
+ }
116039
+ let latestVersion;
116040
+ try {
116041
+ const registryUrl = `${REGISTRY_BASE}${normalizePackageName(packageName)}`;
116042
+ const response = await fetchFn(registryUrl, {
116043
+ headers: { Accept: "application/json" }
116044
+ });
116045
+ if (response.ok) {
116046
+ const data2 = await response.json();
116047
+ latestVersion = data2["dist-tags"]?.latest;
116048
+ }
116049
+ } catch {
116050
+ }
116051
+ state.lastCheckAt = now.toISOString();
116052
+ if (latestVersion) {
116053
+ state.lastKnownVersion = latestVersion;
116054
+ }
116055
+ await store2.save(state);
116056
+ const updateAvailable = latestVersion !== void 0 && compareSemver(latestVersion, options.currentVersion) > 0;
116057
+ return { updateAvailable, latestVersion, checked: true };
116058
+ }
116059
+ async function performUpdate(options = {}) {
116060
+ const packageName = options.packageName ?? DEFAULT_PACKAGE;
116061
+ const store2 = new AutoUpdateStore({
116062
+ configDir: options.configDir,
116063
+ now: options.now
116064
+ });
116065
+ const now = store2.getNow();
116066
+ const packageManager = resolvePackageManager(options.userAgent);
116067
+ const spawnFn = options.spawnFn ?? spawn;
116068
+ const [command, args] = packageManager === "bun" ? ["bun", ["add", "-g", packageName]] : ["npm", ["install", "-g", packageName]];
116069
+ const exitCode = await new Promise((resolve7) => {
116070
+ try {
116071
+ const child = spawnFn(command, args, {
116072
+ stdio: "ignore",
116073
+ env: process.env
116074
+ });
116075
+ child.on("error", () => resolve7(1));
116076
+ child.on("close", (code) => resolve7(code ?? 1));
116077
+ } catch {
116078
+ resolve7(1);
116079
+ }
116080
+ });
116081
+ if (exitCode === 0) {
116082
+ const state = await store2.load();
116083
+ state.lastUpdateAt = now.toISOString();
116084
+ await store2.save(state);
116085
+ return true;
116086
+ }
116087
+ return false;
116088
+ }
116089
+ async function autoUpdateAfterCommand(options) {
116090
+ if (isAutoUpdateDisabled()) return;
116091
+ if (options.isDevMode) return;
116092
+ if (options.commandName === "mcp") return;
116093
+ if (options.format === "json") return;
116094
+ const store2 = new AutoUpdateStore({
116095
+ configDir: options.configDir,
116096
+ now: options.now
116097
+ });
116098
+ const now = store2.getNow();
116099
+ const state = await store2.load();
116100
+ if (isWithinWindow(state.lastUpdateAt, now, UPDATE_THROTTLE_MS)) return;
116101
+ const check = await (options.checkForUpdateFn ?? checkForUpdate)({
116102
+ currentVersion: options.currentVersion,
116103
+ packageName: options.packageName,
116104
+ configDir: options.configDir,
116105
+ now: options.now
116106
+ });
116107
+ if (!check.updateAvailable) return;
116108
+ state.lastUpdateAt = now.toISOString();
116109
+ await store2.save(state);
116110
+ const updated = await (options.performUpdateFn ?? performUpdate)({
116111
+ packageName: options.packageName,
116112
+ configDir: options.configDir,
116113
+ now: options.now
116114
+ });
116115
+ if (updated) return;
116116
+ }
116117
+
116118
+ // src/core/hint-engine.ts
116119
+ init_esm_shims();
116120
+ var DEFAULT_MAX_HINTS = 2;
116121
+ var getCommandCount = (state, command) => state.commands[command]?.count ?? 0;
116122
+ var hasCommand = (state, command) => getCommandCount(state, command) > 0;
116123
+ var hasCommandPrefix = (state, prefix) => Object.entries(state.commands).some(
116124
+ ([name, entry]) => name.startsWith(prefix) && entry.count > 0
116125
+ );
116126
+ var hasMilestone = (state, milestone) => state.milestones[milestone]?.achieved ?? false;
116127
+ var shouldSuppressHints = (context) => context.quiet === true || context.format === "json";
116128
+ var resolveMaxHints = (context) => context.maxHints ?? DEFAULT_MAX_HINTS;
116129
+ var toHint = (rule) => ({
116130
+ id: rule.id,
116131
+ message: rule.message,
116132
+ audience: rule.audience
116133
+ });
116134
+ var DEFAULT_HINT_RULES = [
116135
+ {
116136
+ id: "onboarding.wizard",
116137
+ audience: "onboarding",
116138
+ message: "New here? Run `skill wizard` to set up your first product.",
116139
+ showWhen: (state) => state.totalRuns <= 2 && !hasCommand(state, "wizard"),
116140
+ retireWhen: (state) => hasCommand(state, "wizard") || hasMilestone(state, "wizard_completed")
116141
+ },
116142
+ {
116143
+ id: "onboarding.auth",
116144
+ audience: "onboarding",
116145
+ message: "Configure credentials with `skill init` to unlock the full CLI.",
116146
+ showWhen: (state) => state.totalRuns >= 1 && !hasMilestone(state, "auth_configured"),
116147
+ retireWhen: (state) => hasMilestone(state, "auth_configured")
116148
+ },
116149
+ {
116150
+ id: "discovery.health",
116151
+ audience: "discovery",
116152
+ message: "Check integrations fast with `skill health <app-slug>`.",
116153
+ showWhen: (state) => state.totalRuns >= 2 && !hasCommand(state, "health"),
116154
+ retireWhen: (state) => hasCommand(state, "health")
116155
+ },
116156
+ {
116157
+ id: "discovery.front.inbox",
116158
+ audience: "discovery",
116159
+ message: "List recent conversations via `skill front inbox <name-or-id>`.",
116160
+ showWhen: (state) => state.totalRuns >= 1 && !hasCommand(state, "front.inbox"),
116161
+ retireWhen: (state) => hasCommand(state, "front.inbox")
116162
+ },
116163
+ {
116164
+ id: "discovery.inngest",
116165
+ audience: "discovery",
116166
+ message: "Inspect workflows with `skill inngest stats --after 1d`.",
116167
+ showWhen: (state) => state.totalRuns >= 3 && !hasCommandPrefix(state, "inngest."),
116168
+ retireWhen: (state) => hasCommandPrefix(state, "inngest.")
116169
+ },
116170
+ {
116171
+ id: "discovery.axiom",
116172
+ audience: "discovery",
116173
+ message: 'Query logs quickly with `skill axiom query "<APL>" --since 24h`.',
116174
+ showWhen: (state) => state.totalRuns >= 3 && !hasCommandPrefix(state, "axiom."),
116175
+ retireWhen: (state) => hasCommandPrefix(state, "axiom.")
116176
+ },
116177
+ {
116178
+ id: "context.front.triage",
116179
+ audience: "contextual",
116180
+ postRun: true,
116181
+ message: "Tip: `skill front triage <inbox-id>` surfaces unassigned threads.",
116182
+ showWhen: (state, context) => context.command === "front.inbox" && !hasCommand(state, "front.triage"),
116183
+ retireWhen: (state) => hasCommand(state, "front.triage")
116184
+ },
116185
+ {
116186
+ id: "context.front.conversation",
116187
+ audience: "contextual",
116188
+ postRun: true,
116189
+ message: "Tip: `skill front conversation <id> -m` shows the full thread.",
116190
+ showWhen: (state, context) => context.command === "front.message" && !hasCommand(state, "front.conversation"),
116191
+ retireWhen: (state) => hasCommand(state, "front.conversation")
116192
+ },
116193
+ {
116194
+ id: "context.inngest.run",
116195
+ audience: "contextual",
116196
+ postRun: true,
116197
+ message: "Tip: drill in with `skill inngest run <id>` or `skill inngest trace <run-id>`.",
116198
+ showWhen: (state, context) => context.command === "inngest.failures" && !hasCommand(state, "inngest.run") && !hasCommand(state, "inngest.trace"),
116199
+ retireWhen: (state) => hasCommand(state, "inngest.run") || hasCommand(state, "inngest.trace")
116200
+ }
116201
+ ];
116202
+ var HintEngine = class {
116203
+ rules;
116204
+ constructor(rules = DEFAULT_HINT_RULES) {
116205
+ this.rules = rules;
116206
+ }
116207
+ getHints(state, context) {
116208
+ if (shouldSuppressHints(context)) return [];
116209
+ const maxHints = resolveMaxHints(context);
116210
+ if (maxHints <= 0) return [];
116211
+ const hints = [];
116212
+ for (const rule of this.rules) {
116213
+ if (rule.postRun) continue;
116214
+ if (!rule.showWhen(state, context)) continue;
116215
+ if (rule.retireWhen(state, context)) continue;
116216
+ hints.push(toHint(rule));
116217
+ if (hints.length >= maxHints) break;
116218
+ }
116219
+ return hints;
116220
+ }
116221
+ getPostRunHint(state, context) {
116222
+ if (shouldSuppressHints(context)) return null;
116223
+ const maxHints = resolveMaxHints(context);
116224
+ if (maxHints <= 0) return null;
116225
+ const previouslyShown = context.previouslyShown ?? 0;
116226
+ if (previouslyShown >= maxHints) return null;
116227
+ for (const rule of this.rules) {
116228
+ if (!rule.postRun) continue;
116229
+ if (!rule.showWhen(state, context)) continue;
116230
+ if (rule.retireWhen(state, context)) continue;
116231
+ return toHint(rule);
116232
+ }
116233
+ return null;
116234
+ }
116235
+ };
116236
+ var writeHints = (hints, stderr) => {
116237
+ for (const hint of hints) {
116238
+ stderr.write(`${hint.message}
116239
+ `);
116240
+ }
116241
+ };
116242
+
116243
+ // src/core/telemetry.ts
116244
+ init_esm_shims();
116245
+ import { Axiom as Axiom2 } from "@axiomhq/js";
116246
+ var DEFAULT_DATASET = "support-agent";
116247
+ var axiomClient = null;
116248
+ function isTelemetryDisabled() {
116249
+ return process.env.SKILL_NO_TELEMETRY === "1";
116250
+ }
116251
+ function getAxiomClient2() {
116252
+ const token = process.env.AXIOM_TOKEN;
116253
+ if (!token) return null;
116254
+ if (!axiomClient) {
116255
+ axiomClient = new Axiom2({ token });
116256
+ }
116257
+ return axiomClient;
116258
+ }
116259
+ function resolveTelemetryUser() {
116260
+ const raw = process.env.USER ?? process.env.LOGNAME ?? process.env.USERNAME ?? "";
116261
+ const trimmed = raw.trim();
116262
+ if (!trimmed) return void 0;
116263
+ const base = trimmed.split("@")[0]?.trim();
116264
+ return base || void 0;
116265
+ }
116266
+ async function sendTelemetryEvent(event) {
116267
+ if (isTelemetryDisabled()) return;
116268
+ const client = getAxiomClient2();
116269
+ if (!client) return;
116270
+ const dataset = process.env.AXIOM_DATASET || DEFAULT_DATASET;
116271
+ const safeEvent = {
116272
+ command: event.command,
116273
+ duration: event.duration,
116274
+ success: event.success,
116275
+ platform: event.platform,
116276
+ user: event.user
116277
+ };
116278
+ try {
116279
+ await client.ingest(dataset, {
116280
+ _time: (/* @__PURE__ */ new Date()).toISOString(),
116281
+ ...safeEvent
116282
+ });
116283
+ } catch {
116284
+ }
116285
+ }
116286
+
116287
+ // src/core/usage-tracker.ts
116288
+ init_esm_shims();
116289
+ import { writeFile as writeFile8 } from "fs/promises";
116290
+ import { homedir as homedir3 } from "os";
116291
+ import { dirname as dirname5, join as join13 } from "path";
116292
+ var CONFIG_DIR_NAME2 = "skill-cli";
116293
+ var USAGE_FILE_NAME = "usage.json";
116294
+ function resolveConfigDir2(configDir) {
116295
+ if (configDir) return configDir;
116296
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME;
116297
+ if (xdgConfigHome && xdgConfigHome.trim() !== "") {
116298
+ return join13(xdgConfigHome, CONFIG_DIR_NAME2);
116299
+ }
116300
+ return join13(homedir3(), ".config", CONFIG_DIR_NAME2);
116301
+ }
116302
+ function createDefaultState(now) {
116303
+ return {
116304
+ firstRun: now.toISOString(),
116305
+ totalRuns: 0,
116306
+ commands: {},
116307
+ milestones: {}
116308
+ };
116309
+ }
116310
+ function isUsageState(value) {
116311
+ if (!value || typeof value !== "object") return false;
116312
+ const state = value;
116313
+ if (typeof state.firstRun !== "string") return false;
116314
+ if (typeof state.totalRuns !== "number") return false;
116315
+ if (!state.commands || typeof state.commands !== "object") return false;
116316
+ if (!state.milestones || typeof state.milestones !== "object") return false;
116317
+ for (const entry of Object.values(state.commands)) {
116318
+ if (!entry || typeof entry !== "object") return false;
116319
+ if (typeof entry.count !== "number") return false;
116320
+ if (typeof entry.firstRun !== "string") return false;
116321
+ if (typeof entry.lastRun !== "string") return false;
116322
+ }
116323
+ for (const entry of Object.values(state.milestones)) {
116324
+ if (!entry || typeof entry !== "object") return false;
116325
+ if (typeof entry.achieved !== "boolean") return false;
116326
+ if (entry.achievedAt !== void 0 && typeof entry.achievedAt !== "string") {
116327
+ return false;
116328
+ }
116329
+ }
116330
+ return true;
116331
+ }
116332
+ var UsageTracker = class {
116333
+ filePath;
116334
+ now;
116335
+ statePromise;
116336
+ constructor(options = {}) {
116337
+ const configDir = resolveConfigDir2(options.configDir);
116338
+ this.filePath = join13(configDir, USAGE_FILE_NAME);
116339
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
116340
+ }
116341
+ async loadState() {
116342
+ if (!this.statePromise) {
116343
+ this.statePromise = this.loadStateInternal();
116344
+ }
116345
+ return this.statePromise;
116346
+ }
116347
+ async loadStateInternal() {
116348
+ try {
116349
+ if (await pathExists(this.filePath)) {
116350
+ const data2 = await readJson(this.filePath);
116351
+ if (isUsageState(data2)) {
116352
+ return data2;
116353
+ }
116354
+ }
116355
+ } catch {
116356
+ }
116357
+ return createDefaultState(this.now());
116358
+ }
116359
+ async saveState(state) {
116360
+ try {
116361
+ await ensureDir(dirname5(this.filePath));
116362
+ await writeFile8(this.filePath, JSON.stringify(state, null, 2), "utf-8");
116363
+ } catch {
116364
+ }
116365
+ }
116366
+ async record(command, _opts = {}) {
116367
+ const state = await this.loadState();
116368
+ const nowIso = this.now().toISOString();
116369
+ state.totalRuns += 1;
116370
+ const existing = state.commands[command];
116371
+ if (existing) {
116372
+ existing.count += 1;
116373
+ existing.lastRun = nowIso;
116374
+ } else {
116375
+ state.commands[command] = {
116376
+ count: 1,
116377
+ firstRun: nowIso,
116378
+ lastRun: nowIso
116379
+ };
116380
+ }
116381
+ await this.saveState(state);
116382
+ return state;
116383
+ }
116384
+ async getUsage() {
116385
+ return this.loadState();
116386
+ }
116387
+ async getCommandCount(command) {
116388
+ const state = await this.loadState();
116389
+ return state.commands[command]?.count ?? 0;
116390
+ }
116391
+ async hasMilestone(name) {
116392
+ const state = await this.loadState();
116393
+ return state.milestones[name]?.achieved ?? false;
116394
+ }
116395
+ async setMilestone(name) {
116396
+ const state = await this.loadState();
116397
+ const existing = state.milestones[name];
116398
+ if (!existing || !existing.achieved) {
116399
+ state.milestones[name] = {
116400
+ achieved: true,
116401
+ achievedAt: this.now().toISOString()
116402
+ };
116403
+ await this.saveState(state);
116404
+ }
116405
+ }
116406
+ async totalRuns() {
116407
+ const state = await this.loadState();
116408
+ return state.totalRuns;
116409
+ }
116410
+ async daysSinceFirstRun() {
116411
+ const state = await this.loadState();
116412
+ const firstRunMs = Date.parse(state.firstRun);
116413
+ if (Number.isNaN(firstRunMs)) return 0;
116414
+ const diffMs = this.now().getTime() - firstRunMs;
116415
+ if (diffMs <= 0) return 0;
116416
+ return Math.floor(diffMs / (1e3 * 60 * 60 * 24));
116417
+ }
116418
+ };
116419
+ var cachedUsageTracker;
116420
+ function getUsageTracker() {
116421
+ if (!cachedUsageTracker) {
116422
+ cachedUsageTracker = new UsageTracker();
116423
+ }
116424
+ return cachedUsageTracker;
116425
+ }
116426
+
115847
116427
  // src/mcp/server.ts
115848
116428
  init_esm_shims();
115849
116429
  import { createInterface as createInterface2 } from "readline";
@@ -116348,13 +116928,64 @@ var runtimeTarget = `bun-${process.platform}-${process.arch}`;
116348
116928
  var buildVersion = typeof BUILD_VERSION !== "undefined" && BUILD_VERSION.length > 0 ? BUILD_VERSION : "0.0.0-dev";
116349
116929
  var buildCommit = typeof BUILD_COMMIT !== "undefined" && BUILD_COMMIT.length > 0 ? BUILD_COMMIT : "dev";
116350
116930
  var buildTarget = typeof BUILD_TARGET !== "undefined" && BUILD_TARGET.length > 0 ? BUILD_TARGET : runtimeTarget;
116931
+ var isDevBuild = buildVersion.includes("dev") || buildCommit === "dev";
116351
116932
  var versionLabel = `skill v${buildVersion} (${buildCommit}) ${buildTarget}`;
116352
116933
  var program2 = new Command4();
116353
- program2.name("skill").description("CLI tool for managing app integrations").version(versionLabel).option("-f, --format <format>", "Output format (json|text|table)").option("-v, --verbose", "Enable verbose output").option("-q, --quiet", "Suppress non-error output").option(
116934
+ var hintEngine = new HintEngine();
116935
+ var usageTracker = getUsageTracker();
116936
+ var usageState = await (async () => {
116937
+ try {
116938
+ return await usageTracker.getUsage();
116939
+ } catch {
116940
+ return null;
116941
+ }
116942
+ })();
116943
+ var hintCounts = /* @__PURE__ */ new WeakMap();
116944
+ var commandStartTimes = /* @__PURE__ */ new WeakMap();
116945
+ var resolveCommandName = (command) => {
116946
+ const names = [];
116947
+ let current = command;
116948
+ while (current) {
116949
+ const name = current.name();
116950
+ if (name) names.unshift(name);
116951
+ current = current.parent;
116952
+ }
116953
+ if (names[0] === "skill") names.shift();
116954
+ return names.join(".");
116955
+ };
116956
+ var resolveHintContext = (command) => {
116957
+ const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
116958
+ ...command.parent?.opts(),
116959
+ ...command.opts()
116960
+ };
116961
+ const outputJson = opts.json === true || opts.format === "json";
116962
+ const suppressForPipe = process.env.SKILL_CLI_FORCE_HINTS === "1" ? false : process.stdout.isTTY !== true;
116963
+ return {
116964
+ command: resolveCommandName(command),
116965
+ format: outputJson ? "json" : opts.format,
116966
+ quiet: opts.quiet === true || suppressForPipe
116967
+ };
116968
+ };
116969
+ var resolveMilestones = (commandName) => {
116970
+ switch (commandName) {
116971
+ case "wizard":
116972
+ return ["wizard_completed"];
116973
+ case "auth.setup":
116974
+ case "init":
116975
+ return ["auth_configured"];
116976
+ default:
116977
+ return [];
116978
+ }
116979
+ };
116980
+ program2.name("skill").description(getRootAdaptiveDescription(usageState)).version(versionLabel).option("-f, --format <format>", "Output format (json|text|table)").option("-v, --verbose", "Enable verbose output").option("-q, --quiet", "Suppress non-error output").option(
116354
116981
  "--rate-limit <n>",
116355
116982
  "Override Front API rate limit per minute",
116356
116983
  (v) => Number.parseInt(v, 10)
116357
116984
  );
116985
+ program2.addHelpText(
116986
+ "after",
116987
+ "\n Need help? Start with:\n skill auth setup Set up credentials (1Password)\n skill front inbox See what needs attention\n skill --help This message\n"
116988
+ );
116358
116989
  program2.hook("preAction", (thisCommand, actionCommand) => {
116359
116990
  const opts = typeof actionCommand.optsWithGlobals === "function" ? actionCommand.optsWithGlobals() : thisCommand.opts();
116360
116991
  const rateLimit = opts.rateLimit;
@@ -116362,18 +116993,105 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
116362
116993
  process.env.SKILL_RATE_LIMIT = String(rateLimit);
116363
116994
  }
116364
116995
  });
116996
+ program2.hook("preAction", (_thisCommand, actionCommand) => {
116997
+ commandStartTimes.set(actionCommand, Date.now());
116998
+ });
116999
+ program2.hook("preAction", async (_thisCommand, actionCommand) => {
117000
+ try {
117001
+ const context = resolveHintContext(actionCommand);
117002
+ const state = await usageTracker.getUsage();
117003
+ const hints = hintEngine.getHints(state, context);
117004
+ writeHints(hints, process.stderr);
117005
+ hintCounts.set(actionCommand, hints.length);
117006
+ } catch {
117007
+ }
117008
+ });
117009
+ program2.hook("postAction", async (_thisCommand, actionCommand) => {
117010
+ try {
117011
+ const context = resolveHintContext(actionCommand);
117012
+ const state = await usageTracker.record(context.command);
117013
+ const milestones = resolveMilestones(context.command);
117014
+ for (const milestone of milestones) {
117015
+ await usageTracker.setMilestone(milestone);
117016
+ }
117017
+ const previouslyShown = hintCounts.get(actionCommand) ?? 0;
117018
+ const postHint = hintEngine.getPostRunHint(state, {
117019
+ ...context,
117020
+ previouslyShown
117021
+ });
117022
+ if (postHint) writeHints([postHint], process.stderr);
117023
+ } catch {
117024
+ }
117025
+ try {
117026
+ const startTime = commandStartTimes.get(actionCommand) ?? Date.now();
117027
+ const duration = Math.max(0, Date.now() - startTime);
117028
+ const exitCode = process.exitCode ?? 0;
117029
+ const commandName = resolveCommandName(actionCommand);
117030
+ void sendTelemetryEvent({
117031
+ command: commandName,
117032
+ duration,
117033
+ success: exitCode === 0,
117034
+ platform: process.platform,
117035
+ user: resolveTelemetryUser()
117036
+ });
117037
+ } catch {
117038
+ }
117039
+ try {
117040
+ const context = resolveHintContext(actionCommand);
117041
+ await autoUpdateAfterCommand({
117042
+ commandName: context.command,
117043
+ currentVersion: buildVersion,
117044
+ format: context.format,
117045
+ isDevMode: isDevBuild
117046
+ });
117047
+ } catch {
117048
+ }
117049
+ });
116365
117050
  program2.command("init").description("Initialize a new app integration (quick mode)").argument(
116366
117051
  "[name]",
116367
117052
  "Name of the integration (required in non-interactive mode)"
116368
- ).option("--json", "Output result as JSON (machine-readable)").action(init2);
116369
- program2.command("wizard").description("Interactive wizard for setting up a new property").option("--json", "Output result as JSON (machine-readable)").action(wizard);
117053
+ ).option("--json", "Output result as JSON (machine-readable)").action(async (name, options, command) => {
117054
+ const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
117055
+ ...command.parent?.opts(),
117056
+ ...command.opts()
117057
+ };
117058
+ const ctx = await createContext({
117059
+ format: options.json ? "json" : opts.format,
117060
+ verbose: opts.verbose,
117061
+ quiet: opts.quiet
117062
+ });
117063
+ await init2(ctx, name, options);
117064
+ });
117065
+ program2.command("wizard").description("Interactive wizard for setting up a new property").option("--json", "Output result as JSON (machine-readable)").action(async (options, command) => {
117066
+ const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
117067
+ ...command.parent?.opts(),
117068
+ ...command.opts()
117069
+ };
117070
+ const ctx = await createContext({
117071
+ format: options.json ? "json" : opts.format,
117072
+ verbose: opts.verbose,
117073
+ quiet: opts.quiet
117074
+ });
117075
+ await wizard(ctx, options);
117076
+ });
116370
117077
  program2.command("health").description("Test integration endpoint health").argument(
116371
117078
  "[slug|url]",
116372
117079
  "App slug (from database) or URL (e.g., https://totaltypescript.com)"
116373
117080
  ).option(
116374
117081
  "-s, --secret <secret>",
116375
117082
  "Webhook secret (required for direct URL mode)"
116376
- ).option("-l, --list", "List all registered apps").option("--ids-only", "Output only IDs (one per line)").option("--json", "Output result as JSON (machine-readable)").action(health2);
117083
+ ).option("-l, --list", "List all registered apps").option("--ids-only", "Output only IDs (one per line)").option("--json", "Output result as JSON (machine-readable)").action(async (slugOrUrl, options, command) => {
117084
+ const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
117085
+ ...command.parent?.opts(),
117086
+ ...command.opts()
117087
+ };
117088
+ const ctx = await createContext({
117089
+ format: options.json ? "json" : opts.format,
117090
+ verbose: opts.verbose,
117091
+ quiet: opts.quiet
117092
+ });
117093
+ await health2(ctx, slugOrUrl, options);
117094
+ });
116377
117095
  program2.command("eval").description("Run evals against a dataset").argument("<type>", "Eval type (e.g., routing)").argument("<dataset>", "Path to dataset JSON file").option("--json", "Output result as JSON (machine-readable)").option(
116378
117096
  "--min-precision <number>",
116379
117097
  "Minimum precision threshold (default: 0.92)",
@@ -116410,8 +117128,8 @@ program2.command("eval").description("Run evals against a dataset").argument("<t
116410
117128
  });
116411
117129
  });
116412
117130
  registerDbStatusCommand(program2);
116413
- registerFrontCommands(program2);
116414
- registerInngestCommands(program2);
117131
+ registerFrontCommands(program2, usageState);
117132
+ registerInngestCommands(program2, usageState);
116415
117133
  registerAxiomCommands(program2);
116416
117134
  registerEvalLocalCommands(program2);
116417
117135
  registerEvalPipelineCommands(program2);
@@ -116424,14 +117142,16 @@ registerMemoryCommands(program2);
116424
117142
  registerFaqCommands(program2);
116425
117143
  registerDeployCommands(program2);
116426
117144
  registerKbCommands(program2);
116427
- registerAuthCommands(program2);
117145
+ registerAuthCommands(program2, usageState);
116428
117146
  registerPluginSyncCommand(program2);
116429
- program2.command("mcp").description("Start MCP server for AI coding agent integration").action(async () => {
117147
+ program2.command("mcp").description(
117148
+ "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)"
117149
+ ).action(async () => {
116430
117150
  const server = createMcpServer();
116431
117151
  await server.start();
116432
117152
  });
116433
117153
  program2.parseAsync().finally(async () => {
116434
- const { closeDb: closeDb2 } = await import("./src-IKPHU6BF.js");
117154
+ const { closeDb: closeDb2 } = await import("./src-6OB6UR3N.js");
116435
117155
  await closeDb2();
116436
117156
  });
116437
117157
  /*! Bundled license information: