@ainyc/canonry 2.6.0 → 2.9.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/README.md +23 -2
- package/assets/assets/{index-BsNi8T7D.js → index-U1lA1GKP.js} +29 -29
- package/assets/index.html +1 -1
- package/dist/{chunk-3DUTT6H2.js → chunk-FPZUQADO.js} +11 -3
- package/dist/{chunk-LF4O276A.js → chunk-MGBXRWLX.js} +159 -27
- package/dist/cli.js +484 -73
- package/dist/index.js +2 -2
- package/dist/mcp.js +282 -14
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
setGoogleAuthConfig,
|
|
18
18
|
showFirstRunNotice,
|
|
19
19
|
trackEvent
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-MGBXRWLX.js";
|
|
21
21
|
import {
|
|
22
22
|
CcReleaseSyncStatuses,
|
|
23
23
|
CliError,
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
saveConfig,
|
|
41
41
|
saveConfigPatch,
|
|
42
42
|
usageError
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-FPZUQADO.js";
|
|
44
44
|
import {
|
|
45
45
|
apiKeys,
|
|
46
46
|
competitors,
|
|
@@ -61,9 +61,9 @@ import { parseArgs } from "util";
|
|
|
61
61
|
function commandId(spec) {
|
|
62
62
|
return spec.path.join(".");
|
|
63
63
|
}
|
|
64
|
-
function matchesPath(args,
|
|
65
|
-
if (args.length <
|
|
66
|
-
return
|
|
64
|
+
function matchesPath(args, path8) {
|
|
65
|
+
if (args.length < path8.length) return false;
|
|
66
|
+
return path8.every((segment, index) => args[index] === segment);
|
|
67
67
|
}
|
|
68
68
|
function withFormatOption(options) {
|
|
69
69
|
if (!options) {
|
|
@@ -1569,9 +1569,9 @@ async function gaConnect(project, opts) {
|
|
|
1569
1569
|
propertyId: opts.propertyId
|
|
1570
1570
|
};
|
|
1571
1571
|
if (opts.keyFile) {
|
|
1572
|
-
const
|
|
1572
|
+
const fs9 = await import("fs");
|
|
1573
1573
|
try {
|
|
1574
|
-
const content =
|
|
1574
|
+
const content = fs9.readFileSync(opts.keyFile, "utf-8");
|
|
1575
1575
|
JSON.parse(content);
|
|
1576
1576
|
body.keyJson = content;
|
|
1577
1577
|
} catch (e) {
|
|
@@ -2187,13 +2187,15 @@ async function addCompetitors(project, domains, format) {
|
|
|
2187
2187
|
const client = getClient5();
|
|
2188
2188
|
const existing = await client.listCompetitors(project);
|
|
2189
2189
|
const existingDomains = existing.map((c) => c.domain);
|
|
2190
|
-
const
|
|
2191
|
-
const
|
|
2192
|
-
await client.
|
|
2190
|
+
const existingSet = new Set(existingDomains);
|
|
2191
|
+
const requested = new Set(uniqueStrings(domains));
|
|
2192
|
+
const current = await client.appendCompetitors(project, domains);
|
|
2193
|
+
const currentDomains = current.map((c) => c.domain);
|
|
2194
|
+
const addedDomains = currentDomains.filter((domain) => requested.has(domain) && !existingSet.has(domain));
|
|
2193
2195
|
if (format === "json") {
|
|
2194
2196
|
console.log(JSON.stringify({
|
|
2195
2197
|
project,
|
|
2196
|
-
domains:
|
|
2198
|
+
domains: currentDomains,
|
|
2197
2199
|
addedDomains,
|
|
2198
2200
|
addedCount: addedDomains.length
|
|
2199
2201
|
}, null, 2));
|
|
@@ -2205,6 +2207,35 @@ async function addCompetitors(project, domains, format) {
|
|
|
2205
2207
|
console.log(`Added ${addedDomains.length} competitor(s) to "${project}".`);
|
|
2206
2208
|
}
|
|
2207
2209
|
}
|
|
2210
|
+
async function removeCompetitors(project, domains, format) {
|
|
2211
|
+
const client = getClient5();
|
|
2212
|
+
const existing = await client.listCompetitors(project);
|
|
2213
|
+
const existingDomains = existing.map((c) => c.domain);
|
|
2214
|
+
const requested = new Set(uniqueStrings(domains));
|
|
2215
|
+
const current = await client.deleteCompetitors(project, domains);
|
|
2216
|
+
const currentSet = new Set(current.map((c) => c.domain));
|
|
2217
|
+
const removedDomains = existingDomains.filter((domain) => requested.has(domain) && !currentSet.has(domain));
|
|
2218
|
+
if (format === "json") {
|
|
2219
|
+
console.log(JSON.stringify({
|
|
2220
|
+
project,
|
|
2221
|
+
domains: current.map((c) => c.domain),
|
|
2222
|
+
removedDomains,
|
|
2223
|
+
removedCount: removedDomains.length
|
|
2224
|
+
}, null, 2));
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
console.log(`Removed ${removedDomains.length} competitor(s) from "${project}".`);
|
|
2228
|
+
}
|
|
2229
|
+
function uniqueStrings(values) {
|
|
2230
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2231
|
+
const result = [];
|
|
2232
|
+
for (const value of values) {
|
|
2233
|
+
if (seen.has(value)) continue;
|
|
2234
|
+
seen.add(value);
|
|
2235
|
+
result.push(value);
|
|
2236
|
+
}
|
|
2237
|
+
return result;
|
|
2238
|
+
}
|
|
2208
2239
|
async function listCompetitors(project, format) {
|
|
2209
2240
|
const client = getClient5();
|
|
2210
2241
|
const comps = await client.listCompetitors(project);
|
|
@@ -2243,6 +2274,42 @@ var COMPETITOR_CLI_COMMANDS = [
|
|
|
2243
2274
|
await addCompetitors(project, domains, input.format);
|
|
2244
2275
|
}
|
|
2245
2276
|
},
|
|
2277
|
+
{
|
|
2278
|
+
path: ["competitor", "remove"],
|
|
2279
|
+
usage: "canonry competitor remove <project> <domain...> [--format json]",
|
|
2280
|
+
run: async (input) => {
|
|
2281
|
+
const project = requireProject(input, "competitor.remove", "canonry competitor remove <project> <domain...> [--format json]");
|
|
2282
|
+
const domains = input.positionals.slice(1);
|
|
2283
|
+
if (domains.length === 0) {
|
|
2284
|
+
throw usageError("Error: project name and at least one domain required\nUsage: canonry competitor remove <project> <domain...> [--format json]", {
|
|
2285
|
+
message: "project name and at least one domain required",
|
|
2286
|
+
details: {
|
|
2287
|
+
command: "competitor.remove",
|
|
2288
|
+
usage: "canonry competitor remove <project> <domain...> [--format json]"
|
|
2289
|
+
}
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
await removeCompetitors(project, domains, input.format);
|
|
2293
|
+
}
|
|
2294
|
+
},
|
|
2295
|
+
{
|
|
2296
|
+
path: ["competitor", "delete"],
|
|
2297
|
+
usage: "canonry competitor delete <project> <domain...> [--format json]",
|
|
2298
|
+
run: async (input) => {
|
|
2299
|
+
const project = requireProject(input, "competitor.delete", "canonry competitor delete <project> <domain...> [--format json]");
|
|
2300
|
+
const domains = input.positionals.slice(1);
|
|
2301
|
+
if (domains.length === 0) {
|
|
2302
|
+
throw usageError("Error: project name and at least one domain required\nUsage: canonry competitor delete <project> <domain...> [--format json]", {
|
|
2303
|
+
message: "project name and at least one domain required",
|
|
2304
|
+
details: {
|
|
2305
|
+
command: "competitor.delete",
|
|
2306
|
+
usage: "canonry competitor delete <project> <domain...> [--format json]"
|
|
2307
|
+
}
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
await removeCompetitors(project, domains, input.format);
|
|
2311
|
+
}
|
|
2312
|
+
},
|
|
2246
2313
|
{
|
|
2247
2314
|
path: ["competitor", "list"],
|
|
2248
2315
|
usage: "canonry competitor list <project> [--format json]",
|
|
@@ -2253,12 +2320,12 @@ var COMPETITOR_CLI_COMMANDS = [
|
|
|
2253
2320
|
},
|
|
2254
2321
|
{
|
|
2255
2322
|
path: ["competitor"],
|
|
2256
|
-
usage: "canonry competitor <add|list> <project> [args]",
|
|
2323
|
+
usage: "canonry competitor <add|remove|delete|list> <project> [args]",
|
|
2257
2324
|
run: async (input) => {
|
|
2258
2325
|
unknownSubcommand(input.positionals[0], {
|
|
2259
2326
|
command: "competitor",
|
|
2260
|
-
usage: "canonry competitor <add|list> <project> [args]",
|
|
2261
|
-
available: ["add", "list"]
|
|
2327
|
+
usage: "canonry competitor <add|remove|delete|list> <project> [args]",
|
|
2328
|
+
available: ["add", "remove", "delete", "list"]
|
|
2262
2329
|
});
|
|
2263
2330
|
}
|
|
2264
2331
|
}
|
|
@@ -3124,6 +3191,19 @@ async function addKeywords(project, keywords, format) {
|
|
|
3124
3191
|
}
|
|
3125
3192
|
console.log(`Added ${keywords.length} key phrase(s) to "${project}".`);
|
|
3126
3193
|
}
|
|
3194
|
+
async function replaceKeywords(project, keywords, format) {
|
|
3195
|
+
const client = getClient7();
|
|
3196
|
+
await client.putKeywords(project, keywords);
|
|
3197
|
+
if (format === "json") {
|
|
3198
|
+
console.log(JSON.stringify({
|
|
3199
|
+
project,
|
|
3200
|
+
keywords,
|
|
3201
|
+
replacedCount: keywords.length
|
|
3202
|
+
}, null, 2));
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
3205
|
+
console.log(`Set ${keywords.length} key phrase(s) for "${project}".`);
|
|
3206
|
+
}
|
|
3127
3207
|
async function removeKeywords(project, keywords, format) {
|
|
3128
3208
|
const client = getClient7();
|
|
3129
3209
|
const existing = await client.listKeywords(project);
|
|
@@ -3252,6 +3332,24 @@ var KEYWORD_CLI_COMMANDS = [
|
|
|
3252
3332
|
await addKeywords(project, keywords, input.format);
|
|
3253
3333
|
}
|
|
3254
3334
|
},
|
|
3335
|
+
{
|
|
3336
|
+
path: ["keyword", "replace"],
|
|
3337
|
+
usage: "canonry keyword replace <project> <kw...> [--format json]",
|
|
3338
|
+
run: async (input) => {
|
|
3339
|
+
const project = requireProject(input, "keyword.replace", "canonry keyword replace <project> <kw...> [--format json]");
|
|
3340
|
+
const keywords = input.positionals.slice(1);
|
|
3341
|
+
if (keywords.length === 0) {
|
|
3342
|
+
throw usageError("Error: project name and at least one key phrase required\nUsage: canonry keyword replace <project> <kw...> [--format json]", {
|
|
3343
|
+
message: "project name and at least one key phrase required",
|
|
3344
|
+
details: {
|
|
3345
|
+
command: "keyword.replace",
|
|
3346
|
+
usage: "canonry keyword replace <project> <kw...> [--format json]"
|
|
3347
|
+
}
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
3350
|
+
await replaceKeywords(project, keywords, input.format);
|
|
3351
|
+
}
|
|
3352
|
+
},
|
|
3255
3353
|
{
|
|
3256
3354
|
path: ["keyword", "remove"],
|
|
3257
3355
|
usage: "canonry keyword remove <project> <kw...> [--format json]",
|
|
@@ -3341,12 +3439,324 @@ var KEYWORD_CLI_COMMANDS = [
|
|
|
3341
3439
|
},
|
|
3342
3440
|
{
|
|
3343
3441
|
path: ["keyword"],
|
|
3344
|
-
usage: "canonry keyword <add|remove|delete|list|import|generate> <project> [args]",
|
|
3442
|
+
usage: "canonry keyword <add|replace|remove|delete|list|import|generate> <project> [args]",
|
|
3345
3443
|
run: async (input) => {
|
|
3346
3444
|
unknownSubcommand(input.positionals[0], {
|
|
3347
3445
|
command: "keyword",
|
|
3348
|
-
usage: "canonry keyword <add|remove|delete|list|import|generate> <project> [args]",
|
|
3349
|
-
available: ["add", "remove", "delete", "list", "import", "generate"]
|
|
3446
|
+
usage: "canonry keyword <add|replace|remove|delete|list|import|generate> <project> [args]",
|
|
3447
|
+
available: ["add", "replace", "remove", "delete", "list", "import", "generate"]
|
|
3448
|
+
});
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
];
|
|
3452
|
+
|
|
3453
|
+
// src/commands/mcp.ts
|
|
3454
|
+
import fs2 from "fs";
|
|
3455
|
+
import path2 from "path";
|
|
3456
|
+
import { createRequire } from "module";
|
|
3457
|
+
|
|
3458
|
+
// src/mcp-clients.ts
|
|
3459
|
+
import os from "os";
|
|
3460
|
+
import path from "path";
|
|
3461
|
+
var CLAUDE_DESKTOP_CONFIG_FILENAME = "claude_desktop_config.json";
|
|
3462
|
+
function homeRelative(...segments) {
|
|
3463
|
+
return path.join(os.homedir(), ...segments);
|
|
3464
|
+
}
|
|
3465
|
+
function claudeDesktopConfigPath() {
|
|
3466
|
+
switch (process.platform) {
|
|
3467
|
+
case "darwin":
|
|
3468
|
+
return homeRelative("Library", "Application Support", "Claude", CLAUDE_DESKTOP_CONFIG_FILENAME);
|
|
3469
|
+
case "win32": {
|
|
3470
|
+
const appData = process.env.APPDATA ?? homeRelative("AppData", "Roaming");
|
|
3471
|
+
return path.join(appData, "Claude", CLAUDE_DESKTOP_CONFIG_FILENAME);
|
|
3472
|
+
}
|
|
3473
|
+
default:
|
|
3474
|
+
return homeRelative(".config", "Claude", CLAUDE_DESKTOP_CONFIG_FILENAME);
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
function cursorConfigPath() {
|
|
3478
|
+
return homeRelative(".cursor", "mcp.json");
|
|
3479
|
+
}
|
|
3480
|
+
function codexConfigPath() {
|
|
3481
|
+
return homeRelative(".codex", "config.toml");
|
|
3482
|
+
}
|
|
3483
|
+
var SUPPORTED_MCP_CLIENTS = [
|
|
3484
|
+
{
|
|
3485
|
+
id: "claude-desktop",
|
|
3486
|
+
label: "Claude Desktop",
|
|
3487
|
+
format: "json-mcp-servers",
|
|
3488
|
+
configPath: claudeDesktopConfigPath,
|
|
3489
|
+
installSupported: true
|
|
3490
|
+
},
|
|
3491
|
+
{
|
|
3492
|
+
id: "cursor",
|
|
3493
|
+
label: "Cursor",
|
|
3494
|
+
format: "json-mcp-servers",
|
|
3495
|
+
configPath: cursorConfigPath,
|
|
3496
|
+
installSupported: true
|
|
3497
|
+
},
|
|
3498
|
+
{
|
|
3499
|
+
id: "codex",
|
|
3500
|
+
label: "Codex CLI",
|
|
3501
|
+
format: "toml-mcp-servers",
|
|
3502
|
+
configPath: codexConfigPath,
|
|
3503
|
+
installSupported: false
|
|
3504
|
+
}
|
|
3505
|
+
];
|
|
3506
|
+
function findMcpClient(id) {
|
|
3507
|
+
return SUPPORTED_MCP_CLIENTS.find((client) => client.id === id);
|
|
3508
|
+
}
|
|
3509
|
+
function listMcpClientIds() {
|
|
3510
|
+
return SUPPORTED_MCP_CLIENTS.map((client) => client.id);
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
// src/commands/mcp.ts
|
|
3514
|
+
var _require = createRequire(import.meta.url);
|
|
3515
|
+
function resolveCanonryMcpBin() {
|
|
3516
|
+
const packageJsonPath = _require.resolve("../package.json");
|
|
3517
|
+
const packageRoot = path2.dirname(packageJsonPath);
|
|
3518
|
+
const pkg = _require("../package.json");
|
|
3519
|
+
const relativeBin = pkg.bin?.["canonry-mcp"];
|
|
3520
|
+
if (!relativeBin) {
|
|
3521
|
+
throw new CliError({
|
|
3522
|
+
code: "INTERNAL_ERROR",
|
|
3523
|
+
message: "Could not resolve canonry-mcp bin path from package.json",
|
|
3524
|
+
exitCode: 2
|
|
3525
|
+
});
|
|
3526
|
+
}
|
|
3527
|
+
return path2.resolve(packageRoot, relativeBin);
|
|
3528
|
+
}
|
|
3529
|
+
function buildEntry(opts) {
|
|
3530
|
+
const target = opts.binPath ?? resolveCanonryMcpBin();
|
|
3531
|
+
const flagArgs = opts.readOnly ? ["--read-only"] : [];
|
|
3532
|
+
const platform = opts.platform ?? process.platform;
|
|
3533
|
+
if (platform === "win32" && target.toLowerCase().endsWith(".mjs")) {
|
|
3534
|
+
return { command: "node", args: [target, ...flagArgs] };
|
|
3535
|
+
}
|
|
3536
|
+
return { command: target, args: flagArgs };
|
|
3537
|
+
}
|
|
3538
|
+
function entryArgs(entry) {
|
|
3539
|
+
return Array.isArray(entry.args) ? entry.args : [];
|
|
3540
|
+
}
|
|
3541
|
+
function entriesEqual(a, b) {
|
|
3542
|
+
if (a.command !== b.command) return false;
|
|
3543
|
+
const aArgs = entryArgs(a);
|
|
3544
|
+
const bArgs = entryArgs(b);
|
|
3545
|
+
return aArgs.length === bArgs.length && aArgs.every((arg, i) => arg === bArgs[i]);
|
|
3546
|
+
}
|
|
3547
|
+
function renderJsonSnippet(serverName, entry, format) {
|
|
3548
|
+
const key = format === "json-context-servers" ? "context_servers" : "mcpServers";
|
|
3549
|
+
return JSON.stringify({ [key]: { [serverName]: entry } }, null, 2);
|
|
3550
|
+
}
|
|
3551
|
+
function renderTomlSnippet(serverName, entry) {
|
|
3552
|
+
const argsLine = entry.args.length ? `args = [${entry.args.map((arg) => JSON.stringify(arg)).join(", ")}]` : "args = []";
|
|
3553
|
+
return [`[mcp_servers.${serverName}]`, `command = ${JSON.stringify(entry.command)}`, argsLine, ""].join("\n");
|
|
3554
|
+
}
|
|
3555
|
+
function renderClientSnippet(client, serverName, entry) {
|
|
3556
|
+
if (client.format === "toml-mcp-servers") return renderTomlSnippet(serverName, entry);
|
|
3557
|
+
return renderJsonSnippet(serverName, entry, client.format);
|
|
3558
|
+
}
|
|
3559
|
+
function readJsonConfig(configPath) {
|
|
3560
|
+
if (!fs2.existsSync(configPath)) return {};
|
|
3561
|
+
const raw = fs2.readFileSync(configPath, "utf-8").trim();
|
|
3562
|
+
if (!raw) return {};
|
|
3563
|
+
try {
|
|
3564
|
+
const parsed = JSON.parse(raw);
|
|
3565
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3566
|
+
throw new Error("Config root must be a JSON object");
|
|
3567
|
+
}
|
|
3568
|
+
return parsed;
|
|
3569
|
+
} catch (err) {
|
|
3570
|
+
throw new CliError({
|
|
3571
|
+
code: "VALIDATION_ERROR",
|
|
3572
|
+
message: `Failed to parse ${configPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
3573
|
+
exitCode: 1,
|
|
3574
|
+
details: { configPath }
|
|
3575
|
+
});
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
function writeJsonConfig(configPath, value) {
|
|
3579
|
+
fs2.mkdirSync(path2.dirname(configPath), { recursive: true });
|
|
3580
|
+
fs2.writeFileSync(configPath, `${JSON.stringify(value, null, 2)}
|
|
3581
|
+
`, "utf-8");
|
|
3582
|
+
}
|
|
3583
|
+
function backupConfigIfPresent(configPath) {
|
|
3584
|
+
if (!fs2.existsSync(configPath)) return void 0;
|
|
3585
|
+
const backupPath = `${configPath}.canonry.bak`;
|
|
3586
|
+
fs2.copyFileSync(configPath, backupPath);
|
|
3587
|
+
return backupPath;
|
|
3588
|
+
}
|
|
3589
|
+
function findClientOrThrow(id) {
|
|
3590
|
+
const client = findMcpClient(id);
|
|
3591
|
+
if (client) return client;
|
|
3592
|
+
throw new CliError({
|
|
3593
|
+
code: "VALIDATION_ERROR",
|
|
3594
|
+
message: `Unknown MCP client "${id}". Supported: ${listMcpClientIds().join(", ")}`,
|
|
3595
|
+
exitCode: 1,
|
|
3596
|
+
details: { client: id, supportedClients: listMcpClientIds() }
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
async function installMcp(opts) {
|
|
3600
|
+
const client = findClientOrThrow(opts.client);
|
|
3601
|
+
const serverName = opts.name?.trim() || "canonry";
|
|
3602
|
+
const configPath = opts.configPath ?? client.configPath();
|
|
3603
|
+
const entry = buildEntry({ binPath: opts.binPath, readOnly: opts.readOnly, platform: opts.platform });
|
|
3604
|
+
if (!client.installSupported) {
|
|
3605
|
+
const snippet = renderClientSnippet(client, serverName, entry);
|
|
3606
|
+
const result2 = {
|
|
3607
|
+
client: client.id,
|
|
3608
|
+
configPath,
|
|
3609
|
+
serverName,
|
|
3610
|
+
entry,
|
|
3611
|
+
status: "snippet-only",
|
|
3612
|
+
snippet,
|
|
3613
|
+
message: `Auto-install is not supported for ${client.label}. Add the snippet below to ${configPath}.`
|
|
3614
|
+
};
|
|
3615
|
+
emitInstallResult(result2, opts.format);
|
|
3616
|
+
return result2;
|
|
3617
|
+
}
|
|
3618
|
+
const containerKey = client.format === "json-context-servers" ? "context_servers" : "mcpServers";
|
|
3619
|
+
const existing = readJsonConfig(configPath);
|
|
3620
|
+
const existingContainer = existing[containerKey] ?? {};
|
|
3621
|
+
const existingEntry = existingContainer[serverName];
|
|
3622
|
+
if (existingEntry && entriesEqual(existingEntry, entry)) {
|
|
3623
|
+
const result2 = {
|
|
3624
|
+
client: client.id,
|
|
3625
|
+
configPath,
|
|
3626
|
+
serverName,
|
|
3627
|
+
entry,
|
|
3628
|
+
status: "already-installed",
|
|
3629
|
+
message: `${client.label} already has a "${serverName}" entry pointing to canonry-mcp.`
|
|
3630
|
+
};
|
|
3631
|
+
emitInstallResult(result2, opts.format);
|
|
3632
|
+
return result2;
|
|
3633
|
+
}
|
|
3634
|
+
const status = existingEntry ? "updated" : "installed";
|
|
3635
|
+
if (opts.dryRun) {
|
|
3636
|
+
const result2 = {
|
|
3637
|
+
client: client.id,
|
|
3638
|
+
configPath,
|
|
3639
|
+
serverName,
|
|
3640
|
+
entry,
|
|
3641
|
+
status: "dry-run",
|
|
3642
|
+
snippet: renderClientSnippet(client, serverName, entry),
|
|
3643
|
+
message: `Would ${status === "installed" ? "install" : "update"} "${serverName}" in ${configPath}.`
|
|
3644
|
+
};
|
|
3645
|
+
emitInstallResult(result2, opts.format);
|
|
3646
|
+
return result2;
|
|
3647
|
+
}
|
|
3648
|
+
const backupPath = backupConfigIfPresent(configPath);
|
|
3649
|
+
const next = {
|
|
3650
|
+
...existing,
|
|
3651
|
+
[containerKey]: { ...existingContainer, [serverName]: entry }
|
|
3652
|
+
};
|
|
3653
|
+
writeJsonConfig(configPath, next);
|
|
3654
|
+
const result = {
|
|
3655
|
+
client: client.id,
|
|
3656
|
+
configPath,
|
|
3657
|
+
serverName,
|
|
3658
|
+
entry,
|
|
3659
|
+
status,
|
|
3660
|
+
backupPath,
|
|
3661
|
+
message: `${status === "installed" ? "Installed" : "Updated"} "${serverName}" in ${client.label} at ${configPath}. Restart ${client.label} to load it.`
|
|
3662
|
+
};
|
|
3663
|
+
emitInstallResult(result, opts.format);
|
|
3664
|
+
return result;
|
|
3665
|
+
}
|
|
3666
|
+
async function printMcpConfig(opts) {
|
|
3667
|
+
const client = findClientOrThrow(opts.client);
|
|
3668
|
+
const serverName = opts.name?.trim() || "canonry";
|
|
3669
|
+
const entry = buildEntry({ binPath: opts.binPath, readOnly: opts.readOnly, platform: opts.platform });
|
|
3670
|
+
const snippet = renderClientSnippet(client, serverName, entry);
|
|
3671
|
+
if (opts.format === "json") {
|
|
3672
|
+
console.log(JSON.stringify({
|
|
3673
|
+
client: client.id,
|
|
3674
|
+
configPath: client.configPath(),
|
|
3675
|
+
serverName,
|
|
3676
|
+
entry,
|
|
3677
|
+
snippet
|
|
3678
|
+
}, null, 2));
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
console.log(`# ${client.label} \u2014 paste into ${client.configPath()}`);
|
|
3682
|
+
console.log(snippet);
|
|
3683
|
+
}
|
|
3684
|
+
function emitInstallResult(result, format) {
|
|
3685
|
+
if (format === "json") {
|
|
3686
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3687
|
+
return;
|
|
3688
|
+
}
|
|
3689
|
+
console.log(result.message);
|
|
3690
|
+
if (result.backupPath) console.log(`Backup: ${result.backupPath}`);
|
|
3691
|
+
if (result.snippet && (result.status === "snippet-only" || result.status === "dry-run")) {
|
|
3692
|
+
console.log();
|
|
3693
|
+
console.log(result.snippet);
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
// src/cli-commands/mcp.ts
|
|
3698
|
+
var CLIENT_LIST = listMcpClientIds().join("|");
|
|
3699
|
+
var MCP_CLI_COMMANDS = [
|
|
3700
|
+
{
|
|
3701
|
+
path: ["mcp", "install"],
|
|
3702
|
+
usage: `canonry mcp install --client ${CLIENT_LIST} [--name <server>] [--read-only] [--dry-run] [--config-path <path>] [--format json]`,
|
|
3703
|
+
options: {
|
|
3704
|
+
client: stringOption(),
|
|
3705
|
+
name: stringOption(),
|
|
3706
|
+
"read-only": { type: "boolean" },
|
|
3707
|
+
"dry-run": { type: "boolean" },
|
|
3708
|
+
"config-path": stringOption()
|
|
3709
|
+
},
|
|
3710
|
+
run: async (input) => {
|
|
3711
|
+
const usage = `canonry mcp install --client ${CLIENT_LIST} [--name <server>] [--read-only] [--dry-run] [--config-path <path>] [--format json]`;
|
|
3712
|
+
const client = requireStringOption(input, "client", {
|
|
3713
|
+
command: "mcp.install",
|
|
3714
|
+
usage,
|
|
3715
|
+
message: "--client is required",
|
|
3716
|
+
details: { flag: "client", supportedClients: listMcpClientIds() }
|
|
3717
|
+
});
|
|
3718
|
+
await installMcp({
|
|
3719
|
+
client,
|
|
3720
|
+
name: getString(input.values, "name"),
|
|
3721
|
+
readOnly: getBoolean(input.values, "read-only"),
|
|
3722
|
+
dryRun: getBoolean(input.values, "dry-run"),
|
|
3723
|
+
configPath: getString(input.values, "config-path"),
|
|
3724
|
+
format: input.format
|
|
3725
|
+
});
|
|
3726
|
+
}
|
|
3727
|
+
},
|
|
3728
|
+
{
|
|
3729
|
+
path: ["mcp", "config"],
|
|
3730
|
+
usage: `canonry mcp config --client ${CLIENT_LIST} [--name <server>] [--read-only] [--format json]`,
|
|
3731
|
+
options: {
|
|
3732
|
+
client: stringOption(),
|
|
3733
|
+
name: stringOption(),
|
|
3734
|
+
"read-only": { type: "boolean" }
|
|
3735
|
+
},
|
|
3736
|
+
run: async (input) => {
|
|
3737
|
+
const usage = `canonry mcp config --client ${CLIENT_LIST} [--name <server>] [--read-only] [--format json]`;
|
|
3738
|
+
const client = requireStringOption(input, "client", {
|
|
3739
|
+
command: "mcp.config",
|
|
3740
|
+
usage,
|
|
3741
|
+
message: "--client is required",
|
|
3742
|
+
details: { flag: "client", supportedClients: listMcpClientIds() }
|
|
3743
|
+
});
|
|
3744
|
+
await printMcpConfig({
|
|
3745
|
+
client,
|
|
3746
|
+
name: getString(input.values, "name"),
|
|
3747
|
+
readOnly: getBoolean(input.values, "read-only"),
|
|
3748
|
+
format: input.format
|
|
3749
|
+
});
|
|
3750
|
+
}
|
|
3751
|
+
},
|
|
3752
|
+
{
|
|
3753
|
+
path: ["mcp"],
|
|
3754
|
+
usage: "canonry mcp <install|config> [args]",
|
|
3755
|
+
run: async (input) => {
|
|
3756
|
+
unknownSubcommand(input.positionals[0], {
|
|
3757
|
+
command: "mcp",
|
|
3758
|
+
usage: "canonry mcp <install|config> [args]",
|
|
3759
|
+
available: ["install", "config"]
|
|
3350
3760
|
});
|
|
3351
3761
|
}
|
|
3352
3762
|
}
|
|
@@ -3520,13 +3930,13 @@ var NOTIFY_CLI_COMMANDS = [
|
|
|
3520
3930
|
];
|
|
3521
3931
|
|
|
3522
3932
|
// src/commands/apply.ts
|
|
3523
|
-
import
|
|
3933
|
+
import fs3 from "fs";
|
|
3524
3934
|
import { parseAllDocuments } from "yaml";
|
|
3525
3935
|
async function applyConfigFile(filePath) {
|
|
3526
|
-
if (!
|
|
3936
|
+
if (!fs3.existsSync(filePath)) {
|
|
3527
3937
|
throw new Error(`File not found: ${filePath}`);
|
|
3528
3938
|
}
|
|
3529
|
-
const content =
|
|
3939
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
3530
3940
|
const docs = parseAllDocuments(content);
|
|
3531
3941
|
const client = createApiClient();
|
|
3532
3942
|
const errors = [];
|
|
@@ -4970,12 +5380,12 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
|
|
|
4970
5380
|
];
|
|
4971
5381
|
|
|
4972
5382
|
// src/commands/snapshot.ts
|
|
4973
|
-
import
|
|
4974
|
-
import
|
|
5383
|
+
import fs5 from "fs";
|
|
5384
|
+
import path4 from "path";
|
|
4975
5385
|
|
|
4976
5386
|
// src/snapshot-pdf.ts
|
|
4977
|
-
import
|
|
4978
|
-
import
|
|
5387
|
+
import fs4 from "fs";
|
|
5388
|
+
import path3 from "path";
|
|
4979
5389
|
import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
|
|
4980
5390
|
var PAGE_WIDTH = 612;
|
|
4981
5391
|
var PAGE_HEIGHT = 792;
|
|
@@ -5184,9 +5594,9 @@ async function writeSnapshotPdf(report, outputPath) {
|
|
|
5184
5594
|
renderCompetitors(pdf, report);
|
|
5185
5595
|
renderQueries(pdf, report);
|
|
5186
5596
|
const bytes = await doc.save();
|
|
5187
|
-
const resolvedPath =
|
|
5188
|
-
|
|
5189
|
-
|
|
5597
|
+
const resolvedPath = path3.resolve(outputPath);
|
|
5598
|
+
fs4.mkdirSync(path3.dirname(resolvedPath), { recursive: true });
|
|
5599
|
+
fs4.writeFileSync(resolvedPath, bytes);
|
|
5190
5600
|
return resolvedPath;
|
|
5191
5601
|
}
|
|
5192
5602
|
function renderCover(pdf, report) {
|
|
@@ -5344,9 +5754,9 @@ Markdown saved: ${savedMdPath}`);
|
|
|
5344
5754
|
PDF saved: ${savedPdfPath}`);
|
|
5345
5755
|
}
|
|
5346
5756
|
function writeSnapshotMarkdown(report, outputPath) {
|
|
5347
|
-
const resolvedPath =
|
|
5348
|
-
|
|
5349
|
-
|
|
5757
|
+
const resolvedPath = path4.resolve(outputPath);
|
|
5758
|
+
fs5.mkdirSync(path4.dirname(resolvedPath), { recursive: true });
|
|
5759
|
+
fs5.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
|
|
5350
5760
|
return resolvedPath;
|
|
5351
5761
|
}
|
|
5352
5762
|
function formatSnapshotMarkdown(report) {
|
|
@@ -5655,7 +6065,7 @@ var INTELLIGENCE_CLI_COMMANDS = [
|
|
|
5655
6065
|
|
|
5656
6066
|
// src/commands/bootstrap.ts
|
|
5657
6067
|
import crypto from "crypto";
|
|
5658
|
-
import
|
|
6068
|
+
import path5 from "path";
|
|
5659
6069
|
import { eq as eq2 } from "drizzle-orm";
|
|
5660
6070
|
|
|
5661
6071
|
// ../config/src/index.ts
|
|
@@ -5802,7 +6212,7 @@ async function bootstrapCommand(_opts) {
|
|
|
5802
6212
|
);
|
|
5803
6213
|
}
|
|
5804
6214
|
const configDir = getConfigDir();
|
|
5805
|
-
const databasePath = env.databasePath ||
|
|
6215
|
+
const databasePath = env.databasePath || path5.join(configDir, "data.db");
|
|
5806
6216
|
const existing = configExists();
|
|
5807
6217
|
const existingConfig = existing ? loadConfig() : void 0;
|
|
5808
6218
|
let rawApiKey;
|
|
@@ -5872,10 +6282,10 @@ async function bootstrapCommand(_opts) {
|
|
|
5872
6282
|
|
|
5873
6283
|
// src/commands/daemon.ts
|
|
5874
6284
|
import { spawn } from "child_process";
|
|
5875
|
-
import
|
|
5876
|
-
import
|
|
6285
|
+
import fs6 from "fs";
|
|
6286
|
+
import path6 from "path";
|
|
5877
6287
|
function getPidPath() {
|
|
5878
|
-
return
|
|
6288
|
+
return path6.join(getConfigDir(), "canonry.pid");
|
|
5879
6289
|
}
|
|
5880
6290
|
function isProcessAlive(pid) {
|
|
5881
6291
|
try {
|
|
@@ -5902,8 +6312,8 @@ async function waitForReady(host, port, maxMs = 1e4) {
|
|
|
5902
6312
|
async function startDaemon(opts) {
|
|
5903
6313
|
const pidPath = getPidPath();
|
|
5904
6314
|
const format = opts.format ?? "text";
|
|
5905
|
-
if (
|
|
5906
|
-
const existingPid = parseInt(
|
|
6315
|
+
if (fs6.existsSync(pidPath)) {
|
|
6316
|
+
const existingPid = parseInt(fs6.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
5907
6317
|
if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
|
|
5908
6318
|
throw new CliError({
|
|
5909
6319
|
code: "DAEMON_ALREADY_RUNNING",
|
|
@@ -5914,9 +6324,9 @@ async function startDaemon(opts) {
|
|
|
5914
6324
|
}
|
|
5915
6325
|
});
|
|
5916
6326
|
}
|
|
5917
|
-
|
|
6327
|
+
fs6.unlinkSync(pidPath);
|
|
5918
6328
|
}
|
|
5919
|
-
const cliPath =
|
|
6329
|
+
const cliPath = path6.resolve(new URL(import.meta.url).pathname);
|
|
5920
6330
|
const inSourceMode = new URL(import.meta.url).pathname.endsWith(".ts");
|
|
5921
6331
|
const args = inSourceMode ? ["--import", "tsx", cliPath, "serve"] : [cliPath, "serve"];
|
|
5922
6332
|
if (opts.port) args.push("--port", opts.port);
|
|
@@ -5935,10 +6345,10 @@ async function startDaemon(opts) {
|
|
|
5935
6345
|
});
|
|
5936
6346
|
}
|
|
5937
6347
|
const configDir = getConfigDir();
|
|
5938
|
-
if (!
|
|
5939
|
-
|
|
6348
|
+
if (!fs6.existsSync(configDir)) {
|
|
6349
|
+
fs6.mkdirSync(configDir, { recursive: true });
|
|
5940
6350
|
}
|
|
5941
|
-
|
|
6351
|
+
fs6.writeFileSync(pidPath, String(child.pid), "utf-8");
|
|
5942
6352
|
const port = opts.port ?? "4100";
|
|
5943
6353
|
const host = opts.host ?? "127.0.0.1";
|
|
5944
6354
|
if (format !== "json") {
|
|
@@ -5947,7 +6357,7 @@ async function startDaemon(opts) {
|
|
|
5947
6357
|
const ready = await waitForReady(host, port);
|
|
5948
6358
|
if (!ready) {
|
|
5949
6359
|
try {
|
|
5950
|
-
|
|
6360
|
+
fs6.unlinkSync(pidPath);
|
|
5951
6361
|
} catch {
|
|
5952
6362
|
}
|
|
5953
6363
|
throw new CliError({
|
|
@@ -5979,7 +6389,7 @@ async function startDaemon(opts) {
|
|
|
5979
6389
|
}
|
|
5980
6390
|
function stopDaemon(format = "text") {
|
|
5981
6391
|
const pidPath = getPidPath();
|
|
5982
|
-
if (!
|
|
6392
|
+
if (!fs6.existsSync(pidPath)) {
|
|
5983
6393
|
if (format === "json") {
|
|
5984
6394
|
console.log(JSON.stringify({
|
|
5985
6395
|
stopped: false,
|
|
@@ -5990,7 +6400,7 @@ function stopDaemon(format = "text") {
|
|
|
5990
6400
|
console.log("Canonry is not running (no PID file found)");
|
|
5991
6401
|
return;
|
|
5992
6402
|
}
|
|
5993
|
-
const pid = parseInt(
|
|
6403
|
+
const pid = parseInt(fs6.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
5994
6404
|
if (isNaN(pid)) {
|
|
5995
6405
|
if (format === "json") {
|
|
5996
6406
|
console.log(JSON.stringify({
|
|
@@ -6001,7 +6411,7 @@ function stopDaemon(format = "text") {
|
|
|
6001
6411
|
} else {
|
|
6002
6412
|
console.error("Invalid PID file. Removing it.");
|
|
6003
6413
|
}
|
|
6004
|
-
|
|
6414
|
+
fs6.unlinkSync(pidPath);
|
|
6005
6415
|
return;
|
|
6006
6416
|
}
|
|
6007
6417
|
if (!isProcessAlive(pid)) {
|
|
@@ -6015,12 +6425,12 @@ function stopDaemon(format = "text") {
|
|
|
6015
6425
|
} else {
|
|
6016
6426
|
console.log(`Canonry is not running (stale PID: ${pid}). Cleaning up.`);
|
|
6017
6427
|
}
|
|
6018
|
-
|
|
6428
|
+
fs6.unlinkSync(pidPath);
|
|
6019
6429
|
return;
|
|
6020
6430
|
}
|
|
6021
6431
|
try {
|
|
6022
6432
|
process.kill(pid, "SIGTERM");
|
|
6023
|
-
|
|
6433
|
+
fs6.unlinkSync(pidPath);
|
|
6024
6434
|
if (format === "json") {
|
|
6025
6435
|
console.log(JSON.stringify({
|
|
6026
6436
|
stopped: true,
|
|
@@ -6044,9 +6454,9 @@ function stopDaemon(format = "text") {
|
|
|
6044
6454
|
|
|
6045
6455
|
// src/commands/init.ts
|
|
6046
6456
|
import crypto2 from "crypto";
|
|
6047
|
-
import
|
|
6457
|
+
import fs7 from "fs";
|
|
6048
6458
|
import readline from "readline";
|
|
6049
|
-
import
|
|
6459
|
+
import path7 from "path";
|
|
6050
6460
|
function prompt(question) {
|
|
6051
6461
|
const rl = readline.createInterface({
|
|
6052
6462
|
input: process.stdin,
|
|
@@ -6092,8 +6502,8 @@ async function initCommand(opts) {
|
|
|
6092
6502
|
return void 0;
|
|
6093
6503
|
}
|
|
6094
6504
|
const configDir = getConfigDir();
|
|
6095
|
-
if (!
|
|
6096
|
-
|
|
6505
|
+
if (!fs7.existsSync(configDir)) {
|
|
6506
|
+
fs7.mkdirSync(configDir, { recursive: true });
|
|
6097
6507
|
}
|
|
6098
6508
|
const bootstrapEnv = getBootstrapEnv(process.env, {
|
|
6099
6509
|
GEMINI_API_KEY: opts?.geminiKey,
|
|
@@ -6208,7 +6618,7 @@ async function initCommand(opts) {
|
|
|
6208
6618
|
const rawApiKey = `cnry_${crypto2.randomBytes(16).toString("hex")}`;
|
|
6209
6619
|
const keyHash = crypto2.createHash("sha256").update(rawApiKey).digest("hex");
|
|
6210
6620
|
const keyPrefix = rawApiKey.slice(0, 9);
|
|
6211
|
-
const databasePath =
|
|
6621
|
+
const databasePath = path7.join(configDir, "data.db");
|
|
6212
6622
|
const db = createClient(databasePath);
|
|
6213
6623
|
migrate(db);
|
|
6214
6624
|
db.insert(apiKeys).values({
|
|
@@ -6602,7 +7012,7 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
6602
7012
|
];
|
|
6603
7013
|
|
|
6604
7014
|
// src/cli-commands/wordpress.ts
|
|
6605
|
-
import
|
|
7015
|
+
import fs8 from "fs";
|
|
6606
7016
|
|
|
6607
7017
|
// src/commands/wordpress.ts
|
|
6608
7018
|
function getClient18() {
|
|
@@ -6838,12 +7248,12 @@ async function wordpressSetMeta(project, body) {
|
|
|
6838
7248
|
printPageDetail(result);
|
|
6839
7249
|
}
|
|
6840
7250
|
async function wordpressBulkSetMeta(project, opts) {
|
|
6841
|
-
const
|
|
6842
|
-
const
|
|
6843
|
-
const filePath =
|
|
7251
|
+
const fs9 = await import("fs/promises");
|
|
7252
|
+
const path8 = await import("path");
|
|
7253
|
+
const filePath = path8.resolve(opts.from);
|
|
6844
7254
|
let raw;
|
|
6845
7255
|
try {
|
|
6846
|
-
raw = await
|
|
7256
|
+
raw = await fs9.readFile(filePath, "utf8");
|
|
6847
7257
|
} catch {
|
|
6848
7258
|
throw new CliError({
|
|
6849
7259
|
code: "FILE_READ_ERROR",
|
|
@@ -6940,13 +7350,13 @@ async function wordpressSetSchema(project, body) {
|
|
|
6940
7350
|
printManualAssist(`Schema update for "${body.slug}"`, result);
|
|
6941
7351
|
}
|
|
6942
7352
|
async function wordpressSchemaDeploy(project, opts) {
|
|
6943
|
-
const
|
|
6944
|
-
const
|
|
7353
|
+
const fs9 = await import("fs/promises");
|
|
7354
|
+
const path8 = await import("path");
|
|
6945
7355
|
const yaml = await import("yaml").catch(() => null);
|
|
6946
|
-
const filePath =
|
|
7356
|
+
const filePath = path8.resolve(opts.profile);
|
|
6947
7357
|
let raw;
|
|
6948
7358
|
try {
|
|
6949
|
-
raw = await
|
|
7359
|
+
raw = await fs9.readFile(filePath, "utf8");
|
|
6950
7360
|
} catch {
|
|
6951
7361
|
throw new CliError({
|
|
6952
7362
|
code: "FILE_READ_ERROR",
|
|
@@ -7051,13 +7461,13 @@ async function wordpressOnboard(project, opts) {
|
|
|
7051
7461
|
}
|
|
7052
7462
|
let profileData;
|
|
7053
7463
|
if (opts.profile) {
|
|
7054
|
-
const
|
|
7055
|
-
const
|
|
7464
|
+
const fs9 = await import("fs/promises");
|
|
7465
|
+
const path8 = await import("path");
|
|
7056
7466
|
const yaml = await import("yaml").catch(() => null);
|
|
7057
|
-
const filePath =
|
|
7467
|
+
const filePath = path8.resolve(opts.profile);
|
|
7058
7468
|
let raw;
|
|
7059
7469
|
try {
|
|
7060
|
-
raw = await
|
|
7470
|
+
raw = await fs9.readFile(filePath, "utf8");
|
|
7061
7471
|
} catch {
|
|
7062
7472
|
throw new CliError({
|
|
7063
7473
|
code: "FILE_READ_ERROR",
|
|
@@ -7206,7 +7616,7 @@ function resolveContent(input, command, usage, options) {
|
|
|
7206
7616
|
}
|
|
7207
7617
|
if (contentFile) {
|
|
7208
7618
|
try {
|
|
7209
|
-
return
|
|
7619
|
+
return fs8.readFileSync(contentFile, "utf-8");
|
|
7210
7620
|
} catch (error) {
|
|
7211
7621
|
const message = error instanceof Error ? error.message : String(error);
|
|
7212
7622
|
throw usageError(`Error: could not read --content-file "${contentFile}": ${message}`, {
|
|
@@ -8142,11 +8552,12 @@ var REGISTERED_CLI_COMMANDS = [
|
|
|
8142
8552
|
...CDP_CLI_COMMANDS,
|
|
8143
8553
|
...GA_CLI_COMMANDS,
|
|
8144
8554
|
...INTELLIGENCE_CLI_COMMANDS,
|
|
8145
|
-
...AGENT_CLI_COMMANDS
|
|
8555
|
+
...AGENT_CLI_COMMANDS,
|
|
8556
|
+
...MCP_CLI_COMMANDS
|
|
8146
8557
|
];
|
|
8147
8558
|
|
|
8148
8559
|
// src/cli.ts
|
|
8149
|
-
import { createRequire } from "module";
|
|
8560
|
+
import { createRequire as createRequire2 } from "module";
|
|
8150
8561
|
var USAGE = `
|
|
8151
8562
|
canonry \u2014 AEO monitoring CLI
|
|
8152
8563
|
|
|
@@ -8160,8 +8571,8 @@ Setup:
|
|
|
8160
8571
|
|
|
8161
8572
|
Projects:
|
|
8162
8573
|
project Create, update, list, show, delete projects
|
|
8163
|
-
keyword Add, remove, list, import, generate key phrases
|
|
8164
|
-
competitor Add, list competitors
|
|
8574
|
+
keyword Add, replace, remove, list, import, generate key phrases
|
|
8575
|
+
competitor Add, remove, list competitors
|
|
8165
8576
|
|
|
8166
8577
|
Monitoring:
|
|
8167
8578
|
run Trigger visibility sweeps
|
|
@@ -8198,8 +8609,8 @@ Global options:
|
|
|
8198
8609
|
|
|
8199
8610
|
Run 'canonry <command> --help' for details on a specific command.
|
|
8200
8611
|
`.trim();
|
|
8201
|
-
var
|
|
8202
|
-
var { version: VERSION } =
|
|
8612
|
+
var _require2 = createRequire2(import.meta.url);
|
|
8613
|
+
var { version: VERSION } = _require2("../package.json");
|
|
8203
8614
|
function extractFormat(cmdArgs) {
|
|
8204
8615
|
const idx = cmdArgs.indexOf("--format");
|
|
8205
8616
|
if (idx !== -1 && cmdArgs[idx + 1] === "json") return "json";
|