@ainyc/canonry 1.7.0 → 1.8.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 +69 -12
- package/assets/assets/{index-BxWGYuSH.css → index-Co-wrY40.css} +1 -1
- package/assets/assets/index-xvy_ShfV.js +243 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-2QG7TZ4A.js → chunk-3FHF3YVA.js} +811 -38
- package/dist/cli.js +504 -73
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/assets/assets/index-Cgb-VMub.js +0 -205
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
configExists,
|
|
5
5
|
createClient,
|
|
6
6
|
createServer,
|
|
7
|
+
effectiveDomains,
|
|
7
8
|
getConfigDir,
|
|
8
9
|
getConfigPath,
|
|
9
10
|
getOrCreateAnonymousId,
|
|
@@ -11,11 +12,12 @@ import {
|
|
|
11
12
|
isTelemetryEnabled,
|
|
12
13
|
loadConfig,
|
|
13
14
|
migrate,
|
|
15
|
+
notificationEventSchema,
|
|
14
16
|
providerQuotaPolicySchema,
|
|
15
17
|
saveConfig,
|
|
16
18
|
showFirstRunNotice,
|
|
17
19
|
trackEvent
|
|
18
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-3FHF3YVA.js";
|
|
19
21
|
|
|
20
22
|
// src/cli.ts
|
|
21
23
|
import { parseArgs } from "util";
|
|
@@ -316,6 +318,107 @@ Canonry server running at http://${host === "0.0.0.0" ? "localhost" : host}:${po
|
|
|
316
318
|
}
|
|
317
319
|
}
|
|
318
320
|
|
|
321
|
+
// src/commands/daemon.ts
|
|
322
|
+
import { spawn } from "child_process";
|
|
323
|
+
import fs2 from "fs";
|
|
324
|
+
import path3 from "path";
|
|
325
|
+
function getPidPath() {
|
|
326
|
+
return path3.join(getConfigDir(), "canonry.pid");
|
|
327
|
+
}
|
|
328
|
+
function isProcessAlive(pid) {
|
|
329
|
+
try {
|
|
330
|
+
process.kill(pid, 0);
|
|
331
|
+
return true;
|
|
332
|
+
} catch (err) {
|
|
333
|
+
if (err.code === "EPERM") return true;
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function waitForReady(host, port, maxMs = 1e4) {
|
|
338
|
+
const url = `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}/health`;
|
|
339
|
+
const deadline = Date.now() + maxMs;
|
|
340
|
+
while (Date.now() < deadline) {
|
|
341
|
+
try {
|
|
342
|
+
const res = await fetch(url);
|
|
343
|
+
if (res.ok) return true;
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
async function startDaemon(opts) {
|
|
351
|
+
const pidPath = getPidPath();
|
|
352
|
+
if (fs2.existsSync(pidPath)) {
|
|
353
|
+
const existingPid = parseInt(fs2.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
354
|
+
if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
|
|
355
|
+
console.error(`Canonry is already running (PID: ${existingPid})`);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
fs2.unlinkSync(pidPath);
|
|
359
|
+
}
|
|
360
|
+
const cliPath = path3.resolve(new URL(import.meta.url).pathname);
|
|
361
|
+
const inSourceMode = new URL(import.meta.url).pathname.endsWith(".ts");
|
|
362
|
+
const args = inSourceMode ? ["--import", "tsx", cliPath, "serve"] : [cliPath, "serve"];
|
|
363
|
+
if (opts.port) args.push("--port", opts.port);
|
|
364
|
+
if (opts.host) args.push("--host", opts.host);
|
|
365
|
+
const child = spawn(process.execPath, args, {
|
|
366
|
+
detached: true,
|
|
367
|
+
stdio: "ignore"
|
|
368
|
+
});
|
|
369
|
+
child.unref();
|
|
370
|
+
if (!child.pid) {
|
|
371
|
+
console.error("Failed to start Canonry server");
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
const configDir = getConfigDir();
|
|
375
|
+
if (!fs2.existsSync(configDir)) {
|
|
376
|
+
fs2.mkdirSync(configDir, { recursive: true });
|
|
377
|
+
}
|
|
378
|
+
fs2.writeFileSync(pidPath, String(child.pid), "utf-8");
|
|
379
|
+
const port = opts.port ?? "4100";
|
|
380
|
+
const host = opts.host ?? "127.0.0.1";
|
|
381
|
+
process.stderr.write("Waiting for server to start...");
|
|
382
|
+
const ready = await waitForReady(host, port);
|
|
383
|
+
if (!ready) {
|
|
384
|
+
try {
|
|
385
|
+
fs2.unlinkSync(pidPath);
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
console.error("\nFailed to start: server did not respond within 10s");
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
process.stderr.write("\n");
|
|
392
|
+
console.log(`Canonry started (PID: ${child.pid}), listening on http://${host === "0.0.0.0" ? "localhost" : host}:${port}`);
|
|
393
|
+
}
|
|
394
|
+
function stopDaemon() {
|
|
395
|
+
const pidPath = getPidPath();
|
|
396
|
+
if (!fs2.existsSync(pidPath)) {
|
|
397
|
+
console.log("Canonry is not running (no PID file found)");
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const pid = parseInt(fs2.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
401
|
+
if (isNaN(pid)) {
|
|
402
|
+
console.error("Invalid PID file. Removing it.");
|
|
403
|
+
fs2.unlinkSync(pidPath);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (!isProcessAlive(pid)) {
|
|
407
|
+
console.log(`Canonry is not running (stale PID: ${pid}). Cleaning up.`);
|
|
408
|
+
fs2.unlinkSync(pidPath);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
process.kill(pid, "SIGTERM");
|
|
413
|
+
fs2.unlinkSync(pidPath);
|
|
414
|
+
console.log(`Canonry stopped (PID: ${pid})`);
|
|
415
|
+
} catch (err) {
|
|
416
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
417
|
+
console.error(`Failed to stop Canonry (PID: ${pid}): ${msg}`);
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
319
422
|
// src/client.ts
|
|
320
423
|
var ApiClient = class {
|
|
321
424
|
baseUrl;
|
|
@@ -324,8 +427,8 @@ var ApiClient = class {
|
|
|
324
427
|
this.baseUrl = baseUrl.replace(/\/$/, "") + "/api/v1";
|
|
325
428
|
this.apiKey = apiKey;
|
|
326
429
|
}
|
|
327
|
-
async request(method,
|
|
328
|
-
const url = `${this.baseUrl}${
|
|
430
|
+
async request(method, path4, body) {
|
|
431
|
+
const url = `${this.baseUrl}${path4}`;
|
|
329
432
|
const headers = {
|
|
330
433
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
331
434
|
"Content-Type": "application/json"
|
|
@@ -464,39 +567,56 @@ async function createProject(name, opts) {
|
|
|
464
567
|
const result = await client.putProject(name, {
|
|
465
568
|
displayName: opts.displayName,
|
|
466
569
|
canonicalDomain: opts.domain,
|
|
570
|
+
ownedDomains: opts.ownedDomains ?? [],
|
|
467
571
|
country: opts.country,
|
|
468
572
|
language: opts.language
|
|
469
573
|
});
|
|
470
574
|
console.log(`Project created: ${result.name} (${result.id})`);
|
|
471
575
|
}
|
|
472
|
-
async function listProjects() {
|
|
576
|
+
async function listProjects(format) {
|
|
473
577
|
const client = getClient();
|
|
474
578
|
const projects = await client.listProjects();
|
|
579
|
+
if (format === "json") {
|
|
580
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
475
583
|
if (projects.length === 0) {
|
|
476
584
|
console.log("No projects found.");
|
|
477
585
|
return;
|
|
478
586
|
}
|
|
479
587
|
console.log("Projects:\n");
|
|
480
588
|
const nameWidth = Math.max(4, ...projects.map((p) => p.name.length));
|
|
481
|
-
const
|
|
589
|
+
const domainLabel = (p) => {
|
|
590
|
+
const extra = Math.max(0, effectiveDomains(p).length - 1);
|
|
591
|
+
return extra > 0 ? `${p.canonicalDomain} (+${extra})` : p.canonicalDomain;
|
|
592
|
+
};
|
|
593
|
+
const domainWidth = Math.max(6, ...projects.map((p) => domainLabel(p).length));
|
|
482
594
|
console.log(
|
|
483
595
|
` ${"NAME".padEnd(nameWidth)} ${"DOMAIN".padEnd(domainWidth)} COUNTRY LANGUAGE`
|
|
484
596
|
);
|
|
485
597
|
console.log(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(domainWidth)} \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
486
598
|
for (const p of projects) {
|
|
487
599
|
console.log(
|
|
488
|
-
` ${p.name.padEnd(nameWidth)} ${p.
|
|
600
|
+
` ${p.name.padEnd(nameWidth)} ${domainLabel(p).padEnd(domainWidth)} ${p.country.padEnd(7)} ${p.language}`
|
|
489
601
|
);
|
|
490
602
|
}
|
|
491
603
|
}
|
|
492
|
-
async function showProject(name) {
|
|
604
|
+
async function showProject(name, format) {
|
|
493
605
|
const client = getClient();
|
|
494
606
|
const project = await client.getProject(name);
|
|
607
|
+
if (format === "json") {
|
|
608
|
+
console.log(JSON.stringify(project, null, 2));
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
495
611
|
console.log(`Project: ${project.displayName}
|
|
496
612
|
`);
|
|
497
613
|
console.log(` Name: ${project.name}`);
|
|
498
614
|
console.log(` ID: ${project.id}`);
|
|
499
615
|
console.log(` Domain: ${project.canonicalDomain}`);
|
|
616
|
+
const secondaryDomains = effectiveDomains(project).slice(1);
|
|
617
|
+
if (secondaryDomains.length > 0) {
|
|
618
|
+
console.log(` Owned domains: ${secondaryDomains.join(", ")}`);
|
|
619
|
+
}
|
|
500
620
|
console.log(` Country: ${project.country}`);
|
|
501
621
|
console.log(` Language: ${project.language}`);
|
|
502
622
|
console.log(` Config source: ${project.configSource}`);
|
|
@@ -507,6 +627,27 @@ async function showProject(name) {
|
|
|
507
627
|
console.log(` Created: ${project.createdAt}`);
|
|
508
628
|
console.log(` Updated: ${project.updatedAt}`);
|
|
509
629
|
}
|
|
630
|
+
async function updateProjectSettings(name, opts) {
|
|
631
|
+
const client = getClient();
|
|
632
|
+
const project = await client.getProject(name);
|
|
633
|
+
let ownedDomains = opts.ownedDomains ?? project.ownedDomains ?? [];
|
|
634
|
+
if (opts.addOwnedDomain) {
|
|
635
|
+
const toAdd = opts.addOwnedDomain.filter((d) => !ownedDomains.includes(d));
|
|
636
|
+
ownedDomains = [...ownedDomains, ...toAdd];
|
|
637
|
+
}
|
|
638
|
+
if (opts.removeOwnedDomain) {
|
|
639
|
+
const toRemove = new Set(opts.removeOwnedDomain);
|
|
640
|
+
ownedDomains = ownedDomains.filter((d) => !toRemove.has(d));
|
|
641
|
+
}
|
|
642
|
+
const result = await client.putProject(name, {
|
|
643
|
+
displayName: opts.displayName ?? project.displayName,
|
|
644
|
+
canonicalDomain: opts.domain ?? project.canonicalDomain,
|
|
645
|
+
ownedDomains,
|
|
646
|
+
country: opts.country ?? project.country,
|
|
647
|
+
language: opts.language ?? project.language
|
|
648
|
+
});
|
|
649
|
+
console.log(`Project updated: ${result.name}`);
|
|
650
|
+
}
|
|
510
651
|
async function deleteProject(name) {
|
|
511
652
|
const client = getClient();
|
|
512
653
|
await client.deleteProject(name);
|
|
@@ -514,7 +655,7 @@ async function deleteProject(name) {
|
|
|
514
655
|
}
|
|
515
656
|
|
|
516
657
|
// src/commands/keyword.ts
|
|
517
|
-
import
|
|
658
|
+
import fs3 from "fs";
|
|
518
659
|
function getClient2() {
|
|
519
660
|
const config = loadConfig();
|
|
520
661
|
return new ApiClient(config.apiUrl, config.apiKey);
|
|
@@ -524,9 +665,13 @@ async function addKeywords(project, keywords) {
|
|
|
524
665
|
await client.appendKeywords(project, keywords);
|
|
525
666
|
console.log(`Added ${keywords.length} key phrase(s) to "${project}".`);
|
|
526
667
|
}
|
|
527
|
-
async function listKeywords(project) {
|
|
668
|
+
async function listKeywords(project, format) {
|
|
528
669
|
const client = getClient2();
|
|
529
670
|
const kws = await client.listKeywords(project);
|
|
671
|
+
if (format === "json") {
|
|
672
|
+
console.log(JSON.stringify(kws, null, 2));
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
530
675
|
if (kws.length === 0) {
|
|
531
676
|
console.log(`No key phrases found for "${project}".`);
|
|
532
677
|
return;
|
|
@@ -538,10 +683,10 @@ async function listKeywords(project) {
|
|
|
538
683
|
}
|
|
539
684
|
}
|
|
540
685
|
async function importKeywords(project, filePath) {
|
|
541
|
-
if (!
|
|
686
|
+
if (!fs3.existsSync(filePath)) {
|
|
542
687
|
throw new Error(`File not found: ${filePath}`);
|
|
543
688
|
}
|
|
544
|
-
const content =
|
|
689
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
545
690
|
const keywords = content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
546
691
|
if (keywords.length === 0) {
|
|
547
692
|
console.log("No key phrases found in file.");
|
|
@@ -582,9 +727,13 @@ async function addCompetitors(project, domains) {
|
|
|
582
727
|
await client.putCompetitors(project, allDomains);
|
|
583
728
|
console.log(`Added ${domains.length} competitor(s) to "${project}".`);
|
|
584
729
|
}
|
|
585
|
-
async function listCompetitors(project) {
|
|
730
|
+
async function listCompetitors(project, format) {
|
|
586
731
|
const client = getClient3();
|
|
587
732
|
const comps = await client.listCompetitors(project);
|
|
733
|
+
if (format === "json") {
|
|
734
|
+
console.log(JSON.stringify(comps, null, 2));
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
588
737
|
if (comps.length === 0) {
|
|
589
738
|
console.log(`No competitors found for "${project}".`);
|
|
590
739
|
return;
|
|
@@ -601,6 +750,7 @@ function getClient4() {
|
|
|
601
750
|
const config = loadConfig();
|
|
602
751
|
return new ApiClient(config.apiUrl, config.apiKey);
|
|
603
752
|
}
|
|
753
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "partial", "failed"]);
|
|
604
754
|
async function triggerRun(project, opts) {
|
|
605
755
|
const client = getClient4();
|
|
606
756
|
const body = {};
|
|
@@ -608,6 +758,21 @@ async function triggerRun(project, opts) {
|
|
|
608
758
|
body.providers = [opts.provider];
|
|
609
759
|
}
|
|
610
760
|
const run = await client.triggerRun(project, body);
|
|
761
|
+
if (opts?.wait) {
|
|
762
|
+
process.stderr.write(`Run ${run.id} started`);
|
|
763
|
+
const result = await pollRun(client, run.id);
|
|
764
|
+
if (opts?.format === "json") {
|
|
765
|
+
console.log(JSON.stringify(result, null, 2));
|
|
766
|
+
} else {
|
|
767
|
+
process.stderr.write("\n");
|
|
768
|
+
printRunDetail(result);
|
|
769
|
+
}
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (opts?.format === "json") {
|
|
773
|
+
console.log(JSON.stringify(run, null, 2));
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
611
776
|
console.log(`Run created: ${run.id}`);
|
|
612
777
|
console.log(` Kind: ${run.kind}`);
|
|
613
778
|
console.log(` Status: ${run.status}`);
|
|
@@ -615,9 +780,72 @@ async function triggerRun(project, opts) {
|
|
|
615
780
|
console.log(` Provider: ${opts.provider}`);
|
|
616
781
|
}
|
|
617
782
|
}
|
|
618
|
-
async function
|
|
783
|
+
async function triggerRunAll(opts) {
|
|
784
|
+
const client = getClient4();
|
|
785
|
+
const projects = await client.listProjects();
|
|
786
|
+
if (projects.length === 0) {
|
|
787
|
+
if (opts?.format === "json") {
|
|
788
|
+
console.log("[]");
|
|
789
|
+
} else {
|
|
790
|
+
console.log("No projects found.");
|
|
791
|
+
}
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const body = {};
|
|
795
|
+
if (opts?.provider) {
|
|
796
|
+
body.providers = [opts.provider];
|
|
797
|
+
}
|
|
798
|
+
const results = [];
|
|
799
|
+
for (const p of projects) {
|
|
800
|
+
try {
|
|
801
|
+
const run = await client.triggerRun(p.name, body);
|
|
802
|
+
results.push({ project: p.name, runId: run.id, status: run.status });
|
|
803
|
+
} catch (err) {
|
|
804
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
805
|
+
results.push({ project: p.name, runId: "", status: "error", error: msg });
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (opts?.wait) {
|
|
809
|
+
const pending = results.filter((r) => r.runId && !TERMINAL_STATUSES.has(r.status));
|
|
810
|
+
if (pending.length > 0) {
|
|
811
|
+
process.stderr.write(`Waiting for ${pending.length} run(s)`);
|
|
812
|
+
await Promise.all(pending.map(async (r) => {
|
|
813
|
+
const final = await pollRun(client, r.runId);
|
|
814
|
+
r.status = final.status;
|
|
815
|
+
}));
|
|
816
|
+
process.stderr.write("\n");
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (opts?.format === "json") {
|
|
820
|
+
console.log(JSON.stringify(results, null, 2));
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
console.log(`Triggered ${results.length} run(s):
|
|
824
|
+
`);
|
|
825
|
+
console.log(" PROJECT RUN ID STATUS");
|
|
826
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
827
|
+
for (const r of results) {
|
|
828
|
+
const proj = r.project.padEnd(31);
|
|
829
|
+
const id = (r.runId || "(failed)").padEnd(36);
|
|
830
|
+
console.log(` ${proj} ${id} ${r.status}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
async function showRun(id, format) {
|
|
834
|
+
const client = getClient4();
|
|
835
|
+
const run = await client.getRun(id);
|
|
836
|
+
if (format === "json") {
|
|
837
|
+
console.log(JSON.stringify(run, null, 2));
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
printRunDetail(run);
|
|
841
|
+
}
|
|
842
|
+
async function listRuns(project, format) {
|
|
619
843
|
const client = getClient4();
|
|
620
844
|
const runs = await client.listRuns(project);
|
|
845
|
+
if (format === "json") {
|
|
846
|
+
console.log(JSON.stringify(runs, null, 2));
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
621
849
|
if (runs.length === 0) {
|
|
622
850
|
console.log(`No runs found for "${project}".`);
|
|
623
851
|
return;
|
|
@@ -632,39 +860,77 @@ async function listRuns(project) {
|
|
|
632
860
|
);
|
|
633
861
|
}
|
|
634
862
|
}
|
|
863
|
+
var POLL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
864
|
+
async function pollRun(client, runId) {
|
|
865
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
866
|
+
for (; ; ) {
|
|
867
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
868
|
+
if (Date.now() > deadline) {
|
|
869
|
+
throw new Error(`Timed out waiting for run ${runId} after ${POLL_TIMEOUT_MS / 1e3}s`);
|
|
870
|
+
}
|
|
871
|
+
const run = await client.getRun(runId);
|
|
872
|
+
process.stderr.write(".");
|
|
873
|
+
if (TERMINAL_STATUSES.has(run.status)) {
|
|
874
|
+
return run;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function printRunDetail(run) {
|
|
879
|
+
console.log(`Run: ${run.id}`);
|
|
880
|
+
console.log(` Status: ${run.status}`);
|
|
881
|
+
console.log(` Kind: ${run.kind}`);
|
|
882
|
+
if (run.trigger) console.log(` Trigger: ${run.trigger}`);
|
|
883
|
+
if (run.startedAt) console.log(` Started: ${run.startedAt}`);
|
|
884
|
+
if (run.finishedAt) console.log(` Finished: ${run.finishedAt}`);
|
|
885
|
+
if (run.createdAt) console.log(` Created: ${run.createdAt}`);
|
|
886
|
+
if (run.error) console.log(` Error: ${run.error}`);
|
|
887
|
+
const snapshots = run.snapshots;
|
|
888
|
+
if (snapshots && snapshots.length > 0) {
|
|
889
|
+
console.log(`
|
|
890
|
+
Snapshots: ${snapshots.length}`);
|
|
891
|
+
for (const s of snapshots) {
|
|
892
|
+
const state = s.citationState === "cited" ? " cited " : " not-cited";
|
|
893
|
+
console.log(` ${state} ${s.provider} ${s.keyword}`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
635
897
|
|
|
636
898
|
// src/commands/status.ts
|
|
637
899
|
function getClient5() {
|
|
638
900
|
const config = loadConfig();
|
|
639
901
|
return new ApiClient(config.apiUrl, config.apiKey);
|
|
640
902
|
}
|
|
641
|
-
async function showStatus(project) {
|
|
903
|
+
async function showStatus(project, format) {
|
|
642
904
|
const client = getClient5();
|
|
643
905
|
const projectData = await client.getProject(project);
|
|
906
|
+
let runs = [];
|
|
907
|
+
try {
|
|
908
|
+
runs = await client.listRuns(project);
|
|
909
|
+
} catch {
|
|
910
|
+
}
|
|
911
|
+
if (format === "json") {
|
|
912
|
+
console.log(JSON.stringify({ project: projectData, runs }, null, 2));
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
644
915
|
console.log(`Status: ${projectData.displayName} (${projectData.name})
|
|
645
916
|
`);
|
|
646
917
|
console.log(` Domain: ${projectData.canonicalDomain}`);
|
|
647
918
|
console.log(` Country: ${projectData.country}`);
|
|
648
919
|
console.log(` Language: ${projectData.language}`);
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
const latest = runs[runs.length - 1];
|
|
653
|
-
console.log(`
|
|
920
|
+
if (runs.length > 0) {
|
|
921
|
+
const latest = runs[runs.length - 1];
|
|
922
|
+
console.log(`
|
|
654
923
|
Latest run:`);
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
console.log(`
|
|
662
|
-
Total runs: ${runs.length}`);
|
|
663
|
-
} else {
|
|
664
|
-
console.log('\n No runs yet. Use "canonry run" to trigger one.');
|
|
924
|
+
console.log(` ID: ${latest.id}`);
|
|
925
|
+
console.log(` Status: ${latest.status}`);
|
|
926
|
+
console.log(` Created: ${latest.createdAt}`);
|
|
927
|
+
if (latest.finishedAt) {
|
|
928
|
+
console.log(` Finished: ${latest.finishedAt}`);
|
|
665
929
|
}
|
|
666
|
-
|
|
667
|
-
|
|
930
|
+
console.log(`
|
|
931
|
+
Total runs: ${runs.length}`);
|
|
932
|
+
} else {
|
|
933
|
+
console.log('\n No runs yet. Use "canonry run" to trigger one.');
|
|
668
934
|
}
|
|
669
935
|
}
|
|
670
936
|
|
|
@@ -673,9 +939,13 @@ function getClient6() {
|
|
|
673
939
|
const config = loadConfig();
|
|
674
940
|
return new ApiClient(config.apiUrl, config.apiKey);
|
|
675
941
|
}
|
|
676
|
-
async function showEvidence(project) {
|
|
942
|
+
async function showEvidence(project, format) {
|
|
677
943
|
const client = getClient6();
|
|
678
944
|
const timeline = await client.getTimeline(project);
|
|
945
|
+
if (format === "json") {
|
|
946
|
+
console.log(JSON.stringify(timeline, null, 2));
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
679
949
|
if (timeline.length === 0) {
|
|
680
950
|
console.log('No keyword evidence yet. Trigger a run first with "canonry run".');
|
|
681
951
|
return;
|
|
@@ -700,10 +970,14 @@ function getClient7() {
|
|
|
700
970
|
const config = loadConfig();
|
|
701
971
|
return new ApiClient(config.apiUrl, config.apiKey);
|
|
702
972
|
}
|
|
703
|
-
async function showHistory(project) {
|
|
973
|
+
async function showHistory(project, format) {
|
|
704
974
|
const client = getClient7();
|
|
705
975
|
try {
|
|
706
976
|
const entries = await client.getHistory(project);
|
|
977
|
+
if (format === "json") {
|
|
978
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
707
981
|
if (entries.length === 0) {
|
|
708
982
|
console.log(`No audit history for "${project}".`);
|
|
709
983
|
return;
|
|
@@ -725,13 +999,13 @@ async function showHistory(project) {
|
|
|
725
999
|
}
|
|
726
1000
|
|
|
727
1001
|
// src/commands/apply.ts
|
|
728
|
-
import
|
|
1002
|
+
import fs4 from "fs";
|
|
729
1003
|
import { parseAllDocuments } from "yaml";
|
|
730
1004
|
async function applyConfig(filePath) {
|
|
731
|
-
if (!
|
|
1005
|
+
if (!fs4.existsSync(filePath)) {
|
|
732
1006
|
throw new Error(`File not found: ${filePath}`);
|
|
733
1007
|
}
|
|
734
|
-
const content =
|
|
1008
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
735
1009
|
const docs = parseAllDocuments(content);
|
|
736
1010
|
const clientConfig = loadConfig();
|
|
737
1011
|
const client = new ApiClient(clientConfig.apiUrl, clientConfig.apiKey);
|
|
@@ -789,10 +1063,17 @@ async function setProvider(name, opts) {
|
|
|
789
1063
|
if (result.model) {
|
|
790
1064
|
console.log(` Model: ${result.model}`);
|
|
791
1065
|
}
|
|
1066
|
+
if (result.quota) {
|
|
1067
|
+
console.log(` Quota: ${result.quota.maxConcurrency} concurrent \xB7 ${result.quota.maxRequestsPerMinute}/min \xB7 ${result.quota.maxRequestsPerDay}/day`);
|
|
1068
|
+
}
|
|
792
1069
|
}
|
|
793
|
-
async function showSettings() {
|
|
1070
|
+
async function showSettings(format) {
|
|
794
1071
|
const client = getClient8();
|
|
795
1072
|
const settings = await client.getSettings();
|
|
1073
|
+
if (format === "json") {
|
|
1074
|
+
console.log(JSON.stringify(settings, null, 2));
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
796
1077
|
console.log("Provider settings:\n");
|
|
797
1078
|
for (const provider of settings.providers) {
|
|
798
1079
|
const status = provider.configured ? "configured" : "not configured";
|
|
@@ -822,9 +1103,13 @@ async function setSchedule(project, opts) {
|
|
|
822
1103
|
console.log(`Schedule set for "${project}":`);
|
|
823
1104
|
printSchedule(result);
|
|
824
1105
|
}
|
|
825
|
-
async function showSchedule(project) {
|
|
1106
|
+
async function showSchedule(project, format) {
|
|
826
1107
|
const client = getClient9();
|
|
827
1108
|
const result = await client.getSchedule(project);
|
|
1109
|
+
if (format === "json") {
|
|
1110
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
828
1113
|
printSchedule(result);
|
|
829
1114
|
}
|
|
830
1115
|
async function enableSchedule(project) {
|
|
@@ -884,9 +1169,13 @@ async function addNotification(project, opts) {
|
|
|
884
1169
|
console.log(`Notification created for "${project}":`);
|
|
885
1170
|
printNotification(result);
|
|
886
1171
|
}
|
|
887
|
-
async function listNotifications(project) {
|
|
1172
|
+
async function listNotifications(project, format) {
|
|
888
1173
|
const client = getClient10();
|
|
889
1174
|
const results = await client.listNotifications(project);
|
|
1175
|
+
if (format === "json") {
|
|
1176
|
+
console.log(JSON.stringify(results, null, 2));
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
890
1179
|
if (results.length === 0) {
|
|
891
1180
|
console.log(`No notifications configured for "${project}"`);
|
|
892
1181
|
return;
|
|
@@ -912,6 +1201,23 @@ async function testNotification(project, id) {
|
|
|
912
1201
|
console.error(`Test webhook failed: HTTP ${result.status}`);
|
|
913
1202
|
}
|
|
914
1203
|
}
|
|
1204
|
+
var EVENT_DESCRIPTIONS = {
|
|
1205
|
+
"citation.lost": "A keyword lost its citation status",
|
|
1206
|
+
"citation.gained": "A keyword gained citation status",
|
|
1207
|
+
"run.completed": "A visibility run completed successfully",
|
|
1208
|
+
"run.failed": "A visibility run failed"
|
|
1209
|
+
};
|
|
1210
|
+
function listEvents(format) {
|
|
1211
|
+
const events = notificationEventSchema.options;
|
|
1212
|
+
if (format === "json") {
|
|
1213
|
+
console.log(JSON.stringify(events.map((e) => ({ event: e, description: EVENT_DESCRIPTIONS[e] ?? "" })), null, 2));
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
console.log("Available notification events:\n");
|
|
1217
|
+
for (const event of events) {
|
|
1218
|
+
console.log(` ${event.padEnd(20)} ${EVENT_DESCRIPTIONS[event] ?? ""}`);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
915
1221
|
function printNotification(n) {
|
|
916
1222
|
console.log(` ID: ${n.id}`);
|
|
917
1223
|
console.log(` Channel: ${n.channel}`);
|
|
@@ -987,8 +1293,11 @@ Usage:
|
|
|
987
1293
|
canonry init [--force] Initialize config and database (interactive)
|
|
988
1294
|
canonry init --gemini-key <key> Initialize non-interactively (also reads env vars)
|
|
989
1295
|
canonry bootstrap [--force] Bootstrap config/database from env vars
|
|
990
|
-
canonry serve Start the local server
|
|
1296
|
+
canonry serve Start the local server (foreground)
|
|
1297
|
+
canonry start Start the server as a background daemon
|
|
1298
|
+
canonry stop Stop the background daemon
|
|
991
1299
|
canonry project create <name> Create a project
|
|
1300
|
+
canonry project update <name> Update project settings
|
|
992
1301
|
canonry project list List all projects
|
|
993
1302
|
canonry project show <name> Show project details
|
|
994
1303
|
canonry project delete <name> Delete a project
|
|
@@ -1000,6 +1309,9 @@ Usage:
|
|
|
1000
1309
|
canonry competitor list <project> List competitors
|
|
1001
1310
|
canonry run <project> Trigger a run (all providers)
|
|
1002
1311
|
canonry run <project> --provider <name> Trigger a run for a specific provider
|
|
1312
|
+
canonry run <project> --wait Trigger and wait for completion
|
|
1313
|
+
canonry run --all Trigger runs for all projects
|
|
1314
|
+
canonry run show <id> Show run details and snapshots
|
|
1003
1315
|
canonry runs <project> List runs for a project
|
|
1004
1316
|
canonry status <project> Show project summary
|
|
1005
1317
|
canonry evidence <project> Show per-phrase results
|
|
@@ -1015,8 +1327,9 @@ Usage:
|
|
|
1015
1327
|
canonry notify list <project> List notifications
|
|
1016
1328
|
canonry notify remove <project> <id> Remove notification
|
|
1017
1329
|
canonry notify test <project> <id> Send test webhook
|
|
1330
|
+
canonry notify events List available notification event types
|
|
1018
1331
|
canonry settings Show active provider and quota settings
|
|
1019
|
-
canonry settings provider <name> Update a provider config
|
|
1332
|
+
canonry settings provider <name> Update a provider config
|
|
1020
1333
|
canonry telemetry status Show telemetry status
|
|
1021
1334
|
canonry telemetry enable Enable anonymous telemetry
|
|
1022
1335
|
canonry telemetry disable Disable anonymous telemetry
|
|
@@ -1032,19 +1345,37 @@ Options:
|
|
|
1032
1345
|
--local-key <key> Local LLM API key (or LOCAL_API_KEY env var)
|
|
1033
1346
|
--port <port> Server port (default: 4100)
|
|
1034
1347
|
--host <host> Server bind address (default: 127.0.0.1)
|
|
1035
|
-
--domain <domain> Canonical domain for project create
|
|
1348
|
+
--domain <domain> Canonical domain for project create/update
|
|
1349
|
+
--owned-domain <domain> Additional owned domain for citation matching (repeatable)
|
|
1350
|
+
--add-domain <domain> Add an owned domain (project update, repeatable)
|
|
1351
|
+
--remove-domain <domain> Remove an owned domain (project update, repeatable)
|
|
1352
|
+
--display-name <name> Display name for project create/update
|
|
1036
1353
|
--country <code> Country code (default: US)
|
|
1037
1354
|
--language <lang> Language code (default: en)
|
|
1038
|
-
--provider <name> Provider to use (gemini, openai, claude)
|
|
1355
|
+
--provider <name> Provider to use (gemini, openai, claude, local)
|
|
1356
|
+
--format <fmt> Output format: text (default) or json
|
|
1357
|
+
--wait Wait for run to complete before returning
|
|
1358
|
+
--all Run all projects (with 'run' command)
|
|
1039
1359
|
--include-results Include results in export
|
|
1040
1360
|
--preset <preset> Schedule preset (daily, weekly, twice-daily, daily@HH, weekly@DAY)
|
|
1041
1361
|
--cron <expr> Cron expression for schedule
|
|
1042
1362
|
--timezone <tz> IANA timezone for schedule (default: UTC)
|
|
1043
1363
|
--webhook <url> Webhook URL for notifications
|
|
1044
1364
|
--events <list> Comma-separated notification events
|
|
1365
|
+
--api-key <key> Provider API key (settings provider)
|
|
1366
|
+
--base-url <url> Provider base URL (settings provider)
|
|
1367
|
+
--model <name> Provider model name (settings provider)
|
|
1368
|
+
--max-concurrent <n> Max concurrent requests per provider
|
|
1369
|
+
--max-per-minute <n> Max requests per minute per provider
|
|
1370
|
+
--max-per-day <n> Max requests per day per provider
|
|
1045
1371
|
`.trim();
|
|
1046
1372
|
var _require = createRequire(import.meta.url);
|
|
1047
1373
|
var { version: VERSION } = _require("../package.json");
|
|
1374
|
+
function extractFormat(cmdArgs) {
|
|
1375
|
+
const idx = cmdArgs.indexOf("--format");
|
|
1376
|
+
if (idx !== -1 && cmdArgs[idx + 1] === "json") return "json";
|
|
1377
|
+
return "text";
|
|
1378
|
+
}
|
|
1048
1379
|
async function main() {
|
|
1049
1380
|
const args = process.argv.slice(2);
|
|
1050
1381
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
@@ -1056,6 +1387,7 @@ async function main() {
|
|
|
1056
1387
|
return;
|
|
1057
1388
|
}
|
|
1058
1389
|
const command = args[0];
|
|
1390
|
+
const format = extractFormat(args);
|
|
1059
1391
|
if (command !== "telemetry" && command !== "init" && isTelemetryEnabled() && isFirstRun()) {
|
|
1060
1392
|
showFirstRunNotice();
|
|
1061
1393
|
getOrCreateAnonymousId();
|
|
@@ -1111,6 +1443,22 @@ async function main() {
|
|
|
1111
1443
|
await serveCommand();
|
|
1112
1444
|
break;
|
|
1113
1445
|
}
|
|
1446
|
+
case "start": {
|
|
1447
|
+
const { values } = parseArgs({
|
|
1448
|
+
args: args.slice(1),
|
|
1449
|
+
options: {
|
|
1450
|
+
port: { type: "string", short: "p", default: "4100" },
|
|
1451
|
+
host: { type: "string", short: "H" }
|
|
1452
|
+
},
|
|
1453
|
+
allowPositionals: false
|
|
1454
|
+
});
|
|
1455
|
+
await startDaemon({ port: values.port, host: values.host });
|
|
1456
|
+
break;
|
|
1457
|
+
}
|
|
1458
|
+
case "stop": {
|
|
1459
|
+
stopDaemon();
|
|
1460
|
+
break;
|
|
1461
|
+
}
|
|
1114
1462
|
case "project": {
|
|
1115
1463
|
const subcommand = args[1];
|
|
1116
1464
|
switch (subcommand) {
|
|
@@ -1124,22 +1472,56 @@ async function main() {
|
|
|
1124
1472
|
args: args.slice(3),
|
|
1125
1473
|
options: {
|
|
1126
1474
|
domain: { type: "string", short: "d" },
|
|
1475
|
+
"owned-domain": { type: "string", multiple: true },
|
|
1127
1476
|
country: { type: "string", default: "US" },
|
|
1128
1477
|
language: { type: "string", default: "en" },
|
|
1129
|
-
"display-name": { type: "string" }
|
|
1478
|
+
"display-name": { type: "string" },
|
|
1479
|
+
format: { type: "string" }
|
|
1130
1480
|
},
|
|
1131
1481
|
allowPositionals: false
|
|
1132
1482
|
});
|
|
1133
1483
|
await createProject(name, {
|
|
1134
1484
|
domain: values.domain ?? name,
|
|
1485
|
+
ownedDomains: values["owned-domain"] ?? [],
|
|
1135
1486
|
country: values.country ?? "US",
|
|
1136
1487
|
language: values.language ?? "en",
|
|
1137
1488
|
displayName: values["display-name"] ?? name
|
|
1138
1489
|
});
|
|
1139
1490
|
break;
|
|
1140
1491
|
}
|
|
1492
|
+
case "update": {
|
|
1493
|
+
const name = args[2];
|
|
1494
|
+
if (!name) {
|
|
1495
|
+
console.error("Error: project name is required");
|
|
1496
|
+
process.exit(1);
|
|
1497
|
+
}
|
|
1498
|
+
const { values } = parseArgs({
|
|
1499
|
+
args: args.slice(3),
|
|
1500
|
+
options: {
|
|
1501
|
+
domain: { type: "string", short: "d" },
|
|
1502
|
+
"owned-domain": { type: "string", multiple: true },
|
|
1503
|
+
"add-domain": { type: "string", multiple: true },
|
|
1504
|
+
"remove-domain": { type: "string", multiple: true },
|
|
1505
|
+
country: { type: "string" },
|
|
1506
|
+
language: { type: "string" },
|
|
1507
|
+
"display-name": { type: "string" },
|
|
1508
|
+
format: { type: "string" }
|
|
1509
|
+
},
|
|
1510
|
+
allowPositionals: false
|
|
1511
|
+
});
|
|
1512
|
+
await updateProjectSettings(name, {
|
|
1513
|
+
displayName: values["display-name"],
|
|
1514
|
+
domain: values.domain,
|
|
1515
|
+
ownedDomains: values["owned-domain"],
|
|
1516
|
+
addOwnedDomain: values["add-domain"],
|
|
1517
|
+
removeOwnedDomain: values["remove-domain"],
|
|
1518
|
+
country: values.country,
|
|
1519
|
+
language: values.language
|
|
1520
|
+
});
|
|
1521
|
+
break;
|
|
1522
|
+
}
|
|
1141
1523
|
case "list":
|
|
1142
|
-
await listProjects();
|
|
1524
|
+
await listProjects(format);
|
|
1143
1525
|
break;
|
|
1144
1526
|
case "show": {
|
|
1145
1527
|
const name = args[2];
|
|
@@ -1147,7 +1529,7 @@ async function main() {
|
|
|
1147
1529
|
console.error("Error: project name is required");
|
|
1148
1530
|
process.exit(1);
|
|
1149
1531
|
}
|
|
1150
|
-
await showProject(name);
|
|
1532
|
+
await showProject(name, format);
|
|
1151
1533
|
break;
|
|
1152
1534
|
}
|
|
1153
1535
|
case "delete": {
|
|
@@ -1161,7 +1543,7 @@ async function main() {
|
|
|
1161
1543
|
}
|
|
1162
1544
|
default:
|
|
1163
1545
|
console.error(`Unknown project subcommand: ${subcommand ?? "(none)"}`);
|
|
1164
|
-
console.log("Available: create, list, show, delete");
|
|
1546
|
+
console.log("Available: create, update, list, show, delete");
|
|
1165
1547
|
process.exit(1);
|
|
1166
1548
|
}
|
|
1167
1549
|
break;
|
|
@@ -1171,7 +1553,7 @@ async function main() {
|
|
|
1171
1553
|
switch (subcommand) {
|
|
1172
1554
|
case "add": {
|
|
1173
1555
|
const project = args[2];
|
|
1174
|
-
const kws = args.slice(3);
|
|
1556
|
+
const kws = args.slice(3).filter((a, i, arr) => !a.startsWith("--") && !(i > 0 && arr[i - 1].startsWith("--")));
|
|
1175
1557
|
if (!project || kws.length === 0) {
|
|
1176
1558
|
console.error("Error: project name and at least one key phrase required");
|
|
1177
1559
|
process.exit(1);
|
|
@@ -1185,7 +1567,7 @@ async function main() {
|
|
|
1185
1567
|
console.error("Error: project name is required");
|
|
1186
1568
|
process.exit(1);
|
|
1187
1569
|
}
|
|
1188
|
-
await listKeywords(project);
|
|
1570
|
+
await listKeywords(project, format);
|
|
1189
1571
|
break;
|
|
1190
1572
|
}
|
|
1191
1573
|
case "import": {
|
|
@@ -1209,7 +1591,8 @@ async function main() {
|
|
|
1209
1591
|
options: {
|
|
1210
1592
|
provider: { type: "string" },
|
|
1211
1593
|
count: { type: "string" },
|
|
1212
|
-
save: { type: "boolean", default: false }
|
|
1594
|
+
save: { type: "boolean", default: false },
|
|
1595
|
+
format: { type: "string" }
|
|
1213
1596
|
},
|
|
1214
1597
|
allowPositionals: false
|
|
1215
1598
|
});
|
|
@@ -1235,7 +1618,7 @@ async function main() {
|
|
|
1235
1618
|
switch (subcommand) {
|
|
1236
1619
|
case "add": {
|
|
1237
1620
|
const project = args[2];
|
|
1238
|
-
const domains = args.slice(3);
|
|
1621
|
+
const domains = args.slice(3).filter((a, i, arr) => !a.startsWith("--") && !(i > 0 && arr[i - 1].startsWith("--")));
|
|
1239
1622
|
if (!project || domains.length === 0) {
|
|
1240
1623
|
console.error("Error: project name and at least one domain required");
|
|
1241
1624
|
process.exit(1);
|
|
@@ -1249,7 +1632,7 @@ async function main() {
|
|
|
1249
1632
|
console.error("Error: project name is required");
|
|
1250
1633
|
process.exit(1);
|
|
1251
1634
|
}
|
|
1252
|
-
await listCompetitors(project);
|
|
1635
|
+
await listCompetitors(project, format);
|
|
1253
1636
|
break;
|
|
1254
1637
|
}
|
|
1255
1638
|
default:
|
|
@@ -1260,19 +1643,48 @@ async function main() {
|
|
|
1260
1643
|
break;
|
|
1261
1644
|
}
|
|
1262
1645
|
case "run": {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1646
|
+
if (args[1] === "show") {
|
|
1647
|
+
const id = args[2];
|
|
1648
|
+
if (!id) {
|
|
1649
|
+
console.error("Error: run ID is required");
|
|
1650
|
+
process.exit(1);
|
|
1651
|
+
}
|
|
1652
|
+
await showRun(id, format);
|
|
1653
|
+
break;
|
|
1267
1654
|
}
|
|
1268
1655
|
const runParsed = parseArgs({
|
|
1269
|
-
args: args.slice(
|
|
1656
|
+
args: args.slice(1),
|
|
1270
1657
|
options: {
|
|
1271
|
-
provider: { type: "string" }
|
|
1658
|
+
provider: { type: "string" },
|
|
1659
|
+
wait: { type: "boolean", default: false },
|
|
1660
|
+
all: { type: "boolean", default: false },
|
|
1661
|
+
format: { type: "string" }
|
|
1272
1662
|
},
|
|
1273
|
-
allowPositionals:
|
|
1663
|
+
allowPositionals: true
|
|
1274
1664
|
});
|
|
1275
|
-
|
|
1665
|
+
const runFormat = runParsed.values.format === "json" ? "json" : format;
|
|
1666
|
+
if (runParsed.values.all) {
|
|
1667
|
+
if (runParsed.positionals.length > 0) {
|
|
1668
|
+
console.error("Error: --all cannot be combined with a project name");
|
|
1669
|
+
process.exit(1);
|
|
1670
|
+
}
|
|
1671
|
+
await triggerRunAll({
|
|
1672
|
+
provider: runParsed.values.provider,
|
|
1673
|
+
wait: runParsed.values.wait,
|
|
1674
|
+
format: runFormat
|
|
1675
|
+
});
|
|
1676
|
+
} else {
|
|
1677
|
+
const project = runParsed.positionals[0];
|
|
1678
|
+
if (!project) {
|
|
1679
|
+
console.error("Error: project name is required (or use --all)");
|
|
1680
|
+
process.exit(1);
|
|
1681
|
+
}
|
|
1682
|
+
await triggerRun(project, {
|
|
1683
|
+
provider: runParsed.values.provider,
|
|
1684
|
+
wait: runParsed.values.wait,
|
|
1685
|
+
format: runFormat
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1276
1688
|
break;
|
|
1277
1689
|
}
|
|
1278
1690
|
case "runs": {
|
|
@@ -1281,7 +1693,7 @@ async function main() {
|
|
|
1281
1693
|
console.error("Error: project name is required");
|
|
1282
1694
|
process.exit(1);
|
|
1283
1695
|
}
|
|
1284
|
-
await listRuns(project);
|
|
1696
|
+
await listRuns(project, format);
|
|
1285
1697
|
break;
|
|
1286
1698
|
}
|
|
1287
1699
|
case "status": {
|
|
@@ -1290,7 +1702,7 @@ async function main() {
|
|
|
1290
1702
|
console.error("Error: project name is required");
|
|
1291
1703
|
process.exit(1);
|
|
1292
1704
|
}
|
|
1293
|
-
await showStatus(project);
|
|
1705
|
+
await showStatus(project, format);
|
|
1294
1706
|
break;
|
|
1295
1707
|
}
|
|
1296
1708
|
case "evidence": {
|
|
@@ -1299,7 +1711,7 @@ async function main() {
|
|
|
1299
1711
|
console.error("Error: project name is required");
|
|
1300
1712
|
process.exit(1);
|
|
1301
1713
|
}
|
|
1302
|
-
await showEvidence(project);
|
|
1714
|
+
await showEvidence(project, format);
|
|
1303
1715
|
break;
|
|
1304
1716
|
}
|
|
1305
1717
|
case "history": {
|
|
@@ -1308,7 +1720,7 @@ async function main() {
|
|
|
1308
1720
|
console.error("Error: project name is required");
|
|
1309
1721
|
process.exit(1);
|
|
1310
1722
|
}
|
|
1311
|
-
await showHistory(project);
|
|
1723
|
+
await showHistory(project, format);
|
|
1312
1724
|
break;
|
|
1313
1725
|
}
|
|
1314
1726
|
case "export": {
|
|
@@ -1357,7 +1769,8 @@ async function main() {
|
|
|
1357
1769
|
preset: { type: "string" },
|
|
1358
1770
|
cron: { type: "string" },
|
|
1359
1771
|
timezone: { type: "string" },
|
|
1360
|
-
provider: { type: "string", multiple: true }
|
|
1772
|
+
provider: { type: "string", multiple: true },
|
|
1773
|
+
format: { type: "string" }
|
|
1361
1774
|
},
|
|
1362
1775
|
allowPositionals: false
|
|
1363
1776
|
});
|
|
@@ -1374,7 +1787,7 @@ async function main() {
|
|
|
1374
1787
|
break;
|
|
1375
1788
|
}
|
|
1376
1789
|
case "show":
|
|
1377
|
-
await showSchedule(schedProject);
|
|
1790
|
+
await showSchedule(schedProject, format);
|
|
1378
1791
|
break;
|
|
1379
1792
|
case "enable":
|
|
1380
1793
|
await enableSchedule(schedProject);
|
|
@@ -1394,6 +1807,10 @@ async function main() {
|
|
|
1394
1807
|
}
|
|
1395
1808
|
case "notify": {
|
|
1396
1809
|
const notifSubcommand = args[1];
|
|
1810
|
+
if (notifSubcommand === "events") {
|
|
1811
|
+
listEvents(format);
|
|
1812
|
+
break;
|
|
1813
|
+
}
|
|
1397
1814
|
const notifProject = args[2];
|
|
1398
1815
|
if (!notifProject && notifSubcommand !== void 0) {
|
|
1399
1816
|
console.error("Error: project name is required");
|
|
@@ -1405,7 +1822,8 @@ async function main() {
|
|
|
1405
1822
|
args: args.slice(3),
|
|
1406
1823
|
options: {
|
|
1407
1824
|
webhook: { type: "string" },
|
|
1408
|
-
events: { type: "string" }
|
|
1825
|
+
events: { type: "string" },
|
|
1826
|
+
format: { type: "string" }
|
|
1409
1827
|
},
|
|
1410
1828
|
allowPositionals: false
|
|
1411
1829
|
});
|
|
@@ -1414,7 +1832,7 @@ async function main() {
|
|
|
1414
1832
|
process.exit(1);
|
|
1415
1833
|
}
|
|
1416
1834
|
if (!values.events) {
|
|
1417
|
-
console.error(
|
|
1835
|
+
console.error('Error: --events is required (comma-separated). Use "canonry notify events" to see valid events.');
|
|
1418
1836
|
process.exit(1);
|
|
1419
1837
|
}
|
|
1420
1838
|
await addNotification(notifProject, {
|
|
@@ -1424,7 +1842,7 @@ async function main() {
|
|
|
1424
1842
|
break;
|
|
1425
1843
|
}
|
|
1426
1844
|
case "list":
|
|
1427
|
-
await listNotifications(notifProject);
|
|
1845
|
+
await listNotifications(notifProject, format);
|
|
1428
1846
|
break;
|
|
1429
1847
|
case "remove": {
|
|
1430
1848
|
const notifId = args[3];
|
|
@@ -1446,7 +1864,7 @@ async function main() {
|
|
|
1446
1864
|
}
|
|
1447
1865
|
default:
|
|
1448
1866
|
console.error(`Unknown notify subcommand: ${notifSubcommand ?? "(none)"}`);
|
|
1449
|
-
console.log("Available: add, list, remove, test");
|
|
1867
|
+
console.log("Available: add, list, remove, test, events");
|
|
1450
1868
|
process.exit(1);
|
|
1451
1869
|
}
|
|
1452
1870
|
break;
|
|
@@ -1464,7 +1882,11 @@ async function main() {
|
|
|
1464
1882
|
options: {
|
|
1465
1883
|
"api-key": { type: "string" },
|
|
1466
1884
|
"base-url": { type: "string" },
|
|
1467
|
-
model: { type: "string" }
|
|
1885
|
+
model: { type: "string" },
|
|
1886
|
+
"max-concurrent": { type: "string" },
|
|
1887
|
+
"max-per-minute": { type: "string" },
|
|
1888
|
+
"max-per-day": { type: "string" },
|
|
1889
|
+
format: { type: "string" }
|
|
1468
1890
|
},
|
|
1469
1891
|
allowPositionals: false
|
|
1470
1892
|
});
|
|
@@ -1479,9 +1901,18 @@ async function main() {
|
|
|
1479
1901
|
process.exit(1);
|
|
1480
1902
|
}
|
|
1481
1903
|
}
|
|
1482
|
-
|
|
1904
|
+
const quota = {};
|
|
1905
|
+
if (values["max-concurrent"]) quota.maxConcurrency = parseInt(values["max-concurrent"], 10);
|
|
1906
|
+
if (values["max-per-minute"]) quota.maxRequestsPerMinute = parseInt(values["max-per-minute"], 10);
|
|
1907
|
+
if (values["max-per-day"]) quota.maxRequestsPerDay = parseInt(values["max-per-day"], 10);
|
|
1908
|
+
await setProvider(name, {
|
|
1909
|
+
apiKey: values["api-key"],
|
|
1910
|
+
baseUrl: values["base-url"],
|
|
1911
|
+
model: values.model,
|
|
1912
|
+
quota: Object.keys(quota).length > 0 ? quota : void 0
|
|
1913
|
+
});
|
|
1483
1914
|
} else {
|
|
1484
|
-
await showSettings();
|
|
1915
|
+
await showSettings(format);
|
|
1485
1916
|
}
|
|
1486
1917
|
break;
|
|
1487
1918
|
}
|