pandora-cli-skills 1.1.21 → 1.1.23

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.
@@ -111,8 +111,13 @@ function saveState(filePath, state) {
111
111
  const lockFd = acquireLock(lockPath);
112
112
  try {
113
113
  const tmp = `${resolved}.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString('hex')}.tmp`;
114
- fs.writeFileSync(tmp, JSON.stringify(state, null, 2));
114
+ fs.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 0o600 });
115
115
  fs.renameSync(tmp, resolved);
116
+ try {
117
+ fs.chmodSync(resolved, 0o600);
118
+ } catch {
119
+ // best-effort permission hardening
120
+ }
116
121
  } finally {
117
122
  try {
118
123
  fs.closeSync(lockFd);
@@ -8,6 +8,13 @@ const { expandHome } = require('./mirror_state_store.cjs');
8
8
  const MIRROR_DAEMON_SCHEMA_VERSION = '1.0.0';
9
9
  const STOP_TIMEOUT_MS = 5_000;
10
10
  const STOP_POLL_MS = 100;
11
+ const SENSITIVE_CLI_FLAGS = new Set([
12
+ '--private-key',
13
+ '--webhook-secret',
14
+ '--telegram-bot-token',
15
+ '--discord-webhook-url',
16
+ '--webhook-template',
17
+ ]);
11
18
 
12
19
  function createServiceError(code, message, details = undefined) {
13
20
  const err = new Error(message);
@@ -30,6 +37,34 @@ function resolvePath(filePath) {
30
37
  return path.resolve(expandHome(String(filePath || '').trim()));
31
38
  }
32
39
 
40
+ function sanitizeCliArgs(args = []) {
41
+ const sanitized = [];
42
+ for (let i = 0; i < args.length; i += 1) {
43
+ const token = String(args[i] || '');
44
+ const eqIndex = token.indexOf('=');
45
+ if (eqIndex > 0) {
46
+ const flag = token.slice(0, eqIndex);
47
+ if (SENSITIVE_CLI_FLAGS.has(flag)) {
48
+ sanitized.push(`${flag}=[redacted]`);
49
+ continue;
50
+ }
51
+ }
52
+
53
+ if (SENSITIVE_CLI_FLAGS.has(token)) {
54
+ sanitized.push(token);
55
+ const next = i + 1 < args.length ? String(args[i + 1] || '') : '';
56
+ if (next && !next.startsWith('--')) {
57
+ sanitized.push('[redacted]');
58
+ i += 1;
59
+ }
60
+ continue;
61
+ }
62
+
63
+ sanitized.push(token);
64
+ }
65
+ return sanitized;
66
+ }
67
+
33
68
  function defaultPidFile(strategyHash) {
34
69
  const hash = normalizeStrategyHash(strategyHash);
35
70
  return path.join(os.homedir(), '.pandora', 'mirror', 'daemon', `${hash}.json`);
@@ -44,8 +79,13 @@ function writeJsonFile(filePath, payload) {
44
79
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
45
80
  const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString('hex')}.tmp`;
46
81
  const serialized = JSON.stringify(payload, null, 2);
47
- fs.writeFileSync(tmpPath, serialized);
82
+ fs.writeFileSync(tmpPath, serialized, { mode: 0o600 });
48
83
  fs.renameSync(tmpPath, filePath);
84
+ try {
85
+ fs.chmodSync(filePath, 0o600);
86
+ } catch {
87
+ // best-effort permission hardening
88
+ }
49
89
  }
50
90
 
51
91
  function readJsonFile(filePath) {
@@ -131,6 +171,7 @@ function startDaemon(options = {}) {
131
171
  });
132
172
  child.unref();
133
173
  fs.closeSync(logFd);
174
+ const sanitizedCliArgs = sanitizeCliArgs(cliArgs);
134
175
 
135
176
  const metadata = {
136
177
  schemaVersion: MIRROR_DAEMON_SCHEMA_VERSION,
@@ -143,7 +184,7 @@ function startDaemon(options = {}) {
143
184
  pidFile,
144
185
  logFile,
145
186
  cliPath,
146
- cliArgs,
187
+ cliArgs: sanitizedCliArgs,
147
188
  stateFile: options.stateFile || null,
148
189
  killSwitchFile: options.killSwitchFile || null,
149
190
  mode: options.mode || 'run',
@@ -151,7 +192,7 @@ function startDaemon(options = {}) {
151
192
  pandoraMarketAddress: options.pandoraMarketAddress || null,
152
193
  polymarketMarketId: options.polymarketMarketId || null,
153
194
  polymarketSlug: options.polymarketSlug || null,
154
- launchCommand: [process.execPath, cliPath, ...cliArgs].join(' '),
195
+ launchCommand: [process.execPath, cliPath, ...sanitizedCliArgs].join(' '),
155
196
  };
156
197
  writeJsonFile(pidFile, metadata);
157
198
 
@@ -84,13 +84,18 @@ function saveState(filePath, state) {
84
84
  fs.mkdirSync(path.dirname(resolved), { recursive: true });
85
85
  const tmpPath = `${resolved}.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString('hex')}.tmp`;
86
86
  const serialized = JSON.stringify(state, null, 2);
87
- fs.writeFileSync(tmpPath, serialized);
87
+ fs.writeFileSync(tmpPath, serialized, { mode: 0o600 });
88
88
  try {
89
89
  fs.renameSync(tmpPath, resolved);
90
+ try {
91
+ fs.chmodSync(resolved, 0o600);
92
+ } catch {
93
+ // best-effort permission hardening
94
+ }
90
95
  } catch (err) {
91
96
  if (err && err.code === 'ENOENT') {
92
97
  fs.mkdirSync(path.dirname(resolved), { recursive: true });
93
- fs.writeFileSync(resolved, serialized);
98
+ fs.writeFileSync(resolved, serialized, { mode: 0o600 });
94
99
  if (fs.existsSync(tmpPath)) {
95
100
  try {
96
101
  fs.unlinkSync(tmpPath);
@@ -504,27 +504,37 @@ async function runMirrorSync(options, deps = {}) {
504
504
  if (options.executeLive) {
505
505
  const envCreds = readTradingCredsFromEnv();
506
506
  let hedgeResult;
507
- try {
508
- hedgeResult = await hedgeFn({
509
- host: options.polymarketHost,
510
- mockUrl: options.polymarketMockUrl,
511
- tokenId,
512
- side: hedgeSide,
513
- amountUsd: plannedHedgeUsdc,
514
- privateKey: options.privateKey || envCreds.privateKey,
515
- funder: options.funder || envCreds.funder,
516
- apiKey: envCreds.apiKey,
517
- apiSecret: envCreds.apiSecret,
518
- apiPassphrase: envCreds.apiPassphrase,
519
- });
520
- } catch (err) {
507
+ if (!options.privateKey && envCreds.privateKeyInvalid) {
521
508
  hedgeResult = {
522
509
  ok: false,
523
510
  error: {
524
- code: err && err.code ? String(err.code) : null,
525
- message: err && err.message ? String(err.message) : String(err),
511
+ code: 'INVALID_ENV',
512
+ message: 'POLYMARKET_PRIVATE_KEY must be a valid private key (0x + 64 hex chars).',
526
513
  },
527
514
  };
515
+ } else {
516
+ try {
517
+ hedgeResult = await hedgeFn({
518
+ host: options.polymarketHost,
519
+ mockUrl: options.polymarketMockUrl,
520
+ tokenId,
521
+ side: hedgeSide,
522
+ amountUsd: plannedHedgeUsdc,
523
+ privateKey: options.privateKey || envCreds.privateKey,
524
+ funder: options.funder || envCreds.funder,
525
+ apiKey: envCreds.apiKey,
526
+ apiSecret: envCreds.apiSecret,
527
+ apiPassphrase: envCreds.apiPassphrase,
528
+ });
529
+ } catch (err) {
530
+ hedgeResult = {
531
+ ok: false,
532
+ error: {
533
+ code: err && err.code ? String(err.code) : null,
534
+ message: err && err.message ? String(err.message) : String(err),
535
+ },
536
+ };
537
+ }
528
538
  }
529
539
  action.hedge = {
530
540
  tokenId,
@@ -35,6 +35,10 @@ function isConditionId(value) {
35
35
  return /^0x[a-fA-F0-9]{64}$/.test(String(value || '').trim());
36
36
  }
37
37
 
38
+ function isValidPrivateKey(value) {
39
+ return /^0x[a-fA-F0-9]{64}$/.test(String(value || '').trim());
40
+ }
41
+
38
42
  function toStringOrNull(value) {
39
43
  const normalized = String(value || '').trim();
40
44
  return normalized || null;
@@ -1032,8 +1036,11 @@ async function browsePolymarketMarkets(options = {}) {
1032
1036
  }
1033
1037
 
1034
1038
  function readTradingCredsFromEnv(env = process.env) {
1039
+ const rawPrivateKey = String(env.POLYMARKET_PRIVATE_KEY || '').trim();
1040
+ const privateKey = rawPrivateKey && isValidPrivateKey(rawPrivateKey) ? rawPrivateKey : null;
1035
1041
  const creds = {
1036
- privateKey: env.POLYMARKET_PRIVATE_KEY || null,
1042
+ privateKey,
1043
+ privateKeyInvalid: Boolean(rawPrivateKey && !privateKey),
1037
1044
  funder: env.POLYMARKET_FUNDER || null,
1038
1045
  apiKey: env.POLYMARKET_API_KEY || null,
1039
1046
  apiSecret: env.POLYMARKET_API_SECRET || null,
@@ -1281,6 +1288,9 @@ async function fetchPolymarketPositionSummary(options = {}) {
1281
1288
  }
1282
1289
 
1283
1290
  const envCreds = readTradingCredsFromEnv(options.env || process.env);
1291
+ if (!options.privateKey && envCreds.privateKeyInvalid) {
1292
+ diagnostics.push('POLYMARKET_PRIVATE_KEY is set but invalid (expected 0x + 64 hex chars).');
1293
+ }
1284
1294
  const privateKey = options.privateKey || envCreds.privateKey;
1285
1295
  const funder = options.funder || envCreds.funder;
1286
1296
  const apiKey = options.apiKey || envCreds.apiKey;
@@ -1413,6 +1423,8 @@ async function buildTradingClient(options = {}) {
1413
1423
  const signatureType = resolveSignatureType(options);
1414
1424
  const cacheKey = buildTradingCacheKey(host, chain, options);
1415
1425
  const allowCache = options.disableCache !== true;
1426
+ const timeoutMs = Number.isInteger(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : 12_000;
1427
+ const ClobCtor = options.clobClientClass || ClobClient;
1416
1428
 
1417
1429
  if (allowCache && tradingClientCache.has(cacheKey)) {
1418
1430
  return tradingClientCache.get(cacheKey);
@@ -1443,7 +1455,7 @@ async function buildTradingClient(options = {}) {
1443
1455
  if (allowCache && derivedCredsCache.has(cacheKey)) {
1444
1456
  creds = derivedCredsCache.get(cacheKey);
1445
1457
  } else {
1446
- const bootstrap = new ClobClient(
1458
+ const bootstrap = new ClobCtor(
1447
1459
  host,
1448
1460
  chain,
1449
1461
  signer,
@@ -1459,12 +1471,27 @@ async function buildTradingClient(options = {}) {
1459
1471
  if (typeof bootstrap.deriveApiKey === 'function') {
1460
1472
  try {
1461
1473
  // deriveApiKey expects nonce, not signature type; default to nonce 0.
1462
- creds = await bootstrap.deriveApiKey(0);
1463
- } catch {
1464
- creds = await bootstrap.deriveApiKey();
1474
+ creds = await callWithTimeout(
1475
+ () => bootstrap.deriveApiKey(0),
1476
+ timeoutMs,
1477
+ 'Polymarket deriveApiKey(0)',
1478
+ );
1479
+ } catch (err) {
1480
+ if (err && typeof err.message === 'string' && err.message.includes('timed out')) {
1481
+ throw err;
1482
+ }
1483
+ creds = await callWithTimeout(
1484
+ () => bootstrap.deriveApiKey(),
1485
+ timeoutMs,
1486
+ 'Polymarket deriveApiKey()',
1487
+ );
1465
1488
  }
1466
1489
  } else if (typeof bootstrap.createOrDeriveApiKey === 'function') {
1467
- creds = await bootstrap.createOrDeriveApiKey();
1490
+ creds = await callWithTimeout(
1491
+ () => bootstrap.createOrDeriveApiKey(),
1492
+ timeoutMs,
1493
+ 'Polymarket createOrDeriveApiKey()',
1494
+ );
1468
1495
  } else {
1469
1496
  throw new Error('CLOB client does not support API key derivation.');
1470
1497
  }
@@ -1474,7 +1501,7 @@ async function buildTradingClient(options = {}) {
1474
1501
  }
1475
1502
  }
1476
1503
 
1477
- const client = new ClobClient(
1504
+ const client = new ClobCtor(
1478
1505
  host,
1479
1506
  chain,
1480
1507
  signer,
@@ -1528,25 +1555,44 @@ async function placeHedgeOrder(options = {}) {
1528
1555
  const host = options.host || DEFAULT_POLYMARKET_HOST;
1529
1556
  const chain = options.chain || DEFAULT_POLYMARKET_CHAIN;
1530
1557
  const cacheKey = buildTradingCacheKey(host, chain, options);
1558
+ const timeoutMs = Number.isInteger(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : 12_000;
1531
1559
  const client = options.client || (await buildTradingClient(options));
1532
1560
  const side = resolveOrderSide(options.side || 'buy');
1533
1561
  try {
1534
- const tickSize = options.tickSize || (await client.getTickSize(tokenId));
1535
- const negRisk = typeof options.negRisk === 'boolean' ? options.negRisk : await client.getNegRisk(tokenId);
1562
+ const tickSize =
1563
+ options.tickSize ||
1564
+ (await callWithTimeout(
1565
+ () => client.getTickSize(tokenId),
1566
+ timeoutMs,
1567
+ `Polymarket getTickSize(${tokenId})`,
1568
+ ));
1569
+ const negRisk =
1570
+ typeof options.negRisk === 'boolean'
1571
+ ? options.negRisk
1572
+ : await callWithTimeout(
1573
+ () => client.getNegRisk(tokenId),
1574
+ timeoutMs,
1575
+ `Polymarket getNegRisk(${tokenId})`,
1576
+ );
1536
1577
 
1537
- const response = await client.createAndPostMarketOrder(
1538
- {
1539
- tokenID: tokenId,
1540
- amount: amountUsd,
1541
- side,
1542
- orderType: OrderType.FAK,
1543
- },
1544
- {
1545
- tickSize,
1546
- negRisk,
1547
- },
1548
- OrderType.FAK,
1549
- false,
1578
+ const response = await callWithTimeout(
1579
+ () =>
1580
+ client.createAndPostMarketOrder(
1581
+ {
1582
+ tokenID: tokenId,
1583
+ amount: amountUsd,
1584
+ side,
1585
+ orderType: OrderType.FAK,
1586
+ },
1587
+ {
1588
+ tickSize,
1589
+ negRisk,
1590
+ },
1591
+ OrderType.FAK,
1592
+ false,
1593
+ ),
1594
+ timeoutMs,
1595
+ `Polymarket createAndPostMarketOrder(${tokenId})`,
1550
1596
  );
1551
1597
  const ok = responseIndicatesSuccess(response);
1552
1598
  if (!ok && classifyAuthFailure(response)) {
package/cli/pandora.cjs CHANGED
@@ -572,7 +572,7 @@ function emitJson(payload) {
572
572
  }
573
573
 
574
574
  function emitJsonError(payload) {
575
- console.error(JSON.stringify(payload, null, 2));
575
+ console.log(JSON.stringify(payload, null, 2));
576
576
  }
577
577
 
578
578
  function toErrorEnvelope(error) {
@@ -861,11 +861,17 @@ function parseAddressFlag(value, flagName) {
861
861
  return value.toLowerCase();
862
862
  }
863
863
 
864
+ function redactSensitiveValue(value) {
865
+ const text = String(value || '').trim();
866
+ if (!text || text.length <= 10) return '[redacted]';
867
+ return `${text.slice(0, 6)}...${text.slice(-4)}`;
868
+ }
869
+
864
870
  function parsePrivateKeyFlag(value, flagName = '--private-key') {
865
871
  if (!isValidPrivateKey(value)) {
866
872
  throw new CliError(
867
873
  'INVALID_FLAG_VALUE',
868
- `${flagName} must be a valid private key (0x + 64 hex chars). Received: "${value}"`,
874
+ `${flagName} must be a valid private key (0x + 64 hex chars). Received: "${redactSensitiveValue(value)}"`,
869
875
  );
870
876
  }
871
877
  return value;
@@ -5018,7 +5024,14 @@ function hasPolymarketDoctorInputs() {
5018
5024
  }
5019
5025
 
5020
5026
  function validateEnvValues() {
5021
- const missing = REQUIRED_ENV_KEYS.filter((key) => !process.env[key] || !String(process.env[key]).trim());
5027
+ const missing = REQUIRED_ENV_KEYS.filter((key) => {
5028
+ if (key === 'PRIVATE_KEY') {
5029
+ const primary = String(process.env.PANDORA_PRIVATE_KEY || '').trim();
5030
+ const legacy = String(process.env.PRIVATE_KEY || '').trim();
5031
+ return !primary && !legacy;
5032
+ }
5033
+ return !process.env[key] || !String(process.env[key]).trim();
5034
+ });
5022
5035
  const missingSet = new Set(missing);
5023
5036
  const errors = [];
5024
5037
 
@@ -5038,9 +5051,11 @@ function validateEnvValues() {
5038
5051
  errors.push(`RPC_URL must be a valid http/https URL. Received: "${rpcUrl}"`);
5039
5052
  }
5040
5053
 
5041
- const privateKey = String(process.env.PRIVATE_KEY || '').trim();
5054
+ const privateKey = String(process.env.PANDORA_PRIVATE_KEY || process.env.PRIVATE_KEY || '').trim();
5042
5055
  if (!missingSet.has('PRIVATE_KEY') && !isValidPrivateKey(privateKey)) {
5043
- errors.push('PRIVATE_KEY must be a full 32-byte hex key (0x + 64 hex chars), not a placeholder.');
5056
+ errors.push(
5057
+ 'PANDORA_PRIVATE_KEY (preferred) or PRIVATE_KEY must be a full 32-byte hex key (0x + 64 hex chars), not a placeholder.',
5058
+ );
5044
5059
  }
5045
5060
 
5046
5061
  for (const key of ['ORACLE', 'FACTORY', 'USDC']) {
@@ -7240,9 +7255,12 @@ function resolveTradeRuntimeConfig(options) {
7240
7255
  throw new CliError('INVALID_FLAG_VALUE', `RPC URL must be a valid http/https URL. Received: "${rpcUrl}"`);
7241
7256
  }
7242
7257
 
7243
- const privateKey = options.privateKey || process.env.PRIVATE_KEY || process.env.DEPLOYER_PRIVATE_KEY;
7258
+ const privateKey = options.privateKey || process.env.PANDORA_PRIVATE_KEY || process.env.PRIVATE_KEY;
7244
7259
  if (!privateKey || !isValidPrivateKey(privateKey)) {
7245
- throw new CliError('INVALID_FLAG_VALUE', 'Missing or invalid private key. Set PRIVATE_KEY or pass --private-key.');
7260
+ throw new CliError(
7261
+ 'INVALID_FLAG_VALUE',
7262
+ 'Missing or invalid private key. Set PANDORA_PRIVATE_KEY (preferred) or PRIVATE_KEY, or pass --private-key.',
7263
+ );
7246
7264
  }
7247
7265
 
7248
7266
  const usdc = options.usdc || String(process.env.USDC || '').trim();
@@ -9909,6 +9927,12 @@ async function runPolymarketCommand(args, context) {
9909
9927
  }
9910
9928
 
9911
9929
  const envCreds = readTradingCredsFromEnv();
9930
+ if (!options.privateKey && envCreds.privateKeyInvalid) {
9931
+ throw new CliError(
9932
+ 'INVALID_FLAG_VALUE',
9933
+ 'POLYMARKET_PRIVATE_KEY must be a valid private key (0x + 64 hex chars).',
9934
+ );
9935
+ }
9912
9936
  let result;
9913
9937
  try {
9914
9938
  result = await placeHedgeOrder({
@@ -10530,7 +10554,7 @@ async function main() {
10530
10554
  outputMode = parsed.outputMode;
10531
10555
  args = parsed.args;
10532
10556
  } catch (err) {
10533
- emitFailure(outputMode, err);
10557
+ emitFailure('json', err);
10534
10558
  return;
10535
10559
  }
10536
10560
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pandora-cli-skills",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "description": "Pandora CLI & Skills",
5
5
  "main": "cli/pandora.cjs",
6
6
  "bin": {
@@ -1,6 +1,9 @@
1
1
  # Runtime variables for market launcher scripts
2
2
  CHAIN_ID=1
3
3
  RPC_URL=https://ethereum.publicnode.com
4
+ # Preferred key name for Pandora execution commands:
5
+ PANDORA_PRIVATE_KEY=0x...
6
+ # Backward-compatible legacy key name (still supported):
4
7
  PRIVATE_KEY=0x...
5
8
  ORACLE=0x259308E7d8557e4Ba192De1aB8Cf7e0E21896442
6
9
  FACTORY=0xaB120F1FD31FB1EC39893B75d80a3822b1Cd8d0c