@canonry/canonry 7.14.0 → 7.15.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 (30) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +5 -2
  2. package/assets/assets/{AuditHistoryPanel-gAkqWz9z.js → AuditHistoryPanel-Cl9tUvSZ.js} +1 -1
  3. package/assets/assets/{BacklinksPage-C67DtHo8.js → BacklinksPage-CTeTJ9dh.js} +1 -1
  4. package/assets/assets/{HistoryPage-BQuW6Z6m.js → HistoryPage-DpOoXr2P.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-B1XS6DR0.js → MeasurementPropertyPage-DxXsgM25.js} +1 -1
  6. package/assets/assets/{ProjectPage-CV_7bOA7.js → ProjectPage-By9arjLJ.js} +1 -1
  7. package/assets/assets/{RunsPage-CJYihZYC.js → RunsPage-CfDi7I1z.js} +1 -1
  8. package/assets/assets/{SettingsPage-Dd3J2DQE.js → SettingsPage-BHDb1apK.js} +1 -1
  9. package/assets/assets/{SiteHealthSection-iktyuzt2.js → SiteHealthSection-DQZyy4P_.js} +3 -3
  10. package/assets/assets/{TrafficPage-CWjK7F3j.js → TrafficPage-DX_vN2RL.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-D4_AXQNE.js → TrafficSourceDetailPage-Cg92zrtZ.js} +1 -1
  12. package/assets/assets/{extract-error-message-Cxefvthz.js → extract-error-message-BKxVCbov.js} +1 -1
  13. package/assets/assets/index-DoQJETc6.js +86 -0
  14. package/assets/assets/{react-sigma_core.esm.min-DDEPa7AV.js → react-sigma_core.esm.min-BhqZcSgh.js} +1 -1
  15. package/assets/assets/{v2-overview-adapter-CLxlMdb2.js → v2-overview-adapter-DZ5YsE_G.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-EZRBFH77.js → chunk-7HJYGDRW.js} +152 -4
  18. package/dist/{chunk-A25FO3Z5.js → chunk-KOBK4I5N.js} +5613 -5399
  19. package/dist/{chunk-4LQ4HKVK.js → chunk-LC7GMHOL.js} +2 -2
  20. package/dist/{chunk-KXHPQMBP.js → chunk-N2MRW2DH.js} +6 -6
  21. package/dist/{chunk-WHVEDCHY.js → chunk-SG6T3DEP.js} +2 -2
  22. package/dist/{chunk-ZHXOR2LJ.js → chunk-SGQBPTWQ.js} +2 -2
  23. package/dist/{chunk-LUQ5G5OO.js → chunk-YFCKHEKM.js} +3 -3
  24. package/dist/cli.js +52 -9
  25. package/dist/{demo-server-SIATTXXS.js → demo-server-YWGUGZMI.js} +4 -4
  26. package/dist/index.js +6 -6
  27. package/dist/{intelligence-service-UEEVETII.js → intelligence-service-7RKUUR7O.js} +2 -2
  28. package/dist/mcp.js +4 -4
  29. package/package.json +10 -10
  30. package/assets/assets/index-BbmiGIM_.js +0 -86
@@ -139,7 +139,7 @@ import {
139
139
  trafficSeriesGranularitySchema,
140
140
  visibilityCompareSelectionSchema,
141
141
  visibilityReportRequestSchema
142
- } from "./chunk-A25FO3Z5.js";
142
+ } from "./chunk-KOBK4I5N.js";
143
143
 
144
144
  // src/cli-error.ts
145
145
  function isMachineFormat(format) {
@@ -2280,7 +2280,7 @@ var canonryMcpTools = [
2280
2280
  defineTool({
2281
2281
  name: "canonry_ga_measurement_analysis",
2282
2282
  title: "Analyze GA acquisition and search demand",
2283
- description: "Compare native GA4 channels and lead events with branded/non-brand Search Console demand over fixed 30-day cohorts.",
2283
+ description: "Compare native GA4 channels and lead events (also split by AI engine, with each engine's sessions and lead rate) with branded/non-brand Search Console demand over fixed 30-day cohorts.",
2284
2284
  access: "read",
2285
2285
  tier: "ga",
2286
2286
  inputSchema: gaMeasurementAnalysisInputSchema,
@@ -20,20 +20,20 @@ import {
20
20
  saveConfig,
21
21
  saveConfigPatch,
22
22
  sentimentInstallReadiness
23
- } from "./chunk-ZHXOR2LJ.js";
23
+ } from "./chunk-SGQBPTWQ.js";
24
24
  import {
25
25
  computeFactorAverages,
26
26
  deleteSiteCrawlGraphLayout,
27
27
  persistSiteCrawlGraphLayout,
28
28
  prepareSiteCrawlGraphLayout,
29
29
  recordedSharePct
30
- } from "./chunk-WHVEDCHY.js";
30
+ } from "./chunk-SG6T3DEP.js";
31
31
  import {
32
32
  CanonryMcpToolNames,
33
33
  CliError,
34
34
  PACKAGE_VERSION,
35
35
  canonryMcpTools
36
- } from "./chunk-4LQ4HKVK.js";
36
+ } from "./chunk-LC7GMHOL.js";
37
37
  import {
38
38
  CC_CACHE_DIR,
39
39
  DUCKDB_SPEC,
@@ -217,7 +217,7 @@ import {
217
217
  siteLivenessState,
218
218
  toAlertView,
219
219
  usageCounters
220
- } from "./chunk-EZRBFH77.js";
220
+ } from "./chunk-7HJYGDRW.js";
221
221
  import {
222
222
  AGENT_MEMORY_VALUE_MAX_BYTES,
223
223
  AGENT_NONE,
@@ -430,7 +430,7 @@ import {
430
430
  winnabilityClassLabel,
431
431
  withRetry,
432
432
  withSkippedProviders
433
- } from "./chunk-A25FO3Z5.js";
433
+ } from "./chunk-KOBK4I5N.js";
434
434
 
435
435
  // src/runtime-context.ts
436
436
  function cliRuntimeContext(env = process.env, stdio = process) {
@@ -8042,7 +8042,7 @@ function nonBlank(value) {
8042
8042
  return trimmed ? trimmed : void 0;
8043
8043
  }
8044
8044
  function resolveBuildCommit(env = process.env) {
8045
- const embedded = true ? "8eb9720cf39e4787ffa530b4083372db8c39ae8f" : void 0;
8045
+ const embedded = true ? "e5577fdc9f99f2cc008aeff477aaf284da4532f7" : void 0;
8046
8046
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
8047
8047
  }
8048
8048
  function resolveInstanceIdentity(env = process.env) {
@@ -5,7 +5,7 @@ import {
5
5
  siteCrawlGraphNodes,
6
6
  siteCrawlPages,
7
7
  siteCrawlSnapshots
8
- } from "./chunk-EZRBFH77.js";
8
+ } from "./chunk-7HJYGDRW.js";
9
9
  import {
10
10
  SITE_CRAWL_GRAPH_MAX_EDGES,
11
11
  SITE_CRAWL_GRAPH_MAX_NODES,
@@ -14,7 +14,7 @@ import {
14
14
  factorStatusFromDistribution,
15
15
  factorStatusFromScore,
16
16
  roundPreservingTotal
17
- } from "./chunk-A25FO3Z5.js";
17
+ } from "./chunk-KOBK4I5N.js";
18
18
 
19
19
  // src/site-audit-factors.ts
20
20
  function recordedSharePct(factor) {
@@ -5,7 +5,7 @@ import {
5
5
  PACKAGE_VERSION,
6
6
  canonryMcpTools,
7
7
  isMachineFormat
8
- } from "./chunk-4LQ4HKVK.js";
8
+ } from "./chunk-LC7GMHOL.js";
9
9
  import {
10
10
  AGENT_PROVIDER_IDS,
11
11
  CANONRY_NPM_PACKAGE_URL,
@@ -50,7 +50,7 @@ import {
50
50
  skillsClientSchema,
51
51
  upgradeCaveatFor,
52
52
  upgradeCommandFor
53
- } from "./chunk-A25FO3Z5.js";
53
+ } from "./chunk-KOBK4I5N.js";
54
54
 
55
55
  // src/config.ts
56
56
  import fs from "fs";
@@ -4,14 +4,14 @@ import {
4
4
  installSkills,
5
5
  loadConfigRaw,
6
6
  saveConfigPatch
7
- } from "./chunk-ZHXOR2LJ.js";
7
+ } from "./chunk-SGQBPTWQ.js";
8
8
  import {
9
9
  PACKAGE_VERSION
10
- } from "./chunk-4LQ4HKVK.js";
10
+ } from "./chunk-LC7GMHOL.js";
11
11
  import {
12
12
  SKILL_MANIFEST_FILENAME,
13
13
  SkillsClients
14
- } from "./chunk-A25FO3Z5.js";
14
+ } from "./chunk-KOBK4I5N.js";
15
15
 
16
16
  // src/skills-autosync.ts
17
17
  import fs from "fs";
package/dist/cli.js CHANGED
@@ -33,11 +33,11 @@ import {
33
33
  trackCliCommandFinished,
34
34
  trackEvent,
35
35
  waitForServerRuntimeStartup
36
- } from "./chunk-KXHPQMBP.js";
36
+ } from "./chunk-N2MRW2DH.js";
37
37
  import {
38
38
  autoSyncSkills,
39
39
  formatAutoSyncNotice
40
- } from "./chunk-LUQ5G5OO.js";
40
+ } from "./chunk-YFCKHEKM.js";
41
41
  import {
42
42
  configExists,
43
43
  createApiClient,
@@ -54,13 +54,13 @@ import {
54
54
  saveConfig,
55
55
  saveConfigPatch,
56
56
  shouldPrintServeSkillsNudge
57
- } from "./chunk-ZHXOR2LJ.js";
57
+ } from "./chunk-SGQBPTWQ.js";
58
58
  import {
59
59
  DEFAULT_DEMO_TRUSTED_PROXIES,
60
60
  trustedProxiesProblem,
61
61
  trustedProxyProblem
62
62
  } from "./chunk-RB2F4D3Z.js";
63
- import "./chunk-WHVEDCHY.js";
63
+ import "./chunk-SG6T3DEP.js";
64
64
  import {
65
65
  CliError,
66
66
  EXIT_SYSTEM_ERROR,
@@ -72,7 +72,7 @@ import {
72
72
  runMeasurementDraftAction,
73
73
  systemError,
74
74
  usageError
75
- } from "./chunk-4LQ4HKVK.js";
75
+ } from "./chunk-LC7GMHOL.js";
76
76
  import {
77
77
  CLOUDFLARE_WORKER_BINDINGS,
78
78
  CLOUDFLARE_WORKER_GENERATED_MARKER,
@@ -104,7 +104,7 @@ import {
104
104
  rawEventSamples,
105
105
  readMarketCompetitorNames,
106
106
  runs
107
- } from "./chunk-EZRBFH77.js";
107
+ } from "./chunk-7HJYGDRW.js";
108
108
  import {
109
109
  AdsDeliverySnapshotStatuses,
110
110
  AdsHistoricalCampaignRollupStatuses,
@@ -234,7 +234,7 @@ import {
234
234
  visibilityCompareSelectionSchema,
235
235
  visibilityReportRequestSchema,
236
236
  winnabilityClassSchema
237
- } from "./chunk-A25FO3Z5.js";
237
+ } from "./chunk-KOBK4I5N.js";
238
238
 
239
239
  // src/cli.ts
240
240
  import { pathToFileURL } from "url";
@@ -784,7 +784,7 @@ function parseDemoTrustedProxies(values) {
784
784
  async function demoCommand(options) {
785
785
  const { host, port } = parseDemoListenOptions(options);
786
786
  const trustProxy = parseDemoTrustedProxies(options.trustProxy);
787
- const { createDemoServer } = await import("./demo-server-SIATTXXS.js");
787
+ const { createDemoServer } = await import("./demo-server-YWGUGZMI.js");
788
788
  const app = await createDemoServer({ trustProxy });
789
789
  let stopping = false;
790
790
  const stop = () => {
@@ -1198,7 +1198,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
1198
1198
  }
1199
1199
  }
1200
1200
  async function backfillInsightsCommand(project2, opts) {
1201
- const { IntelligenceService } = await import("./intelligence-service-UEEVETII.js");
1201
+ const { IntelligenceService } = await import("./intelligence-service-7RKUUR7O.js");
1202
1202
  const config = loadConfig();
1203
1203
  const db = createClient(config.database);
1204
1204
  migrate(db);
@@ -5530,6 +5530,48 @@ async function gaTraffic(project2, opts) {
5530
5530
  Last synced: ${result.lastSyncedAt}`);
5531
5531
  }
5532
5532
  }
5533
+ var AI_ENGINE_LEAD_RATE_NOTES = {
5534
+ "no-data": null,
5535
+ "sync-not-ready": "the latest acquisition or lead sync failed, so sessions and lead events may cover different days; rerun the GA sync for lead rates.",
5536
+ "channel-leads-unfiltered": "lead events are channel-level here, so host and path filters narrow sessions but not leads; rerun with --host-scope all and no --path-prefix for lead rates.",
5537
+ "paid-split-needs-landing-page": "lead events are channel-level here, so the paid/organic split cannot read landing-page utm tags, and some AI sessions were paid only by those tags (counted as organic above); tag paid AI links with a paid utm_medium such as cpc for lead rates.",
5538
+ "sessions-behind-leads": "stored lead events run past the last stored session date, so recent leads have no sessions to divide by; rerun the GA sync for lead rates.",
5539
+ "sessions-missing-on-lead-days": "some days in this window have stored lead events but no stored sessions, so those leads have no sessions to divide by; rerun the GA sync with --days covering the window for lead rates."
5540
+ };
5541
+ function hasAiEngineLeadRows(block) {
5542
+ return block.engines.length > 0 || block.unattributed.sources.length > 0;
5543
+ }
5544
+ function printAiEngineLeadClass(heading, block) {
5545
+ const cell2 = (period) => `${period.eventCount} / ${period.sessions} ${formatPercent(period.leadRate)}`;
5546
+ const rows = [
5547
+ ...block.engines.map((engine) => ({ label: engine.label, cells: engine.periods.map(cell2) })),
5548
+ ...block.unattributed.sources.length > 0 ? [{ label: "Other AI Assistant", cells: block.unattributed.periods.map(cell2) }] : [],
5549
+ { label: "All AI", cells: block.periods.map(cell2) }
5550
+ ];
5551
+ const labelWidth = Math.max("ENGINE".length, ...rows.map((row) => row.label.length));
5552
+ const widths = block.periods.map((period, index) => Math.max(
5553
+ period.label.length,
5554
+ ...rows.map((row) => row.cells[index]?.length ?? 0)
5555
+ ));
5556
+ const line = (label, cells) => ` ${label.padEnd(labelWidth)} ${cells.map((value, index) => value.padEnd(widths[index] ?? 0)).join(" ")}`.trimEnd();
5557
+ console.log(` ${heading} (lead events / AI sessions, lead rate)`);
5558
+ console.log(line("ENGINE", block.periods.map((period) => period.label.toUpperCase())));
5559
+ for (const row of rows) console.log(line(row.label, row.cells));
5560
+ }
5561
+ function printAiEngineLeads(aiEngines) {
5562
+ if (!aiEngines) return;
5563
+ if (aiEngines.organic.periods.length === 0) return;
5564
+ if (!hasAiEngineLeadRows(aiEngines.organic) && !hasAiEngineLeadRows(aiEngines.paid)) {
5565
+ console.log(" Leads by AI engine: no AI engine sessions or lead events in this window");
5566
+ return;
5567
+ }
5568
+ printAiEngineLeadClass("Leads by AI engine, organic", aiEngines.organic);
5569
+ if (hasAiEngineLeadRows(aiEngines.paid)) {
5570
+ printAiEngineLeadClass("Leads by AI engine, paid clicks", aiEngines.paid);
5571
+ }
5572
+ const note = aiEngines.leadRateUnavailableReason === null ? null : AI_ENGINE_LEAD_RATE_NOTES[aiEngines.leadRateUnavailableReason];
5573
+ if (note) console.log(` Note: ${note}`);
5574
+ }
5533
5575
  async function gaMeasurementAnalysis(project2, opts) {
5534
5576
  const client = getClient7();
5535
5577
  const params = {};
@@ -5563,6 +5605,7 @@ async function gaMeasurementAnalysis(project2, opts) {
5563
5605
  for (const period of result.leads.periods) {
5564
5606
  console.log(` ${period.label.padEnd(8)} ${period.eventCount} leads ${period.startDate} to ${period.endDate}`);
5565
5607
  }
5608
+ printAiEngineLeads(result.leads.aiEngines);
5566
5609
  console.log(` Search demand: ${result.searchDemand.status}`);
5567
5610
  for (const period of result.searchDemand.periods) {
5568
5611
  console.log(` ${period.label.padEnd(8)} ${period.propertyClicks} clicks / ${period.propertyImpressions} impressions (${period.brandedClicks} branded clicks / ${period.brandedImpressions} branded impressions; ${period.nonBrandedClicks} reported non-brand clicks / ${period.nonBrandedImpressions} reported non-brand impressions; ${period.unreportedClicks} unreported clicks / ${period.unreportedImpressions} unreported impressions)`);
@@ -5,11 +5,11 @@ import {
5
5
  computeFactorAverages,
6
6
  layoutSiteCrawlGraphInput,
7
7
  persistSiteCrawlGraphLayout
8
- } from "./chunk-WHVEDCHY.js";
8
+ } from "./chunk-SG6T3DEP.js";
9
9
  import {
10
10
  PACKAGE_VERSION,
11
11
  canonryMcpTools
12
- } from "./chunk-4LQ4HKVK.js";
12
+ } from "./chunk-LC7GMHOL.js";
13
13
  import {
14
14
  HISTORY_WINDOW_RUNS,
15
15
  IntelligenceService,
@@ -83,7 +83,7 @@ import {
83
83
  siteCrawlPages,
84
84
  siteCrawlSnapshots,
85
85
  trafficSources
86
- } from "./chunk-EZRBFH77.js";
86
+ } from "./chunk-7HJYGDRW.js";
87
87
  import {
88
88
  RunKinds,
89
89
  SiteCrawlFetchStates,
@@ -112,7 +112,7 @@ import {
112
112
  roundPreservingTotal,
113
113
  siteAuditPageFactorSchema,
114
114
  wilsonInterval
115
- } from "./chunk-A25FO3Z5.js";
115
+ } from "./chunk-KOBK4I5N.js";
116
116
 
117
117
  // src/demo-server.ts
118
118
  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-KXHPQMBP.js";
6
+ } from "./chunk-N2MRW2DH.js";
7
7
  import {
8
8
  loadConfig
9
- } from "./chunk-ZHXOR2LJ.js";
10
- import "./chunk-WHVEDCHY.js";
11
- import "./chunk-4LQ4HKVK.js";
12
- import "./chunk-EZRBFH77.js";
13
- import "./chunk-A25FO3Z5.js";
9
+ } from "./chunk-SGQBPTWQ.js";
10
+ import "./chunk-SG6T3DEP.js";
11
+ import "./chunk-LC7GMHOL.js";
12
+ import "./chunk-7HJYGDRW.js";
13
+ import "./chunk-KOBK4I5N.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-EZRBFH77.js";
5
- import "./chunk-A25FO3Z5.js";
4
+ } from "./chunk-7HJYGDRW.js";
5
+ import "./chunk-KOBK4I5N.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-LUQ5G5OO.js";
3
+ } from "./chunk-YFCKHEKM.js";
4
4
  import {
5
5
  createApiClient,
6
6
  createCanonryMcpServer
7
- } from "./chunk-ZHXOR2LJ.js";
8
- import "./chunk-4LQ4HKVK.js";
7
+ } from "./chunk-SGQBPTWQ.js";
8
+ import "./chunk-LC7GMHOL.js";
9
9
  import {
10
10
  isReadOnlyKey,
11
11
  updateCheckEnvOptOut
12
- } from "./chunk-A25FO3Z5.js";
12
+ } from "./chunk-KOBK4I5N.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": "@canonry/canonry",
3
- "version": "7.14.0",
3
+ "version": "7.15.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": [
@@ -87,29 +87,29 @@
87
87
  "tsx": "^4.19.0",
88
88
  "@ainyc/canonry-api-client": "0.0.0",
89
89
  "@ainyc/canonry-api-routes": "0.0.0",
90
- "@ainyc/canonry-config": "0.0.0",
91
- "@ainyc/canonry-web": "0.0.0",
92
90
  "@ainyc/canonry-contracts": "0.0.0",
91
+ "@ainyc/canonry-web": "0.0.0",
93
92
  "@ainyc/canonry-db": "0.0.0",
94
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
93
+ "@ainyc/canonry-config": "0.0.0",
95
94
  "@ainyc/canonry-integration-bing": "0.0.0",
96
95
  "@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
97
96
  "@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
98
- "@ainyc/canonry-integration-google-ads": "0.0.0",
99
97
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
100
98
  "@ainyc/canonry-integration-google": "0.0.0",
99
+ "@ainyc/canonry-integration-google-ads": "0.0.0",
101
100
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
102
- "@ainyc/canonry-integration-openai-ads": "0.0.0",
103
- "@ainyc/canonry-integration-traffic": "0.0.0",
101
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
104
102
  "@ainyc/canonry-integration-google-places": "0.0.0",
103
+ "@ainyc/canonry-integration-openai-ads": "0.0.0",
105
104
  "@ainyc/canonry-integration-google-tag-manager": "0.0.0",
106
- "@ainyc/canonry-provider-claude": "0.0.0",
107
- "@ainyc/canonry-provider-cdp": "0.0.0",
105
+ "@ainyc/canonry-integration-traffic": "0.0.0",
108
106
  "@ainyc/canonry-integration-wordpress": "0.0.0",
107
+ "@ainyc/canonry-provider-cdp": "0.0.0",
109
108
  "@ainyc/canonry-intelligence": "0.0.0",
109
+ "@ainyc/canonry-provider-claude": "0.0.0",
110
110
  "@ainyc/canonry-provider-gemini": "0.0.0",
111
- "@ainyc/canonry-provider-openai": "0.0.0",
112
111
  "@ainyc/canonry-provider-local": "0.0.0",
112
+ "@ainyc/canonry-provider-openai": "0.0.0",
113
113
  "@ainyc/canonry-provider-muse": "0.0.0",
114
114
  "@ainyc/canonry-provider-perplexity": "0.0.0",
115
115
  "@ainyc/canonry-integration-typesafe": "0.0.0"