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.
Files changed (48) hide show
  1. package/README.md +93 -29
  2. package/dist/src/add/detector.js +80 -0
  3. package/dist/src/add/index.js +67 -0
  4. package/dist/src/add/modules/mcp-client.js +65 -0
  5. package/dist/src/add/modules/server.js +100 -0
  6. package/dist/src/add/modules/specialist.js +98 -0
  7. package/dist/src/add/modules/tools.js +102 -0
  8. package/dist/src/add/patcher.js +168 -0
  9. package/dist/src/index.js +55 -9
  10. package/dist/src/prompts.js +102 -25
  11. package/dist/src/scaffold.js +95 -21
  12. package/package.json +1 -1
  13. package/templates/base/autonomous/.env.example +4 -0
  14. package/templates/base/autonomous/package.json +23 -0
  15. package/templates/base/autonomous/src/index.ts +67 -0
  16. package/templates/base/autonomous/tsconfig.json +17 -0
  17. package/templates/base/coding/.env.example +4 -0
  18. package/templates/base/coding/AGENT.md +13 -0
  19. package/templates/base/coding/package.json +24 -0
  20. package/templates/base/coding/src/index.ts +42 -0
  21. package/templates/base/coding/tsconfig.json +17 -0
  22. package/templates/base/minimal/.env.example +1 -0
  23. package/templates/base/minimal/package.json +23 -0
  24. package/templates/base/minimal/src/index.ts +43 -0
  25. package/templates/base/minimal/tsconfig.json +17 -0
  26. package/templates/base/multi-agent/.env.example +7 -0
  27. package/templates/base/multi-agent/package.json +26 -0
  28. package/templates/base/multi-agent/src/agents/orchestrator.ts +22 -0
  29. package/templates/base/multi-agent/src/agents/specialist-a.ts +17 -0
  30. package/templates/base/multi-agent/src/agents/specialist-b.ts +16 -0
  31. package/templates/base/multi-agent/src/config.ts +19 -0
  32. package/templates/base/multi-agent/src/engine.ts +49 -0
  33. package/templates/base/multi-agent/src/providers/openai-compatible.ts +104 -0
  34. package/templates/base/multi-agent/src/tools/example-tools.ts +28 -0
  35. package/templates/base/multi-agent/tsconfig.json +17 -0
  36. package/templates/base/single/.env.example +4 -0
  37. package/templates/base/single/package.json +26 -0
  38. package/templates/base/single/src/agents/my-agent.ts +18 -0
  39. package/templates/base/single/src/config.ts +18 -0
  40. package/templates/base/single/src/engine.ts +44 -0
  41. package/templates/base/single/src/providers/openai-compatible.ts +104 -0
  42. package/templates/base/single/src/tools/example-tools.ts +28 -0
  43. package/templates/base/single/tsconfig.json +17 -0
  44. package/templates/deploy/hybrid/src/index.ts +27 -0
  45. package/templates/deploy/hybrid/src/server.ts +26 -0
  46. package/templates/deploy/mcp-http/src/server.ts +26 -0
  47. package/templates/deploy/mcp-stdio/src/server.ts +25 -0
  48. 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
- const NEXT_STEPS = {
52
- coding: ['Edit AGENT.md to describe your project', 'npm run dev'],
53
- 'domain-agent': ['Rename agents/ and tools/ to match your domain', 'Implement tools in src/tools/', 'npm run dev'],
54
- autonomous: ['Configure triggers in src/index.ts', 'npm run dev (or npm start in production)'],
55
- 'mcp-server': ['Register tools in src/server.ts', 'npm run build && npx my-mcp-server'],
56
- minimal: ['Implement your LLMProvider in src/index.ts', 'npm run dev'],
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.template} agent in ./${choices.projectName} ...`);
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 = NEXT_STEPS[choices.template] ?? ['npm run dev'];
128
+ const steps = buildNextSteps(choices);
83
129
  console.log(`
84
130
  ✅ Done! Your agent is ready.
85
131
 
@@ -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
- const TEMPLATES = [
9
- { title: 'coding', value: 'coding', description: '10 agents + all coding tools included. Ready to run — improve UI and extend' },
10
- { title: 'domain-agent', value: 'domain-agent', description: 'Blank multi-agent architecture. You define agents and tools for your domain' },
11
- { title: 'autonomous', value: 'autonomous', description: 'Event-driven agent — cron schedules, webhooks, custom events' },
12
- { title: 'mcp-server', value: 'mcp-server', description: 'MCP server — expose your tools to Claude Desktop / Cursor / VS Code' },
13
- { title: 'minimal', value: 'minimal', description: 'Bare engine — full control, bring your own agents and tools' },
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 PROVIDERS = [
16
- { title: 'Anthropic (Claude)', value: 'anthropic', description: 'claude-sonnet-4-6 recommended' },
17
- { title: 'OpenAI', value: 'openai', description: 'gpt-4o recommended' },
18
- { title: 'DeepSeek / OpenAI-compatible', value: 'deepseek', description: 'deepseek-chat or any OpenAI-compatible endpoint' },
19
- { title: 'Ollama (local)', value: 'ollama', description: 'llama3.2, codellama, qwen2.5 — no API key needed' },
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 answers = await (0, prompts_1.default)([
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: 'template',
33
- message: 'Template:',
34
- choices: TEMPLATES.map(t => ({
35
- title: t.title.padEnd(14) + ' ' + t.description,
36
- value: t.value,
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: PROVIDERS.map(p => ({
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: () => { return false; } });
51
- if (!answers.projectName || !answers.template || !answers.provider)
121
+ ], { onCancel: () => false });
122
+ if (!raw.projectName || !raw.architecture || !raw.provider)
52
123
  return null;
53
- return answers;
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
  }
@@ -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 template ─────────────────────────────────────────────────────────────
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
- if (entry.isDirectory()) {
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
- // ── Substitute project name in package.json ───────────────────────────────────
64
- function patchPackageJson(dir, projectName) {
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 with provider preset ──────────────────────────────────────────
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 = `# Generated by create-bk-agent\n${p.varName}=${p.placeholder}\nLLM_PROVIDER=${provider}\nLLM_MODEL=${p.model}\n`;
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
- // 1. Copy template files
93
- copyDir(templateDir, targetDir);
94
- // 2. Patch package.json name
95
- patchPackageJson(targetDir, choices.projectName);
96
- // 3. Write .env stub
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bk-agent",
3
- "version": "0.16.1",
3
+ "version": "0.17.1",
4
4
  "description": "Create a new BackendKit agent project",
5
5
  "main": "dist/src/index.js",
6
6
  "bin": {
@@ -0,0 +1,4 @@
1
+ ANTHROPIC_API_KEY=sk-ant-...
2
+ # or: OPENAI_API_KEY=sk-...
3
+ LLM_PROVIDER=anthropic
4
+ WEBHOOK_PORT=3000
@@ -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
+ }