nothumanallowed 14.3.9 → 14.4.1
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/package.json +1 -1
- package/src/constants.mjs +1 -1
- package/src/server/routes/connectors.mjs +233 -26
- package/src/server/routes/webcraft.mjs +13 -6
- package/src/ui-dist/assets/{index-DvUwPsH9.js → index-CqS1_cxK.js} +63 -62
- package/src/ui-dist/assets/index-DQV8GF5r.css +1 -0
- package/src/ui-dist/index.html +2 -2
- package/src/ui-dist/assets/index-C72hElaC.css +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.4.1",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '14.
|
|
8
|
+
export const VERSION = '14.4.1';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -38,6 +38,92 @@ function deleteWorkflow(id) {
|
|
|
38
38
|
if (fs.existsSync(p)) fs.unlinkSync(p);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/** Seed example workflows on first run */
|
|
42
|
+
function seedExamples() {
|
|
43
|
+
ensureDir();
|
|
44
|
+
const marker = path.join(WORKFLOWS_DIR, '.examples-seeded');
|
|
45
|
+
if (fs.existsSync(marker)) return;
|
|
46
|
+
|
|
47
|
+
const examples = [
|
|
48
|
+
{
|
|
49
|
+
id: 'ex_email_digest', name: '📧 Daily Email Digest',
|
|
50
|
+
enabled: false,
|
|
51
|
+
nodes: [
|
|
52
|
+
{ id: 'n1', defId: 'trigger_cron', x: 40, y: 80, config: { schedule: '0 8 * * *' } },
|
|
53
|
+
{ id: 'n2', defId: 'ai_summarize', x: 200, y: 80, config: { prompt: 'Summarize the last 10 unread emails concisely: {{output}}' } },
|
|
54
|
+
{ id: 'n3', defId: 'action_slack', x: 400, y: 40, config: { channel: '#general', text: '📧 Morning Digest:\n{{output}}' } },
|
|
55
|
+
{ id: 'n4', defId: 'action_notify', x: 400, y: 140, config: { message: 'Email digest ready', channel: 'system' } },
|
|
56
|
+
],
|
|
57
|
+
edges: [{ from: 'n1', to: 'n2' }, { from: 'n2', to: 'n3' }, { from: 'n2', to: 'n4' }],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'ex_smart_router', name: '🔀 Smart Email Router',
|
|
61
|
+
enabled: false,
|
|
62
|
+
nodes: [
|
|
63
|
+
{ id: 'n1', defId: 'trigger_email', x: 40, y: 100, config: { filter: 'is:unread' } },
|
|
64
|
+
{ id: 'n2', defId: 'ai_classify', x: 200, y: 100, config: { categories: 'urgent, meeting, newsletter, spam', prompt: 'Classify this email: {{output}}' } },
|
|
65
|
+
{ id: 'n3', defId: 'logic_if', x: 380, y: 100, config: { condition: 'output.includes("urgent")' } },
|
|
66
|
+
{ id: 'n4', defId: 'action_notify', x: 560, y: 40, config: { message: '🚨 Urgent email: {{output}}', channel: 'telegram' } },
|
|
67
|
+
{ id: 'n5', defId: 'action_task', x: 560, y: 160, config: { title: 'Review: {{output}}', priority: 'low' } },
|
|
68
|
+
],
|
|
69
|
+
edges: [
|
|
70
|
+
{ from: 'n1', to: 'n2' }, { from: 'n2', to: 'n3' },
|
|
71
|
+
{ from: 'n3', to: 'n4', fromPort: 'true' },
|
|
72
|
+
{ from: 'n3', to: 'n5', fromPort: 'false' },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'ex_content_pipeline', name: '📝 Content Pipeline',
|
|
77
|
+
enabled: false,
|
|
78
|
+
nodes: [
|
|
79
|
+
{ id: 'n1', defId: 'trigger_manual', x: 40, y: 100, config: { input: 'Write a blog post about AI agents' } },
|
|
80
|
+
{ id: 'n2', defId: 'ai_agent', x: 200, y: 100, config: { agent: 'quill', prompt: 'Write a professional blog post about: {{output}}' } },
|
|
81
|
+
{ id: 'n3', defId: 'ai_translate', x: 400, y: 40, config: { lang: 'Italian', prompt: '{{output}}' } },
|
|
82
|
+
{ id: 'n4', defId: 'action_drive', x: 600, y: 40, config: { name: 'blog-it.md', content: '{{output}}' } },
|
|
83
|
+
{ id: 'n5', defId: 'action_drive', x: 400, y: 160, config: { name: 'blog-en.md', content: '{{output}}' } },
|
|
84
|
+
],
|
|
85
|
+
edges: [{ from: 'n1', to: 'n2' }, { from: 'n2', to: 'n3' }, { from: 'n2', to: 'n5' }, { from: 'n3', to: 'n4' }],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'ex_meeting_prep', name: '📅 Meeting Prep Automation',
|
|
89
|
+
enabled: false,
|
|
90
|
+
nodes: [
|
|
91
|
+
{ id: 'n1', defId: 'trigger_cron', x: 40, y: 100, config: { schedule: '0 7 * * 1-5' } },
|
|
92
|
+
{ id: 'n2', defId: 'ai_agent', x: 200, y: 100, config: { agent: 'herald', prompt: 'List my meetings for today with details' } },
|
|
93
|
+
{ id: 'n3', defId: 'logic_if', x: 380, y: 100, config: { condition: 'output.length > 20' } },
|
|
94
|
+
{ id: 'n4', defId: 'ai_summarize', x: 540, y: 40, config: { prompt: 'Prepare a brief for each meeting. Include talking points: {{output}}' } },
|
|
95
|
+
{ id: 'n5', defId: 'action_email', x: 720, y: 40, config: { to: 'me', subject: '📅 Meeting Prep — Today', body: '{{output}}' } },
|
|
96
|
+
],
|
|
97
|
+
edges: [
|
|
98
|
+
{ from: 'n1', to: 'n2' }, { from: 'n2', to: 'n3' },
|
|
99
|
+
{ from: 'n3', to: 'n4', fromPort: 'true' },
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'ex_web_monitor', name: '🌐 Website Monitor + Alert',
|
|
104
|
+
enabled: false,
|
|
105
|
+
nodes: [
|
|
106
|
+
{ id: 'n1', defId: 'trigger_cron', x: 40, y: 100, config: { schedule: '*/30 * * * *' } },
|
|
107
|
+
{ id: 'n2', defId: 'action_webhook', x: 200, y: 100, config: { url: 'https://nothumanallowed.com', method: 'GET' } },
|
|
108
|
+
{ id: 'n3', defId: 'logic_if', x: 380, y: 100, config: { condition: 'output.includes("Error") || output.length < 100' } },
|
|
109
|
+
{ id: 'n4', defId: 'action_notify', x: 560, y: 40, config: { message: '🚨 Website down or error detected!', channel: 'telegram' } },
|
|
110
|
+
{ id: 'n5', defId: 'logic_error', x: 560, y: 160, config: { retries: '2', fallback: 'Check failed — site may be unreachable' } },
|
|
111
|
+
],
|
|
112
|
+
edges: [
|
|
113
|
+
{ from: 'n1', to: 'n2' }, { from: 'n2', to: 'n3' },
|
|
114
|
+
{ from: 'n3', to: 'n4', fromPort: 'true' },
|
|
115
|
+
{ from: 'n3', to: 'n5', fromPort: 'false' },
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
for (const wf of examples) {
|
|
121
|
+
const p = path.join(WORKFLOWS_DIR, `${wf.id}.json`);
|
|
122
|
+
if (!fs.existsSync(p)) fs.writeFileSync(p, JSON.stringify(wf, null, 2));
|
|
123
|
+
}
|
|
124
|
+
fs.writeFileSync(marker, new Date().toISOString());
|
|
125
|
+
}
|
|
126
|
+
|
|
41
127
|
/** Substitute {{varName}} placeholders in a string using a context map */
|
|
42
128
|
function interpolate(str, ctx) {
|
|
43
129
|
if (typeof str !== 'string') return str;
|
|
@@ -61,24 +147,81 @@ function interpolateObj(obj, ctx) {
|
|
|
61
147
|
async function executeNode(node, nodeDef, ctx, config) {
|
|
62
148
|
const cfg = interpolateObj(node.config ?? {}, ctx);
|
|
63
149
|
|
|
64
|
-
// AI nodes
|
|
150
|
+
// ── AI nodes ──
|
|
65
151
|
if (nodeDef.type === 'ai') {
|
|
152
|
+
if (node.defId === 'ai_code') {
|
|
153
|
+
// Code node — execute JS with `input` variable
|
|
154
|
+
try {
|
|
155
|
+
const fn = new Function('input', 'output', 'ctx', cfg.code || 'return input;');
|
|
156
|
+
const result = fn(ctx.output || '', ctx.output || '', ctx);
|
|
157
|
+
return String(result ?? '');
|
|
158
|
+
} catch (e) { throw new Error(`Code error: ${e.message}`); }
|
|
159
|
+
}
|
|
66
160
|
const prompt = cfg.prompt || `Process this: ${ctx.output || ''}`;
|
|
67
|
-
const
|
|
161
|
+
const agentName = cfg.agent || '';
|
|
162
|
+
const systemPrompt = agentName
|
|
163
|
+
? `You are ${agentName}, a specialist AI agent. Process the input and return a concise result.`
|
|
164
|
+
: 'You are a helpful AI assistant. Process the input and return a concise result.';
|
|
68
165
|
const result = await callLLM(config, systemPrompt, prompt);
|
|
69
166
|
return result?.content || result || '';
|
|
70
167
|
}
|
|
71
168
|
|
|
72
|
-
//
|
|
169
|
+
// ── Logic nodes ──
|
|
170
|
+
if (nodeDef.type === 'logic') {
|
|
171
|
+
if (node.defId === 'logic_if') {
|
|
172
|
+
try {
|
|
173
|
+
const fn = new Function('output', 'input', 'ctx', `return Boolean(${cfg.condition || 'false'});`);
|
|
174
|
+
const result = fn(ctx.output || '', ctx.output || '', ctx);
|
|
175
|
+
return JSON.stringify({ __branch: result ? 'true' : 'false', value: ctx.output || '' });
|
|
176
|
+
} catch (e) { throw new Error(`Condition error: ${e.message}`); }
|
|
177
|
+
}
|
|
178
|
+
if (node.defId === 'logic_switch') {
|
|
179
|
+
const expr = cfg.expression || ctx.output || '';
|
|
180
|
+
const cases = (cfg.cases || '').split(',').map((c) => c.trim());
|
|
181
|
+
const matched = cases.find((c) => expr.toLowerCase().includes(c.toLowerCase()));
|
|
182
|
+
return JSON.stringify({ __branch: matched || 'default', value: ctx.output || '' });
|
|
183
|
+
}
|
|
184
|
+
if (node.defId === 'logic_loop') {
|
|
185
|
+
const sep = cfg.separator === ',' ? ',' : '\n';
|
|
186
|
+
const items = (ctx.output || '').split(sep).filter(Boolean);
|
|
187
|
+
return JSON.stringify({ __loop: true, items, count: items.length });
|
|
188
|
+
}
|
|
189
|
+
if (node.defId === 'logic_merge') {
|
|
190
|
+
// Merge collects from ctx.__mergeInputs (set by the executor)
|
|
191
|
+
const inputs = ctx.__mergeInputs || [ctx.output || ''];
|
|
192
|
+
if (cfg.mode === 'json_array') return JSON.stringify(inputs);
|
|
193
|
+
if (cfg.mode === 'first_non_empty') return inputs.find((i) => i && i.trim()) || '';
|
|
194
|
+
return inputs.join('\n');
|
|
195
|
+
}
|
|
196
|
+
if (node.defId === 'logic_delay') {
|
|
197
|
+
const ms = Math.min(parseInt(cfg.seconds || '1') * 1000, 60_000);
|
|
198
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
199
|
+
return ctx.output || '';
|
|
200
|
+
}
|
|
201
|
+
if (node.defId === 'logic_error') {
|
|
202
|
+
// Error handler wraps previous node execution — handled in runWorkflow
|
|
203
|
+
return ctx.output || cfg.fallback || '';
|
|
204
|
+
}
|
|
205
|
+
return ctx.output || '';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ── Action nodes ──
|
|
73
209
|
const ACTION_MAP = {
|
|
74
|
-
action_email:
|
|
75
|
-
action_slack:
|
|
76
|
-
action_calendar:
|
|
77
|
-
action_task:
|
|
78
|
-
action_drive:
|
|
79
|
-
action_notion:
|
|
80
|
-
action_github:
|
|
81
|
-
action_webhook:
|
|
210
|
+
action_email: ['gmail_send', { to: cfg.to, subject: cfg.subject || 'NHA Workflow', body: cfg.body || ctx.output }],
|
|
211
|
+
action_slack: ['slack_message', { channel: cfg.channel || '#general', text: cfg.text || ctx.output }],
|
|
212
|
+
action_calendar: ['calendar_create', { title: cfg.title || ctx.output, date: cfg.date || new Date().toISOString().split('T')[0], time: cfg.time || '09:00', duration: cfg.duration || '60' }],
|
|
213
|
+
action_task: ['task_create', { title: cfg.title || ctx.output, priority: cfg.priority || 'medium' }],
|
|
214
|
+
action_drive: ['drive_upload', { name: cfg.name || 'workflow-output.txt', content: cfg.content || ctx.output }],
|
|
215
|
+
action_notion: ['notion_page', { title: cfg.title || 'Workflow Output', content: cfg.content || ctx.output }],
|
|
216
|
+
action_github: ['github_issue', { repo: cfg.repo, title: cfg.title || ctx.output, body: cfg.body || '' }],
|
|
217
|
+
action_webhook: ['fetch_url', { url: cfg.url, method: cfg.method || 'POST', body: cfg.body || ctx.output }],
|
|
218
|
+
action_browser: ['browser_open', { url: cfg.url || ctx.output }],
|
|
219
|
+
action_file_read: ['file_read', { path: cfg.path }],
|
|
220
|
+
action_file_write: ['file_write', { path: cfg.path, content: cfg.content || ctx.output }],
|
|
221
|
+
action_contact: ['contact_search', { query: cfg.query || ctx.output }],
|
|
222
|
+
action_screen: ['screen_capture', {}],
|
|
223
|
+
action_maps: ['maps_directions', { from: cfg.from, to: cfg.to }],
|
|
224
|
+
action_notify: ['notify_remind', { message: cfg.message || ctx.output, channel: cfg.channel || 'system' }],
|
|
82
225
|
};
|
|
83
226
|
|
|
84
227
|
const mapped = ACTION_MAP[node.defId];
|
|
@@ -91,7 +234,7 @@ async function executeNode(node, nodeDef, ctx, config) {
|
|
|
91
234
|
}
|
|
92
235
|
}
|
|
93
236
|
|
|
94
|
-
// Trigger nodes
|
|
237
|
+
// Trigger nodes
|
|
95
238
|
if (nodeDef.type === 'trigger') {
|
|
96
239
|
return ctx.output || cfg.input || '';
|
|
97
240
|
}
|
|
@@ -108,19 +251,25 @@ async function runWorkflow(wf, initialInput, config) {
|
|
|
108
251
|
const nodeMap = Object.fromEntries(wf.nodes.map((n) => [n.id, n]));
|
|
109
252
|
const defMap = Object.fromEntries((wf.nodeDefs || []).map((d) => [d.id, d]));
|
|
110
253
|
|
|
111
|
-
// Build adjacency: from → to
|
|
254
|
+
// Build adjacency: from → [{to, fromPort}]
|
|
112
255
|
const next = {};
|
|
113
256
|
for (const e of wf.edges ?? []) {
|
|
114
257
|
if (!next[e.from]) next[e.from] = [];
|
|
115
|
-
next[e.from].push(e.to);
|
|
258
|
+
next[e.from].push({ to: e.to, port: e.fromPort || 'default' });
|
|
116
259
|
}
|
|
117
260
|
|
|
118
|
-
//
|
|
261
|
+
// Check if next node is an error handler
|
|
262
|
+
const isErrorHandler = (nodeId) => {
|
|
263
|
+
const n = nodeMap[nodeId];
|
|
264
|
+
return n?.defId === 'logic_error';
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Find start nodes
|
|
119
268
|
const hasIncoming = new Set((wf.edges ?? []).map((e) => e.to));
|
|
120
269
|
const startCandidates = wf.nodes.filter((n) => !hasIncoming.has(n.id));
|
|
121
|
-
if (startCandidates.length === 0) return [{ nodeId: '__error', output: 'No start node found.' }];
|
|
270
|
+
if (startCandidates.length === 0) return [{ nodeId: '__error', nodeLabel: 'Error', nodeIcon: '❌', output: 'No start node found.' }];
|
|
122
271
|
|
|
123
|
-
// BFS execution
|
|
272
|
+
// BFS execution with branching support
|
|
124
273
|
const queue = startCandidates.map((n) => ({ nodeId: n.id, ctx: { output: initialInput || '', input: initialInput || '' } }));
|
|
125
274
|
const visited = new Set();
|
|
126
275
|
|
|
@@ -136,18 +285,75 @@ async function runWorkflow(wf, initialInput, config) {
|
|
|
136
285
|
|
|
137
286
|
let output = '';
|
|
138
287
|
let error = null;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
288
|
+
|
|
289
|
+
// Error handler: wrap with retry
|
|
290
|
+
const maxRetries = node.defId === 'logic_error' ? parseInt(node.config?.retries || '0') : 0;
|
|
291
|
+
let attempt = 0;
|
|
292
|
+
while (attempt <= maxRetries) {
|
|
293
|
+
try {
|
|
294
|
+
output = await executeNode(node, nodeDef, ctx, config);
|
|
295
|
+
error = null;
|
|
296
|
+
break;
|
|
297
|
+
} catch (e) {
|
|
298
|
+
error = e.message;
|
|
299
|
+
output = node.config?.fallback || '';
|
|
300
|
+
attempt++;
|
|
301
|
+
if (attempt <= maxRetries) {
|
|
302
|
+
steps.push({ nodeId, nodeLabel: nodeDef.label, nodeIcon: '🔄', output: `Retry ${attempt}/${maxRetries}: ${e.message}`, error: null });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
144
305
|
}
|
|
145
306
|
|
|
146
|
-
steps.push({ nodeId, nodeLabel: nodeDef.label, nodeIcon: nodeDef.icon, output, error });
|
|
307
|
+
steps.push({ nodeId, nodeLabel: nodeDef.label, nodeIcon: nodeDef.icon, output: output?.slice?.(0, 2000) || '', error });
|
|
147
308
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
309
|
+
// If error and no error handler downstream, stop this branch
|
|
310
|
+
if (error) {
|
|
311
|
+
const hasHandler = (next[nodeId] ?? []).some((n) => isErrorHandler(n.to));
|
|
312
|
+
if (!hasHandler) continue;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Determine which downstream nodes to execute based on branching
|
|
316
|
+
const downstream = next[nodeId] ?? [];
|
|
317
|
+
let branch = null;
|
|
318
|
+
try {
|
|
319
|
+
const parsed = JSON.parse(output || '{}');
|
|
320
|
+
if (parsed.__branch) branch = parsed.__branch;
|
|
321
|
+
if (parsed.__loop && Array.isArray(parsed.items)) {
|
|
322
|
+
// Loop: execute downstream for each item
|
|
323
|
+
for (const item of parsed.items) {
|
|
324
|
+
const loopCtx = { ...ctx, output: item, loopItem: item };
|
|
325
|
+
for (const { to: toId } of downstream) {
|
|
326
|
+
// Don't mark as visited — allow loop iterations
|
|
327
|
+
const toNode = nodeMap[toId];
|
|
328
|
+
if (!toNode) continue;
|
|
329
|
+
const toDef = defMap[toNode.defId];
|
|
330
|
+
if (!toDef) continue;
|
|
331
|
+
try {
|
|
332
|
+
const loopOut = await executeNode(toNode, toDef, loopCtx, config);
|
|
333
|
+
steps.push({ nodeId: toId, nodeLabel: `${toDef.label} [${item.slice(0, 20)}]`, nodeIcon: toDef.icon, output: loopOut?.slice?.(0, 2000) || '', error: null });
|
|
334
|
+
} catch (e) {
|
|
335
|
+
steps.push({ nodeId: toId, nodeLabel: `${toDef.label} [${item.slice(0, 20)}]`, nodeIcon: toDef.icon, output: '', error: e.message });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
continue; // Loop handles its own downstream
|
|
340
|
+
}
|
|
341
|
+
} catch { /* not JSON — no branching */ }
|
|
342
|
+
|
|
343
|
+
const nextCtx = { ...ctx, output: output || '', [`${node.defId}_output`]: output || '' };
|
|
344
|
+
|
|
345
|
+
if (branch) {
|
|
346
|
+
// Branching: only follow edges that match the branch port
|
|
347
|
+
for (const { to: toId, port } of downstream) {
|
|
348
|
+
if (port === branch || port === 'default') {
|
|
349
|
+
queue.push({ nodeId: toId, ctx: nextCtx });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
// Normal: follow all downstream edges
|
|
354
|
+
for (const { to: toId } of downstream) {
|
|
355
|
+
queue.push({ nodeId: toId, ctx: nextCtx });
|
|
356
|
+
}
|
|
151
357
|
}
|
|
152
358
|
}
|
|
153
359
|
|
|
@@ -158,6 +364,7 @@ export function register(router) {
|
|
|
158
364
|
// GET /api/workflows — list all workflows
|
|
159
365
|
router.get('/api/workflows', async (req, res) => {
|
|
160
366
|
try {
|
|
367
|
+
seedExamples();
|
|
161
368
|
sendJSON(res, 200, { workflows: listWorkflows() });
|
|
162
369
|
} catch (e) {
|
|
163
370
|
sendError(res, 500, e.message);
|
|
@@ -1033,7 +1033,8 @@ RULES:
|
|
|
1033
1033
|
|
|
1034
1034
|
const FILE_PLAN_SYSTEM = `You are the lead architect of a 200-person engineering team. Design an ENTERPRISE-GRADE file structure.
|
|
1035
1035
|
Output ONLY a JSON array: [{"name":"path/to/file.ext","purpose":"detailed description","tokens":N}]
|
|
1036
|
-
where "tokens" is your estimate of content tokens (
|
|
1036
|
+
where "tokens" is your estimate of content tokens (200-600 small, 600-1500 medium, 1500-3000 large).
|
|
1037
|
+
CRITICAL: No single file should exceed 3000 tokens. Split large files into smaller modules.
|
|
1037
1038
|
|
|
1038
1039
|
MANDATORY STRUCTURE (every project MUST have):
|
|
1039
1040
|
- package.json (with ALL dependencies: express, helmet, cors, compression, morgan, bcryptjs, jsonwebtoken, express-rate-limit, cookie-parser, dotenv)
|
|
@@ -1062,7 +1063,11 @@ MANDATORY STRUCTURE (every project MUST have):
|
|
|
1062
1063
|
- public/js/animations.js (intersection observer, scroll effects)
|
|
1063
1064
|
|
|
1064
1065
|
RULES:
|
|
1065
|
-
- Generate
|
|
1066
|
+
- Generate 30-50 files — many small files are better than few large ones
|
|
1067
|
+
- NEVER generate a file larger than 200 lines. Split into components/partials instead
|
|
1068
|
+
- HTML pages: use <script src="js/page.js"> and <link href="css/page.css"> — NOT inline
|
|
1069
|
+
- CSS: one file per concern (max 150 lines each), NOT one giant file
|
|
1070
|
+
- JS: one file per feature (max 150 lines each)
|
|
1066
1071
|
- Token estimates must be realistic: CSS files 1500-3000, JS files 1000-2000, HTML pages 2000-4000
|
|
1067
1072
|
- Use relative paths only
|
|
1068
1073
|
- No explanation, no markdown, ONLY the JSON array.`;
|
|
@@ -1285,9 +1290,9 @@ Design a COMPLETE production-ready file structure. Include ALL files needed for
|
|
|
1285
1290
|
})
|
|
1286
1291
|
.join('\n\n');
|
|
1287
1292
|
|
|
1288
|
-
//
|
|
1289
|
-
const estimatedTokens = fileSpec.tokens ||
|
|
1290
|
-
const maxTokens = Math.min(Math.max(estimatedTokens *
|
|
1293
|
+
// max_tokens scaled to file size — smaller files = less truncation risk
|
|
1294
|
+
const estimatedTokens = fileSpec.tokens || 1500;
|
|
1295
|
+
const maxTokens = Math.min(Math.max(estimatedTokens * 2, 2000), 8192);
|
|
1291
1296
|
|
|
1292
1297
|
const fileSys = `You are a team of 200 senior full-stack developers generating ENTERPRISE-GRADE production code.
|
|
1293
1298
|
|
|
@@ -1321,7 +1326,9 @@ FRONTEND STANDARDS:
|
|
|
1321
1326
|
- Accessible: aria-labels, focus styles, keyboard navigation, alt text
|
|
1322
1327
|
- Professional typography: system font stack, proper hierarchy (clamp() for fluid sizes)
|
|
1323
1328
|
- CSS Grid/Flexbox layouts — no floats
|
|
1324
|
-
-
|
|
1329
|
+
- Each file must be COMPLETE and SELF-CONTAINED — no truncation
|
|
1330
|
+
- Maximum 200 lines per file. If more content needed, split into separate files
|
|
1331
|
+
- HTML: external CSS/JS via link/script tags, NOT inline styles/scripts exceeding 20 lines`;
|
|
1325
1332
|
|
|
1326
1333
|
const filePrompt = `Project: ${projectName}
|
|
1327
1334
|
Description: ${description}
|