ai-prompt-framework-helper 1.0.0
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 +130 -0
- package/ai-prompt.js +380 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marcos Castro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# AI Prompt Framework Helper — CLI
|
|
2
|
+
|
|
3
|
+
> Interactive terminal assistant to build structured AI prompts using **APE**, **CO-STAR**, **RISEN** and **RTCCF** frameworks. Works with any AI CLI — Claude, Gemini, llm, aichat, and more.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/ai-prompt-framework-helper)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g ai-prompt-framework-helper
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# or with other package managers
|
|
18
|
+
pnpm add -g ai-prompt-framework-helper
|
|
19
|
+
yarn global add ai-prompt-framework-helper
|
|
20
|
+
bun add -g ai-prompt-framework-helper
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
No external dependencies. Requires Node.js ≥ 16.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ai-prompt
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The tool will:
|
|
34
|
+
1. Auto-detect any AI CLIs installed on your system
|
|
35
|
+
2. Ask you to choose a framework
|
|
36
|
+
3. Guide you field by field
|
|
37
|
+
4. Show a live preview of the generated prompt
|
|
38
|
+
5. Let you run it directly, copy it, pipe it, or save it
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Pipe the output directly to any AI CLI
|
|
42
|
+
ai-prompt | claude
|
|
43
|
+
ai-prompt | gemini
|
|
44
|
+
ai-prompt | llm
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Frameworks
|
|
50
|
+
|
|
51
|
+
| Framework | Stands for | Best for |
|
|
52
|
+
|-----------|------------|----------|
|
|
53
|
+
| **APE** | Action · Purpose · Expectation | Fast, focused tasks |
|
|
54
|
+
| **CO-STAR** | Context · Objective · Style · Tone · Audience · Response | Copy, emails, content |
|
|
55
|
+
| **RISEN** | Role · Instructions · Steps · End Goal · Narrowing | Workflows, plans, execution |
|
|
56
|
+
| **RTCCF** | Role · Task · Context · Constraints · Format | Full project kickstart |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Demo
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
⬡ AI Prompt Framework Helper · CLI
|
|
64
|
+
by Marcos Castro · marcoscv.github.io
|
|
65
|
+
|
|
66
|
+
✓ Detected AI CLIs: claude gemini
|
|
67
|
+
|
|
68
|
+
Select a framework:
|
|
69
|
+
|
|
70
|
+
[1] APE Action · Purpose · Expectation
|
|
71
|
+
↳ Fastest. Ideal for focused, well-defined tasks.
|
|
72
|
+
|
|
73
|
+
[2] CO-STAR Context · Objective · Style · Tone · Audience · Response
|
|
74
|
+
↳ Best for copy, emails, content, presentations.
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
→ 2
|
|
78
|
+
|
|
79
|
+
── CO-STAR ──────────────────────────────────────────────────────────────
|
|
80
|
+
Context · Objective · Style · Tone · Audience · Response
|
|
81
|
+
↳ Best for copy, emails, content, presentations.
|
|
82
|
+
─────────────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
[C] CONTEXT (required)
|
|
85
|
+
What's the background or situation?
|
|
86
|
+
→ We are launching a B2B SaaS for project management
|
|
87
|
+
|
|
88
|
+
[O] OBJECTIVE (required)
|
|
89
|
+
What outcome do you want to achieve?
|
|
90
|
+
→ Write a landing page that converts visitors into trial sign-ups
|
|
91
|
+
...
|
|
92
|
+
|
|
93
|
+
── Generated Prompt ──────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
C – Context: We are launching a B2B SaaS for project management
|
|
96
|
+
O – Objective: Write a landing page that converts visitors into sign-ups
|
|
97
|
+
S – Style: Conversational, startup-like
|
|
98
|
+
T – Tone: Confident, not salesy
|
|
99
|
+
A – Audience: CTOs and technical leads
|
|
100
|
+
R – Response Format: Hero + 3 benefit sections + CTA, under 300 words
|
|
101
|
+
|
|
102
|
+
─────────────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
What do you want to do?
|
|
105
|
+
|
|
106
|
+
[1] Run with claude (Anthropic Claude)
|
|
107
|
+
[2] Run with gemini (Google Gemini)
|
|
108
|
+
[3] Copy to clipboard
|
|
109
|
+
[4] Print to stdout (pipe-friendly)
|
|
110
|
+
[5] Save to file
|
|
111
|
+
[6] Exit without action
|
|
112
|
+
|
|
113
|
+
→ 1
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Also available as a Chrome Extension
|
|
119
|
+
|
|
120
|
+
Use the same frameworks directly from any text field in your browser — ChatGPT, Gemini, Notion, Gmail, and more.
|
|
121
|
+
|
|
122
|
+
[AI Prompt Framework Helper on Chrome Web Store](https://github.com/marcoscv-work/AI-Prompt-Framework-Helper)
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Author
|
|
127
|
+
|
|
128
|
+
**Marcos Castro** · [marcoscv.github.io](https://marcoscv.github.io) · [marcoscv@gmail.com](mailto:marcoscv@gmail.com)
|
|
129
|
+
|
|
130
|
+
MIT License
|
package/ai-prompt.js
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// ─── AI Prompt Framework Helper — CLI ────────────────────────────────────────
|
|
5
|
+
// Author : Marcos Castro · https://marcoscv.github.io
|
|
6
|
+
// No external dependencies — requires only Node.js ≥ 16
|
|
7
|
+
// Usage : node ai-prompt.js
|
|
8
|
+
// ai-prompt (if installed globally)
|
|
9
|
+
// ai-prompt | claude (pipe output to any CLI)
|
|
10
|
+
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
const { execSync, spawnSync } = require('child_process');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
// ─── ANSI ─────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const A = {
|
|
20
|
+
reset: '\x1b[0m',
|
|
21
|
+
bold: '\x1b[1m',
|
|
22
|
+
dim: '\x1b[2m',
|
|
23
|
+
green: '\x1b[32m',
|
|
24
|
+
blue: '\x1b[34m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
magenta: '\x1b[35m',
|
|
27
|
+
cyan: '\x1b[36m',
|
|
28
|
+
red: '\x1b[31m',
|
|
29
|
+
gray: '\x1b[90m',
|
|
30
|
+
white: '\x1b[97m',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const b = (s) => `${A.bold}${s}${A.reset}`;
|
|
34
|
+
const d = (s) => `${A.dim}${s}${A.reset}`;
|
|
35
|
+
const co = (color, s) => `${A[color]}${s}${A.reset}`;
|
|
36
|
+
|
|
37
|
+
// ─── Frameworks ───────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
const FRAMEWORKS = [
|
|
40
|
+
{
|
|
41
|
+
key: 'APE',
|
|
42
|
+
color: 'green',
|
|
43
|
+
fullName: 'Action · Purpose · Expectation',
|
|
44
|
+
tip: 'Fastest. Ideal for focused, well-defined tasks.',
|
|
45
|
+
fields: [
|
|
46
|
+
{ id: 'action', letter: 'A', label: 'Action', hint: 'What should the AI do?', required: true },
|
|
47
|
+
{ id: 'purpose', letter: 'P', label: 'Purpose', hint: 'Why? What problem does it solve?', required: true },
|
|
48
|
+
{ id: 'expectation', letter: 'E', label: 'Expectation', hint: 'What does a great output look like?', required: true },
|
|
49
|
+
],
|
|
50
|
+
build: (f) => [
|
|
51
|
+
`A – Action: ${f.action}`,
|
|
52
|
+
`P – Purpose: ${f.purpose}`,
|
|
53
|
+
`E – Expectation: ${f.expectation}`,
|
|
54
|
+
].join('\n\n'),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
key: 'CO-STAR',
|
|
58
|
+
color: 'blue',
|
|
59
|
+
fullName: 'Context · Objective · Style · Tone · Audience · Response',
|
|
60
|
+
tip: 'Best for copy, emails, content, presentations.',
|
|
61
|
+
fields: [
|
|
62
|
+
{ id: 'context', letter: 'C', label: 'Context', hint: "What's the background or situation?", required: true },
|
|
63
|
+
{ id: 'objective', letter: 'O', label: 'Objective', hint: 'What outcome do you want to achieve?', required: true },
|
|
64
|
+
{ id: 'style', letter: 'S', label: 'Style', hint: 'Writing style (e.g. conversational, formal)',required: false },
|
|
65
|
+
{ id: 'tone', letter: 'T', label: 'Tone', hint: 'Emotional tone (e.g. confident, friendly)', required: false },
|
|
66
|
+
{ id: 'audience', letter: 'A', label: 'Audience', hint: 'Who will read or receive this?', required: false },
|
|
67
|
+
{ id: 'response', letter: 'R', label: 'Response Format', hint: 'How should the output be structured?', required: true },
|
|
68
|
+
],
|
|
69
|
+
build: (f) => [
|
|
70
|
+
['C – Context', f.context],
|
|
71
|
+
['O – Objective', f.objective],
|
|
72
|
+
['S – Style', f.style],
|
|
73
|
+
['T – Tone', f.tone],
|
|
74
|
+
['A – Audience', f.audience],
|
|
75
|
+
['R – Response Format', f.response],
|
|
76
|
+
].filter(([, v]) => v).map(([k, v]) => `${k}: ${v}`).join('\n\n'),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
key: 'RISEN',
|
|
80
|
+
color: 'yellow',
|
|
81
|
+
fullName: 'Role · Instructions · Steps · End Goal · Narrowing',
|
|
82
|
+
tip: 'Best for workflows, plans, technical execution.',
|
|
83
|
+
fields: [
|
|
84
|
+
{ id: 'role', letter: 'R', label: 'Role', hint: 'What role should the AI embody?', required: true },
|
|
85
|
+
{ id: 'instructions', letter: 'I', label: 'Instructions', hint: 'Specific instructions for the task', required: true },
|
|
86
|
+
{ id: 'steps', letter: 'S', label: 'Steps', hint: 'Sequential steps to follow (optional)', required: false },
|
|
87
|
+
{ id: 'endgoal', letter: 'E', label: 'End Goal', hint: 'What does a successful output look like?', required: true },
|
|
88
|
+
{ id: 'narrowing', letter: 'N', label: 'Narrowing', hint: 'What to exclude, avoid, or keep in scope', required: false },
|
|
89
|
+
],
|
|
90
|
+
build: (f) => [
|
|
91
|
+
['R – Role', f.role],
|
|
92
|
+
['I – Instructions', f.instructions],
|
|
93
|
+
['S – Steps', f.steps],
|
|
94
|
+
['E – End Goal', f.endgoal],
|
|
95
|
+
['N – Narrowing', f.narrowing],
|
|
96
|
+
].filter(([, v]) => v).map(([k, v]) => `${k}: ${v}`).join('\n\n'),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: 'RTCCF',
|
|
100
|
+
color: 'magenta',
|
|
101
|
+
fullName: 'Role · Task · Context · Constraints · Format',
|
|
102
|
+
tip: 'Full project kickstart with maximum structure.',
|
|
103
|
+
fields: [
|
|
104
|
+
{ id: 'role', letter: 'R', label: 'Role', hint: 'What role should the AI assume?', required: true },
|
|
105
|
+
{ id: 'task', letter: 'T', label: 'Task', hint: 'What specific task should it perform?', required: true },
|
|
106
|
+
{ id: 'context', letter: 'C', label: 'Context', hint: 'What does it need to know to do it well?', required: true },
|
|
107
|
+
{ id: 'constraints', letter: 'C', label: 'Constraints', hint: "What can't it do? What limits exist?", required: false },
|
|
108
|
+
{ id: 'format', letter: 'F', label: 'Format', hint: 'How do you want the response delivered?', required: false },
|
|
109
|
+
],
|
|
110
|
+
build: (f) => [
|
|
111
|
+
['R – Role', f.role],
|
|
112
|
+
['T – Task', f.task],
|
|
113
|
+
['C – Context', f.context],
|
|
114
|
+
['C – Constraints', f.constraints],
|
|
115
|
+
['F – Format', f.format],
|
|
116
|
+
].filter(([, v]) => v).map(([k, v]) => `${k}: ${v}`).join('\n\n'),
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
// ─── Detect installed AI CLIs ─────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
const CLI_CATALOG = [
|
|
123
|
+
{ name: 'claude', label: 'Anthropic Claude', args: (p) => [p] },
|
|
124
|
+
{ name: 'gemini', label: 'Google Gemini', args: (p) => [p] },
|
|
125
|
+
{ name: 'llm', label: 'llm (Simon Willison)', args: (p) => [p] },
|
|
126
|
+
{ name: 'aichat', label: 'aichat (multi-model)', args: (p) => [p] },
|
|
127
|
+
{ name: 'gpt', label: 'gpt-cli', args: (p) => [p] },
|
|
128
|
+
{ name: 'sgpt', label: 'ShellGPT', args: (p) => [p] },
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
function detectCLIs() {
|
|
132
|
+
return CLI_CATALOG.filter(({ name }) => {
|
|
133
|
+
try { execSync(`which ${name} 2>/dev/null`, { stdio: 'pipe' }); return true; }
|
|
134
|
+
catch { return false; }
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── Terminal helpers ─────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
const W = Math.min(process.stdout.columns || 72, 80);
|
|
141
|
+
|
|
142
|
+
function line(ch = '─') { return co('gray', ch.repeat(W)); }
|
|
143
|
+
|
|
144
|
+
function box(lines, color = 'cyan') {
|
|
145
|
+
const inner = W - 4;
|
|
146
|
+
const top = co(color, '┌' + '─'.repeat(W - 2) + '┐');
|
|
147
|
+
const bot = co(color, '└' + '─'.repeat(W - 2) + '┘');
|
|
148
|
+
const rows = lines.map(l => co(color, '│') + ' ' + l.padEnd(inner) + ' ' + co(color, '│'));
|
|
149
|
+
return [top, ...rows, bot].join('\n');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function header() {
|
|
153
|
+
const title = '⬡ AI Prompt Framework Helper · CLI';
|
|
154
|
+
const sub = 'by Marcos Castro · marcoscv.github.io';
|
|
155
|
+
const inner = W - 4;
|
|
156
|
+
console.log('\n' + box([
|
|
157
|
+
b(title).padEnd(inner + 9), // +9 for bold codes
|
|
158
|
+
d(sub),
|
|
159
|
+
], 'cyan'));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function sectionHeader(fw) {
|
|
163
|
+
console.log('\n' + line());
|
|
164
|
+
console.log(` ${b(co(fw.color, fw.key))} ${d(fw.fullName)}`);
|
|
165
|
+
console.log(` ${d('↳ ' + fw.tip)}`);
|
|
166
|
+
console.log(line() + '\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function showPreview(prompt) {
|
|
170
|
+
console.log('\n' + line());
|
|
171
|
+
console.log(b(' Generated Prompt'));
|
|
172
|
+
console.log(line() + '\n');
|
|
173
|
+
// indent each line
|
|
174
|
+
prompt.split('\n').forEach(l => {
|
|
175
|
+
if (l.startsWith(' ')) {
|
|
176
|
+
console.log(co('gray', ' ' + l.trimStart()));
|
|
177
|
+
} else if (l.match(/^[A-Z] [–—]/)) {
|
|
178
|
+
const [key, ...rest] = l.split(': ');
|
|
179
|
+
console.log(b(co('cyan', key)) + ': ' + rest.join(': '));
|
|
180
|
+
} else {
|
|
181
|
+
console.log(l);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
console.log('\n' + line());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ─── readline ─────────────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
const rl = readline.createInterface({
|
|
190
|
+
input: process.stdin,
|
|
191
|
+
output: process.stdout,
|
|
192
|
+
terminal: true,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
function ask(question, defaultVal = '') {
|
|
196
|
+
const def = defaultVal ? d(` [${defaultVal}]`) : '';
|
|
197
|
+
return new Promise(resolve => {
|
|
198
|
+
rl.question(` ${question}${def}\n ${co('cyan', '→')} `, (ans) => {
|
|
199
|
+
resolve(ans.trim() || defaultVal);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function askNum(prompt, min, max) {
|
|
205
|
+
return new Promise(resolve => {
|
|
206
|
+
const ask = () => {
|
|
207
|
+
rl.question(`\n ${prompt}\n ${co('cyan', '→')} `, (ans) => {
|
|
208
|
+
const n = parseInt(ans.trim(), 10);
|
|
209
|
+
if (!isNaN(n) && n >= min && n <= max) resolve(n);
|
|
210
|
+
else {
|
|
211
|
+
console.log(co('red', ` Please enter a number between ${min} and ${max}.`));
|
|
212
|
+
ask();
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
ask();
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─── Steps ────────────────────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
async function selectFramework() {
|
|
223
|
+
console.log('\n' + b(' Select a framework:\n'));
|
|
224
|
+
FRAMEWORKS.forEach((fw, i) => {
|
|
225
|
+
console.log(
|
|
226
|
+
` ${b(co(fw.color, `[${i + 1}]`))} ${b(fw.key.padEnd(8))} ${d(fw.fullName)}`
|
|
227
|
+
);
|
|
228
|
+
console.log(` ${d('↳ ' + fw.tip)}\n`);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const n = await askNum(`Choose [1–${FRAMEWORKS.length}]:`, 1, FRAMEWORKS.length);
|
|
232
|
+
return FRAMEWORKS[n - 1];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function fillFields(fw) {
|
|
236
|
+
sectionHeader(fw);
|
|
237
|
+
const values = {};
|
|
238
|
+
|
|
239
|
+
for (const field of fw.fields) {
|
|
240
|
+
const badge = co(fw.color, `[${field.letter}]`);
|
|
241
|
+
const label = b(field.label.toUpperCase());
|
|
242
|
+
const required = field.required ? co('red', '(required)') : d('(optional — press Enter to skip)');
|
|
243
|
+
|
|
244
|
+
console.log(` ${badge} ${label} ${required}`);
|
|
245
|
+
console.log(` ${d(field.hint)}\n`);
|
|
246
|
+
|
|
247
|
+
let value = '';
|
|
248
|
+
while (true) {
|
|
249
|
+
value = await ask('');
|
|
250
|
+
if (value || !field.required) break;
|
|
251
|
+
console.log(co('red', ` This field is required.\n`));
|
|
252
|
+
}
|
|
253
|
+
values[field.id] = value;
|
|
254
|
+
console.log();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return values;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function selectAction(prompt, detectedCLIs) {
|
|
261
|
+
const options = [];
|
|
262
|
+
|
|
263
|
+
// AI CLIs first
|
|
264
|
+
detectedCLIs.forEach(cli => {
|
|
265
|
+
options.push({
|
|
266
|
+
label: `Run with ${b(co('green', cli.name))} ${d('(' + cli.label + ')')}`,
|
|
267
|
+
action: () => runWithCLI(cli, prompt),
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Always-present options
|
|
272
|
+
options.push({ label: `${b('Copy')} to clipboard`, action: () => copyToClipboard(prompt) });
|
|
273
|
+
options.push({ label: `${b('Print')} to stdout ${d('(pipe-friendly)')}`, action: () => printStdout(prompt) });
|
|
274
|
+
options.push({ label: `${b('Save')} to file`, action: () => saveToFile(prompt) });
|
|
275
|
+
options.push({ label: `${d('Exit without action')}`, action: () => {} });
|
|
276
|
+
|
|
277
|
+
console.log('\n' + b(' What do you want to do?\n'));
|
|
278
|
+
options.forEach((opt, i) => {
|
|
279
|
+
console.log(` ${co('cyan', `[${i + 1}]`)} ${opt.label}`);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const n = await askNum(`Choose [1–${options.length}]:`, 1, options.length);
|
|
283
|
+
console.log();
|
|
284
|
+
await options[n - 1].action();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ─── Actions ──────────────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
function runWithCLI(cli, prompt) {
|
|
290
|
+
console.log(co('green', ` Running: ${cli.name} …\n`));
|
|
291
|
+
rl.close();
|
|
292
|
+
|
|
293
|
+
const result = spawnSync(cli.name, cli.args(prompt), {
|
|
294
|
+
stdio: 'inherit',
|
|
295
|
+
shell: false,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (result.error) {
|
|
299
|
+
console.error(co('red', ` Error running ${cli.name}: ${result.error.message}`));
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
process.exit(result.status ?? 0);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function copyToClipboard(prompt) {
|
|
306
|
+
const platform = os.platform();
|
|
307
|
+
try {
|
|
308
|
+
if (platform === 'darwin') {
|
|
309
|
+
execSync('pbcopy', { input: prompt });
|
|
310
|
+
console.log(co('green', ' ✓ Copied to clipboard.'));
|
|
311
|
+
} else if (platform === 'linux') {
|
|
312
|
+
// Try xclip, then xsel
|
|
313
|
+
try {
|
|
314
|
+
execSync('xclip -selection clipboard', { input: prompt });
|
|
315
|
+
} catch {
|
|
316
|
+
execSync('xsel --clipboard --input', { input: prompt });
|
|
317
|
+
}
|
|
318
|
+
console.log(co('green', ' ✓ Copied to clipboard.'));
|
|
319
|
+
} else if (platform === 'win32') {
|
|
320
|
+
execSync('clip', { input: prompt, shell: true });
|
|
321
|
+
console.log(co('green', ' ✓ Copied to clipboard.'));
|
|
322
|
+
}
|
|
323
|
+
} catch {
|
|
324
|
+
console.log(co('yellow', ' Could not copy automatically. Here is the prompt:\n'));
|
|
325
|
+
printStdout(prompt);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function printStdout(prompt) {
|
|
330
|
+
// If stdout is a TTY we show it styled; if piped we print clean
|
|
331
|
+
if (process.stdout.isTTY) {
|
|
332
|
+
showPreview(prompt);
|
|
333
|
+
} else {
|
|
334
|
+
process.stdout.write(prompt + '\n');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function saveToFile(prompt) {
|
|
339
|
+
const defaultPath = path.join(os.homedir(), 'ai-prompt.txt');
|
|
340
|
+
const dest = await ask(`Save to path:`, defaultPath);
|
|
341
|
+
try {
|
|
342
|
+
fs.writeFileSync(dest, prompt, 'utf8');
|
|
343
|
+
console.log(co('green', ` ✓ Saved to ${dest}`));
|
|
344
|
+
} catch (e) {
|
|
345
|
+
console.log(co('red', ` Error saving: ${e.message}`));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
async function main() {
|
|
352
|
+
// Detect before entering interactive mode
|
|
353
|
+
const detectedCLIs = detectCLIs();
|
|
354
|
+
|
|
355
|
+
console.clear();
|
|
356
|
+
header();
|
|
357
|
+
|
|
358
|
+
if (detectedCLIs.length > 0) {
|
|
359
|
+
const names = detectedCLIs.map(c => co(c.color || 'green', c.name)).join(', ');
|
|
360
|
+
console.log(`\n ${co('green', '✓')} Detected AI CLIs: ${detectedCLIs.map(c => b(c.name)).join(' ')}`);
|
|
361
|
+
} else {
|
|
362
|
+
console.log(`\n ${d('No AI CLIs detected. Output options: clipboard, stdout, file.')}`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const fw = await selectFramework();
|
|
366
|
+
const values = await fillFields(fw);
|
|
367
|
+
const prompt = fw.build(values);
|
|
368
|
+
|
|
369
|
+
showPreview(prompt);
|
|
370
|
+
|
|
371
|
+
await selectAction(prompt, detectedCLIs);
|
|
372
|
+
|
|
373
|
+
rl.close();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
main().catch((err) => {
|
|
377
|
+
console.error(co('red', '\n Error: ' + err.message));
|
|
378
|
+
rl.close();
|
|
379
|
+
process.exit(1);
|
|
380
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-prompt-framework-helper",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interactive CLI to build structured AI prompts using APE, CO-STAR, RISEN and RTCCF frameworks. Works with Claude, Gemini, and any AI CLI.",
|
|
5
|
+
"author": "Marcos Castro <marcoscv@gmail.com> (https://marcoscv.github.io)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/marcoscv-work/AI-Prompt-Framework-Helper#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/marcoscv-work/AI-Prompt-Framework-Helper.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/marcoscv-work/AI-Prompt-Framework-Helper/issues",
|
|
14
|
+
"email": "marcoscv@gmail.com"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"ai-prompt": "./ai-prompt.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"ai-prompt.js",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=16"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"ai", "prompt", "prompt-engineering", "cli", "terminal",
|
|
29
|
+
"ape", "costar", "co-star", "risen", "rtccf",
|
|
30
|
+
"claude", "gemini", "chatgpt", "llm", "openai",
|
|
31
|
+
"framework", "interactive", "assistant"
|
|
32
|
+
]
|
|
33
|
+
}
|