pandora-cli-skills 1.1.37 → 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,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 {{
|
|
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))
|
|
@@ -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
|
-
*
|
|
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(
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
const { createParseStreamFlags } = require('./parsers/stream_flags.cjs');
|
|
2
2
|
const { graphqlRequest } = require('./indexer_client.cjs');
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Validate and return a required function dependency.
|
|
6
|
+
* @param {object} deps
|
|
7
|
+
* @param {string} name
|
|
8
|
+
* @returns {Function}
|
|
9
|
+
*/
|
|
4
10
|
function requireDep(deps, name) {
|
|
5
11
|
if (!deps || typeof deps[name] !== 'function') {
|
|
6
12
|
throw new Error(`createRunStreamCommand requires deps.${name}()`);
|
|
@@ -8,6 +14,11 @@ function requireDep(deps, name) {
|
|
|
8
14
|
return deps[name];
|
|
9
15
|
}
|
|
10
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Convert an HTTP(S) indexer URL to WS(S) for stream transport.
|
|
19
|
+
* @param {string} indexerUrl
|
|
20
|
+
* @returns {string|null}
|
|
21
|
+
*/
|
|
11
22
|
function toWsUrl(indexerUrl) {
|
|
12
23
|
try {
|
|
13
24
|
const parsed = new URL(String(indexerUrl || ''));
|
|
@@ -19,6 +30,11 @@ function toWsUrl(indexerUrl) {
|
|
|
19
30
|
}
|
|
20
31
|
}
|
|
21
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Emit one NDJSON line to stdout with backpressure awareness.
|
|
35
|
+
* @param {object} payload
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
22
38
|
function writeNdjson(payload) {
|
|
23
39
|
const line = `${JSON.stringify(payload)}\n`;
|
|
24
40
|
if (process.stdout.write(line)) {
|
|
@@ -29,6 +45,10 @@ function writeNdjson(payload) {
|
|
|
29
45
|
});
|
|
30
46
|
}
|
|
31
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Serialize stream writes so payloads are emitted in-order.
|
|
50
|
+
* @returns {(payload: object) => Promise<void>}
|
|
51
|
+
*/
|
|
32
52
|
function createQueuedWriter() {
|
|
33
53
|
let chain = Promise.resolve();
|
|
34
54
|
return (payload) => {
|
|
@@ -37,6 +57,14 @@ function createQueuedWriter() {
|
|
|
37
57
|
};
|
|
38
58
|
}
|
|
39
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Create stable stream event metadata shared by tick/diagnostic envelopes.
|
|
62
|
+
* @param {'prices'|'events'} channel
|
|
63
|
+
* @param {number} seq
|
|
64
|
+
* @param {'polling'|'websocket'} sourceTransport
|
|
65
|
+
* @param {string|null} sourceUrl
|
|
66
|
+
* @returns {object}
|
|
67
|
+
*/
|
|
40
68
|
function toTickBase(channel, seq, sourceTransport, sourceUrl) {
|
|
41
69
|
return {
|
|
42
70
|
type: 'stream.tick',
|
|
@@ -50,6 +78,13 @@ function toTickBase(channel, seq, sourceTransport, sourceUrl) {
|
|
|
50
78
|
};
|
|
51
79
|
}
|
|
52
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Query latest market rows used by the `prices` channel.
|
|
83
|
+
* @param {string} indexerUrl
|
|
84
|
+
* @param {{marketAddress?: string|null, chainId?: number|null, limit: number}} options
|
|
85
|
+
* @param {number} timeoutMs
|
|
86
|
+
* @returns {Promise<object[]>}
|
|
87
|
+
*/
|
|
53
88
|
async function fetchPriceRows(indexerUrl, options, timeoutMs) {
|
|
54
89
|
const where = {};
|
|
55
90
|
if (options.marketAddress) where.id = options.marketAddress;
|
|
@@ -75,6 +110,13 @@ async function fetchPriceRows(indexerUrl, options, timeoutMs) {
|
|
|
75
110
|
return page;
|
|
76
111
|
}
|
|
77
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Query latest liquidity event rows used by the `events` channel.
|
|
115
|
+
* @param {string} indexerUrl
|
|
116
|
+
* @param {{marketAddress?: string|null, chainId?: number|null, limit: number}} options
|
|
117
|
+
* @param {number} timeoutMs
|
|
118
|
+
* @returns {Promise<object[]>}
|
|
119
|
+
*/
|
|
78
120
|
async function fetchEventRows(indexerUrl, options, timeoutMs) {
|
|
79
121
|
const where = {};
|
|
80
122
|
if (options.marketAddress) where.marketAddress = options.marketAddress;
|
|
@@ -102,6 +144,16 @@ async function fetchEventRows(indexerUrl, options, timeoutMs) {
|
|
|
102
144
|
return page;
|
|
103
145
|
}
|
|
104
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Run polling-mode stream loop.
|
|
149
|
+
* Emits `stream.tick` records for fetched rows and `stream.diagnostic` on fetch failures.
|
|
150
|
+
* @param {string} indexerUrl
|
|
151
|
+
* @param {{channel: 'prices'|'events', intervalMs: number}} options
|
|
152
|
+
* @param {number} timeoutMs
|
|
153
|
+
* @param {(ms: number) => Promise<void>} sleepMs
|
|
154
|
+
* @param {(payload: object) => Promise<void>} emitNdjson
|
|
155
|
+
* @returns {Promise<never>}
|
|
156
|
+
*/
|
|
105
157
|
async function runPollingStream(indexerUrl, options, timeoutMs, sleepMs, emitNdjson) {
|
|
106
158
|
let seq = 0;
|
|
107
159
|
const channelFetcher = options.channel === 'prices' ? fetchPriceRows : fetchEventRows;
|
|
@@ -134,6 +186,14 @@ async function runPollingStream(indexerUrl, options, timeoutMs, sleepMs, emitNdj
|
|
|
134
186
|
}
|
|
135
187
|
}
|
|
136
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Attempt websocket streaming and return whether to continue with polling fallback.
|
|
191
|
+
* Resolves `false` on connection/setup failure or close, rejects on runtime WS errors after open.
|
|
192
|
+
* @param {string} wsUrl
|
|
193
|
+
* @param {{channel: 'prices'|'events'}} options
|
|
194
|
+
* @param {(payload: object) => Promise<void>} emitNdjson
|
|
195
|
+
* @returns {Promise<boolean>}
|
|
196
|
+
*/
|
|
137
197
|
async function tryWebSocketStream(wsUrl, options, emitNdjson) {
|
|
138
198
|
const { WebSocket } = require('ws');
|
|
139
199
|
let seq = 0;
|
|
@@ -202,6 +262,7 @@ async function tryWebSocketStream(wsUrl, options, emitNdjson) {
|
|
|
202
262
|
|
|
203
263
|
/**
|
|
204
264
|
* Create runner for `pandora stream`.
|
|
265
|
+
* In active mode this command emits NDJSON lines continuously, independent of `--output`.
|
|
205
266
|
* @param {object} deps
|
|
206
267
|
* @returns {{runStreamCommand: (args: string[], context: {outputMode: 'table'|'json'}) => Promise<void>}}
|
|
207
268
|
*/
|
|
@@ -227,6 +288,12 @@ function createRunStreamCommand(deps) {
|
|
|
227
288
|
isSecureHttpUrlOrLocal,
|
|
228
289
|
});
|
|
229
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Dispatch `stream` subcommands and start websocket/polling event emission.
|
|
293
|
+
* @param {string[]} args
|
|
294
|
+
* @param {{outputMode: 'table'|'json'}} context
|
|
295
|
+
* @returns {Promise<void>}
|
|
296
|
+
*/
|
|
230
297
|
async function runStreamCommand(args, context) {
|
|
231
298
|
const shared = parseIndexerSharedFlags(args);
|
|
232
299
|
if (!shared.rest.length || includesHelpFlag(shared.rest)) {
|