@ainyc/canonry 5.3.1 → 5.5.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.
Files changed (37) hide show
  1. package/README.md +14 -15
  2. package/assets/assets/{AuditHistoryPanel-D5cOFMKE.js → AuditHistoryPanel-BMGGxHpw.js} +1 -1
  3. package/assets/assets/{BacklinksPage-BiOj8ztV.js → BacklinksPage-CS3wJSWe.js} +1 -1
  4. package/assets/assets/{HistoryPage-DDBCwooU.js → HistoryPage-Ba9ezeuP.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-CMoU2yA_.js → MeasurementPropertyPage-Y9eseEv0.js} +1 -1
  6. package/assets/assets/ProjectPage-Cl1N9Dcw.js +8 -0
  7. package/assets/assets/{RunRow-BLBAwIot.js → RunRow-DbUtgNUI.js} +1 -1
  8. package/assets/assets/{RunsPage-BVgBbE1M.js → RunsPage-BBuFIYuS.js} +1 -1
  9. package/assets/assets/{SettingsPage-DYtN-1_-.js → SettingsPage-DNnR33D-.js} +1 -1
  10. package/assets/assets/SiteHealthSection-Cf4KHWp0.js +4 -0
  11. package/assets/assets/TrafficPage-DSQjaZM9.js +40 -0
  12. package/assets/assets/{TrafficSourceDetailPage-BnLzF82w.js → TrafficSourceDetailPage-C-Pg69TW.js} +1 -1
  13. package/assets/assets/{extract-error-message-DIzjGZxl.js → extract-error-message-DNLmL0ID.js} +1 -1
  14. package/assets/assets/index-BCCrbno5.js +86 -0
  15. package/assets/assets/index-gQj7x9RX.css +1 -0
  16. package/assets/assets/{react-sigma_core.esm.min-DTbIcdn4.js → react-sigma_core.esm.min-DkItaBPY.js} +1 -1
  17. package/assets/assets/{v2-overview-adapter-B_N4ilQV.js → v2-overview-adapter-DKBMcYBh.js} +1 -1
  18. package/assets/assets/vendor-lucide-CneDbhOr.js +1 -0
  19. package/assets/index.html +3 -3
  20. package/dist/{chunk-TF6HFT3Q.js → chunk-75ECQ77U.js} +15 -6
  21. package/dist/{chunk-IACO5R7A.js → chunk-BL3FMPSM.js} +15 -9
  22. package/dist/{chunk-C7PITYL3.js → chunk-DX4CZNNU.js} +2 -2
  23. package/dist/{chunk-IOP36L3C.js → chunk-KKDXJWDK.js} +21 -6
  24. package/dist/{chunk-3COTRCK3.js → chunk-LQAOGFFK.js} +2 -2
  25. package/dist/{chunk-2U5IW3G5.js → chunk-REQEWYNF.js} +44 -2
  26. package/dist/cli.js +132 -65
  27. package/dist/{demo-server-TXW3UX3P.js → demo-server-BSH6SSWD.js} +3 -3
  28. package/dist/index.js +5 -5
  29. package/dist/{intelligence-service-H34EOB6K.js → intelligence-service-CU7W5AVC.js} +2 -2
  30. package/dist/mcp.js +3 -3
  31. package/package.json +9 -9
  32. package/assets/assets/ProjectPage-CrFybBls.js +0 -8
  33. package/assets/assets/SiteHealthSection-ElUwcAS4.js +0 -4
  34. package/assets/assets/TrafficPage-D6QT_2hU.js +0 -1
  35. package/assets/assets/index-CoQVOsWw.js +0 -86
  36. package/assets/assets/index-DCLmKL1s.css +0 -1
  37. package/assets/assets/vendor-lucide-nVpYViBH.js +0 -1
package/dist/cli.js CHANGED
@@ -19,12 +19,14 @@ import {
19
19
  formatUpdateNotice,
20
20
  getCloudflareTrafficConnectionBySourceId,
21
21
  getOrCreateAnonymousId,
22
+ getPrintedUpdateAvailable,
22
23
  getTelemetryStatus,
23
24
  isAeroToolProfile,
24
25
  isFirstRun,
25
26
  isLoopbackBindHost,
26
27
  isTelemetryEnabled,
27
28
  listAgentProviders,
29
+ notePrintedUpdateAvailable,
28
30
  readCachedUpdateAvailable,
29
31
  setGoogleAuthConfig,
30
32
  setTelemetryPreference,
@@ -33,11 +35,11 @@ import {
33
35
  trackCliCommandFinished,
34
36
  trackEvent,
35
37
  waitForServerRuntimeStartup
36
- } from "./chunk-TF6HFT3Q.js";
38
+ } from "./chunk-75ECQ77U.js";
37
39
  import {
38
40
  autoSyncSkills,
39
41
  formatAutoSyncNotice
40
- } from "./chunk-C7PITYL3.js";
42
+ } from "./chunk-DX4CZNNU.js";
41
43
  import {
42
44
  CliError,
43
45
  EXIT_SYSTEM_ERROR,
@@ -61,15 +63,16 @@ import {
61
63
  runMeasurementDraftAction,
62
64
  saveConfig,
63
65
  saveConfigPatch,
66
+ shouldPrintServeSkillsNudge,
64
67
  systemError,
65
68
  usageError
66
- } from "./chunk-IOP36L3C.js";
69
+ } from "./chunk-KKDXJWDK.js";
67
70
  import {
68
71
  DEFAULT_DEMO_TRUSTED_PROXIES,
69
72
  trustedProxiesProblem,
70
73
  trustedProxyProblem
71
74
  } from "./chunk-RB2F4D3Z.js";
72
- import "./chunk-3COTRCK3.js";
75
+ import "./chunk-LQAOGFFK.js";
73
76
  import "./chunk-AOJU5LKX.js";
74
77
  import {
75
78
  CLOUDFLARE_WORKER_BINDINGS,
@@ -83,8 +86,9 @@ import {
83
86
  migrate,
84
87
  projects,
85
88
  queries,
86
- renderReportHtml
87
- } from "./chunk-IACO5R7A.js";
89
+ renderReportHtml,
90
+ runs
91
+ } from "./chunk-BL3FMPSM.js";
88
92
  import {
89
93
  AdsDeliverySnapshotStatuses,
90
94
  AdsHistoricalCampaignRollupStatuses,
@@ -101,7 +105,9 @@ import {
101
105
  READ_ONLY_SCOPE,
102
106
  REPORT_PERIOD_OPTIONS,
103
107
  ResearchRunStatuses,
108
+ RunKinds,
104
109
  RunStatuses,
110
+ RunTriggers,
105
111
  TrafficEventKinds,
106
112
  TrafficSeriesGranularities,
107
113
  USER_PASSWORD_MIN_LENGTH,
@@ -163,7 +169,7 @@ import {
163
169
  snapshotProviderModeSchema,
164
170
  visibilityReportRequestSchema,
165
171
  winnabilityClassSchema
166
- } from "./chunk-2U5IW3G5.js";
172
+ } from "./chunk-REQEWYNF.js";
167
173
 
168
174
  // src/cli.ts
169
175
  import { pathToFileURL } from "url";
@@ -428,7 +434,7 @@ function parseDemoTrustedProxies(values) {
428
434
  async function demoCommand(options) {
429
435
  const { host, port } = parseDemoListenOptions(options);
430
436
  const trustProxy = parseDemoTrustedProxies(options.trustProxy);
431
- const { createDemoServer } = await import("./demo-server-TXW3UX3P.js");
437
+ const { createDemoServer } = await import("./demo-server-BSH6SSWD.js");
432
438
  const app = await createDemoServer({ trustProxy });
433
439
  let stopping = false;
434
440
  const stop = () => {
@@ -1818,18 +1824,18 @@ function errorMessage(err) {
1818
1824
  async function discoverRun(project, opts) {
1819
1825
  const client = getClient4();
1820
1826
  const { angles, multiAngle } = resolveIcpAngles(opts);
1821
- const runs = [];
1827
+ const runs2 = [];
1822
1828
  for (const angle of angles) {
1823
1829
  try {
1824
1830
  const start = await client.triggerDiscoveryRun(project, buildRunBody(opts, angle));
1825
- runs.push({ angle, start });
1831
+ runs2.push({ angle, start });
1826
1832
  } catch (err) {
1827
- if (runs.length > 0) {
1833
+ if (runs2.length > 0) {
1828
1834
  process.stderr.write(
1829
1835
  `
1830
1836
  Failed to start ${angle ? `angle "${angle}"` : "discovery run"}: ${errorMessage(err)}
1831
1837
  Sessions already started (recover with \`canonry discover show ${project} <id>\`):
1832
- ` + runs.map((r) => ` ${r.start.sessionId}`).join("\n") + "\n"
1838
+ ` + runs2.map((r) => ` ${r.start.sessionId}`).join("\n") + "\n"
1833
1839
  );
1834
1840
  }
1835
1841
  throw err;
@@ -1837,10 +1843,10 @@ Sessions already started (recover with \`canonry discover show ${project} <id>\`
1837
1843
  }
1838
1844
  if (!opts.wait) {
1839
1845
  if (isMachineFormat(opts.format)) {
1840
- console.log(JSON.stringify(multiAngle ? runs.map((r) => r.start) : runs[0].start, null, 2));
1846
+ console.log(JSON.stringify(multiAngle ? runs2.map((r) => r.start) : runs2[0].start, null, 2));
1841
1847
  return;
1842
1848
  }
1843
- for (const { angle, start } of runs) {
1849
+ for (const { angle, start } of runs2) {
1844
1850
  if (angle) console.log(`[${angle}]`);
1845
1851
  if (start.consolidated) {
1846
1852
  console.log(`Reusing in-flight discovery session: ${start.sessionId}`);
@@ -1853,20 +1859,20 @@ Sessions already started (recover with \`canonry discover show ${project} <id>\`
1853
1859
  console.log(` Status: ${start.status}`);
1854
1860
  console.log(` Tail: canonry discover show ${project} ${start.sessionId}`);
1855
1861
  }
1856
- if (runs.length > 1) console.log();
1862
+ if (runs2.length > 1) console.log();
1857
1863
  }
1858
1864
  return;
1859
1865
  }
1860
- const parallel = runs.length > 1;
1861
- if (parallel) process.stderr.write(`Waiting for ${runs.length} discovery sessions...
1866
+ const parallel = runs2.length > 1;
1867
+ if (parallel) process.stderr.write(`Waiting for ${runs2.length} discovery sessions...
1862
1868
  `);
1863
1869
  const settled = await Promise.allSettled(
1864
- runs.map((r) => pollSession(client, project, r.start.sessionId, parallel))
1870
+ runs2.map((r) => pollSession(client, project, r.start.sessionId, parallel))
1865
1871
  );
1866
1872
  const results = [];
1867
1873
  const failures = [];
1868
1874
  settled.forEach((outcome, i) => {
1869
- const run = runs[i];
1875
+ const run = runs2[i];
1870
1876
  if (outcome.status === "fulfilled") {
1871
1877
  results.push({ angle: run.angle, session: outcome.value });
1872
1878
  } else {
@@ -1906,7 +1912,7 @@ Sessions already started (recover with \`canonry discover show ${project} <id>\`
1906
1912
  }
1907
1913
  throw new CliError({
1908
1914
  code: "DISCOVERY_INCOMPLETE",
1909
- message: `${failures.length} of ${runs.length} discovery session(s) did not complete`,
1915
+ message: `${failures.length} of ${runs2.length} discovery session(s) did not complete`,
1910
1916
  details: { failed: failures.map((f) => f.sessionId) }
1911
1917
  });
1912
1918
  }
@@ -2750,14 +2756,14 @@ async function researchBatch(project, source, opts) {
2750
2756
  }
2751
2757
  const client = getClient5();
2752
2758
  const started = await client.startResearchBatch(project, parsed.data);
2753
- const runs = opts.wait ? await Promise.all(started.runs.map((run) => TERMINAL_RESEARCH_STATUSES.has(run.status) ? Promise.resolve(run) : pollResearchRun(client, project, run.id, true))) : started.runs;
2759
+ const runs2 = opts.wait ? await Promise.all(started.runs.map((run) => TERMINAL_RESEARCH_STATUSES.has(run.status) ? Promise.resolve(run) : pollResearchRun(client, project, run.id, true))) : started.runs;
2754
2760
  if (opts.format === "jsonl") {
2755
- emitJsonl(runs.map((run) => ({ project, ...run })));
2761
+ emitJsonl(runs2.map((run) => ({ project, ...run })));
2756
2762
  } else if (opts.format === "json") {
2757
- console.log(JSON.stringify({ runs }, null, 2));
2763
+ console.log(JSON.stringify({ runs: runs2 }, null, 2));
2758
2764
  } else {
2759
- console.log(`Research saved for ${runs.length} destinations (${runs.reduce((count, run) => count + run.totalQueries, 0)} queries).`);
2760
- for (const run of runs) {
2765
+ console.log(`Research saved for ${runs2.length} destinations (${runs2.reduce((count, run) => count + run.totalQueries, 0)} queries).`);
2766
+ for (const run of runs2) {
2761
2767
  if (opts.wait) printDetail(project, run);
2762
2768
  else printStarted(project, run);
2763
2769
  }
@@ -2766,24 +2772,24 @@ async function researchBatch(project, source, opts) {
2766
2772
  async function researchList(project, opts) {
2767
2773
  const client = getClient5();
2768
2774
  const result = await client.listResearchRuns(project, opts.limit === void 0 && opts.cursor === void 0 ? void 0 : { limit: opts.limit, cursor: opts.cursor });
2769
- const { runs } = result;
2775
+ const { runs: runs2 } = result;
2770
2776
  if (opts.format === "json") {
2771
2777
  console.log(JSON.stringify(result, null, 2));
2772
2778
  return;
2773
2779
  }
2774
2780
  if (opts.format === "jsonl") {
2775
- emitJsonl(runs.map((run) => ({ project, ...run })));
2781
+ emitJsonl(runs2.map((run) => ({ project, ...run })));
2776
2782
  return;
2777
2783
  }
2778
- if (runs.length === 0) {
2784
+ if (runs2.length === 0) {
2779
2785
  console.log(`No saved research runs for "${project}".`);
2780
2786
  return;
2781
2787
  }
2782
- console.log(`Research history for "${project}" (${runs.length}):
2788
+ console.log(`Research history for "${project}" (${runs2.length}):
2783
2789
  `);
2784
2790
  console.log(" ID STATUS QUERIES PROVIDER / MODEL SCOPE CREATED");
2785
2791
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
2786
- for (const run of runs) {
2792
+ for (const run of runs2) {
2787
2793
  const model = run.resolvedModel ? `${run.provider} / ${run.resolvedModel}` : run.provider;
2788
2794
  console.log(` ${run.id.padEnd(36)} ${run.status.padEnd(10)} ${String(run.totalQueries).padStart(7)} ${model.slice(0, 32).padEnd(32)} ${formatScope(run.scope).slice(0, 24).padEnd(24)} ${run.createdAt}`);
2789
2795
  }
@@ -11535,9 +11541,9 @@ async function loadLatestRunForExport(client, project) {
11535
11541
  } catch (err) {
11536
11542
  if (!isEndpointMissing(err)) throw err;
11537
11543
  }
11538
- const runs = await client.listRuns(project);
11539
- if (runs.length === 0) return null;
11540
- const latestRun = runs.reduce(
11544
+ const runs2 = await client.listRuns(project);
11545
+ if (runs2.length === 0) return null;
11546
+ const latestRun = runs2.reduce(
11541
11547
  (current, candidate) => candidate.createdAt > current.createdAt ? candidate : current
11542
11548
  );
11543
11549
  return client.getRun(latestRun.id);
@@ -11633,14 +11639,14 @@ async function showStatus(project, format) {
11633
11639
  const projectData = await client.getProject(project);
11634
11640
  const latest = await getLatestRunSummary(client, project);
11635
11641
  if (isMachineFormat(format)) {
11636
- let runs = [];
11642
+ let runs2 = [];
11637
11643
  try {
11638
- runs = await client.listRuns(project);
11644
+ runs2 = await client.listRuns(project);
11639
11645
  } catch {
11640
11646
  }
11641
11647
  console.log(JSON.stringify({
11642
11648
  project: projectData,
11643
- runs,
11649
+ runs: runs2,
11644
11650
  latestRun: latest.run,
11645
11651
  totalRuns: latest.totalRuns
11646
11652
  }, null, 2));
@@ -11663,7 +11669,9 @@ async function showStatus(project, format) {
11663
11669
  console.log(`
11664
11670
  Total runs: ${latest.totalRuns}`);
11665
11671
  } else {
11666
- console.log('\n No runs yet. Use "canonry run" to trigger one.');
11672
+ const projectArg = /^[\w.-]+$/.test(project) ? project : `'${project.replaceAll("'", `'\\''`)}'`;
11673
+ console.log(`
11674
+ No runs yet. Capture Page Health with "canonry technical-aeo run ${projectArg}", or run an AI Visibility sweep after adding a provider.`);
11667
11675
  }
11668
11676
  }
11669
11677
  async function getLatestRunSummary(client, project) {
@@ -11671,15 +11679,15 @@ async function getLatestRunSummary(client, project) {
11671
11679
  return await client.getLatestRun(project);
11672
11680
  } catch (err) {
11673
11681
  if (!isEndpointMissing(err)) throw err;
11674
- const runs = await client.listRuns(project);
11675
- if (runs.length === 0) {
11682
+ const runs2 = await client.listRuns(project);
11683
+ if (runs2.length === 0) {
11676
11684
  return { totalRuns: 0, run: null };
11677
11685
  }
11678
- const latestRun = runs.reduce(
11686
+ const latestRun = runs2.reduce(
11679
11687
  (current, candidate) => candidate.createdAt > current.createdAt ? candidate : current
11680
11688
  );
11681
11689
  return {
11682
- totalRuns: runs.length,
11690
+ totalRuns: runs2.length,
11683
11691
  run: latestRun
11684
11692
  };
11685
11693
  }
@@ -12110,6 +12118,7 @@ var PROJECT_CLI_COMMANDS = [
12110
12118
  {
12111
12119
  path: ["project", "create"],
12112
12120
  usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--provider-model provider=model...] [--format json]",
12121
+ help: "Create a project. Pass --domain for the public site to scan. Provider credentials are not required for Page Health.",
12113
12122
  options: {
12114
12123
  domain: { type: "string", short: "d" },
12115
12124
  "owned-domain": multiStringOption(),
@@ -12643,8 +12652,8 @@ async function cancelRun(project, runId, format) {
12643
12652
  const client = getClient23();
12644
12653
  let targetId = runId;
12645
12654
  if (!targetId) {
12646
- const runs = await client.listRuns(project);
12647
- const activeRuns = runs.filter((r) => r.status === "queued" || r.status === "running");
12655
+ const runs2 = await client.listRuns(project);
12656
+ const activeRuns = runs2.filter((r) => r.status === "queued" || r.status === "running");
12648
12657
  if (activeRuns.length > 1) {
12649
12658
  const candidates = activeRuns.map(({ id, kind, status }) => ({ id, kind, status }));
12650
12659
  throw new CliError({
@@ -12698,23 +12707,23 @@ async function showRun(id, format) {
12698
12707
  }
12699
12708
  async function listRuns(project, opts) {
12700
12709
  const client = getClient23();
12701
- const runs = await client.listRuns(project, opts?.limit, opts?.kind, opts?.status);
12710
+ const runs2 = await client.listRuns(project, opts?.limit, opts?.kind, opts?.status);
12702
12711
  if (opts?.format === "json") {
12703
- console.log(JSON.stringify(runs, null, 2));
12712
+ console.log(JSON.stringify(runs2, null, 2));
12704
12713
  return;
12705
12714
  } else if (opts?.format === "jsonl") {
12706
- emitJsonl(runs.map((run) => ({ project, ...run })));
12715
+ emitJsonl(runs2.map((run) => ({ project, ...run })));
12707
12716
  return;
12708
12717
  }
12709
- if (runs.length === 0) {
12718
+ if (runs2.length === 0) {
12710
12719
  console.log(`No runs found for "${project}".`);
12711
12720
  return;
12712
12721
  }
12713
- console.log(`Runs for "${project}" (${runs.length}):
12722
+ console.log(`Runs for "${project}" (${runs2.length}):
12714
12723
  `);
12715
12724
  console.log(" ID STATUS KIND TRIGGER CREATED");
12716
12725
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
12717
- for (const run of runs) {
12726
+ for (const run of runs2) {
12718
12727
  console.log(
12719
12728
  ` ${run.id} ${run.status.padEnd(10)} ${run.kind.padEnd(18)} ${run.trigger.padEnd(9)} ${run.createdAt}`
12720
12729
  );
@@ -14907,7 +14916,7 @@ async function bootstrapCommand(opts) {
14907
14916
  console.log("Providers: none (Page Health works now; add one later to enable AI Visibility).");
14908
14917
  }
14909
14918
  if (generatedApiKey) {
14910
- console.log(`API key: ${generatedApiKey}`);
14919
+ console.log(`API key (for the CLI, MCP, and agents): ${generatedApiKey}`);
14911
14920
  }
14912
14921
  }
14913
14922
 
@@ -15232,7 +15241,7 @@ async function initCommand(opts) {
15232
15241
  throw new CliError({
15233
15242
  code: "INIT_JSON_REQUIRES_NON_INTERACTIVE",
15234
15243
  message: "--format json requires non-interactive provider configuration via flags or environment variables.",
15235
- displayMessage: "--format json requires non-interactive provider configuration via flags or environment variables.",
15244
+ displayMessage: '--format json requires provider flags or environment variables. For provider-free first-run setup, use "canonry bootstrap" instead.',
15236
15245
  details: {
15237
15246
  required: ["provider flags or environment variables"]
15238
15247
  }
@@ -15306,7 +15315,7 @@ async function initCommand(opts) {
15306
15315
  throw new CliError({
15307
15316
  code: "INIT_PROVIDER_REQUIRED",
15308
15317
  message: "At least one provider is required.",
15309
- displayMessage: "\nAt least one provider is required.",
15318
+ displayMessage: '\nAt least one provider is required. For provider-free first-run setup, use "canonry bootstrap" instead.',
15310
15319
  details: {
15311
15320
  required: ["provider"]
15312
15321
  }
@@ -15501,6 +15510,9 @@ function encodeLegacySetupState(state) {
15501
15510
  return flags.length > 0 ? flags.sort().join("|") : "none";
15502
15511
  }
15503
15512
 
15513
+ // src/commands/serve.ts
15514
+ import { and, asc, eq as eq2, ne } from "drizzle-orm";
15515
+
15504
15516
  // src/server-shutdown.ts
15505
15517
  async function closeWithIdleSweep(app, intervalMs = 250) {
15506
15518
  const sweep = setInterval(() => app.server.closeIdleConnections(), intervalMs);
@@ -15710,6 +15722,47 @@ function detectCanonryAgentPlugin(opts = {}) {
15710
15722
  function shouldWarnAboutRemoteSetup(host) {
15711
15723
  return !isLoopbackBindHost(host);
15712
15724
  }
15725
+ function readServeOpenState(db) {
15726
+ try {
15727
+ const rows = db.select({
15728
+ id: projects.id,
15729
+ name: projects.name,
15730
+ createdAt: projects.createdAt
15731
+ }).from(projects).orderBy(asc(projects.createdAt), asc(projects.name)).all();
15732
+ if (rows.length === 0) return { projectCount: 0, hasSiteAudit: false };
15733
+ let scanned;
15734
+ try {
15735
+ const audits = db.select({
15736
+ projectId: runs.projectId,
15737
+ status: runs.status
15738
+ }).from(runs).where(and(
15739
+ eq2(runs.kind, RunKinds["site-audit"]),
15740
+ ne(runs.trigger, RunTriggers.probe)
15741
+ )).all();
15742
+ scanned = new Set(
15743
+ audits.filter((run) => run.status === RunStatuses.completed || run.status === RunStatuses.partial).map((run) => run.projectId)
15744
+ );
15745
+ } catch {
15746
+ return { projectCount: rows.length, firstProjectName: rows[0]?.name, hasSiteAudit: true };
15747
+ }
15748
+ const firstUnscanned = rows.find((row) => !scanned.has(row.id));
15749
+ if (firstUnscanned) {
15750
+ return { projectCount: rows.length, firstProjectName: firstUnscanned.name, hasSiteAudit: false };
15751
+ }
15752
+ return { projectCount: rows.length, firstProjectName: rows[0]?.name, hasSiteAudit: true };
15753
+ } catch {
15754
+ return { projectCount: 0, hasSiteAudit: false };
15755
+ }
15756
+ }
15757
+ function buildServeOpenLine(input) {
15758
+ if (input.projectCount === 0) {
15759
+ return `Open ${input.url}/setup to map your site and run your first Page Health scan.`;
15760
+ }
15761
+ if (!input.hasSiteAudit && input.firstProjectName) {
15762
+ return `Open ${input.url}/setup?onboarding=site-health&setupProject=${encodeURIComponent(input.firstProjectName)} to run your first Page Health scan.`;
15763
+ }
15764
+ return `Open ${input.url}`;
15765
+ }
15713
15766
  async function serveCommand(format = "text") {
15714
15767
  const config = loadConfig();
15715
15768
  const port = resolveServePort(process.env.CANONRY_PORT, config.port);
@@ -15750,8 +15803,6 @@ async function serveCommand(format = "text") {
15750
15803
  let listening = false;
15751
15804
  try {
15752
15805
  await app.listen({ host, port });
15753
- await waitForServerRuntimeStartup(app);
15754
- listening = true;
15755
15806
  let shuttingDown = false;
15756
15807
  const shutdown = (signal) => {
15757
15808
  if (shuttingDown) return;
@@ -15770,24 +15821,29 @@ Received ${signal}, stopping server...`);
15770
15821
  process.on("SIGTERM", () => shutdown("SIGTERM"));
15771
15822
  process.on("SIGINT", () => shutdown("SIGINT"));
15772
15823
  const url = operatorHttpUrl(host, port);
15773
- if (isMachineFormat(format)) {
15774
- console.log(JSON.stringify({
15775
- started: true,
15776
- host,
15777
- port,
15778
- url
15779
- }, null, 2));
15780
- } else {
15824
+ if (!isMachineFormat(format)) {
15781
15825
  console.log(`
15782
15826
  Canonry server running at ${url}`);
15783
- console.log(`Open ${url}/setup to map your site and run your first Page Health scan.`);
15827
+ console.log(buildServeOpenLine({ url, ...readServeOpenState(db) }));
15784
15828
  if (shouldWarnAboutRemoteSetup(host)) {
15785
15829
  console.log("First-run dashboard password setup is unauthenticated only on loopback; complete setup from this machine first or use a bearer cnry_... key.");
15786
15830
  }
15787
15831
  console.log("Press Ctrl+C to stop.\n");
15788
15832
  const nudge = getMissingUserSkillsNudge(process.env.HOME, getAgentPluginState());
15789
- if (nudge) process.stderr.write(`${nudge.message}
15833
+ if (shouldPrintServeSkillsNudge(nudge, getPrintedUpdateAvailable())) {
15834
+ process.stderr.write(`${nudge.message}
15790
15835
  `);
15836
+ }
15837
+ }
15838
+ await waitForServerRuntimeStartup(app);
15839
+ listening = true;
15840
+ if (isMachineFormat(format)) {
15841
+ console.log(JSON.stringify({
15842
+ started: true,
15843
+ host,
15844
+ port,
15845
+ url
15846
+ }, null, 2));
15791
15847
  }
15792
15848
  setTelemetrySource("cli-server");
15793
15849
  const providerNames = Object.keys(config.providers ?? {}).filter(
@@ -15972,6 +16028,7 @@ var SYSTEM_CLI_COMMANDS = [
15972
16028
  {
15973
16029
  path: ["init"],
15974
16030
  usage: "canonry init [--force] [--gemini-key <key>] [--openai-key <key>] [--claude-key <key>] [--perplexity-key <key>] [--local-url <url>] [--local-model <name>] [--local-key <key>] [--google-client-id <id>] [--google-client-secret <key>] [--skip-skills] [--skip-mcp] [--skills-dir <path>] [--format json]",
16031
+ help: 'Optional interactive provider and OAuth setup. First-run Page Health does not need this: use "canonry bootstrap", then "canonry serve". At least one provider is required here. --format json needs provider flags or environment variables.',
15975
16032
  options: {
15976
16033
  force: { type: "boolean", short: "f", default: false },
15977
16034
  "gemini-key": stringOption(),
@@ -16676,6 +16733,7 @@ var TECHNICAL_AEO_CLI_COMMANDS_BASE = [
16676
16733
  {
16677
16734
  path: ["technical-aeo", "run"],
16678
16735
  usage: "canonry technical-aeo run <project> [--sitemap-url <url>] [--max-pages <n>] [--max-edges <n>] [--max-depth <n>] [--check-dead-links] [--wait] [--format json]",
16736
+ help: "Start a Site Health crawl. No answer-engine provider is required. --wait polls for up to 15 minutes. Dead-link checking is off unless --check-dead-links is set.",
16679
16737
  options: {
16680
16738
  "sitemap-url": stringOption(),
16681
16739
  limit: stringOption(),
@@ -19235,9 +19293,17 @@ var REGISTERED_CLI_COMMANDS = [
19235
19293
  var NUDGE_EXEMPT_ROOTS = /* @__PURE__ */ new Set([
19236
19294
  "init",
19237
19295
  "serve",
19296
+ "start",
19297
+ "stop",
19238
19298
  "bootstrap",
19239
19299
  "settings",
19240
19300
  "telemetry",
19301
+ "project",
19302
+ "technical-aeo",
19303
+ "site-health",
19304
+ "doctor",
19305
+ "demo",
19306
+ "status",
19241
19307
  "unknown"
19242
19308
  ]);
19243
19309
  function buildSetupNudgeLine(input) {
@@ -19247,7 +19313,7 @@ function buildSetupNudgeLine(input) {
19247
19313
  const setupState = input.getSetupState();
19248
19314
  if (!setupState) return null;
19249
19315
  if (setupState.provider_count > 0) return null;
19250
- return "\n\u2192 No AI provider is configured, so answer sweeps cannot run yet.\n Finish setup in the dashboard: canonry serve\n Or by CLI: canonry settings provider gemini --api-key <key>\n";
19316
+ return "\n\u2192 AI Visibility needs an answer-engine provider. Page Health does not.\n Add one in the dashboard after `canonry serve`, or:\n canonry settings provider gemini --api-key <key>\n";
19251
19317
  }
19252
19318
 
19253
19319
  // src/cli.ts
@@ -19403,6 +19469,7 @@ async function runCli(args = process.argv.slice(2)) {
19403
19469
  }
19404
19470
  if (!isHelpRequest && command !== "telemetry") {
19405
19471
  const update = readCachedUpdateAvailable();
19472
+ notePrintedUpdateAvailable(update);
19406
19473
  if (update) {
19407
19474
  process.stderr.write(formatUpdateNotice(update, { format, interactive: Boolean(process.stderr.isTTY) }));
19408
19475
  }
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  layoutSiteCrawlGraphInput,
6
6
  persistSiteCrawlGraphLayout
7
- } from "./chunk-3COTRCK3.js";
7
+ } from "./chunk-LQAOGFFK.js";
8
8
  import {
9
9
  PACKAGE_VERSION
10
10
  } from "./chunk-AOJU5LKX.js";
@@ -83,7 +83,7 @@ import {
83
83
  siteCrawlPages,
84
84
  siteCrawlSnapshots,
85
85
  trafficSources
86
- } from "./chunk-IACO5R7A.js";
86
+ } from "./chunk-BL3FMPSM.js";
87
87
  import {
88
88
  RunKinds,
89
89
  SiteCrawlFetchStates,
@@ -104,7 +104,7 @@ import {
104
104
  measurementPlanV2Schema,
105
105
  placementLinkDecision,
106
106
  siteAuditPageFactorSchema
107
- } from "./chunk-2U5IW3G5.js";
107
+ } from "./chunk-REQEWYNF.js";
108
108
 
109
109
  // src/demo-server.ts
110
110
  import { dirname, join as join2 } from "path";
package/dist/index.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-TF6HFT3Q.js";
6
+ } from "./chunk-75ECQ77U.js";
7
7
  import {
8
8
  loadConfig
9
- } from "./chunk-IOP36L3C.js";
10
- import "./chunk-3COTRCK3.js";
9
+ } from "./chunk-KKDXJWDK.js";
10
+ import "./chunk-LQAOGFFK.js";
11
11
  import "./chunk-AOJU5LKX.js";
12
- import "./chunk-IACO5R7A.js";
13
- import "./chunk-2U5IW3G5.js";
12
+ import "./chunk-BL3FMPSM.js";
13
+ import "./chunk-REQEWYNF.js";
14
14
  export {
15
15
  GoogleMarketingRuntimeError,
16
16
  createGoogleMarketingCredentialStore,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  HISTORY_WINDOW_RUNS,
3
3
  IntelligenceService
4
- } from "./chunk-IACO5R7A.js";
5
- import "./chunk-2U5IW3G5.js";
4
+ } from "./chunk-BL3FMPSM.js";
5
+ import "./chunk-REQEWYNF.js";
6
6
  export {
7
7
  HISTORY_WINDOW_RUNS,
8
8
  IntelligenceService
package/dist/mcp.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  autoSyncSkills
3
- } from "./chunk-C7PITYL3.js";
3
+ } from "./chunk-DX4CZNNU.js";
4
4
  import {
5
5
  createApiClient,
6
6
  createCanonryMcpServer
7
- } from "./chunk-IOP36L3C.js";
7
+ } from "./chunk-KKDXJWDK.js";
8
8
  import "./chunk-AOJU5LKX.js";
9
9
  import {
10
10
  isReadOnlyKey,
11
11
  updateCheckEnvOptOut
12
- } from "./chunk-2U5IW3G5.js";
12
+ } from "./chunk-REQEWYNF.js";
13
13
 
14
14
  // src/mcp/cli.ts
15
15
  import { randomUUID } from "crypto";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "5.3.1",
3
+ "version": "5.5.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": [
@@ -88,26 +88,26 @@
88
88
  "tsx": "^4.19.0",
89
89
  "@ainyc/canonry-web": "0.0.0",
90
90
  "@ainyc/canonry-api-client": "0.0.0",
91
+ "@ainyc/canonry-api-routes": "0.0.0",
91
92
  "@ainyc/canonry-config": "0.0.0",
92
93
  "@ainyc/canonry-contracts": "0.0.0",
93
- "@ainyc/canonry-api-routes": "0.0.0",
94
- "@ainyc/canonry-integration-bing": "0.0.0",
95
94
  "@ainyc/canonry-db": "0.0.0",
96
95
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
97
96
  "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
97
+ "@ainyc/canonry-integration-bing": "0.0.0",
98
98
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
99
- "@ainyc/canonry-integration-google": "0.0.0",
100
99
  "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
101
- "@ainyc/canonry-integration-google-places": "0.0.0",
102
- "@ainyc/canonry-integration-google-business-profile": "0.0.0",
100
+ "@ainyc/canonry-integration-google": "0.0.0",
103
101
  "@ainyc/canonry-integration-google-ads": "0.0.0",
102
+ "@ainyc/canonry-integration-google-business-profile": "0.0.0",
103
+ "@ainyc/canonry-integration-google-places": "0.0.0",
104
104
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
105
- "@ainyc/canonry-integration-traffic": "0.0.0",
106
105
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
106
+ "@ainyc/canonry-integration-traffic": "0.0.0",
107
+ "@ainyc/canonry-intelligence": "0.0.0",
107
108
  "@ainyc/canonry-integration-wordpress": "0.0.0",
108
- "@ainyc/canonry-provider-cdp": "0.0.0",
109
109
  "@ainyc/canonry-provider-claude": "0.0.0",
110
- "@ainyc/canonry-intelligence": "0.0.0",
110
+ "@ainyc/canonry-provider-cdp": "0.0.0",
111
111
  "@ainyc/canonry-provider-gemini": "0.0.0",
112
112
  "@ainyc/canonry-provider-openai": "0.0.0",
113
113
  "@ainyc/canonry-provider-local": "0.0.0",