@meltstudio/meltctl 5.8.0 → 5.10.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.
Files changed (2) hide show
  1. package/dist/index.js +365 -144
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7450,7 +7450,7 @@ var CLI_VERSION;
7450
7450
  var init_version = __esm({
7451
7451
  "src/utils/version.ts"() {
7452
7452
  "use strict";
7453
- CLI_VERSION = "5.8.0";
7453
+ CLI_VERSION = "5.10.0";
7454
7454
  }
7455
7455
  });
7456
7456
 
@@ -27935,6 +27935,32 @@ function createProfileAuditsResource(config2) {
27935
27935
  };
27936
27936
  }
27937
27937
 
27938
+ // ../sdk/dist/resources/support-effort.js
27939
+ function createSupportEffortResource(config2) {
27940
+ return {
27941
+ /**
27942
+ * Get the by-project + by-architect rollups, the week/month trend, and the
27943
+ * actionable missing-time worklist. `period` selects the trend granularity
27944
+ * (default 'week').
27945
+ */
27946
+ async get(opts = {}) {
27947
+ const params = new URLSearchParams();
27948
+ if (opts.period)
27949
+ params.set("period", opts.period);
27950
+ const query = params.toString();
27951
+ const path10 = query ? `/support-effort?${query}` : "/support-effort";
27952
+ const { data, status } = await apiFetch(config2, path10);
27953
+ if (status === 403)
27954
+ throw new Error("Access denied. Only Team Managers can view support effort.");
27955
+ if (status !== 200) {
27956
+ const msg = data && typeof data === "object" && "error" in data ? data.error : void 0;
27957
+ throw new Error(msg ?? `Failed to fetch support effort (${status})`);
27958
+ }
27959
+ return data;
27960
+ }
27961
+ };
27962
+ }
27963
+
27938
27964
  // ../sdk/dist/client.js
27939
27965
  var DEFAULT_TIMEOUT_MS = 1e4;
27940
27966
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([502, 503, 504]);
@@ -28006,7 +28032,8 @@ function createMeltClient(config2) {
28006
28032
  me: createMeResource(config2),
28007
28033
  developers: createDevelopersResource(config2),
28008
28034
  endpointChecks: createEndpointChecksResource(config2),
28009
- profileAudits: createProfileAuditsResource(config2)
28035
+ profileAudits: createProfileAuditsResource(config2),
28036
+ supportEffort: createSupportEffortResource(config2)
28010
28037
  };
28011
28038
  }
28012
28039
 
@@ -42515,6 +42542,253 @@ async function logoutCommand() {
42515
42542
  // src/index.ts
42516
42543
  init_version_check();
42517
42544
 
42545
+ // src/utils/skills-sync.ts
42546
+ init_source();
42547
+
42548
+ // src/utils/debug.ts
42549
+ init_source();
42550
+ function debugLog(message) {
42551
+ if (process.env["MELTCTL_DEBUG"]) {
42552
+ console.error(source_default.dim(`[debug] ${message}`));
42553
+ }
42554
+ }
42555
+
42556
+ // src/commands/skills.ts
42557
+ init_source();
42558
+ var import_fs_extra3 = __toESM(require_lib(), 1);
42559
+ import { createHash } from "crypto";
42560
+ import os5 from "os";
42561
+ import path5 from "path";
42562
+
42563
+ // src/utils/api.ts
42564
+ init_source();
42565
+ async function getToken() {
42566
+ const envToken = process.env["MELTCTL_TOKEN"];
42567
+ if (envToken) {
42568
+ return envToken;
42569
+ }
42570
+ const auth = await getStoredAuth();
42571
+ if (!auth) {
42572
+ console.error(
42573
+ source_default.red(
42574
+ "Not authenticated. Run `npx @meltstudio/meltctl@latest login` or set MELTCTL_TOKEN for CI."
42575
+ )
42576
+ );
42577
+ process.exit(1);
42578
+ }
42579
+ if (new Date(auth.expiresAt) <= /* @__PURE__ */ new Date()) {
42580
+ console.error(
42581
+ source_default.red(
42582
+ "Session expired. Run `npx @meltstudio/meltctl@latest login` to re-authenticate, or set MELTCTL_TOKEN for CI."
42583
+ )
42584
+ );
42585
+ process.exit(1);
42586
+ }
42587
+ return auth.token;
42588
+ }
42589
+ async function getClient() {
42590
+ const token = await getToken();
42591
+ return createMeltClient({ baseUrl: API_BASE, token });
42592
+ }
42593
+
42594
+ // src/commands/skills.ts
42595
+ var MARKER_FILE = ".melt-skills.json";
42596
+ function getSkillsRoot() {
42597
+ return path5.join(os5.homedir(), ".claude", "skills");
42598
+ }
42599
+ function computeSkillHash(files) {
42600
+ const hash2 = createHash("sha256");
42601
+ for (const filePath of Object.keys(files).sort()) {
42602
+ hash2.update(filePath);
42603
+ hash2.update(files[filePath] ?? "");
42604
+ }
42605
+ return hash2.digest("hex").slice(0, 12);
42606
+ }
42607
+ function readMarker(skillsRoot) {
42608
+ const markerPath = path5.join(skillsRoot, MARKER_FILE);
42609
+ if (!import_fs_extra3.default.existsSync(markerPath)) return null;
42610
+ try {
42611
+ const raw = import_fs_extra3.default.readJsonSync(markerPath);
42612
+ const skills2 = Array.isArray(raw.skills) ? raw.skills.map((entry) => {
42613
+ if (typeof entry === "string") {
42614
+ return { name: entry, hash: "" };
42615
+ }
42616
+ if (entry && typeof entry === "object") {
42617
+ const e = entry;
42618
+ return {
42619
+ name: typeof e.name === "string" ? e.name : "",
42620
+ hash: typeof e.hash === "string" ? e.hash : ""
42621
+ };
42622
+ }
42623
+ return { name: "", hash: "" };
42624
+ }) : [];
42625
+ return {
42626
+ version: typeof raw.version === "string" ? raw.version : "",
42627
+ installedAt: typeof raw.installedAt === "string" ? raw.installedAt : "",
42628
+ lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : void 0,
42629
+ skills: skills2
42630
+ };
42631
+ } catch {
42632
+ return null;
42633
+ }
42634
+ }
42635
+ function computeDiff(previous, next) {
42636
+ const prevByName = new Map(previous.map((s) => [s.name, s]));
42637
+ const nextNames = new Set(next.map((s) => s.name));
42638
+ const added = [];
42639
+ const changed = [];
42640
+ for (const skill of next) {
42641
+ const prev = prevByName.get(skill.name);
42642
+ if (!prev) {
42643
+ added.push(skill.name);
42644
+ continue;
42645
+ }
42646
+ if (prev.hash && prev.hash !== skill.hash) {
42647
+ changed.push(skill.name);
42648
+ }
42649
+ }
42650
+ const removed = previous.filter((s) => s.name && !nextNames.has(s.name)).map((s) => s.name);
42651
+ return { added, changed, removed };
42652
+ }
42653
+ function applyDevSkills(manifest, skillsRoot) {
42654
+ const previous = readMarker(skillsRoot)?.skills ?? [];
42655
+ const nextEntries = manifest.skills.map((s) => ({
42656
+ name: s.name,
42657
+ hash: computeSkillHash(s.files)
42658
+ }));
42659
+ const diff = computeDiff(previous, nextEntries);
42660
+ for (const name of diff.removed) {
42661
+ import_fs_extra3.default.removeSync(path5.join(skillsRoot, name));
42662
+ }
42663
+ for (const skill of manifest.skills) {
42664
+ const dir = path5.join(skillsRoot, skill.name);
42665
+ import_fs_extra3.default.removeSync(dir);
42666
+ for (const [relPath, content] of Object.entries(skill.files)) {
42667
+ import_fs_extra3.default.outputFileSync(path5.join(dir, relPath), content);
42668
+ }
42669
+ }
42670
+ const now = (/* @__PURE__ */ new Date()).toISOString();
42671
+ const marker = {
42672
+ version: manifest.version,
42673
+ installedAt: now,
42674
+ lastCheckedAt: now,
42675
+ skills: nextEntries
42676
+ };
42677
+ import_fs_extra3.default.outputFileSync(path5.join(skillsRoot, MARKER_FILE), `${JSON.stringify(marker, null, 2)}
42678
+ `);
42679
+ return diff;
42680
+ }
42681
+ function touchMarkerLastChecked(skillsRoot) {
42682
+ const marker = readMarker(skillsRoot);
42683
+ if (!marker) return;
42684
+ marker.lastCheckedAt = (/* @__PURE__ */ new Date()).toISOString();
42685
+ import_fs_extra3.default.outputFileSync(path5.join(skillsRoot, MARKER_FILE), `${JSON.stringify(marker, null, 2)}
42686
+ `);
42687
+ }
42688
+ async function skillsInstallCommand(opts) {
42689
+ const client = await getClient();
42690
+ let manifest;
42691
+ try {
42692
+ manifest = await client.skills.getDevSkills();
42693
+ } catch (error48) {
42694
+ console.error(
42695
+ source_default.red(
42696
+ `Failed to fetch Melt dev skills: ${error48 instanceof Error ? error48.message : "Unknown error"}`
42697
+ )
42698
+ );
42699
+ process.exit(1);
42700
+ }
42701
+ const skillsRoot = getSkillsRoot();
42702
+ const existing = readMarker(skillsRoot);
42703
+ if (existing && existing.version === manifest.version && !opts.force) {
42704
+ console.log(
42705
+ source_default.dim(
42706
+ `
42707
+ Melt dev skills already up to date (v${manifest.version}). Use --force to reinstall.
42708
+ `
42709
+ )
42710
+ );
42711
+ return;
42712
+ }
42713
+ applyDevSkills(manifest, skillsRoot);
42714
+ console.log(
42715
+ source_default.green(
42716
+ `
42717
+ \u2713 Installed ${manifest.skills.length} Melt dev skills (v${manifest.version}) to ~/.claude/skills/.`
42718
+ )
42719
+ );
42720
+ console.log(
42721
+ source_default.dim(
42722
+ " Restart Claude Code to load them (they appear as /melt-dev-plan, /melt-dev-review, \u2026).\n"
42723
+ )
42724
+ );
42725
+ }
42726
+
42727
+ // src/utils/skills-sync.ts
42728
+ var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
42729
+ var SKIP_SYNC_COMMANDS = /* @__PURE__ */ new Set(["skills", "login", "logout", "update", "version"]);
42730
+ function formatSyncNotice(oldVersion, newVersion, diff) {
42731
+ const groups = [];
42732
+ if (diff.changed.length > 0) groups.push(`changed ${diff.changed.join(", ")}`);
42733
+ if (diff.added.length > 0) groups.push(`added ${diff.added.join(", ")}`);
42734
+ if (diff.removed.length > 0) groups.push(`removed ${diff.removed.join(", ")}`);
42735
+ const suffix = groups.length > 0 ? `: ${groups.join("; ")}` : "";
42736
+ return `\u2191 Melt dev skills updated (v${oldVersion} \u2192 v${newVersion})${suffix}`;
42737
+ }
42738
+ function isCheckDue(lastCheckedAt, now) {
42739
+ if (!lastCheckedAt) return true;
42740
+ const last = Date.parse(lastCheckedAt);
42741
+ if (Number.isNaN(last)) return true;
42742
+ return now - last >= TWENTY_FOUR_HOURS_MS;
42743
+ }
42744
+ async function maybeSyncSkills(commandName) {
42745
+ try {
42746
+ if (process.env["MELTCTL_SKIP_SKILLS_SYNC"]) {
42747
+ debugLog("Skills sync disabled via MELTCTL_SKIP_SKILLS_SYNC");
42748
+ return;
42749
+ }
42750
+ if (commandName && SKIP_SYNC_COMMANDS.has(commandName)) {
42751
+ debugLog(`Skills sync skipped for command "${commandName}"`);
42752
+ return;
42753
+ }
42754
+ const auth = await getStoredAuth();
42755
+ if (!auth || new Date(auth.expiresAt) <= /* @__PURE__ */ new Date()) {
42756
+ debugLog("Skills sync skipped: not authenticated or token expired");
42757
+ return;
42758
+ }
42759
+ const skillsRoot = getSkillsRoot();
42760
+ const marker = readMarker(skillsRoot);
42761
+ if (!marker) {
42762
+ debugLog("Skills sync skipped: no skills marker (not installed)");
42763
+ return;
42764
+ }
42765
+ if (!isCheckDue(marker.lastCheckedAt, Date.now())) {
42766
+ debugLog("Skills sync skipped: checked within the last 24h");
42767
+ return;
42768
+ }
42769
+ const client = createMeltClient({ baseUrl: API_BASE, token: auth.token });
42770
+ let manifest;
42771
+ try {
42772
+ manifest = await client.skills.getDevSkills();
42773
+ } catch (e) {
42774
+ touchMarkerLastChecked(skillsRoot);
42775
+ debugLog(`Skills sync fetch error: ${e instanceof Error ? e.message : e}`);
42776
+ return;
42777
+ }
42778
+ if (manifest.version === marker.version) {
42779
+ touchMarkerLastChecked(skillsRoot);
42780
+ debugLog("Skills sync: already up to date");
42781
+ return;
42782
+ }
42783
+ const oldVersion = marker.version;
42784
+ const diff = applyDevSkills(manifest, skillsRoot);
42785
+ console.log(source_default.dim(source_default.green(formatSyncNotice(oldVersion, manifest.version, diff))));
42786
+ debugLog(`Skills sync: updated ${oldVersion} \u2192 ${manifest.version}`);
42787
+ } catch (e) {
42788
+ debugLog(`Skills sync error: ${e instanceof Error ? e.message : e}`);
42789
+ }
42790
+ }
42791
+
42518
42792
  // src/commands/version.ts
42519
42793
  init_source();
42520
42794
  init_version_check();
@@ -42611,39 +42885,6 @@ async function versionCheckCommand() {
42611
42885
  // src/commands/standup.ts
42612
42886
  init_source();
42613
42887
  init_dist11();
42614
-
42615
- // src/utils/api.ts
42616
- init_source();
42617
- async function getToken() {
42618
- const envToken = process.env["MELTCTL_TOKEN"];
42619
- if (envToken) {
42620
- return envToken;
42621
- }
42622
- const auth = await getStoredAuth();
42623
- if (!auth) {
42624
- console.error(
42625
- source_default.red(
42626
- "Not authenticated. Run `npx @meltstudio/meltctl@latest login` or set MELTCTL_TOKEN for CI."
42627
- )
42628
- );
42629
- process.exit(1);
42630
- }
42631
- if (new Date(auth.expiresAt) <= /* @__PURE__ */ new Date()) {
42632
- console.error(
42633
- source_default.red(
42634
- "Session expired. Run `npx @meltstudio/meltctl@latest login` to re-authenticate, or set MELTCTL_TOKEN for CI."
42635
- )
42636
- );
42637
- process.exit(1);
42638
- }
42639
- return auth.token;
42640
- }
42641
- async function getClient() {
42642
- const token = await getToken();
42643
- return createMeltClient({ baseUrl: API_BASE, token });
42644
- }
42645
-
42646
- // src/commands/standup.ts
42647
42888
  var EDITOR_HINT = source_default.dim("(type \\e to open your editor)");
42648
42889
  async function promptField(message, required2) {
42649
42890
  const value = await dist_default7({ message: `${message} ${EDITOR_HINT}` });
@@ -42865,12 +43106,12 @@ async function showLeaderboard() {
42865
43106
 
42866
43107
  // src/commands/audit.ts
42867
43108
  init_source();
42868
- var import_fs_extra4 = __toESM(require_lib(), 1);
42869
- import path6 from "path";
43109
+ var import_fs_extra5 = __toESM(require_lib(), 1);
43110
+ import path7 from "path";
42870
43111
 
42871
43112
  // src/utils/git.ts
42872
- var import_fs_extra3 = __toESM(require_lib(), 1);
42873
- import path5 from "path";
43113
+ var import_fs_extra4 = __toESM(require_lib(), 1);
43114
+ import path6 from "path";
42874
43115
  import { execSync as execSync4 } from "child_process";
42875
43116
  function getGitBranch() {
42876
43117
  try {
@@ -42908,34 +43149,34 @@ function getGitRepository() {
42908
43149
  function getProjectName() {
42909
43150
  const cwd = process.cwd();
42910
43151
  try {
42911
- const pkgPath = path5.join(cwd, "package.json");
42912
- if (import_fs_extra3.default.pathExistsSync(pkgPath)) {
42913
- const pkg = import_fs_extra3.default.readJsonSync(pkgPath);
43152
+ const pkgPath = path6.join(cwd, "package.json");
43153
+ if (import_fs_extra4.default.pathExistsSync(pkgPath)) {
43154
+ const pkg = import_fs_extra4.default.readJsonSync(pkgPath);
42914
43155
  if (pkg.name) {
42915
43156
  return pkg.name;
42916
43157
  }
42917
43158
  }
42918
43159
  } catch {
42919
43160
  }
42920
- return path5.basename(cwd);
43161
+ return path6.basename(cwd);
42921
43162
  }
42922
43163
  function extractTicketId(branch) {
42923
43164
  const match = branch.match(/([A-Z]+-\d+)/i);
42924
43165
  return match ? match[1] : null;
42925
43166
  }
42926
43167
  async function findMdFiles(dir) {
42927
- if (!await import_fs_extra3.default.pathExists(dir)) {
43168
+ if (!await import_fs_extra4.default.pathExists(dir)) {
42928
43169
  return [];
42929
43170
  }
42930
43171
  const results = [];
42931
43172
  async function walk(current) {
42932
- const entries = await import_fs_extra3.default.readdir(current, { withFileTypes: true });
43173
+ const entries = await import_fs_extra4.default.readdir(current, { withFileTypes: true });
42933
43174
  for (const entry of entries) {
42934
- const fullPath = path5.join(current, entry.name);
43175
+ const fullPath = path6.join(current, entry.name);
42935
43176
  if (entry.isDirectory()) {
42936
43177
  await walk(fullPath);
42937
43178
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
42938
- const stat = await import_fs_extra3.default.stat(fullPath);
43179
+ const stat = await import_fs_extra4.default.stat(fullPath);
42939
43180
  results.push({ path: fullPath, mtime: stat.mtimeMs });
42940
43181
  }
42941
43182
  }
@@ -42954,15 +43195,15 @@ function detectAuditType(filename) {
42954
43195
  }
42955
43196
  async function autoDetectAuditFile() {
42956
43197
  const cwd = process.cwd();
42957
- const auditsDir = path6.join(cwd, ".audits");
43198
+ const auditsDir = path7.join(cwd, ".audits");
42958
43199
  const auditFiles = await findMdFiles(auditsDir);
42959
43200
  if (auditFiles.length > 0) {
42960
43201
  return auditFiles[0] ?? null;
42961
43202
  }
42962
43203
  const candidates = ["AUDIT.md", "UX-AUDIT.md", "SECURITY-AUDIT.md"];
42963
43204
  for (const name of candidates) {
42964
- const filePath = path6.join(cwd, name);
42965
- if (await import_fs_extra4.default.pathExists(filePath)) {
43205
+ const filePath = path7.join(cwd, name);
43206
+ if (await import_fs_extra5.default.pathExists(filePath)) {
42966
43207
  return filePath;
42967
43208
  }
42968
43209
  }
@@ -42972,7 +43213,7 @@ async function auditSubmitCommand(file2) {
42972
43213
  const client = await getClient();
42973
43214
  let filePath;
42974
43215
  if (file2) {
42975
- filePath = path6.resolve(file2);
43216
+ filePath = path7.resolve(file2);
42976
43217
  } else {
42977
43218
  const detected = await autoDetectAuditFile();
42978
43219
  if (!detected) {
@@ -42984,14 +43225,14 @@ async function auditSubmitCommand(file2) {
42984
43225
  process.exit(1);
42985
43226
  }
42986
43227
  filePath = detected;
42987
- console.log(source_default.dim(`Auto-detected audit file: ${path6.relative(process.cwd(), filePath)}`));
43228
+ console.log(source_default.dim(`Auto-detected audit file: ${path7.relative(process.cwd(), filePath)}`));
42988
43229
  }
42989
- if (!await import_fs_extra4.default.pathExists(filePath)) {
43230
+ if (!await import_fs_extra5.default.pathExists(filePath)) {
42990
43231
  console.error(source_default.red(`File not found: ${filePath}`));
42991
43232
  process.exit(1);
42992
43233
  }
42993
- const content = await import_fs_extra4.default.readFile(filePath, "utf-8");
42994
- const filename = path6.basename(filePath);
43234
+ const content = await import_fs_extra5.default.readFile(filePath, "utf-8");
43235
+ const filename = path7.basename(filePath);
42995
43236
  const auditType = detectAuditType(filename);
42996
43237
  const project2 = getProjectName();
42997
43238
  const branch = getGitBranch();
@@ -43116,11 +43357,11 @@ async function auditViewCommand(id, options) {
43116
43357
  try {
43117
43358
  const audit2 = await client.audits.get(id);
43118
43359
  if (options.output) {
43119
- const outputPath = path6.resolve(options.output);
43120
- await import_fs_extra4.default.ensureDir(path6.dirname(outputPath));
43121
- await import_fs_extra4.default.writeFile(outputPath, audit2.content, "utf-8");
43360
+ const outputPath = path7.resolve(options.output);
43361
+ await import_fs_extra5.default.ensureDir(path7.dirname(outputPath));
43362
+ await import_fs_extra5.default.writeFile(outputPath, audit2.content, "utf-8");
43122
43363
  console.log(source_default.green(`
43123
- \u2713 Audit saved to ${path6.relative(process.cwd(), outputPath)}
43364
+ \u2713 Audit saved to ${path7.relative(process.cwd(), outputPath)}
43124
43365
  `));
43125
43366
  } else {
43126
43367
  const typeLabels = {
@@ -43152,8 +43393,8 @@ async function auditViewCommand(id, options) {
43152
43393
 
43153
43394
  // src/commands/plan.ts
43154
43395
  init_source();
43155
- var import_fs_extra5 = __toESM(require_lib(), 1);
43156
- import path7 from "path";
43396
+ var import_fs_extra6 = __toESM(require_lib(), 1);
43397
+ import path8 from "path";
43157
43398
  function extractFrontmatterStatus(content) {
43158
43399
  const match = content.match(/^---\n([\s\S]*?)\n---/);
43159
43400
  if (!match) return null;
@@ -43162,7 +43403,7 @@ function extractFrontmatterStatus(content) {
43162
43403
  }
43163
43404
  async function autoDetectPlanFile() {
43164
43405
  const cwd = process.cwd();
43165
- const plansDir = path7.join(cwd, ".plans");
43406
+ const plansDir = path8.join(cwd, ".plans");
43166
43407
  const mdFiles = await findMdFiles(plansDir);
43167
43408
  if (mdFiles.length === 0) {
43168
43409
  return null;
@@ -43171,7 +43412,7 @@ async function autoDetectPlanFile() {
43171
43412
  const ticketId = extractTicketId(branch);
43172
43413
  if (ticketId) {
43173
43414
  const ticketLower = ticketId.toLowerCase();
43174
- const match = mdFiles.find((f) => path7.basename(f).toLowerCase().includes(ticketLower));
43415
+ const match = mdFiles.find((f) => path8.basename(f).toLowerCase().includes(ticketLower));
43175
43416
  if (match) {
43176
43417
  return match;
43177
43418
  }
@@ -43182,7 +43423,7 @@ async function planSubmitCommand(file2) {
43182
43423
  const client = await getClient();
43183
43424
  let filePath;
43184
43425
  if (file2) {
43185
- filePath = path7.resolve(file2);
43426
+ filePath = path8.resolve(file2);
43186
43427
  } else {
43187
43428
  const detected = await autoDetectPlanFile();
43188
43429
  if (!detected) {
@@ -43194,14 +43435,14 @@ async function planSubmitCommand(file2) {
43194
43435
  process.exit(1);
43195
43436
  }
43196
43437
  filePath = detected;
43197
- console.log(source_default.dim(`Auto-detected plan file: ${path7.relative(process.cwd(), filePath)}`));
43438
+ console.log(source_default.dim(`Auto-detected plan file: ${path8.relative(process.cwd(), filePath)}`));
43198
43439
  }
43199
- if (!await import_fs_extra5.default.pathExists(filePath)) {
43440
+ if (!await import_fs_extra6.default.pathExists(filePath)) {
43200
43441
  console.error(source_default.red(`File not found: ${filePath}`));
43201
43442
  process.exit(1);
43202
43443
  }
43203
- const content = await import_fs_extra5.default.readFile(filePath, "utf-8");
43204
- const filename = path7.basename(filePath);
43444
+ const content = await import_fs_extra6.default.readFile(filePath, "utf-8");
43445
+ const filename = path8.basename(filePath);
43205
43446
  const project2 = getProjectName();
43206
43447
  const branch = getGitBranch();
43207
43448
  const commit = getGitCommit();
@@ -43288,84 +43529,9 @@ async function planListCommand(options) {
43288
43529
  }
43289
43530
  }
43290
43531
 
43291
- // src/commands/skills.ts
43292
- init_source();
43293
- var import_fs_extra6 = __toESM(require_lib(), 1);
43294
- import os5 from "os";
43295
- import path8 from "path";
43296
- var MARKER_FILE = ".melt-skills.json";
43297
- function readMarker(skillsRoot) {
43298
- const markerPath = path8.join(skillsRoot, MARKER_FILE);
43299
- if (!import_fs_extra6.default.existsSync(markerPath)) return null;
43300
- try {
43301
- return import_fs_extra6.default.readJsonSync(markerPath);
43302
- } catch {
43303
- return null;
43304
- }
43305
- }
43306
- async function skillsInstallCommand(opts) {
43307
- const client = await getClient();
43308
- let manifest;
43309
- try {
43310
- manifest = await client.skills.getDevSkills();
43311
- } catch (error48) {
43312
- console.error(
43313
- source_default.red(
43314
- `Failed to fetch Melt dev skills: ${error48 instanceof Error ? error48.message : "Unknown error"}`
43315
- )
43316
- );
43317
- process.exit(1);
43318
- }
43319
- const skillsRoot = path8.join(os5.homedir(), ".claude", "skills");
43320
- const existing = readMarker(skillsRoot);
43321
- if (existing && existing.version === manifest.version && !opts.force) {
43322
- console.log(
43323
- source_default.dim(
43324
- `
43325
- Melt dev skills already up to date (v${manifest.version}). Use --force to reinstall.
43326
- `
43327
- )
43328
- );
43329
- return;
43330
- }
43331
- for (const skill of manifest.skills) {
43332
- const dir = path8.join(skillsRoot, skill.name);
43333
- import_fs_extra6.default.removeSync(dir);
43334
- for (const [relPath, content] of Object.entries(skill.files)) {
43335
- import_fs_extra6.default.outputFileSync(path8.join(dir, relPath), content);
43336
- }
43337
- }
43338
- const marker = {
43339
- version: manifest.version,
43340
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
43341
- skills: manifest.skills.map((s) => s.name)
43342
- };
43343
- import_fs_extra6.default.outputFileSync(path8.join(skillsRoot, MARKER_FILE), `${JSON.stringify(marker, null, 2)}
43344
- `);
43345
- console.log(
43346
- source_default.green(
43347
- `
43348
- \u2713 Installed ${manifest.skills.length} Melt dev skills (v${manifest.version}) to ~/.claude/skills/.`
43349
- )
43350
- );
43351
- console.log(
43352
- source_default.dim(
43353
- " Restart Claude Code to load them (they appear as /melt-dev-plan, /melt-dev-review, \u2026).\n"
43354
- )
43355
- );
43356
- }
43357
-
43358
43532
  // src/index.ts
43359
43533
  init_update();
43360
43534
 
43361
- // src/utils/debug.ts
43362
- init_source();
43363
- function debugLog(message) {
43364
- if (process.env["MELTCTL_DEBUG"]) {
43365
- console.error(source_default.dim(`[debug] ${message}`));
43366
- }
43367
- }
43368
-
43369
43535
  // src/utils/analytics.ts
43370
43536
  init_version();
43371
43537
  async function trackCommand(command, success2, errorMessage) {
@@ -54357,6 +54523,30 @@ function createProfileAuditsResource2(config2) {
54357
54523
  }
54358
54524
  };
54359
54525
  }
54526
+ function createSupportEffortResource2(config2) {
54527
+ return {
54528
+ /**
54529
+ * Get the by-project + by-architect rollups, the week/month trend, and the
54530
+ * actionable missing-time worklist. `period` selects the trend granularity
54531
+ * (default 'week').
54532
+ */
54533
+ async get(opts = {}) {
54534
+ const params = new URLSearchParams();
54535
+ if (opts.period)
54536
+ params.set("period", opts.period);
54537
+ const query = params.toString();
54538
+ const path22 = query ? `/support-effort?${query}` : "/support-effort";
54539
+ const { data, status } = await apiFetch2(config2, path22);
54540
+ if (status === 403)
54541
+ throw new Error("Access denied. Only Team Managers can view support effort.");
54542
+ if (status !== 200) {
54543
+ const msg = data && typeof data === "object" && "error" in data ? data.error : void 0;
54544
+ throw new Error(msg ?? `Failed to fetch support effort (${status})`);
54545
+ }
54546
+ return data;
54547
+ }
54548
+ };
54549
+ }
54360
54550
  var DEFAULT_TIMEOUT_MS2 = 1e4;
54361
54551
  var RETRYABLE_STATUS2 = /* @__PURE__ */ new Set([502, 503, 504]);
54362
54552
  function delay2(ms) {
@@ -54427,7 +54617,8 @@ function createMeltClient2(config2) {
54427
54617
  me: createMeResource2(config2),
54428
54618
  developers: createDevelopersResource2(config2),
54429
54619
  endpointChecks: createEndpointChecksResource2(config2),
54430
- profileAudits: createProfileAuditsResource2(config2)
54620
+ profileAudits: createProfileAuditsResource2(config2),
54621
+ supportEffort: createSupportEffortResource2(config2)
54431
54622
  };
54432
54623
  }
54433
54624
  var auditFindingSchema2 = external_exports.object({
@@ -55694,6 +55885,34 @@ function registerEndpointCheckTools(server, getClient2) {
55694
55885
  withClientArgs(getClient2, submitEndpointCheck)
55695
55886
  );
55696
55887
  }
55888
+ async function getSupportEffort(client, input) {
55889
+ return safe(async () => {
55890
+ const data = await client.supportEffort.get({ period: input.period });
55891
+ const totalMissingTime = data.missingTimeTickets.length;
55892
+ return {
55893
+ asOf: data.asOf,
55894
+ byProject: data.byProject,
55895
+ byArchitect: data.byArchitect,
55896
+ trend: data.trend,
55897
+ // Surface the count prominently; the full actionable list is available via
55898
+ // the read API / dashboard page (Slice 2) rather than flooding the tool.
55899
+ missingTimeCount: totalMissingTime
55900
+ };
55901
+ });
55902
+ }
55903
+ function registerSupportEffortTools(server, getClient2) {
55904
+ server.registerTool(
55905
+ "get_support_effort",
55906
+ {
55907
+ title: "Get tech-support effort",
55908
+ description: "Tech-support / DevOps effort rollup for the 'Tech Support Ops' Linear team (key DEV). Returns logged hours (parsed from each ticket's self-reported 'Time Spent' label \u2014 the architect's active effort, NOT cycle time) and ticket counts rolled up two ways: `byProject` (which client/product the support load lands on) and `byArchitect` (who is carrying it). Each rollup entry has ticketCount, totalLoggedMinutes, avgLoggedMinutes, and missingTimeCount. Also returns `missingTimeCount`: the number of Done tickets with no time label (a data-quality gap that makes the hours less trustworthy \u2014 the dashboard page surfaces the actionable list). `period` selects the trend granularity (week|month, default week). Manager-gated. Use for 'how much support work did <project> need?', 'who's carrying the support load?', and 'is our support time logged completely?'.",
55909
+ inputSchema: {
55910
+ period: external_exports.enum(["week", "month"]).optional().describe("Trend bucket granularity. Default week.")
55911
+ }
55912
+ },
55913
+ withClientArgs(getClient2, getSupportEffort)
55914
+ );
55915
+ }
55697
55916
  var VERSION = "0.0.0";
55698
55917
  function createMcpServer(clientOrProvider) {
55699
55918
  const getClient2 = typeof clientOrProvider === "function" ? clientOrProvider : () => Promise.resolve(clientOrProvider);
@@ -55712,6 +55931,7 @@ function createMcpServer(clientOrProvider) {
55712
55931
  registerFindingsTools(server, getClient2);
55713
55932
  registerDailyPlanTools(server, getClient2);
55714
55933
  registerEndpointCheckTools(server, getClient2);
55934
+ registerSupportEffortTools(server, getClient2);
55715
55935
  return server;
55716
55936
  }
55717
55937
  async function startServer() {
@@ -55777,6 +55997,7 @@ ${source_default.dim(" event track --skill Track skill usage (called by skil
55777
55997
  cmd = cmd.parent;
55778
55998
  }
55779
55999
  await checkAndEnforceUpdate(cmd.name());
56000
+ await maybeSyncSkills(cmd.name());
55780
56001
  }).hook("postAction", async (_thisCommand, actionCommand) => {
55781
56002
  try {
55782
56003
  const parts = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meltstudio/meltctl",
3
- "version": "5.8.0",
3
+ "version": "5.10.0",
4
4
  "description": "AI-first development tooling for Melt teams — companion CLI for the org-distributed Claude Code dev skills",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",