create-bk-agent 0.20.0 → 0.21.0
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 +3 -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 +23 -0
- package/dist/src/prompts.js +28 -3
- package/dist/src/scaffold.js +25 -6
- 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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# create-bk-agent
|
|
2
2
|
|
|
3
|
+
**Status: stable** — works and is maintained; will not be expanded during the pilot phase.
|
|
4
|
+
|
|
3
5
|
Scaffold a new [BackendKit agent](https://github.com/BackendKit-labs/backendkit-agent-framework) project in seconds — or add capabilities to an existing one.
|
|
4
6
|
|
|
5
7
|
## Create a project
|
|
@@ -11,7 +13,7 @@ npx create-bk-agent my-project
|
|
|
11
13
|
The CLI asks three questions and generates a production-ready project:
|
|
12
14
|
|
|
13
15
|
```
|
|
14
|
-
✨ create-bk-agent v0.
|
|
16
|
+
✨ create-bk-agent v0.20.0
|
|
15
17
|
|
|
16
18
|
? Project name: my-project
|
|
17
19
|
? 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',
|
|
@@ -64,6 +65,7 @@ function describeChoices(choices) {
|
|
|
64
65
|
'mcp-stdio': 'MCP Server (stdio)',
|
|
65
66
|
hybrid: 'REPL + MCP Server',
|
|
66
67
|
library: 'Library',
|
|
68
|
+
web: 'Web Server (chat + kanban)',
|
|
67
69
|
};
|
|
68
70
|
const archLabel = arch[choices.architecture] ?? choices.architecture;
|
|
69
71
|
if (!choices.deployMode)
|
|
@@ -80,6 +82,27 @@ function buildNextSteps(choices) {
|
|
|
80
82
|
return ['Configure triggers in src/index.ts', 'npm run dev'];
|
|
81
83
|
if (arch === 'minimal')
|
|
82
84
|
return ['Implement your LLM provider in src/index.ts', 'npm run dev'];
|
|
85
|
+
// Enterprise — local LLM + RAG
|
|
86
|
+
if (arch === 'enterprise') {
|
|
87
|
+
if (mode === 'web') {
|
|
88
|
+
return [
|
|
89
|
+
'Make sure Ollama is running: ollama serve',
|
|
90
|
+
'Pull model: ollama pull llama3.2',
|
|
91
|
+
'Review vault path in .env (VAULT_PATH)',
|
|
92
|
+
'npm run dev # indexes vault + starts server on :4001',
|
|
93
|
+
'# Chat: http://localhost:4001/',
|
|
94
|
+
'# Kanban: http://localhost:4001/#kanban',
|
|
95
|
+
'# Monitor: ws://localhost:4001/monitor',
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
return [
|
|
99
|
+
'Make sure Ollama is running: ollama serve',
|
|
100
|
+
'Pull required models: ollama pull llama3.2 && ollama pull nomic-embed-text',
|
|
101
|
+
'Review your vault path in .env (VAULT_PATH)',
|
|
102
|
+
'Customize agents in src/agents/ for your business domain',
|
|
103
|
+
mode === 'repl' ? 'npm run dev' : 'npm run server',
|
|
104
|
+
];
|
|
105
|
+
}
|
|
83
106
|
// Layered architectures (multi-agent, single) — vary by deploy mode
|
|
84
107
|
const domainSteps = arch === 'multi-agent'
|
|
85
108
|
? ['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' },
|
|
@@ -40,7 +45,7 @@ const SKILL_PACK_CHOICES = [
|
|
|
40
45
|
{ title: p('Go', 24) + 'go test, golangci-lint', value: 'go' },
|
|
41
46
|
{ title: p('Docker / K8s', 24) + 'docker, kubectl, helm', value: 'docker' },
|
|
42
47
|
];
|
|
43
|
-
const
|
|
48
|
+
const DEPLOY_MODE_CHOICES_BASE = [
|
|
44
49
|
{
|
|
45
50
|
title: p('Terminal REPL', 28) + 'Humans type requests, agent responds in the terminal',
|
|
46
51
|
value: 'repl',
|
|
@@ -63,6 +68,15 @@ const DEPLOY_MODE_CHOICES = [
|
|
|
63
68
|
value: 'library',
|
|
64
69
|
},
|
|
65
70
|
];
|
|
71
|
+
const WEB_DEPLOY_CHOICE = {
|
|
72
|
+
title: p('Web server', 28) + 'WebSocket chat + kanban monitor UI on port 4001',
|
|
73
|
+
value: 'web',
|
|
74
|
+
description: 'Serves @backendkit-labs/agent-web with all 6 enterprise departments',
|
|
75
|
+
};
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
const deployChoices = (_prev, v) => v.architecture === 'enterprise'
|
|
78
|
+
? [...DEPLOY_MODE_CHOICES_BASE, WEB_DEPLOY_CHOICE]
|
|
79
|
+
: DEPLOY_MODE_CHOICES_BASE;
|
|
66
80
|
const PROVIDER_CHOICES = [
|
|
67
81
|
{ title: p('Anthropic (Claude)', 32) + 'claude-sonnet-4-6 recommended', value: 'anthropic' },
|
|
68
82
|
{ title: p('OpenAI', 32) + 'gpt-4o recommended', value: 'openai' },
|
|
@@ -71,12 +85,14 @@ const PROVIDER_CHOICES = [
|
|
|
71
85
|
];
|
|
72
86
|
// ── Conditional question helpers ──────────────────────────────────────────────
|
|
73
87
|
const NEEDS_SKILLS = ['multi-agent', 'single'];
|
|
74
|
-
const NEEDS_DEPLOY = ['multi-agent', 'single', 'coding'];
|
|
88
|
+
const NEEDS_DEPLOY = ['multi-agent', 'single', 'coding', 'enterprise'];
|
|
75
89
|
// @types/prompts doesn't fully type the function form of `type` — cast at call site.
|
|
76
90
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
91
|
const whenSkills = (_prev, v) => NEEDS_SKILLS.includes(v.architecture) ? 'multiselect' : null;
|
|
78
92
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
93
|
const whenDeploy = (_prev, v) => NEEDS_DEPLOY.includes(v.architecture) ? 'select' : null;
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
+
const whenVault = (_prev, v) => v.architecture === 'enterprise' ? 'text' : null;
|
|
80
96
|
// ── Main question flow ────────────────────────────────────────────────────────
|
|
81
97
|
async function askQuestions(defaultName) {
|
|
82
98
|
const raw = await (0, prompts_1.default)([
|
|
@@ -108,9 +124,17 @@ async function askQuestions(defaultName) {
|
|
|
108
124
|
type: whenDeploy,
|
|
109
125
|
name: 'deployMode',
|
|
110
126
|
message: 'How will this agent be used?',
|
|
111
|
-
choices:
|
|
127
|
+
choices: deployChoices,
|
|
112
128
|
initial: 0,
|
|
113
129
|
},
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
131
|
+
{
|
|
132
|
+
type: whenVault,
|
|
133
|
+
name: 'vaultPath',
|
|
134
|
+
message: 'Obsidian vault path (absolute):',
|
|
135
|
+
initial: '',
|
|
136
|
+
validate: (v) => v.trim().length > 0 || 'Vault path is required for enterprise agents',
|
|
137
|
+
},
|
|
114
138
|
{
|
|
115
139
|
type: 'select',
|
|
116
140
|
name: 'provider',
|
|
@@ -127,5 +151,6 @@ async function askQuestions(defaultName) {
|
|
|
127
151
|
skillPacks: (raw.skillPacks ?? []),
|
|
128
152
|
deployMode: (raw.deployMode ?? undefined),
|
|
129
153
|
provider: raw.provider,
|
|
154
|
+
vaultPath: (raw.vaultPath ?? undefined),
|
|
130
155
|
};
|
|
131
156
|
}
|
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' },
|
|
@@ -77,6 +77,11 @@ const DEPLOY_SCRIPTS = {
|
|
|
77
77
|
delete s['server'];
|
|
78
78
|
},
|
|
79
79
|
// hybrid: keep both dev (REPL) and server (MCP) — no changes
|
|
80
|
+
web: (s) => {
|
|
81
|
+
s['dev'] = 'ts-node src/server.ts';
|
|
82
|
+
s['start'] = 'node dist/server.js';
|
|
83
|
+
delete s['server'];
|
|
84
|
+
},
|
|
80
85
|
library: (s) => {
|
|
81
86
|
delete s['dev'];
|
|
82
87
|
delete s['server'];
|
|
@@ -84,6 +89,7 @@ const DEPLOY_SCRIPTS = {
|
|
|
84
89
|
},
|
|
85
90
|
};
|
|
86
91
|
const MCP_DEPLOY_MODES = ['mcp-http', 'mcp-stdio', 'hybrid'];
|
|
92
|
+
const WEB_DEPLOY_MODES = ['web'];
|
|
87
93
|
function patchPackageJson(dir, projectName, deployMode) {
|
|
88
94
|
const pkgPath = path.join(dir, 'package.json');
|
|
89
95
|
if (!fs.existsSync(pkgPath))
|
|
@@ -98,14 +104,27 @@ function patchPackageJson(dir, projectName, deployMode) {
|
|
|
98
104
|
pkg.dependencies ??= {};
|
|
99
105
|
pkg.dependencies['@backendkit-labs/mcp-server'] ??= '^0.15.0';
|
|
100
106
|
}
|
|
107
|
+
if (deployMode && WEB_DEPLOY_MODES.includes(deployMode)) {
|
|
108
|
+
pkg.dependencies ??= {};
|
|
109
|
+
pkg.dependencies['@backendkit-labs/agent-web'] ??= '^0.1.0';
|
|
110
|
+
}
|
|
101
111
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
102
112
|
}
|
|
103
113
|
// ── Write .env stub ───────────────────────────────────────────────────────────
|
|
104
|
-
function writeEnv(dir, provider) {
|
|
114
|
+
function writeEnv(dir, provider, choices) {
|
|
105
115
|
const envExample = path.join(dir, '.env.example');
|
|
106
116
|
const envFile = path.join(dir, '.env');
|
|
107
117
|
if (!fs.existsSync(envExample) || fs.existsSync(envFile))
|
|
108
118
|
return;
|
|
119
|
+
if (choices.architecture === 'enterprise') {
|
|
120
|
+
// Enterprise uses its own .env.example — just inject VAULT_PATH
|
|
121
|
+
let content = fs.readFileSync(envExample, 'utf-8');
|
|
122
|
+
if (choices.vaultPath) {
|
|
123
|
+
content = content.replace(/^VAULT_PATH=.*/m, `VAULT_PATH=${choices.vaultPath}`);
|
|
124
|
+
}
|
|
125
|
+
fs.writeFileSync(envFile, content);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
109
128
|
const p = PROVIDER_ENV[provider];
|
|
110
129
|
const content = [
|
|
111
130
|
'# Generated by create-bk-agent',
|
|
@@ -156,10 +175,10 @@ function scaffold(choices, targetDir) {
|
|
|
156
175
|
// 2. Deploy layer — overlaid on top of base (layered architectures only)
|
|
157
176
|
if (isLayered && choices.deployMode && choices.deployMode !== 'library') {
|
|
158
177
|
const deployDir = path.join(DEPLOY_DIR, choices.deployMode);
|
|
159
|
-
if (
|
|
160
|
-
|
|
178
|
+
if (fs.existsSync(deployDir)) {
|
|
179
|
+
copyDir(deployDir, targetDir);
|
|
161
180
|
}
|
|
162
|
-
|
|
181
|
+
// No directory = base template already covers this mode
|
|
163
182
|
}
|
|
164
183
|
// 3. Skill packs — appended to engine.ts
|
|
165
184
|
for (const pack of choices.skillPacks) {
|
|
@@ -168,7 +187,7 @@ function scaffold(choices, targetDir) {
|
|
|
168
187
|
// 4. Patch package.json (name + deploy-mode script adjustments)
|
|
169
188
|
patchPackageJson(targetDir, choices.projectName, isLayered ? choices.deployMode : undefined);
|
|
170
189
|
// 5. .env stub
|
|
171
|
-
writeEnv(targetDir, choices.provider);
|
|
190
|
+
writeEnv(targetDir, choices.provider, choices);
|
|
172
191
|
}
|
|
173
192
|
function installDeps(targetDir) {
|
|
174
193
|
(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
|
+
}
|