@rapidthoughtlabs/heku 0.2.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-06-11
11
+
12
+ ### Added
13
+ - **`heku update`** now updates registry-installed configs to their latest versions (instead of updating the heku binary). Accepts an optional target: `heku update github-http`, `heku update linear:graphql`, or `heku update @ns/linear` (all connector variants). Local credentials (`connector.env`) and overlays are preserved on update.
14
+ - **`one.registry_update`** MCP tool — same update logic callable by an LLM agent. Updates one config by `config_id` or all installed configs when no argument is passed.
15
+ - **`one.list_configs`** now returns `[{ id, name, description }]` objects instead of a flat array of IDs, so agents can identify the right config without an extra round-trip.
16
+
17
+ ### Changed
18
+ - Publish modal always shows the version field. New (unpublished) configs show an empty field with a "not published yet" hint; existing configs are pre-filled with the next patch version and show the current published version as a hint.
19
+ - Flat manifest (`search`, `list_configs`, `list_tools`, `invoke`) no longer prefixes tool descriptions with `[one]` — it was noise when only four tools are shown. Namespaced mode is unchanged.
20
+ - Discovery tool descriptions updated with explicit workflow steps and call examples for users without custom system prompts.
21
+
10
22
  ## [0.2.0] - 2026-06-09
11
23
 
12
24
  ### Changed
@@ -35,7 +47,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
35
47
  - Initial public release on npm as `@rapidthoughtlabs/mcpone`.
36
48
  - Single dynamic MCP server that turns JSON configs into working API tools.
37
49
 
38
- [Unreleased]: https://github.com/RapidThoughtLabs/heku/compare/v0.2.0...HEAD
50
+ [Unreleased]: https://github.com/RapidThoughtLabs/heku/compare/v0.3.0...HEAD
51
+ [0.3.0]: https://github.com/RapidThoughtLabs/heku/compare/v0.2.0...v0.3.0
39
52
  [0.2.0]: https://github.com/RapidThoughtLabs/heku/compare/v0.1.2...v0.2.0
40
53
  [0.1.2]: https://github.com/RapidThoughtLabs/heku/compare/v0.1.0...v0.1.2
41
54
  [0.1.0]: https://github.com/RapidThoughtLabs/heku/releases/tag/v0.1.0
package/README.md CHANGED
@@ -14,7 +14,7 @@ Stop building one MCP server per API. Build one config.
14
14
  - **Hot-reload** — add or edit a config, tools update live without restart
15
15
  - **Auto-discovery** — gRPC reflection, GraphQL introspection, and child MCP tool listing fill in tools automatically
16
16
  - **Built-in console UI** — React dashboard for chat, config editing, and registry browsing
17
- - **Registry** — publish and install community configs from [mcp.rapidthoughtlabs.space](https://mcp.rapidthoughtlabs.space)
17
+ - **Registry** — publish and install community configs from [app.rapidthoughtlabs.space](https://app.rapidthoughtlabs.space)
18
18
  - **Auth handled** — bearer, basic, API key, and OAuth2 with `.env`-based credential management
19
19
  - **Self-managing** — the server can create and edit its own configs via internal tools
20
20
 
@@ -186,7 +186,7 @@ Built with React 19, TailwindCSS v4, Zustand, and the MCP SDK.
186
186
 
187
187
  ## Registry
188
188
 
189
- [**mcp.rapidthoughtlabs.space**](https://mcp.rapidthoughtlabs.space) is the default registry for sharing configs.
189
+ [**app.rapidthoughtlabs.space**](https://app.rapidthoughtlabs.space) is the default registry for sharing configs.
190
190
 
191
191
  ```bash
192
192
  heku install @rtl/github
package/dist/cli.js CHANGED
@@ -4601,7 +4601,7 @@ var require_schemes = __commonJS({
4601
4601
  serialize: httpSerialize
4602
4602
  }
4603
4603
  );
4604
- var https2 = (
4604
+ var https = (
4605
4605
  /** @type {SchemeHandler} */
4606
4606
  {
4607
4607
  scheme: "https",
@@ -4650,7 +4650,7 @@ var require_schemes = __commonJS({
4650
4650
  /** @type {Record<SchemeName, SchemeHandler>} */
4651
4651
  {
4652
4652
  http: http2,
4653
- https: https2,
4653
+ https,
4654
4654
  ws,
4655
4655
  wss,
4656
4656
  urn,
@@ -8442,7 +8442,7 @@ var require_supports_color = __commonJS({
8442
8442
  "node_modules/supports-color/index.js"(exports2, module2) {
8443
8443
  "use strict";
8444
8444
  init_esm_shims();
8445
- var os7 = __require("os");
8445
+ var os6 = __require("os");
8446
8446
  var tty = __require("tty");
8447
8447
  var hasFlag = require_has_flag();
8448
8448
  var { env } = process;
@@ -8499,7 +8499,7 @@ var require_supports_color = __commonJS({
8499
8499
  return min;
8500
8500
  }
8501
8501
  if (process.platform === "win32") {
8502
- const osRelease = os7.release().split(".");
8502
+ const osRelease = os6.release().split(".");
8503
8503
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
8504
8504
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
8505
8505
  }
@@ -32320,7 +32320,7 @@ var require_cross_spawn = __commonJS({
32320
32320
  enoent.hookChildProcess(spawned, parsed);
32321
32321
  return spawned;
32322
32322
  }
32323
- function spawnSync2(command2, args2, options) {
32323
+ function spawnSync(command2, args2, options) {
32324
32324
  const parsed = parse3(command2, args2, options);
32325
32325
  const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
32326
32326
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
@@ -32328,7 +32328,7 @@ var require_cross_spawn = __commonJS({
32328
32328
  }
32329
32329
  module2.exports = spawn4;
32330
32330
  module2.exports.spawn = spawn4;
32331
- module2.exports.sync = spawnSync2;
32331
+ module2.exports.sync = spawnSync;
32332
32332
  module2.exports._parse = parse3;
32333
32333
  module2.exports._enoent = enoent;
32334
32334
  }
@@ -33933,7 +33933,7 @@ var require_service_config = __commonJS({
33933
33933
  exports2.validateRetryThrottling = validateRetryThrottling;
33934
33934
  exports2.validateServiceConfig = validateServiceConfig;
33935
33935
  exports2.extractAndSelectServiceConfig = extractAndSelectServiceConfig;
33936
- var os7 = __require("os");
33936
+ var os6 = __require("os");
33937
33937
  var constants_1 = require_constants();
33938
33938
  var DURATION_REGEX = /^\d+(\.\d{1,9})?s$/;
33939
33939
  var CLIENT_LANGUAGE_STRING = "node";
@@ -34232,7 +34232,7 @@ var require_service_config = __commonJS({
34232
34232
  if (Array.isArray(validatedConfig.clientHostname)) {
34233
34233
  let hostnameMatched = false;
34234
34234
  for (const hostname3 of validatedConfig.clientHostname) {
34235
- if (hostname3 === os7.hostname()) {
34235
+ if (hostname3 === os6.hostname()) {
34236
34236
  hostnameMatched = true;
34237
34237
  }
34238
34238
  }
@@ -48781,7 +48781,7 @@ var require_subchannel_call = __commonJS({
48781
48781
  Object.defineProperty(exports2, "__esModule", { value: true });
48782
48782
  exports2.Http2SubchannelCall = void 0;
48783
48783
  var http2 = __require("http2");
48784
- var os7 = __require("os");
48784
+ var os6 = __require("os");
48785
48785
  var constants_1 = require_constants();
48786
48786
  var metadata_1 = require_metadata2();
48787
48787
  var stream_decoder_1 = require_stream_decoder();
@@ -48789,7 +48789,7 @@ var require_subchannel_call = __commonJS({
48789
48789
  var constants_2 = require_constants();
48790
48790
  var TRACER_NAME = "subchannel_call";
48791
48791
  function getSystemErrorName(errno) {
48792
- for (const [name, num] of Object.entries(os7.constants.errno)) {
48792
+ for (const [name, num] of Object.entries(os6.constants.errno)) {
48793
48793
  if (num === errno) {
48794
48794
  return name;
48795
48795
  }
@@ -93824,7 +93824,7 @@ var require_client_metadata = __commonJS({
93824
93824
  exports2.makeClientMetadata = makeClientMetadata;
93825
93825
  exports2.addContainerMetadata = addContainerMetadata;
93826
93826
  exports2.getFAASEnv = getFAASEnv;
93827
- var os7 = __require("os");
93827
+ var os6 = __require("os");
93828
93828
  var process5 = __require("process");
93829
93829
  var bson_1 = require_bson2();
93830
93830
  var error_1 = require_error2();
@@ -93894,7 +93894,7 @@ var require_client_metadata = __commonJS({
93894
93894
  if (!metadataDocument.ifItFitsItSits("platform", runtimeInfo)) {
93895
93895
  throw new error_1.MongoInvalidArgumentError("Unable to include driverInfo platform, metadata cannot exceed 512 bytes");
93896
93896
  }
93897
- const osInfo = (/* @__PURE__ */ new Map()).set("name", process5.platform).set("architecture", process5.arch).set("version", os7.release()).set("type", os7.type());
93897
+ const osInfo = (/* @__PURE__ */ new Map()).set("name", process5.platform).set("architecture", process5.arch).set("version", os6.release()).set("type", os6.type());
93898
93898
  if (!metadataDocument.ifItFitsItSits("os", osInfo)) {
93899
93899
  for (const key of osInfo.keys()) {
93900
93900
  osInfo.delete(key);
@@ -93997,13 +93997,13 @@ var require_client_metadata = __commonJS({
93997
93997
  function getRuntimeInfo() {
93998
93998
  if ("Deno" in globalThis) {
93999
93999
  const version2 = typeof Deno?.version?.deno === "string" ? Deno?.version?.deno : "0.0.0-unknown";
94000
- return `Deno v${version2}, ${os7.endianness()}`;
94000
+ return `Deno v${version2}, ${os6.endianness()}`;
94001
94001
  }
94002
94002
  if ("Bun" in globalThis) {
94003
94003
  const version2 = typeof Bun?.version === "string" ? Bun?.version : "0.0.0-unknown";
94004
- return `Bun v${version2}, ${os7.endianness()}`;
94004
+ return `Bun v${version2}, ${os6.endianness()}`;
94005
94005
  }
94006
- return `Node.js ${process5.version}, ${os7.endianness()}`;
94006
+ return `Node.js ${process5.version}, ${os6.endianness()}`;
94007
94007
  }
94008
94008
  }
94009
94009
  });
@@ -131059,8 +131059,7 @@ function getInstalledEntry(slug, registry2 = "default") {
131059
131059
 
131060
131060
  // src/lib/version.ts
131061
131061
  init_esm_shims();
131062
- var VERSION = true ? "0.2.0" : "0.0.0-dev";
131063
- var PKG_NAME = true ? "@rapidthoughtlabs/heku" : "@rapidthoughtlabs/heku";
131062
+ var VERSION = true ? "0.3.0" : "0.0.0-dev";
131064
131063
 
131065
131064
  // src/connectors/mcp.ts
131066
131065
  init_esm_shims();
@@ -134326,6 +134325,8 @@ var ToolRegistry = class {
134326
134325
  }
134327
134326
  this.tools.set(qualifiedName, {
134328
134327
  configId: config2.id,
134328
+ configName: config2.name,
134329
+ configDescription: config2.description,
134329
134330
  connectorConfig: config2.connector,
134330
134331
  tool
134331
134332
  });
@@ -134373,6 +134374,7 @@ var BLOCKED_WHEN_LOCKED = /* @__PURE__ */ new Set([
134373
134374
  "one.remove_tool",
134374
134375
  "one.update_tool",
134375
134376
  "one.registry_install",
134377
+ "one.registry_update",
134376
134378
  "one.auth_set"
134377
134379
  ]);
134378
134380
  function blockedTool(qualifiedName, args2) {
@@ -134393,7 +134395,7 @@ function makeServer(registry2, transportCtx) {
134393
134395
  const { visible, rewrite } = buildManifestView(manifestStyle, registry2);
134394
134396
  const tools = visible.map((rt) => ({
134395
134397
  name: rewrite(`${rt.configId}.${rt.tool.name}`),
134396
- description: `[${rt.configId}] ${rt.tool.description}`,
134398
+ description: manifestStyle === "flat" ? rt.tool.description : `[${rt.configId}] ${rt.tool.description}`,
134397
134399
  inputSchema: buildInputSchema(rt.tool.params)
134398
134400
  }));
134399
134401
  return { tools };
@@ -137822,6 +137824,9 @@ async function getConfigMeta(namespace, slug, connectorType, registryName = "def
137822
137824
  const slugWithConnector = connectorType ? `${slug}:${connectorType}` : slug;
137823
137825
  return jsonFetch(`/configs/${namespace}/${slugWithConnector}`, { registryName });
137824
137826
  }
137827
+ async function listVersions(namespace, slug, registryName = "default") {
137828
+ return jsonFetch(`/configs/${namespace}/${slug}/versions`, { registryName });
137829
+ }
137825
137830
  async function fetchVersionPayload(namespace, slug, connectorType, version2, registryName = "default") {
137826
137831
  const slugWithConnector = connectorType ? `${slug}:${connectorType}` : slug;
137827
137832
  const vPath = version2 ?? "latest";
@@ -137982,7 +137987,7 @@ function createRegistryRouter() {
137982
137987
  const slug = colonIdx !== -1 ? rest.slice(0, colonIdx) : rest;
137983
137988
  const connectorType = colonIdx !== -1 ? rest.slice(colonIdx + 1) : void 0;
137984
137989
  const meta3 = await getConfigMeta(namespace, slug, connectorType, registry2);
137985
- res.json({ category: meta3.category, tags: meta3.tags });
137990
+ res.json({ category: meta3.category, tags: meta3.tags, latest_version: meta3.latest_version ?? null });
137986
137991
  }));
137987
137992
  router.post("/check-updates", wrap(async (req, res) => {
137988
137993
  const { installed, registry: registry2 = "default" } = req.body;
@@ -138094,6 +138099,12 @@ function createRegistryRouter() {
138094
138099
  const { payload } = await fetchVersionPayload(namespace, slug, connector_type, void 0, registry2);
138095
138100
  res.json(payload);
138096
138101
  }));
138102
+ router.get("/versions/:namespace/:slug", wrap(async (req, res) => {
138103
+ const { namespace, slug } = req.params;
138104
+ const registry2 = req.query["registry"] ?? "default";
138105
+ const versions = await listVersions(namespace, slug, registry2);
138106
+ res.json(versions);
138107
+ }));
138097
138108
  router.delete("/uninstall/:id", wrap(async (req, res) => {
138098
138109
  const id = req.params["id"];
138099
138110
  const registry2 = req.query["registry"] ?? "default";
@@ -138206,12 +138217,12 @@ var INTERNAL_CONFIG = {
138206
138217
  },
138207
138218
  {
138208
138219
  name: "list_configs",
138209
- description: "**Start here.** Returns the names of all active configs as a flat array. Config IDs follow the pattern `<base>-<connector>` where connector is one of: http, cli, file, grpc, graphql, mcp, sql, mongodb. Use these names to scope `one.search` or `one.list_tools`.",
138220
+ description: "**Start here.** Returns all active configs as an array of objects with `id`, `name`, and `description`. Config IDs follow the pattern `<base>-<connector>` (e.g. `github-http`, `linear-graphql`). Use the description to pick the right config, then pass its `id` to `search` or `list_tools`.",
138210
138221
  params: []
138211
138222
  },
138212
138223
  {
138213
138224
  name: "search",
138214
- description: "Find tools by name or intent across all configs (or within a specific one). Returns matching tools with full schemas grouped by match quality: exact name match first, then partial, then description, then related. Indexes both native `one.*` self-management tools and all service tools. Pass config for exact-first matching: 'github-http' returns only github-http tools; 'github' matches both github-http and github-cli.",
138225
+ description: "Find tools by name or intent across all configs (or within a specific one). Returns matching tools with full schemas grouped by quality: exact \u2192 partial \u2192 description \u2192 related. Covers both native `one.*` self-management tools and all loaded service tools. Narrow by config: `github-http` returns only github-http tools; `github` matches github-http and github-cli. Once you have a tool name, call `invoke` with `config_id.tool_name` to execute it.",
138215
138226
  params: [
138216
138227
  {
138217
138228
  name: "query",
@@ -138229,7 +138240,7 @@ var INTERNAL_CONFIG = {
138229
138240
  },
138230
138241
  {
138231
138242
  name: "invoke",
138232
- description: "Execute any registered tool by its qualified name (config_id.tool_name). Use after one.search or one.list_tools to run the tool you found. Works for all service tools (open-meteo-http.get_forecast, github-http.create_issue, etc.) and all one.* self-management tools.",
138243
+ description: "Execute any registered tool by its qualified name (`config_id.tool_name`). Workflow: (1) list_configs \u2192 pick a config id, (2) search or list_tools \u2192 get the tool name and required args, (3) invoke \u2192 run it. Examples: `github-http.create_issue`, `linear-graphql.create_issue`, `one.server_status`.",
138233
138244
  params: [
138234
138245
  {
138235
138246
  name: "tool",
@@ -138345,6 +138356,14 @@ var INTERNAL_CONFIG = {
138345
138356
  { name: "registry", type: "string", required: false, description: "Registry source name (default: 'default')" }
138346
138357
  ]
138347
138358
  },
138359
+ {
138360
+ name: "registry_update",
138361
+ description: "Update registry-installed configs to the latest available version. Preserves local credentials (connector.env) and overlays. Pass config_id to update a single config, or omit to update all installed configs.",
138362
+ params: [
138363
+ { name: "config_id", type: "string", required: false, description: "ID of the config to update (e.g. 'github-http'). Omit to update all installed configs." },
138364
+ { name: "registry", type: "string", required: false, description: "Registry source name (default: 'default')" }
138365
+ ]
138366
+ },
138348
138367
  {
138349
138368
  name: "auth_status",
138350
138369
  description: "Check which configs have missing or misconfigured credentials. Returns per-config auth status and the specific env vars that are missing.",
@@ -141195,7 +141214,17 @@ async function handleGetConfig(ctx, args2) {
141195
141214
  return { success: true, data: raw };
141196
141215
  }
141197
141216
  async function handleListConfigs(ctx, _args) {
141198
- const configs = [...new Set(ctx.registry.list().map((rt) => rt.configId))].sort();
141217
+ const seen = /* @__PURE__ */ new Map();
141218
+ for (const rt of ctx.registry.list()) {
141219
+ if (!seen.has(rt.configId)) {
141220
+ seen.set(rt.configId, {
141221
+ id: rt.configId,
141222
+ ...rt.configName && { name: rt.configName },
141223
+ ...rt.configDescription && { description: rt.configDescription }
141224
+ });
141225
+ }
141226
+ }
141227
+ const configs = [...seen.values()].sort((a, b) => a.id.localeCompare(b.id));
141199
141228
  return { success: true, data: { configs } };
141200
141229
  }
141201
141230
  async function handleUpdateConfig(ctx, args2) {
@@ -141633,6 +141662,112 @@ async function handleRegistryInstall(ctx, args2) {
141633
141662
  return { success: false, data: { error: err.message } };
141634
141663
  }
141635
141664
  }
141665
+ async function handleRegistryUpdate(ctx, args2) {
141666
+ const configId = args2.config_id;
141667
+ const registry2 = args2.registry ?? "default";
141668
+ const { installed } = loadManifest();
141669
+ const forRegistry = installed.filter((e) => e.registry === registry2);
141670
+ let toCheck;
141671
+ if (configId) {
141672
+ const entry = forRegistry.find((e) => {
141673
+ const withoutNs = e.slug.replace(/^@[^/]+\//, "");
141674
+ const colonIdx = withoutNs.indexOf(":");
141675
+ if (colonIdx === -1) return false;
141676
+ const base2 = withoutNs.slice(0, colonIdx);
141677
+ const ct = withoutNs.slice(colonIdx + 1);
141678
+ return `${base2}-${ct}` === configId;
141679
+ });
141680
+ if (!entry) {
141681
+ return {
141682
+ success: false,
141683
+ data: { error: `"${configId}" is not a registry-installed config or was not found in the manifest. Only configs installed from the registry can be updated this way.` }
141684
+ };
141685
+ }
141686
+ toCheck = [entry];
141687
+ } else {
141688
+ if (forRegistry.length === 0) {
141689
+ return { success: true, data: { message: "No registry-installed configs to update", updated: [], up_to_date: [] } };
141690
+ }
141691
+ toCheck = forRegistry;
141692
+ }
141693
+ let updateCheck;
141694
+ try {
141695
+ updateCheck = await checkUpdates(
141696
+ toCheck.map((e) => ({ slug: e.slug, version: e.version })),
141697
+ registry2
141698
+ );
141699
+ } catch (err) {
141700
+ if (err instanceof RegistryError) {
141701
+ return { success: false, data: { error: err.message, status: err.status } };
141702
+ }
141703
+ return { success: false, data: { error: err.message } };
141704
+ }
141705
+ if (updateCheck.updates.length === 0) {
141706
+ return {
141707
+ success: true,
141708
+ data: { message: "All configs are up to date", up_to_date: updateCheck.up_to_date }
141709
+ };
141710
+ }
141711
+ const updated = [];
141712
+ const errors2 = [];
141713
+ for (const info of updateCheck.updates) {
141714
+ const withoutAt = info.slug.startsWith("@") ? info.slug.slice(1) : info.slug;
141715
+ const slashIdx = withoutAt.indexOf("/");
141716
+ if (slashIdx === -1) continue;
141717
+ const namespace = withoutAt.slice(0, slashIdx);
141718
+ const rest = withoutAt.slice(slashIdx + 1);
141719
+ const colonIdx = rest.indexOf(":");
141720
+ const rawSlug = colonIdx !== -1 ? rest.slice(0, colonIdx) : rest;
141721
+ const connType = colonIdx !== -1 ? rest.slice(colonIdx + 1) : void 0;
141722
+ const installedId = `${rawSlug}-${connType ?? ""}`;
141723
+ const outFile = path17.join(ctx.configDir, `mcp.${installedId}.json`);
141724
+ try {
141725
+ const { payload, version: resolvedVersion } = await fetchVersionPayload(
141726
+ namespace,
141727
+ rawSlug,
141728
+ connType,
141729
+ info.latest_version,
141730
+ registry2
141731
+ );
141732
+ const payloadObj = payload;
141733
+ payloadObj.id = installedId;
141734
+ if (fs15.existsSync(outFile)) {
141735
+ try {
141736
+ const existing = JSON.parse(fs15.readFileSync(outFile, "utf-8"));
141737
+ const existingConnector = existing.connector;
141738
+ if (existingConnector?.env) {
141739
+ const newConnector = payloadObj.connector ?? {};
141740
+ newConnector.env = existingConnector.env;
141741
+ payloadObj.connector = newConnector;
141742
+ }
141743
+ const existingOverlays = existing.overlays ?? {};
141744
+ const newOverlays = payloadObj.overlays ?? {};
141745
+ payloadObj.overlays = { ...existingOverlays, ...newOverlays };
141746
+ } catch {
141747
+ }
141748
+ }
141749
+ fs15.mkdirSync(ctx.configDir, { recursive: true });
141750
+ fs15.writeFileSync(outFile, JSON.stringify(payloadObj, null, 2) + "\n", "utf-8");
141751
+ addToManifest(info.slug, resolvedVersion, connType ?? "", registry2);
141752
+ updated.push({ slug: info.slug, id: installedId, from: info.installed_version, to: resolvedVersion });
141753
+ } catch (err) {
141754
+ if (err instanceof RegistryError) {
141755
+ errors2.push({ slug: info.slug, error: err.message });
141756
+ } else {
141757
+ errors2.push({ slug: info.slug, error: err.message });
141758
+ }
141759
+ }
141760
+ }
141761
+ return {
141762
+ success: errors2.length === 0,
141763
+ data: {
141764
+ message: updated.length > 0 ? `Updated ${updated.length} config(s).${errors2.length > 0 ? ` ${errors2.length} failed.` : ""} Server will hot-reload automatically.` : `All configs are up to date.`,
141765
+ updated,
141766
+ up_to_date: updateCheck.up_to_date,
141767
+ ...errors2.length > 0 && { errors: errors2 }
141768
+ }
141769
+ };
141770
+ }
141636
141771
  async function handleRegistryCheckUpdates(_ctx, args2) {
141637
141772
  const registry2 = args2.registry ?? "default";
141638
141773
  try {
@@ -141927,6 +142062,7 @@ var HANDLERS = {
141927
142062
  registry_browse: handleRegistryBrowse,
141928
142063
  registry_install: handleRegistryInstall,
141929
142064
  registry_check_updates: handleRegistryCheckUpdates,
142065
+ registry_update: handleRegistryUpdate,
141930
142066
  // Auth
141931
142067
  auth_status: handleAuthStatus,
141932
142068
  auth_set: handleAuthSet,
@@ -144088,159 +144224,195 @@ async function run8(args2) {
144088
144224
 
144089
144225
  // src/commands/update.ts
144090
144226
  init_esm_shims();
144091
- import { spawnSync } from "child_process";
144092
-
144093
- // src/lib/update-check.ts
144094
- init_esm_shims();
144095
- import https from "https";
144096
144227
  import fs22 from "fs";
144097
144228
  import path23 from "path";
144098
- import os6 from "os";
144099
- var CACHE_FILE = path23.join(os6.homedir(), ".heku", "update-check.json");
144100
- var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
144101
- function readCache() {
144102
- try {
144103
- return JSON.parse(fs22.readFileSync(CACHE_FILE, "utf-8"));
144104
- } catch {
144105
- return null;
144106
- }
144107
- }
144108
- function writeCache(latestVersion) {
144109
- try {
144110
- fs22.mkdirSync(path23.dirname(CACHE_FILE), { recursive: true });
144111
- fs22.writeFileSync(
144112
- CACHE_FILE,
144113
- JSON.stringify({ checkedAt: Date.now(), latestVersion }),
144114
- "utf-8"
144115
- );
144116
- } catch {
144117
- }
144118
- }
144119
- function fetchLatestVersion() {
144120
- return new Promise((resolve3, reject) => {
144121
- const req = https.get(
144122
- `https://registry.npmjs.org/${PKG_NAME}/latest`,
144123
- { headers: { Accept: "application/json" } },
144124
- (res) => {
144125
- let data = "";
144126
- res.on("data", (chunk) => {
144127
- data += chunk;
144128
- });
144129
- res.on("end", () => {
144130
- try {
144131
- if (res.statusCode !== 200) {
144132
- reject(new Error(`npm registry returned HTTP ${res.statusCode}`));
144133
- return;
144134
- }
144135
- const json2 = JSON.parse(data);
144136
- if (!json2.version) {
144137
- reject(new Error("npm registry response missing version field"));
144138
- return;
144139
- }
144140
- resolve3(json2.version);
144141
- } catch (e) {
144142
- reject(e);
144143
- }
144144
- });
144145
- }
144146
- );
144147
- req.on("error", reject);
144148
- req.setTimeout(5e3, () => {
144149
- req.destroy();
144150
- reject(new Error("timeout"));
144151
- });
144152
- });
144153
- }
144154
- function semverGt(a, b) {
144155
- const parse3 = (v) => v.replace(/[^0-9.]/g, "").split(".").map(Number);
144156
- const pa = parse3(a);
144157
- const pb = parse3(b);
144158
- for (let i = 0; i < 3; i++) {
144159
- const na = pa[i] ?? 0;
144160
- const nb = pb[i] ?? 0;
144161
- if (na > nb) return true;
144162
- if (na < nb) return false;
144163
- }
144229
+ function compoundId2(entry) {
144230
+ const withoutNs = entry.slug.replace(/^@[^/]+\//, "");
144231
+ const colonIdx = withoutNs.indexOf(":");
144232
+ const rawSlug = colonIdx !== -1 ? withoutNs.slice(0, colonIdx) : withoutNs;
144233
+ const ct = colonIdx !== -1 ? withoutNs.slice(colonIdx + 1) : entry.connector_type;
144234
+ return `${rawSlug}-${ct}`;
144235
+ }
144236
+ function matchesTarget(entry, target) {
144237
+ const slug = entry.slug;
144238
+ if (slug === target || slug === `@${target}`) return true;
144239
+ const withoutNs = slug.replace(/^@[^/]+\//, "");
144240
+ const colonIdx = withoutNs.indexOf(":");
144241
+ const rawSlug = colonIdx !== -1 ? withoutNs.slice(0, colonIdx) : withoutNs;
144242
+ const ct = colonIdx !== -1 ? withoutNs.slice(colonIdx + 1) : "";
144243
+ if (`${rawSlug}-${ct}` === target) return true;
144244
+ if (withoutNs === target) return true;
144245
+ const withoutConnector = slug.replace(/:.*$/, "");
144246
+ if (withoutConnector === target || withoutConnector === `@${target}`) return true;
144247
+ if (!target.includes(":") && !target.includes("-") && rawSlug === target) return true;
144164
144248
  return false;
144165
144249
  }
144166
- function printUpdateNotice(latestVersion) {
144167
- const line1 = ` Update available: ${VERSION} \u2192 ${latestVersion}`;
144168
- const line2 = ` Run: npm install -g ${PKG_NAME}@latest`;
144169
- const width = Math.max(line1.length, line2.length) + 2;
144170
- const bar = "\u2500".repeat(width);
144171
- process.stderr.write(
144172
- `
144173
- \u250C${bar}\u2510
144174
- \u2502${line1.padEnd(width)}\u2502
144175
- \u2502${line2.padEnd(width)}\u2502
144176
- \u2514${bar}\u2518
144177
-
144178
- `
144250
+ async function downloadAndInstall(entry, latestVersion, configDir) {
144251
+ const withoutAt = entry.slug.startsWith("@") ? entry.slug.slice(1) : entry.slug;
144252
+ const slashIdx = withoutAt.indexOf("/");
144253
+ const namespace = withoutAt.slice(0, slashIdx);
144254
+ const rest = withoutAt.slice(slashIdx + 1);
144255
+ const colonIdx = rest.indexOf(":");
144256
+ const rawSlug = colonIdx !== -1 ? rest.slice(0, colonIdx) : rest;
144257
+ const connType = colonIdx !== -1 ? rest.slice(colonIdx + 1) : entry.connector_type;
144258
+ const id = `${rawSlug}-${connType}`;
144259
+ const outFile = path23.join(configDir, `mcp.${id}.json`);
144260
+ const { payload, version: resolvedVersion } = await fetchVersionPayload(
144261
+ namespace,
144262
+ rawSlug,
144263
+ connType,
144264
+ latestVersion,
144265
+ entry.registry
144179
144266
  );
144180
- }
144181
- function checkForUpdate() {
144182
- const cache2 = readCache();
144183
- if (cache2 && typeof cache2.latestVersion === "string" && cache2.latestVersion.length > 0 && Date.now() - cache2.checkedAt < CACHE_TTL_MS) {
144184
- if (semverGt(cache2.latestVersion, VERSION)) {
144185
- printUpdateNotice(cache2.latestVersion);
144267
+ const payloadObj = payload;
144268
+ payloadObj.id = id;
144269
+ if (fs22.existsSync(outFile)) {
144270
+ try {
144271
+ const existing = JSON.parse(fs22.readFileSync(outFile, "utf-8"));
144272
+ const existingConn = existing.connector;
144273
+ if (existingConn?.env) {
144274
+ const newConn = payloadObj.connector ?? {};
144275
+ newConn.env = existingConn.env;
144276
+ payloadObj.connector = newConn;
144277
+ }
144278
+ const existingOverlays = existing.overlays ?? {};
144279
+ const newOverlays = payloadObj.overlays ?? {};
144280
+ payloadObj.overlays = { ...existingOverlays, ...newOverlays };
144281
+ } catch {
144186
144282
  }
144283
+ }
144284
+ fs22.mkdirSync(configDir, { recursive: true });
144285
+ fs22.writeFileSync(outFile, JSON.stringify(payloadObj, null, 2) + "\n", "utf-8");
144286
+ addToManifest(entry.slug, resolvedVersion, connType, entry.registry, entry.forked_from ?? null);
144287
+ return { id, from: entry.version, to: resolvedVersion };
144288
+ }
144289
+ async function run9(args2) {
144290
+ const registryIdx = args2.indexOf("--registry");
144291
+ const registry2 = registryIdx !== -1 ? args2[registryIdx + 1] ?? "default" : "default";
144292
+ const skipIndices = /* @__PURE__ */ new Set();
144293
+ if (registryIdx !== -1) {
144294
+ skipIndices.add(registryIdx);
144295
+ skipIndices.add(registryIdx + 1);
144296
+ }
144297
+ const target = args2.find((a, i) => !skipIndices.has(i) && !a.startsWith("--"));
144298
+ const systemConfig = loadSystemConfig(process.cwd());
144299
+ const configDir = resolveConfigDir(void 0, systemConfig);
144300
+ const { installed } = loadManifest();
144301
+ const forRegistry = installed.filter((e) => e.registry === registry2);
144302
+ if (forRegistry.length === 0) {
144303
+ console.log();
144304
+ console.log(dim(" No registry-installed configs found."));
144305
+ console.log(dim(` Install one with: ${bold("heku install @ns/slug")}`));
144306
+ console.log();
144187
144307
  return;
144188
144308
  }
144189
- fetchLatestVersion().then((latest) => {
144190
- writeCache(latest);
144191
- if (semverGt(latest, VERSION)) {
144192
- printUpdateNotice(latest);
144309
+ let toCheck;
144310
+ if (target) {
144311
+ toCheck = forRegistry.filter((e) => matchesTarget(e, target));
144312
+ if (toCheck.length === 0) {
144313
+ console.log();
144314
+ console.error(red("\u2717") + ` No installed config matches "${bold(target)}".`);
144315
+ console.error(dim(` Run ${bold("heku list")} to see installed configs.`));
144316
+ console.log();
144317
+ process.exit(1);
144193
144318
  }
144194
- }).catch(() => {
144195
- });
144196
- }
144197
-
144198
- // src/commands/update.ts
144199
- async function run9(_args) {
144200
- process.stderr.write(` Checking for updates to ${bold(PKG_NAME)}...
144201
- `);
144202
- let latest;
144319
+ } else {
144320
+ toCheck = forRegistry;
144321
+ }
144322
+ console.log();
144323
+ if (target) {
144324
+ console.log(bold(` Checking ${cyan(target)}...`));
144325
+ } else {
144326
+ console.log(bold(" Checking for updates..."));
144327
+ }
144328
+ console.log();
144329
+ let updateResult;
144203
144330
  try {
144204
- latest = await fetchLatestVersion();
144205
- } catch {
144206
- process.stderr.write(` ${yellow("\u26A0")} Could not reach npm registry. Check your network and try again.
144207
- `);
144331
+ updateResult = await checkUpdates(
144332
+ toCheck.map((e) => ({ slug: e.slug, version: e.version })),
144333
+ registry2
144334
+ );
144335
+ } catch (err) {
144336
+ console.error(
144337
+ red("\u2717") + ` ${err instanceof RegistryError ? err.message : err.message}`
144338
+ );
144208
144339
  process.exit(1);
144209
144340
  }
144210
- if (!semverGt(latest, VERSION)) {
144211
- process.stderr.write(` ${green("\u2713")} Already up to date (${cyan(VERSION)})
144212
- `);
144341
+ const { updates, up_to_date, deprecated } = updateResult;
144342
+ const colWidth = Math.max(
144343
+ ...[...updates, ...up_to_date, ...deprecated].map((u) => {
144344
+ const entry = toCheck.find((e) => e.slug === u.slug);
144345
+ return entry ? compoundId2(entry).length : 0;
144346
+ }),
144347
+ 12
144348
+ );
144349
+ for (const u of up_to_date) {
144350
+ const entry = toCheck.find((e) => e.slug === u.slug);
144351
+ const id = entry ? compoundId2(entry) : u.slug;
144352
+ console.log(` ${dim(id.padEnd(colWidth))} ${dim(`v${u.version}`)} ${dim("up to date")}`);
144353
+ }
144354
+ for (const u of updates) {
144355
+ const entry = toCheck.find((e) => e.slug === u.slug);
144356
+ const id = entry ? compoundId2(entry) : u.slug;
144357
+ const badge = u.breaking ? yellow(`${u.severity} \xB7 breaking`) : cyan(u.severity);
144358
+ console.log(
144359
+ ` ${bold(id.padEnd(colWidth))} ${dim(`v${u.installed_version}`)} \u2192 ${green(`v${u.latest_version}`)} [${badge}]`
144360
+ );
144361
+ }
144362
+ for (const d of deprecated) {
144363
+ const entry = toCheck.find((e) => e.slug === d.slug);
144364
+ const id = entry ? compoundId2(entry) : d.slug;
144365
+ const note = d.replacement ? ` \u2192 use ${cyan(d.replacement)}` : "";
144366
+ console.log(` ${yellow(id.padEnd(colWidth))} ${dim(`v${d.installed_version}`)} ${yellow("deprecated")}${note}`);
144367
+ }
144368
+ console.log();
144369
+ if (updates.length === 0) {
144370
+ console.log(green("\u2713") + (target ? ` ${cyan(target)} is up to date.` : " All configs are up to date."));
144371
+ if (deprecated.length > 0) {
144372
+ console.log(yellow("\u26A0") + ` ${deprecated.length} deprecated \u2014 consider replacing them.`);
144373
+ }
144374
+ console.log();
144213
144375
  return;
144214
144376
  }
144215
- process.stderr.write(` ${bold(VERSION)} \u2192 ${bold(latest)}
144216
- `);
144217
- process.stderr.write(` Running: npm install -g ${PKG_NAME}@latest
144218
-
144219
- `);
144220
- const result = spawnSync("npm", ["install", "-g", `${PKG_NAME}@latest`], {
144221
- stdio: "inherit",
144222
- shell: true
144223
- });
144224
- if (result.status !== 0) {
144225
- process.stderr.write(
144226
- `
144227
- Update failed. You can update manually:
144228
- npm install -g ${PKG_NAME}@latest
144229
- `
144230
- );
144231
- process.exit(result.status ?? 1);
144377
+ const noun = updates.length === 1 ? "config" : "configs";
144378
+ console.log(bold(` Updating ${updates.length} ${noun}...`));
144379
+ console.log();
144380
+ const entryBySlug = new Map(toCheck.map((e) => [e.slug, e]));
144381
+ let successCount = 0;
144382
+ let failCount = 0;
144383
+ for (const u of updates) {
144384
+ const entry = entryBySlug.get(u.slug);
144385
+ if (!entry) continue;
144386
+ const id = compoundId2(entry);
144387
+ process.stdout.write(` ${bold(id.padEnd(colWidth))} Downloading... `);
144388
+ try {
144389
+ const result = await downloadAndInstall(entry, u.latest_version, configDir);
144390
+ console.log(green("done") + ` ${dim(`${result.from} \u2192 ${result.to}`)}`);
144391
+ successCount++;
144392
+ } catch (err) {
144393
+ console.log(red("failed"));
144394
+ const msg = err instanceof RegistryError ? err.message : err.message;
144395
+ console.error(` ${"".padEnd(colWidth)} ${red("\u2717")} ${msg}`);
144396
+ failCount++;
144397
+ }
144232
144398
  }
144233
- process.stderr.write(`
144234
- ${green("\u2713")} Updated to ${bold(latest)}
144235
- `);
144399
+ console.log();
144400
+ if (failCount === 0) {
144401
+ const upToDateNote = up_to_date.length > 0 ? ` ${dim(`${up_to_date.length} already up to date.`)}` : "";
144402
+ console.log(green("\u2713") + ` ${successCount} ${noun} updated.${upToDateNote}`);
144403
+ } else if (successCount > 0) {
144404
+ console.log(yellow("\u26A0") + ` ${successCount} updated, ${failCount} failed.`);
144405
+ } else {
144406
+ console.log(red("\u2717") + ` All updates failed.`);
144407
+ process.exit(1);
144408
+ }
144409
+ console.log(dim(" Restart or reload heku to apply changes."));
144410
+ console.log();
144236
144411
  }
144237
144412
 
144238
144413
  // src/cli.ts
144239
144414
  var args = process.argv.slice(2);
144240
144415
  var command = args[0] ?? "start";
144241
- if (command !== "update") {
144242
- checkForUpdate();
144243
- }
144244
144416
  switch (command) {
144245
144417
  case "start":
144246
144418
  await run(args.slice(1));
@@ -144296,11 +144468,12 @@ function printUsage() {
144296
144468
  heku auth setup [service] Interactive credential setup wizard
144297
144469
  heku install <target> Install a config from the registry
144298
144470
  heku uninstall <target> Remove an installed registry config
144471
+ heku update Update all installed configs to latest versions
144472
+ heku update <config> Update a specific config (e.g. github-http or @ns/github:http)
144299
144473
  heku login Log in to the mcp.rtl.space registry
144300
144474
  heku logout Log out of the registry
144301
144475
  heku publish [file] Publish a config to the registry
144302
144476
  heku fork <namespace/slug> Fork a registry config into your namespace
144303
- heku update Update heku to the latest version
144304
144477
  heku help Show this message
144305
144478
 
144306
144479
  HTTP mode:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidthoughtlabs/heku",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A single dynamic MCP server that turns JSON configs into working API tools",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",