traderclaw-cli 1.0.127 → 1.0.129

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.
@@ -369,6 +369,21 @@ function canUseSudoWithoutPrompt() {
369
369
  }
370
370
  }
371
371
 
372
+ /**
373
+ * NodeSource/deb npm defaults to a root-owned global tree (/usr/lib/node_modules). Non-root
374
+ * `npm install -g` must target a user prefix; `NPM_CONFIG_PREFIX` is applied to every npm child.
375
+ */
376
+ function ensureNpmUserGlobalPrefixForNonRoot() {
377
+ if (typeof process.getuid !== "function" || process.getuid() === 0) return;
378
+ const prefix = join(homedir(), ".npm-global");
379
+ try {
380
+ mkdirSync(prefix, { recursive: true });
381
+ } catch {
382
+ /* ignore */
383
+ }
384
+ process.env.NPM_CONFIG_PREFIX = prefix;
385
+ }
386
+
372
387
  /** User systemd (openclaw gateway) expects XDG_RUNTIME_DIR under /run/user/<uid> once linger is on. */
373
388
  function primeLinuxUserRuntimeEnv() {
374
389
  if (process.platform !== "linux" || typeof process.getuid !== "function") return;
@@ -599,6 +614,7 @@ function getGlobalOpenClawPackageDir() {
599
614
  */
600
615
  /** Runs `npm install` in the global `openclaw` package directory (fixes missing `grammy` etc.). */
601
616
  export async function ensureOpenClawGlobalPackageDependencies() {
617
+ ensureNpmUserGlobalPrefixForNonRoot();
602
618
  const dir = getGlobalOpenClawPackageDir();
603
619
  if (!dir) {
604
620
  return { skipped: true, reason: "global_openclaw_dir_not_found" };
@@ -2770,6 +2786,7 @@ export class InstallerStepEngine {
2770
2786
  this.state.status = "running";
2771
2787
  this.state.startedAt = nowIso();
2772
2788
  try {
2789
+ ensureNpmUserGlobalPrefixForNonRoot();
2773
2790
  if (!this.options.skipPreflight) {
2774
2791
  await this.runStep("preflight", "Checking prerequisites", async () => {
2775
2792
  if (!commandExists("node") || !commandExists("npm")) throw new Error("node and npm are required");
@@ -3190,6 +3207,8 @@ export async function runTraderClawDeepUpdate(options = {}) {
3190
3207
  onLog({ at: nowIso(), stepId, level, text: clean, urls: [] });
3191
3208
  };
3192
3209
 
3210
+ ensureNpmUserGlobalPrefixForNonRoot();
3211
+
3193
3212
  async function runDeepStep(stepId, title, handler) {
3194
3213
  onStep({ at: nowIso(), stepId, status: "in_progress", detail: title });
3195
3214
  try {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createInterface } from "readline";
4
- import { readFileSync, writeFileSync, mkdirSync, appendFileSync, existsSync } from "fs";
4
+ import { readFileSync, writeFileSync, mkdirSync, appendFileSync, existsSync, renameSync } from "fs";
5
5
  import { dirname, join } from "path";
6
6
  import { fileURLToPath, pathToFileURL } from "url";
7
7
  import { homedir } from "os";
@@ -74,6 +74,86 @@ const CONFIG_DIR = join(homedir(), ".openclaw");
74
74
  const CONFIG_FILE = join(CONFIG_DIR, "openclaw.json");
75
75
  const WALLET_PRIVATE_KEY_ENV = "TRADERCLAW_WALLET_PRIVATE_KEY";
76
76
 
77
+ /** Session sidecar directory: explicit plugins.entries.solana-trader.config.dataDir or ~/.traderclaw-v1-data */
78
+ function resolveTraderClawDataDir(pluginConfig) {
79
+ const raw =
80
+ pluginConfig && typeof pluginConfig.dataDir === "string" ? pluginConfig.dataDir.trim() : "";
81
+ if (raw.length > 0) {
82
+ return raw.replace(/^~(?=$|[/\\])/, homedir());
83
+ }
84
+ return join(homedir(), ".traderclaw-v1-data");
85
+ }
86
+
87
+ function ensureExplicitPluginDataDir(pluginConfig) {
88
+ if (!pluginConfig || typeof pluginConfig !== "object") return;
89
+ const raw = typeof pluginConfig.dataDir === "string" ? pluginConfig.dataDir.trim() : "";
90
+ if (raw.length > 0) {
91
+ pluginConfig.dataDir = raw.replace(/^~(?=$|[/\\])/, homedir());
92
+ } else {
93
+ pluginConfig.dataDir = join(homedir(), ".traderclaw-v1-data");
94
+ }
95
+ }
96
+
97
+ function persistSessionSidecar(pluginConfig, tokens) {
98
+ if (!tokens || typeof tokens.refreshToken !== "string" || tokens.refreshToken.length === 0) {
99
+ return;
100
+ }
101
+ const dataDir = resolveTraderClawDataDir(pluginConfig);
102
+ const sessionTokensPath = join(dataDir, "session-tokens.json");
103
+ mkdirSync(dataDir, { recursive: true });
104
+ let existingSidecar = {};
105
+ try {
106
+ if (existsSync(sessionTokensPath)) {
107
+ existingSidecar = JSON.parse(readFileSync(sessionTokensPath, "utf-8")) || {};
108
+ }
109
+ } catch {
110
+ /* ignore */
111
+ }
112
+ const ttlSec =
113
+ typeof tokens.accessTokenTtlSeconds === "number" && Number.isFinite(tokens.accessTokenTtlSeconds)
114
+ ? tokens.accessTokenTtlSeconds
115
+ : 900;
116
+ const accessTokenExpiresAt = Date.now() + ttlSec * 1000;
117
+ const walletEntry =
118
+ pluginConfig.walletPublicKey && String(pluginConfig.walletPublicKey).trim()
119
+ ? { walletPublicKey: String(pluginConfig.walletPublicKey).trim() }
120
+ : {};
121
+ const recoveryEntry =
122
+ typeof pluginConfig.recoverySecret === "string" && pluginConfig.recoverySecret.trim().length > 0
123
+ ? { recoverySecret: pluginConfig.recoverySecret.trim() }
124
+ : {};
125
+ const payload = {
126
+ ...existingSidecar,
127
+ refreshToken: tokens.refreshToken,
128
+ accessToken: tokens.accessToken,
129
+ accessTokenExpiresAt,
130
+ ...walletEntry,
131
+ ...recoveryEntry,
132
+ };
133
+ const tmp = `${sessionTokensPath}.${process.pid}.${Date.now()}.tmp`;
134
+ writeFileSync(tmp, JSON.stringify(payload, null, 2) + "\n", "utf-8");
135
+ renameSync(tmp, sessionTokensPath);
136
+ }
137
+
138
+ function clearSessionSidecarSessionFields(pluginConfig) {
139
+ const dataDir = resolveTraderClawDataDir(pluginConfig);
140
+ const sessionTokensPath = join(dataDir, "session-tokens.json");
141
+ if (!existsSync(sessionTokensPath)) return;
142
+ try {
143
+ const raw = JSON.parse(readFileSync(sessionTokensPath, "utf-8"));
144
+ if (!raw || typeof raw !== "object") return;
145
+ const next = { ...raw };
146
+ delete next.refreshToken;
147
+ delete next.accessToken;
148
+ delete next.accessTokenExpiresAt;
149
+ const tmp = `${sessionTokensPath}.${process.pid}.${Date.now()}.tmp`;
150
+ writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", "utf-8");
151
+ renameSync(tmp, sessionTokensPath);
152
+ } catch {
153
+ /* ignore */
154
+ }
155
+ }
156
+
77
157
  /** Linked from CLI errors and setup — keep in sync with SKILL / README. */
78
158
  const TRADERCLAW_SESSION_TROUBLESHOOTING_URL =
79
159
  "https://docs.traderclaw.ai/docs/installation#troubleshooting-session-expired-auth-errors-or-the-agent-logged-out";
@@ -1290,6 +1370,8 @@ async function cmdSetup(args) {
1290
1370
  if (!pluginConfig.kaybaFolder) pluginConfig.kaybaFolder = "traderclaw";
1291
1371
  }
1292
1372
 
1373
+ ensureExplicitPluginDataDir(pluginConfig);
1374
+
1293
1375
  print("\nWriting configuration...\n");
1294
1376
 
1295
1377
  const existingConfig = readConfig();
@@ -1327,6 +1409,8 @@ async function cmdSetup(args) {
1327
1409
 
1328
1410
  writeConfig(existingConfig);
1329
1411
 
1412
+ persistSessionSidecar(pluginConfig, sessionTokens);
1413
+
1330
1414
  printSuccess(` Config written to ${CONFIG_FILE}`);
1331
1415
 
1332
1416
  if (kaybaApiKey) {
@@ -1657,7 +1741,9 @@ async function cmdLogin(args) {
1657
1741
  const removedLegacyKey = removeLegacyWalletPrivateKey(pluginConfig);
1658
1742
 
1659
1743
  try {
1660
- await establishSession(orchestratorUrl, pluginConfig, walletPrivateKeyArg);
1744
+ const tokens = await establishSession(orchestratorUrl, pluginConfig, walletPrivateKeyArg);
1745
+ ensureExplicitPluginDataDir(pluginConfig);
1746
+ persistSessionSidecar(pluginConfig, tokens);
1661
1747
  setPluginConfig(config, pluginConfig);
1662
1748
  writeConfig(config);
1663
1749
  printSuccess("\n Session established and saved.");
@@ -1706,6 +1792,7 @@ async function cmdLogout() {
1706
1792
  const removedLegacyKey = removeLegacyWalletPrivateKey(pluginConfig);
1707
1793
  setPluginConfig(config, pluginConfig);
1708
1794
  writeConfig(config);
1795
+ clearSessionSidecarSessionFields(pluginConfig);
1709
1796
 
1710
1797
  printSuccess(" Local session cleared.");
1711
1798
  if (removedLegacyKey) {
@@ -4481,7 +4568,7 @@ async function cmdTestSession(args) {
4481
4568
  process.exit(1);
4482
4569
  }
4483
4570
 
4484
- const dataDir = pluginConfig.dataDir || join(process.cwd(), ".traderclaw-v1-data");
4571
+ const dataDir = resolveTraderClawDataDir(pluginConfig);
4485
4572
  const sessionTokensPath = join(dataDir, "session-tokens.json");
4486
4573
 
4487
4574
  let sidecar = null;
@@ -4666,7 +4753,6 @@ async function cmdTestSession(args) {
4666
4753
  };
4667
4754
  const tmp = `${sessionTokensPath}.${process.pid}.${Date.now()}.tmp`;
4668
4755
  writeFileSync(tmp, JSON.stringify(payload, null, 2) + "\n", "utf-8");
4669
- const { renameSync } = await import("fs");
4670
4756
  renameSync(tmp, sessionTokensPath);
4671
4757
  printSuccess(` OK — written to ${sessionTokensPath}`);
4672
4758
  results.push({ test: "persist_sidecar", status: "ok" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "traderclaw-cli",
3
- "version": "1.0.127",
3
+ "version": "1.0.129",
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.127"
20
+ "solana-traderclaw": "^1.0.129"
21
21
  },
22
22
  "keywords": [
23
23
  "traderclaw",