@elizaos/prompts 2.0.0-alpha.98 → 2.0.11-beta.7

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/README.md CHANGED
@@ -1,63 +1,52 @@
1
1
  # @elizaos/prompts
2
2
 
3
- Shared prompt templates for elizaOS across TypeScript, Python, and Rust runtimes.
3
+ Shared prompt templates and action specs for elizaOS.
4
4
 
5
5
  ## Overview
6
6
 
7
- This package provides a single source of truth for all prompt templates used by elizaOS agents. Prompts are stored as `.txt` files and generated into native formats for each language.
7
+ This package is the single source of truth for prompt templates used by the runtime. Prompts are authored directly in `src/index.ts`.
8
8
 
9
9
  ## Structure
10
10
 
11
11
  ```
12
12
  packages/prompts/
13
- ├── prompts/ # Source prompt templates (.txt files)
14
- ├── reply.txt
15
- ├── choose_option.txt
16
- │ ├── image_generation.txt
17
- │ └── ...
18
- ├── scripts/ # Build scripts
19
- │ └── generate.js # Generates native code from prompts
20
- ├── dist/ # Generated output
21
- │ ├── typescript/ # TypeScript exports
22
- │ ├── python/ # Python module
23
- │ └── rust/ # Rust source
24
- └── package.json
13
+ ├── src/
14
+ └── index.ts # TypeScript prompt template exports
15
+ ├── specs/ # Merged action/provider specs (JSON) + generated plugins.generated.json
16
+ └── scripts/ # Spec + docs generators
17
+ ├── generate-action-docs.js
18
+ ├── generate-plugin-action-spec.js
19
+ ├── prompt-compression.js
20
+ └── check-secrets.js
25
21
  ```
26
22
 
27
23
  ## Template Syntax
28
24
 
29
- All prompts use **Handlebars-style** template variables:
25
+ Prompts use Handlebars-style variables:
30
26
 
31
- - `{{variableName}}` - Simple variable substitution
32
- - `{{#each items}}...{{/each}}` - Iteration over arrays
33
- - `{{#if condition}}...{{/if}}` - Conditional blocks
27
+ - `{{variableName}}` - simple variable substitution
28
+ - `{{#each items}}...{{/each}}` - iteration
29
+ - `{{#if condition}}...{{/if}}` - conditional
34
30
 
35
- ### Variable Naming Convention
31
+ Use camelCase for variables (`{{agentName}}`, `{{providers}}`, `{{recentMessages}}`).
36
32
 
37
- Use camelCase for all template variables to ensure consistency across languages:
33
+ ## Plugin-local `prompts/*.json` (under `plugins/**`)
38
34
 
39
- - `{{agentName}}` - The agent's name
40
- - `{{providers}}` - Provider context
41
- - `{{recentMessages}}` - Recent conversation messages
35
+ Some plugins keep **hand-edited** `actions.json` / `evaluators.json` / `providers.json` next to their source. Those files feed **per-plugin codegen** (for example `generated/specs/spec-helpers.ts` via each plugin’s own workflow). They are **not** inputs to `scripts/generate-plugin-action-spec.js`, which instead scans `plugins/**/*.ts` for `export const …: Action` blocks and writes `specs/actions/plugins.generated.json`.
42
36
 
43
37
  ## Building
44
38
 
45
39
  ```bash
46
- # Build all targets
47
- npm run build
48
-
49
- # Build specific target
50
- npm run build:typescript
51
- npm run build:python
52
- npm run build:rust
40
+ # Generate plugin action spec + action docs
41
+ bun run build
53
42
  ```
54
43
 
55
44
  ## Usage
56
45
 
57
- ### TypeScript
46
+ Runtime code imports the templates through `@elizaos/core`, which re-exports them and provides `composePrompt` to fill the `{{...}}` placeholders:
58
47
 
59
48
  ```typescript
60
- import { REPLY_TEMPLATE, CHOOSE_OPTION_TEMPLATE } from "@elizaos/prompts";
49
+ import { REPLY_TEMPLATE, composePrompt } from "@elizaos/core";
61
50
 
62
51
  const prompt = composePrompt({
63
52
  state: { agentName: "Alice" },
@@ -65,78 +54,31 @@ const prompt = composePrompt({
65
54
  });
66
55
  ```
67
56
 
68
- ### Python
69
-
70
- ```python
71
- from elizaos.prompts import REPLY_TEMPLATE, CHOOSE_OPTION_TEMPLATE
72
-
73
- prompt = compose_prompt(state={'agentName': 'Alice'}, template=REPLY_TEMPLATE)
74
- ```
75
-
76
- ### Rust
77
-
78
- ```rust
79
- use elizaos_prompts::{REPLY_TEMPLATE, CHOOSE_OPTION_TEMPLATE};
80
-
81
- let prompt = compose_prompt(&state, REPLY_TEMPLATE);
82
- ```
57
+ Import directly from `@elizaos/prompts` only inside this package's tooling and tests.
83
58
 
84
59
  ## Adding New Prompts
85
60
 
86
- 1. Create a new `.txt` file in `prompts/` directory
87
- 2. Name the file using snake_case (e.g., `my_new_action.txt`)
88
- 3. Run `npm run build` to generate native code
89
- 4. The prompt will be exported as `MY_NEW_ACTION_TEMPLATE` in all languages
90
-
91
- ## Plugin Prompts
92
-
93
- Plugins can use the same prompt system! See [README-PLUGIN-PROMPTS.md](./README-PLUGIN-PROMPTS.md) for details on how to set up prompts in your plugin.
94
-
95
- The `scripts/generate-plugin-prompts.js` utility can be used by any plugin to generate TypeScript, Python, and Rust exports from `.txt` prompt templates.
61
+ 1. Add a `camelCaseTemplate` string export in `src/index.ts`.
62
+ 2. Add the paired `UPPER_SNAKE_CASE_TEMPLATE` export.
96
63
 
97
64
  ## Template Guidelines
98
65
 
99
- 1. **Start with a task description** - Begin prompts with `# Task:` to clearly state the objective
100
- 2. **Include providers placeholder** - Use `{{providers}}` where provider context should be injected
101
- 3. **Use XML output format** - Standardize on XML response format for consistent parsing
102
- 4. **Add clear instructions** - Include explicit instructions for the LLM
103
- 5. **End with output format** - Always specify the expected output format
66
+ 1. **Start with a task description** begin prompts with `# Task:` to state the objective.
67
+ 2. **Include providers placeholder** use `{{providers}}` where provider context should be injected.
68
+ 3. **Use JSON output format** standardize on JSON response format for consistent parsing.
69
+ 4. **Add clear instructions** explicit instructions for the LLM.
70
+ 5. **End with output format** always specify the expected output format.
104
71
 
105
- Example:
72
+ ## Security & Privacy
106
73
 
107
- ```txt
108
- # Task: Generate dialog for the character {{agentName}}.
109
-
110
- {{providers}}
111
-
112
- # Instructions: Write the next message for {{agentName}}.
113
-
114
- Respond using XML format like this:
115
- <response>
116
- <thought>Your thought here</thought>
117
- <text>Your message here</text>
118
- </response>
119
-
120
- IMPORTANT: Your response must ONLY contain the <response></response> XML block above.
121
- ```
122
-
123
- ## Security & Privacy Guidance (SOC2-aligned)
124
-
125
- - **Do not embed real secrets** in prompt templates. Prompts are source-controlled and often distributed.
74
+ - **Do not embed real secrets** in prompt templates. Prompts are source-controlled.
126
75
  - **Avoid including PII** (emails, phone numbers, addresses, IDs) in templates or examples.
127
- - Prefer placeholders (e.g., `{{apiKey}}`, `{{userEmail}}`) and ensure the runtime injects only the minimum needed.
76
+ - Prefer placeholders (e.g., `{{apiKey}}`, `{{userEmail}}`) and inject only the minimum needed at runtime.
128
77
 
129
78
  ### Secret scan
130
79
 
131
- This package includes a conservative scanner that flags prompt templates containing strings that strongly resemble real credentials (or private key material).
132
-
133
- Run:
134
-
135
80
  ```bash
136
- npm run check:secrets
81
+ bun run check:secrets
137
82
  ```
138
83
 
139
- It scans:
140
-
141
- - `packages/prompts/prompts/**/*.txt`
142
- - `plugins/**/prompts/**/*.txt`
84
+ Scans `packages/prompts/src/**/*.ts`, plugin prompt TS modules (paths matching `prompts/**/*.ts`, `workflow-prompts/**/*.ts`, etc.), and a few explicit files — see `scripts/check-secrets.js`.
package/package.json CHANGED
@@ -1,34 +1,37 @@
1
1
  {
2
2
  "name": "@elizaos/prompts",
3
3
  "private": false,
4
- "version": "2.0.0-alpha.98",
4
+ "version": "2.0.11-beta.7",
5
5
  "description": "Shared prompt templates for elizaOS across TypeScript, Python, and Rust",
6
6
  "type": "module",
7
- "main": "./dist/typescript/index.ts",
8
- "types": "./dist/typescript/index.ts",
7
+ "main": "./src/index.ts",
8
+ "types": "./src/index.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./dist/typescript/index.ts",
12
- "import": "./dist/typescript/index.ts"
11
+ "types": "./src/index.ts",
12
+ "import": "./src/index.ts"
13
13
  },
14
- "./python": "./dist/python/prompts.py",
15
- "./rust": "./dist/rust/prompts.rs"
14
+ "./*.css": "./dist/*.css",
15
+ "./*": {
16
+ "types": "./dist/*.d.ts",
17
+ "import": "./dist/*.js",
18
+ "default": "./dist/*.js"
19
+ }
16
20
  },
17
21
  "scripts": {
18
- "build": "node scripts/generate.js && node scripts/generate-plugin-action-spec.js && node scripts/generate-action-docs.js",
19
- "build:typescript": "node scripts/generate.js --target typescript",
20
- "build:python": "node scripts/generate.js --target python",
21
- "build:rust": "node scripts/generate.js --target rust",
22
+ "build": "node scripts/generate-plugin-action-spec.js && bunx @biomejs/biome format --write specs/actions/plugins.generated.json && node scripts/generate-action-docs.js",
22
23
  "build:action-docs": "node scripts/generate-action-docs.js",
23
- "build:plugin-action-spec": "node scripts/generate-plugin-action-spec.js",
24
+ "build:plugin-action-spec": "node scripts/generate-plugin-action-spec.js && bunx @biomejs/biome format --write specs/actions/plugins.generated.json",
24
25
  "check:secrets": "node scripts/check-secrets.js",
25
26
  "clean": "rm -rf dist",
26
27
  "lint": "bunx @biomejs/biome check --write .",
27
28
  "lint:check": "bunx @biomejs/biome check .",
28
- "typecheck": "echo 'No TypeScript source files to check'"
29
+ "format:check": "bunx @biomejs/biome format .",
30
+ "typecheck": "echo 'No TypeScript source files to check'",
31
+ "test": "bun test ./test"
29
32
  },
30
33
  "files": [
31
- "prompts/",
34
+ "src/",
32
35
  "dist/",
33
36
  "scripts/"
34
37
  ],
@@ -43,5 +46,5 @@
43
46
  "publishConfig": {
44
47
  "access": "public"
45
48
  },
46
- "gitHead": "d8b08e678cc0ac9a64ff2cb22e1387aacfdcba6a"
49
+ "gitHead": "cdbc876f793d96073d7eb0d09715a031ce0cd32e"
47
50
  }
@@ -1,15 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Secret/credential scanner for prompt templates.
4
- *
5
- * This is intentionally conservative: it only fails on patterns that strongly
6
- * resemble real credentials (or private key material) to avoid false positives.
7
- *
8
- * Scans:
9
- * - all .txt files under packages/prompts/prompts/
10
- * - all .txt files under plugin prompt folders (if plugins/ exists)
11
- */
12
-
13
2
  import fs from "node:fs/promises";
14
3
  import path from "node:path";
15
4
  import { fileURLToPath } from "node:url";
@@ -20,47 +9,102 @@ const __dirname = path.dirname(__filename);
20
9
  const PROMPTS_PKG_DIR = path.resolve(__dirname, "..");
21
10
  const REPO_ROOT = path.resolve(PROMPTS_PKG_DIR, "..", "..");
22
11
 
23
- const PROMPTS_DIR = path.join(PROMPTS_PKG_DIR, "prompts");
24
- const PLUGINS_DIR = path.join(REPO_ROOT, "plugins");
12
+ const PROMPT_SCAN_TS_ROOTS = [
13
+ "packages/prompts/src",
14
+ "packages/core/src",
15
+ "plugins",
16
+ ];
17
+
18
+ const PROMPT_SCAN_FILES = [
19
+ "packages/core/src/prompts.ts",
20
+ "packages/core/src/services/message.ts",
21
+ "plugins/plugin-music/src/actions/music-player-action-docs.ts",
22
+ ];
23
+
24
+ const PROMPT_SCAN_FILE_PATTERNS = [
25
+ /(^|\/)prompts?\.ts$/,
26
+ /(^|\/)prompts\/[^/]+\.ts$/,
27
+ /(^|\/)workflow-prompts\/[^/]+\.ts$/,
28
+ /(^|\/)templates?\.ts$/,
29
+ ];
30
+
31
+ const TEST_SOURCE_PATH_PATTERN =
32
+ /(^|\/)(__tests__|tests?|e2e)(\/|$)|\.(test|spec)\.tsx?$/;
33
+
34
+ const SKIP_DIR_NAMES = new Set([
35
+ ".git",
36
+ ".turbo",
37
+ ".next",
38
+ "build",
39
+ "coverage",
40
+ "dist",
41
+ "node_modules",
42
+ "generated",
43
+ ]);
25
44
 
26
45
  /**
27
- * @param {string} dir
46
+ * @param {string} root
47
+ * @param {(absPath: string, relPath: string) => boolean} predicate
28
48
  * @returns {Promise<string[]>}
29
49
  */
30
- async function listPromptTxtFiles(dir) {
50
+ export async function walkFiles(root, predicate) {
31
51
  /** @type {string[]} */
32
52
  const out = [];
33
53
 
34
54
  /** @param {string} current */
35
55
  async function walk(current) {
36
- const entries = await fs.readdir(current, { withFileTypes: true });
56
+ /** @type {Awaited<ReturnType<typeof fs.readdir>>} */
57
+ let entries;
58
+ try {
59
+ entries = await fs.readdir(current, { withFileTypes: true });
60
+ } catch {
61
+ return;
62
+ }
37
63
  for (const entry of entries) {
38
64
  const full = path.join(current, entry.name);
39
65
  if (entry.isDirectory()) {
66
+ if (SKIP_DIR_NAMES.has(entry.name)) continue;
40
67
  await walk(full);
41
68
  continue;
42
69
  }
43
- if (entry.isFile() && entry.name.endsWith(".txt")) {
44
- out.push(full);
45
- }
70
+ if (!entry.isFile()) continue;
71
+ const rel = path.relative(REPO_ROOT, full);
72
+ if (predicate(full, rel)) out.push(full);
46
73
  }
47
74
  }
48
75
 
49
- try {
50
- await walk(dir);
51
- } catch (_e) {
52
- // Directory might not exist (e.g., plugins/ in minimal checkouts).
53
- }
54
-
76
+ await walk(root);
55
77
  return out;
56
78
  }
57
79
 
80
+ /**
81
+ * @returns {Promise<string[]>}
82
+ */
83
+ export async function listPromptTsFiles() {
84
+ /** @type {Set<string>} */
85
+ const set = new Set();
86
+ for (const file of PROMPT_SCAN_FILES) {
87
+ set.add(path.join(REPO_ROOT, file));
88
+ }
89
+ for (const root of PROMPT_SCAN_TS_ROOTS) {
90
+ const absRoot = path.join(REPO_ROOT, root);
91
+ const files = await walkFiles(absRoot, (_abs, rel) => {
92
+ if (!rel.endsWith(".ts") && !rel.endsWith(".tsx")) return false;
93
+ if (TEST_SOURCE_PATH_PATTERN.test(rel)) return false;
94
+ if (/(^|\/)generated\//.test(rel)) return false;
95
+ return PROMPT_SCAN_FILE_PATTERNS.some((p) => p.test(rel));
96
+ });
97
+ for (const f of files) set.add(f);
98
+ }
99
+ return [...set].sort();
100
+ }
101
+
58
102
  /**
59
103
  * @param {string} filePath
60
104
  * @param {string} content
61
105
  * @returns {{errors: string[], warnings: string[]}}
62
106
  */
63
- function scanContent(filePath, content) {
107
+ export function scanContent(filePath, content) {
64
108
  /** @type {string[]} */
65
109
  const errors = [];
66
110
  /** @type {string[]} */
@@ -115,38 +159,28 @@ function scanContent(filePath, content) {
115
159
 
116
160
  for (let i = 0; i < lines.length; i++) {
117
161
  const line = lines[i];
162
+ let hasError = false;
163
+ /** @type {string[]} */
164
+ const lineWarnings = [];
118
165
  for (const rule of rules) {
119
166
  if (rule.re.test(line)) {
120
167
  const msg = `${filePath}:${i + 1} ${rule.name}: ${line.trim()}`;
121
- if (rule.severity === "error") errors.push(msg);
122
- else warnings.push(msg);
168
+ if (rule.severity === "error") {
169
+ errors.push(msg);
170
+ hasError = true;
171
+ } else {
172
+ lineWarnings.push(msg);
173
+ }
123
174
  }
124
175
  }
176
+ if (!hasError) warnings.push(...lineWarnings);
125
177
  }
126
178
 
127
179
  return { errors, warnings };
128
180
  }
129
181
 
130
182
  async function main() {
131
- const promptFiles = await listPromptTxtFiles(PROMPTS_DIR);
132
-
133
- /** @type {string[]} */
134
- let pluginPromptFiles = [];
135
- try {
136
- const pluginEntries = await fs.readdir(PLUGINS_DIR, {
137
- withFileTypes: true,
138
- });
139
- for (const entry of pluginEntries) {
140
- if (!entry.isDirectory()) continue;
141
- const candidate = path.join(PLUGINS_DIR, entry.name, "prompts");
142
- const files = await listPromptTxtFiles(candidate);
143
- pluginPromptFiles = pluginPromptFiles.concat(files);
144
- }
145
- } catch {
146
- // plugins directory missing; ok.
147
- }
148
-
149
- const allFiles = [...promptFiles, ...pluginPromptFiles].sort();
183
+ const allFiles = await listPromptTsFiles();
150
184
 
151
185
  /** @type {string[]} */
152
186
  const errors = [];
@@ -154,34 +188,36 @@ async function main() {
154
188
  const warnings = [];
155
189
 
156
190
  for (const file of allFiles) {
157
- const content = await fs.readFile(file, "utf-8");
191
+ let content;
192
+ try {
193
+ content = await fs.readFile(file, "utf-8");
194
+ } catch {
195
+ continue;
196
+ }
158
197
  const result = scanContent(file, content);
159
198
  errors.push(...result.errors);
160
199
  warnings.push(...result.warnings);
161
200
  }
162
201
 
163
202
  if (warnings.length > 0) {
164
- // eslint-disable-next-line no-console
165
203
  console.warn("\nPrompt secret scan warnings (review recommended):\n");
166
204
  for (const w of warnings) {
167
- // eslint-disable-next-line no-console
168
205
  console.warn(`- ${w}`);
169
206
  }
170
207
  }
171
208
 
172
209
  if (errors.length > 0) {
173
- // eslint-disable-next-line no-console
174
210
  console.error("\nPrompt secret scan errors (must fix):\n");
175
211
  for (const e of errors) {
176
- // eslint-disable-next-line no-console
177
212
  console.error(`- ${e}`);
178
213
  }
179
214
  process.exit(1);
180
215
  }
181
216
  }
182
217
 
183
- main().catch((err) => {
184
- // eslint-disable-next-line no-console
185
- console.error(err);
186
- process.exit(2);
187
- });
218
+ if (process.argv[1] && path.resolve(process.argv[1]) === __filename) {
219
+ main().catch((err) => {
220
+ console.error(err);
221
+ process.exit(2);
222
+ });
223
+ }
@@ -0,0 +1,13 @@
1
+ import fs from "node:fs";
2
+
3
+ export function readText(filePath) {
4
+ return fs.readFileSync(filePath, "utf-8");
5
+ }
6
+
7
+ export function readJson(filePath) {
8
+ return JSON.parse(readText(filePath));
9
+ }
10
+
11
+ export function ensureDirectory(dir) {
12
+ fs.mkdirSync(dir, { recursive: true });
13
+ }