@ainyc/canonry 2.0.0 → 2.2.3

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.
@@ -16,6 +16,7 @@ import { drizzle } from "drizzle-orm/better-sqlite3";
16
16
  // ../db/src/schema.ts
17
17
  var schema_exports = {};
18
18
  __export(schema_exports, {
19
+ agentMemory: () => agentMemory,
19
20
  agentSessions: () => agentSessions,
20
21
  apiKeys: () => apiKeys,
21
22
  auditLog: () => auditLog,
@@ -425,6 +426,18 @@ var agentSessions = sqliteTable("agent_sessions", {
425
426
  index("idx_agent_sessions_project").on(table.projectId),
426
427
  index("idx_agent_sessions_updated").on(table.updatedAt)
427
428
  ]);
429
+ var agentMemory = sqliteTable("agent_memory", {
430
+ id: text("id").primaryKey(),
431
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
432
+ key: text("key").notNull(),
433
+ value: text("value").notNull(),
434
+ source: text("source").notNull(),
435
+ createdAt: text("created_at").notNull(),
436
+ updatedAt: text("updated_at").notNull()
437
+ }, (table) => [
438
+ uniqueIndex("uniq_agent_memory_project_key").on(table.projectId, table.key),
439
+ index("idx_agent_memory_project_updated").on(table.projectId, table.updatedAt)
440
+ ]);
428
441
 
429
442
  // ../db/src/client.ts
430
443
  function createClient(databasePath) {
@@ -894,7 +907,21 @@ var MIGRATIONS = [
894
907
  // registry no longer recognizes 'anthropic'/'google'. Safe to re-run: the
895
908
  // UPDATE is a no-op once the rename has been applied.
896
909
  `UPDATE agent_sessions SET model_provider = 'claude' WHERE model_provider = 'anthropic'`,
897
- `UPDATE agent_sessions SET model_provider = 'gemini' WHERE model_provider = 'google'`
910
+ `UPDATE agent_sessions SET model_provider = 'gemini' WHERE model_provider = 'google'`,
911
+ // v40: Aero durable memory — project-scoped notes + compaction summaries.
912
+ `CREATE TABLE IF NOT EXISTS agent_memory (
913
+ id TEXT PRIMARY KEY,
914
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
915
+ key TEXT NOT NULL,
916
+ value TEXT NOT NULL,
917
+ source TEXT NOT NULL,
918
+ created_at TEXT NOT NULL,
919
+ updated_at TEXT NOT NULL
920
+ )`,
921
+ `CREATE UNIQUE INDEX IF NOT EXISTS uniq_agent_memory_project_key
922
+ ON agent_memory(project_id, key)`,
923
+ `CREATE INDEX IF NOT EXISTS idx_agent_memory_project_updated
924
+ ON agent_memory(project_id, updated_at)`
898
925
  ];
899
926
  function isDuplicateColumnError(err) {
900
927
  if (!(err instanceof Error)) return false;
@@ -1425,6 +1452,7 @@ export {
1425
1452
  insights,
1426
1453
  healthSnapshots,
1427
1454
  agentSessions,
1455
+ agentMemory,
1428
1456
  createClient,
1429
1457
  parseJsonColumn,
1430
1458
  extractLegacyCredentials,
package/dist/cli.js CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  getConfigDir,
18
18
  getConfigPath,
19
19
  getOrCreateAnonymousId,
20
+ isEndpointMissing,
20
21
  isFirstRun,
21
22
  isTelemetryEnabled,
22
23
  listAgentProviders,
@@ -35,7 +36,7 @@ import {
35
36
  showFirstRunNotice,
36
37
  trackEvent,
37
38
  usageError
38
- } from "./chunk-YZKLIUH4.js";
39
+ } from "./chunk-MXUOJWNL.js";
39
40
  import {
40
41
  apiKeys,
41
42
  competitors,
@@ -45,7 +46,7 @@ import {
45
46
  projects,
46
47
  querySnapshots,
47
48
  runs
48
- } from "./chunk-GH6WGN5B.js";
49
+ } from "./chunk-TAII35VC.js";
49
50
 
50
51
  // src/cli.ts
51
52
  import { pathToFileURL } from "url";
@@ -292,7 +293,7 @@ async function backfillAnswerVisibilityCommand(opts) {
292
293
  console.log(` Errors: ${providerErrors}`);
293
294
  }
294
295
  async function backfillInsightsCommand(project, opts) {
295
- const { IntelligenceService } = await import("./intelligence-service-LHWXONQJ.js");
296
+ const { IntelligenceService } = await import("./intelligence-service-C5LAYDFM.js");
296
297
  const config = loadConfig();
297
298
  const db = createClient(config.database);
298
299
  migrate(db);
@@ -3327,14 +3328,8 @@ async function exportProject(project, opts) {
3327
3328
  const client = createApiClient();
3328
3329
  const data = await client.getExport(project);
3329
3330
  if (opts.includeResults) {
3330
- try {
3331
- const runs2 = await client.listRuns(project);
3332
- if (runs2.length > 0) {
3333
- const latestRun = await client.getRun(runs2[runs2.length - 1].id);
3334
- data.results = latestRun;
3335
- }
3336
- } catch {
3337
- }
3331
+ const results = await loadLatestRunForExport(client, project);
3332
+ if (results) data.results = results;
3338
3333
  }
3339
3334
  if (opts.format === "json") {
3340
3335
  console.log(JSON.stringify(data, null, 2));
@@ -3342,6 +3337,20 @@ async function exportProject(project, opts) {
3342
3337
  }
3343
3338
  console.log(stringify(data));
3344
3339
  }
3340
+ async function loadLatestRunForExport(client, project) {
3341
+ try {
3342
+ const latest = await client.getLatestRun(project);
3343
+ return latest.run ?? null;
3344
+ } catch (err) {
3345
+ if (!isEndpointMissing(err)) throw err;
3346
+ }
3347
+ const runs2 = await client.listRuns(project);
3348
+ if (runs2.length === 0) return null;
3349
+ const latestRun = runs2.reduce(
3350
+ (current, candidate) => candidate.createdAt > current.createdAt ? candidate : current
3351
+ );
3352
+ return client.getRun(latestRun.id);
3353
+ }
3345
3354
 
3346
3355
  // src/commands/history.ts
3347
3356
  function getClient10() {
@@ -3389,13 +3398,19 @@ function getClient11() {
3389
3398
  async function showStatus(project, format) {
3390
3399
  const client = getClient11();
3391
3400
  const projectData = await client.getProject(project);
3392
- let runs2 = [];
3393
- try {
3394
- runs2 = await client.listRuns(project);
3395
- } catch {
3396
- }
3401
+ const latest = await getLatestRunSummary(client, project);
3397
3402
  if (format === "json") {
3398
- console.log(JSON.stringify({ project: projectData, runs: runs2 }, null, 2));
3403
+ let runs2 = [];
3404
+ try {
3405
+ runs2 = await client.listRuns(project);
3406
+ } catch {
3407
+ }
3408
+ console.log(JSON.stringify({
3409
+ project: projectData,
3410
+ runs: runs2,
3411
+ latestRun: latest.run,
3412
+ totalRuns: latest.totalRuns
3413
+ }, null, 2));
3399
3414
  return;
3400
3415
  }
3401
3416
  console.log(`Status: ${projectData.displayName ?? projectData.name} (${projectData.name})
@@ -3403,24 +3418,39 @@ async function showStatus(project, format) {
3403
3418
  console.log(` Domain: ${projectData.canonicalDomain}`);
3404
3419
  console.log(` Country: ${projectData.country}`);
3405
3420
  console.log(` Language: ${projectData.language}`);
3406
- if (runs2.length > 0) {
3407
- const latest = runs2.reduce(
3408
- (current, candidate) => candidate.createdAt > current.createdAt ? candidate : current
3409
- );
3421
+ if (latest.run) {
3410
3422
  console.log(`
3411
3423
  Latest run:`);
3412
- console.log(` ID: ${latest.id}`);
3413
- console.log(` Status: ${latest.status}`);
3414
- console.log(` Created: ${latest.createdAt}`);
3415
- if (latest.finishedAt) {
3416
- console.log(` Finished: ${latest.finishedAt}`);
3424
+ console.log(` ID: ${latest.run.id}`);
3425
+ console.log(` Status: ${latest.run.status}`);
3426
+ console.log(` Created: ${latest.run.createdAt}`);
3427
+ if (latest.run.finishedAt) {
3428
+ console.log(` Finished: ${latest.run.finishedAt}`);
3417
3429
  }
3418
3430
  console.log(`
3419
- Total runs: ${runs2.length}`);
3431
+ Total runs: ${latest.totalRuns}`);
3420
3432
  } else {
3421
3433
  console.log('\n No runs yet. Use "canonry run" to trigger one.');
3422
3434
  }
3423
3435
  }
3436
+ async function getLatestRunSummary(client, project) {
3437
+ try {
3438
+ return await client.getLatestRun(project);
3439
+ } catch (err) {
3440
+ if (!isEndpointMissing(err)) throw err;
3441
+ const runs2 = await client.listRuns(project);
3442
+ if (runs2.length === 0) {
3443
+ return { totalRuns: 0, run: null };
3444
+ }
3445
+ const latestRun = runs2.reduce(
3446
+ (current, candidate) => candidate.createdAt > current.createdAt ? candidate : current
3447
+ );
3448
+ return {
3449
+ totalRuns: runs2.length,
3450
+ run: latestRun
3451
+ };
3452
+ }
3453
+ }
3424
3454
 
3425
3455
  // src/cli-commands/operator.ts
3426
3456
  var OPERATOR_CLI_COMMANDS = [
@@ -7440,6 +7470,73 @@ async function agentTranscriptReset(opts) {
7440
7470
  }
7441
7471
  }
7442
7472
 
7473
+ // src/commands/agent-memory.ts
7474
+ function toFormat2(raw) {
7475
+ return raw === "json" ? "json" : "text";
7476
+ }
7477
+ async function agentMemoryList(opts) {
7478
+ const format = toFormat2(opts.format);
7479
+ try {
7480
+ const client = createApiClient();
7481
+ const result = await client.listAgentMemory(opts.project);
7482
+ if (format === "json") {
7483
+ console.log(JSON.stringify(result, null, 2));
7484
+ return;
7485
+ }
7486
+ if (result.entries.length === 0) {
7487
+ console.log(`No Aero memory notes for "${opts.project}".`);
7488
+ return;
7489
+ }
7490
+ console.log(`Aero memory for ${opts.project} \u2014 ${result.entries.length} note(s)
7491
+ `);
7492
+ for (const entry of result.entries) {
7493
+ console.log(`[${entry.source}] ${entry.key} (updated ${entry.updatedAt})`);
7494
+ console.log(` ${entry.value.replace(/\n/g, "\n ")}`);
7495
+ console.log();
7496
+ }
7497
+ } catch (err) {
7498
+ printCliError(err, format);
7499
+ process.exitCode = err instanceof CliError ? err.exitCode : 2;
7500
+ }
7501
+ }
7502
+ async function agentMemorySet(opts) {
7503
+ const format = toFormat2(opts.format);
7504
+ try {
7505
+ const client = createApiClient();
7506
+ const result = await client.setAgentMemory(opts.project, {
7507
+ key: opts.key,
7508
+ value: opts.value
7509
+ });
7510
+ if (format === "json") {
7511
+ console.log(JSON.stringify(result, null, 2));
7512
+ return;
7513
+ }
7514
+ console.log(`Stored note "${result.entry.key}" for "${opts.project}" (source=${result.entry.source}).`);
7515
+ } catch (err) {
7516
+ printCliError(err, format);
7517
+ process.exitCode = err instanceof CliError ? err.exitCode : 2;
7518
+ }
7519
+ }
7520
+ async function agentMemoryForget(opts) {
7521
+ const format = toFormat2(opts.format);
7522
+ try {
7523
+ const client = createApiClient();
7524
+ const result = await client.forgetAgentMemory(opts.project, opts.key);
7525
+ if (format === "json") {
7526
+ console.log(JSON.stringify(result, null, 2));
7527
+ return;
7528
+ }
7529
+ if (result.status === "forgotten") {
7530
+ console.log(`Forgot note "${opts.key}" for "${opts.project}".`);
7531
+ } else {
7532
+ console.log(`No note with key "${opts.key}" for "${opts.project}".`);
7533
+ }
7534
+ } catch (err) {
7535
+ printCliError(err, format);
7536
+ process.exitCode = err instanceof CliError ? err.exitCode : 2;
7537
+ }
7538
+ }
7539
+
7443
7540
  // src/cli-commands/agent.ts
7444
7541
  var AGENT_ASK_SCOPES = ["all", "read-only"];
7445
7542
  var AGENT_CLI_COMMANDS = [
@@ -7452,27 +7549,48 @@ var AGENT_CLI_COMMANDS = [
7452
7549
  scope: stringOption()
7453
7550
  },
7454
7551
  run: async (input) => {
7455
- const [project, ...rest] = input.positionals;
7456
- if (!project || rest.length === 0) {
7457
- console.error('Usage: canonry agent ask <project> "<prompt>"');
7458
- process.exitCode = 1;
7459
- return;
7552
+ const usage = `canonry agent ask <project> "<prompt>" [--provider ${listAgentProviders().join("|")}] [--model <id>] [--scope all|read-only] [--format json]`;
7553
+ const project = requireProject(input, "agent.ask", usage);
7554
+ const prompt2 = input.positionals.slice(1).join(" ").trim();
7555
+ if (!prompt2) {
7556
+ throw usageError(`Error: prompt is required
7557
+ Usage: ${usage}`, {
7558
+ message: "prompt is required",
7559
+ details: {
7560
+ command: "agent.ask",
7561
+ usage
7562
+ }
7563
+ });
7460
7564
  }
7461
7565
  const providerInput = getString(input.values, "provider");
7462
7566
  if (providerInput && !coerceAgentProvider(providerInput)) {
7463
- console.error(`--provider must be one of: ${listAgentProviders().join(", ")}`);
7464
- process.exitCode = 1;
7465
- return;
7567
+ throw usageError(`Error: --provider must be one of: ${listAgentProviders().join(", ")}
7568
+ Usage: ${usage}`, {
7569
+ message: `--provider must be one of: ${listAgentProviders().join(", ")}`,
7570
+ details: {
7571
+ command: "agent.ask",
7572
+ usage,
7573
+ provider: providerInput,
7574
+ validProviders: listAgentProviders()
7575
+ }
7576
+ });
7466
7577
  }
7467
7578
  const scopeInput = getString(input.values, "scope");
7468
7579
  if (scopeInput && !AGENT_ASK_SCOPES.includes(scopeInput)) {
7469
- console.error(`--scope must be one of: ${AGENT_ASK_SCOPES.join(", ")}`);
7470
- process.exitCode = 1;
7471
- return;
7580
+ throw usageError(`Error: --scope must be one of: ${AGENT_ASK_SCOPES.join(", ")}
7581
+ Usage: ${usage}`, {
7582
+ message: `--scope must be one of: ${AGENT_ASK_SCOPES.join(", ")}`,
7583
+ details: {
7584
+ command: "agent.ask",
7585
+ usage,
7586
+ scope: scopeInput,
7587
+ validScopes: AGENT_ASK_SCOPES
7588
+ }
7589
+ });
7472
7590
  }
7473
7591
  await agentAsk({
7474
7592
  project,
7475
- prompt: rest.join(" "),
7593
+ prompt: prompt2,
7476
7594
  provider: coerceAgentProvider(providerInput),
7477
7595
  modelId: getString(input.values, "model"),
7478
7596
  scope: scopeInput,
@@ -7485,12 +7603,7 @@ var AGENT_CLI_COMMANDS = [
7485
7603
  usage: "canonry agent providers <project> [--format json]",
7486
7604
  options: {},
7487
7605
  run: async (input) => {
7488
- const project = input.positionals[0];
7489
- if (!project) {
7490
- console.error("Usage: canonry agent providers <project>");
7491
- process.exitCode = 1;
7492
- return;
7493
- }
7606
+ const project = requireProject(input, "agent.providers", "canonry agent providers <project> [--format json]");
7494
7607
  await agentProviders({ project, format: input.format });
7495
7608
  }
7496
7609
  },
@@ -7501,18 +7614,16 @@ var AGENT_CLI_COMMANDS = [
7501
7614
  url: stringOption()
7502
7615
  },
7503
7616
  run: async (input) => {
7504
- const project = input.positionals[0];
7505
- if (!project) {
7506
- console.error("Usage: canonry agent attach <project> --url <webhook-url>");
7507
- process.exitCode = 1;
7508
- return;
7509
- }
7510
- const url = getString(input.values, "url");
7511
- if (!url) {
7512
- console.error("Missing required --url flag. Specify the agent webhook URL to attach.");
7513
- process.exitCode = 1;
7514
- return;
7515
- }
7617
+ const usage = "canonry agent attach <project> --url <webhook-url> [--format json]";
7618
+ const project = requireProject(input, "agent.attach", usage);
7619
+ const url = requireStringOption(input, "url", {
7620
+ command: "agent.attach",
7621
+ usage,
7622
+ message: "--url is required",
7623
+ details: {
7624
+ flag: "url"
7625
+ }
7626
+ });
7516
7627
  await agentAttach({ project, url, format: input.format });
7517
7628
  }
7518
7629
  },
@@ -7521,12 +7632,7 @@ var AGENT_CLI_COMMANDS = [
7521
7632
  usage: "canonry agent detach <project> [--format json]",
7522
7633
  options: {},
7523
7634
  run: async (input) => {
7524
- const project = input.positionals[0];
7525
- if (!project) {
7526
- console.error("Usage: canonry agent detach <project>");
7527
- process.exitCode = 1;
7528
- return;
7529
- }
7635
+ const project = requireProject(input, "agent.detach", "canonry agent detach <project> [--format json]");
7530
7636
  await agentDetach({ project, format: input.format });
7531
7637
  }
7532
7638
  },
@@ -7535,12 +7641,7 @@ var AGENT_CLI_COMMANDS = [
7535
7641
  usage: "canonry agent transcript <project> [--format json]",
7536
7642
  options: {},
7537
7643
  run: async (input) => {
7538
- const project = input.positionals[0];
7539
- if (!project) {
7540
- console.error("Usage: canonry agent transcript <project>");
7541
- process.exitCode = 1;
7542
- return;
7543
- }
7644
+ const project = requireProject(input, "agent.transcript", "canonry agent transcript <project> [--format json]");
7544
7645
  await agentTranscript({ project, format: input.format });
7545
7646
  }
7546
7647
  },
@@ -7549,14 +7650,68 @@ var AGENT_CLI_COMMANDS = [
7549
7650
  usage: "canonry agent reset <project> [--format json]",
7550
7651
  options: {},
7551
7652
  run: async (input) => {
7552
- const project = input.positionals[0];
7553
- if (!project) {
7554
- console.error("Usage: canonry agent reset <project>");
7555
- process.exitCode = 1;
7556
- return;
7557
- }
7653
+ const project = requireProject(input, "agent.reset", "canonry agent reset <project> [--format json]");
7558
7654
  await agentTranscriptReset({ project, format: input.format });
7559
7655
  }
7656
+ },
7657
+ {
7658
+ path: ["agent", "memory", "list"],
7659
+ usage: "canonry agent memory list <project> [--format json]",
7660
+ options: {},
7661
+ run: async (input) => {
7662
+ const usage = "canonry agent memory list <project> [--format json]";
7663
+ const project = requireProject(input, "agent.memory.list", usage);
7664
+ await agentMemoryList({ project, format: input.format });
7665
+ }
7666
+ },
7667
+ {
7668
+ path: ["agent", "memory", "set"],
7669
+ usage: "canonry agent memory set <project> --key <k> --value <v> [--format json]",
7670
+ options: {
7671
+ key: stringOption(),
7672
+ value: stringOption()
7673
+ },
7674
+ run: async (input) => {
7675
+ const usage = "canonry agent memory set <project> --key <k> --value <v> [--format json]";
7676
+ const project = requireProject(input, "agent.memory.set", usage);
7677
+ const key = requireStringOption(input, "key", {
7678
+ command: "agent.memory.set",
7679
+ usage,
7680
+ message: "--key is required",
7681
+ details: {
7682
+ flag: "key"
7683
+ }
7684
+ });
7685
+ const value = requireStringOption(input, "value", {
7686
+ command: "agent.memory.set",
7687
+ usage,
7688
+ message: "--value is required",
7689
+ details: {
7690
+ flag: "value"
7691
+ }
7692
+ });
7693
+ await agentMemorySet({ project, key, value, format: input.format });
7694
+ }
7695
+ },
7696
+ {
7697
+ path: ["agent", "memory", "forget"],
7698
+ usage: "canonry agent memory forget <project> --key <k> [--format json]",
7699
+ options: {
7700
+ key: stringOption()
7701
+ },
7702
+ run: async (input) => {
7703
+ const usage = "canonry agent memory forget <project> --key <k> [--format json]";
7704
+ const project = requireProject(input, "agent.memory.forget", usage);
7705
+ const key = requireStringOption(input, "key", {
7706
+ command: "agent.memory.forget",
7707
+ usage,
7708
+ message: "--key is required",
7709
+ details: {
7710
+ flag: "key"
7711
+ }
7712
+ });
7713
+ await agentMemoryForget({ project, key, format: input.format });
7714
+ }
7560
7715
  }
7561
7716
  ];
7562
7717
 
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createServer,
3
3
  loadConfig
4
- } from "./chunk-YZKLIUH4.js";
5
- import "./chunk-GH6WGN5B.js";
4
+ } from "./chunk-MXUOJWNL.js";
5
+ import "./chunk-TAII35VC.js";
6
6
  export {
7
7
  createServer,
8
8
  loadConfig
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-GH6WGN5B.js";
3
+ } from "./chunk-TAII35VC.js";
4
4
  export {
5
5
  IntelligenceService
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "2.0.0",
3
+ "version": "2.2.3",
4
4
  "type": "module",
5
5
  "description": "The ultimate open-source AEO monitoring tool - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -58,19 +58,19 @@
58
58
  "tsup": "^8.5.1",
59
59
  "tsx": "^4.19.0",
60
60
  "@ainyc/canonry-api-routes": "0.0.0",
61
- "@ainyc/canonry-contracts": "0.0.0",
62
61
  "@ainyc/canonry-config": "0.0.0",
62
+ "@ainyc/canonry-contracts": "0.0.0",
63
63
  "@ainyc/canonry-db": "0.0.0",
64
- "@ainyc/canonry-integration-google": "0.0.0",
65
64
  "@ainyc/canonry-intelligence": "0.0.0",
66
65
  "@ainyc/canonry-integration-bing": "0.0.0",
67
66
  "@ainyc/canonry-integration-wordpress": "0.0.0",
67
+ "@ainyc/canonry-integration-google": "0.0.0",
68
68
  "@ainyc/canonry-provider-cdp": "0.0.0",
69
69
  "@ainyc/canonry-provider-claude": "0.0.0",
70
70
  "@ainyc/canonry-provider-gemini": "0.0.0",
71
- "@ainyc/canonry-provider-openai": "0.0.0",
71
+ "@ainyc/canonry-provider-local": "0.0.0",
72
72
  "@ainyc/canonry-provider-perplexity": "0.0.0",
73
- "@ainyc/canonry-provider-local": "0.0.0"
73
+ "@ainyc/canonry-provider-openai": "0.0.0"
74
74
  },
75
75
  "scripts": {
76
76
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",