@ainyc/canonry 1.28.2 → 1.30.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/assets/index-DmFB_uXa.js +246 -0
- package/assets/assets/{index-DwPC0zVy.css → index-r6biprHB.css} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-GF5TI2U2.js → chunk-LMSO32GF.js} +1899 -78
- package/dist/cli.js +887 -61
- package/dist/index.js +1 -1
- package/package.json +12 -10
- package/assets/assets/index-CIHb8Gk8.js +0 -246
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createClient,
|
|
6
6
|
createServer,
|
|
7
7
|
effectiveDomains,
|
|
8
|
+
formatAuditFactorScore,
|
|
8
9
|
getConfigDir,
|
|
9
10
|
getConfigPath,
|
|
10
11
|
getOrCreateAnonymousId,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
setGoogleAuthConfig,
|
|
21
22
|
showFirstRunNotice,
|
|
22
23
|
trackEvent
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-LMSO32GF.js";
|
|
24
25
|
|
|
25
26
|
// src/cli.ts
|
|
26
27
|
import { pathToFileURL } from "url";
|
|
@@ -96,9 +97,9 @@ import { parseArgs } from "util";
|
|
|
96
97
|
function commandId(spec) {
|
|
97
98
|
return spec.path.join(".");
|
|
98
99
|
}
|
|
99
|
-
function matchesPath(args,
|
|
100
|
-
if (args.length <
|
|
101
|
-
return
|
|
100
|
+
function matchesPath(args, path5) {
|
|
101
|
+
if (args.length < path5.length) return false;
|
|
102
|
+
return path5.every((segment, index) => args[index] === segment);
|
|
102
103
|
}
|
|
103
104
|
function withFormatOption(options) {
|
|
104
105
|
if (!options) {
|
|
@@ -197,9 +198,9 @@ var ApiClient = class {
|
|
|
197
198
|
}
|
|
198
199
|
return this.probePromise;
|
|
199
200
|
}
|
|
200
|
-
async request(method,
|
|
201
|
+
async request(method, path5, body) {
|
|
201
202
|
await this.probeBasePath();
|
|
202
|
-
const url = `${this.baseUrl}${
|
|
203
|
+
const url = `${this.baseUrl}${path5}`;
|
|
203
204
|
const serializedBody = body != null ? JSON.stringify(body) : void 0;
|
|
204
205
|
const headers = {
|
|
205
206
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
@@ -297,6 +298,9 @@ var ApiClient = class {
|
|
|
297
298
|
async getSettings() {
|
|
298
299
|
return this.request("GET", "/settings");
|
|
299
300
|
}
|
|
301
|
+
async createSnapshot(body) {
|
|
302
|
+
return this.request("POST", "/snapshot", body);
|
|
303
|
+
}
|
|
300
304
|
async updateProvider(name, body) {
|
|
301
305
|
return this.request("PUT", `/settings/providers/${encodeURIComponent(name)}`, body);
|
|
302
306
|
}
|
|
@@ -506,6 +510,9 @@ var ApiClient = class {
|
|
|
506
510
|
async wordpressSetMeta(project, body) {
|
|
507
511
|
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/page/meta`, body);
|
|
508
512
|
}
|
|
513
|
+
async wordpressBulkSetMeta(project, body) {
|
|
514
|
+
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/pages/meta/bulk`, body);
|
|
515
|
+
}
|
|
509
516
|
async wordpressSchema(project, slug, env) {
|
|
510
517
|
const params = new URLSearchParams({ slug });
|
|
511
518
|
if (env) params.set("env", env);
|
|
@@ -514,6 +521,18 @@ var ApiClient = class {
|
|
|
514
521
|
async wordpressSetSchema(project, body) {
|
|
515
522
|
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/schema/manual`, body);
|
|
516
523
|
}
|
|
524
|
+
async wordpressSchemaDeploy(project, body) {
|
|
525
|
+
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/schema/deploy`, body);
|
|
526
|
+
}
|
|
527
|
+
async wordpressSchemaStatus(project, env) {
|
|
528
|
+
const params = new URLSearchParams();
|
|
529
|
+
if (env) params.set("env", env);
|
|
530
|
+
const qs = params.toString();
|
|
531
|
+
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/schema/status${qs ? `?${qs}` : ""}`);
|
|
532
|
+
}
|
|
533
|
+
async wordpressOnboard(project, body) {
|
|
534
|
+
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/onboard`, body);
|
|
535
|
+
}
|
|
517
536
|
async wordpressLlmsTxt(project, env) {
|
|
518
537
|
const qs = env ? `?env=${encodeURIComponent(env)}` : "";
|
|
519
538
|
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/llms-txt${qs}`);
|
|
@@ -1275,9 +1294,9 @@ async function gaConnect(project, opts) {
|
|
|
1275
1294
|
propertyId: opts.propertyId
|
|
1276
1295
|
};
|
|
1277
1296
|
if (opts.keyFile) {
|
|
1278
|
-
const
|
|
1297
|
+
const fs7 = await import("fs");
|
|
1279
1298
|
try {
|
|
1280
|
-
const content =
|
|
1299
|
+
const content = fs7.readFileSync(opts.keyFile, "utf-8");
|
|
1281
1300
|
JSON.parse(content);
|
|
1282
1301
|
body.keyJson = content;
|
|
1283
1302
|
} catch (e) {
|
|
@@ -1338,7 +1357,8 @@ async function gaSync(project, opts) {
|
|
|
1338
1357
|
return;
|
|
1339
1358
|
}
|
|
1340
1359
|
console.log(`GA4 sync complete for "${project}".`);
|
|
1341
|
-
console.log(`
|
|
1360
|
+
console.log(` Page rows: ${result.rowCount}`);
|
|
1361
|
+
console.log(` AI rows: ${result.aiReferralCount}`);
|
|
1342
1362
|
console.log(` Period: ${result.days} days`);
|
|
1343
1363
|
console.log(` Synced at: ${result.syncedAt}`);
|
|
1344
1364
|
}
|
|
@@ -1351,7 +1371,7 @@ async function gaTraffic(project, opts) {
|
|
|
1351
1371
|
console.log(JSON.stringify(result, null, 2));
|
|
1352
1372
|
return;
|
|
1353
1373
|
}
|
|
1354
|
-
if (result.topPages.length === 0) {
|
|
1374
|
+
if (result.topPages.length === 0 && result.aiReferrals.length === 0) {
|
|
1355
1375
|
console.log('No GA4 traffic data. Run "canonry ga sync <project>" first.');
|
|
1356
1376
|
return;
|
|
1357
1377
|
}
|
|
@@ -1361,14 +1381,28 @@ async function gaTraffic(project, opts) {
|
|
|
1361
1381
|
console.log(` Organic Sessions: ${result.totalOrganicSessions}`);
|
|
1362
1382
|
console.log(` Total Users: ${result.totalUsers}`);
|
|
1363
1383
|
console.log();
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1384
|
+
if (result.aiReferrals.length > 0) {
|
|
1385
|
+
console.log(" AI REFERRAL SOURCES");
|
|
1386
|
+
console.log(` ${"SOURCE".padEnd(25)} ${"MEDIUM".padEnd(15)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1387
|
+
console.log(` ${"\u2500".repeat(25)} ${"\u2500".repeat(15)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1388
|
+
for (const ref of result.aiReferrals) {
|
|
1389
|
+
console.log(
|
|
1390
|
+
` ${ref.source.padEnd(25)} ${ref.medium.padEnd(15)} ${String(ref.sessions).padEnd(10)}${String(ref.users).padEnd(8)}`
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
console.log();
|
|
1394
|
+
}
|
|
1395
|
+
if (result.topPages.length > 0) {
|
|
1396
|
+
const pageWidth = Math.min(60, Math.max(15, ...result.topPages.map((r) => r.landingPage.length)));
|
|
1397
|
+
console.log(` TOP LANDING PAGES`);
|
|
1398
|
+
console.log(` ${"PAGE".padEnd(pageWidth)} ${"SESSIONS".padEnd(10)}${"ORGANIC".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1399
|
+
console.log(` ${"\u2500".repeat(pageWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1400
|
+
for (const row of result.topPages) {
|
|
1401
|
+
const page = row.landingPage.length > pageWidth ? row.landingPage.slice(0, pageWidth - 3) + "..." : row.landingPage;
|
|
1402
|
+
console.log(
|
|
1403
|
+
` ${page.padEnd(pageWidth)} ${String(row.sessions).padEnd(10)}${String(row.organicSessions).padEnd(10)}${String(row.users).padEnd(8)}`
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1372
1406
|
}
|
|
1373
1407
|
if (result.lastSyncedAt) {
|
|
1374
1408
|
console.log(`
|
|
@@ -4212,9 +4246,462 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
|
|
|
4212
4246
|
}
|
|
4213
4247
|
];
|
|
4214
4248
|
|
|
4249
|
+
// src/snapshot-pdf.ts
|
|
4250
|
+
import fs3 from "fs";
|
|
4251
|
+
import path from "path";
|
|
4252
|
+
import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
|
|
4253
|
+
var PAGE_WIDTH = 612;
|
|
4254
|
+
var PAGE_HEIGHT = 792;
|
|
4255
|
+
var MARGIN = 48;
|
|
4256
|
+
var BRAND = rgb(0.58, 0, 0);
|
|
4257
|
+
var INK = rgb(0.1, 0.1, 0.1);
|
|
4258
|
+
var MUTED = rgb(0.38, 0.38, 0.38);
|
|
4259
|
+
var LINE = rgb(0.82, 0.8, 0.76);
|
|
4260
|
+
var PASS = rgb(0.18, 0.49, 0.31);
|
|
4261
|
+
var CAUTION = rgb(0.72, 0.45, 0.2);
|
|
4262
|
+
var FAIL = rgb(0.7, 0.15, 0.15);
|
|
4263
|
+
var PdfWriter = class {
|
|
4264
|
+
constructor(doc, regular, bold) {
|
|
4265
|
+
this.doc = doc;
|
|
4266
|
+
this.regular = regular;
|
|
4267
|
+
this.bold = bold;
|
|
4268
|
+
this.addPage();
|
|
4269
|
+
}
|
|
4270
|
+
usableWidth = PAGE_WIDTH - MARGIN * 2;
|
|
4271
|
+
page;
|
|
4272
|
+
y = 0;
|
|
4273
|
+
addPage() {
|
|
4274
|
+
this.page = this.doc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
4275
|
+
this.y = PAGE_HEIGHT - MARGIN;
|
|
4276
|
+
}
|
|
4277
|
+
ensureSpace(height) {
|
|
4278
|
+
if (this.y - height < MARGIN) {
|
|
4279
|
+
this.addPage();
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
heading(text, size = 18) {
|
|
4283
|
+
this.ensureSpace(size + 12);
|
|
4284
|
+
this.page.drawText(text, {
|
|
4285
|
+
x: MARGIN,
|
|
4286
|
+
y: this.y,
|
|
4287
|
+
size,
|
|
4288
|
+
font: this.bold,
|
|
4289
|
+
color: BRAND
|
|
4290
|
+
});
|
|
4291
|
+
this.y -= size + 8;
|
|
4292
|
+
}
|
|
4293
|
+
subheading(text, size = 12) {
|
|
4294
|
+
this.ensureSpace(size + 8);
|
|
4295
|
+
this.page.drawText(text, {
|
|
4296
|
+
x: MARGIN,
|
|
4297
|
+
y: this.y,
|
|
4298
|
+
size,
|
|
4299
|
+
font: this.bold,
|
|
4300
|
+
color: INK
|
|
4301
|
+
});
|
|
4302
|
+
this.y -= size + 6;
|
|
4303
|
+
}
|
|
4304
|
+
paragraph(text, opts) {
|
|
4305
|
+
const size = opts?.size ?? 10;
|
|
4306
|
+
const color = opts?.color ?? INK;
|
|
4307
|
+
const lineHeight = opts?.lineHeight ?? size + 4;
|
|
4308
|
+
const lines = wrapText(this.regular, text, size, this.usableWidth);
|
|
4309
|
+
this.ensureSpace(lines.length * lineHeight + 4);
|
|
4310
|
+
for (const line of lines) {
|
|
4311
|
+
this.page.drawText(line, {
|
|
4312
|
+
x: MARGIN,
|
|
4313
|
+
y: this.y,
|
|
4314
|
+
size,
|
|
4315
|
+
font: this.regular,
|
|
4316
|
+
color
|
|
4317
|
+
});
|
|
4318
|
+
this.y -= lineHeight;
|
|
4319
|
+
}
|
|
4320
|
+
this.y -= 2;
|
|
4321
|
+
}
|
|
4322
|
+
bullet(text) {
|
|
4323
|
+
const lines = wrapText(this.regular, text, 10, this.usableWidth - 14);
|
|
4324
|
+
this.ensureSpace(lines.length * 14 + 2);
|
|
4325
|
+
this.page.drawText("-", {
|
|
4326
|
+
x: MARGIN,
|
|
4327
|
+
y: this.y,
|
|
4328
|
+
size: 10,
|
|
4329
|
+
font: this.bold,
|
|
4330
|
+
color: BRAND
|
|
4331
|
+
});
|
|
4332
|
+
let first = true;
|
|
4333
|
+
for (const line of lines) {
|
|
4334
|
+
this.page.drawText(line, {
|
|
4335
|
+
x: MARGIN + 14,
|
|
4336
|
+
y: this.y,
|
|
4337
|
+
size: 10,
|
|
4338
|
+
font: this.regular,
|
|
4339
|
+
color: INK
|
|
4340
|
+
});
|
|
4341
|
+
this.y -= 14;
|
|
4342
|
+
if (first) first = false;
|
|
4343
|
+
}
|
|
4344
|
+
this.y -= 2;
|
|
4345
|
+
}
|
|
4346
|
+
rule() {
|
|
4347
|
+
this.ensureSpace(8);
|
|
4348
|
+
this.page.drawLine({
|
|
4349
|
+
start: { x: MARGIN, y: this.y },
|
|
4350
|
+
end: { x: PAGE_WIDTH - MARGIN, y: this.y },
|
|
4351
|
+
thickness: 1,
|
|
4352
|
+
color: LINE
|
|
4353
|
+
});
|
|
4354
|
+
this.y -= 10;
|
|
4355
|
+
}
|
|
4356
|
+
keyValue(label, value) {
|
|
4357
|
+
const size = 10;
|
|
4358
|
+
const labelWidth = this.bold.widthOfTextAtSize(`${label}: `, size);
|
|
4359
|
+
this.ensureSpace(16);
|
|
4360
|
+
this.page.drawText(`${label}:`, {
|
|
4361
|
+
x: MARGIN,
|
|
4362
|
+
y: this.y,
|
|
4363
|
+
size,
|
|
4364
|
+
font: this.bold,
|
|
4365
|
+
color: INK
|
|
4366
|
+
});
|
|
4367
|
+
const lines = wrapText(this.regular, value, size, this.usableWidth - labelWidth - 4);
|
|
4368
|
+
let currentY = this.y;
|
|
4369
|
+
for (const line of lines) {
|
|
4370
|
+
this.page.drawText(line, {
|
|
4371
|
+
x: MARGIN + labelWidth + 4,
|
|
4372
|
+
y: currentY,
|
|
4373
|
+
size,
|
|
4374
|
+
font: this.regular,
|
|
4375
|
+
color: INK
|
|
4376
|
+
});
|
|
4377
|
+
currentY -= 14;
|
|
4378
|
+
}
|
|
4379
|
+
this.y = currentY - 2;
|
|
4380
|
+
}
|
|
4381
|
+
table(headers, rows, widths) {
|
|
4382
|
+
const columnWidths = widths ?? headers.map(() => this.usableWidth / headers.length);
|
|
4383
|
+
const headerHeight = 20;
|
|
4384
|
+
this.ensureSpace(headerHeight + 10);
|
|
4385
|
+
let x = MARGIN;
|
|
4386
|
+
for (let i = 0; i < headers.length; i++) {
|
|
4387
|
+
const width = columnWidths[i];
|
|
4388
|
+
this.page.drawRectangle({
|
|
4389
|
+
x,
|
|
4390
|
+
y: this.y - headerHeight + 4,
|
|
4391
|
+
width,
|
|
4392
|
+
height: headerHeight,
|
|
4393
|
+
color: BRAND
|
|
4394
|
+
});
|
|
4395
|
+
const lines = wrapText(this.bold, headers[i], 9, width - 8);
|
|
4396
|
+
let lineY = this.y - 10;
|
|
4397
|
+
for (const line of lines) {
|
|
4398
|
+
this.page.drawText(line, {
|
|
4399
|
+
x: x + 4,
|
|
4400
|
+
y: lineY,
|
|
4401
|
+
size: 9,
|
|
4402
|
+
font: this.bold,
|
|
4403
|
+
color: rgb(1, 0.98, 0.93)
|
|
4404
|
+
});
|
|
4405
|
+
lineY -= 10;
|
|
4406
|
+
}
|
|
4407
|
+
x += width;
|
|
4408
|
+
}
|
|
4409
|
+
this.y -= headerHeight + 4;
|
|
4410
|
+
for (const row of rows) {
|
|
4411
|
+
const lineCounts = row.map((cell, index) => wrapText(this.regular, cell, 9, columnWidths[index] - 8).length);
|
|
4412
|
+
const rowHeight = Math.max(18, Math.max(...lineCounts) * 11 + 6);
|
|
4413
|
+
this.ensureSpace(rowHeight + 4);
|
|
4414
|
+
let cellX = MARGIN;
|
|
4415
|
+
for (let i = 0; i < row.length; i++) {
|
|
4416
|
+
const width = columnWidths[i];
|
|
4417
|
+
this.page.drawRectangle({
|
|
4418
|
+
x: cellX,
|
|
4419
|
+
y: this.y - rowHeight + 4,
|
|
4420
|
+
width,
|
|
4421
|
+
height: rowHeight,
|
|
4422
|
+
borderColor: LINE,
|
|
4423
|
+
borderWidth: 0.5
|
|
4424
|
+
});
|
|
4425
|
+
const lines = wrapText(this.regular, row[i], 9, width - 8);
|
|
4426
|
+
let lineY = this.y - 10;
|
|
4427
|
+
for (const line of lines) {
|
|
4428
|
+
this.page.drawText(line, {
|
|
4429
|
+
x: cellX + 4,
|
|
4430
|
+
y: lineY,
|
|
4431
|
+
size: 9,
|
|
4432
|
+
font: this.regular,
|
|
4433
|
+
color: INK
|
|
4434
|
+
});
|
|
4435
|
+
lineY -= 11;
|
|
4436
|
+
}
|
|
4437
|
+
cellX += width;
|
|
4438
|
+
}
|
|
4439
|
+
this.y -= rowHeight + 2;
|
|
4440
|
+
}
|
|
4441
|
+
this.y -= 4;
|
|
4442
|
+
}
|
|
4443
|
+
};
|
|
4444
|
+
async function writeSnapshotPdf(report, outputPath) {
|
|
4445
|
+
const doc = await PDFDocument.create();
|
|
4446
|
+
doc.setTitle(`${report.companyName} AI Perception Snapshot`);
|
|
4447
|
+
doc.setAuthor("Canonry");
|
|
4448
|
+
doc.setSubject("AEO snapshot report");
|
|
4449
|
+
doc.setProducer("Canonry");
|
|
4450
|
+
doc.setCreator("Canonry");
|
|
4451
|
+
const regular = await doc.embedFont(StandardFonts.Helvetica);
|
|
4452
|
+
const bold = await doc.embedFont(StandardFonts.HelveticaBold);
|
|
4453
|
+
const pdf = new PdfWriter(doc, regular, bold);
|
|
4454
|
+
renderCover(pdf, report);
|
|
4455
|
+
renderSummary(pdf, report);
|
|
4456
|
+
renderAudit(pdf, report);
|
|
4457
|
+
renderCompetitors(pdf, report);
|
|
4458
|
+
renderQueries(pdf, report);
|
|
4459
|
+
const bytes = await doc.save();
|
|
4460
|
+
const resolvedPath = path.resolve(outputPath);
|
|
4461
|
+
fs3.mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
4462
|
+
fs3.writeFileSync(resolvedPath, bytes);
|
|
4463
|
+
return resolvedPath;
|
|
4464
|
+
}
|
|
4465
|
+
function renderCover(pdf, report) {
|
|
4466
|
+
pdf.heading("AI Perception Snapshot", 24);
|
|
4467
|
+
pdf.paragraph(report.companyName, { size: 15, color: INK, lineHeight: 18 });
|
|
4468
|
+
pdf.paragraph(report.domain, { size: 11, color: MUTED, lineHeight: 14 });
|
|
4469
|
+
pdf.rule();
|
|
4470
|
+
pdf.keyValue("Generated", new Date(report.generatedAt).toLocaleString("en-US", {
|
|
4471
|
+
year: "numeric",
|
|
4472
|
+
month: "long",
|
|
4473
|
+
day: "numeric",
|
|
4474
|
+
hour: "numeric",
|
|
4475
|
+
minute: "2-digit"
|
|
4476
|
+
}));
|
|
4477
|
+
pdf.keyValue("AEO Audit", `${report.audit.overallScore}/100 (${report.audit.overallGrade})`);
|
|
4478
|
+
pdf.keyValue("Visibility Gap", report.summary.visibilityGap);
|
|
4479
|
+
pdf.paragraph(report.profile.summary, { size: 11, color: INK, lineHeight: 16 });
|
|
4480
|
+
pdf.rule();
|
|
4481
|
+
}
|
|
4482
|
+
function renderSummary(pdf, report) {
|
|
4483
|
+
pdf.heading("What This Means");
|
|
4484
|
+
for (const line of report.summary.whatThisMeans) {
|
|
4485
|
+
pdf.bullet(line);
|
|
4486
|
+
}
|
|
4487
|
+
pdf.subheading("Recommended Actions");
|
|
4488
|
+
for (const action of report.summary.recommendedActions) {
|
|
4489
|
+
pdf.bullet(action);
|
|
4490
|
+
}
|
|
4491
|
+
pdf.rule();
|
|
4492
|
+
}
|
|
4493
|
+
function renderAudit(pdf, report) {
|
|
4494
|
+
pdf.heading("Audit Snapshot");
|
|
4495
|
+
pdf.paragraph(report.audit.summary, { size: 10, color: MUTED, lineHeight: 14 });
|
|
4496
|
+
const factorRows = [...report.audit.factors].sort((a, b) => a.score - b.score || a.name.localeCompare(b.name)).slice(0, 5).map((factor) => [
|
|
4497
|
+
factor.name,
|
|
4498
|
+
formatAuditFactorScore(factor),
|
|
4499
|
+
factor.status
|
|
4500
|
+
]);
|
|
4501
|
+
if (factorRows.length > 0) {
|
|
4502
|
+
pdf.table(["Weakest factor", "Score / Weight", "Status"], factorRows, [270, 120, 126]);
|
|
4503
|
+
}
|
|
4504
|
+
pdf.rule();
|
|
4505
|
+
}
|
|
4506
|
+
function renderCompetitors(pdf, report) {
|
|
4507
|
+
pdf.heading("Recommended Instead");
|
|
4508
|
+
if (report.summary.topCompetitors.length === 0) {
|
|
4509
|
+
pdf.paragraph("No clear competitor cluster was extracted from the responses.", {
|
|
4510
|
+
size: 10,
|
|
4511
|
+
color: MUTED
|
|
4512
|
+
});
|
|
4513
|
+
pdf.rule();
|
|
4514
|
+
return;
|
|
4515
|
+
}
|
|
4516
|
+
pdf.table(
|
|
4517
|
+
["Competitor", "Mentions"],
|
|
4518
|
+
report.summary.topCompetitors.map((entry) => [entry.name, String(entry.count)]),
|
|
4519
|
+
[420, 96]
|
|
4520
|
+
);
|
|
4521
|
+
pdf.rule();
|
|
4522
|
+
}
|
|
4523
|
+
function renderQueries(pdf, report) {
|
|
4524
|
+
pdf.heading("Provider Comparison");
|
|
4525
|
+
for (const query of report.queryResults) {
|
|
4526
|
+
pdf.subheading(query.phrase, 11);
|
|
4527
|
+
for (const result of query.providerResults) {
|
|
4528
|
+
const status = result.error ? "error" : result.mentioned ? result.cited ? "mentioned and cited" : "mentioned" : "not mentioned";
|
|
4529
|
+
const accuracy = result.describedAccurately === "not-mentioned" ? "" : `; accuracy: ${result.describedAccurately}`;
|
|
4530
|
+
const competitors = result.recommendedCompetitors.length > 0 ? `; recommended instead: ${result.recommendedCompetitors.join(", ")}` : "";
|
|
4531
|
+
const line = `${result.displayName}: ${status}${accuracy}${competitors}`;
|
|
4532
|
+
pdf.bullet(line);
|
|
4533
|
+
if (result.error) {
|
|
4534
|
+
pdf.paragraph(`Error: ${result.error}`, { size: 9, color: FAIL, lineHeight: 12 });
|
|
4535
|
+
} else if (result.accuracyNotes) {
|
|
4536
|
+
const color = result.describedAccurately === "yes" ? PASS : result.describedAccurately === "no" ? FAIL : CAUTION;
|
|
4537
|
+
pdf.paragraph(result.accuracyNotes, { size: 9, color, lineHeight: 12 });
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
pdf.rule();
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
function wrapText(font, text, size, maxWidth) {
|
|
4544
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
4545
|
+
if (!normalized) return [""];
|
|
4546
|
+
const words = normalized.split(" ");
|
|
4547
|
+
const lines = [];
|
|
4548
|
+
let current = "";
|
|
4549
|
+
for (const word of words) {
|
|
4550
|
+
const next = current ? `${current} ${word}` : word;
|
|
4551
|
+
if (font.widthOfTextAtSize(next, size) <= maxWidth) {
|
|
4552
|
+
current = next;
|
|
4553
|
+
continue;
|
|
4554
|
+
}
|
|
4555
|
+
if (current) {
|
|
4556
|
+
lines.push(current);
|
|
4557
|
+
current = word;
|
|
4558
|
+
continue;
|
|
4559
|
+
}
|
|
4560
|
+
let chunk = "";
|
|
4561
|
+
for (const char of word) {
|
|
4562
|
+
const candidate = `${chunk}${char}`;
|
|
4563
|
+
if (font.widthOfTextAtSize(candidate, size) <= maxWidth) {
|
|
4564
|
+
chunk = candidate;
|
|
4565
|
+
} else {
|
|
4566
|
+
if (chunk) lines.push(chunk);
|
|
4567
|
+
chunk = char;
|
|
4568
|
+
}
|
|
4569
|
+
}
|
|
4570
|
+
current = chunk;
|
|
4571
|
+
}
|
|
4572
|
+
if (current) lines.push(current);
|
|
4573
|
+
return lines;
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
// src/commands/snapshot.ts
|
|
4577
|
+
function getClient16() {
|
|
4578
|
+
return createApiClient();
|
|
4579
|
+
}
|
|
4580
|
+
async function createSnapshotReport(companyName, opts) {
|
|
4581
|
+
const client = getClient16();
|
|
4582
|
+
const report = await client.createSnapshot({
|
|
4583
|
+
companyName,
|
|
4584
|
+
domain: opts.domain,
|
|
4585
|
+
...opts.phrases && opts.phrases.length > 0 ? { phrases: opts.phrases } : {},
|
|
4586
|
+
...opts.competitors && opts.competitors.length > 0 ? { competitors: opts.competitors } : {}
|
|
4587
|
+
});
|
|
4588
|
+
let savedPdfPath;
|
|
4589
|
+
if (opts.pdf) {
|
|
4590
|
+
savedPdfPath = await writeSnapshotPdf(report, opts.pdf);
|
|
4591
|
+
}
|
|
4592
|
+
if (opts.format === "json") {
|
|
4593
|
+
console.log(JSON.stringify(report, null, 2));
|
|
4594
|
+
if (savedPdfPath) {
|
|
4595
|
+
process.stderr.write(`Saved PDF: ${savedPdfPath}
|
|
4596
|
+
`);
|
|
4597
|
+
}
|
|
4598
|
+
return;
|
|
4599
|
+
}
|
|
4600
|
+
console.log(formatSnapshotText(report));
|
|
4601
|
+
if (savedPdfPath) {
|
|
4602
|
+
console.log(`
|
|
4603
|
+
PDF saved: ${savedPdfPath}`);
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
function formatSnapshotText(report) {
|
|
4607
|
+
const lines = [];
|
|
4608
|
+
lines.push(`Snapshot: ${report.companyName} (${report.domain})`);
|
|
4609
|
+
lines.push(`AEO audit: ${report.audit.overallScore}/100 (${report.audit.overallGrade})`);
|
|
4610
|
+
lines.push(report.summary.visibilityGap);
|
|
4611
|
+
lines.push("");
|
|
4612
|
+
if (report.summary.topCompetitors.length > 0) {
|
|
4613
|
+
lines.push(
|
|
4614
|
+
`Top competitors AI recommended instead: ${report.summary.topCompetitors.map((entry) => `${entry.name} (${entry.count})`).join(", ")}`
|
|
4615
|
+
);
|
|
4616
|
+
lines.push("");
|
|
4617
|
+
}
|
|
4618
|
+
if (report.summary.whatThisMeans.length > 0) {
|
|
4619
|
+
lines.push("What this means:");
|
|
4620
|
+
for (const item of report.summary.whatThisMeans) {
|
|
4621
|
+
lines.push(` - ${item}`);
|
|
4622
|
+
}
|
|
4623
|
+
lines.push("");
|
|
4624
|
+
}
|
|
4625
|
+
const providerWidth = Math.max(
|
|
4626
|
+
8,
|
|
4627
|
+
...report.queryResults.flatMap((query) => query.providerResults.map((result) => result.displayName.length))
|
|
4628
|
+
);
|
|
4629
|
+
for (const query of report.queryResults) {
|
|
4630
|
+
lines.push(`"${query.phrase}"`);
|
|
4631
|
+
for (const result of query.providerResults) {
|
|
4632
|
+
lines.push(` ${result.displayName.padEnd(providerWidth)} ${formatProviderLine(result)}`);
|
|
4633
|
+
}
|
|
4634
|
+
lines.push("");
|
|
4635
|
+
}
|
|
4636
|
+
if (report.summary.recommendedActions.length > 0) {
|
|
4637
|
+
lines.push("Recommended actions:");
|
|
4638
|
+
for (const action of report.summary.recommendedActions) {
|
|
4639
|
+
lines.push(` - ${action}`);
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
return lines.join("\n").trimEnd();
|
|
4643
|
+
}
|
|
4644
|
+
function formatProviderLine(result) {
|
|
4645
|
+
if (result.error) {
|
|
4646
|
+
return `ERROR: ${result.error}`;
|
|
4647
|
+
}
|
|
4648
|
+
const bits = [];
|
|
4649
|
+
bits.push(result.mentioned ? "YES mentioned" : "NO mention");
|
|
4650
|
+
if (result.cited) bits.push("cited");
|
|
4651
|
+
if (result.describedAccurately !== "not-mentioned") {
|
|
4652
|
+
bits.push(`accuracy=${result.describedAccurately}`);
|
|
4653
|
+
}
|
|
4654
|
+
if (result.recommendedCompetitors.length > 0) {
|
|
4655
|
+
bits.push(`recommended instead: ${result.recommendedCompetitors.join(", ")}`);
|
|
4656
|
+
}
|
|
4657
|
+
if (result.incorrectClaims.length > 0) {
|
|
4658
|
+
bits.push(`incorrect: ${result.incorrectClaims.join("; ")}`);
|
|
4659
|
+
}
|
|
4660
|
+
return bits.join(" | ");
|
|
4661
|
+
}
|
|
4662
|
+
|
|
4663
|
+
// src/cli-commands/snapshot.ts
|
|
4664
|
+
function parseCsvOption(value) {
|
|
4665
|
+
if (!value) return void 0;
|
|
4666
|
+
const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
4667
|
+
return parts.length > 0 ? [...new Set(parts)] : void 0;
|
|
4668
|
+
}
|
|
4669
|
+
var SNAPSHOT_CLI_COMMANDS = [
|
|
4670
|
+
{
|
|
4671
|
+
path: ["snapshot"],
|
|
4672
|
+
usage: 'canonry snapshot <company-name> --domain <domain> [--phrases "a,b"] [--competitors "x,y"] [--pdf <path>] [--format table|json]',
|
|
4673
|
+
options: {
|
|
4674
|
+
domain: stringOption(),
|
|
4675
|
+
phrases: stringOption(),
|
|
4676
|
+
competitors: stringOption(),
|
|
4677
|
+
pdf: stringOption()
|
|
4678
|
+
},
|
|
4679
|
+
run: async (input) => {
|
|
4680
|
+
const usage = 'canonry snapshot <company-name> --domain <domain> [--phrases "a,b"] [--competitors "x,y"] [--pdf <path>] [--format table|json]';
|
|
4681
|
+
const companyName = requirePositional(input, 0, {
|
|
4682
|
+
command: "snapshot",
|
|
4683
|
+
usage,
|
|
4684
|
+
message: "company name is required"
|
|
4685
|
+
});
|
|
4686
|
+
const domain = requireStringOption(input, "domain", {
|
|
4687
|
+
command: "snapshot",
|
|
4688
|
+
usage,
|
|
4689
|
+
message: "--domain is required"
|
|
4690
|
+
});
|
|
4691
|
+
await createSnapshotReport(companyName, {
|
|
4692
|
+
domain,
|
|
4693
|
+
phrases: parseCsvOption(getString(input.values, "phrases")),
|
|
4694
|
+
competitors: parseCsvOption(getString(input.values, "competitors")),
|
|
4695
|
+
pdf: getString(input.values, "pdf"),
|
|
4696
|
+
format: input.format
|
|
4697
|
+
});
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
];
|
|
4701
|
+
|
|
4215
4702
|
// src/commands/bootstrap.ts
|
|
4216
4703
|
import crypto from "crypto";
|
|
4217
|
-
import
|
|
4704
|
+
import path2 from "path";
|
|
4218
4705
|
import { eq } from "drizzle-orm";
|
|
4219
4706
|
|
|
4220
4707
|
// ../config/src/index.ts
|
|
@@ -4360,7 +4847,7 @@ async function bootstrapCommand(_opts) {
|
|
|
4360
4847
|
);
|
|
4361
4848
|
}
|
|
4362
4849
|
const configDir = getConfigDir();
|
|
4363
|
-
const databasePath = env.databasePath ||
|
|
4850
|
+
const databasePath = env.databasePath || path2.join(configDir, "data.db");
|
|
4364
4851
|
const existing = configExists();
|
|
4365
4852
|
const existingConfig = existing ? loadConfig() : void 0;
|
|
4366
4853
|
let rawApiKey;
|
|
@@ -4428,10 +4915,10 @@ async function bootstrapCommand(_opts) {
|
|
|
4428
4915
|
|
|
4429
4916
|
// src/commands/daemon.ts
|
|
4430
4917
|
import { spawn } from "child_process";
|
|
4431
|
-
import
|
|
4432
|
-
import
|
|
4918
|
+
import fs4 from "fs";
|
|
4919
|
+
import path3 from "path";
|
|
4433
4920
|
function getPidPath() {
|
|
4434
|
-
return
|
|
4921
|
+
return path3.join(getConfigDir(), "canonry.pid");
|
|
4435
4922
|
}
|
|
4436
4923
|
function isProcessAlive(pid) {
|
|
4437
4924
|
try {
|
|
@@ -4458,8 +4945,8 @@ async function waitForReady(host, port, maxMs = 1e4) {
|
|
|
4458
4945
|
async function startDaemon(opts) {
|
|
4459
4946
|
const pidPath = getPidPath();
|
|
4460
4947
|
const format = opts.format ?? "text";
|
|
4461
|
-
if (
|
|
4462
|
-
const existingPid = parseInt(
|
|
4948
|
+
if (fs4.existsSync(pidPath)) {
|
|
4949
|
+
const existingPid = parseInt(fs4.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
4463
4950
|
if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
|
|
4464
4951
|
throw new CliError({
|
|
4465
4952
|
code: "DAEMON_ALREADY_RUNNING",
|
|
@@ -4470,9 +4957,9 @@ async function startDaemon(opts) {
|
|
|
4470
4957
|
}
|
|
4471
4958
|
});
|
|
4472
4959
|
}
|
|
4473
|
-
|
|
4960
|
+
fs4.unlinkSync(pidPath);
|
|
4474
4961
|
}
|
|
4475
|
-
const cliPath =
|
|
4962
|
+
const cliPath = path3.resolve(new URL(import.meta.url).pathname);
|
|
4476
4963
|
const inSourceMode = new URL(import.meta.url).pathname.endsWith(".ts");
|
|
4477
4964
|
const args = inSourceMode ? ["--import", "tsx", cliPath, "serve"] : [cliPath, "serve"];
|
|
4478
4965
|
if (opts.port) args.push("--port", opts.port);
|
|
@@ -4491,10 +4978,10 @@ async function startDaemon(opts) {
|
|
|
4491
4978
|
});
|
|
4492
4979
|
}
|
|
4493
4980
|
const configDir = getConfigDir();
|
|
4494
|
-
if (!
|
|
4495
|
-
|
|
4981
|
+
if (!fs4.existsSync(configDir)) {
|
|
4982
|
+
fs4.mkdirSync(configDir, { recursive: true });
|
|
4496
4983
|
}
|
|
4497
|
-
|
|
4984
|
+
fs4.writeFileSync(pidPath, String(child.pid), "utf-8");
|
|
4498
4985
|
const port = opts.port ?? "4100";
|
|
4499
4986
|
const host = opts.host ?? "127.0.0.1";
|
|
4500
4987
|
if (format !== "json") {
|
|
@@ -4503,7 +4990,7 @@ async function startDaemon(opts) {
|
|
|
4503
4990
|
const ready = await waitForReady(host, port);
|
|
4504
4991
|
if (!ready) {
|
|
4505
4992
|
try {
|
|
4506
|
-
|
|
4993
|
+
fs4.unlinkSync(pidPath);
|
|
4507
4994
|
} catch {
|
|
4508
4995
|
}
|
|
4509
4996
|
throw new CliError({
|
|
@@ -4535,7 +5022,7 @@ async function startDaemon(opts) {
|
|
|
4535
5022
|
}
|
|
4536
5023
|
function stopDaemon(format = "text") {
|
|
4537
5024
|
const pidPath = getPidPath();
|
|
4538
|
-
if (!
|
|
5025
|
+
if (!fs4.existsSync(pidPath)) {
|
|
4539
5026
|
if (format === "json") {
|
|
4540
5027
|
console.log(JSON.stringify({
|
|
4541
5028
|
stopped: false,
|
|
@@ -4546,7 +5033,7 @@ function stopDaemon(format = "text") {
|
|
|
4546
5033
|
console.log("Canonry is not running (no PID file found)");
|
|
4547
5034
|
return;
|
|
4548
5035
|
}
|
|
4549
|
-
const pid = parseInt(
|
|
5036
|
+
const pid = parseInt(fs4.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
4550
5037
|
if (isNaN(pid)) {
|
|
4551
5038
|
if (format === "json") {
|
|
4552
5039
|
console.log(JSON.stringify({
|
|
@@ -4557,7 +5044,7 @@ function stopDaemon(format = "text") {
|
|
|
4557
5044
|
} else {
|
|
4558
5045
|
console.error("Invalid PID file. Removing it.");
|
|
4559
5046
|
}
|
|
4560
|
-
|
|
5047
|
+
fs4.unlinkSync(pidPath);
|
|
4561
5048
|
return;
|
|
4562
5049
|
}
|
|
4563
5050
|
if (!isProcessAlive(pid)) {
|
|
@@ -4571,12 +5058,12 @@ function stopDaemon(format = "text") {
|
|
|
4571
5058
|
} else {
|
|
4572
5059
|
console.log(`Canonry is not running (stale PID: ${pid}). Cleaning up.`);
|
|
4573
5060
|
}
|
|
4574
|
-
|
|
5061
|
+
fs4.unlinkSync(pidPath);
|
|
4575
5062
|
return;
|
|
4576
5063
|
}
|
|
4577
5064
|
try {
|
|
4578
5065
|
process.kill(pid, "SIGTERM");
|
|
4579
|
-
|
|
5066
|
+
fs4.unlinkSync(pidPath);
|
|
4580
5067
|
if (format === "json") {
|
|
4581
5068
|
console.log(JSON.stringify({
|
|
4582
5069
|
stopped: true,
|
|
@@ -4600,9 +5087,9 @@ function stopDaemon(format = "text") {
|
|
|
4600
5087
|
|
|
4601
5088
|
// src/commands/init.ts
|
|
4602
5089
|
import crypto2 from "crypto";
|
|
4603
|
-
import
|
|
5090
|
+
import fs5 from "fs";
|
|
4604
5091
|
import readline from "readline";
|
|
4605
|
-
import
|
|
5092
|
+
import path4 from "path";
|
|
4606
5093
|
function prompt(question) {
|
|
4607
5094
|
const rl = readline.createInterface({
|
|
4608
5095
|
input: process.stdin,
|
|
@@ -4639,8 +5126,8 @@ async function initCommand(opts) {
|
|
|
4639
5126
|
return;
|
|
4640
5127
|
}
|
|
4641
5128
|
const configDir = getConfigDir();
|
|
4642
|
-
if (!
|
|
4643
|
-
|
|
5129
|
+
if (!fs5.existsSync(configDir)) {
|
|
5130
|
+
fs5.mkdirSync(configDir, { recursive: true });
|
|
4644
5131
|
}
|
|
4645
5132
|
const bootstrapEnv = getBootstrapEnv(process.env, {
|
|
4646
5133
|
GEMINI_API_KEY: opts?.geminiKey,
|
|
@@ -4755,7 +5242,7 @@ async function initCommand(opts) {
|
|
|
4755
5242
|
const rawApiKey = `cnry_${crypto2.randomBytes(16).toString("hex")}`;
|
|
4756
5243
|
const keyHash = crypto2.createHash("sha256").update(rawApiKey).digest("hex");
|
|
4757
5244
|
const keyPrefix = rawApiKey.slice(0, 9);
|
|
4758
|
-
const databasePath =
|
|
5245
|
+
const databasePath = path4.join(configDir, "data.db");
|
|
4759
5246
|
const db = createClient(databasePath);
|
|
4760
5247
|
migrate(db);
|
|
4761
5248
|
db.insert(apiKeys).values({
|
|
@@ -5108,10 +5595,10 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
5108
5595
|
];
|
|
5109
5596
|
|
|
5110
5597
|
// src/cli-commands/wordpress.ts
|
|
5111
|
-
import
|
|
5598
|
+
import fs6 from "fs";
|
|
5112
5599
|
|
|
5113
5600
|
// src/commands/wordpress.ts
|
|
5114
|
-
function
|
|
5601
|
+
function getClient17() {
|
|
5115
5602
|
return createApiClient();
|
|
5116
5603
|
}
|
|
5117
5604
|
async function promptForAppPassword() {
|
|
@@ -5268,7 +5755,7 @@ async function wordpressConnect(project, opts) {
|
|
|
5268
5755
|
details: { project }
|
|
5269
5756
|
});
|
|
5270
5757
|
}
|
|
5271
|
-
const client =
|
|
5758
|
+
const client = getClient17();
|
|
5272
5759
|
const result = await client.wordpressConnect(project, {
|
|
5273
5760
|
url: opts.url,
|
|
5274
5761
|
stagingUrl: opts.stagingUrl,
|
|
@@ -5285,7 +5772,7 @@ async function wordpressConnect(project, opts) {
|
|
|
5285
5772
|
printWordpressStatus(project, result);
|
|
5286
5773
|
}
|
|
5287
5774
|
async function wordpressDisconnect(project, format) {
|
|
5288
|
-
const client =
|
|
5775
|
+
const client = getClient17();
|
|
5289
5776
|
await client.wordpressDisconnect(project);
|
|
5290
5777
|
if (format === "json") {
|
|
5291
5778
|
printJson({ project, disconnected: true });
|
|
@@ -5294,7 +5781,7 @@ async function wordpressDisconnect(project, format) {
|
|
|
5294
5781
|
console.log(`WordPress disconnected from project "${project}".`);
|
|
5295
5782
|
}
|
|
5296
5783
|
async function wordpressStatus(project, format) {
|
|
5297
|
-
const client =
|
|
5784
|
+
const client = getClient17();
|
|
5298
5785
|
const result = await client.wordpressStatus(project);
|
|
5299
5786
|
if (format === "json") {
|
|
5300
5787
|
printJson(result);
|
|
@@ -5303,7 +5790,7 @@ async function wordpressStatus(project, format) {
|
|
|
5303
5790
|
printWordpressStatus(project, result);
|
|
5304
5791
|
}
|
|
5305
5792
|
async function wordpressPages(project, opts) {
|
|
5306
|
-
const client =
|
|
5793
|
+
const client = getClient17();
|
|
5307
5794
|
const result = await client.wordpressPages(project, opts.env);
|
|
5308
5795
|
if (opts.format === "json") {
|
|
5309
5796
|
printJson(result);
|
|
@@ -5312,7 +5799,7 @@ async function wordpressPages(project, opts) {
|
|
|
5312
5799
|
printPages(project, result.env, result.pages);
|
|
5313
5800
|
}
|
|
5314
5801
|
async function wordpressPage(project, slug, opts) {
|
|
5315
|
-
const client =
|
|
5802
|
+
const client = getClient17();
|
|
5316
5803
|
const result = await client.wordpressPage(project, slug, opts.env);
|
|
5317
5804
|
if (opts.format === "json") {
|
|
5318
5805
|
printJson(result);
|
|
@@ -5321,7 +5808,7 @@ async function wordpressPage(project, slug, opts) {
|
|
|
5321
5808
|
printPageDetail(result);
|
|
5322
5809
|
}
|
|
5323
5810
|
async function wordpressCreatePage(project, body) {
|
|
5324
|
-
const client =
|
|
5811
|
+
const client = getClient17();
|
|
5325
5812
|
const result = await client.wordpressCreatePage(project, body);
|
|
5326
5813
|
if (body.format === "json") {
|
|
5327
5814
|
printJson(result);
|
|
@@ -5332,7 +5819,7 @@ async function wordpressCreatePage(project, body) {
|
|
|
5332
5819
|
printPageDetail(result);
|
|
5333
5820
|
}
|
|
5334
5821
|
async function wordpressUpdatePage(project, body) {
|
|
5335
|
-
const client =
|
|
5822
|
+
const client = getClient17();
|
|
5336
5823
|
const result = await client.wordpressUpdatePage(project, body);
|
|
5337
5824
|
if (body.format === "json") {
|
|
5338
5825
|
printJson(result);
|
|
@@ -5343,7 +5830,7 @@ async function wordpressUpdatePage(project, body) {
|
|
|
5343
5830
|
printPageDetail(result);
|
|
5344
5831
|
}
|
|
5345
5832
|
async function wordpressSetMeta(project, body) {
|
|
5346
|
-
const client =
|
|
5833
|
+
const client = getClient17();
|
|
5347
5834
|
const result = await client.wordpressSetMeta(project, body);
|
|
5348
5835
|
if (body.format === "json") {
|
|
5349
5836
|
printJson(result);
|
|
@@ -5353,8 +5840,90 @@ async function wordpressSetMeta(project, body) {
|
|
|
5353
5840
|
`);
|
|
5354
5841
|
printPageDetail(result);
|
|
5355
5842
|
}
|
|
5843
|
+
async function wordpressBulkSetMeta(project, opts) {
|
|
5844
|
+
const fs7 = await import("fs/promises");
|
|
5845
|
+
const path5 = await import("path");
|
|
5846
|
+
const filePath = path5.resolve(opts.from);
|
|
5847
|
+
let raw;
|
|
5848
|
+
try {
|
|
5849
|
+
raw = await fs7.readFile(filePath, "utf8");
|
|
5850
|
+
} catch {
|
|
5851
|
+
throw new CliError({
|
|
5852
|
+
code: "FILE_READ_ERROR",
|
|
5853
|
+
message: `Cannot read file: ${filePath}`,
|
|
5854
|
+
displayMessage: `Error: cannot read file "${opts.from}". Check the path and permissions.`,
|
|
5855
|
+
details: { path: filePath }
|
|
5856
|
+
});
|
|
5857
|
+
}
|
|
5858
|
+
let parsed;
|
|
5859
|
+
try {
|
|
5860
|
+
parsed = JSON.parse(raw);
|
|
5861
|
+
} catch {
|
|
5862
|
+
throw new CliError({
|
|
5863
|
+
code: "INVALID_JSON",
|
|
5864
|
+
message: `File is not valid JSON: ${filePath}`,
|
|
5865
|
+
displayMessage: `Error: "${opts.from}" is not valid JSON.`,
|
|
5866
|
+
details: { path: filePath }
|
|
5867
|
+
});
|
|
5868
|
+
}
|
|
5869
|
+
const entries = Object.entries(parsed).map(([slug, meta]) => ({
|
|
5870
|
+
slug,
|
|
5871
|
+
title: meta.title,
|
|
5872
|
+
description: meta.description,
|
|
5873
|
+
noindex: meta.noindex
|
|
5874
|
+
}));
|
|
5875
|
+
if (entries.length === 0) {
|
|
5876
|
+
throw new CliError({
|
|
5877
|
+
code: "EMPTY_META_FILE",
|
|
5878
|
+
message: "Meta file contains no entries",
|
|
5879
|
+
displayMessage: `Error: "${opts.from}" contains no entries. Expected JSON object keyed by slug.`,
|
|
5880
|
+
details: { path: filePath }
|
|
5881
|
+
});
|
|
5882
|
+
}
|
|
5883
|
+
const client = getClient17();
|
|
5884
|
+
const result = await client.wordpressBulkSetMeta(project, { entries, env: opts.env });
|
|
5885
|
+
if (opts.format === "json") {
|
|
5886
|
+
printJson(result);
|
|
5887
|
+
return;
|
|
5888
|
+
}
|
|
5889
|
+
const applied = result.results.filter((r) => r.status === "applied");
|
|
5890
|
+
const skipped = result.results.filter((r) => r.status === "skipped");
|
|
5891
|
+
const manual = result.results.filter((r) => r.status === "manual");
|
|
5892
|
+
console.log(`Bulk SEO meta update (${result.env}, strategy: ${result.strategy}):
|
|
5893
|
+
`);
|
|
5894
|
+
if (applied.length > 0) {
|
|
5895
|
+
console.log(` Applied (${applied.length}):`);
|
|
5896
|
+
for (const r of applied) {
|
|
5897
|
+
console.log(` ${r.slug}`);
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
if (skipped.length > 0) {
|
|
5901
|
+
console.log(`
|
|
5902
|
+
Skipped (${skipped.length}):`);
|
|
5903
|
+
for (const r of skipped) {
|
|
5904
|
+
console.log(` ${r.slug}: ${r.error ?? "unknown reason"}`);
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5907
|
+
if (manual.length > 0) {
|
|
5908
|
+
console.log(`
|
|
5909
|
+
Manual action required (${manual.length}):`);
|
|
5910
|
+
console.log(" No SEO plugin with REST-writable meta fields was detected.");
|
|
5911
|
+
console.log(" Install Yoast SEO, Rank Math, or AIOSEO, or update these pages manually:\n");
|
|
5912
|
+
for (const r of manual) {
|
|
5913
|
+
if (r.manualAssist) {
|
|
5914
|
+
console.log(` ${r.slug}:`);
|
|
5915
|
+
console.log(` Admin: ${r.manualAssist.adminUrl ?? "-"}`);
|
|
5916
|
+
console.log(` Values: ${r.manualAssist.content}`);
|
|
5917
|
+
} else {
|
|
5918
|
+
console.log(` ${r.slug}`);
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5922
|
+
console.log(`
|
|
5923
|
+
Total: ${applied.length} applied, ${skipped.length} skipped, ${manual.length} manual`);
|
|
5924
|
+
}
|
|
5356
5925
|
async function wordpressSchema(project, slug, opts) {
|
|
5357
|
-
const client =
|
|
5926
|
+
const client = getClient17();
|
|
5358
5927
|
const result = await client.wordpressSchema(project, slug, opts.env);
|
|
5359
5928
|
if (opts.format === "json") {
|
|
5360
5929
|
printJson(result);
|
|
@@ -5365,7 +5934,7 @@ async function wordpressSchema(project, slug, opts) {
|
|
|
5365
5934
|
printSchemaBlocks(result.blocks);
|
|
5366
5935
|
}
|
|
5367
5936
|
async function wordpressSetSchema(project, body) {
|
|
5368
|
-
const client =
|
|
5937
|
+
const client = getClient17();
|
|
5369
5938
|
const result = await client.wordpressSetSchema(project, body);
|
|
5370
5939
|
if (body.format === "json") {
|
|
5371
5940
|
printJson(result);
|
|
@@ -5373,8 +5942,170 @@ async function wordpressSetSchema(project, body) {
|
|
|
5373
5942
|
}
|
|
5374
5943
|
printManualAssist(`Schema update for "${body.slug}"`, result);
|
|
5375
5944
|
}
|
|
5945
|
+
async function wordpressSchemaDeploy(project, opts) {
|
|
5946
|
+
const fs7 = await import("fs/promises");
|
|
5947
|
+
const path5 = await import("path");
|
|
5948
|
+
const yaml = await import("yaml").catch(() => null);
|
|
5949
|
+
const filePath = path5.resolve(opts.profile);
|
|
5950
|
+
let raw;
|
|
5951
|
+
try {
|
|
5952
|
+
raw = await fs7.readFile(filePath, "utf8");
|
|
5953
|
+
} catch {
|
|
5954
|
+
throw new CliError({
|
|
5955
|
+
code: "FILE_READ_ERROR",
|
|
5956
|
+
message: `Cannot read file: ${filePath}`,
|
|
5957
|
+
displayMessage: `Error: cannot read file "${opts.profile}". Check the path and permissions.`,
|
|
5958
|
+
details: { path: filePath }
|
|
5959
|
+
});
|
|
5960
|
+
}
|
|
5961
|
+
let parsed;
|
|
5962
|
+
try {
|
|
5963
|
+
if (yaml?.parse) {
|
|
5964
|
+
parsed = yaml.parse(raw);
|
|
5965
|
+
} else {
|
|
5966
|
+
parsed = JSON.parse(raw);
|
|
5967
|
+
}
|
|
5968
|
+
} catch {
|
|
5969
|
+
throw new CliError({
|
|
5970
|
+
code: "INVALID_PROFILE",
|
|
5971
|
+
message: `File is not valid YAML or JSON: ${filePath}`,
|
|
5972
|
+
displayMessage: `Error: "${opts.profile}" is not valid YAML or JSON.`,
|
|
5973
|
+
details: { path: filePath }
|
|
5974
|
+
});
|
|
5975
|
+
}
|
|
5976
|
+
const profile = parsed;
|
|
5977
|
+
if (!profile?.business?.name || !profile?.pages || Object.keys(profile.pages).length === 0) {
|
|
5978
|
+
throw new CliError({
|
|
5979
|
+
code: "INVALID_PROFILE",
|
|
5980
|
+
message: "Profile must have business.name and non-empty pages",
|
|
5981
|
+
displayMessage: "Error: profile file must contain business.name and at least one page entry.",
|
|
5982
|
+
details: { path: filePath }
|
|
5983
|
+
});
|
|
5984
|
+
}
|
|
5985
|
+
const client = getClient17();
|
|
5986
|
+
const result = await client.wordpressSchemaDeploy(project, { profile: parsed, env: opts.env });
|
|
5987
|
+
if (opts.format === "json") {
|
|
5988
|
+
printJson(result);
|
|
5989
|
+
return;
|
|
5990
|
+
}
|
|
5991
|
+
console.log(`Schema deploy (${result.env}):
|
|
5992
|
+
`);
|
|
5993
|
+
for (const r of result.results) {
|
|
5994
|
+
const types = r.schemasInjected?.join(", ") ?? "";
|
|
5995
|
+
switch (r.status) {
|
|
5996
|
+
case "deployed":
|
|
5997
|
+
console.log(` ${r.slug}: deployed (${types})`);
|
|
5998
|
+
break;
|
|
5999
|
+
case "stripped":
|
|
6000
|
+
console.log(` ${r.slug}: STRIPPED \u2014 WordPress removed <script> tags. Manual action required.`);
|
|
6001
|
+
if (r.manualAssist) {
|
|
6002
|
+
console.log(` Admin: ${r.manualAssist.adminUrl ?? "-"}`);
|
|
6003
|
+
for (const step of r.manualAssist.nextSteps) {
|
|
6004
|
+
console.log(` - ${step}`);
|
|
6005
|
+
}
|
|
6006
|
+
}
|
|
6007
|
+
break;
|
|
6008
|
+
case "skipped":
|
|
6009
|
+
console.log(` ${r.slug}: skipped \u2014 ${r.error ?? "unknown"}`);
|
|
6010
|
+
break;
|
|
6011
|
+
case "failed":
|
|
6012
|
+
console.log(` ${r.slug}: FAILED \u2014 ${r.error ?? "unknown"}`);
|
|
6013
|
+
break;
|
|
6014
|
+
}
|
|
6015
|
+
}
|
|
6016
|
+
const deployed = result.results.filter((r) => r.status === "deployed").length;
|
|
6017
|
+
const stripped = result.results.filter((r) => r.status === "stripped").length;
|
|
6018
|
+
const skipped = result.results.filter((r) => r.status === "skipped").length;
|
|
6019
|
+
const failed = result.results.filter((r) => r.status === "failed").length;
|
|
6020
|
+
console.log(`
|
|
6021
|
+
Total: ${deployed} deployed, ${stripped} stripped, ${skipped} skipped, ${failed} failed`);
|
|
6022
|
+
}
|
|
6023
|
+
async function wordpressSchemaStatus(project, opts) {
|
|
6024
|
+
const client = getClient17();
|
|
6025
|
+
const result = await client.wordpressSchemaStatus(project, opts.env);
|
|
6026
|
+
if (opts.format === "json") {
|
|
6027
|
+
printJson(result);
|
|
6028
|
+
return;
|
|
6029
|
+
}
|
|
6030
|
+
console.log(`Schema status (${result.env}):
|
|
6031
|
+
`);
|
|
6032
|
+
if (result.pages.length === 0) {
|
|
6033
|
+
console.log(" No pages found.");
|
|
6034
|
+
return;
|
|
6035
|
+
}
|
|
6036
|
+
const slugWidth = Math.max(4, ...result.pages.map((p) => p.slug.length));
|
|
6037
|
+
console.log(` ${"SLUG".padEnd(slugWidth)} CANONRY THIRD-PARTY`);
|
|
6038
|
+
console.log(` ${"\u2500".repeat(slugWidth)} ${"\u2500".repeat(17)} ${"\u2500".repeat(20)}`);
|
|
6039
|
+
for (const page of result.pages) {
|
|
6040
|
+
const canonry = page.canonrySchemas.length > 0 ? page.canonrySchemas.join(", ") : "-";
|
|
6041
|
+
const thirdParty = page.thirdPartySchemas.length > 0 ? page.thirdPartySchemas.join(", ") : "-";
|
|
6042
|
+
console.log(` ${page.slug.padEnd(slugWidth)} ${canonry.padEnd(17)} ${thirdParty}`);
|
|
6043
|
+
}
|
|
6044
|
+
}
|
|
6045
|
+
async function wordpressOnboard(project, opts) {
|
|
6046
|
+
const appPassword = opts.appPassword ?? await promptForAppPassword();
|
|
6047
|
+
if (!appPassword) {
|
|
6048
|
+
throw new CliError({
|
|
6049
|
+
code: "WORDPRESS_APP_PASSWORD_REQUIRED",
|
|
6050
|
+
message: "WordPress Application Password is required",
|
|
6051
|
+
displayMessage: "Error: WordPress Application Password is required (pass --app-password or enter interactively).",
|
|
6052
|
+
details: { project }
|
|
6053
|
+
});
|
|
6054
|
+
}
|
|
6055
|
+
let profileData;
|
|
6056
|
+
if (opts.profile) {
|
|
6057
|
+
const fs7 = await import("fs/promises");
|
|
6058
|
+
const path5 = await import("path");
|
|
6059
|
+
const yaml = await import("yaml").catch(() => null);
|
|
6060
|
+
const filePath = path5.resolve(opts.profile);
|
|
6061
|
+
let raw;
|
|
6062
|
+
try {
|
|
6063
|
+
raw = await fs7.readFile(filePath, "utf8");
|
|
6064
|
+
} catch {
|
|
6065
|
+
throw new CliError({
|
|
6066
|
+
code: "FILE_READ_ERROR",
|
|
6067
|
+
message: `Cannot read file: ${filePath}`,
|
|
6068
|
+
displayMessage: `Error: cannot read file "${opts.profile}".`,
|
|
6069
|
+
details: { path: filePath }
|
|
6070
|
+
});
|
|
6071
|
+
}
|
|
6072
|
+
try {
|
|
6073
|
+
profileData = yaml?.parse ? yaml.parse(raw) : JSON.parse(raw);
|
|
6074
|
+
} catch {
|
|
6075
|
+
throw new CliError({
|
|
6076
|
+
code: "INVALID_PROFILE",
|
|
6077
|
+
message: `File is not valid YAML or JSON: ${filePath}`,
|
|
6078
|
+
displayMessage: `Error: "${opts.profile}" is not valid YAML or JSON.`,
|
|
6079
|
+
details: { path: filePath }
|
|
6080
|
+
});
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
const client = getClient17();
|
|
6084
|
+
const result = await client.wordpressOnboard(project, {
|
|
6085
|
+
url: opts.url,
|
|
6086
|
+
username: opts.user,
|
|
6087
|
+
appPassword,
|
|
6088
|
+
stagingUrl: opts.stagingUrl,
|
|
6089
|
+
defaultEnv: opts.defaultEnv,
|
|
6090
|
+
profile: profileData,
|
|
6091
|
+
skipSchema: opts.skipSchema,
|
|
6092
|
+
skipSubmit: opts.skipSubmit
|
|
6093
|
+
});
|
|
6094
|
+
if (opts.format === "json") {
|
|
6095
|
+
printJson(result);
|
|
6096
|
+
return;
|
|
6097
|
+
}
|
|
6098
|
+
console.log(`WordPress onboarding for "${project}":
|
|
6099
|
+
`);
|
|
6100
|
+
for (const step of result.steps) {
|
|
6101
|
+
const icon = step.status === "completed" ? "+" : step.status === "skipped" ? "-" : "x";
|
|
6102
|
+
console.log(` [${icon}] ${step.name}: ${step.status}`);
|
|
6103
|
+
if (step.summary) console.log(` ${step.summary}`);
|
|
6104
|
+
if (step.error) console.log(` Error: ${step.error}`);
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
5376
6107
|
async function wordpressLlmsTxt(project, opts) {
|
|
5377
|
-
const client =
|
|
6108
|
+
const client = getClient17();
|
|
5378
6109
|
const result = await client.wordpressLlmsTxt(project, opts.env);
|
|
5379
6110
|
if (opts.format === "json") {
|
|
5380
6111
|
printJson(result);
|
|
@@ -5385,7 +6116,7 @@ async function wordpressLlmsTxt(project, opts) {
|
|
|
5385
6116
|
console.log(result.content ?? "(not found)");
|
|
5386
6117
|
}
|
|
5387
6118
|
async function wordpressSetLlmsTxt(project, body) {
|
|
5388
|
-
const client =
|
|
6119
|
+
const client = getClient17();
|
|
5389
6120
|
const result = await client.wordpressSetLlmsTxt(project, body);
|
|
5390
6121
|
if (body.format === "json") {
|
|
5391
6122
|
printJson(result);
|
|
@@ -5394,7 +6125,7 @@ async function wordpressSetLlmsTxt(project, body) {
|
|
|
5394
6125
|
printManualAssist(`llms.txt update for "${project}"`, result);
|
|
5395
6126
|
}
|
|
5396
6127
|
async function wordpressAudit(project, opts) {
|
|
5397
|
-
const client =
|
|
6128
|
+
const client = getClient17();
|
|
5398
6129
|
const result = await client.wordpressAudit(project, opts.env);
|
|
5399
6130
|
if (opts.format === "json") {
|
|
5400
6131
|
printJson(result);
|
|
@@ -5408,7 +6139,7 @@ async function wordpressAudit(project, opts) {
|
|
|
5408
6139
|
printAuditIssues(result.issues);
|
|
5409
6140
|
}
|
|
5410
6141
|
async function wordpressDiff(project, slug, format) {
|
|
5411
|
-
const client =
|
|
6142
|
+
const client = getClient17();
|
|
5412
6143
|
const result = await client.wordpressDiff(project, slug);
|
|
5413
6144
|
if (format === "json") {
|
|
5414
6145
|
printJson(result);
|
|
@@ -5417,7 +6148,7 @@ async function wordpressDiff(project, slug, format) {
|
|
|
5417
6148
|
printDiff(result);
|
|
5418
6149
|
}
|
|
5419
6150
|
async function wordpressStagingStatus(project, format) {
|
|
5420
|
-
const client =
|
|
6151
|
+
const client = getClient17();
|
|
5421
6152
|
const result = await client.wordpressStagingStatus(project);
|
|
5422
6153
|
if (format === "json") {
|
|
5423
6154
|
printJson(result);
|
|
@@ -5431,7 +6162,7 @@ async function wordpressStagingStatus(project, format) {
|
|
|
5431
6162
|
console.log(` Admin URL: ${result.adminUrl}`);
|
|
5432
6163
|
}
|
|
5433
6164
|
async function wordpressStagingPush(project, format) {
|
|
5434
|
-
const client =
|
|
6165
|
+
const client = getClient17();
|
|
5435
6166
|
const result = await client.wordpressStagingPush(project);
|
|
5436
6167
|
if (format === "json") {
|
|
5437
6168
|
printJson(result);
|
|
@@ -5478,7 +6209,7 @@ function resolveContent(input, command, usage, options) {
|
|
|
5478
6209
|
}
|
|
5479
6210
|
if (contentFile) {
|
|
5480
6211
|
try {
|
|
5481
|
-
return
|
|
6212
|
+
return fs6.readFileSync(contentFile, "utf-8");
|
|
5482
6213
|
} catch (error) {
|
|
5483
6214
|
const message = error instanceof Error ? error.message : String(error);
|
|
5484
6215
|
throw usageError(`Error: could not read --content-file "${contentFile}": ${message}`, {
|
|
@@ -5650,15 +6381,27 @@ var WORDPRESS_CLI_COMMANDS = [
|
|
|
5650
6381
|
},
|
|
5651
6382
|
{
|
|
5652
6383
|
path: ["wordpress", "set-meta"],
|
|
5653
|
-
usage: "canonry wordpress set-meta <project> <slug> [--title <title>] [--description <text>] [--noindex|--index] [--live|--staging] [--format json]",
|
|
6384
|
+
usage: "canonry wordpress set-meta <project> <slug> [--title <title>] [--description <text>] [--noindex|--index] [--from <file>] [--live|--staging] [--format json]",
|
|
5654
6385
|
options: {
|
|
5655
6386
|
title: stringOption(),
|
|
5656
6387
|
description: stringOption(),
|
|
5657
6388
|
noindex: { type: "boolean", default: false },
|
|
5658
6389
|
index: { type: "boolean", default: false },
|
|
6390
|
+
from: stringOption(),
|
|
5659
6391
|
...envOptions
|
|
5660
6392
|
},
|
|
5661
6393
|
run: async (input) => {
|
|
6394
|
+
const fromFile = getString(input.values, "from");
|
|
6395
|
+
if (fromFile) {
|
|
6396
|
+
const usage2 = "canonry wordpress set-meta <project> --from <file> [--live|--staging] [--format json]";
|
|
6397
|
+
const project2 = requireProject(input, "wordpress.set-meta", usage2);
|
|
6398
|
+
await wordpressBulkSetMeta(project2, {
|
|
6399
|
+
from: fromFile,
|
|
6400
|
+
env: resolveEnv(input, "wordpress.set-meta", usage2),
|
|
6401
|
+
format: input.format
|
|
6402
|
+
});
|
|
6403
|
+
return;
|
|
6404
|
+
}
|
|
5662
6405
|
const usage = "canonry wordpress set-meta <project> <slug> [--title <title>] [--description <text>] [--noindex|--index] [--live|--staging] [--format json]";
|
|
5663
6406
|
const project = requireProject(input, "wordpress.set-meta", usage);
|
|
5664
6407
|
const slug = requirePositional(input, 1, {
|
|
@@ -5676,6 +6419,41 @@ var WORDPRESS_CLI_COMMANDS = [
|
|
|
5676
6419
|
});
|
|
5677
6420
|
}
|
|
5678
6421
|
},
|
|
6422
|
+
{
|
|
6423
|
+
path: ["wordpress", "schema", "deploy"],
|
|
6424
|
+
usage: "canonry wordpress schema deploy <project> --profile <file> [--live|--staging] [--format json]",
|
|
6425
|
+
options: {
|
|
6426
|
+
profile: stringOption(),
|
|
6427
|
+
...envOptions
|
|
6428
|
+
},
|
|
6429
|
+
run: async (input) => {
|
|
6430
|
+
const usage = "canonry wordpress schema deploy <project> --profile <file> [--live|--staging] [--format json]";
|
|
6431
|
+
const project = requireProject(input, "wordpress.schema.deploy", usage);
|
|
6432
|
+
const profile = requireStringOption(input, "profile", {
|
|
6433
|
+
message: "--profile is required",
|
|
6434
|
+
command: "wordpress.schema.deploy",
|
|
6435
|
+
usage
|
|
6436
|
+
});
|
|
6437
|
+
await wordpressSchemaDeploy(project, {
|
|
6438
|
+
profile,
|
|
6439
|
+
env: resolveEnv(input, "wordpress.schema.deploy", usage),
|
|
6440
|
+
format: input.format
|
|
6441
|
+
});
|
|
6442
|
+
}
|
|
6443
|
+
},
|
|
6444
|
+
{
|
|
6445
|
+
path: ["wordpress", "schema", "status"],
|
|
6446
|
+
usage: "canonry wordpress schema status <project> [--live|--staging] [--format json]",
|
|
6447
|
+
options: envOptions,
|
|
6448
|
+
run: async (input) => {
|
|
6449
|
+
const usage = "canonry wordpress schema status <project> [--live|--staging] [--format json]";
|
|
6450
|
+
const project = requireProject(input, "wordpress.schema.status", usage);
|
|
6451
|
+
await wordpressSchemaStatus(project, {
|
|
6452
|
+
env: resolveEnv(input, "wordpress.schema.status", usage),
|
|
6453
|
+
format: input.format
|
|
6454
|
+
});
|
|
6455
|
+
}
|
|
6456
|
+
},
|
|
5679
6457
|
{
|
|
5680
6458
|
path: ["wordpress", "schema"],
|
|
5681
6459
|
usage: "canonry wordpress schema <project> <slug> [--live|--staging] [--format json]",
|
|
@@ -5757,6 +6535,45 @@ var WORDPRESS_CLI_COMMANDS = [
|
|
|
5757
6535
|
});
|
|
5758
6536
|
}
|
|
5759
6537
|
},
|
|
6538
|
+
{
|
|
6539
|
+
path: ["wordpress", "onboard"],
|
|
6540
|
+
usage: "canonry wordpress onboard <project> --url <url> --user <user> [--app-password <pw>] [--profile <file>] [--skip-schema] [--skip-submit] [--live|--staging] [--format json]",
|
|
6541
|
+
options: {
|
|
6542
|
+
url: stringOption(),
|
|
6543
|
+
user: stringOption(),
|
|
6544
|
+
"app-password": stringOption(),
|
|
6545
|
+
"staging-url": stringOption(),
|
|
6546
|
+
profile: stringOption(),
|
|
6547
|
+
"skip-schema": { type: "boolean", default: false },
|
|
6548
|
+
"skip-submit": { type: "boolean", default: false },
|
|
6549
|
+
...envOptions
|
|
6550
|
+
},
|
|
6551
|
+
run: async (input) => {
|
|
6552
|
+
const usage = "canonry wordpress onboard <project> --url <url> --user <user> [--app-password <pw>] [--profile <file>] [--format json]";
|
|
6553
|
+
const project = requireProject(input, "wordpress.onboard", usage);
|
|
6554
|
+
const url = requireStringOption(input, "url", {
|
|
6555
|
+
message: "--url is required",
|
|
6556
|
+
command: "wordpress.onboard",
|
|
6557
|
+
usage
|
|
6558
|
+
});
|
|
6559
|
+
const user = requireStringOption(input, "user", {
|
|
6560
|
+
message: "--user is required",
|
|
6561
|
+
command: "wordpress.onboard",
|
|
6562
|
+
usage
|
|
6563
|
+
});
|
|
6564
|
+
await wordpressOnboard(project, {
|
|
6565
|
+
url,
|
|
6566
|
+
user,
|
|
6567
|
+
appPassword: getString(input.values, "app-password"),
|
|
6568
|
+
stagingUrl: getString(input.values, "staging-url"),
|
|
6569
|
+
defaultEnv: resolveEnv(input, "wordpress.onboard", usage),
|
|
6570
|
+
profile: getString(input.values, "profile"),
|
|
6571
|
+
skipSchema: getBoolean(input.values, "skip-schema"),
|
|
6572
|
+
skipSubmit: getBoolean(input.values, "skip-submit"),
|
|
6573
|
+
format: input.format
|
|
6574
|
+
});
|
|
6575
|
+
}
|
|
6576
|
+
},
|
|
5760
6577
|
{
|
|
5761
6578
|
path: ["wordpress", "audit"],
|
|
5762
6579
|
usage: "canonry wordpress audit <project> [--live|--staging] [--format json]",
|
|
@@ -5831,6 +6648,7 @@ var REGISTERED_CLI_COMMANDS = [
|
|
|
5831
6648
|
...KEYWORD_CLI_COMMANDS,
|
|
5832
6649
|
...COMPETITOR_CLI_COMMANDS,
|
|
5833
6650
|
...SETTINGS_CLI_COMMANDS,
|
|
6651
|
+
...SNAPSHOT_CLI_COMMANDS,
|
|
5834
6652
|
...RUN_CLI_COMMANDS,
|
|
5835
6653
|
...OPERATOR_CLI_COMMANDS,
|
|
5836
6654
|
...SCHEDULE_CLI_COMMANDS,
|
|
@@ -5870,6 +6688,7 @@ Usage:
|
|
|
5870
6688
|
canonry keyword generate <project> Auto-generate key phrases (--provider, --count, --save)
|
|
5871
6689
|
canonry competitor add <project> <domain> Add competitors
|
|
5872
6690
|
canonry competitor list <project> List competitors
|
|
6691
|
+
canonry snapshot <company> --domain <domain> One-shot AI perception report
|
|
5873
6692
|
canonry run <project> Trigger a run (all providers)
|
|
5874
6693
|
canonry run <project> --provider <name> Trigger a run for a specific provider
|
|
5875
6694
|
canonry run <project> --location <label> Run with a specific location
|
|
@@ -5933,8 +6752,12 @@ Usage:
|
|
|
5933
6752
|
canonry wordpress create-page <project> Create a WordPress page (--title, --slug, --content/--content-file)
|
|
5934
6753
|
canonry wordpress update-page <project> <slug> Update a WordPress page (--content/--content-file)
|
|
5935
6754
|
canonry wordpress set-meta <project> <slug> Update REST-exposed SEO meta
|
|
6755
|
+
canonry wordpress set-meta <project> --from <file> Bulk update SEO meta from JSON file
|
|
5936
6756
|
canonry wordpress schema <project> <slug> Read rendered JSON-LD schema
|
|
6757
|
+
canonry wordpress schema deploy <project> --profile <file> Deploy JSON-LD schema to pages
|
|
6758
|
+
canonry wordpress schema status <project> Show schema status per page
|
|
5937
6759
|
canonry wordpress set-schema <project> <slug> Generate manual schema handoff
|
|
6760
|
+
canonry wordpress onboard <project> --url <url> --user <user> Full onboarding workflow
|
|
5938
6761
|
canonry wordpress llms-txt <project> Read /llms.txt
|
|
5939
6762
|
canonry wordpress set-llms-txt <project> Generate manual llms.txt handoff
|
|
5940
6763
|
canonry wordpress audit <project> Audit WordPress pages for SEO/content issues
|
|
@@ -5971,6 +6794,9 @@ Options:
|
|
|
5971
6794
|
--language <lang> Language code (default: en)
|
|
5972
6795
|
--provider <name> Provider to use (gemini, openai, claude, perplexity, local, cdp:chatgpt, or cdp for all CDP targets)
|
|
5973
6796
|
--format <fmt> Output format: text (default) or json
|
|
6797
|
+
--phrases <list> Comma-separated category queries (snapshot)
|
|
6798
|
+
--competitors <list> Comma-separated competitor hints (snapshot)
|
|
6799
|
+
--pdf <path> Write a PDF snapshot report to a file
|
|
5974
6800
|
--location <label> Run with a specific configured location
|
|
5975
6801
|
--all-locations Run for every configured location
|
|
5976
6802
|
--no-location Explicitly skip location context
|