create-bk-agent 0.20.0 → 0.20.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 +1 -1
- package/dist/src/add/detector.js +6 -1
- package/dist/src/add/index.js +10 -0
- package/dist/src/add/modules/knowledge.js +254 -0
- package/dist/src/index.js +11 -0
- package/dist/src/prompts.js +17 -1
- package/dist/src/scaffold.js +12 -3
- package/package.json +1 -1
- package/templates/base/enterprise/.env.example +13 -0
- package/templates/base/enterprise/package.json +27 -0
- package/templates/base/enterprise/src/agents/domain-specialist.ts +24 -0
- package/templates/base/enterprise/src/agents/orchestrator.ts +20 -0
- package/templates/base/enterprise/src/config.ts +20 -0
- package/templates/base/enterprise/src/engine.ts +90 -0
- package/templates/base/enterprise/src/tools/example-tools.ts +19 -0
- package/templates/base/enterprise/tsconfig.json +17 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npx create-bk-agent my-project
|
|
|
11
11
|
The CLI asks three questions and generates a production-ready project:
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
✨ create-bk-agent v0.
|
|
14
|
+
✨ create-bk-agent v0.20.0
|
|
15
15
|
|
|
16
16
|
? Project name: my-project
|
|
17
17
|
? Architecture: Multi-agent (orchestrator + specialists)
|
package/dist/src/add/detector.js
CHANGED
|
@@ -53,12 +53,16 @@ function detectProject(cwd) {
|
|
|
53
53
|
};
|
|
54
54
|
if (!deps['@backendkit-labs/agent-core'])
|
|
55
55
|
return null;
|
|
56
|
+
const isEnterprise = !!deps['@backendkit-labs/agent-enterprise'];
|
|
56
57
|
const engineFile = path.join(cwd, 'src', 'engine.ts');
|
|
57
58
|
let architecture = 'unknown';
|
|
58
59
|
let engineFnName = 'createEngine';
|
|
59
60
|
if (fs.existsSync(engineFile)) {
|
|
60
61
|
const content = fs.readFileSync(engineFile, 'utf-8');
|
|
61
|
-
if (
|
|
62
|
+
if (isEnterprise && content.includes('ObsidianRAGProvider')) {
|
|
63
|
+
architecture = 'enterprise';
|
|
64
|
+
}
|
|
65
|
+
else if (content.includes('ORCHESTRATOR_PROFILE') || content.includes("id: 'orchestrator'")) {
|
|
62
66
|
architecture = 'multi-agent';
|
|
63
67
|
}
|
|
64
68
|
else if (content.includes('export function create')) {
|
|
@@ -76,5 +80,6 @@ function detectProject(cwd) {
|
|
|
76
80
|
serverFile: path.join(cwd, 'src', 'server.ts'),
|
|
77
81
|
pkgFile,
|
|
78
82
|
engineFnName,
|
|
83
|
+
isEnterprise,
|
|
79
84
|
};
|
|
80
85
|
}
|
package/dist/src/add/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const specialist_1 = require("./modules/specialist");
|
|
|
7
7
|
const tools_1 = require("./modules/tools");
|
|
8
8
|
const mcp_client_1 = require("./modules/mcp-client");
|
|
9
9
|
const plugin_1 = require("./modules/plugin");
|
|
10
|
+
const knowledge_1 = require("./modules/knowledge");
|
|
10
11
|
const HELP = `
|
|
11
12
|
Usage: npx create-bk-agent add <module> [options]
|
|
12
13
|
|
|
@@ -21,6 +22,9 @@ Modules:
|
|
|
21
22
|
tools <domain> Add a tool file for a new domain
|
|
22
23
|
(creates src/tools/<domain>-tools.ts and patches engine.ts)
|
|
23
24
|
|
|
25
|
+
knowledge <path> Add a folder or vault to the RAG knowledge base (enterprise agents)
|
|
26
|
+
--vault <path> Register an entirely new Obsidian vault (instead of a subfolder)
|
|
27
|
+
|
|
24
28
|
mcp-client <url> Add a remote MCP server to the engine
|
|
25
29
|
--name <s> Name for the mcpServers entry (default: last URL segment)
|
|
26
30
|
|
|
@@ -31,6 +35,9 @@ Examples:
|
|
|
31
35
|
npx create-bk-agent add server --port 4000
|
|
32
36
|
npx create-bk-agent add specialist data-analyst
|
|
33
37
|
npx create-bk-agent add tools github
|
|
38
|
+
npx create-bk-agent add knowledge ./docs
|
|
39
|
+
npx create-bk-agent add knowledge "04-Recursos/Backend"
|
|
40
|
+
npx create-bk-agent add knowledge --vault "C:/Company/HR-Vault"
|
|
34
41
|
npx create-bk-agent add mcp-client http://localhost:3001/mcp --name billing
|
|
35
42
|
npx create-bk-agent add plugin docker
|
|
36
43
|
npx create-bk-agent add plugin k8s
|
|
@@ -68,6 +75,9 @@ function runAdd(args, cwd) {
|
|
|
68
75
|
case 'plugin':
|
|
69
76
|
(0, plugin_1.addPlugin)(info, rest);
|
|
70
77
|
break;
|
|
78
|
+
case 'knowledge':
|
|
79
|
+
(0, knowledge_1.addKnowledge)(info, rest);
|
|
80
|
+
break;
|
|
71
81
|
default:
|
|
72
82
|
console.error(`\n✗ Unknown module: "${module}"\n`);
|
|
73
83
|
console.log(HELP);
|
|
@@ -0,0 +1,254 @@
|
|
|
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.addKnowledge = addKnowledge;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
40
|
+
const patcher_1 = require("../patcher");
|
|
41
|
+
function parseArgs(args) {
|
|
42
|
+
const vaultIdx = args.indexOf('--vault');
|
|
43
|
+
if (vaultIdx !== -1) {
|
|
44
|
+
const target = args[vaultIdx + 1];
|
|
45
|
+
if (!target)
|
|
46
|
+
return null;
|
|
47
|
+
return { target, isNewVault: true };
|
|
48
|
+
}
|
|
49
|
+
if (args[0])
|
|
50
|
+
return { target: args[0], isNewVault: false };
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Adds `folderPath` to the `folders:` array inside ObsidianRAGProvider({...}).
|
|
55
|
+
*
|
|
56
|
+
* Handles three states of engine.ts:
|
|
57
|
+
* 1. `folders:` already exists (uncommented) → append entry
|
|
58
|
+
* 2. `// folders:` is commented out (template default) → uncomment + set
|
|
59
|
+
* 3. No `folders:` at all → insert before closing `}` of RAG constructor
|
|
60
|
+
*/
|
|
61
|
+
function patchFolders(engineFile, folderPath) {
|
|
62
|
+
if (!fs.existsSync(engineFile))
|
|
63
|
+
return 'no_rag_found';
|
|
64
|
+
const content = fs.readFileSync(engineFile, 'utf-8');
|
|
65
|
+
if (!content.includes('ObsidianRAGProvider'))
|
|
66
|
+
return 'no_rag_found';
|
|
67
|
+
const normalized = folderPath.replace(/\\/g, '/');
|
|
68
|
+
if (content.includes(`'${normalized}'`))
|
|
69
|
+
return 'already_exists';
|
|
70
|
+
// Case 1: folders array already exists (uncommented)
|
|
71
|
+
if (/\bfolders:\s*\[/.test(content)) {
|
|
72
|
+
const patched = content.replace(/(\bfolders:\s*\[)([^\]]*)(\])/, (_full, open, inner, close) => {
|
|
73
|
+
const trimmed = inner.trimEnd();
|
|
74
|
+
const needsComma = trimmed.length > 0 && !trimmed.endsWith(',');
|
|
75
|
+
return `${open}${inner}${needsComma ? ',' : ''}\n '${normalized}',\n ${close}`;
|
|
76
|
+
});
|
|
77
|
+
if (patched !== content) {
|
|
78
|
+
fs.writeFileSync(engineFile, patched);
|
|
79
|
+
return 'added';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Case 2: folders line is commented out (// TODO: ... \n // folders: [...])
|
|
83
|
+
if (content.includes('// folders:') || content.includes('// TODO: restrict indexing')) {
|
|
84
|
+
const patched = content.replace(/[ \t]*\/\/\s*TODO:\s*restrict indexing[^\n]*\n[ \t]*\/\/\s*folders:[^\n]*\n/, ` folders: ['${normalized}'],\n`);
|
|
85
|
+
if (patched !== content) {
|
|
86
|
+
fs.writeFileSync(engineFile, patched);
|
|
87
|
+
return 'added';
|
|
88
|
+
}
|
|
89
|
+
// Fallback: just the commented folders line
|
|
90
|
+
const patched2 = content.replace(/[ \t]*\/\/\s*folders:[^\n]*\n/, ` folders: ['${normalized}'],\n`);
|
|
91
|
+
if (patched2 !== content) {
|
|
92
|
+
fs.writeFileSync(engineFile, patched2);
|
|
93
|
+
return 'added';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Case 3: no folders key — insert before closing `}` of RAG constructor
|
|
97
|
+
const lines = content.split('\n');
|
|
98
|
+
let inRag = false;
|
|
99
|
+
let depth = 0;
|
|
100
|
+
let closeIdx = -1;
|
|
101
|
+
for (let i = 0; i < lines.length; i++) {
|
|
102
|
+
if (lines[i].includes('new ObsidianRAGProvider(')) {
|
|
103
|
+
inRag = true;
|
|
104
|
+
depth = 0;
|
|
105
|
+
}
|
|
106
|
+
if (inRag) {
|
|
107
|
+
depth += (lines[i].match(/\{/g) ?? []).length;
|
|
108
|
+
depth -= (lines[i].match(/\}/g) ?? []).length;
|
|
109
|
+
if (depth === 0) {
|
|
110
|
+
closeIdx = i;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (closeIdx === -1)
|
|
116
|
+
return 'no_rag_found';
|
|
117
|
+
lines.splice(closeIdx, 0, ` folders: ['${normalized}'],`);
|
|
118
|
+
fs.writeFileSync(engineFile, lines.join('\n'));
|
|
119
|
+
return 'added';
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Adds a second ObsidianRAGProvider for a completely new vault and merges
|
|
123
|
+
* its tool into the existing tools registry.
|
|
124
|
+
* Used when --vault flag is passed to add a new vault, not just a folder.
|
|
125
|
+
*/
|
|
126
|
+
function addNewVault(engineFile, vaultPath) {
|
|
127
|
+
if (!fs.existsSync(engineFile))
|
|
128
|
+
return 'no_rag_found';
|
|
129
|
+
const content = fs.readFileSync(engineFile, 'utf-8');
|
|
130
|
+
if (!content.includes('ObsidianRAGProvider'))
|
|
131
|
+
return 'no_rag_found';
|
|
132
|
+
const normalized = vaultPath.replace(/\\/g, '/');
|
|
133
|
+
const varName = `rag_${path.basename(normalized).replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}`;
|
|
134
|
+
if (content.includes(normalized))
|
|
135
|
+
return 'already_exists';
|
|
136
|
+
// Insert new RAG instance after the first one
|
|
137
|
+
const ragBlock = [
|
|
138
|
+
'',
|
|
139
|
+
` // Additional vault: ${path.basename(normalized)}`,
|
|
140
|
+
` const ${varName} = new ObsidianRAGProvider({`,
|
|
141
|
+
` vaultPath: '${normalized}',`,
|
|
142
|
+
` ollamaHost: config.ollamaHost,`,
|
|
143
|
+
` embeddingModel: config.embeddingModel,`,
|
|
144
|
+
` });`,
|
|
145
|
+
].join('\n');
|
|
146
|
+
const patched = content.replace(/(const rag = new ObsidianRAGProvider\(\{[\s\S]*?\}\);)/, `$1\n${ragBlock}`);
|
|
147
|
+
// Register the new tool
|
|
148
|
+
const withTool = patched.replace(/tools\s*\n?\s*\.register\(rag\.createTool\(\)\)/, `tools\n .register(rag.createTool())\n .register(${varName}.createTool())`);
|
|
149
|
+
if (withTool === content)
|
|
150
|
+
return 'no_rag_found';
|
|
151
|
+
fs.writeFileSync(engineFile, withTool);
|
|
152
|
+
return 'added';
|
|
153
|
+
}
|
|
154
|
+
// ── Non-enterprise setup ──────────────────────────────────────────────────────
|
|
155
|
+
function setupRagFromScratch(info, vaultPath) {
|
|
156
|
+
console.log('\n📦 Installing @backendkit-labs/agent-enterprise ...');
|
|
157
|
+
const added = (0, patcher_1.addPackageDependency)(info.pkgFile, '@backendkit-labs/agent-enterprise', '^0.1.0');
|
|
158
|
+
if (added) {
|
|
159
|
+
(0, child_process_1.execSync)('npm install', { cwd: info.cwd, stdio: 'inherit' });
|
|
160
|
+
}
|
|
161
|
+
if (!fs.existsSync(info.engineFile)) {
|
|
162
|
+
console.log('\n⚠ src/engine.ts not found — add RAG manually:');
|
|
163
|
+
printManualInstructions(vaultPath);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const content = fs.readFileSync(info.engineFile, 'utf-8');
|
|
167
|
+
if (content.includes('ObsidianRAGProvider')) {
|
|
168
|
+
console.log('\n⚠ ObsidianRAGProvider already present in engine.ts');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Add import
|
|
172
|
+
(0, patcher_1.addImportLine)(info.engineFile, `import { ObsidianRAGProvider } from '@backendkit-labs/agent-enterprise';`);
|
|
173
|
+
// Insert RAG instance + tool registration
|
|
174
|
+
const normalized = vaultPath.replace(/\\/g, '/');
|
|
175
|
+
const ragBlock = [
|
|
176
|
+
'',
|
|
177
|
+
` // Knowledge base (RAG over Obsidian vault)`,
|
|
178
|
+
` const rag = new ObsidianRAGProvider({`,
|
|
179
|
+
` vaultPath: '${normalized}',`,
|
|
180
|
+
` // ollamaHost: process.env.OLLAMA_HOST ?? 'http://localhost:11434',`,
|
|
181
|
+
` // embeddingModel: process.env.EMBEDDING_MODEL ?? 'nomic-embed-text',`,
|
|
182
|
+
` });`,
|
|
183
|
+
'',
|
|
184
|
+
].join('\n');
|
|
185
|
+
// Insert before first ToolRegistry or ToolRegistry instantiation
|
|
186
|
+
let patched = fs.readFileSync(info.engineFile, 'utf-8');
|
|
187
|
+
patched = patched.replace(/(const tools = new ToolRegistry\(\);)/, `${ragBlock} $1`);
|
|
188
|
+
// Register the tool
|
|
189
|
+
patched = patched.replace(/(const tools = new ToolRegistry\(\);[\s\S]*?tools)/, (match) => match.replace(/tools\s*\n?\s*\.register\(/, `tools\n .register(rag.createTool())\n .register(`));
|
|
190
|
+
fs.writeFileSync(info.engineFile, patched);
|
|
191
|
+
console.log('\n✓ Added ObsidianRAGProvider to src/engine.ts');
|
|
192
|
+
console.log('✓ Registered search_knowledge tool');
|
|
193
|
+
console.log('\n Make sure Ollama is running and nomic-embed-text is available:');
|
|
194
|
+
console.log(' ollama pull nomic-embed-text\n');
|
|
195
|
+
}
|
|
196
|
+
function printManualInstructions(vaultPath) {
|
|
197
|
+
const normalized = vaultPath.replace(/\\/g, '/');
|
|
198
|
+
console.log(`
|
|
199
|
+
1. Import in engine.ts:
|
|
200
|
+
import { ObsidianRAGProvider } from '@backendkit-labs/agent-enterprise';
|
|
201
|
+
|
|
202
|
+
2. Create RAG instance:
|
|
203
|
+
const rag = new ObsidianRAGProvider({ vaultPath: '${normalized}' });
|
|
204
|
+
|
|
205
|
+
3. Register tool:
|
|
206
|
+
tools.register(rag.createTool());
|
|
207
|
+
`);
|
|
208
|
+
}
|
|
209
|
+
// ── Main export ───────────────────────────────────────────────────────────────
|
|
210
|
+
const USAGE = `
|
|
211
|
+
Usage: npx create-bk-agent add knowledge <folder-path>
|
|
212
|
+
npx create-bk-agent add knowledge --vault <vault-path>
|
|
213
|
+
|
|
214
|
+
Examples:
|
|
215
|
+
npx create-bk-agent add knowledge ./docs
|
|
216
|
+
npx create-bk-agent add knowledge "04-Recursos/Backend"
|
|
217
|
+
npx create-bk-agent add knowledge --vault "C:/Company/HR-Vault"
|
|
218
|
+
`;
|
|
219
|
+
function addKnowledge(info, args) {
|
|
220
|
+
const parsed = parseArgs(args);
|
|
221
|
+
if (!parsed) {
|
|
222
|
+
console.log(USAGE);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
const absPath = path.resolve(info.cwd, parsed.target);
|
|
226
|
+
if (!info.isEnterprise) {
|
|
227
|
+
console.log('\n⚠ This project does not use @backendkit-labs/agent-enterprise.');
|
|
228
|
+
console.log(' Setting up RAG from scratch...\n');
|
|
229
|
+
setupRagFromScratch(info, absPath);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
let result;
|
|
233
|
+
if (parsed.isNewVault) {
|
|
234
|
+
result = addNewVault(info.engineFile, absPath);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
result = patchFolders(info.engineFile, absPath);
|
|
238
|
+
}
|
|
239
|
+
switch (result) {
|
|
240
|
+
case 'added':
|
|
241
|
+
console.log(`\n✓ Added knowledge source to src/engine.ts`);
|
|
242
|
+
console.log(` Path: ${absPath}`);
|
|
243
|
+
console.log('\n The vault will be re-indexed on next run (incremental — only new/changed files).\n');
|
|
244
|
+
break;
|
|
245
|
+
case 'already_exists':
|
|
246
|
+
console.log(`\n⚠ '${absPath}' is already registered in engine.ts\n`);
|
|
247
|
+
break;
|
|
248
|
+
case 'no_rag_found':
|
|
249
|
+
console.log('\n✗ Could not find ObsidianRAGProvider in src/engine.ts');
|
|
250
|
+
console.log(' Add it manually:\n');
|
|
251
|
+
printManualInstructions(absPath);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -57,6 +57,7 @@ function describeChoices(choices) {
|
|
|
57
57
|
coding: 'Coding assistant',
|
|
58
58
|
autonomous: 'Autonomous daemon',
|
|
59
59
|
minimal: 'Minimal / embedded',
|
|
60
|
+
enterprise: 'Enterprise (local LLM + RAG)',
|
|
60
61
|
};
|
|
61
62
|
const deploy = {
|
|
62
63
|
repl: 'Terminal REPL',
|
|
@@ -80,6 +81,16 @@ function buildNextSteps(choices) {
|
|
|
80
81
|
return ['Configure triggers in src/index.ts', 'npm run dev'];
|
|
81
82
|
if (arch === 'minimal')
|
|
82
83
|
return ['Implement your LLM provider in src/index.ts', 'npm run dev'];
|
|
84
|
+
// Enterprise — local LLM + RAG
|
|
85
|
+
if (arch === 'enterprise') {
|
|
86
|
+
return [
|
|
87
|
+
'Make sure Ollama is running: ollama serve',
|
|
88
|
+
'Pull required models: ollama pull llama3.2 && ollama pull nomic-embed-text',
|
|
89
|
+
'Review your vault path in .env (VAULT_PATH)',
|
|
90
|
+
'Customize agents in src/agents/ for your business domain',
|
|
91
|
+
mode === 'repl' ? 'npm run dev' : 'npm run server',
|
|
92
|
+
];
|
|
93
|
+
}
|
|
83
94
|
// Layered architectures (multi-agent, single) — vary by deploy mode
|
|
84
95
|
const domainSteps = arch === 'multi-agent'
|
|
85
96
|
? ['Rename agents/ and tools/ to match your domain', 'Implement tools in src/tools/']
|
package/dist/src/prompts.js
CHANGED
|
@@ -33,6 +33,11 @@ const ARCHITECTURE_CHOICES = [
|
|
|
33
33
|
value: 'minimal',
|
|
34
34
|
description: 'Best for embedding in an existing app or custom architecture',
|
|
35
35
|
},
|
|
36
|
+
{
|
|
37
|
+
title: p('Enterprise', 22) + 'Local LLM + Obsidian knowledge base — no cloud needed',
|
|
38
|
+
value: 'enterprise',
|
|
39
|
+
description: 'Business agent with RAG over your company docs, runs 100% on-premise',
|
|
40
|
+
},
|
|
36
41
|
];
|
|
37
42
|
const SKILL_PACK_CHOICES = [
|
|
38
43
|
{ title: p('Node.js / TypeScript', 24) + 'npm, jest, tsc, eslint', value: 'node' },
|
|
@@ -71,12 +76,14 @@ const PROVIDER_CHOICES = [
|
|
|
71
76
|
];
|
|
72
77
|
// ── Conditional question helpers ──────────────────────────────────────────────
|
|
73
78
|
const NEEDS_SKILLS = ['multi-agent', 'single'];
|
|
74
|
-
const NEEDS_DEPLOY = ['multi-agent', 'single', 'coding'];
|
|
79
|
+
const NEEDS_DEPLOY = ['multi-agent', 'single', 'coding', 'enterprise'];
|
|
75
80
|
// @types/prompts doesn't fully type the function form of `type` — cast at call site.
|
|
76
81
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
82
|
const whenSkills = (_prev, v) => NEEDS_SKILLS.includes(v.architecture) ? 'multiselect' : null;
|
|
78
83
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
84
|
const whenDeploy = (_prev, v) => NEEDS_DEPLOY.includes(v.architecture) ? 'select' : null;
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
const whenVault = (_prev, v) => v.architecture === 'enterprise' ? 'text' : null;
|
|
80
87
|
// ── Main question flow ────────────────────────────────────────────────────────
|
|
81
88
|
async function askQuestions(defaultName) {
|
|
82
89
|
const raw = await (0, prompts_1.default)([
|
|
@@ -111,6 +118,14 @@ async function askQuestions(defaultName) {
|
|
|
111
118
|
choices: DEPLOY_MODE_CHOICES,
|
|
112
119
|
initial: 0,
|
|
113
120
|
},
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
|
+
{
|
|
123
|
+
type: whenVault,
|
|
124
|
+
name: 'vaultPath',
|
|
125
|
+
message: 'Obsidian vault path (absolute):',
|
|
126
|
+
initial: '',
|
|
127
|
+
validate: (v) => v.trim().length > 0 || 'Vault path is required for enterprise agents',
|
|
128
|
+
},
|
|
114
129
|
{
|
|
115
130
|
type: 'select',
|
|
116
131
|
name: 'provider',
|
|
@@ -127,5 +142,6 @@ async function askQuestions(defaultName) {
|
|
|
127
142
|
skillPacks: (raw.skillPacks ?? []),
|
|
128
143
|
deployMode: (raw.deployMode ?? undefined),
|
|
129
144
|
provider: raw.provider,
|
|
145
|
+
vaultPath: (raw.vaultPath ?? undefined),
|
|
130
146
|
};
|
|
131
147
|
}
|
package/dist/src/scaffold.js
CHANGED
|
@@ -43,7 +43,7 @@ const BASE_DIR = path.join(TEMPLATES_DIR, 'base');
|
|
|
43
43
|
const DEPLOY_DIR = path.join(TEMPLATES_DIR, 'deploy');
|
|
44
44
|
// Only these architectures use the base + deploy layer pattern.
|
|
45
45
|
// The rest (coding, autonomous, minimal) are self-contained in base/.
|
|
46
|
-
const LAYERED = ['multi-agent', 'single'];
|
|
46
|
+
const LAYERED = ['multi-agent', 'single', 'enterprise'];
|
|
47
47
|
// ── Provider env var hints ────────────────────────────────────────────────────
|
|
48
48
|
const PROVIDER_ENV = {
|
|
49
49
|
anthropic: { varName: 'ANTHROPIC_API_KEY', placeholder: 'sk-ant-...', model: 'claude-sonnet-4-6' },
|
|
@@ -101,11 +101,20 @@ function patchPackageJson(dir, projectName, deployMode) {
|
|
|
101
101
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
102
102
|
}
|
|
103
103
|
// ── Write .env stub ───────────────────────────────────────────────────────────
|
|
104
|
-
function writeEnv(dir, provider) {
|
|
104
|
+
function writeEnv(dir, provider, choices) {
|
|
105
105
|
const envExample = path.join(dir, '.env.example');
|
|
106
106
|
const envFile = path.join(dir, '.env');
|
|
107
107
|
if (!fs.existsSync(envExample) || fs.existsSync(envFile))
|
|
108
108
|
return;
|
|
109
|
+
if (choices.architecture === 'enterprise') {
|
|
110
|
+
// Enterprise uses its own .env.example — just inject VAULT_PATH
|
|
111
|
+
let content = fs.readFileSync(envExample, 'utf-8');
|
|
112
|
+
if (choices.vaultPath) {
|
|
113
|
+
content = content.replace(/^VAULT_PATH=.*/m, `VAULT_PATH=${choices.vaultPath}`);
|
|
114
|
+
}
|
|
115
|
+
fs.writeFileSync(envFile, content);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
109
118
|
const p = PROVIDER_ENV[provider];
|
|
110
119
|
const content = [
|
|
111
120
|
'# Generated by create-bk-agent',
|
|
@@ -168,7 +177,7 @@ function scaffold(choices, targetDir) {
|
|
|
168
177
|
// 4. Patch package.json (name + deploy-mode script adjustments)
|
|
169
178
|
patchPackageJson(targetDir, choices.projectName, isLayered ? choices.deployMode : undefined);
|
|
170
179
|
// 5. .env stub
|
|
171
|
-
writeEnv(targetDir, choices.provider);
|
|
180
|
+
writeEnv(targetDir, choices.provider, choices);
|
|
172
181
|
}
|
|
173
182
|
function installDeps(targetDir) {
|
|
174
183
|
(0, child_process_1.execSync)('npm install', { cwd: targetDir, stdio: 'inherit' });
|
package/package.json
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Enterprise Agent — Local LLM + Knowledge Base
|
|
2
|
+
# Make sure Ollama is running before starting: ollama serve
|
|
3
|
+
# Pull required models: ollama pull llama3.2 && ollama pull nomic-embed-text
|
|
4
|
+
|
|
5
|
+
# Local LLM (via Ollama)
|
|
6
|
+
OLLAMA_HOST=http://localhost:11434
|
|
7
|
+
OLLAMA_MODEL=llama3.2
|
|
8
|
+
|
|
9
|
+
# Embeddings model for knowledge base search
|
|
10
|
+
EMBEDDING_MODEL=nomic-embed-text
|
|
11
|
+
|
|
12
|
+
# Absolute path to your Obsidian vault
|
|
13
|
+
VAULT_PATH=/path/to/your/Obsidian/Vault
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Enterprise agent with local LLM and Obsidian knowledge base",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "ts-node src/index.ts",
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"start": "node dist/index.js",
|
|
11
|
+
"server": "ts-node src/server.ts",
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@backendkit-labs/agent-core": "^0.20.0",
|
|
16
|
+
"@backendkit-labs/agent-coding": "^0.19.2",
|
|
17
|
+
"@backendkit-labs/agent-enterprise": "^0.2.0",
|
|
18
|
+
"dotenv": "^16.0.0",
|
|
19
|
+
"zod": "^3.25.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.0.0",
|
|
23
|
+
"ts-node": "^10.9.0",
|
|
24
|
+
"typescript": "^5.4.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": { "node": ">=20.0.0" }
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AgentProfile } from '@backendkit-labs/agent-core';
|
|
2
|
+
|
|
3
|
+
export const DOMAIN_SPECIALIST_PROFILE: AgentProfile = {
|
|
4
|
+
id: 'domain-specialist',
|
|
5
|
+
name: 'Domain Specialist',
|
|
6
|
+
icon: '📚',
|
|
7
|
+
description: 'Answers questions using the company knowledge base',
|
|
8
|
+
systemPrompt: `You are a specialist with access to the company's knowledge base.
|
|
9
|
+
|
|
10
|
+
When answering questions:
|
|
11
|
+
1. Use search_knowledge to find relevant information
|
|
12
|
+
2. Base your answer strictly on what you find in the knowledge base
|
|
13
|
+
3. Quote or reference the source when helpful
|
|
14
|
+
4. If information is not found, clearly say "I don't have information about that in the knowledge base"
|
|
15
|
+
5. Never make up information that isn't in the documentation
|
|
16
|
+
6. When you reach a conclusion or discover something useful, use write_knowledge to persist it for future sessions
|
|
17
|
+
|
|
18
|
+
// TODO: customize this prompt for your specific domain:
|
|
19
|
+
// - HR agent: focus on policies, onboarding, benefits
|
|
20
|
+
// - IT support: focus on troubleshooting guides, procedures
|
|
21
|
+
// - Sales: focus on product catalog, pricing, FAQs`,
|
|
22
|
+
allowedTools: ['search_knowledge', 'write_knowledge'],
|
|
23
|
+
delegatesTo: [],
|
|
24
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentProfile } from '@backendkit-labs/agent-core';
|
|
2
|
+
|
|
3
|
+
export const ORCHESTRATOR_PROFILE: AgentProfile = {
|
|
4
|
+
id: 'orchestrator',
|
|
5
|
+
name: 'Orchestrator',
|
|
6
|
+
icon: '🎯',
|
|
7
|
+
description: 'Routes user requests to the right specialist agent',
|
|
8
|
+
systemPrompt: `You are the main orchestrator for an enterprise knowledge agent.
|
|
9
|
+
Your role is to understand the user's question and route it to the right specialist.
|
|
10
|
+
|
|
11
|
+
Guidelines:
|
|
12
|
+
- Always delegate to domain-specialist for knowledge base questions
|
|
13
|
+
- Use search_knowledge directly for quick lookups
|
|
14
|
+
- Keep your own responses brief — let specialists do the detailed work
|
|
15
|
+
- If the request is outside the company knowledge base, say so clearly
|
|
16
|
+
|
|
17
|
+
// TODO: customize routing logic for your specific business domains`,
|
|
18
|
+
allowedTools: ['search_knowledge', 'ask_agent'],
|
|
19
|
+
delegatesTo: ['domain-specialist'],
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
|
|
3
|
+
export interface Config {
|
|
4
|
+
ollamaHost: string;
|
|
5
|
+
ollamaModel: string;
|
|
6
|
+
embeddingModel: string;
|
|
7
|
+
vaultPath: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function loadConfig(): Config {
|
|
11
|
+
const vaultPath = process.env.VAULT_PATH ?? '';
|
|
12
|
+
if (!vaultPath) throw new Error('Missing VAULT_PATH in .env — set the absolute path to your Obsidian vault');
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
ollamaHost: process.env.OLLAMA_HOST ?? 'http://localhost:11434',
|
|
16
|
+
ollamaModel: process.env.OLLAMA_MODEL ?? 'llama3.2',
|
|
17
|
+
embeddingModel: process.env.EMBEDDING_MODEL ?? 'nomic-embed-text',
|
|
18
|
+
vaultPath,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as os from 'os';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import {
|
|
4
|
+
AgentEngine,
|
|
5
|
+
AgentRegistry,
|
|
6
|
+
ToolRegistry,
|
|
7
|
+
ProviderRegistry,
|
|
8
|
+
CallbackTransport,
|
|
9
|
+
createMemorySystem,
|
|
10
|
+
} from '@backendkit-labs/agent-core';
|
|
11
|
+
import { OllamaProvider } from '@backendkit-labs/agent-coding';
|
|
12
|
+
import { ObsidianRAGProvider, VaultWriter } from '@backendkit-labs/agent-enterprise';
|
|
13
|
+
import { loadConfig } from './config';
|
|
14
|
+
import { ORCHESTRATOR_PROFILE } from './agents/orchestrator';
|
|
15
|
+
import { DOMAIN_SPECIALIST_PROFILE } from './agents/domain-specialist';
|
|
16
|
+
import { exampleAction } from './tools/example-tools';
|
|
17
|
+
|
|
18
|
+
// ── Persistent storage paths ──────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const APP_DIR = path.join(os.homedir(), '.my-agent');
|
|
21
|
+
const HISTORY = path.join(APP_DIR, 'history.json');
|
|
22
|
+
const MEM_DIR = path.join(APP_DIR, 'memory');
|
|
23
|
+
|
|
24
|
+
// ── Memory system ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const memory = createMemorySystem({
|
|
27
|
+
episodic: { provider: 'json', path: path.join(MEM_DIR, 'episodes.json') },
|
|
28
|
+
semantic: { provider: 'json', path: path.join(MEM_DIR, 'knowledge.json') },
|
|
29
|
+
procedural: { provider: 'json', path: path.join(MEM_DIR, 'patterns.json') },
|
|
30
|
+
retention: { maxEpisodes: 500 },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export function createEngine(transport: CallbackTransport): AgentEngine {
|
|
34
|
+
const config = loadConfig();
|
|
35
|
+
|
|
36
|
+
// ── Knowledge base (RAG) ──────────────────────────────────────────────────
|
|
37
|
+
// Indexes the Obsidian vault on first use. Subsequent runs are incremental
|
|
38
|
+
// (only re-embeds new or modified files).
|
|
39
|
+
const rag = new ObsidianRAGProvider({
|
|
40
|
+
vaultPath: config.vaultPath,
|
|
41
|
+
ollamaHost: config.ollamaHost,
|
|
42
|
+
embeddingModel: config.embeddingModel,
|
|
43
|
+
// TODO: restrict indexing to specific folders:
|
|
44
|
+
// folders: ['01-Procesos', '02-Politicas', '04-FAQs'],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ── Vault write-back ──────────────────────────────────────────────────────
|
|
48
|
+
// Agents can contribute new knowledge back to the vault (write_knowledge tool).
|
|
49
|
+
// Notes land in <vaultPath>/agentes/ and are searchable on next index().
|
|
50
|
+
const writer = new VaultWriter({ vaultPath: config.vaultPath });
|
|
51
|
+
|
|
52
|
+
// ── Tools ─────────────────────────────────────────────────────────────────
|
|
53
|
+
const tools = new ToolRegistry();
|
|
54
|
+
tools
|
|
55
|
+
.register(rag.createTool()) // search_knowledge — semantic search
|
|
56
|
+
.register(writer.createTool('specialist')) // write_knowledge — agent write-back
|
|
57
|
+
.register(exampleAction);
|
|
58
|
+
for (const t of memory.getTools()) tools.register(t);
|
|
59
|
+
// TODO: register business-specific tools (CRM, ticketing system, internal APIs)
|
|
60
|
+
|
|
61
|
+
// ── Agents ────────────────────────────────────────────────────────────────
|
|
62
|
+
const agents = new AgentRegistry();
|
|
63
|
+
agents
|
|
64
|
+
.register(ORCHESTRATOR_PROFILE)
|
|
65
|
+
.register(DOMAIN_SPECIALIST_PROFILE);
|
|
66
|
+
// TODO: add more specialists for each business domain
|
|
67
|
+
|
|
68
|
+
// ── LLM Provider (local via Ollama) ───────────────────────────────────────
|
|
69
|
+
const providers = new ProviderRegistry();
|
|
70
|
+
providers.register('default', new OllamaProvider({
|
|
71
|
+
host: config.ollamaHost,
|
|
72
|
+
model: config.ollamaModel,
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
return new AgentEngine({
|
|
76
|
+
model: { provider: 'default', id: config.ollamaModel },
|
|
77
|
+
agents,
|
|
78
|
+
tools,
|
|
79
|
+
providers,
|
|
80
|
+
defaultProvider: 'default',
|
|
81
|
+
defaultAgentId: 'orchestrator',
|
|
82
|
+
transport,
|
|
83
|
+
maxIterations: 30,
|
|
84
|
+
iterationMode: 'auto',
|
|
85
|
+
historyPath: HISTORY,
|
|
86
|
+
maxContextMessages: 100,
|
|
87
|
+
memorySystem: memory,
|
|
88
|
+
additionalRoots: [APP_DIR],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineTool, z } from '@backendkit-labs/agent-core';
|
|
2
|
+
|
|
3
|
+
// Example tool — replace with tools that connect to your business systems
|
|
4
|
+
// e.g. CRM lookup, ticketing system, internal APIs, databases
|
|
5
|
+
|
|
6
|
+
export const exampleAction = defineTool({
|
|
7
|
+
name: 'create_ticket',
|
|
8
|
+
description: 'Creates a support ticket in the ticketing system (example — replace with your integration)',
|
|
9
|
+
input: z.object({
|
|
10
|
+
title: z.string().describe('Short description of the issue'),
|
|
11
|
+
priority: z.enum(['low', 'medium', 'high']).describe('Ticket priority'),
|
|
12
|
+
details: z.string().optional().describe('Additional details'),
|
|
13
|
+
}),
|
|
14
|
+
execute: async ({ title, priority, details }) => {
|
|
15
|
+
// TODO: integrate with your ticketing system (Jira, ServiceNow, Linear, etc.)
|
|
16
|
+
const id = `TICKET-${Date.now()}`;
|
|
17
|
+
return `Ticket created: ${id} — "${title}" [${priority}]${details ? `\nDetails: ${details}` : ''}`;
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"outDir": "dist",
|
|
9
|
+
"rootDir": "src",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"resolveJsonModule": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|