@elizaos/prompts 2.0.0-beta.1 → 2.0.3-beta.2
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 -11
- 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 +9 -41
- package/scripts/generate-plugin-action-spec.js +437 -155
- package/src/index.ts +118 -148
- 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
|
+
}
|
|
236
341
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
+
}
|
|
399
|
+
|
|
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,61 @@ 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 resolveRequireActionSpecName(src, source) {
|
|
1147
|
+
const match = source.trim().match(/^([A-Za-z_$][A-Za-z0-9_$]*)\.name$/);
|
|
1148
|
+
if (!match) return null;
|
|
1149
|
+
const specName = match[1];
|
|
1150
|
+
const specRe = new RegExp(
|
|
1151
|
+
`\\bconst\\s+${specName}\\s*=\\s*requireActionSpec\\((["'\`])([^"'\`]+)\\1\\)`,
|
|
1152
|
+
);
|
|
1153
|
+
return specRe.exec(src)?.[2] ?? null;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function extractResolvedStringProp(objText, propName, constants, src) {
|
|
1157
|
+
const direct = extractTopLevelStringProp(objText, propName);
|
|
1158
|
+
if (typeof direct === "string") return direct;
|
|
1159
|
+
|
|
1160
|
+
const source = extractTopLevelValueSource(objText, propName);
|
|
1161
|
+
if (!source) return null;
|
|
1162
|
+
const parsed = parseTsLiteralValue(source, 0, constants).value;
|
|
1163
|
+
if (typeof parsed === "string") return parsed;
|
|
1164
|
+
|
|
1165
|
+
return resolveRequireActionSpecName(src, source);
|
|
882
1166
|
}
|
|
883
1167
|
|
|
884
1168
|
function isRecordValue(value) {
|
|
@@ -1176,13 +1460,6 @@ function main() {
|
|
|
1176
1460
|
["slippage", { description: "Max slippage percentage.", example: 1 }],
|
|
1177
1461
|
]);
|
|
1178
1462
|
|
|
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
1463
|
function inferParamType(objText, key) {
|
|
1187
1464
|
const re = new RegExp(`state\\?\\.${key}\\s+as\\s+([A-Za-z0-9_]+)`, "g");
|
|
1188
1465
|
const m = re.exec(objText);
|
|
@@ -1206,7 +1483,7 @@ function main() {
|
|
|
1206
1483
|
const type = inferParamType(objText, key);
|
|
1207
1484
|
const known = commonParamDocs.get(key);
|
|
1208
1485
|
const description =
|
|
1209
|
-
known?.description ?? `The ${
|
|
1486
|
+
known?.description ?? `The ${humanizeParamKey(key)} to use.`;
|
|
1210
1487
|
const example =
|
|
1211
1488
|
known?.example ??
|
|
1212
1489
|
(type === "boolean" ? false : type === "number" ? 1 : "example");
|
|
@@ -1269,20 +1546,25 @@ function main() {
|
|
|
1269
1546
|
const actionDocsByName = new Map();
|
|
1270
1547
|
|
|
1271
1548
|
for (const filePath of tsFiles) {
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
) {
|
|
1277
|
-
continue;
|
|
1549
|
+
if (process.env.DEBUG_ACTION_SPEC) {
|
|
1550
|
+
console.error(
|
|
1551
|
+
`[generate-plugin-action-spec] ${path.relative(REPO_ROOT, filePath)}`,
|
|
1552
|
+
);
|
|
1278
1553
|
}
|
|
1279
1554
|
const src = readText(filePath);
|
|
1280
1555
|
if (!src.includes(": Action")) continue;
|
|
1556
|
+
const constants = extractConstLiterals(src);
|
|
1281
1557
|
|
|
1282
1558
|
const objects = extractActionObjects(filePath, src);
|
|
1283
1559
|
for (const obj of objects) {
|
|
1284
|
-
const name =
|
|
1560
|
+
const name = extractResolvedStringProp(
|
|
1561
|
+
obj.objectText,
|
|
1562
|
+
"name",
|
|
1563
|
+
constants,
|
|
1564
|
+
src,
|
|
1565
|
+
);
|
|
1285
1566
|
if (!name) continue;
|
|
1567
|
+
if (!isRegisteredActionObject(filePath, obj.exportName)) continue;
|
|
1286
1568
|
if (RETIRED_IMPLEMENTATION_ONLY_ACTIONS.has(name)) continue;
|
|
1287
1569
|
if (coreActionNames.has(name)) continue;
|
|
1288
1570
|
const description = expandDescriptionTemplateLiterals(
|
|
@@ -1293,15 +1575,16 @@ function main() {
|
|
|
1293
1575
|
obj.objectText,
|
|
1294
1576
|
"descriptionCompressed",
|
|
1295
1577
|
);
|
|
1296
|
-
const similes = extractTopLevelStringArrayProp(
|
|
1578
|
+
const similes = extractTopLevelStringArrayProp(
|
|
1579
|
+
obj.objectText,
|
|
1580
|
+
"similes",
|
|
1581
|
+
).filter((simile) => !RETIRED_IMPLEMENTATION_ONLY_ACTIONS.has(simile));
|
|
1297
1582
|
const explicitParameters = sanitizeParameters(
|
|
1298
|
-
extractTopLevelLiteralProp(obj.objectText, "parameters"),
|
|
1583
|
+
extractTopLevelLiteralProp(obj.objectText, "parameters", constants),
|
|
1299
1584
|
);
|
|
1300
1585
|
const descriptionParameters = inferParametersFromDescription(description);
|
|
1301
1586
|
const jsonTemplateParameters =
|
|
1302
|
-
explicitParameters.length === 0 &&
|
|
1303
|
-
descriptionParameters.length === 0 &&
|
|
1304
|
-
(name.endsWith("") || description.toLowerCase().includes("router"))
|
|
1587
|
+
explicitParameters.length === 0 && descriptionParameters.length === 0
|
|
1305
1588
|
? inferParametersFromJsonTemplate(src)
|
|
1306
1589
|
: [];
|
|
1307
1590
|
const parameters =
|
|
@@ -1314,7 +1597,6 @@ function main() {
|
|
|
1314
1597
|
: inferParameters(obj.objectText);
|
|
1315
1598
|
const exampleCalls = buildExampleCallForAction(name, parameters);
|
|
1316
1599
|
|
|
1317
|
-
// Do not overwrite existing entries; prefer the first seen (stable ordering).
|
|
1318
1600
|
if (!actionDocsByName.has(name)) {
|
|
1319
1601
|
actionDocsByName.set(name, {
|
|
1320
1602
|
name,
|
|
@@ -1351,7 +1633,7 @@ function main() {
|
|
|
1351
1633
|
|
|
1352
1634
|
const outRoot = { version, actions };
|
|
1353
1635
|
|
|
1354
|
-
|
|
1636
|
+
ensureDirectory(path.dirname(OUTPUT_PATH));
|
|
1355
1637
|
fs.writeFileSync(OUTPUT_PATH, `${JSON.stringify(outRoot, null, 2)}\n`);
|
|
1356
1638
|
console.log(
|
|
1357
1639
|
`Wrote ${actions.length} plugin actions to ${path.relative(REPO_ROOT, OUTPUT_PATH)}`,
|