@elizaos/prompts 2.0.0-beta.1 → 2.0.3-beta.3
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/LICENSE +21 -0
- package/README.md +12 -12
- package/package.json +11 -3
- package/scripts/check-secrets.js +19 -31
- package/scripts/file-utils.js +13 -0
- package/scripts/generate-action-docs.js +10 -42
- package/scripts/generate-plugin-action-spec.js +519 -155
- package/src/index.ts +119 -147
- package/{scripts/prompt-compression.js → src/prompt-compression.ts} +23 -13
- package/dist/python/__init__.py +0 -2
- package/dist/python/prompts.py +0 -1170
- package/dist/rust/mod.rs +0 -3
- package/dist/rust/prompts.rs +0 -1124
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Generate plugin action docs spec (best-effort).
|
|
4
|
-
*
|
|
5
|
-
* Scans plugins/** TypeScript action definitions and emits a merged spec file:
|
|
6
|
-
* packages/prompts/specs/actions/plugins.generated.json
|
|
7
|
-
*
|
|
8
|
-
* This is intentionally dependency-free and uses a small brace-aware extractor to
|
|
9
|
-
* locate `export const X: Action = { ... }` blocks.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
2
|
import fs from "node:fs";
|
|
13
3
|
import path from "node:path";
|
|
14
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { ensureDirectory, readJson, readText } from "./file-utils.js";
|
|
15
6
|
|
|
16
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
8
|
const __dirname = path.dirname(__filename);
|
|
@@ -37,7 +28,16 @@ const RETIRED_IMPLEMENTATION_ONLY_ACTIONS = new Set([
|
|
|
37
28
|
"ASK_USER_QUESTION",
|
|
38
29
|
"CHECKIN",
|
|
39
30
|
"CHECK_AVAILABILITY",
|
|
31
|
+
"CLEAR_HISTORY",
|
|
32
|
+
"CREATE_PLAN",
|
|
33
|
+
"CREATE_PAYMENT_REQUEST",
|
|
34
|
+
"DESKTOP",
|
|
35
|
+
"DEVICE_FILE_READ",
|
|
36
|
+
"DEVICE_FILE_WRITE",
|
|
37
|
+
"DEVICE_LIST_DIR",
|
|
40
38
|
"DISCORD_SETUP_CREDENTIALS",
|
|
39
|
+
"DOC",
|
|
40
|
+
"DELIVER_PAYMENT_LINK",
|
|
41
41
|
"EDIT",
|
|
42
42
|
"ENTER_WORKTREE",
|
|
43
43
|
"EXIT_WORKTREE",
|
|
@@ -67,13 +67,14 @@ const RETIRED_IMPLEMENTATION_ONLY_ACTIONS = new Set([
|
|
|
67
67
|
"GOOGLE_CALENDAR",
|
|
68
68
|
"LIFEOPS_PAUSE",
|
|
69
69
|
"NOSTR_PUBLISH_PROFILE",
|
|
70
|
-
"PAYMENT",
|
|
71
70
|
"PLACE_CALL",
|
|
72
71
|
"PLAY_AUDIO",
|
|
73
72
|
"PLAYBACK",
|
|
74
73
|
"READ",
|
|
74
|
+
"READING",
|
|
75
75
|
"RELEASE_BLOCK",
|
|
76
76
|
"SCREEN_TIME",
|
|
77
|
+
"SEND_TO_ADMIN",
|
|
77
78
|
"TOGGLE_FEATURE",
|
|
78
79
|
"TAILSCALE",
|
|
79
80
|
"START_TUNNEL",
|
|
@@ -82,6 +83,11 @@ const RETIRED_IMPLEMENTATION_ONLY_ACTIONS = new Set([
|
|
|
82
83
|
"WRITE",
|
|
83
84
|
"LS",
|
|
84
85
|
"MUSIC_LIBRARY",
|
|
86
|
+
"MYSTICISM_PAYMENT",
|
|
87
|
+
"VERIFY_PAYMENT_PAYLOAD",
|
|
88
|
+
"SETTLE_PAYMENT",
|
|
89
|
+
"AWAIT_PAYMENT_CALLBACK",
|
|
90
|
+
"CANCEL_PAYMENT_REQUEST",
|
|
85
91
|
"LIST_OVERDUE_FOLLOWUPS",
|
|
86
92
|
"MARK_FOLLOWUP_DONE",
|
|
87
93
|
"SET_FOLLOWUP_THRESHOLD",
|
|
@@ -99,19 +105,17 @@ const RETIRED_IMPLEMENTATION_ONLY_ACTIONS = new Set([
|
|
|
99
105
|
"GET_LINEAR_ACTIVITY",
|
|
100
106
|
"CLEAR_LINEAR_ACTIVITY",
|
|
101
107
|
"SEARCH_LINEAR_ISSUES",
|
|
108
|
+
"BROWSER_ACTIONS",
|
|
109
|
+
"WALLET_ACTIONS",
|
|
110
|
+
"CHARACTER_ACTIONS",
|
|
111
|
+
"SETTINGS_ACTIONS",
|
|
112
|
+
"CONNECTOR_ACTIONS",
|
|
113
|
+
"AUTOMATION_ACTIONS",
|
|
114
|
+
"PHONE_ACTIONS",
|
|
115
|
+
"OWNER_ACTIONS",
|
|
102
116
|
]);
|
|
103
117
|
|
|
104
|
-
function readText(filePath) {
|
|
105
|
-
return fs.readFileSync(filePath, "utf-8");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function readJson(filePath) {
|
|
109
|
-
return JSON.parse(readText(filePath));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
118
|
/**
|
|
113
|
-
* Template literals are extracted as raw source text; expand known `${...}` patterns
|
|
114
|
-
* so generated JSON/docs contain real strings (Biome flags `${` inside quoted TS strings).
|
|
115
119
|
* @param {string} description
|
|
116
120
|
* @param {string} actionFilePath
|
|
117
121
|
* @returns {string}
|
|
@@ -123,7 +127,7 @@ function expandDescriptionTemplateLiterals(description, actionFilePath) {
|
|
|
123
127
|
const emotesPath = path.join(path.dirname(actionFilePath), "emotes.ts");
|
|
124
128
|
if (!fs.existsSync(emotesPath)) {
|
|
125
129
|
console.warn(
|
|
126
|
-
`[generate-plugin-action-spec] VALID_EMOTE_IDS
|
|
130
|
+
`[generate-plugin-action-spec] VALID_EMOTE_IDS template expression but missing ${emotesPath}`,
|
|
127
131
|
);
|
|
128
132
|
return description;
|
|
129
133
|
}
|
|
@@ -152,18 +156,6 @@ function expandDescriptionTemplateLiterals(description, actionFilePath) {
|
|
|
152
156
|
return description.replace(/\$\{VALID_EMOTE_IDS\.join\([^)]*\)\}/g, joined);
|
|
153
157
|
}
|
|
154
158
|
|
|
155
|
-
/**
|
|
156
|
-
* Ensures a directory exists, creating it and parent directories if necessary.
|
|
157
|
-
* @param {string} dir - The directory path to ensure exists
|
|
158
|
-
* @throws {Error} If the directory path is empty or whitespace-only
|
|
159
|
-
*/
|
|
160
|
-
function ensureDir(dir) {
|
|
161
|
-
if (!dir || dir.trim() === "") {
|
|
162
|
-
throw new Error("Directory path cannot be empty");
|
|
163
|
-
}
|
|
164
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
159
|
function listTsFiles(rootDir) {
|
|
168
160
|
const out = [];
|
|
169
161
|
if (!fs.existsSync(rootDir)) return out;
|
|
@@ -174,7 +166,6 @@ function listTsFiles(rootDir) {
|
|
|
174
166
|
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
175
167
|
const full = path.join(dir, ent.name);
|
|
176
168
|
if (ent.isDirectory()) {
|
|
177
|
-
// ignore generated + dist trees
|
|
178
169
|
if (
|
|
179
170
|
ent.name === "dist" ||
|
|
180
171
|
ent.name === "generated" ||
|
|
@@ -184,6 +175,7 @@ function listTsFiles(rootDir) {
|
|
|
184
175
|
}
|
|
185
176
|
stack.push(full);
|
|
186
177
|
} else if (ent.isFile() && ent.name.endsWith(".ts")) {
|
|
178
|
+
if (!isActionCandidateFile(full)) continue;
|
|
187
179
|
if (full.includes(`${path.sep}__tests__${path.sep}`)) continue;
|
|
188
180
|
if (full.endsWith(".test.ts")) continue;
|
|
189
181
|
out.push(full);
|
|
@@ -193,110 +185,323 @@ function listTsFiles(rootDir) {
|
|
|
193
185
|
return out.sort((a, b) => a.localeCompare(b));
|
|
194
186
|
}
|
|
195
187
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
188
|
+
function isActionCandidateFile(filePath) {
|
|
189
|
+
return (
|
|
190
|
+
filePath.includes(`${path.sep}actions${path.sep}`) ||
|
|
191
|
+
filePath.endsWith(`${path.sep}actions.ts`)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const registeredActionBindingsByPluginRoot = new Map();
|
|
196
|
+
|
|
197
|
+
function getPluginRoot(filePath) {
|
|
198
|
+
const relative = path.relative(PLUGINS_ROOT, filePath);
|
|
199
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) return null;
|
|
200
|
+
const [pluginName] = relative.split(path.sep);
|
|
201
|
+
return pluginName ? path.join(PLUGINS_ROOT, pluginName) : null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function resolveTsImport(entryDir, importPath) {
|
|
205
|
+
if (!importPath.startsWith(".")) return null;
|
|
206
|
+
const base = path.resolve(entryDir, importPath);
|
|
207
|
+
const candidates = [
|
|
208
|
+
base,
|
|
209
|
+
base.replace(/\.js$/u, ".ts"),
|
|
210
|
+
`${base}.ts`,
|
|
211
|
+
path.join(base, "index.ts"),
|
|
212
|
+
];
|
|
213
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) ?? null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function readBalancedArraySource(src, bracketStart) {
|
|
217
|
+
let depth = 0;
|
|
218
|
+
let i = bracketStart;
|
|
219
|
+
let inSingle = false;
|
|
220
|
+
let inDouble = false;
|
|
221
|
+
let inTemplate = false;
|
|
222
|
+
let inLineComment = false;
|
|
223
|
+
let inBlockComment = false;
|
|
224
|
+
let escaped = false;
|
|
225
|
+
|
|
226
|
+
while (i < src.length) {
|
|
227
|
+
const ch = src[i];
|
|
228
|
+
const next = i + 1 < src.length ? src[i + 1] : "";
|
|
229
|
+
|
|
230
|
+
if (inLineComment) {
|
|
231
|
+
if (ch === "\n") inLineComment = false;
|
|
232
|
+
i++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (inBlockComment) {
|
|
236
|
+
if (ch === "*" && next === "/") {
|
|
237
|
+
inBlockComment = false;
|
|
238
|
+
i += 2;
|
|
225
239
|
continue;
|
|
226
240
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
241
|
+
i++;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
245
|
+
if (ch === "/" && next === "/") {
|
|
246
|
+
inLineComment = true;
|
|
247
|
+
i += 2;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (ch === "/" && next === "*") {
|
|
251
|
+
inBlockComment = true;
|
|
252
|
+
i += 2;
|
|
234
253
|
continue;
|
|
235
254
|
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (inSingle) {
|
|
258
|
+
if (!escaped && ch === "'") inSingle = false;
|
|
259
|
+
escaped = !escaped && ch === "\\";
|
|
260
|
+
i++;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (inDouble) {
|
|
264
|
+
if (!escaped && ch === '"') inDouble = false;
|
|
265
|
+
escaped = !escaped && ch === "\\";
|
|
266
|
+
i++;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (inTemplate) {
|
|
270
|
+
if (!escaped && ch === "`") inTemplate = false;
|
|
271
|
+
escaped = !escaped && ch === "\\";
|
|
272
|
+
i++;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (ch === "'") {
|
|
277
|
+
inSingle = true;
|
|
278
|
+
i++;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (ch === '"') {
|
|
282
|
+
inDouble = true;
|
|
283
|
+
i++;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (ch === "`") {
|
|
287
|
+
inTemplate = true;
|
|
288
|
+
i++;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (ch === "[") depth++;
|
|
293
|
+
if (ch === "]") {
|
|
294
|
+
depth--;
|
|
295
|
+
if (depth === 0) return src.slice(bracketStart, i + 1);
|
|
296
|
+
}
|
|
297
|
+
i++;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function extractActionArraySources(src) {
|
|
304
|
+
const sources = [];
|
|
305
|
+
const re = /\bactions\s*:\s*\[/gm;
|
|
306
|
+
for (;;) {
|
|
307
|
+
const match = re.exec(src);
|
|
308
|
+
if (match === null) break;
|
|
309
|
+
const bracketStart = match.index + match[0].lastIndexOf("[");
|
|
310
|
+
const source = readBalancedArraySource(src, bracketStart);
|
|
311
|
+
if (source) sources.push(source);
|
|
312
|
+
re.lastIndex = Math.max(re.lastIndex, bracketStart + (source?.length ?? 1));
|
|
313
|
+
}
|
|
314
|
+
return sources;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function extractNamedImports(src, entryDir) {
|
|
318
|
+
const imports = new Map();
|
|
319
|
+
const re = /import\s+\{([\s\S]*?)\}\s+from\s+(["'])([^"']+)\2/gm;
|
|
320
|
+
for (;;) {
|
|
321
|
+
const match = re.exec(src);
|
|
322
|
+
if (match === null) break;
|
|
323
|
+
const resolved = resolveTsImport(entryDir, match[3]);
|
|
324
|
+
if (!resolved || !isActionCandidateFile(resolved)) continue;
|
|
325
|
+
for (const rawPart of match[1].split(",")) {
|
|
326
|
+
const part = rawPart.trim();
|
|
327
|
+
if (!part) continue;
|
|
328
|
+
const [importedRaw, localRaw] = part.split(/\s+as\s+/u);
|
|
329
|
+
const imported = importedRaw.trim();
|
|
330
|
+
const local = (localRaw ?? importedRaw).trim();
|
|
331
|
+
if (
|
|
332
|
+
/^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(imported) &&
|
|
333
|
+
/^[A-Za-z_$][A-Za-z0-9_$]*$/u.test(local)
|
|
334
|
+
) {
|
|
335
|
+
imports.set(local, imported);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return imports;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function entrypointCandidatesForPlugin(pluginRoot) {
|
|
343
|
+
return [
|
|
344
|
+
path.join(pluginRoot, "src", "plugin.ts"),
|
|
345
|
+
path.join(pluginRoot, "src", "index.ts"),
|
|
346
|
+
path.join(pluginRoot, "index.ts"),
|
|
347
|
+
].filter((candidate) => fs.existsSync(candidate));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function getRegisteredActionBindings(pluginRoot) {
|
|
351
|
+
if (!pluginRoot) return null;
|
|
352
|
+
if (registeredActionBindingsByPluginRoot.has(pluginRoot)) {
|
|
353
|
+
return registeredActionBindingsByPluginRoot.get(pluginRoot);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const entrypoints = entrypointCandidatesForPlugin(pluginRoot);
|
|
357
|
+
const registeredLocalNames = new Set();
|
|
358
|
+
const registeredExportNames = new Set();
|
|
359
|
+
let foundActionArray = false;
|
|
360
|
+
|
|
361
|
+
for (const entrypoint of entrypoints) {
|
|
362
|
+
const src = readText(entrypoint);
|
|
363
|
+
const actionArrays = extractActionArraySources(src);
|
|
364
|
+
if (actionArrays.length === 0) continue;
|
|
365
|
+
foundActionArray = true;
|
|
366
|
+
const imports = extractNamedImports(src, path.dirname(entrypoint));
|
|
367
|
+
const actionArraySource = actionArrays.join("\n");
|
|
368
|
+
for (const [localName, importedName] of imports) {
|
|
369
|
+
const localRe = new RegExp(`\\b${localName}\\b`, "u");
|
|
370
|
+
if (localRe.test(actionArraySource)) {
|
|
371
|
+
registeredLocalNames.add(localName);
|
|
372
|
+
registeredExportNames.add(importedName);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const identifierRe = /\b[A-Za-z_$][A-Za-z0-9_$]*\b/g;
|
|
377
|
+
for (;;) {
|
|
378
|
+
const match = identifierRe.exec(actionArraySource);
|
|
379
|
+
if (match === null) break;
|
|
380
|
+
registeredLocalNames.add(match[0]);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const result = foundActionArray
|
|
385
|
+
? { localNames: registeredLocalNames, exportNames: registeredExportNames }
|
|
386
|
+
: null;
|
|
387
|
+
registeredActionBindingsByPluginRoot.set(pluginRoot, result);
|
|
388
|
+
return result;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function isRegisteredActionObject(filePath, exportName) {
|
|
392
|
+
const registered = getRegisteredActionBindings(getPluginRoot(filePath));
|
|
393
|
+
if (!registered || !exportName) return true;
|
|
394
|
+
return (
|
|
395
|
+
registered.exportNames.has(exportName) ||
|
|
396
|
+
registered.localNames.has(exportName)
|
|
397
|
+
);
|
|
398
|
+
}
|
|
236
399
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
400
|
+
function extractActionObjects(filePath, src) {
|
|
401
|
+
const results = [];
|
|
402
|
+
const patterns = [
|
|
403
|
+
/\b(?:export\s+)?const\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*:\s*Action(?:\s*&\s*\{[\s\S]*?\})?\s*=\s*\{/gm,
|
|
404
|
+
/\b(?:export\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*\([^)]*\)\s*:\s*Action\s*\{[\s\S]*?\breturn\s+\{/gm,
|
|
405
|
+
];
|
|
406
|
+
for (const re of patterns) {
|
|
407
|
+
for (;;) {
|
|
408
|
+
const m = re.exec(src);
|
|
409
|
+
if (m === null) break;
|
|
410
|
+
const exportName = m[1];
|
|
411
|
+
const braceStart = m.index + m[0].lastIndexOf("{");
|
|
412
|
+
|
|
413
|
+
let depth = 0;
|
|
414
|
+
let j = braceStart;
|
|
415
|
+
let inSingle = false;
|
|
416
|
+
let inDouble = false;
|
|
417
|
+
let inTemplate = false;
|
|
418
|
+
let inLineComment = false;
|
|
419
|
+
let inBlockComment = false;
|
|
420
|
+
let escaped = false;
|
|
421
|
+
|
|
422
|
+
while (j < src.length) {
|
|
423
|
+
const ch = src[j];
|
|
424
|
+
const next = j + 1 < src.length ? src[j + 1] : "";
|
|
425
|
+
|
|
426
|
+
if (inLineComment) {
|
|
427
|
+
if (ch === "\n") inLineComment = false;
|
|
428
|
+
j++;
|
|
241
429
|
continue;
|
|
242
430
|
}
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
431
|
+
if (inBlockComment) {
|
|
432
|
+
if (ch === "*" && next === "/") {
|
|
433
|
+
inBlockComment = false;
|
|
434
|
+
j += 2;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
j++;
|
|
246
438
|
continue;
|
|
247
439
|
}
|
|
248
|
-
}
|
|
249
440
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (
|
|
264
|
-
|
|
441
|
+
if (!inSingle && !inDouble && !inTemplate) {
|
|
442
|
+
if (ch === "/" && next === "/") {
|
|
443
|
+
inLineComment = true;
|
|
444
|
+
j += 2;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (ch === "/" && next === "*") {
|
|
448
|
+
inBlockComment = true;
|
|
449
|
+
j += 2;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (inSingle) {
|
|
455
|
+
if (!escaped && ch === "'") inSingle = false;
|
|
456
|
+
escaped = !escaped && ch === "\\";
|
|
457
|
+
j++;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (inDouble) {
|
|
461
|
+
if (!escaped && ch === '"') inDouble = false;
|
|
462
|
+
escaped = !escaped && ch === "\\";
|
|
463
|
+
j++;
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (inTemplate) {
|
|
467
|
+
if (!escaped && ch === "`") {
|
|
468
|
+
inTemplate = false;
|
|
469
|
+
j++;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
escaped = !escaped && ch === "\\";
|
|
265
473
|
j++;
|
|
266
474
|
continue;
|
|
267
475
|
}
|
|
268
|
-
escaped = !escaped && ch === "\\";
|
|
269
|
-
j++;
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
476
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
477
|
+
if (ch === "'") {
|
|
478
|
+
inSingle = true;
|
|
479
|
+
j++;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (ch === '"') {
|
|
483
|
+
inDouble = true;
|
|
484
|
+
j++;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (ch === "`") {
|
|
488
|
+
inTemplate = true;
|
|
489
|
+
j++;
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
288
492
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
493
|
+
if (ch === "{") depth++;
|
|
494
|
+
if (ch === "}") {
|
|
495
|
+
depth--;
|
|
496
|
+
if (depth === 0) {
|
|
497
|
+
const objectText = src.slice(braceStart, j + 1);
|
|
498
|
+
results.push({ filePath, exportName, objectText });
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
296
501
|
}
|
|
297
|
-
}
|
|
298
502
|
|
|
299
|
-
|
|
503
|
+
j++;
|
|
504
|
+
}
|
|
300
505
|
}
|
|
301
506
|
}
|
|
302
507
|
|
|
@@ -329,7 +534,6 @@ function isWs(ch) {
|
|
|
329
534
|
}
|
|
330
535
|
|
|
331
536
|
function scanTopLevelPropertyValue(objText, propName) {
|
|
332
|
-
// objText is `{ ... }`
|
|
333
537
|
let i = 0;
|
|
334
538
|
let braceDepth = 0;
|
|
335
539
|
let bracketDepth = 0;
|
|
@@ -414,7 +618,6 @@ function scanTopLevelPropertyValue(objText, propName) {
|
|
|
414
618
|
|
|
415
619
|
// Top-level inside the object: braceDepth === 1
|
|
416
620
|
if (braceDepth === 1 && bracketDepth === 0) {
|
|
417
|
-
// Look for `<propName>:` starting at identifier boundary.
|
|
418
621
|
if (objText.startsWith(propName, i)) {
|
|
419
622
|
const before = i > 0 ? objText[i - 1] : "";
|
|
420
623
|
const after =
|
|
@@ -446,7 +649,6 @@ function scanTopLevelPropertyValue(objText, propName) {
|
|
|
446
649
|
function extractTopLevelStringProp(objText, propName) {
|
|
447
650
|
const tail = scanTopLevelPropertyValue(objText, propName);
|
|
448
651
|
if (!tail) return null;
|
|
449
|
-
// Parse a single string literal token.
|
|
450
652
|
const first = tail.trimStart();
|
|
451
653
|
if (
|
|
452
654
|
!(first.startsWith('"') || first.startsWith("'") || first.startsWith("`"))
|
|
@@ -454,7 +656,6 @@ function extractTopLevelStringProp(objText, propName) {
|
|
|
454
656
|
return null;
|
|
455
657
|
}
|
|
456
658
|
|
|
457
|
-
// Grab up to the end of the literal (best-effort, supports escaped quotes).
|
|
458
659
|
const quote = first[0];
|
|
459
660
|
let i = 1;
|
|
460
661
|
let escaped = false;
|
|
@@ -765,13 +966,33 @@ function skipTypeAssertionSuffix(src, cursor) {
|
|
|
765
966
|
const after = i + 2 < src.length ? src[i + 2] : "";
|
|
766
967
|
if (/[A-Za-z0-9_$]/.test(before) || /[A-Za-z0-9_$]/.test(after)) break;
|
|
767
968
|
i = skipTrivia(src, i + 2);
|
|
768
|
-
|
|
969
|
+
let bracketDepth = 0;
|
|
970
|
+
let angleDepth = 0;
|
|
971
|
+
let parenDepth = 0;
|
|
972
|
+
while (i < src.length) {
|
|
973
|
+
const ch = src[i];
|
|
974
|
+
if (
|
|
975
|
+
bracketDepth === 0 &&
|
|
976
|
+
angleDepth === 0 &&
|
|
977
|
+
parenDepth === 0 &&
|
|
978
|
+
[",", "]", "}"].includes(ch)
|
|
979
|
+
) {
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
if (ch === "[") bracketDepth++;
|
|
983
|
+
else if (ch === "]" && bracketDepth > 0) bracketDepth--;
|
|
984
|
+
else if (ch === "<") angleDepth++;
|
|
985
|
+
else if (ch === ">" && angleDepth > 0) angleDepth--;
|
|
986
|
+
else if (ch === "(") parenDepth++;
|
|
987
|
+
else if (ch === ")" && parenDepth > 0) parenDepth--;
|
|
988
|
+
i++;
|
|
989
|
+
}
|
|
769
990
|
i = skipTrivia(src, i);
|
|
770
991
|
}
|
|
771
992
|
return i;
|
|
772
993
|
}
|
|
773
994
|
|
|
774
|
-
function parseTsLiteralValue(src, cursor = 0) {
|
|
995
|
+
function parseTsLiteralValue(src, cursor = 0, constants = new Map()) {
|
|
775
996
|
let i = skipTrivia(src, cursor);
|
|
776
997
|
const ch = src[i];
|
|
777
998
|
if (ch === undefined) return { value: undefined, end: i };
|
|
@@ -793,9 +1014,15 @@ function parseTsLiteralValue(src, cursor = 0) {
|
|
|
793
1014
|
break;
|
|
794
1015
|
}
|
|
795
1016
|
if (src.startsWith("...", i)) {
|
|
796
|
-
|
|
1017
|
+
const ident = readIdentifier(src, skipTrivia(src, i + 3));
|
|
1018
|
+
if (ident && Array.isArray(constants.get(ident.value))) {
|
|
1019
|
+
arr.push(...constants.get(ident.value));
|
|
1020
|
+
i = ident.end;
|
|
1021
|
+
} else {
|
|
1022
|
+
i = skipUnknownExpression(src, i + 3);
|
|
1023
|
+
}
|
|
797
1024
|
} else {
|
|
798
|
-
const parsed = parseTsLiteralValue(src, i);
|
|
1025
|
+
const parsed = parseTsLiteralValue(src, i, constants);
|
|
799
1026
|
if (parsed.value !== undefined) arr.push(parsed.value);
|
|
800
1027
|
i = parsed.end;
|
|
801
1028
|
}
|
|
@@ -845,7 +1072,7 @@ function parseTsLiteralValue(src, cursor = 0) {
|
|
|
845
1072
|
if (src[i] === ",") i++;
|
|
846
1073
|
continue;
|
|
847
1074
|
}
|
|
848
|
-
const parsed = parseTsLiteralValue(src, i + 1);
|
|
1075
|
+
const parsed = parseTsLiteralValue(src, i + 1, constants);
|
|
849
1076
|
if (parsed.value !== undefined) obj[key] = parsed.value;
|
|
850
1077
|
i = skipTrivia(src, parsed.end);
|
|
851
1078
|
if (src[i] === ",") i++;
|
|
@@ -861,6 +1088,12 @@ function parseTsLiteralValue(src, cursor = 0) {
|
|
|
861
1088
|
|
|
862
1089
|
const ident = readIdentifier(src, i);
|
|
863
1090
|
if (ident) {
|
|
1091
|
+
if (constants.has(ident.value)) {
|
|
1092
|
+
return {
|
|
1093
|
+
value: constants.get(ident.value),
|
|
1094
|
+
end: skipTypeAssertionSuffix(src, ident.end),
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
864
1097
|
if (ident.value === "true") {
|
|
865
1098
|
return { value: true, end: skipTypeAssertionSuffix(src, ident.end) };
|
|
866
1099
|
}
|
|
@@ -875,10 +1108,134 @@ function parseTsLiteralValue(src, cursor = 0) {
|
|
|
875
1108
|
return { value: undefined, end: skipUnknownExpression(src, i) };
|
|
876
1109
|
}
|
|
877
1110
|
|
|
878
|
-
function extractTopLevelLiteralProp(objText, propName) {
|
|
1111
|
+
function extractTopLevelLiteralProp(objText, propName, constants = new Map()) {
|
|
879
1112
|
const source = extractTopLevelValueSource(objText, propName);
|
|
880
1113
|
if (!source) return undefined;
|
|
881
|
-
return parseTsLiteralValue(source).value;
|
|
1114
|
+
return parseTsLiteralValue(source, 0, constants).value;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function extractConstLiterals(src) {
|
|
1118
|
+
const constants = new Map();
|
|
1119
|
+
const re = /\bconst\s+([A-Za-z_$][A-Za-z0-9_$]*)(?:\s*:[^=]+)?\s*=\s*/gm;
|
|
1120
|
+
for (;;) {
|
|
1121
|
+
const match = re.exec(src);
|
|
1122
|
+
if (match === null) break;
|
|
1123
|
+
const name = match[1];
|
|
1124
|
+
const initializerStart = skipTrivia(src, re.lastIndex);
|
|
1125
|
+
if (src[initializerStart] === "{") {
|
|
1126
|
+
re.lastIndex = Math.max(
|
|
1127
|
+
skipUnknownExpression(src, initializerStart),
|
|
1128
|
+
re.lastIndex,
|
|
1129
|
+
);
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
const parsed = parseTsLiteralValue(src, re.lastIndex, constants);
|
|
1133
|
+
if (
|
|
1134
|
+
typeof parsed.value === "string" ||
|
|
1135
|
+
typeof parsed.value === "number" ||
|
|
1136
|
+
typeof parsed.value === "boolean" ||
|
|
1137
|
+
Array.isArray(parsed.value)
|
|
1138
|
+
) {
|
|
1139
|
+
constants.set(name, parsed.value);
|
|
1140
|
+
}
|
|
1141
|
+
re.lastIndex = Math.max(parsed.end, re.lastIndex);
|
|
1142
|
+
}
|
|
1143
|
+
return constants;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function readArrayLiteralAfter(src, marker) {
|
|
1147
|
+
const markerIndex = src.indexOf(marker);
|
|
1148
|
+
if (markerIndex < 0) return [];
|
|
1149
|
+
const bracketStart = src.indexOf("[", markerIndex);
|
|
1150
|
+
if (bracketStart < 0) return [];
|
|
1151
|
+
const source = readBalancedArraySource(src, bracketStart);
|
|
1152
|
+
if (!source) return [];
|
|
1153
|
+
const parsed = parseTsLiteralValue(source, 0);
|
|
1154
|
+
return Array.isArray(parsed.value) ? parsed.value : [];
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function dynamicCommandActionDocs(filePath, name) {
|
|
1158
|
+
const relativePath = path.relative(REPO_ROOT, filePath);
|
|
1159
|
+
const commandActionNameTemplate = "$" + "{key.toUpperCase()}_COMMAND";
|
|
1160
|
+
if (
|
|
1161
|
+
relativePath !==
|
|
1162
|
+
path.join(
|
|
1163
|
+
"plugins",
|
|
1164
|
+
"plugin-commands",
|
|
1165
|
+
"src",
|
|
1166
|
+
"actions",
|
|
1167
|
+
"command-actions.ts",
|
|
1168
|
+
) ||
|
|
1169
|
+
name !== commandActionNameTemplate
|
|
1170
|
+
) {
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const pluginCommandsRoot = path.join(PLUGINS_ROOT, "plugin-commands");
|
|
1175
|
+
const handlersSrc = readText(
|
|
1176
|
+
path.join(pluginCommandsRoot, "src", "actions", "handlers.ts"),
|
|
1177
|
+
);
|
|
1178
|
+
const deterministicCommandKeys = extractConstLiterals(handlersSrc).get(
|
|
1179
|
+
"DETERMINISTIC_COMMAND_KEYS",
|
|
1180
|
+
);
|
|
1181
|
+
if (!Array.isArray(deterministicCommandKeys)) return [];
|
|
1182
|
+
|
|
1183
|
+
const registrySrc = readText(
|
|
1184
|
+
path.join(pluginCommandsRoot, "src", "registry.ts"),
|
|
1185
|
+
);
|
|
1186
|
+
const commandDefinitions = readArrayLiteralAfter(
|
|
1187
|
+
registrySrc,
|
|
1188
|
+
"DEFAULT_COMMANDS",
|
|
1189
|
+
);
|
|
1190
|
+
const definitionsByKey = new Map();
|
|
1191
|
+
for (const definition of commandDefinitions) {
|
|
1192
|
+
if (isRecordValue(definition) && typeof definition.key === "string") {
|
|
1193
|
+
definitionsByKey.set(definition.key, definition);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
return deterministicCommandKeys
|
|
1198
|
+
.filter((key) => typeof key === "string")
|
|
1199
|
+
.map((key) => {
|
|
1200
|
+
const definition = definitionsByKey.get(key);
|
|
1201
|
+
const aliases = Array.isArray(definition?.textAliases)
|
|
1202
|
+
? definition.textAliases.filter((alias) => typeof alias === "string")
|
|
1203
|
+
: [];
|
|
1204
|
+
const doc = {
|
|
1205
|
+
name: `${key.toUpperCase()}_COMMAND`,
|
|
1206
|
+
description:
|
|
1207
|
+
typeof definition?.description === "string"
|
|
1208
|
+
? definition.description
|
|
1209
|
+
: "",
|
|
1210
|
+
parameters: sanitizeParameters(definition?.args),
|
|
1211
|
+
};
|
|
1212
|
+
if (aliases.length > 0) {
|
|
1213
|
+
doc.similes = aliases;
|
|
1214
|
+
}
|
|
1215
|
+
return doc;
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function resolveRequireActionSpecName(src, source) {
|
|
1220
|
+
const match = source.trim().match(/^([A-Za-z_$][A-Za-z0-9_$]*)\.name$/);
|
|
1221
|
+
if (!match) return null;
|
|
1222
|
+
const specName = match[1];
|
|
1223
|
+
const specRe = new RegExp(
|
|
1224
|
+
`\\bconst\\s+${specName}\\s*=\\s*requireActionSpec\\((["'\`])([^"'\`]+)\\1\\)`,
|
|
1225
|
+
);
|
|
1226
|
+
return specRe.exec(src)?.[2] ?? null;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function extractResolvedStringProp(objText, propName, constants, src) {
|
|
1230
|
+
const direct = extractTopLevelStringProp(objText, propName);
|
|
1231
|
+
if (typeof direct === "string") return direct;
|
|
1232
|
+
|
|
1233
|
+
const source = extractTopLevelValueSource(objText, propName);
|
|
1234
|
+
if (!source) return null;
|
|
1235
|
+
const parsed = parseTsLiteralValue(source, 0, constants).value;
|
|
1236
|
+
if (typeof parsed === "string") return parsed;
|
|
1237
|
+
|
|
1238
|
+
return resolveRequireActionSpecName(src, source);
|
|
882
1239
|
}
|
|
883
1240
|
|
|
884
1241
|
function isRecordValue(value) {
|
|
@@ -1176,13 +1533,6 @@ function main() {
|
|
|
1176
1533
|
["slippage", { description: "Max slippage percentage.", example: 1 }],
|
|
1177
1534
|
]);
|
|
1178
1535
|
|
|
1179
|
-
function humanizeKey(key) {
|
|
1180
|
-
return key
|
|
1181
|
-
.replaceAll(/[_-]+/g, " ")
|
|
1182
|
-
.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
1183
|
-
.toLowerCase();
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
1536
|
function inferParamType(objText, key) {
|
|
1187
1537
|
const re = new RegExp(`state\\?\\.${key}\\s+as\\s+([A-Za-z0-9_]+)`, "g");
|
|
1188
1538
|
const m = re.exec(objText);
|
|
@@ -1206,7 +1556,7 @@ function main() {
|
|
|
1206
1556
|
const type = inferParamType(objText, key);
|
|
1207
1557
|
const known = commonParamDocs.get(key);
|
|
1208
1558
|
const description =
|
|
1209
|
-
known?.description ?? `The ${
|
|
1559
|
+
known?.description ?? `The ${humanizeParamKey(key)} to use.`;
|
|
1210
1560
|
const example =
|
|
1211
1561
|
known?.example ??
|
|
1212
1562
|
(type === "boolean" ? false : type === "number" ? 1 : "example");
|
|
@@ -1269,22 +1619,36 @@ function main() {
|
|
|
1269
1619
|
const actionDocsByName = new Map();
|
|
1270
1620
|
|
|
1271
1621
|
for (const filePath of tsFiles) {
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
) {
|
|
1277
|
-
continue;
|
|
1622
|
+
if (process.env.DEBUG_ACTION_SPEC) {
|
|
1623
|
+
console.error(
|
|
1624
|
+
`[generate-plugin-action-spec] ${path.relative(REPO_ROOT, filePath)}`,
|
|
1625
|
+
);
|
|
1278
1626
|
}
|
|
1279
1627
|
const src = readText(filePath);
|
|
1280
1628
|
if (!src.includes(": Action")) continue;
|
|
1629
|
+
const constants = extractConstLiterals(src);
|
|
1281
1630
|
|
|
1282
1631
|
const objects = extractActionObjects(filePath, src);
|
|
1283
1632
|
for (const obj of objects) {
|
|
1284
|
-
const name =
|
|
1633
|
+
const name = extractResolvedStringProp(
|
|
1634
|
+
obj.objectText,
|
|
1635
|
+
"name",
|
|
1636
|
+
constants,
|
|
1637
|
+
src,
|
|
1638
|
+
);
|
|
1285
1639
|
if (!name) continue;
|
|
1640
|
+
if (!isRegisteredActionObject(filePath, obj.exportName)) continue;
|
|
1286
1641
|
if (RETIRED_IMPLEMENTATION_ONLY_ACTIONS.has(name)) continue;
|
|
1287
1642
|
if (coreActionNames.has(name)) continue;
|
|
1643
|
+
const dynamicDocs = dynamicCommandActionDocs(filePath, name);
|
|
1644
|
+
if (dynamicDocs) {
|
|
1645
|
+
for (const doc of dynamicDocs) {
|
|
1646
|
+
if (!actionDocsByName.has(doc.name)) {
|
|
1647
|
+
actionDocsByName.set(doc.name, doc);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1288
1652
|
const description = expandDescriptionTemplateLiterals(
|
|
1289
1653
|
extractTopLevelStringProp(obj.objectText, "description") ?? "",
|
|
1290
1654
|
filePath,
|
|
@@ -1293,15 +1657,16 @@ function main() {
|
|
|
1293
1657
|
obj.objectText,
|
|
1294
1658
|
"descriptionCompressed",
|
|
1295
1659
|
);
|
|
1296
|
-
const similes = extractTopLevelStringArrayProp(
|
|
1660
|
+
const similes = extractTopLevelStringArrayProp(
|
|
1661
|
+
obj.objectText,
|
|
1662
|
+
"similes",
|
|
1663
|
+
).filter((simile) => !RETIRED_IMPLEMENTATION_ONLY_ACTIONS.has(simile));
|
|
1297
1664
|
const explicitParameters = sanitizeParameters(
|
|
1298
|
-
extractTopLevelLiteralProp(obj.objectText, "parameters"),
|
|
1665
|
+
extractTopLevelLiteralProp(obj.objectText, "parameters", constants),
|
|
1299
1666
|
);
|
|
1300
1667
|
const descriptionParameters = inferParametersFromDescription(description);
|
|
1301
1668
|
const jsonTemplateParameters =
|
|
1302
|
-
explicitParameters.length === 0 &&
|
|
1303
|
-
descriptionParameters.length === 0 &&
|
|
1304
|
-
(name.endsWith("") || description.toLowerCase().includes("router"))
|
|
1669
|
+
explicitParameters.length === 0 && descriptionParameters.length === 0
|
|
1305
1670
|
? inferParametersFromJsonTemplate(src)
|
|
1306
1671
|
: [];
|
|
1307
1672
|
const parameters =
|
|
@@ -1314,7 +1679,6 @@ function main() {
|
|
|
1314
1679
|
: inferParameters(obj.objectText);
|
|
1315
1680
|
const exampleCalls = buildExampleCallForAction(name, parameters);
|
|
1316
1681
|
|
|
1317
|
-
// Do not overwrite existing entries; prefer the first seen (stable ordering).
|
|
1318
1682
|
if (!actionDocsByName.has(name)) {
|
|
1319
1683
|
actionDocsByName.set(name, {
|
|
1320
1684
|
name,
|
|
@@ -1351,7 +1715,7 @@ function main() {
|
|
|
1351
1715
|
|
|
1352
1716
|
const outRoot = { version, actions };
|
|
1353
1717
|
|
|
1354
|
-
|
|
1718
|
+
ensureDirectory(path.dirname(OUTPUT_PATH));
|
|
1355
1719
|
fs.writeFileSync(OUTPUT_PATH, `${JSON.stringify(outRoot, null, 2)}\n`);
|
|
1356
1720
|
console.log(
|
|
1357
1721
|
`Wrote ${actions.length} plugin actions to ${path.relative(REPO_ROOT, OUTPUT_PATH)}`,
|