@ainyc/canonry 4.27.2 → 4.29.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.
package/dist/cli.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  setTelemetrySource,
21
21
  showFirstRunNotice,
22
22
  trackEvent
23
- } from "./chunk-ICWFH4JA.js";
23
+ } from "./chunk-HONTKYY7.js";
24
24
  import {
25
25
  CliError,
26
26
  EXIT_SYSTEM_ERROR,
@@ -36,7 +36,7 @@ import {
36
36
  saveConfig,
37
37
  saveConfigPatch,
38
38
  usageError
39
- } from "./chunk-2FAEQ56I.js";
39
+ } from "./chunk-GB3QJURO.js";
40
40
  import {
41
41
  apiKeys,
42
42
  competitors,
@@ -49,7 +49,7 @@ import {
49
49
  queries,
50
50
  querySnapshots,
51
51
  runs
52
- } from "./chunk-NXXD6TX7.js";
52
+ } from "./chunk-UEV3HSRL.js";
53
53
  import {
54
54
  CcReleaseSyncStatuses,
55
55
  CheckScopes,
@@ -62,6 +62,7 @@ import {
62
62
  SkillsClients,
63
63
  TrafficEventKinds,
64
64
  determineAnswerMentioned,
65
+ discoveryBucketSchema,
65
66
  effectiveDomains,
66
67
  formatRunErrorOneLine,
67
68
  normalizeUrlPath,
@@ -69,7 +70,7 @@ import {
69
70
  providerQuotaPolicySchema,
70
71
  resolveProviderInput,
71
72
  skillsClientSchema
72
- } from "./chunk-HVW665A4.js";
73
+ } from "./chunk-RLLFB3M3.js";
73
74
 
74
75
  // src/cli.ts
75
76
  import { pathToFileURL } from "url";
@@ -621,7 +622,7 @@ function readStoredGroundingSources(rawResponse) {
621
622
  return result;
622
623
  }
623
624
  async function backfillInsightsCommand(project, opts) {
624
- const { IntelligenceService } = await import("./intelligence-service-Z6QIELKP.js");
625
+ const { IntelligenceService } = await import("./intelligence-service-O6KB6YAM.js");
625
626
  const config = loadConfig();
626
627
  const db = createClient(config.database);
627
628
  migrate(db);
@@ -1964,30 +1965,120 @@ var TERMINAL_DISCOVERY_STATUSES = /* @__PURE__ */ new Set([
1964
1965
  function getClient4() {
1965
1966
  return createApiClient();
1966
1967
  }
1967
- async function discoverRun(project, opts) {
1968
- const client = getClient4();
1968
+ function buildRunBody(opts, icpDescription) {
1969
1969
  const body = {};
1970
- if (opts.icp) body.icpDescription = opts.icp;
1970
+ if (icpDescription) body.icpDescription = icpDescription;
1971
1971
  if (opts.dedupThreshold !== void 0) body.dedupThreshold = opts.dedupThreshold;
1972
1972
  if (opts.maxProbes !== void 0) body.maxProbes = opts.maxProbes;
1973
- const start = await client.triggerDiscoveryRun(project, body);
1973
+ return body;
1974
+ }
1975
+ function resolveIcpAngles(opts) {
1976
+ const angles = (opts.icpAngles ?? []).map((a) => a.trim()).filter((a) => a.length > 0);
1977
+ if (angles.length > 0) return { angles, multiAngle: true };
1978
+ const icp = opts.icp?.trim();
1979
+ if (icp) return { angles: [icp], multiAngle: false };
1980
+ return { angles: [void 0], multiAngle: false };
1981
+ }
1982
+ function summarizeAngles(sessions) {
1983
+ return {
1984
+ angleCount: sessions.length,
1985
+ totalProbes: sessions.reduce((sum, s) => sum + (s.probeCount ?? 0), 0),
1986
+ totalCited: sessions.reduce((sum, s) => sum + (s.citedCount ?? 0), 0),
1987
+ totalWasted: sessions.reduce((sum, s) => sum + (s.wastedCount ?? 0), 0),
1988
+ totalAspirational: sessions.reduce((sum, s) => sum + (s.aspirationalCount ?? 0), 0)
1989
+ };
1990
+ }
1991
+ function errorMessage(err) {
1992
+ return err instanceof Error ? err.message : String(err);
1993
+ }
1994
+ async function discoverRun(project, opts) {
1995
+ const client = getClient4();
1996
+ const { angles, multiAngle } = resolveIcpAngles(opts);
1997
+ const runs2 = [];
1998
+ for (const angle of angles) {
1999
+ try {
2000
+ const start = await client.triggerDiscoveryRun(project, buildRunBody(opts, angle));
2001
+ runs2.push({ angle, start });
2002
+ } catch (err) {
2003
+ if (runs2.length > 0) {
2004
+ process.stderr.write(
2005
+ `
2006
+ Failed to start ${angle ? `angle "${angle}"` : "discovery run"}: ${errorMessage(err)}
2007
+ Sessions already started (recover with \`canonry discover show ${project} <id>\`):
2008
+ ` + runs2.map((r) => ` ${r.start.sessionId}`).join("\n") + "\n"
2009
+ );
2010
+ }
2011
+ throw err;
2012
+ }
2013
+ }
1974
2014
  if (!opts.wait) {
1975
2015
  if (opts.format === "json") {
1976
- console.log(JSON.stringify(start, null, 2));
2016
+ console.log(JSON.stringify(multiAngle ? runs2.map((r) => r.start) : runs2[0].start, null, 2));
1977
2017
  return;
1978
2018
  }
1979
- console.log(`Discovery run started: ${start.runId}`);
1980
- console.log(` Session: ${start.sessionId}`);
1981
- console.log(` Status: ${start.status}`);
1982
- console.log(` Tail: canonry discover show ${project} ${start.sessionId}`);
2019
+ for (const { angle, start } of runs2) {
2020
+ if (angle) console.log(`[${angle}]`);
2021
+ console.log(`Discovery run started: ${start.runId}`);
2022
+ console.log(` Session: ${start.sessionId}`);
2023
+ console.log(` Status: ${start.status}`);
2024
+ console.log(` Tail: canonry discover show ${project} ${start.sessionId}`);
2025
+ if (runs2.length > 1) console.log();
2026
+ }
1983
2027
  return;
1984
2028
  }
1985
- const final = await pollSession(client, project, start.sessionId);
1986
- if (opts.format === "json") {
1987
- console.log(JSON.stringify(final, null, 2));
1988
- return;
2029
+ const parallel = runs2.length > 1;
2030
+ if (parallel) process.stderr.write(`Waiting for ${runs2.length} discovery sessions...
2031
+ `);
2032
+ const settled = await Promise.allSettled(
2033
+ runs2.map((r) => pollSession(client, project, r.start.sessionId, parallel))
2034
+ );
2035
+ const results = [];
2036
+ const failures = [];
2037
+ settled.forEach((outcome, i) => {
2038
+ const run = runs2[i];
2039
+ if (outcome.status === "fulfilled") {
2040
+ results.push({ angle: run.angle, session: outcome.value });
2041
+ } else {
2042
+ failures.push({ angle: run.angle, sessionId: run.start.sessionId, reason: outcome.reason });
2043
+ }
2044
+ });
2045
+ if (results.length > 0) {
2046
+ if (opts.format === "json") {
2047
+ console.log(JSON.stringify(multiAngle ? results.map((r) => r.session) : results[0].session, null, 2));
2048
+ } else {
2049
+ for (const { angle, session } of results) {
2050
+ if (angle) console.log(`## ICP angle: ${angle}
2051
+ `);
2052
+ printSessionDetail(session);
2053
+ if (results.length > 1) console.log();
2054
+ }
2055
+ if (results.length > 1) {
2056
+ const summary = summarizeAngles(results.map((r) => r.session));
2057
+ console.log(`\u2500\u2500 Summary across ${summary.angleCount} angle(s) \u2500\u2500`);
2058
+ console.log(
2059
+ ` Probes: ${summary.totalProbes} Cited: ${summary.totalCited} Wasted: ${summary.totalWasted} Aspirational: ${summary.totalAspirational}`
2060
+ );
2061
+ console.log("\n Promote each session:");
2062
+ for (const { session } of results) {
2063
+ console.log(` canonry discover promote ${project} ${session.id}`);
2064
+ }
2065
+ }
2066
+ }
2067
+ }
2068
+ if (failures.length > 0) {
2069
+ if (!multiAngle) throw failures[0].reason;
2070
+ for (const f of failures) {
2071
+ process.stderr.write(
2072
+ `Discovery session ${f.sessionId}${f.angle ? ` ("${f.angle}")` : ""} did not complete: ${errorMessage(f.reason)}
2073
+ `
2074
+ );
2075
+ }
2076
+ throw new CliError({
2077
+ code: "DISCOVERY_INCOMPLETE",
2078
+ message: `${failures.length} of ${runs2.length} discovery session(s) did not complete`,
2079
+ details: { failed: failures.map((f) => f.sessionId) }
2080
+ });
1989
2081
  }
1990
- printSessionDetail(final);
1991
2082
  }
1992
2083
  async function discoverSeed(project, opts) {
1993
2084
  await discoverRun(project, opts);
@@ -2054,7 +2145,28 @@ async function discoverPromotePreview(project, sessionId, opts) {
2054
2145
  for (const c of preview.suggestedCompetitors) console.log(` - ${c.domain} (${c.hits} hits)`);
2055
2146
  }
2056
2147
  console.log(`
2057
- (PR 2 will add \`canonry discover promote\` to actually merge these into the project.)`);
2148
+ Run \`canonry discover promote ${project} ${sessionId}\` to merge cited + aspirational queries.`);
2149
+ console.log(" Add `--bucket wasted-surface` only when off-ICP competitor gaps should be tracked.");
2150
+ }
2151
+ async function discoverPromote(project, sessionId, opts) {
2152
+ const client = getClient4();
2153
+ const body = {};
2154
+ if (opts.buckets && opts.buckets.length > 0) body.buckets = opts.buckets;
2155
+ if (opts.includeCompetitors === false) body.includeCompetitors = false;
2156
+ const result = await client.promoteDiscovery(project, sessionId, body);
2157
+ if (opts.format === "json") {
2158
+ console.log(JSON.stringify(result, null, 2));
2159
+ return;
2160
+ }
2161
+ const { promoted, skipped } = result;
2162
+ console.log(`Promoted discovery session ${sessionId} into "${project}":`);
2163
+ console.log(` Queries: ${promoted.queries.length} added, ${skipped.queries.length} already tracked`);
2164
+ for (const q of promoted.queries) console.log(` + ${q}`);
2165
+ console.log(` Competitors: ${promoted.competitors.length} added, ${skipped.competitors.length} already tracked`);
2166
+ for (const c of promoted.competitors) console.log(` + ${c}`);
2167
+ if (promoted.queries.length === 0 && promoted.competitors.length === 0) {
2168
+ console.log(` Nothing new \u2014 the project's basket already covers this session.`);
2169
+ }
2058
2170
  }
2059
2171
  function printSessionDetail(session) {
2060
2172
  console.log(`Discovery session: ${session.id}`);
@@ -2088,8 +2200,8 @@ function printSessionDetail(session) {
2088
2200
  }
2089
2201
  var POLL_INTERVAL_MS = 3e3;
2090
2202
  var POLL_TIMEOUT_MS = 15 * 60 * 1e3;
2091
- async function pollSession(client, project, sessionId) {
2092
- process.stderr.write(`Waiting for discovery session ${sessionId}`);
2203
+ async function pollSession(client, project, sessionId, quiet = false) {
2204
+ if (!quiet) process.stderr.write(`Waiting for discovery session ${sessionId}`);
2093
2205
  const deadline = Date.now() + POLL_TIMEOUT_MS;
2094
2206
  for (; ; ) {
2095
2207
  await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
@@ -2100,9 +2212,9 @@ async function pollSession(client, project, sessionId) {
2100
2212
  });
2101
2213
  }
2102
2214
  const session = await client.getDiscoverySession(project, sessionId);
2103
- process.stderr.write(".");
2215
+ if (!quiet) process.stderr.write(".");
2104
2216
  if (TERMINAL_DISCOVERY_STATUSES.has(session.status)) {
2105
- process.stderr.write("\n");
2217
+ if (!quiet) process.stderr.write("\n");
2106
2218
  return session;
2107
2219
  }
2108
2220
  }
@@ -2122,25 +2234,54 @@ Usage: ${usage}`, {
2122
2234
  }
2123
2235
  return parsed;
2124
2236
  }
2237
+ function parseBucketsOption(values, usage) {
2238
+ const raw = getStringArray(values, "bucket");
2239
+ if (!raw || raw.length === 0) return void 0;
2240
+ const expanded = raw.flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
2241
+ if (expanded.length === 0) {
2242
+ throw usageError(
2243
+ `Error: --bucket must include at least one value (valid: cited, aspirational, wasted-surface)
2244
+ Usage: ${usage}`,
2245
+ {
2246
+ message: "--bucket must include at least one value",
2247
+ details: { command: "discover.promote", usage, option: "bucket", value: raw }
2248
+ }
2249
+ );
2250
+ }
2251
+ const buckets = [];
2252
+ for (const value of expanded) {
2253
+ const parsed = discoveryBucketSchema.safeParse(value);
2254
+ if (!parsed.success) {
2255
+ throw usageError(
2256
+ `Error: invalid --bucket value "${value}" (valid: cited, aspirational, wasted-surface)
2257
+ Usage: ${usage}`,
2258
+ {
2259
+ message: `invalid --bucket value "${value}"`,
2260
+ details: { command: "discover.promote", usage, option: "bucket", value }
2261
+ }
2262
+ );
2263
+ }
2264
+ buckets.push(parsed.data);
2265
+ }
2266
+ return buckets;
2267
+ }
2125
2268
  var DISCOVER_CLI_COMMANDS = [
2126
2269
  {
2127
2270
  path: ["discover", "run"],
2128
- usage: 'canonry discover run <project> [--icp "..."] [--dedup-threshold 0.85] [--max-probes 100] [--wait] [--format json]',
2271
+ usage: 'canonry discover run <project> [--icp "..."] [--icp-angle "..."] [--dedup-threshold 0.85] [--max-probes 100] [--wait] [--format json]',
2129
2272
  options: {
2130
2273
  icp: stringOption(),
2274
+ "icp-angle": multiStringOption(),
2131
2275
  "dedup-threshold": stringOption(),
2132
2276
  "max-probes": stringOption(),
2133
2277
  wait: { type: "boolean", default: false }
2134
2278
  },
2135
2279
  run: async (input) => {
2136
- const project = requireProject(
2137
- input,
2138
- "discover.run",
2139
- 'canonry discover run <project> [--icp "..."] [--wait] [--format json]'
2140
- );
2141
- const usage = 'canonry discover run <project> [--icp "..."] [--dedup-threshold 0.85] [--max-probes 100] [--wait] [--format json]';
2280
+ const usage = 'canonry discover run <project> [--icp "..."] [--icp-angle "..."] [--dedup-threshold 0.85] [--max-probes 100] [--wait] [--format json]';
2281
+ const project = requireProject(input, "discover.run", usage);
2142
2282
  await discoverRun(project, {
2143
2283
  icp: getString(input.values, "icp"),
2284
+ icpAngles: getStringArray(input.values, "icp-angle"),
2144
2285
  dedupThreshold: parseFloatOption(input.values, "dedup-threshold", usage),
2145
2286
  maxProbes: parseIntegerOption(input, "max-probes", {
2146
2287
  command: "discover.run",
@@ -2154,22 +2295,20 @@ var DISCOVER_CLI_COMMANDS = [
2154
2295
  },
2155
2296
  {
2156
2297
  path: ["discover", "seed"],
2157
- usage: 'canonry discover seed <project> [--icp "..."] [--dedup-threshold 0.85] [--max-probes 100] [--wait] [--format json]',
2298
+ usage: 'canonry discover seed <project> [--icp "..."] [--icp-angle "..."] [--dedup-threshold 0.85] [--max-probes 100] [--wait] [--format json]',
2158
2299
  options: {
2159
2300
  icp: stringOption(),
2301
+ "icp-angle": multiStringOption(),
2160
2302
  "dedup-threshold": stringOption(),
2161
2303
  "max-probes": stringOption(),
2162
2304
  wait: { type: "boolean", default: false }
2163
2305
  },
2164
2306
  run: async (input) => {
2165
- const project = requireProject(
2166
- input,
2167
- "discover.seed",
2168
- 'canonry discover seed <project> [--icp "..."] [--wait] [--format json]'
2169
- );
2170
- const usage = 'canonry discover seed <project> [--icp "..."] [--dedup-threshold 0.85] [--max-probes 100] [--wait] [--format json]';
2307
+ const usage = 'canonry discover seed <project> [--icp "..."] [--icp-angle "..."] [--dedup-threshold 0.85] [--max-probes 100] [--wait] [--format json]';
2308
+ const project = requireProject(input, "discover.seed", usage);
2171
2309
  await discoverSeed(project, {
2172
2310
  icp: getString(input.values, "icp"),
2311
+ icpAngles: getStringArray(input.values, "icp-angle"),
2173
2312
  dedupThreshold: parseFloatOption(input.values, "dedup-threshold", usage),
2174
2313
  maxProbes: parseIntegerOption(input, "max-probes", {
2175
2314
  command: "discover.seed",
@@ -2237,6 +2376,28 @@ var DISCOVER_CLI_COMMANDS = [
2237
2376
  await discoverShow(project, sessionId, { format: input.format });
2238
2377
  }
2239
2378
  },
2379
+ {
2380
+ path: ["discover", "promote"],
2381
+ usage: "canonry discover promote <project> <session-id> [--bucket cited,aspirational,wasted-surface] [--no-competitors] [--format json]",
2382
+ options: {
2383
+ bucket: multiStringOption(),
2384
+ "no-competitors": { type: "boolean", default: false }
2385
+ },
2386
+ run: async (input) => {
2387
+ const usage = "canonry discover promote <project> <session-id> [--bucket cited,aspirational,wasted-surface] [--no-competitors] [--format json]";
2388
+ const project = requireProject(input, "discover.promote", usage);
2389
+ const sessionId = requirePositional(input, 1, {
2390
+ command: "discover.promote",
2391
+ usage,
2392
+ message: "session ID is required"
2393
+ });
2394
+ await discoverPromote(project, sessionId, {
2395
+ buckets: parseBucketsOption(input.values, usage),
2396
+ includeCompetitors: !getBoolean(input.values, "no-competitors"),
2397
+ format: input.format
2398
+ });
2399
+ }
2400
+ },
2240
2401
  {
2241
2402
  path: ["discover", "promote", "preview"],
2242
2403
  usage: "canonry discover promote preview <project> <session-id> [--format json]",
@@ -3204,11 +3365,11 @@ async function trafficBackfill(project, opts) {
3204
3365
  console.log(`Inspect rebuilt rollups: canonry traffic events ${project} --source ${opts.source} --since-minutes ${submitted.daysApplied * 24 * 60}`);
3205
3366
  return;
3206
3367
  }
3207
- const errorMessage = final.error?.message ?? null;
3368
+ const errorMessage2 = final.error?.message ?? null;
3208
3369
  throw new CliError({
3209
3370
  code: "TRAFFIC_BACKFILL_FAILED",
3210
- message: errorMessage ?? "backfill run did not complete successfully",
3211
- displayMessage: `Error: backfill run ${final.id} ${final.status}${errorMessage ? ` \u2014 ${errorMessage}` : ""}`,
3371
+ message: errorMessage2 ?? "backfill run did not complete successfully",
3372
+ displayMessage: `Error: backfill run ${final.id} ${final.status}${errorMessage2 ? ` \u2014 ${errorMessage2}` : ""}`,
3212
3373
  details: { project, runId: final.id, status: final.status }
3213
3374
  });
3214
3375
  }
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-ICWFH4JA.js";
3
+ } from "./chunk-HONTKYY7.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-2FAEQ56I.js";
7
- import "./chunk-NXXD6TX7.js";
8
- import "./chunk-HVW665A4.js";
6
+ } from "./chunk-GB3QJURO.js";
7
+ import "./chunk-UEV3HSRL.js";
8
+ import "./chunk-RLLFB3M3.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-NXXD6TX7.js";
4
- import "./chunk-HVW665A4.js";
3
+ } from "./chunk-UEV3HSRL.js";
4
+ import "./chunk-RLLFB3M3.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  CliError,
3
3
  canonryMcpTools,
4
4
  createApiClient
5
- } from "./chunk-2FAEQ56I.js";
6
- import "./chunk-HVW665A4.js";
5
+ } from "./chunk-GB3QJURO.js";
6
+ import "./chunk-RLLFB3M3.js";
7
7
 
8
8
  // src/mcp/cli.ts
9
9
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.27.2",
3
+ "version": "4.29.0",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -61,21 +61,21 @@
61
61
  "tsx": "^4.19.0",
62
62
  "@ainyc/canonry-api-routes": "0.0.0",
63
63
  "@ainyc/canonry-contracts": "0.0.0",
64
- "@ainyc/canonry-config": "0.0.0",
65
- "@ainyc/canonry-intelligence": "0.0.0",
66
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
67
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
68
64
  "@ainyc/canonry-db": "0.0.0",
65
+ "@ainyc/canonry-intelligence": "0.0.0",
69
66
  "@ainyc/canonry-integration-bing": "0.0.0",
70
- "@ainyc/canonry-integration-traffic": "0.0.0",
67
+ "@ainyc/canonry-config": "0.0.0",
68
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
69
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
71
70
  "@ainyc/canonry-integration-google": "0.0.0",
72
- "@ainyc/canonry-integration-wordpress": "0.0.0",
71
+ "@ainyc/canonry-integration-traffic": "0.0.0",
73
72
  "@ainyc/canonry-provider-cdp": "0.0.0",
74
73
  "@ainyc/canonry-provider-claude": "0.0.0",
75
- "@ainyc/canonry-provider-gemini": "0.0.0",
76
74
  "@ainyc/canonry-provider-local": "0.0.0",
77
- "@ainyc/canonry-provider-perplexity": "0.0.0",
78
- "@ainyc/canonry-provider-openai": "0.0.0"
75
+ "@ainyc/canonry-provider-gemini": "0.0.0",
76
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
77
+ "@ainyc/canonry-provider-openai": "0.0.0",
78
+ "@ainyc/canonry-provider-perplexity": "0.0.0"
79
79
  },
80
80
  "scripts": {
81
81
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",