traderclaw-cli 1.0.101 → 1.0.103

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.
@@ -198,23 +198,34 @@ export async function ensureLinuxGatewayPersistence(options = {}) {
198
198
  let unitEnabled = false;
199
199
  try {
200
200
  await runSpawn("systemctl", ["--user", "daemon-reload"]);
201
- await runSpawn("systemctl", ["--user", "enable", unitName]);
201
+ await runSpawn("systemctl", ["--user", "enable", "--now", unitName]);
202
202
  unitEnabled = true;
203
- emit("info", `systemd user unit enabled: ${unitName}`);
203
+ emit("info", `systemd user unit enabled and started: ${unitName}`);
204
204
  } catch (err) {
205
205
  const msg = err?.stderr || err?.message || String(err);
206
- errors.push(`systemctl --user enable: ${msg}`);
206
+ errors.push(`systemctl --user enable --now: ${msg}`);
207
207
  emit(
208
208
  "warn",
209
- `Could not enable user unit ${unitName} (${msg.trim()}). If the gateway was installed, try: systemctl --user enable ${unitName}`,
209
+ `Could not enable/start user unit ${unitName} (${msg.trim()}). If the gateway was installed, try: systemctl --user enable --now ${unitName}`,
210
210
  );
211
211
  }
212
212
 
213
+ let unitActive = false;
214
+ try {
215
+ await runSpawn("systemctl", ["--user", "is-active", unitName]);
216
+ unitActive = true;
217
+ emit("info", `${unitName} is active.`);
218
+ } catch {
219
+ // is-active exits non-zero when inactive; not a hard error
220
+ emit("info", `${unitName} is not yet active (may need: openclaw gateway restart).`);
221
+ }
222
+
213
223
  return {
214
224
  skipped: false,
215
225
  linger: lingerOk,
216
226
  unitName,
217
227
  unitEnabled,
228
+ unitActive,
218
229
  errors: errors.length ? errors : undefined,
219
230
  };
220
231
  }
@@ -6,7 +6,7 @@ import { dirname, join } from "path";
6
6
  import { fileURLToPath, pathToFileURL } from "url";
7
7
  import { homedir } from "os";
8
8
  import { randomUUID, createPrivateKey, sign as cryptoSign } from "crypto";
9
- import { execFile, execFileSync, execSync } from "child_process";
9
+ import { execFile, execFileSync, execSync, spawnSync } from "child_process";
10
10
  import { promisify } from "util";
11
11
  import { createServer } from "http";
12
12
  import { resolvePluginPackageRoot } from "./resolve-plugin-root.mjs";
@@ -65,6 +65,9 @@ try {
65
65
  const VERSION = CLI_VERSION || PLUGIN_VERSION;
66
66
  const PLUGIN_ID = "solana-trader";
67
67
  const LEGACY_PLUGIN_IDS = ["traderclaw-v1", "solana-traderclaw-v1", "solana-traderclaw"];
68
+ const KAYBA_TRACING_PLUGIN_ID = "kayba-tracing";
69
+ const KAYBA_TRACING_NPM_PACKAGE = "@kayba_ai/openclaw-tracing";
70
+ const KAYBA_TRACING_FOLDER_DEFAULT = "traderclaw-agent";
68
71
  const CONFIG_DIR = join(homedir(), ".openclaw");
69
72
  const CONFIG_FILE = join(CONFIG_DIR, "openclaw.json");
70
73
  const WALLET_PRIVATE_KEY_ENV = "TRADERCLAW_WALLET_PRIVATE_KEY";
@@ -171,6 +174,53 @@ function extractJson(raw) {
171
174
  /** Env vars passed to every openclaw CLI invocation to suppress colour output. */
172
175
  const NO_COLOR_ENV = { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" };
173
176
 
177
+ function delay(ms) {
178
+ return new Promise((resolve) => setTimeout(resolve, ms));
179
+ }
180
+
181
+ /** True when OpenClaw / ClawHub reports HTTP 429 or rate limiting (retry installs instead of failing). */
182
+ function isOpenClawClawHubRateLimit(combinedOutput) {
183
+ const t = stripAnsi(String(combinedOutput || "")).toLowerCase();
184
+ return /\b429\b/.test(t) || t.includes("rate limit") || t.includes("too many requests");
185
+ }
186
+
187
+ /**
188
+ * Runs `openclaw plugins install … --force` with backoff retries when ClawHub returns 429.
189
+ */
190
+ async function runOpenclawPluginsInstallWithRetry(installArgs, installSpec) {
191
+ const maxAttempts = 5;
192
+ const baseDelayMs = 12000;
193
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
194
+ try {
195
+ const r = await execFileAsync("openclaw", installArgs, {
196
+ maxBuffer: 15 * 1024 * 1024,
197
+ env: NO_COLOR_ENV,
198
+ });
199
+ if (r.stdout) process.stdout.write(r.stdout);
200
+ if (r.stderr) process.stderr.write(r.stderr);
201
+ return;
202
+ } catch (e) {
203
+ const stdout =
204
+ typeof e.stdout === "string" ? e.stdout : Buffer.isBuffer(e.stdout) ? e.stdout.toString("utf-8") : "";
205
+ const stderr =
206
+ typeof e.stderr === "string" ? e.stderr : Buffer.isBuffer(e.stderr) ? e.stderr.toString("utf-8") : "";
207
+ const combined = `${stdout}${stderr}${e.message || ""}`;
208
+ if (stdout) process.stdout.write(stdout);
209
+ if (stderr) process.stderr.write(stderr);
210
+ const retryable = isOpenClawClawHubRateLimit(combined);
211
+ if (attempt < maxAttempts && retryable) {
212
+ const waitMs = Math.min(baseDelayMs * 2 ** (attempt - 1), 180000);
213
+ printWarn(
214
+ ` ClawHub rate limited while installing ${installSpec} (attempt ${attempt}/${maxAttempts}). Waiting ${Math.round(waitMs / 1000)}s, then retrying…`,
215
+ );
216
+ await delay(waitMs);
217
+ continue;
218
+ }
219
+ throw e;
220
+ }
221
+ }
222
+ }
223
+
174
224
  function getCommandOutput(cmd) {
175
225
  try {
176
226
  return execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], shell: true, maxBuffer: 50 * 1024 * 1024, env: NO_COLOR_ENV }).trim();
@@ -422,6 +472,48 @@ function setPluginConfig(config, pluginConfig) {
422
472
  };
423
473
  }
424
474
 
475
+ function setKaybaTracingPluginConfig(config, kaybaApiKey, folder) {
476
+ normalizePluginConfigShape(config);
477
+ if (!config.plugins) config.plugins = {};
478
+ if (!config.plugins.entries) config.plugins.entries = {};
479
+ if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
480
+ if (!config.plugins.allow.includes(KAYBA_TRACING_PLUGIN_ID)) {
481
+ config.plugins.allow.push(KAYBA_TRACING_PLUGIN_ID);
482
+ }
483
+ const prev = config.plugins.entries[KAYBA_TRACING_PLUGIN_ID];
484
+ const prevConfig = prev && typeof prev.config === "object" && prev.config !== null ? prev.config : {};
485
+ config.plugins.entries[KAYBA_TRACING_PLUGIN_ID] = {
486
+ enabled: true,
487
+ hooks: { allowConversationAccess: true },
488
+ config: {
489
+ ...prevConfig,
490
+ apiKey: kaybaApiKey,
491
+ folder: prevConfig.folder || folder || KAYBA_TRACING_FOLDER_DEFAULT,
492
+ },
493
+ };
494
+ }
495
+
496
+ async function installAndEnableKaybaTracingPlugin() {
497
+ // Best-effort: openclaw plugins install <pkg> + enable <id>. Returns null on success, error message on failure.
498
+ try {
499
+ await execFileAsync("openclaw", ["plugins", "install", KAYBA_TRACING_NPM_PACKAGE], { env: NO_COLOR_ENV });
500
+ } catch (err) {
501
+ const text = `${err?.message || ""}\n${err?.stderr || ""}`.toLowerCase();
502
+ if (!text.includes("already exists") && !text.includes("already installed")) {
503
+ return err instanceof Error ? err.message : String(err);
504
+ }
505
+ }
506
+ try {
507
+ await execFileAsync("openclaw", ["plugins", "enable", KAYBA_TRACING_PLUGIN_ID], { env: NO_COLOR_ENV });
508
+ } catch (err) {
509
+ const text = `${err?.message || ""}\n${err?.stderr || ""}`.toLowerCase();
510
+ if (!text.includes("already enabled")) {
511
+ return err instanceof Error ? err.message : String(err);
512
+ }
513
+ }
514
+ return null;
515
+ }
516
+
425
517
  function getGatewayConfig(config) {
426
518
  if (!config || typeof config !== "object") return {};
427
519
  if (!config.gateway || typeof config.gateway !== "object") return {};
@@ -782,6 +874,7 @@ async function cmdSetup(args) {
782
874
  let forwardTelegramRecipientArg = "";
783
875
  let referralCodeArg = "";
784
876
  let referralInvalidRetries = 0;
877
+ let kaybaApiKeyArg = "";
785
878
 
786
879
  for (let i = 0; i < args.length; i++) {
787
880
  if ((args[i] === "--api-key" || args[i] === "-k") && args[i + 1]) {
@@ -831,6 +924,9 @@ async function cmdSetup(args) {
831
924
  if ((args[i] === "--referral-code" || args[i] === "-r") && args[i + 1]) {
832
925
  referralCodeArg = args[++i];
833
926
  }
927
+ if ((args[i] === "--kayba-key" || args[i] === "--kayba-api-key") && args[i + 1]) {
928
+ kaybaApiKeyArg = args[++i];
929
+ }
834
930
  }
835
931
  const runtimeWalletPrivateKey = getRuntimeWalletPrivateKey(walletPrivateKey);
836
932
 
@@ -1169,12 +1265,30 @@ async function cmdSetup(args) {
1169
1265
  }
1170
1266
  }
1171
1267
 
1268
+ let kaybaApiKey = (kaybaApiKeyArg || process.env.KAYBA_API_KEY || prevPlugin?.kaybaApiKey || "").trim();
1269
+ if (!kaybaApiKey) {
1270
+ print("\nKayba tracing (optional)...\n");
1271
+ printInfo(" Capture every agent turn — full LLM prompts, tool calls, replies — for observability.");
1272
+ printInfo(" Get a key at https://use.kayba.ai/settings/api-keys, or use one provided by your operator.");
1273
+ kaybaApiKey = (await prompt("Kayba API key (kayba_ak_..., Enter to skip)", "")).trim();
1274
+ } else {
1275
+ printInfo(`\n Reusing Kayba API key: ${maskKey(kaybaApiKey)}`);
1276
+ }
1277
+ if (kaybaApiKey) {
1278
+ pluginConfig.kaybaApiKey = kaybaApiKey;
1279
+ if (!pluginConfig.kaybaFolder) pluginConfig.kaybaFolder = "traderclaw";
1280
+ }
1281
+
1172
1282
  print("\nWriting configuration...\n");
1173
1283
 
1174
1284
  const existingConfig = readConfig();
1175
1285
  removeLegacyWalletPrivateKey(pluginConfig);
1176
1286
  setPluginConfig(existingConfig, pluginConfig);
1177
1287
 
1288
+ if (kaybaApiKey) {
1289
+ setKaybaTracingPluginConfig(existingConfig, kaybaApiKey, KAYBA_TRACING_FOLDER_DEFAULT);
1290
+ }
1291
+
1178
1292
  if (!existingConfig.agents || typeof existingConfig.agents !== "object") {
1179
1293
  existingConfig.agents = {};
1180
1294
  }
@@ -1204,6 +1318,17 @@ async function cmdSetup(args) {
1204
1318
 
1205
1319
  printSuccess(` Config written to ${CONFIG_FILE}`);
1206
1320
 
1321
+ if (kaybaApiKey) {
1322
+ print(`\nInstalling ${KAYBA_TRACING_PLUGIN_ID} plugin (one-time)...\n`);
1323
+ const installErr = await installAndEnableKaybaTracingPlugin();
1324
+ if (installErr) {
1325
+ printWarn(` Plugin install failed: ${installErr}`);
1326
+ printWarn(` Finish manually: openclaw plugins install ${KAYBA_TRACING_NPM_PACKAGE} && openclaw plugins enable ${KAYBA_TRACING_PLUGIN_ID}`);
1327
+ } else {
1328
+ printSuccess(` ${KAYBA_TRACING_PLUGIN_ID} installed and enabled (folder: ${KAYBA_TRACING_FOLDER_DEFAULT})`);
1329
+ }
1330
+ }
1331
+
1207
1332
  if (!skipGatewayRegistration) {
1208
1333
  print("\nGateway forwarding setup (required for event-driven wakeups)...\n");
1209
1334
 
@@ -4195,15 +4320,17 @@ Setup options:
4195
4320
  --gateway-token, -t Gateway bearer token (defaults to API key)
4196
4321
  --telegram-recipient Telegram @username or chat id (aliases: --forward-telegram-chat-id, --telegram-chat-id)
4197
4322
  --referral-code, -r Optional referral code for new signups (extra trial time when valid)
4323
+ --kayba-key Kayba API key (kayba_ak_...) — captures every agent turn to your Kayba dashboard.
4324
+ Aliases: --kayba-api-key. Env fallback: KAYBA_API_KEY. Skip with empty value.
4198
4325
  --skip-gateway-registration Skip gateway URL registration with orchestrator
4199
4326
  --show-api-key Extra hint after signup (full key is always shown once; confirm with API_KEY_STORED)
4200
4327
  --show-wallet-private-key Reveal full wallet private key in setup output
4201
4328
  --signup Force signup flow (create new account)
4202
4329
  --write-gateway-env Write TRADERCLAW_WALLET_PRIVATE_KEY to a systemd EnvironmentFile for the user gateway (Linux)
4203
- --no-ensure-gateway-persistent Skip automatic Linux loginctl linger + user unit enable after setup
4330
+ --no-ensure-gateway-persistent Skip automatic Linux loginctl linger + systemctl --user enable --now after setup
4204
4331
 
4205
4332
  Gateway subcommands:
4206
- gateway ensure-persistent Linux: enable loginctl linger and systemd --user unit for OpenClaw gateway
4333
+ gateway ensure-persistent Linux: enable loginctl linger and systemctl --user enable --now for OpenClaw gateway
4207
4334
 
4208
4335
  Login options:
4209
4336
  --wallet-private-key <k> Base58 key for wallet proof (runtime only, never saved). Optional in a
@@ -4240,6 +4367,8 @@ Examples:
4240
4367
  traderclaw setup --api-key oc_xxx --url https://api.traderclaw.ai
4241
4368
  traderclaw setup --gateway-base-url https://gateway.myhost.ts.net
4242
4369
  traderclaw setup --telegram-recipient @myusername
4370
+ traderclaw setup --kayba-key kayba_ak_xxx # one-shot Kayba tracing setup (installs kayba-tracing plugin)
4371
+ KAYBA_API_KEY=kayba_ak_xxx traderclaw setup # same via env var
4243
4372
  traderclaw login
4244
4373
  traderclaw login --force-reauth --wallet-private-key <base58_key>
4245
4374
  traderclaw logout
@@ -4361,9 +4490,27 @@ async function cmdUpdate(args) {
4361
4490
  `\n Forcing full integration: openclaw plugins install ${installSpec} --force\n` +
4362
4491
  " (extension on disk + plugins.installs / integrity.)\n",
4363
4492
  );
4364
- execFileSync("openclaw", installArgs, { stdio: "inherit" });
4493
+ await runOpenclawPluginsInstallWithRetry(installArgs, installSpec);
4365
4494
  printSuccess(`\n Forced install complete: ${installSpec}.`);
4366
- } catch {
4495
+ } catch (e) {
4496
+ const out =
4497
+ typeof e.stdout === "string"
4498
+ ? e.stdout
4499
+ : Buffer.isBuffer(e.stdout)
4500
+ ? e.stdout.toString("utf-8")
4501
+ : "";
4502
+ const err =
4503
+ typeof e.stderr === "string"
4504
+ ? e.stderr
4505
+ : Buffer.isBuffer(e.stderr)
4506
+ ? e.stderr.toString("utf-8")
4507
+ : "";
4508
+ const blob = `${out}${err}${e.message || ""}`;
4509
+ if (isOpenClawClawHubRateLimit(blob)) {
4510
+ printWarn(
4511
+ ` ClawHub rate limit persisted after retries — plugin files may still match the version from "plugins update" until this step succeeds.`,
4512
+ );
4513
+ }
4367
4514
  printError(`"openclaw plugins install ${installSpec} --force" failed. Try manually:`);
4368
4515
  print(` openclaw plugins install ${installSpec} --force`);
4369
4516
  process.exit(1);
@@ -4388,17 +4535,46 @@ async function cmdUpdate(args) {
4388
4535
  }
4389
4536
 
4390
4537
  if (dryRun) {
4391
- print(`\n [dry-run] Would run: openclaw gateway restart\n`);
4538
+ print(`\n [dry-run] Would run: systemctl --user enable --now + restart (Linux) or openclaw gateway restart\n`);
4392
4539
  } else {
4393
- if (!commandExists("openclaw")) {
4394
- printWarn("\n Skipping gateway restart: openclaw not in PATH. After fixing PATH: openclaw gateway restart");
4395
- } else {
4540
+ const { isLinuxGatewayPersistenceEligible, ensureLinuxGatewayPersistence, resolveGatewayUnitNameFromStatusJson, readOpenclawGatewayStatusJson } = await import("./gateway-persistence-linux.mjs");
4541
+ if (isLinuxGatewayPersistenceEligible()) {
4542
+ print("\n Gateway persistence (Linux)...\n");
4543
+ let unitName = "openclaw-gateway.service";
4544
+ try {
4545
+ const statusJson = readOpenclawGatewayStatusJson();
4546
+ unitName = resolveGatewayUnitNameFromStatusJson(statusJson);
4547
+ await ensureLinuxGatewayPersistence({
4548
+ emitLog: (level, text) => {
4549
+ if (level === "warn") printWarn(` ${text}`);
4550
+ else printInfo(` ${text}`);
4551
+ },
4552
+ });
4553
+ } catch (err) {
4554
+ printWarn(` Gateway persistence (optional): ${err.message || err}`);
4555
+ }
4396
4556
  print("\n Restarting gateway...\n");
4397
4557
  try {
4398
- execFileSync("openclaw", ["gateway", "restart"], { stdio: "inherit" });
4399
- printSuccess(" Gateway restarted.");
4400
- } catch {
4401
- printWarn(" Gateway restart returned non-zero. Try manually: openclaw gateway restart");
4558
+ const r = spawnSync("systemctl", ["--user", "restart", unitName], { stdio: "inherit" });
4559
+ if (r.status === 0) {
4560
+ printSuccess(` Gateway restarted (systemctl --user restart ${unitName}).`);
4561
+ } else {
4562
+ printWarn(` systemctl --user restart returned ${r.status}. Try: systemctl --user restart ${unitName}`);
4563
+ }
4564
+ } catch (err) {
4565
+ printWarn(` systemctl --user restart: ${err.message || err}. Try manually: systemctl --user restart ${unitName}`);
4566
+ }
4567
+ } else {
4568
+ if (!commandExists("openclaw")) {
4569
+ printWarn("\n Skipping gateway restart: openclaw not in PATH. After fixing PATH: openclaw gateway restart");
4570
+ } else {
4571
+ print("\n Restarting gateway...\n");
4572
+ try {
4573
+ execFileSync("openclaw", ["gateway", "restart"], { stdio: "inherit" });
4574
+ printSuccess(" Gateway restarted.");
4575
+ } catch {
4576
+ printWarn(" Gateway restart returned non-zero. Try manually: openclaw gateway restart");
4577
+ }
4402
4578
  }
4403
4579
  }
4404
4580
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "traderclaw-cli",
3
- "version": "1.0.101",
3
+ "version": "1.0.103",
4
4
  "description": "Global TraderClaw CLI (install --wizard, setup, precheck). Installs solana-traderclaw as a dependency for OpenClaw plugin files.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "node": ">=22"
18
18
  },
19
19
  "dependencies": {
20
- "solana-traderclaw": "^1.0.101"
20
+ "solana-traderclaw": "^1.0.103"
21
21
  },
22
22
  "keywords": [
23
23
  "traderclaw",