claudemesh-cli 1.24.0 → 1.26.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.
@@ -103,7 +103,7 @@ __export(exports_urls, {
103
103
  VERSION: () => VERSION,
104
104
  URLS: () => URLS
105
105
  });
106
- var URLS, VERSION = "1.24.0", env;
106
+ var URLS, VERSION = "1.26.0", env;
107
107
  var init_urls = __esm(() => {
108
108
  URLS = {
109
109
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -1026,6 +1026,12 @@ var init_file_crypto = __esm(() => {
1026
1026
  });
1027
1027
 
1028
1028
  // src/services/crypto/box.ts
1029
+ var exports_box = {};
1030
+ __export(exports_box, {
1031
+ isDirectTarget: () => isDirectTarget,
1032
+ encryptDirect: () => encryptDirect,
1033
+ decryptDirect: () => decryptDirect
1034
+ });
1029
1035
  function isDirectTarget(targetSpec) {
1030
1036
  return HEX_PUBKEY.test(targetSpec);
1031
1037
  }
@@ -6790,105 +6796,6 @@ var init_client3 = __esm(() => {
6790
6796
  init_protocol();
6791
6797
  });
6792
6798
 
6793
- // src/commands/peers.ts
6794
- var exports_peers = {};
6795
- __export(exports_peers, {
6796
- runPeers: () => runPeers
6797
- });
6798
- function projectFields(record, fields) {
6799
- const out = {};
6800
- for (const f of fields) {
6801
- const sourceKey = FIELD_ALIAS[f] ?? f;
6802
- out[f] = record[sourceKey];
6803
- }
6804
- return out;
6805
- }
6806
- async function listPeersForMesh(slug) {
6807
- const config = readConfig();
6808
- const joined = config.meshes.find((m) => m.slug === slug);
6809
- const selfMemberPubkey = joined?.pubkey ?? null;
6810
- const bridged = await tryBridge(slug, "peers");
6811
- if (bridged && bridged.ok) {
6812
- const peers = bridged.result;
6813
- return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
6814
- }
6815
- let result = [];
6816
- await withMesh({ meshSlug: slug }, async (client) => {
6817
- const all = await client.listPeers();
6818
- const selfSessionPubkey = client.getSessionPubkey();
6819
- result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
6820
- });
6821
- return result;
6822
- }
6823
- function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
6824
- const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
6825
- const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
6826
- return { ...peer, isSelf, isThisSession };
6827
- }
6828
- async function runPeers(flags) {
6829
- const config = readConfig();
6830
- const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
6831
- if (slugs.length === 0) {
6832
- render.err("No meshes joined.");
6833
- render.hint("claudemesh <invite-url> # join + launch");
6834
- process.exit(1);
6835
- }
6836
- const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
6837
- const wantsJson = flags.json !== undefined && flags.json !== false;
6838
- const allJson = [];
6839
- for (const slug of slugs) {
6840
- try {
6841
- const peers = await listPeersForMesh(slug);
6842
- if (wantsJson) {
6843
- const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
6844
- allJson.push({ mesh: slug, peers: projected });
6845
- continue;
6846
- }
6847
- render.section(`peers on ${slug} (${peers.length})`);
6848
- if (peers.length === 0) {
6849
- render.info(dim(" (no peers connected)"));
6850
- continue;
6851
- }
6852
- for (const p of peers) {
6853
- const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
6854
- const statusDot = p.status === "working" ? yellow("●") : green("●");
6855
- const name = bold(p.displayName);
6856
- const meta = [];
6857
- if (p.peerType)
6858
- meta.push(p.peerType);
6859
- if (p.channel)
6860
- meta.push(p.channel);
6861
- if (p.model)
6862
- meta.push(p.model);
6863
- const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
6864
- const summary = p.summary ? dim(` — ${p.summary}`) : "";
6865
- const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
6866
- const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
6867
- render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
6868
- if (p.cwd)
6869
- render.info(dim(` cwd: ${p.cwd}`));
6870
- }
6871
- } catch (e) {
6872
- render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
6873
- }
6874
- }
6875
- if (wantsJson) {
6876
- process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
6877
- `);
6878
- }
6879
- }
6880
- var FIELD_ALIAS;
6881
- var init_peers = __esm(() => {
6882
- init_connect();
6883
- init_facade();
6884
- init_client3();
6885
- init_render();
6886
- init_styles();
6887
- FIELD_ALIAS = {
6888
- name: "displayName"
6889
- };
6890
- });
6891
-
6892
6799
  // src/daemon/local-token.ts
6893
6800
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
6894
6801
  import { dirname as dirname3 } from "node:path";
@@ -6970,7 +6877,61 @@ var init_client4 = __esm(() => {
6970
6877
  });
6971
6878
 
6972
6879
  // src/services/bridge/daemon-route.ts
6880
+ var exports_daemon_route = {};
6881
+ __export(exports_daemon_route, {
6882
+ trySendViaDaemon: () => trySendViaDaemon,
6883
+ tryListSkillsViaDaemon: () => tryListSkillsViaDaemon,
6884
+ tryListPeersViaDaemon: () => tryListPeersViaDaemon,
6885
+ tryGetSkillViaDaemon: () => tryGetSkillViaDaemon
6886
+ });
6973
6887
  import { existsSync as existsSync7 } from "node:fs";
6888
+ async function tryListPeersViaDaemon() {
6889
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6890
+ return null;
6891
+ try {
6892
+ const res = await ipc({ path: "/v1/peers", timeoutMs: 3000 });
6893
+ if (res.status !== 200)
6894
+ return null;
6895
+ return Array.isArray(res.body.peers) ? res.body.peers : [];
6896
+ } catch (err) {
6897
+ const msg = String(err);
6898
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
6899
+ return null;
6900
+ return null;
6901
+ }
6902
+ }
6903
+ async function tryListSkillsViaDaemon() {
6904
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6905
+ return null;
6906
+ try {
6907
+ const res = await ipc({ path: "/v1/skills", timeoutMs: 3000 });
6908
+ if (res.status !== 200)
6909
+ return null;
6910
+ return Array.isArray(res.body.skills) ? res.body.skills : [];
6911
+ } catch (err) {
6912
+ const msg = String(err);
6913
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
6914
+ return null;
6915
+ return null;
6916
+ }
6917
+ }
6918
+ async function tryGetSkillViaDaemon(name) {
6919
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6920
+ return null;
6921
+ try {
6922
+ const res = await ipc({
6923
+ path: `/v1/skills/${encodeURIComponent(name)}`,
6924
+ timeoutMs: 3000
6925
+ });
6926
+ if (res.status === 404)
6927
+ return null;
6928
+ if (res.status !== 200)
6929
+ return null;
6930
+ return res.body.skill ?? null;
6931
+ } catch {
6932
+ return null;
6933
+ }
6934
+ }
6974
6935
  async function trySendViaDaemon(args) {
6975
6936
  if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6976
6937
  return null;
@@ -6983,7 +6944,8 @@ async function trySendViaDaemon(args) {
6983
6944
  to: args.to,
6984
6945
  message: args.message,
6985
6946
  priority: args.priority,
6986
- ...args.idempotencyKey ? { client_message_id: args.idempotencyKey } : {}
6947
+ ...args.idempotencyKey ? { client_message_id: args.idempotencyKey } : {},
6948
+ ...args.expectedMesh ? { mesh: args.expectedMesh } : {}
6987
6949
  }
6988
6950
  });
6989
6951
  if (res.status === 202 || res.status === 200) {
@@ -7007,6 +6969,112 @@ var init_daemon_route = __esm(() => {
7007
6969
  init_paths2();
7008
6970
  });
7009
6971
 
6972
+ // src/commands/peers.ts
6973
+ var exports_peers = {};
6974
+ __export(exports_peers, {
6975
+ runPeers: () => runPeers
6976
+ });
6977
+ function projectFields(record, fields) {
6978
+ const out = {};
6979
+ for (const f of fields) {
6980
+ const sourceKey = FIELD_ALIAS[f] ?? f;
6981
+ out[f] = record[sourceKey];
6982
+ }
6983
+ return out;
6984
+ }
6985
+ async function listPeersForMesh(slug) {
6986
+ const config = readConfig();
6987
+ const joined = config.meshes.find((m) => m.slug === slug);
6988
+ const selfMemberPubkey = joined?.pubkey ?? null;
6989
+ try {
6990
+ const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
6991
+ const dr = await tryListPeersViaDaemon2();
6992
+ if (dr !== null) {
6993
+ return dr.map((p) => annotateSelf(p, selfMemberPubkey, null));
6994
+ }
6995
+ } catch {}
6996
+ const bridged = await tryBridge(slug, "peers");
6997
+ if (bridged && bridged.ok) {
6998
+ const peers = bridged.result;
6999
+ return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
7000
+ }
7001
+ let result = [];
7002
+ await withMesh({ meshSlug: slug }, async (client) => {
7003
+ const all = await client.listPeers();
7004
+ const selfSessionPubkey = client.getSessionPubkey();
7005
+ result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
7006
+ });
7007
+ return result;
7008
+ }
7009
+ function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
7010
+ const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
7011
+ const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
7012
+ return { ...peer, isSelf, isThisSession };
7013
+ }
7014
+ async function runPeers(flags) {
7015
+ const config = readConfig();
7016
+ const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
7017
+ if (slugs.length === 0) {
7018
+ render.err("No meshes joined.");
7019
+ render.hint("claudemesh <invite-url> # join + launch");
7020
+ process.exit(1);
7021
+ }
7022
+ const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
7023
+ const wantsJson = flags.json !== undefined && flags.json !== false;
7024
+ const allJson = [];
7025
+ for (const slug of slugs) {
7026
+ try {
7027
+ const peers = await listPeersForMesh(slug);
7028
+ if (wantsJson) {
7029
+ const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
7030
+ allJson.push({ mesh: slug, peers: projected });
7031
+ continue;
7032
+ }
7033
+ render.section(`peers on ${slug} (${peers.length})`);
7034
+ if (peers.length === 0) {
7035
+ render.info(dim(" (no peers connected)"));
7036
+ continue;
7037
+ }
7038
+ for (const p of peers) {
7039
+ const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
7040
+ const statusDot = p.status === "working" ? yellow("●") : green("●");
7041
+ const name = bold(p.displayName);
7042
+ const meta = [];
7043
+ if (p.peerType)
7044
+ meta.push(p.peerType);
7045
+ if (p.channel)
7046
+ meta.push(p.channel);
7047
+ if (p.model)
7048
+ meta.push(p.model);
7049
+ const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
7050
+ const summary = p.summary ? dim(` — ${p.summary}`) : "";
7051
+ const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
7052
+ const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
7053
+ render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
7054
+ if (p.cwd)
7055
+ render.info(dim(` cwd: ${p.cwd}`));
7056
+ }
7057
+ } catch (e) {
7058
+ render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
7059
+ }
7060
+ }
7061
+ if (wantsJson) {
7062
+ process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
7063
+ `);
7064
+ }
7065
+ }
7066
+ var FIELD_ALIAS;
7067
+ var init_peers = __esm(() => {
7068
+ init_connect();
7069
+ init_facade();
7070
+ init_client3();
7071
+ init_render();
7072
+ init_styles();
7073
+ FIELD_ALIAS = {
7074
+ name: "displayName"
7075
+ };
7076
+ });
7077
+
7010
7078
  // src/commands/send.ts
7011
7079
  var exports_send = {};
7012
7080
  __export(exports_send, {
@@ -8441,12 +8509,32 @@ function migrateOutbox(db) {
8441
8509
  CREATE INDEX IF NOT EXISTS outbox_aborted
8442
8510
  ON outbox(status, aborted_at) WHERE status = 'aborted';
8443
8511
  `);
8512
+ const hasMesh = columnExists(db, "outbox", "mesh");
8513
+ const hasTargetSpec = columnExists(db, "outbox", "target_spec");
8514
+ const hasNonce = columnExists(db, "outbox", "nonce");
8515
+ const hasCiphertext = columnExists(db, "outbox", "ciphertext");
8516
+ const hasPriority = columnExists(db, "outbox", "priority");
8517
+ if (!hasMesh)
8518
+ db.exec(`ALTER TABLE outbox ADD COLUMN mesh TEXT`);
8519
+ if (!hasTargetSpec)
8520
+ db.exec(`ALTER TABLE outbox ADD COLUMN target_spec TEXT`);
8521
+ if (!hasNonce)
8522
+ db.exec(`ALTER TABLE outbox ADD COLUMN nonce TEXT`);
8523
+ if (!hasCiphertext)
8524
+ db.exec(`ALTER TABLE outbox ADD COLUMN ciphertext TEXT`);
8525
+ if (!hasPriority)
8526
+ db.exec(`ALTER TABLE outbox ADD COLUMN priority TEXT`);
8527
+ }
8528
+ function columnExists(db, table, column) {
8529
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
8530
+ return rows.some((r) => r.name === column);
8444
8531
  }
8445
8532
  function findByClientId(db, clientMessageId) {
8446
8533
  const row = db.prepare(`
8447
8534
  SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8448
8535
  attempts, next_attempt_at, status, last_error, delivered_at,
8449
- broker_message_id, aborted_at, aborted_by, superseded_by
8536
+ broker_message_id, aborted_at, aborted_by, superseded_by,
8537
+ mesh, target_spec, nonce, ciphertext, priority
8450
8538
  FROM outbox WHERE client_message_id = ?
8451
8539
  `).get(clientMessageId);
8452
8540
  return row ?? null;
@@ -8455,9 +8543,10 @@ function insertPending(db, input) {
8455
8543
  db.prepare(`
8456
8544
  INSERT INTO outbox (
8457
8545
  id, client_message_id, request_fingerprint, payload,
8458
- enqueued_at, attempts, next_attempt_at, status
8459
- ) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending')
8460
- `).run(input.id, input.client_message_id, input.request_fingerprint, input.payload, input.now, input.now);
8546
+ enqueued_at, attempts, next_attempt_at, status,
8547
+ mesh, target_spec, nonce, ciphertext, priority
8548
+ ) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending', ?, ?, ?, ?, ?)
8549
+ `).run(input.id, input.client_message_id, input.request_fingerprint, input.payload, input.now, input.now, input.mesh ?? null, input.target_spec ?? null, input.nonce ?? null, input.ciphertext ?? null, input.priority ?? null);
8461
8550
  }
8462
8551
  function fingerprintsEqual(a, b) {
8463
8552
  if (a.length !== b.length)
@@ -8477,7 +8566,8 @@ function listOutbox(db, p = {}) {
8477
8566
  const sql = `
8478
8567
  SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8479
8568
  attempts, next_attempt_at, status, last_error, delivered_at,
8480
- broker_message_id, aborted_at, aborted_by, superseded_by
8569
+ broker_message_id, aborted_at, aborted_by, superseded_by,
8570
+ mesh, target_spec, nonce, ciphertext, priority
8481
8571
  FROM outbox
8482
8572
  ${where.length ? "WHERE " + where.join(" AND ") : ""}
8483
8573
  ORDER BY enqueued_at DESC
@@ -8490,7 +8580,8 @@ function findById(db, id) {
8490
8580
  return db.prepare(`
8491
8581
  SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8492
8582
  attempts, next_attempt_at, status, last_error, delivered_at,
8493
- broker_message_id, aborted_at, aborted_by, superseded_by
8583
+ broker_message_id, aborted_at, aborted_by, superseded_by,
8584
+ mesh, target_spec, nonce, ciphertext, priority
8494
8585
  FROM outbox WHERE id = ?
8495
8586
  `).get(id) ?? null;
8496
8587
  }
@@ -8633,7 +8724,12 @@ function acceptSend(req, deps) {
8633
8724
  client_message_id: clientId,
8634
8725
  request_fingerprint: fingerprint,
8635
8726
  payload: body,
8636
- now
8727
+ now,
8728
+ mesh: req.mesh,
8729
+ target_spec: req.target_spec,
8730
+ nonce: req.nonce,
8731
+ ciphertext: req.ciphertext,
8732
+ priority: req.priority
8637
8733
  });
8638
8734
  return { kind: "accepted_pending", status: 202, client_message_id: clientId };
8639
8735
  }
@@ -8813,7 +8909,8 @@ function startIpcServer(opts) {
8813
8909
  outboxDb: opts.outboxDb,
8814
8910
  inboxDb: opts.inboxDb,
8815
8911
  bus: opts.bus,
8816
- broker: opts.broker,
8912
+ brokers: opts.brokers,
8913
+ meshConfigs: opts.meshConfigs,
8817
8914
  onPendingInserted: opts.onPendingInserted
8818
8915
  });
8819
8916
  if (existsSync9(DAEMON_PATHS.SOCK_FILE)) {
@@ -8924,34 +9021,58 @@ function makeHandler(opts) {
8924
9021
  return;
8925
9022
  }
8926
9023
  if (req.method === "GET" && url.pathname === "/v1/peers") {
8927
- if (!opts.broker) {
9024
+ if (!opts.brokers || opts.brokers.size === 0) {
8928
9025
  respond(res, 503, { error: "broker not initialised" });
8929
9026
  return;
8930
9027
  }
9028
+ const filterMesh = url.searchParams.get("mesh") ?? undefined;
8931
9029
  try {
8932
- const peers = await opts.broker.listPeers();
8933
- respond(res, 200, { peers });
9030
+ const all = [];
9031
+ for (const [slug, b] of opts.brokers.entries()) {
9032
+ if (filterMesh && filterMesh !== slug)
9033
+ continue;
9034
+ try {
9035
+ const peers = await b.listPeers();
9036
+ for (const p of peers)
9037
+ all.push({ ...p, mesh: slug });
9038
+ } catch (e) {
9039
+ opts.log("warn", "ipc_peers_broker_failed", { mesh: slug, err: String(e) });
9040
+ }
9041
+ }
9042
+ respond(res, 200, { peers: all });
8934
9043
  } catch (e) {
8935
9044
  respond(res, 502, { error: "broker_unreachable", detail: String(e) });
8936
9045
  }
8937
9046
  return;
8938
9047
  }
8939
9048
  if (req.method === "GET" && url.pathname === "/v1/skills") {
8940
- if (!opts.broker) {
9049
+ if (!opts.brokers || opts.brokers.size === 0) {
8941
9050
  respond(res, 503, { error: "broker not initialised" });
8942
9051
  return;
8943
9052
  }
8944
9053
  const query = url.searchParams.get("query") ?? undefined;
9054
+ const filterMesh = url.searchParams.get("mesh") ?? undefined;
8945
9055
  try {
8946
- const skills = await opts.broker.listSkills(query);
8947
- respond(res, 200, { skills });
9056
+ const all = [];
9057
+ for (const [slug, b] of opts.brokers.entries()) {
9058
+ if (filterMesh && filterMesh !== slug)
9059
+ continue;
9060
+ try {
9061
+ const skills = await b.listSkills(query);
9062
+ for (const s of skills)
9063
+ all.push({ ...s, mesh: slug });
9064
+ } catch (e) {
9065
+ opts.log("warn", "ipc_skills_broker_failed", { mesh: slug, err: String(e) });
9066
+ }
9067
+ }
9068
+ respond(res, 200, { skills: all });
8948
9069
  } catch (e) {
8949
9070
  respond(res, 502, { error: "broker_unreachable", detail: String(e) });
8950
9071
  }
8951
9072
  return;
8952
9073
  }
8953
9074
  if (req.method === "GET" && url.pathname.startsWith("/v1/skills/")) {
8954
- if (!opts.broker) {
9075
+ if (!opts.brokers || opts.brokers.size === 0) {
8955
9076
  respond(res, 503, { error: "broker not initialised" });
8956
9077
  return;
8957
9078
  }
@@ -8960,20 +9081,25 @@ function makeHandler(opts) {
8960
9081
  respond(res, 400, { error: "missing skill name" });
8961
9082
  return;
8962
9083
  }
9084
+ const filterMesh = url.searchParams.get("mesh") ?? undefined;
8963
9085
  try {
8964
- const skill = await opts.broker.getSkill(name);
8965
- if (!skill) {
8966
- respond(res, 404, { error: "skill_not_found", name });
8967
- return;
9086
+ for (const [slug, b] of opts.brokers.entries()) {
9087
+ if (filterMesh && filterMesh !== slug)
9088
+ continue;
9089
+ const skill = await b.getSkill(name).catch(() => null);
9090
+ if (skill) {
9091
+ respond(res, 200, { skill: { ...skill, mesh: slug } });
9092
+ return;
9093
+ }
8968
9094
  }
8969
- respond(res, 200, { skill });
9095
+ respond(res, 404, { error: "skill_not_found", name });
8970
9096
  } catch (e) {
8971
9097
  respond(res, 502, { error: "broker_unreachable", detail: String(e) });
8972
9098
  }
8973
9099
  return;
8974
9100
  }
8975
9101
  if (req.method === "POST" && url.pathname === "/v1/profile") {
8976
- if (!opts.broker) {
9102
+ if (!opts.brokers || opts.brokers.size === 0) {
8977
9103
  respond(res, 503, { error: "broker not initialised" });
8978
9104
  return;
8979
9105
  }
@@ -8983,26 +9109,34 @@ function makeHandler(opts) {
8983
9109
  respond(res, 400, { error: "expected JSON object" });
8984
9110
  return;
8985
9111
  }
9112
+ const requested = (typeof body.mesh === "string" ? body.mesh : url.searchParams.get("mesh")) || null;
9113
+ const targets = requested ? [opts.brokers.get(requested)].filter(Boolean) : [...opts.brokers.values()];
9114
+ if (targets.length === 0) {
9115
+ respond(res, 404, { error: "mesh_not_attached", mesh: requested });
9116
+ return;
9117
+ }
8986
9118
  const updates = {};
8987
- if (typeof body.summary === "string")
8988
- opts.broker.setSummary(body.summary);
8989
- if (body.status === "idle" || body.status === "working" || body.status === "dnd")
8990
- opts.broker.setStatus(body.status);
8991
- if (typeof body.visible === "boolean")
8992
- opts.broker.setVisible(body.visible);
8993
- const profile = {};
8994
- if (typeof body.avatar === "string")
8995
- profile.avatar = body.avatar;
8996
- if (typeof body.title === "string")
8997
- profile.title = body.title;
8998
- if (typeof body.bio === "string")
8999
- profile.bio = body.bio;
9000
- if (Array.isArray(body.capabilities))
9001
- profile.capabilities = body.capabilities.filter((c) => typeof c === "string");
9002
- if (Object.keys(profile).length > 0)
9003
- opts.broker.setProfile(profile);
9119
+ for (const b of targets) {
9120
+ if (typeof body.summary === "string")
9121
+ b.setSummary(body.summary);
9122
+ if (body.status === "idle" || body.status === "working" || body.status === "dnd")
9123
+ b.setStatus(body.status);
9124
+ if (typeof body.visible === "boolean")
9125
+ b.setVisible(body.visible);
9126
+ const profile = {};
9127
+ if (typeof body.avatar === "string")
9128
+ profile.avatar = body.avatar;
9129
+ if (typeof body.title === "string")
9130
+ profile.title = body.title;
9131
+ if (typeof body.bio === "string")
9132
+ profile.bio = body.bio;
9133
+ if (Array.isArray(body.capabilities))
9134
+ profile.capabilities = body.capabilities.filter((c) => typeof c === "string");
9135
+ if (Object.keys(profile).length > 0)
9136
+ b.setProfile(profile);
9137
+ }
9004
9138
  Object.assign(updates, body);
9005
- respond(res, 200, { ok: true, applied: Object.keys(updates) });
9139
+ respond(res, 200, { ok: true, applied: Object.keys(updates), meshes: requested ? [requested] : [...opts.brokers.keys()] });
9006
9140
  } catch (e) {
9007
9141
  respond(res, 400, { error: String(e) });
9008
9142
  }
@@ -9120,6 +9254,36 @@ function makeHandler(opts) {
9120
9254
  respond(res, 400, { error: parsed.error });
9121
9255
  return;
9122
9256
  }
9257
+ if (opts.brokers && opts.brokers.size > 0 && opts.meshConfigs) {
9258
+ let chosenSlug = parsed.req.mesh ?? null;
9259
+ if (!chosenSlug && opts.brokers.size === 1) {
9260
+ chosenSlug = opts.brokers.keys().next().value;
9261
+ }
9262
+ if (!chosenSlug) {
9263
+ respond(res, 400, {
9264
+ error: "mesh_required",
9265
+ detail: `daemon attached to ${opts.brokers.size} meshes; pass 'mesh' in request body`,
9266
+ attached: [...opts.brokers.keys()]
9267
+ });
9268
+ return;
9269
+ }
9270
+ const broker = opts.brokers.get(chosenSlug);
9271
+ const meshCfg = opts.meshConfigs.get(chosenSlug);
9272
+ if (!broker || !meshCfg) {
9273
+ respond(res, 404, { error: "mesh_not_attached", mesh: chosenSlug });
9274
+ return;
9275
+ }
9276
+ try {
9277
+ const routed = await resolveAndEncrypt(parsed.req, broker, meshCfg.secretKey, chosenSlug);
9278
+ parsed.req.target_spec = routed.target_spec;
9279
+ parsed.req.ciphertext = routed.ciphertext;
9280
+ parsed.req.nonce = routed.nonce;
9281
+ parsed.req.mesh = routed.mesh;
9282
+ } catch (e) {
9283
+ respond(res, 502, { error: "route_failed", detail: String(e) });
9284
+ return;
9285
+ }
9286
+ }
9123
9287
  const outcome = acceptSend(parsed.req, { db: opts.outboxDb });
9124
9288
  switch (outcome.kind) {
9125
9289
  case "accepted_pending":
@@ -9212,6 +9376,7 @@ function parseSendRequest(body, idempotencyHeader) {
9212
9376
  const headerId = Array.isArray(idempotencyHeader) ? idempotencyHeader[0] : idempotencyHeader;
9213
9377
  const client_message_id = typeof b.client_message_id === "string" && b.client_message_id.trim() ? b.client_message_id.trim() : typeof headerId === "string" && headerId.trim() ? headerId.trim() : undefined;
9214
9378
  const reply_to_id = typeof b.reply_to_id === "string" ? b.reply_to_id : undefined;
9379
+ const mesh = typeof b.mesh === "string" ? b.mesh.trim() : undefined;
9215
9380
  return {
9216
9381
  req: {
9217
9382
  to,
@@ -9221,10 +9386,53 @@ function parseSendRequest(body, idempotencyHeader) {
9221
9386
  reply_to_id,
9222
9387
  client_message_id,
9223
9388
  destination_kind,
9224
- destination_ref
9389
+ destination_ref,
9390
+ mesh
9225
9391
  }
9226
9392
  };
9227
9393
  }
9394
+ async function resolveAndEncrypt(req, broker, meshSecretKey, meshSlug) {
9395
+ const { encryptDirect: encryptDirect2 } = await Promise.resolve().then(() => (init_box(), exports_box));
9396
+ const { randomBytes: randomBytes5 } = await import("node:crypto");
9397
+ const to = req.to.trim();
9398
+ if (to.startsWith("#") && /^#[0-9a-z_-]{20,}$/i.test(to)) {
9399
+ const ciphertext = Buffer.from(req.message, "utf8").toString("base64");
9400
+ const nonce = randomBytes5(24).toString("base64");
9401
+ return { target_spec: to, ciphertext, nonce, mesh: meshSlug ?? "" };
9402
+ }
9403
+ if (to.startsWith("@") || to === "*") {
9404
+ const ciphertext = Buffer.from(req.message, "utf8").toString("base64");
9405
+ const nonce = randomBytes5(24).toString("base64");
9406
+ return { target_spec: to, ciphertext, nonce, mesh: meshSlug ?? "" };
9407
+ }
9408
+ if (/^[0-9a-f]{64}$/i.test(to)) {
9409
+ const sessionKeys2 = broker.getSessionKeys();
9410
+ const senderSecret2 = sessionKeys2?.sessionSecretKey ?? meshSecretKey;
9411
+ const env3 = await encryptDirect2(req.message, to, senderSecret2);
9412
+ return { target_spec: to, ciphertext: env3.ciphertext, nonce: env3.nonce, mesh: meshSlug ?? "" };
9413
+ }
9414
+ const peers = await broker.listPeers().catch(() => []);
9415
+ if (/^[0-9a-f]{16,63}$/i.test(to)) {
9416
+ const matches2 = peers.filter((p) => p.pubkey.toLowerCase().startsWith(to.toLowerCase()) || (p.memberPubkey ?? "").toLowerCase().startsWith(to.toLowerCase()));
9417
+ if (matches2.length === 0)
9418
+ throw new Error(`no peer matching prefix "${to}"`);
9419
+ if (matches2.length > 1)
9420
+ throw new Error(`prefix "${to}" is ambiguous (${matches2.length} matches)`);
9421
+ const recipient2 = matches2[0].pubkey;
9422
+ const sessionKeys2 = broker.getSessionKeys();
9423
+ const senderSecret2 = sessionKeys2?.sessionSecretKey ?? meshSecretKey;
9424
+ const env3 = await encryptDirect2(req.message, recipient2, senderSecret2);
9425
+ return { target_spec: recipient2, ciphertext: env3.ciphertext, nonce: env3.nonce, mesh: meshSlug ?? "" };
9426
+ }
9427
+ const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
9428
+ if (!match)
9429
+ throw new Error(`peer "${to}" not found`);
9430
+ const recipient = match.pubkey;
9431
+ const sessionKeys = broker.getSessionKeys();
9432
+ const senderSecret = sessionKeys?.sessionSecretKey ?? meshSecretKey;
9433
+ const env2 = await encryptDirect2(req.message, recipient, senderSecret);
9434
+ return { target_spec: recipient, ciphertext: env2.ciphertext, nonce: env2.nonce, mesh: meshSlug ?? "" };
9435
+ }
9228
9436
  function respond(res, status, body) {
9229
9437
  const json = JSON.stringify(body);
9230
9438
  res.statusCode = status;
@@ -9632,7 +9840,8 @@ function startDrainWorker(opts) {
9632
9840
  async function drainOnce(opts, log2) {
9633
9841
  const now = Date.now();
9634
9842
  const rows = opts.db.prepare(`
9635
- SELECT id, client_message_id, request_fingerprint, payload, attempts
9843
+ SELECT id, client_message_id, request_fingerprint, payload, attempts,
9844
+ target_spec, nonce, ciphertext, priority, mesh
9636
9845
  FROM outbox
9637
9846
  WHERE status = 'pending' AND next_attempt_at <= ?
9638
9847
  ORDER BY enqueued_at
@@ -9644,15 +9853,37 @@ async function drainOnce(opts, log2) {
9644
9853
  if (markInflight(opts.db, row.id, now) === 0)
9645
9854
  continue;
9646
9855
  const fpHex = bufferToHex(row.request_fingerprint);
9647
- const sessionKeys = opts.broker.getSessionKeys();
9648
- const targetSpec = "*";
9649
- const nonce = await randomNonce2();
9650
- const ciphertext = Buffer.from(row.payload).toString("base64");
9856
+ let broker;
9857
+ if (row.mesh) {
9858
+ broker = opts.brokers.get(row.mesh);
9859
+ } else if (opts.brokers.size === 1) {
9860
+ broker = opts.brokers.values().next().value;
9861
+ }
9862
+ if (!broker) {
9863
+ log2("warn", "drain_no_broker_for_mesh", { id: row.id, mesh: row.mesh ?? "(null)" });
9864
+ markDead(opts.db, row.id, `no_broker_for_mesh:${row.mesh ?? "null"}`);
9865
+ continue;
9866
+ }
9867
+ let targetSpec;
9868
+ let nonce;
9869
+ let ciphertext;
9870
+ let priority;
9871
+ if (row.target_spec && row.nonce && row.ciphertext) {
9872
+ targetSpec = row.target_spec;
9873
+ nonce = row.nonce;
9874
+ ciphertext = row.ciphertext;
9875
+ priority = row.priority === "now" || row.priority === "low" ? row.priority : "next";
9876
+ } else {
9877
+ targetSpec = "*";
9878
+ nonce = await randomNonce2();
9879
+ ciphertext = Buffer.from(row.payload).toString("base64");
9880
+ priority = "next";
9881
+ }
9651
9882
  let res;
9652
9883
  try {
9653
- res = await opts.broker.send({
9884
+ res = await broker.send({
9654
9885
  targetSpec,
9655
- priority: "next",
9886
+ priority,
9656
9887
  nonce,
9657
9888
  ciphertext,
9658
9889
  client_message_id: row.client_message_id,
@@ -9996,10 +10227,10 @@ async function runDaemon(opts = {}) {
9996
10227
  }
9997
10228
  const bus = new EventBus;
9998
10229
  const cfg = readConfig();
9999
- let mesh = null;
10230
+ let meshes;
10000
10231
  if (opts.mesh) {
10001
- mesh = cfg.meshes.find((m) => m.slug === opts.mesh) ?? null;
10002
- if (!mesh) {
10232
+ const found = cfg.meshes.find((m) => m.slug === opts.mesh);
10233
+ if (!found) {
10003
10234
  process.stderr.write(`mesh not found: ${opts.mesh}
10004
10235
  `);
10005
10236
  process.stderr.write(`joined meshes: ${cfg.meshes.map((m) => m.slug).join(", ") || "(none)"}
@@ -10010,8 +10241,7 @@ async function runDaemon(opts = {}) {
10010
10241
  } catch {}
10011
10242
  return 2;
10012
10243
  }
10013
- } else if (cfg.meshes.length === 1) {
10014
- mesh = cfg.meshes[0];
10244
+ meshes = [found];
10015
10245
  } else if (cfg.meshes.length === 0) {
10016
10246
  process.stderr.write(`no mesh joined; run \`claudemesh join <invite-url>\` first
10017
10247
  `);
@@ -10021,43 +10251,41 @@ async function runDaemon(opts = {}) {
10021
10251
  } catch {}
10022
10252
  return 2;
10023
10253
  } else {
10024
- process.stderr.write(`multiple meshes joined; pass --mesh <slug>
10025
- `);
10026
- process.stderr.write(`available: ${cfg.meshes.map((m) => m.slug).join(", ")}
10027
- `);
10028
- releaseSingletonLock();
10029
- try {
10030
- outboxDb.close();
10031
- } catch {}
10032
- return 2;
10033
- }
10034
- const broker = new DaemonBrokerClient(mesh, {
10035
- displayName: opts.displayName,
10036
- onStatusChange: (s) => {
10037
- process.stdout.write(JSON.stringify({
10038
- msg: "broker_status",
10039
- status: s,
10040
- mesh: mesh.slug,
10041
- ts: new Date().toISOString()
10042
- }) + `
10254
+ meshes = cfg.meshes;
10255
+ }
10256
+ const brokers = new Map;
10257
+ const meshConfigs = new Map;
10258
+ for (const mesh of meshes) {
10259
+ meshConfigs.set(mesh.slug, mesh);
10260
+ const broker = new DaemonBrokerClient(mesh, {
10261
+ displayName: opts.displayName,
10262
+ onStatusChange: (s) => {
10263
+ process.stdout.write(JSON.stringify({
10264
+ msg: "broker_status",
10265
+ status: s,
10266
+ mesh: mesh.slug,
10267
+ ts: new Date().toISOString()
10268
+ }) + `
10043
10269
  `);
10044
- bus.publish("broker_status", { mesh: mesh.slug, status: s });
10045
- },
10046
- onPush: (m) => {
10047
- const sessionKeys = broker.getSessionKeys();
10048
- handleBrokerPush(m, {
10049
- db: inboxDb,
10050
- bus,
10051
- meshSlug: mesh.slug,
10052
- recipientSecretKeyHex: mesh.secretKey,
10053
- sessionSecretKeyHex: sessionKeys?.sessionSecretKey
10054
- });
10055
- }
10056
- });
10057
- broker.connect().catch((err) => process.stderr.write(`broker connect failed: ${String(err)}
10270
+ bus.publish("broker_status", { mesh: mesh.slug, status: s });
10271
+ },
10272
+ onPush: (m) => {
10273
+ const sessionKeys = broker.getSessionKeys();
10274
+ handleBrokerPush(m, {
10275
+ db: inboxDb,
10276
+ bus,
10277
+ meshSlug: mesh.slug,
10278
+ recipientSecretKeyHex: mesh.secretKey,
10279
+ sessionSecretKeyHex: sessionKeys?.sessionSecretKey
10280
+ });
10281
+ }
10282
+ });
10283
+ broker.connect().catch((err) => process.stderr.write(`broker connect failed for ${mesh.slug}: ${String(err)}
10058
10284
  `));
10285
+ brokers.set(mesh.slug, broker);
10286
+ }
10059
10287
  let drain = null;
10060
- drain = startDrainWorker({ db: outboxDb, broker });
10288
+ drain = startDrainWorker({ db: outboxDb, brokers });
10061
10289
  const ipc2 = startIpcServer({
10062
10290
  localToken,
10063
10291
  tcpEnabled,
@@ -10065,7 +10293,8 @@ async function runDaemon(opts = {}) {
10065
10293
  outboxDb,
10066
10294
  inboxDb,
10067
10295
  bus,
10068
- broker,
10296
+ brokers,
10297
+ meshConfigs,
10069
10298
  onPendingInserted: () => drain?.wake()
10070
10299
  });
10071
10300
  try {
@@ -10081,7 +10310,7 @@ async function runDaemon(opts = {}) {
10081
10310
  pid: process.pid,
10082
10311
  sock: DAEMON_PATHS.SOCK_FILE,
10083
10312
  tcp: tcpEnabled ? `127.0.0.1:47823` : null,
10084
- mesh: mesh.slug,
10313
+ meshes: meshes.map((m) => m.slug),
10085
10314
  ts: new Date().toISOString()
10086
10315
  }) + `
10087
10316
  `);
@@ -10094,7 +10323,11 @@ async function runDaemon(opts = {}) {
10094
10323
  `);
10095
10324
  if (drain)
10096
10325
  await drain.close();
10097
- await broker.close();
10326
+ for (const b of brokers.values()) {
10327
+ try {
10328
+ await b.close();
10329
+ } catch {}
10330
+ }
10098
10331
  await ipc2.close();
10099
10332
  try {
10100
10333
  outboxDb.close();
@@ -13263,6 +13496,32 @@ async function runSqlSchema(opts) {
13263
13496
  });
13264
13497
  }
13265
13498
  async function runSkillList(opts) {
13499
+ try {
13500
+ const { tryListSkillsViaDaemon: tryListSkillsViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
13501
+ const dr = await tryListSkillsViaDaemon2();
13502
+ if (dr !== null) {
13503
+ const skills = dr;
13504
+ if (opts.json) {
13505
+ emitJson(skills);
13506
+ return EXIT.SUCCESS;
13507
+ }
13508
+ if (skills.length === 0) {
13509
+ render.info(dim("(no skills)"));
13510
+ return EXIT.SUCCESS;
13511
+ }
13512
+ render.section(`mesh skills (${skills.length})`);
13513
+ for (const s of skills) {
13514
+ process.stdout.write(` ${bold(s.name)} ${dim("· by " + s.author)}
13515
+ `);
13516
+ process.stdout.write(` ${s.description}
13517
+ `);
13518
+ if (s.tags?.length)
13519
+ process.stdout.write(` ${dim("tags: " + s.tags.join(", "))}
13520
+ `);
13521
+ }
13522
+ return EXIT.SUCCESS;
13523
+ }
13524
+ } catch {}
13266
13525
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
13267
13526
  const skills = await client.listSkills(opts.query);
13268
13527
  if (opts.json) {
@@ -13291,6 +13550,29 @@ async function runSkillGet(name, opts) {
13291
13550
  render.err("Usage: claudemesh skill get <name>");
13292
13551
  return EXIT.INVALID_ARGS;
13293
13552
  }
13553
+ try {
13554
+ const { tryGetSkillViaDaemon: tryGetSkillViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
13555
+ const dr = await tryGetSkillViaDaemon2(name);
13556
+ if (dr !== null) {
13557
+ const skill = dr;
13558
+ if (opts.json) {
13559
+ emitJson(skill);
13560
+ return EXIT.SUCCESS;
13561
+ }
13562
+ render.section(skill.name);
13563
+ render.kv([
13564
+ ["author", skill.author],
13565
+ ["created", skill.createdAt],
13566
+ ["tags", skill.tags?.join(", ") || dim("(none)")]
13567
+ ]);
13568
+ render.blank();
13569
+ render.info(skill.description);
13570
+ render.blank();
13571
+ process.stdout.write(skill.instructions + `
13572
+ `);
13573
+ return EXIT.SUCCESS;
13574
+ }
13575
+ } catch {}
13294
13576
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
13295
13577
  const skill = await client.getSkill(name);
13296
13578
  if (!skill) {
@@ -13808,7 +14090,7 @@ claudemesh send "<from_name>" "..." --mesh "<mesh_slug>"
13808
14090
 
13809
14091
  If the parent Claude session was launched via \`claudemesh launch\`, an MCP push-pipe is running and holds the per-mesh WS connection. CLI invocations dial \`~/.claudemesh/sockets/<mesh-slug>.sock\` and reuse that warm connection (~200ms total round-trip including Node.js startup). If no push-pipe is running (cron, scripts, hooks fired outside a session), the CLI opens its own WS, which takes ~500-700ms cold. **You don't manage this** — every verb auto-detects and falls through.
13810
14092
 
13811
- ### Daemon path (v0.9.0, opt-in, fastest)
14093
+ ### Daemon path (v1.24.0+, REQUIRED for in-Claude-Code use)
13812
14094
 
13813
14095
  \`claudemesh daemon up [--mesh <slug>]\` starts a persistent per-user runtime that holds the broker WS, a durable SQLite outbox/inbox, and listens on \`~/.claudemesh/daemon/daemon.sock\` (UDS) plus an optional loopback TCP. When the daemon socket is present, every verb routes through it first (~1ms IPC) before falling back to bridge / cold paths. The send envelope carries a caller-stable \`client_message_id\`, so a \`claudemesh send\` that started before a daemon crash survives the restart via the on-disk outbox.
13814
14096
 
@@ -13823,11 +14105,15 @@ claudemesh daemon outbox requeue <id> # re-enqueue an aborted/d
13823
14105
  claudemesh daemon down # SIGTERM + wait
13824
14106
  \`\`\`
13825
14107
 
13826
- \`claudemesh install\` (MCP + hooks registration) and the daemon are independent install does not start the daemon, and the daemon does not require install. Run both for the warmest path: install gives you the in-session push-pipe, daemon gives you cross-invocation persistence and a survivable outbox.
14108
+ As of 1.24.0 \`claudemesh install\` registers the MCP entry **and** installs/starts the daemon service for the user's primary mesh. The MCP shim hard-requires the daemon to be running — it bails at boot with actionable instructions if the socket isn't present. There is no fallback. CLI verbs (\`send\`, \`peer list\`, \`inbox\`, \`skill list/get\`, etc.) keep working without a daemon via bridge or cold paths, but for any in-Claude-Code use the daemon must be up.
14109
+
14110
+ ### Ambient mode (1.25.0+)
14111
+
14112
+ Once \`claudemesh install\` has run (registers MCP entry + starts daemon service), **raw \`claude\` Just Works** for the daemon's attached mesh. No \`claudemesh launch\` ceremony, no manual flags, no per-session keypair. Channel push, slash commands, and resources all flow through the daemon-backed MCP shim. Use \`claudemesh launch\` only when you need to override defaults (different mesh, custom display name, system-prompt injection, headless modes).
13827
14113
 
13828
14114
  ## Spawning new sessions (no wizard)
13829
14115
 
13830
- \`claudemesh launch\` is the canonical way to start a new Claude Code session connected to claudemesh. Pass every required flag up front so no interactive prompt fires — that's what makes the verb scriptable from tmux send-keys, AppleScript/iTerm spawn helpers, hooks, cron, and the \`claudemesh launch\` you call from inside another session. **Always use this verb, never \`claude\` directly with hand-rolled flags** — it sets up the per-session ed25519 keypair, exports \`CLAUDEMESH_DISPLAY_NAME\`, isolates the mesh config in a tmpdir, and passes the \`--dangerously-load-development-channels server:claudemesh\` plumbing that the MCP push-pipe needs.
14116
+ \`claudemesh launch\` remains useful for non-default cases: explicit mesh selection, fresh display name, headless \`--quiet\` runs, system-prompt injection, multi-mesh users with one daemon attached to mesh A who want to spawn into mesh B. For the common case (single joined mesh, daemon installed), prefer raw \`claude\`. Pass every required flag up front so no interactive prompt fires — that's what makes the verb scriptable from tmux send-keys, AppleScript/iTerm spawn helpers, hooks, cron, and the \`claudemesh launch\` you call from inside another session.
13831
14117
 
13832
14118
  ### Full flag surface
13833
14119
 
@@ -18376,4 +18662,4 @@ main().catch((err) => {
18376
18662
  process.exit(EXIT.INTERNAL_ERROR);
18377
18663
  });
18378
18664
 
18379
- //# debugId=BAEC2C4FE7977E7264756E2164756E21
18665
+ //# debugId=3BADD117052552A564756E2164756E21