@ainyc/canonry 1.45.3 → 1.46.1
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 +73 -436
- package/assets/agent-workspace/skills/aero/SKILL.md +42 -0
- package/assets/agent-workspace/skills/aero/references/memory-patterns.md +37 -0
- package/assets/agent-workspace/skills/aero/references/orchestration.md +52 -0
- package/assets/agent-workspace/skills/aero/references/regression-playbook.md +34 -0
- package/assets/agent-workspace/skills/aero/references/reporting.md +67 -0
- package/assets/agent-workspace/skills/canonry-setup/SKILL.md +274 -0
- package/assets/agent-workspace/skills/canonry-setup/references/aeo-analysis.md +130 -0
- package/assets/agent-workspace/skills/canonry-setup/references/canonry-cli.md +339 -0
- package/assets/agent-workspace/skills/canonry-setup/references/indexing.md +155 -0
- package/assets/agent-workspace/skills/canonry-setup/references/wordpress-integration.md +57 -0
- package/assets/assets/index-Cxg_4UWs.js +282 -0
- package/assets/index.html +1 -1
- package/dist/{chunk-WNOUK4KA.js → chunk-RMLIF47M.js} +69 -33
- package/dist/cli.js +850 -55
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1 -1
- package/package.json +9 -9
- package/assets/assets/index-BR8Krvpj.js +0 -282
package/dist/cli.js
CHANGED
|
@@ -28,11 +28,12 @@ import {
|
|
|
28
28
|
setGoogleAuthConfig,
|
|
29
29
|
showFirstRunNotice,
|
|
30
30
|
trackEvent
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-RMLIF47M.js";
|
|
32
32
|
import {
|
|
33
33
|
apiKeys,
|
|
34
34
|
competitors,
|
|
35
35
|
createClient,
|
|
36
|
+
createLogger,
|
|
36
37
|
migrate,
|
|
37
38
|
parseJsonColumn,
|
|
38
39
|
projects,
|
|
@@ -118,9 +119,9 @@ import { parseArgs } from "util";
|
|
|
118
119
|
function commandId(spec) {
|
|
119
120
|
return spec.path.join(".");
|
|
120
121
|
}
|
|
121
|
-
function matchesPath(args,
|
|
122
|
-
if (args.length <
|
|
123
|
-
return
|
|
122
|
+
function matchesPath(args, path9) {
|
|
123
|
+
if (args.length < path9.length) return false;
|
|
124
|
+
return path9.every((segment, index) => args[index] === segment);
|
|
124
125
|
}
|
|
125
126
|
function withFormatOption(options) {
|
|
126
127
|
if (!options) {
|
|
@@ -613,9 +614,9 @@ var ApiClient = class {
|
|
|
613
614
|
}
|
|
614
615
|
return this.probePromise;
|
|
615
616
|
}
|
|
616
|
-
async request(method,
|
|
617
|
+
async request(method, path9, body) {
|
|
617
618
|
await this.probeBasePath();
|
|
618
|
-
const url = `${this.baseUrl}${
|
|
619
|
+
const url = `${this.baseUrl}${path9}`;
|
|
619
620
|
const serializedBody = body != null ? JSON.stringify(body) : void 0;
|
|
620
621
|
const headers = {
|
|
621
622
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
@@ -909,11 +910,13 @@ var ApiClient = class {
|
|
|
909
910
|
async gaCoverage(project) {
|
|
910
911
|
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/coverage`);
|
|
911
912
|
}
|
|
912
|
-
async gaAiReferralHistory(project) {
|
|
913
|
-
|
|
913
|
+
async gaAiReferralHistory(project, params) {
|
|
914
|
+
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
915
|
+
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/ai-referral-history${qs}`);
|
|
914
916
|
}
|
|
915
|
-
async gaSocialReferralHistory(project) {
|
|
916
|
-
|
|
917
|
+
async gaSocialReferralHistory(project, params) {
|
|
918
|
+
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
919
|
+
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/social-referral-history${qs}`);
|
|
917
920
|
}
|
|
918
921
|
async gaSocialReferralTrend(project) {
|
|
919
922
|
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/social-referral-trend`);
|
|
@@ -921,8 +924,9 @@ var ApiClient = class {
|
|
|
921
924
|
async gaAttributionTrend(project) {
|
|
922
925
|
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/attribution-trend`);
|
|
923
926
|
}
|
|
924
|
-
async gaSessionHistory(project) {
|
|
925
|
-
|
|
927
|
+
async gaSessionHistory(project, params) {
|
|
928
|
+
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
929
|
+
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/session-history${qs}`);
|
|
926
930
|
}
|
|
927
931
|
async wordpressConnect(project, body) {
|
|
928
932
|
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/connect`, body);
|
|
@@ -1691,9 +1695,9 @@ async function gaConnect(project, opts) {
|
|
|
1691
1695
|
propertyId: opts.propertyId
|
|
1692
1696
|
};
|
|
1693
1697
|
if (opts.keyFile) {
|
|
1694
|
-
const
|
|
1698
|
+
const fs10 = await import("fs");
|
|
1695
1699
|
try {
|
|
1696
|
-
const content =
|
|
1700
|
+
const content = fs10.readFileSync(opts.keyFile, "utf-8");
|
|
1697
1701
|
JSON.parse(content);
|
|
1698
1702
|
body.keyJson = content;
|
|
1699
1703
|
} catch (e) {
|
|
@@ -1781,13 +1785,18 @@ async function gaTraffic(project, opts) {
|
|
|
1781
1785
|
const client = getClient3();
|
|
1782
1786
|
const params = {};
|
|
1783
1787
|
if (opts?.limit) params.limit = String(opts.limit);
|
|
1788
|
+
if (opts?.window) params.window = opts.window;
|
|
1784
1789
|
const result = await client.gaTraffic(project, Object.keys(params).length > 0 ? params : void 0);
|
|
1785
1790
|
if (opts?.format === "json") {
|
|
1786
1791
|
console.log(JSON.stringify(result, null, 2));
|
|
1787
1792
|
return;
|
|
1788
1793
|
}
|
|
1789
1794
|
if (result.topPages.length === 0 && result.aiReferrals.length === 0 && result.socialReferrals.length === 0) {
|
|
1790
|
-
|
|
1795
|
+
if (!result.lastSyncedAt) {
|
|
1796
|
+
console.log('No GA4 traffic data. Run "canonry ga sync <project>" first.');
|
|
1797
|
+
} else {
|
|
1798
|
+
console.log(`No GA4 traffic data for the selected period.${opts?.window ? ` Try a wider window or omit --window.` : ""}`);
|
|
1799
|
+
}
|
|
1791
1800
|
return;
|
|
1792
1801
|
}
|
|
1793
1802
|
console.log(`GA4 Traffic for "${project}"
|
|
@@ -1847,15 +1856,15 @@ async function gaTraffic(project, opts) {
|
|
|
1847
1856
|
Last synced: ${result.lastSyncedAt}`);
|
|
1848
1857
|
}
|
|
1849
1858
|
}
|
|
1850
|
-
async function gaAiReferralHistory(project,
|
|
1859
|
+
async function gaAiReferralHistory(project, opts) {
|
|
1851
1860
|
const client = getClient3();
|
|
1852
|
-
const result = await client.gaAiReferralHistory(project);
|
|
1853
|
-
if (format === "json") {
|
|
1861
|
+
const result = await client.gaAiReferralHistory(project, opts?.window ? { window: opts.window } : void 0);
|
|
1862
|
+
if (opts?.format === "json") {
|
|
1854
1863
|
console.log(JSON.stringify(result, null, 2));
|
|
1855
1864
|
return;
|
|
1856
1865
|
}
|
|
1857
1866
|
if (result.length === 0) {
|
|
1858
|
-
console.log(
|
|
1867
|
+
console.log(`No AI referral history.${opts?.window ? " Try a wider window or omit --window." : ' Run "canonry ga sync <project>" first.'}`);
|
|
1859
1868
|
return;
|
|
1860
1869
|
}
|
|
1861
1870
|
const dateWidth = 12;
|
|
@@ -1872,15 +1881,15 @@ async function gaAiReferralHistory(project, format) {
|
|
|
1872
1881
|
);
|
|
1873
1882
|
}
|
|
1874
1883
|
}
|
|
1875
|
-
async function gaSocialReferralHistory(project,
|
|
1884
|
+
async function gaSocialReferralHistory(project, opts) {
|
|
1876
1885
|
const client = getClient3();
|
|
1877
|
-
const result = await client.gaSocialReferralHistory(project);
|
|
1878
|
-
if (format === "json") {
|
|
1886
|
+
const result = await client.gaSocialReferralHistory(project, opts?.window ? { window: opts.window } : void 0);
|
|
1887
|
+
if (opts?.format === "json") {
|
|
1879
1888
|
console.log(JSON.stringify(result, null, 2));
|
|
1880
1889
|
return;
|
|
1881
1890
|
}
|
|
1882
1891
|
if (result.length === 0) {
|
|
1883
|
-
console.log(
|
|
1892
|
+
console.log(`No social referral history.${opts?.window ? " Try a wider window or omit --window." : ' Run "canonry ga sync <project>" first.'}`);
|
|
1884
1893
|
return;
|
|
1885
1894
|
}
|
|
1886
1895
|
const dateWidth = 12;
|
|
@@ -1897,6 +1906,28 @@ async function gaSocialReferralHistory(project, format) {
|
|
|
1897
1906
|
);
|
|
1898
1907
|
}
|
|
1899
1908
|
}
|
|
1909
|
+
async function gaSessionHistory(project, opts) {
|
|
1910
|
+
const client = getClient3();
|
|
1911
|
+
const result = await client.gaSessionHistory(project, opts?.window ? { window: opts.window } : void 0);
|
|
1912
|
+
if (opts?.format === "json") {
|
|
1913
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
if (result.length === 0) {
|
|
1917
|
+
console.log(`No session history.${opts?.window ? " Try a wider window or omit --window." : ' Run "canonry ga sync <project>" first.'}`);
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
const dateWidth = 12;
|
|
1921
|
+
console.log(`GA4 Session History for "${project}":
|
|
1922
|
+
`);
|
|
1923
|
+
console.log(` ${"DATE".padEnd(dateWidth)} ${"SESSIONS".padEnd(10)}${"ORGANIC".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1924
|
+
console.log(` ${"\u2500".repeat(dateWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1925
|
+
for (const row of result) {
|
|
1926
|
+
console.log(
|
|
1927
|
+
` ${row.date.padEnd(dateWidth)} ${String(row.sessions).padEnd(10)}${String(row.organicSessions).padEnd(10)}${String(row.users).padEnd(8)}`
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1900
1931
|
async function gaCoverage(project, format) {
|
|
1901
1932
|
const client = getClient3();
|
|
1902
1933
|
const result = await client.gaCoverage(project);
|
|
@@ -2034,9 +2065,12 @@ async function gaAttribution(project, opts) {
|
|
|
2034
2065
|
const m = trend.socialBiggestMover;
|
|
2035
2066
|
console.log(` Social Mover: ${m.source} (${m.changePct >= 0 ? "+" : ""}${m.changePct}%, ${m.sessionsPrev7d}\u2192${m.sessions7d} sessions/7d)`);
|
|
2036
2067
|
}
|
|
2037
|
-
if (traffic.
|
|
2068
|
+
if (traffic.periodStart && traffic.periodEnd) {
|
|
2038
2069
|
console.log(`
|
|
2039
|
-
|
|
2070
|
+
Period: ${traffic.periodStart} to ${traffic.periodEnd}`);
|
|
2071
|
+
}
|
|
2072
|
+
if (traffic.lastSyncedAt) {
|
|
2073
|
+
console.log(` Last synced: ${traffic.lastSyncedAt}`);
|
|
2040
2074
|
}
|
|
2041
2075
|
return;
|
|
2042
2076
|
}
|
|
@@ -2053,7 +2087,9 @@ async function gaAttribution(project, opts) {
|
|
|
2053
2087
|
socialSharePct: traffic.socialSharePct,
|
|
2054
2088
|
organicSharePct: traffic.organicSharePct,
|
|
2055
2089
|
aiReferrals: traffic.aiReferrals,
|
|
2056
|
-
socialReferrals: traffic.socialReferrals
|
|
2090
|
+
socialReferrals: traffic.socialReferrals,
|
|
2091
|
+
periodStart: traffic.periodStart,
|
|
2092
|
+
periodEnd: traffic.periodEnd
|
|
2057
2093
|
}, null, 2));
|
|
2058
2094
|
return;
|
|
2059
2095
|
}
|
|
@@ -2091,9 +2127,12 @@ async function gaAttribution(project, opts) {
|
|
|
2091
2127
|
console.log(` ${ref.source.padEnd(25)} ${String(ref.sessions).padEnd(8)} sessions (${chanLabel})`);
|
|
2092
2128
|
}
|
|
2093
2129
|
}
|
|
2094
|
-
if (traffic.
|
|
2130
|
+
if (traffic.periodStart && traffic.periodEnd) {
|
|
2095
2131
|
console.log(`
|
|
2096
|
-
|
|
2132
|
+
Period: ${traffic.periodStart} to ${traffic.periodEnd}`);
|
|
2133
|
+
}
|
|
2134
|
+
if (traffic.lastSyncedAt) {
|
|
2135
|
+
console.log(` Last synced: ${traffic.lastSyncedAt}`);
|
|
2097
2136
|
}
|
|
2098
2137
|
}
|
|
2099
2138
|
|
|
@@ -2158,16 +2197,19 @@ var GA_CLI_COMMANDS = [
|
|
|
2158
2197
|
},
|
|
2159
2198
|
{
|
|
2160
2199
|
path: ["ga", "traffic"],
|
|
2161
|
-
usage: "canonry ga traffic <project> [--limit 50] [--format json]",
|
|
2200
|
+
usage: "canonry ga traffic <project> [--limit 50] [--window 30d] [--format json]",
|
|
2162
2201
|
options: {
|
|
2163
|
-
limit: stringOption()
|
|
2202
|
+
limit: stringOption(),
|
|
2203
|
+
window: stringOption()
|
|
2164
2204
|
},
|
|
2165
2205
|
run: async (input) => {
|
|
2166
|
-
const project = requireProject(input, "ga.traffic", "canonry ga traffic <project> [--limit 50] [--format json]");
|
|
2206
|
+
const project = requireProject(input, "ga.traffic", "canonry ga traffic <project> [--limit 50] [--window 30d] [--format json]");
|
|
2167
2207
|
const limitStr = getString(input.values, "limit");
|
|
2168
2208
|
const limit = limitStr ? parseInt(limitStr, 10) : void 0;
|
|
2209
|
+
const window = getString(input.values, "window");
|
|
2169
2210
|
await gaTraffic(project, {
|
|
2170
2211
|
limit,
|
|
2212
|
+
window,
|
|
2171
2213
|
format: input.format
|
|
2172
2214
|
});
|
|
2173
2215
|
}
|
|
@@ -2182,18 +2224,44 @@ var GA_CLI_COMMANDS = [
|
|
|
2182
2224
|
},
|
|
2183
2225
|
{
|
|
2184
2226
|
path: ["ga", "ai-referral-history"],
|
|
2185
|
-
usage: "canonry ga ai-referral-history <project> [--format json]",
|
|
2227
|
+
usage: "canonry ga ai-referral-history <project> [--window 30d] [--format json]",
|
|
2228
|
+
options: {
|
|
2229
|
+
window: stringOption()
|
|
2230
|
+
},
|
|
2186
2231
|
run: async (input) => {
|
|
2187
|
-
const project = requireProject(input, "ga.ai-referral-history", "canonry ga ai-referral-history <project> [--format json]");
|
|
2188
|
-
await gaAiReferralHistory(project,
|
|
2232
|
+
const project = requireProject(input, "ga.ai-referral-history", "canonry ga ai-referral-history <project> [--window 30d] [--format json]");
|
|
2233
|
+
await gaAiReferralHistory(project, {
|
|
2234
|
+
window: getString(input.values, "window"),
|
|
2235
|
+
format: input.format
|
|
2236
|
+
});
|
|
2189
2237
|
}
|
|
2190
2238
|
},
|
|
2191
2239
|
{
|
|
2192
2240
|
path: ["ga", "social-referral-history"],
|
|
2193
|
-
usage: "canonry ga social-referral-history <project> [--format json]",
|
|
2241
|
+
usage: "canonry ga social-referral-history <project> [--window 30d] [--format json]",
|
|
2242
|
+
options: {
|
|
2243
|
+
window: stringOption()
|
|
2244
|
+
},
|
|
2194
2245
|
run: async (input) => {
|
|
2195
|
-
const project = requireProject(input, "ga.social-referral-history", "canonry ga social-referral-history <project> [--format json]");
|
|
2196
|
-
await gaSocialReferralHistory(project,
|
|
2246
|
+
const project = requireProject(input, "ga.social-referral-history", "canonry ga social-referral-history <project> [--window 30d] [--format json]");
|
|
2247
|
+
await gaSocialReferralHistory(project, {
|
|
2248
|
+
window: getString(input.values, "window"),
|
|
2249
|
+
format: input.format
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
},
|
|
2253
|
+
{
|
|
2254
|
+
path: ["ga", "session-history"],
|
|
2255
|
+
usage: "canonry ga session-history <project> [--window 30d] [--format json]",
|
|
2256
|
+
options: {
|
|
2257
|
+
window: stringOption()
|
|
2258
|
+
},
|
|
2259
|
+
run: async (input) => {
|
|
2260
|
+
const project = requireProject(input, "ga.session-history", "canonry ga session-history <project> [--window 30d] [--format json]");
|
|
2261
|
+
await gaSessionHistory(project, {
|
|
2262
|
+
window: getString(input.values, "window"),
|
|
2263
|
+
format: input.format
|
|
2264
|
+
});
|
|
2197
2265
|
}
|
|
2198
2266
|
},
|
|
2199
2267
|
{
|
|
@@ -2231,7 +2299,7 @@ var GA_CLI_COMMANDS = [
|
|
|
2231
2299
|
unknownSubcommand(input.positionals[0], {
|
|
2232
2300
|
command: "ga",
|
|
2233
2301
|
usage: "canonry ga <subcommand> <project> [args]",
|
|
2234
|
-
available: ["connect", "disconnect", "status", "sync", "traffic", "coverage", "ai-referral-history", "social-referral-history", "social-referral-summary", "attribution"]
|
|
2302
|
+
available: ["connect", "disconnect", "status", "sync", "traffic", "coverage", "ai-referral-history", "social-referral-history", "session-history", "social-referral-summary", "attribution"]
|
|
2235
2303
|
});
|
|
2236
2304
|
}
|
|
2237
2305
|
}
|
|
@@ -2387,10 +2455,10 @@ Open this URL in your browser to authorize Google ${opts.type.toUpperCase()} acc
|
|
|
2387
2455
|
console.log("(Ensure this URI is listed in your Google Cloud Console OAuth client's authorized redirect URIs)\n");
|
|
2388
2456
|
}
|
|
2389
2457
|
try {
|
|
2390
|
-
const { spawn:
|
|
2458
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
2391
2459
|
const platform = process.platform;
|
|
2392
2460
|
const [cmd, ...extraArgs] = platform === "darwin" ? ["open", authUrl] : platform === "win32" ? ["cmd", "/c", "start", "", authUrl] : ["xdg-open", authUrl];
|
|
2393
|
-
|
|
2461
|
+
spawn3(cmd, [...extraArgs], { detached: true, stdio: "ignore" }).unref();
|
|
2394
2462
|
console.log("(Browser opened automatically)");
|
|
2395
2463
|
} catch {
|
|
2396
2464
|
console.log("(Could not open browser automatically \u2014 please copy the URL above)");
|
|
@@ -6075,6 +6143,15 @@ var DEFAULT_QUOTA = {
|
|
|
6075
6143
|
maxRequestsPerMinute: 10,
|
|
6076
6144
|
maxRequestsPerDay: 500
|
|
6077
6145
|
};
|
|
6146
|
+
var DEFAULT_AGENT_MODELS = {
|
|
6147
|
+
anthropic: "anthropic/claude-sonnet-4-6",
|
|
6148
|
+
openai: "openai/gpt-4o",
|
|
6149
|
+
openrouter: "openrouter/anthropic/claude-sonnet-4-6",
|
|
6150
|
+
groq: "groq/llama-4-scout-17b",
|
|
6151
|
+
google: "google/gemini-2.5-flash",
|
|
6152
|
+
mistral: "mistral/mistral-large-latest",
|
|
6153
|
+
xai: "xai/grok-2"
|
|
6154
|
+
};
|
|
6078
6155
|
async function initCommand(opts) {
|
|
6079
6156
|
const format = opts?.format ?? "text";
|
|
6080
6157
|
if (format !== "json") {
|
|
@@ -6087,11 +6164,11 @@ async function initCommand(opts) {
|
|
|
6087
6164
|
reason: "config_exists",
|
|
6088
6165
|
configPath: getConfigPath()
|
|
6089
6166
|
}, null, 2));
|
|
6090
|
-
return;
|
|
6167
|
+
return void 0;
|
|
6091
6168
|
}
|
|
6092
6169
|
console.log(`Config already exists at ${getConfigPath()}`);
|
|
6093
6170
|
console.log('To reinitialize, run "canonry init --force".');
|
|
6094
|
-
return;
|
|
6171
|
+
return void 0;
|
|
6095
6172
|
}
|
|
6096
6173
|
const configDir = getConfigDir();
|
|
6097
6174
|
if (!fs6.existsSync(configDir)) {
|
|
@@ -6246,6 +6323,29 @@ Config saved to ${getConfigPath()}`);
|
|
|
6246
6323
|
console.log(`API key: ${rawApiKey}`);
|
|
6247
6324
|
console.log(`Providers: ${providerNames.join(", ")}`);
|
|
6248
6325
|
}
|
|
6326
|
+
let agentLLM;
|
|
6327
|
+
const agentProvider = opts?.agentProvider;
|
|
6328
|
+
const agentKey = opts?.agentKey;
|
|
6329
|
+
const agentModel = opts?.agentModel;
|
|
6330
|
+
if (agentProvider || agentKey || agentModel) {
|
|
6331
|
+
const provider = agentProvider ?? "anthropic";
|
|
6332
|
+
agentLLM = {
|
|
6333
|
+
provider,
|
|
6334
|
+
key: agentKey,
|
|
6335
|
+
model: agentModel ?? DEFAULT_AGENT_MODELS[provider]
|
|
6336
|
+
};
|
|
6337
|
+
} else if (!nonInteractive) {
|
|
6338
|
+
console.log("\nConfigure agent LLM (the model that powers the agent):");
|
|
6339
|
+
console.log("Supported providers: anthropic, openai, openrouter, groq, mistral, xai, google, cerebras\n");
|
|
6340
|
+
const provider = await prompt("Provider [anthropic]: ") || "anthropic";
|
|
6341
|
+
const key = await prompt("API key (press Enter to skip): ");
|
|
6342
|
+
if (key) {
|
|
6343
|
+
const defaultModel = DEFAULT_AGENT_MODELS[provider];
|
|
6344
|
+
const modelText = defaultModel ? `Model [${defaultModel}]: ` : "Model: ";
|
|
6345
|
+
const model = await prompt(modelText) || defaultModel;
|
|
6346
|
+
agentLLM = { provider, key, model };
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
6249
6349
|
if (format !== "json") {
|
|
6250
6350
|
showFirstRunNotice();
|
|
6251
6351
|
console.log('Run "canonry serve" to start the server.');
|
|
@@ -6254,6 +6354,7 @@ Config saved to ${getConfigPath()}`);
|
|
|
6254
6354
|
providerCount: providerNames.length,
|
|
6255
6355
|
providers: providerNames
|
|
6256
6356
|
});
|
|
6357
|
+
return agentLLM;
|
|
6257
6358
|
}
|
|
6258
6359
|
|
|
6259
6360
|
// src/commands/serve.ts
|
|
@@ -6816,12 +6917,12 @@ async function wordpressSetMeta(project, body) {
|
|
|
6816
6917
|
printPageDetail(result);
|
|
6817
6918
|
}
|
|
6818
6919
|
async function wordpressBulkSetMeta(project, opts) {
|
|
6819
|
-
const
|
|
6820
|
-
const
|
|
6821
|
-
const filePath =
|
|
6920
|
+
const fs10 = await import("fs/promises");
|
|
6921
|
+
const path9 = await import("path");
|
|
6922
|
+
const filePath = path9.resolve(opts.from);
|
|
6822
6923
|
let raw;
|
|
6823
6924
|
try {
|
|
6824
|
-
raw = await
|
|
6925
|
+
raw = await fs10.readFile(filePath, "utf8");
|
|
6825
6926
|
} catch {
|
|
6826
6927
|
throw new CliError({
|
|
6827
6928
|
code: "FILE_READ_ERROR",
|
|
@@ -6918,13 +7019,13 @@ async function wordpressSetSchema(project, body) {
|
|
|
6918
7019
|
printManualAssist(`Schema update for "${body.slug}"`, result);
|
|
6919
7020
|
}
|
|
6920
7021
|
async function wordpressSchemaDeploy(project, opts) {
|
|
6921
|
-
const
|
|
6922
|
-
const
|
|
7022
|
+
const fs10 = await import("fs/promises");
|
|
7023
|
+
const path9 = await import("path");
|
|
6923
7024
|
const yaml = await import("yaml").catch(() => null);
|
|
6924
|
-
const filePath =
|
|
7025
|
+
const filePath = path9.resolve(opts.profile);
|
|
6925
7026
|
let raw;
|
|
6926
7027
|
try {
|
|
6927
|
-
raw = await
|
|
7028
|
+
raw = await fs10.readFile(filePath, "utf8");
|
|
6928
7029
|
} catch {
|
|
6929
7030
|
throw new CliError({
|
|
6930
7031
|
code: "FILE_READ_ERROR",
|
|
@@ -7029,13 +7130,13 @@ async function wordpressOnboard(project, opts) {
|
|
|
7029
7130
|
}
|
|
7030
7131
|
let profileData;
|
|
7031
7132
|
if (opts.profile) {
|
|
7032
|
-
const
|
|
7033
|
-
const
|
|
7133
|
+
const fs10 = await import("fs/promises");
|
|
7134
|
+
const path9 = await import("path");
|
|
7034
7135
|
const yaml = await import("yaml").catch(() => null);
|
|
7035
|
-
const filePath =
|
|
7136
|
+
const filePath = path9.resolve(opts.profile);
|
|
7036
7137
|
let raw;
|
|
7037
7138
|
try {
|
|
7038
|
-
raw = await
|
|
7139
|
+
raw = await fs10.readFile(filePath, "utf8");
|
|
7039
7140
|
} catch {
|
|
7040
7141
|
throw new CliError({
|
|
7041
7142
|
code: "FILE_READ_ERROR",
|
|
@@ -7616,6 +7717,699 @@ var WORDPRESS_CLI_COMMANDS = [
|
|
|
7616
7717
|
}
|
|
7617
7718
|
];
|
|
7618
7719
|
|
|
7720
|
+
// src/commands/agent.ts
|
|
7721
|
+
import path8 from "path";
|
|
7722
|
+
|
|
7723
|
+
// src/agent-manager.ts
|
|
7724
|
+
import { execFileSync, spawn as spawn2 } from "child_process";
|
|
7725
|
+
import fs8 from "fs";
|
|
7726
|
+
import path6 from "path";
|
|
7727
|
+
var log = createLogger("AgentManager");
|
|
7728
|
+
var PROCESS_MARKER = "canonry-openclaw-gateway";
|
|
7729
|
+
var AgentManager = class {
|
|
7730
|
+
constructor(config, stateDir) {
|
|
7731
|
+
this.config = config;
|
|
7732
|
+
this.stateDir = stateDir;
|
|
7733
|
+
this.processJsonPath = path6.join(stateDir, "process.json");
|
|
7734
|
+
}
|
|
7735
|
+
processJsonPath;
|
|
7736
|
+
/**
|
|
7737
|
+
* Check if the gateway process is running.
|
|
7738
|
+
* Cleans up stale process.json if the process is dead or belongs to a
|
|
7739
|
+
* different process (PID reuse).
|
|
7740
|
+
*/
|
|
7741
|
+
status() {
|
|
7742
|
+
const info = this.readProcessInfo();
|
|
7743
|
+
if (!info) {
|
|
7744
|
+
return { state: "stopped" };
|
|
7745
|
+
}
|
|
7746
|
+
if (info.marker !== PROCESS_MARKER) {
|
|
7747
|
+
this.removeProcessJson();
|
|
7748
|
+
return { state: "stopped" };
|
|
7749
|
+
}
|
|
7750
|
+
if (isProcessAlive2(info.pid) && this.verifyProcessIdentity(info.pid)) {
|
|
7751
|
+
return {
|
|
7752
|
+
state: "running",
|
|
7753
|
+
pid: info.pid,
|
|
7754
|
+
port: info.gatewayPort,
|
|
7755
|
+
startedAt: info.startedAt
|
|
7756
|
+
};
|
|
7757
|
+
}
|
|
7758
|
+
this.removeProcessJson();
|
|
7759
|
+
return { state: "stopped" };
|
|
7760
|
+
}
|
|
7761
|
+
/**
|
|
7762
|
+
* Start the OpenClaw gateway as a detached background process.
|
|
7763
|
+
* Idempotent — no-op if already running.
|
|
7764
|
+
* Waits briefly for the process to confirm it hasn't crashed on startup.
|
|
7765
|
+
*/
|
|
7766
|
+
async start() {
|
|
7767
|
+
const currentStatus = this.status();
|
|
7768
|
+
if (currentStatus.state === "running") {
|
|
7769
|
+
log.info("already.running", { pid: currentStatus.pid });
|
|
7770
|
+
return;
|
|
7771
|
+
}
|
|
7772
|
+
const binary = this.config.binary ?? "openclaw";
|
|
7773
|
+
const profile = this.config.profile ?? "aero";
|
|
7774
|
+
const port = this.config.gatewayPort ?? 3579;
|
|
7775
|
+
if (!fs8.existsSync(this.stateDir)) {
|
|
7776
|
+
fs8.mkdirSync(this.stateDir, { recursive: true });
|
|
7777
|
+
}
|
|
7778
|
+
const logFile = path6.join(this.stateDir, "gateway.log");
|
|
7779
|
+
const logFd = fs8.openSync(logFile, "a");
|
|
7780
|
+
const dotEnv = this.loadDotEnv();
|
|
7781
|
+
const child = spawn2(binary, ["--profile", profile, "gateway"], {
|
|
7782
|
+
detached: true,
|
|
7783
|
+
stdio: ["ignore", logFd, logFd],
|
|
7784
|
+
env: {
|
|
7785
|
+
...process.env,
|
|
7786
|
+
...dotEnv,
|
|
7787
|
+
OPENCLAW_PROFILE: profile,
|
|
7788
|
+
OPENCLAW_GATEWAY_PORT: String(port),
|
|
7789
|
+
OPENCLAW_STATE_DIR: this.stateDir
|
|
7790
|
+
}
|
|
7791
|
+
});
|
|
7792
|
+
const startupResult = await new Promise((resolve) => {
|
|
7793
|
+
let settled = false;
|
|
7794
|
+
const settle = (r) => {
|
|
7795
|
+
if (settled) return;
|
|
7796
|
+
settled = true;
|
|
7797
|
+
resolve(r);
|
|
7798
|
+
};
|
|
7799
|
+
child.on("error", (err) => settle({ error: err }));
|
|
7800
|
+
child.on("exit", (code) => settle({ exitCode: code }));
|
|
7801
|
+
setTimeout(() => settle({}), 500);
|
|
7802
|
+
});
|
|
7803
|
+
child.unref();
|
|
7804
|
+
fs8.closeSync(logFd);
|
|
7805
|
+
if (startupResult.error) {
|
|
7806
|
+
throw new Error(`Failed to start OpenClaw gateway: ${startupResult.error.message}`);
|
|
7807
|
+
}
|
|
7808
|
+
if (startupResult.exitCode != null) {
|
|
7809
|
+
throw new Error(`OpenClaw gateway exited immediately (code ${startupResult.exitCode}). Check ${path6.join(this.stateDir, "gateway.log")} for details.`);
|
|
7810
|
+
}
|
|
7811
|
+
if (child.pid == null) {
|
|
7812
|
+
throw new Error("Failed to start OpenClaw gateway: no PID returned by spawn");
|
|
7813
|
+
}
|
|
7814
|
+
if (!isProcessAlive2(child.pid)) {
|
|
7815
|
+
throw new Error(`OpenClaw gateway exited immediately after spawn. Check ${path6.join(this.stateDir, "gateway.log")} for details.`);
|
|
7816
|
+
}
|
|
7817
|
+
const processInfo = {
|
|
7818
|
+
pid: child.pid,
|
|
7819
|
+
gatewayPort: port,
|
|
7820
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7821
|
+
marker: PROCESS_MARKER
|
|
7822
|
+
};
|
|
7823
|
+
fs8.writeFileSync(this.processJsonPath, JSON.stringify(processInfo, null, 2), "utf-8");
|
|
7824
|
+
log.info("started", { pid: child.pid, port });
|
|
7825
|
+
}
|
|
7826
|
+
/**
|
|
7827
|
+
* Stop the gateway process.
|
|
7828
|
+
* Uses DenchClaw escalation: SIGTERM → 800ms poll → SIGKILL.
|
|
7829
|
+
* Idempotent — no-op if already stopped.
|
|
7830
|
+
*/
|
|
7831
|
+
async stop() {
|
|
7832
|
+
const info = this.readProcessInfo();
|
|
7833
|
+
if (!info) return;
|
|
7834
|
+
if (isProcessAlive2(info.pid) && info.marker === PROCESS_MARKER && this.verifyProcessIdentity(info.pid)) {
|
|
7835
|
+
await terminateWithEscalation(info.pid);
|
|
7836
|
+
}
|
|
7837
|
+
this.removeProcessJson();
|
|
7838
|
+
log.info("stopped", { pid: info.pid });
|
|
7839
|
+
}
|
|
7840
|
+
/**
|
|
7841
|
+
* Stop the gateway, wipe the workspace directory, and prepare for re-seeding.
|
|
7842
|
+
*/
|
|
7843
|
+
async reset() {
|
|
7844
|
+
await this.stop();
|
|
7845
|
+
const workspaceDir = path6.join(this.stateDir, "workspace");
|
|
7846
|
+
if (fs8.existsSync(workspaceDir)) {
|
|
7847
|
+
fs8.rmSync(workspaceDir, { recursive: true, force: true });
|
|
7848
|
+
log.info("workspace.wiped", { dir: workspaceDir });
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
7851
|
+
/**
|
|
7852
|
+
* Verify that the PID actually belongs to an openclaw process by checking
|
|
7853
|
+
* the full command line. Requires "openclaw" in the args to avoid matching
|
|
7854
|
+
* unrelated Node processes after PID reuse.
|
|
7855
|
+
*/
|
|
7856
|
+
verifyProcessIdentity(pid) {
|
|
7857
|
+
try {
|
|
7858
|
+
if (process.platform === "darwin") {
|
|
7859
|
+
const out = execFileSync("ps", ["-p", String(pid), "-o", "args="], {
|
|
7860
|
+
encoding: "utf-8",
|
|
7861
|
+
timeout: 2e3
|
|
7862
|
+
}).trim();
|
|
7863
|
+
return out.includes("openclaw");
|
|
7864
|
+
}
|
|
7865
|
+
if (process.platform === "linux") {
|
|
7866
|
+
const cmdline = fs8.readFileSync(`/proc/${pid}/cmdline`, "utf-8");
|
|
7867
|
+
return cmdline.includes("openclaw");
|
|
7868
|
+
}
|
|
7869
|
+
return true;
|
|
7870
|
+
} catch {
|
|
7871
|
+
return false;
|
|
7872
|
+
}
|
|
7873
|
+
}
|
|
7874
|
+
readProcessInfo() {
|
|
7875
|
+
if (!fs8.existsSync(this.processJsonPath)) return null;
|
|
7876
|
+
try {
|
|
7877
|
+
return JSON.parse(fs8.readFileSync(this.processJsonPath, "utf-8"));
|
|
7878
|
+
} catch {
|
|
7879
|
+
return null;
|
|
7880
|
+
}
|
|
7881
|
+
}
|
|
7882
|
+
removeProcessJson() {
|
|
7883
|
+
try {
|
|
7884
|
+
fs8.unlinkSync(this.processJsonPath);
|
|
7885
|
+
} catch {
|
|
7886
|
+
}
|
|
7887
|
+
}
|
|
7888
|
+
/** Parse a simple KEY=value dotenv file from the state dir. */
|
|
7889
|
+
loadDotEnv() {
|
|
7890
|
+
const envFile = path6.join(this.stateDir, ".env");
|
|
7891
|
+
if (!fs8.existsSync(envFile)) return {};
|
|
7892
|
+
const result = {};
|
|
7893
|
+
for (const line of fs8.readFileSync(envFile, "utf-8").split("\n")) {
|
|
7894
|
+
const trimmed = line.trim();
|
|
7895
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
7896
|
+
const eq3 = trimmed.indexOf("=");
|
|
7897
|
+
if (eq3 < 1) continue;
|
|
7898
|
+
result[trimmed.slice(0, eq3)] = trimmed.slice(eq3 + 1);
|
|
7899
|
+
}
|
|
7900
|
+
return result;
|
|
7901
|
+
}
|
|
7902
|
+
};
|
|
7903
|
+
function isProcessAlive2(pid) {
|
|
7904
|
+
try {
|
|
7905
|
+
process.kill(pid, 0);
|
|
7906
|
+
return true;
|
|
7907
|
+
} catch {
|
|
7908
|
+
return false;
|
|
7909
|
+
}
|
|
7910
|
+
}
|
|
7911
|
+
async function terminateWithEscalation(pid) {
|
|
7912
|
+
try {
|
|
7913
|
+
process.kill(pid, "SIGTERM");
|
|
7914
|
+
} catch {
|
|
7915
|
+
return;
|
|
7916
|
+
}
|
|
7917
|
+
const deadline = Date.now() + 800;
|
|
7918
|
+
while (Date.now() < deadline) {
|
|
7919
|
+
if (!isProcessAlive2(pid)) return;
|
|
7920
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
7921
|
+
}
|
|
7922
|
+
try {
|
|
7923
|
+
process.kill(pid, "SIGKILL");
|
|
7924
|
+
} catch {
|
|
7925
|
+
}
|
|
7926
|
+
}
|
|
7927
|
+
|
|
7928
|
+
// src/agent-bootstrap.ts
|
|
7929
|
+
import { execFileSync as execFileSync2, execSync } from "child_process";
|
|
7930
|
+
import fs9 from "fs";
|
|
7931
|
+
import os from "os";
|
|
7932
|
+
import path7 from "path";
|
|
7933
|
+
import { fileURLToPath } from "url";
|
|
7934
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
7935
|
+
var cachedResult = null;
|
|
7936
|
+
var cachedAt = 0;
|
|
7937
|
+
function getAeroStateDir(profile = "aero") {
|
|
7938
|
+
return path7.join(os.homedir(), `.openclaw-${profile}`);
|
|
7939
|
+
}
|
|
7940
|
+
async function detectOpenClaw(config) {
|
|
7941
|
+
if (cachedResult && Date.now() - cachedAt < CACHE_TTL_MS) {
|
|
7942
|
+
return cachedResult;
|
|
7943
|
+
}
|
|
7944
|
+
let result;
|
|
7945
|
+
if (config?.binary) {
|
|
7946
|
+
const version = probeVersion(config.binary);
|
|
7947
|
+
if (version) {
|
|
7948
|
+
result = { found: true, path: config.binary, version };
|
|
7949
|
+
cachedResult = result;
|
|
7950
|
+
cachedAt = Date.now();
|
|
7951
|
+
return result;
|
|
7952
|
+
}
|
|
7953
|
+
}
|
|
7954
|
+
const binaryPath = findInPath();
|
|
7955
|
+
if (binaryPath) {
|
|
7956
|
+
const version = probeVersion(binaryPath);
|
|
7957
|
+
if (version) {
|
|
7958
|
+
result = { found: true, path: binaryPath, version };
|
|
7959
|
+
cachedResult = result;
|
|
7960
|
+
cachedAt = Date.now();
|
|
7961
|
+
return result;
|
|
7962
|
+
}
|
|
7963
|
+
}
|
|
7964
|
+
result = { found: false };
|
|
7965
|
+
cachedResult = result;
|
|
7966
|
+
cachedAt = Date.now();
|
|
7967
|
+
return result;
|
|
7968
|
+
}
|
|
7969
|
+
detectOpenClaw.resetCache = () => {
|
|
7970
|
+
cachedResult = null;
|
|
7971
|
+
cachedAt = 0;
|
|
7972
|
+
};
|
|
7973
|
+
function probeVersion(binaryPath) {
|
|
7974
|
+
try {
|
|
7975
|
+
const output = execFileSync2(binaryPath, ["--version"], {
|
|
7976
|
+
timeout: 5e3,
|
|
7977
|
+
encoding: "utf-8"
|
|
7978
|
+
});
|
|
7979
|
+
const match = output.toString().trim().match(/(\d+\.\d+\.\d+)/);
|
|
7980
|
+
return match ? match[1] : output.toString().trim();
|
|
7981
|
+
} catch {
|
|
7982
|
+
return null;
|
|
7983
|
+
}
|
|
7984
|
+
}
|
|
7985
|
+
function findInPath() {
|
|
7986
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
7987
|
+
try {
|
|
7988
|
+
const output = execFileSync2(cmd, ["openclaw"], {
|
|
7989
|
+
timeout: 5e3,
|
|
7990
|
+
encoding: "utf-8"
|
|
7991
|
+
});
|
|
7992
|
+
return output.toString().trim().split("\n")[0] || null;
|
|
7993
|
+
} catch {
|
|
7994
|
+
return null;
|
|
7995
|
+
}
|
|
7996
|
+
}
|
|
7997
|
+
async function installOpenClaw(opts) {
|
|
7998
|
+
try {
|
|
7999
|
+
execSync("npm install -g openclaw", {
|
|
8000
|
+
timeout: 12e4,
|
|
8001
|
+
stdio: opts?.silent ? "pipe" : "inherit"
|
|
8002
|
+
});
|
|
8003
|
+
} catch (err) {
|
|
8004
|
+
return {
|
|
8005
|
+
success: false,
|
|
8006
|
+
error: err instanceof Error ? err.message : String(err)
|
|
8007
|
+
};
|
|
8008
|
+
}
|
|
8009
|
+
detectOpenClaw.resetCache();
|
|
8010
|
+
const detection = await detectOpenClaw();
|
|
8011
|
+
if (!detection.found) {
|
|
8012
|
+
return {
|
|
8013
|
+
success: false,
|
|
8014
|
+
error: "npm install succeeded but openclaw binary was not found in PATH"
|
|
8015
|
+
};
|
|
8016
|
+
}
|
|
8017
|
+
return { success: true, detection };
|
|
8018
|
+
}
|
|
8019
|
+
function seedWorkspace(stateDir) {
|
|
8020
|
+
const workspaceDir = path7.join(stateDir, "workspace");
|
|
8021
|
+
fs9.mkdirSync(workspaceDir, { recursive: true });
|
|
8022
|
+
const __dirname = path7.dirname(fileURLToPath(import.meta.url));
|
|
8023
|
+
const assetsDir = path7.join(__dirname, "..", "assets", "agent-workspace");
|
|
8024
|
+
if (!fs9.existsSync(assetsDir)) {
|
|
8025
|
+
return;
|
|
8026
|
+
}
|
|
8027
|
+
copyDirRecursive(assetsDir, workspaceDir);
|
|
8028
|
+
}
|
|
8029
|
+
function initializeOpenClawProfile(binary, profile, workspaceDir) {
|
|
8030
|
+
try {
|
|
8031
|
+
execFileSync2(binary, [
|
|
8032
|
+
"--profile",
|
|
8033
|
+
profile,
|
|
8034
|
+
"onboard",
|
|
8035
|
+
"--non-interactive",
|
|
8036
|
+
"--accept-risk",
|
|
8037
|
+
"--mode",
|
|
8038
|
+
"local",
|
|
8039
|
+
"--workspace",
|
|
8040
|
+
workspaceDir,
|
|
8041
|
+
"--skip-channels",
|
|
8042
|
+
"--skip-skills",
|
|
8043
|
+
"--skip-health",
|
|
8044
|
+
"--no-install-daemon"
|
|
8045
|
+
], { timeout: 3e4, stdio: "pipe" });
|
|
8046
|
+
} catch (err) {
|
|
8047
|
+
const stderr = err instanceof Error && "stderr" in err ? String(err.stderr) : "";
|
|
8048
|
+
if (stderr.toLowerCase().includes("already")) return;
|
|
8049
|
+
throw new CliError({
|
|
8050
|
+
code: "AGENT_PROFILE_INIT_FAILED",
|
|
8051
|
+
message: `Failed to initialize OpenClaw profile: ${stderr || (err instanceof Error ? err.message : String(err))}`,
|
|
8052
|
+
displayMessage: `Failed to initialize OpenClaw profile "${profile}".`
|
|
8053
|
+
});
|
|
8054
|
+
}
|
|
8055
|
+
}
|
|
8056
|
+
function configureOpenClawGateway(binary, profile, gatewayPort) {
|
|
8057
|
+
const entries = [
|
|
8058
|
+
["gateway.mode", "local", false],
|
|
8059
|
+
["gateway.port", String(gatewayPort), true]
|
|
8060
|
+
];
|
|
8061
|
+
for (const [key, value, strict] of entries) {
|
|
8062
|
+
try {
|
|
8063
|
+
const args = ["--profile", profile, "config", "set", key, value];
|
|
8064
|
+
if (strict) args.push("--strict-json");
|
|
8065
|
+
execFileSync2(binary, args, { timeout: 1e4, stdio: "pipe" });
|
|
8066
|
+
} catch (err) {
|
|
8067
|
+
throw new CliError({
|
|
8068
|
+
code: "AGENT_GATEWAY_CONFIG_FAILED",
|
|
8069
|
+
message: `Failed to set ${key}=${value}: ${err instanceof Error ? err.message : String(err)}`,
|
|
8070
|
+
displayMessage: `Failed to configure OpenClaw gateway (${key}).`
|
|
8071
|
+
});
|
|
8072
|
+
}
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
8075
|
+
function setOpenClawModel(binary, profile, model) {
|
|
8076
|
+
try {
|
|
8077
|
+
execFileSync2(binary, [
|
|
8078
|
+
"--profile",
|
|
8079
|
+
profile,
|
|
8080
|
+
"models",
|
|
8081
|
+
"set",
|
|
8082
|
+
model
|
|
8083
|
+
], { timeout: 1e4, stdio: "pipe" });
|
|
8084
|
+
} catch (err) {
|
|
8085
|
+
throw new CliError({
|
|
8086
|
+
code: "AGENT_MODEL_SET_FAILED",
|
|
8087
|
+
message: `Failed to set agent model to ${model}: ${err instanceof Error ? err.message : String(err)}`,
|
|
8088
|
+
displayMessage: `Failed to set agent model to "${model}".`
|
|
8089
|
+
});
|
|
8090
|
+
}
|
|
8091
|
+
}
|
|
8092
|
+
function providerEnvVar(provider) {
|
|
8093
|
+
const map = {
|
|
8094
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
8095
|
+
openai: "OPENAI_API_KEY",
|
|
8096
|
+
google: "GOOGLE_API_KEY",
|
|
8097
|
+
"google-vertex": "GOOGLE_API_KEY",
|
|
8098
|
+
groq: "GROQ_API_KEY",
|
|
8099
|
+
mistral: "MISTRAL_API_KEY",
|
|
8100
|
+
xai: "XAI_API_KEY",
|
|
8101
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
8102
|
+
cerebras: "CEREBRAS_API_KEY"
|
|
8103
|
+
};
|
|
8104
|
+
return map[provider] ?? `${provider.toUpperCase().replace(/-/g, "_")}_API_KEY`;
|
|
8105
|
+
}
|
|
8106
|
+
function writeAgentEnv(stateDir, key, value) {
|
|
8107
|
+
const envFile = path7.join(stateDir, ".env");
|
|
8108
|
+
let lines = [];
|
|
8109
|
+
if (fs9.existsSync(envFile)) {
|
|
8110
|
+
lines = fs9.readFileSync(envFile, "utf-8").split("\n");
|
|
8111
|
+
}
|
|
8112
|
+
const prefix = `${key}=`;
|
|
8113
|
+
const idx = lines.findIndex((l) => l.startsWith(prefix));
|
|
8114
|
+
const entry = `${key}=${value}`;
|
|
8115
|
+
if (idx >= 0) {
|
|
8116
|
+
lines[idx] = entry;
|
|
8117
|
+
} else {
|
|
8118
|
+
lines.push(entry);
|
|
8119
|
+
}
|
|
8120
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
8121
|
+
fs9.writeFileSync(envFile, lines.join("\n") + "\n", "utf-8");
|
|
8122
|
+
}
|
|
8123
|
+
function resolveAgentCredentials(opts) {
|
|
8124
|
+
const provider = opts.agentProvider ?? "anthropic";
|
|
8125
|
+
if (opts.agentKey) {
|
|
8126
|
+
return { provider, key: opts.agentKey, model: opts.agentModel };
|
|
8127
|
+
}
|
|
8128
|
+
const envVar = providerEnvVar(provider);
|
|
8129
|
+
const envKey = process.env[envVar];
|
|
8130
|
+
if (envKey) {
|
|
8131
|
+
return { provider, key: envKey, model: opts.agentModel };
|
|
8132
|
+
}
|
|
8133
|
+
const genericKey = process.env.CANONRY_AGENT_KEY;
|
|
8134
|
+
if (genericKey) {
|
|
8135
|
+
return { provider, key: genericKey, model: opts.agentModel };
|
|
8136
|
+
}
|
|
8137
|
+
const envFile = path7.join(opts.stateDir, ".env");
|
|
8138
|
+
if (fs9.existsSync(envFile)) {
|
|
8139
|
+
const hasKey = fs9.readFileSync(envFile, "utf-8").split("\n").some((l) => l.includes("_API_KEY="));
|
|
8140
|
+
if (hasKey) {
|
|
8141
|
+
return { provider, key: void 0, model: opts.agentModel };
|
|
8142
|
+
}
|
|
8143
|
+
}
|
|
8144
|
+
return { provider, key: void 0, model: opts.agentModel };
|
|
8145
|
+
}
|
|
8146
|
+
function copyDirRecursive(src, dest) {
|
|
8147
|
+
fs9.mkdirSync(dest, { recursive: true });
|
|
8148
|
+
for (const entry of fs9.readdirSync(src, { withFileTypes: true })) {
|
|
8149
|
+
const srcPath = path7.join(src, entry.name);
|
|
8150
|
+
const destPath = path7.join(dest, entry.name);
|
|
8151
|
+
if (entry.isDirectory()) {
|
|
8152
|
+
copyDirRecursive(srcPath, destPath);
|
|
8153
|
+
} else {
|
|
8154
|
+
fs9.copyFileSync(srcPath, destPath);
|
|
8155
|
+
}
|
|
8156
|
+
}
|
|
8157
|
+
}
|
|
8158
|
+
|
|
8159
|
+
// src/commands/agent.ts
|
|
8160
|
+
function resolveStateDir(opts) {
|
|
8161
|
+
if (opts?.stateDir) return opts.stateDir;
|
|
8162
|
+
try {
|
|
8163
|
+
const config = loadConfig();
|
|
8164
|
+
const profile = config.agent?.profile ?? "aero";
|
|
8165
|
+
return getAeroStateDir(profile);
|
|
8166
|
+
} catch {
|
|
8167
|
+
return getAeroStateDir();
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
8170
|
+
function resolveConfig() {
|
|
8171
|
+
try {
|
|
8172
|
+
return loadConfig().agent ?? {};
|
|
8173
|
+
} catch {
|
|
8174
|
+
return {};
|
|
8175
|
+
}
|
|
8176
|
+
}
|
|
8177
|
+
async function agentStatus(opts) {
|
|
8178
|
+
const stateDir = resolveStateDir(opts);
|
|
8179
|
+
const config = resolveConfig();
|
|
8180
|
+
const mgr = new AgentManager(config, stateDir);
|
|
8181
|
+
const status = mgr.status();
|
|
8182
|
+
if (opts?.format === "json") {
|
|
8183
|
+
console.log(JSON.stringify(status, null, 2));
|
|
8184
|
+
return;
|
|
8185
|
+
}
|
|
8186
|
+
if (status.state === "running") {
|
|
8187
|
+
console.log(`Agent: running (PID ${status.pid}, port ${status.port})`);
|
|
8188
|
+
if (status.startedAt) {
|
|
8189
|
+
console.log(`Started: ${status.startedAt}`);
|
|
8190
|
+
}
|
|
8191
|
+
} else {
|
|
8192
|
+
console.log("Agent: stopped");
|
|
8193
|
+
}
|
|
8194
|
+
}
|
|
8195
|
+
async function agentStart(opts) {
|
|
8196
|
+
const stateDir = resolveStateDir(opts);
|
|
8197
|
+
const config = resolveConfig();
|
|
8198
|
+
const mgr = new AgentManager(config, stateDir);
|
|
8199
|
+
await mgr.start();
|
|
8200
|
+
const status = mgr.status();
|
|
8201
|
+
if (opts?.format === "json") {
|
|
8202
|
+
console.log(JSON.stringify(status, null, 2));
|
|
8203
|
+
} else {
|
|
8204
|
+
console.log(`Agent started (PID ${status.pid}, port ${status.port})`);
|
|
8205
|
+
}
|
|
8206
|
+
}
|
|
8207
|
+
async function agentStop(opts) {
|
|
8208
|
+
const stateDir = resolveStateDir(opts);
|
|
8209
|
+
const config = resolveConfig();
|
|
8210
|
+
const mgr = new AgentManager(config, stateDir);
|
|
8211
|
+
await mgr.stop();
|
|
8212
|
+
if (opts?.format === "json") {
|
|
8213
|
+
console.log(JSON.stringify({ state: "stopped" }, null, 2));
|
|
8214
|
+
} else {
|
|
8215
|
+
console.log("Agent stopped");
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
8218
|
+
async function agentReset(opts) {
|
|
8219
|
+
const stateDir = resolveStateDir(opts);
|
|
8220
|
+
const config = resolveConfig();
|
|
8221
|
+
const mgr = new AgentManager(config, stateDir);
|
|
8222
|
+
await mgr.reset();
|
|
8223
|
+
if (opts?.format === "json") {
|
|
8224
|
+
console.log(JSON.stringify({ state: "reset" }, null, 2));
|
|
8225
|
+
} else {
|
|
8226
|
+
console.log('Agent reset \u2014 workspace wiped. Run "canonry agent setup" to re-initialize.');
|
|
8227
|
+
}
|
|
8228
|
+
}
|
|
8229
|
+
async function agentSetup(opts) {
|
|
8230
|
+
const isJson = opts?.format === "json";
|
|
8231
|
+
let agentLLM;
|
|
8232
|
+
if (!configExists()) {
|
|
8233
|
+
const initOpts = {
|
|
8234
|
+
geminiKey: opts?.geminiKey,
|
|
8235
|
+
openaiKey: opts?.openaiKey,
|
|
8236
|
+
claudeKey: opts?.claudeKey,
|
|
8237
|
+
perplexityKey: opts?.perplexityKey,
|
|
8238
|
+
localUrl: opts?.localUrl,
|
|
8239
|
+
localModel: opts?.localModel,
|
|
8240
|
+
localKey: opts?.localKey,
|
|
8241
|
+
googleClientId: opts?.googleClientId,
|
|
8242
|
+
googleClientSecret: opts?.googleClientSecret,
|
|
8243
|
+
agentProvider: opts?.agentProvider,
|
|
8244
|
+
agentKey: opts?.agentKey,
|
|
8245
|
+
agentModel: opts?.agentModel
|
|
8246
|
+
};
|
|
8247
|
+
if (isJson) {
|
|
8248
|
+
agentLLM = await suppressStdout(() => initCommand(initOpts)) ?? void 0;
|
|
8249
|
+
} else {
|
|
8250
|
+
agentLLM = await initCommand(initOpts) ?? void 0;
|
|
8251
|
+
}
|
|
8252
|
+
}
|
|
8253
|
+
const existingConfig = resolveConfig();
|
|
8254
|
+
let detection = await detectOpenClaw(existingConfig);
|
|
8255
|
+
if (!detection.found) {
|
|
8256
|
+
detection = await autoInstallOrFail(opts?.format);
|
|
8257
|
+
}
|
|
8258
|
+
const profile = existingConfig.profile ?? "aero";
|
|
8259
|
+
const gatewayPort = opts?.gatewayPort ?? existingConfig.gatewayPort ?? 3579;
|
|
8260
|
+
const stateDir = opts?.stateDir ?? getAeroStateDir(profile);
|
|
8261
|
+
saveConfigPatch({
|
|
8262
|
+
agent: {
|
|
8263
|
+
binary: detection.path,
|
|
8264
|
+
profile,
|
|
8265
|
+
gatewayPort,
|
|
8266
|
+
autoStart: existingConfig.autoStart
|
|
8267
|
+
}
|
|
8268
|
+
});
|
|
8269
|
+
initializeOpenClawProfile(detection.path, profile, path8.join(stateDir, "workspace"));
|
|
8270
|
+
configureOpenClawGateway(detection.path, profile, gatewayPort);
|
|
8271
|
+
const creds = agentLLM ?? resolveAgentCredentials({
|
|
8272
|
+
agentProvider: opts?.agentProvider,
|
|
8273
|
+
agentKey: opts?.agentKey,
|
|
8274
|
+
agentModel: opts?.agentModel,
|
|
8275
|
+
stateDir
|
|
8276
|
+
});
|
|
8277
|
+
if (creds.key) {
|
|
8278
|
+
writeAgentEnv(stateDir, providerEnvVar(creds.provider), creds.key);
|
|
8279
|
+
if (opts?.format !== "json") {
|
|
8280
|
+
console.log(`Agent LLM: ${creds.provider} credentials configured`);
|
|
8281
|
+
}
|
|
8282
|
+
}
|
|
8283
|
+
if (creds.model) {
|
|
8284
|
+
setOpenClawModel(detection.path, profile, creds.model);
|
|
8285
|
+
if (opts?.format !== "json") {
|
|
8286
|
+
console.log(`Agent model: ${creds.model}`);
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
8289
|
+
seedWorkspace(stateDir);
|
|
8290
|
+
if (opts?.format === "json") {
|
|
8291
|
+
console.log(JSON.stringify({
|
|
8292
|
+
state: "configured",
|
|
8293
|
+
binary: detection.path,
|
|
8294
|
+
version: detection.version,
|
|
8295
|
+
profile,
|
|
8296
|
+
gatewayPort,
|
|
8297
|
+
stateDir
|
|
8298
|
+
}, null, 2));
|
|
8299
|
+
} else {
|
|
8300
|
+
console.log(`OpenClaw: ${detection.path} (${detection.version})`);
|
|
8301
|
+
console.log(`Profile: ${profile}`);
|
|
8302
|
+
console.log(`Gateway port: ${gatewayPort}`);
|
|
8303
|
+
console.log(`State dir: ${stateDir}`);
|
|
8304
|
+
console.log("Agent setup complete.");
|
|
8305
|
+
}
|
|
8306
|
+
}
|
|
8307
|
+
async function autoInstallOrFail(format) {
|
|
8308
|
+
if (format !== "json") {
|
|
8309
|
+
console.log("OpenClaw not found, installing via npm...");
|
|
8310
|
+
}
|
|
8311
|
+
const install = await installOpenClaw({ silent: format === "json" });
|
|
8312
|
+
if (!install.success) {
|
|
8313
|
+
const msg = `Failed to install OpenClaw: ${install.error}`;
|
|
8314
|
+
if (format === "json") {
|
|
8315
|
+
console.error(JSON.stringify({ error: { code: "AGENT_INSTALL_FAILED", message: msg } }));
|
|
8316
|
+
} else {
|
|
8317
|
+
console.error(msg);
|
|
8318
|
+
}
|
|
8319
|
+
process.exitCode = 1;
|
|
8320
|
+
throw new Error(msg);
|
|
8321
|
+
}
|
|
8322
|
+
if (format !== "json") {
|
|
8323
|
+
console.log(`Installed OpenClaw ${install.detection.version}`);
|
|
8324
|
+
}
|
|
8325
|
+
return install.detection;
|
|
8326
|
+
}
|
|
8327
|
+
async function suppressStdout(fn) {
|
|
8328
|
+
const original = console.log;
|
|
8329
|
+
console.log = () => {
|
|
8330
|
+
};
|
|
8331
|
+
try {
|
|
8332
|
+
return await fn();
|
|
8333
|
+
} finally {
|
|
8334
|
+
console.log = original;
|
|
8335
|
+
}
|
|
8336
|
+
}
|
|
8337
|
+
|
|
8338
|
+
// src/cli-commands/agent.ts
|
|
8339
|
+
var AGENT_CLI_COMMANDS = [
|
|
8340
|
+
{
|
|
8341
|
+
path: ["agent", "status"],
|
|
8342
|
+
usage: "canonry agent status [--format json]",
|
|
8343
|
+
options: {},
|
|
8344
|
+
run: async (input) => {
|
|
8345
|
+
await agentStatus({ format: input.format });
|
|
8346
|
+
}
|
|
8347
|
+
},
|
|
8348
|
+
{
|
|
8349
|
+
path: ["agent", "start"],
|
|
8350
|
+
usage: "canonry agent start [--format json]",
|
|
8351
|
+
options: {},
|
|
8352
|
+
run: async (input) => {
|
|
8353
|
+
await agentStart({ format: input.format });
|
|
8354
|
+
}
|
|
8355
|
+
},
|
|
8356
|
+
{
|
|
8357
|
+
path: ["agent", "stop"],
|
|
8358
|
+
usage: "canonry agent stop [--format json]",
|
|
8359
|
+
options: {},
|
|
8360
|
+
run: async (input) => {
|
|
8361
|
+
await agentStop({ format: input.format });
|
|
8362
|
+
}
|
|
8363
|
+
},
|
|
8364
|
+
{
|
|
8365
|
+
path: ["agent", "reset"],
|
|
8366
|
+
usage: "canonry agent reset [--format json]",
|
|
8367
|
+
options: {},
|
|
8368
|
+
run: async (input) => {
|
|
8369
|
+
await agentReset({ format: input.format });
|
|
8370
|
+
}
|
|
8371
|
+
},
|
|
8372
|
+
{
|
|
8373
|
+
path: ["agent", "setup"],
|
|
8374
|
+
usage: "canonry agent setup [--agent-provider <id>] [--agent-key <key>] [--agent-model <model>] [--gateway-port <port>] [--gemini-key <key>] [--openai-key <key>] [--claude-key <key>] [--perplexity-key <key>] [--format json]",
|
|
8375
|
+
options: {
|
|
8376
|
+
"agent-provider": stringOption(),
|
|
8377
|
+
"agent-key": stringOption(),
|
|
8378
|
+
"agent-model": stringOption(),
|
|
8379
|
+
"gateway-port": { type: "string" },
|
|
8380
|
+
"gemini-key": stringOption(),
|
|
8381
|
+
"openai-key": stringOption(),
|
|
8382
|
+
"claude-key": stringOption(),
|
|
8383
|
+
"perplexity-key": stringOption(),
|
|
8384
|
+
"local-url": stringOption(),
|
|
8385
|
+
"local-model": stringOption(),
|
|
8386
|
+
"local-key": stringOption(),
|
|
8387
|
+
"google-client-id": stringOption(),
|
|
8388
|
+
"google-client-secret": stringOption()
|
|
8389
|
+
},
|
|
8390
|
+
run: async (input) => {
|
|
8391
|
+
const portStr = input.values["gateway-port"];
|
|
8392
|
+
const gatewayPort = typeof portStr === "string" ? Number.parseInt(portStr, 10) : void 0;
|
|
8393
|
+
await agentSetup({
|
|
8394
|
+
agentProvider: getString(input.values, "agent-provider"),
|
|
8395
|
+
agentKey: getString(input.values, "agent-key"),
|
|
8396
|
+
agentModel: getString(input.values, "agent-model"),
|
|
8397
|
+
gatewayPort,
|
|
8398
|
+
format: input.format,
|
|
8399
|
+
geminiKey: getString(input.values, "gemini-key"),
|
|
8400
|
+
openaiKey: getString(input.values, "openai-key"),
|
|
8401
|
+
claudeKey: getString(input.values, "claude-key"),
|
|
8402
|
+
perplexityKey: getString(input.values, "perplexity-key"),
|
|
8403
|
+
localUrl: getString(input.values, "local-url"),
|
|
8404
|
+
localModel: getString(input.values, "local-model"),
|
|
8405
|
+
localKey: getString(input.values, "local-key"),
|
|
8406
|
+
googleClientId: getString(input.values, "google-client-id"),
|
|
8407
|
+
googleClientSecret: getString(input.values, "google-client-secret")
|
|
8408
|
+
});
|
|
8409
|
+
}
|
|
8410
|
+
}
|
|
8411
|
+
];
|
|
8412
|
+
|
|
7619
8413
|
// src/cli-commands.ts
|
|
7620
8414
|
var REGISTERED_CLI_COMMANDS = [
|
|
7621
8415
|
...BACKFILL_CLI_COMMANDS,
|
|
@@ -7634,7 +8428,8 @@ var REGISTERED_CLI_COMMANDS = [
|
|
|
7634
8428
|
...WORDPRESS_CLI_COMMANDS,
|
|
7635
8429
|
...CDP_CLI_COMMANDS,
|
|
7636
8430
|
...GA_CLI_COMMANDS,
|
|
7637
|
-
...INTELLIGENCE_CLI_COMMANDS
|
|
8431
|
+
...INTELLIGENCE_CLI_COMMANDS,
|
|
8432
|
+
...AGENT_CLI_COMMANDS
|
|
7638
8433
|
];
|
|
7639
8434
|
|
|
7640
8435
|
// src/cli.ts
|