@ainyc/canonry 3.2.5 → 3.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/aero/SKILL.md +16 -4
- package/assets/agent-workspace/skills/canonry-setup/SKILL.md +30 -149
- package/assets/agent-workspace/skills/canonry-setup/references/aeo-analysis.md +15 -0
- package/assets/agent-workspace/skills/canonry-setup/references/canonry-cli.md +29 -11
- package/assets/assets/{index-B8epngdo.js → index-C1kUp1aS.js} +107 -107
- package/assets/assets/{index-CQGbgHZY.css → index-JG7aBJrz.css} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-PS7JRDL3.js → chunk-ALMP3NBQ.js} +136 -67
- package/dist/{chunk-AQ3AS25Y.js → chunk-HQ47AA6H.js} +72 -42
- package/dist/cli.js +531 -60
- package/dist/index.js +2 -2
- package/dist/mcp.js +1 -1
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -17,17 +17,19 @@ import {
|
|
|
17
17
|
setGoogleAuthConfig,
|
|
18
18
|
showFirstRunNotice,
|
|
19
19
|
trackEvent
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-HQ47AA6H.js";
|
|
21
21
|
import {
|
|
22
22
|
CcReleaseSyncStatuses,
|
|
23
23
|
CheckScopes,
|
|
24
24
|
CheckStatuses,
|
|
25
25
|
CliError,
|
|
26
|
+
CodingAgents,
|
|
26
27
|
EXIT_SYSTEM_ERROR,
|
|
27
28
|
EXIT_USER_ERROR,
|
|
28
29
|
ProviderNames,
|
|
29
30
|
RunKinds,
|
|
30
31
|
RunStatuses,
|
|
32
|
+
SkillsClients,
|
|
31
33
|
configExists,
|
|
32
34
|
createApiClient,
|
|
33
35
|
determineAnswerMentioned,
|
|
@@ -44,8 +46,9 @@ import {
|
|
|
44
46
|
resolveProviderInput,
|
|
45
47
|
saveConfig,
|
|
46
48
|
saveConfigPatch,
|
|
49
|
+
skillsClientSchema,
|
|
47
50
|
usageError
|
|
48
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-ALMP3NBQ.js";
|
|
49
52
|
import {
|
|
50
53
|
apiKeys,
|
|
51
54
|
competitors,
|
|
@@ -68,9 +71,9 @@ import { parseArgs } from "util";
|
|
|
68
71
|
function commandId(spec) {
|
|
69
72
|
return spec.path.join(".");
|
|
70
73
|
}
|
|
71
|
-
function matchesPath(args,
|
|
72
|
-
if (args.length <
|
|
73
|
-
return
|
|
74
|
+
function matchesPath(args, path9) {
|
|
75
|
+
if (args.length < path9.length) return false;
|
|
76
|
+
return path9.every((segment, index) => args[index] === segment);
|
|
74
77
|
}
|
|
75
78
|
function withFormatOption(options) {
|
|
76
79
|
if (!options) {
|
|
@@ -448,6 +451,131 @@ async function backfillAiReferralPathsCommand(opts) {
|
|
|
448
451
|
console.log(` Updated: ${updated}`);
|
|
449
452
|
console.log(` Unchanged: ${unchanged}`);
|
|
450
453
|
}
|
|
454
|
+
async function backfillAnswerMentionsCommand(opts) {
|
|
455
|
+
const config = loadConfig();
|
|
456
|
+
const db = createClient(config.database);
|
|
457
|
+
migrate(db);
|
|
458
|
+
const projectFilter = opts?.project?.trim();
|
|
459
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
460
|
+
let examined = 0;
|
|
461
|
+
let updated = 0;
|
|
462
|
+
let mentioned = 0;
|
|
463
|
+
if (scopedProjects.length > 0) {
|
|
464
|
+
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and(
|
|
465
|
+
eq(runs.kind, RunKinds["answer-visibility"]),
|
|
466
|
+
inArray(runs.projectId, scopedProjects.map((project) => project.id))
|
|
467
|
+
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq(runs.kind, RunKinds["answer-visibility"])).all();
|
|
468
|
+
const runIdsByProject = /* @__PURE__ */ new Map();
|
|
469
|
+
for (const run of runRows) {
|
|
470
|
+
const existing = runIdsByProject.get(run.projectId);
|
|
471
|
+
if (existing) existing.push(run.id);
|
|
472
|
+
else runIdsByProject.set(run.projectId, [run.id]);
|
|
473
|
+
}
|
|
474
|
+
for (const project of scopedProjects) {
|
|
475
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq(competitors.projectId, project.id)).all().map((row) => row.domain);
|
|
476
|
+
const runIds = runIdsByProject.get(project.id) ?? [];
|
|
477
|
+
if (runIds.length === 0) continue;
|
|
478
|
+
const projectDomains = effectiveDomains({
|
|
479
|
+
canonicalDomain: project.canonicalDomain,
|
|
480
|
+
ownedDomains: parseJsonColumn(project.ownedDomains, [])
|
|
481
|
+
});
|
|
482
|
+
for (let offset = 0; offset < runIds.length; offset += SNAPSHOT_BATCH_SIZE) {
|
|
483
|
+
const batchRunIds = runIds.slice(offset, offset + SNAPSHOT_BATCH_SIZE);
|
|
484
|
+
const snapshotRows = db.select({
|
|
485
|
+
id: querySnapshots.id,
|
|
486
|
+
provider: querySnapshots.provider,
|
|
487
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
488
|
+
answerText: querySnapshots.answerText,
|
|
489
|
+
citedDomains: querySnapshots.citedDomains,
|
|
490
|
+
competitorOverlap: querySnapshots.competitorOverlap,
|
|
491
|
+
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
492
|
+
rawResponse: querySnapshots.rawResponse
|
|
493
|
+
}).from(querySnapshots).where(inArray(querySnapshots.runId, batchRunIds)).all();
|
|
494
|
+
const pendingUpdates = [];
|
|
495
|
+
for (const snapshot of snapshotRows) {
|
|
496
|
+
examined++;
|
|
497
|
+
const answerText = snapshot.answerText ?? "";
|
|
498
|
+
const nextAnswerMentioned = determineAnswerMentioned(answerText, project.displayName, projectDomains);
|
|
499
|
+
if (nextAnswerMentioned) mentioned++;
|
|
500
|
+
const citedDomains = parseJsonColumn(snapshot.citedDomains, []);
|
|
501
|
+
const groundingSources = readStoredGroundingSources(snapshot.rawResponse);
|
|
502
|
+
const normalized = {
|
|
503
|
+
provider: snapshot.provider,
|
|
504
|
+
answerText,
|
|
505
|
+
citedDomains,
|
|
506
|
+
groundingSources,
|
|
507
|
+
searchQueries: []
|
|
508
|
+
};
|
|
509
|
+
const nextCompetitorOverlap = JSON.stringify(
|
|
510
|
+
computeCompetitorOverlap(normalized, competitorDomains)
|
|
511
|
+
);
|
|
512
|
+
const nextRecommendedCompetitors = JSON.stringify(
|
|
513
|
+
extractRecommendedCompetitors(
|
|
514
|
+
answerText,
|
|
515
|
+
projectDomains,
|
|
516
|
+
citedDomains,
|
|
517
|
+
competitorDomains
|
|
518
|
+
)
|
|
519
|
+
);
|
|
520
|
+
const nextPatch = {};
|
|
521
|
+
if (snapshot.answerMentioned !== nextAnswerMentioned) {
|
|
522
|
+
nextPatch.answerMentioned = nextAnswerMentioned;
|
|
523
|
+
}
|
|
524
|
+
if (snapshot.competitorOverlap !== nextCompetitorOverlap) {
|
|
525
|
+
nextPatch.competitorOverlap = nextCompetitorOverlap;
|
|
526
|
+
}
|
|
527
|
+
if (snapshot.recommendedCompetitors !== nextRecommendedCompetitors) {
|
|
528
|
+
nextPatch.recommendedCompetitors = nextRecommendedCompetitors;
|
|
529
|
+
}
|
|
530
|
+
if (Object.keys(nextPatch).length > 0) {
|
|
531
|
+
pendingUpdates.push({ id: snapshot.id, patch: nextPatch });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (pendingUpdates.length > 0) {
|
|
535
|
+
db.transaction((tx) => {
|
|
536
|
+
for (const update of pendingUpdates) {
|
|
537
|
+
tx.update(querySnapshots).set(update.patch).where(eq(querySnapshots.id, update.id)).run();
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
updated += pendingUpdates.length;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const result = {
|
|
546
|
+
project: projectFilter ?? null,
|
|
547
|
+
projects: scopedProjects.length,
|
|
548
|
+
examined,
|
|
549
|
+
updated,
|
|
550
|
+
mentioned
|
|
551
|
+
};
|
|
552
|
+
if (opts?.format === "json") {
|
|
553
|
+
console.log(JSON.stringify(result, null, 2));
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
console.log("Answer mentions backfill complete.\n");
|
|
557
|
+
if (projectFilter) console.log(` Project: ${projectFilter}`);
|
|
558
|
+
console.log(` Projects: ${scopedProjects.length}`);
|
|
559
|
+
console.log(` Examined: ${examined}`);
|
|
560
|
+
console.log(` Updated: ${updated}`);
|
|
561
|
+
console.log(` Mentioned: ${mentioned}`);
|
|
562
|
+
}
|
|
563
|
+
function readStoredGroundingSources(rawResponse) {
|
|
564
|
+
const envelope = parseJsonColumn(rawResponse, {});
|
|
565
|
+
const sources = envelope.groundingSources;
|
|
566
|
+
if (!Array.isArray(sources)) return [];
|
|
567
|
+
const result = [];
|
|
568
|
+
for (const source of sources) {
|
|
569
|
+
if (source && typeof source === "object") {
|
|
570
|
+
const uri = source.uri;
|
|
571
|
+
const title = source.title;
|
|
572
|
+
if (typeof uri === "string") {
|
|
573
|
+
result.push({ uri, title: typeof title === "string" ? title : "" });
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return result;
|
|
578
|
+
}
|
|
451
579
|
async function backfillInsightsCommand(project, opts) {
|
|
452
580
|
const { IntelligenceService } = await import("./intelligence-service-FNJTFSI3.js");
|
|
453
581
|
const config = loadConfig();
|
|
@@ -628,6 +756,20 @@ var BACKFILL_CLI_COMMANDS = [
|
|
|
628
756
|
});
|
|
629
757
|
}
|
|
630
758
|
},
|
|
759
|
+
{
|
|
760
|
+
path: ["backfill", "answer-mentions"],
|
|
761
|
+
usage: "canonry backfill answer-mentions [--project <name>] [--format json]",
|
|
762
|
+
options: {
|
|
763
|
+
project: stringOption()
|
|
764
|
+
},
|
|
765
|
+
allowPositionals: false,
|
|
766
|
+
run: async (input) => {
|
|
767
|
+
await backfillAnswerMentionsCommand({
|
|
768
|
+
project: getString(input.values, "project"),
|
|
769
|
+
format: input.format
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
},
|
|
631
773
|
{
|
|
632
774
|
path: ["backfill", "insights"],
|
|
633
775
|
usage: "canonry backfill insights <project> [--from-run <id>] [--to-run <id>] [--format json]",
|
|
@@ -675,12 +817,12 @@ var BACKFILL_CLI_COMMANDS = [
|
|
|
675
817
|
},
|
|
676
818
|
{
|
|
677
819
|
path: ["backfill"],
|
|
678
|
-
usage: "canonry backfill <answer-visibility|insights|normalized-paths|ai-referral-paths> [options]",
|
|
820
|
+
usage: "canonry backfill <answer-visibility|answer-mentions|insights|normalized-paths|ai-referral-paths> [options]",
|
|
679
821
|
run: async (input) => {
|
|
680
822
|
unknownSubcommand(input.positionals[0], {
|
|
681
823
|
command: "backfill",
|
|
682
|
-
usage: "canonry backfill <answer-visibility|insights|normalized-paths|ai-referral-paths> [options]",
|
|
683
|
-
available: ["answer-visibility", "insights", "normalized-paths", "ai-referral-paths"]
|
|
824
|
+
usage: "canonry backfill <answer-visibility|answer-mentions|insights|normalized-paths|ai-referral-paths> [options]",
|
|
825
|
+
available: ["answer-visibility", "answer-mentions", "insights", "normalized-paths", "ai-referral-paths"]
|
|
684
826
|
});
|
|
685
827
|
}
|
|
686
828
|
}
|
|
@@ -1873,9 +2015,9 @@ async function gaConnect(project, opts) {
|
|
|
1873
2015
|
propertyId: opts.propertyId
|
|
1874
2016
|
};
|
|
1875
2017
|
if (opts.keyFile) {
|
|
1876
|
-
const
|
|
2018
|
+
const fs10 = await import("fs");
|
|
1877
2019
|
try {
|
|
1878
|
-
const content =
|
|
2020
|
+
const content = fs10.readFileSync(opts.keyFile, "utf-8");
|
|
1879
2021
|
JSON.parse(content);
|
|
1880
2022
|
body.keyJson = content;
|
|
1881
2023
|
} catch (e) {
|
|
@@ -5739,13 +5881,307 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
|
|
|
5739
5881
|
}
|
|
5740
5882
|
];
|
|
5741
5883
|
|
|
5884
|
+
// src/commands/skills.ts
|
|
5885
|
+
import fs4 from "fs";
|
|
5886
|
+
import path3 from "path";
|
|
5887
|
+
import { fileURLToPath } from "url";
|
|
5888
|
+
var BUNDLED_SKILL_NAMES = ["canonry-setup", "aero"];
|
|
5889
|
+
function resolveBundledSkillsRoot(pkgDir) {
|
|
5890
|
+
const here = pkgDir ?? path3.dirname(fileURLToPath(import.meta.url));
|
|
5891
|
+
const candidates = [
|
|
5892
|
+
path3.join(here, "../assets/agent-workspace/skills"),
|
|
5893
|
+
path3.join(here, "../../assets/agent-workspace/skills"),
|
|
5894
|
+
path3.join(here, "../../../../skills")
|
|
5895
|
+
];
|
|
5896
|
+
for (const candidate of candidates) {
|
|
5897
|
+
if (BUNDLED_SKILL_NAMES.every((name) => fs4.existsSync(path3.join(candidate, name, "SKILL.md")))) {
|
|
5898
|
+
return candidate;
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
throw new CliError({
|
|
5902
|
+
code: "INTERNAL_ERROR",
|
|
5903
|
+
message: `Bundled skills not found. Searched:
|
|
5904
|
+
${candidates.join("\n ")}`,
|
|
5905
|
+
exitCode: 2
|
|
5906
|
+
});
|
|
5907
|
+
}
|
|
5908
|
+
function parseDescription(content) {
|
|
5909
|
+
const fmMatch = /^---\n([\s\S]*?)\n---/.exec(content);
|
|
5910
|
+
if (!fmMatch) return "";
|
|
5911
|
+
const descMatch = /^description:\s*(.+?)$/m.exec(fmMatch[1]);
|
|
5912
|
+
if (!descMatch) return "";
|
|
5913
|
+
return descMatch[1].replace(/^["']|["']$/g, "").trim();
|
|
5914
|
+
}
|
|
5915
|
+
function getBundledSkills(pkgDir) {
|
|
5916
|
+
const root = resolveBundledSkillsRoot(pkgDir);
|
|
5917
|
+
return BUNDLED_SKILL_NAMES.map((name) => {
|
|
5918
|
+
const skillDir = path3.join(root, name);
|
|
5919
|
+
const skillFile = path3.join(skillDir, "SKILL.md");
|
|
5920
|
+
const content = fs4.readFileSync(skillFile, "utf-8");
|
|
5921
|
+
return { name, description: parseDescription(content), bundledPath: skillDir };
|
|
5922
|
+
});
|
|
5923
|
+
}
|
|
5924
|
+
function walkRelative(dir, prefix = "") {
|
|
5925
|
+
const out = [];
|
|
5926
|
+
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
5927
|
+
const rel = prefix ? path3.join(prefix, entry.name) : entry.name;
|
|
5928
|
+
const full = path3.join(dir, entry.name);
|
|
5929
|
+
if (entry.isDirectory()) {
|
|
5930
|
+
out.push(...walkRelative(full, rel));
|
|
5931
|
+
} else if (entry.isFile()) {
|
|
5932
|
+
out.push(rel);
|
|
5933
|
+
}
|
|
5934
|
+
}
|
|
5935
|
+
return out.sort();
|
|
5936
|
+
}
|
|
5937
|
+
function compareDirContent(srcDir, destDir) {
|
|
5938
|
+
if (!fs4.existsSync(destDir)) return "missing";
|
|
5939
|
+
if (!fs4.statSync(destDir).isDirectory()) return "different";
|
|
5940
|
+
const srcFiles = walkRelative(srcDir);
|
|
5941
|
+
const destFiles = walkRelative(destDir);
|
|
5942
|
+
if (srcFiles.length !== destFiles.length) return "different";
|
|
5943
|
+
for (let i = 0; i < srcFiles.length; i++) {
|
|
5944
|
+
if (srcFiles[i] !== destFiles[i]) return "different";
|
|
5945
|
+
const srcBytes = fs4.readFileSync(path3.join(srcDir, srcFiles[i]));
|
|
5946
|
+
const destBytes = fs4.readFileSync(path3.join(destDir, destFiles[i]));
|
|
5947
|
+
if (!srcBytes.equals(destBytes)) return "different";
|
|
5948
|
+
}
|
|
5949
|
+
return "match";
|
|
5950
|
+
}
|
|
5951
|
+
function copyDirRecursive(src, dest) {
|
|
5952
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
5953
|
+
for (const entry of fs4.readdirSync(src, { withFileTypes: true })) {
|
|
5954
|
+
const srcPath = path3.join(src, entry.name);
|
|
5955
|
+
const destPath = path3.join(dest, entry.name);
|
|
5956
|
+
if (entry.isDirectory()) {
|
|
5957
|
+
copyDirRecursive(srcPath, destPath);
|
|
5958
|
+
} else if (entry.isFile()) {
|
|
5959
|
+
fs4.copyFileSync(srcPath, destPath);
|
|
5960
|
+
}
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
function installClaudeSkill(skill, targetDir, force) {
|
|
5964
|
+
const targetPath = path3.join(targetDir, ".claude", "skills", skill.name);
|
|
5965
|
+
const compare = compareDirContent(skill.bundledPath, targetPath);
|
|
5966
|
+
if (compare === "match") {
|
|
5967
|
+
return {
|
|
5968
|
+
skill: skill.name,
|
|
5969
|
+
client: CodingAgents.claude,
|
|
5970
|
+
targetPath,
|
|
5971
|
+
status: "already-installed",
|
|
5972
|
+
message: `Already installed: .claude/skills/${skill.name}`
|
|
5973
|
+
};
|
|
5974
|
+
}
|
|
5975
|
+
if (compare === "different" && !force) {
|
|
5976
|
+
throw new CliError({
|
|
5977
|
+
code: "VALIDATION_ERROR",
|
|
5978
|
+
message: `.claude/skills/${skill.name}/ already exists and differs from the bundled skill. Pass --force to overwrite.`,
|
|
5979
|
+
details: { skill: skill.name, targetPath },
|
|
5980
|
+
exitCode: 1
|
|
5981
|
+
});
|
|
5982
|
+
}
|
|
5983
|
+
if (compare === "different") {
|
|
5984
|
+
fs4.rmSync(targetPath, { recursive: true, force: true });
|
|
5985
|
+
}
|
|
5986
|
+
copyDirRecursive(skill.bundledPath, targetPath);
|
|
5987
|
+
return {
|
|
5988
|
+
skill: skill.name,
|
|
5989
|
+
client: CodingAgents.claude,
|
|
5990
|
+
targetPath,
|
|
5991
|
+
status: compare === "missing" ? "installed" : "updated",
|
|
5992
|
+
message: compare === "missing" ? `Installed .claude/skills/${skill.name}` : `Updated .claude/skills/${skill.name}`
|
|
5993
|
+
};
|
|
5994
|
+
}
|
|
5995
|
+
function installCodexSymlink(skill, targetDir, force) {
|
|
5996
|
+
const codexPath = path3.join(targetDir, ".codex", "skills", skill.name);
|
|
5997
|
+
const claudePath = path3.join(targetDir, ".claude", "skills", skill.name);
|
|
5998
|
+
const linkTarget = path3.relative(path3.dirname(codexPath), claudePath);
|
|
5999
|
+
fs4.mkdirSync(path3.dirname(codexPath), { recursive: true });
|
|
6000
|
+
let stat;
|
|
6001
|
+
try {
|
|
6002
|
+
stat = fs4.lstatSync(codexPath);
|
|
6003
|
+
} catch {
|
|
6004
|
+
stat = void 0;
|
|
6005
|
+
}
|
|
6006
|
+
if (stat?.isSymbolicLink()) {
|
|
6007
|
+
const existing = fs4.readlinkSync(codexPath);
|
|
6008
|
+
if (existing === linkTarget) {
|
|
6009
|
+
return {
|
|
6010
|
+
skill: skill.name,
|
|
6011
|
+
client: CodingAgents.codex,
|
|
6012
|
+
targetPath: codexPath,
|
|
6013
|
+
status: "already-linked",
|
|
6014
|
+
message: `Already linked: .codex/skills/${skill.name}`
|
|
6015
|
+
};
|
|
6016
|
+
}
|
|
6017
|
+
if (!force) {
|
|
6018
|
+
throw new CliError({
|
|
6019
|
+
code: "VALIDATION_ERROR",
|
|
6020
|
+
message: `.codex/skills/${skill.name} is a symlink pointing elsewhere (${existing}). Pass --force to relink.`,
|
|
6021
|
+
details: { skill: skill.name, targetPath: codexPath, existingTarget: existing },
|
|
6022
|
+
exitCode: 1
|
|
6023
|
+
});
|
|
6024
|
+
}
|
|
6025
|
+
fs4.unlinkSync(codexPath);
|
|
6026
|
+
fs4.symlinkSync(linkTarget, codexPath);
|
|
6027
|
+
return {
|
|
6028
|
+
skill: skill.name,
|
|
6029
|
+
client: CodingAgents.codex,
|
|
6030
|
+
targetPath: codexPath,
|
|
6031
|
+
status: "relinked",
|
|
6032
|
+
message: `Relinked .codex/skills/${skill.name} \u2192 ${linkTarget}`
|
|
6033
|
+
};
|
|
6034
|
+
}
|
|
6035
|
+
if (stat) {
|
|
6036
|
+
if (!force) {
|
|
6037
|
+
throw new CliError({
|
|
6038
|
+
code: "VALIDATION_ERROR",
|
|
6039
|
+
message: `.codex/skills/${skill.name} exists but is not a symlink. Pass --force to replace.`,
|
|
6040
|
+
details: { skill: skill.name, targetPath: codexPath },
|
|
6041
|
+
exitCode: 1
|
|
6042
|
+
});
|
|
6043
|
+
}
|
|
6044
|
+
fs4.rmSync(codexPath, { recursive: true, force: true });
|
|
6045
|
+
}
|
|
6046
|
+
fs4.symlinkSync(linkTarget, codexPath);
|
|
6047
|
+
return {
|
|
6048
|
+
skill: skill.name,
|
|
6049
|
+
client: CodingAgents.codex,
|
|
6050
|
+
targetPath: codexPath,
|
|
6051
|
+
status: stat ? "relinked" : "linked",
|
|
6052
|
+
message: stat ? `Replaced and linked .codex/skills/${skill.name} \u2192 ${linkTarget}` : `Linked .codex/skills/${skill.name} \u2192 ${linkTarget}`
|
|
6053
|
+
};
|
|
6054
|
+
}
|
|
6055
|
+
function buildSummaryMessage(results) {
|
|
6056
|
+
const counts = {};
|
|
6057
|
+
for (const r of results) counts[r.status] = (counts[r.status] ?? 0) + 1;
|
|
6058
|
+
const parts = Object.entries(counts).map(([status, n]) => `${n} ${status}`);
|
|
6059
|
+
return `Skills install summary: ${parts.join(", ")}.`;
|
|
6060
|
+
}
|
|
6061
|
+
async function installSkills(opts = {}) {
|
|
6062
|
+
const targetDir = path3.resolve(opts.dir ?? process.cwd());
|
|
6063
|
+
const client = opts.client ?? SkillsClients.all;
|
|
6064
|
+
const force = opts.force ?? false;
|
|
6065
|
+
const allSkills = getBundledSkills();
|
|
6066
|
+
const requestedNames = opts.skills && opts.skills.length > 0 ? opts.skills : allSkills.map((s) => s.name);
|
|
6067
|
+
const knownNames = new Set(allSkills.map((s) => s.name));
|
|
6068
|
+
const unknown = requestedNames.filter((n) => !knownNames.has(n));
|
|
6069
|
+
if (unknown.length > 0) {
|
|
6070
|
+
throw new CliError({
|
|
6071
|
+
code: "VALIDATION_ERROR",
|
|
6072
|
+
message: `Unknown skill(s): ${unknown.join(", ")}. Available: ${[...knownNames].join(", ")}`,
|
|
6073
|
+
details: { unknownSkills: unknown, availableSkills: [...knownNames] },
|
|
6074
|
+
exitCode: 1
|
|
6075
|
+
});
|
|
6076
|
+
}
|
|
6077
|
+
const skillsToInstall = allSkills.filter((s) => requestedNames.includes(s.name));
|
|
6078
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
6079
|
+
const results = [];
|
|
6080
|
+
for (const skill of skillsToInstall) {
|
|
6081
|
+
results.push(installClaudeSkill(skill, targetDir, force));
|
|
6082
|
+
if (client !== SkillsClients.claude) {
|
|
6083
|
+
results.push(installCodexSymlink(skill, targetDir, force));
|
|
6084
|
+
}
|
|
6085
|
+
}
|
|
6086
|
+
return {
|
|
6087
|
+
targetDir,
|
|
6088
|
+
results,
|
|
6089
|
+
message: buildSummaryMessage(results)
|
|
6090
|
+
};
|
|
6091
|
+
}
|
|
6092
|
+
async function listSkills(opts = {}) {
|
|
6093
|
+
const skills = getBundledSkills();
|
|
6094
|
+
if (opts.format === "json") {
|
|
6095
|
+
console.log(JSON.stringify({
|
|
6096
|
+
skills: skills.map((s) => ({
|
|
6097
|
+
name: s.name,
|
|
6098
|
+
description: s.description,
|
|
6099
|
+
claudePath: `.claude/skills/${s.name}`,
|
|
6100
|
+
codexPath: `.codex/skills/${s.name}`
|
|
6101
|
+
}))
|
|
6102
|
+
}, null, 2));
|
|
6103
|
+
return;
|
|
6104
|
+
}
|
|
6105
|
+
console.log("Bundled canonry skills:\n");
|
|
6106
|
+
for (const skill of skills) {
|
|
6107
|
+
console.log(` ${skill.name}`);
|
|
6108
|
+
if (skill.description) console.log(` ${skill.description}`);
|
|
6109
|
+
console.log(` Claude: .claude/skills/${skill.name}/`);
|
|
6110
|
+
console.log(` Codex: .codex/skills/${skill.name} (symlink \u2192 ../../.claude/skills/${skill.name})`);
|
|
6111
|
+
console.log();
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
6114
|
+
function emitInstallSummary(summary, format) {
|
|
6115
|
+
if (format === "json") {
|
|
6116
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
6117
|
+
return;
|
|
6118
|
+
}
|
|
6119
|
+
for (const r of summary.results) console.log(r.message);
|
|
6120
|
+
console.log(`
|
|
6121
|
+
Target: ${summary.targetDir}`);
|
|
6122
|
+
console.log(summary.message);
|
|
6123
|
+
}
|
|
6124
|
+
function parseSkillsClient(value) {
|
|
6125
|
+
if (!value) return SkillsClients.all;
|
|
6126
|
+
const parsed = skillsClientSchema.safeParse(value);
|
|
6127
|
+
if (parsed.success) return parsed.data;
|
|
6128
|
+
const allowed = skillsClientSchema.options;
|
|
6129
|
+
throw new CliError({
|
|
6130
|
+
code: "VALIDATION_ERROR",
|
|
6131
|
+
message: `Invalid --client value "${value}". Must be one of: ${allowed.join(", ")}`,
|
|
6132
|
+
details: { flag: "client", value, allowed },
|
|
6133
|
+
exitCode: 1
|
|
6134
|
+
});
|
|
6135
|
+
}
|
|
6136
|
+
|
|
6137
|
+
// src/cli-commands/skills.ts
|
|
6138
|
+
var SKILLS_CLI_COMMANDS = [
|
|
6139
|
+
{
|
|
6140
|
+
path: ["skills", "list"],
|
|
6141
|
+
usage: "canonry skills list [--format json]",
|
|
6142
|
+
run: async (input) => {
|
|
6143
|
+
await listSkills({ format: input.format });
|
|
6144
|
+
}
|
|
6145
|
+
},
|
|
6146
|
+
{
|
|
6147
|
+
path: ["skills", "install"],
|
|
6148
|
+
usage: "canonry skills install [skill...] [--dir <path>] [--client claude|codex|all] [--force] [--format json]",
|
|
6149
|
+
options: {
|
|
6150
|
+
dir: stringOption(),
|
|
6151
|
+
client: stringOption(),
|
|
6152
|
+
force: { type: "boolean" }
|
|
6153
|
+
},
|
|
6154
|
+
allowPositionals: true,
|
|
6155
|
+
run: async (input) => {
|
|
6156
|
+
const summary = await installSkills({
|
|
6157
|
+
dir: getString(input.values, "dir"),
|
|
6158
|
+
skills: input.positionals.length > 0 ? input.positionals : void 0,
|
|
6159
|
+
client: parseSkillsClient(getString(input.values, "client")),
|
|
6160
|
+
force: getBoolean(input.values, "force")
|
|
6161
|
+
});
|
|
6162
|
+
emitInstallSummary(summary, input.format);
|
|
6163
|
+
}
|
|
6164
|
+
},
|
|
6165
|
+
{
|
|
6166
|
+
path: ["skills"],
|
|
6167
|
+
usage: "canonry skills <list|install> [args]",
|
|
6168
|
+
run: async (input) => {
|
|
6169
|
+
unknownSubcommand(input.positionals[0], {
|
|
6170
|
+
command: "skills",
|
|
6171
|
+
usage: "canonry skills <list|install> [args]",
|
|
6172
|
+
available: ["list", "install"]
|
|
6173
|
+
});
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
];
|
|
6177
|
+
|
|
5742
6178
|
// src/commands/snapshot.ts
|
|
5743
|
-
import
|
|
5744
|
-
import
|
|
6179
|
+
import fs6 from "fs";
|
|
6180
|
+
import path5 from "path";
|
|
5745
6181
|
|
|
5746
6182
|
// src/snapshot-pdf.ts
|
|
5747
|
-
import
|
|
5748
|
-
import
|
|
6183
|
+
import fs5 from "fs";
|
|
6184
|
+
import path4 from "path";
|
|
5749
6185
|
import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
|
|
5750
6186
|
var PAGE_WIDTH = 612;
|
|
5751
6187
|
var PAGE_HEIGHT = 792;
|
|
@@ -5954,9 +6390,9 @@ async function writeSnapshotPdf(report, outputPath) {
|
|
|
5954
6390
|
renderCompetitors(pdf, report);
|
|
5955
6391
|
renderQueries(pdf, report);
|
|
5956
6392
|
const bytes = await doc.save();
|
|
5957
|
-
const resolvedPath =
|
|
5958
|
-
|
|
5959
|
-
|
|
6393
|
+
const resolvedPath = path4.resolve(outputPath);
|
|
6394
|
+
fs5.mkdirSync(path4.dirname(resolvedPath), { recursive: true });
|
|
6395
|
+
fs5.writeFileSync(resolvedPath, bytes);
|
|
5960
6396
|
return resolvedPath;
|
|
5961
6397
|
}
|
|
5962
6398
|
function renderCover(pdf, report) {
|
|
@@ -6114,9 +6550,9 @@ Markdown saved: ${savedMdPath}`);
|
|
|
6114
6550
|
PDF saved: ${savedPdfPath}`);
|
|
6115
6551
|
}
|
|
6116
6552
|
function writeSnapshotMarkdown(report, outputPath) {
|
|
6117
|
-
const resolvedPath =
|
|
6118
|
-
|
|
6119
|
-
|
|
6553
|
+
const resolvedPath = path5.resolve(outputPath);
|
|
6554
|
+
fs6.mkdirSync(path5.dirname(resolvedPath), { recursive: true });
|
|
6555
|
+
fs6.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
|
|
6120
6556
|
return resolvedPath;
|
|
6121
6557
|
}
|
|
6122
6558
|
function formatSnapshotMarkdown(report) {
|
|
@@ -6771,7 +7207,7 @@ var CONTENT_CLI_COMMANDS = [
|
|
|
6771
7207
|
|
|
6772
7208
|
// src/commands/bootstrap.ts
|
|
6773
7209
|
import crypto from "crypto";
|
|
6774
|
-
import
|
|
7210
|
+
import path6 from "path";
|
|
6775
7211
|
import { eq as eq2 } from "drizzle-orm";
|
|
6776
7212
|
|
|
6777
7213
|
// ../config/src/index.ts
|
|
@@ -6918,7 +7354,7 @@ async function bootstrapCommand(_opts) {
|
|
|
6918
7354
|
);
|
|
6919
7355
|
}
|
|
6920
7356
|
const configDir = getConfigDir();
|
|
6921
|
-
const databasePath = env.databasePath ||
|
|
7357
|
+
const databasePath = env.databasePath || path6.join(configDir, "data.db");
|
|
6922
7358
|
const existing = configExists();
|
|
6923
7359
|
const existingConfig = existing ? loadConfig() : void 0;
|
|
6924
7360
|
let rawApiKey;
|
|
@@ -6988,10 +7424,10 @@ async function bootstrapCommand(_opts) {
|
|
|
6988
7424
|
|
|
6989
7425
|
// src/commands/daemon.ts
|
|
6990
7426
|
import { spawn } from "child_process";
|
|
6991
|
-
import
|
|
6992
|
-
import
|
|
7427
|
+
import fs7 from "fs";
|
|
7428
|
+
import path7 from "path";
|
|
6993
7429
|
function getPidPath() {
|
|
6994
|
-
return
|
|
7430
|
+
return path7.join(getConfigDir(), "canonry.pid");
|
|
6995
7431
|
}
|
|
6996
7432
|
function isProcessAlive(pid) {
|
|
6997
7433
|
try {
|
|
@@ -7018,8 +7454,8 @@ async function waitForReady(host, port, maxMs = 1e4) {
|
|
|
7018
7454
|
async function startDaemon(opts) {
|
|
7019
7455
|
const pidPath = getPidPath();
|
|
7020
7456
|
const format = opts.format ?? "text";
|
|
7021
|
-
if (
|
|
7022
|
-
const existingPid = parseInt(
|
|
7457
|
+
if (fs7.existsSync(pidPath)) {
|
|
7458
|
+
const existingPid = parseInt(fs7.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
7023
7459
|
if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
|
|
7024
7460
|
throw new CliError({
|
|
7025
7461
|
code: "DAEMON_ALREADY_RUNNING",
|
|
@@ -7030,9 +7466,9 @@ async function startDaemon(opts) {
|
|
|
7030
7466
|
}
|
|
7031
7467
|
});
|
|
7032
7468
|
}
|
|
7033
|
-
|
|
7469
|
+
fs7.unlinkSync(pidPath);
|
|
7034
7470
|
}
|
|
7035
|
-
const cliPath =
|
|
7471
|
+
const cliPath = path7.resolve(new URL(import.meta.url).pathname);
|
|
7036
7472
|
const inSourceMode = new URL(import.meta.url).pathname.endsWith(".ts");
|
|
7037
7473
|
const args = inSourceMode ? ["--import", "tsx", cliPath, "serve"] : [cliPath, "serve"];
|
|
7038
7474
|
if (opts.port) args.push("--port", opts.port);
|
|
@@ -7051,10 +7487,10 @@ async function startDaemon(opts) {
|
|
|
7051
7487
|
});
|
|
7052
7488
|
}
|
|
7053
7489
|
const configDir = getConfigDir();
|
|
7054
|
-
if (!
|
|
7055
|
-
|
|
7490
|
+
if (!fs7.existsSync(configDir)) {
|
|
7491
|
+
fs7.mkdirSync(configDir, { recursive: true });
|
|
7056
7492
|
}
|
|
7057
|
-
|
|
7493
|
+
fs7.writeFileSync(pidPath, String(child.pid), "utf-8");
|
|
7058
7494
|
const port = opts.port ?? "4100";
|
|
7059
7495
|
const host = opts.host ?? "127.0.0.1";
|
|
7060
7496
|
if (format !== "json") {
|
|
@@ -7063,7 +7499,7 @@ async function startDaemon(opts) {
|
|
|
7063
7499
|
const ready = await waitForReady(host, port);
|
|
7064
7500
|
if (!ready) {
|
|
7065
7501
|
try {
|
|
7066
|
-
|
|
7502
|
+
fs7.unlinkSync(pidPath);
|
|
7067
7503
|
} catch {
|
|
7068
7504
|
}
|
|
7069
7505
|
throw new CliError({
|
|
@@ -7095,7 +7531,7 @@ async function startDaemon(opts) {
|
|
|
7095
7531
|
}
|
|
7096
7532
|
function stopDaemon(format = "text") {
|
|
7097
7533
|
const pidPath = getPidPath();
|
|
7098
|
-
if (!
|
|
7534
|
+
if (!fs7.existsSync(pidPath)) {
|
|
7099
7535
|
if (format === "json") {
|
|
7100
7536
|
console.log(JSON.stringify({
|
|
7101
7537
|
stopped: false,
|
|
@@ -7106,7 +7542,7 @@ function stopDaemon(format = "text") {
|
|
|
7106
7542
|
console.log("Canonry is not running (no PID file found)");
|
|
7107
7543
|
return;
|
|
7108
7544
|
}
|
|
7109
|
-
const pid = parseInt(
|
|
7545
|
+
const pid = parseInt(fs7.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
7110
7546
|
if (isNaN(pid)) {
|
|
7111
7547
|
if (format === "json") {
|
|
7112
7548
|
console.log(JSON.stringify({
|
|
@@ -7117,7 +7553,7 @@ function stopDaemon(format = "text") {
|
|
|
7117
7553
|
} else {
|
|
7118
7554
|
console.error("Invalid PID file. Removing it.");
|
|
7119
7555
|
}
|
|
7120
|
-
|
|
7556
|
+
fs7.unlinkSync(pidPath);
|
|
7121
7557
|
return;
|
|
7122
7558
|
}
|
|
7123
7559
|
if (!isProcessAlive(pid)) {
|
|
@@ -7131,12 +7567,12 @@ function stopDaemon(format = "text") {
|
|
|
7131
7567
|
} else {
|
|
7132
7568
|
console.log(`Canonry is not running (stale PID: ${pid}). Cleaning up.`);
|
|
7133
7569
|
}
|
|
7134
|
-
|
|
7570
|
+
fs7.unlinkSync(pidPath);
|
|
7135
7571
|
return;
|
|
7136
7572
|
}
|
|
7137
7573
|
try {
|
|
7138
7574
|
process.kill(pid, "SIGTERM");
|
|
7139
|
-
|
|
7575
|
+
fs7.unlinkSync(pidPath);
|
|
7140
7576
|
if (format === "json") {
|
|
7141
7577
|
console.log(JSON.stringify({
|
|
7142
7578
|
stopped: true,
|
|
@@ -7160,9 +7596,9 @@ function stopDaemon(format = "text") {
|
|
|
7160
7596
|
|
|
7161
7597
|
// src/commands/init.ts
|
|
7162
7598
|
import crypto2 from "crypto";
|
|
7163
|
-
import
|
|
7599
|
+
import fs8 from "fs";
|
|
7164
7600
|
import readline from "readline";
|
|
7165
|
-
import
|
|
7601
|
+
import path8 from "path";
|
|
7166
7602
|
function prompt(question) {
|
|
7167
7603
|
const rl = readline.createInterface({
|
|
7168
7604
|
input: process.stdin,
|
|
@@ -7180,6 +7616,12 @@ var DEFAULT_QUOTA = {
|
|
|
7180
7616
|
maxRequestsPerMinute: 10,
|
|
7181
7617
|
maxRequestsPerDay: 500
|
|
7182
7618
|
};
|
|
7619
|
+
var PROJECT_MARKERS = [".git", "canonry.yaml", "canonry.yml", "package.json"];
|
|
7620
|
+
function cwdLooksLikeProject(dir) {
|
|
7621
|
+
const home = process.env.HOME ?? "";
|
|
7622
|
+
if (home && path8.resolve(dir) === path8.resolve(home)) return false;
|
|
7623
|
+
return PROJECT_MARKERS.some((marker) => fs8.existsSync(path8.join(dir, marker)));
|
|
7624
|
+
}
|
|
7183
7625
|
var DEFAULT_AGENT_MODELS = {
|
|
7184
7626
|
anthropic: "anthropic/claude-sonnet-4-6",
|
|
7185
7627
|
openai: "openai/gpt-4o",
|
|
@@ -7208,8 +7650,8 @@ async function initCommand(opts) {
|
|
|
7208
7650
|
return void 0;
|
|
7209
7651
|
}
|
|
7210
7652
|
const configDir = getConfigDir();
|
|
7211
|
-
if (!
|
|
7212
|
-
|
|
7653
|
+
if (!fs8.existsSync(configDir)) {
|
|
7654
|
+
fs8.mkdirSync(configDir, { recursive: true });
|
|
7213
7655
|
}
|
|
7214
7656
|
const bootstrapEnv = getBootstrapEnv(process.env, {
|
|
7215
7657
|
GEMINI_API_KEY: opts?.geminiKey,
|
|
@@ -7324,7 +7766,7 @@ async function initCommand(opts) {
|
|
|
7324
7766
|
const rawApiKey = `cnry_${crypto2.randomBytes(16).toString("hex")}`;
|
|
7325
7767
|
const keyHash = crypto2.createHash("sha256").update(rawApiKey).digest("hex");
|
|
7326
7768
|
const keyPrefix = rawApiKey.slice(0, 9);
|
|
7327
|
-
const databasePath =
|
|
7769
|
+
const databasePath = path8.join(configDir, "data.db");
|
|
7328
7770
|
const db = createClient(databasePath);
|
|
7329
7771
|
migrate(db);
|
|
7330
7772
|
db.insert(apiKeys).values({
|
|
@@ -7343,6 +7785,20 @@ async function initCommand(opts) {
|
|
|
7343
7785
|
google
|
|
7344
7786
|
});
|
|
7345
7787
|
const providerNames = Object.keys(providers);
|
|
7788
|
+
let skillsSummary;
|
|
7789
|
+
let skillsTip;
|
|
7790
|
+
if (!opts?.skipSkills) {
|
|
7791
|
+
const skillsTarget = opts?.skillsDir ?? process.cwd();
|
|
7792
|
+
if (cwdLooksLikeProject(skillsTarget)) {
|
|
7793
|
+
try {
|
|
7794
|
+
skillsSummary = await installSkills({ dir: skillsTarget });
|
|
7795
|
+
} catch (err) {
|
|
7796
|
+
skillsTip = `Skills auto-install failed: ${err instanceof Error ? err.message : String(err)}. Run "canonry skills install" manually.`;
|
|
7797
|
+
}
|
|
7798
|
+
} else {
|
|
7799
|
+
skillsTip = 'Run "canonry skills install" in a project directory to add the canonry + Aero playbook to .claude/skills/ and .codex/skills/.';
|
|
7800
|
+
}
|
|
7801
|
+
}
|
|
7346
7802
|
if (format === "json") {
|
|
7347
7803
|
console.log(JSON.stringify({
|
|
7348
7804
|
initialized: true,
|
|
@@ -7351,7 +7807,9 @@ async function initCommand(opts) {
|
|
|
7351
7807
|
apiUrl: `http://localhost:${process.env.CANONRY_PORT || "4100"}`,
|
|
7352
7808
|
apiKey: rawApiKey,
|
|
7353
7809
|
providers: providerNames,
|
|
7354
|
-
googleConfigured: !!google
|
|
7810
|
+
googleConfigured: !!google,
|
|
7811
|
+
skills: skillsSummary,
|
|
7812
|
+
skillsTip
|
|
7355
7813
|
}, null, 2));
|
|
7356
7814
|
} else {
|
|
7357
7815
|
console.log(`
|
|
@@ -7359,6 +7817,13 @@ Config saved to ${getConfigPath()}`);
|
|
|
7359
7817
|
console.log(`Database created at ${databasePath}`);
|
|
7360
7818
|
console.log(`API key: ${rawApiKey}`);
|
|
7361
7819
|
console.log(`Providers: ${providerNames.join(", ")}`);
|
|
7820
|
+
if (skillsSummary) {
|
|
7821
|
+
console.log(`
|
|
7822
|
+
${skillsSummary.message}`);
|
|
7823
|
+
console.log(`Skills target: ${skillsSummary.targetDir}`);
|
|
7824
|
+
}
|
|
7825
|
+
if (skillsTip) console.log(`
|
|
7826
|
+
${skillsTip}`);
|
|
7362
7827
|
}
|
|
7363
7828
|
let agentLLM;
|
|
7364
7829
|
const agentProvider = opts?.agentProvider;
|
|
@@ -7623,7 +8088,7 @@ function applyServerEnv(values) {
|
|
|
7623
8088
|
var SYSTEM_CLI_COMMANDS = [
|
|
7624
8089
|
{
|
|
7625
8090
|
path: ["init"],
|
|
7626
|
-
usage: "canonry init [--force] [--gemini-key <key>] [--openai-key <key>] [--claude-key <key>] [--perplexity-key <key>] [--local-url <url>] [--local-model <name>] [--local-key <key>] [--google-client-id <id>] [--google-client-secret <key>] [--format json]",
|
|
8091
|
+
usage: "canonry init [--force] [--gemini-key <key>] [--openai-key <key>] [--claude-key <key>] [--perplexity-key <key>] [--local-url <url>] [--local-model <name>] [--local-key <key>] [--google-client-id <id>] [--google-client-secret <key>] [--skip-skills] [--skills-dir <path>] [--format json]",
|
|
7627
8092
|
options: {
|
|
7628
8093
|
force: { type: "boolean", short: "f", default: false },
|
|
7629
8094
|
"gemini-key": stringOption(),
|
|
@@ -7634,7 +8099,9 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
7634
8099
|
"local-model": stringOption(),
|
|
7635
8100
|
"local-key": stringOption(),
|
|
7636
8101
|
"google-client-id": stringOption(),
|
|
7637
|
-
"google-client-secret": stringOption()
|
|
8102
|
+
"google-client-secret": stringOption(),
|
|
8103
|
+
"skip-skills": { type: "boolean" },
|
|
8104
|
+
"skills-dir": stringOption()
|
|
7638
8105
|
},
|
|
7639
8106
|
allowPositionals: false,
|
|
7640
8107
|
run: async (input) => {
|
|
@@ -7649,6 +8116,8 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
7649
8116
|
localKey: getString(input.values, "local-key"),
|
|
7650
8117
|
googleClientId: getString(input.values, "google-client-id"),
|
|
7651
8118
|
googleClientSecret: getString(input.values, "google-client-secret"),
|
|
8119
|
+
skipSkills: getBoolean(input.values, "skip-skills"),
|
|
8120
|
+
skillsDir: getString(input.values, "skills-dir"),
|
|
7652
8121
|
format: input.format
|
|
7653
8122
|
});
|
|
7654
8123
|
}
|
|
@@ -7745,7 +8214,7 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
7745
8214
|
];
|
|
7746
8215
|
|
|
7747
8216
|
// src/cli-commands/wordpress.ts
|
|
7748
|
-
import
|
|
8217
|
+
import fs9 from "fs";
|
|
7749
8218
|
|
|
7750
8219
|
// src/commands/wordpress.ts
|
|
7751
8220
|
function getClient18() {
|
|
@@ -7981,12 +8450,12 @@ async function wordpressSetMeta(project, body) {
|
|
|
7981
8450
|
printPageDetail(result);
|
|
7982
8451
|
}
|
|
7983
8452
|
async function wordpressBulkSetMeta(project, opts) {
|
|
7984
|
-
const
|
|
7985
|
-
const
|
|
7986
|
-
const filePath =
|
|
8453
|
+
const fs10 = await import("fs/promises");
|
|
8454
|
+
const path9 = await import("path");
|
|
8455
|
+
const filePath = path9.resolve(opts.from);
|
|
7987
8456
|
let raw;
|
|
7988
8457
|
try {
|
|
7989
|
-
raw = await
|
|
8458
|
+
raw = await fs10.readFile(filePath, "utf8");
|
|
7990
8459
|
} catch {
|
|
7991
8460
|
throw new CliError({
|
|
7992
8461
|
code: "FILE_READ_ERROR",
|
|
@@ -8083,13 +8552,13 @@ async function wordpressSetSchema(project, body) {
|
|
|
8083
8552
|
printManualAssist(`Schema update for "${body.slug}"`, result);
|
|
8084
8553
|
}
|
|
8085
8554
|
async function wordpressSchemaDeploy(project, opts) {
|
|
8086
|
-
const
|
|
8087
|
-
const
|
|
8555
|
+
const fs10 = await import("fs/promises");
|
|
8556
|
+
const path9 = await import("path");
|
|
8088
8557
|
const yaml = await import("yaml").catch(() => null);
|
|
8089
|
-
const filePath =
|
|
8558
|
+
const filePath = path9.resolve(opts.profile);
|
|
8090
8559
|
let raw;
|
|
8091
8560
|
try {
|
|
8092
|
-
raw = await
|
|
8561
|
+
raw = await fs10.readFile(filePath, "utf8");
|
|
8093
8562
|
} catch {
|
|
8094
8563
|
throw new CliError({
|
|
8095
8564
|
code: "FILE_READ_ERROR",
|
|
@@ -8194,13 +8663,13 @@ async function wordpressOnboard(project, opts) {
|
|
|
8194
8663
|
}
|
|
8195
8664
|
let profileData;
|
|
8196
8665
|
if (opts.profile) {
|
|
8197
|
-
const
|
|
8198
|
-
const
|
|
8666
|
+
const fs10 = await import("fs/promises");
|
|
8667
|
+
const path9 = await import("path");
|
|
8199
8668
|
const yaml = await import("yaml").catch(() => null);
|
|
8200
|
-
const filePath =
|
|
8669
|
+
const filePath = path9.resolve(opts.profile);
|
|
8201
8670
|
let raw;
|
|
8202
8671
|
try {
|
|
8203
|
-
raw = await
|
|
8672
|
+
raw = await fs10.readFile(filePath, "utf8");
|
|
8204
8673
|
} catch {
|
|
8205
8674
|
throw new CliError({
|
|
8206
8675
|
code: "FILE_READ_ERROR",
|
|
@@ -8349,7 +8818,7 @@ function resolveContent(input, command, usage, options) {
|
|
|
8349
8818
|
}
|
|
8350
8819
|
if (contentFile) {
|
|
8351
8820
|
try {
|
|
8352
|
-
return
|
|
8821
|
+
return fs9.readFileSync(contentFile, "utf-8");
|
|
8353
8822
|
} catch (error) {
|
|
8354
8823
|
const message = error instanceof Error ? error.message : String(error);
|
|
8355
8824
|
throw usageError(`Error: could not read --content-file "${contentFile}": ${message}`, {
|
|
@@ -9283,6 +9752,7 @@ var REGISTERED_CLI_COMMANDS = [
|
|
|
9283
9752
|
...KEYWORD_CLI_COMMANDS,
|
|
9284
9753
|
...COMPETITOR_CLI_COMMANDS,
|
|
9285
9754
|
...SETTINGS_CLI_COMMANDS,
|
|
9755
|
+
...SKILLS_CLI_COMMANDS,
|
|
9286
9756
|
...SNAPSHOT_CLI_COMMANDS,
|
|
9287
9757
|
...RUN_CLI_COMMANDS,
|
|
9288
9758
|
...OPERATOR_CLI_COMMANDS,
|
|
@@ -9312,6 +9782,7 @@ Setup:
|
|
|
9312
9782
|
bootstrap Bootstrap config/database from env vars
|
|
9313
9783
|
serve Start the local server (foreground)
|
|
9314
9784
|
start / stop Start/stop as a background daemon
|
|
9785
|
+
skills List or install bundled agent skills (claude/codex)
|
|
9315
9786
|
|
|
9316
9787
|
Projects:
|
|
9317
9788
|
project Create, update, list, show, delete projects
|