@meltstudio/meltctl 5.26.0 → 5.28.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 +345 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7499,7 +7499,7 @@ var CLI_VERSION;
7499
7499
  var init_version = __esm({
7500
7500
  "src/utils/version.ts"() {
7501
7501
  "use strict";
7502
- CLI_VERSION = "5.26.0";
7502
+ CLI_VERSION = "5.28.0";
7503
7503
  }
7504
7504
  });
7505
7505
 
@@ -26933,6 +26933,43 @@ function createTemplatesResource(config2) {
26933
26933
  };
26934
26934
  }
26935
26935
 
26936
+ // ../sdk/dist/resources/skills.js
26937
+ function skillPath(plugin, skill) {
26938
+ return `/skills/${encodeURIComponent(plugin)}/${encodeURIComponent(skill)}`;
26939
+ }
26940
+ function createSkillsResource(config2) {
26941
+ return {
26942
+ /** Every Melt skill (`melt-dev:plan`, …) with its frontmatter description. Auth-only. */
26943
+ async list() {
26944
+ const { data, status } = await apiFetch(config2, "/skills");
26945
+ if (status !== 200)
26946
+ throw new Error(data.error ?? `Failed to list skills (${status})`);
26947
+ return data.skills;
26948
+ },
26949
+ /** One skill's instructions plus the list of its reference files. */
26950
+ async get(plugin, skill, opts = {}) {
26951
+ const query = opts.client ? `?client=${encodeURIComponent(opts.client)}` : "";
26952
+ const { data, status } = await apiFetch(config2, `${skillPath(plugin, skill)}${query}`);
26953
+ if (status === 404)
26954
+ throw new Error(`Skill not found: ${plugin}:${skill}`);
26955
+ if (status !== 200)
26956
+ throw new Error(data.error ?? `Failed to fetch skill (${status})`);
26957
+ return data;
26958
+ },
26959
+ /** One file from the skill's folder, by the path `get` lists (`references/x.md`). */
26960
+ async getFile(plugin, skill, path10, opts = {}) {
26961
+ const params = new URLSearchParams({ path: path10 });
26962
+ if (opts.client)
26963
+ params.set("client", opts.client);
26964
+ const { data, status } = await apiFetch(config2, `${skillPath(plugin, skill)}/file?${params.toString()}`);
26965
+ if (status !== 200) {
26966
+ throw new Error(data.error ?? `Failed to fetch skill file (${status})`);
26967
+ }
26968
+ return data;
26969
+ }
26970
+ };
26971
+ }
26972
+
26936
26973
  // ../sdk/dist/resources/auth.js
26937
26974
  function createAuthResource(config2) {
26938
26975
  return {
@@ -27958,6 +27995,58 @@ function createEndpointChecksResource(config2) {
27958
27995
  throw new Error(data.error ?? `Failed to delete endpoint check (${status})`);
27959
27996
  }
27960
27997
  },
27998
+ /**
27999
+ * The caller's own devices, derived from their checks, with the latest
28000
+ * check and status of each. Self-scoped by the JWT.
28001
+ */
28002
+ async listMyDevices() {
28003
+ const { data, status } = await apiFetch(config2, "/endpoint-checks/devices/me");
28004
+ if (status !== 200) {
28005
+ throw new Error(data.error ?? `Failed to list your devices (${status})`);
28006
+ }
28007
+ return data;
28008
+ },
28009
+ /**
28010
+ * Ask to retire one of the caller's own devices (status
28011
+ * `retire_requested`). It still counts as active until a manager confirms.
28012
+ */
28013
+ async requestRetireDevice(input) {
28014
+ const { data, status } = await apiFetch(config2, "/endpoint-checks/devices/me/retire-request", { method: "POST", body: JSON.stringify(input) });
28015
+ if (status !== 200) {
28016
+ throw new Error(data.error ?? `Failed to request device retirement (${status})`);
28017
+ }
28018
+ return data.device;
28019
+ },
28020
+ /**
28021
+ * Every device a person ever checked (or everyone's, without `email`),
28022
+ * with status, last check and any pending retire request. Manager-gated:
28023
+ * the offboarding read.
28024
+ */
28025
+ async listDevices(email3) {
28026
+ const query = email3 ? `?email=${encodeURIComponent(email3)}` : "";
28027
+ const { data, status } = await apiFetch(config2, `/endpoint-checks/devices${query}`);
28028
+ if (status === 403) {
28029
+ throw new Error("Access denied. Only Team Managers can list endpoint devices.");
28030
+ }
28031
+ if (status !== 200) {
28032
+ throw new Error(data.error ?? `Failed to list endpoint devices (${status})`);
28033
+ }
28034
+ return data;
28035
+ },
28036
+ /**
28037
+ * Set a device's status (active / retired / wiped). Manager-gated.
28038
+ * Confirming a person's retire request means setting `retired`.
28039
+ */
28040
+ async setDeviceStatus(input) {
28041
+ const { data, status } = await apiFetch(config2, "/endpoint-checks/devices/status", { method: "PUT", body: JSON.stringify(input) });
28042
+ if (status === 403) {
28043
+ throw new Error("Access denied. Only Team Managers can change a device status.");
28044
+ }
28045
+ if (status !== 200) {
28046
+ throw new Error(data.error ?? `Failed to set device status (${status})`);
28047
+ }
28048
+ return data.device;
28049
+ },
27961
28050
  /** Weekly Slack reminder settings (#651). Manager-gated. */
27962
28051
  async getReminderSettings() {
27963
28052
  const { data, status } = await apiFetch(config2, "/endpoint-reminders/settings");
@@ -28138,6 +28227,7 @@ function createMeltClient(config2) {
28138
28227
  feedback: createFeedbackResource(config2),
28139
28228
  coins: createCoinsResource(config2),
28140
28229
  templates: createTemplatesResource(config2),
28230
+ skills: createSkillsResource(config2),
28141
28231
  auth: createAuthResource(config2),
28142
28232
  people: createPeopleResource(config2),
28143
28233
  projects: createProjectsResource(config2),
@@ -54172,6 +54262,41 @@ function createTemplatesResource2(config2) {
54172
54262
  }
54173
54263
  };
54174
54264
  }
54265
+ function skillPath2(plugin, skill) {
54266
+ return `/skills/${encodeURIComponent(plugin)}/${encodeURIComponent(skill)}`;
54267
+ }
54268
+ function createSkillsResource2(config2) {
54269
+ return {
54270
+ /** Every Melt skill (`melt-dev:plan`, …) with its frontmatter description. Auth-only. */
54271
+ async list() {
54272
+ const { data, status } = await apiFetch2(config2, "/skills");
54273
+ if (status !== 200)
54274
+ throw new Error(data.error ?? `Failed to list skills (${status})`);
54275
+ return data.skills;
54276
+ },
54277
+ /** One skill's instructions plus the list of its reference files. */
54278
+ async get(plugin, skill, opts = {}) {
54279
+ const query = opts.client ? `?client=${encodeURIComponent(opts.client)}` : "";
54280
+ const { data, status } = await apiFetch2(config2, `${skillPath2(plugin, skill)}${query}`);
54281
+ if (status === 404)
54282
+ throw new Error(`Skill not found: ${plugin}:${skill}`);
54283
+ if (status !== 200)
54284
+ throw new Error(data.error ?? `Failed to fetch skill (${status})`);
54285
+ return data;
54286
+ },
54287
+ /** One file from the skill's folder, by the path `get` lists (`references/x.md`). */
54288
+ async getFile(plugin, skill, path22, opts = {}) {
54289
+ const params = new URLSearchParams({ path: path22 });
54290
+ if (opts.client)
54291
+ params.set("client", opts.client);
54292
+ const { data, status } = await apiFetch2(config2, `${skillPath2(plugin, skill)}/file?${params.toString()}`);
54293
+ if (status !== 200) {
54294
+ throw new Error(data.error ?? `Failed to fetch skill file (${status})`);
54295
+ }
54296
+ return data;
54297
+ }
54298
+ };
54299
+ }
54175
54300
  function createAuthResource2(config2) {
54176
54301
  return {
54177
54302
  async exchangeToken(code, redirectUri) {
@@ -55172,6 +55297,58 @@ function createEndpointChecksResource2(config2) {
55172
55297
  throw new Error(data.error ?? `Failed to delete endpoint check (${status})`);
55173
55298
  }
55174
55299
  },
55300
+ /**
55301
+ * The caller's own devices, derived from their checks, with the latest
55302
+ * check and status of each. Self-scoped by the JWT.
55303
+ */
55304
+ async listMyDevices() {
55305
+ const { data, status } = await apiFetch2(config2, "/endpoint-checks/devices/me");
55306
+ if (status !== 200) {
55307
+ throw new Error(data.error ?? `Failed to list your devices (${status})`);
55308
+ }
55309
+ return data;
55310
+ },
55311
+ /**
55312
+ * Ask to retire one of the caller's own devices (status
55313
+ * `retire_requested`). It still counts as active until a manager confirms.
55314
+ */
55315
+ async requestRetireDevice(input) {
55316
+ const { data, status } = await apiFetch2(config2, "/endpoint-checks/devices/me/retire-request", { method: "POST", body: JSON.stringify(input) });
55317
+ if (status !== 200) {
55318
+ throw new Error(data.error ?? `Failed to request device retirement (${status})`);
55319
+ }
55320
+ return data.device;
55321
+ },
55322
+ /**
55323
+ * Every device a person ever checked (or everyone's, without `email`),
55324
+ * with status, last check and any pending retire request. Manager-gated:
55325
+ * the offboarding read.
55326
+ */
55327
+ async listDevices(email3) {
55328
+ const query = email3 ? `?email=${encodeURIComponent(email3)}` : "";
55329
+ const { data, status } = await apiFetch2(config2, `/endpoint-checks/devices${query}`);
55330
+ if (status === 403) {
55331
+ throw new Error("Access denied. Only Team Managers can list endpoint devices.");
55332
+ }
55333
+ if (status !== 200) {
55334
+ throw new Error(data.error ?? `Failed to list endpoint devices (${status})`);
55335
+ }
55336
+ return data;
55337
+ },
55338
+ /**
55339
+ * Set a device's status (active / retired / wiped). Manager-gated.
55340
+ * Confirming a person's retire request means setting `retired`.
55341
+ */
55342
+ async setDeviceStatus(input) {
55343
+ const { data, status } = await apiFetch2(config2, "/endpoint-checks/devices/status", { method: "PUT", body: JSON.stringify(input) });
55344
+ if (status === 403) {
55345
+ throw new Error("Access denied. Only Team Managers can change a device status.");
55346
+ }
55347
+ if (status !== 200) {
55348
+ throw new Error(data.error ?? `Failed to set device status (${status})`);
55349
+ }
55350
+ return data.device;
55351
+ },
55175
55352
  /** Weekly Slack reminder settings (#651). Manager-gated. */
55176
55353
  async getReminderSettings() {
55177
55354
  const { data, status } = await apiFetch2(config2, "/endpoint-reminders/settings");
@@ -55346,6 +55523,7 @@ function createMeltClient2(config2) {
55346
55523
  feedback: createFeedbackResource2(config2),
55347
55524
  coins: createCoinsResource2(config2),
55348
55525
  templates: createTemplatesResource2(config2),
55526
+ skills: createSkillsResource2(config2),
55349
55527
  auth: createAuthResource2(config2),
55350
55528
  people: createPeopleResource2(config2),
55351
55529
  projects: createProjectsResource2(config2),
@@ -56732,6 +56910,38 @@ var submitEndpointCheckSchema = external_exports.object(submitEndpointCheckShape
56732
56910
  async function submitEndpointCheck(client, input) {
56733
56911
  return safe(() => client.endpointChecks.submit(input));
56734
56912
  }
56913
+ async function listMyEndpointDevices(client) {
56914
+ return safe(() => client.endpointChecks.listMyDevices());
56915
+ }
56916
+ var requestRetireShape = {
56917
+ deviceKey: external_exports.string().min(1).describe(
56918
+ "The device key from list_my_endpoint_devices (serial, else device name, else model)."
56919
+ ),
56920
+ note: external_exports.string().max(500).optional().describe(`Why, in the person's words, e.g. "kept at home, not used for work anymore".`)
56921
+ };
56922
+ var requestRetireEndpointDeviceInputSchema = external_exports.object(requestRetireShape);
56923
+ async function requestRetireEndpointDevice(client, input) {
56924
+ return safe(() => client.endpointChecks.requestRetireDevice(input));
56925
+ }
56926
+ var listDevicesShape = {
56927
+ email: external_exports.string().optional().describe("The person's @meltstudio.co email. Omit to list every person's devices.")
56928
+ };
56929
+ var listEndpointDevicesInputSchema = external_exports.object(listDevicesShape);
56930
+ async function listEndpointDevices(client, input) {
56931
+ return safe(() => client.endpointChecks.listDevices(input.email));
56932
+ }
56933
+ var setStatusShape = {
56934
+ email: external_exports.string().min(1).describe("The device owner's @meltstudio.co email."),
56935
+ deviceKey: external_exports.string().min(1).describe("The device key from list_endpoint_devices."),
56936
+ status: external_exports.enum(["active", "retired", "wiped"]).describe(
56937
+ "active = in use for work; retired = still owned, not used for work (confirms a retire request); wiped = cleaned."
56938
+ ),
56939
+ note: external_exports.string().max(500).optional().describe('Optional note, e.g. "wiped on hand-back". Omit to keep the current note.')
56940
+ };
56941
+ var setEndpointDeviceStatusInputSchema = external_exports.object(setStatusShape);
56942
+ async function setEndpointDeviceStatus(client, input) {
56943
+ return safe(() => client.endpointChecks.setDeviceStatus(input));
56944
+ }
56735
56945
  function registerEndpointCheckTools(server, getClient2) {
56736
56946
  server.registerTool(
56737
56947
  "submit_endpoint_check",
@@ -56742,6 +56952,42 @@ function registerEndpointCheckTools(server, getClient2) {
56742
56952
  },
56743
56953
  withClientArgs(getClient2, submitEndpointCheck)
56744
56954
  );
56955
+ server.registerTool(
56956
+ "list_my_endpoint_devices",
56957
+ {
56958
+ title: "List my endpoint devices",
56959
+ description: "Lists the calling person's own devices, derived from their endpoint checks: label, deviceKey, model, serial, last check date, number of checks and status (active / retire_requested / retired / wiped). Self-scoped from the JWT. melt-company:verify-endpoint calls it after submitting to spot other devices that haven't been checked in a while.",
56960
+ inputSchema: {}
56961
+ },
56962
+ withClient(getClient2, listMyEndpointDevices)
56963
+ );
56964
+ server.registerTool(
56965
+ "request_retire_endpoint_device",
56966
+ {
56967
+ title: "Ask to retire one of my endpoint devices",
56968
+ description: "Asks to retire one of the calling person's OWN devices: one they still have but no longer use for work. Sets status retire_requested; the device still counts for the weekly reminders until a manager confirms it (set_endpoint_device_status with retired). Self-scoped: only the caller's own device keys (from list_my_endpoint_devices) are accepted. Fails for a device already retired or wiped.",
56969
+ inputSchema: requestRetireShape
56970
+ },
56971
+ withClientArgs(getClient2, requestRetireEndpointDevice)
56972
+ );
56973
+ server.registerTool(
56974
+ "list_endpoint_devices",
56975
+ {
56976
+ title: "List endpoint devices (manager)",
56977
+ description: "Every device a person ever checked (or everyone's, without email), with status, last check, the pending retire request (requestedBy / requestedAt / note) and who last set the status. Nothing is ever deleted, so retired and wiped devices are listed too. Use it when offboarding someone to make sure each of their machines is handled, or to find pending retire requests (status retire_requested). Manager-gated.",
56978
+ inputSchema: listDevicesShape
56979
+ },
56980
+ withClientArgs(getClient2, listEndpointDevices)
56981
+ );
56982
+ server.registerTool(
56983
+ "set_endpoint_device_status",
56984
+ {
56985
+ title: "Set an endpoint device status (manager)",
56986
+ description: "Sets a person's device to active, retired or wiped. retired = still owned but not used for work: left out of the endpoint-check reminders and the overdue math, still listed. Setting retired confirms the person's retire request. wiped = the device was cleaned; who and when are recorded. Get the email and deviceKey from list_endpoint_devices. Manager-gated.",
56987
+ inputSchema: setStatusShape
56988
+ },
56989
+ withClientArgs(getClient2, setEndpointDeviceStatus)
56990
+ );
56745
56991
  }
56746
56992
  async function getSupportEffort(client, input) {
56747
56993
  return safe(async () => {
@@ -56772,6 +57018,103 @@ function registerSupportEffortTools(server, getClient2) {
56772
57018
  withClientArgs(getClient2, getSupportEffort)
56773
57019
  );
56774
57020
  }
57021
+ var clientSchema = external_exports.enum(["claude", "cursor", "other"]).optional().describe(
57022
+ 'Which agent you are. Use "cursor" in Cursor; "other" (the default) for any agent without the Melt Claude plugins; "claude" only to get the file exactly as Claude Code would.'
57023
+ );
57024
+ var skillNameSchema = external_exports.string().min(1).describe(
57025
+ 'The skill, as "<plugin>:<skill>" (melt-company:verify-endpoint) or "<plugin>-<skill>" (melt-company-verify-endpoint). A leading "/" is ignored.'
57026
+ );
57027
+ var getSkillShape = { name: skillNameSchema, client: clientSchema };
57028
+ var getSkillInputSchema = external_exports.object(getSkillShape);
57029
+ var getSkillReferenceShape = {
57030
+ name: skillNameSchema,
57031
+ path: external_exports.string().min(1).describe(
57032
+ 'The file as the skill names it, relative to the skill folder, e.g. "references/standards.md" (as listed by get_skill).'
57033
+ ),
57034
+ client: clientSchema
57035
+ };
57036
+ var getSkillReferenceInputSchema = external_exports.object(getSkillReferenceShape);
57037
+ async function safeText(fn) {
57038
+ try {
57039
+ return { content: [{ type: "text", text: await fn() }] };
57040
+ } catch (err) {
57041
+ return fail(err instanceof Error ? err.message : String(err));
57042
+ }
57043
+ }
57044
+ async function resolveSkillName(client, rawName) {
57045
+ const name = rawName.trim().replace(/^\//, "");
57046
+ const colon = name.indexOf(":");
57047
+ if (colon > 0) return { plugin: name.slice(0, colon), skill: name.slice(colon + 1) };
57048
+ const match = (await client.skills.list()).find((s) => `${s.plugin}-${s.skill}` === name);
57049
+ if (!match) throw new Error(`Skill not found: ${rawName}. Call list_skills to see the names.`);
57050
+ return { plugin: match.plugin, skill: match.skill };
57051
+ }
57052
+ async function listSkills(client) {
57053
+ return safe(() => client.skills.list());
57054
+ }
57055
+ async function getSkill(client, input) {
57056
+ return safeText(async () => {
57057
+ const { plugin, skill } = await resolveSkillName(client, input.name);
57058
+ const detail = await client.skills.get(plugin, skill, { client: input.client ?? "other" });
57059
+ const references = detail.references.length ? detail.references.map((path22) => `- ${path22}`).join("\n") : "(none)";
57060
+ return [
57061
+ `# Melt skill: ${detail.name}`,
57062
+ "",
57063
+ `Follow the instructions below as if you were running the ${detail.name} skill. When they say to read a file (for example references/standards.md), fetch it with get_skill_reference (name: "${detail.name}", path: the file).`,
57064
+ "",
57065
+ `Files in this skill:
57066
+ ${references}`,
57067
+ "",
57068
+ "---",
57069
+ "",
57070
+ detail.body
57071
+ ].join("\n");
57072
+ });
57073
+ }
57074
+ async function getSkillReference(client, input) {
57075
+ return safeText(async () => {
57076
+ const { plugin, skill } = await resolveSkillName(client, input.name);
57077
+ const file2 = await client.skills.getFile(plugin, skill, input.path, {
57078
+ client: input.client ?? "other"
57079
+ });
57080
+ return file2.content;
57081
+ });
57082
+ }
57083
+ function registerSkillTools(server, getClient2) {
57084
+ server.registerTool(
57085
+ "list_skills",
57086
+ {
57087
+ title: "List Melt skills",
57088
+ description: "Lists Melt Studio's skills (the dev pipeline and audits, the PM suite, company checks like the endpoint check) with what each does. Use it when the user asks for a Melt skill you don't have installed, then load the one that fits with get_skill.",
57089
+ inputSchema: {}
57090
+ },
57091
+ withClient(getClient2, listSkills)
57092
+ );
57093
+ server.registerTool(
57094
+ "get_skill",
57095
+ {
57096
+ title: "Load a Melt skill",
57097
+ description: `Loads a Melt skill's full instructions. Treat what it returns as the skill itself: follow the instructions exactly, step by step, as if the skill were installed. When they say to read a file ("read references/x.md"), fetch it with get_skill_reference instead of looking on disk. Pass client: "cursor" when running in Cursor.`,
57098
+ inputSchema: getSkillShape
57099
+ },
57100
+ withClientArgs(
57101
+ getClient2,
57102
+ (client, args) => getSkill(client, args)
57103
+ )
57104
+ );
57105
+ server.registerTool(
57106
+ "get_skill_reference",
57107
+ {
57108
+ title: "Read a file from a Melt skill",
57109
+ description: `Returns one of a Melt skill's reference files (a checklist, template or standards doc) as markdown. Use it whenever a skill loaded with get_skill says to read a file like "references/standards.md"; the files are not on disk. Follow its contents as part of the skill.`,
57110
+ inputSchema: getSkillReferenceShape
57111
+ },
57112
+ withClientArgs(
57113
+ getClient2,
57114
+ (client, args) => getSkillReference(client, args)
57115
+ )
57116
+ );
57117
+ }
56775
57118
  var VERSION = "0.0.0";
56776
57119
  function createMcpServer(clientOrProvider) {
56777
57120
  const getClient2 = typeof clientOrProvider === "function" ? clientOrProvider : () => Promise.resolve(clientOrProvider);
@@ -56791,6 +57134,7 @@ function createMcpServer(clientOrProvider) {
56791
57134
  registerDailyPlanTools(server, getClient2);
56792
57135
  registerEndpointCheckTools(server, getClient2);
56793
57136
  registerSupportEffortTools(server, getClient2);
57137
+ registerSkillTools(server, getClient2);
56794
57138
  return server;
56795
57139
  }
56796
57140
  async function startServer() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meltstudio/meltctl",
3
- "version": "5.26.0",
3
+ "version": "5.28.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",