@lhi/n8m 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/agentic/nodes/architect.js +28 -34
- package/dist/commands/config.js +27 -4
- package/dist/commands/create.js +33 -26
- package/dist/commands/modify.js +8 -2
- package/dist/services/ai.service.js +9 -1
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.js +14 -2
- package/dist/utils/theme.d.ts +6 -0
- package/dist/utils/theme.js +13 -0
- package/docs/index.html +73 -5
- package/docs/social-card.html +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -27,10 +27,10 @@ No account. No server. Bring your own AI key and your n8n instance.
|
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
# Option A: Run without installing (npx)
|
|
30
|
-
npx n8m <command>
|
|
30
|
+
npx @lhi/n8m <command>
|
|
31
31
|
|
|
32
32
|
# Option B: Install globally
|
|
33
|
-
npm install -g n8m
|
|
33
|
+
npm install -g @lhi/n8m
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## Setup
|
|
@@ -42,16 +42,16 @@ npm install -g n8m
|
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
44
|
# OpenAI
|
|
45
|
-
npx n8m config --ai-provider openai --ai-key sk-...
|
|
45
|
+
npx @lhi/n8m config --ai-provider openai --ai-key sk-...
|
|
46
46
|
|
|
47
47
|
# Anthropic (Claude)
|
|
48
|
-
npx n8m config --ai-provider anthropic --ai-key sk-ant-...
|
|
48
|
+
npx @lhi/n8m config --ai-provider anthropic --ai-key sk-ant-...
|
|
49
49
|
|
|
50
50
|
# Google Gemini
|
|
51
|
-
npx n8m config --ai-provider gemini --ai-key AIza...
|
|
51
|
+
npx @lhi/n8m config --ai-provider gemini --ai-key AIza...
|
|
52
52
|
|
|
53
53
|
# Any OpenAI-compatible API (Ollama, Groq, Together, LM Studio, etc.)
|
|
54
|
-
npx n8m config --ai-base-url http://localhost:11434/v1 --ai-key ollama --ai-model llama3
|
|
54
|
+
npx @lhi/n8m config --ai-base-url http://localhost:11434/v1 --ai-key ollama --ai-model llama3
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
You can also use environment variables or a `.env` file — stored config takes
|
|
@@ -69,7 +69,7 @@ Default models per provider: `gpt-4o` · `claude-sonnet-4-6` · `gemini-2.5-flas
|
|
|
69
69
|
### 2. Configure your n8n instance
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
npx n8m config --n8n-url https://your-n8n.example.com --n8n-key <your-n8n-api-key>
|
|
72
|
+
npx @lhi/n8m config --n8n-url https://your-n8n.example.com --n8n-key <your-n8n-api-key>
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
Credentials are saved locally to `~/.n8m/config.json`. You can also use
|
|
@@ -408,7 +408,7 @@ Add it to your MCP client config (e.g. Claude Desktop's `claude_desktop_config.j
|
|
|
408
408
|
"mcpServers": {
|
|
409
409
|
"n8m": {
|
|
410
410
|
"command": "npx",
|
|
411
|
-
"args": ["n8m", "mcp"]
|
|
411
|
+
"args": ["@lhi/n8m", "mcp"]
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
414
|
}
|
|
@@ -16,38 +16,32 @@ export const architectNode = async (state) => {
|
|
|
16
16
|
collaborationLog: [`Architect: Modification plan — ${plan.description}`],
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
console.error("Architect failed:", error);
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
19
|
+
const credentials = state.availableCredentials ?? [];
|
|
20
|
+
const spec = await aiService.generateSpec(state.userGoal, credentials);
|
|
21
|
+
// Check if the spec requires clarification
|
|
22
|
+
const questions = spec.questions;
|
|
23
|
+
const needsClarification = questions && questions.length > 0;
|
|
24
|
+
// Multi-agent collaboration: generate an alternative strategy in parallel with the primary.
|
|
25
|
+
// Both are handed off to separate Engineer agents that run concurrently.
|
|
26
|
+
const alternativeSpec = await aiService.generateAlternativeSpec(state.userGoal, spec, credentials);
|
|
27
|
+
const alternativeModel = aiService.getAlternativeModel();
|
|
28
|
+
const strategies = [
|
|
29
|
+
{
|
|
30
|
+
...spec,
|
|
31
|
+
strategyName: "Primary Strategy",
|
|
32
|
+
aiModel: aiService.getDefaultModel()
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
...alternativeSpec,
|
|
36
|
+
strategyName: "Alternative Strategy",
|
|
37
|
+
aiModel: alternativeModel
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
const logEntry = `Architect: Generated 2 strategies — "${strategies[0].suggestedName}" (primary) and "${strategies[1].suggestedName}" (alternative)`;
|
|
41
|
+
return {
|
|
42
|
+
spec,
|
|
43
|
+
strategies,
|
|
44
|
+
needsClarification,
|
|
45
|
+
collaborationLog: [logEntry],
|
|
46
|
+
};
|
|
53
47
|
};
|
package/dist/commands/config.js
CHANGED
|
@@ -29,18 +29,41 @@ export default class Config extends Command {
|
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
// Update config
|
|
32
|
-
if (flags['n8n-url'])
|
|
32
|
+
if (flags['n8n-url']) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = new URL(flags['n8n-url']);
|
|
35
|
+
if (!['http:', 'https:'].includes(parsed.protocol))
|
|
36
|
+
throw new Error('bad protocol');
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
this.error(`Invalid n8n URL: "${flags['n8n-url']}". Must be a valid http/https URL (e.g. https://your-n8n.example.com).`);
|
|
40
|
+
}
|
|
33
41
|
config.n8nUrl = flags['n8n-url'];
|
|
42
|
+
}
|
|
34
43
|
if (flags['n8n-key'])
|
|
35
44
|
config.n8nKey = flags['n8n-key'];
|
|
36
45
|
if (flags['ai-key'])
|
|
37
46
|
config.aiKey = flags['ai-key'];
|
|
38
|
-
if (flags['ai-provider'])
|
|
39
|
-
|
|
47
|
+
if (flags['ai-provider']) {
|
|
48
|
+
const KNOWN_PROVIDERS = ['openai', 'anthropic', 'gemini'];
|
|
49
|
+
if (!KNOWN_PROVIDERS.includes(flags['ai-provider'].toLowerCase())) {
|
|
50
|
+
this.error(`Unknown AI provider: "${flags['ai-provider']}". Must be one of: ${KNOWN_PROVIDERS.join(', ')}.`);
|
|
51
|
+
}
|
|
52
|
+
config.aiProvider = flags['ai-provider'].toLowerCase();
|
|
53
|
+
}
|
|
40
54
|
if (flags['ai-model'])
|
|
41
55
|
config.aiModel = flags['ai-model'];
|
|
42
|
-
if (flags['ai-base-url'])
|
|
56
|
+
if (flags['ai-base-url']) {
|
|
57
|
+
try {
|
|
58
|
+
const parsed = new URL(flags['ai-base-url']);
|
|
59
|
+
if (!['http:', 'https:'].includes(parsed.protocol))
|
|
60
|
+
throw new Error('bad protocol');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
this.error(`Invalid AI base URL: "${flags['ai-base-url']}". Must be a valid http/https URL (e.g. http://localhost:11434/v1).`);
|
|
64
|
+
}
|
|
43
65
|
config.aiBaseUrl = flags['ai-base-url'];
|
|
66
|
+
}
|
|
44
67
|
await ConfigManager.save(config);
|
|
45
68
|
this.log(theme.done('Configuration updated successfully'));
|
|
46
69
|
}
|
package/dist/commands/create.js
CHANGED
|
@@ -72,12 +72,14 @@ export default class Create extends Command {
|
|
|
72
72
|
if (description && description.startsWith('```') && description.endsWith('```')) {
|
|
73
73
|
description = description.slice(3, -3).trim();
|
|
74
74
|
}
|
|
75
|
-
if (!description) {
|
|
75
|
+
if (!description || !description.trim()) {
|
|
76
76
|
this.error('Description is required.');
|
|
77
77
|
}
|
|
78
|
+
description = description.trim();
|
|
78
79
|
// 2. AGENTIC EXECUTION
|
|
79
80
|
const threadId = randomUUID();
|
|
80
|
-
this.log(theme.
|
|
81
|
+
this.log('\n' + theme.session(threadId));
|
|
82
|
+
this.log(theme.stageStart('Architect', 'Analyzing requirements...'));
|
|
81
83
|
// Fetch available credentials for AI guidance (gracefully skipped if n8n not configured)
|
|
82
84
|
let availableCredentials = [];
|
|
83
85
|
{
|
|
@@ -100,18 +102,13 @@ export default class Create extends Command {
|
|
|
100
102
|
const nodeName = Object.keys(event)[0];
|
|
101
103
|
const stateUpdate = event[nodeName];
|
|
102
104
|
if (nodeName === 'architect') {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.log(` Proposed Nodes: ${s.nodes.map((n) => n.type.split('.').pop()).join(', ')}`);
|
|
111
|
-
}
|
|
112
|
-
this.log('');
|
|
113
|
-
});
|
|
114
|
-
}
|
|
105
|
+
const count = stateUpdate.strategies?.length ?? 1;
|
|
106
|
+
this.log(theme.stagePass('Blueprint ready', `${count} approach${count !== 1 ? 'es' : ''}`));
|
|
107
|
+
const topNodes = stateUpdate.strategies?.[0]?.nodes
|
|
108
|
+
?.map((n) => n.type?.split('.').pop())
|
|
109
|
+
.join(' → ');
|
|
110
|
+
if (topNodes)
|
|
111
|
+
this.log(theme.treeItem(topNodes));
|
|
115
112
|
}
|
|
116
113
|
}
|
|
117
114
|
// Handle interrupt/pause loop
|
|
@@ -122,12 +119,14 @@ export default class Create extends Command {
|
|
|
122
119
|
const isRepair = (snapshot.values.validationErrors || []).length > 0;
|
|
123
120
|
if (isRepair) {
|
|
124
121
|
// Repair iteration — auto-continue without asking the user
|
|
122
|
+
this.log(theme.stageStart('Engineer', 'Applying fixes...'));
|
|
125
123
|
const repairStream = await graph.stream(null, { configurable: { thread_id: threadId } });
|
|
126
124
|
for await (const event of repairStream) {
|
|
127
125
|
const n = Object.keys(event)[0];
|
|
128
126
|
const u = event[n];
|
|
129
127
|
if (n === 'engineer') {
|
|
130
|
-
|
|
128
|
+
const lineCount = u.workflowJson ? JSON.stringify(u.workflowJson, null, 2).split('\n').length : 0;
|
|
129
|
+
this.log(theme.stagePass('Fixes applied', `${lineCount} lines`));
|
|
131
130
|
if (u.workflowJson)
|
|
132
131
|
lastWorkflowJson = u.workflowJson;
|
|
133
132
|
}
|
|
@@ -186,7 +185,7 @@ export default class Create extends Command {
|
|
|
186
185
|
choices,
|
|
187
186
|
}]);
|
|
188
187
|
if (choice.type === 'exit') {
|
|
189
|
-
this.log(theme.
|
|
188
|
+
this.log(theme.muted(`\n Session saved. Resume later with: n8m resume ${threadId}`));
|
|
190
189
|
return;
|
|
191
190
|
}
|
|
192
191
|
let chosenSpec = choice.strategy ?? spec;
|
|
@@ -235,12 +234,14 @@ export default class Create extends Command {
|
|
|
235
234
|
this.log(theme.agent(`Building "${chosenSpec?.suggestedName}"...`));
|
|
236
235
|
}
|
|
237
236
|
await graph.updateState({ configurable: { thread_id: threadId } }, stateUpdate);
|
|
237
|
+
this.log(theme.stageStart('Engineer', 'Building workflow JSON...'));
|
|
238
238
|
const buildStream = await graph.stream(null, { configurable: { thread_id: threadId } });
|
|
239
239
|
for await (const event of buildStream) {
|
|
240
240
|
const n = Object.keys(event)[0];
|
|
241
241
|
const u = event[n];
|
|
242
242
|
if (n === 'engineer') {
|
|
243
|
-
|
|
243
|
+
const lineCount = u.workflowJson ? JSON.stringify(u.workflowJson, null, 2).split('\n').length : 0;
|
|
244
|
+
this.log(theme.stagePass('Workflow generated', `${lineCount} lines`));
|
|
244
245
|
if (u.workflowJson)
|
|
245
246
|
lastWorkflowJson = u.workflowJson;
|
|
246
247
|
}
|
|
@@ -248,7 +249,7 @@ export default class Create extends Command {
|
|
|
248
249
|
lastWorkflowJson = u.workflowJson;
|
|
249
250
|
}
|
|
250
251
|
else if (n === 'reviewer' && u.validationStatus === 'failed') {
|
|
251
|
-
this.log(theme.
|
|
252
|
+
this.log(theme.stageFail('Reviewer flagged issues — Engineer will revise'));
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
255
|
}
|
|
@@ -261,25 +262,25 @@ export default class Create extends Command {
|
|
|
261
262
|
default: true,
|
|
262
263
|
}]);
|
|
263
264
|
if (!proceed) {
|
|
264
|
-
this.log(theme.
|
|
265
|
+
this.log(theme.muted(`\n Session saved. Resume later with: n8m resume ${threadId}`));
|
|
265
266
|
return;
|
|
266
267
|
}
|
|
268
|
+
this.log(theme.stageStart('QA', 'Validating structure...'));
|
|
267
269
|
const qaStream = await graph.stream(null, { configurable: { thread_id: threadId } });
|
|
268
270
|
for await (const event of qaStream) {
|
|
269
271
|
const n = Object.keys(event)[0];
|
|
270
272
|
const u = event[n];
|
|
271
273
|
if (n === 'qa') {
|
|
272
274
|
if (u.validationStatus === 'passed') {
|
|
273
|
-
this.log(theme.
|
|
275
|
+
this.log(theme.stagePass('All checks passed'));
|
|
274
276
|
if (u.workflowJson)
|
|
275
277
|
lastWorkflowJson = u.workflowJson;
|
|
276
278
|
}
|
|
277
279
|
else {
|
|
278
|
-
this.log(theme.
|
|
280
|
+
this.log(theme.stageFail('Validation failed'));
|
|
279
281
|
if (u.validationErrors?.length) {
|
|
280
|
-
u.validationErrors.forEach(e => this.log(theme.
|
|
282
|
+
u.validationErrors.forEach(e => this.log(theme.treeItem(` ${e}`)));
|
|
281
283
|
}
|
|
282
|
-
this.log(theme.warn(` Looping back to Engineer for repairs...`));
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
else if (n === 'supervisor' && u.workflowJson) {
|
|
@@ -297,7 +298,12 @@ export default class Create extends Command {
|
|
|
297
298
|
}
|
|
298
299
|
}
|
|
299
300
|
catch (error) {
|
|
300
|
-
|
|
301
|
+
const msg = error.message ?? '';
|
|
302
|
+
const status = error.status;
|
|
303
|
+
if (status === 401 || /authentication_error|invalid.*api.?key|incorrect api key/i.test(msg)) {
|
|
304
|
+
this.error(`Authentication failed: invalid API key.\nRun: npx @lhi/n8m config --ai-key <your-key>`);
|
|
305
|
+
}
|
|
306
|
+
this.error(`Agent ran into an unrecoverable error: ${msg}`);
|
|
301
307
|
}
|
|
302
308
|
if (!lastWorkflowJson) {
|
|
303
309
|
this.error('Agent finished but no workflow JSON was produced.');
|
|
@@ -318,13 +324,14 @@ export default class Create extends Command {
|
|
|
318
324
|
const targetFile = path.join(targetDir, 'workflow.json');
|
|
319
325
|
await fs.mkdir(targetDir, { recursive: true });
|
|
320
326
|
await fs.writeFile(targetFile, JSON.stringify(workflow, null, 2));
|
|
321
|
-
this.log(theme.success(`\nWorkflow organized at: ${targetDir}`));
|
|
322
327
|
// Auto-Generate Documentation
|
|
323
|
-
this.log(theme.agent("Generating initial documentation..."));
|
|
324
328
|
const mermaid = docService.generateMermaid(workflow);
|
|
325
329
|
const readmeContent = await docService.generateReadme(workflow);
|
|
326
330
|
const fullDoc = `# ${projectTitle}\n\n## Visual Flow\n\n\`\`\`mermaid\n${mermaid}\`\`\`\n\n${readmeContent}`;
|
|
327
331
|
await fs.writeFile(path.join(targetDir, 'README.md'), fullDoc);
|
|
332
|
+
this.log(theme.savedDir(path.relative(process.cwd(), targetDir)));
|
|
333
|
+
this.log(theme.treeItem('├── workflow.json'));
|
|
334
|
+
this.log(theme.treeItem('└── README.md'));
|
|
328
335
|
savedResources.push({ path: targetFile, name: projectTitle, original: workflow });
|
|
329
336
|
}
|
|
330
337
|
// 4. DEPLOY PROMPT
|
package/dist/commands/modify.js
CHANGED
|
@@ -150,9 +150,10 @@ export default class Modify extends Command {
|
|
|
150
150
|
if (!instruction) {
|
|
151
151
|
instruction = await promptMultiline('Describe the modifications you want to apply (use ``` for multiline): ');
|
|
152
152
|
}
|
|
153
|
-
if (!instruction) {
|
|
153
|
+
if (!instruction || !instruction.trim()) {
|
|
154
154
|
this.error('Modification instructions are required.');
|
|
155
155
|
}
|
|
156
|
+
instruction = instruction.trim();
|
|
156
157
|
// 4. AGENTIC EXECUTION
|
|
157
158
|
const threadId = randomUUID();
|
|
158
159
|
this.log(theme.info(`\nInitializing Agentic Modification for: "${workflowName}"`));
|
|
@@ -304,7 +305,12 @@ export default class Modify extends Command {
|
|
|
304
305
|
}
|
|
305
306
|
}
|
|
306
307
|
catch (error) {
|
|
307
|
-
|
|
308
|
+
const msg = error.message ?? '';
|
|
309
|
+
const status = error.status;
|
|
310
|
+
if (status === 401 || /authentication_error|invalid.*api.?key|incorrect api key/i.test(msg)) {
|
|
311
|
+
this.error(`Authentication failed: invalid API key.\nRun: npx @lhi/n8m config --ai-key <your-key>`);
|
|
312
|
+
}
|
|
313
|
+
this.error(`Agent encountered an error: ${msg}`);
|
|
308
314
|
}
|
|
309
315
|
// 5. POST-MODIFICATION ACTIONS
|
|
310
316
|
const modifiedWorkflow = lastWorkflowJson.workflows ? lastWorkflowJson.workflows[0] : lastWorkflowJson;
|
|
@@ -128,7 +128,15 @@ export class AIService {
|
|
|
128
128
|
});
|
|
129
129
|
if (!response.ok) {
|
|
130
130
|
const errorText = await response.text();
|
|
131
|
-
|
|
131
|
+
let cleanMessage = `Anthropic API Error: ${response.status}`;
|
|
132
|
+
try {
|
|
133
|
+
const parsed = JSON.parse(errorText);
|
|
134
|
+
cleanMessage = parsed?.error?.message ?? cleanMessage;
|
|
135
|
+
}
|
|
136
|
+
catch { /* not JSON */ }
|
|
137
|
+
const err = new Error(cleanMessage);
|
|
138
|
+
err.status = response.status;
|
|
139
|
+
throw err;
|
|
132
140
|
}
|
|
133
141
|
const result = await response.json();
|
|
134
142
|
return result.content?.[0]?.text || '';
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface N8mConfig {
|
|
|
9
9
|
export declare class ConfigManager {
|
|
10
10
|
private static configDir;
|
|
11
11
|
private static configFile;
|
|
12
|
+
private static loadFile;
|
|
12
13
|
static load(): Promise<N8mConfig>;
|
|
13
14
|
static save(config: Partial<N8mConfig>): Promise<void>;
|
|
14
15
|
static clear(): Promise<void>;
|
package/dist/utils/config.js
CHANGED
|
@@ -8,7 +8,7 @@ dotenv.config({ quiet: true });
|
|
|
8
8
|
export class ConfigManager {
|
|
9
9
|
static configDir = path.join(os.homedir(), '.n8m');
|
|
10
10
|
static configFile = path.join(os.homedir(), '.n8m', 'config.json');
|
|
11
|
-
static async
|
|
11
|
+
static async loadFile() {
|
|
12
12
|
try {
|
|
13
13
|
const data = await fs.readFile(this.configFile, 'utf-8');
|
|
14
14
|
return JSON.parse(data);
|
|
@@ -17,9 +17,21 @@ export class ConfigManager {
|
|
|
17
17
|
return {};
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
static async load() {
|
|
21
|
+
const fileConfig = await this.loadFile();
|
|
22
|
+
// Env vars (including .env in cwd) take priority over file config.
|
|
23
|
+
return {
|
|
24
|
+
n8nUrl: process.env.N8N_API_URL || fileConfig.n8nUrl,
|
|
25
|
+
n8nKey: process.env.N8N_API_KEY || fileConfig.n8nKey,
|
|
26
|
+
aiKey: process.env.AI_API_KEY || fileConfig.aiKey,
|
|
27
|
+
aiProvider: process.env.AI_PROVIDER || fileConfig.aiProvider,
|
|
28
|
+
aiModel: process.env.AI_MODEL || fileConfig.aiModel,
|
|
29
|
+
aiBaseUrl: process.env.AI_BASE_URL || fileConfig.aiBaseUrl,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
20
32
|
static async save(config) {
|
|
21
33
|
await fs.mkdir(this.configDir, { recursive: true });
|
|
22
|
-
const existing = await this.
|
|
34
|
+
const existing = await this.loadFile();
|
|
23
35
|
const merged = { ...existing, ...config };
|
|
24
36
|
await fs.writeFile(this.configFile, JSON.stringify(merged, null, 2));
|
|
25
37
|
}
|
package/dist/utils/theme.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ export declare const theme: {
|
|
|
9
9
|
warn: (text: string) => string;
|
|
10
10
|
fail: (text: string) => string;
|
|
11
11
|
agent: (text: string) => string;
|
|
12
|
+
stageStart: (name: string, desc: string) => string;
|
|
13
|
+
stagePass: (text: string, sub?: string) => string;
|
|
14
|
+
stageFail: (text: string) => string;
|
|
15
|
+
savedDir: (dirPath: string) => string;
|
|
16
|
+
treeItem: (line: string) => string;
|
|
17
|
+
session: (id: string) => string;
|
|
12
18
|
brand: () => string;
|
|
13
19
|
tag: (text: string) => string;
|
|
14
20
|
primary: import("chalk").ChalkInstance;
|
package/dist/utils/theme.js
CHANGED
|
@@ -64,6 +64,19 @@ export const theme = {
|
|
|
64
64
|
fail: (text) => c.error('✘ ') + c.foreground(text),
|
|
65
65
|
// AI/Agentic
|
|
66
66
|
agent: (text) => c.ai('✧ ') + c.ai.italic(text),
|
|
67
|
+
// Site-terminal stage style (matches n8m.run terminal demo)
|
|
68
|
+
stageStart: (name, desc) => {
|
|
69
|
+
const padded = name.padEnd(12);
|
|
70
|
+
return chalk.hex('#fb923c').bold(` ${padded}`) + chalk.hex('#64748b')(desc);
|
|
71
|
+
},
|
|
72
|
+
stagePass: (text, sub) => {
|
|
73
|
+
const line = chalk.hex('#4ade80')(' ✓') + ' ' + chalk.hex('#f1f5f9')(text);
|
|
74
|
+
return sub ? line + chalk.hex('#64748b')(` · ${sub}`) : line;
|
|
75
|
+
},
|
|
76
|
+
stageFail: (text) => chalk.hex('#f87171')(' ✗') + ' ' + chalk.hex('#f1f5f9')(text),
|
|
77
|
+
savedDir: (dirPath) => chalk.hex('#4ade80').bold('\n Saved') + ' ' + chalk.hex('#94a3b8')(dirPath),
|
|
78
|
+
treeItem: (line) => chalk.hex('#94a3b8')(` ${line}`),
|
|
79
|
+
session: (id) => chalk.hex('#3d5070')(` Session: ${id}`),
|
|
67
80
|
// Brand/Banner
|
|
68
81
|
brand: () => {
|
|
69
82
|
const bannerPath = join(rootPath, 'banner.txt');
|
package/docs/index.html
CHANGED
|
@@ -837,19 +837,87 @@
|
|
|
837
837
|
/* ============================================================
|
|
838
838
|
RESPONSIVE
|
|
839
839
|
============================================================ */
|
|
840
|
+
|
|
841
|
+
/* ---- tap targets ---- */
|
|
842
|
+
a, button { -webkit-tap-highlight-color: transparent; }
|
|
843
|
+
.btn, .install-box, .cta-install, .badge, .nav-links a { touch-action: manipulation; }
|
|
844
|
+
|
|
845
|
+
/* ---- tablet (≤960px) ---- */
|
|
840
846
|
@media (max-width: 960px) {
|
|
841
847
|
.hero-wrap { flex-direction: column; padding-top: 5.5rem; }
|
|
842
848
|
.hero-r { max-width: 100%; }
|
|
849
|
+
.hero-sub { max-width: 100%; }
|
|
843
850
|
.pipeline { flex-direction: column; }
|
|
844
851
|
.parr { transform: rotate(90deg); padding: .4rem 0; justify-content: center; }
|
|
845
852
|
.mcp-split, .rm-grid, .sponsor-split { grid-template-columns: 1fr; gap: 2rem; }
|
|
846
853
|
}
|
|
847
854
|
|
|
855
|
+
/* ---- mobile (≤640px) ---- */
|
|
848
856
|
@media (max-width: 640px) {
|
|
849
857
|
h1 { font-size: 2.2rem; }
|
|
858
|
+
h2 { font-size: 1.65rem; }
|
|
850
859
|
.cmds-grid, .feats-grid { grid-template-columns: 1fr; }
|
|
851
|
-
|
|
860
|
+
/* keep GitHub + npm links visible; hide interior nav links */
|
|
861
|
+
.nav-links a:not(.nav-cta):not([href*="github"]):not([href*="npmjs"]) { display: none; }
|
|
852
862
|
section { padding: 3.5rem 1.25rem; }
|
|
863
|
+
|
|
864
|
+
/* install command — scroll rather than overflow */
|
|
865
|
+
.install-box {
|
|
866
|
+
display: flex;
|
|
867
|
+
width: 100%;
|
|
868
|
+
overflow-x: auto;
|
|
869
|
+
white-space: nowrap;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/* code blocks that can overflow */
|
|
873
|
+
.config-box, .json-box, .cmd-ex { overflow-x: auto; }
|
|
874
|
+
|
|
875
|
+
/* CTA install box */
|
|
876
|
+
.cta-install {
|
|
877
|
+
width: 100%;
|
|
878
|
+
max-width: 100%;
|
|
879
|
+
overflow-x: auto;
|
|
880
|
+
font-size: .85rem;
|
|
881
|
+
justify-content: flex-start;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/* hero buttons — tighter gap, slightly smaller */
|
|
885
|
+
.hero-btns { gap: .5rem; }
|
|
886
|
+
.btn { padding: .55rem 1rem; font-size: .82rem; }
|
|
887
|
+
|
|
888
|
+
/* terminal — smaller font saves vertical space */
|
|
889
|
+
.term-body { font-size: .7rem; padding: .9rem 1rem 1rem; min-height: 240px; }
|
|
890
|
+
|
|
891
|
+
/* footer — stacked */
|
|
892
|
+
footer { flex-direction: column; align-items: flex-start; gap: .65rem; }
|
|
893
|
+
.footer-links { flex-wrap: wrap; gap: .65rem; }
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/* ---- small phone (≤430px) ---- */
|
|
897
|
+
@media (max-width: 430px) {
|
|
898
|
+
h1 { font-size: 1.85rem; }
|
|
899
|
+
h2 { font-size: 1.4rem; }
|
|
900
|
+
.hero-wrap { padding-top: 4.75rem; padding-left: 1rem; padding-right: 1rem; }
|
|
901
|
+
section { padding: 3rem 1rem; }
|
|
902
|
+
nav { padding: .55rem 1rem; }
|
|
903
|
+
|
|
904
|
+
/* badge — tighter on very narrow screens */
|
|
905
|
+
.badge { font-size: .65rem; padding: .25rem .65rem; }
|
|
906
|
+
|
|
907
|
+
/* hero buttons stack full-width */
|
|
908
|
+
.hero-btns { flex-direction: column; }
|
|
909
|
+
.hero-btns .btn { width: 100%; justify-content: center; }
|
|
910
|
+
|
|
911
|
+
/* provider pills — slightly smaller */
|
|
912
|
+
.prov-pill { font-size: .75rem; }
|
|
913
|
+
|
|
914
|
+
/* CTA section buttons stack */
|
|
915
|
+
.cta-btns { flex-direction: column; align-items: stretch; }
|
|
916
|
+
.cta-btns .btn { width: 100%; justify-content: center; }
|
|
917
|
+
|
|
918
|
+
/* sponsor section buttons */
|
|
919
|
+
#sponsors .btn { width: 100%; justify-content: center; }
|
|
920
|
+
#sponsors div[style*="display:flex"] { flex-direction: column; }
|
|
853
921
|
}
|
|
854
922
|
|
|
855
923
|
/* ============================================================
|
|
@@ -920,10 +988,10 @@
|
|
|
920
988
|
No account. No server. Bring your own AI key.
|
|
921
989
|
</p>
|
|
922
990
|
|
|
923
|
-
<div class="install-box" onclick="copy('npx n8m create "your workflow"','copyBtn')">
|
|
991
|
+
<div class="install-box" onclick="copy('npx @lhi/n8m create "your workflow"','copyBtn')">
|
|
924
992
|
<span class="ip">$</span>
|
|
925
993
|
<span class="ic">npx</span>
|
|
926
|
-
<span style="color:var(--text-3);">&thinsp
|
|
994
|
+
<span style="color:var(--text-3);"> @lhi/n8m</span>
|
|
927
995
|
<span class="ec" style="color:var(--brand-hi);"> create</span>
|
|
928
996
|
<span class="is"> "your workflow"</span>
|
|
929
997
|
<button class="copy-btn" id="copyBtn" aria-label="Copy install command">copy</button>
|
|
@@ -1417,10 +1485,10 @@
|
|
|
1417
1485
|
<h2>Ready to stop clicking?</h2>
|
|
1418
1486
|
<p class="sec-sub">No account. No server. No lock-in. Just your n8n instance and an AI key.</p>
|
|
1419
1487
|
|
|
1420
|
-
<div class="cta-install" onclick="copy('npx n8m create "describe your workflow"','copyBtnCta')">
|
|
1488
|
+
<div class="cta-install" onclick="copy('npx @lhi/n8m create "describe your workflow"','copyBtnCta')">
|
|
1421
1489
|
<span style="color:var(--text-5);">$</span>
|
|
1422
1490
|
<span class="ic">npx</span>
|
|
1423
|
-
<span style="color:var(--text-3);">&thinsp
|
|
1491
|
+
<span style="color:var(--text-3);"> @lhi/n8m</span>
|
|
1424
1492
|
<span class="ec" style="color:var(--brand-hi);"> create</span>
|
|
1425
1493
|
<span class="is"> "describe your workflow"</span>
|
|
1426
1494
|
<button class="copy-btn" id="copyBtnCta" aria-label="Copy command">copy</button>
|
package/docs/social-card.html
CHANGED
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lhi/n8m",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Agentic n8n CLI wrapper - A Skill Bridge for n8n workflow automation",
|
|
5
5
|
"author": "Lem Canady",
|
|
6
6
|
"keywords": [
|
|
@@ -129,6 +129,7 @@
|
|
|
129
129
|
"start": "npm run build && ./bin/run.js",
|
|
130
130
|
"dev": "tsc -b -w",
|
|
131
131
|
"generate-patterns": "tsx scripts/generate-patterns.ts",
|
|
132
|
-
"version": "oclif readme && git add README.md"
|
|
132
|
+
"version": "oclif readme && git add README.md",
|
|
133
|
+
"test:security": "mocha '__tests__/security/**/*.spec.js'"
|
|
133
134
|
}
|
|
134
135
|
}
|