camstack 1.2.212 → 1.2.213
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/{chunk-MLWXP2SI.js → chunk-MJJWVAEM.js} +941 -168
- package/dist/cli.js +4 -4
- package/dist/{launcher-EACOVUIT.js → launcher-6TBFW332.js} +3770 -1105
- package/dist/{server-2GOCBDEY.js → server-EFXGQ7FD.js} +1085 -589
- package/package.json +1 -1
|
@@ -11,13 +11,15 @@ import {
|
|
|
11
11
|
buildViewerLink,
|
|
12
12
|
capUnitResolver,
|
|
13
13
|
classifyInferenceHardware,
|
|
14
|
+
convertLegacyWhen,
|
|
15
|
+
countWhenLeaves,
|
|
14
16
|
findHubs,
|
|
15
17
|
generateAutomationBlock,
|
|
16
18
|
isReadingQuantity,
|
|
17
19
|
prepareRecipeUnits,
|
|
18
20
|
unitsForQuantity,
|
|
19
21
|
validateRecipeBounds
|
|
20
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-MJJWVAEM.js";
|
|
21
23
|
import "./chunk-LMMQX4CK.js";
|
|
22
24
|
|
|
23
25
|
// src/mcp/server.ts
|
|
@@ -28,9 +30,9 @@ import { MutationGate, tryConnectHub } from "@camstack/develop";
|
|
|
28
30
|
// src/mcp/configure-tools.ts
|
|
29
31
|
import {
|
|
30
32
|
runGated as runGated4,
|
|
31
|
-
text as
|
|
33
|
+
text as text13
|
|
32
34
|
} from "@camstack/develop";
|
|
33
|
-
import { z as
|
|
35
|
+
import { z as z28 } from "zod";
|
|
34
36
|
|
|
35
37
|
// src/mcp/configure-automations.ts
|
|
36
38
|
import { z as z4 } from "zod";
|
|
@@ -987,7 +989,7 @@ function toActions(actions) {
|
|
|
987
989
|
function quantityNotes(recipe) {
|
|
988
990
|
const notes = [];
|
|
989
991
|
const walk = (c, path) => {
|
|
990
|
-
if (c.kind === "
|
|
992
|
+
if (c.kind === "device-is" && typeof c.value === "number" && c.unit !== void 0) {
|
|
991
993
|
notes.push({
|
|
992
994
|
where: `${path} ${c.cap}.${c.fieldPath}`,
|
|
993
995
|
value: c.value,
|
|
@@ -997,7 +999,7 @@ function quantityNotes(recipe) {
|
|
|
997
999
|
if (c.kind === "not") walk(c.child, path);
|
|
998
1000
|
if (c.kind === "all" || c.kind === "any") c.children.forEach((child) => walk(child, path));
|
|
999
1001
|
};
|
|
1000
|
-
|
|
1002
|
+
walk(recipe.when, "condition");
|
|
1001
1003
|
recipe.actions.forEach((a, i) => {
|
|
1002
1004
|
if (a.kind !== "cap" || a.args === void 0 || a.argUnits === void 0) return;
|
|
1003
1005
|
for (const [arg, unit] of Object.entries(a.argUnits)) {
|
|
@@ -1085,8 +1087,7 @@ async function interviewAutomation(client, answers) {
|
|
|
1085
1087
|
const conditions = answers.conditions ?? [];
|
|
1086
1088
|
const actions = answers.actions ?? [];
|
|
1087
1089
|
const draft = {
|
|
1088
|
-
triggers: [outcome.trigger],
|
|
1089
|
-
...toCondition(conditions) !== void 0 ? { conditions: toCondition(conditions) } : {},
|
|
1090
|
+
when: convertLegacyWhen({ triggers: [outcome.trigger], conditions: toCondition(conditions) }),
|
|
1090
1091
|
actions: toActions(actions),
|
|
1091
1092
|
...answers.cooldownMinutes !== void 0 ? {
|
|
1092
1093
|
runMode: {
|
|
@@ -1184,7 +1185,7 @@ function saveAutomationSpec(client) {
|
|
|
1184
1185
|
runsOn: "the hub",
|
|
1185
1186
|
enabled: args.enabled,
|
|
1186
1187
|
effect: args.enabled ? "it starts running as soon as it is saved" : "it is saved switched off",
|
|
1187
|
-
|
|
1188
|
+
rows: countWhenLeaves(finished.recipe.when),
|
|
1188
1189
|
steps: finished.recipe.actions.length,
|
|
1189
1190
|
quantities: quantityNotes(finished.recipe),
|
|
1190
1191
|
generatedBytes: code.length
|
|
@@ -1217,7 +1218,7 @@ var GATE_HOW = "Two steps: call without confirm_token for a preview and a one-sh
|
|
|
1217
1218
|
function registerAutomationTools(server, ctx) {
|
|
1218
1219
|
server.tool(
|
|
1219
1220
|
"configure_automation_questionnaire",
|
|
1220
|
-
"Interview for a new automation (a recipe:
|
|
1221
|
+
"Interview for a new automation (a recipe: ONE `when` tree \u2014 events that start it, states that must hold \u2014 and the actions). 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 recipe. A number on a quantity field is { value, unit }. Writes nothing.",
|
|
1221
1222
|
{ answers: AutomationAnswersSchema },
|
|
1222
1223
|
async ({ answers }) => text(await automationQuestionnaire(ctx, answers))
|
|
1223
1224
|
);
|
|
@@ -2345,33 +2346,527 @@ function registerExportTools(server, ctx) {
|
|
|
2345
2346
|
);
|
|
2346
2347
|
}
|
|
2347
2348
|
|
|
2348
|
-
// src/mcp/configure-
|
|
2349
|
-
import { z as z9 } from "zod";
|
|
2349
|
+
// src/mcp/configure-oidc.ts
|
|
2350
2350
|
import { text as text6 } from "@camstack/develop";
|
|
2351
|
+
import { z as z10 } from "zod";
|
|
2352
|
+
|
|
2353
|
+
// src/mcp/configure-oidc-providers.ts
|
|
2354
|
+
import { z as z9 } from "zod";
|
|
2355
|
+
var OidcProviderSchema = z9.enum([
|
|
2356
|
+
"authentik",
|
|
2357
|
+
"pocket-id",
|
|
2358
|
+
"authelia",
|
|
2359
|
+
"keycloak",
|
|
2360
|
+
"zitadel",
|
|
2361
|
+
"kanidm",
|
|
2362
|
+
"google",
|
|
2363
|
+
"microsoft-entra",
|
|
2364
|
+
"auth0",
|
|
2365
|
+
"okta",
|
|
2366
|
+
"generic"
|
|
2367
|
+
]);
|
|
2368
|
+
var PIECE_QUESTION = {
|
|
2369
|
+
host: "host: the provider host name, with or without https:// (no path)",
|
|
2370
|
+
application_slug: "application_slug: the application slug (authentik) or client name (kanidm)",
|
|
2371
|
+
realm: "realm: the Keycloak realm name",
|
|
2372
|
+
tenant_id: "tenant_id: the Entra directory (tenant) id, a GUID or a verified domain"
|
|
2373
|
+
};
|
|
2374
|
+
function pieceQuestion(piece) {
|
|
2375
|
+
return PIECE_QUESTION[piece];
|
|
2376
|
+
}
|
|
2377
|
+
function hostOrigin(host) {
|
|
2378
|
+
const trimmed = host.trim().replace(/\/+$/, "");
|
|
2379
|
+
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
2380
|
+
}
|
|
2381
|
+
var COMMON_STEPS = [
|
|
2382
|
+
"Create the client as an OpenID Connect / OAuth2 web application, authorization-code flow.",
|
|
2383
|
+
"Paste the redirect URI shown below as the ONLY allowed redirect URI.",
|
|
2384
|
+
"Allow the scopes listed below."
|
|
2385
|
+
];
|
|
2386
|
+
var OIDC_PROVIDERS = {
|
|
2387
|
+
authentik: {
|
|
2388
|
+
label: "authentik",
|
|
2389
|
+
pieces: ["host", "application_slug"],
|
|
2390
|
+
issuerFormat: "https://<host>/application/o/<application_slug>/",
|
|
2391
|
+
scopes: "openid profile email",
|
|
2392
|
+
publicPkce: true,
|
|
2393
|
+
usernameClaim: "preferred_username",
|
|
2394
|
+
whereToCreate: "Admin interface > Applications > Applications > Create with Provider (OAuth2/OpenID)",
|
|
2395
|
+
steps: [
|
|
2396
|
+
"Create an application with an OAuth2/OpenID provider; its slug is the application_slug.",
|
|
2397
|
+
"Set the client type to Public for PKCE without a secret, or Confidential.",
|
|
2398
|
+
"Add the redirect URI below as a Strict redirect URI."
|
|
2399
|
+
],
|
|
2400
|
+
build: (p) => `${hostOrigin(p.host)}/application/o/${p.application_slug}/`
|
|
2401
|
+
},
|
|
2402
|
+
"pocket-id": {
|
|
2403
|
+
label: "Pocket ID",
|
|
2404
|
+
pieces: ["host"],
|
|
2405
|
+
issuerFormat: "https://<host>",
|
|
2406
|
+
scopes: "openid profile email",
|
|
2407
|
+
publicPkce: true,
|
|
2408
|
+
usernameClaim: "preferred_username",
|
|
2409
|
+
whereToCreate: "Settings > Administration > OIDC Clients > Add OIDC Client",
|
|
2410
|
+
steps: [
|
|
2411
|
+
"Add an OIDC client and paste the redirect URI below as its callback URL.",
|
|
2412
|
+
'Switch on "Public Client" for PKCE without a secret; leave it off for a confidential client.',
|
|
2413
|
+
"Copy the client ID."
|
|
2414
|
+
],
|
|
2415
|
+
build: (p) => hostOrigin(p.host)
|
|
2416
|
+
},
|
|
2417
|
+
authelia: {
|
|
2418
|
+
label: "Authelia",
|
|
2419
|
+
pieces: ["host"],
|
|
2420
|
+
issuerFormat: "https://<host>",
|
|
2421
|
+
scopes: "openid profile email",
|
|
2422
|
+
publicPkce: true,
|
|
2423
|
+
usernameClaim: "preferred_username",
|
|
2424
|
+
whereToCreate: "configuration.yml > identity_providers.oidc.clients (a file edit, then a restart)",
|
|
2425
|
+
steps: [
|
|
2426
|
+
"Add a client with its redirect URI below and `authorization_policy` of your choice.",
|
|
2427
|
+
"For PKCE without a secret set `public: true` and `require_pkce: true`.",
|
|
2428
|
+
"Set `token_endpoint_auth_method: client_secret_post` for a confidential client."
|
|
2429
|
+
],
|
|
2430
|
+
build: (p) => hostOrigin(p.host)
|
|
2431
|
+
},
|
|
2432
|
+
keycloak: {
|
|
2433
|
+
label: "Keycloak",
|
|
2434
|
+
pieces: ["host", "realm"],
|
|
2435
|
+
issuerFormat: "https://<host>/realms/<realm>",
|
|
2436
|
+
scopes: "openid profile email",
|
|
2437
|
+
publicPkce: true,
|
|
2438
|
+
usernameClaim: "preferred_username",
|
|
2439
|
+
whereToCreate: "Admin console > the realm > Clients > Create client",
|
|
2440
|
+
steps: [
|
|
2441
|
+
'Client type OpenID Connect; switch "Client authentication" on for confidential, off for public.',
|
|
2442
|
+
'Under Advanced, set "Proof Key for Code Exchange Code Challenge Method" to S256.',
|
|
2443
|
+
"Add the redirect URI below as a valid redirect URI."
|
|
2444
|
+
],
|
|
2445
|
+
build: (p) => `${hostOrigin(p.host)}/realms/${p.realm}`
|
|
2446
|
+
},
|
|
2447
|
+
zitadel: {
|
|
2448
|
+
label: "ZITADEL",
|
|
2449
|
+
pieces: ["host"],
|
|
2450
|
+
issuerFormat: "https://<host>",
|
|
2451
|
+
scopes: "openid profile email",
|
|
2452
|
+
publicPkce: true,
|
|
2453
|
+
usernameClaim: "preferred_username",
|
|
2454
|
+
whereToCreate: "Console > Project > Applications > New > Web",
|
|
2455
|
+
steps: [
|
|
2456
|
+
"Choose authentication method PKCE (public) or Basic/Post (confidential).",
|
|
2457
|
+
"Add the redirect URI below.",
|
|
2458
|
+
"Copy the client ID."
|
|
2459
|
+
],
|
|
2460
|
+
build: (p) => hostOrigin(p.host)
|
|
2461
|
+
},
|
|
2462
|
+
kanidm: {
|
|
2463
|
+
label: "Kanidm",
|
|
2464
|
+
pieces: ["host", "application_slug"],
|
|
2465
|
+
issuerFormat: "https://<host>/oauth2/openid/<application_slug>",
|
|
2466
|
+
scopes: "openid profile email",
|
|
2467
|
+
publicPkce: true,
|
|
2468
|
+
usernameClaim: "preferred_username",
|
|
2469
|
+
whereToCreate: "`kanidm system oauth2 create(-public) <name> <display> <origin>` on the server",
|
|
2470
|
+
steps: [
|
|
2471
|
+
"Use `create-public` for PKCE without a secret, `create` for a confidential client.",
|
|
2472
|
+
"Add the redirect URI below with `add-redirect-url`.",
|
|
2473
|
+
"Grant scopes with `update-scope-map <name> <group> openid profile email`.",
|
|
2474
|
+
"The application_slug is the client name you created."
|
|
2475
|
+
],
|
|
2476
|
+
build: (p) => `${hostOrigin(p.host)}/oauth2/openid/${p.application_slug}`
|
|
2477
|
+
},
|
|
2478
|
+
google: {
|
|
2479
|
+
label: "Google",
|
|
2480
|
+
pieces: [],
|
|
2481
|
+
issuerFormat: "https://accounts.google.com",
|
|
2482
|
+
scopes: "openid profile email",
|
|
2483
|
+
publicPkce: false,
|
|
2484
|
+
usernameClaim: "email",
|
|
2485
|
+
whereToCreate: "Google Cloud console > APIs & Services > Credentials > Create credentials > OAuth client ID (Web application)",
|
|
2486
|
+
steps: [
|
|
2487
|
+
'Choose "Web application"; Google issues a client secret for it, so the client is confidential.',
|
|
2488
|
+
"Add the redirect URI below as an authorized redirect URI (it must be a public https host)."
|
|
2489
|
+
],
|
|
2490
|
+
build: () => "https://accounts.google.com"
|
|
2491
|
+
},
|
|
2492
|
+
"microsoft-entra": {
|
|
2493
|
+
label: "Microsoft Entra ID",
|
|
2494
|
+
pieces: ["tenant_id"],
|
|
2495
|
+
issuerFormat: "https://login.microsoftonline.com/<tenant_id>/v2.0",
|
|
2496
|
+
scopes: "openid profile email",
|
|
2497
|
+
publicPkce: true,
|
|
2498
|
+
usernameClaim: "preferred_username",
|
|
2499
|
+
whereToCreate: "Entra admin center > App registrations > New registration",
|
|
2500
|
+
steps: [
|
|
2501
|
+
"Register a Web app (confidential, with a client secret) or a Single-page application (public, PKCE).",
|
|
2502
|
+
"Add the redirect URI below under Authentication.",
|
|
2503
|
+
"Copy the Application (client) ID and the Directory (tenant) ID."
|
|
2504
|
+
],
|
|
2505
|
+
build: (p) => `https://login.microsoftonline.com/${p.tenant_id}/v2.0`
|
|
2506
|
+
},
|
|
2507
|
+
auth0: {
|
|
2508
|
+
label: "Auth0",
|
|
2509
|
+
pieces: ["host"],
|
|
2510
|
+
issuerFormat: "https://<host>/",
|
|
2511
|
+
scopes: "openid profile email",
|
|
2512
|
+
publicPkce: true,
|
|
2513
|
+
usernameClaim: "email",
|
|
2514
|
+
whereToCreate: "Dashboard > Applications > Applications > Create Application",
|
|
2515
|
+
steps: [
|
|
2516
|
+
"Regular Web Application for confidential, Single Page Application for public (PKCE).",
|
|
2517
|
+
"Add the redirect URI below under Allowed Callback URLs.",
|
|
2518
|
+
"host is the tenant domain, for example example.eu.auth0.com."
|
|
2519
|
+
],
|
|
2520
|
+
build: (p) => `${hostOrigin(p.host)}/`
|
|
2521
|
+
},
|
|
2522
|
+
okta: {
|
|
2523
|
+
label: "Okta",
|
|
2524
|
+
pieces: ["host"],
|
|
2525
|
+
issuerFormat: "https://<host>/oauth2/default",
|
|
2526
|
+
scopes: "openid profile email",
|
|
2527
|
+
publicPkce: true,
|
|
2528
|
+
usernameClaim: "preferred_username",
|
|
2529
|
+
whereToCreate: "Admin console > Applications > Applications > Create App Integration > OIDC",
|
|
2530
|
+
steps: [
|
|
2531
|
+
"Web Application for confidential, Single-Page Application for public (PKCE).",
|
|
2532
|
+
"Add the redirect URI below as a sign-in redirect URI.",
|
|
2533
|
+
"host is your Okta domain; the default authorization server is used."
|
|
2534
|
+
],
|
|
2535
|
+
build: (p) => `${hostOrigin(p.host)}/oauth2/default`
|
|
2536
|
+
},
|
|
2537
|
+
generic: {
|
|
2538
|
+
label: "Any OpenID Connect provider",
|
|
2539
|
+
pieces: [],
|
|
2540
|
+
issuerFormat: "the `issuer` value of the provider's discovery document",
|
|
2541
|
+
scopes: "openid profile email",
|
|
2542
|
+
publicPkce: true,
|
|
2543
|
+
usernameClaim: "preferred_username",
|
|
2544
|
+
whereToCreate: "the provider's admin interface (create an OIDC web client)",
|
|
2545
|
+
steps: COMMON_STEPS,
|
|
2546
|
+
note: "Pass `issuer` exactly as the provider's /.well-known/openid-configuration reports it.",
|
|
2547
|
+
build: () => ""
|
|
2548
|
+
}
|
|
2549
|
+
};
|
|
2550
|
+
function resolveIssuer(provider, explicit, pieces) {
|
|
2551
|
+
if (explicit !== void 0 && explicit.trim() !== "") {
|
|
2552
|
+
return { issuer: explicit.trim(), missing: [] };
|
|
2553
|
+
}
|
|
2554
|
+
const spec = OIDC_PROVIDERS[provider];
|
|
2555
|
+
const missing = spec.pieces.filter((k) => (pieces[k] ?? "").trim() === "");
|
|
2556
|
+
if (missing.length > 0 || provider === "generic") return { issuer: null, missing };
|
|
2557
|
+
return {
|
|
2558
|
+
issuer: spec.build({
|
|
2559
|
+
host: pieces.host ?? "",
|
|
2560
|
+
application_slug: pieces.application_slug ?? "",
|
|
2561
|
+
realm: pieces.realm ?? "",
|
|
2562
|
+
tenant_id: pieces.tenant_id ?? ""
|
|
2563
|
+
}),
|
|
2564
|
+
missing: []
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
// src/mcp/configure-oidc.ts
|
|
2569
|
+
var OIDC_TOOL = "configure_oidc";
|
|
2570
|
+
var OIDC_ADMIN_ROUTE = "/system/identity?tab=authentication";
|
|
2571
|
+
var DISCOVERY_TIMEOUT_MS = 5e3;
|
|
2572
|
+
var ClientTypeSchema = z10.enum(["public", "confidential"]);
|
|
2573
|
+
var AddressesSchema3 = z10.looseObject({
|
|
2574
|
+
roles: z10.looseObject({
|
|
2575
|
+
publicEndpoint: z10.looseObject({ resolved: z10.string().nullish() }).optional(),
|
|
2576
|
+
appConnections: z10.looseObject({ resolved: z10.array(z10.string()) }).optional()
|
|
2577
|
+
})
|
|
2578
|
+
});
|
|
2579
|
+
var LOOPBACK2 = /^(localhost|127\.|\[?::1\]?$|0\.0\.0\.0)/i;
|
|
2580
|
+
function isLoopback(url) {
|
|
2581
|
+
try {
|
|
2582
|
+
return LOOPBACK2.test(new URL(url).hostname);
|
|
2583
|
+
} catch {
|
|
2584
|
+
return true;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
async function resolveHubBase(client) {
|
|
2588
|
+
const read = await readQuery(client, "localNetwork.getAddresses", {}, AddressesSchema3);
|
|
2589
|
+
if (!read.ok) return { base: null, reason: read.reason };
|
|
2590
|
+
const pub = read.value.roles.publicEndpoint?.resolved;
|
|
2591
|
+
if (typeof pub === "string" && pub !== "" && !isLoopback(pub)) {
|
|
2592
|
+
return { base: pub.replace(/\/+$/, ""), source: "publicEndpoint" };
|
|
2593
|
+
}
|
|
2594
|
+
const app = read.value.roles.appConnections?.resolved.find((u) => !isLoopback(u));
|
|
2595
|
+
if (app !== void 0) return { base: app.replace(/\/+$/, ""), source: "appConnections" };
|
|
2596
|
+
return {
|
|
2597
|
+
base: null,
|
|
2598
|
+
reason: "no non-loopback public endpoint or app-connections address is set (Network page)"
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
function callbackPath(connectionId) {
|
|
2602
|
+
return `/addon/auth-oidc/${connectionId}/callback`;
|
|
2603
|
+
}
|
|
2604
|
+
var DiscoverySchema = z10.looseObject({
|
|
2605
|
+
issuer: z10.string().optional(),
|
|
2606
|
+
authorization_endpoint: z10.string().optional(),
|
|
2607
|
+
token_endpoint: z10.string().optional(),
|
|
2608
|
+
jwks_uri: z10.string().optional(),
|
|
2609
|
+
code_challenge_methods_supported: z10.array(z10.string()).optional(),
|
|
2610
|
+
token_endpoint_auth_methods_supported: z10.array(z10.string()).optional()
|
|
2611
|
+
});
|
|
2612
|
+
var trimSlash = (s) => s.replace(/\/+$/, "");
|
|
2613
|
+
async function checkDiscovery(fetchFn, issuer, clientType) {
|
|
2614
|
+
const url = `${trimSlash(issuer)}/.well-known/openid-configuration`;
|
|
2615
|
+
let body;
|
|
2616
|
+
try {
|
|
2617
|
+
const res = await fetchFn(url, { signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS) });
|
|
2618
|
+
if (!res.ok) {
|
|
2619
|
+
return {
|
|
2620
|
+
checked: false,
|
|
2621
|
+
url,
|
|
2622
|
+
warnings: [`discovery answered ${res.status}: check the issuer URL`]
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
body = await res.json();
|
|
2626
|
+
} catch (err) {
|
|
2627
|
+
return {
|
|
2628
|
+
checked: false,
|
|
2629
|
+
url,
|
|
2630
|
+
warnings: [`discovery could not be fetched from this machine (${reasonOf(err)})`]
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
const parsed = DiscoverySchema.safeParse(body);
|
|
2634
|
+
if (!parsed.success) {
|
|
2635
|
+
return {
|
|
2636
|
+
checked: false,
|
|
2637
|
+
url,
|
|
2638
|
+
warnings: ["discovery answered something that is not a JSON document"]
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
const doc = parsed.data;
|
|
2642
|
+
const warnings = [];
|
|
2643
|
+
const issuerMatches = doc.issuer !== void 0 && trimSlash(doc.issuer) === trimSlash(issuer);
|
|
2644
|
+
if (!issuerMatches) {
|
|
2645
|
+
warnings.push(
|
|
2646
|
+
`the provider reports issuer "${doc.issuer ?? "(none)"}", which differs from "${issuer}"`
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
if (doc.authorization_endpoint === void 0 || doc.token_endpoint === void 0) {
|
|
2650
|
+
warnings.push("the discovery document lacks an authorization or token endpoint");
|
|
2651
|
+
}
|
|
2652
|
+
const s256 = doc.code_challenge_methods_supported?.includes("S256") === true;
|
|
2653
|
+
if (!s256)
|
|
2654
|
+
warnings.push("code_challenge_methods_supported does not include S256 (PKCE is always used)");
|
|
2655
|
+
const post = doc.token_endpoint_auth_methods_supported?.includes("client_secret_post") === true;
|
|
2656
|
+
if (clientType === "confidential" && !post) {
|
|
2657
|
+
warnings.push(
|
|
2658
|
+
"token_endpoint_auth_methods_supported does not include client_secret_post, which CamStack uses for a confidential client"
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
return {
|
|
2662
|
+
checked: true,
|
|
2663
|
+
url,
|
|
2664
|
+
issuerMatches,
|
|
2665
|
+
...doc.authorization_endpoint !== void 0 ? { authorizationEndpoint: doc.authorization_endpoint } : {},
|
|
2666
|
+
...doc.token_endpoint !== void 0 ? { tokenEndpoint: doc.token_endpoint } : {},
|
|
2667
|
+
s256,
|
|
2668
|
+
clientSecretPost: post,
|
|
2669
|
+
warnings
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
var ConnectionListSchema = z10.array(
|
|
2673
|
+
z10.looseObject({ id: z10.string(), displayName: z10.string().optional() })
|
|
2674
|
+
);
|
|
2675
|
+
function connectionIdOf(args) {
|
|
2676
|
+
return args.connection_id ?? args.provider;
|
|
2677
|
+
}
|
|
2678
|
+
function questionnaire(spec, args, redirectUri) {
|
|
2679
|
+
const questions = [
|
|
2680
|
+
...spec.pieces.map(pieceQuestion),
|
|
2681
|
+
...args.provider === "generic" ? ["issuer: the exact issuer from the discovery document"] : [],
|
|
2682
|
+
"client_id: the client id the provider shows after you create the client",
|
|
2683
|
+
`client_type: "public" (PKCE, no secret${spec.publicPkce ? "" : " - NOT supported by this provider"}) or "confidential" (a secret you type into the admin UI)`,
|
|
2684
|
+
"display_name (optional): the text of the login button",
|
|
2685
|
+
"connection_id (optional): to update an existing connection instead of creating one"
|
|
2686
|
+
];
|
|
2687
|
+
return {
|
|
2688
|
+
status: "questions",
|
|
2689
|
+
provider: args.provider,
|
|
2690
|
+
label: spec.label,
|
|
2691
|
+
questions,
|
|
2692
|
+
issuerFormat: spec.issuerFormat,
|
|
2693
|
+
defaultScopes: spec.scopes,
|
|
2694
|
+
publicClientSupported: spec.publicPkce,
|
|
2695
|
+
whereToCreateTheClient: spec.whereToCreate,
|
|
2696
|
+
providerSideSteps: spec.steps,
|
|
2697
|
+
...spec.note !== void 0 ? { note: spec.note } : {},
|
|
2698
|
+
redirectUri,
|
|
2699
|
+
...redirectUri === null ? {
|
|
2700
|
+
redirectUriNote: "the hub address could not be resolved; set a public endpoint on the Network page"
|
|
2701
|
+
} : {},
|
|
2702
|
+
next: `Call ${OIDC_TOOL} again with the answers; it previews the connection and gives a confirm_token.`,
|
|
2703
|
+
secretRule: "A client secret is never a tool argument. For a confidential client you type it in the admin UI."
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
function oidcSpec(client, fetchFn) {
|
|
2707
|
+
return {
|
|
2708
|
+
tool: OIDC_TOOL,
|
|
2709
|
+
async dryRun(args) {
|
|
2710
|
+
const spec = OIDC_PROVIDERS[args.provider];
|
|
2711
|
+
const clientType = args.client_type ?? "public";
|
|
2712
|
+
const resolved = resolveIssuer(args.provider, args.issuer, args);
|
|
2713
|
+
if (resolved.issuer === null || args.client_id === void 0 || args.client_id.trim() === "") {
|
|
2714
|
+
return { confirmable: false, describe: { problem: "issuer and client_id are required" } };
|
|
2715
|
+
}
|
|
2716
|
+
if (clientType === "public" && !spec.publicPkce) {
|
|
2717
|
+
return {
|
|
2718
|
+
confirmable: false,
|
|
2719
|
+
describe: {
|
|
2720
|
+
problem: `${spec.label} does not issue public clients; use client_type "confidential"`
|
|
2721
|
+
}
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
const id = connectionIdOf(args);
|
|
2725
|
+
const list = await readQuery(
|
|
2726
|
+
client,
|
|
2727
|
+
"oidcConnections.listConnections",
|
|
2728
|
+
{},
|
|
2729
|
+
ConnectionListSchema
|
|
2730
|
+
);
|
|
2731
|
+
if (!list.ok) return { confirmable: false, describe: { problem: list.reason } };
|
|
2732
|
+
const exists = list.value.some((c) => c.id === id);
|
|
2733
|
+
if (exists && args.connection_id === void 0) {
|
|
2734
|
+
return {
|
|
2735
|
+
confirmable: false,
|
|
2736
|
+
describe: {
|
|
2737
|
+
problem: `a connection "${id}" already exists; pass connection_id "${id}" to update it, or another id to create a new one`
|
|
2738
|
+
}
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
const base = await resolveHubBase(client);
|
|
2742
|
+
const discovery = await checkDiscovery(fetchFn, resolved.issuer, clientType);
|
|
2743
|
+
const link = await adminLink(client, OIDC_ADMIN_ROUTE);
|
|
2744
|
+
return {
|
|
2745
|
+
confirmable: true,
|
|
2746
|
+
describe: {
|
|
2747
|
+
action: exists ? "update OIDC connection" : "create OIDC connection",
|
|
2748
|
+
connection: {
|
|
2749
|
+
id,
|
|
2750
|
+
displayName: args.display_name ?? spec.label,
|
|
2751
|
+
issuerUrl: resolved.issuer,
|
|
2752
|
+
clientId: args.client_id.trim(),
|
|
2753
|
+
clientType,
|
|
2754
|
+
scopes: spec.scopes,
|
|
2755
|
+
usernameClaim: spec.usernameClaim,
|
|
2756
|
+
clientSecret: clientType === "public" ? "none (public PKCE client)" : "entered in the admin UI"
|
|
2757
|
+
},
|
|
2758
|
+
redirectUri: base.base !== null ? `${base.base}${callbackPath(id)}` : null,
|
|
2759
|
+
...base.base === null ? { redirectUriProblem: base.reason } : {},
|
|
2760
|
+
discovery,
|
|
2761
|
+
warnings: discovery.warnings,
|
|
2762
|
+
...secretStep(clientType, link.url)
|
|
2763
|
+
}
|
|
2764
|
+
};
|
|
2765
|
+
},
|
|
2766
|
+
async execute(args) {
|
|
2767
|
+
const spec = OIDC_PROVIDERS[args.provider];
|
|
2768
|
+
const clientType = args.client_type ?? "public";
|
|
2769
|
+
const resolved = resolveIssuer(args.provider, args.issuer, args);
|
|
2770
|
+
const id = connectionIdOf(args);
|
|
2771
|
+
const displayName = args.display_name ?? spec.label;
|
|
2772
|
+
const list = await readQuery(
|
|
2773
|
+
client,
|
|
2774
|
+
"oidcConnections.listConnections",
|
|
2775
|
+
{},
|
|
2776
|
+
ConnectionListSchema
|
|
2777
|
+
);
|
|
2778
|
+
const exists = list.ok && list.value.some((c) => c.id === id);
|
|
2779
|
+
if (!exists) {
|
|
2780
|
+
await client.call("POST", "oidcConnections.createConnection", { id, displayName });
|
|
2781
|
+
}
|
|
2782
|
+
await client.call("POST", "oidcConnections.updateConnection", {
|
|
2783
|
+
id,
|
|
2784
|
+
displayName,
|
|
2785
|
+
issuerUrl: resolved.issuer ?? "",
|
|
2786
|
+
clientId: (args.client_id ?? "").trim(),
|
|
2787
|
+
scopes: spec.scopes,
|
|
2788
|
+
usernameClaim: spec.usernameClaim,
|
|
2789
|
+
...exists && clientType === "public" ? { clientSecret: { $secretClear: true } } : {}
|
|
2790
|
+
});
|
|
2791
|
+
const link = await adminLink(client, OIDC_ADMIN_ROUTE);
|
|
2792
|
+
return {
|
|
2793
|
+
connectionId: id,
|
|
2794
|
+
applied: exists ? "updated" : "created",
|
|
2795
|
+
...secretStep(clientType, link.url)
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
function secretStep(clientType, url) {
|
|
2801
|
+
if (clientType === "public") return {};
|
|
2802
|
+
return {
|
|
2803
|
+
secretStep: {
|
|
2804
|
+
required: true,
|
|
2805
|
+
how: url !== null ? `open ${url}, edit the connection and type the client secret in the Client Secret field` : `open ${OIDC_ADMIN_ROUTE} in the admin UI, edit the connection and type the client secret in the Client Secret field`,
|
|
2806
|
+
note: "The secret is never a tool argument and is not shown here."
|
|
2807
|
+
}
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
async function configureOidc(ctx, args, confirmToken) {
|
|
2811
|
+
const spec = OIDC_PROVIDERS[args.provider];
|
|
2812
|
+
const resolved = resolveIssuer(args.provider, args.issuer, args);
|
|
2813
|
+
const asking = resolved.issuer === null || (args.client_id ?? "").trim() === "";
|
|
2814
|
+
if (asking) {
|
|
2815
|
+
const base = await withHub(ctx, async ({ client }) => resolveHubBase(client));
|
|
2816
|
+
const redirect = "status" in base || base.base === null ? null : `${base.base}${callbackPath(connectionIdOf(args))}`;
|
|
2817
|
+
return questionnaire(spec, args, redirect);
|
|
2818
|
+
}
|
|
2819
|
+
return adminGated(ctx, (client) => oidcSpec(client, ctx.fetch), args, confirmToken);
|
|
2820
|
+
}
|
|
2821
|
+
var OIDC_TOOL_SHAPE = {
|
|
2822
|
+
provider: OidcProviderSchema,
|
|
2823
|
+
issuer: z10.string().optional(),
|
|
2824
|
+
host: z10.string().optional(),
|
|
2825
|
+
application_slug: z10.string().optional(),
|
|
2826
|
+
realm: z10.string().optional(),
|
|
2827
|
+
tenant_id: z10.string().optional(),
|
|
2828
|
+
client_id: z10.string().optional(),
|
|
2829
|
+
client_type: ClientTypeSchema.optional(),
|
|
2830
|
+
display_name: z10.string().optional(),
|
|
2831
|
+
connection_id: z10.string().optional(),
|
|
2832
|
+
confirm_token: ConfirmTokenSchema
|
|
2833
|
+
};
|
|
2834
|
+
function registerOidcTools(server, ctx) {
|
|
2835
|
+
server.tool(
|
|
2836
|
+
OIDC_TOOL,
|
|
2837
|
+
"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
|
+
OIDC_TOOL_SHAPE,
|
|
2839
|
+
async ({ confirm_token, ...args }) => text6(await configureOidc(ctx, args, confirm_token))
|
|
2840
|
+
);
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
// src/mcp/configure-nc-rules.ts
|
|
2844
|
+
import { z as z11 } from "zod";
|
|
2845
|
+
import { text as text7 } from "@camstack/develop";
|
|
2351
2846
|
var TIME = /^\d{1,2}:\d{2}$/;
|
|
2352
2847
|
var ALL_DAYS = [0, 1, 2, 3, 4, 5, 6];
|
|
2353
|
-
var QuietHoursAnswerSchema =
|
|
2848
|
+
var QuietHoursAnswerSchema = z11.object({
|
|
2354
2849
|
/** Notifications are muted from this time … */
|
|
2355
|
-
from:
|
|
2850
|
+
from: z11.string().regex(TIME),
|
|
2356
2851
|
/** … until this one (HH:MM, hub time zone). A window may cross midnight. */
|
|
2357
|
-
to:
|
|
2852
|
+
to: z11.string().regex(TIME),
|
|
2358
2853
|
/** Days the quiet window STARTS on (0 = Sunday); absent = every day. */
|
|
2359
|
-
days:
|
|
2854
|
+
days: z11.array(z11.number().int().min(0).max(6)).min(1).optional()
|
|
2360
2855
|
});
|
|
2361
|
-
var NcRuleAnswersSchema =
|
|
2362
|
-
name:
|
|
2363
|
-
delivery:
|
|
2856
|
+
var NcRuleAnswersSchema = z11.object({
|
|
2857
|
+
name: z11.string().min(1).max(200).optional(),
|
|
2858
|
+
delivery: z11.enum(["immediate", "track-end"]).optional(),
|
|
2364
2859
|
/** Camera ids; `[]` means every camera. */
|
|
2365
|
-
cameras:
|
|
2366
|
-
classes:
|
|
2860
|
+
cameras: z11.array(z11.number().int().nonnegative()).optional(),
|
|
2861
|
+
classes: z11.array(z11.string().min(1)).optional(),
|
|
2367
2862
|
/** Zone ids or names; `[]` means anywhere in view. */
|
|
2368
|
-
zones:
|
|
2369
|
-
minConfidence:
|
|
2863
|
+
zones: z11.array(z11.string().min(1)).optional(),
|
|
2864
|
+
minConfidence: z11.number().min(0).max(1).optional(),
|
|
2370
2865
|
/** `null` states "no quiet hours"; absent means not asked yet. */
|
|
2371
2866
|
quietHours: QuietHoursAnswerSchema.nullable().optional(),
|
|
2372
2867
|
/** Target ids or names. */
|
|
2373
|
-
targets:
|
|
2374
|
-
cooldownSeconds:
|
|
2868
|
+
targets: z11.array(z11.string().min(1)).optional(),
|
|
2869
|
+
cooldownSeconds: z11.number().int().min(0).max(86400).optional()
|
|
2375
2870
|
});
|
|
2376
2871
|
var SAVE_RULE_TOOL = "configure_notification_rule_save";
|
|
2377
2872
|
var DELIVERY_CHOICES = [
|
|
@@ -2557,11 +3052,11 @@ async function interviewNcRule(client, answers) {
|
|
|
2557
3052
|
async function ncRuleQuestionnaire(ctx, answers) {
|
|
2558
3053
|
return withHub(ctx, ({ client }) => interviewNcRule(client, answers));
|
|
2559
3054
|
}
|
|
2560
|
-
var StoredRuleSchema =
|
|
2561
|
-
id:
|
|
2562
|
-
name:
|
|
2563
|
-
conditions:
|
|
2564
|
-
schedule:
|
|
3055
|
+
var StoredRuleSchema = z11.looseObject({
|
|
3056
|
+
id: z11.string().optional(),
|
|
3057
|
+
name: z11.string().optional(),
|
|
3058
|
+
conditions: z11.record(z11.string(), z11.unknown()).optional(),
|
|
3059
|
+
schedule: z11.unknown().optional()
|
|
2565
3060
|
});
|
|
2566
3061
|
var AUTHORED_CONDITION_KEYS = [
|
|
2567
3062
|
"devices",
|
|
@@ -2693,17 +3188,17 @@ function registerNcRuleTools(server, ctx) {
|
|
|
2693
3188
|
"configure_notification_rule_questionnaire",
|
|
2694
3189
|
"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.",
|
|
2695
3190
|
{ answers: NcRuleAnswersSchema },
|
|
2696
|
-
async ({ answers }) =>
|
|
3191
|
+
async ({ answers }) => text7(await ncRuleQuestionnaire(ctx, answers))
|
|
2697
3192
|
);
|
|
2698
3193
|
server.tool(
|
|
2699
3194
|
SAVE_RULE_TOOL,
|
|
2700
3195
|
`Create a notification rule (the \`rule\` of a ready questionnaire), or update the one named by ruleId (the full conditions are sent, so keys the interview does not author are kept). ${GATE_HOW4}`,
|
|
2701
3196
|
{
|
|
2702
3197
|
rule: NcRuleInputSchema,
|
|
2703
|
-
ruleId:
|
|
3198
|
+
ruleId: z11.string().optional(),
|
|
2704
3199
|
confirm_token: ConfirmTokenSchema
|
|
2705
3200
|
},
|
|
2706
|
-
async ({ rule, ruleId, confirm_token }) =>
|
|
3201
|
+
async ({ rule, ruleId, confirm_token }) => text7(
|
|
2707
3202
|
await saveRule(ctx, { rule, ...ruleId !== void 0 ? { ruleId } : {} }, confirm_token)
|
|
2708
3203
|
)
|
|
2709
3204
|
);
|
|
@@ -2716,20 +3211,21 @@ function registerConfigureP3bTools(server, ctx) {
|
|
|
2716
3211
|
registerCalibrateTools(server, ctx);
|
|
2717
3212
|
registerExportTools(server, ctx);
|
|
2718
3213
|
registerNetworkTools(server, ctx);
|
|
3214
|
+
registerOidcTools(server, ctx);
|
|
2719
3215
|
registerZonesAlarmTools(server, ctx);
|
|
2720
3216
|
}
|
|
2721
3217
|
|
|
2722
3218
|
// src/mcp/configure-addons.ts
|
|
2723
3219
|
import {
|
|
2724
|
-
text as
|
|
3220
|
+
text as text8
|
|
2725
3221
|
} from "@camstack/develop";
|
|
2726
|
-
import { z as
|
|
3222
|
+
import { z as z13 } from "zod";
|
|
2727
3223
|
|
|
2728
3224
|
// src/mcp/configure-support.ts
|
|
2729
3225
|
import {
|
|
2730
3226
|
runGated as runGated2
|
|
2731
3227
|
} from "@camstack/develop";
|
|
2732
|
-
import { z as
|
|
3228
|
+
import { z as z12 } from "zod";
|
|
2733
3229
|
async function runGatedAdmin(ctx, makeSpec, args, confirmToken) {
|
|
2734
3230
|
const outcome = await ctx.hub();
|
|
2735
3231
|
if (!outcome.ok) return noSessionResult(outcome);
|
|
@@ -2752,28 +3248,28 @@ async function withAdminHub(ctx, body) {
|
|
|
2752
3248
|
}
|
|
2753
3249
|
return body(outcome.connection);
|
|
2754
3250
|
}
|
|
2755
|
-
var FormOptionSchema =
|
|
2756
|
-
value:
|
|
2757
|
-
label:
|
|
3251
|
+
var FormOptionSchema = z12.looseObject({
|
|
3252
|
+
value: z12.union([z12.string(), z12.number(), z12.boolean()]),
|
|
3253
|
+
label: z12.string().optional()
|
|
2758
3254
|
});
|
|
2759
|
-
var FormFieldSchema =
|
|
2760
|
-
type:
|
|
2761
|
-
key:
|
|
2762
|
-
label:
|
|
2763
|
-
description:
|
|
2764
|
-
required:
|
|
2765
|
-
secret:
|
|
2766
|
-
default:
|
|
2767
|
-
options:
|
|
2768
|
-
itemFields:
|
|
2769
|
-
|
|
2770
|
-
type:
|
|
2771
|
-
key:
|
|
3255
|
+
var FormFieldSchema = z12.looseObject({
|
|
3256
|
+
type: z12.string().optional(),
|
|
3257
|
+
key: z12.string().optional(),
|
|
3258
|
+
label: z12.string().optional(),
|
|
3259
|
+
description: z12.string().optional(),
|
|
3260
|
+
required: z12.boolean().optional(),
|
|
3261
|
+
secret: z12.boolean().optional(),
|
|
3262
|
+
default: z12.unknown().optional(),
|
|
3263
|
+
options: z12.array(FormOptionSchema).optional(),
|
|
3264
|
+
itemFields: z12.array(
|
|
3265
|
+
z12.looseObject({
|
|
3266
|
+
type: z12.string().optional(),
|
|
3267
|
+
key: z12.string().optional()
|
|
2772
3268
|
})
|
|
2773
3269
|
).optional()
|
|
2774
3270
|
});
|
|
2775
|
-
var FormSchemaShape =
|
|
2776
|
-
sections:
|
|
3271
|
+
var FormSchemaShape = z12.looseObject({
|
|
3272
|
+
sections: z12.array(z12.looseObject({ fields: z12.array(FormFieldSchema) }))
|
|
2777
3273
|
});
|
|
2778
3274
|
var NON_INPUT_TYPES = /* @__PURE__ */ new Set(["info", "divider", "heading", "button"]);
|
|
2779
3275
|
var CREDENTIAL_NAME = /pass(word|wd|phrase)?|token|secret|api[_-]?key|credential|private[_-]?key/i;
|
|
@@ -2874,21 +3370,21 @@ function splitConfig(fields, config) {
|
|
|
2874
3370
|
function fieldValidator(field) {
|
|
2875
3371
|
switch (field.type) {
|
|
2876
3372
|
case "number":
|
|
2877
|
-
return
|
|
3373
|
+
return z12.number();
|
|
2878
3374
|
case "boolean":
|
|
2879
3375
|
case "switch":
|
|
2880
3376
|
case "checkbox":
|
|
2881
3377
|
case "toggle":
|
|
2882
|
-
return
|
|
3378
|
+
return z12.boolean();
|
|
2883
3379
|
case "tags":
|
|
2884
|
-
return
|
|
3380
|
+
return z12.array(z12.string());
|
|
2885
3381
|
case "editable-array":
|
|
2886
|
-
return
|
|
3382
|
+
return z12.array(z12.record(z12.string(), z12.unknown()));
|
|
2887
3383
|
case "select":
|
|
2888
3384
|
case "radio": {
|
|
2889
|
-
if (field.options === null || field.options.length === 0) return
|
|
3385
|
+
if (field.options === null || field.options.length === 0) return z12.unknown();
|
|
2890
3386
|
const values = field.options.map((o) => o.value);
|
|
2891
|
-
return
|
|
3387
|
+
return z12.custom(
|
|
2892
3388
|
(v) => values.some((allowed) => allowed === v),
|
|
2893
3389
|
`one of: ${values.map(String).join(", ")}`
|
|
2894
3390
|
);
|
|
@@ -2898,9 +3394,9 @@ function fieldValidator(field) {
|
|
|
2898
3394
|
case "textarea":
|
|
2899
3395
|
case "url":
|
|
2900
3396
|
case "email":
|
|
2901
|
-
return
|
|
3397
|
+
return z12.string();
|
|
2902
3398
|
default:
|
|
2903
|
-
return
|
|
3399
|
+
return z12.unknown();
|
|
2904
3400
|
}
|
|
2905
3401
|
}
|
|
2906
3402
|
function formValidator(fields) {
|
|
@@ -2910,7 +3406,7 @@ function formValidator(fields) {
|
|
|
2910
3406
|
const base = fieldValidator(field);
|
|
2911
3407
|
shape[field.key] = field.required ? base : base.optional();
|
|
2912
3408
|
}
|
|
2913
|
-
return
|
|
3409
|
+
return z12.object(shape);
|
|
2914
3410
|
}
|
|
2915
3411
|
function validationProblems(error) {
|
|
2916
3412
|
return error.issues.map((issue) => {
|
|
@@ -2942,64 +3438,64 @@ function viewConfig(fields, config) {
|
|
|
2942
3438
|
var REGISTRY = "https://registry.npmjs.org";
|
|
2943
3439
|
var REGISTRY_TIMEOUT_MS = 5e3;
|
|
2944
3440
|
var MAX_RESULTS = 15;
|
|
2945
|
-
var PackageName =
|
|
2946
|
-
var CatalogShape =
|
|
2947
|
-
name:
|
|
2948
|
-
version:
|
|
2949
|
-
description:
|
|
2950
|
-
keywords:
|
|
2951
|
-
installed:
|
|
2952
|
-
capabilities:
|
|
3441
|
+
var PackageName = z13.string().min(1).regex(/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/, "not an npm package name");
|
|
3442
|
+
var CatalogShape = z13.looseObject({
|
|
3443
|
+
name: z13.string(),
|
|
3444
|
+
version: z13.string(),
|
|
3445
|
+
description: z13.string(),
|
|
3446
|
+
keywords: z13.array(z13.string()).optional(),
|
|
3447
|
+
installed: z13.boolean(),
|
|
3448
|
+
capabilities: z13.array(z13.string()).nullable()
|
|
2953
3449
|
});
|
|
2954
|
-
var ListedAddonShape =
|
|
2955
|
-
manifest:
|
|
2956
|
-
id:
|
|
2957
|
-
packageName:
|
|
2958
|
-
packageVersion:
|
|
3450
|
+
var ListedAddonShape = z13.looseObject({
|
|
3451
|
+
manifest: z13.looseObject({
|
|
3452
|
+
id: z13.string(),
|
|
3453
|
+
packageName: z13.string(),
|
|
3454
|
+
packageVersion: z13.string(),
|
|
2959
3455
|
// The hub answers null, not absent, for a manifest without the flag.
|
|
2960
|
-
protected:
|
|
2961
|
-
removable:
|
|
3456
|
+
protected: z13.boolean().nullish(),
|
|
3457
|
+
removable: z13.boolean().nullish()
|
|
2962
3458
|
})
|
|
2963
3459
|
});
|
|
2964
|
-
var UpdateShape =
|
|
2965
|
-
name:
|
|
2966
|
-
currentVersion:
|
|
2967
|
-
latestVersion:
|
|
2968
|
-
category:
|
|
2969
|
-
requiresRestart:
|
|
2970
|
-
isSystem:
|
|
3460
|
+
var UpdateShape = z13.looseObject({
|
|
3461
|
+
name: z13.string(),
|
|
3462
|
+
currentVersion: z13.string(),
|
|
3463
|
+
latestVersion: z13.string(),
|
|
3464
|
+
category: z13.enum(["addon", "core"]),
|
|
3465
|
+
requiresRestart: z13.boolean(),
|
|
3466
|
+
isSystem: z13.boolean()
|
|
2971
3467
|
});
|
|
2972
|
-
var InstallationsShape =
|
|
2973
|
-
installations:
|
|
2974
|
-
|
|
2975
|
-
nodeId:
|
|
2976
|
-
isHub:
|
|
2977
|
-
isOnline:
|
|
2978
|
-
version:
|
|
2979
|
-
source:
|
|
3468
|
+
var InstallationsShape = z13.looseObject({
|
|
3469
|
+
installations: z13.array(
|
|
3470
|
+
z13.looseObject({
|
|
3471
|
+
nodeId: z13.string(),
|
|
3472
|
+
isHub: z13.boolean(),
|
|
3473
|
+
isOnline: z13.boolean(),
|
|
3474
|
+
version: z13.string().nullable(),
|
|
3475
|
+
source: z13.string()
|
|
2980
3476
|
})
|
|
2981
3477
|
),
|
|
2982
|
-
unreachable:
|
|
3478
|
+
unreachable: z13.array(z13.looseObject({ nodeId: z13.string(), error: z13.string() }))
|
|
2983
3479
|
});
|
|
2984
|
-
var VersionsShape =
|
|
2985
|
-
|
|
3480
|
+
var VersionsShape = z13.array(
|
|
3481
|
+
z13.looseObject({ version: z13.string(), deprecated: z13.string().optional() })
|
|
2986
3482
|
);
|
|
2987
|
-
var UninstallShape =
|
|
2988
|
-
success:
|
|
2989
|
-
packageName:
|
|
2990
|
-
results:
|
|
2991
|
-
|
|
2992
|
-
nodeId:
|
|
2993
|
-
outcome:
|
|
2994
|
-
error:
|
|
3483
|
+
var UninstallShape = z13.looseObject({
|
|
3484
|
+
success: z13.boolean(),
|
|
3485
|
+
packageName: z13.string(),
|
|
3486
|
+
results: z13.array(
|
|
3487
|
+
z13.looseObject({
|
|
3488
|
+
nodeId: z13.string(),
|
|
3489
|
+
outcome: z13.string(),
|
|
3490
|
+
error: z13.string().optional()
|
|
2995
3491
|
})
|
|
2996
3492
|
)
|
|
2997
3493
|
});
|
|
2998
|
-
var ServerStatusShape =
|
|
2999
|
-
runningVersion:
|
|
3494
|
+
var ServerStatusShape = z13.looseObject({
|
|
3495
|
+
runningVersion: z13.string().nullable().optional()
|
|
3000
3496
|
});
|
|
3001
3497
|
async function readProtection(client, packageName) {
|
|
3002
|
-
const list = await readQuery(client, "addons.list", {},
|
|
3498
|
+
const list = await readQuery(client, "addons.list", {}, z13.array(ListedAddonShape));
|
|
3003
3499
|
if (!list.ok) return null;
|
|
3004
3500
|
const mine = list.value.filter((a) => a.manifest.packageName === packageName);
|
|
3005
3501
|
return {
|
|
@@ -3010,13 +3506,13 @@ async function readProtection(client, packageName) {
|
|
|
3010
3506
|
}
|
|
3011
3507
|
var PROTECTED_REASON = (name) => `${name} is a protected package (manifest protected / removable:false): the kernel needs it, so it is never installed over, updated or removed here`;
|
|
3012
3508
|
var UNREADABLE_PROTECTION_REASON = (name) => `could not read whether ${name} is protected (addons.list did not answer with the expected shape); nothing is changed until it does`;
|
|
3013
|
-
var PackumentShape =
|
|
3014
|
-
peerDependencies:
|
|
3015
|
-
engines:
|
|
3016
|
-
camstack:
|
|
3017
|
-
addons:
|
|
3018
|
-
|
|
3019
|
-
execution:
|
|
3509
|
+
var PackumentShape = z13.looseObject({
|
|
3510
|
+
peerDependencies: z13.record(z13.string(), z13.string()).optional(),
|
|
3511
|
+
engines: z13.record(z13.string(), z13.string()).optional(),
|
|
3512
|
+
camstack: z13.looseObject({
|
|
3513
|
+
addons: z13.array(
|
|
3514
|
+
z13.looseObject({
|
|
3515
|
+
execution: z13.looseObject({ placement: z13.string().optional() }).optional()
|
|
3020
3516
|
})
|
|
3021
3517
|
).optional()
|
|
3022
3518
|
}).optional()
|
|
@@ -3068,10 +3564,10 @@ async function configureAddonsSearch(ctx, args) {
|
|
|
3068
3564
|
...args.query !== void 0 ? { query: args.query } : {},
|
|
3069
3565
|
...args.capability !== void 0 ? { capability: args.capability } : {}
|
|
3070
3566
|
},
|
|
3071
|
-
|
|
3567
|
+
z13.array(CatalogShape)
|
|
3072
3568
|
),
|
|
3073
|
-
readQuery(client, "addons.list", {},
|
|
3074
|
-
readQuery(client, "addons.listUpdates", {},
|
|
3569
|
+
readQuery(client, "addons.list", {}, z13.array(ListedAddonShape)),
|
|
3570
|
+
readQuery(client, "addons.listUpdates", {}, z13.array(UpdateShape)),
|
|
3075
3571
|
readQuery(client, "serverManagement.getServerPackageStatus", void 0, ServerStatusShape)
|
|
3076
3572
|
]);
|
|
3077
3573
|
if (!catalog.ok) return { status: "unavailable", reason: catalog.reason };
|
|
@@ -3150,7 +3646,7 @@ function installAddonSpec(client, fetchFn) {
|
|
|
3150
3646
|
client,
|
|
3151
3647
|
"addons.searchAvailable",
|
|
3152
3648
|
{ query: args.packageName },
|
|
3153
|
-
|
|
3649
|
+
z13.array(CatalogShape)
|
|
3154
3650
|
);
|
|
3155
3651
|
if (!catalog.ok) return { confirmable: false, describe: { problem: catalog.reason } };
|
|
3156
3652
|
const entry = catalog.value.find((c) => c.name === args.packageName);
|
|
@@ -3253,7 +3749,7 @@ function updateAddonSpec(client) {
|
|
|
3253
3749
|
client,
|
|
3254
3750
|
"addons.listUpdates",
|
|
3255
3751
|
args.nodeId !== void 0 ? { nodeId: args.nodeId } : {},
|
|
3256
|
-
|
|
3752
|
+
z13.array(UpdateShape)
|
|
3257
3753
|
);
|
|
3258
3754
|
if (!updates.ok) return { confirmable: false, describe: { problem: updates.reason } };
|
|
3259
3755
|
const entry = updates.value.find((u) => u.name === args.packageName);
|
|
@@ -3374,10 +3870,10 @@ function registerAddonTools(server, ctx) {
|
|
|
3374
3870
|
"configure_addons_search",
|
|
3375
3871
|
"Search the addon store (the catalog the admin UI uses) with the installed version, an available update, whether the package is protected, and compatibility (placement, @camstack/types peer range, node engine; no addon declares a minimum server version).",
|
|
3376
3872
|
{
|
|
3377
|
-
query:
|
|
3378
|
-
capability:
|
|
3873
|
+
query: z13.string().max(100).optional(),
|
|
3874
|
+
capability: z13.string().max(100).optional()
|
|
3379
3875
|
},
|
|
3380
|
-
async ({ query, capability }) =>
|
|
3876
|
+
async ({ query, capability }) => text8(
|
|
3381
3877
|
await configureAddonsSearch(ctx, {
|
|
3382
3878
|
...query !== void 0 ? { query } : {},
|
|
3383
3879
|
...capability !== void 0 ? { capability } : {}
|
|
@@ -3389,10 +3885,10 @@ function registerAddonTools(server, ctx) {
|
|
|
3389
3885
|
`Install an addon package on the hub from the store. ${TWO_STEP} Agents reconcile later; the result says so.`,
|
|
3390
3886
|
{
|
|
3391
3887
|
packageName: PackageName,
|
|
3392
|
-
version:
|
|
3393
|
-
confirm_token:
|
|
3888
|
+
version: z13.string().max(64).optional(),
|
|
3889
|
+
confirm_token: z13.string().optional()
|
|
3394
3890
|
},
|
|
3395
|
-
async ({ packageName, version, confirm_token }) =>
|
|
3891
|
+
async ({ packageName, version, confirm_token }) => text8(
|
|
3396
3892
|
await installAddon(
|
|
3397
3893
|
ctx,
|
|
3398
3894
|
{ packageName, ...version !== void 0 ? { version } : {} },
|
|
@@ -3405,11 +3901,11 @@ function registerAddonTools(server, ctx) {
|
|
|
3405
3901
|
`Update an installed addon package (hub by default, or one node). Protected and framework packages are refused in the preview. ${TWO_STEP}`,
|
|
3406
3902
|
{
|
|
3407
3903
|
packageName: PackageName,
|
|
3408
|
-
version:
|
|
3409
|
-
nodeId:
|
|
3410
|
-
confirm_token:
|
|
3904
|
+
version: z13.string().max(64).optional(),
|
|
3905
|
+
nodeId: z13.string().max(100).optional(),
|
|
3906
|
+
confirm_token: z13.string().optional()
|
|
3411
3907
|
},
|
|
3412
|
-
async ({ packageName, version, nodeId, confirm_token }) =>
|
|
3908
|
+
async ({ packageName, version, nodeId, confirm_token }) => text8(
|
|
3413
3909
|
await updateAddon(
|
|
3414
3910
|
ctx,
|
|
3415
3911
|
{
|
|
@@ -3426,11 +3922,11 @@ function registerAddonTools(server, ctx) {
|
|
|
3426
3922
|
`Uninstall an addon package from the hub, one node, or everywhere. Protected packages (manifest protected / removable:false) are refused in the preview with the reason. ${TWO_STEP}`,
|
|
3427
3923
|
{
|
|
3428
3924
|
packageName: PackageName,
|
|
3429
|
-
nodeId:
|
|
3430
|
-
everywhere:
|
|
3431
|
-
confirm_token:
|
|
3925
|
+
nodeId: z13.string().max(100).optional(),
|
|
3926
|
+
everywhere: z13.boolean().optional(),
|
|
3927
|
+
confirm_token: z13.string().optional()
|
|
3432
3928
|
},
|
|
3433
|
-
async ({ packageName, nodeId, everywhere, confirm_token }) =>
|
|
3929
|
+
async ({ packageName, nodeId, everywhere, confirm_token }) => text8(
|
|
3434
3930
|
await uninstallAddon(
|
|
3435
3931
|
ctx,
|
|
3436
3932
|
{
|
|
@@ -3446,9 +3942,9 @@ function registerAddonTools(server, ctx) {
|
|
|
3446
3942
|
|
|
3447
3943
|
// src/mcp/configure-cameras.ts
|
|
3448
3944
|
import {
|
|
3449
|
-
text as
|
|
3945
|
+
text as text9
|
|
3450
3946
|
} from "@camstack/develop";
|
|
3451
|
-
import { z as
|
|
3947
|
+
import { z as z14 } from "zod";
|
|
3452
3948
|
var UNSUPPORTED_HINT = "use support_diagnose_camera / support_draft_issue";
|
|
3453
3949
|
var MAX_PROVIDERS = 40;
|
|
3454
3950
|
var MAX_CANDIDATES = 50;
|
|
@@ -3461,34 +3957,34 @@ function unsupported(reason, provider) {
|
|
|
3461
3957
|
hint: UNSUPPORTED_HINT
|
|
3462
3958
|
};
|
|
3463
3959
|
}
|
|
3464
|
-
var CapabilityEntry =
|
|
3465
|
-
var InstalledAddonShape =
|
|
3466
|
-
manifest:
|
|
3467
|
-
id:
|
|
3468
|
-
name:
|
|
3469
|
-
description:
|
|
3470
|
-
packageName:
|
|
3471
|
-
packageVersion:
|
|
3472
|
-
capabilities:
|
|
3960
|
+
var CapabilityEntry = z14.union([z14.string(), z14.looseObject({ name: z14.string() })]);
|
|
3961
|
+
var InstalledAddonShape = z14.looseObject({
|
|
3962
|
+
manifest: z14.looseObject({
|
|
3963
|
+
id: z14.string(),
|
|
3964
|
+
name: z14.string().optional(),
|
|
3965
|
+
description: z14.string().optional(),
|
|
3966
|
+
packageName: z14.string(),
|
|
3967
|
+
packageVersion: z14.string(),
|
|
3968
|
+
capabilities: z14.array(CapabilityEntry).optional()
|
|
3473
3969
|
})
|
|
3474
3970
|
});
|
|
3475
|
-
var CatalogShape2 =
|
|
3476
|
-
name:
|
|
3477
|
-
version:
|
|
3478
|
-
description:
|
|
3479
|
-
keywords:
|
|
3480
|
-
installed:
|
|
3481
|
-
capabilities:
|
|
3971
|
+
var CatalogShape2 = z14.looseObject({
|
|
3972
|
+
name: z14.string(),
|
|
3973
|
+
version: z14.string(),
|
|
3974
|
+
description: z14.string(),
|
|
3975
|
+
keywords: z14.array(z14.string()),
|
|
3976
|
+
installed: z14.boolean(),
|
|
3977
|
+
capabilities: z14.array(z14.string()).nullable()
|
|
3482
3978
|
});
|
|
3483
|
-
var DiscoveryProvidersShape =
|
|
3484
|
-
providers:
|
|
3979
|
+
var DiscoveryProvidersShape = z14.looseObject({
|
|
3980
|
+
providers: z14.array(z14.looseObject({ addonId: z14.string(), label: z14.string() }))
|
|
3485
3981
|
});
|
|
3486
3982
|
function capabilityName(entry) {
|
|
3487
3983
|
return typeof entry === "string" ? entry : entry.name;
|
|
3488
3984
|
}
|
|
3489
3985
|
async function readInstalledProviders(client) {
|
|
3490
3986
|
const [list, discovery] = await Promise.all([
|
|
3491
|
-
readQuery(client, "addons.list", {},
|
|
3987
|
+
readQuery(client, "addons.list", {}, z14.array(InstalledAddonShape)),
|
|
3492
3988
|
readQuery(client, "deviceManager.discoveryProviders", {}, DiscoveryProvidersShape)
|
|
3493
3989
|
]);
|
|
3494
3990
|
if (!list.ok) return list;
|
|
@@ -3519,7 +4015,7 @@ async function configureCameraProviders(ctx, query) {
|
|
|
3519
4015
|
client,
|
|
3520
4016
|
"addons.searchAvailable",
|
|
3521
4017
|
{ capability: DEVICE_PROVIDER },
|
|
3522
|
-
|
|
4018
|
+
z14.array(CatalogShape2)
|
|
3523
4019
|
)
|
|
3524
4020
|
]);
|
|
3525
4021
|
if (!installed.ok) return { status: "unavailable", reason: installed.reason };
|
|
@@ -3553,13 +4049,13 @@ async function configureCameraProviders(ctx, query) {
|
|
|
3553
4049
|
};
|
|
3554
4050
|
});
|
|
3555
4051
|
}
|
|
3556
|
-
var CandidateShape =
|
|
3557
|
-
stableId:
|
|
3558
|
-
type:
|
|
3559
|
-
suggestedName:
|
|
3560
|
-
prefilledConfig:
|
|
4052
|
+
var CandidateShape = z14.looseObject({
|
|
4053
|
+
stableId: z14.string(),
|
|
4054
|
+
type: z14.string(),
|
|
4055
|
+
suggestedName: z14.string(),
|
|
4056
|
+
prefilledConfig: z14.record(z14.string(), z14.unknown())
|
|
3561
4057
|
});
|
|
3562
|
-
var DiscoverShape =
|
|
4058
|
+
var DiscoverShape = z14.looseObject({ candidates: z14.array(CandidateShape) });
|
|
3563
4059
|
async function configureCameraDiscover(ctx, args) {
|
|
3564
4060
|
return withAdminHub(ctx, async ({ client }) => {
|
|
3565
4061
|
const providers = await readQuery(
|
|
@@ -3645,7 +4141,7 @@ async function planAdd(client, args) {
|
|
|
3645
4141
|
client,
|
|
3646
4142
|
"deviceManager.getCreationSchema",
|
|
3647
4143
|
{ addonId: args.addonId, type: "camera" },
|
|
3648
|
-
|
|
4144
|
+
z14.unknown()
|
|
3649
4145
|
);
|
|
3650
4146
|
if (!schema.ok) return { ok: false, problems: [schema.reason] };
|
|
3651
4147
|
if (schema.value === null || schema.value === void 0) {
|
|
@@ -3696,7 +4192,7 @@ async function providerPage(client, addonId) {
|
|
|
3696
4192
|
const link = await resolveLinkBase(client);
|
|
3697
4193
|
return link.base === null ? null : `${link.base}/integrations/${addonId}`;
|
|
3698
4194
|
}
|
|
3699
|
-
var CreatedShape =
|
|
4195
|
+
var CreatedShape = z14.looseObject({ id: z14.number(), name: z14.string() });
|
|
3700
4196
|
function addCameraSpec(client) {
|
|
3701
4197
|
return {
|
|
3702
4198
|
tool: ADD_CAMERA_TOOL,
|
|
@@ -3773,17 +4269,17 @@ function registerCameraTools(server, ctx) {
|
|
|
3773
4269
|
server.tool(
|
|
3774
4270
|
"configure_camera_providers",
|
|
3775
4271
|
"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.",
|
|
3776
|
-
{ query:
|
|
3777
|
-
async ({ query }) =>
|
|
4272
|
+
{ query: z14.string().max(100).optional() },
|
|
4273
|
+
async ({ query }) => text9(await configureCameraProviders(ctx, query))
|
|
3778
4274
|
);
|
|
3779
4275
|
server.tool(
|
|
3780
4276
|
"configure_camera_discover",
|
|
3781
4277
|
'Scan the network through one camera provider (the admin UI "add camera" discovery) and list the candidates with the non-secret fields to pass to configure_camera_add. Changes nothing on the hub. A credential in `params` is refused (`secret-required`). Needs an admin `camstack login`.',
|
|
3782
4278
|
{
|
|
3783
|
-
addonId:
|
|
3784
|
-
params:
|
|
4279
|
+
addonId: z14.string().min(1),
|
|
4280
|
+
params: z14.record(z14.string(), z14.unknown()).optional()
|
|
3785
4281
|
},
|
|
3786
|
-
async ({ addonId, params }) =>
|
|
4282
|
+
async ({ addonId, params }) => text9(
|
|
3787
4283
|
await configureCameraDiscover(ctx, {
|
|
3788
4284
|
addonId,
|
|
3789
4285
|
...params !== void 0 ? { params } : {}
|
|
@@ -3794,46 +4290,46 @@ function registerCameraTools(server, ctx) {
|
|
|
3794
4290
|
ADD_CAMERA_TOOL,
|
|
3795
4291
|
"Add a camera through a provider's creation form (the fields are the provider's own schema). Two steps: call without confirm_token for a preview and a one-shot token, then again with the SAME arguments and that token. `config` takes NON-secret fields only; a password is refused and the answer carries the admin-UI link where the operator types it (`secret-required`). A provider that is not installed or has no manual form is `unsupported`.",
|
|
3796
4292
|
{
|
|
3797
|
-
addonId:
|
|
3798
|
-
config:
|
|
3799
|
-
confirm_token:
|
|
4293
|
+
addonId: z14.string().min(1),
|
|
4294
|
+
config: z14.record(z14.string(), z14.unknown()),
|
|
4295
|
+
confirm_token: z14.string().optional()
|
|
3800
4296
|
},
|
|
3801
|
-
async ({ addonId, config, confirm_token }) =>
|
|
4297
|
+
async ({ addonId, config, confirm_token }) => text9(await addCamera(ctx, { addonId, config }, confirm_token))
|
|
3802
4298
|
);
|
|
3803
4299
|
}
|
|
3804
4300
|
|
|
3805
4301
|
// src/mcp/configure-notifiers.ts
|
|
3806
4302
|
import {
|
|
3807
|
-
text as
|
|
4303
|
+
text as text10
|
|
3808
4304
|
} from "@camstack/develop";
|
|
3809
|
-
import { z as
|
|
4305
|
+
import { z as z15 } from "zod";
|
|
3810
4306
|
var MAX_TARGETS = 50;
|
|
3811
|
-
var TargetKindShape =
|
|
3812
|
-
kind:
|
|
3813
|
-
label:
|
|
3814
|
-
addonId:
|
|
3815
|
-
supportsDiscovery:
|
|
3816
|
-
configSchema:
|
|
3817
|
-
destinationKeys:
|
|
4307
|
+
var TargetKindShape = z15.looseObject({
|
|
4308
|
+
kind: z15.string(),
|
|
4309
|
+
label: z15.string(),
|
|
4310
|
+
addonId: z15.string(),
|
|
4311
|
+
supportsDiscovery: z15.boolean().optional(),
|
|
4312
|
+
configSchema: z15.unknown(),
|
|
4313
|
+
destinationKeys: z15.array(z15.string()).optional()
|
|
3818
4314
|
});
|
|
3819
|
-
var TargetShape =
|
|
3820
|
-
id:
|
|
3821
|
-
name:
|
|
3822
|
-
kind:
|
|
3823
|
-
addonId:
|
|
3824
|
-
enabled:
|
|
3825
|
-
config:
|
|
4315
|
+
var TargetShape = z15.looseObject({
|
|
4316
|
+
id: z15.string(),
|
|
4317
|
+
name: z15.string(),
|
|
4318
|
+
kind: z15.string(),
|
|
4319
|
+
addonId: z15.string(),
|
|
4320
|
+
enabled: z15.boolean(),
|
|
4321
|
+
config: z15.record(z15.string(), z15.unknown())
|
|
3826
4322
|
});
|
|
3827
|
-
var SendResultShape =
|
|
3828
|
-
success:
|
|
3829
|
-
error:
|
|
4323
|
+
var SendResultShape = z15.looseObject({
|
|
4324
|
+
success: z15.boolean(),
|
|
4325
|
+
error: z15.string().optional()
|
|
3830
4326
|
});
|
|
3831
4327
|
async function readKinds(client) {
|
|
3832
4328
|
const read = await readQuery(
|
|
3833
4329
|
client,
|
|
3834
4330
|
"notificationOutput.listTargetKinds",
|
|
3835
4331
|
{},
|
|
3836
|
-
|
|
4332
|
+
z15.array(TargetKindShape)
|
|
3837
4333
|
);
|
|
3838
4334
|
if (!read.ok) return read;
|
|
3839
4335
|
return {
|
|
@@ -3888,7 +4384,7 @@ async function configureNotificationTargets(ctx) {
|
|
|
3888
4384
|
return withAdminHub(ctx, async ({ client }) => {
|
|
3889
4385
|
const [kinds, targets] = await Promise.all([
|
|
3890
4386
|
readKinds(client),
|
|
3891
|
-
readQuery(client, "notificationOutput.listTargets", {},
|
|
4387
|
+
readQuery(client, "notificationOutput.listTargets", {}, z15.array(TargetShape))
|
|
3892
4388
|
]);
|
|
3893
4389
|
if (!targets.ok) return { status: "unavailable", reason: targets.reason };
|
|
3894
4390
|
const forms = kinds.ok ? kinds.value : [];
|
|
@@ -3929,7 +4425,7 @@ async function planUpsert(client, args) {
|
|
|
3929
4425
|
const fields = form.fields;
|
|
3930
4426
|
let existing = null;
|
|
3931
4427
|
if (args.targetId !== void 0) {
|
|
3932
|
-
const list = await readQuery(client, "notificationOutput.listTargets", {},
|
|
4428
|
+
const list = await readQuery(client, "notificationOutput.listTargets", {}, z15.array(TargetShape));
|
|
3933
4429
|
if (!list.ok) return { ok: false, problems: [list.reason] };
|
|
3934
4430
|
existing = list.value.find((t) => t.id === args.targetId) ?? null;
|
|
3935
4431
|
if (existing === null) return { ok: false, problems: [`target ${args.targetId} was not found`] };
|
|
@@ -4036,7 +4532,7 @@ function testTargetSpec(client) {
|
|
|
4036
4532
|
async dryRun(args) {
|
|
4037
4533
|
const [kinds, targets] = await Promise.all([
|
|
4038
4534
|
readKinds(client),
|
|
4039
|
-
readQuery(client, "notificationOutput.listTargets", {},
|
|
4535
|
+
readQuery(client, "notificationOutput.listTargets", {}, z15.array(TargetShape))
|
|
4040
4536
|
]);
|
|
4041
4537
|
if (!targets.ok) return { confirmable: false, describe: { problem: targets.reason } };
|
|
4042
4538
|
const target = targets.value.find((t) => t.id === args.targetId);
|
|
@@ -4108,26 +4604,26 @@ function registerNotifierTools(server, ctx) {
|
|
|
4108
4604
|
"configure_notifiers",
|
|
4109
4605
|
"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`.",
|
|
4110
4606
|
{},
|
|
4111
|
-
async () =>
|
|
4607
|
+
async () => text10(await configureNotifiers(ctx))
|
|
4112
4608
|
);
|
|
4113
4609
|
server.tool(
|
|
4114
4610
|
"configure_notification_targets",
|
|
4115
4611
|
"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`.",
|
|
4116
4612
|
{},
|
|
4117
|
-
async () =>
|
|
4613
|
+
async () => text10(await configureNotificationTargets(ctx))
|
|
4118
4614
|
);
|
|
4119
4615
|
server.tool(
|
|
4120
4616
|
UPSERT_TARGET_TOOL,
|
|
4121
4617
|
"Create (no targetId) or edit (targetId) a notification target from its notifier schema (see configure_notifiers). Two steps: call without confirm_token for a preview and a one-shot token, then again with the SAME arguments and that token. `config` takes NON-secret fields only; a secret field is refused, and the target is saved incomplete with the admin-UI link where the operator types the secret (`secret-required`).",
|
|
4122
4618
|
{
|
|
4123
|
-
targetId:
|
|
4124
|
-
kind:
|
|
4125
|
-
name:
|
|
4126
|
-
config:
|
|
4127
|
-
enabled:
|
|
4128
|
-
confirm_token:
|
|
4619
|
+
targetId: z15.string().min(1).optional(),
|
|
4620
|
+
kind: z15.string().min(1),
|
|
4621
|
+
name: z15.string().min(1),
|
|
4622
|
+
config: z15.record(z15.string(), z15.unknown()),
|
|
4623
|
+
enabled: z15.boolean().optional(),
|
|
4624
|
+
confirm_token: z15.string().optional()
|
|
4129
4625
|
},
|
|
4130
|
-
async ({ targetId, kind, name, config, enabled, confirm_token }) =>
|
|
4626
|
+
async ({ targetId, kind, name, config, enabled, confirm_token }) => text10(
|
|
4131
4627
|
await upsertNotificationTarget(
|
|
4132
4628
|
ctx,
|
|
4133
4629
|
{
|
|
@@ -4144,51 +4640,51 @@ function registerNotifierTools(server, ctx) {
|
|
|
4144
4640
|
server.tool(
|
|
4145
4641
|
TEST_TARGET_TOOL,
|
|
4146
4642
|
"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.",
|
|
4147
|
-
{ targetId:
|
|
4148
|
-
async ({ targetId, confirm_token }) =>
|
|
4643
|
+
{ targetId: z15.string().min(1), confirm_token: z15.string().optional() },
|
|
4644
|
+
async ({ targetId, confirm_token }) => text10(await testNotificationTarget(ctx, { targetId }, confirm_token))
|
|
4149
4645
|
);
|
|
4150
4646
|
}
|
|
4151
4647
|
|
|
4152
4648
|
// src/mcp/ops-tools.ts
|
|
4153
|
-
import { z as
|
|
4154
|
-
import { jwtClaims, text as
|
|
4649
|
+
import { z as z27 } from "zod";
|
|
4650
|
+
import { jwtClaims, text as text12 } from "@camstack/develop";
|
|
4155
4651
|
|
|
4156
4652
|
// src/mcp/ops-accelerators.ts
|
|
4157
|
-
import { z as
|
|
4158
|
-
var NodesSchema =
|
|
4159
|
-
|
|
4160
|
-
id:
|
|
4161
|
-
isOnline:
|
|
4162
|
-
name:
|
|
4653
|
+
import { z as z16 } from "zod";
|
|
4654
|
+
var NodesSchema = z16.array(
|
|
4655
|
+
z16.looseObject({
|
|
4656
|
+
id: z16.string(),
|
|
4657
|
+
isOnline: z16.boolean(),
|
|
4658
|
+
name: z16.string().optional()
|
|
4163
4659
|
})
|
|
4164
4660
|
);
|
|
4165
|
-
var HardwareSchema =
|
|
4166
|
-
platform:
|
|
4167
|
-
arch:
|
|
4168
|
-
cpuModel:
|
|
4169
|
-
cpuCores:
|
|
4170
|
-
totalRAM_MB:
|
|
4171
|
-
availableRAM_MB:
|
|
4172
|
-
gpu:
|
|
4173
|
-
type:
|
|
4174
|
-
name:
|
|
4661
|
+
var HardwareSchema = z16.looseObject({
|
|
4662
|
+
platform: z16.enum(["darwin", "linux", "win32"]),
|
|
4663
|
+
arch: z16.enum(["arm64", "x64"]),
|
|
4664
|
+
cpuModel: z16.string(),
|
|
4665
|
+
cpuCores: z16.number(),
|
|
4666
|
+
totalRAM_MB: z16.number(),
|
|
4667
|
+
availableRAM_MB: z16.number(),
|
|
4668
|
+
gpu: z16.looseObject({
|
|
4669
|
+
type: z16.enum(["nvidia", "amd", "intel", "apple"]),
|
|
4670
|
+
name: z16.string()
|
|
4175
4671
|
}).nullable(),
|
|
4176
|
-
npu:
|
|
4177
|
-
type:
|
|
4672
|
+
npu: z16.looseObject({
|
|
4673
|
+
type: z16.enum(["apple-ane", "intel-npu", "amd-xdna"])
|
|
4178
4674
|
}).nullable()
|
|
4179
4675
|
});
|
|
4180
|
-
var InferenceDevicesSchema =
|
|
4181
|
-
reachable:
|
|
4182
|
-
devices:
|
|
4183
|
-
|
|
4184
|
-
key:
|
|
4185
|
-
backend:
|
|
4186
|
-
device:
|
|
4187
|
-
available:
|
|
4188
|
-
presence:
|
|
4189
|
-
enabled:
|
|
4190
|
-
exclusion:
|
|
4191
|
-
defaultModelId:
|
|
4676
|
+
var InferenceDevicesSchema = z16.looseObject({
|
|
4677
|
+
reachable: z16.boolean(),
|
|
4678
|
+
devices: z16.array(
|
|
4679
|
+
z16.looseObject({
|
|
4680
|
+
key: z16.string(),
|
|
4681
|
+
backend: z16.string(),
|
|
4682
|
+
device: z16.string(),
|
|
4683
|
+
available: z16.boolean(),
|
|
4684
|
+
presence: z16.enum(["present", "absent", "unknown"]).optional(),
|
|
4685
|
+
enabled: z16.boolean(),
|
|
4686
|
+
exclusion: z16.string().nullable(),
|
|
4687
|
+
defaultModelId: z16.string()
|
|
4192
4688
|
})
|
|
4193
4689
|
)
|
|
4194
4690
|
});
|
|
@@ -4312,81 +4808,81 @@ async function opsAccelerators(ctx) {
|
|
|
4312
4808
|
}
|
|
4313
4809
|
|
|
4314
4810
|
// src/mcp/ops-device.ts
|
|
4315
|
-
import { z as
|
|
4316
|
-
var DeviceSchema =
|
|
4317
|
-
id:
|
|
4318
|
-
name:
|
|
4319
|
-
type:
|
|
4320
|
-
addonId:
|
|
4321
|
-
location:
|
|
4322
|
-
disabled:
|
|
4323
|
-
online:
|
|
4324
|
-
isCamera:
|
|
4325
|
-
features:
|
|
4811
|
+
import { z as z17 } from "zod";
|
|
4812
|
+
var DeviceSchema = z17.looseObject({
|
|
4813
|
+
id: z17.number(),
|
|
4814
|
+
name: z17.string(),
|
|
4815
|
+
type: z17.string().optional(),
|
|
4816
|
+
addonId: z17.string().optional(),
|
|
4817
|
+
location: z17.string().nullable().optional(),
|
|
4818
|
+
disabled: z17.boolean().optional(),
|
|
4819
|
+
online: z17.boolean().optional(),
|
|
4820
|
+
isCamera: z17.boolean().optional(),
|
|
4821
|
+
features: z17.array(z17.string()).optional()
|
|
4326
4822
|
});
|
|
4327
4823
|
var NullableDeviceSchema = DeviceSchema.nullable();
|
|
4328
|
-
var DeviceListSchema =
|
|
4329
|
-
var BindingsSchema2 =
|
|
4330
|
-
entries:
|
|
4331
|
-
|
|
4332
|
-
capName:
|
|
4333
|
-
kind:
|
|
4334
|
-
providerAddonId:
|
|
4335
|
-
providerNodeId:
|
|
4824
|
+
var DeviceListSchema = z17.array(DeviceSchema);
|
|
4825
|
+
var BindingsSchema2 = z17.looseObject({
|
|
4826
|
+
entries: z17.array(
|
|
4827
|
+
z17.looseObject({
|
|
4828
|
+
capName: z17.string(),
|
|
4829
|
+
kind: z17.string(),
|
|
4830
|
+
providerAddonId: z17.string(),
|
|
4831
|
+
providerNodeId: z17.string()
|
|
4336
4832
|
})
|
|
4337
4833
|
)
|
|
4338
4834
|
});
|
|
4339
|
-
var StatusAggregateSchema =
|
|
4340
|
-
var DegradationSchema =
|
|
4341
|
-
stage:
|
|
4342
|
-
reason:
|
|
4343
|
-
elapsedMs:
|
|
4835
|
+
var StatusAggregateSchema = z17.record(z17.string(), z17.unknown());
|
|
4836
|
+
var DegradationSchema = z17.looseObject({
|
|
4837
|
+
stage: z17.string(),
|
|
4838
|
+
reason: z17.string(),
|
|
4839
|
+
elapsedMs: z17.number().optional()
|
|
4344
4840
|
});
|
|
4345
|
-
var CameraStatusSchema =
|
|
4346
|
-
deviceId:
|
|
4347
|
-
assignment:
|
|
4348
|
-
detectionNodeId:
|
|
4349
|
-
decoderNodeId:
|
|
4350
|
-
sourceNodeId:
|
|
4841
|
+
var CameraStatusSchema = z17.looseObject({
|
|
4842
|
+
deviceId: z17.number(),
|
|
4843
|
+
assignment: z17.looseObject({
|
|
4844
|
+
detectionNodeId: z17.string().nullable(),
|
|
4845
|
+
decoderNodeId: z17.string().nullable(),
|
|
4846
|
+
sourceNodeId: z17.string().nullable(),
|
|
4351
4847
|
/** Why a stage sits where it does; `armed:on-motion` means idle by design. */
|
|
4352
|
-
reasons:
|
|
4848
|
+
reasons: z17.record(z17.string(), z17.string().optional()).optional()
|
|
4353
4849
|
}),
|
|
4354
|
-
broker:
|
|
4355
|
-
profiles:
|
|
4356
|
-
|
|
4357
|
-
profile:
|
|
4358
|
-
status:
|
|
4359
|
-
subscribers:
|
|
4850
|
+
broker: z17.looseObject({
|
|
4851
|
+
profiles: z17.array(
|
|
4852
|
+
z17.looseObject({
|
|
4853
|
+
profile: z17.string(),
|
|
4854
|
+
status: z17.string(),
|
|
4855
|
+
subscribers: z17.number()
|
|
4360
4856
|
})
|
|
4361
4857
|
)
|
|
4362
4858
|
}).nullable(),
|
|
4363
|
-
detection:
|
|
4364
|
-
nodeId:
|
|
4365
|
-
phase:
|
|
4366
|
-
actualFps:
|
|
4367
|
-
configuredFps:
|
|
4368
|
-
provisioning:
|
|
4369
|
-
state:
|
|
4370
|
-
error:
|
|
4859
|
+
detection: z17.looseObject({
|
|
4860
|
+
nodeId: z17.string(),
|
|
4861
|
+
phase: z17.string(),
|
|
4862
|
+
actualFps: z17.number(),
|
|
4863
|
+
configuredFps: z17.number(),
|
|
4864
|
+
provisioning: z17.looseObject({
|
|
4865
|
+
state: z17.string(),
|
|
4866
|
+
error: z17.string().optional()
|
|
4371
4867
|
})
|
|
4372
4868
|
}).nullable(),
|
|
4373
|
-
recording:
|
|
4374
|
-
mode:
|
|
4375
|
-
active:
|
|
4376
|
-
storageBytes:
|
|
4869
|
+
recording: z17.looseObject({
|
|
4870
|
+
mode: z17.string(),
|
|
4871
|
+
active: z17.boolean(),
|
|
4872
|
+
storageBytes: z17.number()
|
|
4377
4873
|
}).nullable(),
|
|
4378
|
-
switchedOff:
|
|
4379
|
-
degraded:
|
|
4380
|
-
fetchedAt:
|
|
4874
|
+
switchedOff: z17.array(z17.string()),
|
|
4875
|
+
degraded: z17.array(DegradationSchema),
|
|
4876
|
+
fetchedAt: z17.number()
|
|
4381
4877
|
});
|
|
4382
|
-
var CameraStatusListSchema =
|
|
4383
|
-
var ObjectEventSchema =
|
|
4384
|
-
id:
|
|
4385
|
-
timestamp:
|
|
4386
|
-
className:
|
|
4387
|
-
trackId:
|
|
4878
|
+
var CameraStatusListSchema = z17.array(CameraStatusSchema);
|
|
4879
|
+
var ObjectEventSchema = z17.looseObject({
|
|
4880
|
+
id: z17.number(),
|
|
4881
|
+
timestamp: z17.number(),
|
|
4882
|
+
className: z17.string().optional(),
|
|
4883
|
+
trackId: z17.string().optional()
|
|
4388
4884
|
});
|
|
4389
|
-
var ObjectEventsSchema2 =
|
|
4885
|
+
var ObjectEventsSchema2 = z17.array(ObjectEventSchema);
|
|
4390
4886
|
var BINDING_LIMIT = 40;
|
|
4391
4887
|
var EVENT_LIMIT = 5;
|
|
4392
4888
|
var UNHEALTHY_LIMIT = 50;
|
|
@@ -4394,9 +4890,9 @@ function freshness(aggregate, now) {
|
|
|
4394
4890
|
const fetched = [];
|
|
4395
4891
|
let changed = null;
|
|
4396
4892
|
for (const [capName, value] of Object.entries(aggregate)) {
|
|
4397
|
-
const parsed =
|
|
4398
|
-
lastFetchedAt:
|
|
4399
|
-
lastChangedAt:
|
|
4893
|
+
const parsed = z17.looseObject({
|
|
4894
|
+
lastFetchedAt: z17.number().optional(),
|
|
4895
|
+
lastChangedAt: z17.number().optional()
|
|
4400
4896
|
}).safeParse(value);
|
|
4401
4897
|
if (!parsed.success) continue;
|
|
4402
4898
|
const at = parsed.data.lastFetchedAt;
|
|
@@ -4647,26 +5143,26 @@ async function opsUnhealthyDevices(ctx) {
|
|
|
4647
5143
|
}
|
|
4648
5144
|
|
|
4649
5145
|
// src/mcp/ops-logs.ts
|
|
4650
|
-
import { z as
|
|
4651
|
-
var LevelSchema =
|
|
4652
|
-
var LogEntrySchema =
|
|
4653
|
-
timestamp:
|
|
4654
|
-
level:
|
|
4655
|
-
scope:
|
|
4656
|
-
message:
|
|
4657
|
-
tags:
|
|
4658
|
-
meta:
|
|
5146
|
+
import { z as z18 } from "zod";
|
|
5147
|
+
var LevelSchema = z18.enum(["debug", "info", "warn", "error"]);
|
|
5148
|
+
var LogEntrySchema = z18.looseObject({
|
|
5149
|
+
timestamp: z18.string(),
|
|
5150
|
+
level: z18.string(),
|
|
5151
|
+
scope: z18.string().optional(),
|
|
5152
|
+
message: z18.string(),
|
|
5153
|
+
tags: z18.record(z18.string(), z18.string().optional()).optional(),
|
|
5154
|
+
meta: z18.record(z18.string(), z18.unknown()).optional()
|
|
4659
5155
|
});
|
|
4660
|
-
var LogPageSchema =
|
|
4661
|
-
entries:
|
|
4662
|
-
done:
|
|
4663
|
-
source:
|
|
5156
|
+
var LogPageSchema = z18.looseObject({
|
|
5157
|
+
entries: z18.array(LogEntrySchema),
|
|
5158
|
+
done: z18.boolean(),
|
|
5159
|
+
source: z18.string()
|
|
4664
5160
|
});
|
|
4665
|
-
var LogSourcesSchema =
|
|
4666
|
-
|
|
4667
|
-
addonId:
|
|
4668
|
-
readable:
|
|
4669
|
-
isDefault:
|
|
5161
|
+
var LogSourcesSchema = z18.array(
|
|
5162
|
+
z18.looseObject({
|
|
5163
|
+
addonId: z18.string(),
|
|
5164
|
+
readable: z18.boolean(),
|
|
5165
|
+
isDefault: z18.boolean()
|
|
4670
5166
|
})
|
|
4671
5167
|
);
|
|
4672
5168
|
var RING_SOURCE = "ring";
|
|
@@ -4881,37 +5377,37 @@ async function opsLogs(ctx, args, now = Date.now()) {
|
|
|
4881
5377
|
}
|
|
4882
5378
|
|
|
4883
5379
|
// src/mcp/ops-status.ts
|
|
4884
|
-
import { z as
|
|
4885
|
-
var TopologyNodeSchema =
|
|
4886
|
-
id:
|
|
4887
|
-
name:
|
|
4888
|
-
platform:
|
|
4889
|
-
isHub:
|
|
4890
|
-
isOnline:
|
|
4891
|
-
addons:
|
|
5380
|
+
import { z as z19 } from "zod";
|
|
5381
|
+
var TopologyNodeSchema = z19.looseObject({
|
|
5382
|
+
id: z19.string(),
|
|
5383
|
+
name: z19.string().optional(),
|
|
5384
|
+
platform: z19.string().optional(),
|
|
5385
|
+
isHub: z19.boolean().optional(),
|
|
5386
|
+
isOnline: z19.boolean(),
|
|
5387
|
+
addons: z19.array(z19.looseObject({ capabilities: z19.array(z19.string()) })).optional(),
|
|
4892
5388
|
/** Agents report it; the hub does not. Absent means "not claimed". */
|
|
4893
|
-
capabilitiesRegistered:
|
|
5389
|
+
capabilitiesRegistered: z19.boolean().optional()
|
|
4894
5390
|
});
|
|
4895
|
-
var TopologySchema =
|
|
4896
|
-
var ServerStatusSchema =
|
|
4897
|
-
runningVersion:
|
|
4898
|
-
restartRequired:
|
|
4899
|
-
updateAvailable:
|
|
4900
|
-
updateState:
|
|
5391
|
+
var TopologySchema = z19.array(TopologyNodeSchema);
|
|
5392
|
+
var ServerStatusSchema = z19.looseObject({
|
|
5393
|
+
runningVersion: z19.string(),
|
|
5394
|
+
restartRequired: z19.boolean().optional(),
|
|
5395
|
+
updateAvailable: z19.boolean().optional(),
|
|
5396
|
+
updateState: z19.string().optional()
|
|
4901
5397
|
});
|
|
4902
|
-
var AlertSchema =
|
|
4903
|
-
severity:
|
|
4904
|
-
title:
|
|
4905
|
-
message:
|
|
4906
|
-
status:
|
|
5398
|
+
var AlertSchema = z19.looseObject({
|
|
5399
|
+
severity: z19.string(),
|
|
5400
|
+
title: z19.string(),
|
|
5401
|
+
message: z19.string().optional(),
|
|
5402
|
+
status: z19.string()
|
|
4907
5403
|
});
|
|
4908
|
-
var AlertsSchema =
|
|
4909
|
-
var AddonUpdateSchema =
|
|
4910
|
-
name:
|
|
4911
|
-
currentVersion:
|
|
4912
|
-
latestVersion:
|
|
5404
|
+
var AlertsSchema = z19.array(AlertSchema);
|
|
5405
|
+
var AddonUpdateSchema = z19.looseObject({
|
|
5406
|
+
name: z19.string(),
|
|
5407
|
+
currentVersion: z19.string(),
|
|
5408
|
+
latestVersion: z19.string()
|
|
4913
5409
|
});
|
|
4914
|
-
var AddonUpdatesSchema =
|
|
5410
|
+
var AddonUpdatesSchema = z19.array(AddonUpdateSchema);
|
|
4915
5411
|
var NODE_LIMIT2 = 40;
|
|
4916
5412
|
var ALERT_LIMIT = 10;
|
|
4917
5413
|
var UPDATE_LIMIT = 20;
|
|
@@ -5044,65 +5540,65 @@ async function opsStatus(ctx) {
|
|
|
5044
5540
|
import {
|
|
5045
5541
|
runGated as runGated3
|
|
5046
5542
|
} from "@camstack/develop";
|
|
5047
|
-
import { z as
|
|
5543
|
+
import { z as z25 } from "zod";
|
|
5048
5544
|
|
|
5049
5545
|
// src/mcp/ops-update-model.ts
|
|
5050
|
-
import { z as
|
|
5051
|
-
var ImageContractShape =
|
|
5052
|
-
state:
|
|
5053
|
-
seedVersion:
|
|
5054
|
-
contractVersion:
|
|
5055
|
-
message:
|
|
5546
|
+
import { z as z20 } from "zod";
|
|
5547
|
+
var ImageContractShape = z20.looseObject({
|
|
5548
|
+
state: z20.enum(["in-sync", "behind-patch", "behind-series", "ahead", "unknown"]),
|
|
5549
|
+
seedVersion: z20.string().nullable(),
|
|
5550
|
+
contractVersion: z20.string().nullable(),
|
|
5551
|
+
message: z20.string().optional()
|
|
5056
5552
|
});
|
|
5057
|
-
var WrapperShape =
|
|
5058
|
-
kind:
|
|
5059
|
-
currentVersion:
|
|
5553
|
+
var WrapperShape = z20.looseObject({
|
|
5554
|
+
kind: z20.enum(["docker", "electron", "native", "contradictory", "unknown"]),
|
|
5555
|
+
currentVersion: z20.string().nullable().optional()
|
|
5060
5556
|
});
|
|
5061
|
-
var ServerStatusShape2 =
|
|
5062
|
-
runningVersion:
|
|
5063
|
-
installedVersion:
|
|
5064
|
-
latestVersion:
|
|
5065
|
-
pendingVersion:
|
|
5066
|
-
restartRequired:
|
|
5067
|
-
updateAvailable:
|
|
5068
|
-
updateState:
|
|
5069
|
-
lastCheckedAtMs:
|
|
5070
|
-
rolledBack:
|
|
5071
|
-
fromVersion:
|
|
5072
|
-
toVersion:
|
|
5073
|
-
reason:
|
|
5557
|
+
var ServerStatusShape2 = z20.looseObject({
|
|
5558
|
+
runningVersion: z20.string().nullable(),
|
|
5559
|
+
installedVersion: z20.string().nullable().optional(),
|
|
5560
|
+
latestVersion: z20.string().nullable().optional(),
|
|
5561
|
+
pendingVersion: z20.string().nullable().optional(),
|
|
5562
|
+
restartRequired: z20.boolean().optional(),
|
|
5563
|
+
updateAvailable: z20.boolean().optional(),
|
|
5564
|
+
updateState: z20.string().optional(),
|
|
5565
|
+
lastCheckedAtMs: z20.number().nullable().optional(),
|
|
5566
|
+
rolledBack: z20.looseObject({
|
|
5567
|
+
fromVersion: z20.string(),
|
|
5568
|
+
toVersion: z20.string().nullable(),
|
|
5569
|
+
reason: z20.string()
|
|
5074
5570
|
}).nullable().optional(),
|
|
5075
5571
|
imageContract: ImageContractShape.optional(),
|
|
5076
5572
|
wrapper: WrapperShape.optional()
|
|
5077
5573
|
});
|
|
5078
|
-
var AddonUpdateShape =
|
|
5079
|
-
name:
|
|
5080
|
-
currentVersion:
|
|
5081
|
-
latestVersion:
|
|
5082
|
-
category:
|
|
5083
|
-
requiresRestart:
|
|
5084
|
-
isSystem:
|
|
5574
|
+
var AddonUpdateShape = z20.looseObject({
|
|
5575
|
+
name: z20.string(),
|
|
5576
|
+
currentVersion: z20.string(),
|
|
5577
|
+
latestVersion: z20.string(),
|
|
5578
|
+
category: z20.enum(["addon", "core"]),
|
|
5579
|
+
requiresRestart: z20.boolean().optional(),
|
|
5580
|
+
isSystem: z20.boolean()
|
|
5085
5581
|
});
|
|
5086
|
-
var InstallationsShape2 =
|
|
5087
|
-
installations:
|
|
5088
|
-
|
|
5089
|
-
packageName:
|
|
5090
|
-
nodeId:
|
|
5091
|
-
isHub:
|
|
5092
|
-
isOnline:
|
|
5093
|
-
version:
|
|
5094
|
-
onHub:
|
|
5582
|
+
var InstallationsShape2 = z20.looseObject({
|
|
5583
|
+
installations: z20.array(
|
|
5584
|
+
z20.looseObject({
|
|
5585
|
+
packageName: z20.string(),
|
|
5586
|
+
nodeId: z20.string(),
|
|
5587
|
+
isHub: z20.boolean(),
|
|
5588
|
+
isOnline: z20.boolean(),
|
|
5589
|
+
version: z20.string().nullable(),
|
|
5590
|
+
onHub: z20.boolean().optional()
|
|
5095
5591
|
})
|
|
5096
5592
|
),
|
|
5097
|
-
unreachable:
|
|
5593
|
+
unreachable: z20.array(z20.looseObject({ nodeId: z20.string(), error: z20.string() }))
|
|
5098
5594
|
});
|
|
5099
|
-
var ServerActionShape =
|
|
5100
|
-
accepted:
|
|
5101
|
-
outcome:
|
|
5102
|
-
reason:
|
|
5103
|
-
error:
|
|
5104
|
-
message:
|
|
5105
|
-
blockers:
|
|
5595
|
+
var ServerActionShape = z20.looseObject({
|
|
5596
|
+
accepted: z20.boolean().optional(),
|
|
5597
|
+
outcome: z20.enum(["staged", "restart-scheduled", "already-current", "refused", "failed"]).optional(),
|
|
5598
|
+
reason: z20.string().nullable().optional(),
|
|
5599
|
+
error: z20.string().nullable().optional(),
|
|
5600
|
+
message: z20.string().optional(),
|
|
5601
|
+
blockers: z20.array(z20.looseObject({ addonId: z20.string(), reason: z20.string() })).optional()
|
|
5106
5602
|
});
|
|
5107
5603
|
function statusInput(node) {
|
|
5108
5604
|
return node.isHub === true ? void 0 : { nodeId: node.id };
|
|
@@ -5221,8 +5717,8 @@ function addonDrift(installations) {
|
|
|
5221
5717
|
import { execFile } from "child_process";
|
|
5222
5718
|
|
|
5223
5719
|
// src/mcp/setup-tools.ts
|
|
5224
|
-
import { text as
|
|
5225
|
-
import { z as
|
|
5720
|
+
import { text as text11 } from "@camstack/develop";
|
|
5721
|
+
import { z as z24 } from "zod";
|
|
5226
5722
|
|
|
5227
5723
|
// src/mcp/setup-hubs.ts
|
|
5228
5724
|
import * as https from "https";
|
|
@@ -5235,13 +5731,13 @@ import {
|
|
|
5235
5731
|
normalizeHubAddress,
|
|
5236
5732
|
resolveHubTls
|
|
5237
5733
|
} from "@camstack/develop";
|
|
5238
|
-
import { z as
|
|
5734
|
+
import { z as z23 } from "zod";
|
|
5239
5735
|
|
|
5240
5736
|
// src/mcp/setup-install.ts
|
|
5241
|
-
import { z as
|
|
5737
|
+
import { z as z22 } from "zod";
|
|
5242
5738
|
|
|
5243
5739
|
// src/mcp/setup-install-rules.ts
|
|
5244
|
-
import { z as
|
|
5740
|
+
import { z as z21 } from "zod";
|
|
5245
5741
|
var HOST_KINDS = [
|
|
5246
5742
|
"docker-compose",
|
|
5247
5743
|
"docker",
|
|
@@ -5249,24 +5745,24 @@ var HOST_KINDS = [
|
|
|
5249
5745
|
"proxmox-lxc",
|
|
5250
5746
|
"macos-desktop"
|
|
5251
5747
|
];
|
|
5252
|
-
var HostKindSchema =
|
|
5748
|
+
var HostKindSchema = z21.enum(HOST_KINDS);
|
|
5253
5749
|
var ROLES = ["hub", "agent"];
|
|
5254
|
-
var RoleSchema =
|
|
5750
|
+
var RoleSchema = z21.enum(ROLES);
|
|
5255
5751
|
var ACCELERATORS = ["intel", "amd", "coral", "apple", "cpu"];
|
|
5256
|
-
var AcceleratorSchema =
|
|
5257
|
-
var InstallPathsSchema =
|
|
5258
|
-
data:
|
|
5259
|
-
recordings:
|
|
5260
|
-
config:
|
|
5261
|
-
backups:
|
|
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()
|
|
5262
5758
|
});
|
|
5263
|
-
var InstallGuideInputSchema =
|
|
5759
|
+
var InstallGuideInputSchema = z21.object({
|
|
5264
5760
|
hostKind: HostKindSchema.optional(),
|
|
5265
5761
|
role: RoleSchema.optional(),
|
|
5266
5762
|
accelerator: AcceleratorSchema.optional(),
|
|
5267
5763
|
paths: InstallPathsSchema.optional(),
|
|
5268
5764
|
/** Agents only: the hub the agent should dial (host or IP). */
|
|
5269
|
-
hubAddress:
|
|
5765
|
+
hubAddress: z21.string().min(1).optional()
|
|
5270
5766
|
});
|
|
5271
5767
|
var RAW = "https://raw.githubusercontent.com/camstack";
|
|
5272
5768
|
var TEMPLATE_URLS = {
|
|
@@ -5405,20 +5901,20 @@ function readService(body) {
|
|
|
5405
5901
|
if (raw.trim() === "" || raw.trim().startsWith("#")) continue;
|
|
5406
5902
|
const line = stripComment(raw);
|
|
5407
5903
|
const indent = line.length - line.trimStart().length;
|
|
5408
|
-
const
|
|
5904
|
+
const text15 = line.trim();
|
|
5409
5905
|
if (indent === 4) {
|
|
5410
5906
|
sub = null;
|
|
5411
|
-
const m = /^([A-Za-z_]+):\s*(.*)$/.exec(
|
|
5907
|
+
const m = /^([A-Za-z_]+):\s*(.*)$/.exec(text15);
|
|
5412
5908
|
if (m === null) continue;
|
|
5413
5909
|
key = m[1] ?? null;
|
|
5414
5910
|
if (key !== null && (m[2] ?? "") !== "") fields.scalars.set(key, unquote(m[2] ?? ""));
|
|
5415
5911
|
} else if (indent === 6 && key !== null) {
|
|
5416
|
-
if (
|
|
5912
|
+
if (text15.startsWith("- ")) {
|
|
5417
5913
|
const list = fields.lists.get(key) ?? [];
|
|
5418
|
-
list.push(unquote(
|
|
5914
|
+
list.push(unquote(text15.slice(2)));
|
|
5419
5915
|
fields.lists.set(key, list);
|
|
5420
5916
|
} else {
|
|
5421
|
-
const m = /^([A-Za-z_-]+):\s*(.*)$/.exec(
|
|
5917
|
+
const m = /^([A-Za-z_-]+):\s*(.*)$/.exec(text15);
|
|
5422
5918
|
if (m === null) continue;
|
|
5423
5919
|
sub = m[1] ?? null;
|
|
5424
5920
|
const map = fields.maps.get(key) ?? /* @__PURE__ */ new Map();
|
|
@@ -5426,7 +5922,7 @@ function readService(body) {
|
|
|
5426
5922
|
fields.maps.set(key, map);
|
|
5427
5923
|
}
|
|
5428
5924
|
} else if (indent === 8 && key !== null && sub !== null) {
|
|
5429
|
-
const m = /^([A-Za-z_-]+):\s*(.*)$/.exec(
|
|
5925
|
+
const m = /^([A-Za-z_-]+):\s*(.*)$/.exec(text15);
|
|
5430
5926
|
if (m === null) continue;
|
|
5431
5927
|
const map = fields.maps.get(`${key}.${sub}`) ?? /* @__PURE__ */ new Map();
|
|
5432
5928
|
map.set(m[1] ?? "", unquote(m[2] ?? ""));
|
|
@@ -5526,9 +6022,9 @@ async function fetchText(url, fetchFn) {
|
|
|
5526
6022
|
return { ok: false, failure: { status: "fetch-failed", url, error } };
|
|
5527
6023
|
}
|
|
5528
6024
|
}
|
|
5529
|
-
var ReleaseSchema =
|
|
5530
|
-
tag_name:
|
|
5531
|
-
assets:
|
|
6025
|
+
var ReleaseSchema = z22.object({
|
|
6026
|
+
tag_name: z22.string(),
|
|
6027
|
+
assets: z22.array(z22.object({ name: z22.string(), browser_download_url: z22.string() }))
|
|
5532
6028
|
});
|
|
5533
6029
|
var DMG = /^CamStack-[0-9][0-9A-Za-z.-]*-(arm64|x64)\.dmg$/;
|
|
5534
6030
|
function acceleratorEdits(accelerator, role) {
|
|
@@ -5819,15 +6315,15 @@ async function connectTool(server, deps) {
|
|
|
5819
6315
|
next: session === null ? LOGIN(url) : "a session exists: the ops tools can use it"
|
|
5820
6316
|
};
|
|
5821
6317
|
}
|
|
5822
|
-
var DiscoveredAgentsSchema =
|
|
5823
|
-
agents:
|
|
5824
|
-
|
|
5825
|
-
nodeId:
|
|
5826
|
-
host:
|
|
5827
|
-
port:
|
|
5828
|
-
version:
|
|
5829
|
-
name:
|
|
5830
|
-
state:
|
|
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"])
|
|
5831
6327
|
})
|
|
5832
6328
|
)
|
|
5833
6329
|
});
|
|
@@ -5874,7 +6370,7 @@ async function addNodeTool(input, ctx) {
|
|
|
5874
6370
|
var SUPPORTED_HOST_KINDS = HOST_KINDS;
|
|
5875
6371
|
var REGISTRY2 = "https://registry.npmjs.org";
|
|
5876
6372
|
var REGISTRY_TIMEOUT_MS2 = 5e3;
|
|
5877
|
-
var RegistryLatestSchema =
|
|
6373
|
+
var RegistryLatestSchema = z24.object({ version: z24.string() });
|
|
5878
6374
|
async function latestOnNpm(name, fetchFn) {
|
|
5879
6375
|
const url = `${REGISTRY2}/${name.replace("/", "%2f")}/latest`;
|
|
5880
6376
|
try {
|
|
@@ -5908,7 +6404,7 @@ function registerSetupTools(server, ctx, deps = {}) {
|
|
|
5908
6404
|
"setup_about",
|
|
5909
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.",
|
|
5910
6406
|
{},
|
|
5911
|
-
async () =>
|
|
6407
|
+
async () => text11(await about(ctx))
|
|
5912
6408
|
);
|
|
5913
6409
|
server.tool(
|
|
5914
6410
|
"setup_install_guide",
|
|
@@ -5918,21 +6414,21 @@ function registerSetupTools(server, ctx, deps = {}) {
|
|
|
5918
6414
|
role: RoleSchema.optional(),
|
|
5919
6415
|
accelerator: AcceleratorSchema.optional(),
|
|
5920
6416
|
paths: InstallPathsSchema.optional(),
|
|
5921
|
-
hubAddress:
|
|
6417
|
+
hubAddress: z24.string().min(1).optional().describe("Agents only: the hub to dial.")
|
|
5922
6418
|
},
|
|
5923
|
-
async (input) =>
|
|
6419
|
+
async (input) => text11(await installGuide(input, ctx.fetch))
|
|
5924
6420
|
);
|
|
5925
6421
|
server.tool(
|
|
5926
6422
|
"setup_discover_hubs",
|
|
5927
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.",
|
|
5928
|
-
{ timeoutMs:
|
|
5929
|
-
async ({ timeoutMs }) =>
|
|
6424
|
+
{ timeoutMs: z24.number().int().min(200).max(15e3).optional() },
|
|
6425
|
+
async ({ timeoutMs }) => text11(await discoverHubsTool(timeoutMs, deps))
|
|
5930
6426
|
);
|
|
5931
6427
|
server.tool(
|
|
5932
6428
|
"setup_connect",
|
|
5933
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.",
|
|
5934
|
-
{ server:
|
|
5935
|
-
async ({ server: address }) =>
|
|
6430
|
+
{ server: z24.string().min(1) },
|
|
6431
|
+
async ({ server: address }) => text11(await connectTool(address, deps))
|
|
5936
6432
|
);
|
|
5937
6433
|
server.tool(
|
|
5938
6434
|
"setup_add_node",
|
|
@@ -5941,9 +6437,9 @@ function registerSetupTools(server, ctx, deps = {}) {
|
|
|
5941
6437
|
hostKind: HostKindSchema.optional(),
|
|
5942
6438
|
accelerator: AcceleratorSchema.optional(),
|
|
5943
6439
|
paths: InstallPathsSchema.optional(),
|
|
5944
|
-
hubAddress:
|
|
6440
|
+
hubAddress: z24.string().min(1).optional()
|
|
5945
6441
|
},
|
|
5946
|
-
async (input) =>
|
|
6442
|
+
async (input) => text11(await addNodeTool(input, ctx))
|
|
5947
6443
|
);
|
|
5948
6444
|
}
|
|
5949
6445
|
|
|
@@ -6053,7 +6549,7 @@ async function readPlan(client, args, ctx, deps) {
|
|
|
6053
6549
|
wrapper: wrapperVerdict(status.value)
|
|
6054
6550
|
});
|
|
6055
6551
|
}
|
|
6056
|
-
const updates = args.addons === false ? null : await readQuery(client, "addons.listUpdates", {},
|
|
6552
|
+
const updates = args.addons === false ? null : await readQuery(client, "addons.listUpdates", {}, z25.array(AddonUpdateShape));
|
|
6057
6553
|
const cli = args.cli === false ? null : await cliVerdict(ctx.cliVersion, ctx.fetch, deps.entryPath ?? process.argv[1] ?? "");
|
|
6058
6554
|
const hub = plans.find((p) => p.node.isHub === true) ?? null;
|
|
6059
6555
|
return {
|
|
@@ -6369,14 +6865,14 @@ async function opsUpdateApply(ctx, args, confirmToken, deps = {}) {
|
|
|
6369
6865
|
}
|
|
6370
6866
|
|
|
6371
6867
|
// src/mcp/ops-update-check.ts
|
|
6372
|
-
import { z as
|
|
6868
|
+
import { z as z26 } from "zod";
|
|
6373
6869
|
var ADDON_LIMIT = 30;
|
|
6374
6870
|
async function opsUpdateCheck(ctx, deps = {}) {
|
|
6375
6871
|
return withAdminHub(ctx, async ({ client, server }) => {
|
|
6376
6872
|
const unavailable = {};
|
|
6377
6873
|
const [topology, updates, installs, cli] = await Promise.all([
|
|
6378
6874
|
readTopology(client),
|
|
6379
|
-
readQuery(client, "addons.listUpdates", {},
|
|
6875
|
+
readQuery(client, "addons.listUpdates", {}, z26.array(AddonUpdateShape)),
|
|
6380
6876
|
readInstallations(client),
|
|
6381
6877
|
cliVerdict(ctx.cliVersion, ctx.fetch, deps.entryPath ?? process.argv[1] ?? "")
|
|
6382
6878
|
]);
|
|
@@ -6494,40 +6990,40 @@ function registerOpsTools(server, ctx) {
|
|
|
6494
6990
|
"ops_whoami",
|
|
6495
6991
|
"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.",
|
|
6496
6992
|
{},
|
|
6497
|
-
async () =>
|
|
6993
|
+
async () => text12(await whoami(ctx))
|
|
6498
6994
|
);
|
|
6499
6995
|
server.tool(
|
|
6500
6996
|
"ops_status",
|
|
6501
6997
|
'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`.',
|
|
6502
6998
|
{},
|
|
6503
|
-
async () =>
|
|
6999
|
+
async () => text12(await opsStatus(ctx))
|
|
6504
7000
|
);
|
|
6505
7001
|
server.tool(
|
|
6506
7002
|
"ops_device",
|
|
6507
7003
|
"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.",
|
|
6508
|
-
{ deviceId:
|
|
6509
|
-
async ({ deviceId }) =>
|
|
7004
|
+
{ deviceId: z27.number().int() },
|
|
7005
|
+
async ({ deviceId }) => text12(await opsDevice(ctx, deviceId))
|
|
6510
7006
|
);
|
|
6511
7007
|
server.tool(
|
|
6512
7008
|
"ops_unhealthy_devices",
|
|
6513
7009
|
"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`.",
|
|
6514
7010
|
{},
|
|
6515
|
-
async () =>
|
|
7011
|
+
async () => text12(await opsUnhealthyDevices(ctx))
|
|
6516
7012
|
);
|
|
6517
7013
|
server.tool(
|
|
6518
7014
|
"ops_logs",
|
|
6519
7015
|
"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`.",
|
|
6520
7016
|
{
|
|
6521
|
-
deviceId:
|
|
6522
|
-
addonId:
|
|
6523
|
-
nodeId:
|
|
6524
|
-
level:
|
|
6525
|
-
windowMinutes:
|
|
6526
|
-
text:
|
|
6527
|
-
around:
|
|
6528
|
-
contextLines:
|
|
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()
|
|
6529
7025
|
},
|
|
6530
|
-
async (args) =>
|
|
7026
|
+
async (args) => text12(
|
|
6531
7027
|
await opsLogs(ctx, {
|
|
6532
7028
|
...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
|
|
6533
7029
|
...args.addonId ? { addonId: args.addonId } : {},
|
|
@@ -6544,14 +7040,14 @@ function registerOpsTools(server, ctx) {
|
|
|
6544
7040
|
"ops_events",
|
|
6545
7041
|
"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.",
|
|
6546
7042
|
{
|
|
6547
|
-
deviceId:
|
|
6548
|
-
className:
|
|
6549
|
-
zone:
|
|
6550
|
-
text:
|
|
6551
|
-
windowMinutes:
|
|
6552
|
-
limit:
|
|
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()
|
|
6553
7049
|
},
|
|
6554
|
-
async (args) =>
|
|
7050
|
+
async (args) => text12(
|
|
6555
7051
|
await opsEvents(ctx, {
|
|
6556
7052
|
...args.deviceId !== void 0 ? { deviceId: args.deviceId } : {},
|
|
6557
7053
|
...args.className ? { className: args.className } : {},
|
|
@@ -6565,31 +7061,31 @@ function registerOpsTools(server, ctx) {
|
|
|
6565
7061
|
server.tool(
|
|
6566
7062
|
"ops_track",
|
|
6567
7063
|
"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.",
|
|
6568
|
-
{ deviceId:
|
|
6569
|
-
async ({ deviceId, trackId }) =>
|
|
7064
|
+
{ deviceId: z27.number().int(), trackId: z27.string().min(1) },
|
|
7065
|
+
async ({ deviceId, trackId }) => text12(await opsTrack(ctx, { deviceId, trackId }))
|
|
6570
7066
|
);
|
|
6571
7067
|
server.tool(
|
|
6572
7068
|
"ops_accelerators",
|
|
6573
7069
|
'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`.',
|
|
6574
7070
|
{},
|
|
6575
|
-
async () =>
|
|
7071
|
+
async () => text12(await opsAccelerators(ctx))
|
|
6576
7072
|
);
|
|
6577
7073
|
server.tool(
|
|
6578
7074
|
"ops_update_check",
|
|
6579
7075
|
'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`.',
|
|
6580
7076
|
{},
|
|
6581
|
-
async () =>
|
|
7077
|
+
async () => text12(await opsUpdateCheck(ctx))
|
|
6582
7078
|
);
|
|
6583
7079
|
server.tool(
|
|
6584
7080
|
UPDATE_APPLY_TOOL,
|
|
6585
7081
|
"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`.",
|
|
6586
7082
|
{
|
|
6587
|
-
version:
|
|
6588
|
-
addons:
|
|
6589
|
-
cli:
|
|
6590
|
-
confirm_token:
|
|
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()
|
|
6591
7087
|
},
|
|
6592
|
-
async ({ version, addons, cli, confirm_token }) =>
|
|
7088
|
+
async ({ version, addons, cli, confirm_token }) => text12(
|
|
6593
7089
|
await opsUpdateApply(
|
|
6594
7090
|
ctx,
|
|
6595
7091
|
{
|
|
@@ -6604,7 +7100,7 @@ function registerOpsTools(server, ctx) {
|
|
|
6604
7100
|
}
|
|
6605
7101
|
|
|
6606
7102
|
// src/mcp/configure-tools.ts
|
|
6607
|
-
var CurrentDeviceSchema =
|
|
7103
|
+
var CurrentDeviceSchema = z28.object({ name: z28.string() }).nullable();
|
|
6608
7104
|
var RENAME_DEVICE_TOOL = "configure_rename_device";
|
|
6609
7105
|
function renameDeviceSpec(client) {
|
|
6610
7106
|
return {
|
|
@@ -6666,11 +7162,11 @@ function registerConfigureTools(server, ctx) {
|
|
|
6666
7162
|
RENAME_DEVICE_TOOL,
|
|
6667
7163
|
"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.",
|
|
6668
7164
|
{
|
|
6669
|
-
deviceId:
|
|
6670
|
-
name:
|
|
6671
|
-
confirm_token:
|
|
7165
|
+
deviceId: z28.number().int(),
|
|
7166
|
+
name: z28.string().min(1),
|
|
7167
|
+
confirm_token: z28.string().optional()
|
|
6672
7168
|
},
|
|
6673
|
-
async ({ deviceId, name, confirm_token }) =>
|
|
7169
|
+
async ({ deviceId, name, confirm_token }) => text13(await renameDevice(ctx, { deviceId, name }, confirm_token))
|
|
6674
7170
|
);
|
|
6675
7171
|
registerAddonTools(server, ctx);
|
|
6676
7172
|
registerCameraTools(server, ctx);
|
|
@@ -6679,11 +7175,11 @@ function registerConfigureTools(server, ctx) {
|
|
|
6679
7175
|
}
|
|
6680
7176
|
|
|
6681
7177
|
// src/mcp/support-tools.ts
|
|
6682
|
-
import { text as
|
|
6683
|
-
import { z as
|
|
7178
|
+
import { text as text14 } from "@camstack/develop";
|
|
7179
|
+
import { z as z33 } from "zod";
|
|
6684
7180
|
|
|
6685
7181
|
// src/mcp/support-accelerator.ts
|
|
6686
|
-
import { z as
|
|
7182
|
+
import { z as z29 } from "zod";
|
|
6687
7183
|
var EXPECTED_ACCELERATORS = [
|
|
6688
7184
|
"intel-igpu",
|
|
6689
7185
|
"intel-npu",
|
|
@@ -6695,23 +7191,23 @@ var EXPECTED_ACCELERATORS = [
|
|
|
6695
7191
|
"apple-ane",
|
|
6696
7192
|
"nvidia"
|
|
6697
7193
|
];
|
|
6698
|
-
var ExpectedAcceleratorSchema =
|
|
6699
|
-
var HardwareSchema2 =
|
|
6700
|
-
platform:
|
|
6701
|
-
arch:
|
|
6702
|
-
cpuModel:
|
|
6703
|
-
gpu:
|
|
6704
|
-
npu:
|
|
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()
|
|
6705
7201
|
});
|
|
6706
|
-
var InferenceDevicesSchema2 =
|
|
6707
|
-
reachable:
|
|
6708
|
-
devices:
|
|
6709
|
-
|
|
6710
|
-
key:
|
|
6711
|
-
available:
|
|
6712
|
-
presence:
|
|
6713
|
-
enabled:
|
|
6714
|
-
exclusion:
|
|
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()
|
|
6715
7211
|
})
|
|
6716
7212
|
)
|
|
6717
7213
|
});
|
|
@@ -6990,7 +7486,7 @@ async function supportDiagnoseAccelerator(ctx, args) {
|
|
|
6990
7486
|
import {
|
|
6991
7487
|
runGated as runGated5
|
|
6992
7488
|
} from "@camstack/develop";
|
|
6993
|
-
import { z as
|
|
7489
|
+
import { z as z30 } from "zod";
|
|
6994
7490
|
var BENCHMARK_RUN_TOOL = "support_benchmark_run";
|
|
6995
7491
|
var AUTO_TUNE_MODELS = [
|
|
6996
7492
|
"yolov9t-320",
|
|
@@ -7012,64 +7508,64 @@ var CONTRIBUTE_HOW = [
|
|
|
7012
7508
|
"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.",
|
|
7013
7509
|
'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.'
|
|
7014
7510
|
];
|
|
7015
|
-
var HardwareSchema3 =
|
|
7016
|
-
platform:
|
|
7017
|
-
arch:
|
|
7018
|
-
cpuModel:
|
|
7019
|
-
cpuCores:
|
|
7020
|
-
totalRAM_MB:
|
|
7021
|
-
availableRAM_MB:
|
|
7022
|
-
gpu:
|
|
7023
|
-
type:
|
|
7024
|
-
name:
|
|
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()
|
|
7025
7521
|
}).nullable(),
|
|
7026
|
-
npu:
|
|
7522
|
+
npu: z30.looseObject({ type: z30.enum(["apple-ane", "intel-npu", "amd-xdna"]) }).nullable()
|
|
7027
7523
|
});
|
|
7028
|
-
var InferenceDevicesSchema3 =
|
|
7029
|
-
reachable:
|
|
7030
|
-
devices:
|
|
7031
|
-
|
|
7032
|
-
key:
|
|
7033
|
-
backend:
|
|
7034
|
-
available:
|
|
7035
|
-
enabled:
|
|
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()
|
|
7036
7532
|
})
|
|
7037
7533
|
)
|
|
7038
7534
|
});
|
|
7039
|
-
var RunStatusSchema =
|
|
7040
|
-
run:
|
|
7041
|
-
runId:
|
|
7042
|
-
nodeId:
|
|
7043
|
-
state:
|
|
7044
|
-
endReason:
|
|
7045
|
-
error:
|
|
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()
|
|
7046
7542
|
}).nullable()
|
|
7047
7543
|
});
|
|
7048
|
-
var ModelListSchema =
|
|
7049
|
-
|
|
7050
|
-
id:
|
|
7051
|
-
formats:
|
|
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()
|
|
7052
7548
|
})
|
|
7053
7549
|
);
|
|
7054
|
-
var PipelineTestSchema =
|
|
7055
|
-
kind:
|
|
7056
|
-
detections:
|
|
7057
|
-
|
|
7058
|
-
debug:
|
|
7059
|
-
stepLatencyMs:
|
|
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()
|
|
7060
7556
|
}).optional()
|
|
7061
7557
|
})
|
|
7062
7558
|
).optional(),
|
|
7063
|
-
discarded:
|
|
7064
|
-
debug:
|
|
7065
|
-
stepTimings:
|
|
7066
|
-
|
|
7067
|
-
source:
|
|
7068
|
-
ms:
|
|
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()
|
|
7069
7565
|
})
|
|
7070
7566
|
).optional(),
|
|
7071
|
-
predictMs:
|
|
7072
|
-
totalInferenceMs:
|
|
7567
|
+
predictMs: z30.number().optional(),
|
|
7568
|
+
totalInferenceMs: z30.number().optional()
|
|
7073
7569
|
}).optional()
|
|
7074
7570
|
});
|
|
7075
7571
|
var FORMAT_BY_BACKEND = {
|
|
@@ -7356,15 +7852,15 @@ async function supportBenchmarkRun(ctx, deps, args, confirmToken) {
|
|
|
7356
7852
|
}
|
|
7357
7853
|
return runGated5(ctx.gate, benchmarkRunSpec(outcome.connection.client, deps), args, confirmToken);
|
|
7358
7854
|
}
|
|
7359
|
-
var ResultsSchema =
|
|
7360
|
-
|
|
7361
|
-
nodeId:
|
|
7362
|
-
cell:
|
|
7363
|
-
phase:
|
|
7364
|
-
deviceKey:
|
|
7365
|
-
modelId:
|
|
7366
|
-
p50Ms:
|
|
7367
|
-
fps:
|
|
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()
|
|
7368
7864
|
})
|
|
7369
7865
|
})
|
|
7370
7866
|
);
|
|
@@ -7417,17 +7913,17 @@ async function supportBenchmarkStatus(ctx, args) {
|
|
|
7417
7913
|
}
|
|
7418
7914
|
|
|
7419
7915
|
// src/mcp/support-camera.ts
|
|
7420
|
-
import { z as
|
|
7421
|
-
var DeviceSchema2 =
|
|
7422
|
-
id:
|
|
7423
|
-
name:
|
|
7424
|
-
addonId:
|
|
7425
|
-
disabled:
|
|
7426
|
-
online:
|
|
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()
|
|
7427
7923
|
}).nullable();
|
|
7428
7924
|
var GENERIC = /onvif|rtsp/i;
|
|
7429
|
-
function words(
|
|
7430
|
-
return
|
|
7925
|
+
function words(text15) {
|
|
7926
|
+
return text15.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 3);
|
|
7431
7927
|
}
|
|
7432
7928
|
function isGeneric(parts2) {
|
|
7433
7929
|
return parts2.some((p) => GENERIC.test(p));
|
|
@@ -7624,7 +8120,7 @@ import {
|
|
|
7624
8120
|
digestArgs,
|
|
7625
8121
|
runGated as runGated6
|
|
7626
8122
|
} from "@camstack/develop";
|
|
7627
|
-
import { z as
|
|
8123
|
+
import { z as z32 } from "zod";
|
|
7628
8124
|
|
|
7629
8125
|
// src/mcp/support-scrub.ts
|
|
7630
8126
|
import { redactToolArgs, scrubSecrets } from "@camstack/develop";
|
|
@@ -7677,16 +8173,16 @@ var INFERENCE_PACKAGE = "@camstack/addon-pipeline";
|
|
|
7677
8173
|
var TITLE_MAX = 140;
|
|
7678
8174
|
var DIAGNOSIS_MAX = 4e3;
|
|
7679
8175
|
var DRAFT_KINDS = ["accelerator", "camera", "benchmark", "other"];
|
|
7680
|
-
var DraftKindSchema =
|
|
7681
|
-
var BenchmarkCellsSchema =
|
|
7682
|
-
|
|
7683
|
-
|
|
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() })
|
|
7684
8180
|
);
|
|
7685
8181
|
var PackageNameRe = /^(@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
|
|
7686
8182
|
var GithubRepoRe = /github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?(?:[/#?].*)?$/;
|
|
7687
8183
|
var ShorthandRe = /^(?:github:)?([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/;
|
|
7688
|
-
var RegistryDocSchema =
|
|
7689
|
-
repository:
|
|
8184
|
+
var RegistryDocSchema = z32.looseObject({
|
|
8185
|
+
repository: z32.union([z32.string(), z32.looseObject({ url: z32.string() })]).optional()
|
|
7690
8186
|
});
|
|
7691
8187
|
function githubRepoOf(value) {
|
|
7692
8188
|
const url = GithubRepoRe.exec(value);
|
|
@@ -7728,7 +8224,7 @@ async function resolveRepo(ctx, packageName) {
|
|
|
7728
8224
|
reason: null
|
|
7729
8225
|
};
|
|
7730
8226
|
}
|
|
7731
|
-
var ServerStatusSchema2 =
|
|
8227
|
+
var ServerStatusSchema2 = z32.looseObject({ runningVersion: z32.string() });
|
|
7732
8228
|
async function serverVersion(client, nodeId) {
|
|
7733
8229
|
const status = await readQuery(
|
|
7734
8230
|
client,
|
|
@@ -7930,8 +8426,8 @@ var BENCHMARK_FILES = [
|
|
|
7930
8426
|
"docs/benchmarks/detector-latency.json",
|
|
7931
8427
|
"packages/types/src/pipeline/auto-tune/detector-performance.ts"
|
|
7932
8428
|
];
|
|
7933
|
-
function slug(
|
|
7934
|
-
const s =
|
|
8429
|
+
function slug(text15) {
|
|
8430
|
+
const s = text15.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
7935
8431
|
return s === "" ? "change" : s;
|
|
7936
8432
|
}
|
|
7937
8433
|
function prProblem(args) {
|
|
@@ -7994,20 +8490,20 @@ async function supportDraftPr(ctx, args, confirmToken) {
|
|
|
7994
8490
|
}
|
|
7995
8491
|
|
|
7996
8492
|
// src/mcp/support-tools.ts
|
|
7997
|
-
var ModelId =
|
|
8493
|
+
var ModelId = z33.string().regex(/^[a-z0-9][a-z0-9._-]{1,63}$/);
|
|
7998
8494
|
var DraftShape = {
|
|
7999
8495
|
kind: DraftKindSchema,
|
|
8000
|
-
title:
|
|
8001
|
-
summary:
|
|
8002
|
-
nodeId:
|
|
8496
|
+
title: z33.string().min(3).max(300),
|
|
8497
|
+
summary: z33.string().min(3).max(4e3),
|
|
8498
|
+
nodeId: z33.string().max(100).optional(),
|
|
8003
8499
|
expected: ExpectedAcceleratorSchema.optional(),
|
|
8004
|
-
brand:
|
|
8005
|
-
model:
|
|
8006
|
-
deviceId:
|
|
8007
|
-
packageName:
|
|
8008
|
-
column:
|
|
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(),
|
|
8009
8505
|
cells: BenchmarkCellsSchema.optional(),
|
|
8010
|
-
confirm_token:
|
|
8506
|
+
confirm_token: z33.string().optional()
|
|
8011
8507
|
};
|
|
8012
8508
|
function draftArgs(input) {
|
|
8013
8509
|
return {
|
|
@@ -8028,20 +8524,20 @@ function registerSupportTools(server, ctx, deps = defaultSupportDeps) {
|
|
|
8028
8524
|
server.tool(
|
|
8029
8525
|
"support_benchmark_status",
|
|
8030
8526
|
"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`.",
|
|
8031
|
-
{ nodeId:
|
|
8032
|
-
async ({ nodeId }) =>
|
|
8527
|
+
{ nodeId: z33.string().max(100).optional() },
|
|
8528
|
+
async ({ nodeId }) => text14(await supportBenchmarkStatus(ctx, nodeId !== void 0 ? { nodeId } : {}))
|
|
8033
8529
|
);
|
|
8034
8530
|
server.tool(
|
|
8035
8531
|
BENCHMARK_RUN_TOOL,
|
|
8036
8532
|
"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`.",
|
|
8037
8533
|
{
|
|
8038
|
-
nodeId:
|
|
8039
|
-
deviceKey:
|
|
8040
|
-
models:
|
|
8041
|
-
warmRuns:
|
|
8042
|
-
confirm_token:
|
|
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()
|
|
8043
8539
|
},
|
|
8044
|
-
async ({ nodeId, deviceKey, models, warmRuns, confirm_token }) =>
|
|
8540
|
+
async ({ nodeId, deviceKey, models, warmRuns, confirm_token }) => text14(
|
|
8045
8541
|
await supportBenchmarkRun(
|
|
8046
8542
|
ctx,
|
|
8047
8543
|
deps,
|
|
@@ -8058,18 +8554,18 @@ function registerSupportTools(server, ctx, deps = defaultSupportDeps) {
|
|
|
8058
8554
|
server.tool(
|
|
8059
8555
|
"support_diagnose_accelerator",
|
|
8060
8556
|
"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.",
|
|
8061
|
-
{ nodeId:
|
|
8062
|
-
async ({ nodeId, expected }) =>
|
|
8557
|
+
{ nodeId: z33.string().min(1).max(100), expected: ExpectedAcceleratorSchema },
|
|
8558
|
+
async ({ nodeId, expected }) => text14(await supportDiagnoseAccelerator(ctx, { nodeId, expected }))
|
|
8063
8559
|
);
|
|
8064
8560
|
server.tool(
|
|
8065
8561
|
"support_diagnose_camera",
|
|
8066
8562
|
"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.",
|
|
8067
8563
|
{
|
|
8068
|
-
brand:
|
|
8069
|
-
model:
|
|
8070
|
-
deviceId:
|
|
8564
|
+
brand: z33.string().max(100).optional(),
|
|
8565
|
+
model: z33.string().max(100).optional(),
|
|
8566
|
+
deviceId: z33.number().int().optional()
|
|
8071
8567
|
},
|
|
8072
|
-
async ({ brand, model, deviceId }) =>
|
|
8568
|
+
async ({ brand, model, deviceId }) => text14(
|
|
8073
8569
|
await supportDiagnoseCamera(ctx, {
|
|
8074
8570
|
...brand !== void 0 ? { brand } : {},
|
|
8075
8571
|
...model !== void 0 ? { model } : {},
|
|
@@ -8081,13 +8577,13 @@ function registerSupportTools(server, ctx, deps = defaultSupportDeps) {
|
|
|
8081
8577
|
DRAFT_ISSUE_TOOL,
|
|
8082
8578
|
"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.",
|
|
8083
8579
|
DraftShape,
|
|
8084
|
-
async (input) =>
|
|
8580
|
+
async (input) => text14(await supportDraftIssue(ctx, deps, draftArgs(input), input.confirm_token))
|
|
8085
8581
|
);
|
|
8086
8582
|
server.tool(
|
|
8087
8583
|
DRAFT_PR_TOOL,
|
|
8088
8584
|
'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.',
|
|
8089
8585
|
DraftShape,
|
|
8090
|
-
async (input) =>
|
|
8586
|
+
async (input) => text14(await supportDraftPr(ctx, draftArgs(input), input.confirm_token))
|
|
8091
8587
|
);
|
|
8092
8588
|
}
|
|
8093
8589
|
|