create-bk-agent 0.16.1 → 0.17.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/README.md +93 -29
- package/dist/src/add/detector.js +80 -0
- package/dist/src/add/index.js +67 -0
- package/dist/src/add/modules/mcp-client.js +65 -0
- package/dist/src/add/modules/server.js +100 -0
- package/dist/src/add/modules/specialist.js +98 -0
- package/dist/src/add/modules/tools.js +102 -0
- package/dist/src/add/patcher.js +168 -0
- package/dist/src/index.js +55 -9
- package/dist/src/prompts.js +102 -25
- package/dist/src/scaffold.js +95 -21
- package/package.json +1 -1
- package/templates/base/autonomous/.env.example +4 -0
- package/templates/base/autonomous/package.json +23 -0
- package/templates/base/autonomous/src/index.ts +67 -0
- package/templates/base/autonomous/tsconfig.json +17 -0
- package/templates/base/coding/.env.example +4 -0
- package/templates/base/coding/AGENT.md +13 -0
- package/templates/base/coding/package.json +24 -0
- package/templates/base/coding/src/index.ts +42 -0
- package/templates/base/coding/tsconfig.json +17 -0
- package/templates/base/minimal/.env.example +1 -0
- package/templates/base/minimal/package.json +23 -0
- package/templates/base/minimal/src/index.ts +43 -0
- package/templates/base/minimal/tsconfig.json +17 -0
- package/templates/base/multi-agent/.env.example +7 -0
- package/templates/base/multi-agent/package.json +26 -0
- package/templates/base/multi-agent/src/agents/orchestrator.ts +22 -0
- package/templates/base/multi-agent/src/agents/specialist-a.ts +17 -0
- package/templates/base/multi-agent/src/agents/specialist-b.ts +16 -0
- package/templates/base/multi-agent/src/config.ts +19 -0
- package/templates/base/multi-agent/src/engine.ts +49 -0
- package/templates/base/multi-agent/src/providers/openai-compatible.ts +104 -0
- package/templates/base/multi-agent/src/tools/example-tools.ts +28 -0
- package/templates/base/multi-agent/tsconfig.json +17 -0
- package/templates/base/single/.env.example +4 -0
- package/templates/base/single/package.json +26 -0
- package/templates/base/single/src/agents/my-agent.ts +18 -0
- package/templates/base/single/src/config.ts +18 -0
- package/templates/base/single/src/engine.ts +44 -0
- package/templates/base/single/src/providers/openai-compatible.ts +104 -0
- package/templates/base/single/src/tools/example-tools.ts +28 -0
- package/templates/base/single/tsconfig.json +17 -0
- package/templates/deploy/hybrid/src/index.ts +27 -0
- package/templates/deploy/hybrid/src/server.ts +26 -0
- package/templates/deploy/mcp-http/src/server.ts +26 -0
- package/templates/deploy/mcp-stdio/src/server.ts +25 -0
- package/templates/deploy/repl/src/index.ts +27 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.addImportLine = addImportLine;
|
|
37
|
+
exports.insertAfterPattern = insertAfterPattern;
|
|
38
|
+
exports.insertBeforePattern = insertBeforePattern;
|
|
39
|
+
exports.addPackageScript = addPackageScript;
|
|
40
|
+
exports.addPackageDependency = addPackageDependency;
|
|
41
|
+
exports.addMcpServerEntry = addMcpServerEntry;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
// ── Import block ──────────────────────────────────────────────────────────────
|
|
44
|
+
/** Adds `importLine` after the last import statement. No-op if already present. */
|
|
45
|
+
function addImportLine(file, importLine) {
|
|
46
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
47
|
+
if (content.includes(importLine.trim()))
|
|
48
|
+
return;
|
|
49
|
+
const lines = content.split('\n');
|
|
50
|
+
let lastImportIdx = -1;
|
|
51
|
+
for (let i = 0; i < lines.length; i++) {
|
|
52
|
+
if (/^import\s/.test(lines[i]))
|
|
53
|
+
lastImportIdx = i;
|
|
54
|
+
}
|
|
55
|
+
if (lastImportIdx === -1) {
|
|
56
|
+
fs.writeFileSync(file, importLine + '\n' + content);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
lines.splice(lastImportIdx + 1, 0, importLine);
|
|
60
|
+
fs.writeFileSync(file, lines.join('\n'));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// ── Line insertion ────────────────────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Inserts `newLine` after the LAST line that matches `pattern`.
|
|
66
|
+
* Returns true if the insertion was done, false if the pattern wasn't found.
|
|
67
|
+
* No-op if `newLine` (trimmed) already exists in the file.
|
|
68
|
+
*/
|
|
69
|
+
function insertAfterPattern(file, pattern, newLine) {
|
|
70
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
71
|
+
if (content.includes(newLine.trim()))
|
|
72
|
+
return true;
|
|
73
|
+
const lines = content.split('\n');
|
|
74
|
+
let lastIdx = -1;
|
|
75
|
+
for (let i = 0; i < lines.length; i++) {
|
|
76
|
+
if (pattern.test(lines[i]))
|
|
77
|
+
lastIdx = i;
|
|
78
|
+
}
|
|
79
|
+
if (lastIdx === -1)
|
|
80
|
+
return false;
|
|
81
|
+
lines.splice(lastIdx + 1, 0, newLine);
|
|
82
|
+
fs.writeFileSync(file, lines.join('\n'));
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Inserts `newLine` before the FIRST line that matches `pattern`.
|
|
87
|
+
* Returns true if the insertion was done, false if the pattern wasn't found.
|
|
88
|
+
* No-op if `newLine` (trimmed) already exists in the file.
|
|
89
|
+
*/
|
|
90
|
+
function insertBeforePattern(file, pattern, newLine) {
|
|
91
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
92
|
+
if (content.includes(newLine.trim()))
|
|
93
|
+
return true;
|
|
94
|
+
const lines = content.split('\n');
|
|
95
|
+
let firstIdx = -1;
|
|
96
|
+
for (let i = 0; i < lines.length; i++) {
|
|
97
|
+
if (pattern.test(lines[i])) {
|
|
98
|
+
firstIdx = i;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (firstIdx === -1)
|
|
103
|
+
return false;
|
|
104
|
+
lines.splice(firstIdx, 0, newLine);
|
|
105
|
+
fs.writeFileSync(file, lines.join('\n'));
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
// ── package.json helpers ──────────────────────────────────────────────────────
|
|
109
|
+
function addPackageScript(pkgFile, name, cmd) {
|
|
110
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
|
|
111
|
+
pkg.scripts ??= {};
|
|
112
|
+
pkg.scripts[name] = cmd;
|
|
113
|
+
fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + '\n');
|
|
114
|
+
}
|
|
115
|
+
/** Adds `depName@version` to dependencies if not already present. */
|
|
116
|
+
function addPackageDependency(pkgFile, depName, version) {
|
|
117
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
|
|
118
|
+
pkg.dependencies ??= {};
|
|
119
|
+
if (pkg.dependencies[depName])
|
|
120
|
+
return false;
|
|
121
|
+
pkg.dependencies[depName] = version;
|
|
122
|
+
fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + '\n');
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
// ── AgentEngine options patcher ───────────────────────────────────────────────
|
|
126
|
+
/**
|
|
127
|
+
* Adds `mcpServers: [{ name, url }]` to the first AgentEngine constructor call.
|
|
128
|
+
* If mcpServers already exists, appends a new entry to the existing array.
|
|
129
|
+
* Returns true on success.
|
|
130
|
+
*/
|
|
131
|
+
function addMcpServerEntry(file, name, url) {
|
|
132
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
133
|
+
const entry = ` { name: '${name}', url: '${url}' },`;
|
|
134
|
+
if (content.includes(`url: '${url}'`))
|
|
135
|
+
return true; // already registered
|
|
136
|
+
if (content.includes('mcpServers:')) {
|
|
137
|
+
// Append into existing array — insert after 'mcpServers: ['
|
|
138
|
+
const updated = content.replace(/(mcpServers:\s*\[)/, `$1\n${entry}`);
|
|
139
|
+
if (updated === content)
|
|
140
|
+
return false;
|
|
141
|
+
fs.writeFileSync(file, updated);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
// Insert new mcpServers block before the closing of AgentEngine({ ... })
|
|
145
|
+
const lines = content.split('\n');
|
|
146
|
+
let depth = 0;
|
|
147
|
+
let engineStart = -1;
|
|
148
|
+
let closeIdx = -1;
|
|
149
|
+
for (let i = 0; i < lines.length; i++) {
|
|
150
|
+
if (lines[i].includes('new AgentEngine({')) {
|
|
151
|
+
engineStart = i;
|
|
152
|
+
depth = 0;
|
|
153
|
+
}
|
|
154
|
+
if (engineStart !== -1) {
|
|
155
|
+
depth += (lines[i].match(/\{/g) ?? []).length;
|
|
156
|
+
depth -= (lines[i].match(/\}/g) ?? []).length;
|
|
157
|
+
if (depth === 0 && i > engineStart) {
|
|
158
|
+
closeIdx = i;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (closeIdx === -1)
|
|
164
|
+
return false;
|
|
165
|
+
lines.splice(closeIdx, 0, ` mcpServers: [\n${entry}\n ],`);
|
|
166
|
+
fs.writeFileSync(file, lines.join('\n'));
|
|
167
|
+
return true;
|
|
168
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -38,6 +38,7 @@ const path = __importStar(require("path"));
|
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const prompts_1 = require("./prompts");
|
|
40
40
|
const scaffold_1 = require("./scaffold");
|
|
41
|
+
const index_1 = require("./add/index");
|
|
41
42
|
// Read version from package.json so it always matches the published version
|
|
42
43
|
const FRAMEWORK_VERSION = (() => {
|
|
43
44
|
try {
|
|
@@ -48,14 +49,59 @@ const FRAMEWORK_VERSION = (() => {
|
|
|
48
49
|
return '0.16.0';
|
|
49
50
|
}
|
|
50
51
|
})();
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
// ── Human-readable label for the scaffold banner ──────────────────────────────
|
|
53
|
+
function describeChoices(choices) {
|
|
54
|
+
const arch = {
|
|
55
|
+
'multi-agent': 'Multi-agent',
|
|
56
|
+
single: 'Single-agent',
|
|
57
|
+
coding: 'Coding assistant',
|
|
58
|
+
autonomous: 'Autonomous daemon',
|
|
59
|
+
minimal: 'Minimal / embedded',
|
|
60
|
+
};
|
|
61
|
+
const deploy = {
|
|
62
|
+
repl: 'Terminal REPL',
|
|
63
|
+
'mcp-http': 'MCP Server (HTTP)',
|
|
64
|
+
'mcp-stdio': 'MCP Server (stdio)',
|
|
65
|
+
hybrid: 'REPL + MCP Server',
|
|
66
|
+
library: 'Library',
|
|
67
|
+
};
|
|
68
|
+
const archLabel = arch[choices.architecture] ?? choices.architecture;
|
|
69
|
+
if (!choices.deployMode)
|
|
70
|
+
return archLabel;
|
|
71
|
+
return `${archLabel} — ${deploy[choices.deployMode] ?? choices.deployMode}`;
|
|
72
|
+
}
|
|
73
|
+
// ── Next steps after scaffold ─────────────────────────────────────────────────
|
|
74
|
+
function buildNextSteps(choices) {
|
|
75
|
+
const { architecture: arch, deployMode: mode } = choices;
|
|
76
|
+
// Self-contained architectures
|
|
77
|
+
if (arch === 'coding')
|
|
78
|
+
return ['Edit AGENT.md to describe your project', 'npm run dev'];
|
|
79
|
+
if (arch === 'autonomous')
|
|
80
|
+
return ['Configure triggers in src/index.ts', 'npm run dev'];
|
|
81
|
+
if (arch === 'minimal')
|
|
82
|
+
return ['Implement your LLM provider in src/index.ts', 'npm run dev'];
|
|
83
|
+
// Layered architectures (multi-agent, single) — vary by deploy mode
|
|
84
|
+
const domainSteps = arch === 'multi-agent'
|
|
85
|
+
? ['Rename agents/ and tools/ to match your domain', 'Implement tools in src/tools/']
|
|
86
|
+
: ['Implement tools in src/tools/'];
|
|
87
|
+
if (mode === 'repl')
|
|
88
|
+
return [...domainSteps, 'npm run dev'];
|
|
89
|
+
if (mode === 'mcp-http')
|
|
90
|
+
return [...domainSteps, 'npm run server # starts HTTP MCP on port 3001'];
|
|
91
|
+
if (mode === 'mcp-stdio')
|
|
92
|
+
return [...domainSteps, 'npm run build', 'node dist/server.js'];
|
|
93
|
+
if (mode === 'hybrid')
|
|
94
|
+
return [...domainSteps, 'npm run dev', '# in another terminal:', 'npm run server'];
|
|
95
|
+
if (mode === 'library')
|
|
96
|
+
return [...domainSteps, '# import { createEngine } from \'./src/engine\' in your app'];
|
|
97
|
+
return ['npm run dev'];
|
|
98
|
+
}
|
|
99
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
58
100
|
async function main(argv) {
|
|
101
|
+
if (argv[2] === 'add') {
|
|
102
|
+
(0, index_1.runAdd)(argv.slice(3), process.cwd());
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
59
105
|
const projectArg = argv[2];
|
|
60
106
|
console.log(`\n✨ create-bk-agent v${FRAMEWORK_VERSION}\n`);
|
|
61
107
|
const defaultName = projectArg ?? 'my-agent';
|
|
@@ -69,7 +115,7 @@ async function main(argv) {
|
|
|
69
115
|
console.error(`\n✗ Directory already exists: ${targetDir}`);
|
|
70
116
|
process.exit(1);
|
|
71
117
|
}
|
|
72
|
-
console.log(`\nCreating ${choices
|
|
118
|
+
console.log(`\nCreating ${describeChoices(choices)} agent in ./${choices.projectName} ...`);
|
|
73
119
|
try {
|
|
74
120
|
(0, scaffold_1.scaffold)(choices, targetDir);
|
|
75
121
|
}
|
|
@@ -79,7 +125,7 @@ async function main(argv) {
|
|
|
79
125
|
}
|
|
80
126
|
console.log('Installing dependencies...\n');
|
|
81
127
|
(0, scaffold_1.installDeps)(targetDir);
|
|
82
|
-
const steps =
|
|
128
|
+
const steps = buildNextSteps(choices);
|
|
83
129
|
console.log(`
|
|
84
130
|
✅ Done! Your agent is ready.
|
|
85
131
|
|
package/dist/src/prompts.js
CHANGED
|
@@ -5,21 +5,81 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.askQuestions = askQuestions;
|
|
7
7
|
const prompts_1 = __importDefault(require("prompts"));
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
// ── Option definitions ────────────────────────────────────────────────────────
|
|
9
|
+
const p = (s, n) => s.padEnd(n);
|
|
10
|
+
const ARCHITECTURE_CHOICES = [
|
|
11
|
+
{
|
|
12
|
+
title: p('Multi-agent', 22) + 'Orchestrator delegates to specialized sub-agents',
|
|
13
|
+
value: 'multi-agent',
|
|
14
|
+
description: 'Best for complex domains (git: commit / branch / merge / recovery)',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
title: p('Single agent', 22) + 'One agent handles all requests with tools',
|
|
18
|
+
value: 'single',
|
|
19
|
+
description: 'Best for focused single-domain tasks and quick prototypes',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
title: p('Coding assistant', 22) + '10 pre-built agents for software dev — ready to run',
|
|
23
|
+
value: 'coding',
|
|
24
|
+
description: 'Just add your API key and run',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
title: p('Autonomous daemon', 22) + 'Cron / webhook triggers, no human input',
|
|
28
|
+
value: 'autonomous',
|
|
29
|
+
description: 'Best for background monitoring, alerts, automated reports',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
title: p('Minimal / embedded', 22) + 'Bare engine factory, full control',
|
|
33
|
+
value: 'minimal',
|
|
34
|
+
description: 'Best for embedding in an existing app or custom architecture',
|
|
35
|
+
},
|
|
14
36
|
];
|
|
15
|
-
const
|
|
16
|
-
{ title: '
|
|
17
|
-
{ title: '
|
|
18
|
-
{ title: '
|
|
19
|
-
{ title:
|
|
37
|
+
const SKILL_PACK_CHOICES = [
|
|
38
|
+
{ title: p('Node.js / TypeScript', 24) + 'npm, jest, tsc, eslint', value: 'node' },
|
|
39
|
+
{ title: p('Python', 24) + 'pytest, ruff, mypy, pip', value: 'python' },
|
|
40
|
+
{ title: p('Go', 24) + 'go test, golangci-lint', value: 'go' },
|
|
41
|
+
{ title: p('Docker / K8s', 24) + 'docker, kubectl, helm', value: 'docker' },
|
|
20
42
|
];
|
|
43
|
+
const DEPLOY_MODE_CHOICES = [
|
|
44
|
+
{
|
|
45
|
+
title: p('Terminal REPL', 28) + 'Humans type requests, agent responds in the terminal',
|
|
46
|
+
value: 'repl',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
title: p('MCP Server — HTTP', 28) + 'Other agents / tools connect via HTTP protocol',
|
|
50
|
+
value: 'mcp-http',
|
|
51
|
+
description: 'e.g. git-agent server that another bk-agent connects to',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
title: p('MCP Server — stdio', 28) + 'Claude Desktop / Cursor / VS Code integration',
|
|
55
|
+
value: 'mcp-stdio',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
title: p('REPL + MCP Server', 28) + 'Hybrid — humans use it AND other agents connect',
|
|
59
|
+
value: 'hybrid',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
title: p('Library only', 28) + 'No entry point — imported programmatically',
|
|
63
|
+
value: 'library',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
const PROVIDER_CHOICES = [
|
|
67
|
+
{ title: p('Anthropic (Claude)', 32) + 'claude-sonnet-4-6 recommended', value: 'anthropic' },
|
|
68
|
+
{ title: p('OpenAI', 32) + 'gpt-4o recommended', value: 'openai' },
|
|
69
|
+
{ title: p('DeepSeek / OpenAI-compatible', 32) + 'deepseek-chat or any OpenAI-compatible endpoint', value: 'deepseek' },
|
|
70
|
+
{ title: p('Ollama (local)', 32) + 'llama3.2, codellama — no API key needed', value: 'ollama' },
|
|
71
|
+
];
|
|
72
|
+
// ── Conditional question helpers ──────────────────────────────────────────────
|
|
73
|
+
const NEEDS_SKILLS = ['multi-agent', 'single'];
|
|
74
|
+
const NEEDS_DEPLOY = ['multi-agent', 'single', 'coding'];
|
|
75
|
+
// @types/prompts doesn't fully type the function form of `type` — cast at call site.
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
const whenSkills = (_prev, v) => NEEDS_SKILLS.includes(v.architecture) ? 'multiselect' : null;
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
const whenDeploy = (_prev, v) => NEEDS_DEPLOY.includes(v.architecture) ? 'select' : null;
|
|
80
|
+
// ── Main question flow ────────────────────────────────────────────────────────
|
|
21
81
|
async function askQuestions(defaultName) {
|
|
22
|
-
const
|
|
82
|
+
const raw = await (0, prompts_1.default)([
|
|
23
83
|
{
|
|
24
84
|
type: 'text',
|
|
25
85
|
name: 'projectName',
|
|
@@ -29,26 +89,43 @@ async function askQuestions(defaultName) {
|
|
|
29
89
|
},
|
|
30
90
|
{
|
|
31
91
|
type: 'select',
|
|
32
|
-
name: '
|
|
33
|
-
message: '
|
|
34
|
-
choices:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
92
|
+
name: 'architecture',
|
|
93
|
+
message: 'What type of agent are you building?',
|
|
94
|
+
choices: ARCHITECTURE_CHOICES,
|
|
95
|
+
initial: 0,
|
|
96
|
+
},
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
{
|
|
99
|
+
type: whenSkills,
|
|
100
|
+
name: 'skillPacks',
|
|
101
|
+
message: 'Include skill packs? (space to toggle, enter to confirm)',
|
|
102
|
+
choices: SKILL_PACK_CHOICES,
|
|
103
|
+
instructions: false,
|
|
104
|
+
hint: 'Adds domain-specific instructions to your agents. Can be skipped.',
|
|
105
|
+
},
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
107
|
+
{
|
|
108
|
+
type: whenDeploy,
|
|
109
|
+
name: 'deployMode',
|
|
110
|
+
message: 'How will this agent be used?',
|
|
111
|
+
choices: DEPLOY_MODE_CHOICES,
|
|
38
112
|
initial: 0,
|
|
39
113
|
},
|
|
40
114
|
{
|
|
41
115
|
type: 'select',
|
|
42
116
|
name: 'provider',
|
|
43
117
|
message: 'LLM provider:',
|
|
44
|
-
choices:
|
|
45
|
-
title: p.title.padEnd(30) + ' ' + p.description,
|
|
46
|
-
value: p.value,
|
|
47
|
-
})),
|
|
118
|
+
choices: PROVIDER_CHOICES,
|
|
48
119
|
initial: 0,
|
|
49
120
|
},
|
|
50
|
-
], { onCancel: () =>
|
|
51
|
-
if (!
|
|
121
|
+
], { onCancel: () => false });
|
|
122
|
+
if (!raw.projectName || !raw.architecture || !raw.provider)
|
|
52
123
|
return null;
|
|
53
|
-
return
|
|
124
|
+
return {
|
|
125
|
+
projectName: raw.projectName,
|
|
126
|
+
architecture: raw.architecture,
|
|
127
|
+
skillPacks: (raw.skillPacks ?? []),
|
|
128
|
+
deployMode: (raw.deployMode ?? undefined),
|
|
129
|
+
provider: raw.provider,
|
|
130
|
+
};
|
|
54
131
|
}
|
package/dist/src/scaffold.js
CHANGED
|
@@ -39,6 +39,11 @@ const fs = __importStar(require("fs"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const child_process_1 = require("child_process");
|
|
41
41
|
const TEMPLATES_DIR = path.resolve(__dirname, '../../templates');
|
|
42
|
+
const BASE_DIR = path.join(TEMPLATES_DIR, 'base');
|
|
43
|
+
const DEPLOY_DIR = path.join(TEMPLATES_DIR, 'deploy');
|
|
44
|
+
// Only these architectures use the base + deploy layer pattern.
|
|
45
|
+
// The rest (coding, autonomous, minimal) are self-contained in base/.
|
|
46
|
+
const LAYERED = ['multi-agent', 'single'];
|
|
42
47
|
// ── Provider env var hints ────────────────────────────────────────────────────
|
|
43
48
|
const PROVIDER_ENV = {
|
|
44
49
|
anthropic: { varName: 'ANTHROPIC_API_KEY', placeholder: 'sk-ant-...', model: 'claude-sonnet-4-6' },
|
|
@@ -46,54 +51,123 @@ const PROVIDER_ENV = {
|
|
|
46
51
|
deepseek: { varName: 'LLM_API_KEY', placeholder: 'sk-...', model: 'deepseek-chat' },
|
|
47
52
|
ollama: { varName: 'OLLAMA_BASE_URL', placeholder: 'http://localhost:11434', model: 'llama3.2' },
|
|
48
53
|
};
|
|
49
|
-
// ── Copy
|
|
54
|
+
// ── Copy directory (overlay-safe) ─────────────────────────────────────────────
|
|
55
|
+
// dst may already exist from the base layer — files are overwritten, dirs merged.
|
|
50
56
|
function copyDir(src, dst) {
|
|
51
57
|
fs.mkdirSync(dst, { recursive: true });
|
|
52
58
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
53
59
|
const srcPath = path.join(src, entry.name);
|
|
54
60
|
const dstPath = path.join(dst, entry.name);
|
|
55
|
-
|
|
56
|
-
copyDir(srcPath, dstPath);
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
fs.copyFileSync(srcPath, dstPath);
|
|
60
|
-
}
|
|
61
|
+
entry.isDirectory() ? copyDir(srcPath, dstPath) : fs.copyFileSync(srcPath, dstPath);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
// ──
|
|
64
|
-
|
|
64
|
+
// ── Patch package.json — name + scripts ──────────────────────────────────────
|
|
65
|
+
const DEPLOY_SCRIPTS = {
|
|
66
|
+
repl: (s) => {
|
|
67
|
+
delete s['server'];
|
|
68
|
+
},
|
|
69
|
+
'mcp-http': (s) => {
|
|
70
|
+
s['dev'] = 'ts-node src/server.ts';
|
|
71
|
+
s['start'] = 'node dist/server.js';
|
|
72
|
+
delete s['server'];
|
|
73
|
+
},
|
|
74
|
+
'mcp-stdio': (s) => {
|
|
75
|
+
s['dev'] = 'ts-node src/server.ts';
|
|
76
|
+
s['start'] = 'node dist/server.js';
|
|
77
|
+
delete s['server'];
|
|
78
|
+
},
|
|
79
|
+
// hybrid: keep both dev (REPL) and server (MCP) — no changes
|
|
80
|
+
library: (s) => {
|
|
81
|
+
delete s['dev'];
|
|
82
|
+
delete s['server'];
|
|
83
|
+
delete s['start'];
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
const MCP_DEPLOY_MODES = ['mcp-http', 'mcp-stdio', 'hybrid'];
|
|
87
|
+
function patchPackageJson(dir, projectName, deployMode) {
|
|
65
88
|
const pkgPath = path.join(dir, 'package.json');
|
|
66
89
|
if (!fs.existsSync(pkgPath))
|
|
67
90
|
return;
|
|
68
91
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
69
92
|
pkg.name = projectName;
|
|
93
|
+
if (deployMode && DEPLOY_SCRIPTS[deployMode]) {
|
|
94
|
+
pkg.scripts ??= {};
|
|
95
|
+
DEPLOY_SCRIPTS[deployMode](pkg.scripts);
|
|
96
|
+
}
|
|
97
|
+
if (deployMode && MCP_DEPLOY_MODES.includes(deployMode)) {
|
|
98
|
+
pkg.dependencies ??= {};
|
|
99
|
+
pkg.dependencies['@backendkit-labs/mcp-server'] ??= '^0.15.0';
|
|
100
|
+
}
|
|
70
101
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
71
102
|
}
|
|
72
|
-
// ── Write .env
|
|
103
|
+
// ── Write .env stub ───────────────────────────────────────────────────────────
|
|
73
104
|
function writeEnv(dir, provider) {
|
|
74
105
|
const envExample = path.join(dir, '.env.example');
|
|
75
106
|
const envFile = path.join(dir, '.env');
|
|
76
|
-
// Only create .env if .env.example exists and .env doesn't
|
|
77
107
|
if (!fs.existsSync(envExample) || fs.existsSync(envFile))
|
|
78
108
|
return;
|
|
79
109
|
const p = PROVIDER_ENV[provider];
|
|
80
|
-
const content =
|
|
110
|
+
const content = [
|
|
111
|
+
'# Generated by create-bk-agent',
|
|
112
|
+
`${p.varName}=${p.placeholder}`,
|
|
113
|
+
`LLM_PROVIDER=${provider}`,
|
|
114
|
+
`LLM_MODEL=${p.model}`,
|
|
115
|
+
...(provider !== 'anthropic' && provider !== 'ollama'
|
|
116
|
+
? ['LLM_BASE_URL=https://api.openai.com/v1']
|
|
117
|
+
: []),
|
|
118
|
+
'',
|
|
119
|
+
].join('\n');
|
|
81
120
|
fs.writeFileSync(envFile, content);
|
|
82
121
|
}
|
|
122
|
+
// ── Inject skill pack hint into engine.ts ─────────────────────────────────────
|
|
123
|
+
// Appends a clearly marked comment block so the developer knows what to wire.
|
|
124
|
+
// Full YAML-based skill injection is handled by `create-bk-agent add skills`.
|
|
125
|
+
const SKILL_HINTS = {
|
|
126
|
+
node: 'npm, jest, tsc, eslint — Node.js / TypeScript conventions',
|
|
127
|
+
python: 'pytest, ruff, mypy, pip — Python conventions',
|
|
128
|
+
go: 'go test, golangci-lint — Go conventions',
|
|
129
|
+
docker: 'docker, kubectl, helm — Docker / K8s conventions',
|
|
130
|
+
};
|
|
131
|
+
function injectSkillPack(targetDir, pack) {
|
|
132
|
+
const engineFile = path.join(targetDir, 'src', 'engine.ts');
|
|
133
|
+
if (!fs.existsSync(engineFile))
|
|
134
|
+
return;
|
|
135
|
+
const block = [
|
|
136
|
+
'',
|
|
137
|
+
`// ── Skill pack: ${pack} ──────────────────────────────────────────────────────`,
|
|
138
|
+
`// ${SKILL_HINTS[pack]}`,
|
|
139
|
+
`// To activate: add YAML skill files in .backendkit-agent/skills/`,
|
|
140
|
+
`// and wire SkillLoader + SkillActivator into the engine above.`,
|
|
141
|
+
].join('\n');
|
|
142
|
+
fs.appendFileSync(engineFile, block + '\n');
|
|
143
|
+
}
|
|
83
144
|
// ── Main scaffold ─────────────────────────────────────────────────────────────
|
|
84
145
|
function scaffold(choices, targetDir) {
|
|
85
|
-
const templateDir = path.join(TEMPLATES_DIR, choices.template);
|
|
86
|
-
if (!fs.existsSync(templateDir)) {
|
|
87
|
-
throw new Error(`Template not found: ${choices.template} (looked in ${templateDir})`);
|
|
88
|
-
}
|
|
89
146
|
if (fs.existsSync(targetDir)) {
|
|
90
147
|
throw new Error(`Directory already exists: ${targetDir}`);
|
|
91
148
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
149
|
+
const isLayered = LAYERED.includes(choices.architecture);
|
|
150
|
+
// 1. Base layer — always copied
|
|
151
|
+
const baseDir = path.join(BASE_DIR, choices.architecture);
|
|
152
|
+
if (!fs.existsSync(baseDir)) {
|
|
153
|
+
throw new Error(`Base template not found: ${choices.architecture} (in ${baseDir})`);
|
|
154
|
+
}
|
|
155
|
+
copyDir(baseDir, targetDir);
|
|
156
|
+
// 2. Deploy layer — overlaid on top of base (layered architectures only)
|
|
157
|
+
if (isLayered && choices.deployMode && choices.deployMode !== 'library') {
|
|
158
|
+
const deployDir = path.join(DEPLOY_DIR, choices.deployMode);
|
|
159
|
+
if (!fs.existsSync(deployDir)) {
|
|
160
|
+
throw new Error(`Deploy template not found: ${choices.deployMode} (in ${deployDir})`);
|
|
161
|
+
}
|
|
162
|
+
copyDir(deployDir, targetDir);
|
|
163
|
+
}
|
|
164
|
+
// 3. Skill packs — appended to engine.ts
|
|
165
|
+
for (const pack of choices.skillPacks) {
|
|
166
|
+
injectSkillPack(targetDir, pack);
|
|
167
|
+
}
|
|
168
|
+
// 4. Patch package.json (name + deploy-mode script adjustments)
|
|
169
|
+
patchPackageJson(targetDir, choices.projectName, isLayered ? choices.deployMode : undefined);
|
|
170
|
+
// 5. .env stub
|
|
97
171
|
writeEnv(targetDir, choices.provider);
|
|
98
172
|
}
|
|
99
173
|
function installDeps(targetDir) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-autonomous-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Autonomous agent — activates on cron, webhooks, or custom events",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "ts-node src/index.ts",
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"start": "node dist/index.js",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@backendkit-labs/agent-core": "^0.16.0",
|
|
15
|
+
"dotenv": "^16.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.0.0",
|
|
19
|
+
"ts-node": "^10.9.0",
|
|
20
|
+
"typescript": "^5.4.0"
|
|
21
|
+
},
|
|
22
|
+
"engines": { "node": ">=20.0.0" }
|
|
23
|
+
}
|