@bahulam/code 2.6.17 → 2.6.18

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/src/index.mjs DELETED
@@ -1,430 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * @bahulamai/code — Bahulam Code CLI (Bahulam's coding agent).
4
- *
5
- * Phase 3: Hybrid local/remote/auto + advanced features.
6
- */
7
-
8
- // Load .env file from cwd or ~/.bahulam/.env
9
- import { readFileSync, existsSync } from 'node:fs';
10
- import { join } from 'node:path';
11
- import { homedir } from 'node:os';
12
-
13
- for (const envPath of [join(process.cwd(), '.env'), join(homedir(), '.bahulam', '.env')]) {
14
- if (existsSync(envPath)) {
15
- for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
16
- const match = line.match(/^\s*([\w]+)\s*=\s*(.+?)\s*$/);
17
- if (match && !process.env[match[1]]) {
18
- process.env[match[1]] = match[2];
19
- }
20
- }
21
- break;
22
- }
23
- }
24
-
25
- import { TarangStreamClient, EVENT_TYPES } from './core/stream-client.mjs';
26
- import { LocalAgent } from './core/local-agent.mjs';
27
- import { createToolExecutor } from './core/tool-executor.mjs';
28
- import { TarangAuth } from './auth/tarang-auth.mjs';
29
- import { ApprovalManager } from './core/approval.mjs';
30
- import { SessionManager } from './core/session-manager.mjs';
31
- import { EventFormatter } from './ui/formatter.mjs';
32
- import { COMMANDS } from './ui/slash-commands.mjs';
33
- import { selectMode } from './core/mode-selector.mjs';
34
- import { printBanner, printProjectInfo, printHints, printAuthStatus, printStyledConfig, printGoodbye } from './ui/banner.mjs';
35
- import { ContextRetriever } from './context/retriever.mjs';
36
- import { loadSettings } from './config/settings.mjs';
37
-
38
- const VERSION = '2.6.17';
39
-
40
- // ── Arg Parsing (consolidated from index.mjs + cli-args.mjs) ──
41
-
42
- function parseArgs(argv) {
43
- const args = {
44
- // Commands
45
- command: null, instruction: null,
46
- // Tarang mode flags
47
- verbose: false, yes: false, plan: false, strict: false,
48
- local: false, remote: false, debug: false,
49
- version: false, help: false,
50
- // Config subcommand flags
51
- showConfig: false, openRouterKey: null, anthropicKey: null,
52
- backendUrl: null, mode: null,
53
- model: null, route: null,
54
- // Extended flags (from cli-args.mjs)
55
- permissionMode: null,
56
- outputFormat: null,
57
- systemPrompt: null,
58
- addDirs: [],
59
- maxTurns: null,
60
- allowedTools: null,
61
- disallowedTools: null,
62
- };
63
- let i = 0;
64
- while (i < argv.length) {
65
- const arg = argv[i];
66
- switch (arg) {
67
- // Version / help
68
- case '--version': case '-V': args.version = true; break;
69
- case '--help': case '-h': args.help = true; break;
70
- // Behavior flags
71
- case '--verbose': case '-v': args.verbose = true; break;
72
- case '--debug': case '-d': args.debug = true; args.verbose = true; break;
73
- case '--yes': case '-y': args.yes = true; break;
74
- case '--plan': args.plan = true; break;
75
- case '--strict': args.strict = true; break;
76
- // Mode flags
77
- case '--local': args.local = true; break;
78
- case '--remote': args.remote = true; break;
79
- case '--mode': args.mode = argv[++i]; break;
80
- // Commands
81
- case 'login': args.command = 'login'; break;
82
- case 'resume': args.command = 'resume'; break;
83
- case 'config': args.command = 'config'; break;
84
- case 'configure': args.command = 'configure'; break;
85
- case 'sync':
86
- if (args.command === 'workflow' && !args.workflowSubcommand) args.workflowSubcommand = 'sync';
87
- else if (args.command === 'agent' && !args.agentSubcommand) args.agentSubcommand = 'sync';
88
- else if (!args.command) args.command = 'sync';
89
- break;
90
- case 'workflow': args.command = 'workflow'; break;
91
- case 'agent': args.command = 'agent'; break;
92
- // Config flags
93
- case '--show': args.showConfig = true; break;
94
- case '--openrouter-key': case '-k': args.openRouterKey = argv[++i]; break;
95
- case '--anthropic-key': args.anthropicKey = argv[++i]; break;
96
- case '--openai-key': args.openaiKey = argv[++i]; break;
97
- case '--google-key': args.googleKey = argv[++i]; break;
98
- case '--gateway': args.gateway = argv[++i]; break;
99
- // --backend-url removed: use TARANG_ENV
100
- case '--model': case '-m': args.model = argv[++i]; break;
101
- case '--route': args.route = argv[++i]; break;
102
- // Extended flags
103
- case '--permission-mode': args.permissionMode = argv[++i]; break;
104
- case '--print': case '-p': case '--instruction': case '--input': args.instruction = argv[++i]; break;
105
- case '--output-format': args.outputFormat = argv[++i]; break;
106
- case '--system-prompt': args.systemPrompt = argv[++i]; break;
107
- case '--add-dir': args.addDirs.push(argv[++i]); break;
108
- case '--max-turns': args.maxTurns = parseInt(argv[++i], 10); break;
109
- case '--allowedTools': args.allowedTools = argv[++i]?.split(',').map(s => s.trim()); break;
110
- case '--disallowedTools': args.disallowedTools = argv[++i]?.split(',').map(s => s.trim()); break;
111
- // Workflow flags
112
- case '--file': case '-f': args.workflowFile = argv[++i]; break;
113
- case '--dir': args.workflowDir = argv[++i]; break;
114
- case '--agent-dir': args.agentDir = argv[++i]; break;
115
- case '--pattern': args.pattern = argv[++i]; break;
116
- default:
117
- if (!arg.startsWith('-')) {
118
- if (args.command === 'workflow' && !args.workflowSubcommand) {
119
- // First positional after 'workflow' is the subcommand
120
- const subcommands = new Set(['create', 'create-multi', 'run', 'run-multi', 'list', 'get', 'delete', 'sync']);
121
- if (subcommands.has(arg)) {
122
- args.workflowSubcommand = arg;
123
- } else {
124
- // Second positional is the workflow name/slug
125
- args.workflowSlug = arg;
126
- }
127
- } else if (args.command === 'workflow' && args.workflowSubcommand && !args.workflowSlug) {
128
- args.workflowSlug = arg;
129
- } else if (args.command === 'agent' && !args.agentSubcommand) {
130
- const agentSubs = new Set(['list', 'get', 'sync']);
131
- if (agentSubs.has(arg)) {
132
- args.agentSubcommand = arg;
133
- } else {
134
- args.agentSlug = arg;
135
- }
136
- } else if (args.command === 'agent' && args.agentSubcommand && !args.agentSlug) {
137
- args.agentSlug = arg;
138
- } else if (!args.command && !args.instruction) {
139
- args.instruction = arg;
140
- }
141
- }
142
- break;
143
- }
144
- i++;
145
- }
146
- if (!args.verbose && process.env.TARANG_VERBOSE === '1') args.verbose = true;
147
- if (!args.yes && process.env.TARANG_YES === '1') args.yes = true;
148
- return args;
149
- }
150
-
151
- function printUsage() {
152
- printBanner(VERSION);
153
-
154
- const B = '\x1b[1m', C = '\x1b[36m', D = '\x1b[2m', G = '\x1b[32m', R = '\x1b[0m';
155
-
156
- process.stderr.write(`${B}USAGE${R}\n`);
157
- process.stderr.write(` ${C}bahulam "instruction"${R} Execute instruction\n`);
158
- process.stderr.write(` ${C}bahulam${R} Interactive mode (REPL)\n`);
159
- process.stderr.write(` ${C}bahulam login${R} Authenticate via GitHub OAuth\n`);
160
- process.stderr.write(` ${C}bahulam configure${R} Open settings in browser\n`);
161
- process.stderr.write(` ${C}bahulam config --show${R} Display local configuration\n`);
162
- process.stderr.write(` ${C}bahulam resume${R} Resume a paused session\n`);
163
- process.stderr.write(` ${C}bahulam workflow create --file <path>${R} Create workflow from YAML\n`);
164
- process.stderr.write(` ${C}bahulam workflow run <name>${R} Run a workflow\n`);
165
- process.stderr.write(` ${C}bahulam workflow list${R} List workflows\n`);
166
- process.stderr.write(` ${C}bahulam workflow get <name>${R} Show workflow details\n`);
167
- process.stderr.write(` ${C}bahulam workflow delete <name>${R} Delete a workflow\n`);
168
- process.stderr.write(` ${C}bahulam workflow sync${R} Sync workflow YAML files\n`);
169
- process.stderr.write(` ${C}bahulam agent list${R} List user-defined agents\n`);
170
- process.stderr.write(` ${C}bahulam agent get <slug>${R} Show agent details\n`);
171
- process.stderr.write(` ${C}bahulam agent sync${R} Sync agent YAML files\n`);
172
- process.stderr.write('\n');
173
- process.stderr.write(`${B}MODE FLAGS${R}\n`);
174
- process.stderr.write(` ${G}--local${R} Direct LLM API ${D}(<100ms, offline)${R}\n`);
175
- process.stderr.write(` ${G}--remote${R} SSE backend ${D}(multi-agent orchestration)${R}\n`);
176
- process.stderr.write(` ${G}--mode <auto|local|remote>${R} Set mode explicitly\n`);
177
- process.stderr.write(` ${D}(default: auto-select based on task complexity)${R}\n`);
178
- process.stderr.write('\n');
179
- process.stderr.write(`${B}MODEL FLAGS${R}\n`);
180
- process.stderr.write(` ${G}--model, -m <id|role=id>${R} Session model override ${D}(also: fast|thinking|extra|max)${R}\n`);
181
- process.stderr.write(` ${G}--route <platform|byok>${R} Model route ${D}(platform validates against curated catalog)${R}\n`);
182
- process.stderr.write(` ${G}--system-prompt <text>${R} Override system prompt\n`);
183
- process.stderr.write(` ${G}--max-turns <n>${R} Maximum conversation turns\n`);
184
- process.stderr.write(` ${D}Persistent defaults are configured via: bahulam configure${R}\n`);
185
- process.stderr.write('\n');
186
- process.stderr.write(`${B}PERMISSION FLAGS${R}\n`);
187
- process.stderr.write(` ${G}--yes, -y${R} Auto-approve all operations\n`);
188
- process.stderr.write(` ${G}--plan${R} Read-only mode (block all writes)\n`);
189
- process.stderr.write(` ${G}--strict${R} Deny tools not in allowed list\n`);
190
- process.stderr.write(` ${G}--permission-mode <mode>${R} Permission mode ${D}(auto, plan, strict)${R}\n`);
191
- process.stderr.write(` ${G}--allowedTools <tools>${R} Comma-separated allowed tools\n`);
192
- process.stderr.write(` ${G}--disallowedTools <tools>${R} Comma-separated denied tools\n`);
193
- process.stderr.write('\n');
194
- process.stderr.write(`${B}OUTPUT FLAGS${R}\n`);
195
- process.stderr.write(` ${G}--print, -p <prompt>${R} Non-interactive: run prompt and exit\n`);
196
- process.stderr.write(` ${G}--output-format <fmt>${R} Output format: text, json, stream-json\n`);
197
- process.stderr.write(` ${G}--verbose, -v${R} Show tool details and thinking\n`);
198
- process.stderr.write(` ${G}--debug, -d${R} Debug mode ${D}(implies verbose)${R}\n`);
199
- process.stderr.write('\n');
200
- process.stderr.write(`${B}OTHER FLAGS${R}\n`);
201
- process.stderr.write(` ${G}--version, -V${R} Show version\n`);
202
- process.stderr.write(` ${G}--help, -h${R} Show this help\n`);
203
- process.stderr.write(` ${G}--add-dir <dir>${R} Additional CLAUDE.md directory\n`);
204
- process.stderr.write('\n');
205
- process.stderr.write(`${B}SLASH COMMANDS${R} ${D}(interactive mode)${R}\n`);
206
- for (const [k, v] of Object.entries(COMMANDS)) {
207
- process.stderr.write(` ${C}${k.padEnd(14)}${R} ${v}\n`);
208
- }
209
- process.stderr.write('\n');
210
- }
211
-
212
- // ── Execute ─────────────────────────────────────────────────
213
-
214
- async function executeInstruction(executor, instruction, formatter, sessionMgr) {
215
- sessionMgr.start(instruction);
216
- for await (const event of executor) {
217
- formatter.render(event);
218
- if (event.type === 'session_info') sessionMgr.setSessionInfo(event.data);
219
- if (event.type === 'tool_call' || event.type === 'tool_request') sessionMgr.recordToolCall(event.data?.tool);
220
- if (event.type === 'complete') sessionMgr.complete(event.data?.summary);
221
- if (event.type === 'error' && event.data?.fatal) sessionMgr.fail(event.data?.message);
222
- if (event.type === 'cancelled') sessionMgr.cancel();
223
- if (event.type === 'paused') sessionMgr.pause();
224
- }
225
- }
226
-
227
- // ── REPL ────────────────────────────────────────────────────
228
-
229
- // Inline REPL removed — now delegates to startTerminalRepl() from terminal/repl.mjs
230
- // which has full markdown rendering, turn summaries, cost display, and ANSI UI.
231
-
232
- import { startTerminalRepl } from './terminal/repl.mjs';
233
-
234
- // ── Main ────────────────────────────────────────────────────
235
-
236
- async function main() {
237
- const args = parseArgs(process.argv.slice(2));
238
- if (args.version) { console.log(`@bahulam/code ${VERSION}`); process.exit(0); }
239
- if (args.help) { printUsage(); process.exit(0); }
240
-
241
- const auth = new TarangAuth();
242
-
243
- if (args.command === 'login') {
244
- printBanner(VERSION);
245
- process.stderr.write('\x1b[1mAuthentication\x1b[0m\n\n');
246
- await auth.login();
247
- process.stderr.write('\n\x1b[32m✓ Login successful!\x1b[0m\n');
248
- // Sync settings from web after login
249
- try {
250
- process.stderr.write('\x1b[2mSyncing settings from server...\x1b[0m\n');
251
- const remote = await auth.syncSettings();
252
- process.stderr.write(`\x1b[32m✓ Settings synced\x1b[0m ${remote.gateway_type ? `(gateway: ${remote.gateway_type})` : ''}\n`);
253
- } catch {
254
- process.stderr.write('\x1b[2mSettings sync skipped — configure at bahulam.ai/dashboard/settings\x1b[0m\n');
255
- }
256
- process.stderr.write('\n');
257
- // Fall through to REPL — user starts working right away
258
- }
259
-
260
- if (args.command === 'sync') {
261
- printBanner(VERSION);
262
- process.stderr.write('\x1b[1mSyncing settings...\x1b[0m\n\n');
263
- try {
264
- const remote = await auth.syncSettings();
265
- process.stderr.write(`\x1b[32m✓ Gateway:\x1b[0m ${remote.gateway_type}\n`);
266
- if (remote.models?.orchestrator) process.stderr.write(`\x1b[32m✓ Orchestrator:\x1b[0m ${remote.models.orchestrator}\n`);
267
- if (remote.models?.reasoning) process.stderr.write(`\x1b[32m✓ Coding:\x1b[0m ${remote.models.reasoning}\n`);
268
- if (remote.models?.local) process.stderr.write(`\x1b[32m✓ Local:\x1b[0m ${remote.models.local}\n`);
269
- if (remote.configured_providers?.length) process.stderr.write(`\x1b[32m✓ Providers:\x1b[0m ${remote.configured_providers.join(', ')}\n`);
270
- process.stderr.write('\n\x1b[32m✓ Settings saved to ~/.bahulam/config.json\x1b[0m\n');
271
- } catch (err) {
272
- process.stderr.write(`\x1b[31m✗ ${err.message}\x1b[0m\n`);
273
- }
274
- process.exit(0);
275
- }
276
-
277
- if (args.command === 'configure') {
278
- printBanner(VERSION);
279
- const { resolveWebUrl } = await import('./core/backend-url.mjs');
280
- const webUrl = resolveWebUrl();
281
- const settingsUrl = `${webUrl}/dashboard/settings?tab=providers&source=cli`;
282
- process.stderr.write('\x1b[36mOpening settings in browser...\x1b[0m\n');
283
- process.stderr.write(`\x1b[2m${settingsUrl}\x1b[0m\n`);
284
- const openCmd = process.platform === 'darwin' ? 'open' :
285
- process.platform === 'win32' ? 'start' : 'xdg-open';
286
- const { exec } = await import('node:child_process');
287
- exec(`${openCmd} "${settingsUrl}"`, () => {});
288
- process.stderr.write('\n\x1b[2mConfigure your provider, models, and CLI preferences in the browser.\x1b[0m\n');
289
- process.stderr.write('\x1b[2mChanges sync automatically to the backend.\x1b[0m\n');
290
- process.exit(0);
291
- }
292
-
293
- if (args.command === 'config') {
294
- let changed = false;
295
- if (args.openRouterKey) { auth.saveOpenRouterKey(args.openRouterKey); process.stderr.write('\x1b[32m✓ OpenRouter key saved.\x1b[0m\n'); changed = true; }
296
- if (args.anthropicKey) { auth.saveAnthropicKey(args.anthropicKey); process.stderr.write('\x1b[32m✓ Anthropic key saved.\x1b[0m\n'); changed = true; }
297
- if (args.openaiKey) { auth.saveOpenAIKey(args.openaiKey); process.stderr.write('\x1b[32m✓ OpenAI key saved.\x1b[0m\n'); changed = true; }
298
- if (args.googleKey) { auth.saveGoogleKey(args.googleKey); process.stderr.write('\x1b[32m✓ Google AI key saved.\x1b[0m\n'); changed = true; }
299
- if (args.gateway) { auth.saveCredentials({ gateway_type: args.gateway }); process.stderr.write(`\x1b[32m✓ Gateway set to ${args.gateway}\x1b[0m\n`); changed = true; }
300
- if (args.mode) { auth.setMode(args.mode); process.stderr.write(`\x1b[32m✓ Mode set to ${args.mode}\x1b[0m\n`); changed = true; }
301
- if (args.showConfig || !changed) {
302
- auth.printConfig();
303
- }
304
- process.exit(0);
305
- }
306
-
307
- if (args.command === 'workflow') {
308
- const { handleWorkflowCommand } = await import('./commands/workflow.mjs');
309
- await handleWorkflowCommand(args);
310
- process.exit(0);
311
- }
312
-
313
- if (args.command === 'agent') {
314
- const { handleAgentCommand } = await import('./commands/agent.mjs');
315
- await handleAgentCommand(args);
316
- process.exit(0);
317
- }
318
-
319
- // Load settings (user ~/.claude/settings.json + project .claude/settings.json + local)
320
- const settings = await loadSettings();
321
-
322
- /** Re-read credentials from disk (so /login updates take effect). */
323
- function freshCreds() {
324
- const c = auth.loadCredentials();
325
- return {
326
- token: process.env.TARANG_TOKEN || c.token,
327
- openRouterKey: process.env.OPENROUTER_API_KEY || c.openRouterKey,
328
- anthropicKey: process.env.ANTHROPIC_API_KEY || c.anthropicKey,
329
- openaiKey: process.env.OPENAI_API_KEY || c.openaiKey,
330
- googleKey: process.env.GOOGLE_API_KEY || c.googleKey,
331
- backendUrl: c.backendUrl,
332
- gatewayType: c.gatewayType,
333
- models: c.models,
334
- };
335
- }
336
-
337
- // Initial load — used for settings and startup checks
338
- const initCreds = freshCreds();
339
-
340
- // Apply settings as defaults (CLI flags override settings)
341
- if (!args.verbose && settings.debugMode) args.verbose = true;
342
- if (!args.permissionMode && settings.permissions?.defaultMode !== 'default') {
343
- args.permissionMode = settings.permissions.defaultMode;
344
- }
345
-
346
- const toolExecutor = createToolExecutor();
347
- const approval = new ApprovalManager({ autoApprove: args.yes, planMode: args.plan });
348
- const formatter = new EventFormatter({ verbose: args.verbose });
349
- const sessionMgr = new SessionManager();
350
- const contextRetriever = new ContextRetriever(process.cwd());
351
-
352
- /** Create an executor (local or remote) for a given instruction. */
353
- async function createExecutor(instruction, messages = null) {
354
- // Always re-read credentials so /login changes are picked up
355
- const creds = freshCreds();
356
- const mode = await selectMode(instruction, args, { ...creds, backendUrl: creds.backendUrl });
357
-
358
- if (mode === 'local') {
359
- // Model priority: CLI flag > synced web setting > settings.json > default
360
- const localModel = creds.models?.local || settings.model || 'anthropic/claude-sonnet-4-6';
361
- if (args.verbose) process.stderr.write(`\x1b[2m[mode] local (${localModel})\x1b[0m\n`);
362
-
363
- // Pick the right API key based on the model/gateway
364
- let apiKey = creds.anthropicKey;
365
- let orKey = creds.openRouterKey;
366
- if (creds.gatewayType === 'openai') apiKey = creds.openaiKey;
367
- if (creds.gatewayType === 'googleai') apiKey = creds.googleKey;
368
-
369
- return new LocalAgent({
370
- apiKey,
371
- openRouterKey: orKey,
372
- model: localModel,
373
- toolExecutor,
374
- verbose: args.verbose,
375
- cwd: process.cwd(),
376
- systemPromptOverride: args.systemPrompt,
377
- maxTurns: args.maxTurns,
378
- stagnationDetection: settings.stagnationDetection,
379
- stagnationThreshold: settings.stagnationThreshold,
380
- }).execute(instruction, { cwd: process.cwd() });
381
- } else {
382
- if (args.verbose) process.stderr.write('\x1b[2m[mode] remote\x1b[0m\n');
383
-
384
- // Retrieve BM25 context to send to backend
385
- let indexedContext = {};
386
- try {
387
- const chunks = contextRetriever.retrieve(instruction, 8);
388
- if (chunks.length > 0) {
389
- indexedContext = {
390
- indexed: chunks.map(c => ({ id: c.id, score: c.score, text: c.text })),
391
- };
392
- if (args.verbose) process.stderr.write(`\x1b[2m[context] ${chunks.length} chunks from BM25 index\x1b[0m\n`);
393
- }
394
- } catch {
395
- // No index available — send without context
396
- }
397
-
398
- const client = new TarangStreamClient({
399
- baseUrl: creds.backendUrl, token: creds.token, toolExecutor,
400
- verbose: args.verbose, approvalManager: approval,
401
- });
402
- // Cancel on SIGINT but don't exit (REPL handles exit)
403
- const sigHandler = async () => { await client.cancel().catch(() => {}); };
404
- process.once('SIGINT', sigHandler);
405
- return client.execute(instruction, { cwd: process.cwd(), ...indexedContext }, messages);
406
- }
407
- }
408
-
409
- if (args.command === 'resume') {
410
- const state = sessionMgr.loadState();
411
- if (!state || state.status === 'completed') { process.stderr.write('No resumable session.\n'); process.exit(1); }
412
- const resumeCreds = freshCreds();
413
- const client = new TarangStreamClient({ baseUrl: resumeCreds.backendUrl, token: resumeCreds.token, toolExecutor, verbose: args.verbose, approvalManager: approval });
414
- client.currentTaskId = state.task_id;
415
- await client.resume();
416
- for await (const event of client.execute(state.instruction)) formatter.render(event);
417
- process.exit(0);
418
- }
419
-
420
- if (args.instruction) {
421
- const exec = await createExecutor(args.instruction);
422
- await executeInstruction(exec, args.instruction, formatter, sessionMgr);
423
- process.stdout.write('\n');
424
- process.exit(0);
425
- }
426
-
427
- await startTerminalRepl();
428
- }
429
-
430
- main().catch(err => { process.stderr.write(`\x1b[31mFatal: ${err.message}\x1b[0m\n`); process.exit(1); });