@canonry/canonry 7.78.2 → 7.79.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.
@@ -36,6 +36,12 @@ no provider calls, reads no project data, and changes no permissions.
36
36
  requires approval; listing an action does not authorize or execute it.
37
37
  `includeCatalog: true` additionally returns toolkit details when needed.
38
38
 
39
+ Analysis routes also return `playbooks`: Canonry's analyst playbooks for that
40
+ work, the same files its built-in agent reads. Read each one before
41
+ interpreting results. `canonry_help({ playbook: "<slug>" })` returns its full
42
+ text, a note on using it over MCP, and every available slug. Playbooks are
43
+ guidance, not permission.
44
+
39
45
  Hosted connections use a fixed catalog. Help only suggests tools offered by that
40
46
  connection; it never tells a hosted agent to dynamically load a toolkit. A
41
47
  progressive local stdio connection may return `loadToolkits`: call
@@ -27,6 +27,12 @@ no provider calls, reads no project data, and changes no permissions.
27
27
  requires approval; listing an action does not authorize or execute it.
28
28
  `includeCatalog: true` additionally returns toolkit details when needed.
29
29
 
30
+ Analysis routes also return `playbooks`: Canonry's analyst playbooks for that
31
+ work, the same files its built-in agent reads. Read each one before
32
+ interpreting results. `canonry_help({ playbook: "<slug>" })` returns its full
33
+ text, a note on using it over MCP, and every available slug. Playbooks are
34
+ guidance, not permission.
35
+
30
36
  Hosted connections use a fixed catalog. Help only suggests tools offered by that
31
37
  connection; it never tells a hosted agent to dynamically load a toolkit. A
32
38
  progressive local stdio connection may return `loadToolkits`: call
@@ -47,7 +47,7 @@ function queueServeHandoff() {
47
47
  async function runPendingServeHandoff(format) {
48
48
  if (!pendingServeHandoff) return;
49
49
  pendingServeHandoff = false;
50
- const { serveCommand } = await import("./serve-J7WR3J6I.js");
50
+ const { serveCommand } = await import("./serve-XAI5YFWR.js");
51
51
  await serveCommand(format);
52
52
  }
53
53
 
@@ -7,8 +7,9 @@ import {
7
7
  truncateToolResult
8
8
  } from "./chunk-IYMTQHPD.js";
9
9
  import {
10
- CANONRY_MCP_TOOLKITS
11
- } from "./chunk-WVCDUKJ2.js";
10
+ CANONRY_MCP_TOOLKITS,
11
+ resolveAeroSkillDir
12
+ } from "./chunk-JI7FDO7I.js";
12
13
  import {
13
14
  AGENT_REASONING_LEVELS,
14
15
  aeroStreamFn,
@@ -45,8 +46,8 @@ import {
45
46
  } from "./chunk-PM2DB3HE.js";
46
47
 
47
48
  // src/agent/session.ts
48
- import fs3 from "fs";
49
- import path3 from "path";
49
+ import fs2 from "fs";
50
+ import path2 from "path";
50
51
  import { Agent } from "@earendil-works/pi-agent-core";
51
52
 
52
53
  // src/agent-config.ts
@@ -75,27 +76,9 @@ function resolveAgentReasoning(env, config, provider, modelId) {
75
76
  return config.agent?.reasoning ?? defaultAgentReasoning(provider, modelId);
76
77
  }
77
78
 
78
- // src/agent/skill-paths.ts
79
+ // src/agent/skill-tools.ts
79
80
  import fs from "fs";
80
81
  import path from "path";
81
- import { fileURLToPath } from "url";
82
- function resolveAeroSkillDir(pkgDir) {
83
- const here = pkgDir ?? path.dirname(fileURLToPath(import.meta.url));
84
- const candidates = [
85
- path.join(here, "../assets/agent-workspace/skills/aero"),
86
- path.join(here, "../../assets/agent-workspace/skills/aero"),
87
- path.join(here, "../../../../skills/aero")
88
- ];
89
- for (const candidate of candidates) {
90
- if (fs.existsSync(path.join(candidate, "SKILL.md"))) return candidate;
91
- }
92
- throw new Error(`Aero skill not found. Searched:
93
- ${candidates.join("\n ")}`);
94
- }
95
-
96
- // src/agent/skill-tools.ts
97
- import fs2 from "fs";
98
- import path2 from "path";
99
82
  import { Type } from "@earendil-works/pi-ai";
100
83
  var MAX_DOC_CHARS = 2e4;
101
84
  function textResult(details) {
@@ -116,13 +99,13 @@ function parseDescription(body) {
116
99
  return "(no description)";
117
100
  }
118
101
  function scanSkillDocs(skillDir) {
119
- const refsDir = path2.join(skillDir ?? resolveAeroSkillDir(), "references");
120
- if (!fs2.existsSync(refsDir)) return [];
102
+ const refsDir = path.join(skillDir ?? resolveAeroSkillDir(), "references");
103
+ if (!fs.existsSync(refsDir)) return [];
121
104
  const entries = [];
122
- for (const file of fs2.readdirSync(refsDir)) {
105
+ for (const file of fs.readdirSync(refsDir)) {
123
106
  if (!file.endsWith(".md")) continue;
124
- const filePath = path2.join(refsDir, file);
125
- const body = fs2.readFileSync(filePath, "utf-8");
107
+ const filePath = path.join(refsDir, file);
108
+ const body = fs.readFileSync(filePath, "utf-8");
126
109
  entries.push({
127
110
  slug: file.replace(/\.md$/, ""),
128
111
  description: parseDescription(body),
@@ -165,8 +148,8 @@ function buildReadSkillDocTool() {
165
148
  availableSlugs: docs.map((d) => d.slug)
166
149
  });
167
150
  }
168
- const filePath = path2.join(skillDir, "references", `${match.slug}.md`);
169
- const content = fs2.readFileSync(filePath, "utf-8");
151
+ const filePath = path.join(skillDir, "references", `${match.slug}.md`);
152
+ const content = fs.readFileSync(filePath, "utf-8");
170
153
  if (content.length > MAX_DOC_CHARS) {
171
154
  return textResult({
172
155
  slug: match.slug,
@@ -360,9 +343,9 @@ function resolvePageReferences(runtime, tool, params) {
360
343
  }
361
344
  return resolved;
362
345
  }
363
- function pageOwner(value, path4) {
346
+ function pageOwner(value, path3) {
364
347
  let owner = value;
365
- for (const key of path4) {
348
+ for (const key of path3) {
366
349
  if (owner === null || typeof owner !== "object" || Array.isArray(owner)) return void 0;
367
350
  owner = owner[key];
368
351
  }
@@ -397,8 +380,8 @@ function referencePages(runtime, tool, params, value) {
397
380
  });
398
381
  owner[cursor.key] = reference;
399
382
  const pagination = pageOwner(root.__pagination, []);
400
- for (const [path4, note] of Object.entries(pagination ?? {})) {
401
- if (typeof note === "string") pagination[path4] = note.split(token).join(reference);
383
+ for (const [path3, note] of Object.entries(pagination ?? {})) {
384
+ if (typeof note === "string") pagination[path3] = note.split(token).join(reference);
402
385
  }
403
386
  changed = true;
404
387
  }
@@ -927,9 +910,9 @@ function ensureBuiltinsRegistered() {
927
910
  }
928
911
  function loadAeroSystemPrompt(pkgDir, opts = {}) {
929
912
  const skillDir = resolveAeroSkillDir(pkgDir);
930
- const skillBody = fs3.readFileSync(path3.join(skillDir, "SKILL.md"), "utf-8");
931
- const soulPath = path3.join(skillDir, "soul.md");
932
- const base = fs3.existsSync(soulPath) ? `${fs3.readFileSync(soulPath, "utf-8").trimEnd()}
913
+ const skillBody = fs2.readFileSync(path2.join(skillDir, "SKILL.md"), "utf-8");
914
+ const soulPath = path2.join(skillDir, "soul.md");
915
+ const base = fs2.existsSync(soulPath) ? `${fs2.readFileSync(soulPath, "utf-8").trimEnd()}
933
916
 
934
917
  ---
935
918
 
@@ -942,7 +925,7 @@ function appendSystemPromptExtras(base, env = process.env) {
942
925
  const filePath = env.AERO_SYSTEM_PROMPT_FILE?.trim();
943
926
  if (filePath) {
944
927
  try {
945
- fileBody = fs3.readFileSync(filePath, "utf-8").trim();
928
+ fileBody = fs2.readFileSync(filePath, "utf-8").trim();
946
929
  } catch {
947
930
  fileBody = "";
948
931
  }
@@ -1070,7 +1053,6 @@ export {
1070
1053
  resolveAgentAllowViewers,
1071
1054
  resolveAgentReasoning,
1072
1055
  AeroLlmUsageFeatures,
1073
- resolveAeroSkillDir,
1074
1056
  buildSkillDocTools,
1075
1057
  configureAeroRuntime,
1076
1058
  isSystemMessage,
@@ -1,3 +1,21 @@
1
+ // src/agent/skill-paths.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ function resolveAeroSkillDir(pkgDir) {
6
+ const here = pkgDir ?? path.dirname(fileURLToPath(import.meta.url));
7
+ const candidates = [
8
+ path.join(here, "../assets/agent-workspace/skills/aero"),
9
+ path.join(here, "../../assets/agent-workspace/skills/aero"),
10
+ path.join(here, "../../../../skills/aero")
11
+ ];
12
+ for (const candidate of candidates) {
13
+ if (fs.existsSync(path.join(candidate, "SKILL.md"))) return candidate;
14
+ }
15
+ throw new Error(`Aero skill not found. Searched:
16
+ ${candidates.join("\n ")}`);
17
+ }
18
+
1
19
  // src/mcp/toolkits.ts
2
20
  var CANONRY_MCP_TIERS = [
3
21
  "seo",
@@ -125,6 +143,7 @@ function isCanonryMcpToolkitName(value) {
125
143
  }
126
144
 
127
145
  export {
146
+ resolveAeroSkillDir,
128
147
  CANONRY_MCP_TIERS,
129
148
  CANONRY_MCP_TOOLKIT_NAMES,
130
149
  CANONRY_MCP_TOOLKITS,
@@ -27,7 +27,7 @@ import {
27
27
  resolveConfiguredAgentProvider,
28
28
  resolveSessionProviderAndModel,
29
29
  setAeroSystemPrompt
30
- } from "./chunk-OES6PSQK.js";
30
+ } from "./chunk-GL5HD74G.js";
31
31
  import {
32
32
  AeroToolProfiles,
33
33
  AeroToolScopes,
@@ -99,11 +99,11 @@ import {
99
99
  } from "./chunk-4GEEYY3N.js";
100
100
  import {
101
101
  createCanonryMcpServer
102
- } from "./chunk-BKOG3GEI.js";
102
+ } from "./chunk-RPTOVEZT.js";
103
103
  import {
104
104
  CANONRY_MCP_TIERS,
105
105
  CANONRY_MCP_TOOLKIT_NAMES
106
- } from "./chunk-WVCDUKJ2.js";
106
+ } from "./chunk-JI7FDO7I.js";
107
107
  import {
108
108
  ApiClient
109
109
  } from "./chunk-7OAXEMOF.js";
@@ -5937,7 +5937,7 @@ function nonBlank(value) {
5937
5937
  return trimmed ? trimmed : void 0;
5938
5938
  }
5939
5939
  function resolveBuildCommit(env = process.env) {
5940
- const embedded = true ? "3e41f6b61554e34c70c1fdc85a111f880cc15215" : void 0;
5940
+ const embedded = true ? "4ef4905de19fbd19f9571174a291d4ef01463cbb" : void 0;
5941
5941
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
5942
5942
  }
5943
5943
  function resolveInstanceIdentity(env = process.env) {
@@ -6868,7 +6868,7 @@ var SYSTEM_CLI_COMMANDS = [
6868
6868
  options: { access: stringOption() },
6869
6869
  run: async (input) => {
6870
6870
  if (input.positionals.length > 1) throw usageError("Expected at most one command group");
6871
- const [{ printCliCatalog }, { REGISTERED_CLI_COMMANDS: REGISTERED_CLI_COMMANDS2 }] = await Promise.all([import("./cli-catalog-KC6JYHSW.js"), import("./cli-commands-NGXSLPIA.js")]);
6871
+ const [{ printCliCatalog }, { REGISTERED_CLI_COMMANDS: REGISTERED_CLI_COMMANDS2 }] = await Promise.all([import("./cli-catalog-KC6JYHSW.js"), import("./cli-commands-DEY6C7JD.js")]);
6872
6872
  printCliCatalog(REGISTERED_CLI_COMMANDS2, input.format, input.positionals[0], getString(input.values, "access"));
6873
6873
  }
6874
6874
  },
@@ -6917,7 +6917,7 @@ var SYSTEM_CLI_COMMANDS = [
6917
6917
  },
6918
6918
  allowPositionals: false,
6919
6919
  run: async (input) => {
6920
- const { initCommand } = await import("./init-SMBVJSP6.js");
6920
+ const { initCommand } = await import("./init-7VNM45TF.js");
6921
6921
  await initCommand({
6922
6922
  force: getBoolean(input.values, "force"),
6923
6923
  ref: getString(input.values, "ref"),
@@ -6972,7 +6972,7 @@ var SYSTEM_CLI_COMMANDS = [
6972
6972
  allowPositionals: false,
6973
6973
  run: async (input) => {
6974
6974
  applyServerEnv(input.values);
6975
- const { serveCommand } = await import("./serve-J7WR3J6I.js");
6975
+ const { serveCommand } = await import("./serve-XAI5YFWR.js");
6976
6976
  await serveCommand(input.format);
6977
6977
  }
6978
6978
  },
@@ -8290,7 +8290,7 @@ var AGENT_CLI_COMMANDS = [
8290
8290
  "timeout-ms": stringOption()
8291
8291
  },
8292
8292
  run: async (input) => {
8293
- const { coerceAgentProvider } = await import("./session-FBHHWILQ.js");
8293
+ const { coerceAgentProvider } = await import("./session-5ZH53SET.js");
8294
8294
  const { AERO_TOOL_PROFILES, isAeroToolProfile } = await import("./tools-3ACR2D2V.js");
8295
8295
  const { agentAsk } = await import("./agent-ask-32RTOL2M.js");
8296
8296
  const usage = `canonry agent ask <project> "<prompt>" [--provider ${AGENT_PROVIDER_IDS.join("|")}] [--model <id>] [--scope all|read-only] [--profile default|ads-operator] [--context <json>] [--conversation <id>] [--max-tool-calls <n>] [--timeout-ms <ms>] [--format json]`;
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  CANONRY_MCP_TOOLKITS,
3
3
  CANONRY_MCP_TOOLKIT_NAMES,
4
- isCanonryMcpToolkitName
5
- } from "./chunk-WVCDUKJ2.js";
4
+ isCanonryMcpToolkitName,
5
+ resolveAeroSkillDir
6
+ } from "./chunk-JI7FDO7I.js";
6
7
  import {
7
8
  createApiClient,
8
9
  runWithUsageTags
@@ -125,8 +126,8 @@ function formatZodIssue(issue) {
125
126
  function zodErrorMessage(error) {
126
127
  const first = error.issues[0];
127
128
  if (!first) return "Input validation failed";
128
- const path = first.path.map(String).join(".");
129
- return path ? `${path}: ${first.message}` : first.message;
129
+ const path2 = first.path.map(String).join(".");
130
+ return path2 ? `${path2}: ${first.message}` : first.message;
130
131
  }
131
132
 
132
133
  // src/mcp/dynamic-catalog.ts
@@ -239,6 +240,8 @@ var OPERATIONS_GUIDE = {
239
240
  ],
240
241
  "approvalRule": "Get explicit approval for the exact target, action, and bounded provider work before crossing a boundary. Existing approval covers only its stated scope; stop on refusal or ambiguous results.",
241
242
  "authority": "Guidance and tool visibility are not permission grants. Server-side roles, scopes, project boundaries, quotas, and approval receipts remain authoritative. Never bypass a refusal with different credentials or endpoints.",
243
+ "playbookRule": 'Read each listed playbook with canonry_help({playbook:"<slug>"}) before interpreting results.',
244
+ "playbookNote": `Shared with Canonry's built-in agent and CLI hosts. Where it names aero_* tools, read_skill_doc or shell cnry commands, use the matching canonry_* tool on this connection. Read a reference it names (references/<slug>.md) with canonry_help({playbook:"<slug>"}). Playbooks are guidance, not permission.`,
242
245
  "workflows": {
243
246
  "seo": {
244
247
  "keywords": [
@@ -258,6 +261,9 @@ var OPERATIONS_GUIDE = {
258
261
  "canonry_seo_sync",
259
262
  "canonry_seo_run_cancel"
260
263
  ],
264
+ "playbooks": [
265
+ "seo-analysis"
266
+ ],
261
267
  "guidance": "Read saved SEO evidence first. Preserve profile, exact domain or host, market, language, plan revision, dataset and collection date. Market rankings (canonry_seo_market_rankings) are the site's Google position for its tracked queries by market, the same queries AI visibility measures; start with canonry_seo_market_summary for counts by outcome, and treat failed or unsearched queries as missing data. SEO traffic and keyword metrics are third-party estimates, separate from Search Console observations and AI citation and mention coverage. Refresh requires explicit approval for the profile and work, a fresh refresh intent and an idempotency key. Hosted financial controls are internal; no credential or billing information belongs in the response."
262
268
  },
263
269
  "operations": {
@@ -288,6 +294,9 @@ var OPERATIONS_GUIDE = {
288
294
  "canonry_projects_list",
289
295
  "canonry_project_overview"
290
296
  ],
297
+ "playbooks": [
298
+ "aero"
299
+ ],
291
300
  "guidance": "Read canonry_status first for stored integration health, freshness, and repeated failures across accessible projects, then inspect a project overview for detail. Report missing evidence; do not start a sweep or sync to fill a gap."
292
301
  },
293
302
  "diagnose": {
@@ -304,6 +313,10 @@ var OPERATIONS_GUIDE = {
304
313
  "canonry_project_overview",
305
314
  "canonry_project_history"
306
315
  ],
316
+ "playbooks": [
317
+ "aero",
318
+ "regression-playbook"
319
+ ],
307
320
  "guidance": "Compare stored history in the same project, period, provider/model, location, and query-class scope. Separate observed changes from possible causes; propose any live verification for approval."
308
321
  },
309
322
  "prospecting": {
@@ -338,6 +351,10 @@ var OPERATIONS_GUIDE = {
338
351
  "canonry_measurement_setup",
339
352
  "canonry_research_runs_list"
340
353
  ],
354
+ "playbooks": [
355
+ "portfolio-analysis",
356
+ "aeo-discovery"
357
+ ],
341
358
  "guidance": "Inspect existing measurement before editing it. Preserve Simple versus Advanced scope, Property/Target identity, market, provider/model, and query class. Direct research submits final queries in one context; reviewed batches submit explicit destinations under one retry key. Expand patterns client-side: scope records a destination and never rewrites queries or fans out a group. Research is separate from tracking; publication does not authorize a sweep."
342
359
  },
343
360
  "integrations": {
@@ -359,6 +376,9 @@ var OPERATIONS_GUIDE = {
359
376
  "canonry_ga_status",
360
377
  "canonry_google_ads_status"
361
378
  ],
379
+ "playbooks": [
380
+ "ai-traffic-tracking"
381
+ ],
362
382
  "guidance": "Read canonry_status first for the stored instance or project health view, then use specialist tools for detail. Connecting, selecting resources, syncing, and live provider reads require scoped approval; never request credentials in chat."
363
383
  },
364
384
  "reports": {
@@ -373,14 +393,18 @@ var OPERATIONS_GUIDE = {
373
393
  "canonry_project_overview",
374
394
  "canonry_organic_evidence"
375
395
  ],
396
+ "playbooks": [
397
+ "aero",
398
+ "reporting"
399
+ ],
376
400
  "guidance": "Build the requested summary from stored project overview, organic evidence, and the relevant visibility, measurement, integration, or traffic reads. Retain evidence dates, scope, sample sizes, missing data, and separate mention/citation signals; do not generate fresh runs implicitly."
377
401
  }
378
402
  },
379
- "markdown": "# Canonry Operations Guide v1\n\nCanonry is an agent-first AI visibility platform. MCP is the universal entry\npoint for connected agents. Host-native skills are optional upgrades, not a\nprerequisite or a permission mechanism.\n\n## Connect and choose a route\n\nRead the initialization guidance, then call `canonry_help` with an `intent`:\n`status`, `diagnose`, `operations`, `prospecting`, `measurement`, `integrations`,\n`reports`, or a short task\ndescription. Select an accessible project with `canonry_projects_list` before\nusing its exact name in project tools. Inspect each listed tool's input schema;\nhelp suggests tool names, not invented arguments or authorization.\n\nHelp returns a versioned, compact route: connection `mode`, available `next`\ntools, workflow guidance, approval boundaries, and this guide's URL. It performs\nno provider calls, reads no project data, and changes no permissions.\n`next` lists stored reads. Optional `actions` lists loaded tools for work that\nrequires approval; listing an action does not authorize or execute it.\n`includeCatalog: true` additionally returns toolkit details when needed.\n\nHosted connections use a fixed catalog. Help only suggests tools offered by that\nconnection; it never tells a hosted agent to dynamically load a toolkit. A\nprogressive local stdio connection may return `loadToolkits`: call\n`canonry_load_toolkit` with one returned name, await it, then call help again.\nLoading only changes local tool discovery, never server authority.\n\nThe optional `canonry://agent-operations/v1` MCP resource contains this same\nguide. If the host cannot read resources or open links, continue through help.\nDo not install a plugin, local runtime, or skill merely to use connected MCP.\nAn installed Codex or Claude Canonry skill contains a generated copy of this\nguide plus links to host-native references. It does not replace runtime help.\n\n## Vocabulary and evidence\n\n- **Mentioned** means the brand appears in answer text. **Cited** means its\n domain appears in source links. Either, both, or neither can occur; never\n compute one signal from the other.\n- `answerMentioned: null` means not checked, not false. Missing runs and empty\n populations mean no measurement, not zero visibility.\n- Preserve project, time window, provider, requested/served model, location,\n sample size, and query class when comparing evidence. Use server-returned\n metrics; do not invent a score from incompatible populations.\n- Simple projects and Advanced portfolios share the workflow. For Advanced\n results, preserve Property, Target, market, plan revision, and class scope.\n Groups organize navigation; do not infer an unrequested fan-out.\n- Research is isolated evidence, not tracked measurement. A probe still spends\n quota and persists evidence but is excluded from normal tracking metrics.\n\n## Workflows\n\n**Status:** read the stored overview and freshness first. Say when evidence is\nmissing instead of silently creating it.\n\n**Setup:** Page Health needs no answer-engine provider; AI Visibility does.\nAfter a Page Health result on an install with no provider (a `NO_PROVIDER`\nnotice, or doctor's `providers.none-configured`), propose AI Visibility setup\nas the next step. The operator adds the provider key in the dashboard or in\ntheir own terminal; never ask for it in chat.\nRerunning `canonry bootstrap` after a provider change reloads saved provider\nsettings on a matching running local server and reports whether they became\nactive. If the server is offline, start it to apply the saved settings. For an\nexplicit reload of the connected server's own config, use\n`canonry settings reload-providers` or `canonry_providers_reload` with install\nadministrator authority. Remote servers are never reloaded automatically by\nbootstrap, and a local server that belongs to another install is left alone.\nPorts and database settings still need a restart.\n\n**Diagnose:** inspect stored history and comparable evidence. Explain what\nchanged separately from why it might have changed. A hypothesis is not a\nmeasured cause. Propose bounded verification if stored evidence is insufficient.\n\n**Site Health:** read `canonry_site_health_issues` for stored technical findings,\naffected-page counts and example URLs, then `canonry_site_health_issue_pages`\nfor a selected issue's remaining pages. Pin subsequent reads to the returned\nrun. Inspect coverage and omissions: a passing score can still have findings,\nan older scan may lack cross-page evidence, and a partial scan cannot establish\nsite-wide absence. Advisory findings require judgment before proposing a repair.\nAudit evidence informs technical repairs and existing-page recommendations;\nit does not itself establish a new-blog opportunity or authorize any edit.\n\n**Prospecting:** generate a one-shot company snapshot without creating a project.\nInspect stored provider settings first. Agree on the company, domain, selected\nproviders, and queries before starting the quota-spending snapshot action.\nBrowser-only selection requires manual queries. Progressive stdio help offers\nthe discovery toolkit when this connection permits snapshots; load it, then\ncall help again. Fixed catalogs offer only already available actions. Read-only\nand restricted connections must not bypass missing snapshot access.\n\n**Measurement:** inspect the existing setup and results before proposing edits.\nKeep research, query tracking, plan publication, and sweep execution separate.\nFor direct research, submit the final editable query text in one context. For a\nreviewed batch, submit each explicit destination with its final text and one\nidempotency key. Pattern substitution happens in the client before either\nrequest; choosing a market or Property records a destination only and never\nrewrites a query or creates an automatic fan-out. `research.run` does not\nauthorize saving patterns, changing tracking, publishing plans, or settings.\nUse a supported preview where available, inspect its exact destination and\nrevision, then seek approval for the actual change. A preview may itself require\nwrite permission; never treat a dry-run flag as a universal safety guarantee.\n\n**Integrations:** inspect stored connection state and snapshot freshness first.\nProvider configuration evidence does not prove a browser event fired or a\nconversion was recorded. Connection, resource selection, refresh/sync, and live\nreads are separate actions. Credentials belong in the operator's secure setup\nflow, never in chat, tool arguments, reports, or public guidance.\n\n**Reports:** use saved evidence for the requested period and scope. Keep mention\nand citation signals separate, include dates and sample sizes, and state missing\nor stale inputs. For Advanced Property mention rankings, use\n`canonry_measurement_portfolio_summary` and its `mentionRanking.strongest`,\n`.weakest`, and `.excluded` lists. It defaults to non-brand questions; state the\nreturned class and report branded results separately. An unavailable portfolio\naggregate does not invalidate available Property mention rates. Flag excluded\nProperties individually; do not silently replace mention ranking with citation\nranking. Compact `nextCursor` completes only `pageList`; use `list` to enumerate\none ranking, markets, observed names or cited domains, preserving filters.\nFirst-page sibling lists are bounded summaries. Keep sample sizes and ties\nvisible. Preparing a report does not\nauthorize new measurement.\n\n## Authority and approval\n\n### Agent operations\n\nUse `canonry_key_self` (CLI `canonry key whoami --format json`) to inspect the\ncurrent credential's scopes, project boundary, and host-derived `operator`\nauthority without exposing its token. Missing `operator` means unapproved.\n`canonry_settings_get` and\n`canonry_telemetry_get` describe the connected server, not the agent's local\nmachine. Telemetry reports configured preference, effective state, and any\nenvironment override; inspecting status never creates an anonymous identifier.\nAfter approval, `canonry_telemetry_update` changes that preference and\n`canonry_provider_settings_update` changes an already-configured provider's\nmodel/quota. Both require `settings.write`; neither accepts credentials.\nServer telemetry reads and updates additionally require operator authority.\nOrdinary audit-history reads omit internal telemetry events and their state.\n\nOperator authority is deny-by-default and separate from customer admin roles.\nThe deployment owner must approve a dedicated, instance-wide API key's ID in\nthe server environment variable `CANONRY_OPERATOR_KEY_IDS` (comma-separated IDs),\nthen restart the server. Empty/unset approves nobody; wildcards are invalid.\nKeep the bearer private to internal operators; never approve a customer-held or\nshared proxy/bootstrap key. Use `logs.read` for read-only diagnostics, adding\n`settings.write` only when telemetry control is required. Ordinary key creation,\naccount roles, OAuth consent, and caller headers cannot grant operator status.\nRevoking an approved key invalidates it immediately. Host enrollment is a trust\nbootstrap step, intentionally unavailable through customer-facing APIs.\nAPI, CLI, and MCP enforce the same boundary; MCP hides internal tools unless\nthe server confirms operator authority, including in explicit read-only mode.\nProject analytics, research, and normal project permissions are unchanged.\n\n`canonry_logs_list` reads bounded, redacted runtime events from both the\napplication logger and Fastify request/error logging. It requires an\ninstance-wide `logs.read` grant (or wildcard) and a host-approved direct bearer.\nBrowser sessions, OAuth/delegated credentials, customer admins, and project-scoped\nkeys cannot use it, even with a project filter or a matching allowlist ID.\nA `logs.read`-only key is read-only automatically, without a\nsecond `read` marker. Named `*.read` scopes cannot grant mutations; an explicit\nwrite grant is needed and remains subject to its route gates. Returned messages\nare sanitized and bounded; raw request or response bodies, headers, cookies,\nprovider payloads, and stacks are not\npart of the queryable surface. The same secret-redaction policy runs before\nconsole output and storage. Do not deliberately log secrets: redaction is a\ndefense in depth, not permission to put credentials into diagnostic strings.\nOpaque escaped payloads containing secret assignments are omitted when safe\npartial masking cannot be guaranteed; correlate their retained error codes and IDs.\n\nFile-backed hosts retain runtime logs in SQLite across restarts, bounded to\n10,000 events and seven days. In-memory hosts report `retention: \"process\"`.\nFilter by `actor`, `requestId`, `runId`, `projectId`, `module`, `level`, or an\ninclusive `since`/`until` interval. Keep filters unchanged when resuming an\nopaque cursor; retention eviction can invalidate it. Inspect `retentionPolicy`,\n`captureErrors`, `dropped`, `truncated`, and `retention` before drawing\nconclusions. Missing logs are not proof that an action did not happen. Use\n`canonry_project_history` or `canonry_history_global` for persistent audit events;\noffset pages have deterministic ordering but are not snapshots of concurrent writes.\n\nAudit `actor` comes from authenticated identity (`user:<id>` or `api-key:<id>`),\nnot a caller-supplied header. A delegated MCP credential records its originating\nuser as actor and the actual credential in `credentialId`. `requestId` correlates\nHTTP events; `userAgent` and `actorSession` are bounded, untrusted client hints,\nnever identity or permission grants. Older audit rows are not backfilled with\nidentities the server cannot prove.\n\nBoth shipped HTTP hosts issue restart-safe UUID request IDs and return them in\n`x-request-id`. Use that value to correlate a CLI/API failure with log entries;\nHTTP diagnostics retain the method and route template, not raw URL parameters.\nRequest-bound loggers retain completion attribution, while generic background\ncontinuations stop inheriting caller identity after the response completes.\nCapture covers the owning server process after initialization, not arbitrary\nconsole output, other worker processes, or host/container logs. Run one server\ninstance per process and database, as required by the single-tenant deployment\nmodel; this is not a cross-tenant or distributed log collector.\n\nFor CLI use, settings reads are remote. Google setup and telemetry retain their\nlocal defaults: pass `--target server` explicitly to configure the connected\nserver. `schedule list <project>` lists all schedule kinds, and\n`notify events --target server` discovers the server's event catalog.\n\nMCP returns legacy text JSON plus structured results. Objects keep their shape;\narrays use `{items: [...]}` in `structuredContent`, and scalars use `{value: ...}`.\nErrors preserve the existing envelope and CLI exit codes: HTTP 4xx (including\n429 policy limits) use exit 1; HTTP 5xx use exit 2. Server-provided `Retry-After`\nand request IDs are exposed as `retryAfterMs` and `requestId` when available;\nclients do not infer retryability from HTTP 429 or retry automatically.\nFor a write with an ambiguous outcome, inspect saved state or its receipt before\nretrying; a retry hint is not proof that repeating a write is safe.\n\n### Action boundaries\n\nStart with stored evidence. Before a live provider read, sweep, probe, research\nrun, sync, write, or externally visible action, obtain approval covering its\nexact target, action, and bounded work. Approval already given for that exact\noperation need not be asked for again, but does not extend to more projects,\nlarger batches, retries with new identities, or recurring work.\n\nHTTP GET and MCP `readOnlyHint` describe aspects of an operation, not its cost\nor permission. Provider discovery, account reads, and live diagnostics may\nconsume quota even when labeled read-only. If the tool's effect is unclear,\ninspect its description and request direction before calling it.\n\nAuthentication, role/scope checks, project restrictions, quotas, and guarded\napproval receipts are enforced by the server. Help, skills, resources, and tool\nvisibility cannot grant authority. Never change credentials, endpoints, or\nproject identifiers to work around a missing tool or a `403` response.\n\nFor guarded ads writes, inspect unresolved operation receipts before retrying.\nUse the receipt's supported recovery action; do not replay a mutation under a\nnew identity. An executor cannot create or widen its own human approval grant.\nOn ambiguous results, exhausted bounds, or refusal, stop and report what is\nknown and what permission or operator action is needed.\n\n## Version and source\n\nThis public, versioned document is the source for initialization guidance,\nintent routes, the optional resource, and generated Canonry `SKILL.md` files.\nGuide v1 may receive compatible clarifications; incompatible routing contracts\nrequire a new guide version. The running server's help describes its actual\ncatalog and remains usable without fetching this document.\n"
403
+ "markdown": "# Canonry Operations Guide v1\n\nCanonry is an agent-first AI visibility platform. MCP is the universal entry\npoint for connected agents. Host-native skills are optional upgrades, not a\nprerequisite or a permission mechanism.\n\n## Connect and choose a route\n\nRead the initialization guidance, then call `canonry_help` with an `intent`:\n`status`, `diagnose`, `operations`, `prospecting`, `measurement`, `integrations`,\n`reports`, or a short task\ndescription. Select an accessible project with `canonry_projects_list` before\nusing its exact name in project tools. Inspect each listed tool's input schema;\nhelp suggests tool names, not invented arguments or authorization.\n\nHelp returns a versioned, compact route: connection `mode`, available `next`\ntools, workflow guidance, approval boundaries, and this guide's URL. It performs\nno provider calls, reads no project data, and changes no permissions.\n`next` lists stored reads. Optional `actions` lists loaded tools for work that\nrequires approval; listing an action does not authorize or execute it.\n`includeCatalog: true` additionally returns toolkit details when needed.\n\nAnalysis routes also return `playbooks`: Canonry's analyst playbooks for that\nwork, the same files its built-in agent reads. Read each one before\ninterpreting results. `canonry_help({ playbook: \"<slug>\" })` returns its full\ntext, a note on using it over MCP, and every available slug. Playbooks are\nguidance, not permission.\n\nHosted connections use a fixed catalog. Help only suggests tools offered by that\nconnection; it never tells a hosted agent to dynamically load a toolkit. A\nprogressive local stdio connection may return `loadToolkits`: call\n`canonry_load_toolkit` with one returned name, await it, then call help again.\nLoading only changes local tool discovery, never server authority.\n\nThe optional `canonry://agent-operations/v1` MCP resource contains this same\nguide. If the host cannot read resources or open links, continue through help.\nDo not install a plugin, local runtime, or skill merely to use connected MCP.\nAn installed Codex or Claude Canonry skill contains a generated copy of this\nguide plus links to host-native references. It does not replace runtime help.\n\n## Vocabulary and evidence\n\n- **Mentioned** means the brand appears in answer text. **Cited** means its\n domain appears in source links. Either, both, or neither can occur; never\n compute one signal from the other.\n- `answerMentioned: null` means not checked, not false. Missing runs and empty\n populations mean no measurement, not zero visibility.\n- Preserve project, time window, provider, requested/served model, location,\n sample size, and query class when comparing evidence. Use server-returned\n metrics; do not invent a score from incompatible populations.\n- Simple projects and Advanced portfolios share the workflow. For Advanced\n results, preserve Property, Target, market, plan revision, and class scope.\n Groups organize navigation; do not infer an unrequested fan-out.\n- Research is isolated evidence, not tracked measurement. A probe still spends\n quota and persists evidence but is excluded from normal tracking metrics.\n\n## Workflows\n\n**Status:** read the stored overview and freshness first. Say when evidence is\nmissing instead of silently creating it.\n\n**Setup:** Page Health needs no answer-engine provider; AI Visibility does.\nAfter a Page Health result on an install with no provider (a `NO_PROVIDER`\nnotice, or doctor's `providers.none-configured`), propose AI Visibility setup\nas the next step. The operator adds the provider key in the dashboard or in\ntheir own terminal; never ask for it in chat.\nRerunning `canonry bootstrap` after a provider change reloads saved provider\nsettings on a matching running local server and reports whether they became\nactive. If the server is offline, start it to apply the saved settings. For an\nexplicit reload of the connected server's own config, use\n`canonry settings reload-providers` or `canonry_providers_reload` with install\nadministrator authority. Remote servers are never reloaded automatically by\nbootstrap, and a local server that belongs to another install is left alone.\nPorts and database settings still need a restart.\n\n**Diagnose:** inspect stored history and comparable evidence. Explain what\nchanged separately from why it might have changed. A hypothesis is not a\nmeasured cause. Propose bounded verification if stored evidence is insufficient.\n\n**Site Health:** read `canonry_site_health_issues` for stored technical findings,\naffected-page counts and example URLs, then `canonry_site_health_issue_pages`\nfor a selected issue's remaining pages. Pin subsequent reads to the returned\nrun. Inspect coverage and omissions: a passing score can still have findings,\nan older scan may lack cross-page evidence, and a partial scan cannot establish\nsite-wide absence. Advisory findings require judgment before proposing a repair.\nAudit evidence informs technical repairs and existing-page recommendations;\nit does not itself establish a new-blog opportunity or authorize any edit.\n\n**Prospecting:** generate a one-shot company snapshot without creating a project.\nInspect stored provider settings first. Agree on the company, domain, selected\nproviders, and queries before starting the quota-spending snapshot action.\nBrowser-only selection requires manual queries. Progressive stdio help offers\nthe discovery toolkit when this connection permits snapshots; load it, then\ncall help again. Fixed catalogs offer only already available actions. Read-only\nand restricted connections must not bypass missing snapshot access.\n\n**Measurement:** inspect the existing setup and results before proposing edits.\nKeep research, query tracking, plan publication, and sweep execution separate.\nFor direct research, submit the final editable query text in one context. For a\nreviewed batch, submit each explicit destination with its final text and one\nidempotency key. Pattern substitution happens in the client before either\nrequest; choosing a market or Property records a destination only and never\nrewrites a query or creates an automatic fan-out. `research.run` does not\nauthorize saving patterns, changing tracking, publishing plans, or settings.\nUse a supported preview where available, inspect its exact destination and\nrevision, then seek approval for the actual change. A preview may itself require\nwrite permission; never treat a dry-run flag as a universal safety guarantee.\n\n**Integrations:** inspect stored connection state and snapshot freshness first.\nProvider configuration evidence does not prove a browser event fired or a\nconversion was recorded. Connection, resource selection, refresh/sync, and live\nreads are separate actions. Credentials belong in the operator's secure setup\nflow, never in chat, tool arguments, reports, or public guidance.\n\n**Reports:** use saved evidence for the requested period and scope. Keep mention\nand citation signals separate, include dates and sample sizes, and state missing\nor stale inputs. For Advanced Property mention rankings, use\n`canonry_measurement_portfolio_summary` and its `mentionRanking.strongest`,\n`.weakest`, and `.excluded` lists. It defaults to non-brand questions; state the\nreturned class and report branded results separately. An unavailable portfolio\naggregate does not invalidate available Property mention rates. Flag excluded\nProperties individually; do not silently replace mention ranking with citation\nranking. Compact `nextCursor` completes only `pageList`; use `list` to enumerate\none ranking, markets, observed names or cited domains, preserving filters.\nFirst-page sibling lists are bounded summaries. Keep sample sizes and ties\nvisible. Preparing a report does not\nauthorize new measurement.\n\n## Authority and approval\n\n### Agent operations\n\nUse `canonry_key_self` (CLI `canonry key whoami --format json`) to inspect the\ncurrent credential's scopes, project boundary, and host-derived `operator`\nauthority without exposing its token. Missing `operator` means unapproved.\n`canonry_settings_get` and\n`canonry_telemetry_get` describe the connected server, not the agent's local\nmachine. Telemetry reports configured preference, effective state, and any\nenvironment override; inspecting status never creates an anonymous identifier.\nAfter approval, `canonry_telemetry_update` changes that preference and\n`canonry_provider_settings_update` changes an already-configured provider's\nmodel/quota. Both require `settings.write`; neither accepts credentials.\nServer telemetry reads and updates additionally require operator authority.\nOrdinary audit-history reads omit internal telemetry events and their state.\n\nOperator authority is deny-by-default and separate from customer admin roles.\nThe deployment owner must approve a dedicated, instance-wide API key's ID in\nthe server environment variable `CANONRY_OPERATOR_KEY_IDS` (comma-separated IDs),\nthen restart the server. Empty/unset approves nobody; wildcards are invalid.\nKeep the bearer private to internal operators; never approve a customer-held or\nshared proxy/bootstrap key. Use `logs.read` for read-only diagnostics, adding\n`settings.write` only when telemetry control is required. Ordinary key creation,\naccount roles, OAuth consent, and caller headers cannot grant operator status.\nRevoking an approved key invalidates it immediately. Host enrollment is a trust\nbootstrap step, intentionally unavailable through customer-facing APIs.\nAPI, CLI, and MCP enforce the same boundary; MCP hides internal tools unless\nthe server confirms operator authority, including in explicit read-only mode.\nProject analytics, research, and normal project permissions are unchanged.\n\n`canonry_logs_list` reads bounded, redacted runtime events from both the\napplication logger and Fastify request/error logging. It requires an\ninstance-wide `logs.read` grant (or wildcard) and a host-approved direct bearer.\nBrowser sessions, OAuth/delegated credentials, customer admins, and project-scoped\nkeys cannot use it, even with a project filter or a matching allowlist ID.\nA `logs.read`-only key is read-only automatically, without a\nsecond `read` marker. Named `*.read` scopes cannot grant mutations; an explicit\nwrite grant is needed and remains subject to its route gates. Returned messages\nare sanitized and bounded; raw request or response bodies, headers, cookies,\nprovider payloads, and stacks are not\npart of the queryable surface. The same secret-redaction policy runs before\nconsole output and storage. Do not deliberately log secrets: redaction is a\ndefense in depth, not permission to put credentials into diagnostic strings.\nOpaque escaped payloads containing secret assignments are omitted when safe\npartial masking cannot be guaranteed; correlate their retained error codes and IDs.\n\nFile-backed hosts retain runtime logs in SQLite across restarts, bounded to\n10,000 events and seven days. In-memory hosts report `retention: \"process\"`.\nFilter by `actor`, `requestId`, `runId`, `projectId`, `module`, `level`, or an\ninclusive `since`/`until` interval. Keep filters unchanged when resuming an\nopaque cursor; retention eviction can invalidate it. Inspect `retentionPolicy`,\n`captureErrors`, `dropped`, `truncated`, and `retention` before drawing\nconclusions. Missing logs are not proof that an action did not happen. Use\n`canonry_project_history` or `canonry_history_global` for persistent audit events;\noffset pages have deterministic ordering but are not snapshots of concurrent writes.\n\nAudit `actor` comes from authenticated identity (`user:<id>` or `api-key:<id>`),\nnot a caller-supplied header. A delegated MCP credential records its originating\nuser as actor and the actual credential in `credentialId`. `requestId` correlates\nHTTP events; `userAgent` and `actorSession` are bounded, untrusted client hints,\nnever identity or permission grants. Older audit rows are not backfilled with\nidentities the server cannot prove.\n\nBoth shipped HTTP hosts issue restart-safe UUID request IDs and return them in\n`x-request-id`. Use that value to correlate a CLI/API failure with log entries;\nHTTP diagnostics retain the method and route template, not raw URL parameters.\nRequest-bound loggers retain completion attribution, while generic background\ncontinuations stop inheriting caller identity after the response completes.\nCapture covers the owning server process after initialization, not arbitrary\nconsole output, other worker processes, or host/container logs. Run one server\ninstance per process and database, as required by the single-tenant deployment\nmodel; this is not a cross-tenant or distributed log collector.\n\nFor CLI use, settings reads are remote. Google setup and telemetry retain their\nlocal defaults: pass `--target server` explicitly to configure the connected\nserver. `schedule list <project>` lists all schedule kinds, and\n`notify events --target server` discovers the server's event catalog.\n\nMCP returns legacy text JSON plus structured results. Objects keep their shape;\narrays use `{items: [...]}` in `structuredContent`, and scalars use `{value: ...}`.\nErrors preserve the existing envelope and CLI exit codes: HTTP 4xx (including\n429 policy limits) use exit 1; HTTP 5xx use exit 2. Server-provided `Retry-After`\nand request IDs are exposed as `retryAfterMs` and `requestId` when available;\nclients do not infer retryability from HTTP 429 or retry automatically.\nFor a write with an ambiguous outcome, inspect saved state or its receipt before\nretrying; a retry hint is not proof that repeating a write is safe.\n\n### Action boundaries\n\nStart with stored evidence. Before a live provider read, sweep, probe, research\nrun, sync, write, or externally visible action, obtain approval covering its\nexact target, action, and bounded work. Approval already given for that exact\noperation need not be asked for again, but does not extend to more projects,\nlarger batches, retries with new identities, or recurring work.\n\nHTTP GET and MCP `readOnlyHint` describe aspects of an operation, not its cost\nor permission. Provider discovery, account reads, and live diagnostics may\nconsume quota even when labeled read-only. If the tool's effect is unclear,\ninspect its description and request direction before calling it.\n\nAuthentication, role/scope checks, project restrictions, quotas, and guarded\napproval receipts are enforced by the server. Help, skills, resources, and tool\nvisibility cannot grant authority. Never change credentials, endpoints, or\nproject identifiers to work around a missing tool or a `403` response.\n\nFor guarded ads writes, inspect unresolved operation receipts before retrying.\nUse the receipt's supported recovery action; do not replay a mutation under a\nnew identity. An executor cannot create or widen its own human approval grant.\nOn ambiguous results, exhausted bounds, or refusal, stop and report what is\nknown and what permission or operator action is needed.\n\n## Version and source\n\nThis public, versioned document is the source for initialization guidance,\nintent routes, the optional resource, and generated Canonry `SKILL.md` files.\nGuide v1 may receive compatible clarifications; incompatible routing contracts\nrequire a new guide version. The running server's help describes its actual\ncatalog and remains usable without fetching this document.\n"
380
404
  };
381
405
 
382
406
  // src/mcp/operations-guide.ts
383
- function operationsHelp(catalog, mode, intent = "", includeCatalog = false) {
407
+ function operationsHelp(catalog, mode, intent = "", includeCatalog = false, playbooks = []) {
384
408
  const workflows = Object.keys(OPERATIONS_GUIDE.workflows);
385
409
  const words = new Set(intent.toLowerCase().split(/[^a-z0-9-]+/));
386
410
  const workflow = workflows.find((name) => OPERATIONS_GUIDE.workflows[name].keywords.some((word) => words.has(word))) ?? "status";
@@ -388,8 +412,10 @@ function operationsHelp(catalog, mode, intent = "", includeCatalog = false) {
388
412
  const available = /* @__PURE__ */ new Set([...catalog.coreTools, ...catalog.toolkits.filter((t) => t.loaded).flatMap((t) => t.tools)]);
389
413
  const reads = route.next;
390
414
  const requestedActions = "actions" in route ? route.actions : [];
415
+ const routePlaybooks = "playbooks" in route ? route.playbooks : [];
391
416
  const next = reads.filter((name) => available.has(name)).slice(0, 4);
392
417
  const actions = requestedActions.filter((name) => available.has(name));
418
+ const listedPlaybooks = routePlaybooks.filter((slug) => playbooks.includes(slug));
393
419
  if (reads.includes("canonry_projects_list") && next.length < 2 && available.has("canonry_project_overview") && !next.includes("canonry_project_overview")) {
394
420
  next.push("canonry_project_overview");
395
421
  }
@@ -406,6 +432,7 @@ function operationsHelp(catalog, mode, intent = "", includeCatalog = false) {
406
432
  ...actions.length ? { actions } : {},
407
433
  workflows,
408
434
  guidance: route.guidance,
435
+ ...listedPlaybooks.length ? { playbooks: listedPlaybooks, playbookRule: OPERATIONS_GUIDE.playbookRule } : {},
409
436
  approvalBoundary: OPERATIONS_GUIDE.approvalBoundary,
410
437
  approvalRule: OPERATIONS_GUIDE.approvalRule,
411
438
  authority: OPERATIONS_GUIDE.authority,
@@ -416,6 +443,45 @@ function operationsHelp(catalog, mode, intent = "", includeCatalog = false) {
416
443
  };
417
444
  }
418
445
 
446
+ // src/mcp/playbooks.ts
447
+ import fs from "fs";
448
+ import path from "path";
449
+ var PLAYBOOK_SLUG = /^[a-z0-9-]{1,64}$/;
450
+ var SKILL_SLUG = "aero";
451
+ var GUIDE_SLUG = "agent-operations";
452
+ function availablePlaybooks() {
453
+ const dir = aeroSkillDir();
454
+ if (!dir) return [];
455
+ const references = path.join(dir, "references");
456
+ const slugs = fs.existsSync(references) ? fs.readdirSync(references).filter((file) => file.endsWith(".md")).map((file) => file.slice(0, -".md".length)).filter((slug) => PLAYBOOK_SLUG.test(slug) && slug !== SKILL_SLUG).sort() : [];
457
+ return [SKILL_SLUG, ...slugs];
458
+ }
459
+ function readPlaybook(slug) {
460
+ const playbooks = availablePlaybooks();
461
+ const dir = aeroSkillDir();
462
+ if (!dir || !playbooks.includes(slug)) {
463
+ throw new CliError({
464
+ code: "NOT_FOUND",
465
+ message: `No playbook "${slug}". Use a slug from availablePlaybooks.`,
466
+ details: { availablePlaybooks: playbooks }
467
+ });
468
+ }
469
+ const file = slug === SKILL_SLUG ? "SKILL.md" : path.join("references", `${slug}.md`);
470
+ const content = slug === GUIDE_SLUG ? OPERATIONS_GUIDE.markdown : fs.readFileSync(path.join(dir, file), "utf8");
471
+ return `Playbook: ${slug}
472
+ Note: ${OPERATIONS_GUIDE.playbookNote}
473
+ Available: ${playbooks.join(", ")}
474
+
475
+ ${content}`;
476
+ }
477
+ function aeroSkillDir() {
478
+ try {
479
+ return resolveAeroSkillDir();
480
+ } catch {
481
+ return null;
482
+ }
483
+ }
484
+
419
485
  // src/mcp/server.ts
420
486
  function createCanonryMcpServer(options = {}) {
421
487
  return createCanonryMcpServerWithCatalog(options).server;
@@ -496,23 +562,27 @@ var loadToolkitInputSchema = z.object({
496
562
  });
497
563
  var helpInputSchema = z.object({
498
564
  intent: z.string().max(200).optional().describe("Workflow (status, diagnose, prospecting, measurement, integrations, reports) or a short task description."),
499
- includeCatalog: z.boolean().optional().describe("Include full toolkit details. Omit for a compact, actionable route.")
565
+ includeCatalog: z.boolean().optional().describe("Include full toolkit details. Omit for a compact, actionable route."),
566
+ playbook: z.string().regex(PLAYBOOK_SLUG).optional().describe('Slug from a help result\'s `playbooks`, such as "regression-playbook". Returns that analyst playbook in full instead of a route.')
500
567
  });
501
568
  function registerMetaTools(server, catalog, opts) {
502
569
  server.registerTool(
503
570
  "canonry_help",
504
571
  {
505
572
  title: "Guide a Canonry workflow",
506
- description: "Start here: route an intent to available stored-evidence tools, separately listed actions requiring approval, and workflow guidance. No provider calls or installation required. Optionally include full toolkit details.",
573
+ description: "Start here: route an intent to available stored-evidence tools, separately listed actions requiring approval, workflow guidance, and the analyst playbooks to read. Pass `playbook` to read one in full. No provider calls or installation required. Optionally include full toolkit details.",
507
574
  inputSchema: helpInputSchema,
508
575
  annotations: { readOnlyHint: true, idempotentHint: true, destructiveHint: false, openWorldHint: false }
509
576
  },
510
577
  async (input) => {
511
578
  try {
512
579
  const parsed = helpInputSchema.parse(input ?? {});
580
+ if (parsed.playbook !== void 0) {
581
+ return { content: [{ type: "text", text: readPlaybook(parsed.playbook) }] };
582
+ }
513
583
  const update = readUpdateAvailable(opts.updateAvailable);
514
584
  const result = {
515
- ...operationsHelp(catalog.helpResult(), opts.mode, parsed.intent, parsed.includeCatalog),
585
+ ...operationsHelp(catalog.helpResult(), opts.mode, parsed.intent, parsed.includeCatalog, availablePlaybooks()),
516
586
  ...update ? { updateAvailable: updateNoticePayload(update) } : {}
517
587
  };
518
588
  return { ...jsonToolResult(result), structuredContent: result };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  REGISTERED_CLI_COMMANDS
3
- } from "./chunk-UHCOJCT3.js";
3
+ } from "./chunk-R7HQNCG5.js";
4
4
  import "./chunk-6GO5VVAC.js";
5
5
  import "./chunk-2EWZ3DJ4.js";
6
6
  import "./chunk-VJWDBFSL.js";
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node --import tsx
2
2
  import {
3
3
  REGISTERED_CLI_COMMANDS
4
- } from "./chunk-UHCOJCT3.js";
4
+ } from "./chunk-R7HQNCG5.js";
5
5
  import {
6
6
  buildSetupState,
7
7
  runPendingServeHandoff
8
- } from "./chunk-GEHRGKQT.js";
8
+ } from "./chunk-4UXTNBXS.js";
9
9
  import "./chunk-6GO5VVAC.js";
10
10
  import "./chunk-2EWZ3DJ4.js";
11
11
  import "./chunk-VJWDBFSL.js";
package/dist/index.d.ts CHANGED
@@ -70,10 +70,10 @@ declare const googleAdsAccessibleCustomerDtoSchema: z.ZodObject<{
70
70
  level: z.ZodNumber;
71
71
  status: z.ZodEnum<{
72
72
  unknown: "unknown";
73
+ canceled: "canceled";
73
74
  enabled: "enabled";
74
75
  suspended: "suspended";
75
76
  closed: "closed";
76
- canceled: "canceled";
77
77
  unspecified: "unspecified";
78
78
  }>;
79
79
  }, z.core.$strict>;
@@ -92,10 +92,10 @@ declare const googleAdsAccessibleCustomersResponseSchema: z.ZodObject<{
92
92
  level: z.ZodNumber;
93
93
  status: z.ZodEnum<{
94
94
  unknown: "unknown";
95
+ canceled: "canceled";
95
96
  enabled: "enabled";
96
97
  suspended: "suspended";
97
98
  closed: "closed";
98
- canceled: "canceled";
99
99
  unspecified: "unspecified";
100
100
  }>;
101
101
  }, z.core.$strict>>;
@@ -115,8 +115,8 @@ declare const googleAdsEffectiveGoalGraphDtoSchema: z.ZodObject<{
115
115
  campaigns: z.ZodArray<z.ZodObject<{
116
116
  campaignId: z.ZodString;
117
117
  goalConfigLevel: z.ZodEnum<{
118
- customer: "customer";
119
118
  campaign: "campaign";
119
+ customer: "customer";
120
120
  }>;
121
121
  customGoalId: z.ZodNullable<z.ZodString>;
122
122
  missingCustomGoalId: z.ZodNullable<z.ZodString>;
@@ -180,10 +180,10 @@ declare const googleAdsRawSnapshotDtoSchema: z.ZodObject<{
180
180
  level: z.ZodNumber;
181
181
  status: z.ZodEnum<{
182
182
  unknown: "unknown";
183
+ canceled: "canceled";
183
184
  enabled: "enabled";
184
185
  suspended: "suspended";
185
186
  closed: "closed";
186
- canceled: "canceled";
187
187
  unspecified: "unspecified";
188
188
  }>;
189
189
  }, z.core.$strict>>;
@@ -208,8 +208,8 @@ declare const googleAdsRawSnapshotDtoSchema: z.ZodObject<{
208
208
  status: z.ZodEnum<{
209
209
  unknown: "unknown";
210
210
  enabled: "enabled";
211
- removed: "removed";
212
211
  paused: "paused";
212
+ removed: "removed";
213
213
  }>;
214
214
  advertisingChannelType: z.ZodNullable<z.ZodString>;
215
215
  biddingStrategyType: z.ZodNullable<z.ZodString>;
@@ -221,8 +221,8 @@ declare const googleAdsRawSnapshotDtoSchema: z.ZodObject<{
221
221
  status: z.ZodEnum<{
222
222
  unknown: "unknown";
223
223
  enabled: "enabled";
224
- removed: "removed";
225
224
  hidden: "hidden";
225
+ removed: "removed";
226
226
  }>;
227
227
  category: z.ZodString;
228
228
  origin: z.ZodString;
@@ -249,8 +249,8 @@ declare const googleAdsRawSnapshotDtoSchema: z.ZodObject<{
249
249
  campaignGoalConfigurations: z.ZodArray<z.ZodObject<{
250
250
  campaignId: z.ZodString;
251
251
  goalConfigLevel: z.ZodEnum<{
252
- customer: "customer";
253
252
  campaign: "campaign";
253
+ customer: "customer";
254
254
  }>;
255
255
  customGoalId: z.ZodNullable<z.ZodString>;
256
256
  }, z.core.$strict>>;
@@ -331,10 +331,10 @@ declare const gtmRawSnapshotDtoSchema: z.ZodObject<{
331
331
  connectionId: z.ZodString;
332
332
  runId: z.ZodString;
333
333
  kind: z.ZodEnum<{
334
- live: "live";
335
- draft: "draft";
336
334
  accounts: "accounts";
337
335
  container: "container";
336
+ live: "live";
337
+ draft: "draft";
338
338
  }>;
339
339
  accountId: z.ZodNullable<z.ZodString>;
340
340
  containerId: z.ZodNullable<z.ZodString>;
@@ -1095,8 +1095,8 @@ declare const googleMarketingProviderSchema: z.ZodEnum<{
1095
1095
  type GoogleMarketingProvider = z.infer<typeof googleMarketingProviderSchema>;
1096
1096
 
1097
1097
  declare const gbpReviewOriginSchema: z.ZodEnum<{
1098
- places: "places";
1099
1098
  gbp: "gbp";
1099
+ places: "places";
1100
1100
  }>;
1101
1101
  type GbpReviewOrigin = z.infer<typeof gbpReviewOriginSchema>;
1102
1102
 
@@ -1446,8 +1446,8 @@ declare const seoMarketRankingSchema: z.ZodObject<{
1446
1446
  searchStatus: z.ZodEnum<{
1447
1447
  partial: "partial";
1448
1448
  failed: "failed";
1449
- completed: "completed";
1450
1449
  cancelled: "cancelled";
1450
+ completed: "completed";
1451
1451
  }>;
1452
1452
  rankOrganic: z.ZodNullable<z.ZodNumber>;
1453
1453
  rankAbsolute: z.ZodNullable<z.ZodNumber>;
@@ -1594,8 +1594,8 @@ declare const seoResultsSchema: z.ZodObject<{
1594
1594
  searchStatus: z.ZodEnum<{
1595
1595
  partial: "partial";
1596
1596
  failed: "failed";
1597
- completed: "completed";
1598
1597
  cancelled: "cancelled";
1598
+ completed: "completed";
1599
1599
  }>;
1600
1600
  rankOrganic: z.ZodNullable<z.ZodNumber>;
1601
1601
  rankAbsolute: z.ZodNullable<z.ZodNumber>;
@@ -1831,7 +1831,7 @@ declare const seoProfiles: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
1831
1831
  languageCode: string;
1832
1832
  limit: number;
1833
1833
  scope: {
1834
- kind: "project" | "property" | "market" | "group";
1834
+ kind: "group" | "market" | "property" | "project";
1835
1835
  key?: string | undefined;
1836
1836
  expectedPlanRevision?: number | undefined;
1837
1837
  };
@@ -1876,7 +1876,7 @@ declare const seoProfiles: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
1876
1876
  languageCode: string;
1877
1877
  limit: number;
1878
1878
  scope: {
1879
- kind: "project" | "property" | "market" | "group";
1879
+ kind: "group" | "market" | "property" | "project";
1880
1880
  key?: string | undefined;
1881
1881
  expectedPlanRevision?: number | undefined;
1882
1882
  };
@@ -2073,7 +2073,7 @@ declare const seoJobs: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
2073
2073
  languageCode: string;
2074
2074
  limit: number;
2075
2075
  scope: {
2076
- kind: "project" | "property" | "market" | "group";
2076
+ kind: "group" | "market" | "property" | "project";
2077
2077
  key?: string | undefined;
2078
2078
  expectedPlanRevision?: number | undefined;
2079
2079
  };
@@ -2118,7 +2118,7 @@ declare const seoJobs: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
2118
2118
  languageCode: string;
2119
2119
  limit: number;
2120
2120
  scope: {
2121
- kind: "project" | "property" | "market" | "group";
2121
+ kind: "group" | "market" | "property" | "project";
2122
2122
  key?: string | undefined;
2123
2123
  expectedPlanRevision?: number | undefined;
2124
2124
  };
@@ -2277,7 +2277,7 @@ declare const seoJobs: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
2277
2277
  tableName: "seo_jobs";
2278
2278
  dataType: "string";
2279
2279
  columnType: "SQLiteText";
2280
- data: "queued" | "running" | "partial" | "failed" | "completed" | "cancelled" | "reconciliation-required";
2280
+ data: "queued" | "partial" | "failed" | "cancelled" | "running" | "completed" | "reconciliation-required";
2281
2281
  driverParam: string;
2282
2282
  notNull: true;
2283
2283
  hasDefault: false;
@@ -2290,7 +2290,7 @@ declare const seoJobs: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
2290
2290
  generated: undefined;
2291
2291
  }, {}, {
2292
2292
  length: number | undefined;
2293
- $type: "queued" | "running" | "partial" | "failed" | "completed" | "cancelled" | "reconciliation-required";
2293
+ $type: "queued" | "partial" | "failed" | "cancelled" | "running" | "completed" | "reconciliation-required";
2294
2294
  }>;
2295
2295
  cancelRequested: drizzle_orm_sqlite_core.SQLiteColumn<{
2296
2296
  name: "cancel_requested";
@@ -2454,7 +2454,7 @@ declare const seoTasks: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
2454
2454
  tableName: "seo_tasks";
2455
2455
  dataType: "string";
2456
2456
  columnType: "SQLiteText";
2457
- data: "queued" | "running" | "partial" | "failed" | "completed" | "cancelled" | "reconciliation-required";
2457
+ data: "queued" | "partial" | "failed" | "cancelled" | "running" | "completed" | "reconciliation-required";
2458
2458
  driverParam: string;
2459
2459
  notNull: true;
2460
2460
  hasDefault: false;
@@ -2467,7 +2467,7 @@ declare const seoTasks: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
2467
2467
  generated: undefined;
2468
2468
  }, {}, {
2469
2469
  length: number | undefined;
2470
- $type: "queued" | "running" | "partial" | "failed" | "completed" | "cancelled" | "reconciliation-required";
2470
+ $type: "queued" | "partial" | "failed" | "cancelled" | "running" | "completed" | "reconciliation-required";
2471
2471
  }>;
2472
2472
  leaseOwner: drizzle_orm_sqlite_core.SQLiteColumn<{
2473
2473
  name: "lease_owner";
@@ -2985,7 +2985,7 @@ declare const projects: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
2985
2985
  tableName: "projects";
2986
2986
  dataType: "string";
2987
2987
  columnType: "SQLiteText";
2988
- data: "competitors" | "full";
2988
+ data: "full" | "competitors";
2989
2989
  driverParam: string;
2990
2990
  notNull: false;
2991
2991
  hasDefault: false;
@@ -2998,7 +2998,7 @@ declare const projects: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
2998
2998
  generated: undefined;
2999
2999
  }, {}, {
3000
3000
  length: number | undefined;
3001
- $type: "competitors" | "full";
3001
+ $type: "full" | "competitors";
3002
3002
  }>;
3003
3003
  country: drizzle_orm_sqlite_core.SQLiteColumn<{
3004
3004
  name: "country";
@@ -6256,7 +6256,7 @@ declare const querySnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
6256
6256
  tableName: "query_snapshots";
6257
6257
  dataType: "string";
6258
6258
  columnType: "SQLiteText";
6259
- data: "unknown" | "not-applicable" | "used" | "not-used";
6259
+ data: "unknown" | "used" | "not-used" | "not-applicable";
6260
6260
  driverParam: string;
6261
6261
  notNull: false;
6262
6262
  hasDefault: false;
@@ -6269,7 +6269,7 @@ declare const querySnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
6269
6269
  generated: undefined;
6270
6270
  }, {}, {
6271
6271
  length: number | undefined;
6272
- $type: "unknown" | "not-applicable" | "used" | "not-used";
6272
+ $type: "unknown" | "used" | "not-used" | "not-applicable";
6273
6273
  }>;
6274
6274
  retrievalContract: drizzle_orm_sqlite_core.SQLiteColumn<{
6275
6275
  name: "retrieval_contract";
@@ -6617,8 +6617,8 @@ declare const querySnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
6617
6617
  status?: string | null | undefined;
6618
6618
  }[];
6619
6619
  searchQueries: string[];
6620
- retrievalStatus: "unknown" | "not-applicable" | "used" | "not-used";
6621
- citationBasis: "none" | "mixed" | "answer-support" | "explicit-citation";
6620
+ retrievalStatus: "unknown" | "used" | "not-used" | "not-applicable";
6621
+ citationBasis: "answer-support" | "explicit-citation" | "mixed" | "none";
6622
6622
  citationMeasurable: boolean;
6623
6623
  directSourceUrls: boolean;
6624
6624
  location: {
@@ -6722,8 +6722,8 @@ declare const querySnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
6722
6722
  status?: string | null | undefined;
6723
6723
  }[];
6724
6724
  searchQueries: string[];
6725
- retrievalStatus: "unknown" | "not-applicable" | "used" | "not-used";
6726
- citationBasis: "none" | "mixed" | "answer-support" | "explicit-citation";
6725
+ retrievalStatus: "unknown" | "used" | "not-used" | "not-applicable";
6726
+ citationBasis: "answer-support" | "explicit-citation" | "mixed" | "none";
6727
6727
  citationMeasurable: boolean;
6728
6728
  directSourceUrls: boolean;
6729
6729
  location: {
@@ -7137,7 +7137,7 @@ declare const providerBatches: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
7137
7137
  tableName: "provider_batches";
7138
7138
  dataType: "string";
7139
7139
  columnType: "SQLiteText";
7140
- data: "unknown" | "failed" | "submitted" | "cancelled" | "submitting" | "ended" | "ingested";
7140
+ data: "unknown" | "failed" | "submitting" | "submitted" | "ended" | "ingested" | "cancelled";
7141
7141
  driverParam: string;
7142
7142
  notNull: true;
7143
7143
  hasDefault: false;
@@ -7150,7 +7150,7 @@ declare const providerBatches: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
7150
7150
  generated: undefined;
7151
7151
  }, {}, {
7152
7152
  length: number | undefined;
7153
- $type: "unknown" | "failed" | "submitted" | "cancelled" | "submitting" | "ended" | "ingested";
7153
+ $type: "unknown" | "failed" | "submitting" | "submitted" | "ended" | "ingested" | "cancelled";
7154
7154
  }>;
7155
7155
  requestCount: drizzle_orm_sqlite_core.SQLiteColumn<{
7156
7156
  name: "request_count";
@@ -7596,7 +7596,7 @@ declare const providerBatchRequests: drizzle_orm_sqlite_core.SQLiteTableWithColu
7596
7596
  tableName: "provider_batch_requests";
7597
7597
  dataType: "string";
7598
7598
  columnType: "SQLiteText";
7599
- data: "duplicate" | "canceled" | "recorded" | "errored" | "expired" | "parse_failed";
7599
+ data: "recorded" | "errored" | "expired" | "canceled" | "parse_failed" | "duplicate";
7600
7600
  driverParam: string;
7601
7601
  notNull: false;
7602
7602
  hasDefault: false;
@@ -7609,7 +7609,7 @@ declare const providerBatchRequests: drizzle_orm_sqlite_core.SQLiteTableWithColu
7609
7609
  generated: undefined;
7610
7610
  }, {}, {
7611
7611
  length: number | undefined;
7612
- $type: "duplicate" | "canceled" | "recorded" | "errored" | "expired" | "parse_failed";
7612
+ $type: "recorded" | "errored" | "expired" | "canceled" | "parse_failed" | "duplicate";
7613
7613
  }>;
7614
7614
  error: drizzle_orm_sqlite_core.SQLiteColumn<{
7615
7615
  name: "error";
@@ -9353,7 +9353,7 @@ declare const oauthTokens: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
9353
9353
  tableName: "oauth_tokens";
9354
9354
  dataType: "string";
9355
9355
  columnType: "SQLiteText";
9356
- data: "refresh" | "access";
9356
+ data: "access" | "refresh";
9357
9357
  driverParam: string;
9358
9358
  notNull: true;
9359
9359
  hasDefault: false;
@@ -9366,7 +9366,7 @@ declare const oauthTokens: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
9366
9366
  generated: undefined;
9367
9367
  }, {}, {
9368
9368
  length: number | undefined;
9369
- $type: "refresh" | "access";
9369
+ $type: "access" | "refresh";
9370
9370
  }>;
9371
9371
  grantId: drizzle_orm_sqlite_core.SQLiteColumn<{
9372
9372
  name: "grant_id";
@@ -12427,7 +12427,7 @@ declare const siteAuditSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColumns
12427
12427
  weight: number;
12428
12428
  sharePct: number | null;
12429
12429
  avgScore: number;
12430
- status: "partial" | "fail" | "pass";
12430
+ status: "partial" | "pass" | "fail";
12431
12431
  pagesPassing: number;
12432
12432
  pagesPartial: number;
12433
12433
  pagesFailing: number;
@@ -12449,7 +12449,7 @@ declare const siteAuditSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColumns
12449
12449
  weight: number;
12450
12450
  sharePct: number | null;
12451
12451
  avgScore: number;
12452
- status: "partial" | "fail" | "pass";
12452
+ status: "partial" | "pass" | "fail";
12453
12453
  pagesPassing: number;
12454
12454
  pagesPartial: number;
12455
12455
  pagesFailing: number;
@@ -25702,7 +25702,7 @@ declare const discoverySessions: drizzle_orm_sqlite_core.SQLiteTableWithColumns<
25702
25702
  data: {
25703
25703
  domain: string;
25704
25704
  hits: number;
25705
- competitorType: "unknown" | "other" | "direct-competitor" | "ota-aggregator" | "editorial-media";
25705
+ competitorType: "unknown" | "direct-competitor" | "ota-aggregator" | "editorial-media" | "other";
25706
25706
  evaluator?: {
25707
25707
  provider: string;
25708
25708
  requestedModel: string;
@@ -25724,7 +25724,7 @@ declare const discoverySessions: drizzle_orm_sqlite_core.SQLiteTableWithColumns<
25724
25724
  $type: {
25725
25725
  domain: string;
25726
25726
  hits: number;
25727
- competitorType: "unknown" | "other" | "direct-competitor" | "ota-aggregator" | "editorial-media";
25727
+ competitorType: "unknown" | "direct-competitor" | "ota-aggregator" | "editorial-media" | "other";
25728
25728
  evaluator?: {
25729
25729
  provider: string;
25730
25730
  requestedModel: string;
@@ -25975,7 +25975,7 @@ declare const researchRuns: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
25975
25975
  dataType: "json";
25976
25976
  columnType: "SQLiteTextJson";
25977
25977
  data: {
25978
- kind: "property" | "market";
25978
+ kind: "market" | "property";
25979
25979
  key: string;
25980
25980
  label: string;
25981
25981
  planRevision: number;
@@ -25992,7 +25992,7 @@ declare const researchRuns: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
25992
25992
  generated: undefined;
25993
25993
  }, {}, {
25994
25994
  $type: {
25995
- kind: "property" | "market";
25995
+ kind: "market" | "property";
25996
25996
  key: string;
25997
25997
  label: string;
25998
25998
  planRevision: number;
@@ -26143,7 +26143,7 @@ declare const researchRuns: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
26143
26143
  dataType: "json";
26144
26144
  columnType: "SQLiteTextJson";
26145
26145
  data: {
26146
- kind: "user" | "api-key";
26146
+ kind: "api-key" | "user";
26147
26147
  id: string;
26148
26148
  name: string;
26149
26149
  role: "admin" | "viewer" | null;
@@ -26161,7 +26161,7 @@ declare const researchRuns: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
26161
26161
  generated: undefined;
26162
26162
  }, {}, {
26163
26163
  $type: {
26164
- kind: "user" | "api-key";
26164
+ kind: "api-key" | "user";
26165
26165
  id: string;
26166
26166
  name: string;
26167
26167
  role: "admin" | "viewer" | null;
@@ -26698,8 +26698,8 @@ declare const researchRunQueries: drizzle_orm_sqlite_core.SQLiteTableWithColumns
26698
26698
  status?: string | null | undefined;
26699
26699
  }[];
26700
26700
  searchQueries: string[];
26701
- retrievalStatus: "unknown" | "not-applicable" | "used" | "not-used";
26702
- citationBasis: "none" | "mixed" | "answer-support" | "explicit-citation";
26701
+ retrievalStatus: "unknown" | "used" | "not-used" | "not-applicable";
26702
+ citationBasis: "answer-support" | "explicit-citation" | "mixed" | "none";
26703
26703
  citationMeasurable: boolean;
26704
26704
  directSourceUrls: boolean;
26705
26705
  location: {
@@ -26803,8 +26803,8 @@ declare const researchRunQueries: drizzle_orm_sqlite_core.SQLiteTableWithColumns
26803
26803
  status?: string | null | undefined;
26804
26804
  }[];
26805
26805
  searchQueries: string[];
26806
- retrievalStatus: "unknown" | "not-applicable" | "used" | "not-used";
26807
- citationBasis: "none" | "mixed" | "answer-support" | "explicit-citation";
26806
+ retrievalStatus: "unknown" | "used" | "not-used" | "not-applicable";
26807
+ citationBasis: "answer-support" | "explicit-citation" | "mixed" | "none";
26808
26808
  citationMeasurable: boolean;
26809
26809
  directSourceUrls: boolean;
26810
26810
  location: {
@@ -27185,7 +27185,7 @@ declare const domainClassifications: drizzle_orm_sqlite_core.SQLiteTableWithColu
27185
27185
  tableName: "domain_classifications";
27186
27186
  dataType: "string";
27187
27187
  columnType: "SQLiteText";
27188
- data: "unknown" | "other" | "direct-competitor" | "ota-aggregator" | "editorial-media";
27188
+ data: "unknown" | "direct-competitor" | "ota-aggregator" | "editorial-media" | "other";
27189
27189
  driverParam: string;
27190
27190
  notNull: true;
27191
27191
  hasDefault: false;
@@ -27198,7 +27198,7 @@ declare const domainClassifications: drizzle_orm_sqlite_core.SQLiteTableWithColu
27198
27198
  generated: undefined;
27199
27199
  }, {}, {
27200
27200
  length: number | undefined;
27201
- $type: "unknown" | "other" | "direct-competitor" | "ota-aggregator" | "editorial-media";
27201
+ $type: "unknown" | "direct-competitor" | "ota-aggregator" | "editorial-media" | "other";
27202
27202
  }>;
27203
27203
  hits: drizzle_orm_sqlite_core.SQLiteColumn<{
27204
27204
  name: "hits";
@@ -29541,7 +29541,7 @@ declare const gbpReviews: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
29541
29541
  tableName: "gbp_reviews";
29542
29542
  dataType: "string";
29543
29543
  columnType: "SQLiteText";
29544
- data: "places" | "gbp";
29544
+ data: "gbp" | "places";
29545
29545
  driverParam: string;
29546
29546
  notNull: true;
29547
29547
  hasDefault: false;
@@ -29554,7 +29554,7 @@ declare const gbpReviews: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
29554
29554
  generated: undefined;
29555
29555
  }, {}, {
29556
29556
  length: number | undefined;
29557
- $type: "places" | "gbp";
29557
+ $type: "gbp" | "places";
29558
29558
  }>;
29559
29559
  reviewName: drizzle_orm_sqlite_core.SQLiteColumn<{
29560
29560
  name: "review_name";
@@ -29787,7 +29787,7 @@ declare const gbpReviews: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
29787
29787
  tableName: "gbp_reviews";
29788
29788
  dataType: "string";
29789
29789
  columnType: "SQLiteText";
29790
- data: "none" | "baseline" | "stale" | "pending" | "sent" | "skipped" | "suppressed";
29790
+ data: "pending" | "none" | "sent" | "baseline" | "stale" | "skipped" | "suppressed";
29791
29791
  driverParam: string;
29792
29792
  notNull: true;
29793
29793
  hasDefault: true;
@@ -29800,7 +29800,7 @@ declare const gbpReviews: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
29800
29800
  generated: undefined;
29801
29801
  }, {}, {
29802
29802
  length: number | undefined;
29803
- $type: "none" | "baseline" | "stale" | "pending" | "sent" | "skipped" | "suppressed";
29803
+ $type: "pending" | "none" | "sent" | "baseline" | "stale" | "skipped" | "suppressed";
29804
29804
  }>;
29805
29805
  alertStateAt: drizzle_orm_sqlite_core.SQLiteColumn<{
29806
29806
  name: "alert_state_at";
@@ -29952,7 +29952,7 @@ declare const gbpReviewRatings: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
29952
29952
  tableName: "gbp_review_ratings";
29953
29953
  dataType: "string";
29954
29954
  columnType: "SQLiteText";
29955
- data: "places" | "gbp";
29955
+ data: "gbp" | "places";
29956
29956
  driverParam: string;
29957
29957
  notNull: true;
29958
29958
  hasDefault: false;
@@ -29965,7 +29965,7 @@ declare const gbpReviewRatings: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
29965
29965
  generated: undefined;
29966
29966
  }, {}, {
29967
29967
  length: number | undefined;
29968
- $type: "places" | "gbp";
29968
+ $type: "gbp" | "places";
29969
29969
  }>;
29970
29970
  rating: drizzle_orm_sqlite_core.SQLiteColumn<{
29971
29971
  name: "rating";
@@ -30097,7 +30097,7 @@ declare const gbpReviewRatings: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
30097
30097
  tableName: "gbp_review_ratings";
30098
30098
  dataType: "string";
30099
30099
  columnType: "SQLiteText";
30100
- data: "none" | "baseline" | "stale" | "pending" | "sent" | "skipped" | "suppressed";
30100
+ data: "pending" | "none" | "sent" | "baseline" | "stale" | "skipped" | "suppressed";
30101
30101
  driverParam: string;
30102
30102
  notNull: true;
30103
30103
  hasDefault: true;
@@ -30110,7 +30110,7 @@ declare const gbpReviewRatings: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
30110
30110
  generated: undefined;
30111
30111
  }, {}, {
30112
30112
  length: number | undefined;
30113
- $type: "none" | "baseline" | "stale" | "pending" | "sent" | "skipped" | "suppressed";
30113
+ $type: "pending" | "none" | "sent" | "baseline" | "stale" | "skipped" | "suppressed";
30114
30114
  }>;
30115
30115
  alertStateAt: drizzle_orm_sqlite_core.SQLiteColumn<{
30116
30116
  name: "alert_state_at";
@@ -31356,7 +31356,7 @@ declare const adsOperationSteps: drizzle_orm_sqlite_core.SQLiteTableWithColumns<
31356
31356
  tableName: "ads_operation_steps";
31357
31357
  dataType: "string";
31358
31358
  columnType: "SQLiteText";
31359
- data: "unknown" | "failed" | "active" | "pending" | "executing" | "rollback_executing" | "rolled_back" | "rollback_failed";
31359
+ data: "unknown" | "failed" | "pending" | "active" | "executing" | "rollback_executing" | "rolled_back" | "rollback_failed";
31360
31360
  driverParam: string;
31361
31361
  notNull: true;
31362
31362
  hasDefault: false;
@@ -31369,7 +31369,7 @@ declare const adsOperationSteps: drizzle_orm_sqlite_core.SQLiteTableWithColumns<
31369
31369
  generated: undefined;
31370
31370
  }, {}, {
31371
31371
  length: number | undefined;
31372
- $type: "unknown" | "failed" | "active" | "pending" | "executing" | "rollback_executing" | "rolled_back" | "rollback_failed";
31372
+ $type: "unknown" | "failed" | "pending" | "active" | "executing" | "rollback_executing" | "rolled_back" | "rollback_failed";
31373
31373
  }>;
31374
31374
  providerUpdatedAt: drizzle_orm_sqlite_core.SQLiteColumn<{
31375
31375
  name: "provider_updated_at";
@@ -32676,7 +32676,7 @@ declare const googleAdsConnections: drizzle_orm_sqlite_core.SQLiteTableWithColum
32676
32676
  tableName: "google_ads_connections";
32677
32677
  dataType: "string";
32678
32678
  columnType: "SQLiteText";
32679
- data: "unknown" | "enabled" | "suspended" | "closed" | "canceled" | "unspecified";
32679
+ data: "unknown" | "canceled" | "enabled" | "suspended" | "closed" | "unspecified";
32680
32680
  driverParam: string;
32681
32681
  notNull: false;
32682
32682
  hasDefault: false;
@@ -32689,7 +32689,7 @@ declare const googleAdsConnections: drizzle_orm_sqlite_core.SQLiteTableWithColum
32689
32689
  generated: undefined;
32690
32690
  }, {}, {
32691
32691
  length: number | undefined;
32692
- $type: "unknown" | "enabled" | "suspended" | "closed" | "canceled" | "unspecified";
32692
+ $type: "unknown" | "canceled" | "enabled" | "suspended" | "closed" | "unspecified";
32693
32693
  }>;
32694
32694
  scopes: drizzle_orm_sqlite_core.SQLiteColumn<{
32695
32695
  name: "scopes";
@@ -33636,7 +33636,7 @@ declare const googleAdsRawSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColu
33636
33636
  hidden: boolean;
33637
33637
  testAccount: boolean;
33638
33638
  level: number;
33639
- status: "unknown" | "enabled" | "suspended" | "closed" | "canceled" | "unspecified";
33639
+ status: "unknown" | "canceled" | "enabled" | "suspended" | "closed" | "unspecified";
33640
33640
  }[];
33641
33641
  totalAccessible: number;
33642
33642
  truncated: boolean;
@@ -33656,7 +33656,7 @@ declare const googleAdsRawSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColu
33656
33656
  id: string;
33657
33657
  resourceName: string;
33658
33658
  name: string;
33659
- status: "unknown" | "enabled" | "removed" | "paused";
33659
+ status: "unknown" | "enabled" | "paused" | "removed";
33660
33660
  advertisingChannelType: string | null;
33661
33661
  biddingStrategyType: string | null;
33662
33662
  }[];
@@ -33664,7 +33664,7 @@ declare const googleAdsRawSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColu
33664
33664
  id: string;
33665
33665
  resourceName: string;
33666
33666
  name: string;
33667
- status: "unknown" | "enabled" | "removed" | "hidden";
33667
+ status: "unknown" | "enabled" | "hidden" | "removed";
33668
33668
  category: string;
33669
33669
  origin: string;
33670
33670
  primaryForGoal: boolean;
@@ -33688,7 +33688,7 @@ declare const googleAdsRawSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColu
33688
33688
  }[];
33689
33689
  campaignGoalConfigurations: {
33690
33690
  campaignId: string;
33691
- goalConfigLevel: "customer" | "campaign";
33691
+ goalConfigLevel: "campaign" | "customer";
33692
33692
  customGoalId: string | null;
33693
33693
  }[];
33694
33694
  campaignConversionGoalsComplete?: boolean | undefined;
@@ -33739,7 +33739,7 @@ declare const googleAdsRawSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColu
33739
33739
  hidden: boolean;
33740
33740
  testAccount: boolean;
33741
33741
  level: number;
33742
- status: "unknown" | "enabled" | "suspended" | "closed" | "canceled" | "unspecified";
33742
+ status: "unknown" | "canceled" | "enabled" | "suspended" | "closed" | "unspecified";
33743
33743
  }[];
33744
33744
  totalAccessible: number;
33745
33745
  truncated: boolean;
@@ -33759,7 +33759,7 @@ declare const googleAdsRawSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColu
33759
33759
  id: string;
33760
33760
  resourceName: string;
33761
33761
  name: string;
33762
- status: "unknown" | "enabled" | "removed" | "paused";
33762
+ status: "unknown" | "enabled" | "paused" | "removed";
33763
33763
  advertisingChannelType: string | null;
33764
33764
  biddingStrategyType: string | null;
33765
33765
  }[];
@@ -33767,7 +33767,7 @@ declare const googleAdsRawSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColu
33767
33767
  id: string;
33768
33768
  resourceName: string;
33769
33769
  name: string;
33770
- status: "unknown" | "enabled" | "removed" | "hidden";
33770
+ status: "unknown" | "enabled" | "hidden" | "removed";
33771
33771
  category: string;
33772
33772
  origin: string;
33773
33773
  primaryForGoal: boolean;
@@ -33791,7 +33791,7 @@ declare const googleAdsRawSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColu
33791
33791
  }[];
33792
33792
  campaignGoalConfigurations: {
33793
33793
  campaignId: string;
33794
- goalConfigLevel: "customer" | "campaign";
33794
+ goalConfigLevel: "campaign" | "customer";
33795
33795
  customGoalId: string | null;
33796
33796
  }[];
33797
33797
  campaignConversionGoalsComplete?: boolean | undefined;
@@ -33948,7 +33948,7 @@ declare const gtmRawSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
33948
33948
  tableName: "gtm_raw_snapshots";
33949
33949
  dataType: "string";
33950
33950
  columnType: "SQLiteText";
33951
- data: "live" | "draft" | "accounts" | "container";
33951
+ data: "accounts" | "container" | "live" | "draft";
33952
33952
  driverParam: string;
33953
33953
  notNull: true;
33954
33954
  hasDefault: false;
@@ -33961,7 +33961,7 @@ declare const gtmRawSnapshots: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
33961
33961
  generated: undefined;
33962
33962
  }, {}, {
33963
33963
  length: number | undefined;
33964
- $type: "live" | "draft" | "accounts" | "container";
33964
+ $type: "accounts" | "container" | "live" | "draft";
33965
33965
  }>;
33966
33966
  accountId: drizzle_orm_sqlite_core.SQLiteColumn<{
33967
33967
  name: "account_id";
package/dist/index.js CHANGED
@@ -3,9 +3,9 @@ import {
3
3
  createGoogleMarketingCredentialStore,
4
4
  createGoogleMarketingRuntime,
5
5
  createServer
6
- } from "./chunk-COUM462G.js";
6
+ } from "./chunk-QYBHWA76.js";
7
7
  import "./chunk-WSWHLKKJ.js";
8
- import "./chunk-OES6PSQK.js";
8
+ import "./chunk-GL5HD74G.js";
9
9
  import "./chunk-IYMTQHPD.js";
10
10
  import "./chunk-ELCLGWUT.js";
11
11
  import "./chunk-XEBKF4N5.js";
@@ -15,8 +15,8 @@ import "./chunk-PLLDJUG2.js";
15
15
  import "./chunk-TSENU4GG.js";
16
16
  import "./chunk-FCYGY4FT.js";
17
17
  import "./chunk-4GEEYY3N.js";
18
- import "./chunk-BKOG3GEI.js";
19
- import "./chunk-WVCDUKJ2.js";
18
+ import "./chunk-RPTOVEZT.js";
19
+ import "./chunk-JI7FDO7I.js";
20
20
  import "./chunk-7OAXEMOF.js";
21
21
  import {
22
22
  loadConfig
@@ -7,7 +7,7 @@ import {
7
7
  import {
8
8
  buildSetupState,
9
9
  queueServeHandoff
10
- } from "./chunk-GEHRGKQT.js";
10
+ } from "./chunk-4UXTNBXS.js";
11
11
  import "./chunk-VJWDBFSL.js";
12
12
  import "./chunk-PLLDJUG2.js";
13
13
  import "./chunk-FCYGY4FT.js";
package/dist/mcp.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  } from "./chunk-BLZCNL3B.js";
4
4
  import {
5
5
  createCanonryMcpServer
6
- } from "./chunk-BKOG3GEI.js";
7
- import "./chunk-WVCDUKJ2.js";
6
+ } from "./chunk-RPTOVEZT.js";
7
+ import "./chunk-JI7FDO7I.js";
8
8
  import {
9
9
  createApiClient
10
10
  } from "./chunk-7OAXEMOF.js";
@@ -9,9 +9,9 @@ import {
9
9
  allowsKeylessFirstRunSetup,
10
10
  createServer,
11
11
  waitForServerRuntimeStartup
12
- } from "./chunk-COUM462G.js";
12
+ } from "./chunk-QYBHWA76.js";
13
13
  import "./chunk-WSWHLKKJ.js";
14
- import "./chunk-OES6PSQK.js";
14
+ import "./chunk-GL5HD74G.js";
15
15
  import "./chunk-IYMTQHPD.js";
16
16
  import "./chunk-ELCLGWUT.js";
17
17
  import "./chunk-XEBKF4N5.js";
@@ -35,8 +35,8 @@ import {
35
35
  setTelemetrySource,
36
36
  trackEvent
37
37
  } from "./chunk-4GEEYY3N.js";
38
- import "./chunk-BKOG3GEI.js";
39
- import "./chunk-WVCDUKJ2.js";
38
+ import "./chunk-RPTOVEZT.js";
39
+ import "./chunk-JI7FDO7I.js";
40
40
  import "./chunk-7OAXEMOF.js";
41
41
  import {
42
42
  getConfigDir,
@@ -7,12 +7,13 @@ import {
7
7
  loadAeroSystemPrompt,
8
8
  missingProviderMessage,
9
9
  resolveAeroModel,
10
- resolveAeroSkillDir,
11
10
  resolveConfiguredAgentProvider,
12
11
  resolveSessionProviderAndModel
13
- } from "./chunk-OES6PSQK.js";
12
+ } from "./chunk-GL5HD74G.js";
14
13
  import "./chunk-IYMTQHPD.js";
15
- import "./chunk-WVCDUKJ2.js";
14
+ import {
15
+ resolveAeroSkillDir
16
+ } from "./chunk-JI7FDO7I.js";
16
17
  import "./chunk-7OAXEMOF.js";
17
18
  import {
18
19
  AgentProviders,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonry/canonry",
3
- "version": "7.78.2",
3
+ "version": "7.79.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": [