@ainyc/canonry 1.46.0 → 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/dist/cli.js CHANGED
@@ -28,11 +28,12 @@ import {
28
28
  setGoogleAuthConfig,
29
29
  showFirstRunNotice,
30
30
  trackEvent
31
- } from "./chunk-22RIKNII.js";
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, path6) {
122
- if (args.length < path6.length) return false;
123
- return path6.every((segment, index) => args[index] === segment);
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, path6, body) {
617
+ async request(method, path9, body) {
617
618
  await this.probeBasePath();
618
- const url = `${this.baseUrl}${path6}`;
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}`,
@@ -1694,9 +1695,9 @@ async function gaConnect(project, opts) {
1694
1695
  propertyId: opts.propertyId
1695
1696
  };
1696
1697
  if (opts.keyFile) {
1697
- const fs8 = await import("fs");
1698
+ const fs10 = await import("fs");
1698
1699
  try {
1699
- const content = fs8.readFileSync(opts.keyFile, "utf-8");
1700
+ const content = fs10.readFileSync(opts.keyFile, "utf-8");
1700
1701
  JSON.parse(content);
1701
1702
  body.keyJson = content;
1702
1703
  } catch (e) {
@@ -2454,10 +2455,10 @@ Open this URL in your browser to authorize Google ${opts.type.toUpperCase()} acc
2454
2455
  console.log("(Ensure this URI is listed in your Google Cloud Console OAuth client's authorized redirect URIs)\n");
2455
2456
  }
2456
2457
  try {
2457
- const { spawn: spawn2 } = await import("child_process");
2458
+ const { spawn: spawn3 } = await import("child_process");
2458
2459
  const platform = process.platform;
2459
2460
  const [cmd, ...extraArgs] = platform === "darwin" ? ["open", authUrl] : platform === "win32" ? ["cmd", "/c", "start", "", authUrl] : ["xdg-open", authUrl];
2460
- spawn2(cmd, [...extraArgs], { detached: true, stdio: "ignore" }).unref();
2461
+ spawn3(cmd, [...extraArgs], { detached: true, stdio: "ignore" }).unref();
2461
2462
  console.log("(Browser opened automatically)");
2462
2463
  } catch {
2463
2464
  console.log("(Could not open browser automatically \u2014 please copy the URL above)");
@@ -6142,6 +6143,15 @@ var DEFAULT_QUOTA = {
6142
6143
  maxRequestsPerMinute: 10,
6143
6144
  maxRequestsPerDay: 500
6144
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
+ };
6145
6155
  async function initCommand(opts) {
6146
6156
  const format = opts?.format ?? "text";
6147
6157
  if (format !== "json") {
@@ -6154,11 +6164,11 @@ async function initCommand(opts) {
6154
6164
  reason: "config_exists",
6155
6165
  configPath: getConfigPath()
6156
6166
  }, null, 2));
6157
- return;
6167
+ return void 0;
6158
6168
  }
6159
6169
  console.log(`Config already exists at ${getConfigPath()}`);
6160
6170
  console.log('To reinitialize, run "canonry init --force".');
6161
- return;
6171
+ return void 0;
6162
6172
  }
6163
6173
  const configDir = getConfigDir();
6164
6174
  if (!fs6.existsSync(configDir)) {
@@ -6313,6 +6323,29 @@ Config saved to ${getConfigPath()}`);
6313
6323
  console.log(`API key: ${rawApiKey}`);
6314
6324
  console.log(`Providers: ${providerNames.join(", ")}`);
6315
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
+ }
6316
6349
  if (format !== "json") {
6317
6350
  showFirstRunNotice();
6318
6351
  console.log('Run "canonry serve" to start the server.');
@@ -6321,6 +6354,7 @@ Config saved to ${getConfigPath()}`);
6321
6354
  providerCount: providerNames.length,
6322
6355
  providers: providerNames
6323
6356
  });
6357
+ return agentLLM;
6324
6358
  }
6325
6359
 
6326
6360
  // src/commands/serve.ts
@@ -6883,12 +6917,12 @@ async function wordpressSetMeta(project, body) {
6883
6917
  printPageDetail(result);
6884
6918
  }
6885
6919
  async function wordpressBulkSetMeta(project, opts) {
6886
- const fs8 = await import("fs/promises");
6887
- const path6 = await import("path");
6888
- const filePath = path6.resolve(opts.from);
6920
+ const fs10 = await import("fs/promises");
6921
+ const path9 = await import("path");
6922
+ const filePath = path9.resolve(opts.from);
6889
6923
  let raw;
6890
6924
  try {
6891
- raw = await fs8.readFile(filePath, "utf8");
6925
+ raw = await fs10.readFile(filePath, "utf8");
6892
6926
  } catch {
6893
6927
  throw new CliError({
6894
6928
  code: "FILE_READ_ERROR",
@@ -6985,13 +7019,13 @@ async function wordpressSetSchema(project, body) {
6985
7019
  printManualAssist(`Schema update for "${body.slug}"`, result);
6986
7020
  }
6987
7021
  async function wordpressSchemaDeploy(project, opts) {
6988
- const fs8 = await import("fs/promises");
6989
- const path6 = await import("path");
7022
+ const fs10 = await import("fs/promises");
7023
+ const path9 = await import("path");
6990
7024
  const yaml = await import("yaml").catch(() => null);
6991
- const filePath = path6.resolve(opts.profile);
7025
+ const filePath = path9.resolve(opts.profile);
6992
7026
  let raw;
6993
7027
  try {
6994
- raw = await fs8.readFile(filePath, "utf8");
7028
+ raw = await fs10.readFile(filePath, "utf8");
6995
7029
  } catch {
6996
7030
  throw new CliError({
6997
7031
  code: "FILE_READ_ERROR",
@@ -7096,13 +7130,13 @@ async function wordpressOnboard(project, opts) {
7096
7130
  }
7097
7131
  let profileData;
7098
7132
  if (opts.profile) {
7099
- const fs8 = await import("fs/promises");
7100
- const path6 = await import("path");
7133
+ const fs10 = await import("fs/promises");
7134
+ const path9 = await import("path");
7101
7135
  const yaml = await import("yaml").catch(() => null);
7102
- const filePath = path6.resolve(opts.profile);
7136
+ const filePath = path9.resolve(opts.profile);
7103
7137
  let raw;
7104
7138
  try {
7105
- raw = await fs8.readFile(filePath, "utf8");
7139
+ raw = await fs10.readFile(filePath, "utf8");
7106
7140
  } catch {
7107
7141
  throw new CliError({
7108
7142
  code: "FILE_READ_ERROR",
@@ -7683,6 +7717,699 @@ var WORDPRESS_CLI_COMMANDS = [
7683
7717
  }
7684
7718
  ];
7685
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
+
7686
8413
  // src/cli-commands.ts
7687
8414
  var REGISTERED_CLI_COMMANDS = [
7688
8415
  ...BACKFILL_CLI_COMMANDS,
@@ -7701,7 +8428,8 @@ var REGISTERED_CLI_COMMANDS = [
7701
8428
  ...WORDPRESS_CLI_COMMANDS,
7702
8429
  ...CDP_CLI_COMMANDS,
7703
8430
  ...GA_CLI_COMMANDS,
7704
- ...INTELLIGENCE_CLI_COMMANDS
8431
+ ...INTELLIGENCE_CLI_COMMANDS,
8432
+ ...AGENT_CLI_COMMANDS
7705
8433
  ];
7706
8434
 
7707
8435
  // src/cli.ts