camstack 1.2.213 → 1.2.214

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.
@@ -13,13 +13,23 @@ import {
13
13
  classifyInferenceHardware,
14
14
  convertLegacyWhen,
15
15
  countWhenLeaves,
16
- findHubs,
17
16
  generateAutomationBlock,
18
17
  isReadingQuantity,
19
18
  prepareRecipeUnits,
20
19
  unitsForQuantity,
21
20
  validateRecipeBounds
22
- } from "./chunk-MJJWVAEM.js";
21
+ } from "./chunk-4Y3CBGOY.js";
22
+ import {
23
+ IMAGE_TAG_BY_ACCELERATOR,
24
+ MUST_NOT_OMIT,
25
+ NO_BARE_LATEST,
26
+ cliVerdict,
27
+ isOlder,
28
+ registerSetupTools,
29
+ runCommand,
30
+ text
31
+ } from "./chunk-37A556YF.js";
32
+ import "./chunk-JPGJK5DC.js";
23
33
  import "./chunk-LMMQX4CK.js";
24
34
 
25
35
  // src/mcp/server.ts
@@ -29,14 +39,12 @@ import { MutationGate, tryConnectHub } from "@camstack/develop";
29
39
 
30
40
  // src/mcp/configure-tools.ts
31
41
  import {
32
- runGated as runGated4,
33
- text as text13
42
+ runGated as runGated4
34
43
  } from "@camstack/develop";
35
- import { z as z28 } from "zod";
44
+ import { z as z24 } from "zod";
36
45
 
37
46
  // src/mcp/configure-automations.ts
38
47
  import { z as z4 } from "zod";
39
- import { text } from "@camstack/develop";
40
48
 
41
49
  // src/mcp/configure-common.ts
42
50
  import { runGated } from "@camstack/develop";
@@ -1236,7 +1244,6 @@ function registerAutomationTools(server, ctx) {
1236
1244
  }
1237
1245
 
1238
1246
  // src/mcp/configure-calibrate.ts
1239
- import { text as text2 } from "@camstack/develop";
1240
1247
  import { z as z5 } from "zod";
1241
1248
  var CALIBRATE_APPLY_TOOL = "configure_calibrate_apply";
1242
1249
  var WINDOW_MS = 24 * 36e5;
@@ -1602,7 +1609,7 @@ function registerCalibrateTools(server, ctx) {
1602
1609
  "configure_calibrate_camera",
1603
1610
  "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
1611
  { deviceId: z5.number().int().nonnegative() },
1605
- async ({ deviceId }) => text2(await calibrateCamera(ctx, { deviceId }))
1612
+ async ({ deviceId }) => text(await calibrateCamera(ctx, { deviceId }))
1606
1613
  );
1607
1614
  server.tool(
1608
1615
  CALIBRATE_APPLY_TOOL,
@@ -1613,13 +1620,12 @@ function registerCalibrateTools(server, ctx) {
1613
1620
  patch: z5.record(z5.string(), z5.union([z5.number(), z5.string(), z5.boolean()])),
1614
1621
  confirm_token: ConfirmTokenSchema
1615
1622
  },
1616
- async ({ deviceId, cap: cap2, patch, confirm_token }) => text2(await calibrateApply(ctx, { deviceId, cap: cap2, patch }, confirm_token))
1623
+ async ({ deviceId, cap: cap2, patch, confirm_token }) => text(await calibrateApply(ctx, { deviceId, cap: cap2, patch }, confirm_token))
1617
1624
  );
1618
1625
  }
1619
1626
 
1620
1627
  // src/mcp/configure-zones-alarm.ts
1621
1628
  import { randomUUID } from "crypto";
1622
- import { text as text3 } from "@camstack/develop";
1623
1629
  import { z as z6 } from "zod";
1624
1630
  var ZONE_UPSERT_TOOL = "configure_zone_upsert";
1625
1631
  var ALARM_SET_TOOL = "configure_alarm_set";
@@ -1910,7 +1916,7 @@ function registerZonesAlarmTools(server, ctx) {
1910
1916
  "configure_zones",
1911
1917
  "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
1918
  { deviceId: z6.number().int().nonnegative() },
1913
- async ({ deviceId }) => text3(await readZonesTool(ctx, { deviceId }))
1919
+ async ({ deviceId }) => text(await readZonesTool(ctx, { deviceId }))
1914
1920
  );
1915
1921
  server.tool(
1916
1922
  ZONE_UPSERT_TOOL,
@@ -1923,7 +1929,7 @@ function registerZonesAlarmTools(server, ctx) {
1923
1929
  points: z6.array(PointSchema).min(2).max(64),
1924
1930
  confirm_token: ConfirmTokenSchema
1925
1931
  },
1926
- async ({ deviceId, zoneId, name, kind, points, confirm_token }) => text3(
1932
+ async ({ deviceId, zoneId, name, kind, points, confirm_token }) => text(
1927
1933
  await zoneUpsert(
1928
1934
  ctx,
1929
1935
  {
@@ -1941,7 +1947,7 @@ function registerZonesAlarmTools(server, ctx) {
1941
1947
  "configure_alarm",
1942
1948
  "Read the alarm panels of the hub: state, armed mode, the modes each accepts and whether it needs a PIN. Writes nothing.",
1943
1949
  { deviceId: z6.number().int().nonnegative().optional() },
1944
- async ({ deviceId }) => text3(await readAlarm(ctx, deviceId !== void 0 ? { deviceId } : {}))
1950
+ async ({ deviceId }) => text(await readAlarm(ctx, deviceId !== void 0 ? { deviceId } : {}))
1945
1951
  );
1946
1952
  server.tool(
1947
1953
  ALARM_SET_TOOL,
@@ -1952,7 +1958,7 @@ function registerZonesAlarmTools(server, ctx) {
1952
1958
  mode: AlarmArmModeSchema.optional(),
1953
1959
  confirm_token: ConfirmTokenSchema
1954
1960
  },
1955
- async ({ deviceId, action, mode, confirm_token }) => text3(
1961
+ async ({ deviceId, action, mode, confirm_token }) => text(
1956
1962
  await alarmSet(
1957
1963
  ctx,
1958
1964
  { deviceId, action, ...mode !== void 0 ? { mode } : {} },
@@ -1963,7 +1969,6 @@ function registerZonesAlarmTools(server, ctx) {
1963
1969
  }
1964
1970
 
1965
1971
  // src/mcp/configure-network.ts
1966
- import { text as text4 } from "@camstack/develop";
1967
1972
  import { z as z7 } from "zod";
1968
1973
  var REMOTE_ACCESS_SET_TOOL = "configure_remote_access_set";
1969
1974
  var RemoteAddonSchema = z7.enum(["cloudflare-tunnel", "cloudflare-turn", "static-turn"]);
@@ -2147,7 +2152,7 @@ function registerNetworkTools(server, ctx) {
2147
2152
  "configure_remote_access",
2148
2153
  "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
2154
  {},
2150
- async () => text4(await readRemoteAccess(ctx))
2155
+ async () => text(await readRemoteAccess(ctx))
2151
2156
  );
2152
2157
  server.tool(
2153
2158
  REMOTE_ACCESS_SET_TOOL,
@@ -2157,12 +2162,11 @@ function registerNetworkTools(server, ctx) {
2157
2162
  patch: z7.record(z7.string(), z7.union([z7.string(), z7.number(), z7.boolean()])),
2158
2163
  confirm_token: ConfirmTokenSchema
2159
2164
  },
2160
- async ({ addonId, patch, confirm_token }) => text4(await remoteAccessSet(ctx, { addonId, patch }, confirm_token))
2165
+ async ({ addonId, patch, confirm_token }) => text(await remoteAccessSet(ctx, { addonId, patch }, confirm_token))
2161
2166
  );
2162
2167
  }
2163
2168
 
2164
2169
  // src/mcp/configure-exports.ts
2165
- import { text as text5 } from "@camstack/develop";
2166
2170
  import { z as z8 } from "zod";
2167
2171
  var EXPORT_SET_TOOL = "configure_export_set";
2168
2172
  var ProvidersSchema = z8.array(z8.looseObject({ addonId: z8.string() }));
@@ -2325,7 +2329,7 @@ function registerExportTools(server, ctx) {
2325
2329
  "configure_exports",
2326
2330
  "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
2331
  { deviceId: z8.number().int().nonnegative().optional() },
2328
- async ({ deviceId }) => text5(await readExports(ctx, deviceId !== void 0 ? { deviceId } : {}))
2332
+ async ({ deviceId }) => text(await readExports(ctx, deviceId !== void 0 ? { deviceId } : {}))
2329
2333
  );
2330
2334
  server.tool(
2331
2335
  EXPORT_SET_TOOL,
@@ -2336,18 +2340,17 @@ function registerExportTools(server, ctx) {
2336
2340
  exposed: z8.boolean(),
2337
2341
  confirm_token: ConfirmTokenSchema
2338
2342
  },
2339
- async ({ addonId, deviceId, exposed, confirm_token }) => text5(await exportSet(ctx, { addonId, deviceId, exposed }, confirm_token))
2343
+ async ({ addonId, deviceId, exposed, confirm_token }) => text(await exportSet(ctx, { addonId, deviceId, exposed }, confirm_token))
2340
2344
  );
2341
2345
  server.tool(
2342
2346
  "configure_ha_guide",
2343
2347
  "Read-only guide for showing CamStack cameras in Home Assistant, including the HACS route. Needs no hub.",
2344
2348
  {},
2345
- async () => text5({ status: "ok", guide: HA_GUIDE })
2349
+ async () => text({ status: "ok", guide: HA_GUIDE })
2346
2350
  );
2347
2351
  }
2348
2352
 
2349
2353
  // src/mcp/configure-oidc.ts
2350
- import { text as text6 } from "@camstack/develop";
2351
2354
  import { z as z10 } from "zod";
2352
2355
 
2353
2356
  // src/mcp/configure-oidc-providers.ts
@@ -2836,13 +2839,12 @@ function registerOidcTools(server, ctx) {
2836
2839
  OIDC_TOOL,
2837
2840
  "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
2841
  OIDC_TOOL_SHAPE,
2839
- async ({ confirm_token, ...args }) => text6(await configureOidc(ctx, args, confirm_token))
2842
+ async ({ confirm_token, ...args }) => text(await configureOidc(ctx, args, confirm_token))
2840
2843
  );
2841
2844
  }
2842
2845
 
2843
2846
  // src/mcp/configure-nc-rules.ts
2844
2847
  import { z as z11 } from "zod";
2845
- import { text as text7 } from "@camstack/develop";
2846
2848
  var TIME = /^\d{1,2}:\d{2}$/;
2847
2849
  var ALL_DAYS = [0, 1, 2, 3, 4, 5, 6];
2848
2850
  var QuietHoursAnswerSchema = z11.object({
@@ -3188,7 +3190,7 @@ function registerNcRuleTools(server, ctx) {
3188
3190
  "configure_notification_rule_questionnaire",
3189
3191
  "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
3192
  { answers: NcRuleAnswersSchema },
3191
- async ({ answers }) => text7(await ncRuleQuestionnaire(ctx, answers))
3193
+ async ({ answers }) => text(await ncRuleQuestionnaire(ctx, answers))
3192
3194
  );
3193
3195
  server.tool(
3194
3196
  SAVE_RULE_TOOL,
@@ -3198,7 +3200,7 @@ function registerNcRuleTools(server, ctx) {
3198
3200
  ruleId: z11.string().optional(),
3199
3201
  confirm_token: ConfirmTokenSchema
3200
3202
  },
3201
- async ({ rule, ruleId, confirm_token }) => text7(
3203
+ async ({ rule, ruleId, confirm_token }) => text(
3202
3204
  await saveRule(ctx, { rule, ...ruleId !== void 0 ? { ruleId } : {} }, confirm_token)
3203
3205
  )
3204
3206
  );
@@ -3216,9 +3218,6 @@ function registerConfigureP3bTools(server, ctx) {
3216
3218
  }
3217
3219
 
3218
3220
  // src/mcp/configure-addons.ts
3219
- import {
3220
- text as text8
3221
- } from "@camstack/develop";
3222
3221
  import { z as z13 } from "zod";
3223
3222
 
3224
3223
  // src/mcp/configure-support.ts
@@ -3873,7 +3872,7 @@ function registerAddonTools(server, ctx) {
3873
3872
  query: z13.string().max(100).optional(),
3874
3873
  capability: z13.string().max(100).optional()
3875
3874
  },
3876
- async ({ query, capability }) => text8(
3875
+ async ({ query, capability }) => text(
3877
3876
  await configureAddonsSearch(ctx, {
3878
3877
  ...query !== void 0 ? { query } : {},
3879
3878
  ...capability !== void 0 ? { capability } : {}
@@ -3888,7 +3887,7 @@ function registerAddonTools(server, ctx) {
3888
3887
  version: z13.string().max(64).optional(),
3889
3888
  confirm_token: z13.string().optional()
3890
3889
  },
3891
- async ({ packageName, version, confirm_token }) => text8(
3890
+ async ({ packageName, version, confirm_token }) => text(
3892
3891
  await installAddon(
3893
3892
  ctx,
3894
3893
  { packageName, ...version !== void 0 ? { version } : {} },
@@ -3905,7 +3904,7 @@ function registerAddonTools(server, ctx) {
3905
3904
  nodeId: z13.string().max(100).optional(),
3906
3905
  confirm_token: z13.string().optional()
3907
3906
  },
3908
- async ({ packageName, version, nodeId, confirm_token }) => text8(
3907
+ async ({ packageName, version, nodeId, confirm_token }) => text(
3909
3908
  await updateAddon(
3910
3909
  ctx,
3911
3910
  {
@@ -3926,7 +3925,7 @@ function registerAddonTools(server, ctx) {
3926
3925
  everywhere: z13.boolean().optional(),
3927
3926
  confirm_token: z13.string().optional()
3928
3927
  },
3929
- async ({ packageName, nodeId, everywhere, confirm_token }) => text8(
3928
+ async ({ packageName, nodeId, everywhere, confirm_token }) => text(
3930
3929
  await uninstallAddon(
3931
3930
  ctx,
3932
3931
  {
@@ -3941,9 +3940,6 @@ function registerAddonTools(server, ctx) {
3941
3940
  }
3942
3941
 
3943
3942
  // src/mcp/configure-cameras.ts
3944
- import {
3945
- text as text9
3946
- } from "@camstack/develop";
3947
3943
  import { z as z14 } from "zod";
3948
3944
  var UNSUPPORTED_HINT = "use support_diagnose_camera / support_draft_issue";
3949
3945
  var MAX_PROVIDERS = 40;
@@ -4270,7 +4266,7 @@ function registerCameraTools(server, ctx) {
4270
4266
  "configure_camera_providers",
4271
4267
  "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
4268
  { query: z14.string().max(100).optional() },
4273
- async ({ query }) => text9(await configureCameraProviders(ctx, query))
4269
+ async ({ query }) => text(await configureCameraProviders(ctx, query))
4274
4270
  );
4275
4271
  server.tool(
4276
4272
  "configure_camera_discover",
@@ -4279,7 +4275,7 @@ function registerCameraTools(server, ctx) {
4279
4275
  addonId: z14.string().min(1),
4280
4276
  params: z14.record(z14.string(), z14.unknown()).optional()
4281
4277
  },
4282
- async ({ addonId, params }) => text9(
4278
+ async ({ addonId, params }) => text(
4283
4279
  await configureCameraDiscover(ctx, {
4284
4280
  addonId,
4285
4281
  ...params !== void 0 ? { params } : {}
@@ -4294,14 +4290,11 @@ function registerCameraTools(server, ctx) {
4294
4290
  config: z14.record(z14.string(), z14.unknown()),
4295
4291
  confirm_token: z14.string().optional()
4296
4292
  },
4297
- async ({ addonId, config, confirm_token }) => text9(await addCamera(ctx, { addonId, config }, confirm_token))
4293
+ async ({ addonId, config, confirm_token }) => text(await addCamera(ctx, { addonId, config }, confirm_token))
4298
4294
  );
4299
4295
  }
4300
4296
 
4301
4297
  // src/mcp/configure-notifiers.ts
4302
- import {
4303
- text as text10
4304
- } from "@camstack/develop";
4305
4298
  import { z as z15 } from "zod";
4306
4299
  var MAX_TARGETS = 50;
4307
4300
  var TargetKindShape = z15.looseObject({
@@ -4604,13 +4597,13 @@ function registerNotifierTools(server, ctx) {
4604
4597
  "configure_notifiers",
4605
4598
  "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
4599
  {},
4607
- async () => text10(await configureNotifiers(ctx))
4600
+ async () => text(await configureNotifiers(ctx))
4608
4601
  );
4609
4602
  server.tool(
4610
4603
  "configure_notification_targets",
4611
4604
  "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
4605
  {},
4613
- async () => text10(await configureNotificationTargets(ctx))
4606
+ async () => text(await configureNotificationTargets(ctx))
4614
4607
  );
4615
4608
  server.tool(
4616
4609
  UPSERT_TARGET_TOOL,
@@ -4623,7 +4616,7 @@ function registerNotifierTools(server, ctx) {
4623
4616
  enabled: z15.boolean().optional(),
4624
4617
  confirm_token: z15.string().optional()
4625
4618
  },
4626
- async ({ targetId, kind, name, config, enabled, confirm_token }) => text10(
4619
+ async ({ targetId, kind, name, config, enabled, confirm_token }) => text(
4627
4620
  await upsertNotificationTarget(
4628
4621
  ctx,
4629
4622
  {
@@ -4641,13 +4634,13 @@ function registerNotifierTools(server, ctx) {
4641
4634
  TEST_TARGET_TOOL,
4642
4635
  "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
4636
  { targetId: z15.string().min(1), confirm_token: z15.string().optional() },
4644
- async ({ targetId, confirm_token }) => text10(await testNotificationTarget(ctx, { targetId }, confirm_token))
4637
+ async ({ targetId, confirm_token }) => text(await testNotificationTarget(ctx, { targetId }, confirm_token))
4645
4638
  );
4646
4639
  }
4647
4640
 
4648
4641
  // src/mcp/ops-tools.ts
4649
- import { z as z27 } from "zod";
4650
- import { jwtClaims, text as text12 } from "@camstack/develop";
4642
+ import { z as z23 } from "zod";
4643
+ import { jwtClaims } from "@camstack/develop";
4651
4644
 
4652
4645
  // src/mcp/ops-accelerators.ts
4653
4646
  import { z as z16 } from "zod";
@@ -5540,7 +5533,7 @@ async function opsStatus(ctx) {
5540
5533
  import {
5541
5534
  runGated as runGated3
5542
5535
  } from "@camstack/develop";
5543
- import { z as z25 } from "zod";
5536
+ import { z as z21 } from "zod";
5544
5537
 
5545
5538
  // src/mcp/ops-update-model.ts
5546
5539
  import { z as z20 } from "zod";
@@ -5713,790 +5706,6 @@ function addonDrift(installations) {
5713
5706
  return drift;
5714
5707
  }
5715
5708
 
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
5709
  // src/mcp/ops-update-apply.ts
6501
5710
  var UPDATE_APPLY_TOOL = "ops_update_apply";
6502
5711
  function timing(deps) {
@@ -6549,7 +5758,7 @@ async function readPlan(client, args, ctx, deps) {
6549
5758
  wrapper: wrapperVerdict(status.value)
6550
5759
  });
6551
5760
  }
6552
- const updates = args.addons === false ? null : await readQuery(client, "addons.listUpdates", {}, z25.array(AddonUpdateShape));
5761
+ const updates = args.addons === false ? null : await readQuery(client, "addons.listUpdates", {}, z21.array(AddonUpdateShape));
6553
5762
  const cli = args.cli === false ? null : await cliVerdict(ctx.cliVersion, ctx.fetch, deps.entryPath ?? process.argv[1] ?? "");
6554
5763
  const hub = plans.find((p) => p.node.isHub === true) ?? null;
6555
5764
  return {
@@ -6865,14 +6074,14 @@ async function opsUpdateApply(ctx, args, confirmToken, deps = {}) {
6865
6074
  }
6866
6075
 
6867
6076
  // src/mcp/ops-update-check.ts
6868
- import { z as z26 } from "zod";
6077
+ import { z as z22 } from "zod";
6869
6078
  var ADDON_LIMIT = 30;
6870
6079
  async function opsUpdateCheck(ctx, deps = {}) {
6871
6080
  return withAdminHub(ctx, async ({ client, server }) => {
6872
6081
  const unavailable = {};
6873
6082
  const [topology, updates, installs, cli] = await Promise.all([
6874
6083
  readTopology(client),
6875
- readQuery(client, "addons.listUpdates", {}, z26.array(AddonUpdateShape)),
6084
+ readQuery(client, "addons.listUpdates", {}, z22.array(AddonUpdateShape)),
6876
6085
  readInstallations(client),
6877
6086
  cliVerdict(ctx.cliVersion, ctx.fetch, deps.entryPath ?? process.argv[1] ?? "")
6878
6087
  ]);
@@ -6990,40 +6199,40 @@ function registerOpsTools(server, ctx) {
6990
6199
  "ops_whoami",
6991
6200
  "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
6201
  {},
6993
- async () => text12(await whoami(ctx))
6202
+ async () => text(await whoami(ctx))
6994
6203
  );
6995
6204
  server.tool(
6996
6205
  "ops_status",
6997
6206
  '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
6207
  {},
6999
- async () => text12(await opsStatus(ctx))
6208
+ async () => text(await opsStatus(ctx))
7000
6209
  );
7001
6210
  server.tool(
7002
6211
  "ops_device",
7003
6212
  "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))
6213
+ { deviceId: z23.number().int() },
6214
+ async ({ deviceId }) => text(await opsDevice(ctx, deviceId))
7006
6215
  );
7007
6216
  server.tool(
7008
6217
  "ops_unhealthy_devices",
7009
6218
  "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
6219
  {},
7011
- async () => text12(await opsUnhealthyDevices(ctx))
6220
+ async () => text(await opsUnhealthyDevices(ctx))
7012
6221
  );
7013
6222
  server.tool(
7014
6223
  "ops_logs",
7015
6224
  "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
6225
  {
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()
6226
+ deviceId: z23.number().int().optional(),
6227
+ addonId: z23.string().optional(),
6228
+ nodeId: z23.string().optional(),
6229
+ level: z23.enum(["debug", "info", "warn", "error"]).optional(),
6230
+ windowMinutes: z23.number().int().min(1).max(1440).optional(),
6231
+ text: z23.string().max(200).optional(),
6232
+ around: z23.string().max(200).optional(),
6233
+ contextLines: z23.number().int().min(1).max(50).optional()
7025
6234
  },
7026
- async (args) => text12(
6235
+ async (args) => text(
7027
6236
  await opsLogs(ctx, {
7028
6237
  ...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
7029
6238
  ...args.addonId ? { addonId: args.addonId } : {},
@@ -7040,14 +6249,14 @@ function registerOpsTools(server, ctx) {
7040
6249
  "ops_events",
7041
6250
  "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
6251
  {
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()
6252
+ deviceId: z23.number().int().optional(),
6253
+ className: z23.string().optional(),
6254
+ zone: z23.string().optional(),
6255
+ text: z23.string().max(200).optional(),
6256
+ windowMinutes: z23.number().int().min(1).max(10080).optional(),
6257
+ limit: z23.number().int().min(1).max(50).optional()
7049
6258
  },
7050
- async (args) => text12(
6259
+ async (args) => text(
7051
6260
  await opsEvents(ctx, {
7052
6261
  ...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
7053
6262
  ...args.className ? { className: args.className } : {},
@@ -7061,31 +6270,31 @@ function registerOpsTools(server, ctx) {
7061
6270
  server.tool(
7062
6271
  "ops_track",
7063
6272
  "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 }))
6273
+ { deviceId: z23.number().int(), trackId: z23.string().min(1) },
6274
+ async ({ deviceId, trackId }) => text(await opsTrack(ctx, { deviceId, trackId }))
7066
6275
  );
7067
6276
  server.tool(
7068
6277
  "ops_accelerators",
7069
6278
  '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
6279
  {},
7071
- async () => text12(await opsAccelerators(ctx))
6280
+ async () => text(await opsAccelerators(ctx))
7072
6281
  );
7073
6282
  server.tool(
7074
6283
  "ops_update_check",
7075
6284
  '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
6285
  {},
7077
- async () => text12(await opsUpdateCheck(ctx))
6286
+ async () => text(await opsUpdateCheck(ctx))
7078
6287
  );
7079
6288
  server.tool(
7080
6289
  UPDATE_APPLY_TOOL,
7081
6290
  "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
6291
  {
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()
6292
+ version: z23.string().min(1).optional().describe("Server target; default = the hub's latest."),
6293
+ addons: z23.boolean().optional().describe("Update addons too (default true)."),
6294
+ cli: z23.boolean().optional().describe("Update this CLI last (default true)."),
6295
+ confirm_token: z23.string().optional()
7087
6296
  },
7088
- async ({ version, addons, cli, confirm_token }) => text12(
6297
+ async ({ version, addons, cli, confirm_token }) => text(
7089
6298
  await opsUpdateApply(
7090
6299
  ctx,
7091
6300
  {
@@ -7100,7 +6309,7 @@ function registerOpsTools(server, ctx) {
7100
6309
  }
7101
6310
 
7102
6311
  // src/mcp/configure-tools.ts
7103
- var CurrentDeviceSchema = z28.object({ name: z28.string() }).nullable();
6312
+ var CurrentDeviceSchema = z24.object({ name: z24.string() }).nullable();
7104
6313
  var RENAME_DEVICE_TOOL = "configure_rename_device";
7105
6314
  function renameDeviceSpec(client) {
7106
6315
  return {
@@ -7162,11 +6371,11 @@ function registerConfigureTools(server, ctx) {
7162
6371
  RENAME_DEVICE_TOOL,
7163
6372
  "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
6373
  {
7165
- deviceId: z28.number().int(),
7166
- name: z28.string().min(1),
7167
- confirm_token: z28.string().optional()
6374
+ deviceId: z24.number().int(),
6375
+ name: z24.string().min(1),
6376
+ confirm_token: z24.string().optional()
7168
6377
  },
7169
- async ({ deviceId, name, confirm_token }) => text13(await renameDevice(ctx, { deviceId, name }, confirm_token))
6378
+ async ({ deviceId, name, confirm_token }) => text(await renameDevice(ctx, { deviceId, name }, confirm_token))
7170
6379
  );
7171
6380
  registerAddonTools(server, ctx);
7172
6381
  registerCameraTools(server, ctx);
@@ -7175,11 +6384,10 @@ function registerConfigureTools(server, ctx) {
7175
6384
  }
7176
6385
 
7177
6386
  // src/mcp/support-tools.ts
7178
- import { text as text14 } from "@camstack/develop";
7179
- import { z as z33 } from "zod";
6387
+ import { z as z29 } from "zod";
7180
6388
 
7181
6389
  // src/mcp/support-accelerator.ts
7182
- import { z as z29 } from "zod";
6390
+ import { z as z25 } from "zod";
7183
6391
  var EXPECTED_ACCELERATORS = [
7184
6392
  "intel-igpu",
7185
6393
  "intel-npu",
@@ -7191,23 +6399,23 @@ var EXPECTED_ACCELERATORS = [
7191
6399
  "apple-ane",
7192
6400
  "nvidia"
7193
6401
  ];
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()
6402
+ var ExpectedAcceleratorSchema = z25.enum(EXPECTED_ACCELERATORS);
6403
+ var HardwareSchema2 = z25.looseObject({
6404
+ platform: z25.enum(["darwin", "linux", "win32"]),
6405
+ arch: z25.enum(["arm64", "x64"]),
6406
+ cpuModel: z25.string(),
6407
+ gpu: z25.looseObject({ type: z25.string(), name: z25.string() }).nullable(),
6408
+ npu: z25.looseObject({ type: z25.string() }).nullable()
7201
6409
  });
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()
6410
+ var InferenceDevicesSchema2 = z25.looseObject({
6411
+ reachable: z25.boolean(),
6412
+ devices: z25.array(
6413
+ z25.looseObject({
6414
+ key: z25.string(),
6415
+ available: z25.boolean(),
6416
+ presence: z25.enum(["present", "absent", "unknown"]).optional(),
6417
+ enabled: z25.boolean(),
6418
+ exclusion: z25.string().nullable()
7211
6419
  })
7212
6420
  )
7213
6421
  });
@@ -7403,11 +6611,11 @@ function diagnoseAccelerator(input) {
7403
6611
  }
7404
6612
  const matched = devices.filter((d) => expectation.deviceKeys(d.key));
7405
6613
  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) {
6614
+ const ready = matched.filter((d) => d.available && d.enabled && d.exclusion === null);
6615
+ if (ready.length > 0) {
7408
6616
  return finish(
7409
6617
  "ok",
7410
- `The node offers ${ready2.map((d) => d.key).join(", ")}, available and enabled.`,
6618
+ `The node offers ${ready.map((d) => d.key).join(", ")}, available and enabled.`,
7411
6619
  matchedKeys,
7412
6620
  [],
7413
6621
  null
@@ -7486,7 +6694,7 @@ async function supportDiagnoseAccelerator(ctx, args) {
7486
6694
  import {
7487
6695
  runGated as runGated5
7488
6696
  } from "@camstack/develop";
7489
- import { z as z30 } from "zod";
6697
+ import { z as z26 } from "zod";
7490
6698
  var BENCHMARK_RUN_TOOL = "support_benchmark_run";
7491
6699
  var AUTO_TUNE_MODELS = [
7492
6700
  "yolov9t-320",
@@ -7508,64 +6716,64 @@ var CONTRIBUTE_HOW = [
7508
6716
  "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
6717
  '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
6718
  ];
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()
6719
+ var HardwareSchema3 = z26.looseObject({
6720
+ platform: z26.enum(["darwin", "linux", "win32"]),
6721
+ arch: z26.enum(["arm64", "x64"]),
6722
+ cpuModel: z26.string(),
6723
+ cpuCores: z26.number(),
6724
+ totalRAM_MB: z26.number(),
6725
+ availableRAM_MB: z26.number(),
6726
+ gpu: z26.looseObject({
6727
+ type: z26.enum(["nvidia", "amd", "intel", "apple"]),
6728
+ name: z26.string()
7521
6729
  }).nullable(),
7522
- npu: z30.looseObject({ type: z30.enum(["apple-ane", "intel-npu", "amd-xdna"]) }).nullable()
6730
+ npu: z26.looseObject({ type: z26.enum(["apple-ane", "intel-npu", "amd-xdna"]) }).nullable()
7523
6731
  });
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()
6732
+ var InferenceDevicesSchema3 = z26.looseObject({
6733
+ reachable: z26.boolean(),
6734
+ devices: z26.array(
6735
+ z26.looseObject({
6736
+ key: z26.string(),
6737
+ backend: z26.string(),
6738
+ available: z26.boolean(),
6739
+ enabled: z26.boolean()
7532
6740
  })
7533
6741
  )
7534
6742
  });
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()
6743
+ var RunStatusSchema = z26.looseObject({
6744
+ run: z26.looseObject({
6745
+ runId: z26.string(),
6746
+ nodeId: z26.string(),
6747
+ state: z26.string(),
6748
+ endReason: z26.string().nullable().optional(),
6749
+ error: z26.string().nullable().optional()
7542
6750
  }).nullable()
7543
6751
  });
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()
6752
+ var ModelListSchema = z26.array(
6753
+ z26.looseObject({
6754
+ id: z26.string(),
6755
+ formats: z26.record(z26.string(), z26.looseObject({ downloaded: z26.boolean().optional() })).optional()
7548
6756
  })
7549
6757
  );
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()
6758
+ var PipelineTestSchema = z26.looseObject({
6759
+ kind: z26.string().optional(),
6760
+ detections: z26.array(
6761
+ z26.looseObject({
6762
+ debug: z26.looseObject({
6763
+ stepLatencyMs: z26.record(z26.string(), z26.number()).optional()
7556
6764
  }).optional()
7557
6765
  })
7558
6766
  ).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()
6767
+ discarded: z26.array(z26.unknown()).optional(),
6768
+ debug: z26.looseObject({
6769
+ stepTimings: z26.array(
6770
+ z26.looseObject({
6771
+ source: z26.string().optional(),
6772
+ ms: z26.number().optional()
7565
6773
  })
7566
6774
  ).optional(),
7567
- predictMs: z30.number().optional(),
7568
- totalInferenceMs: z30.number().optional()
6775
+ predictMs: z26.number().optional(),
6776
+ totalInferenceMs: z26.number().optional()
7569
6777
  }).optional()
7570
6778
  });
7571
6779
  var FORMAT_BY_BACKEND = {
@@ -7852,15 +7060,15 @@ async function supportBenchmarkRun(ctx, deps, args, confirmToken) {
7852
7060
  }
7853
7061
  return runGated5(ctx.gate, benchmarkRunSpec(outcome.connection.client, deps), args, confirmToken);
7854
7062
  }
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()
7063
+ var ResultsSchema = z26.array(
7064
+ z26.looseObject({
7065
+ nodeId: z26.string(),
7066
+ cell: z26.looseObject({
7067
+ phase: z26.string(),
7068
+ deviceKey: z26.string(),
7069
+ modelId: z26.string(),
7070
+ p50Ms: z26.number().optional(),
7071
+ fps: z26.number().optional()
7864
7072
  })
7865
7073
  })
7866
7074
  );
@@ -7913,20 +7121,20 @@ async function supportBenchmarkStatus(ctx, args) {
7913
7121
  }
7914
7122
 
7915
7123
  // 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()
7124
+ import { z as z27 } from "zod";
7125
+ var DeviceSchema2 = z27.looseObject({
7126
+ id: z27.number(),
7127
+ name: z27.string(),
7128
+ addonId: z27.string().optional(),
7129
+ disabled: z27.boolean().optional(),
7130
+ online: z27.boolean().optional()
7923
7131
  }).nullable();
7924
7132
  var GENERIC = /onvif|rtsp/i;
7925
- function words(text15) {
7926
- return text15.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 3);
7133
+ function words(text2) {
7134
+ return text2.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 3);
7927
7135
  }
7928
- function isGeneric(parts2) {
7929
- return parts2.some((p) => GENERIC.test(p));
7136
+ function isGeneric(parts) {
7137
+ return parts.some((p) => GENERIC.test(p));
7930
7138
  }
7931
7139
  function installedPick(p) {
7932
7140
  return {
@@ -7947,9 +7155,9 @@ function availablePick(p) {
7947
7155
  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
7156
  function matchIn(pools, tokens) {
7949
7157
  if (tokens.length === 0) return null;
7950
- const hit = (parts2) => {
7951
- if (isGeneric(parts2)) return false;
7952
- const joined = parts2.join(" ").toLowerCase();
7158
+ const hit = (parts) => {
7159
+ if (isGeneric(parts)) return false;
7160
+ const joined = parts.join(" ").toLowerCase();
7953
7161
  return tokens.some((t) => joined.includes(t));
7954
7162
  };
7955
7163
  const installed = pools.installed.find(
@@ -8085,10 +7293,10 @@ async function supportDiagnoseCamera(ctx, args) {
8085
7293
  }
8086
7294
 
8087
7295
  // src/mcp/support-deps.ts
8088
- import { execFile as execFile2 } from "child_process";
7296
+ import { execFile } from "child_process";
8089
7297
  var RUN_TIMEOUT_MS = 6e4;
8090
7298
  var spawnRunner = (command, args) => new Promise((resolve) => {
8091
- execFile2(
7299
+ execFile(
8092
7300
  command,
8093
7301
  [...args],
8094
7302
  { timeout: RUN_TIMEOUT_MS, maxBuffer: 1e6 },
@@ -8120,7 +7328,7 @@ import {
8120
7328
  digestArgs,
8121
7329
  runGated as runGated6
8122
7330
  } from "@camstack/develop";
8123
- import { z as z32 } from "zod";
7331
+ import { z as z28 } from "zod";
8124
7332
 
8125
7333
  // src/mcp/support-scrub.ts
8126
7334
  import { redactToolArgs, scrubSecrets } from "@camstack/develop";
@@ -8129,10 +7337,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
7337
  var MAC = /\b(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}\b/g;
8130
7338
  var HOME_DIR = /(\/Users|\/home)\/[^/\s'"]+/g;
8131
7339
  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;
7340
+ const parts = ip.split(".").map((p) => Number(p));
7341
+ if (parts.length !== 4) return null;
7342
+ if (parts.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) return null;
7343
+ return parts;
8136
7344
  }
8137
7345
  function maskIpv4(ip) {
8138
7346
  const o = octets(ip);
@@ -8173,16 +7381,16 @@ var INFERENCE_PACKAGE = "@camstack/addon-pipeline";
8173
7381
  var TITLE_MAX = 140;
8174
7382
  var DIAGNOSIS_MAX = 4e3;
8175
7383
  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() })
7384
+ var DraftKindSchema = z28.enum(DRAFT_KINDS);
7385
+ var BenchmarkCellsSchema = z28.record(
7386
+ z28.string().regex(/^[a-z0-9][a-z0-9._-]{1,63}$/),
7387
+ z28.object({ p50Ms: z28.number().positive(), fps: z28.number().positive() })
8180
7388
  );
8181
7389
  var PackageNameRe = /^(@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
8182
7390
  var GithubRepoRe = /github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?(?:[/#?].*)?$/;
8183
7391
  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()
7392
+ var RegistryDocSchema = z28.looseObject({
7393
+ repository: z28.union([z28.string(), z28.looseObject({ url: z28.string() })]).optional()
8186
7394
  });
8187
7395
  function githubRepoOf(value) {
8188
7396
  const url = GithubRepoRe.exec(value);
@@ -8224,7 +7432,7 @@ async function resolveRepo(ctx, packageName) {
8224
7432
  reason: null
8225
7433
  };
8226
7434
  }
8227
- var ServerStatusSchema2 = z32.looseObject({ runningVersion: z32.string() });
7435
+ var ServerStatusSchema2 = z28.looseObject({ runningVersion: z28.string() });
8228
7436
  async function serverVersion(client, nodeId) {
8229
7437
  const status = await readQuery(
8230
7438
  client,
@@ -8339,11 +7547,11 @@ async function buildDraft(ctx, args, heading) {
8339
7547
  ].filter((s) => s !== "");
8340
7548
  return { target, title: cleanTitle(args.title), body: sections.join("\n\n") };
8341
7549
  }
8342
- function shellQuote2(value) {
7550
+ function shellQuote(value) {
8343
7551
  return `'${value.replace(/'/g, `'\\''`)}'`;
8344
7552
  }
8345
7553
  function issueCommand(draft, bodyFile) {
8346
- return `gh issue create --repo ${draft.target.repo} --title ${shellQuote2(draft.title)} --body-file ${bodyFile}`;
7554
+ return `gh issue create --repo ${draft.target.repo} --title ${shellQuote(draft.title)} --body-file ${bodyFile}`;
8347
7555
  }
8348
7556
  var ISSUE_BODY_FILE = "./camstack-issue.md";
8349
7557
  async function issueDraft(ctx, args) {
@@ -8426,8 +7634,8 @@ var BENCHMARK_FILES = [
8426
7634
  "docs/benchmarks/detector-latency.json",
8427
7635
  "packages/types/src/pipeline/auto-tune/detector-performance.ts"
8428
7636
  ];
8429
- function slug(text15) {
8430
- const s = text15.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
7637
+ function slug(text2) {
7638
+ const s = text2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
8431
7639
  return s === "" ? "change" : s;
8432
7640
  }
8433
7641
  function prProblem(args) {
@@ -8450,7 +7658,7 @@ function planFor(args, draft) {
8450
7658
  `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
7659
  "npx vitest run packages/types/src/pipeline/auto-tune/__tests__/detector-performance.spec.ts (it fails until the two files agree)."
8452
7660
  ] : ["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}`)}`,
7661
+ `git commit -am ${shellQuote(benchmark ? `docs: measured detector latency for ${args.column ?? ""}` : `fix: ${draft.title}`)}`,
8454
7662
  `git push -u origin ${branch}`,
8455
7663
  `Save the body below as ${PR_BODY_FILE}, then run the command.`
8456
7664
  ];
@@ -8463,7 +7671,7 @@ function planFor(args, draft) {
8463
7671
  branch,
8464
7672
  files,
8465
7673
  steps,
8466
- command: `gh pr create --repo ${draft.target.repo} --head <your-github-user>:${branch} --title ${shellQuote2(draft.title)} --body-file ${PR_BODY_FILE}`,
7674
+ command: `gh pr create --repo ${draft.target.repo} --head <your-github-user>:${branch} --title ${shellQuote(draft.title)} --body-file ${PR_BODY_FILE}`,
8467
7675
  note: "This tool pushes no code and calls no GitHub API; you run these steps with your own git and gh."
8468
7676
  };
8469
7677
  }
@@ -8490,20 +7698,20 @@ async function supportDraftPr(ctx, args, confirmToken) {
8490
7698
  }
8491
7699
 
8492
7700
  // src/mcp/support-tools.ts
8493
- var ModelId = z33.string().regex(/^[a-z0-9][a-z0-9._-]{1,63}$/);
7701
+ var ModelId = z29.string().regex(/^[a-z0-9][a-z0-9._-]{1,63}$/);
8494
7702
  var DraftShape = {
8495
7703
  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(),
7704
+ title: z29.string().min(3).max(300),
7705
+ summary: z29.string().min(3).max(4e3),
7706
+ nodeId: z29.string().max(100).optional(),
8499
7707
  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(),
7708
+ brand: z29.string().max(100).optional(),
7709
+ model: z29.string().max(100).optional(),
7710
+ deviceId: z29.number().int().optional(),
7711
+ packageName: z29.string().max(214).optional(),
7712
+ column: z29.string().max(100).optional(),
8505
7713
  cells: BenchmarkCellsSchema.optional(),
8506
- confirm_token: z33.string().optional()
7714
+ confirm_token: z29.string().optional()
8507
7715
  };
8508
7716
  function draftArgs(input) {
8509
7717
  return {
@@ -8524,20 +7732,20 @@ function registerSupportTools(server, ctx, deps = defaultSupportDeps) {
8524
7732
  server.tool(
8525
7733
  "support_benchmark_status",
8526
7734
  "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 } : {}))
7735
+ { nodeId: z29.string().max(100).optional() },
7736
+ async ({ nodeId }) => text(await supportBenchmarkStatus(ctx, nodeId !== void 0 ? { nodeId } : {}))
8529
7737
  );
8530
7738
  server.tool(
8531
7739
  BENCHMARK_RUN_TOOL,
8532
7740
  "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
7741
  {
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()
7742
+ nodeId: z29.string().min(1).max(100),
7743
+ deviceKey: z29.string().min(1).max(60),
7744
+ models: z29.array(ModelId).min(1).max(MAX_MODELS).optional(),
7745
+ warmRuns: z29.number().int().min(1).max(MAX_WARM_RUNS).optional(),
7746
+ confirm_token: z29.string().optional()
8539
7747
  },
8540
- async ({ nodeId, deviceKey, models, warmRuns, confirm_token }) => text14(
7748
+ async ({ nodeId, deviceKey, models, warmRuns, confirm_token }) => text(
8541
7749
  await supportBenchmarkRun(
8542
7750
  ctx,
8543
7751
  deps,
@@ -8554,18 +7762,18 @@ function registerSupportTools(server, ctx, deps = defaultSupportDeps) {
8554
7762
  server.tool(
8555
7763
  "support_diagnose_accelerator",
8556
7764
  "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 }))
7765
+ { nodeId: z29.string().min(1).max(100), expected: ExpectedAcceleratorSchema },
7766
+ async ({ nodeId, expected }) => text(await supportDiagnoseAccelerator(ctx, { nodeId, expected }))
8559
7767
  );
8560
7768
  server.tool(
8561
7769
  "support_diagnose_camera",
8562
7770
  "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
7771
  {
8564
- brand: z33.string().max(100).optional(),
8565
- model: z33.string().max(100).optional(),
8566
- deviceId: z33.number().int().optional()
7772
+ brand: z29.string().max(100).optional(),
7773
+ model: z29.string().max(100).optional(),
7774
+ deviceId: z29.number().int().optional()
8567
7775
  },
8568
- async ({ brand, model, deviceId }) => text14(
7776
+ async ({ brand, model, deviceId }) => text(
8569
7777
  await supportDiagnoseCamera(ctx, {
8570
7778
  ...brand !== void 0 ? { brand } : {},
8571
7779
  ...model !== void 0 ? { model } : {},
@@ -8577,13 +7785,13 @@ function registerSupportTools(server, ctx, deps = defaultSupportDeps) {
8577
7785
  DRAFT_ISSUE_TOOL,
8578
7786
  "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
7787
  DraftShape,
8580
- async (input) => text14(await supportDraftIssue(ctx, deps, draftArgs(input), input.confirm_token))
7788
+ async (input) => text(await supportDraftIssue(ctx, deps, draftArgs(input), input.confirm_token))
8581
7789
  );
8582
7790
  server.tool(
8583
7791
  DRAFT_PR_TOOL,
8584
7792
  '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
7793
  DraftShape,
8586
- async (input) => text14(await supportDraftPr(ctx, draftArgs(input), input.confirm_token))
7794
+ async (input) => text(await supportDraftPr(ctx, draftArgs(input), input.confirm_token))
8587
7795
  );
8588
7796
  }
8589
7797