@ainyc/canonry 4.64.1 → 4.67.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/assets/agent-workspace/skills/canonry/references/canonry-cli.md +30 -1
- package/dist/{chunk-MDNDIBUM.js → chunk-4V3V4MFF.js} +13 -12
- package/dist/{chunk-64IDABSF.js → chunk-RQCVITY4.js} +7 -30
- package/dist/cli.js +410 -211
- package/dist/index.js +2 -2
- package/dist/mcp.js +1 -1
- package/package.json +11 -11
|
@@ -524,4 +524,33 @@ cnry agent detach <project> --format json # JSON output
|
|
|
524
524
|
|
|
525
525
|
## Output Formats
|
|
526
526
|
|
|
527
|
-
|
|
527
|
+
Every command takes `--format`:
|
|
528
|
+
|
|
529
|
+
- **`text`** (default) — human-readable, decorated. Not a stable parse target.
|
|
530
|
+
- **`json`** — one pretty-printed JSON document (the full envelope). Stable contract.
|
|
531
|
+
- **`jsonl`** — newline-delimited JSON: the command's **primary collection**, one self-contained record per line. The agent-friendly machine format — no envelope key to guess (`.checks` vs `.results` vs `.rows`), no `jq` flattening, greppable line by line.
|
|
532
|
+
|
|
533
|
+
`jsonl` is supported by every **collection** command — one whose primary output is a list: `insights`, `runs`, `evidence`, `history`, `query/keyword/competitor list`, `notify list/events`, `google` reads (`performance`, `performance-daily`, `inspections`, `coverage-history`, `deindexed`, `status`, `properties`, `list-sitemaps`), `bing` reads (`coverage-history`, `inspections`, `performance`, `sites`), `ga` reads (`ai-referral-history`, `social-referral-history`, `session-history`, `coverage`), `traffic events/sources/status`, `discover list/show`, `content targets/sources/gaps`, `backlinks list/releases`, `project list/locations`, `agent memory list`, `agent providers`, and `doctor`.
|
|
534
|
+
|
|
535
|
+
Each `jsonl` line re-injects the envelope context it would otherwise lose, so a line lifted out still self-describes:
|
|
536
|
+
|
|
537
|
+
- project-scoped lists stamp `{ "project": "<name>", …row }`;
|
|
538
|
+
- `ga *-history` also stamps `window`; `traffic events` stamps `windowStart`/`windowEnd`; `backlinks list` stamps `release`/`targetDomain`; `discover show` stamps `sessionId`; `content targets` stamps `latestRunId`; `project locations` stamps `isDefault`;
|
|
539
|
+
- global lists whose rows already self-identify (`project list`, `notify events`, `backlinks releases`) emit bare rows.
|
|
540
|
+
|
|
541
|
+
Empty collection → **no output** (the exit code still conveys success, so "no records" stays distinct from "failure"). On failure a command prints its records (if any), then exits non-zero — branch on the **exit code**, never on parsing stderr. JSON field names and the `{ "error": { "code", "message" } }` envelope are a public contract.
|
|
542
|
+
|
|
543
|
+
**Composite** commands return a single aggregate object (not a list), so there is nothing to stream — on them `--format jsonl` **degrades to the same JSON document** as `--format json`; it never falls through to decorated human text. So `--format jsonl` is safe to pass to *any* command: collection commands stream their records, every other command emits its JSON document. Composite shapes are below.
|
|
544
|
+
|
|
545
|
+
## Output schema per command
|
|
546
|
+
|
|
547
|
+
Compact reference for the composite / keyed commands agents read most (shapes can drift — the linked DTO source file is the source of truth; collection commands simply emit their primary array, see each command's own section above).
|
|
548
|
+
|
|
549
|
+
| Command | JSON output shape (top-level keys → DTO) | `jsonl` |
|
|
550
|
+
|---|---|---|
|
|
551
|
+
| `cnry doctor [--project p] [--all]` | `{ scope, project, generatedAt, durationMs, summary{total,ok,warn,fail,skipped}, checks[] }` — `DoctorReportDto` @ `contracts/doctor.ts`. `checks[]` = `CheckResultDto{ id, category, scope, title, status(ok\|warn\|fail\|skipped), code, summary, remediation?, details?, durationMs }`. With `--all`: an object keyed by `__global__` + each project name, each value a full report. | ✅ one check / line as `{project, …check}`; still exits non-zero if any `fail` |
|
|
552
|
+
| `cnry analytics <p> [--feature metrics\|gaps\|sources] [--window 7d\|30d\|90d\|all]` | Object **keyed by feature**: `{ metrics?, gaps?, sources? }` (all three present with no `--feature`; one with `--feature X`). `metrics`=`BrandMetricsDto{ window, buckets[], overall, byProvider, trend, mentionTrend, queryChanges[] }`; `gaps`=`GapAnalysisDto{ cited[], gap[], uncited[], mentionedQueries[], mentionGap[], notMentioned[], runId, window }` (each `[]`=`GapQuery`); `sources`=`SourceBreakdownDto{ overall[], byQuery, runId, window }`. @ `contracts/analytics.ts` | → degrades to the `json` document |
|
|
553
|
+
| `cnry google coverage <p>` (index coverage) | `{ summary{total,indexed,notIndexed,deindexed,percentage}, lastInspectedAt, lastSyncedAt, indexed[], notIndexed[], deindexed[], reasonGroups[] }` — `GscCoverageSummaryDto` @ `contracts/google.ts`. `indexed[]`/`notIndexed[]`=`GscUrlInspectionDto`, `deindexed[]`=`GscDeindexedRowDto`. | → degrades to the `json` document. The single-array reads `google inspections` / `coverage-history` / `deindexed` **stream** `jsonl`. |
|
|
554
|
+
| `cnry ga traffic <p> [--window …]` | Object summary — `GA4TrafficSummaryDto` / `GaTrafficResponse` @ `contracts/ga.ts`: `{ totalSessions, totalOrganicSessions, totalDirectSessions, totalUsers, aiSessionsDeduped, aiUsersDeduped, aiSessionsBySession, aiUsersBySession, socialSessions, socialUsers, channelBreakdown{organic,social,direct,ai,other→{sessions,sharePct,sharePctDisplay}}, *SharePct (+ `*Display`), topPages[], aiReferrals[], aiReferralLandingPages[], socialReferrals[], lastSyncedAt, periodStart, periodEnd }`. | → degrades to the `json` document |
|
|
555
|
+
| `cnry ga attribution <p> [--trend]` | Object — a **renamed projection** of `GaTrafficResponse` (⚠️ field names differ from the DTO): `aiSessions`(←`aiSessionsDeduped`), `organicSessions`(←`totalOrganicSessions`), `directSessions`(←`totalDirectSessions`), plus `totalSessions, totalUsers, aiUsers, aiSessionsBySession, aiUsersBySession, socialSessions, socialUsers, {ai,social,organic,direct}SharePct (+ `*Display`), otherSessions, otherSharePct, channelBreakdown, aiReferrals[], aiReferralLandingPages[], socialReferrals[], periodStart, periodEnd`. With `--trend`: drops `periodStart/End`, adds `trend` (`GaAttributionTrendResponse`). Assembled inline in `commands/ga.ts`. | → degrades to the `json` document |
|
|
556
|
+
| `cnry gbp summary <p> [--location …]` | `{ scope{locationName,locationCount}, performance{totals,recent7d,prior7d,deltaPct} (metric-keyed maps; keys are raw `BUSINESS_*` / `WEBSITE_CLICKS` tokens — label via `formatGbpMetricLabel`), freshness{dataThroughDate,latestStoredDate,pendingDays}, timeseries[], keywords{total,thresholdedCount,thresholdedPct}, placeActions{total,hasReservationCta,hasBookingCta,hasDirectMerchantCta}, lodging{lodgingLocationCount,populatedLodgingCount,emptyLodgingCount} }` — `GbpSummaryDto` @ `contracts/gbp.ts`. `timeseries[]`=`{date,pending,metrics}`. | → degrades to the `json` document |
|
|
@@ -5,10 +5,11 @@ import {
|
|
|
5
5
|
canonryMcpTools,
|
|
6
6
|
configExists,
|
|
7
7
|
getConfigPath,
|
|
8
|
+
isMachineFormat,
|
|
8
9
|
loadConfig,
|
|
9
10
|
loadConfigRaw,
|
|
10
11
|
saveConfigPatch
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-RQCVITY4.js";
|
|
12
13
|
import {
|
|
13
14
|
CC_CACHE_DIR,
|
|
14
15
|
DUCKDB_SPEC,
|
|
@@ -5296,7 +5297,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
5296
5297
|
result.dryRun = true;
|
|
5297
5298
|
result.wouldUpdate = wouldUpdate;
|
|
5298
5299
|
}
|
|
5299
|
-
if (opts?.format
|
|
5300
|
+
if (isMachineFormat(opts?.format)) {
|
|
5300
5301
|
console.log(JSON.stringify(result, null, 2));
|
|
5301
5302
|
return;
|
|
5302
5303
|
}
|
|
@@ -5366,7 +5367,7 @@ async function backfillNormalizedPathsCommand(opts) {
|
|
|
5366
5367
|
updated: 0,
|
|
5367
5368
|
unchanged: 0
|
|
5368
5369
|
};
|
|
5369
|
-
if (opts?.format
|
|
5370
|
+
if (isMachineFormat(opts?.format)) {
|
|
5370
5371
|
console.log(JSON.stringify(result2, null, 2));
|
|
5371
5372
|
return;
|
|
5372
5373
|
}
|
|
@@ -5382,7 +5383,7 @@ async function backfillNormalizedPathsCommand(opts) {
|
|
|
5382
5383
|
updated,
|
|
5383
5384
|
unchanged
|
|
5384
5385
|
};
|
|
5385
|
-
if (opts?.format
|
|
5386
|
+
if (isMachineFormat(opts?.format)) {
|
|
5386
5387
|
console.log(JSON.stringify(result, null, 2));
|
|
5387
5388
|
return;
|
|
5388
5389
|
}
|
|
@@ -5438,7 +5439,7 @@ async function backfillAiReferralPathsCommand(opts) {
|
|
|
5438
5439
|
updated: 0,
|
|
5439
5440
|
unchanged: 0
|
|
5440
5441
|
};
|
|
5441
|
-
if (opts?.format
|
|
5442
|
+
if (isMachineFormat(opts?.format)) {
|
|
5442
5443
|
console.log(JSON.stringify(result2, null, 2));
|
|
5443
5444
|
return;
|
|
5444
5445
|
}
|
|
@@ -5454,7 +5455,7 @@ async function backfillAiReferralPathsCommand(opts) {
|
|
|
5454
5455
|
updated,
|
|
5455
5456
|
unchanged
|
|
5456
5457
|
};
|
|
5457
|
-
if (opts?.format
|
|
5458
|
+
if (isMachineFormat(opts?.format)) {
|
|
5458
5459
|
console.log(JSON.stringify(result, null, 2));
|
|
5459
5460
|
return;
|
|
5460
5461
|
}
|
|
@@ -5579,7 +5580,7 @@ async function backfillAnswerMentionsCommand(opts) {
|
|
|
5579
5580
|
result.dryRun = true;
|
|
5580
5581
|
result.wouldUpdate = wouldUpdate;
|
|
5581
5582
|
}
|
|
5582
|
-
if (opts?.format
|
|
5583
|
+
if (isMachineFormat(opts?.format)) {
|
|
5583
5584
|
console.log(JSON.stringify(result, null, 2));
|
|
5584
5585
|
return;
|
|
5585
5586
|
}
|
|
@@ -5621,7 +5622,7 @@ async function backfillInsightsCommand(project, opts) {
|
|
|
5621
5622
|
const db = createClient(config.database);
|
|
5622
5623
|
migrate(db);
|
|
5623
5624
|
const service = new IntelligenceService2(db);
|
|
5624
|
-
const isJson = opts?.format
|
|
5625
|
+
const isJson = isMachineFormat(opts?.format);
|
|
5625
5626
|
const isDryRun = opts?.dryRun === true;
|
|
5626
5627
|
if (!isJson) {
|
|
5627
5628
|
const scope = opts?.since ? ` (since ${opts.since})` : "";
|
|
@@ -5779,7 +5780,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
5779
5780
|
if (!project) {
|
|
5780
5781
|
throw new Error(`Project "${opts.project}" not found`);
|
|
5781
5782
|
}
|
|
5782
|
-
const isJson = opts.format
|
|
5783
|
+
const isJson = isMachineFormat(opts.format);
|
|
5783
5784
|
const isDryRun = opts.dryRun === true;
|
|
5784
5785
|
if (!isJson) {
|
|
5785
5786
|
const mode = isDryRun ? " [DRY RUN \u2014 no writes]" : "";
|
|
@@ -5958,7 +5959,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
5958
5959
|
migrate(db);
|
|
5959
5960
|
const projectFilter = opts?.project?.trim();
|
|
5960
5961
|
const isDryRun = opts?.dryRun === true;
|
|
5961
|
-
const isJson = opts?.format
|
|
5962
|
+
const isJson = isMachineFormat(opts?.format);
|
|
5962
5963
|
const scopedProjects = projectFilter ? db.select().from(projects).where(eq10(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
5963
5964
|
if (scopedProjects.length === 0) {
|
|
5964
5965
|
if (projectFilter && !isJson) {
|
|
@@ -6395,7 +6396,7 @@ async function installSkills(opts = {}) {
|
|
|
6395
6396
|
}
|
|
6396
6397
|
async function listSkills(opts = {}) {
|
|
6397
6398
|
const skills = getBundledSkills();
|
|
6398
|
-
if (opts.format
|
|
6399
|
+
if (isMachineFormat(opts.format)) {
|
|
6399
6400
|
console.log(JSON.stringify({
|
|
6400
6401
|
skills: skills.map((s) => ({
|
|
6401
6402
|
name: s.name,
|
|
@@ -6416,7 +6417,7 @@ async function listSkills(opts = {}) {
|
|
|
6416
6417
|
}
|
|
6417
6418
|
}
|
|
6418
6419
|
function emitInstallSummary(summary, format) {
|
|
6419
|
-
if (format
|
|
6420
|
+
if (isMachineFormat(format)) {
|
|
6420
6421
|
console.log(JSON.stringify(summary, null, 2));
|
|
6421
6422
|
return;
|
|
6422
6423
|
}
|
|
@@ -190,6 +190,9 @@ function configExists() {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
// src/cli-error.ts
|
|
193
|
+
function isMachineFormat(format) {
|
|
194
|
+
return format === "json" || format === "jsonl";
|
|
195
|
+
}
|
|
193
196
|
var EXIT_USER_ERROR = 1;
|
|
194
197
|
var EXIT_SYSTEM_ERROR = 2;
|
|
195
198
|
var CliError = class extends Error {
|
|
@@ -221,36 +224,9 @@ function isEndpointMissing(err) {
|
|
|
221
224
|
return status === 404 || status === 405;
|
|
222
225
|
}
|
|
223
226
|
function printCliError(err, format) {
|
|
224
|
-
if (format
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
JSON.stringify(
|
|
228
|
-
{
|
|
229
|
-
error: {
|
|
230
|
-
code: err.code,
|
|
231
|
-
message: err.message,
|
|
232
|
-
...err.details ? { details: err.details } : {}
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
null,
|
|
236
|
-
2
|
|
237
|
-
)
|
|
238
|
-
);
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
const message = err instanceof Error ? err.message : "An unexpected error occurred";
|
|
242
|
-
console.error(
|
|
243
|
-
JSON.stringify(
|
|
244
|
-
{
|
|
245
|
-
error: {
|
|
246
|
-
code: "CLI_ERROR",
|
|
247
|
-
message
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
null,
|
|
251
|
-
2
|
|
252
|
-
)
|
|
253
|
-
);
|
|
227
|
+
if (isMachineFormat(format)) {
|
|
228
|
+
const envelope = err instanceof CliError ? { error: { code: err.code, message: err.message, ...err.details ? { details: err.details } : {} } } : { error: { code: "CLI_ERROR", message: err instanceof Error ? err.message : "An unexpected error occurred" } };
|
|
229
|
+
console.error(JSON.stringify(envelope, null, format === "jsonl" ? 0 : 2));
|
|
254
230
|
return;
|
|
255
231
|
}
|
|
256
232
|
if (err instanceof CliError && err.displayMessage) {
|
|
@@ -6256,6 +6232,7 @@ export {
|
|
|
6256
6232
|
saveConfig,
|
|
6257
6233
|
saveConfigPatch,
|
|
6258
6234
|
configExists,
|
|
6235
|
+
isMachineFormat,
|
|
6259
6236
|
EXIT_USER_ERROR,
|
|
6260
6237
|
EXIT_SYSTEM_ERROR,
|
|
6261
6238
|
CliError,
|