claudemesh-cli 0.7.0 → 0.8.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 +611 -31
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -47478,6 +47478,10 @@ var TOOLS = [
47478
47478
  required: ["name", "description", "inputSchema"]
47479
47479
  },
47480
47480
  description: "Tool definitions to expose"
47481
+ },
47482
+ persistent: {
47483
+ type: "boolean",
47484
+ description: "If true, registration survives peer disconnect. Other peers see it as 'offline' until you reconnect. Default: false"
47481
47485
  }
47482
47486
  },
47483
47487
  required: ["server_name", "description", "tools"]
@@ -47659,6 +47663,75 @@ var TOOLS = [
47659
47663
  },
47660
47664
  required: ["name"]
47661
47665
  }
47666
+ },
47667
+ {
47668
+ name: "mesh_mcp_deploy",
47669
+ description: "Deploy an MCP server to the mesh from a zip file or git repo. Runs on the broker VPS, persists across peer sessions. Default scope: private (only you).",
47670
+ inputSchema: {
47671
+ type: "object",
47672
+ properties: {
47673
+ server_name: { type: "string", description: "Unique name for the server in this mesh" },
47674
+ file_id: { type: "string", description: "File ID of uploaded zip (from share_file)" },
47675
+ git_url: { type: "string", description: "Git repo URL" },
47676
+ git_branch: { type: "string", description: "Branch to clone (default: main)" },
47677
+ env: { type: "object", description: "Environment variables. Use $vault:<key> for vault secrets." },
47678
+ runtime: { type: "string", enum: ["node", "python", "bun"], description: "Runtime (auto-detected if omitted)" },
47679
+ memory_mb: { type: "number", description: "Memory limit in MB (default: 256)" },
47680
+ network_allow: { type: "array", items: { type: "string" }, description: "Allowed outbound hosts (default: none)" },
47681
+ scope: { description: "Visibility: 'peer' (default), 'mesh', or {group/groups/role/peers}" }
47682
+ },
47683
+ required: ["server_name"]
47684
+ }
47685
+ },
47686
+ {
47687
+ name: "mesh_mcp_undeploy",
47688
+ description: "Stop and remove a managed MCP server from the mesh.",
47689
+ inputSchema: { type: "object", properties: { server_name: { type: "string" } }, required: ["server_name"] }
47690
+ },
47691
+ {
47692
+ name: "mesh_mcp_update",
47693
+ description: "Pull latest code and restart a git-sourced MCP server.",
47694
+ inputSchema: { type: "object", properties: { server_name: { type: "string" } }, required: ["server_name"] }
47695
+ },
47696
+ {
47697
+ name: "mesh_mcp_logs",
47698
+ description: "View recent logs from a managed MCP server.",
47699
+ inputSchema: { type: "object", properties: { server_name: { type: "string" }, lines: { type: "number", description: "Lines (default: 50, max: 1000)" } }, required: ["server_name"] }
47700
+ },
47701
+ {
47702
+ name: "mesh_mcp_scope",
47703
+ description: "Get or set the visibility scope of a deployed MCP server.",
47704
+ inputSchema: { type: "object", properties: { server_name: { type: "string" }, scope: { description: "New scope to set. Omit to read current." } }, required: ["server_name"] }
47705
+ },
47706
+ {
47707
+ name: "mesh_mcp_schema",
47708
+ description: "Inspect tool schemas for a deployed MCP server.",
47709
+ inputSchema: { type: "object", properties: { server_name: { type: "string" }, tool_name: { type: "string", description: "Specific tool (omit for all)" } }, required: ["server_name"] }
47710
+ },
47711
+ {
47712
+ name: "mesh_mcp_catalog",
47713
+ description: "List all deployed services in the mesh with status, scope, and tool count.",
47714
+ inputSchema: { type: "object", properties: {} }
47715
+ },
47716
+ {
47717
+ name: "mesh_skill_deploy",
47718
+ description: "Deploy a multi-file skill bundle from a zip or git repo.",
47719
+ inputSchema: { type: "object", properties: { file_id: { type: "string" }, git_url: { type: "string" }, git_branch: { type: "string" } } }
47720
+ },
47721
+ {
47722
+ name: "vault_set",
47723
+ description: "Store an encrypted credential in your vault. Reference in mesh_mcp_deploy with $vault:<key>.",
47724
+ inputSchema: { type: "object", properties: { key: { type: "string" }, value: { type: "string", description: "Secret value or local file path (for type=file)" }, type: { type: "string", enum: ["env", "file"] }, mount_path: { type: "string" }, description: { type: "string" } }, required: ["key", "value"] }
47725
+ },
47726
+ {
47727
+ name: "vault_list",
47728
+ description: "List your vault entries (keys and metadata only, no secret values).",
47729
+ inputSchema: { type: "object", properties: {} }
47730
+ },
47731
+ {
47732
+ name: "vault_delete",
47733
+ description: "Remove a credential from your vault.",
47734
+ inputSchema: { type: "object", properties: { key: { type: "string" } }, required: ["key"] }
47662
47735
  }
47663
47736
  ];
47664
47737
 
@@ -47747,6 +47820,10 @@ class BrokerClient {
47747
47820
  peerFileResponseResolvers = new Map;
47748
47821
  peerDirResponseResolvers = new Map;
47749
47822
  sharedDirs = [process.cwd()];
47823
+ _serviceCatalog = [];
47824
+ get serviceCatalog() {
47825
+ return this._serviceCatalog;
47826
+ }
47750
47827
  closed = false;
47751
47828
  reconnectAttempt = 0;
47752
47829
  helloTimer = null;
@@ -47810,6 +47887,7 @@ class BrokerClient {
47810
47887
  sessionId: `${process.pid}-${Date.now()}`,
47811
47888
  pid: process.pid,
47812
47889
  cwd: process.cwd(),
47890
+ hostname: __require("os").hostname(),
47813
47891
  peerType: "ai",
47814
47892
  channel: "claude-code",
47815
47893
  model: process.env.CLAUDE_MODEL || undefined,
@@ -47841,6 +47919,21 @@ class BrokerClient {
47841
47919
  this.reconnectAttempt = 0;
47842
47920
  this.flushOutbound();
47843
47921
  this.startStatsReporting();
47922
+ if (msg.restored) {
47923
+ const groups = msg.restoredGroups ? msg.restoredGroups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "none";
47924
+ process.stderr.write(`[claudemesh] session restored — last seen ${msg.lastSeenAt ?? "unknown"}, groups: ${groups}
47925
+ `);
47926
+ if (msg.restoredStats) {
47927
+ const rs = msg.restoredStats;
47928
+ this._statsCounters.messagesIn = rs.messagesIn ?? 0;
47929
+ this._statsCounters.messagesOut = rs.messagesOut ?? 0;
47930
+ this._statsCounters.toolCalls = rs.toolCalls ?? 0;
47931
+ this._statsCounters.errors = rs.errors ?? 0;
47932
+ }
47933
+ }
47934
+ if (msg.services) {
47935
+ this._serviceCatalog = msg.services;
47936
+ }
47844
47937
  resolve();
47845
47938
  return;
47846
47939
  }
@@ -48129,6 +48222,14 @@ class BrokerClient {
48129
48222
  mcpListResolvers = new Map;
48130
48223
  mcpCallResolvers = new Map;
48131
48224
  mcpCallForwardHandler = null;
48225
+ vaultAckResolvers = new Map;
48226
+ vaultListResolvers = new Map;
48227
+ mcpDeployResolvers = new Map;
48228
+ mcpLogsResolvers = new Map;
48229
+ mcpSchemaServiceResolvers = new Map;
48230
+ mcpCatalogResolvers = new Map;
48231
+ mcpScopeResolvers = new Map;
48232
+ skillDeployResolvers = new Map;
48132
48233
  async messageStatus(messageId) {
48133
48234
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48134
48235
  return null;
@@ -48427,7 +48528,7 @@ class BrokerClient {
48427
48528
  this.stateChangeHandlers.add(handler);
48428
48529
  return () => this.stateChangeHandlers.delete(handler);
48429
48530
  }
48430
- async mcpRegister(serverName, description, tools) {
48531
+ async mcpRegister(serverName, description, tools, persistent) {
48431
48532
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48432
48533
  return null;
48433
48534
  return new Promise((resolve) => {
@@ -48436,7 +48537,7 @@ class BrokerClient {
48436
48537
  if (this.mcpRegisterResolvers.delete(reqId))
48437
48538
  resolve(null);
48438
48539
  }, 5000) });
48439
- this.ws.send(JSON.stringify({ type: "mcp_register", serverName, description, tools, _reqId: reqId }));
48540
+ this.ws.send(JSON.stringify({ type: "mcp_register", serverName, description, tools, ...persistent ? { persistent: true } : {}, _reqId: reqId }));
48440
48541
  });
48441
48542
  }
48442
48543
  async mcpUnregister(serverName) {
@@ -48661,6 +48762,138 @@ class BrokerClient {
48661
48762
  this.ws.send(JSON.stringify({ type: "peer_dir_request", targetPubkey, dirPath, ...pattern ? { pattern } : {}, _reqId: reqId }));
48662
48763
  });
48663
48764
  }
48765
+ async vaultSet(key, ciphertext, nonce, sealedKey, entryType, mountPath, description) {
48766
+ return new Promise((resolve) => {
48767
+ const reqId = `vset_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
48768
+ const timer = setTimeout(() => {
48769
+ this.vaultAckResolvers.delete(reqId);
48770
+ resolve(false);
48771
+ }, 1e4);
48772
+ this.vaultAckResolvers.set(reqId, { resolve, timer });
48773
+ this.sendRaw({ type: "vault_set", key, ciphertext, nonce, sealed_key: sealedKey, entry_type: entryType, mount_path: mountPath, description, _reqId: reqId });
48774
+ });
48775
+ }
48776
+ async vaultList() {
48777
+ return new Promise((resolve) => {
48778
+ const reqId = `vlist_${Date.now()}`;
48779
+ const timer = setTimeout(() => {
48780
+ this.vaultListResolvers.delete(reqId);
48781
+ resolve([]);
48782
+ }, 1e4);
48783
+ this.vaultListResolvers.set(reqId, { resolve, timer });
48784
+ this.sendRaw({ type: "vault_list", _reqId: reqId });
48785
+ });
48786
+ }
48787
+ async vaultDelete(key) {
48788
+ return new Promise((resolve) => {
48789
+ const reqId = `vdel_${Date.now()}`;
48790
+ const timer = setTimeout(() => {
48791
+ this.vaultAckResolvers.delete(reqId);
48792
+ resolve(false);
48793
+ }, 1e4);
48794
+ this.vaultAckResolvers.set(reqId, { resolve, timer });
48795
+ this.sendRaw({ type: "vault_delete", key, _reqId: reqId });
48796
+ });
48797
+ }
48798
+ async mcpDeploy(serverName, source, config2, scope) {
48799
+ return new Promise((resolve) => {
48800
+ const reqId = `deploy_${Date.now()}`;
48801
+ const timer = setTimeout(() => {
48802
+ this.mcpDeployResolvers.delete(reqId);
48803
+ resolve({ status: "timeout" });
48804
+ }, 60000);
48805
+ this.mcpDeployResolvers.set(reqId, { resolve, timer });
48806
+ this.sendRaw({ type: "mcp_deploy", server_name: serverName, source, config: config2, scope, _reqId: reqId });
48807
+ });
48808
+ }
48809
+ async mcpUndeploy(serverName) {
48810
+ return new Promise((resolve) => {
48811
+ const reqId = `undeploy_${Date.now()}`;
48812
+ const timer = setTimeout(() => {
48813
+ this.mcpDeployResolvers.delete(reqId);
48814
+ resolve(false);
48815
+ }, 1e4);
48816
+ this.mcpDeployResolvers.set(reqId, { resolve: (r) => resolve(r.status === "stopped"), timer });
48817
+ this.sendRaw({ type: "mcp_undeploy", server_name: serverName, _reqId: reqId });
48818
+ });
48819
+ }
48820
+ async mcpUpdate(serverName) {
48821
+ return new Promise((resolve) => {
48822
+ const reqId = `update_${Date.now()}`;
48823
+ const timer = setTimeout(() => {
48824
+ this.mcpDeployResolvers.delete(reqId);
48825
+ resolve({ status: "timeout" });
48826
+ }, 60000);
48827
+ this.mcpDeployResolvers.set(reqId, { resolve, timer });
48828
+ this.sendRaw({ type: "mcp_update", server_name: serverName, _reqId: reqId });
48829
+ });
48830
+ }
48831
+ async mcpLogs(serverName, lines) {
48832
+ return new Promise((resolve) => {
48833
+ const reqId = `logs_${Date.now()}`;
48834
+ const timer = setTimeout(() => {
48835
+ this.mcpLogsResolvers.delete(reqId);
48836
+ resolve([]);
48837
+ }, 1e4);
48838
+ this.mcpLogsResolvers.set(reqId, { resolve, timer });
48839
+ this.sendRaw({ type: "mcp_logs", server_name: serverName, lines, _reqId: reqId });
48840
+ });
48841
+ }
48842
+ async mcpScope(serverName, scope) {
48843
+ return new Promise((resolve) => {
48844
+ const reqId = `scope_${Date.now()}`;
48845
+ const timer = setTimeout(() => {
48846
+ this.mcpScopeResolvers.delete(reqId);
48847
+ resolve({ scope: { type: "peer" }, deployed_by: "unknown" });
48848
+ }, 1e4);
48849
+ this.mcpScopeResolvers.set(reqId, { resolve, timer });
48850
+ this.sendRaw({ type: "mcp_scope", server_name: serverName, scope, _reqId: reqId });
48851
+ });
48852
+ }
48853
+ async mcpServiceSchema(serverName, toolName) {
48854
+ return new Promise((resolve) => {
48855
+ const reqId = `schema_${Date.now()}`;
48856
+ const timer = setTimeout(() => {
48857
+ this.mcpSchemaServiceResolvers.delete(reqId);
48858
+ resolve([]);
48859
+ }, 1e4);
48860
+ this.mcpSchemaServiceResolvers.set(reqId, { resolve, timer });
48861
+ this.sendRaw({ type: "mcp_schema", server_name: serverName, tool_name: toolName, _reqId: reqId });
48862
+ });
48863
+ }
48864
+ async mcpCatalog() {
48865
+ return new Promise((resolve) => {
48866
+ const reqId = `catalog_${Date.now()}`;
48867
+ const timer = setTimeout(() => {
48868
+ this.mcpCatalogResolvers.delete(reqId);
48869
+ resolve([]);
48870
+ }, 1e4);
48871
+ this.mcpCatalogResolvers.set(reqId, { resolve, timer });
48872
+ this.sendRaw({ type: "mcp_catalog", _reqId: reqId });
48873
+ });
48874
+ }
48875
+ async skillDeploy(source) {
48876
+ return new Promise((resolve) => {
48877
+ const reqId = `skilldeploy_${Date.now()}`;
48878
+ const timer = setTimeout(() => {
48879
+ this.skillDeployResolvers.delete(reqId);
48880
+ resolve({ name: "unknown", files: [] });
48881
+ }, 30000);
48882
+ this.skillDeployResolvers.set(reqId, { resolve, timer });
48883
+ this.sendRaw({ type: "skill_deploy", source, _reqId: reqId });
48884
+ });
48885
+ }
48886
+ async getServiceTools(serviceName) {
48887
+ const cached2 = this._serviceCatalog.find((s) => s.name === serviceName);
48888
+ if (cached2?.tools?.length)
48889
+ return cached2.tools;
48890
+ return this.mcpServiceSchema(serviceName);
48891
+ }
48892
+ sendRaw(payload) {
48893
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48894
+ return;
48895
+ this.ws.send(JSON.stringify(payload));
48896
+ }
48664
48897
  close() {
48665
48898
  this.closed = true;
48666
48899
  this.stopStatsReporting();
@@ -49162,6 +49395,78 @@ class BrokerClient {
49162
49395
  this.resolveFromMap(this.webhookListResolvers, msgReqId, webhooks);
49163
49396
  return;
49164
49397
  }
49398
+ if (msg.type === "vault_ack") {
49399
+ const reqId = msg._reqId;
49400
+ if (reqId && this.vaultAckResolvers.has(reqId)) {
49401
+ const r = this.vaultAckResolvers.get(reqId);
49402
+ clearTimeout(r.timer);
49403
+ this.vaultAckResolvers.delete(reqId);
49404
+ r.resolve(msg.action !== "not_found");
49405
+ }
49406
+ }
49407
+ if (msg.type === "vault_list_result") {
49408
+ const reqId = msg._reqId;
49409
+ if (reqId && this.vaultListResolvers.has(reqId)) {
49410
+ const r = this.vaultListResolvers.get(reqId);
49411
+ clearTimeout(r.timer);
49412
+ this.vaultListResolvers.delete(reqId);
49413
+ r.resolve(msg.entries ?? []);
49414
+ }
49415
+ }
49416
+ if (msg.type === "mcp_deploy_status") {
49417
+ const reqId = msg._reqId;
49418
+ if (reqId && this.mcpDeployResolvers.has(reqId)) {
49419
+ const r = this.mcpDeployResolvers.get(reqId);
49420
+ clearTimeout(r.timer);
49421
+ this.mcpDeployResolvers.delete(reqId);
49422
+ r.resolve({ status: msg.status, tools: msg.tools, error: msg.error });
49423
+ }
49424
+ }
49425
+ if (msg.type === "mcp_logs_result") {
49426
+ const reqId = msg._reqId;
49427
+ if (reqId && this.mcpLogsResolvers.has(reqId)) {
49428
+ const r = this.mcpLogsResolvers.get(reqId);
49429
+ clearTimeout(r.timer);
49430
+ this.mcpLogsResolvers.delete(reqId);
49431
+ r.resolve(msg.lines ?? []);
49432
+ }
49433
+ }
49434
+ if (msg.type === "mcp_schema_result") {
49435
+ const reqId = msg._reqId;
49436
+ if (reqId && this.mcpSchemaServiceResolvers.has(reqId)) {
49437
+ const r = this.mcpSchemaServiceResolvers.get(reqId);
49438
+ clearTimeout(r.timer);
49439
+ this.mcpSchemaServiceResolvers.delete(reqId);
49440
+ r.resolve(msg.tools ?? []);
49441
+ }
49442
+ }
49443
+ if (msg.type === "mcp_catalog_result") {
49444
+ const reqId = msg._reqId;
49445
+ if (reqId && this.mcpCatalogResolvers.has(reqId)) {
49446
+ const r = this.mcpCatalogResolvers.get(reqId);
49447
+ clearTimeout(r.timer);
49448
+ this.mcpCatalogResolvers.delete(reqId);
49449
+ r.resolve(msg.services ?? []);
49450
+ }
49451
+ }
49452
+ if (msg.type === "mcp_scope_result") {
49453
+ const reqId = msg._reqId;
49454
+ if (reqId && this.mcpScopeResolvers.has(reqId)) {
49455
+ const r = this.mcpScopeResolvers.get(reqId);
49456
+ clearTimeout(r.timer);
49457
+ this.mcpScopeResolvers.delete(reqId);
49458
+ r.resolve({ scope: msg.scope, deployed_by: msg.deployed_by });
49459
+ }
49460
+ }
49461
+ if (msg.type === "skill_deploy_ack") {
49462
+ const reqId = msg._reqId;
49463
+ if (reqId && this.skillDeployResolvers.has(reqId)) {
49464
+ const r = this.skillDeployResolvers.get(reqId);
49465
+ clearTimeout(r.timer);
49466
+ this.skillDeployResolvers.delete(reqId);
49467
+ r.resolve({ name: msg.name, files: msg.files ?? [] });
49468
+ }
49469
+ }
49165
49470
  if (msg.type === "error") {
49166
49471
  this.debug(`broker error: ${msg.code} ${msg.message}`);
49167
49472
  const id = msg.id ? String(msg.id) : null;
@@ -49215,7 +49520,15 @@ class BrokerClient {
49215
49520
  [this.peerFileResponseResolvers, { error: "broker error" }],
49216
49521
  [this.peerDirResponseResolvers, { error: "broker error" }],
49217
49522
  [this.webhookAckResolvers, null],
49218
- [this.webhookListResolvers, []]
49523
+ [this.webhookListResolvers, []],
49524
+ [this.vaultAckResolvers, false],
49525
+ [this.vaultListResolvers, []],
49526
+ [this.mcpDeployResolvers, { status: "error" }],
49527
+ [this.mcpLogsResolvers, []],
49528
+ [this.mcpSchemaServiceResolvers, []],
49529
+ [this.mcpCatalogResolvers, []],
49530
+ [this.mcpScopeResolvers, { scope: { type: "peer" }, deployed_by: "unknown" }],
49531
+ [this.skillDeployResolvers, { name: "unknown", files: [] }]
49219
49532
  ];
49220
49533
  for (const [map2, defaultVal] of allMaps) {
49221
49534
  const first = map2.entries().next().value;
@@ -49312,6 +49625,25 @@ function stopAll() {
49312
49625
  }
49313
49626
 
49314
49627
  // src/mcp/server.ts
49628
+ function relativeTime(isoStr) {
49629
+ const then = new Date(isoStr).getTime();
49630
+ if (isNaN(then))
49631
+ return "unknown";
49632
+ const diffMs = Date.now() - then;
49633
+ if (diffMs < 0)
49634
+ return "just now";
49635
+ const seconds = Math.floor(diffMs / 1000);
49636
+ if (seconds < 60)
49637
+ return `${seconds}s ago`;
49638
+ const minutes = Math.floor(seconds / 60);
49639
+ if (minutes < 60)
49640
+ return `${minutes}m ago`;
49641
+ const hours = Math.floor(minutes / 60);
49642
+ if (hours < 24)
49643
+ return `${hours}h ago`;
49644
+ const days = Math.floor(hours / 24);
49645
+ return `${days} day${days !== 1 ? "s" : ""} ago`;
49646
+ }
49315
49647
  function text(msg, isError = false) {
49316
49648
  return {
49317
49649
  content: [{ type: "text", text: msg }],
@@ -49392,6 +49724,10 @@ function formatPush(p, meshSlug) {
49392
49724
  ${body}`;
49393
49725
  }
49394
49726
  async function startMcpServer() {
49727
+ const serviceIdx = process.argv.indexOf("--service");
49728
+ if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
49729
+ return startServiceProxy(process.argv[serviceIdx + 1]);
49730
+ }
49395
49731
  const config2 = loadConfig();
49396
49732
  const myName = config2.displayName ?? "unnamed";
49397
49733
  const myRole = config2.role ?? process.env.CLAUDEMESH_ROLE ?? null;
@@ -49484,9 +49820,14 @@ Shared key-value store scoped to the mesh. Use get_state/set_state for live coor
49484
49820
  ## Memory
49485
49821
  Persistent knowledge that survives across sessions. Use remember(content, tags?) to store lessons, decisions, and incidents. Use recall(query) to search before asking peers. New peers should recall at session start to load institutional knowledge.
49486
49822
 
49487
- ## Files
49488
- share_file for persistent references, send_message(file:) for ephemeral attachments.
49489
- Tags on shared files make them searchable. Use list_files to find what peers shared.
49823
+ ## File access — decision guide
49824
+ Three ways to access files. Pick the right one:
49825
+
49826
+ 1. **Local peer (same machine, [local] tag):** Read files directly via filesystem using their \`cwd\` path from list_peers. No limit, instant. This is the default for local peers.
49827
+ 2. **Remote peer (different machine, [remote] tag):** Use \`read_peer_file(peer, path)\` — relays through the mesh. **1 MB limit**, base64 encoded. Use \`list_peer_files\` to browse first.
49828
+ 3. **Persistent sharing (any peer):** Use \`share_file(path)\` — uploads to mesh storage (MinIO). **No size limit**. All peers can download anytime via \`get_file\`. Use for files that need to persist or be shared with multiple peers.
49829
+
49830
+ **Rule of thumb:** local peer → filesystem. Remote peer, small file → read_peer_file. Large file or needs to persist → share_file.
49490
49831
 
49491
49832
  ## Vectors
49492
49833
  Store and search semantic embeddings. Use vector_store to index content, vector_search to find similar content.
@@ -49583,10 +49924,12 @@ No peers connected.`);
49583
49924
  meta2.push(`model:${p.model}`);
49584
49925
  const metaStr = meta2.length ? ` {${meta2.join(", ")}}` : "";
49585
49926
  const cwdStr = p.cwd ? ` cwd:${p.cwd}` : "";
49927
+ const locality = p.hostname && p.hostname === __require("os").hostname() ? "local" : "remote";
49928
+ const localityTag = ` [${locality}]`;
49586
49929
  const profileAvatar = p.profile?.avatar ? `${p.profile.avatar} ` : "";
49587
49930
  const profileTitle = p.profile?.title ? ` (${p.profile.title})` : "";
49588
49931
  const hiddenTag = p.visible === false ? " [hidden]" : "";
49589
- return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${hiddenTag}${groupsStr}${metaStr} (${p.pubkey.slice(0, 12)}…)${cwdStr}${summary}`;
49932
+ return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${localityTag}${hiddenTag}${groupsStr}${metaStr} (${p.pubkey.slice(0, 12)}…)${cwdStr}${summary}`;
49590
49933
  });
49591
49934
  sections.push(`${header}
49592
49935
  ${peerLines.join(`
@@ -50423,16 +50766,17 @@ ${lines.join(`
50423
50766
  `));
50424
50767
  }
50425
50768
  case "mesh_mcp_register": {
50426
- const { server_name, description, tools: regTools } = args ?? {};
50769
+ const { server_name, description, tools: regTools, persistent: regPersistent } = args ?? {};
50427
50770
  if (!server_name || !description || !regTools?.length)
50428
50771
  return text("mesh_mcp_register: `server_name`, `description`, and `tools` required", true);
50429
50772
  const client2 = allClients()[0];
50430
50773
  if (!client2)
50431
50774
  return text("mesh_mcp_register: not connected", true);
50432
- const result = await client2.mcpRegister(server_name, description, regTools);
50775
+ const result = await client2.mcpRegister(server_name, description, regTools, regPersistent);
50433
50776
  if (!result)
50434
50777
  return text("mesh_mcp_register: broker did not acknowledge", true);
50435
- return text(`Registered MCP server "${result.serverName}" with ${result.toolCount} tool(s). Other peers can now call its tools via mesh_tool_call.`);
50778
+ const persistLabel = regPersistent ? " (persistent survives disconnect)" : "";
50779
+ return text(`Registered MCP server "${result.serverName}" with ${result.toolCount} tool(s)${persistLabel}. Other peers can now call its tools via mesh_tool_call.`);
50436
50780
  }
50437
50781
  case "mesh_mcp_list": {
50438
50782
  const client2 = allClients()[0];
@@ -50444,7 +50788,8 @@ ${lines.join(`
50444
50788
  const lines = servers.map((s) => {
50445
50789
  const toolList = s.tools.map((t) => ` - **${t.name}**: ${t.description}`).join(`
50446
50790
  `);
50447
- return `- **${s.name}** (hosted by ${s.hostedBy}): ${s.description}
50791
+ const status = s.online === false ? ` [OFFLINE${s.offlineSince ? ` since ${s.offlineSince}` : ""}]` : "";
50792
+ return `- **${s.name}** (hosted by ${s.hostedBy})${status}: ${s.description}
50448
50793
  ${toolList}`;
50449
50794
  });
50450
50795
  return text(`${servers.length} MCP server(s) in mesh:
@@ -50531,6 +50876,17 @@ ${lines.join(`
50531
50876
  targetPubkey = match.pubkey;
50532
50877
  }
50533
50878
  }
50879
+ const resolvedPeer = peers.find((p) => p.pubkey === targetPubkey);
50880
+ const isLocal = resolvedPeer?.hostname && resolvedPeer.hostname === __require("os").hostname();
50881
+ let localHint = "";
50882
+ if (isLocal && resolvedPeer?.cwd) {
50883
+ const directPath = __require("path").resolve(resolvedPeer.cwd, filePath);
50884
+ localHint = `
50885
+
50886
+ > **Hint:** This peer is LOCAL (same machine). Next time, read directly: \`${directPath}\` — faster, no size limit.
50887
+
50888
+ `;
50889
+ }
50534
50890
  const result = await client2.requestFile(targetPubkey, filePath);
50535
50891
  if (result.error)
50536
50892
  return text(`read_peer_file: ${result.error}`, true);
@@ -50538,7 +50894,7 @@ ${lines.join(`
50538
50894
  return text("read_peer_file: empty response from peer", true);
50539
50895
  try {
50540
50896
  const decoded = Buffer.from(result.content, "base64").toString("utf-8");
50541
- return text(decoded);
50897
+ return text(localHint + decoded);
50542
50898
  } catch {
50543
50899
  return text("read_peer_file: failed to decode file content (binary file?)", true);
50544
50900
  }
@@ -50638,8 +50994,21 @@ ${lines.join(`
50638
50994
  content2 = `[heartbeat] tick ${tick} | sim time: ${simTime} | speed: x${speed}`;
50639
50995
  } else if (eventName === "peer_joined") {
50640
50996
  content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
50997
+ } else if (eventName === "peer_returned") {
50998
+ const peerName = String(data.name ?? "unknown");
50999
+ const lastSeenAt = data.lastSeenAt ? relativeTime(String(data.lastSeenAt)) : "unknown";
51000
+ const groups = Array.isArray(data.groups) ? data.groups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "";
51001
+ const summary = data.summary ? ` Summary: "${data.summary}"` : "";
51002
+ content2 = `[system] Welcome back, "${peerName}"! Last seen ${lastSeenAt}.${groups ? ` Restored: ${groups}` : ""}${summary}`;
50641
51003
  } else if (eventName === "peer_left") {
50642
51004
  content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
51005
+ } else if (eventName === "mcp_registered") {
51006
+ const tools = Array.isArray(data.tools) ? data.tools.join(", ") : "";
51007
+ content2 = `[system] New MCP server available: "${data.serverName}" (hosted by ${data.hostedBy}). Tools: ${tools}. Use mesh_tool_call to invoke.`;
51008
+ } else if (eventName === "mcp_unregistered") {
51009
+ content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
51010
+ } else if (eventName === "mcp_restored") {
51011
+ content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
50643
51012
  } else {
50644
51013
  content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
50645
51014
  }
@@ -50761,6 +51130,120 @@ ${lines.join(`
50761
51130
  process.on("SIGTERM", shutdown);
50762
51131
  process.on("SIGINT", shutdown);
50763
51132
  }
51133
+ async function startServiceProxy(serviceName) {
51134
+ const config2 = loadConfig();
51135
+ if (config2.meshes.length === 0) {
51136
+ process.stderr.write(`[mesh:${serviceName}] no meshes joined
51137
+ `);
51138
+ process.exit(1);
51139
+ }
51140
+ const mesh = config2.meshes[0];
51141
+ const client2 = new BrokerClient(mesh, {
51142
+ displayName: config2.displayName ?? `proxy:${serviceName}`
51143
+ });
51144
+ try {
51145
+ await client2.connect();
51146
+ } catch (e) {
51147
+ process.stderr.write(`[mesh:${serviceName}] broker connect failed: ${e instanceof Error ? e.message : String(e)}
51148
+ `);
51149
+ process.exit(1);
51150
+ }
51151
+ await new Promise((r) => setTimeout(r, 1500));
51152
+ let tools = [];
51153
+ try {
51154
+ const fetched = await client2.getServiceTools(serviceName);
51155
+ tools = fetched;
51156
+ } catch {
51157
+ const cached2 = client2.serviceCatalog.find((s) => s.name === serviceName);
51158
+ if (cached2) {
51159
+ tools = cached2.tools;
51160
+ }
51161
+ }
51162
+ if (tools.length === 0) {
51163
+ process.stderr.write(`[mesh:${serviceName}] no tools found — service may not be running
51164
+ `);
51165
+ }
51166
+ const server = new Server({ name: `mesh:${serviceName}`, version: "0.1.0" }, { capabilities: { tools: {} } });
51167
+ server.setRequestHandler(ListToolsRequestSchema, () => ({
51168
+ tools: tools.map((t) => ({
51169
+ name: t.name,
51170
+ description: `[mesh:${serviceName}] ${t.description}`,
51171
+ inputSchema: t.inputSchema
51172
+ }))
51173
+ }));
51174
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
51175
+ const toolName = req.params.name;
51176
+ const args = req.params.arguments ?? {};
51177
+ if (client2.status !== "open") {
51178
+ let waited = 0;
51179
+ while (client2.status !== "open" && waited < 1e4) {
51180
+ await new Promise((r) => setTimeout(r, 500));
51181
+ waited += 500;
51182
+ }
51183
+ if (client2.status !== "open") {
51184
+ return {
51185
+ content: [
51186
+ {
51187
+ type: "text",
51188
+ text: `Service temporarily unavailable — broker reconnecting. Retry in a few seconds.`
51189
+ }
51190
+ ],
51191
+ isError: true
51192
+ };
51193
+ }
51194
+ }
51195
+ try {
51196
+ const result = await client2.mcpCall(serviceName, toolName, args);
51197
+ if (result.error) {
51198
+ return {
51199
+ content: [{ type: "text", text: `Error: ${result.error}` }],
51200
+ isError: true
51201
+ };
51202
+ }
51203
+ const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
51204
+ return {
51205
+ content: [{ type: "text", text: resultText }]
51206
+ };
51207
+ } catch (e) {
51208
+ return {
51209
+ content: [
51210
+ {
51211
+ type: "text",
51212
+ text: `Call failed: ${e instanceof Error ? e.message : String(e)}`
51213
+ }
51214
+ ],
51215
+ isError: true
51216
+ };
51217
+ }
51218
+ });
51219
+ client2.onPush((push) => {
51220
+ if (push.event === "mcp_undeployed" && push.eventData?.name === serviceName) {
51221
+ process.stderr.write(`[mesh:${serviceName}] service undeployed — exiting
51222
+ `);
51223
+ client2.close();
51224
+ process.exit(0);
51225
+ }
51226
+ if (push.event === "mcp_updated" && push.eventData?.name === serviceName) {
51227
+ const newTools = push.eventData?.tools;
51228
+ if (Array.isArray(newTools)) {
51229
+ tools = newTools;
51230
+ server.notification({
51231
+ method: "notifications/tools/list_changed"
51232
+ }).catch(() => {});
51233
+ }
51234
+ }
51235
+ });
51236
+ const transport = new StdioServerTransport;
51237
+ await server.connect(transport);
51238
+ const keepalive = setInterval(() => {}, 1000);
51239
+ const shutdown = () => {
51240
+ clearInterval(keepalive);
51241
+ client2.close();
51242
+ process.exit(0);
51243
+ };
51244
+ process.on("SIGTERM", shutdown);
51245
+ process.on("SIGINT", shutdown);
51246
+ }
50764
51247
 
50765
51248
  // src/commands/install.ts
50766
51249
  init_config();
@@ -51457,8 +51940,8 @@ async function runHook(args) {
51457
51940
  // src/commands/launch.ts
51458
51941
  init_config();
51459
51942
  import { spawn } from "node:child_process";
51460
- import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync } from "node:fs";
51461
- import { tmpdir, hostname as hostname2 } from "node:os";
51943
+ import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
51944
+ import { tmpdir, hostname as hostname2, homedir as homedir4 } from "node:os";
51462
51945
  import { join as join4 } from "node:path";
51463
51946
  import { createInterface } from "node:readline";
51464
51947
  async function pickMesh(meshes) {
@@ -51657,6 +52140,44 @@ async function runLaunch(flags, rawArgs) {
51657
52140
  rmSync(full, { recursive: true, force: true });
51658
52141
  }
51659
52142
  } catch {}
52143
+ try {
52144
+ const claudeConfigPath = join4(homedir4(), ".claude.json");
52145
+ if (existsSync3(claudeConfigPath)) {
52146
+ const claudeConfig = JSON.parse(readFileSync3(claudeConfigPath, "utf-8"));
52147
+ const mcpServers = claudeConfig.mcpServers ?? {};
52148
+ let cleaned = 0;
52149
+ for (const key of Object.keys(mcpServers)) {
52150
+ if (!key.startsWith("mesh:"))
52151
+ continue;
52152
+ const meta2 = mcpServers[key]?._meshSession;
52153
+ if (!meta2?.pid)
52154
+ continue;
52155
+ try {
52156
+ process.kill(meta2.pid, 0);
52157
+ } catch {
52158
+ delete mcpServers[key];
52159
+ cleaned++;
52160
+ }
52161
+ }
52162
+ if (cleaned > 0) {
52163
+ claudeConfig.mcpServers = mcpServers;
52164
+ writeFileSync4(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
52165
+ `, "utf-8");
52166
+ }
52167
+ }
52168
+ } catch {}
52169
+ let serviceCatalog = [];
52170
+ try {
52171
+ const tmpClient = new BrokerClient(mesh, { displayName });
52172
+ await tmpClient.connect();
52173
+ await new Promise((r) => setTimeout(r, 2000));
52174
+ serviceCatalog = tmpClient.serviceCatalog;
52175
+ tmpClient.close();
52176
+ } catch {
52177
+ if (!args.quiet) {
52178
+ console.log(" (Could not fetch service catalog — mesh services won't be natively available)");
52179
+ }
52180
+ }
51660
52181
  const tmpDir = mkdtempSync(join4(tmpdir(), "claudemesh-"));
51661
52182
  const sessionConfig = {
51662
52183
  version: 1,
@@ -51674,6 +52195,50 @@ async function runLaunch(flags, rawArgs) {
51674
52195
  await confirmPermissions();
51675
52196
  }
51676
52197
  }
52198
+ const meshMcpEntries = [];
52199
+ if (serviceCatalog.length > 0) {
52200
+ const claudeConfigPath = join4(homedir4(), ".claude.json");
52201
+ let claudeConfig = {};
52202
+ try {
52203
+ claudeConfig = JSON.parse(readFileSync3(claudeConfigPath, "utf-8"));
52204
+ } catch {
52205
+ claudeConfig = {};
52206
+ }
52207
+ const mcpServers = claudeConfig.mcpServers ?? {};
52208
+ const sessionTag = `${process.pid}`;
52209
+ for (const svc of serviceCatalog) {
52210
+ if (svc.status !== "running")
52211
+ continue;
52212
+ const entryKey = `mesh:${svc.name}:${sessionTag}`;
52213
+ const entry = {
52214
+ command: "claudemesh",
52215
+ args: ["mcp", "--service", svc.name],
52216
+ env: {
52217
+ CLAUDEMESH_CONFIG_DIR: tmpDir
52218
+ },
52219
+ _meshSession: {
52220
+ pid: process.pid,
52221
+ meshSlug: mesh.slug,
52222
+ serviceName: svc.name,
52223
+ createdAt: new Date().toISOString()
52224
+ }
52225
+ };
52226
+ mcpServers[entryKey] = entry;
52227
+ meshMcpEntries.push({ key: entryKey, entry });
52228
+ }
52229
+ claudeConfig.mcpServers = mcpServers;
52230
+ writeFileSync4(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
52231
+ `, "utf-8");
52232
+ if (!args.quiet && meshMcpEntries.length > 0) {
52233
+ console.log(` ${meshMcpEntries.length} mesh service(s) registered as native MCPs:`);
52234
+ for (const { key } of meshMcpEntries) {
52235
+ const svcName = key.split(":")[1];
52236
+ const svc = serviceCatalog.find((s) => s.name === svcName);
52237
+ console.log(` ${svcName} (${svc?.tools.length ?? 0} tools)`);
52238
+ }
52239
+ console.log("");
52240
+ }
52241
+ }
51677
52242
  const filtered = [];
51678
52243
  for (let i = 0;i < args.claudeArgs.length; i++) {
51679
52244
  if (args.claudeArgs[i] === "--dangerously-load-development-channels" || args.claudeArgs[i] === "--dangerously-skip-permissions") {
@@ -51698,10 +52263,25 @@ async function runLaunch(flags, rawArgs) {
51698
52263
  ...process.env,
51699
52264
  CLAUDEMESH_CONFIG_DIR: tmpDir,
51700
52265
  CLAUDEMESH_DISPLAY_NAME: displayName,
52266
+ MCP_TIMEOUT: process.env.MCP_TIMEOUT ?? "30000",
52267
+ MAX_MCP_OUTPUT_TOKENS: process.env.MAX_MCP_OUTPUT_TOKENS ?? "50000",
51701
52268
  ...role ? { CLAUDEMESH_ROLE: role } : {}
51702
52269
  }
51703
52270
  });
51704
52271
  const cleanup = () => {
52272
+ if (meshMcpEntries.length > 0) {
52273
+ try {
52274
+ const claudeConfigPath = join4(homedir4(), ".claude.json");
52275
+ const claudeConfig = JSON.parse(readFileSync3(claudeConfigPath, "utf-8"));
52276
+ const mcpServers = claudeConfig.mcpServers ?? {};
52277
+ for (const { key } of meshMcpEntries) {
52278
+ delete mcpServers[key];
52279
+ }
52280
+ claudeConfig.mcpServers = mcpServers;
52281
+ writeFileSync4(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
52282
+ `, "utf-8");
52283
+ } catch {}
52284
+ }
51705
52285
  try {
51706
52286
  rmSync(tmpDir, { recursive: true, force: true });
51707
52287
  } catch {}
@@ -51734,12 +52314,12 @@ async function runLaunch(flags, rawArgs) {
51734
52314
  }
51735
52315
 
51736
52316
  // src/commands/status.ts
51737
- import { statSync as statSync2, existsSync as existsSync3 } from "node:fs";
52317
+ import { statSync as statSync2, existsSync as existsSync4 } from "node:fs";
51738
52318
  init_config();
51739
52319
  // package.json
51740
52320
  var package_default = {
51741
52321
  name: "claudemesh-cli",
51742
- version: "0.7.0",
52322
+ version: "0.8.0",
51743
52323
  description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
51744
52324
  keywords: [
51745
52325
  "claude-code",
@@ -51840,7 +52420,7 @@ async function runStatus() {
51840
52420
  console.log("─".repeat(60));
51841
52421
  const configPath = getConfigPath();
51842
52422
  let configPerms = "missing";
51843
- if (existsSync3(configPath)) {
52423
+ if (existsSync4(configPath)) {
51844
52424
  const st = statSync2(configPath);
51845
52425
  const mode = (st.mode & 511).toString(8).padStart(4, "0");
51846
52426
  configPerms = mode === "0600" ? `${mode} ✓` : `${mode} ⚠ (expected 0600)`;
@@ -51889,8 +52469,8 @@ async function runStatus() {
51889
52469
 
51890
52470
  // src/commands/doctor.ts
51891
52471
  init_config();
51892
- import { existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
51893
- import { homedir as homedir4, platform as platform2 } from "node:os";
52472
+ import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
52473
+ import { homedir as homedir5, platform as platform2 } from "node:os";
51894
52474
  import { join as join5 } from "node:path";
51895
52475
  import { spawnSync as spawnSync2 } from "node:child_process";
51896
52476
  function checkNode() {
@@ -51915,8 +52495,8 @@ function checkClaudeOnPath() {
51915
52495
  };
51916
52496
  }
51917
52497
  function checkMcpRegistered() {
51918
- const claudeConfig = join5(homedir4(), ".claude.json");
51919
- if (!existsSync4(claudeConfig)) {
52498
+ const claudeConfig = join5(homedir5(), ".claude.json");
52499
+ if (!existsSync5(claudeConfig)) {
51920
52500
  return {
51921
52501
  name: "claudemesh MCP registered in ~/.claude.json",
51922
52502
  pass: false,
@@ -51924,7 +52504,7 @@ function checkMcpRegistered() {
51924
52504
  };
51925
52505
  }
51926
52506
  try {
51927
- const cfg = JSON.parse(readFileSync3(claudeConfig, "utf-8"));
52507
+ const cfg = JSON.parse(readFileSync4(claudeConfig, "utf-8"));
51928
52508
  const registered = Boolean(cfg.mcpServers?.["claudemesh"]);
51929
52509
  return {
51930
52510
  name: "claudemesh MCP registered in ~/.claude.json",
@@ -51941,8 +52521,8 @@ function checkMcpRegistered() {
51941
52521
  }
51942
52522
  }
51943
52523
  function checkHooksRegistered() {
51944
- const settings = join5(homedir4(), ".claude", "settings.json");
51945
- if (!existsSync4(settings)) {
52524
+ const settings = join5(homedir5(), ".claude", "settings.json");
52525
+ if (!existsSync5(settings)) {
51946
52526
  return {
51947
52527
  name: "Status hooks registered in ~/.claude/settings.json",
51948
52528
  pass: false,
@@ -51950,7 +52530,7 @@ function checkHooksRegistered() {
51950
52530
  };
51951
52531
  }
51952
52532
  try {
51953
- const raw = readFileSync3(settings, "utf-8");
52533
+ const raw = readFileSync4(settings, "utf-8");
51954
52534
  const has = raw.includes("claudemesh hook ");
51955
52535
  return {
51956
52536
  name: "Status hooks registered in ~/.claude/settings.json",
@@ -51967,7 +52547,7 @@ function checkHooksRegistered() {
51967
52547
  }
51968
52548
  function checkConfigFile() {
51969
52549
  const path = getConfigPath();
51970
- if (!existsSync4(path)) {
52550
+ if (!existsSync5(path)) {
51971
52551
  return {
51972
52552
  name: "~/.claudemesh/config.json exists and parses",
51973
52553
  pass: true,
@@ -52071,15 +52651,15 @@ async function runDoctor() {
52071
52651
 
52072
52652
  // src/commands/welcome.ts
52073
52653
  init_config();
52074
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
52075
- import { homedir as homedir5 } from "node:os";
52654
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
52655
+ import { homedir as homedir6 } from "node:os";
52076
52656
  import { join as join6 } from "node:path";
52077
52657
  function detectState() {
52078
- const claudeConfig = join6(homedir5(), ".claude.json");
52658
+ const claudeConfig = join6(homedir6(), ".claude.json");
52079
52659
  let mcpRegistered = false;
52080
- if (existsSync5(claudeConfig)) {
52660
+ if (existsSync6(claudeConfig)) {
52081
52661
  try {
52082
- const cfg = JSON.parse(readFileSync4(claudeConfig, "utf-8"));
52662
+ const cfg = JSON.parse(readFileSync5(claudeConfig, "utf-8"));
52083
52663
  mcpRegistered = Boolean(cfg.mcpServers?.["claudemesh"]);
52084
52664
  } catch {}
52085
52665
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemesh-cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -48,10 +48,10 @@
48
48
  "prettier": "3.6.2",
49
49
  "typescript": "5.9.3",
50
50
  "vitest": "4.0.14",
51
- "@turbostarter/tsconfig": "0.1.0",
51
+ "@turbostarter/eslint-config": "0.1.0",
52
52
  "@turbostarter/prettier-config": "0.1.0",
53
- "@turbostarter/vitest-config": "0.1.0",
54
- "@turbostarter/eslint-config": "0.1.0"
53
+ "@turbostarter/tsconfig": "0.1.0",
54
+ "@turbostarter/vitest-config": "0.1.0"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "bun build src/index.ts --target=node --outfile dist/index.js --banner \"#!/usr/bin/env node\" && chmod +x dist/index.js",