@ainyc/canonry 3.2.7 → 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-BJz9S5-O.js → index-C1kUp1aS.js} +67 -67
- package/assets/index.html +1 -1
- package/dist/{chunk-GY4MNUTI.js → chunk-ALMP3NBQ.js} +72 -62
- package/dist/{chunk-7R5NSWF7.js → chunk-HQ47AA6H.js} +1 -1
- package/dist/cli.js +389 -57
- package/dist/index.js +2 -2
- package/dist/mcp.js +1 -1
- package/package.json +6 -6
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) {
|
|
@@ -2012,9 +2015,9 @@ async function gaConnect(project, opts) {
|
|
|
2012
2015
|
propertyId: opts.propertyId
|
|
2013
2016
|
};
|
|
2014
2017
|
if (opts.keyFile) {
|
|
2015
|
-
const
|
|
2018
|
+
const fs10 = await import("fs");
|
|
2016
2019
|
try {
|
|
2017
|
-
const content =
|
|
2020
|
+
const content = fs10.readFileSync(opts.keyFile, "utf-8");
|
|
2018
2021
|
JSON.parse(content);
|
|
2019
2022
|
body.keyJson = content;
|
|
2020
2023
|
} catch (e) {
|
|
@@ -5878,13 +5881,307 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
|
|
|
5878
5881
|
}
|
|
5879
5882
|
];
|
|
5880
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
|
+
|
|
5881
6178
|
// src/commands/snapshot.ts
|
|
5882
|
-
import
|
|
5883
|
-
import
|
|
6179
|
+
import fs6 from "fs";
|
|
6180
|
+
import path5 from "path";
|
|
5884
6181
|
|
|
5885
6182
|
// src/snapshot-pdf.ts
|
|
5886
|
-
import
|
|
5887
|
-
import
|
|
6183
|
+
import fs5 from "fs";
|
|
6184
|
+
import path4 from "path";
|
|
5888
6185
|
import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
|
|
5889
6186
|
var PAGE_WIDTH = 612;
|
|
5890
6187
|
var PAGE_HEIGHT = 792;
|
|
@@ -6093,9 +6390,9 @@ async function writeSnapshotPdf(report, outputPath) {
|
|
|
6093
6390
|
renderCompetitors(pdf, report);
|
|
6094
6391
|
renderQueries(pdf, report);
|
|
6095
6392
|
const bytes = await doc.save();
|
|
6096
|
-
const resolvedPath =
|
|
6097
|
-
|
|
6098
|
-
|
|
6393
|
+
const resolvedPath = path4.resolve(outputPath);
|
|
6394
|
+
fs5.mkdirSync(path4.dirname(resolvedPath), { recursive: true });
|
|
6395
|
+
fs5.writeFileSync(resolvedPath, bytes);
|
|
6099
6396
|
return resolvedPath;
|
|
6100
6397
|
}
|
|
6101
6398
|
function renderCover(pdf, report) {
|
|
@@ -6253,9 +6550,9 @@ Markdown saved: ${savedMdPath}`);
|
|
|
6253
6550
|
PDF saved: ${savedPdfPath}`);
|
|
6254
6551
|
}
|
|
6255
6552
|
function writeSnapshotMarkdown(report, outputPath) {
|
|
6256
|
-
const resolvedPath =
|
|
6257
|
-
|
|
6258
|
-
|
|
6553
|
+
const resolvedPath = path5.resolve(outputPath);
|
|
6554
|
+
fs6.mkdirSync(path5.dirname(resolvedPath), { recursive: true });
|
|
6555
|
+
fs6.writeFileSync(resolvedPath, formatSnapshotMarkdown(report), "utf-8");
|
|
6259
6556
|
return resolvedPath;
|
|
6260
6557
|
}
|
|
6261
6558
|
function formatSnapshotMarkdown(report) {
|
|
@@ -6910,7 +7207,7 @@ var CONTENT_CLI_COMMANDS = [
|
|
|
6910
7207
|
|
|
6911
7208
|
// src/commands/bootstrap.ts
|
|
6912
7209
|
import crypto from "crypto";
|
|
6913
|
-
import
|
|
7210
|
+
import path6 from "path";
|
|
6914
7211
|
import { eq as eq2 } from "drizzle-orm";
|
|
6915
7212
|
|
|
6916
7213
|
// ../config/src/index.ts
|
|
@@ -7057,7 +7354,7 @@ async function bootstrapCommand(_opts) {
|
|
|
7057
7354
|
);
|
|
7058
7355
|
}
|
|
7059
7356
|
const configDir = getConfigDir();
|
|
7060
|
-
const databasePath = env.databasePath ||
|
|
7357
|
+
const databasePath = env.databasePath || path6.join(configDir, "data.db");
|
|
7061
7358
|
const existing = configExists();
|
|
7062
7359
|
const existingConfig = existing ? loadConfig() : void 0;
|
|
7063
7360
|
let rawApiKey;
|
|
@@ -7127,10 +7424,10 @@ async function bootstrapCommand(_opts) {
|
|
|
7127
7424
|
|
|
7128
7425
|
// src/commands/daemon.ts
|
|
7129
7426
|
import { spawn } from "child_process";
|
|
7130
|
-
import
|
|
7131
|
-
import
|
|
7427
|
+
import fs7 from "fs";
|
|
7428
|
+
import path7 from "path";
|
|
7132
7429
|
function getPidPath() {
|
|
7133
|
-
return
|
|
7430
|
+
return path7.join(getConfigDir(), "canonry.pid");
|
|
7134
7431
|
}
|
|
7135
7432
|
function isProcessAlive(pid) {
|
|
7136
7433
|
try {
|
|
@@ -7157,8 +7454,8 @@ async function waitForReady(host, port, maxMs = 1e4) {
|
|
|
7157
7454
|
async function startDaemon(opts) {
|
|
7158
7455
|
const pidPath = getPidPath();
|
|
7159
7456
|
const format = opts.format ?? "text";
|
|
7160
|
-
if (
|
|
7161
|
-
const existingPid = parseInt(
|
|
7457
|
+
if (fs7.existsSync(pidPath)) {
|
|
7458
|
+
const existingPid = parseInt(fs7.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
7162
7459
|
if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
|
|
7163
7460
|
throw new CliError({
|
|
7164
7461
|
code: "DAEMON_ALREADY_RUNNING",
|
|
@@ -7169,9 +7466,9 @@ async function startDaemon(opts) {
|
|
|
7169
7466
|
}
|
|
7170
7467
|
});
|
|
7171
7468
|
}
|
|
7172
|
-
|
|
7469
|
+
fs7.unlinkSync(pidPath);
|
|
7173
7470
|
}
|
|
7174
|
-
const cliPath =
|
|
7471
|
+
const cliPath = path7.resolve(new URL(import.meta.url).pathname);
|
|
7175
7472
|
const inSourceMode = new URL(import.meta.url).pathname.endsWith(".ts");
|
|
7176
7473
|
const args = inSourceMode ? ["--import", "tsx", cliPath, "serve"] : [cliPath, "serve"];
|
|
7177
7474
|
if (opts.port) args.push("--port", opts.port);
|
|
@@ -7190,10 +7487,10 @@ async function startDaemon(opts) {
|
|
|
7190
7487
|
});
|
|
7191
7488
|
}
|
|
7192
7489
|
const configDir = getConfigDir();
|
|
7193
|
-
if (!
|
|
7194
|
-
|
|
7490
|
+
if (!fs7.existsSync(configDir)) {
|
|
7491
|
+
fs7.mkdirSync(configDir, { recursive: true });
|
|
7195
7492
|
}
|
|
7196
|
-
|
|
7493
|
+
fs7.writeFileSync(pidPath, String(child.pid), "utf-8");
|
|
7197
7494
|
const port = opts.port ?? "4100";
|
|
7198
7495
|
const host = opts.host ?? "127.0.0.1";
|
|
7199
7496
|
if (format !== "json") {
|
|
@@ -7202,7 +7499,7 @@ async function startDaemon(opts) {
|
|
|
7202
7499
|
const ready = await waitForReady(host, port);
|
|
7203
7500
|
if (!ready) {
|
|
7204
7501
|
try {
|
|
7205
|
-
|
|
7502
|
+
fs7.unlinkSync(pidPath);
|
|
7206
7503
|
} catch {
|
|
7207
7504
|
}
|
|
7208
7505
|
throw new CliError({
|
|
@@ -7234,7 +7531,7 @@ async function startDaemon(opts) {
|
|
|
7234
7531
|
}
|
|
7235
7532
|
function stopDaemon(format = "text") {
|
|
7236
7533
|
const pidPath = getPidPath();
|
|
7237
|
-
if (!
|
|
7534
|
+
if (!fs7.existsSync(pidPath)) {
|
|
7238
7535
|
if (format === "json") {
|
|
7239
7536
|
console.log(JSON.stringify({
|
|
7240
7537
|
stopped: false,
|
|
@@ -7245,7 +7542,7 @@ function stopDaemon(format = "text") {
|
|
|
7245
7542
|
console.log("Canonry is not running (no PID file found)");
|
|
7246
7543
|
return;
|
|
7247
7544
|
}
|
|
7248
|
-
const pid = parseInt(
|
|
7545
|
+
const pid = parseInt(fs7.readFileSync(pidPath, "utf-8").trim(), 10);
|
|
7249
7546
|
if (isNaN(pid)) {
|
|
7250
7547
|
if (format === "json") {
|
|
7251
7548
|
console.log(JSON.stringify({
|
|
@@ -7256,7 +7553,7 @@ function stopDaemon(format = "text") {
|
|
|
7256
7553
|
} else {
|
|
7257
7554
|
console.error("Invalid PID file. Removing it.");
|
|
7258
7555
|
}
|
|
7259
|
-
|
|
7556
|
+
fs7.unlinkSync(pidPath);
|
|
7260
7557
|
return;
|
|
7261
7558
|
}
|
|
7262
7559
|
if (!isProcessAlive(pid)) {
|
|
@@ -7270,12 +7567,12 @@ function stopDaemon(format = "text") {
|
|
|
7270
7567
|
} else {
|
|
7271
7568
|
console.log(`Canonry is not running (stale PID: ${pid}). Cleaning up.`);
|
|
7272
7569
|
}
|
|
7273
|
-
|
|
7570
|
+
fs7.unlinkSync(pidPath);
|
|
7274
7571
|
return;
|
|
7275
7572
|
}
|
|
7276
7573
|
try {
|
|
7277
7574
|
process.kill(pid, "SIGTERM");
|
|
7278
|
-
|
|
7575
|
+
fs7.unlinkSync(pidPath);
|
|
7279
7576
|
if (format === "json") {
|
|
7280
7577
|
console.log(JSON.stringify({
|
|
7281
7578
|
stopped: true,
|
|
@@ -7299,9 +7596,9 @@ function stopDaemon(format = "text") {
|
|
|
7299
7596
|
|
|
7300
7597
|
// src/commands/init.ts
|
|
7301
7598
|
import crypto2 from "crypto";
|
|
7302
|
-
import
|
|
7599
|
+
import fs8 from "fs";
|
|
7303
7600
|
import readline from "readline";
|
|
7304
|
-
import
|
|
7601
|
+
import path8 from "path";
|
|
7305
7602
|
function prompt(question) {
|
|
7306
7603
|
const rl = readline.createInterface({
|
|
7307
7604
|
input: process.stdin,
|
|
@@ -7319,6 +7616,12 @@ var DEFAULT_QUOTA = {
|
|
|
7319
7616
|
maxRequestsPerMinute: 10,
|
|
7320
7617
|
maxRequestsPerDay: 500
|
|
7321
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
|
+
}
|
|
7322
7625
|
var DEFAULT_AGENT_MODELS = {
|
|
7323
7626
|
anthropic: "anthropic/claude-sonnet-4-6",
|
|
7324
7627
|
openai: "openai/gpt-4o",
|
|
@@ -7347,8 +7650,8 @@ async function initCommand(opts) {
|
|
|
7347
7650
|
return void 0;
|
|
7348
7651
|
}
|
|
7349
7652
|
const configDir = getConfigDir();
|
|
7350
|
-
if (!
|
|
7351
|
-
|
|
7653
|
+
if (!fs8.existsSync(configDir)) {
|
|
7654
|
+
fs8.mkdirSync(configDir, { recursive: true });
|
|
7352
7655
|
}
|
|
7353
7656
|
const bootstrapEnv = getBootstrapEnv(process.env, {
|
|
7354
7657
|
GEMINI_API_KEY: opts?.geminiKey,
|
|
@@ -7463,7 +7766,7 @@ async function initCommand(opts) {
|
|
|
7463
7766
|
const rawApiKey = `cnry_${crypto2.randomBytes(16).toString("hex")}`;
|
|
7464
7767
|
const keyHash = crypto2.createHash("sha256").update(rawApiKey).digest("hex");
|
|
7465
7768
|
const keyPrefix = rawApiKey.slice(0, 9);
|
|
7466
|
-
const databasePath =
|
|
7769
|
+
const databasePath = path8.join(configDir, "data.db");
|
|
7467
7770
|
const db = createClient(databasePath);
|
|
7468
7771
|
migrate(db);
|
|
7469
7772
|
db.insert(apiKeys).values({
|
|
@@ -7482,6 +7785,20 @@ async function initCommand(opts) {
|
|
|
7482
7785
|
google
|
|
7483
7786
|
});
|
|
7484
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
|
+
}
|
|
7485
7802
|
if (format === "json") {
|
|
7486
7803
|
console.log(JSON.stringify({
|
|
7487
7804
|
initialized: true,
|
|
@@ -7490,7 +7807,9 @@ async function initCommand(opts) {
|
|
|
7490
7807
|
apiUrl: `http://localhost:${process.env.CANONRY_PORT || "4100"}`,
|
|
7491
7808
|
apiKey: rawApiKey,
|
|
7492
7809
|
providers: providerNames,
|
|
7493
|
-
googleConfigured: !!google
|
|
7810
|
+
googleConfigured: !!google,
|
|
7811
|
+
skills: skillsSummary,
|
|
7812
|
+
skillsTip
|
|
7494
7813
|
}, null, 2));
|
|
7495
7814
|
} else {
|
|
7496
7815
|
console.log(`
|
|
@@ -7498,6 +7817,13 @@ Config saved to ${getConfigPath()}`);
|
|
|
7498
7817
|
console.log(`Database created at ${databasePath}`);
|
|
7499
7818
|
console.log(`API key: ${rawApiKey}`);
|
|
7500
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}`);
|
|
7501
7827
|
}
|
|
7502
7828
|
let agentLLM;
|
|
7503
7829
|
const agentProvider = opts?.agentProvider;
|
|
@@ -7762,7 +8088,7 @@ function applyServerEnv(values) {
|
|
|
7762
8088
|
var SYSTEM_CLI_COMMANDS = [
|
|
7763
8089
|
{
|
|
7764
8090
|
path: ["init"],
|
|
7765
|
-
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]",
|
|
7766
8092
|
options: {
|
|
7767
8093
|
force: { type: "boolean", short: "f", default: false },
|
|
7768
8094
|
"gemini-key": stringOption(),
|
|
@@ -7773,7 +8099,9 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
7773
8099
|
"local-model": stringOption(),
|
|
7774
8100
|
"local-key": stringOption(),
|
|
7775
8101
|
"google-client-id": stringOption(),
|
|
7776
|
-
"google-client-secret": stringOption()
|
|
8102
|
+
"google-client-secret": stringOption(),
|
|
8103
|
+
"skip-skills": { type: "boolean" },
|
|
8104
|
+
"skills-dir": stringOption()
|
|
7777
8105
|
},
|
|
7778
8106
|
allowPositionals: false,
|
|
7779
8107
|
run: async (input) => {
|
|
@@ -7788,6 +8116,8 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
7788
8116
|
localKey: getString(input.values, "local-key"),
|
|
7789
8117
|
googleClientId: getString(input.values, "google-client-id"),
|
|
7790
8118
|
googleClientSecret: getString(input.values, "google-client-secret"),
|
|
8119
|
+
skipSkills: getBoolean(input.values, "skip-skills"),
|
|
8120
|
+
skillsDir: getString(input.values, "skills-dir"),
|
|
7791
8121
|
format: input.format
|
|
7792
8122
|
});
|
|
7793
8123
|
}
|
|
@@ -7884,7 +8214,7 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
7884
8214
|
];
|
|
7885
8215
|
|
|
7886
8216
|
// src/cli-commands/wordpress.ts
|
|
7887
|
-
import
|
|
8217
|
+
import fs9 from "fs";
|
|
7888
8218
|
|
|
7889
8219
|
// src/commands/wordpress.ts
|
|
7890
8220
|
function getClient18() {
|
|
@@ -8120,12 +8450,12 @@ async function wordpressSetMeta(project, body) {
|
|
|
8120
8450
|
printPageDetail(result);
|
|
8121
8451
|
}
|
|
8122
8452
|
async function wordpressBulkSetMeta(project, opts) {
|
|
8123
|
-
const
|
|
8124
|
-
const
|
|
8125
|
-
const filePath =
|
|
8453
|
+
const fs10 = await import("fs/promises");
|
|
8454
|
+
const path9 = await import("path");
|
|
8455
|
+
const filePath = path9.resolve(opts.from);
|
|
8126
8456
|
let raw;
|
|
8127
8457
|
try {
|
|
8128
|
-
raw = await
|
|
8458
|
+
raw = await fs10.readFile(filePath, "utf8");
|
|
8129
8459
|
} catch {
|
|
8130
8460
|
throw new CliError({
|
|
8131
8461
|
code: "FILE_READ_ERROR",
|
|
@@ -8222,13 +8552,13 @@ async function wordpressSetSchema(project, body) {
|
|
|
8222
8552
|
printManualAssist(`Schema update for "${body.slug}"`, result);
|
|
8223
8553
|
}
|
|
8224
8554
|
async function wordpressSchemaDeploy(project, opts) {
|
|
8225
|
-
const
|
|
8226
|
-
const
|
|
8555
|
+
const fs10 = await import("fs/promises");
|
|
8556
|
+
const path9 = await import("path");
|
|
8227
8557
|
const yaml = await import("yaml").catch(() => null);
|
|
8228
|
-
const filePath =
|
|
8558
|
+
const filePath = path9.resolve(opts.profile);
|
|
8229
8559
|
let raw;
|
|
8230
8560
|
try {
|
|
8231
|
-
raw = await
|
|
8561
|
+
raw = await fs10.readFile(filePath, "utf8");
|
|
8232
8562
|
} catch {
|
|
8233
8563
|
throw new CliError({
|
|
8234
8564
|
code: "FILE_READ_ERROR",
|
|
@@ -8333,13 +8663,13 @@ async function wordpressOnboard(project, opts) {
|
|
|
8333
8663
|
}
|
|
8334
8664
|
let profileData;
|
|
8335
8665
|
if (opts.profile) {
|
|
8336
|
-
const
|
|
8337
|
-
const
|
|
8666
|
+
const fs10 = await import("fs/promises");
|
|
8667
|
+
const path9 = await import("path");
|
|
8338
8668
|
const yaml = await import("yaml").catch(() => null);
|
|
8339
|
-
const filePath =
|
|
8669
|
+
const filePath = path9.resolve(opts.profile);
|
|
8340
8670
|
let raw;
|
|
8341
8671
|
try {
|
|
8342
|
-
raw = await
|
|
8672
|
+
raw = await fs10.readFile(filePath, "utf8");
|
|
8343
8673
|
} catch {
|
|
8344
8674
|
throw new CliError({
|
|
8345
8675
|
code: "FILE_READ_ERROR",
|
|
@@ -8488,7 +8818,7 @@ function resolveContent(input, command, usage, options) {
|
|
|
8488
8818
|
}
|
|
8489
8819
|
if (contentFile) {
|
|
8490
8820
|
try {
|
|
8491
|
-
return
|
|
8821
|
+
return fs9.readFileSync(contentFile, "utf-8");
|
|
8492
8822
|
} catch (error) {
|
|
8493
8823
|
const message = error instanceof Error ? error.message : String(error);
|
|
8494
8824
|
throw usageError(`Error: could not read --content-file "${contentFile}": ${message}`, {
|
|
@@ -9422,6 +9752,7 @@ var REGISTERED_CLI_COMMANDS = [
|
|
|
9422
9752
|
...KEYWORD_CLI_COMMANDS,
|
|
9423
9753
|
...COMPETITOR_CLI_COMMANDS,
|
|
9424
9754
|
...SETTINGS_CLI_COMMANDS,
|
|
9755
|
+
...SKILLS_CLI_COMMANDS,
|
|
9425
9756
|
...SNAPSHOT_CLI_COMMANDS,
|
|
9426
9757
|
...RUN_CLI_COMMANDS,
|
|
9427
9758
|
...OPERATOR_CLI_COMMANDS,
|
|
@@ -9451,6 +9782,7 @@ Setup:
|
|
|
9451
9782
|
bootstrap Bootstrap config/database from env vars
|
|
9452
9783
|
serve Start the local server (foreground)
|
|
9453
9784
|
start / stop Start/stop as a background daemon
|
|
9785
|
+
skills List or install bundled agent skills (claude/codex)
|
|
9454
9786
|
|
|
9455
9787
|
Projects:
|
|
9456
9788
|
project Create, update, list, show, delete projects
|