@canonry/canonry 7.22.0 → 7.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,7 @@ cnry stop # stop daemon
12
12
  cnry serve # foreground mode
13
13
  cnry serve --host 0.0.0.0 --port 4100
14
14
  cnry serve --embed --embed-allow-origin https://app.example.com # read-only embed mode (#716)
15
+ cnry dashboard reset-password # clear a forgotten dashboard password (edits local config.yaml)
15
16
  cnry --version
16
17
  ```
17
18
 
@@ -41,6 +42,19 @@ install's server, or restart it, to apply the saved providers. Any other
41
42
  rejected reload reports `failed` and exits nonzero; the saved config remains
42
43
  available. Bootstrap never reloads a remote server automatically.
43
44
 
45
+ `cnry dashboard reset-password` clears a forgotten dashboard password. It edits
46
+ `config.yaml` under `$CANONRY_CONFIG_DIR` on this machine and never calls the
47
+ server, so run it where the server's config lives. A missing file is a
48
+ `CONFIG_REQUIRED` error, not "nothing to reset". Restart the server afterward:
49
+ until then it still accepts the old password. The dashboard then asks for a new
50
+ password under the first-run rules, so stop any port forwarder first and set it
51
+ right away. `--format json` returns
52
+ `{ reset, configPath, restartRequired, namedAccounts, nextSteps[] }`; `reset` is
53
+ `false` when no password was set. On an install with named accounts
54
+ (`namedAccounts: true`) the shared password is not used, so clearing it changes
55
+ nothing: restore an account with `cnry user delete <name>` and `cnry user
56
+ create`. The command has no API route and no MCP tool.
57
+
44
58
  ### Read-only embed mode (#716)
45
59
 
46
60
  Opt-in, OFF by default. Renders the dashboard "chromeless" (no nav/topbar/settings) so it can be iframed read-only, and emits a fail-closed framing contract. With `--embed` absent, served HTML + headers are byte-for-byte unchanged.
@@ -20,7 +20,7 @@ import {
20
20
  saveConfig,
21
21
  saveConfigPatch,
22
22
  sentimentInstallReadiness
23
- } from "./chunk-NDQOCFJQ.js";
23
+ } from "./chunk-UF7MH6TU.js";
24
24
  import {
25
25
  computeFactorAverages,
26
26
  deleteSiteCrawlGraphLayout,
@@ -3572,7 +3572,7 @@ import path8 from "path";
3572
3572
  import { isIP } from "net";
3573
3573
  import { fileURLToPath as fileURLToPath3 } from "url";
3574
3574
  import { setImmediate as yieldToEventLoop4 } from "timers/promises";
3575
- import { and as and24, eq as eq29, isNotNull as isNotNull4, isNull as isNull4, lte as lte2, ne as ne3, or as or3, sql as sql11 } from "drizzle-orm";
3575
+ import { and as and24, eq as eq29, isNotNull as isNotNull4, isNull as isNull4, lte as lte2, max, ne as ne3, or as or3, sql as sql11 } from "drizzle-orm";
3576
3576
 
3577
3577
  // ../integration-typesafe/src/classifier.ts
3578
3578
  import { createHash } from "crypto";
@@ -8052,7 +8052,7 @@ function nonBlank(value) {
8052
8052
  return trimmed ? trimmed : void 0;
8053
8053
  }
8054
8054
  function resolveBuildCommit(env = process.env) {
8055
- const embedded = true ? "d61f9e21fee5d1062ab9af132047f3c6474e0dea" : void 0;
8055
+ const embedded = true ? "8c7755db32156f7620744d4a8993c66ac41c40d8" : void 0;
8056
8056
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
8057
8057
  }
8058
8058
  function resolveInstanceIdentity(env = process.env) {
@@ -10715,10 +10715,10 @@ async function executeGscSync(db, runId, projectId, opts) {
10715
10715
  createdAt: dailyTotalsNow
10716
10716
  }).run();
10717
10717
  }
10718
- const observedThrough = totalRows.map((row) => row.keys[0] ?? "").filter((date) => date !== "").reduce((max, date) => max === null || date > max ? date : max, null);
10718
+ const observedThrough = totalRows.map((row) => row.keys[0] ?? "").filter((date) => date !== "").reduce((max2, date) => max2 === null || date > max2 ? date : max2, null);
10719
10719
  if (observedThrough !== null || endDate) {
10720
10720
  const existing = db.select().from(gscDataWatermarks).where(eq5(gscDataWatermarks.projectId, projectId)).get();
10721
- const nextThrough = [existing?.dataThroughDate ?? null, observedThrough].filter((d) => d !== null).reduce((max, d) => max === null || d > max ? d : max, null);
10721
+ const nextThrough = [existing?.dataThroughDate ?? null, observedThrough].filter((d) => d !== null).reduce((max2, d) => max2 === null || d > max2 ? d : max2, null);
10722
10722
  if (nextThrough !== null) {
10723
10723
  db.insert(gscDataWatermarks).values({
10724
10724
  projectId,
@@ -13420,7 +13420,7 @@ function buildSeedPrompt(input) {
13420
13420
  `Generate EXACTLY ${QUERIES_PER_INTENT_BUCKET} queries per bucket \u2014 ${QUERIES_PER_INTENT_BUCKET * 5} total. Return ONE query per line. Plain text only \u2014 no numbering, bullets, quotes, bucket labels, or commentary.`
13421
13421
  ].join("\n");
13422
13422
  }
13423
- function parseQueryLines(text, max) {
13423
+ function parseQueryLines(text, max2) {
13424
13424
  const lines = text.split("\n");
13425
13425
  const out = [];
13426
13426
  const seen = /* @__PURE__ */ new Set();
@@ -13434,7 +13434,7 @@ function parseQueryLines(text, max) {
13434
13434
  if (seen.has(key)) continue;
13435
13435
  seen.add(key);
13436
13436
  out.push(line);
13437
- if (out.length >= max) break;
13437
+ if (out.length >= max2) break;
13438
13438
  }
13439
13439
  return out;
13440
13440
  }
@@ -17059,9 +17059,9 @@ function largestArrayKey(obj) {
17059
17059
  function fitsCap(text) {
17060
17060
  return text.length <= MAX_TOOL_RESULT_CHARS;
17061
17061
  }
17062
- function largestFitting(max, fits) {
17062
+ function largestFitting(max2, fits) {
17063
17063
  let low = 0;
17064
- let high = max;
17064
+ let high = max2;
17065
17065
  while (low < high) {
17066
17066
  const middle = Math.ceil((low + high) / 2);
17067
17067
  if (fits(middle)) low = middle;
@@ -17211,7 +17211,7 @@ function trimNestedArrays(full) {
17211
17211
  };
17212
17212
  const fits = () => spent <= TRUNCATION_SERIALIZE_BUDGET_CHARS && fitsCap(render2());
17213
17213
  const members2 = (stage) => lists.flatMap((_, index) => stagesOf[index].includes(stage) ? [index] : []);
17214
- const longest = (indexes) => indexes.reduce((max, index) => Math.max(max, lists[index].rows.length), 0);
17214
+ const longest = (indexes) => indexes.reduce((max2, index) => Math.max(max2, lists[index].rows.length), 0);
17215
17215
  let out = render2();
17216
17216
  let fittedAt = -1;
17217
17217
  for (let stage = 0; stage < TRIM_STAGES.length && !fitsCap(out); stage++) {
@@ -17695,9 +17695,9 @@ async function readSection(fn) {
17695
17695
  };
17696
17696
  }
17697
17697
  }
17698
- function clampInteger(value, fallback, min, max) {
17698
+ function clampInteger(value, fallback, min, max2) {
17699
17699
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
17700
- return Math.max(min, Math.min(max, Math.trunc(value)));
17700
+ return Math.max(min, Math.min(max2, Math.trunc(value)));
17701
17701
  }
17702
17702
  function isoDateDaysAgo(days) {
17703
17703
  const d = new Date(Date.now() - (days - 1) * 24 * 60 * 60 * 1e3);
@@ -19776,9 +19776,9 @@ function viewerAeroDailyLimitExceeded(limit) {
19776
19776
  var VIEWER_AERO_KEY_PREFIX = "aero-viewer:";
19777
19777
  var VIEWER_AERO_PROMPT = "\n\nThis conversation is with a view-only account. You can read this project's data but cannot change anything, start or schedule sweeps, or spend on live provider reads. If asked to, say an administrator has to do it. You cannot see the operator's own Aero conversations or notes.";
19778
19778
  var MAX_VIEWER_MESSAGES = 40;
19779
- function trimViewerTranscript(messages, max = MAX_VIEWER_MESSAGES) {
19780
- if (messages.length <= max) return messages;
19781
- for (let i = messages.length - max; i < messages.length; i++) {
19779
+ function trimViewerTranscript(messages, max2 = MAX_VIEWER_MESSAGES) {
19780
+ if (messages.length <= max2) return messages;
19781
+ for (let i = messages.length - max2; i < messages.length; i++) {
19782
19782
  if (messages[i].role === "user") return messages.slice(i);
19783
19783
  }
19784
19784
  const lastUser = messages.map((message) => message.role).lastIndexOf("user");
@@ -23005,6 +23005,19 @@ async function createServer(opts) {
23005
23005
  ne3(dashboardSessions.passwordFingerprint, currentPasswordFingerprint())
23006
23006
  )
23007
23007
  )).run();
23008
+ if (!opts.config.dashboardPasswordHash) {
23009
+ const lastPasswordEventAt = (action) => opts.db.select({ at: max(auditLog.createdAt) }).from(auditLog).where(and24(eq29(auditLog.entityType, "dashboard-password"), eq29(auditLog.action, action))).get()?.at ?? null;
23010
+ const createdAt = lastPasswordEventAt("dashboard-password.created");
23011
+ const clearedAt = lastPasswordEventAt("dashboard-password.cleared");
23012
+ if (createdAt !== null && (clearedAt === null || clearedAt < createdAt)) {
23013
+ writeAuditLog(opts.db, {
23014
+ actor: "system",
23015
+ action: "dashboard-password.cleared",
23016
+ entityType: "dashboard-password",
23017
+ diff: { detectedAt: "startup" }
23018
+ });
23019
+ }
23020
+ }
23008
23021
  const createSession = (key, passwordBased = false, db = opts.db) => {
23009
23022
  const now = /* @__PURE__ */ new Date();
23010
23023
  db.delete(dashboardSessions).where(lte2(dashboardSessions.expiresAt, now.toISOString())).run();
@@ -23121,7 +23134,8 @@ async function createServer(opts) {
23121
23134
  const err = serverApiKeyMissing();
23122
23135
  return reply.status(err.statusCode).send(err.toJSON());
23123
23136
  }
23124
- opts.config.dashboardPasswordHash = hashDashboardPassword(password);
23137
+ const passwordHash = hashDashboardPassword(password);
23138
+ opts.config.dashboardPasswordHash = passwordHash;
23125
23139
  let sessionId;
23126
23140
  try {
23127
23141
  sessionId = opts.db.transaction((tx) => {
@@ -23134,7 +23148,7 @@ async function createServer(opts) {
23134
23148
  diff: { authorizedBy: presentedRootKey ? "root-api-key" : "local-request" }
23135
23149
  }));
23136
23150
  const created = createSession(defaultKey, true, tx);
23137
- saveConfigPatch(opts.config);
23151
+ saveConfigPatch(opts.config, { setDashboardPasswordHash: passwordHash });
23138
23152
  return created;
23139
23153
  });
23140
23154
  } catch (err) {
@@ -4,7 +4,7 @@ import {
4
4
  installSkills,
5
5
  loadConfigRaw,
6
6
  saveConfigPatch
7
- } from "./chunk-NDQOCFJQ.js";
7
+ } from "./chunk-UF7MH6TU.js";
8
8
  import {
9
9
  PACKAGE_VERSION
10
10
  } from "./chunk-5IXQAOVK.js";
@@ -582,17 +582,20 @@ function saveConfig(config) {
582
582
  const yaml = stringify(merged);
583
583
  writeConfigAtomically(configPath, yaml);
584
584
  }
585
- function saveConfigPatch(patch) {
585
+ function saveConfigPatch(patch, options = {}) {
586
586
  const configDir = getConfigDir();
587
587
  if (!fs.existsSync(configDir)) {
588
588
  fs.mkdirSync(configDir, { recursive: true });
589
589
  }
590
590
  const configPath = getConfigPath();
591
591
  let base = {};
592
+ let baseRead = false;
592
593
  if (fs.existsSync(configPath)) {
593
594
  try {
594
595
  const raw = fs.readFileSync(configPath, "utf-8");
595
- base = parse(raw) ?? {};
596
+ const parsed = parse(raw);
597
+ base = parsed ?? {};
598
+ baseRead = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed);
596
599
  } catch {
597
600
  base = {};
598
601
  }
@@ -602,6 +605,8 @@ function saveConfigPatch(patch) {
602
605
  if (base.apiKey) merged.apiKey = base.apiKey;
603
606
  if (base.anonymousId) merged.anonymousId = base.anonymousId;
604
607
  if (base.dashboardPasswordHash) merged.dashboardPasswordHash = base.dashboardPasswordHash;
608
+ else if (options.setDashboardPasswordHash) merged.dashboardPasswordHash = options.setDashboardPasswordHash;
609
+ else if (baseRead) delete merged.dashboardPasswordHash;
605
610
  if (base.sentiment === void 0) delete merged.sentiment;
606
611
  else merged.sentiment = base.sentiment;
607
612
  if (base.providers && patch.providers) {
@@ -614,6 +619,35 @@ function saveConfigPatch(patch) {
614
619
  const yaml = stringify(merged);
615
620
  writeConfigAtomically(configPath, yaml);
616
621
  }
622
+ function clearDashboardPassword() {
623
+ const configPath = getConfigPath();
624
+ if (!fs.existsSync(configPath)) {
625
+ throw new CliError({
626
+ code: "CONFIG_REQUIRED",
627
+ message: `No config found at ${configPath}. If the server uses another config directory, set CANONRY_CONFIG_DIR to it and retry.`
628
+ });
629
+ }
630
+ const raw = fs.readFileSync(configPath, "utf-8");
631
+ let parsed;
632
+ try {
633
+ parsed = parse(raw) ?? {};
634
+ } catch {
635
+ parsed = void 0;
636
+ }
637
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
638
+ throw new CliError({
639
+ code: "CONFIG_INVALID",
640
+ message: `Invalid config at ${configPath}: it could not be read as YAML settings. Repair the file and retry.`
641
+ });
642
+ }
643
+ const config = parsed;
644
+ if (!config.dashboardPasswordHash) {
645
+ return { cleared: false, configPath };
646
+ }
647
+ delete config.dashboardPasswordHash;
648
+ writeConfigAtomically(configPath, stringify(config));
649
+ return { cleared: true, configPath };
650
+ }
617
651
  function configExists() {
618
652
  return fs.existsSync(getConfigPath());
619
653
  }
@@ -10590,6 +10624,7 @@ export {
10590
10624
  loadConfigRaw,
10591
10625
  saveConfig,
10592
10626
  saveConfigPatch,
10627
+ clearDashboardPassword,
10593
10628
  configExists,
10594
10629
  BUNDLED_SKILL_NAMES,
10595
10630
  getBundledSkillSnapshots,
package/dist/cli.js CHANGED
@@ -35,12 +35,13 @@ import {
35
35
  trackCliCommandFinished,
36
36
  trackEvent,
37
37
  waitForServerRuntimeStartup
38
- } from "./chunk-6Z2LFFRT.js";
38
+ } from "./chunk-3OPV5XBY.js";
39
39
  import {
40
40
  autoSyncSkills,
41
41
  formatAutoSyncNotice
42
- } from "./chunk-6H2BC5EY.js";
42
+ } from "./chunk-KWBFKTOA.js";
43
43
  import {
44
+ clearDashboardPassword,
44
45
  configExists,
45
46
  createApiClient,
46
47
  emitInstallSummary,
@@ -57,7 +58,7 @@ import {
57
58
  saveConfigPatch,
58
59
  shouldPrintServeSkillsNudge,
59
60
  withBootstrapProviderDefaults
60
- } from "./chunk-NDQOCFJQ.js";
61
+ } from "./chunk-UF7MH6TU.js";
61
62
  import {
62
63
  DEFAULT_DEMO_TRUSTED_PROXIES,
63
64
  trustedProxiesProblem,
@@ -82,6 +83,7 @@ import {
82
83
  CLOUDFLARE_WRANGLER_GENERATED_MARKER,
83
84
  DEFAULT_BOT_LIST,
84
85
  aiUserFetchEventsHourly,
86
+ anyUsersExist,
85
87
  apiKeys,
86
88
  auditLog,
87
89
  backfillProjectAnswerMentions,
@@ -3015,6 +3017,80 @@ var CDP_CLI_COMMANDS = [
3015
3017
  }
3016
3018
  ];
3017
3019
 
3020
+ // src/commands/dashboard.ts
3021
+ import fs2 from "fs";
3022
+ function namedAccountsInUse() {
3023
+ const database = loadConfigRaw()?.database;
3024
+ if (typeof database !== "string" || !fs2.existsSync(database)) return false;
3025
+ try {
3026
+ const db = createClient(database);
3027
+ try {
3028
+ return anyUsersExist(db);
3029
+ } finally {
3030
+ db.$client.close();
3031
+ }
3032
+ } catch {
3033
+ return false;
3034
+ }
3035
+ }
3036
+ function nextStepsFor(cleared, namedAccounts) {
3037
+ if (namedAccounts) {
3038
+ return [
3039
+ "This install signs in with named accounts, so the shared dashboard password is not used and clearing it does not change who can sign in.",
3040
+ 'To restore an account, run "canonry user list", then "canonry user delete <name>" and "canonry user create".'
3041
+ ];
3042
+ }
3043
+ if (!cleared) {
3044
+ return ["If the dashboard still asks for a password, restart the server: it keeps the password it started with."];
3045
+ }
3046
+ return [
3047
+ "Restart the server to apply the reset. Until then it still accepts the old password.",
3048
+ // First-run setup on a loopback bind takes the password from any process
3049
+ // that can open a loopback connection (docs/deployment.md).
3050
+ "Then open the dashboard and create the new password right away. Until one is set, a server that listens only on this machine accepts it from any local connection, so stop any port forwarder (ssh -R, ngrok tcp, tailscale serve --tcp) first."
3051
+ ];
3052
+ }
3053
+ async function resetDashboardPassword(opts) {
3054
+ const { cleared, configPath } = clearDashboardPassword();
3055
+ const namedAccounts = namedAccountsInUse();
3056
+ const nextSteps = nextStepsFor(cleared, namedAccounts);
3057
+ if (isMachineFormat(opts.format)) {
3058
+ console.log(JSON.stringify({
3059
+ reset: cleared,
3060
+ configPath,
3061
+ restartRequired: cleared && !namedAccounts,
3062
+ namedAccounts,
3063
+ nextSteps
3064
+ }, null, 2));
3065
+ return;
3066
+ }
3067
+ console.log(cleared ? `Dashboard password cleared from ${configPath}.` : `No dashboard password is set in ${configPath}; nothing to reset.`);
3068
+ for (const step of nextSteps) console.log(step);
3069
+ }
3070
+
3071
+ // src/cli-commands/dashboard.ts
3072
+ var DASHBOARD_CLI_COMMANDS = [
3073
+ {
3074
+ path: ["dashboard", "reset-password"],
3075
+ usage: "canonry dashboard reset-password [--format json]",
3076
+ help: "Clears the dashboard password from config.yaml, so the dashboard asks for a new one after a restart.\nLocal only: it edits the config file under CANONRY_CONFIG_DIR and does not call the server.",
3077
+ run: async (input) => {
3078
+ await resetDashboardPassword({ format: input.format });
3079
+ }
3080
+ },
3081
+ {
3082
+ path: ["dashboard"],
3083
+ usage: "canonry dashboard <reset-password>",
3084
+ run: async (input) => {
3085
+ unknownSubcommand(input.positionals[0], {
3086
+ command: "dashboard",
3087
+ usage: "canonry dashboard <reset-password>",
3088
+ available: ["reset-password"]
3089
+ });
3090
+ }
3091
+ }
3092
+ ];
3093
+
3018
3094
  // src/commands/discover.ts
3019
3095
  var TERMINAL_DISCOVERY_STATUSES = /* @__PURE__ */ new Set([
3020
3096
  "completed",
@@ -3360,7 +3436,7 @@ async function pollSession(client, project2, sessionId, quiet = false) {
3360
3436
  }
3361
3437
 
3362
3438
  // src/commands/discover-eval.ts
3363
- import fs2 from "fs";
3439
+ import fs3 from "fs";
3364
3440
  import path from "path";
3365
3441
 
3366
3442
  // src/discovery-eval.ts
@@ -3589,7 +3665,7 @@ async function discoverEvalWithClient(client, opts) {
3589
3665
  const baselinePath = path.resolve(opts.baseline ?? DEFAULT_BASELINE_PATH);
3590
3666
  if (opts.updateBaseline) {
3591
3667
  const baseline2 = { capturedAt: (/* @__PURE__ */ new Date()).toISOString(), scorecards };
3592
- fs2.writeFileSync(baselinePath, JSON.stringify(baseline2, null, 2) + "\n");
3668
+ fs3.writeFileSync(baselinePath, JSON.stringify(baseline2, null, 2) + "\n");
3593
3669
  if (opts.format === "json" || opts.format === "jsonl") {
3594
3670
  console.log(JSON.stringify({ scorecards, baselineWritten: baselinePath }, null, 2));
3595
3671
  } else {
@@ -3598,14 +3674,14 @@ async function discoverEvalWithClient(client, opts) {
3598
3674
  }
3599
3675
  return;
3600
3676
  }
3601
- if (!fs2.existsSync(baselinePath)) {
3677
+ if (!fs3.existsSync(baselinePath)) {
3602
3678
  throw new CliError({
3603
3679
  code: "EVAL_NO_BASELINE",
3604
3680
  message: `no baseline at ${baselinePath}; run with --update-baseline to capture one`,
3605
3681
  exitCode: EXIT_USER_ERROR
3606
3682
  });
3607
3683
  }
3608
- const baseline = JSON.parse(fs2.readFileSync(baselinePath, "utf8"));
3684
+ const baseline = JSON.parse(fs3.readFileSync(baselinePath, "utf8"));
3609
3685
  const verdict = compareToBaseline(scorecards, baseline);
3610
3686
  if (opts.format === "json" || opts.format === "jsonl") {
3611
3687
  console.log(JSON.stringify({ scorecards, verdict, baseline: { capturedAt: baseline.capturedAt } }, null, 2));
@@ -4437,7 +4513,7 @@ var DOCTOR_CLI_COMMANDS = [
4437
4513
  ];
4438
4514
 
4439
4515
  // src/commands/ads.ts
4440
- import fs3 from "fs";
4516
+ import fs4 from "fs";
4441
4517
  function getClient6() {
4442
4518
  return createApiClient();
4443
4519
  }
@@ -4450,7 +4526,7 @@ function readRequest(inputPath, schema) {
4450
4526
  });
4451
4527
  }
4452
4528
  try {
4453
- const raw = fs3.readFileSync(inputPath === "-" ? 0 : inputPath, "utf8");
4529
+ const raw = fs4.readFileSync(inputPath === "-" ? 0 : inputPath, "utf8");
4454
4530
  return schema.parse(JSON.parse(raw));
4455
4531
  } catch (err) {
4456
4532
  if (err instanceof CliError) throw err;
@@ -5326,9 +5402,9 @@ async function gaConnect(project2, opts) {
5326
5402
  propertyId: opts.propertyId
5327
5403
  };
5328
5404
  if (opts.keyFile) {
5329
- const fs19 = await import("fs");
5405
+ const fs20 = await import("fs");
5330
5406
  try {
5331
- const content = fs19.readFileSync(opts.keyFile, "utf-8");
5407
+ const content = fs20.readFileSync(opts.keyFile, "utf-8");
5332
5408
  JSON.parse(content);
5333
5409
  body.keyJson = content;
5334
5410
  } catch (e) {
@@ -6916,7 +6992,7 @@ var GBP_CLI_COMMANDS = [
6916
6992
  ];
6917
6993
 
6918
6994
  // src/commands/google-marketing.ts
6919
- import fs4 from "fs";
6995
+ import fs5 from "fs";
6920
6996
  function printMachine(value, format) {
6921
6997
  if (!isMachineFormat(format)) return false;
6922
6998
  console.log(JSON.stringify(value, null, 2));
@@ -6957,7 +7033,7 @@ function readConversionTrackingContractInput(inputPath) {
6957
7033
  });
6958
7034
  }
6959
7035
  try {
6960
- const raw = fs4.readFileSync(inputPath === "-" ? 0 : inputPath, "utf8");
7036
+ const raw = fs5.readFileSync(inputPath === "-" ? 0 : inputPath, "utf8");
6961
7037
  return conversionTrackingContractWriteRequestSchema.parse(JSON.parse(raw));
6962
7038
  } catch (err) {
6963
7039
  const message = describeError(err);
@@ -7671,12 +7747,12 @@ var GET_CLI_COMMANDS = [
7671
7747
  ];
7672
7748
 
7673
7749
  // src/commands/traffic.ts
7674
- import fs6 from "fs";
7750
+ import fs7 from "fs";
7675
7751
 
7676
7752
  // src/cloudflare-worker-deploy.ts
7677
7753
  import { spawn } from "child_process";
7678
7754
  import { randomUUID } from "crypto";
7679
- import fs5 from "fs";
7755
+ import fs6 from "fs";
7680
7756
  import os from "os";
7681
7757
  import path2 from "path";
7682
7758
  import { stripVTControlCharacters } from "util";
@@ -7694,7 +7770,7 @@ function projectSlug(project2) {
7694
7770
  }
7695
7771
  function lstatIfPresent(filePath) {
7696
7772
  try {
7697
- return fs5.lstatSync(filePath);
7773
+ return fs6.lstatSync(filePath);
7698
7774
  } catch (error) {
7699
7775
  if (error.code === "ENOENT") return null;
7700
7776
  throw error;
@@ -7712,7 +7788,7 @@ function prepareCloudflareWorkerOutputDirectory(outputDirectory) {
7712
7788
  throw new Error(`output directory is not a regular directory: ${resolved}`);
7713
7789
  }
7714
7790
  } else {
7715
- fs5.mkdirSync(resolved, { recursive: true, mode: 448 });
7791
+ fs6.mkdirSync(resolved, { recursive: true, mode: 448 });
7716
7792
  }
7717
7793
  const artifacts = {
7718
7794
  outputDirectory: resolved,
@@ -7725,7 +7801,7 @@ function prepareCloudflareWorkerOutputDirectory(outputDirectory) {
7725
7801
  if (!stat.isFile() || stat.isSymbolicLink()) {
7726
7802
  throw new Error(`artifact path is not a regular file: ${artifactPath}`);
7727
7803
  }
7728
- const contents = fs5.readFileSync(artifactPath, "utf-8");
7804
+ const contents = fs6.readFileSync(artifactPath, "utf-8");
7729
7805
  if (!isRecognizablyCanonryGenerated(artifactPath, contents)) {
7730
7806
  throw new Error(`refusing to overwrite an operator-owned artifact: ${artifactPath}`);
7731
7807
  }
@@ -7738,7 +7814,7 @@ function readRegularArtifact(filePath) {
7738
7814
  if (!stat.isFile() || stat.isSymbolicLink()) {
7739
7815
  throw new Error(`artifact path is not a regular file: ${filePath}`);
7740
7816
  }
7741
- return fs5.readFileSync(filePath, "utf-8");
7817
+ return fs6.readFileSync(filePath, "utf-8");
7742
7818
  }
7743
7819
  function isRecognizablyCanonryGenerated(filePath, contents) {
7744
7820
  if (path2.basename(filePath) === "worker.js") {
@@ -7755,22 +7831,22 @@ function writePrivateTemporaryFile(filePath, contents) {
7755
7831
  path2.dirname(filePath),
7756
7832
  `.${path2.basename(filePath)}.${randomUUID()}.tmp`
7757
7833
  );
7758
- const fd = fs5.openSync(temporaryPath, "wx", 384);
7834
+ const fd = fs6.openSync(temporaryPath, "wx", 384);
7759
7835
  let closed = false;
7760
7836
  try {
7761
- fs5.writeFileSync(fd, contents, "utf-8");
7762
- fs5.fsyncSync(fd);
7763
- fs5.closeSync(fd);
7837
+ fs6.writeFileSync(fd, contents, "utf-8");
7838
+ fs6.fsyncSync(fd);
7839
+ fs6.closeSync(fd);
7764
7840
  closed = true;
7765
7841
  } catch (error) {
7766
7842
  if (!closed) {
7767
7843
  try {
7768
- fs5.closeSync(fd);
7844
+ fs6.closeSync(fd);
7769
7845
  } catch {
7770
7846
  }
7771
7847
  }
7772
7848
  try {
7773
- fs5.unlinkSync(temporaryPath);
7849
+ fs6.unlinkSync(temporaryPath);
7774
7850
  } catch {
7775
7851
  }
7776
7852
  throw error;
@@ -7804,10 +7880,10 @@ function writeCloudflareWorkerArtifacts(artifacts, contents) {
7804
7880
  throw new Error(`artifact changed during update: ${change.filePath}`);
7805
7881
  }
7806
7882
  if (change.previous === null) {
7807
- fs5.linkSync(change.temporaryPath, change.filePath);
7808
- fs5.unlinkSync(change.temporaryPath);
7883
+ fs6.linkSync(change.temporaryPath, change.filePath);
7884
+ fs6.unlinkSync(change.temporaryPath);
7809
7885
  } else {
7810
- fs5.renameSync(change.temporaryPath, change.filePath);
7886
+ fs6.renameSync(change.temporaryPath, change.filePath);
7811
7887
  }
7812
7888
  applied.push(change);
7813
7889
  }
@@ -7816,10 +7892,10 @@ function writeCloudflareWorkerArtifacts(artifacts, contents) {
7816
7892
  try {
7817
7893
  if (readRegularArtifact(change.filePath) !== change.contents) continue;
7818
7894
  if (change.previous === null) {
7819
- fs5.unlinkSync(change.filePath);
7895
+ fs6.unlinkSync(change.filePath);
7820
7896
  } else {
7821
7897
  const restorePath = writePrivateTemporaryFile(change.filePath, change.previous);
7822
- fs5.renameSync(restorePath, change.filePath);
7898
+ fs6.renameSync(restorePath, change.filePath);
7823
7899
  }
7824
7900
  } catch {
7825
7901
  }
@@ -7828,7 +7904,7 @@ function writeCloudflareWorkerArtifacts(artifacts, contents) {
7828
7904
  } finally {
7829
7905
  for (const change of pending) {
7830
7906
  try {
7831
- if (fs5.existsSync(change.temporaryPath)) fs5.unlinkSync(change.temporaryPath);
7907
+ if (fs6.existsSync(change.temporaryPath)) fs6.unlinkSync(change.temporaryPath);
7832
7908
  } catch {
7833
7909
  }
7834
7910
  }
@@ -7914,7 +7990,7 @@ async function preflightCloudflareWrangler(opts = {}) {
7914
7990
  if (missing.length > 0) {
7915
7991
  throw new Error(`Wrangler deploy does not support required flags: ${missing.join(", ")}`);
7916
7992
  }
7917
- const tempDirectory = fs5.mkdtempSync(path2.join(
7993
+ const tempDirectory = fs6.mkdtempSync(path2.join(
7918
7994
  opts.tempRoot ?? os.tmpdir(),
7919
7995
  "canonry-cloudflare-wrangler-preflight-"
7920
7996
  ));
@@ -7922,13 +7998,13 @@ async function preflightCloudflareWrangler(opts = {}) {
7922
7998
  const wranglerTomlPath = path2.join(tempDirectory, "wrangler.toml");
7923
7999
  const secretsPath = path2.join(tempDirectory, "secrets.json");
7924
8000
  try {
7925
- fs5.chmodSync(tempDirectory, 448);
7926
- fs5.writeFileSync(workerScriptPath, generateWorkerScript({
8001
+ fs6.chmodSync(tempDirectory, 448);
8002
+ fs6.writeFileSync(workerScriptPath, generateWorkerScript({
7927
8003
  deliveryMode,
7928
8004
  workerVersion: "preflight",
7929
8005
  botList: DEFAULT_BOT_LIST
7930
8006
  }), { encoding: "utf-8", flag: "wx", mode: 384 });
7931
- fs5.writeFileSync(wranglerTomlPath, generateWranglerToml(
8007
+ fs6.writeFileSync(wranglerTomlPath, generateWranglerToml(
7932
8008
  deliveryMode === "queue-pull" ? {
7933
8009
  deliveryMode,
7934
8010
  sourceId: "preflight",
@@ -7944,7 +8020,7 @@ async function preflightCloudflareWrangler(opts = {}) {
7944
8020
  }
7945
8021
  ), { encoding: "utf-8", flag: "wx", mode: 384 });
7946
8022
  if (deliveryMode === "direct-push") {
7947
- fs5.writeFileSync(secretsPath, JSON.stringify({
8023
+ fs6.writeFileSync(secretsPath, JSON.stringify({
7948
8024
  [CLOUDFLARE_WORKER_BINDINGS.bearerToken]: "preflight-bearer",
7949
8025
  [CLOUDFLARE_WORKER_BINDINGS.hmacSecret]: "preflight-hmac"
7950
8026
  }), { encoding: "utf-8", flag: "wx", mode: 384 });
@@ -7972,7 +8048,7 @@ ${wranglerDiagnostic(validationOutput)}`
7972
8048
  );
7973
8049
  }
7974
8050
  } finally {
7975
- fs5.rmSync(tempDirectory, { recursive: true, force: true });
8051
+ fs6.rmSync(tempDirectory, { recursive: true, force: true });
7976
8052
  }
7977
8053
  }
7978
8054
  async function deployCloudflareWorker(opts) {
@@ -7988,11 +8064,11 @@ async function deployCloudflareWorker(opts) {
7988
8064
  }
7989
8065
  if (!opts.secrets) throw new Error("Direct-push Worker deploy requires bearer and HMAC secrets");
7990
8066
  const secrets = opts.secrets;
7991
- const tempDirectory = fs5.mkdtempSync(path2.join(opts.tempRoot ?? os.tmpdir(), "canonry-cloudflare-secrets-"));
8067
+ const tempDirectory = fs6.mkdtempSync(path2.join(opts.tempRoot ?? os.tmpdir(), "canonry-cloudflare-secrets-"));
7992
8068
  const secretsPath = path2.join(tempDirectory, "secrets.json");
7993
8069
  try {
7994
- fs5.chmodSync(tempDirectory, 448);
7995
- fs5.writeFileSync(
8070
+ fs6.chmodSync(tempDirectory, 448);
8071
+ fs6.writeFileSync(
7996
8072
  secretsPath,
7997
8073
  JSON.stringify({
7998
8074
  [CLOUDFLARE_WORKER_BINDINGS.bearerToken]: secrets.bearerToken,
@@ -8000,7 +8076,7 @@ async function deployCloudflareWorker(opts) {
8000
8076
  }),
8001
8077
  { encoding: "utf-8", flag: "wx", mode: 384 }
8002
8078
  );
8003
- fs5.chmodSync(secretsPath, 384);
8079
+ fs6.chmodSync(secretsPath, 384);
8004
8080
  const runner = opts.run ?? runWrangler;
8005
8081
  await runner(
8006
8082
  "wrangler",
@@ -8016,9 +8092,9 @@ async function deployCloudflareWorker(opts) {
8016
8092
  );
8017
8093
  } finally {
8018
8094
  try {
8019
- if (fs5.existsSync(secretsPath)) fs5.unlinkSync(secretsPath);
8095
+ if (fs6.existsSync(secretsPath)) fs6.unlinkSync(secretsPath);
8020
8096
  } finally {
8021
- fs5.rmSync(tempDirectory, { recursive: true, force: true });
8097
+ fs6.rmSync(tempDirectory, { recursive: true, force: true });
8022
8098
  }
8023
8099
  }
8024
8100
  }
@@ -8048,11 +8124,11 @@ function requireCloudflareQueueOption(project2, value, option) {
8048
8124
  }
8049
8125
  function readCloudflareQueueApiToken(project2, tokenFile) {
8050
8126
  try {
8051
- const stat = fs6.lstatSync(tokenFile);
8127
+ const stat = fs7.lstatSync(tokenFile);
8052
8128
  if (!stat.isFile() || stat.isSymbolicLink()) {
8053
8129
  throw new Error("not a regular file");
8054
8130
  }
8055
- const apiToken = fs6.readFileSync(tokenFile, "utf-8").trim();
8131
+ const apiToken = fs7.readFileSync(tokenFile, "utf-8").trim();
8056
8132
  if (!apiToken) throw new Error("file is empty");
8057
8133
  return apiToken;
8058
8134
  } catch (error) {
@@ -8334,9 +8410,9 @@ async function trafficConnectWordpress(project2, opts) {
8334
8410
  }
8335
8411
  let applicationPassword = opts.appPassword?.trim() ?? "";
8336
8412
  if (!applicationPassword && opts.appPasswordFile) {
8337
- const fs19 = await import("fs");
8413
+ const fs20 = await import("fs");
8338
8414
  try {
8339
- applicationPassword = fs19.readFileSync(opts.appPasswordFile, "utf-8").trim();
8415
+ applicationPassword = fs20.readFileSync(opts.appPasswordFile, "utf-8").trim();
8340
8416
  } catch (e) {
8341
8417
  const msg = describeError(e);
8342
8418
  throw new CliError({
@@ -8392,10 +8468,10 @@ async function trafficConnectCloudRun(project2, opts) {
8392
8468
  details: { project: project2 }
8393
8469
  });
8394
8470
  }
8395
- const fs19 = await import("fs");
8471
+ const fs20 = await import("fs");
8396
8472
  let keyJson;
8397
8473
  try {
8398
- keyJson = fs19.readFileSync(opts.serviceAccountKey, "utf-8");
8474
+ keyJson = fs20.readFileSync(opts.serviceAccountKey, "utf-8");
8399
8475
  JSON.parse(keyJson);
8400
8476
  } catch (e) {
8401
8477
  const msg = describeError(e);
@@ -8455,9 +8531,9 @@ async function trafficConnectVercel(project2, opts) {
8455
8531
  }
8456
8532
  let token = opts.token?.trim() ?? "";
8457
8533
  if (!token && opts.tokenFile) {
8458
- const fs19 = await import("fs");
8534
+ const fs20 = await import("fs");
8459
8535
  try {
8460
- token = fs19.readFileSync(opts.tokenFile, "utf-8").trim();
8536
+ token = fs20.readFileSync(opts.tokenFile, "utf-8").trim();
8461
8537
  } catch (e) {
8462
8538
  const msg = describeError(e);
8463
8539
  throw new CliError({
@@ -11504,7 +11580,7 @@ var USERS_CLI_COMMANDS = [
11504
11580
  ];
11505
11581
 
11506
11582
  // src/commands/keyword.ts
11507
- import fs7 from "fs";
11583
+ import fs8 from "fs";
11508
11584
  function getClient14() {
11509
11585
  return createApiClient();
11510
11586
  }
@@ -11572,7 +11648,7 @@ async function listKeywords(project2, format) {
11572
11648
  }
11573
11649
  }
11574
11650
  async function importKeywords(project2, filePath, format) {
11575
- if (!fs7.existsSync(filePath)) {
11651
+ if (!fs8.existsSync(filePath)) {
11576
11652
  throw new CliError({
11577
11653
  code: "KEYWORD_IMPORT_FILE_NOT_FOUND",
11578
11654
  message: `File not found: ${filePath}`,
@@ -11583,7 +11659,7 @@ async function importKeywords(project2, filePath, format) {
11583
11659
  }
11584
11660
  });
11585
11661
  }
11586
- const content = fs7.readFileSync(filePath, "utf-8");
11662
+ const content = fs8.readFileSync(filePath, "utf-8");
11587
11663
  const keywords = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
11588
11664
  if (keywords.length === 0) {
11589
11665
  if (isMachineFormat(format)) {
@@ -11784,7 +11860,7 @@ var KEYWORD_CLI_COMMANDS = [
11784
11860
  ];
11785
11861
 
11786
11862
  // src/commands/query.ts
11787
- import fs8 from "fs";
11863
+ import fs9 from "fs";
11788
11864
  function getClient15() {
11789
11865
  return createApiClient();
11790
11866
  }
@@ -11875,7 +11951,7 @@ async function listQueries(project2, format) {
11875
11951
  }
11876
11952
  }
11877
11953
  async function importQueries(project2, filePath, format) {
11878
- if (!fs8.existsSync(filePath)) {
11954
+ if (!fs9.existsSync(filePath)) {
11879
11955
  throw new CliError({
11880
11956
  code: "QUERY_IMPORT_FILE_NOT_FOUND",
11881
11957
  message: `File not found: ${filePath}`,
@@ -11886,7 +11962,7 @@ async function importQueries(project2, filePath, format) {
11886
11962
  }
11887
11963
  });
11888
11964
  }
11889
- const content = fs8.readFileSync(filePath, "utf-8");
11965
+ const content = fs9.readFileSync(filePath, "utf-8");
11890
11966
  const queries2 = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
11891
11967
  if (queries2.length === 0) {
11892
11968
  if (isMachineFormat(format)) {
@@ -11950,16 +12026,16 @@ Saved ${result.queries.length} ${result.queries.length === 1 ? "query" : "querie
11950
12026
 
11951
12027
  // src/commands/measurement-plan.ts
11952
12028
  import crypto from "crypto";
11953
- import fs9 from "fs";
12029
+ import fs10 from "fs";
11954
12030
  import { parse as parse2 } from "yaml";
11955
12031
  import { z as z2 } from "zod";
11956
12032
  function readPlan(source) {
11957
- const content = source === "-" ? fs9.readFileSync(0, "utf8") : fs9.readFileSync(source, "utf8");
12033
+ const content = source === "-" ? fs10.readFileSync(0, "utf8") : fs10.readFileSync(source, "utf8");
11958
12034
  const parsed = source.endsWith(".json") ? JSON.parse(content) : parse2(content);
11959
12035
  return measurementPlanInputSchema.parse(parsed);
11960
12036
  }
11961
12037
  function readDiscoveryRule(source) {
11962
- const content = source === "-" ? fs9.readFileSync(0, "utf8") : fs9.readFileSync(source, "utf8");
12038
+ const content = source === "-" ? fs10.readFileSync(0, "utf8") : fs10.readFileSync(source, "utf8");
11963
12039
  const parsed = source.endsWith(".json") ? JSON.parse(content) : parse2(content);
11964
12040
  return measurementDiscoveryRuleSchema.parse(parsed);
11965
12041
  }
@@ -12012,7 +12088,7 @@ var advancedDeactivateInputSchema = z2.object({
12012
12088
  }).strict();
12013
12089
  function readAdvancedMeasurementInput(source) {
12014
12090
  if (source === void 0) return {};
12015
- const content = source === "-" ? fs9.readFileSync(0, "utf8") : fs9.readFileSync(source, "utf8");
12091
+ const content = source === "-" ? fs10.readFileSync(0, "utf8") : fs10.readFileSync(source, "utf8");
12016
12092
  return JSON.parse(content);
12017
12093
  }
12018
12094
  var advancedMeasurementCollectionKeys = {
@@ -12211,7 +12287,7 @@ async function replaceMeasurementPlanAssignments(project2, opts) {
12211
12287
  console.log(JSON.stringify({ preview, result }, null, 2));
12212
12288
  }
12213
12289
  function readGroupMembershipCsv(source) {
12214
- return source === "-" ? fs9.readFileSync(0, "utf8") : fs9.readFileSync(source, "utf8");
12290
+ return source === "-" ? fs10.readFileSync(0, "utf8") : fs10.readFileSync(source, "utf8");
12215
12291
  }
12216
12292
  async function previewMeasurementPlanGroups(project2, source) {
12217
12293
  const preview = await createApiClient().previewMeasurementDraftGroupMembership(project2, {
@@ -12562,7 +12638,7 @@ var QUERY_CLI_COMMANDS = [
12562
12638
  ];
12563
12639
 
12564
12640
  // src/commands/mcp.ts
12565
- import fs10 from "fs";
12641
+ import fs11 from "fs";
12566
12642
  import path4 from "path";
12567
12643
  import { createRequire } from "module";
12568
12644
 
@@ -12678,8 +12754,8 @@ function renderClientSnippet(client, serverName, entry) {
12678
12754
  return renderJsonSnippet(serverName, entry, client.format);
12679
12755
  }
12680
12756
  function readJsonConfig(configPath) {
12681
- if (!fs10.existsSync(configPath)) return {};
12682
- const raw = fs10.readFileSync(configPath, "utf-8").trim();
12757
+ if (!fs11.existsSync(configPath)) return {};
12758
+ const raw = fs11.readFileSync(configPath, "utf-8").trim();
12683
12759
  if (!raw) return {};
12684
12760
  try {
12685
12761
  const parsed = JSON.parse(raw);
@@ -12697,14 +12773,14 @@ function readJsonConfig(configPath) {
12697
12773
  }
12698
12774
  }
12699
12775
  function writeJsonConfig(configPath, value) {
12700
- fs10.mkdirSync(path4.dirname(configPath), { recursive: true });
12701
- fs10.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
12776
+ fs11.mkdirSync(path4.dirname(configPath), { recursive: true });
12777
+ fs11.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
12702
12778
  `, "utf-8");
12703
12779
  }
12704
12780
  function backupConfigIfPresent(configPath) {
12705
- if (!fs10.existsSync(configPath)) return void 0;
12781
+ if (!fs11.existsSync(configPath)) return void 0;
12706
12782
  const backupPath = `${configPath}.canonry.bak`;
12707
- fs10.copyFileSync(configPath, backupPath);
12783
+ fs11.copyFileSync(configPath, backupPath);
12708
12784
  return backupPath;
12709
12785
  }
12710
12786
  function findClientOrThrow(id) {
@@ -13067,13 +13143,13 @@ var NOTIFY_CLI_COMMANDS = [
13067
13143
  ];
13068
13144
 
13069
13145
  // src/commands/apply.ts
13070
- import fs11 from "fs";
13146
+ import fs12 from "fs";
13071
13147
  import { parseAllDocuments } from "yaml";
13072
13148
  async function applyConfigFile(filePath) {
13073
- if (!fs11.existsSync(filePath)) {
13149
+ if (!fs12.existsSync(filePath)) {
13074
13150
  throw new Error(`File not found: ${filePath}`);
13075
13151
  }
13076
- const content = fs11.readFileSync(filePath, "utf-8");
13152
+ const content = fs12.readFileSync(filePath, "utf-8");
13077
13153
  const docs = parseAllDocuments(content);
13078
13154
  const client = createApiClient();
13079
13155
  const errors = [];
@@ -13539,7 +13615,7 @@ async function loadLatestRunForExport(client, project2) {
13539
13615
  }
13540
13616
 
13541
13617
  // src/commands/results-export.ts
13542
- import fs12 from "fs";
13618
+ import fs13 from "fs";
13543
13619
  import path5 from "path";
13544
13620
  async function exportResults(project2, opts) {
13545
13621
  const { output, ...request } = opts;
@@ -13549,8 +13625,8 @@ async function exportResults(project2, opts) {
13549
13625
  return;
13550
13626
  }
13551
13627
  const target = output ? path5.resolve(output) : path5.resolve(process.cwd(), path5.basename(artifact.filename));
13552
- fs12.mkdirSync(path5.dirname(target), { recursive: true });
13553
- fs12.writeFileSync(target, artifact.content, "utf8");
13628
+ fs13.mkdirSync(path5.dirname(target), { recursive: true });
13629
+ fs13.writeFileSync(target, artifact.content, "utf8");
13554
13630
  console.log(`Results export written to ${target}`);
13555
13631
  }
13556
13632
 
@@ -15852,11 +15928,11 @@ var SKILLS_CLI_COMMANDS = [
15852
15928
  ];
15853
15929
 
15854
15930
  // src/commands/snapshot.ts
15855
- import fs14 from "fs";
15931
+ import fs15 from "fs";
15856
15932
  import path7 from "path";
15857
15933
 
15858
15934
  // src/snapshot-pdf.ts
15859
- import fs13 from "fs";
15935
+ import fs14 from "fs";
15860
15936
  import path6 from "path";
15861
15937
  import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
15862
15938
  var PAGE_WIDTH = 612;
@@ -16070,8 +16146,8 @@ async function writeSnapshotPdf(report, outputPath) {
16070
16146
  renderQueries(pdf, report);
16071
16147
  const bytes = await doc.save();
16072
16148
  const resolvedPath = path6.resolve(outputPath);
16073
- fs13.mkdirSync(path6.dirname(resolvedPath), { recursive: true });
16074
- fs13.writeFileSync(resolvedPath, bytes);
16149
+ fs14.mkdirSync(path6.dirname(resolvedPath), { recursive: true });
16150
+ fs14.writeFileSync(resolvedPath, bytes);
16075
16151
  return resolvedPath;
16076
16152
  }
16077
16153
  function renderCover(pdf, report) {
@@ -16232,8 +16308,8 @@ PDF saved: ${savedPdfPath}`);
16232
16308
  }
16233
16309
  function writeSnapshotMarkdown(report, outputPath) {
16234
16310
  const resolvedPath = path7.resolve(outputPath);
16235
- fs14.mkdirSync(path7.dirname(resolvedPath), { recursive: true });
16236
- fs14.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
16311
+ fs15.mkdirSync(path7.dirname(resolvedPath), { recursive: true });
16312
+ fs15.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
16237
16313
  return resolvedPath;
16238
16314
  }
16239
16315
  function formatSnapshotMarkdown(report) {
@@ -17569,7 +17645,7 @@ async function bootstrapCommand(opts) {
17569
17645
 
17570
17646
  // src/commands/daemon.ts
17571
17647
  import { spawn as spawn2 } from "child_process";
17572
- import fs15 from "fs";
17648
+ import fs16 from "fs";
17573
17649
  import path9 from "path";
17574
17650
 
17575
17651
  // src/operator-url.ts
@@ -17640,8 +17716,8 @@ function buildServeForwardArgs(opts) {
17640
17716
  async function startDaemon(opts) {
17641
17717
  const pidPath = getPidPath();
17642
17718
  const format = opts.format ?? "text";
17643
- if (fs15.existsSync(pidPath)) {
17644
- const existingPid = parseInt(fs15.readFileSync(pidPath, "utf-8").trim(), 10);
17719
+ if (fs16.existsSync(pidPath)) {
17720
+ const existingPid = parseInt(fs16.readFileSync(pidPath, "utf-8").trim(), 10);
17645
17721
  if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
17646
17722
  throw new CliError({
17647
17723
  code: "DAEMON_ALREADY_RUNNING",
@@ -17652,7 +17728,7 @@ async function startDaemon(opts) {
17652
17728
  }
17653
17729
  });
17654
17730
  }
17655
- fs15.unlinkSync(pidPath);
17731
+ fs16.unlinkSync(pidPath);
17656
17732
  }
17657
17733
  const config = loadConfig();
17658
17734
  const endpoint = resolveDaemonEndpoint(opts, process.env, config.port);
@@ -17675,10 +17751,10 @@ async function startDaemon(opts) {
17675
17751
  });
17676
17752
  }
17677
17753
  const configDir = getConfigDir();
17678
- if (!fs15.existsSync(configDir)) {
17679
- fs15.mkdirSync(configDir, { recursive: true });
17754
+ if (!fs16.existsSync(configDir)) {
17755
+ fs16.mkdirSync(configDir, { recursive: true });
17680
17756
  }
17681
- fs15.writeFileSync(pidPath, String(child.pid), "utf-8");
17757
+ fs16.writeFileSync(pidPath, String(child.pid), "utf-8");
17682
17758
  if (!isMachineFormat(format)) {
17683
17759
  process.stderr.write("Waiting for server to start...");
17684
17760
  }
@@ -17689,7 +17765,7 @@ async function startDaemon(opts) {
17689
17765
  } catch {
17690
17766
  }
17691
17767
  try {
17692
- fs15.unlinkSync(pidPath);
17768
+ fs16.unlinkSync(pidPath);
17693
17769
  } catch {
17694
17770
  }
17695
17771
  throw new CliError({
@@ -17721,7 +17797,7 @@ async function startDaemon(opts) {
17721
17797
  }
17722
17798
  function stopDaemon(format = "text") {
17723
17799
  const pidPath = getPidPath();
17724
- if (!fs15.existsSync(pidPath)) {
17800
+ if (!fs16.existsSync(pidPath)) {
17725
17801
  if (isMachineFormat(format)) {
17726
17802
  console.log(JSON.stringify({
17727
17803
  stopped: false,
@@ -17732,7 +17808,7 @@ function stopDaemon(format = "text") {
17732
17808
  console.log("Canonry is not running (no PID file found)");
17733
17809
  return;
17734
17810
  }
17735
- const pid = parseInt(fs15.readFileSync(pidPath, "utf-8").trim(), 10);
17811
+ const pid = parseInt(fs16.readFileSync(pidPath, "utf-8").trim(), 10);
17736
17812
  if (isNaN(pid)) {
17737
17813
  if (isMachineFormat(format)) {
17738
17814
  console.log(JSON.stringify({
@@ -17743,7 +17819,7 @@ function stopDaemon(format = "text") {
17743
17819
  } else {
17744
17820
  console.error("Invalid PID file. Removing it.");
17745
17821
  }
17746
- fs15.unlinkSync(pidPath);
17822
+ fs16.unlinkSync(pidPath);
17747
17823
  return;
17748
17824
  }
17749
17825
  if (!isProcessAlive(pid)) {
@@ -17757,12 +17833,12 @@ function stopDaemon(format = "text") {
17757
17833
  } else {
17758
17834
  console.log(`Canonry is not running (stale PID: ${pid}). Cleaning up.`);
17759
17835
  }
17760
- fs15.unlinkSync(pidPath);
17836
+ fs16.unlinkSync(pidPath);
17761
17837
  return;
17762
17838
  }
17763
17839
  try {
17764
17840
  process.kill(pid, "SIGTERM");
17765
- fs15.unlinkSync(pidPath);
17841
+ fs16.unlinkSync(pidPath);
17766
17842
  if (isMachineFormat(format)) {
17767
17843
  console.log(JSON.stringify({
17768
17844
  stopped: true,
@@ -17786,7 +17862,7 @@ function stopDaemon(format = "text") {
17786
17862
 
17787
17863
  // src/commands/init.ts
17788
17864
  import crypto3 from "crypto";
17789
- import fs16 from "fs";
17865
+ import fs17 from "fs";
17790
17866
  import readline2 from "readline";
17791
17867
  import path10 from "path";
17792
17868
  var pendingServeHandoff = false;
@@ -17824,7 +17900,7 @@ var PROJECT_MARKERS = [".git", "canonry.yaml", "canonry.yml", "package.json"];
17824
17900
  function cwdLooksLikeProject(dir) {
17825
17901
  const home = process.env.HOME ?? "";
17826
17902
  if (home && path10.resolve(dir) === path10.resolve(home)) return false;
17827
- return PROJECT_MARKERS.some((marker) => fs16.existsSync(path10.join(dir, marker)));
17903
+ return PROJECT_MARKERS.some((marker) => fs17.existsSync(path10.join(dir, marker)));
17828
17904
  }
17829
17905
  var DEFAULT_AGENT_MODELS = {
17830
17906
  anthropic: "anthropic/claude-sonnet-4-6",
@@ -17855,8 +17931,8 @@ async function initCommand(opts) {
17855
17931
  return void 0;
17856
17932
  }
17857
17933
  const configDir = getConfigDir();
17858
- if (!fs16.existsSync(configDir)) {
17859
- fs16.mkdirSync(configDir, { recursive: true });
17934
+ if (!fs17.existsSync(configDir)) {
17935
+ fs17.mkdirSync(configDir, { recursive: true });
17860
17936
  }
17861
17937
  const bootstrapEnv = getBootstrapEnv(process.env, {
17862
17938
  GEMINI_API_KEY: opts?.geminiKey,
@@ -18208,7 +18284,7 @@ async function repairPathsOnce(db, name, repair) {
18208
18284
  }
18209
18285
 
18210
18286
  // src/agent-plugin.ts
18211
- import fs17 from "fs";
18287
+ import fs18 from "fs";
18212
18288
  import path11 from "path";
18213
18289
  var CANONRY_PLUGIN_ID = "canonry@canonry";
18214
18290
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -18253,7 +18329,7 @@ function isCanonryPluginId(value) {
18253
18329
  }
18254
18330
  function readJson(filePath) {
18255
18331
  try {
18256
- return JSON.parse(fs17.readFileSync(filePath, "utf8"));
18332
+ return JSON.parse(fs18.readFileSync(filePath, "utf8"));
18257
18333
  } catch {
18258
18334
  return null;
18259
18335
  }
@@ -18272,7 +18348,7 @@ function readCodexPluginConfigured(filePath) {
18272
18348
  try {
18273
18349
  let currentPlugin = null;
18274
18350
  let inPluginsTable = false;
18275
- for (const line of fs17.readFileSync(filePath, "utf8").split(/\r?\n/)) {
18351
+ for (const line of fs18.readFileSync(filePath, "utf8").split(/\r?\n/)) {
18276
18352
  const section = /^\s*\[plugins\.(?:"([^"]+)"|'([^']+)'|(\S+))\]\s*(?:#.*)?$/.exec(line);
18277
18353
  if (section) {
18278
18354
  const captures = section.slice(1, 4);
@@ -18305,7 +18381,7 @@ function readCodexPluginConfigured(filePath) {
18305
18381
  }
18306
18382
  }
18307
18383
  function verifiedPluginVersion(root, client) {
18308
- if (client === "codex" && fs17.existsSync(path11.join(root, "plugin.json"))) {
18384
+ if (client === "codex" && fs18.existsSync(path11.join(root, "plugin.json"))) {
18309
18385
  const manifest2 = readJson(path11.join(root, "plugin.json"));
18310
18386
  if (manifest2 && typeof manifest2 === "object") {
18311
18387
  const plugin2 = manifest2;
@@ -18319,7 +18395,7 @@ function verifiedPluginVersion(root, client) {
18319
18395
  if (portableMcp.$schema !== AGENT_PLUGINS_MCP_SCHEMA || portableMcp.mcpServers?.canonry?.type !== "stdio" || portableMcp.mcpServers.canonry.command !== "canonry-mcp") {
18320
18396
  return null;
18321
18397
  }
18322
- return REQUIRED_SKILLS.every((name) => fs17.existsSync(path11.join(root, "skills", name, "SKILL.md"))) ? plugin2.version : null;
18398
+ return REQUIRED_SKILLS.every((name) => fs18.existsSync(path11.join(root, "skills", name, "SKILL.md"))) ? plugin2.version : null;
18323
18399
  }
18324
18400
  if (typeof plugin2.$schema === "string" && plugin2.$schema.startsWith("https://agent-plugins.org/schemas/")) {
18325
18401
  return null;
@@ -18337,7 +18413,7 @@ function verifiedPluginVersion(root, client) {
18337
18413
  if (!mcp || typeof mcp !== "object") return null;
18338
18414
  const canonryServer = mcp.mcpServers?.canonry;
18339
18415
  if (canonryServer?.command !== "canonry-mcp") return null;
18340
- return REQUIRED_SKILLS.every((name) => fs17.existsSync(path11.join(root, "skills", name, "SKILL.md"))) ? plugin.version : null;
18416
+ return REQUIRED_SKILLS.every((name) => fs18.existsSync(path11.join(root, "skills", name, "SKILL.md"))) ? plugin.version : null;
18341
18417
  }
18342
18418
  function readClaudeUserInstallPaths(claudeConfigDir) {
18343
18419
  const parsed = readJson(path11.join(claudeConfigDir, "plugins", "installed_plugins.json"));
@@ -18354,7 +18430,7 @@ function readClaudeUserInstallPaths(claudeConfigDir) {
18354
18430
  }
18355
18431
  function listDirectories(dir) {
18356
18432
  try {
18357
- return fs17.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path11.join(dir, entry.name));
18433
+ return fs18.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path11.join(dir, entry.name));
18358
18434
  } catch {
18359
18435
  return [];
18360
18436
  }
@@ -20242,7 +20318,7 @@ var VISIBILITY_STATS_CLI_COMMANDS = [
20242
20318
  ];
20243
20319
 
20244
20320
  // src/cli-commands/wordpress.ts
20245
- import fs18 from "fs";
20321
+ import fs19 from "fs";
20246
20322
 
20247
20323
  // src/commands/wordpress.ts
20248
20324
  function getClient28() {
@@ -20481,12 +20557,12 @@ async function wordpressSetMeta(project2, body) {
20481
20557
  printPageDetail(result);
20482
20558
  }
20483
20559
  async function wordpressBulkSetMeta(project2, opts) {
20484
- const fs19 = await import("fs/promises");
20560
+ const fs20 = await import("fs/promises");
20485
20561
  const path12 = await import("path");
20486
20562
  const filePath = path12.resolve(opts.from);
20487
20563
  let raw;
20488
20564
  try {
20489
- raw = await fs19.readFile(filePath, "utf8");
20565
+ raw = await fs20.readFile(filePath, "utf8");
20490
20566
  } catch {
20491
20567
  throw new CliError({
20492
20568
  code: "FILE_READ_ERROR",
@@ -20583,13 +20659,13 @@ async function wordpressSetSchema(project2, body) {
20583
20659
  printManualAssist(`Schema update for "${body.slug}"`, result);
20584
20660
  }
20585
20661
  async function wordpressSchemaDeploy(project2, opts) {
20586
- const fs19 = await import("fs/promises");
20662
+ const fs20 = await import("fs/promises");
20587
20663
  const path12 = await import("path");
20588
20664
  const yaml = await loadYamlModule();
20589
20665
  const filePath = path12.resolve(opts.profile);
20590
20666
  let raw;
20591
20667
  try {
20592
- raw = await fs19.readFile(filePath, "utf8");
20668
+ raw = await fs20.readFile(filePath, "utf8");
20593
20669
  } catch {
20594
20670
  throw new CliError({
20595
20671
  code: "FILE_READ_ERROR",
@@ -20694,13 +20770,13 @@ async function wordpressOnboard(project2, opts) {
20694
20770
  }
20695
20771
  let profileData;
20696
20772
  if (opts.profile) {
20697
- const fs19 = await import("fs/promises");
20773
+ const fs20 = await import("fs/promises");
20698
20774
  const path12 = await import("path");
20699
20775
  const yaml = await loadYamlModule();
20700
20776
  const filePath = path12.resolve(opts.profile);
20701
20777
  let raw;
20702
20778
  try {
20703
- raw = await fs19.readFile(filePath, "utf8");
20779
+ raw = await fs20.readFile(filePath, "utf8");
20704
20780
  } catch {
20705
20781
  throw new CliError({
20706
20782
  code: "FILE_READ_ERROR",
@@ -20849,7 +20925,7 @@ function resolveContent(input, command, usage, options) {
20849
20925
  }
20850
20926
  if (contentFile) {
20851
20927
  try {
20852
- return fs18.readFileSync(contentFile, "utf-8");
20928
+ return fs19.readFileSync(contentFile, "utf-8");
20853
20929
  } catch (error) {
20854
20930
  const message = describeError(error);
20855
20931
  throw usageError(`Error: could not read --content-file "${contentFile}": ${message}`, {
@@ -22165,6 +22241,7 @@ var REGISTERED_CLI_COMMANDS = [
22165
22241
  ...USERS_CLI_COMMANDS,
22166
22242
  ...COMPETITOR_CLI_COMMANDS,
22167
22243
  ...SETTINGS_CLI_COMMANDS,
22244
+ ...DASHBOARD_CLI_COMMANDS,
22168
22245
  ...SKILLS_CLI_COMMANDS,
22169
22246
  ...SNAPSHOT_CLI_COMMANDS,
22170
22247
  ...RUN_CLI_COMMANDS,
@@ -22304,6 +22381,7 @@ Automation:
22304
22381
 
22305
22382
  Admin:
22306
22383
  settings Show/update provider and quota settings
22384
+ dashboard Reset the dashboard password
22307
22385
  backfill Backfill answer visibility or insights
22308
22386
  telemetry Manage anonymous telemetry
22309
22387
  history <project> Show audit trail
package/dist/index.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-6Z2LFFRT.js";
6
+ } from "./chunk-3OPV5XBY.js";
7
7
  import {
8
8
  loadConfig
9
- } from "./chunk-NDQOCFJQ.js";
9
+ } from "./chunk-UF7MH6TU.js";
10
10
  import "./chunk-222NAFTY.js";
11
11
  import "./chunk-5IXQAOVK.js";
12
12
  import "./chunk-46JZR2IV.js";
package/dist/mcp.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  autoSyncSkills
3
- } from "./chunk-6H2BC5EY.js";
3
+ } from "./chunk-KWBFKTOA.js";
4
4
  import {
5
5
  createApiClient,
6
6
  createCanonryMcpServer
7
- } from "./chunk-NDQOCFJQ.js";
7
+ } from "./chunk-UF7MH6TU.js";
8
8
  import "./chunk-5IXQAOVK.js";
9
9
  import {
10
10
  isReadOnlyKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "7.22.0",
3
+ "version": "7.23.0",
4
4
  "type": "module",
5
5
  "description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
6
6
  "keywords": [
@@ -85,34 +85,34 @@
85
85
  "@types/node-cron": "^3.0.11",
86
86
  "tsup": "^8.5.1",
87
87
  "tsx": "^4.19.0",
88
- "@ainyc/canonry-api-client": "0.0.0",
89
- "@ainyc/canonry-api-routes": "0.0.0",
90
88
  "@ainyc/canonry-web": "0.0.0",
89
+ "@ainyc/canonry-api-routes": "0.0.0",
90
+ "@ainyc/canonry-api-client": "0.0.0",
91
91
  "@ainyc/canonry-config": "0.0.0",
92
- "@ainyc/canonry-db": "0.0.0",
93
92
  "@ainyc/canonry-contracts": "0.0.0",
93
+ "@ainyc/canonry-db": "0.0.0",
94
+ "@ainyc/canonry-integration-bing": "0.0.0",
94
95
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
95
96
  "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
96
97
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
97
98
  "@ainyc/canonry-integration-google": "0.0.0",
98
99
  "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
99
- "@ainyc/canonry-integration-bing": "0.0.0",
100
- "@ainyc/canonry-integration-google-ads": "0.0.0",
101
- "@ainyc/canonry-integration-google-places": "0.0.0",
102
- "@ainyc/canonry-integration-google-business-profile": "0.0.0",
103
100
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
101
+ "@ainyc/canonry-integration-google-business-profile": "0.0.0",
102
+ "@ainyc/canonry-integration-google-places": "0.0.0",
103
+ "@ainyc/canonry-integration-google-ads": "0.0.0",
104
104
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
105
105
  "@ainyc/canonry-integration-traffic": "0.0.0",
106
106
  "@ainyc/canonry-integration-wordpress": "0.0.0",
107
- "@ainyc/canonry-intelligence": "0.0.0",
108
- "@ainyc/canonry-provider-gemini": "0.0.0",
109
107
  "@ainyc/canonry-provider-cdp": "0.0.0",
110
108
  "@ainyc/canonry-provider-claude": "0.0.0",
111
- "@ainyc/canonry-provider-local": "0.0.0",
109
+ "@ainyc/canonry-intelligence": "0.0.0",
110
+ "@ainyc/canonry-provider-gemini": "0.0.0",
111
+ "@ainyc/canonry-provider-muse": "0.0.0",
112
112
  "@ainyc/canonry-provider-openai": "0.0.0",
113
+ "@ainyc/canonry-integration-typesafe": "0.0.0",
113
114
  "@ainyc/canonry-provider-perplexity": "0.0.0",
114
- "@ainyc/canonry-provider-muse": "0.0.0",
115
- "@ainyc/canonry-integration-typesafe": "0.0.0"
115
+ "@ainyc/canonry-provider-local": "0.0.0"
116
116
  },
117
117
  "scripts": {
118
118
  "build": "pnpm run build:cli && pnpm run build:web",