pandora-cli-skills 1.1.36 → 1.1.38

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/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: pandora-cli-skills
3
3
  summary: Canonical skill and operator guide for Pandora CLI including mirror, polymarket, resolve, and LP flows.
4
- version: 1.1.36
4
+ version: 1.1.37
5
5
  ---
6
6
 
7
7
  # Pandora CLI & Skills
@@ -1,6 +1,11 @@
1
1
  const path = require('path');
2
2
  const { spawnSync } = require('child_process');
3
3
 
4
+ /**
5
+ * Normalize unknown error-ish values into a loggable message string.
6
+ * @param {*} value
7
+ * @returns {string}
8
+ */
4
9
  function coerceErrorMessage(value) {
5
10
  if (typeof value === 'string') return value;
6
11
  if (value && typeof value.message === 'string') return value.message;
@@ -11,6 +16,12 @@ function coerceErrorMessage(value) {
11
16
  }
12
17
  }
13
18
 
19
+ /**
20
+ * Parse a candidate JSON envelope from raw process output.
21
+ * Accepts only objects that include a boolean `ok` field.
22
+ * @param {string} text
23
+ * @returns {object|null}
24
+ */
14
25
  function parseEnvelopeCandidate(text) {
15
26
  const candidate = String(text || '').trim();
16
27
  if (!candidate) return null;
@@ -25,6 +36,12 @@ function parseEnvelopeCandidate(text) {
25
36
  return null;
26
37
  }
27
38
 
39
+ /**
40
+ * Extract the last syntactically valid top-level JSON object from text.
41
+ * Useful when diagnostics/noise precede the structured CLI envelope.
42
+ * @param {string} text
43
+ * @returns {object|null}
44
+ */
28
45
  function extractLastJsonObject(text) {
29
46
  const source = String(text || '');
30
47
  if (!source) return null;
@@ -70,6 +87,12 @@ function extractLastJsonObject(text) {
70
87
  return parseEnvelopeCandidate(last);
71
88
  }
72
89
 
90
+ /**
91
+ * Parse a CLI JSON envelope from stdout/stderr with robust fallbacks.
92
+ * @param {string} stdout
93
+ * @param {string} stderr
94
+ * @returns {object|null}
95
+ */
73
96
  function parseEnvelopeFromOutput(stdout, stderr) {
74
97
  const candidates = [stdout, stderr, `${stdout || ''}\n${stderr || ''}`];
75
98
  for (const candidate of candidates) {
@@ -81,10 +104,31 @@ function parseEnvelopeFromOutput(stdout, stderr) {
81
104
  return null;
82
105
  }
83
106
 
107
+ /**
108
+ * @typedef {object} ExecutorEnvelope
109
+ * @property {boolean} ok
110
+ * @property {string} [command]
111
+ * @property {object} [data]
112
+ * @property {{code: string, message: string, details?: object}} [error]
113
+ */
114
+
115
+ /**
116
+ * @typedef {object} ExecuteJsonCommandResult
117
+ * @property {boolean} ok
118
+ * @property {number} exitCode
119
+ * @property {string} stdout
120
+ * @property {string} stderr
121
+ * @property {ExecutorEnvelope} envelope
122
+ */
123
+
84
124
  /**
85
125
  * Build a child-process command executor for JSON-mode CLI invocations.
126
+ * The executor always prepends `--output json` and normalizes failures into an envelope shape.
86
127
  * @param {{cliPath?: string, defaultTimeoutMs?: number, env?: object}} [options]
87
- * @returns {{executeJsonCommand: (commandArgs: string[], runtime?: {timeoutMs?: number, env?: object}) => {ok: boolean, envelope: object, exitCode: number, stdout: string, stderr: string}}}
128
+ * @returns {{
129
+ * executeJsonCommand: (commandArgs: string[], runtime?: {timeoutMs?: number, env?: object}) => ExecuteJsonCommandResult,
130
+ * coerceErrorMessage: (value: *) => string
131
+ * }}
88
132
  */
89
133
  function createCommandExecutorService(options = {}) {
90
134
  const cliPath =
@@ -94,6 +138,12 @@ function createCommandExecutorService(options = {}) {
94
138
  const defaultTimeoutMs = Number.isFinite(options.defaultTimeoutMs) ? Math.max(1_000, Math.trunc(options.defaultTimeoutMs)) : 60_000;
95
139
  const baseEnv = options.env && typeof options.env === 'object' ? options.env : process.env;
96
140
 
141
+ /**
142
+ * Execute the CLI synchronously and parse the envelope from process output.
143
+ * @param {string[]} commandArgs
144
+ * @param {{timeoutMs?: number, env?: object}} [runtime]
145
+ * @returns {ExecuteJsonCommandResult}
146
+ */
97
147
  function executeJsonCommand(commandArgs, runtime = {}) {
98
148
  const timeoutMs = Number.isFinite(runtime.timeoutMs)
99
149
  ? Math.max(1_000, Math.trunc(runtime.timeoutMs))
@@ -39,6 +39,18 @@ function buildPolymarketPreflightCommand(cliName) {
39
39
  return `${cliName} polymarket preflight`;
40
40
  }
41
41
 
42
+ function buildMirrorDeployRetryCommand(cliName) {
43
+ return `${cliName} mirror deploy --dry-run --plan-file <plan-file>`;
44
+ }
45
+
46
+ function buildMirrorSyncRetryCommand(cliName) {
47
+ return `${cliName} mirror sync once --paper --pandora-market-address <address> --polymarket-market-id <id>`;
48
+ }
49
+
50
+ function buildMcpRestartCommand(cliName) {
51
+ return `${cliName} mcp`;
52
+ }
53
+
42
54
  /**
43
55
  * Build deterministic Next-Best-Action recovery hints for JSON errors.
44
56
  * @param {{cliName?: string}} [options]
@@ -83,6 +95,45 @@ function createErrorRecoveryService(options = {}) {
83
95
  command: buildPolymarketPreflightCommand(cliName),
84
96
  retryable: true,
85
97
  };
98
+ case 'MIRROR_DEPLOY_FAILED':
99
+ case 'MIRROR_GO_FAILED':
100
+ case 'MIRROR_GO_VERIFY_FAILED':
101
+ case 'MIRROR_GO_PREFLIGHT_FAILED':
102
+ return {
103
+ action: 'Re-run mirror deploy/verify in dry-run mode',
104
+ command: buildMirrorDeployRetryCommand(cliName),
105
+ retryable: true,
106
+ };
107
+ case 'MIRROR_SYNC_FAILED':
108
+ case 'MIRROR_GO_SYNC_FAILED':
109
+ case 'MIRROR_SYNC_PREFLIGHT_FAILED':
110
+ case 'MIRROR_SYNC_DAEMON_START_FAILED':
111
+ case 'MIRROR_SYNC_DAEMON_STOP_FAILED':
112
+ case 'MIRROR_SYNC_DAEMON_STATUS_FAILED':
113
+ return {
114
+ action: 'Run a bounded mirror sync iteration to isolate the failing gate',
115
+ command: buildMirrorSyncRetryCommand(cliName),
116
+ retryable: true,
117
+ };
118
+ case 'MCP_EXECUTE_INTENT_REQUIRED':
119
+ return {
120
+ action: 'Retry MCP tools/call with execute intent enabled',
121
+ command: buildMcpRestartCommand(cliName),
122
+ retryable: true,
123
+ };
124
+ case 'MCP_LONG_RUNNING_MODE_BLOCKED':
125
+ return {
126
+ action: 'Switch to a bounded command variant for MCP',
127
+ command: `${cliName} mirror sync once --help`,
128
+ retryable: true,
129
+ };
130
+ case 'MCP_TOOL_FAILED':
131
+ case 'UNKNOWN_TOOL':
132
+ return {
133
+ action: 'Inspect available MCP tools and retry with a supported tool name',
134
+ command: `${cliName} --output json schema`,
135
+ retryable: true,
136
+ };
86
137
  case 'MISSING_REQUIRED_FLAG':
87
138
  case 'MISSING_FLAG_VALUE':
88
139
  case 'INVALID_FLAG_VALUE':
@@ -1,3 +1,38 @@
1
+ /**
2
+ * @typedef {string|number|boolean|null|undefined|Array<string|number|boolean>} FlagInputValue
3
+ */
4
+
5
+ /**
6
+ * @typedef {{[flagName: string]: FlagInputValue}} ToolFlags
7
+ */
8
+
9
+ /**
10
+ * @typedef {{
11
+ * name: string,
12
+ * command: string[],
13
+ * description: string,
14
+ * mutating?: boolean,
15
+ * safeFlags?: string[],
16
+ * executeFlags?: string[],
17
+ * longRunningBlocked?: boolean
18
+ * }} ToolDefinition
19
+ */
20
+
21
+ /**
22
+ * @typedef {{
23
+ * positionals?: Array<string|number|boolean>,
24
+ * flags?: ToolFlags,
25
+ * intent?: { execute?: boolean }
26
+ * }} ToolInvocationArgs
27
+ */
28
+
29
+ /**
30
+ * Normalize a flag token to a CLI-prefixed name.
31
+ * Leaves existing `--foo`/`-f` tokens unchanged and prefixes bare names.
32
+ *
33
+ * @param {string} name Raw flag key.
34
+ * @returns {string} Normalized CLI flag token or empty string.
35
+ */
1
36
  function normalizeFlagName(name) {
2
37
  const normalized = String(name || '').trim();
3
38
  if (!normalized) return '';
@@ -6,6 +41,13 @@ function normalizeFlagName(name) {
6
41
  return `--${normalized}`;
7
42
  }
8
43
 
44
+ /**
45
+ * Convert a flag value (including nested arrays) into scalar CLI values.
46
+ * Booleans are preserved so callers can emit bare switch flags.
47
+ *
48
+ * @param {FlagInputValue} value Flag value candidate.
49
+ * @returns {Array<string|boolean>} Flattened scalar values.
50
+ */
9
51
  function flagValueToStrings(value) {
10
52
  if (value === null || value === undefined) return [];
11
53
  if (Array.isArray(value)) {
@@ -17,6 +59,13 @@ function flagValueToStrings(value) {
17
59
  return [String(value)];
18
60
  }
19
61
 
62
+ /**
63
+ * Build argv segments from a JSON-style flags map.
64
+ * Truthy booleans produce bare switch flags; scalar values produce pairs.
65
+ *
66
+ * @param {ToolFlags} flags Flag map where keys may be with/without `--`.
67
+ * @returns {string[]} CLI argv segment for flags.
68
+ */
20
69
  function buildFlagArgv(flags) {
21
70
  if (!flags || typeof flags !== 'object') return [];
22
71
  const argv = [];
@@ -37,6 +86,13 @@ function buildFlagArgv(flags) {
37
86
  return argv;
38
87
  }
39
88
 
89
+ /**
90
+ * Collect normalized flags that are meaningfully provided by the caller.
91
+ * Used by execution guardrails to detect safe/live mode intent flags.
92
+ *
93
+ * @param {ToolFlags} flags Candidate flags object.
94
+ * @returns {Set<string>} Set of normalized provided flag names.
95
+ */
40
96
  function providedFlagSet(flags) {
41
97
  const set = new Set();
42
98
  if (!flags || typeof flags !== 'object' || Array.isArray(flags)) return set;
@@ -53,6 +109,12 @@ function providedFlagSet(flags) {
53
109
  return set;
54
110
  }
55
111
 
112
+ /**
113
+ * Convert a tool definition to MCP descriptor format.
114
+ *
115
+ * @param {ToolDefinition} definition Tool registration definition.
116
+ * @returns {{name: string, description: string, inputSchema: object}} MCP tool descriptor.
117
+ */
56
118
  function toToolDescriptor(definition) {
57
119
  return {
58
120
  name: definition.name,
@@ -104,6 +166,7 @@ function toToolDescriptor(definition) {
104
166
  };
105
167
  }
106
168
 
169
+ /** @type {ToolDefinition[]} */
107
170
  const TOOL_DEFINITIONS = [
108
171
  { name: 'help', command: ['help'], description: 'Show top-level command help.' },
109
172
  { name: 'version', command: ['version'], description: 'Return the installed CLI version.' },
@@ -276,15 +339,33 @@ const TOOL_DEFINITIONS = [
276
339
 
277
340
  /**
278
341
  * Registry for MCP-exposed Pandora tools with execution guardrails.
279
- * @returns {{listTools: () => object[], prepareInvocation: (toolName: string, args?: object) => {argv: string[]}, hasTool: (toolName: string) => boolean}}
342
+ *
343
+ * @returns {{
344
+ * listTools: () => object[],
345
+ * prepareInvocation: (toolName: string, args?: ToolInvocationArgs) => {argv: string[]},
346
+ * hasTool: (toolName: string) => boolean
347
+ * }} MCP tool registry API.
280
348
  */
281
349
  function createMcpToolRegistry() {
282
350
  const byName = new Map(TOOL_DEFINITIONS.map((definition) => [definition.name, definition]));
283
351
 
352
+ /**
353
+ * List all MCP-exposed Pandora tools and their shared JSON contract.
354
+ *
355
+ * @returns {object[]} Tool descriptors.
356
+ */
284
357
  function listTools() {
285
358
  return TOOL_DEFINITIONS.map((definition) => toToolDescriptor(definition));
286
359
  }
287
360
 
361
+ /**
362
+ * Validate and convert a tool invocation request into Pandora CLI argv.
363
+ * Applies guardrails for unknown/blocked tools and mutating intent rules.
364
+ *
365
+ * @param {string} toolName Registered MCP tool name.
366
+ * @param {ToolInvocationArgs} [args={}] Invocation payload from MCP client.
367
+ * @returns {{argv: string[]}} Prepared command argv (without binary prefix).
368
+ */
288
369
  function prepareInvocation(toolName, args = {}) {
289
370
  if (toolName === 'launch' || toolName === 'clone-bet') {
290
371
  const unsupported = new Error(