camstack 1.2.213 → 1.2.215

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.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ALL_CAPABILITY_DEFINITIONS,
4
+ ActionTargetSchema,
4
5
  AlarmArmModeSchema,
5
6
  AutomationRecipeSchema,
6
7
  DETECTOR_PERF,
@@ -13,13 +14,23 @@ import {
13
14
  classifyInferenceHardware,
14
15
  convertLegacyWhen,
15
16
  countWhenLeaves,
16
- findHubs,
17
17
  generateAutomationBlock,
18
18
  isReadingQuantity,
19
19
  prepareRecipeUnits,
20
20
  unitsForQuantity,
21
21
  validateRecipeBounds
22
- } from "./chunk-MJJWVAEM.js";
22
+ } from "./chunk-WGHOCSCW.js";
23
+ import {
24
+ IMAGE_TAG_BY_ACCELERATOR,
25
+ MUST_NOT_OMIT,
26
+ NO_BARE_LATEST,
27
+ cliVerdict,
28
+ isOlder,
29
+ registerSetupTools,
30
+ runCommand,
31
+ text
32
+ } from "./chunk-37A556YF.js";
33
+ import "./chunk-JPGJK5DC.js";
23
34
  import "./chunk-LMMQX4CK.js";
24
35
 
25
36
  // src/mcp/server.ts
@@ -29,14 +40,12 @@ import { MutationGate, tryConnectHub } from "@camstack/develop";
29
40
 
30
41
  // src/mcp/configure-tools.ts
31
42
  import {
32
- runGated as runGated4,
33
- text as text13
43
+ runGated as runGated4
34
44
  } from "@camstack/develop";
35
- import { z as z28 } from "zod";
45
+ import { z as z24 } from "zod";
36
46
 
37
47
  // src/mcp/configure-automations.ts
38
48
  import { z as z4 } from "zod";
39
- import { text } from "@camstack/develop";
40
49
 
41
50
  // src/mcp/configure-common.ts
42
51
  import { runGated } from "@camstack/develop";
@@ -594,16 +603,29 @@ var ActionAnswerSchema = z4.discriminatedUnion("kind", [
594
603
  z4.object({ kind: z4.literal("wait"), seconds: z4.number().min(0).max(300) }),
595
604
  z4.object({
596
605
  kind: z4.literal("cap"),
597
- deviceId: z4.union([
598
- z4.number().int().nonnegative(),
599
- z4.array(z4.number().int().nonnegative()).min(1)
600
- ]),
606
+ /** WHO the step acts on: named devices, or every device offering `actionId` (optionally in a room / of a type). */
607
+ target: ActionTargetSchema.optional(),
608
+ /** The vocabulary action (`cover.open`); required for a `domain` target. */
609
+ actionId: z4.string().min(1).optional(),
610
+ /** Shorthand for `target: { kind: 'devices', ids }`. */
611
+ deviceId: z4.union([z4.number().int().nonnegative(), z4.array(z4.number().int().nonnegative()).min(1)]).optional(),
601
612
  cap: z4.string().min(1),
602
613
  method: z4.string().min(1),
603
614
  /** A quantity argument is `{ value, unit }`; a bare number is stamped with the declared unit. */
604
615
  args: z4.record(z4.string(), z4.unknown()).optional()
616
+ }).refine((a) => a.target !== void 0 || a.deviceId !== void 0, {
617
+ message: "a cap step needs a target (or a deviceId)",
618
+ path: ["target"]
619
+ }).refine((a) => a.target?.kind !== "domain" || a.actionId !== void 0, {
620
+ message: "a domain target needs the vocabulary action it applies to (actionId)",
621
+ path: ["actionId"]
605
622
  })
606
623
  ]);
624
+ function targetOfAnswer(a) {
625
+ if (a.target !== void 0) return a.target;
626
+ const ids = Array.isArray(a.deviceId) ? [...a.deviceId] : a.deviceId === void 0 ? [] : [a.deviceId];
627
+ return { kind: "devices", ids };
628
+ }
607
629
  var AutomationTriggerAnswerSchema = z4.object({
608
630
  kind: z4.enum(["detection", "device-state", "schedule"]),
609
631
  // detection
@@ -653,7 +675,7 @@ var CONDITIONS_QUESTION = {
653
675
  };
654
676
  var ACTIONS_QUESTION = {
655
677
  field: "actions",
656
- ask: 'What should it do? A list of steps, in order: { kind: "cap", deviceId, cap, method, args? } or { kind: "wait", seconds }. deviceId may be a list to run the same call on several devices.'
678
+ ask: 'What should it do? A list of steps, in order: { kind: "cap", target, actionId?, cap, method, args? } or { kind: "wait", seconds }. target is { kind: "devices", ids: [...] } to name devices, or { kind: "domain", roomId?, deviceType? } with the vocabulary actionId (for example "cover.open") to act on every device that offers it \u2014 resolved when the step runs, so a device added later is included. deviceId (a number or a list) is still accepted as shorthand for named devices.'
657
679
  };
658
680
  function fieldChoices(fields) {
659
681
  return fields.map((f) => {
@@ -941,7 +963,8 @@ async function checkActions(client, actions) {
941
963
  const cache = /* @__PURE__ */ new Map();
942
964
  for (const [idx, action] of actions.entries()) {
943
965
  if (action.kind !== "cap") continue;
944
- const ids = Array.isArray(action.deviceId) ? action.deviceId : [action.deviceId];
966
+ const target = targetOfAnswer(action);
967
+ const ids = target.kind === "devices" ? target.ids : [];
945
968
  for (const id of ids) {
946
969
  let caps = cache.get(id);
947
970
  if (caps === void 0) {
@@ -979,7 +1002,8 @@ function toActions(actions) {
979
1002
  if (a.kind === "wait") return { kind: "wait", seconds: a.seconds };
980
1003
  return {
981
1004
  kind: "cap",
982
- deviceId: Array.isArray(a.deviceId) ? [...a.deviceId] : a.deviceId,
1005
+ target: targetOfAnswer(a),
1006
+ ...a.actionId !== void 0 ? { actionId: a.actionId } : {},
983
1007
  cap: a.cap,
984
1008
  method: a.method,
985
1009
  ...a.args !== void 0 ? { args: a.args } : {}
@@ -1023,9 +1047,10 @@ function summarize(trigger, conditions, actions, cooldown) {
1023
1047
  for (const a of actions) {
1024
1048
  if (a.kind === "wait") lines.push(` then wait ${a.seconds}s`);
1025
1049
  else {
1026
- const ids = Array.isArray(a.deviceId) ? a.deviceId.join(", ") : String(a.deviceId);
1050
+ const t = targetOfAnswer(a);
1051
+ const who = t.kind === "devices" ? `device ${t.ids.join(", ")}` : t.kind === "var" ? `device $${t.name}` : `every device that can ${a.actionId ?? a.method}${t.roomId === void 0 ? "" : ` in ${t.roomId}`}${t.deviceType === void 0 ? "" : ` of type ${t.deviceType}`}`;
1027
1052
  lines.push(
1028
- ` then ${a.cap}.${a.method} on device ${ids}${a.args ? ` with ${JSON.stringify(a.args)}` : ""}`
1053
+ ` then ${a.cap}.${a.method} on ${who}${a.args ? ` with ${JSON.stringify(a.args)}` : ""}`
1029
1054
  );
1030
1055
  }
1031
1056
  }
@@ -1236,7 +1261,6 @@ function registerAutomationTools(server, ctx) {
1236
1261
  }
1237
1262
 
1238
1263
  // src/mcp/configure-calibrate.ts
1239
- import { text as text2 } from "@camstack/develop";
1240
1264
  import { z as z5 } from "zod";
1241
1265
  var CALIBRATE_APPLY_TOOL = "configure_calibrate_apply";
1242
1266
  var WINDOW_MS = 24 * 36e5;
@@ -1602,7 +1626,7 @@ function registerCalibrateTools(server, ctx) {
1602
1626
  "configure_calibrate_camera",
1603
1627
  "Read-only calibration report for one camera: recent tracks and their top-score distribution per class, likely false-positive and missed patterns, the current motion and detection settings, zones and pipeline health, and the changes it proposes. Writes nothing.",
1604
1628
  { deviceId: z5.number().int().nonnegative() },
1605
- async ({ deviceId }) => text2(await calibrateCamera(ctx, { deviceId }))
1629
+ async ({ deviceId }) => text(await calibrateCamera(ctx, { deviceId }))
1606
1630
  );
1607
1631
  server.tool(
1608
1632
  CALIBRATE_APPLY_TOOL,
@@ -1613,13 +1637,12 @@ function registerCalibrateTools(server, ctx) {
1613
1637
  patch: z5.record(z5.string(), z5.union([z5.number(), z5.string(), z5.boolean()])),
1614
1638
  confirm_token: ConfirmTokenSchema
1615
1639
  },
1616
- async ({ deviceId, cap: cap2, patch, confirm_token }) => text2(await calibrateApply(ctx, { deviceId, cap: cap2, patch }, confirm_token))
1640
+ async ({ deviceId, cap: cap2, patch, confirm_token }) => text(await calibrateApply(ctx, { deviceId, cap: cap2, patch }, confirm_token))
1617
1641
  );
1618
1642
  }
1619
1643
 
1620
1644
  // src/mcp/configure-zones-alarm.ts
1621
1645
  import { randomUUID } from "crypto";
1622
- import { text as text3 } from "@camstack/develop";
1623
1646
  import { z as z6 } from "zod";
1624
1647
  var ZONE_UPSERT_TOOL = "configure_zone_upsert";
1625
1648
  var ALARM_SET_TOOL = "configure_alarm_set";
@@ -1910,7 +1933,7 @@ function registerZonesAlarmTools(server, ctx) {
1910
1933
  "configure_zones",
1911
1934
  "Read the detection zones drawn on one camera (id, name, kind, points, normalised 0..1) and a summary of its motion mask. Writes nothing.",
1912
1935
  { deviceId: z6.number().int().nonnegative() },
1913
- async ({ deviceId }) => text3(await readZonesTool(ctx, { deviceId }))
1936
+ async ({ deviceId }) => text(await readZonesTool(ctx, { deviceId }))
1914
1937
  );
1915
1938
  server.tool(
1916
1939
  ZONE_UPSERT_TOOL,
@@ -1923,7 +1946,7 @@ function registerZonesAlarmTools(server, ctx) {
1923
1946
  points: z6.array(PointSchema).min(2).max(64),
1924
1947
  confirm_token: ConfirmTokenSchema
1925
1948
  },
1926
- async ({ deviceId, zoneId, name, kind, points, confirm_token }) => text3(
1949
+ async ({ deviceId, zoneId, name, kind, points, confirm_token }) => text(
1927
1950
  await zoneUpsert(
1928
1951
  ctx,
1929
1952
  {
@@ -1941,7 +1964,7 @@ function registerZonesAlarmTools(server, ctx) {
1941
1964
  "configure_alarm",
1942
1965
  "Read the alarm panels of the hub: state, armed mode, the modes each accepts and whether it needs a PIN. Writes nothing.",
1943
1966
  { deviceId: z6.number().int().nonnegative().optional() },
1944
- async ({ deviceId }) => text3(await readAlarm(ctx, deviceId !== void 0 ? { deviceId } : {}))
1967
+ async ({ deviceId }) => text(await readAlarm(ctx, deviceId !== void 0 ? { deviceId } : {}))
1945
1968
  );
1946
1969
  server.tool(
1947
1970
  ALARM_SET_TOOL,
@@ -1952,7 +1975,7 @@ function registerZonesAlarmTools(server, ctx) {
1952
1975
  mode: AlarmArmModeSchema.optional(),
1953
1976
  confirm_token: ConfirmTokenSchema
1954
1977
  },
1955
- async ({ deviceId, action, mode, confirm_token }) => text3(
1978
+ async ({ deviceId, action, mode, confirm_token }) => text(
1956
1979
  await alarmSet(
1957
1980
  ctx,
1958
1981
  { deviceId, action, ...mode !== void 0 ? { mode } : {} },
@@ -1963,7 +1986,6 @@ function registerZonesAlarmTools(server, ctx) {
1963
1986
  }
1964
1987
 
1965
1988
  // src/mcp/configure-network.ts
1966
- import { text as text4 } from "@camstack/develop";
1967
1989
  import { z as z7 } from "zod";
1968
1990
  var REMOTE_ACCESS_SET_TOOL = "configure_remote_access_set";
1969
1991
  var RemoteAddonSchema = z7.enum(["cloudflare-tunnel", "cloudflare-turn", "static-turn"]);
@@ -2147,7 +2169,7 @@ function registerNetworkTools(server, ctx) {
2147
2169
  "configure_remote_access",
2148
2170
  "Read how the hub is reachable from outside: the access status and public endpoint, and the settings of the tunnel and TURN addons. Secret fields are reported as configured true/false, never their value. Writes nothing.",
2149
2171
  {},
2150
- async () => text4(await readRemoteAccess(ctx))
2172
+ async () => text(await readRemoteAccess(ctx))
2151
2173
  );
2152
2174
  server.tool(
2153
2175
  REMOTE_ACCESS_SET_TOOL,
@@ -2157,12 +2179,11 @@ function registerNetworkTools(server, ctx) {
2157
2179
  patch: z7.record(z7.string(), z7.union([z7.string(), z7.number(), z7.boolean()])),
2158
2180
  confirm_token: ConfirmTokenSchema
2159
2181
  },
2160
- async ({ addonId, patch, confirm_token }) => text4(await remoteAccessSet(ctx, { addonId, patch }, confirm_token))
2182
+ async ({ addonId, patch, confirm_token }) => text(await remoteAccessSet(ctx, { addonId, patch }, confirm_token))
2161
2183
  );
2162
2184
  }
2163
2185
 
2164
2186
  // src/mcp/configure-exports.ts
2165
- import { text as text5 } from "@camstack/develop";
2166
2187
  import { z as z8 } from "zod";
2167
2188
  var EXPORT_SET_TOOL = "configure_export_set";
2168
2189
  var ProvidersSchema = z8.array(z8.looseObject({ addonId: z8.string() }));
@@ -2325,7 +2346,7 @@ function registerExportTools(server, ctx) {
2325
2346
  "configure_exports",
2326
2347
  "Read the exporters (Alexa, Google Home, Home Assistant, HomeKit): link state, how many devices each shows and which cameras. Pairing codes are never shown. Writes nothing.",
2327
2348
  { deviceId: z8.number().int().nonnegative().optional() },
2328
- async ({ deviceId }) => text5(await readExports(ctx, deviceId !== void 0 ? { deviceId } : {}))
2349
+ async ({ deviceId }) => text(await readExports(ctx, deviceId !== void 0 ? { deviceId } : {}))
2329
2350
  );
2330
2351
  server.tool(
2331
2352
  EXPORT_SET_TOOL,
@@ -2336,18 +2357,17 @@ function registerExportTools(server, ctx) {
2336
2357
  exposed: z8.boolean(),
2337
2358
  confirm_token: ConfirmTokenSchema
2338
2359
  },
2339
- async ({ addonId, deviceId, exposed, confirm_token }) => text5(await exportSet(ctx, { addonId, deviceId, exposed }, confirm_token))
2360
+ async ({ addonId, deviceId, exposed, confirm_token }) => text(await exportSet(ctx, { addonId, deviceId, exposed }, confirm_token))
2340
2361
  );
2341
2362
  server.tool(
2342
2363
  "configure_ha_guide",
2343
2364
  "Read-only guide for showing CamStack cameras in Home Assistant, including the HACS route. Needs no hub.",
2344
2365
  {},
2345
- async () => text5({ status: "ok", guide: HA_GUIDE })
2366
+ async () => text({ status: "ok", guide: HA_GUIDE })
2346
2367
  );
2347
2368
  }
2348
2369
 
2349
2370
  // src/mcp/configure-oidc.ts
2350
- import { text as text6 } from "@camstack/develop";
2351
2371
  import { z as z10 } from "zod";
2352
2372
 
2353
2373
  // src/mcp/configure-oidc-providers.ts
@@ -2836,13 +2856,12 @@ function registerOidcTools(server, ctx) {
2836
2856
  OIDC_TOOL,
2837
2857
  "Connect an OpenID Connect identity provider (authentik, pocket-id, authelia, keycloak, zitadel, kanidm, google, microsoft-entra, auth0, okta, generic). Call with only `provider` for the questions, the provider-side setup steps and the redirect URI to paste into the provider. With the answers it previews the connection and checks the provider discovery document (warnings, never a crash); call again with the SAME arguments and the confirm_token to create or update it. A client secret is NEVER an argument or output: a confidential client is finished by typing the secret in the admin UI (the route is given); a public PKCE client needs none. Needs an admin `camstack login` session for the write.",
2838
2858
  OIDC_TOOL_SHAPE,
2839
- async ({ confirm_token, ...args }) => text6(await configureOidc(ctx, args, confirm_token))
2859
+ async ({ confirm_token, ...args }) => text(await configureOidc(ctx, args, confirm_token))
2840
2860
  );
2841
2861
  }
2842
2862
 
2843
2863
  // src/mcp/configure-nc-rules.ts
2844
2864
  import { z as z11 } from "zod";
2845
- import { text as text7 } from "@camstack/develop";
2846
2865
  var TIME = /^\d{1,2}:\d{2}$/;
2847
2866
  var ALL_DAYS = [0, 1, 2, 3, 4, 5, 6];
2848
2867
  var QuietHoursAnswerSchema = z11.object({
@@ -3188,7 +3207,7 @@ function registerNcRuleTools(server, ctx) {
3188
3207
  "configure_notification_rule_questionnaire",
3189
3208
  "Interview for a detection notification rule (cameras, classes, zones, quiet hours, targets). Stateless: send every answer so far in `answers`; it returns the questions still open, with choices read from the hub, or `ready` with a summary and the rule. Writes nothing.",
3190
3209
  { answers: NcRuleAnswersSchema },
3191
- async ({ answers }) => text7(await ncRuleQuestionnaire(ctx, answers))
3210
+ async ({ answers }) => text(await ncRuleQuestionnaire(ctx, answers))
3192
3211
  );
3193
3212
  server.tool(
3194
3213
  SAVE_RULE_TOOL,
@@ -3198,7 +3217,7 @@ function registerNcRuleTools(server, ctx) {
3198
3217
  ruleId: z11.string().optional(),
3199
3218
  confirm_token: ConfirmTokenSchema
3200
3219
  },
3201
- async ({ rule, ruleId, confirm_token }) => text7(
3220
+ async ({ rule, ruleId, confirm_token }) => text(
3202
3221
  await saveRule(ctx, { rule, ...ruleId !== void 0 ? { ruleId } : {} }, confirm_token)
3203
3222
  )
3204
3223
  );
@@ -3216,9 +3235,6 @@ function registerConfigureP3bTools(server, ctx) {
3216
3235
  }
3217
3236
 
3218
3237
  // src/mcp/configure-addons.ts
3219
- import {
3220
- text as text8
3221
- } from "@camstack/develop";
3222
3238
  import { z as z13 } from "zod";
3223
3239
 
3224
3240
  // src/mcp/configure-support.ts
@@ -3873,7 +3889,7 @@ function registerAddonTools(server, ctx) {
3873
3889
  query: z13.string().max(100).optional(),
3874
3890
  capability: z13.string().max(100).optional()
3875
3891
  },
3876
- async ({ query, capability }) => text8(
3892
+ async ({ query, capability }) => text(
3877
3893
  await configureAddonsSearch(ctx, {
3878
3894
  ...query !== void 0 ? { query } : {},
3879
3895
  ...capability !== void 0 ? { capability } : {}
@@ -3888,7 +3904,7 @@ function registerAddonTools(server, ctx) {
3888
3904
  version: z13.string().max(64).optional(),
3889
3905
  confirm_token: z13.string().optional()
3890
3906
  },
3891
- async ({ packageName, version, confirm_token }) => text8(
3907
+ async ({ packageName, version, confirm_token }) => text(
3892
3908
  await installAddon(
3893
3909
  ctx,
3894
3910
  { packageName, ...version !== void 0 ? { version } : {} },
@@ -3905,7 +3921,7 @@ function registerAddonTools(server, ctx) {
3905
3921
  nodeId: z13.string().max(100).optional(),
3906
3922
  confirm_token: z13.string().optional()
3907
3923
  },
3908
- async ({ packageName, version, nodeId, confirm_token }) => text8(
3924
+ async ({ packageName, version, nodeId, confirm_token }) => text(
3909
3925
  await updateAddon(
3910
3926
  ctx,
3911
3927
  {
@@ -3926,7 +3942,7 @@ function registerAddonTools(server, ctx) {
3926
3942
  everywhere: z13.boolean().optional(),
3927
3943
  confirm_token: z13.string().optional()
3928
3944
  },
3929
- async ({ packageName, nodeId, everywhere, confirm_token }) => text8(
3945
+ async ({ packageName, nodeId, everywhere, confirm_token }) => text(
3930
3946
  await uninstallAddon(
3931
3947
  ctx,
3932
3948
  {
@@ -3941,9 +3957,6 @@ function registerAddonTools(server, ctx) {
3941
3957
  }
3942
3958
 
3943
3959
  // src/mcp/configure-cameras.ts
3944
- import {
3945
- text as text9
3946
- } from "@camstack/develop";
3947
3960
  import { z as z14 } from "zod";
3948
3961
  var UNSUPPORTED_HINT = "use support_diagnose_camera / support_draft_issue";
3949
3962
  var MAX_PROVIDERS = 40;
@@ -4270,7 +4283,7 @@ function registerCameraTools(server, ctx) {
4270
4283
  "configure_camera_providers",
4271
4284
  "Installed camera-provider addons and the published ones not yet installed. A manifest declares no brand/model list, so `query` matches on name, description and keywords; a brand nothing matches is `unsupported` with a pointer to support_diagnose_camera / support_draft_issue.",
4272
4285
  { query: z14.string().max(100).optional() },
4273
- async ({ query }) => text9(await configureCameraProviders(ctx, query))
4286
+ async ({ query }) => text(await configureCameraProviders(ctx, query))
4274
4287
  );
4275
4288
  server.tool(
4276
4289
  "configure_camera_discover",
@@ -4279,7 +4292,7 @@ function registerCameraTools(server, ctx) {
4279
4292
  addonId: z14.string().min(1),
4280
4293
  params: z14.record(z14.string(), z14.unknown()).optional()
4281
4294
  },
4282
- async ({ addonId, params }) => text9(
4295
+ async ({ addonId, params }) => text(
4283
4296
  await configureCameraDiscover(ctx, {
4284
4297
  addonId,
4285
4298
  ...params !== void 0 ? { params } : {}
@@ -4294,14 +4307,11 @@ function registerCameraTools(server, ctx) {
4294
4307
  config: z14.record(z14.string(), z14.unknown()),
4295
4308
  confirm_token: z14.string().optional()
4296
4309
  },
4297
- async ({ addonId, config, confirm_token }) => text9(await addCamera(ctx, { addonId, config }, confirm_token))
4310
+ async ({ addonId, config, confirm_token }) => text(await addCamera(ctx, { addonId, config }, confirm_token))
4298
4311
  );
4299
4312
  }
4300
4313
 
4301
4314
  // src/mcp/configure-notifiers.ts
4302
- import {
4303
- text as text10
4304
- } from "@camstack/develop";
4305
4315
  import { z as z15 } from "zod";
4306
4316
  var MAX_TARGETS = 50;
4307
4317
  var TargetKindShape = z15.looseObject({
@@ -4604,13 +4614,13 @@ function registerNotifierTools(server, ctx) {
4604
4614
  "configure_notifiers",
4605
4615
  "The notifier kinds a target can be created from (ntfy, telegram, email, \u2026), each with its settings fields, which are required and which are SECRETS. Secrets are never tool arguments. Needs an admin `camstack login`.",
4606
4616
  {},
4607
- async () => text10(await configureNotifiers(ctx))
4617
+ async () => text(await configureNotifiers(ctx))
4608
4618
  );
4609
4619
  server.tool(
4610
4620
  "configure_notification_targets",
4611
4621
  "Existing notification targets with their non-secret settings. A secret is shown only as set/unset, and a target missing a required secret is listed under `incomplete`. Needs an admin `camstack login`.",
4612
4622
  {},
4613
- async () => text10(await configureNotificationTargets(ctx))
4623
+ async () => text(await configureNotificationTargets(ctx))
4614
4624
  );
4615
4625
  server.tool(
4616
4626
  UPSERT_TARGET_TOOL,
@@ -4623,7 +4633,7 @@ function registerNotifierTools(server, ctx) {
4623
4633
  enabled: z15.boolean().optional(),
4624
4634
  confirm_token: z15.string().optional()
4625
4635
  },
4626
- async ({ targetId, kind, name, config, enabled, confirm_token }) => text10(
4636
+ async ({ targetId, kind, name, config, enabled, confirm_token }) => text(
4627
4637
  await upsertNotificationTarget(
4628
4638
  ctx,
4629
4639
  {
@@ -4641,13 +4651,13 @@ function registerNotifierTools(server, ctx) {
4641
4651
  TEST_TARGET_TOOL,
4642
4652
  "Send a test notification to one target. Two steps: the preview checks the target exists and has every required secret, and only the confirmed call delivers a real message.",
4643
4653
  { targetId: z15.string().min(1), confirm_token: z15.string().optional() },
4644
- async ({ targetId, confirm_token }) => text10(await testNotificationTarget(ctx, { targetId }, confirm_token))
4654
+ async ({ targetId, confirm_token }) => text(await testNotificationTarget(ctx, { targetId }, confirm_token))
4645
4655
  );
4646
4656
  }
4647
4657
 
4648
4658
  // src/mcp/ops-tools.ts
4649
- import { z as z27 } from "zod";
4650
- import { jwtClaims, text as text12 } from "@camstack/develop";
4659
+ import { z as z23 } from "zod";
4660
+ import { jwtClaims } from "@camstack/develop";
4651
4661
 
4652
4662
  // src/mcp/ops-accelerators.ts
4653
4663
  import { z as z16 } from "zod";
@@ -5540,7 +5550,7 @@ async function opsStatus(ctx) {
5540
5550
  import {
5541
5551
  runGated as runGated3
5542
5552
  } from "@camstack/develop";
5543
- import { z as z25 } from "zod";
5553
+ import { z as z21 } from "zod";
5544
5554
 
5545
5555
  // src/mcp/ops-update-model.ts
5546
5556
  import { z as z20 } from "zod";
@@ -5713,790 +5723,6 @@ function addonDrift(installations) {
5713
5723
  return drift;
5714
5724
  }
5715
5725
 
5716
- // src/mcp/ops-update-cli.ts
5717
- import { execFile } from "child_process";
5718
-
5719
- // src/mcp/setup-tools.ts
5720
- import { text as text11 } from "@camstack/develop";
5721
- import { z as z24 } from "zod";
5722
-
5723
- // src/mcp/setup-hubs.ts
5724
- import * as https from "https";
5725
- import {
5726
- createHubAgent,
5727
- explainTlsFailure,
5728
- HubAddressError,
5729
- hubCaFileForServer,
5730
- loadSession,
5731
- normalizeHubAddress,
5732
- resolveHubTls
5733
- } from "@camstack/develop";
5734
- import { z as z23 } from "zod";
5735
-
5736
- // src/mcp/setup-install.ts
5737
- import { z as z22 } from "zod";
5738
-
5739
- // src/mcp/setup-install-rules.ts
5740
- import { z as z21 } from "zod";
5741
- var HOST_KINDS = [
5742
- "docker-compose",
5743
- "docker",
5744
- "unraid",
5745
- "proxmox-lxc",
5746
- "macos-desktop"
5747
- ];
5748
- var HostKindSchema = z21.enum(HOST_KINDS);
5749
- var ROLES = ["hub", "agent"];
5750
- var RoleSchema = z21.enum(ROLES);
5751
- var ACCELERATORS = ["intel", "amd", "coral", "apple", "cpu"];
5752
- var AcceleratorSchema = z21.enum(ACCELERATORS);
5753
- var InstallPathsSchema = z21.object({
5754
- data: z21.string().min(1),
5755
- recordings: z21.string().min(1),
5756
- config: z21.string().min(1).optional(),
5757
- backups: z21.string().min(1).optional()
5758
- });
5759
- var InstallGuideInputSchema = z21.object({
5760
- hostKind: HostKindSchema.optional(),
5761
- role: RoleSchema.optional(),
5762
- accelerator: AcceleratorSchema.optional(),
5763
- paths: InstallPathsSchema.optional(),
5764
- /** Agents only: the hub the agent should dial (host or IP). */
5765
- hubAddress: z21.string().min(1).optional()
5766
- });
5767
- var RAW = "https://raw.githubusercontent.com/camstack";
5768
- var TEMPLATE_URLS = {
5769
- compose: {
5770
- hub: `${RAW}/proxmox/main/compose/hub.yml`,
5771
- agent: `${RAW}/proxmox/main/compose/agent.yml`
5772
- },
5773
- proxmoxInstaller: `${RAW}/proxmox/main/install-camstack-proxmox.sh`,
5774
- unraid: {
5775
- hub: `${RAW}/unraid-templates/main/templates/camstack.xml`,
5776
- agent: `${RAW}/unraid-templates/main/templates/camstack-agent.xml`
5777
- },
5778
- latestRelease: "https://api.github.com/repos/camstack/server/releases/latest"
5779
- };
5780
- var IMAGE_REPOSITORY = "ghcr.io/camstack/camstack";
5781
- var IMAGE_TAG_BY_ACCELERATOR = {
5782
- intel: "intel-latest",
5783
- amd: "amd-latest",
5784
- // No Coral- or CPU-specific image: Intel hosts and the Unraid templates track intel-latest.
5785
- coral: "intel-latest",
5786
- cpu: "intel-latest",
5787
- apple: "intel-latest"
5788
- };
5789
- var NO_BARE_LATEST = "There is no bare :latest tag: use intel-<version>/intel-latest or amd-<version>/amd-latest, and never mix them across vendors.";
5790
- var MUST_NOT_OMIT = [
5791
- "--pids-limit=8192 (pids_limit: 8192): the default 2048 counts threads and silently stops media on a busy hub.",
5792
- "CAMSTACK_MEDIA_ROOT=/recordings: without it recordings land in /data and fill the cache drive.",
5793
- "/config mapped: it is a VOLUME, so an unmapped path mints an anonymous volume on every re-create.",
5794
- "--restart unless-stopped: the NVR must come back after a host reboot.",
5795
- "CAMSTACK_MOLECULER_USE_HOSTNAME=false: otherwise a multi-machine cluster never forms.",
5796
- "Coral USB: /dev/bus/usb AND the device-cgroup rule c 189:* rmw."
5797
- ];
5798
- var COMPOSE_MARKERS_BY_ACCELERATOR = {
5799
- intel: ["devices", "dri", "accel"],
5800
- amd: ["devices", "dri"],
5801
- coral: ["devices", "usb"],
5802
- cpu: [],
5803
- apple: []
5804
- };
5805
- function firstRunHub(host) {
5806
- return [
5807
- `Open https://${host}:4443 (a self-signed certificate: accept the browser warning).`,
5808
- "Sign in as admin / changeme; CamStack asks for a new password at once. Change it now.",
5809
- `Then, where your MCP client runs: camstack login ${host}`
5810
- ];
5811
- }
5812
-
5813
- // src/mcp/setup-compose.ts
5814
- var MARKER_LINE = /^(\s*)# (.*#@([A-Za-z0-9_]+)\s*)$/;
5815
- var VOLUME_HOST_PATHS = [
5816
- { container: "/data", key: "data" },
5817
- { container: "/recordings", key: "recordings" },
5818
- { container: "/config", key: "config" },
5819
- { container: "/backups", key: "backups" }
5820
- ];
5821
- function swapImageTag(body, imageTag) {
5822
- const escaped = IMAGE_REPOSITORY.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5823
- return body.replace(new RegExp(`(${escaped}):[A-Za-z0-9._-]+`), `$1:${imageTag}`);
5824
- }
5825
- function enableMarkers(body, markers) {
5826
- return body.split("\n").map((line) => {
5827
- const match = MARKER_LINE.exec(line);
5828
- if (match === null) return line;
5829
- const tag = match[3];
5830
- return tag !== void 0 && markers.includes(tag) ? `${match[1] ?? ""}${match[2] ?? ""}` : line;
5831
- }).join("\n");
5832
- }
5833
- function setVolumeHosts(body, paths) {
5834
- const edits = [];
5835
- let out = body;
5836
- for (const { container, key } of VOLUME_HOST_PATHS) {
5837
- const wanted = paths?.[key];
5838
- const volume = new RegExp(`^(\\s*- )([^\\s:]+)(:${container}\\s*)$`, "m");
5839
- if (!volume.test(out)) {
5840
- if (wanted !== void 0) {
5841
- edits.push(
5842
- `${container}: no volume line found in the template; map ${wanted} to it yourself`
5843
- );
5844
- }
5845
- continue;
5846
- }
5847
- if (wanted === void 0) {
5848
- edits.push(
5849
- `${container}: left at the template's host path, change it if that is not where you want it`
5850
- );
5851
- continue;
5852
- }
5853
- out = out.replace(
5854
- volume,
5855
- (_m, pre, _host, post) => `${pre}${wanted}${post}`
5856
- );
5857
- }
5858
- return { body: out, edits };
5859
- }
5860
- function setHubAddress(body, hubAddress) {
5861
- const line = /^(\s*- CAMSTACK_HUB_ADDRESS=).*$/m;
5862
- if (!line.test(body)) return { body, edits: [] };
5863
- if (hubAddress === void 0) {
5864
- return {
5865
- body,
5866
- edits: [
5867
- "CAMSTACK_HUB_ADDRESS: export it before `docker compose up` (or edit the line) with the hub IP or hostname"
5868
- ]
5869
- };
5870
- }
5871
- return {
5872
- body: body.replace(line, (_m, pre) => `${pre}${hubAddress}`),
5873
- edits: []
5874
- };
5875
- }
5876
- function customizeCompose(source, c) {
5877
- const tagged = enableMarkers(swapImageTag(source, c.imageTag), c.markers);
5878
- const volumes = setVolumeHosts(tagged, c.paths);
5879
- const hub = setHubAddress(volumes.body, c.hubAddress);
5880
- return { body: hub.body, edits: [...volumes.edits, ...hub.edits] };
5881
- }
5882
- function unquote(value) {
5883
- const v = value.trim();
5884
- if (v.length >= 2 && (v.startsWith('"') || v.startsWith("'")) && v.endsWith(v[0] ?? "")) {
5885
- return v.slice(1, -1);
5886
- }
5887
- return v;
5888
- }
5889
- function stripComment(line) {
5890
- return line.replace(/\s+#.*$/, "");
5891
- }
5892
- function readService(body) {
5893
- const fields = {
5894
- scalars: /* @__PURE__ */ new Map(),
5895
- lists: /* @__PURE__ */ new Map(),
5896
- maps: /* @__PURE__ */ new Map()
5897
- };
5898
- let key = null;
5899
- let sub = null;
5900
- for (const raw of body.split("\n")) {
5901
- if (raw.trim() === "" || raw.trim().startsWith("#")) continue;
5902
- const line = stripComment(raw);
5903
- const indent = line.length - line.trimStart().length;
5904
- const text15 = line.trim();
5905
- if (indent === 4) {
5906
- sub = null;
5907
- const m = /^([A-Za-z_]+):\s*(.*)$/.exec(text15);
5908
- if (m === null) continue;
5909
- key = m[1] ?? null;
5910
- if (key !== null && (m[2] ?? "") !== "") fields.scalars.set(key, unquote(m[2] ?? ""));
5911
- } else if (indent === 6 && key !== null) {
5912
- if (text15.startsWith("- ")) {
5913
- const list = fields.lists.get(key) ?? [];
5914
- list.push(unquote(text15.slice(2)));
5915
- fields.lists.set(key, list);
5916
- } else {
5917
- const m = /^([A-Za-z_-]+):\s*(.*)$/.exec(text15);
5918
- if (m === null) continue;
5919
- sub = m[1] ?? null;
5920
- const map = fields.maps.get(key) ?? /* @__PURE__ */ new Map();
5921
- if ((m[2] ?? "") !== "" && sub !== null) map.set(sub, unquote(m[2] ?? ""));
5922
- fields.maps.set(key, map);
5923
- }
5924
- } else if (indent === 8 && key !== null && sub !== null) {
5925
- const m = /^([A-Za-z_-]+):\s*(.*)$/.exec(text15);
5926
- if (m === null) continue;
5927
- const map = fields.maps.get(`${key}.${sub}`) ?? /* @__PURE__ */ new Map();
5928
- map.set(m[1] ?? "", unquote(m[2] ?? ""));
5929
- fields.maps.set(`${key}.${sub}`, map);
5930
- }
5931
- }
5932
- return fields;
5933
- }
5934
- function shellQuote(value) {
5935
- return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
5936
- }
5937
- function composeToDockerRun(body) {
5938
- const f = readService(body);
5939
- const image = f.scalars.get("image");
5940
- if (image === void 0) return null;
5941
- const args = ["docker run -d"];
5942
- const name = f.scalars.get("container_name");
5943
- if (name !== void 0) args.push(`--name ${shellQuote(name)}`);
5944
- const restart = f.scalars.get("restart");
5945
- if (restart !== void 0) args.push(`--restart ${shellQuote(restart)}`);
5946
- const network = f.scalars.get("network_mode");
5947
- if (network !== void 0) args.push(`--network ${shellQuote(network)}`);
5948
- const pids = f.scalars.get("pids_limit");
5949
- if (pids !== void 0) args.push(`--pids-limit=${pids}`);
5950
- for (const d of f.lists.get("devices") ?? []) args.push(`--device ${shellQuote(d)}`);
5951
- for (const r of f.lists.get("device_cgroup_rules") ?? []) {
5952
- args.push(`--device-cgroup-rule ${shellQuote(r)}`);
5953
- }
5954
- for (const g of f.lists.get("group_add") ?? []) args.push(`--group-add ${shellQuote(g)}`);
5955
- for (const v of f.lists.get("volumes") ?? []) args.push(`-v ${shellQuote(v)}`);
5956
- for (const e of f.lists.get("environment") ?? []) {
5957
- if (!e.includes("${")) args.push(`-e ${shellQuote(e)}`);
5958
- }
5959
- const driver = f.maps.get("logging")?.get("driver");
5960
- if (driver !== void 0) args.push(`--log-driver ${shellQuote(driver)}`);
5961
- for (const [k, v] of f.maps.get("logging.options") ?? []) {
5962
- args.push(`--log-opt ${shellQuote(`${k}=${v}`)}`);
5963
- }
5964
- args.push(shellQuote(image));
5965
- return args.join(" \\\n ");
5966
- }
5967
-
5968
- // src/mcp/setup-install.ts
5969
- var FETCH_TIMEOUT_MS = 1e4;
5970
- var CONTAINER_ROUTES = ["docker-compose", "docker"];
5971
- var Q_HOST = {
5972
- id: "hostKind",
5973
- question: "Where will CamStack run?",
5974
- options: ["docker-compose", "docker", "unraid", "proxmox-lxc", "macos-desktop"]
5975
- };
5976
- var Q_ROLE = {
5977
- id: "role",
5978
- question: "Which role? Start with a hub; an agent only adds a second machine to an existing hub.",
5979
- options: ["hub", "agent"]
5980
- };
5981
- var Q_ACCEL = {
5982
- id: "accelerator",
5983
- question: "Which accelerator does this host have?",
5984
- options: ["intel", "amd", "coral", "cpu"]
5985
- };
5986
- var Q_PATHS = {
5987
- id: "paths",
5988
- question: "Host paths as {data, recordings} (optionally config, backups). Recordings is the one that grows: put it on a roomy disk.",
5989
- options: []
5990
- };
5991
- function missingQuestions(input) {
5992
- const out = [];
5993
- if (input.hostKind === void 0) out.push(Q_HOST);
5994
- if (input.role === void 0) out.push(Q_ROLE);
5995
- const desktop = input.hostKind === "macos-desktop";
5996
- if (!desktop && (input.accelerator === void 0 || input.accelerator === "apple")) {
5997
- out.push(Q_ACCEL);
5998
- }
5999
- if (input.hostKind !== void 0 && CONTAINER_ROUTES.includes(input.hostKind) && input.paths === void 0) {
6000
- out.push(Q_PATHS);
6001
- }
6002
- return out;
6003
- }
6004
- async function fetchText(url, fetchFn) {
6005
- try {
6006
- const res = await fetchFn(url, {
6007
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
6008
- });
6009
- if (!res.ok) {
6010
- return {
6011
- ok: false,
6012
- failure: {
6013
- status: "fetch-failed",
6014
- url,
6015
- error: `answered ${res.status}`
6016
- }
6017
- };
6018
- }
6019
- return { ok: true, body: await res.text() };
6020
- } catch (err) {
6021
- const error = err instanceof Error ? err.message : String(err);
6022
- return { ok: false, failure: { status: "fetch-failed", url, error } };
6023
- }
6024
- }
6025
- var ReleaseSchema = z22.object({
6026
- tag_name: z22.string(),
6027
- assets: z22.array(z22.object({ name: z22.string(), browser_download_url: z22.string() }))
6028
- });
6029
- var DMG = /^CamStack-[0-9][0-9A-Za-z.-]*-(arm64|x64)\.dmg$/;
6030
- function acceleratorEdits(accelerator, role) {
6031
- const note = `Image tag ${IMAGE_TAG_BY_ACCELERATOR[accelerator]} for ${accelerator}. ${NO_BARE_LATEST}`;
6032
- return role === "agent" ? [note, "Do not run a hub and an agent on the same host."] : [note];
6033
- }
6034
- async function composeGuide(input, fetchFn, asDockerRun) {
6035
- const url = TEMPLATE_URLS.compose[input.role];
6036
- const fetched = await fetchText(url, fetchFn);
6037
- if (!fetched.ok) return fetched.failure;
6038
- const imageTag = IMAGE_TAG_BY_ACCELERATOR[input.accelerator];
6039
- const custom = customizeCompose(fetched.body, {
6040
- imageTag,
6041
- markers: COMPOSE_MARKERS_BY_ACCELERATOR[input.accelerator],
6042
- paths: input.paths,
6043
- hubAddress: input.hubAddress
6044
- });
6045
- const edits = [...custom.edits, ...acceleratorEdits(input.accelerator, input.role)];
6046
- const first = firstRun(input);
6047
- if (!asDockerRun) {
6048
- return ready(input, imageTag, {
6049
- steps: [
6050
- "Save docker-compose.yml (below) in an empty directory on the host.",
6051
- "docker compose up -d && docker compose logs -f camstack",
6052
- "The first boot seeds addons and pulls models: allow a few minutes.",
6053
- ...first
6054
- ],
6055
- files: [{ path: "docker-compose.yml", body: custom.body, source: url }],
6056
- edits
6057
- });
6058
- }
6059
- const run = composeToDockerRun(custom.body);
6060
- if (run === null) {
6061
- return {
6062
- status: "fetch-failed",
6063
- url,
6064
- error: "the official compose file has no image line; cannot derive docker run"
6065
- };
6066
- }
6067
- return ready(input, imageTag, {
6068
- steps: [
6069
- "Create the host directories named by the -v lines, then run the file below as a shell script.",
6070
- "docker logs -f camstack (the first boot seeds addons and pulls models).",
6071
- ...first
6072
- ],
6073
- files: [{ path: "run-camstack.sh", body: `#!/bin/sh
6074
- ${run}
6075
- `, source: url }],
6076
- edits
6077
- });
6078
- }
6079
- async function unraidGuide(input, fetchFn) {
6080
- const url = TEMPLATE_URLS.unraid[input.role];
6081
- const fetched = await fetchText(url, fetchFn);
6082
- if (!fetched.ok) return fetched.failure;
6083
- const imageTag = IMAGE_TAG_BY_ACCELERATOR[input.accelerator];
6084
- const edits = [
6085
- `In the template form, set Repository to ${IMAGE_REPOSITORY}:${imageTag} (the template ships intel-latest). ${NO_BARE_LATEST}`,
6086
- "Recordings: point it at a roomy array share, not the cache; leave Media root as /recordings.",
6087
- "Keep ExtraParams as shipped: --device-cgroup-rule and --pids-limit=8192.",
6088
- ...unraidDeviceEdits(input.accelerator)
6089
- ];
6090
- const first = firstRun(input);
6091
- return ready(input, imageTag, {
6092
- steps: [
6093
- `Unraid: Docker > Add Container > Template, paste ${url}`,
6094
- "Review the paths and Repository (edits below), then Apply.",
6095
- ...first
6096
- ],
6097
- files: [{ path: urlName(url), body: fetched.body, source: url }],
6098
- edits
6099
- });
6100
- }
6101
- function unraidDeviceEdits(accelerator) {
6102
- if (accelerator === "intel" || accelerator === "amd") {
6103
- return ["Intel/AMD iGPU: enter /dev/dri in that field. Intel NPU: enter /dev/accel."];
6104
- }
6105
- if (accelerator === "coral") {
6106
- return ["Coral USB: enter /dev/bus/usb (the whole bus). Coral PCIe/M.2: /dev/apex_0 (and _1)."];
6107
- }
6108
- return [];
6109
- }
6110
- function urlName(url) {
6111
- return url.slice(url.lastIndexOf("/") + 1);
6112
- }
6113
- async function proxmoxGuide(input, fetchFn) {
6114
- const url = TEMPLATE_URLS.proxmoxInstaller;
6115
- const fetched = await fetchText(url, fetchFn);
6116
- if (!fetched.ok) return fetched.failure;
6117
- const imageTag = IMAGE_TAG_BY_ACCELERATOR[input.accelerator];
6118
- const vars = [`ROLE=${input.role}`];
6119
- vars.push(`IMAGE_TAG=${IMAGE_REPOSITORY}:${imageTag}`);
6120
- if (input.accelerator === "cpu") vars.push("PASS_ACCEL=none");
6121
- if (input.role === "agent") vars.push(`CAMSTACK_HUB_ADDRESS=${input.hubAddress ?? "<hub-ip>"}`);
6122
- if (input.paths !== void 0) vars.push(`RECORDINGS_HOST_PATH=${input.paths.recordings}`);
6123
- const header = fetched.body.split("\n").slice(1, 20).filter((l) => l.startsWith("#")).join("\n");
6124
- const first = firstRun(input);
6125
- return ready(input, imageTag, {
6126
- steps: [
6127
- "Run on the Proxmox HOST (needs `pct`), not inside a container. It asks nothing: everything is an environment variable (defaults: VMID 200 hub / 201 agent, 8 cores, 8 GB, 64 GB disk, bridge vmbr0).",
6128
- `${vars.join(" ")} bash -c "$(curl -fsSL ${url})"`,
6129
- "It builds an Ubuntu 24.04 LXC with Docker, passes through the accelerators the host has (PASS_ACCEL=none skips it) and starts CamStack.",
6130
- ...first
6131
- ],
6132
- files: [
6133
- {
6134
- path: "install-camstack-proxmox.sh (header)",
6135
- body: header,
6136
- source: url
6137
- }
6138
- ],
6139
- edits: [
6140
- `The script defaults to the amd image; IMAGE_TAG above sets ${imageTag}. ${NO_BARE_LATEST}`,
6141
- "A hub and an agent on one machine need two CTs (host networking collides otherwise)."
6142
- ]
6143
- });
6144
- }
6145
- async function macGuide(input, fetchFn) {
6146
- const url = TEMPLATE_URLS.latestRelease;
6147
- const fetched = await fetchText(url, fetchFn);
6148
- if (!fetched.ok) return fetched.failure;
6149
- let release;
6150
- try {
6151
- const parsed = ReleaseSchema.safeParse(JSON.parse(fetched.body));
6152
- if (!parsed.success) {
6153
- return {
6154
- status: "fetch-failed",
6155
- url,
6156
- error: "the release answer had no assets"
6157
- };
6158
- }
6159
- release = parsed.data;
6160
- } catch {
6161
- return {
6162
- status: "fetch-failed",
6163
- url,
6164
- error: "the release answer was not JSON"
6165
- };
6166
- }
6167
- const dmgs = release.assets.filter((a) => DMG.test(a.name));
6168
- const first = firstRun(input);
6169
- return ready(input, null, {
6170
- steps: [
6171
- `Download the disk image for your Mac from release ${release.tag_name}: ${dmgs.map((a) => `${a.name} (${a.browser_download_url})`).join(" or ")}. Apple silicon (M1 and later) takes arm64; Intel takes x64. The CamStackViewer files are a different app.`,
6172
- "Drag CamStack into Applications and open it.",
6173
- `The setup window asks what this Mac should be: choose ${input.role === "hub" ? "Hub (full server)" : "Agent (worker)"}, then Save and start.`,
6174
- ...first
6175
- ],
6176
- files: [],
6177
- edits: ["A Mac uses the app, not Docker: a container there cannot reach the Neural Engine."]
6178
- });
6179
- }
6180
- function firstRun(input) {
6181
- const host = input.hostKind === "macos-desktop" ? "localhost" : "<host>";
6182
- if (input.role === "hub") return firstRunHub(host);
6183
- return [
6184
- "The agent status dashboard answers on http://<agent-host>:4444.",
6185
- "Pair it from the hub: run setup_add_node (it lists agents found on the network), then in the admin UI Cluster > Nodes > Agents found on the network. Type the six digits shown on the other screen on BOTH sides."
6186
- ];
6187
- }
6188
- function ready(input, imageTag, body) {
6189
- return {
6190
- status: "ready",
6191
- hostKind: input.hostKind,
6192
- role: input.role,
6193
- imageTag,
6194
- steps: body.steps,
6195
- files: body.files,
6196
- edits: body.edits,
6197
- mustNotOmit: input.hostKind === "macos-desktop" ? [] : MUST_NOT_OMIT
6198
- };
6199
- }
6200
- async function installGuide(input, fetchFn) {
6201
- const questions = missingQuestions(input);
6202
- if (questions.length > 0) return { status: "need-answers", questions };
6203
- const { hostKind, role } = input;
6204
- if (hostKind === void 0 || role === void 0) {
6205
- return { status: "need-answers", questions: missingQuestions({}) };
6206
- }
6207
- const resolved = {
6208
- hostKind,
6209
- role,
6210
- accelerator: hostKind === "macos-desktop" ? "apple" : input.accelerator ?? "cpu",
6211
- paths: input.paths,
6212
- hubAddress: input.hubAddress
6213
- };
6214
- switch (hostKind) {
6215
- case "docker-compose":
6216
- return composeGuide(resolved, fetchFn, false);
6217
- case "docker":
6218
- return composeGuide(resolved, fetchFn, true);
6219
- case "unraid":
6220
- return unraidGuide(resolved, fetchFn);
6221
- case "proxmox-lxc":
6222
- return proxmoxGuide(resolved, fetchFn);
6223
- case "macos-desktop":
6224
- return macGuide(resolved, fetchFn);
6225
- }
6226
- }
6227
-
6228
- // src/mcp/setup-hubs.ts
6229
- var DEFAULT_DISCOVER_TIMEOUT_MS = 2500;
6230
- var MAX_DISCOVER_TIMEOUT_MS = 15e3;
6231
- var HEALTH_TIMEOUT_MS = 5e3;
6232
- var LOGIN = (server) => `run \`camstack login ${server}\` in a terminal`;
6233
- var httpsHealth = (url, tls) => new Promise((resolve) => {
6234
- const req = https.get(
6235
- `${url}/health`,
6236
- { agent: createHubAgent(tls), timeout: HEALTH_TIMEOUT_MS },
6237
- (res) => {
6238
- res.resume();
6239
- resolve({ ok: true, status: res.statusCode ?? 0 });
6240
- }
6241
- );
6242
- req.on("timeout", () => req.destroy(new Error("timed out")));
6243
- req.on("error", (err) => {
6244
- resolve({
6245
- ok: false,
6246
- error: err.message,
6247
- tlsHint: explainTlsFailure(url, err)
6248
- });
6249
- });
6250
- });
6251
- async function discoverHubsTool(timeoutMs, deps) {
6252
- const wait = Math.min(timeoutMs ?? DEFAULT_DISCOVER_TIMEOUT_MS, MAX_DISCOVER_TIMEOUT_MS);
6253
- const finder = deps.findHubs ?? ((ms) => findHubs({
6254
- mdnsTimeoutMs: ms,
6255
- ...deps.sessionDir !== void 0 ? { sessionDir: deps.sessionDir } : {},
6256
- ...deps.env !== void 0 ? { env: deps.env } : {}
6257
- }));
6258
- const found = await finder(wait);
6259
- const hubs = found.map((h) => ({
6260
- host: h.host,
6261
- port: h.port,
6262
- name: h.nodeId,
6263
- version: h.version,
6264
- sources: h.sources
6265
- }));
6266
- return {
6267
- status: "ok",
6268
- hubs,
6269
- next: hubs.length === 0 ? "no hub answered: install one with setup_install_guide, or pass its address to setup_connect" : "pass the host of the hub you want to setup_connect"
6270
- };
6271
- }
6272
- function pinnedTls(url, deps) {
6273
- const caFile = hubCaFileForServer(url, deps.sessionDir);
6274
- try {
6275
- return {
6276
- tls: resolveHubTls(url, deps.env ?? process.env, deps.sessionDir),
6277
- caFile
6278
- };
6279
- } catch {
6280
- return { tls: {}, caFile };
6281
- }
6282
- }
6283
- async function connectTool(server, deps) {
6284
- let url;
6285
- try {
6286
- url = normalizeHubAddress(server);
6287
- } catch (err) {
6288
- const message = err instanceof HubAddressError ? err.message : String(err);
6289
- return { status: "invalid-address", server, message };
6290
- }
6291
- const { tls, caFile } = pinnedTls(url, deps);
6292
- const outcome = await (deps.health ?? httpsHealth)(url, tls);
6293
- const caPinned = tls.ca !== void 0;
6294
- const session = loadSession(url, deps.sessionDir);
6295
- const sessionInfo = {
6296
- exists: session !== null,
6297
- username: session?.username ?? null
6298
- };
6299
- if (!outcome.ok) {
6300
- return {
6301
- status: "unreachable",
6302
- server: url,
6303
- message: outcome.error,
6304
- tls: { caPinned, caFile, hint: outcome.tlsHint },
6305
- session: sessionInfo,
6306
- next: outcome.tlsHint === null ? "check the host, that the hub is running, and that port 4443 is reachable" : `the hub answers but its certificate is not trusted: ${LOGIN(url)}; login pins the CA (never disable verification)`
6307
- };
6308
- }
6309
- return {
6310
- status: "reachable",
6311
- server: url,
6312
- health: { status: outcome.status },
6313
- tls: { caPinned, caFile, hint: null },
6314
- session: sessionInfo,
6315
- next: session === null ? LOGIN(url) : "a session exists: the ops tools can use it"
6316
- };
6317
- }
6318
- var DiscoveredAgentsSchema = z23.object({
6319
- agents: z23.array(
6320
- z23.object({
6321
- nodeId: z23.string(),
6322
- host: z23.string(),
6323
- port: z23.number(),
6324
- version: z23.string().nullable(),
6325
- name: z23.string().nullable(),
6326
- state: z23.enum(["unpaired", "pending", "paired"])
6327
- })
6328
- )
6329
- });
6330
- var PAIRING_STEPS = [
6331
- "Pairing is started from the HUB (D857): admin UI, Cluster > Nodes > Agents found on the network. Pick the agent, or add it by address (host and port 4444).",
6332
- "Both screens now show six digits. Type the digits shown on the OTHER screen, on BOTH sides: the hub operator types the code the agent shows, and the agent operator confirms on the agent.",
6333
- "The agent installs its certificate only after its own operator confirmed. Nothing here (and no tool of this MCP) confirms a pairing for you.",
6334
- "The agent then appears under Cluster > Nodes."
6335
- ];
6336
- async function addNodeTool(input, ctx) {
6337
- const guide = await installGuide({ ...input, role: "agent" }, ctx.fetch);
6338
- const outcome = await ctx.hub();
6339
- if (!outcome.ok) {
6340
- return {
6341
- guide,
6342
- pairing: PAIRING_STEPS,
6343
- agentsOnNetwork: { status: "no-session", action: outcome.action }
6344
- };
6345
- }
6346
- try {
6347
- const raw = await outcome.connection.client.call("GET", "agentPairing.listDiscoveredAgents", {});
6348
- const parsed = DiscoveredAgentsSchema.safeParse(raw);
6349
- return {
6350
- guide,
6351
- pairing: PAIRING_STEPS,
6352
- agentsOnNetwork: parsed.success ? { status: "ok", agents: parsed.data.agents } : {
6353
- status: "unavailable",
6354
- error: "the hub answered in an unexpected shape"
6355
- }
6356
- };
6357
- } catch (err) {
6358
- return {
6359
- guide,
6360
- pairing: PAIRING_STEPS,
6361
- agentsOnNetwork: {
6362
- status: "unavailable",
6363
- error: err instanceof Error ? err.message : String(err)
6364
- }
6365
- };
6366
- }
6367
- }
6368
-
6369
- // src/mcp/setup-tools.ts
6370
- var SUPPORTED_HOST_KINDS = HOST_KINDS;
6371
- var REGISTRY2 = "https://registry.npmjs.org";
6372
- var REGISTRY_TIMEOUT_MS2 = 5e3;
6373
- var RegistryLatestSchema = z24.object({ version: z24.string() });
6374
- async function latestOnNpm(name, fetchFn) {
6375
- const url = `${REGISTRY2}/${name.replace("/", "%2f")}/latest`;
6376
- try {
6377
- const res = await fetchFn(url, {
6378
- signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS2)
6379
- });
6380
- if (!res.ok) return { latest: null, error: `registry answered ${res.status}` };
6381
- const parsed = RegistryLatestSchema.safeParse(await res.json());
6382
- if (!parsed.success) return { latest: null, error: "registry answer had no version" };
6383
- return { latest: parsed.data.version };
6384
- } catch (err) {
6385
- return {
6386
- latest: null,
6387
- error: err instanceof Error ? err.message : String(err)
6388
- };
6389
- }
6390
- }
6391
- async function about(ctx) {
6392
- const [cli, server] = await Promise.all([
6393
- latestOnNpm("camstack", ctx.fetch),
6394
- latestOnNpm("@camstack/server", ctx.fetch)
6395
- ]);
6396
- return {
6397
- cli: { installed: ctx.cliVersion, ...cli },
6398
- server,
6399
- supportedHostKinds: SUPPORTED_HOST_KINDS
6400
- };
6401
- }
6402
- function registerSetupTools(server, ctx, deps = {}) {
6403
- server.tool(
6404
- "setup_about",
6405
- "Versions of the camstack CLI (installed and latest on npm), the latest @camstack/server on npm, and the host kinds CamStack supports. Needs no hub and no login.",
6406
- {},
6407
- async () => text11(await about(ctx))
6408
- );
6409
- server.tool(
6410
- "setup_install_guide",
6411
- 'Interview-style install guide. Call it with whatever you know; while an answer is missing it returns {status:"need-answers", questions} listing only what is still unknown. When complete it returns the steps and the OFFICIAL template files (fetched live) for that route: docker-compose, docker, unraid, proxmox-lxc or macos-desktop. Start with a hub.',
6412
- {
6413
- hostKind: HostKindSchema.optional(),
6414
- role: RoleSchema.optional(),
6415
- accelerator: AcceleratorSchema.optional(),
6416
- paths: InstallPathsSchema.optional(),
6417
- hubAddress: z24.string().min(1).optional().describe("Agents only: the hub to dial.")
6418
- },
6419
- async (input) => text11(await installGuide(input, ctx.fetch))
6420
- );
6421
- server.tool(
6422
- "setup_discover_hubs",
6423
- "Find CamStack hubs on the local network (mDNS) plus the hubs this machine already knows, with host, port, name and version. Needs no login.",
6424
- { timeoutMs: z24.number().int().min(200).max(15e3).optional() },
6425
- async ({ timeoutMs }) => text11(await discoverHubsTool(timeoutMs, deps))
6426
- );
6427
- server.tool(
6428
- "setup_connect",
6429
- "Check that a hub answers on https://<server>:4443/health, whether its CA is already pinned for TLS, and whether a `camstack login` session exists. Never takes a password: when there is no session it says to run `camstack login <server>` in a terminal.",
6430
- { server: z24.string().min(1) },
6431
- async ({ server: address }) => text11(await connectTool(address, deps))
6432
- );
6433
- server.tool(
6434
- "setup_add_node",
6435
- "Guide to add an AGENT (a second machine) to a hub: the install route for that host, then the pairing flow where the operator types the six digits shown on the other screen on both sides. With a login session it also lists the agents found on the network. It never confirms a pairing.",
6436
- {
6437
- hostKind: HostKindSchema.optional(),
6438
- accelerator: AcceleratorSchema.optional(),
6439
- paths: InstallPathsSchema.optional(),
6440
- hubAddress: z24.string().min(1).optional()
6441
- },
6442
- async (input) => text11(await addNodeTool(input, ctx))
6443
- );
6444
- }
6445
-
6446
- // src/mcp/ops-update-cli.ts
6447
- var CLI_PACKAGE = "camstack";
6448
- var NPM_GLOBAL = /[\\/]lib[\\/]node_modules[\\/]camstack[\\/]|[\\/]npm[\\/]node_modules[\\/]camstack[\\/]/;
6449
- var OTHER_MANAGER = /[\\/]\.bun[\\/]|[\\/]pnpm[\\/]|[\\/]\.yarn[\\/]|[\\/]yarn[\\/]|[\\/]\.volta[\\/]/;
6450
- function detectCliInstall(entryPath) {
6451
- const manual = `Update it with the package manager it was installed with, for example \`npm install -g ${CLI_PACKAGE}@latest\` (no sudo: fix the npm prefix permissions instead).`;
6452
- if (/[\\/]_npx[\\/]/.test(entryPath)) {
6453
- return {
6454
- method: "npx",
6455
- command: null,
6456
- instructions: `The CLI runs through npx: there is nothing installed to update. Start it as \`npx ${CLI_PACKAGE}@latest mcp\` (or clear the npx cache), then restart the MCP in your client.`
6457
- };
6458
- }
6459
- if (OTHER_MANAGER.test(entryPath) || !NPM_GLOBAL.test(entryPath)) {
6460
- return { method: "unknown", command: null, instructions: manual };
6461
- }
6462
- return {
6463
- method: "npm-global",
6464
- command: ["npm", "install", "-g", `${CLI_PACKAGE}@latest`],
6465
- instructions: `npm install -g ${CLI_PACKAGE}@latest`
6466
- };
6467
- }
6468
- var runCommand = (file, args) => new Promise((resolve) => {
6469
- execFile(file, [...args], { timeout: 5 * 6e4 }, (err, stdout, stderr) => {
6470
- const out = `${stdout}${stderr}`.trim().slice(-600);
6471
- resolve({
6472
- ok: err === null,
6473
- output: err ? `${err.message} ${out}`.trim() : out
6474
- });
6475
- });
6476
- });
6477
- function parts(v) {
6478
- return v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
6479
- }
6480
- function isOlder(a, b) {
6481
- const x = parts(a);
6482
- const y = parts(b);
6483
- for (let i = 0; i < 3; i++) {
6484
- const d = (x[i] ?? 0) - (y[i] ?? 0);
6485
- if (d !== 0) return d < 0;
6486
- }
6487
- return false;
6488
- }
6489
- async function cliVerdict(installed, fetchFn, entryPath) {
6490
- const latest = await latestOnNpm(CLI_PACKAGE, fetchFn);
6491
- return {
6492
- installed,
6493
- latest: latest.latest,
6494
- updateAvailable: latest.latest === null ? null : isOlder(installed, latest.latest),
6495
- ...latest.error !== void 0 ? { error: latest.error } : {},
6496
- install: detectCliInstall(entryPath)
6497
- };
6498
- }
6499
-
6500
5726
  // src/mcp/ops-update-apply.ts
6501
5727
  var UPDATE_APPLY_TOOL = "ops_update_apply";
6502
5728
  function timing(deps) {
@@ -6549,7 +5775,7 @@ async function readPlan(client, args, ctx, deps) {
6549
5775
  wrapper: wrapperVerdict(status.value)
6550
5776
  });
6551
5777
  }
6552
- const updates = args.addons === false ? null : await readQuery(client, "addons.listUpdates", {}, z25.array(AddonUpdateShape));
5778
+ const updates = args.addons === false ? null : await readQuery(client, "addons.listUpdates", {}, z21.array(AddonUpdateShape));
6553
5779
  const cli = args.cli === false ? null : await cliVerdict(ctx.cliVersion, ctx.fetch, deps.entryPath ?? process.argv[1] ?? "");
6554
5780
  const hub = plans.find((p) => p.node.isHub === true) ?? null;
6555
5781
  return {
@@ -6865,14 +6091,14 @@ async function opsUpdateApply(ctx, args, confirmToken, deps = {}) {
6865
6091
  }
6866
6092
 
6867
6093
  // src/mcp/ops-update-check.ts
6868
- import { z as z26 } from "zod";
6094
+ import { z as z22 } from "zod";
6869
6095
  var ADDON_LIMIT = 30;
6870
6096
  async function opsUpdateCheck(ctx, deps = {}) {
6871
6097
  return withAdminHub(ctx, async ({ client, server }) => {
6872
6098
  const unavailable = {};
6873
6099
  const [topology, updates, installs, cli] = await Promise.all([
6874
6100
  readTopology(client),
6875
- readQuery(client, "addons.listUpdates", {}, z26.array(AddonUpdateShape)),
6101
+ readQuery(client, "addons.listUpdates", {}, z22.array(AddonUpdateShape)),
6876
6102
  readInstallations(client),
6877
6103
  cliVerdict(ctx.cliVersion, ctx.fetch, deps.entryPath ?? process.argv[1] ?? "")
6878
6104
  ]);
@@ -6990,40 +6216,40 @@ function registerOpsTools(server, ctx) {
6990
6216
  "ops_whoami",
6991
6217
  "Which hub this MCP talks to and as whom, from the `camstack login` session (no network call). If there is no session, says to run `camstack login <host>` in a terminal.",
6992
6218
  {},
6993
- async () => text12(await whoami(ctx))
6219
+ async () => text(await whoami(ctx))
6994
6220
  );
6995
6221
  server.tool(
6996
6222
  "ops_status",
6997
6223
  'Cluster summary: every node (online, platform, server version, capability count, "online-but-capless" flag), the active alerts and the addons with an update. A part the hub did not answer is null with its reason in `unavailable`, never zero. Node list and updates need an admin `camstack login`.',
6998
6224
  {},
6999
- async () => text12(await opsStatus(ctx))
6225
+ async () => text(await opsStatus(ctx))
7000
6226
  );
7001
6227
  server.tool(
7002
6228
  "ops_device",
7003
6229
  "One device: online state, provider addon and node, bound capabilities, runtime-state age and, for a camera, its pipeline, recording state, the functions an operator switched off (not faults) and its latest object events.",
7004
- { deviceId: z27.number().int() },
7005
- async ({ deviceId }) => text12(await opsDevice(ctx, deviceId))
6230
+ { deviceId: z23.number().int() },
6231
+ async ({ deviceId }) => text(await opsDevice(ctx, deviceId))
7006
6232
  );
7007
6233
  server.tool(
7008
6234
  "ops_unhealthy_devices",
7009
6235
  "Every enabled device that is unhealthy, each with a typed reason (offline, detection-provisioning-failed, detection-not-assigned, recording-inactive). A function an operator switched off is never listed; a camera whose status could not be fully read is listed under `unknown`.",
7010
6236
  {},
7011
- async () => text12(await opsUnhealthyDevices(ctx))
6237
+ async () => text(await opsUnhealthyDevices(ctx))
7012
6238
  );
7013
6239
  server.tool(
7014
6240
  "ops_logs",
7015
6241
  "Hub logs scoped by deviceId, addon, node, level, window and free text, grouped and deduplicated with counts. Says which tier answered (ring = recent in-RAM; persisted = the default log destination). With `around` (a track or event id) returns `contextLines` lines on each side of its first mention. Needs an admin `camstack login`.",
7016
6242
  {
7017
- deviceId: z27.number().int().optional(),
7018
- addonId: z27.string().optional(),
7019
- nodeId: z27.string().optional(),
7020
- level: z27.enum(["debug", "info", "warn", "error"]).optional(),
7021
- windowMinutes: z27.number().int().min(1).max(1440).optional(),
7022
- text: z27.string().max(200).optional(),
7023
- around: z27.string().max(200).optional(),
7024
- contextLines: z27.number().int().min(1).max(50).optional()
6243
+ deviceId: z23.number().int().optional(),
6244
+ addonId: z23.string().optional(),
6245
+ nodeId: z23.string().optional(),
6246
+ level: z23.enum(["debug", "info", "warn", "error"]).optional(),
6247
+ windowMinutes: z23.number().int().min(1).max(1440).optional(),
6248
+ text: z23.string().max(200).optional(),
6249
+ around: z23.string().max(200).optional(),
6250
+ contextLines: z23.number().int().min(1).max(50).optional()
7025
6251
  },
7026
- async (args) => text12(
6252
+ async (args) => text(
7027
6253
  await opsLogs(ctx, {
7028
6254
  ...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
7029
6255
  ...args.addonId ? { addonId: args.addonId } : {},
@@ -7040,14 +6266,14 @@ function registerOpsTools(server, ctx) {
7040
6266
  "ops_events",
7041
6267
  "Find tracks (and, for one camera, object events) by camera, class, zone, time window and free text. Each result carries an admin-UI link and a viewer link built on the hub address flagged for app connections.",
7042
6268
  {
7043
- deviceId: z27.number().int().optional(),
7044
- className: z27.string().optional(),
7045
- zone: z27.string().optional(),
7046
- text: z27.string().max(200).optional(),
7047
- windowMinutes: z27.number().int().min(1).max(10080).optional(),
7048
- limit: z27.number().int().min(1).max(50).optional()
6269
+ deviceId: z23.number().int().optional(),
6270
+ className: z23.string().optional(),
6271
+ zone: z23.string().optional(),
6272
+ text: z23.string().max(200).optional(),
6273
+ windowMinutes: z23.number().int().min(1).max(10080).optional(),
6274
+ limit: z23.number().int().min(1).max(50).optional()
7049
6275
  },
7050
- async (args) => text12(
6276
+ async (args) => text(
7051
6277
  await opsEvents(ctx, {
7052
6278
  ...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
7053
6279
  ...args.className ? { className: args.className } : {},
@@ -7061,31 +6287,31 @@ function registerOpsTools(server, ctx) {
7061
6287
  server.tool(
7062
6288
  "ops_track",
7063
6289
  "One track with its media audit: best shot, keyframes, every missing media kind with the reason the track recorded for it, and the audit findings. Links to the admin UI and the viewer.",
7064
- { deviceId: z27.number().int(), trackId: z27.string().min(1) },
7065
- async ({ deviceId, trackId }) => text12(await opsTrack(ctx, { deviceId, trackId }))
6290
+ { deviceId: z23.number().int(), trackId: z23.string().min(1) },
6291
+ async ({ deviceId, trackId }) => text(await opsTrack(ctx, { deviceId, trackId }))
7066
6292
  );
7067
6293
  server.tool(
7068
6294
  "ops_accelerators",
7069
6295
  'Per node: the inference devices it has, the Auto-tune hardware class the node maps to, and whether the measured detector-latency table has a row for that class and accelerator. A node that could not be read is null with a reason, never "no accelerator". Needs an admin `camstack login`.',
7070
6296
  {},
7071
- async () => text12(await opsAccelerators(ctx))
6297
+ async () => text(await opsAccelerators(ctx))
7072
6298
  );
7073
6299
  server.tool(
7074
6300
  "ops_update_check",
7075
6301
  'Read-only update report for the whole cluster. Every node from the topology: running vs installed vs latest @camstack/server, restartRequired / updateState / rolledBack, the wrapper (Docker image / desktop app) verdict, and whether its addon versions match the hub; the hub\'s addon updates; the viewer (not exposed by any API, said so); and this CLI/MCP against npm. A node that cannot be read is null with a reason, never "up to date". Needs an admin `camstack login`.',
7076
6302
  {},
7077
- async () => text12(await opsUpdateCheck(ctx))
6303
+ async () => text(await opsUpdateCheck(ctx))
7078
6304
  );
7079
6305
  server.tool(
7080
6306
  UPDATE_APPLY_TOOL,
7081
6307
  "Bring the cluster up to date, verified step by step: hub server closure, wait until the hub runs it, then each online agent, then addons (agents follow by reconciliation, verified), then this CLI last. Two steps: call without confirm_token for a preview (order, versions, wrapper instructions, timeouts) and a one-shot token, then again with the SAME arguments and that token. A failed step stops the chain and names the node. Wrapper (image/app) updates are only reported with instructions. Needs an admin `camstack login`.",
7082
6308
  {
7083
- version: z27.string().min(1).optional().describe("Server target; default = the hub's latest."),
7084
- addons: z27.boolean().optional().describe("Update addons too (default true)."),
7085
- cli: z27.boolean().optional().describe("Update this CLI last (default true)."),
7086
- confirm_token: z27.string().optional()
6309
+ version: z23.string().min(1).optional().describe("Server target; default = the hub's latest."),
6310
+ addons: z23.boolean().optional().describe("Update addons too (default true)."),
6311
+ cli: z23.boolean().optional().describe("Update this CLI last (default true)."),
6312
+ confirm_token: z23.string().optional()
7087
6313
  },
7088
- async ({ version, addons, cli, confirm_token }) => text12(
6314
+ async ({ version, addons, cli, confirm_token }) => text(
7089
6315
  await opsUpdateApply(
7090
6316
  ctx,
7091
6317
  {
@@ -7100,7 +6326,7 @@ function registerOpsTools(server, ctx) {
7100
6326
  }
7101
6327
 
7102
6328
  // src/mcp/configure-tools.ts
7103
- var CurrentDeviceSchema = z28.object({ name: z28.string() }).nullable();
6329
+ var CurrentDeviceSchema = z24.object({ name: z24.string() }).nullable();
7104
6330
  var RENAME_DEVICE_TOOL = "configure_rename_device";
7105
6331
  function renameDeviceSpec(client) {
7106
6332
  return {
@@ -7162,11 +6388,11 @@ function registerConfigureTools(server, ctx) {
7162
6388
  RENAME_DEVICE_TOOL,
7163
6389
  "Rename a device. Two steps: call without confirm_token for a preview and a one-shot token, then call again with the SAME arguments and that token to apply. Needs an admin `camstack login` session.",
7164
6390
  {
7165
- deviceId: z28.number().int(),
7166
- name: z28.string().min(1),
7167
- confirm_token: z28.string().optional()
6391
+ deviceId: z24.number().int(),
6392
+ name: z24.string().min(1),
6393
+ confirm_token: z24.string().optional()
7168
6394
  },
7169
- async ({ deviceId, name, confirm_token }) => text13(await renameDevice(ctx, { deviceId, name }, confirm_token))
6395
+ async ({ deviceId, name, confirm_token }) => text(await renameDevice(ctx, { deviceId, name }, confirm_token))
7170
6396
  );
7171
6397
  registerAddonTools(server, ctx);
7172
6398
  registerCameraTools(server, ctx);
@@ -7175,11 +6401,10 @@ function registerConfigureTools(server, ctx) {
7175
6401
  }
7176
6402
 
7177
6403
  // src/mcp/support-tools.ts
7178
- import { text as text14 } from "@camstack/develop";
7179
- import { z as z33 } from "zod";
6404
+ import { z as z29 } from "zod";
7180
6405
 
7181
6406
  // src/mcp/support-accelerator.ts
7182
- import { z as z29 } from "zod";
6407
+ import { z as z25 } from "zod";
7183
6408
  var EXPECTED_ACCELERATORS = [
7184
6409
  "intel-igpu",
7185
6410
  "intel-npu",
@@ -7191,23 +6416,23 @@ var EXPECTED_ACCELERATORS = [
7191
6416
  "apple-ane",
7192
6417
  "nvidia"
7193
6418
  ];
7194
- var ExpectedAcceleratorSchema = z29.enum(EXPECTED_ACCELERATORS);
7195
- var HardwareSchema2 = z29.looseObject({
7196
- platform: z29.enum(["darwin", "linux", "win32"]),
7197
- arch: z29.enum(["arm64", "x64"]),
7198
- cpuModel: z29.string(),
7199
- gpu: z29.looseObject({ type: z29.string(), name: z29.string() }).nullable(),
7200
- npu: z29.looseObject({ type: z29.string() }).nullable()
6419
+ var ExpectedAcceleratorSchema = z25.enum(EXPECTED_ACCELERATORS);
6420
+ var HardwareSchema2 = z25.looseObject({
6421
+ platform: z25.enum(["darwin", "linux", "win32"]),
6422
+ arch: z25.enum(["arm64", "x64"]),
6423
+ cpuModel: z25.string(),
6424
+ gpu: z25.looseObject({ type: z25.string(), name: z25.string() }).nullable(),
6425
+ npu: z25.looseObject({ type: z25.string() }).nullable()
7201
6426
  });
7202
- var InferenceDevicesSchema2 = z29.looseObject({
7203
- reachable: z29.boolean(),
7204
- devices: z29.array(
7205
- z29.looseObject({
7206
- key: z29.string(),
7207
- available: z29.boolean(),
7208
- presence: z29.enum(["present", "absent", "unknown"]).optional(),
7209
- enabled: z29.boolean(),
7210
- exclusion: z29.string().nullable()
6427
+ var InferenceDevicesSchema2 = z25.looseObject({
6428
+ reachable: z25.boolean(),
6429
+ devices: z25.array(
6430
+ z25.looseObject({
6431
+ key: z25.string(),
6432
+ available: z25.boolean(),
6433
+ presence: z25.enum(["present", "absent", "unknown"]).optional(),
6434
+ enabled: z25.boolean(),
6435
+ exclusion: z25.string().nullable()
7211
6436
  })
7212
6437
  )
7213
6438
  });
@@ -7403,11 +6628,11 @@ function diagnoseAccelerator(input) {
7403
6628
  }
7404
6629
  const matched = devices.filter((d) => expectation.deviceKeys(d.key));
7405
6630
  const matchedKeys = matched.map((d) => d.key);
7406
- const ready2 = matched.filter((d) => d.available && d.enabled && d.exclusion === null);
7407
- if (ready2.length > 0) {
6631
+ const ready = matched.filter((d) => d.available && d.enabled && d.exclusion === null);
6632
+ if (ready.length > 0) {
7408
6633
  return finish(
7409
6634
  "ok",
7410
- `The node offers ${ready2.map((d) => d.key).join(", ")}, available and enabled.`,
6635
+ `The node offers ${ready.map((d) => d.key).join(", ")}, available and enabled.`,
7411
6636
  matchedKeys,
7412
6637
  [],
7413
6638
  null
@@ -7486,7 +6711,7 @@ async function supportDiagnoseAccelerator(ctx, args) {
7486
6711
  import {
7487
6712
  runGated as runGated5
7488
6713
  } from "@camstack/develop";
7489
- import { z as z30 } from "zod";
6714
+ import { z as z26 } from "zod";
7490
6715
  var BENCHMARK_RUN_TOOL = "support_benchmark_run";
7491
6716
  var AUTO_TUNE_MODELS = [
7492
6717
  "yolov9t-320",
@@ -7508,64 +6733,64 @@ var CONTRIBUTE_HOW = [
7508
6733
  "The measured medians belong in the Auto-tune latency table: docs/benchmarks/detector-latency.json and packages/types/src/pipeline/auto-tune/detector-performance.ts, changed together (detector-performance.spec.ts fails until they agree), in the camstack/server repository.",
7509
6734
  'Use support_draft_pr with kind "benchmark", the column ("<hardwareClass>/<accelerator>") and these cells to get the branch plan and the `gh pr create` command.'
7510
6735
  ];
7511
- var HardwareSchema3 = z30.looseObject({
7512
- platform: z30.enum(["darwin", "linux", "win32"]),
7513
- arch: z30.enum(["arm64", "x64"]),
7514
- cpuModel: z30.string(),
7515
- cpuCores: z30.number(),
7516
- totalRAM_MB: z30.number(),
7517
- availableRAM_MB: z30.number(),
7518
- gpu: z30.looseObject({
7519
- type: z30.enum(["nvidia", "amd", "intel", "apple"]),
7520
- name: z30.string()
6736
+ var HardwareSchema3 = z26.looseObject({
6737
+ platform: z26.enum(["darwin", "linux", "win32"]),
6738
+ arch: z26.enum(["arm64", "x64"]),
6739
+ cpuModel: z26.string(),
6740
+ cpuCores: z26.number(),
6741
+ totalRAM_MB: z26.number(),
6742
+ availableRAM_MB: z26.number(),
6743
+ gpu: z26.looseObject({
6744
+ type: z26.enum(["nvidia", "amd", "intel", "apple"]),
6745
+ name: z26.string()
7521
6746
  }).nullable(),
7522
- npu: z30.looseObject({ type: z30.enum(["apple-ane", "intel-npu", "amd-xdna"]) }).nullable()
6747
+ npu: z26.looseObject({ type: z26.enum(["apple-ane", "intel-npu", "amd-xdna"]) }).nullable()
7523
6748
  });
7524
- var InferenceDevicesSchema3 = z30.looseObject({
7525
- reachable: z30.boolean(),
7526
- devices: z30.array(
7527
- z30.looseObject({
7528
- key: z30.string(),
7529
- backend: z30.string(),
7530
- available: z30.boolean(),
7531
- enabled: z30.boolean()
6749
+ var InferenceDevicesSchema3 = z26.looseObject({
6750
+ reachable: z26.boolean(),
6751
+ devices: z26.array(
6752
+ z26.looseObject({
6753
+ key: z26.string(),
6754
+ backend: z26.string(),
6755
+ available: z26.boolean(),
6756
+ enabled: z26.boolean()
7532
6757
  })
7533
6758
  )
7534
6759
  });
7535
- var RunStatusSchema = z30.looseObject({
7536
- run: z30.looseObject({
7537
- runId: z30.string(),
7538
- nodeId: z30.string(),
7539
- state: z30.string(),
7540
- endReason: z30.string().nullable().optional(),
7541
- error: z30.string().nullable().optional()
6760
+ var RunStatusSchema = z26.looseObject({
6761
+ run: z26.looseObject({
6762
+ runId: z26.string(),
6763
+ nodeId: z26.string(),
6764
+ state: z26.string(),
6765
+ endReason: z26.string().nullable().optional(),
6766
+ error: z26.string().nullable().optional()
7542
6767
  }).nullable()
7543
6768
  });
7544
- var ModelListSchema = z30.array(
7545
- z30.looseObject({
7546
- id: z30.string(),
7547
- formats: z30.record(z30.string(), z30.looseObject({ downloaded: z30.boolean().optional() })).optional()
6769
+ var ModelListSchema = z26.array(
6770
+ z26.looseObject({
6771
+ id: z26.string(),
6772
+ formats: z26.record(z26.string(), z26.looseObject({ downloaded: z26.boolean().optional() })).optional()
7548
6773
  })
7549
6774
  );
7550
- var PipelineTestSchema = z30.looseObject({
7551
- kind: z30.string().optional(),
7552
- detections: z30.array(
7553
- z30.looseObject({
7554
- debug: z30.looseObject({
7555
- stepLatencyMs: z30.record(z30.string(), z30.number()).optional()
6775
+ var PipelineTestSchema = z26.looseObject({
6776
+ kind: z26.string().optional(),
6777
+ detections: z26.array(
6778
+ z26.looseObject({
6779
+ debug: z26.looseObject({
6780
+ stepLatencyMs: z26.record(z26.string(), z26.number()).optional()
7556
6781
  }).optional()
7557
6782
  })
7558
6783
  ).optional(),
7559
- discarded: z30.array(z30.unknown()).optional(),
7560
- debug: z30.looseObject({
7561
- stepTimings: z30.array(
7562
- z30.looseObject({
7563
- source: z30.string().optional(),
7564
- ms: z30.number().optional()
6784
+ discarded: z26.array(z26.unknown()).optional(),
6785
+ debug: z26.looseObject({
6786
+ stepTimings: z26.array(
6787
+ z26.looseObject({
6788
+ source: z26.string().optional(),
6789
+ ms: z26.number().optional()
7565
6790
  })
7566
6791
  ).optional(),
7567
- predictMs: z30.number().optional(),
7568
- totalInferenceMs: z30.number().optional()
6792
+ predictMs: z26.number().optional(),
6793
+ totalInferenceMs: z26.number().optional()
7569
6794
  }).optional()
7570
6795
  });
7571
6796
  var FORMAT_BY_BACKEND = {
@@ -7852,15 +7077,15 @@ async function supportBenchmarkRun(ctx, deps, args, confirmToken) {
7852
7077
  }
7853
7078
  return runGated5(ctx.gate, benchmarkRunSpec(outcome.connection.client, deps), args, confirmToken);
7854
7079
  }
7855
- var ResultsSchema = z30.array(
7856
- z30.looseObject({
7857
- nodeId: z30.string(),
7858
- cell: z30.looseObject({
7859
- phase: z30.string(),
7860
- deviceKey: z30.string(),
7861
- modelId: z30.string(),
7862
- p50Ms: z30.number().optional(),
7863
- fps: z30.number().optional()
7080
+ var ResultsSchema = z26.array(
7081
+ z26.looseObject({
7082
+ nodeId: z26.string(),
7083
+ cell: z26.looseObject({
7084
+ phase: z26.string(),
7085
+ deviceKey: z26.string(),
7086
+ modelId: z26.string(),
7087
+ p50Ms: z26.number().optional(),
7088
+ fps: z26.number().optional()
7864
7089
  })
7865
7090
  })
7866
7091
  );
@@ -7913,20 +7138,20 @@ async function supportBenchmarkStatus(ctx, args) {
7913
7138
  }
7914
7139
 
7915
7140
  // src/mcp/support-camera.ts
7916
- import { z as z31 } from "zod";
7917
- var DeviceSchema2 = z31.looseObject({
7918
- id: z31.number(),
7919
- name: z31.string(),
7920
- addonId: z31.string().optional(),
7921
- disabled: z31.boolean().optional(),
7922
- online: z31.boolean().optional()
7141
+ import { z as z27 } from "zod";
7142
+ var DeviceSchema2 = z27.looseObject({
7143
+ id: z27.number(),
7144
+ name: z27.string(),
7145
+ addonId: z27.string().optional(),
7146
+ disabled: z27.boolean().optional(),
7147
+ online: z27.boolean().optional()
7923
7148
  }).nullable();
7924
7149
  var GENERIC = /onvif|rtsp/i;
7925
- function words(text15) {
7926
- return text15.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 3);
7150
+ function words(text2) {
7151
+ return text2.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 3);
7927
7152
  }
7928
- function isGeneric(parts2) {
7929
- return parts2.some((p) => GENERIC.test(p));
7153
+ function isGeneric(parts) {
7154
+ return parts.some((p) => GENERIC.test(p));
7930
7155
  }
7931
7156
  function installedPick(p) {
7932
7157
  return {
@@ -7947,9 +7172,9 @@ function availablePick(p) {
7947
7172
  var MATCHING_NOTE = "A provider manifest declares no brand or model list: matching is on provider name, description and keywords, so a brand that is supported under another name can be missed.";
7948
7173
  function matchIn(pools, tokens) {
7949
7174
  if (tokens.length === 0) return null;
7950
- const hit = (parts2) => {
7951
- if (isGeneric(parts2)) return false;
7952
- const joined = parts2.join(" ").toLowerCase();
7175
+ const hit = (parts) => {
7176
+ if (isGeneric(parts)) return false;
7177
+ const joined = parts.join(" ").toLowerCase();
7953
7178
  return tokens.some((t) => joined.includes(t));
7954
7179
  };
7955
7180
  const installed = pools.installed.find(
@@ -8085,10 +7310,10 @@ async function supportDiagnoseCamera(ctx, args) {
8085
7310
  }
8086
7311
 
8087
7312
  // src/mcp/support-deps.ts
8088
- import { execFile as execFile2 } from "child_process";
7313
+ import { execFile } from "child_process";
8089
7314
  var RUN_TIMEOUT_MS = 6e4;
8090
7315
  var spawnRunner = (command, args) => new Promise((resolve) => {
8091
- execFile2(
7316
+ execFile(
8092
7317
  command,
8093
7318
  [...args],
8094
7319
  { timeout: RUN_TIMEOUT_MS, maxBuffer: 1e6 },
@@ -8120,7 +7345,7 @@ import {
8120
7345
  digestArgs,
8121
7346
  runGated as runGated6
8122
7347
  } from "@camstack/develop";
8123
- import { z as z32 } from "zod";
7348
+ import { z as z28 } from "zod";
8124
7349
 
8125
7350
  // src/mcp/support-scrub.ts
8126
7351
  import { redactToolArgs, scrubSecrets } from "@camstack/develop";
@@ -8129,10 +7354,10 @@ var IPV6 = /\b(?:[0-9a-fA-F]{1,4}:){4,7}[0-9a-fA-F]{1,4}(?![\w:])|\b[0-9a-fA-F]{
8129
7354
  var MAC = /\b(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}\b/g;
8130
7355
  var HOME_DIR = /(\/Users|\/home)\/[^/\s'"]+/g;
8131
7356
  function octets(ip) {
8132
- const parts2 = ip.split(".").map((p) => Number(p));
8133
- if (parts2.length !== 4) return null;
8134
- if (parts2.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) return null;
8135
- return parts2;
7357
+ const parts = ip.split(".").map((p) => Number(p));
7358
+ if (parts.length !== 4) return null;
7359
+ if (parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) return null;
7360
+ return parts;
8136
7361
  }
8137
7362
  function maskIpv4(ip) {
8138
7363
  const o = octets(ip);
@@ -8173,16 +7398,16 @@ var INFERENCE_PACKAGE = "@camstack/addon-pipeline";
8173
7398
  var TITLE_MAX = 140;
8174
7399
  var DIAGNOSIS_MAX = 4e3;
8175
7400
  var DRAFT_KINDS = ["accelerator", "camera", "benchmark", "other"];
8176
- var DraftKindSchema = z32.enum(DRAFT_KINDS);
8177
- var BenchmarkCellsSchema = z32.record(
8178
- z32.string().regex(/^[a-z0-9][a-z0-9._-]{1,63}$/),
8179
- z32.object({ p50Ms: z32.number().positive(), fps: z32.number().positive() })
7401
+ var DraftKindSchema = z28.enum(DRAFT_KINDS);
7402
+ var BenchmarkCellsSchema = z28.record(
7403
+ z28.string().regex(/^[a-z0-9][a-z0-9._-]{1,63}$/),
7404
+ z28.object({ p50Ms: z28.number().positive(), fps: z28.number().positive() })
8180
7405
  );
8181
7406
  var PackageNameRe = /^(@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
8182
7407
  var GithubRepoRe = /github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?(?:[/#?].*)?$/;
8183
7408
  var ShorthandRe = /^(?:github:)?([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/;
8184
- var RegistryDocSchema = z32.looseObject({
8185
- repository: z32.union([z32.string(), z32.looseObject({ url: z32.string() })]).optional()
7409
+ var RegistryDocSchema = z28.looseObject({
7410
+ repository: z28.union([z28.string(), z28.looseObject({ url: z28.string() })]).optional()
8186
7411
  });
8187
7412
  function githubRepoOf(value) {
8188
7413
  const url = GithubRepoRe.exec(value);
@@ -8224,7 +7449,7 @@ async function resolveRepo(ctx, packageName) {
8224
7449
  reason: null
8225
7450
  };
8226
7451
  }
8227
- var ServerStatusSchema2 = z32.looseObject({ runningVersion: z32.string() });
7452
+ var ServerStatusSchema2 = z28.looseObject({ runningVersion: z28.string() });
8228
7453
  async function serverVersion(client, nodeId) {
8229
7454
  const status = await readQuery(
8230
7455
  client,
@@ -8339,11 +7564,11 @@ async function buildDraft(ctx, args, heading) {
8339
7564
  ].filter((s) => s !== "");
8340
7565
  return { target, title: cleanTitle(args.title), body: sections.join("\n\n") };
8341
7566
  }
8342
- function shellQuote2(value) {
7567
+ function shellQuote(value) {
8343
7568
  return `'${value.replace(/'/g, `'\\''`)}'`;
8344
7569
  }
8345
7570
  function issueCommand(draft, bodyFile) {
8346
- return `gh issue create --repo ${draft.target.repo} --title ${shellQuote2(draft.title)} --body-file ${bodyFile}`;
7571
+ return `gh issue create --repo ${draft.target.repo} --title ${shellQuote(draft.title)} --body-file ${bodyFile}`;
8347
7572
  }
8348
7573
  var ISSUE_BODY_FILE = "./camstack-issue.md";
8349
7574
  async function issueDraft(ctx, args) {
@@ -8426,8 +7651,8 @@ var BENCHMARK_FILES = [
8426
7651
  "docs/benchmarks/detector-latency.json",
8427
7652
  "packages/types/src/pipeline/auto-tune/detector-performance.ts"
8428
7653
  ];
8429
- function slug(text15) {
8430
- const s = text15.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
7654
+ function slug(text2) {
7655
+ const s = text2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
8431
7656
  return s === "" ? "change" : s;
8432
7657
  }
8433
7658
  function prProblem(args) {
@@ -8450,7 +7675,7 @@ function planFor(args, draft) {
8450
7675
  `Merge the measured cells into docs/benchmarks/detector-latency.json under "${args.column ?? ""}" (set "measured": true on the column when every kept cell was measured on the box) and mirror the same cells into DETECTOR_PERF in packages/types/src/pipeline/auto-tune/detector-performance.ts.`,
8451
7676
  "npx vitest run packages/types/src/pipeline/auto-tune/__tests__/detector-performance.spec.ts (it fails until the two files agree)."
8452
7677
  ] : ["Make the change with a spec that fails first, then passes."],
8453
- `git commit -am ${shellQuote2(benchmark ? `docs: measured detector latency for ${args.column ?? ""}` : `fix: ${draft.title}`)}`,
7678
+ `git commit -am ${shellQuote(benchmark ? `docs: measured detector latency for ${args.column ?? ""}` : `fix: ${draft.title}`)}`,
8454
7679
  `git push -u origin ${branch}`,
8455
7680
  `Save the body below as ${PR_BODY_FILE}, then run the command.`
8456
7681
  ];
@@ -8463,7 +7688,7 @@ function planFor(args, draft) {
8463
7688
  branch,
8464
7689
  files,
8465
7690
  steps,
8466
- command: `gh pr create --repo ${draft.target.repo} --head <your-github-user>:${branch} --title ${shellQuote2(draft.title)} --body-file ${PR_BODY_FILE}`,
7691
+ command: `gh pr create --repo ${draft.target.repo} --head <your-github-user>:${branch} --title ${shellQuote(draft.title)} --body-file ${PR_BODY_FILE}`,
8467
7692
  note: "This tool pushes no code and calls no GitHub API; you run these steps with your own git and gh."
8468
7693
  };
8469
7694
  }
@@ -8490,20 +7715,20 @@ async function supportDraftPr(ctx, args, confirmToken) {
8490
7715
  }
8491
7716
 
8492
7717
  // src/mcp/support-tools.ts
8493
- var ModelId = z33.string().regex(/^[a-z0-9][a-z0-9._-]{1,63}$/);
7718
+ var ModelId = z29.string().regex(/^[a-z0-9][a-z0-9._-]{1,63}$/);
8494
7719
  var DraftShape = {
8495
7720
  kind: DraftKindSchema,
8496
- title: z33.string().min(3).max(300),
8497
- summary: z33.string().min(3).max(4e3),
8498
- nodeId: z33.string().max(100).optional(),
7721
+ title: z29.string().min(3).max(300),
7722
+ summary: z29.string().min(3).max(4e3),
7723
+ nodeId: z29.string().max(100).optional(),
8499
7724
  expected: ExpectedAcceleratorSchema.optional(),
8500
- brand: z33.string().max(100).optional(),
8501
- model: z33.string().max(100).optional(),
8502
- deviceId: z33.number().int().optional(),
8503
- packageName: z33.string().max(214).optional(),
8504
- column: z33.string().max(100).optional(),
7725
+ brand: z29.string().max(100).optional(),
7726
+ model: z29.string().max(100).optional(),
7727
+ deviceId: z29.number().int().optional(),
7728
+ packageName: z29.string().max(214).optional(),
7729
+ column: z29.string().max(100).optional(),
8505
7730
  cells: BenchmarkCellsSchema.optional(),
8506
- confirm_token: z33.string().optional()
7731
+ confirm_token: z29.string().optional()
8507
7732
  };
8508
7733
  function draftArgs(input) {
8509
7734
  return {
@@ -8524,20 +7749,20 @@ function registerSupportTools(server, ctx, deps = defaultSupportDeps) {
8524
7749
  server.tool(
8525
7750
  "support_benchmark_status",
8526
7751
  "The hub's own node-benchmark state: the active or last run and the per-device latency cells it stored. Read-only. The Auto-tune latency table is filled with support_benchmark_run. Needs an admin `camstack login`.",
8527
- { nodeId: z33.string().max(100).optional() },
8528
- async ({ nodeId }) => text14(await supportBenchmarkStatus(ctx, nodeId !== void 0 ? { nodeId } : {}))
7752
+ { nodeId: z29.string().max(100).optional() },
7753
+ async ({ nodeId }) => text(await supportBenchmarkStatus(ctx, nodeId !== void 0 ? { nodeId } : {}))
8529
7754
  );
8530
7755
  server.tool(
8531
7756
  BENCHMARK_RUN_TOOL,
8532
7757
  "Measure detector latency on a node x device (cold run, then warm median per model, via runPipelineTest) and return it next to the Auto-tune table cell for the same hardware, plus how to contribute it. LOADS THE NODE: the detection pool restarts between models. Pick the node and deviceKey from ops_accelerators. Two steps: call without confirm_token for the plan and a one-shot token, then again with the SAME arguments and that token. Needs an admin `camstack login`.",
8533
7758
  {
8534
- nodeId: z33.string().min(1).max(100),
8535
- deviceKey: z33.string().min(1).max(60),
8536
- models: z33.array(ModelId).min(1).max(MAX_MODELS).optional(),
8537
- warmRuns: z33.number().int().min(1).max(MAX_WARM_RUNS).optional(),
8538
- confirm_token: z33.string().optional()
7759
+ nodeId: z29.string().min(1).max(100),
7760
+ deviceKey: z29.string().min(1).max(60),
7761
+ models: z29.array(ModelId).min(1).max(MAX_MODELS).optional(),
7762
+ warmRuns: z29.number().int().min(1).max(MAX_WARM_RUNS).optional(),
7763
+ confirm_token: z29.string().optional()
8539
7764
  },
8540
- async ({ nodeId, deviceKey, models, warmRuns, confirm_token }) => text14(
7765
+ async ({ nodeId, deviceKey, models, warmRuns, confirm_token }) => text(
8541
7766
  await supportBenchmarkRun(
8542
7767
  ctx,
8543
7768
  deps,
@@ -8554,18 +7779,18 @@ function registerSupportTools(server, ctx, deps = defaultSupportDeps) {
8554
7779
  server.tool(
8555
7780
  "support_diagnose_accelerator",
8556
7781
  "A node should have an accelerator (Intel iGPU/NPU, AMD WebGPU/VAAPI/NPU, Coral USB/PCIe, Apple Neural Engine, NVIDIA) and the hub does not offer it. Reads what the platform probe saw and what the node's inference runtime offers, and answers ok | misconfigured (with fix steps: passthrough, driver, image tag, opt-in) | unsupported (which addon would need extending) | unknown (a read failed). A Coral is never auto-picked: the answer says how to opt in. Read-only.",
8557
- { nodeId: z33.string().min(1).max(100), expected: ExpectedAcceleratorSchema },
8558
- async ({ nodeId, expected }) => text14(await supportDiagnoseAccelerator(ctx, { nodeId, expected }))
7782
+ { nodeId: z29.string().min(1).max(100), expected: ExpectedAcceleratorSchema },
7783
+ async ({ nodeId, expected }) => text(await supportDiagnoseAccelerator(ctx, { nodeId, expected }))
8559
7784
  );
8560
7785
  server.tool(
8561
7786
  "support_diagnose_camera",
8562
7787
  "A camera brand or model, or an existing device that fails: which provider addons could serve it (installed, then published), and a verdict use-provider | generic-fallback (ONVIF / RTSP) | unsupported | unknown, with the next steps. Give a brand and/or model, or a deviceId. Read-only.",
8563
7788
  {
8564
- brand: z33.string().max(100).optional(),
8565
- model: z33.string().max(100).optional(),
8566
- deviceId: z33.number().int().optional()
7789
+ brand: z29.string().max(100).optional(),
7790
+ model: z29.string().max(100).optional(),
7791
+ deviceId: z29.number().int().optional()
8567
7792
  },
8568
- async ({ brand, model, deviceId }) => text14(
7793
+ async ({ brand, model, deviceId }) => text(
8569
7794
  await supportDiagnoseCamera(ctx, {
8570
7795
  ...brand !== void 0 ? { brand } : {},
8571
7796
  ...model !== void 0 ? { model } : {},
@@ -8577,13 +7802,13 @@ function registerSupportTools(server, ctx, deps = defaultSupportDeps) {
8577
7802
  DRAFT_ISSUE_TOOL,
8578
7803
  "Draft a GitHub issue for the public repository that owns the addon (the package's `repository` field; camstack/server when it names none): title, an environment block (server and CLI version, node platform, accelerator or camera facts from the diagnose tools), secrets and private addresses removed. Two steps: the dry run is the full draft and the target; the confirmed call runs YOUR own `gh issue create` (only if `gh auth status` succeeds, otherwise it returns the draft and a ready command). Never uses a token of ours.",
8579
7804
  DraftShape,
8580
- async (input) => text14(await supportDraftIssue(ctx, deps, draftArgs(input), input.confirm_token))
7805
+ async (input) => text(await supportDraftIssue(ctx, deps, draftArgs(input), input.confirm_token))
8581
7806
  );
8582
7807
  server.tool(
8583
7808
  DRAFT_PR_TOOL,
8584
7809
  'Plan a pull request for the public repository that owns the addon: branch, files to change, steps and the `gh pr create` command, with a scrubbed body. For kind "benchmark" give column and cells from support_benchmark_run. Two steps like every gated tool; it pushes no code and calls no GitHub API, you run the plan with your own git and gh.',
8585
7810
  DraftShape,
8586
- async (input) => text14(await supportDraftPr(ctx, draftArgs(input), input.confirm_token))
7811
+ async (input) => text(await supportDraftPr(ctx, draftArgs(input), input.confirm_token))
8587
7812
  );
8588
7813
  }
8589
7814