@koi-language/koi 1.0.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/QUICKSTART.md +89 -0
- package/README.md +545 -0
- package/examples/actions-demo.koi +177 -0
- package/examples/cache-test.koi +29 -0
- package/examples/calculator.koi +61 -0
- package/examples/clear-registry.js +33 -0
- package/examples/clear-registry.koi +30 -0
- package/examples/code-introspection-test.koi +149 -0
- package/examples/counter.koi +132 -0
- package/examples/delegation-test.koi +52 -0
- package/examples/directory-import-test.koi +84 -0
- package/examples/hello-world-claude.koi +52 -0
- package/examples/hello-world.koi +52 -0
- package/examples/hello.koi +24 -0
- package/examples/mcp-example.koi +70 -0
- package/examples/multi-event-handler-test.koi +144 -0
- package/examples/new-import-test.koi +89 -0
- package/examples/pipeline.koi +162 -0
- package/examples/registry-demo.koi +184 -0
- package/examples/registry-playbook-demo.koi +162 -0
- package/examples/registry-playbook-email-compositor-2.koi +140 -0
- package/examples/registry-playbook-email-compositor.koi +140 -0
- package/examples/sentiment.koi +90 -0
- package/examples/simple.koi +48 -0
- package/examples/skill-import-test.koi +76 -0
- package/examples/skills/advanced/index.koi +95 -0
- package/examples/skills/math-operations.koi +69 -0
- package/examples/skills/string-operations.koi +56 -0
- package/examples/task-chaining-demo.koi +244 -0
- package/examples/test-await.koi +22 -0
- package/examples/test-crypto-sha256.koi +196 -0
- package/examples/test-delegation.koi +41 -0
- package/examples/test-multi-team-routing.koi +258 -0
- package/examples/test-no-handler.koi +35 -0
- package/examples/test-npm-import.koi +67 -0
- package/examples/test-parse.koi +10 -0
- package/examples/test-peers-with-team.koi +59 -0
- package/examples/test-permissions-fail.koi +20 -0
- package/examples/test-permissions.koi +36 -0
- package/examples/test-simple-registry.koi +31 -0
- package/examples/test-typescript-import.koi +64 -0
- package/examples/test-uses-team-syntax.koi +25 -0
- package/examples/test-uses-team.koi +31 -0
- package/examples/utils/calculator.test.ts +144 -0
- package/examples/utils/calculator.ts +56 -0
- package/examples/utils/math-helpers.js +50 -0
- package/examples/utils/math-helpers.ts +55 -0
- package/examples/web-delegation-demo.koi +165 -0
- package/package.json +78 -0
- package/src/cli/koi.js +793 -0
- package/src/compiler/build-optimizer.js +447 -0
- package/src/compiler/cache-manager.js +274 -0
- package/src/compiler/import-resolver.js +369 -0
- package/src/compiler/parser.js +7542 -0
- package/src/compiler/transpiler.js +1105 -0
- package/src/compiler/typescript-transpiler.js +148 -0
- package/src/grammar/koi.pegjs +767 -0
- package/src/runtime/action-registry.js +172 -0
- package/src/runtime/actions/call-skill.js +45 -0
- package/src/runtime/actions/format.js +115 -0
- package/src/runtime/actions/print.js +42 -0
- package/src/runtime/actions/registry-delete.js +37 -0
- package/src/runtime/actions/registry-get.js +37 -0
- package/src/runtime/actions/registry-keys.js +33 -0
- package/src/runtime/actions/registry-search.js +34 -0
- package/src/runtime/actions/registry-set.js +50 -0
- package/src/runtime/actions/return.js +31 -0
- package/src/runtime/actions/send-message.js +58 -0
- package/src/runtime/actions/update-state.js +36 -0
- package/src/runtime/agent.js +1368 -0
- package/src/runtime/cli-logger.js +205 -0
- package/src/runtime/incremental-json-parser.js +201 -0
- package/src/runtime/index.js +33 -0
- package/src/runtime/llm-provider.js +1372 -0
- package/src/runtime/mcp-client.js +1171 -0
- package/src/runtime/planner.js +273 -0
- package/src/runtime/registry-backends/keyv-sqlite.js +215 -0
- package/src/runtime/registry-backends/local.js +260 -0
- package/src/runtime/registry.js +162 -0
- package/src/runtime/role.js +14 -0
- package/src/runtime/router.js +395 -0
- package/src/runtime/runtime.js +113 -0
- package/src/runtime/skill-selector.js +173 -0
- package/src/runtime/skill.js +25 -0
- package/src/runtime/team.js +162 -0
package/src/cli/koi.js
ADDED
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { KoiTranspiler } from '../compiler/transpiler.js';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
const VERSION = '1.0.0';
|
|
12
|
+
|
|
13
|
+
const COMMANDS = {
|
|
14
|
+
compile: 'Compile Koi files to JavaScript',
|
|
15
|
+
run: 'Compile and run Koi files',
|
|
16
|
+
init: 'Initialize a new Koi project',
|
|
17
|
+
cache: 'Manage build cache (stats, clear)',
|
|
18
|
+
registry: 'Manage registry data (stats, clear)',
|
|
19
|
+
version: 'Show version information',
|
|
20
|
+
help: 'Show this help message'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const FLAGS = {
|
|
24
|
+
'--no-precalculate': 'Disable embedding pre-calculation (dynamic at runtime)',
|
|
25
|
+
'--no-cache': 'Disable persistent cache (always regenerate embeddings)',
|
|
26
|
+
'--verbose': 'Show detailed output',
|
|
27
|
+
'--debug': 'Show all LLM prompts and responses',
|
|
28
|
+
'--output, -o': 'Specify output file path',
|
|
29
|
+
'--help, -h': 'Show help for a command'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function showBanner() {
|
|
33
|
+
console.log('š Koi');
|
|
34
|
+
console.log(' Agent-first language. Calm orchestration.\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatBytes(bytes) {
|
|
38
|
+
if (bytes === 0) return '0 Bytes';
|
|
39
|
+
const k = 1024;
|
|
40
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
41
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
42
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function showHelp(command = null) {
|
|
46
|
+
if (command) {
|
|
47
|
+
showCommandHelp(command);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
showBanner();
|
|
52
|
+
console.log('Usage: koi <command> [options] [file]\n');
|
|
53
|
+
|
|
54
|
+
console.log('Commands:');
|
|
55
|
+
for (const [cmd, desc] of Object.entries(COMMANDS)) {
|
|
56
|
+
console.log(` ${cmd.padEnd(12)} ${desc}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log('\nGlobal Flags:');
|
|
60
|
+
for (const [flag, desc] of Object.entries(FLAGS)) {
|
|
61
|
+
console.log(` ${flag.padEnd(20)} ${desc}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('\nExamples:');
|
|
65
|
+
console.log(' koi run examples/hello-world.koi # Runs with auto-optimization');
|
|
66
|
+
console.log(' koi compile examples/simple.koi # Compiles with cache');
|
|
67
|
+
console.log(' koi compile -o output.js examples/simple.koi');
|
|
68
|
+
console.log(' koi init my-project');
|
|
69
|
+
console.log(' koi cache stats');
|
|
70
|
+
console.log(' koi registry clear');
|
|
71
|
+
console.log(' koi version');
|
|
72
|
+
|
|
73
|
+
console.log('\nGet started:');
|
|
74
|
+
console.log(' 1. Create a .env file with your OpenAI API key');
|
|
75
|
+
console.log(' 2. Run: koi run examples/hello-world.koi');
|
|
76
|
+
console.log(' 3. Embeddings are pre-computed and cached automatically!');
|
|
77
|
+
|
|
78
|
+
console.log('\nDocumentation:');
|
|
79
|
+
console.log(' README.md - Full language documentation');
|
|
80
|
+
console.log(' SETUP_LLM.md - LLM integration guide');
|
|
81
|
+
console.log(' examples/ - Example Koi programs');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function showCommandHelp(command) {
|
|
85
|
+
showBanner();
|
|
86
|
+
|
|
87
|
+
switch (command) {
|
|
88
|
+
case 'compile':
|
|
89
|
+
console.log('koi compile - Compile Koi source to JavaScript\n');
|
|
90
|
+
console.log('By default: Pre-computes embeddings and caches them for fast builds.');
|
|
91
|
+
console.log('Use --no-precalculate or --no-cache to customize behavior.\n');
|
|
92
|
+
console.log('Usage:');
|
|
93
|
+
console.log(' koi compile <file.koi>');
|
|
94
|
+
console.log(' koi compile -o <output.js> <file.koi>\n');
|
|
95
|
+
console.log('Options:');
|
|
96
|
+
console.log(' -o, --output <path> Output file path');
|
|
97
|
+
console.log(' --no-precalculate Disable embedding pre-calculation (dynamic at runtime)');
|
|
98
|
+
console.log(' --no-cache Disable persistent cache (always regenerate)');
|
|
99
|
+
console.log('\nExamples:');
|
|
100
|
+
console.log(' koi compile examples/simple.koi # Default: pre-compute + cache');
|
|
101
|
+
console.log(' koi compile --no-cache examples/simple.koi # Pre-compute without cache');
|
|
102
|
+
console.log(' koi compile --no-precalculate examples/simple.koi # No pre-computation');
|
|
103
|
+
console.log(' koi compile -o dist/app.js src/main.koi');
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case 'run':
|
|
107
|
+
console.log('koi run - Compile and execute Koi programs\n');
|
|
108
|
+
console.log('By default: Pre-computes embeddings and caches them.\n');
|
|
109
|
+
console.log('Usage:');
|
|
110
|
+
console.log(' koi run <file.koi>\n');
|
|
111
|
+
console.log('Options:');
|
|
112
|
+
console.log(' --no-precalculate Disable embedding pre-calculation');
|
|
113
|
+
console.log(' --no-cache Disable persistent cache');
|
|
114
|
+
console.log(' --verbose Show detailed execution logs');
|
|
115
|
+
console.log('\nExamples:');
|
|
116
|
+
console.log(' koi run examples/hello-world.koi # Default: pre-compute + cache');
|
|
117
|
+
console.log(' koi run --no-cache examples/hello-world.koi # No persistent cache');
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 'init':
|
|
121
|
+
console.log('koi init - Initialize a new Koi project\n');
|
|
122
|
+
console.log('Usage:');
|
|
123
|
+
console.log(' koi init <project-name>\n');
|
|
124
|
+
console.log('Examples:');
|
|
125
|
+
console.log(' koi init my-agent-system');
|
|
126
|
+
console.log(' koi init ./my-project');
|
|
127
|
+
break;
|
|
128
|
+
|
|
129
|
+
case 'cache':
|
|
130
|
+
console.log('koi cache - Manage build cache\n');
|
|
131
|
+
console.log('Usage:');
|
|
132
|
+
console.log(' koi cache stats Show cache statistics');
|
|
133
|
+
console.log(' koi cache clear Clear all cache');
|
|
134
|
+
console.log(' koi cache clear <file> Clear cache for specific file\n');
|
|
135
|
+
console.log('The cache stores pre-computed embeddings to avoid API calls.');
|
|
136
|
+
console.log('Cache is stored in .koi-cache/ directory.\n');
|
|
137
|
+
console.log('Examples:');
|
|
138
|
+
console.log(' koi cache stats');
|
|
139
|
+
console.log(' koi cache clear');
|
|
140
|
+
console.log(' koi cache clear src/main.koi');
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
case 'registry':
|
|
144
|
+
console.log('koi registry - Manage registry data\n');
|
|
145
|
+
console.log('Usage:');
|
|
146
|
+
console.log(' koi registry stats Show registry statistics');
|
|
147
|
+
console.log(' koi registry clear Clear all registry data\n');
|
|
148
|
+
console.log('The registry is a shared data store for agent collaboration.');
|
|
149
|
+
console.log('Data is stored based on the configured backend (default: local files).\n');
|
|
150
|
+
console.log('Examples:');
|
|
151
|
+
console.log(' koi registry stats');
|
|
152
|
+
console.log(' koi registry clear');
|
|
153
|
+
break;
|
|
154
|
+
|
|
155
|
+
default:
|
|
156
|
+
console.log(`Unknown command: ${command}\n`);
|
|
157
|
+
showHelp();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function showVersion() {
|
|
162
|
+
showBanner();
|
|
163
|
+
console.log(`Version: ${VERSION}`);
|
|
164
|
+
console.log('Node:', process.version);
|
|
165
|
+
console.log('Platform:', process.platform);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function compileFile(sourcePath, outputPath = null, options = {}) {
|
|
169
|
+
const { cliLogger } = await import('../runtime/cli-logger.js');
|
|
170
|
+
const verbose = options.verbose || false;
|
|
171
|
+
|
|
172
|
+
if (verbose) {
|
|
173
|
+
console.log(`\nš¦ Compiling: ${sourcePath}`);
|
|
174
|
+
|
|
175
|
+
// Show development mode if KOI_RUNTIME_PATH is set
|
|
176
|
+
if (process.env.KOI_RUNTIME_PATH) {
|
|
177
|
+
console.log(`š§ Development mode: Using local runtime from ${process.env.KOI_RUNTIME_PATH}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check if we need to build the parser first
|
|
182
|
+
const parserPath = path.join(__dirname, '../compiler/parser.js');
|
|
183
|
+
if (!fs.existsSync(parserPath)) {
|
|
184
|
+
cliLogger.progress('ā ļø Parser not found. Building grammar...');
|
|
185
|
+
await buildGrammar();
|
|
186
|
+
cliLogger.clear();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Import parser dynamically
|
|
190
|
+
const { parse } = await import('../compiler/parser.js');
|
|
191
|
+
|
|
192
|
+
// Read source
|
|
193
|
+
const source = fs.readFileSync(sourcePath, 'utf-8');
|
|
194
|
+
|
|
195
|
+
// Parse
|
|
196
|
+
if (verbose) {
|
|
197
|
+
cliLogger.progress('š Parsing...');
|
|
198
|
+
}
|
|
199
|
+
let ast;
|
|
200
|
+
try {
|
|
201
|
+
ast = parse(source);
|
|
202
|
+
if (verbose) {
|
|
203
|
+
cliLogger.clear();
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (verbose) {
|
|
207
|
+
cliLogger.clear();
|
|
208
|
+
}
|
|
209
|
+
console.error('ā Parse error:');
|
|
210
|
+
if (error.location) {
|
|
211
|
+
console.error(` Line ${error.location.start.line}, Column ${error.location.start.column}`);
|
|
212
|
+
console.error(` ${error.message}`);
|
|
213
|
+
|
|
214
|
+
// Show source context
|
|
215
|
+
const lines = source.split('\n');
|
|
216
|
+
const lineNum = error.location.start.line - 1;
|
|
217
|
+
if (lineNum >= 0 && lineNum < lines.length) {
|
|
218
|
+
console.error(`\n ${lines[lineNum]}`);
|
|
219
|
+
console.error(` ${' '.repeat(error.location.start.column - 1)}^`);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
console.error(` ${error.message}`);
|
|
223
|
+
}
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Resolve imports
|
|
228
|
+
if (verbose) {
|
|
229
|
+
cliLogger.progress('š¦ Resolving imports...');
|
|
230
|
+
}
|
|
231
|
+
const { ImportResolver } = await import('../compiler/import-resolver.js');
|
|
232
|
+
const importResolver = new ImportResolver(parse);
|
|
233
|
+
try {
|
|
234
|
+
ast = await importResolver.resolveImports(ast, sourcePath);
|
|
235
|
+
|
|
236
|
+
if (verbose) {
|
|
237
|
+
const counts = {
|
|
238
|
+
skills: importResolver.importedSkills.length,
|
|
239
|
+
agents: importResolver.importedAgents.length,
|
|
240
|
+
roles: importResolver.importedRoles.length,
|
|
241
|
+
teams: importResolver.importedTeams.length,
|
|
242
|
+
external: importResolver.externalImports.length
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const total = counts.skills + counts.agents + counts.roles + counts.teams + counts.external;
|
|
246
|
+
|
|
247
|
+
if (total > 0) {
|
|
248
|
+
cliLogger.clear();
|
|
249
|
+
const parts = [];
|
|
250
|
+
if (counts.skills > 0) parts.push(`${counts.skills} skill(s)`);
|
|
251
|
+
if (counts.agents > 0) parts.push(`${counts.agents} agent(s)`);
|
|
252
|
+
if (counts.roles > 0) parts.push(`${counts.roles} role(s)`);
|
|
253
|
+
if (counts.teams > 0) parts.push(`${counts.teams} team(s)`);
|
|
254
|
+
if (counts.external > 0) parts.push(`${counts.external} external module(s)`);
|
|
255
|
+
console.log(`ā
Imported ${parts.join(', ')}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (verbose) {
|
|
259
|
+
cliLogger.clear();
|
|
260
|
+
}
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (verbose) {
|
|
263
|
+
cliLogger.clear();
|
|
264
|
+
}
|
|
265
|
+
console.error('ā Import error:', error.message);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Build-time optimization: BY DEFAULT pre-calculate and cache
|
|
270
|
+
// Use --no-precalculate to disable pre-calculation (dynamic at runtime)
|
|
271
|
+
// Use --no-cache to disable persistent cache (always regenerate)
|
|
272
|
+
let cacheData = null;
|
|
273
|
+
|
|
274
|
+
const shouldPrecalculate = !options.noPrecalculate;
|
|
275
|
+
const shouldCache = !options.noCache;
|
|
276
|
+
|
|
277
|
+
if (shouldPrecalculate) {
|
|
278
|
+
const { CacheManager } = await import('../compiler/cache-manager.js');
|
|
279
|
+
const cacheManager = new CacheManager({ verbose: options.verbose });
|
|
280
|
+
|
|
281
|
+
// Try to load from cache if caching is enabled
|
|
282
|
+
if (shouldCache) {
|
|
283
|
+
if (verbose) {
|
|
284
|
+
cliLogger.progress('š Checking cache...');
|
|
285
|
+
}
|
|
286
|
+
cacheData = cacheManager.get(source, sourcePath);
|
|
287
|
+
|
|
288
|
+
if (cacheData && verbose) {
|
|
289
|
+
cliLogger.success(`ā
Using cached embeddings (${cacheData.metadata.totalAffordances} affordances)`);
|
|
290
|
+
} else if (verbose) {
|
|
291
|
+
cliLogger.clear();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Generate if no cache or cache disabled
|
|
296
|
+
if (!cacheData) {
|
|
297
|
+
const { BuildTimeOptimizer } = await import('../compiler/build-optimizer.js');
|
|
298
|
+
const optimizer = new BuildTimeOptimizer({
|
|
299
|
+
cache: shouldCache,
|
|
300
|
+
verbose: options.verbose
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
if (shouldCache) {
|
|
305
|
+
if (verbose) {
|
|
306
|
+
cliLogger.progress('š Pre-computing embeddings...');
|
|
307
|
+
}
|
|
308
|
+
cacheData = await optimizer.optimizeAST(ast, source, sourcePath);
|
|
309
|
+
if (verbose) {
|
|
310
|
+
cliLogger.success(`ā
Pre-computed ${cacheData.metadata.totalAffordances} embeddings`);
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
if (verbose) {
|
|
314
|
+
cliLogger.progress('š Pre-computing embeddings (no cache)...');
|
|
315
|
+
}
|
|
316
|
+
cacheData = await optimizer.optimizeASTWithoutCache(ast);
|
|
317
|
+
if (verbose) {
|
|
318
|
+
cliLogger.success(`ā
Pre-computed ${cacheData.metadata.totalAffordances} embeddings`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (verbose) {
|
|
323
|
+
cliLogger.clear();
|
|
324
|
+
}
|
|
325
|
+
console.error('ā ļø Build-time optimization failed:', error.message);
|
|
326
|
+
console.error(' Continuing without pre-computed embeddings...\n');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
if (options.verbose) {
|
|
331
|
+
cliLogger.info('[Optimizer] Pre-calculation disabled. Runtime will generate embeddings dynamically.');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Determine output path first (needed for transpiler to calculate runtime path)
|
|
336
|
+
if (!outputPath) {
|
|
337
|
+
// Create .build directory if it doesn't exist
|
|
338
|
+
const buildDir = path.join(path.dirname(sourcePath), '.build');
|
|
339
|
+
if (!fs.existsSync(buildDir)) {
|
|
340
|
+
fs.mkdirSync(buildDir, { recursive: true });
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Ensure package.json exists in .build directory for ES modules
|
|
344
|
+
const packageJsonPath = path.join(buildDir, 'package.json');
|
|
345
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
346
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify({ type: 'module' }, null, 2));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const basename = path.basename(sourcePath, '.koi');
|
|
350
|
+
outputPath = path.join(buildDir, basename + '.js');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Transpile with output path so it can calculate correct runtime import path
|
|
354
|
+
if (verbose) {
|
|
355
|
+
cliLogger.progress('šØ Transpiling...');
|
|
356
|
+
}
|
|
357
|
+
const runtimePath = path.join(__dirname, '../runtime/index.js');
|
|
358
|
+
const transpiler = new KoiTranspiler(path.basename(sourcePath), {
|
|
359
|
+
cacheData,
|
|
360
|
+
outputPath: path.resolve(outputPath),
|
|
361
|
+
runtimePath: path.resolve(runtimePath),
|
|
362
|
+
externalImports: importResolver.externalImports
|
|
363
|
+
});
|
|
364
|
+
const { code, map } = transpiler.transpile(ast);
|
|
365
|
+
if (verbose) {
|
|
366
|
+
cliLogger.clear();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Ensure output directory exists
|
|
370
|
+
const outputDir = path.dirname(outputPath);
|
|
371
|
+
if (!fs.existsSync(outputDir)) {
|
|
372
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
373
|
+
|
|
374
|
+
// Ensure package.json exists in output directory for ES modules
|
|
375
|
+
const packageJsonPath = path.join(outputDir, 'package.json');
|
|
376
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
377
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify({ type: 'module' }, null, 2));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Write output
|
|
382
|
+
if (verbose) {
|
|
383
|
+
cliLogger.progress(`š¾ Writing: ${outputPath}`);
|
|
384
|
+
}
|
|
385
|
+
fs.writeFileSync(outputPath, code + `\n//# sourceMappingURL=${path.basename(outputPath)}.map`);
|
|
386
|
+
fs.writeFileSync(outputPath + '.map', map);
|
|
387
|
+
if (verbose) {
|
|
388
|
+
cliLogger.clear();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (verbose) {
|
|
392
|
+
console.log('ā
Compilation complete!\n');
|
|
393
|
+
}
|
|
394
|
+
return outputPath;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function runFile(sourcePath, options = {}) {
|
|
398
|
+
const verbose = options.verbose || false;
|
|
399
|
+
const debug = options.debug || false;
|
|
400
|
+
|
|
401
|
+
// Show simple 1-line log if not verbose
|
|
402
|
+
if (!verbose) {
|
|
403
|
+
console.log(`š Running ${sourcePath}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Compile first
|
|
407
|
+
const jsPath = await compileFile(sourcePath, null, options);
|
|
408
|
+
|
|
409
|
+
// Run
|
|
410
|
+
if (verbose) {
|
|
411
|
+
console.log(`š Executing: ${jsPath}\n`);
|
|
412
|
+
console.log('ā'.repeat(60));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Execute in a child process to avoid module cache issues
|
|
416
|
+
// This ensures each run uses the freshly compiled code
|
|
417
|
+
const { spawn } = await import('child_process');
|
|
418
|
+
|
|
419
|
+
// Prepare environment variables
|
|
420
|
+
const env = { ...process.env };
|
|
421
|
+
if (debug) {
|
|
422
|
+
env.KOI_DEBUG_LLM = '1';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const child = spawn('node', [jsPath], {
|
|
426
|
+
stdio: 'inherit',
|
|
427
|
+
env
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
return new Promise((resolve, reject) => {
|
|
431
|
+
child.on('exit', (code) => {
|
|
432
|
+
if (code !== 0) {
|
|
433
|
+
// Exit silently - error message already shown by child process
|
|
434
|
+
process.exit(code);
|
|
435
|
+
} else {
|
|
436
|
+
resolve();
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
child.on('error', (error) => {
|
|
441
|
+
console.error('\nā Runtime error:');
|
|
442
|
+
console.error(error);
|
|
443
|
+
process.exit(1);
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function buildGrammar() {
|
|
449
|
+
const { execSync } = await import('child_process');
|
|
450
|
+
const grammarPath = path.join(__dirname, '../grammar/koi.pegjs');
|
|
451
|
+
const outputPath = path.join(__dirname, '../compiler/parser.js');
|
|
452
|
+
|
|
453
|
+
console.log('šļø Building parser from grammar...');
|
|
454
|
+
try {
|
|
455
|
+
execSync(`npx peggy -o "${outputPath}" "${grammarPath}"`, {
|
|
456
|
+
stdio: 'inherit',
|
|
457
|
+
cwd: path.join(__dirname, '../..')
|
|
458
|
+
});
|
|
459
|
+
console.log('ā
Parser built successfully!\n');
|
|
460
|
+
} catch (error) {
|
|
461
|
+
console.error('ā Failed to build parser');
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function initProject(projectName) {
|
|
467
|
+
if (!projectName) {
|
|
468
|
+
console.error('ā Please provide a project name');
|
|
469
|
+
console.log('Usage: koi init <project-name>');
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const projectPath = path.resolve(projectName);
|
|
474
|
+
|
|
475
|
+
if (fs.existsSync(projectPath)) {
|
|
476
|
+
console.error(`ā Directory already exists: ${projectPath}`);
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
console.log(`š Initializing Koi project: ${projectName}\n`);
|
|
481
|
+
|
|
482
|
+
// Create directories
|
|
483
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
484
|
+
fs.mkdirSync(path.join(projectPath, 'src'));
|
|
485
|
+
fs.mkdirSync(path.join(projectPath, 'dist'));
|
|
486
|
+
|
|
487
|
+
// Create example main.koi
|
|
488
|
+
const exampleCode = `// ${projectName} - Koi Project
|
|
489
|
+
package "${projectName}"
|
|
490
|
+
|
|
491
|
+
role Worker { can execute }
|
|
492
|
+
role Lead { can delegate }
|
|
493
|
+
|
|
494
|
+
Agent HelloAgent : Worker {
|
|
495
|
+
on greet(args: Json) {
|
|
496
|
+
const name = args.name
|
|
497
|
+
return { message: "Hello from ${projectName}, " + name + "!" }
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
Team MainTeam {
|
|
502
|
+
hello = HelloAgent
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
Agent Orchestrator : Lead {
|
|
506
|
+
uses Team MainTeam
|
|
507
|
+
|
|
508
|
+
on start(args: Json) {
|
|
509
|
+
const result =
|
|
510
|
+
await send peers.event("greet").role(Worker).any()({ name: "World" }) timeout 5s
|
|
511
|
+
return result
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
run Orchestrator.start({})
|
|
516
|
+
`;
|
|
517
|
+
|
|
518
|
+
fs.writeFileSync(path.join(projectPath, 'src', 'main.koi'), exampleCode);
|
|
519
|
+
|
|
520
|
+
// Create package.json
|
|
521
|
+
const packageJson = {
|
|
522
|
+
name: projectName.toLowerCase().replace(/\s+/g, '-'),
|
|
523
|
+
version: '1.0.0',
|
|
524
|
+
description: `${projectName} - Koi project`,
|
|
525
|
+
type: 'module',
|
|
526
|
+
scripts: {
|
|
527
|
+
start: 'koi run src/main.koi',
|
|
528
|
+
compile: 'koi compile src/main.koi'
|
|
529
|
+
},
|
|
530
|
+
keywords: ['koi', 'agents'],
|
|
531
|
+
author: '',
|
|
532
|
+
license: 'MIT',
|
|
533
|
+
dependencies: {
|
|
534
|
+
'koi-lang': '^1.0.0'
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
fs.writeFileSync(path.join(projectPath, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
539
|
+
|
|
540
|
+
// Create .env.example
|
|
541
|
+
const envExample = `# OpenAI API Key
|
|
542
|
+
OPENAI_API_KEY=your-key-here
|
|
543
|
+
|
|
544
|
+
# Anthropic API Key (optional)
|
|
545
|
+
# ANTHROPIC_API_KEY=your-key-here
|
|
546
|
+
`;
|
|
547
|
+
|
|
548
|
+
fs.writeFileSync(path.join(projectPath, '.env.example'), envExample);
|
|
549
|
+
|
|
550
|
+
// Create README
|
|
551
|
+
const readme = `# ${projectName}
|
|
552
|
+
|
|
553
|
+
Koi project - Agent-first language. Calm orchestration.
|
|
554
|
+
|
|
555
|
+
## Quick Start
|
|
556
|
+
|
|
557
|
+
1. Install dependencies:
|
|
558
|
+
\`\`\`bash
|
|
559
|
+
npm install
|
|
560
|
+
\`\`\`
|
|
561
|
+
|
|
562
|
+
2. Configure API key:
|
|
563
|
+
\`\`\`bash
|
|
564
|
+
cp .env.example .env
|
|
565
|
+
# Edit .env and add your OpenAI API key
|
|
566
|
+
\`\`\`
|
|
567
|
+
|
|
568
|
+
3. Run:
|
|
569
|
+
\`\`\`bash
|
|
570
|
+
npm start
|
|
571
|
+
# or directly: koi run src/main.koi
|
|
572
|
+
\`\`\`
|
|
573
|
+
|
|
574
|
+
## Structure
|
|
575
|
+
|
|
576
|
+
- \`src/\` - Koi source files (.koi)
|
|
577
|
+
- \`dist/\` - Compiled JavaScript output
|
|
578
|
+
|
|
579
|
+
## Documentation
|
|
580
|
+
|
|
581
|
+
- [Koi Language Docs](https://github.com/yourusername/koi)
|
|
582
|
+
`;
|
|
583
|
+
|
|
584
|
+
fs.writeFileSync(path.join(projectPath, 'README.md'), readme);
|
|
585
|
+
|
|
586
|
+
// Create .gitignore
|
|
587
|
+
const gitignore = `node_modules/
|
|
588
|
+
.env
|
|
589
|
+
dist/
|
|
590
|
+
.build/
|
|
591
|
+
.koi-cache/
|
|
592
|
+
*.js.map
|
|
593
|
+
.DS_Store
|
|
594
|
+
`;
|
|
595
|
+
|
|
596
|
+
fs.writeFileSync(path.join(projectPath, '.gitignore'), gitignore);
|
|
597
|
+
|
|
598
|
+
console.log('ā
Project initialized!');
|
|
599
|
+
console.log(`\nNext steps:`);
|
|
600
|
+
console.log(` cd ${projectName}`);
|
|
601
|
+
console.log(` npm install`);
|
|
602
|
+
console.log(` cp .env.example .env`);
|
|
603
|
+
console.log(` # Edit .env and add your API key`);
|
|
604
|
+
console.log(` npm start`);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Parse arguments and flags
|
|
608
|
+
function parseArgs(argv) {
|
|
609
|
+
const flags = {};
|
|
610
|
+
const positional = [];
|
|
611
|
+
|
|
612
|
+
// Define which flags are boolean (don't take values)
|
|
613
|
+
const booleanFlags = ['no-precalculate', 'no-cache', 'verbose', 'debug', 'help', 'h', 'version', 'v'];
|
|
614
|
+
// Define which flags take values
|
|
615
|
+
const valuedFlags = ['output', 'o'];
|
|
616
|
+
|
|
617
|
+
for (let i = 0; i < argv.length; i++) {
|
|
618
|
+
const arg = argv[i];
|
|
619
|
+
|
|
620
|
+
if (arg.startsWith('--')) {
|
|
621
|
+
// Long flag
|
|
622
|
+
const flagName = arg.slice(2);
|
|
623
|
+
|
|
624
|
+
if (booleanFlags.includes(flagName)) {
|
|
625
|
+
flags[flagName] = true;
|
|
626
|
+
} else if (valuedFlags.includes(flagName)) {
|
|
627
|
+
if (argv[i + 1] && !argv[i + 1].startsWith('-')) {
|
|
628
|
+
flags[flagName] = argv[++i];
|
|
629
|
+
} else {
|
|
630
|
+
console.error(`ā Flag --${flagName} requires a value`);
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
// Unknown flag: try to guess
|
|
635
|
+
if (argv[i + 1] && !argv[i + 1].startsWith('-') && !argv[i + 1].endsWith('.koi')) {
|
|
636
|
+
flags[flagName] = argv[++i];
|
|
637
|
+
} else {
|
|
638
|
+
flags[flagName] = true;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
} else if (arg.startsWith('-') && arg.length === 2) {
|
|
642
|
+
// Short flag
|
|
643
|
+
const flagChar = arg[1];
|
|
644
|
+
|
|
645
|
+
if (valuedFlags.includes(flagChar)) {
|
|
646
|
+
if (argv[i + 1] && !argv[i + 1].startsWith('-')) {
|
|
647
|
+
flags[flagChar] = argv[++i];
|
|
648
|
+
} else {
|
|
649
|
+
console.error(`ā Flag -${flagChar} requires a value`);
|
|
650
|
+
process.exit(1);
|
|
651
|
+
}
|
|
652
|
+
} else {
|
|
653
|
+
flags[flagChar] = true;
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
positional.push(arg);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return { flags, positional };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Main
|
|
664
|
+
const rawArgs = process.argv.slice(2);
|
|
665
|
+
|
|
666
|
+
if (rawArgs.length === 0 || rawArgs[0] === 'help' || rawArgs[0] === '--help' || rawArgs[0] === '-h') {
|
|
667
|
+
showHelp(rawArgs[1]);
|
|
668
|
+
process.exit(0);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const { flags, positional } = parseArgs(rawArgs);
|
|
672
|
+
const command = positional[0];
|
|
673
|
+
|
|
674
|
+
// Handle version
|
|
675
|
+
if (command === 'version' || flags.version || flags.v) {
|
|
676
|
+
showVersion();
|
|
677
|
+
process.exit(0);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Check command exists
|
|
681
|
+
if (!COMMANDS[command]) {
|
|
682
|
+
console.error(`ā Unknown command: ${command}\n`);
|
|
683
|
+
showHelp();
|
|
684
|
+
process.exit(1);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Handle init
|
|
688
|
+
if (command === 'init') {
|
|
689
|
+
await initProject(positional[1]);
|
|
690
|
+
process.exit(0);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Handle cache
|
|
694
|
+
if (command === 'cache') {
|
|
695
|
+
const { CacheManager } = await import('../compiler/cache-manager.js');
|
|
696
|
+
const cacheManager = new CacheManager({ verbose: true });
|
|
697
|
+
|
|
698
|
+
const subcommand = positional[1];
|
|
699
|
+
|
|
700
|
+
if (!subcommand || subcommand === 'stats') {
|
|
701
|
+
cacheManager.printStats();
|
|
702
|
+
} else if (subcommand === 'clear') {
|
|
703
|
+
const targetFile = positional[2];
|
|
704
|
+
if (targetFile) {
|
|
705
|
+
cacheManager.clear(targetFile);
|
|
706
|
+
} else {
|
|
707
|
+
cacheManager.clear();
|
|
708
|
+
}
|
|
709
|
+
} else {
|
|
710
|
+
console.error(`ā Unknown cache subcommand: ${subcommand}\n`);
|
|
711
|
+
console.log('Usage: koi cache [stats|clear]');
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
process.exit(0);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Handle registry
|
|
719
|
+
if (command === 'registry') {
|
|
720
|
+
const { registry } = await import('../runtime/registry.js');
|
|
721
|
+
const subcommand = positional[1];
|
|
722
|
+
|
|
723
|
+
if (!subcommand || subcommand === 'stats') {
|
|
724
|
+
console.log('š Registry Statistics\n');
|
|
725
|
+
try {
|
|
726
|
+
const stats = await registry.stats();
|
|
727
|
+
console.log(`Total entries: ${stats.count}`);
|
|
728
|
+
console.log(`Storage size: ${formatBytes(stats.size)}`);
|
|
729
|
+
console.log(`Backend: ${stats.backend || 'local'}`);
|
|
730
|
+
if (stats.location) {
|
|
731
|
+
console.log(`Location: ${stats.location}`);
|
|
732
|
+
}
|
|
733
|
+
} catch (error) {
|
|
734
|
+
console.error(`ā Failed to get registry stats: ${error.message}`);
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
} else if (subcommand === 'clear') {
|
|
738
|
+
console.log('š Clearing Registry\n');
|
|
739
|
+
try {
|
|
740
|
+
const statsBefore = await registry.stats();
|
|
741
|
+
console.log(`Entries before: ${statsBefore.count}`);
|
|
742
|
+
|
|
743
|
+
await registry.clear();
|
|
744
|
+
|
|
745
|
+
const statsAfter = await registry.stats();
|
|
746
|
+
console.log(`Entries after: ${statsAfter.count}`);
|
|
747
|
+
console.log('\nā
Registry cleared successfully!');
|
|
748
|
+
} catch (error) {
|
|
749
|
+
console.error(`ā Failed to clear registry: ${error.message}`);
|
|
750
|
+
process.exit(1);
|
|
751
|
+
}
|
|
752
|
+
} else {
|
|
753
|
+
console.error(`ā Unknown registry subcommand: ${subcommand}\n`);
|
|
754
|
+
console.log('Usage: koi registry [stats|clear]');
|
|
755
|
+
process.exit(1);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
process.exit(0);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Handle compile and run
|
|
762
|
+
if (command === 'compile' || command === 'run') {
|
|
763
|
+
const file = positional[1];
|
|
764
|
+
|
|
765
|
+
if (!file) {
|
|
766
|
+
console.error(`ā Please provide a Koi file\n`);
|
|
767
|
+
console.log(`Usage: koi ${command} <file.koi>`);
|
|
768
|
+
process.exit(1);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (!fs.existsSync(file)) {
|
|
772
|
+
console.error(`ā File not found: ${file}`);
|
|
773
|
+
process.exit(1);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Build options object
|
|
777
|
+
// By default: pre-calculate AND cache (best performance)
|
|
778
|
+
// Use --no-precalculate to disable pre-calculation (dynamic at runtime)
|
|
779
|
+
// Use --no-cache to disable persistent cache (always regenerate)
|
|
780
|
+
const options = {
|
|
781
|
+
noPrecalculate: flags['no-precalculate'] || false,
|
|
782
|
+
noCache: flags['no-cache'] || false,
|
|
783
|
+
verbose: flags.verbose || false,
|
|
784
|
+
debug: flags.debug || false
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
if (command === 'compile') {
|
|
788
|
+
const outputPath = flags.output || flags.o;
|
|
789
|
+
await compileFile(file, outputPath, options);
|
|
790
|
+
} else if (command === 'run') {
|
|
791
|
+
await runFile(file, options);
|
|
792
|
+
}
|
|
793
|
+
}
|