@ainyc/canonry 1.46.1 → 1.48.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/README.md +26 -11
- package/assets/agent-workspace/AGENTS.md +89 -0
- package/assets/agent-workspace/SOUL.md +54 -0
- package/assets/agent-workspace/USER.md +23 -0
- package/assets/agent-workspace/skills/aero/SKILL.md +1 -0
- package/assets/agent-workspace/skills/aero/references/wordpress-elementor-mcp.md +218 -0
- package/assets/agent-workspace/skills/canonry-setup/SKILL.md +14 -74
- package/assets/agent-workspace/skills/canonry-setup/references/canonry-cli.md +11 -1
- package/assets/agent-workspace/skills/canonry-setup/references/wordpress-integration.md +4 -0
- package/assets/assets/{index-Cxg_4UWs.js → index-CVk23m8J.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-HO22LHTY.js → chunk-JTKHPNGL.js} +7 -2
- package/dist/{chunk-RMLIF47M.js → chunk-YPTVJRJY.js} +976 -154
- package/dist/cli.js +180 -538
- package/dist/index.js +2 -2
- package/dist/{intelligence-service-ZISLIU4S.js → intelligence-service-Q4WX46MJ.js} +1 -1
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -1,127 +1,75 @@
|
|
|
1
1
|
#!/usr/bin/env node --import tsx
|
|
2
2
|
import {
|
|
3
|
+
AGENT_WEBHOOK_EVENTS,
|
|
4
|
+
AgentManager,
|
|
5
|
+
CliError,
|
|
6
|
+
EXIT_SYSTEM_ERROR,
|
|
7
|
+
EXIT_USER_ERROR,
|
|
3
8
|
ProviderNames,
|
|
4
9
|
RunKinds,
|
|
10
|
+
attachAgentWebhookDirect,
|
|
11
|
+
buildAgentWebhookUrl,
|
|
5
12
|
computeCompetitorOverlap,
|
|
6
13
|
configExists,
|
|
14
|
+
configureOpenClawGateway,
|
|
7
15
|
createServer,
|
|
16
|
+
detectOpenClaw,
|
|
8
17
|
determineAnswerMentioned,
|
|
9
18
|
determineCitationState,
|
|
10
19
|
effectiveDomains,
|
|
11
20
|
extractRecommendedCompetitors,
|
|
12
21
|
formatAuditFactorScore,
|
|
22
|
+
getAeroStateDir,
|
|
13
23
|
getConfigDir,
|
|
14
24
|
getConfigPath,
|
|
15
25
|
getOrCreateAnonymousId,
|
|
26
|
+
initializeOpenClawProfile,
|
|
27
|
+
installOpenClaw,
|
|
16
28
|
isFirstRun,
|
|
17
29
|
isTelemetryEnabled,
|
|
18
30
|
loadConfig,
|
|
19
31
|
notificationEventSchema,
|
|
32
|
+
printCliError,
|
|
33
|
+
providerEnvVar,
|
|
20
34
|
providerQuotaPolicySchema,
|
|
21
35
|
reparseStoredResult,
|
|
22
36
|
reparseStoredResult2,
|
|
23
37
|
reparseStoredResult3,
|
|
24
38
|
reparseStoredResult4,
|
|
39
|
+
resolveAgentCredentials,
|
|
25
40
|
resolveProviderInput,
|
|
26
41
|
saveConfig,
|
|
27
42
|
saveConfigPatch,
|
|
43
|
+
seedWorkspace,
|
|
28
44
|
setGoogleAuthConfig,
|
|
45
|
+
setOpenClawModel,
|
|
29
46
|
showFirstRunNotice,
|
|
30
|
-
trackEvent
|
|
31
|
-
|
|
47
|
+
trackEvent,
|
|
48
|
+
usageError,
|
|
49
|
+
writeAgentEnv
|
|
50
|
+
} from "./chunk-YPTVJRJY.js";
|
|
32
51
|
import {
|
|
33
52
|
apiKeys,
|
|
34
53
|
competitors,
|
|
35
54
|
createClient,
|
|
36
|
-
createLogger,
|
|
37
55
|
migrate,
|
|
38
56
|
parseJsonColumn,
|
|
39
57
|
projects,
|
|
40
58
|
querySnapshots,
|
|
41
59
|
runs
|
|
42
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-JTKHPNGL.js";
|
|
43
61
|
|
|
44
62
|
// src/cli.ts
|
|
45
63
|
import { pathToFileURL } from "url";
|
|
46
64
|
|
|
47
|
-
// src/cli-error.ts
|
|
48
|
-
var EXIT_USER_ERROR = 1;
|
|
49
|
-
var EXIT_SYSTEM_ERROR = 2;
|
|
50
|
-
var CliError = class extends Error {
|
|
51
|
-
code;
|
|
52
|
-
displayMessage;
|
|
53
|
-
details;
|
|
54
|
-
exitCode;
|
|
55
|
-
constructor(options) {
|
|
56
|
-
super(options.message);
|
|
57
|
-
this.name = "CliError";
|
|
58
|
-
this.code = options.code;
|
|
59
|
-
this.displayMessage = options.displayMessage;
|
|
60
|
-
this.details = options.details;
|
|
61
|
-
this.exitCode = options.exitCode ?? EXIT_USER_ERROR;
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
function usageError(displayMessage, options) {
|
|
65
|
-
const firstLine = displayMessage.split("\n", 1)[0] ?? "Error: invalid command usage";
|
|
66
|
-
return new CliError({
|
|
67
|
-
code: "CLI_USAGE_ERROR",
|
|
68
|
-
message: options?.message ?? firstLine.replace(/^Error:\s*/, ""),
|
|
69
|
-
displayMessage,
|
|
70
|
-
details: options?.details
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
function printCliError(err, format) {
|
|
74
|
-
if (format === "json") {
|
|
75
|
-
if (err instanceof CliError) {
|
|
76
|
-
console.error(
|
|
77
|
-
JSON.stringify(
|
|
78
|
-
{
|
|
79
|
-
error: {
|
|
80
|
-
code: err.code,
|
|
81
|
-
message: err.message,
|
|
82
|
-
...err.details ? { details: err.details } : {}
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
null,
|
|
86
|
-
2
|
|
87
|
-
)
|
|
88
|
-
);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const message = err instanceof Error ? err.message : "An unexpected error occurred";
|
|
92
|
-
console.error(
|
|
93
|
-
JSON.stringify(
|
|
94
|
-
{
|
|
95
|
-
error: {
|
|
96
|
-
code: "CLI_ERROR",
|
|
97
|
-
message
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
null,
|
|
101
|
-
2
|
|
102
|
-
)
|
|
103
|
-
);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (err instanceof CliError && err.displayMessage) {
|
|
107
|
-
console.error(err.displayMessage);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (err instanceof Error) {
|
|
111
|
-
console.error(`Error: ${err.message}`);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
console.error("An unexpected error occurred");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
65
|
// src/cli-dispatch.ts
|
|
118
66
|
import { parseArgs } from "util";
|
|
119
67
|
function commandId(spec) {
|
|
120
68
|
return spec.path.join(".");
|
|
121
69
|
}
|
|
122
|
-
function matchesPath(args,
|
|
123
|
-
if (args.length <
|
|
124
|
-
return
|
|
70
|
+
function matchesPath(args, path7) {
|
|
71
|
+
if (args.length < path7.length) return false;
|
|
72
|
+
return path7.every((segment, index) => args[index] === segment);
|
|
125
73
|
}
|
|
126
74
|
function withFormatOption(options) {
|
|
127
75
|
if (!options) {
|
|
@@ -356,7 +304,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
356
304
|
console.log(` Errors: ${providerErrors}`);
|
|
357
305
|
}
|
|
358
306
|
async function backfillInsightsCommand(project, opts) {
|
|
359
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
307
|
+
const { IntelligenceService } = await import("./intelligence-service-Q4WX46MJ.js");
|
|
360
308
|
const config = loadConfig();
|
|
361
309
|
const db = createClient(config.database);
|
|
362
310
|
migrate(db);
|
|
@@ -614,9 +562,9 @@ var ApiClient = class {
|
|
|
614
562
|
}
|
|
615
563
|
return this.probePromise;
|
|
616
564
|
}
|
|
617
|
-
async request(method,
|
|
565
|
+
async request(method, path7, body) {
|
|
618
566
|
await this.probeBasePath();
|
|
619
|
-
const url = `${this.baseUrl}${
|
|
567
|
+
const url = `${this.baseUrl}${path7}`;
|
|
620
568
|
const serializedBody = body != null ? JSON.stringify(body) : void 0;
|
|
621
569
|
const headers = {
|
|
622
570
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
@@ -1695,9 +1643,9 @@ async function gaConnect(project, opts) {
|
|
|
1695
1643
|
propertyId: opts.propertyId
|
|
1696
1644
|
};
|
|
1697
1645
|
if (opts.keyFile) {
|
|
1698
|
-
const
|
|
1646
|
+
const fs8 = await import("fs");
|
|
1699
1647
|
try {
|
|
1700
|
-
const content =
|
|
1648
|
+
const content = fs8.readFileSync(opts.keyFile, "utf-8");
|
|
1701
1649
|
JSON.parse(content);
|
|
1702
1650
|
body.keyJson = content;
|
|
1703
1651
|
} catch (e) {
|
|
@@ -2455,10 +2403,10 @@ Open this URL in your browser to authorize Google ${opts.type.toUpperCase()} acc
|
|
|
2455
2403
|
console.log("(Ensure this URI is listed in your Google Cloud Console OAuth client's authorized redirect URIs)\n");
|
|
2456
2404
|
}
|
|
2457
2405
|
try {
|
|
2458
|
-
const { spawn:
|
|
2406
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
2459
2407
|
const platform = process.platform;
|
|
2460
2408
|
const [cmd, ...extraArgs] = platform === "darwin" ? ["open", authUrl] : platform === "win32" ? ["cmd", "/c", "start", "", authUrl] : ["xdg-open", authUrl];
|
|
2461
|
-
|
|
2409
|
+
spawn2(cmd, [...extraArgs], { detached: true, stdio: "ignore" }).unref();
|
|
2462
2410
|
console.log("(Browser opened automatically)");
|
|
2463
2411
|
} catch {
|
|
2464
2412
|
console.log("(Could not open browser automatically \u2014 please copy the URL above)");
|
|
@@ -3524,7 +3472,9 @@ var EVENT_DESCRIPTIONS = {
|
|
|
3524
3472
|
"citation.lost": "A keyword lost its citation status",
|
|
3525
3473
|
"citation.gained": "A keyword gained citation status",
|
|
3526
3474
|
"run.completed": "A visibility run completed successfully",
|
|
3527
|
-
"run.failed": "A visibility run failed"
|
|
3475
|
+
"run.failed": "A visibility run failed",
|
|
3476
|
+
"insight.critical": "A critical-severity insight was generated",
|
|
3477
|
+
"insight.high": "A high-severity insight was generated"
|
|
3528
3478
|
};
|
|
3529
3479
|
function listEvents(format) {
|
|
3530
3480
|
const events = notificationEventSchema.options;
|
|
@@ -6917,12 +6867,12 @@ async function wordpressSetMeta(project, body) {
|
|
|
6917
6867
|
printPageDetail(result);
|
|
6918
6868
|
}
|
|
6919
6869
|
async function wordpressBulkSetMeta(project, opts) {
|
|
6920
|
-
const
|
|
6921
|
-
const
|
|
6922
|
-
const filePath =
|
|
6870
|
+
const fs8 = await import("fs/promises");
|
|
6871
|
+
const path7 = await import("path");
|
|
6872
|
+
const filePath = path7.resolve(opts.from);
|
|
6923
6873
|
let raw;
|
|
6924
6874
|
try {
|
|
6925
|
-
raw = await
|
|
6875
|
+
raw = await fs8.readFile(filePath, "utf8");
|
|
6926
6876
|
} catch {
|
|
6927
6877
|
throw new CliError({
|
|
6928
6878
|
code: "FILE_READ_ERROR",
|
|
@@ -7019,13 +6969,13 @@ async function wordpressSetSchema(project, body) {
|
|
|
7019
6969
|
printManualAssist(`Schema update for "${body.slug}"`, result);
|
|
7020
6970
|
}
|
|
7021
6971
|
async function wordpressSchemaDeploy(project, opts) {
|
|
7022
|
-
const
|
|
7023
|
-
const
|
|
6972
|
+
const fs8 = await import("fs/promises");
|
|
6973
|
+
const path7 = await import("path");
|
|
7024
6974
|
const yaml = await import("yaml").catch(() => null);
|
|
7025
|
-
const filePath =
|
|
6975
|
+
const filePath = path7.resolve(opts.profile);
|
|
7026
6976
|
let raw;
|
|
7027
6977
|
try {
|
|
7028
|
-
raw = await
|
|
6978
|
+
raw = await fs8.readFile(filePath, "utf8");
|
|
7029
6979
|
} catch {
|
|
7030
6980
|
throw new CliError({
|
|
7031
6981
|
code: "FILE_READ_ERROR",
|
|
@@ -7130,13 +7080,13 @@ async function wordpressOnboard(project, opts) {
|
|
|
7130
7080
|
}
|
|
7131
7081
|
let profileData;
|
|
7132
7082
|
if (opts.profile) {
|
|
7133
|
-
const
|
|
7134
|
-
const
|
|
7083
|
+
const fs8 = await import("fs/promises");
|
|
7084
|
+
const path7 = await import("path");
|
|
7135
7085
|
const yaml = await import("yaml").catch(() => null);
|
|
7136
|
-
const filePath =
|
|
7086
|
+
const filePath = path7.resolve(opts.profile);
|
|
7137
7087
|
let raw;
|
|
7138
7088
|
try {
|
|
7139
|
-
raw = await
|
|
7089
|
+
raw = await fs8.readFile(filePath, "utf8");
|
|
7140
7090
|
} catch {
|
|
7141
7091
|
throw new CliError({
|
|
7142
7092
|
code: "FILE_READ_ERROR",
|
|
@@ -7718,445 +7668,7 @@ var WORDPRESS_CLI_COMMANDS = [
|
|
|
7718
7668
|
];
|
|
7719
7669
|
|
|
7720
7670
|
// 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
7671
|
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
7672
|
function resolveStateDir(opts) {
|
|
8161
7673
|
if (opts?.stateDir) return opts.stateDir;
|
|
8162
7674
|
try {
|
|
@@ -8266,7 +7778,7 @@ async function agentSetup(opts) {
|
|
|
8266
7778
|
autoStart: existingConfig.autoStart
|
|
8267
7779
|
}
|
|
8268
7780
|
});
|
|
8269
|
-
initializeOpenClawProfile(detection.path, profile,
|
|
7781
|
+
initializeOpenClawProfile(detection.path, profile, path6.join(stateDir, "workspace"));
|
|
8270
7782
|
configureOpenClawGateway(detection.path, profile, gatewayPort);
|
|
8271
7783
|
const creds = agentLLM ?? resolveAgentCredentials({
|
|
8272
7784
|
agentProvider: opts?.agentProvider,
|
|
@@ -8287,6 +7799,7 @@ async function agentSetup(opts) {
|
|
|
8287
7799
|
}
|
|
8288
7800
|
}
|
|
8289
7801
|
seedWorkspace(stateDir);
|
|
7802
|
+
const attachSummary = await attachAgentWebhookToAllProjects(gatewayPort);
|
|
8290
7803
|
if (opts?.format === "json") {
|
|
8291
7804
|
console.log(JSON.stringify({
|
|
8292
7805
|
state: "configured",
|
|
@@ -8294,19 +7807,120 @@ async function agentSetup(opts) {
|
|
|
8294
7807
|
version: detection.version,
|
|
8295
7808
|
profile,
|
|
8296
7809
|
gatewayPort,
|
|
8297
|
-
stateDir
|
|
7810
|
+
stateDir,
|
|
7811
|
+
attached: attachSummary
|
|
8298
7812
|
}, null, 2));
|
|
8299
7813
|
} else {
|
|
8300
7814
|
console.log(`OpenClaw: ${detection.path} (${detection.version})`);
|
|
8301
7815
|
console.log(`Profile: ${profile}`);
|
|
8302
7816
|
console.log(`Gateway port: ${gatewayPort}`);
|
|
8303
7817
|
console.log(`State dir: ${stateDir}`);
|
|
7818
|
+
if (attachSummary.attached > 0 || attachSummary.alreadyAttached > 0) {
|
|
7819
|
+
console.log(
|
|
7820
|
+
`Agent webhook: ${attachSummary.attached} attached, ${attachSummary.alreadyAttached} already present (via ${attachSummary.path})`
|
|
7821
|
+
);
|
|
7822
|
+
}
|
|
8304
7823
|
console.log("Agent setup complete.");
|
|
8305
7824
|
}
|
|
8306
7825
|
}
|
|
7826
|
+
async function attachAgentWebhookToAllProjects(gatewayPort) {
|
|
7827
|
+
let config;
|
|
7828
|
+
try {
|
|
7829
|
+
config = loadConfig();
|
|
7830
|
+
} catch {
|
|
7831
|
+
return { path: "skipped", attached: 0, alreadyAttached: 0 };
|
|
7832
|
+
}
|
|
7833
|
+
try {
|
|
7834
|
+
const client = createApiClient();
|
|
7835
|
+
const projectList = await client.listProjects();
|
|
7836
|
+
const agentUrl = buildAgentWebhookUrl(gatewayPort);
|
|
7837
|
+
let attached2 = 0;
|
|
7838
|
+
let alreadyAttached2 = 0;
|
|
7839
|
+
for (const project of projectList) {
|
|
7840
|
+
const existing = await client.listNotifications(project.name);
|
|
7841
|
+
if (existing.some((n) => n.source === "agent")) {
|
|
7842
|
+
alreadyAttached2++;
|
|
7843
|
+
continue;
|
|
7844
|
+
}
|
|
7845
|
+
await client.createNotification(project.name, {
|
|
7846
|
+
channel: "webhook",
|
|
7847
|
+
url: agentUrl,
|
|
7848
|
+
events: [...AGENT_WEBHOOK_EVENTS],
|
|
7849
|
+
source: "agent"
|
|
7850
|
+
});
|
|
7851
|
+
attached2++;
|
|
7852
|
+
}
|
|
7853
|
+
return { path: "api", attached: attached2, alreadyAttached: alreadyAttached2 };
|
|
7854
|
+
} catch (err) {
|
|
7855
|
+
if (!isConnectionError(err)) throw err;
|
|
7856
|
+
}
|
|
7857
|
+
const db = createClient(config.database);
|
|
7858
|
+
migrate(db);
|
|
7859
|
+
const rows = db.select({ id: projects.id }).from(projects).all();
|
|
7860
|
+
let attached = 0;
|
|
7861
|
+
let alreadyAttached = 0;
|
|
7862
|
+
for (const row of rows) {
|
|
7863
|
+
const result = attachAgentWebhookDirect(db, row.id, gatewayPort);
|
|
7864
|
+
if (result === "attached") attached++;
|
|
7865
|
+
else alreadyAttached++;
|
|
7866
|
+
}
|
|
7867
|
+
return { path: "db", attached, alreadyAttached };
|
|
7868
|
+
}
|
|
7869
|
+
function isConnectionError(err) {
|
|
7870
|
+
if (!(err instanceof Error)) return false;
|
|
7871
|
+
const msg = err.message.toLowerCase();
|
|
7872
|
+
const code = err.code ?? "";
|
|
7873
|
+
return code === "CONNECTION_ERROR" || code === "ECONNREFUSED" || code === "ENOTFOUND" || msg.includes("could not connect") || msg.includes("econnrefused") || msg.includes("fetch failed") || msg.includes("connection refused");
|
|
7874
|
+
}
|
|
7875
|
+
async function agentAttach(opts) {
|
|
7876
|
+
const config = loadConfig();
|
|
7877
|
+
const gatewayPort = config.agent?.gatewayPort ?? 3579;
|
|
7878
|
+
const agentUrl = buildAgentWebhookUrl(gatewayPort);
|
|
7879
|
+
const client = createApiClient();
|
|
7880
|
+
const existing = await client.listNotifications(opts.project);
|
|
7881
|
+
const hasAgent = existing.some((n) => n.source === "agent");
|
|
7882
|
+
if (hasAgent) {
|
|
7883
|
+
if (opts.format === "json") {
|
|
7884
|
+
console.log(JSON.stringify({ status: "already-attached", project: opts.project }));
|
|
7885
|
+
} else {
|
|
7886
|
+
console.log(`Agent webhook already attached to "${opts.project}"`);
|
|
7887
|
+
}
|
|
7888
|
+
return;
|
|
7889
|
+
}
|
|
7890
|
+
const result = await client.createNotification(opts.project, {
|
|
7891
|
+
channel: "webhook",
|
|
7892
|
+
url: agentUrl,
|
|
7893
|
+
events: [...AGENT_WEBHOOK_EVENTS],
|
|
7894
|
+
source: "agent"
|
|
7895
|
+
});
|
|
7896
|
+
if (opts.format === "json") {
|
|
7897
|
+
console.log(JSON.stringify({ status: "attached", project: opts.project, notificationId: result.id }));
|
|
7898
|
+
} else {
|
|
7899
|
+
console.log(`Agent webhook attached to "${opts.project}"`);
|
|
7900
|
+
}
|
|
7901
|
+
}
|
|
7902
|
+
async function agentDetach(opts) {
|
|
7903
|
+
const client = createApiClient();
|
|
7904
|
+
const existing = await client.listNotifications(opts.project);
|
|
7905
|
+
const agentNotif = existing.find((n) => n.source === "agent");
|
|
7906
|
+
if (!agentNotif) {
|
|
7907
|
+
if (opts.format === "json") {
|
|
7908
|
+
console.log(JSON.stringify({ status: "not-attached", project: opts.project }));
|
|
7909
|
+
} else {
|
|
7910
|
+
console.log(`No agent webhook found on "${opts.project}"`);
|
|
7911
|
+
}
|
|
7912
|
+
return;
|
|
7913
|
+
}
|
|
7914
|
+
await client.deleteNotification(opts.project, agentNotif.id);
|
|
7915
|
+
if (opts.format === "json") {
|
|
7916
|
+
console.log(JSON.stringify({ status: "detached", project: opts.project }));
|
|
7917
|
+
} else {
|
|
7918
|
+
console.log(`Agent webhook detached from "${opts.project}"`);
|
|
7919
|
+
}
|
|
7920
|
+
}
|
|
8307
7921
|
async function autoInstallOrFail(format) {
|
|
8308
7922
|
if (format !== "json") {
|
|
8309
|
-
console.log("OpenClaw not found, installing via npm...");
|
|
7923
|
+
console.log("OpenClaw not found, installing pinned openclaw@2026.4.14 via npm...");
|
|
8310
7924
|
}
|
|
8311
7925
|
const install = await installOpenClaw({ silent: format === "json" });
|
|
8312
7926
|
if (!install.success) {
|
|
@@ -8369,6 +7983,34 @@ var AGENT_CLI_COMMANDS = [
|
|
|
8369
7983
|
await agentReset({ format: input.format });
|
|
8370
7984
|
}
|
|
8371
7985
|
},
|
|
7986
|
+
{
|
|
7987
|
+
path: ["agent", "attach"],
|
|
7988
|
+
usage: "canonry agent attach <project> [--format json]",
|
|
7989
|
+
options: {},
|
|
7990
|
+
run: async (input) => {
|
|
7991
|
+
const project = input.positionals[0];
|
|
7992
|
+
if (!project) {
|
|
7993
|
+
console.error("Usage: canonry agent attach <project>");
|
|
7994
|
+
process.exitCode = 1;
|
|
7995
|
+
return;
|
|
7996
|
+
}
|
|
7997
|
+
await agentAttach({ project, format: input.format });
|
|
7998
|
+
}
|
|
7999
|
+
},
|
|
8000
|
+
{
|
|
8001
|
+
path: ["agent", "detach"],
|
|
8002
|
+
usage: "canonry agent detach <project> [--format json]",
|
|
8003
|
+
options: {},
|
|
8004
|
+
run: async (input) => {
|
|
8005
|
+
const project = input.positionals[0];
|
|
8006
|
+
if (!project) {
|
|
8007
|
+
console.error("Usage: canonry agent detach <project>");
|
|
8008
|
+
process.exitCode = 1;
|
|
8009
|
+
return;
|
|
8010
|
+
}
|
|
8011
|
+
await agentDetach({ project, format: input.format });
|
|
8012
|
+
}
|
|
8013
|
+
},
|
|
8372
8014
|
{
|
|
8373
8015
|
path: ["agent", "setup"],
|
|
8374
8016
|
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]",
|