@bahulam/code 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/auth/{tarang-auth.mjs → bahulam-auth.mjs} +1 -1
  4. package/src/commands/agent.mjs +3 -3
  5. package/src/commands/device.mjs +2 -2
  6. package/src/commands/pair.mjs +2 -2
  7. package/src/commands/remote.mjs +3 -3
  8. package/src/commands/workflow.mjs +5 -5
  9. package/src/config/env.mjs +9 -2
  10. package/src/config/memory-loader.mjs +1 -1
  11. package/src/config/settings.mjs +2 -2
  12. package/src/core/agent-loop.mjs +17 -0
  13. package/src/core/attachments.mjs +1 -1
  14. package/src/core/backend-url.mjs +11 -12
  15. package/src/core/callback-client.mjs +1 -1
  16. package/src/core/headless.mjs +4 -4
  17. package/src/core/jsonl-writer.mjs +15 -15
  18. package/src/core/local-agent.mjs +26 -5
  19. package/src/core/local-store.mjs +1 -1
  20. package/src/core/mode-selector.mjs +1 -1
  21. package/src/core/output-filter.mjs +1 -1
  22. package/src/core/project-artifacts.mjs +3 -3
  23. package/src/core/project-context-loader.mjs +9 -9
  24. package/src/core/settings-sync.mjs +1 -1
  25. package/src/core/stagnation.mjs +61 -1
  26. package/src/core/stream-client.mjs +4 -4
  27. package/src/core/system-prompt.mjs +2 -2
  28. package/src/core/tool-executor.mjs +78 -60
  29. package/src/local-service/agent-relay.mjs +8 -8
  30. package/src/local-service/server.mjs +2 -2
  31. package/src/mcp/client.mjs +4 -4
  32. package/src/onboarding/preflight.mjs +1 -1
  33. package/src/terminal/agents.mjs +2 -2
  34. package/src/terminal/main.mjs +6 -6
  35. package/src/terminal/repl-render.mjs +2 -2
  36. package/src/terminal/repl-resume.mjs +2 -2
  37. package/src/terminal/repl.mjs +17 -17
  38. package/src/terminal/tool-display.mjs +1 -1
  39. package/src/tools/agent.mjs +2 -2
  40. package/src/tools/analyze-image.mjs +2 -2
  41. package/src/tools/generate-image.mjs +4 -4
  42. package/src/tools/project-overview.mjs +12 -12
  43. package/src/ui/formatter.mjs +2 -2
  44. package/src/ui/input-dock.mjs +7 -7
  45. package/src/ui/spinner.mjs +1 -1
  46. package/src/ui/term.mjs +2 -6
  47. package/src/ui/tool-card.mjs +74 -3
@@ -1,4 +1,5 @@
1
1
  export const DEFAULT_STAGNATION_THRESHOLD = 3;
2
+ const NO_CHANGE_EDIT_TOOLS = new Set(['edit_file']);
2
3
 
3
4
  function canonicalize(value) {
4
5
  if (Array.isArray(value)) return value.map(canonicalize);
@@ -12,6 +13,26 @@ function canonicalize(value) {
12
13
  return value;
13
14
  }
14
15
 
16
+ function editTarget(input = {}, result = {}) {
17
+ return String(
18
+ input.file_path || input.path || result.file_path || result.path || '',
19
+ ).trim();
20
+ }
21
+
22
+ function isNoChangeEdit(tool, result = {}) {
23
+ if (!NO_CHANGE_EDIT_TOOLS.has(tool)) return false;
24
+ if (!result || typeof result !== 'object') return false;
25
+ if (result._no_change || result.no_change) return true;
26
+ if (
27
+ result.success !== false
28
+ && result.lines_added === 0
29
+ && result.lines_removed === 0
30
+ ) {
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+
15
36
  export function createStagnationTracker({
16
37
  enabled = true,
17
38
  threshold = DEFAULT_STAGNATION_THRESHOLD,
@@ -21,6 +42,8 @@ export function createStagnationTracker({
21
42
  : DEFAULT_STAGNATION_THRESHOLD;
22
43
  let previousSignature = null;
23
44
  let consecutiveCount = 0;
45
+ let previousResultSignature = null;
46
+ let consecutiveResultCount = 0;
24
47
 
25
48
  return {
26
49
  record(tool, input) {
@@ -43,14 +66,51 @@ export function createStagnationTracker({
43
66
  };
44
67
  },
45
68
 
69
+ recordResult(tool, input, result) {
70
+ if (!enabled) return { detected: false, count: 0 };
71
+ if (!isNoChangeEdit(tool, result)) {
72
+ previousResultSignature = null;
73
+ consecutiveResultCount = 0;
74
+ return { detected: false, count: 0 };
75
+ }
76
+
77
+ const target = editTarget(input, result);
78
+ const signature = JSON.stringify({
79
+ kind: 'no_change_edit',
80
+ tool,
81
+ target,
82
+ });
83
+ if (signature === previousResultSignature) {
84
+ consecutiveResultCount++;
85
+ } else {
86
+ previousResultSignature = signature;
87
+ consecutiveResultCount = 1;
88
+ }
89
+
90
+ return {
91
+ detected: consecutiveResultCount >= effectiveThreshold,
92
+ count: consecutiveResultCount,
93
+ kind: 'no_change_edit',
94
+ target,
95
+ };
96
+ },
97
+
46
98
  reset() {
47
99
  previousSignature = null;
48
100
  consecutiveCount = 0;
101
+ previousResultSignature = null;
102
+ consecutiveResultCount = 0;
49
103
  },
50
104
  };
51
105
  }
52
106
 
53
- export function stagnationMessage(tool, count) {
107
+ export function stagnationMessage(tool, count, details = {}) {
108
+ if (details.kind === 'no_change_edit') {
109
+ const target = details.target ? ` for "${details.target}"` : '';
110
+ return `STAGNATION WARNING: Tool "${tool}" made no file changes ${count} consecutive times${target}. ` +
111
+ `The duplicate edit loop was stopped. Re-read the target range, compare the current file content, ` +
112
+ `change the edit strategy, or finish the task.`;
113
+ }
54
114
  return `STAGNATION WARNING: Tool "${tool}" was called ${count} consecutive times ` +
55
115
  `with identical arguments. The duplicate call was skipped. Review the previous ` +
56
116
  `result, change the arguments, try another tool, or finish the task.`;
@@ -1,8 +1,8 @@
1
1
  /**
2
- * TarangStreamClient — SSE consumer for Tarang backend.
2
+ * BahulamStreamClient — SSE consumer for Bahulam backend.
3
3
  *
4
4
  * Replaces OCC's agent-loop.mjs. Instead of calling the LLM API directly,
5
- * this client POSTs to the Tarang backend, parses the SSE stream, intercepts
5
+ * this client POSTs to the Bahulam backend, parses the SSE stream, intercepts
6
6
  * tool_request/tool_call events (executes locally, POSTs callback), and
7
7
  * yields all other events to the caller for rendering.
8
8
  *
@@ -117,10 +117,10 @@ function isOfflineLikelyError(err) {
117
117
  );
118
118
  }
119
119
 
120
- export class TarangStreamClient {
120
+ export class BahulamStreamClient {
121
121
  /**
122
122
  * @param {Object} opts
123
- * @param {string} opts.baseUrl - Tarang backend URL (ignored when mode='bundled')
123
+ * @param {string} opts.baseUrl - Bahulam backend URL (ignored when mode='bundled')
124
124
  * @param {string} opts.token - CLI auth token
125
125
  * @param {Object} opts.toolExecutor - { execute(name, args) }
126
126
  * @param {boolean} [opts.verbose=false]
@@ -10,7 +10,7 @@
10
10
  import fs from 'fs';
11
11
  import path from 'path';
12
12
  import os from 'os';
13
- import { loadKeplerMemory } from '../config/memory-loader.mjs';
13
+ import { loadBahulamMemory } from '../config/memory-loader.mjs';
14
14
 
15
15
  /**
16
16
  * Load all CLAUDE.md files and merge them in order.
@@ -89,7 +89,7 @@ export function buildSystemPrompt({ cwd, tools, override, addDirs } = {}) {
89
89
  parts.push(f.content);
90
90
  }
91
91
 
92
- for (const f of loadKeplerMemory({ cwd })) {
92
+ for (const f of loadBahulamMemory({ cwd })) {
93
93
  parts.push(f.content);
94
94
  }
95
95
 
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Tool Executor Bridge — maps Tarang backend tool names to OCC tool calls.
2
+ * Tool Executor Bridge — maps Bahulam backend tool names to OCC tool calls.
3
3
  *
4
- * The Tarang backend sends tool_request events with its own tool names and arg shapes.
4
+ * The Bahulam backend sends tool_request events with its own tool names and arg shapes.
5
5
  * This bridge translates those into OCC tool calls and wraps the results.
6
6
  *
7
7
  * Safety guardrails integrated — prevents destructive operations on source code.
@@ -18,7 +18,7 @@ import { SkillInstaller } from '../skills/installer.mjs';
18
18
  import { SkillsLoader } from '../skills/loader.mjs';
19
19
  import { createAgentFile, listLocalAgents, syncAgentsToBackend } from '../agents/scaffold.mjs';
20
20
  import { createWorkflowFile, listLocalWorkflows, WORKFLOW_SYNC_ENDPOINT, slugifyWorkflowName } from '../agents/workflow_scaffold.mjs';
21
- import { TarangAuth } from '../auth/tarang-auth.mjs';
21
+ import { BahulamAuth } from '../auth/bahulam-auth.mjs';
22
22
  import { detectImageFile } from './attachments.mjs';
23
23
  import { streamResponse } from './streaming.mjs';
24
24
  import { sendApprovalDecision, sendCallback } from './callback-client.mjs';
@@ -31,10 +31,10 @@ import * as fs from 'node:fs';
31
31
  import * as os from 'node:os';
32
32
  import * as path from 'node:path';
33
33
  import * as crypto from 'node:crypto';
34
- import { execSync } from 'node:child_process';
34
+ import { exec, execSync } from 'node:child_process';
35
35
 
36
36
  /**
37
- * Create a tool executor that bridges Tarang tool names to OCC tools.
37
+ * Create a tool executor that bridges Bahulam tool names to OCC tools.
38
38
  * @param {Object} [options]
39
39
  * @param {ProjectRegistry} [options.projectRegistry] - session-owned project registry
40
40
  * @returns {{ execute(name, args): Promise<Object>, listTools(): string[] }}
@@ -472,7 +472,7 @@ export function createToolExecutor({
472
472
  }
473
473
 
474
474
  /**
475
- * Wrap an OCC string result into Tarang's { success, output } format.
475
+ * Wrap an OCC string result into Bahulam's { success, output } format.
476
476
  */
477
477
  function wrapResult(result, toolName) {
478
478
  if (typeof result === 'object' && result !== null && 'success' in result) {
@@ -495,7 +495,24 @@ export function createToolExecutor({
495
495
  const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]/g;
496
496
  function stripAnsi(s) { return String(s || '').replace(ANSI_RE, ''); }
497
497
 
498
- function autoLint(filePath) {
498
+ function runAutoLintCommand(lint) {
499
+ return new Promise((resolve) => {
500
+ exec(lint.command, {
501
+ encoding: 'utf-8',
502
+ timeout: 15_000,
503
+ cwd: lint.cwd,
504
+ maxBuffer: 1_000_000,
505
+ env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1', TERM: 'dumb' },
506
+ }, (err, stdout = '', stderr = '') => {
507
+ const output = err
508
+ ? stripAnsi(stderr || stdout || '').trim()
509
+ : stripAnsi(stdout || stderr || '').trim();
510
+ resolve(output || null);
511
+ });
512
+ });
513
+ }
514
+
515
+ async function autoLint(filePath) {
499
516
  const project = projectRegistry.projectForPath(filePath);
500
517
  const lint = resolveLintCommand(filePath, {
501
518
  projectRoot: project?.resource?.root || projectRootFor(filePath),
@@ -504,23 +521,7 @@ export function createToolExecutor({
504
521
  });
505
522
  if (!lint?.command) return null;
506
523
 
507
- try {
508
- const output = execSync(lint.command, {
509
- encoding: 'utf-8',
510
- timeout: 15_000,
511
- cwd: lint.cwd,
512
- stdio: ['pipe', 'pipe', 'pipe'],
513
- env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1', TERM: 'dumb' },
514
- });
515
- const trimmed = stripAnsi(output).trim();
516
- if (!trimmed) return null;
517
- return trimmed;
518
- } catch (err) {
519
- // Non-zero exit means lint errors found
520
- const output = stripAnsi(err.stderr || err.stdout || '').trim();
521
- if (!output) return null;
522
- return output;
523
- }
524
+ return runAutoLintCommand(lint);
524
525
  }
525
526
 
526
527
  // ── Post-edit verification hint ──────────────────────────────
@@ -1143,7 +1144,7 @@ export function createToolExecutor({
1143
1144
  updateProjectIndex(filePath);
1144
1145
 
1145
1146
  // Auto-lint the written file
1146
- const lintOutput = autoLint(filePath);
1147
+ const lintOutput = await autoLint(filePath);
1147
1148
  if (lintOutput) {
1148
1149
  wrapped.output += `\n\n--- Lint ---\n${lintOutput}`;
1149
1150
  wrapped.lint = lintOutput;
@@ -1236,9 +1237,21 @@ export function createToolExecutor({
1236
1237
  // 4. edit_file → Edit + auto-lint + auto-fallback to sed
1237
1238
  edit_file: async (args) => {
1238
1239
  const rawPath = args.file_path || args.path;
1240
+ const searchText = args.search ?? args.old_string ?? args.oldString;
1241
+ const replaceText = args.replace ?? args.new_string ?? args.newString;
1242
+ const replaceAll = args.replace_all === true || args.replaceAll === true;
1243
+ if (!rawPath || rawPath === 'file' || rawPath.length < 3) {
1244
+ return { success: false, output: `Error: Invalid file path "${rawPath || ''}". Register the project, then use an absolute path.`, _tool: 'edit_file' };
1245
+ }
1246
+ if (typeof searchText !== 'string' || searchText.length === 0) {
1247
+ return { success: false, output: 'Error: edit_file requires a non-empty search string.', _tool: 'edit_file' };
1248
+ }
1249
+ if (typeof replaceText !== 'string') {
1250
+ return { success: false, output: 'Error: edit_file requires a replacement string.', _tool: 'edit_file' };
1251
+ }
1239
1252
  const filePath = await resolvePath(rawPath, args);
1240
1253
  const before = readTextIfExists(filePath);
1241
- const writeCheck = validateWrite(filePath, args.replace, projectRootFor(filePath));
1254
+ const writeCheck = validateWrite(filePath, replaceText, projectRootFor(filePath));
1242
1255
  if (!writeCheck.safe) {
1243
1256
  return { success: false, output: `BLOCKED: ${writeCheck.reason}`, _tool: 'edit_file', _blocked: true };
1244
1257
  }
@@ -1256,46 +1269,51 @@ export function createToolExecutor({
1256
1269
  try {
1257
1270
  result = await occRegistry.call('edit_file', {
1258
1271
  file_path: filePath,
1259
- search: args.search,
1260
- replace: args.replace,
1261
- replace_all: args.replace_all || false,
1272
+ search: searchText,
1273
+ replace: replaceText,
1274
+ replace_all: replaceAll,
1262
1275
  });
1263
1276
  } catch (editErr) {
1264
- // OCC Edit failed (string not found) — fallback to Python replacement
1277
+ // OCC Edit failed (string not found) — fallback to direct text replacement.
1265
1278
  try {
1266
- const search = args.search.replace(/'/g, "\\'").replace(/\n/g, "\\n");
1267
- const replace = args.replace.replace(/'/g, "\\'").replace(/\n/g, "\\n");
1268
- const pyCmd = `python3 -c "
1269
- import sys
1270
- with open('${filePath}', 'r') as f: content = f.read()
1271
- old = '''${args.search}'''
1272
- new = '''${args.replace}'''
1273
- if old not in content:
1274
- print('ERROR: search string not found in file', file=sys.stderr)
1275
- sys.exit(1)
1276
- content = content.replace(old, new, 1)
1277
- with open('${filePath}', 'w') as f: f.write(content)
1278
- print('OK: replaced')
1279
- "`;
1280
- const fallbackResult = execSync(pyCmd, {
1281
- encoding: 'utf-8',
1282
- timeout: 5000,
1283
- cwd: projectRootFor(filePath),
1284
- });
1285
- result = `Edited ${filePath} (via fallback): ${fallbackResult.trim()}`;
1286
- } catch (sedErr) {
1287
- return { success: false, output: `edit_file failed: ${editErr?.message || 'unknown'}. Fallback also failed: ${sedErr?.message || 'unknown'}. Try shell(sed) manually.`, _tool: 'edit_file' };
1279
+ const content = fs.readFileSync(filePath, 'utf-8');
1280
+ if (!content.includes(searchText)) {
1281
+ throw new Error('search string not found in file');
1282
+ }
1283
+ const nextContent = replaceAll
1284
+ ? content.split(searchText).join(replaceText)
1285
+ : content.replace(searchText, replaceText);
1286
+ if (nextContent !== content) {
1287
+ fs.writeFileSync(filePath, nextContent, 'utf-8');
1288
+ }
1289
+ result = `Edited ${filePath} (via fallback): OK: replaced`;
1290
+ } catch (fallbackErr) {
1291
+ return { success: false, output: `edit_file failed: ${editErr?.message || 'unknown'}. Fallback also failed: ${fallbackErr?.message || 'unknown'}. Re-read the target range and provide an exact search string.`, _tool: 'edit_file' };
1288
1292
  }
1289
1293
  }
1290
1294
 
1291
1295
  const wrapped = wrapResult(result, 'edit_file');
1292
1296
  const after = readTextIfExists(filePath);
1297
+ if (wrapped.success !== false && before === after) {
1298
+ const relativePath = path.relative(projectRootFor(filePath), filePath) || path.basename(filePath);
1299
+ return {
1300
+ success: false,
1301
+ output: `edit_file made no changes to ${relativePath}. The search string may already be replaced, the replacement may be identical, or the target content may have drifted. Re-read the target range before trying a different edit.`,
1302
+ _tool: 'edit_file',
1303
+ _no_change: true,
1304
+ no_change: true,
1305
+ file_path: filePath,
1306
+ relative_path: relativePath,
1307
+ lines_added: 0,
1308
+ lines_removed: 0,
1309
+ };
1310
+ }
1293
1311
  attachFileDiff(wrapped, filePath, before, after);
1294
1312
  updateProjectIndex(filePath);
1295
1313
  _hasEdited = true;
1296
1314
 
1297
1315
  // Auto-lint the edited file
1298
- const lintOutput = autoLint(filePath);
1316
+ const lintOutput = await autoLint(filePath);
1299
1317
  if (lintOutput) {
1300
1318
  wrapped.output += `\n\n--- Lint ---\n${lintOutput}`;
1301
1319
  wrapped.lint = lintOutput;
@@ -1505,7 +1523,7 @@ print('OK: replaced')
1505
1523
  };
1506
1524
  },
1507
1525
 
1508
- // ── Tarang-specific tools (no OCC bridge) ──────────────
1526
+ // ── Bahulam-specific tools (no OCC bridge) ──────────────
1509
1527
 
1510
1528
  // 8. read_files → batch Read (with AST truncation for large files)
1511
1529
  read_files: async (args) => {
@@ -1934,7 +1952,7 @@ print('OK: replaced')
1934
1952
  const target = args.name || args.slug || '';
1935
1953
  throw new Error(target ? `No local agent found: ${target}` : 'No local agents found in .bahulam/agents');
1936
1954
  }
1937
- const creds = new TarangAuth().loadCredentials();
1955
+ const creds = new BahulamAuth().loadCredentials();
1938
1956
  const result = await syncAgentsToBackend({
1939
1957
  backendUrl: creds.backendUrl,
1940
1958
  token: creds.token,
@@ -1956,7 +1974,7 @@ print('OK: replaced')
1956
1974
  const local = filterLocalWorkflows(args).map(compactWorkflowMetadata);
1957
1975
  let backend = [];
1958
1976
  try {
1959
- const creds = new TarangAuth().loadCredentials();
1977
+ const creds = new BahulamAuth().loadCredentials();
1960
1978
  if (creds.backendUrl && creds.token) {
1961
1979
  const resp = await fetch(`${creds.backendUrl}${WORKFLOW_SYNC_ENDPOINT}`, {
1962
1980
  headers: {
@@ -2022,7 +2040,7 @@ print('OK: replaced')
2022
2040
  const target = args.name || args.slug || '';
2023
2041
  throw new Error(target ? `No local workflow found: ${target}` : 'No local workflows found in .bahulam/workflows');
2024
2042
  }
2025
- const creds = new TarangAuth().loadCredentials();
2043
+ const creds = new BahulamAuth().loadCredentials();
2026
2044
  if (!creds.backendUrl || !creds.token) {
2027
2045
  throw new Error('Not logged in. Run bahulam login first.');
2028
2046
  }
@@ -2094,7 +2112,7 @@ print('OK: replaced')
2094
2112
  if (!target) {
2095
2113
  throw new Error('workflow_id is required');
2096
2114
  }
2097
- const creds = new TarangAuth().loadCredentials();
2115
+ const creds = new BahulamAuth().loadCredentials();
2098
2116
  if (!creds.backendUrl || !creds.token) {
2099
2117
  throw new Error('Not logged in. Run bahulam login first.');
2100
2118
  }
@@ -2273,8 +2291,8 @@ print('OK: replaced')
2273
2291
 
2274
2292
  return {
2275
2293
  /**
2276
- * Execute a Tarang tool by name.
2277
- * @param {string} name - Tarang tool name
2294
+ * Execute a Bahulam tool by name.
2295
+ * @param {string} name - Bahulam tool name
2278
2296
  * @param {Object} args - Tool arguments
2279
2297
  * @returns {Promise<Object>} - { success, output, ... }
2280
2298
  */
@@ -2,12 +2,12 @@
2
2
  * Browser-local agent relay.
3
3
  *
4
4
  * Bridges a local workspace browser session to the same CLI-owned remote
5
- * agent path used by the terminal: TarangStreamClient + local ToolExecutor.
5
+ * agent path used by the terminal: BahulamStreamClient + local ToolExecutor.
6
6
  */
7
7
 
8
8
  import * as fs from 'node:fs';
9
9
  import { createRequire } from 'node:module';
10
- import { TarangAuth } from '../auth/tarang-auth.mjs';
10
+ import { BahulamAuth } from '../auth/bahulam-auth.mjs';
11
11
  import { AgentHistoryTurnBuilder } from '../core/agent-history.mjs';
12
12
  import { JsonlWriter } from '../core/jsonl-writer.mjs';
13
13
  import {
@@ -15,7 +15,7 @@ import {
15
15
  getRecentSessions,
16
16
  getSessionDetail,
17
17
  } from '../core/local-store.mjs';
18
- import { TarangStreamClient } from '../core/stream-client.mjs';
18
+ import { BahulamStreamClient } from '../core/stream-client.mjs';
19
19
  import { createToolExecutor } from '../core/tool-executor.mjs';
20
20
  import { buildWorkScope } from '../core/work-scope.mjs';
21
21
  import { BrowserApprovalManager } from './approval-bridge.mjs';
@@ -208,7 +208,7 @@ export class LocalAgentRelay {
208
208
  this.agentHistory.length ? this.agentHistory : null,
209
209
  )) {
210
210
  eventCount += 1;
211
- writer.writeKeplerEvent(event);
211
+ writer.writeBahulamEvent(event);
212
212
 
213
213
  const contentUpdate = contentDeltaForEvent(event, assistantContent);
214
214
  if (contentUpdate) {
@@ -256,7 +256,7 @@ export class LocalAgentRelay {
256
256
  this._markUndeliveredFollowupsQueued(wasCancelled ? 'Task cancelled' : '');
257
257
  if (!userTurnWritten && (this.client.sessionId || wasCancelled)) writeUserTurn();
258
258
  if (wasCancelled) {
259
- writer.writeKeplerEvent({
259
+ writer.writeBahulamEvent({
260
260
  type: 'cancelled',
261
261
  data: {
262
262
  task_id: this.client?.currentTaskId || null,
@@ -482,7 +482,7 @@ export class LocalAgentRelay {
482
482
  error: result.error || null,
483
483
  http_status: result.httpStatus || null,
484
484
  };
485
- this._ensureJsonlWriter().writeKeplerEvent({
485
+ this._ensureJsonlWriter().writeBahulamEvent({
486
486
  type: 'user_intervention',
487
487
  data: {
488
488
  instruction: item.instruction,
@@ -511,7 +511,7 @@ export class LocalAgentRelay {
511
511
  }
512
512
 
513
513
  async _initialize() {
514
- const auth = new TarangAuth();
514
+ const auth = new BahulamAuth();
515
515
  const creds = auth.loadCredentials();
516
516
  if (!creds.token) {
517
517
  const err = new Error('Not logged in. Run `bahulam login` from the CLI, then retry.');
@@ -539,7 +539,7 @@ export class LocalAgentRelay {
539
539
  this.creds = creds;
540
540
  this.toolExecutor = toolExecutor;
541
541
  this.approvalManager = approval;
542
- this.client = new TarangStreamClient({
542
+ this.client = new BahulamStreamClient({
543
543
  baseUrl: creds.backendUrl,
544
544
  token: creds.token,
545
545
  toolExecutor,
@@ -10,7 +10,7 @@ import * as fs from 'node:fs';
10
10
  import * as http from 'node:http';
11
11
  import * as path from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
- import { TarangAuth } from '../auth/tarang-auth.mjs';
13
+ import { BahulamAuth } from '../auth/bahulam-auth.mjs';
14
14
  import { resolveWebUrl } from '../core/backend-url.mjs';
15
15
  import { LocalAgentRelay } from './agent-relay.mjs';
16
16
  import {
@@ -722,7 +722,7 @@ function staticContentType(filePath) {
722
722
 
723
723
  function localAuthInfo() {
724
724
  try {
725
- const credentials = new TarangAuth().loadCredentials();
725
+ const credentials = new BahulamAuth().loadCredentials();
726
726
  return {
727
727
  authenticated: Boolean(credentials.token),
728
728
  backendUrl: credentials.backendUrl || '',
@@ -78,7 +78,7 @@ export class McpClient {
78
78
  const initResult = await this._request('initialize', {
79
79
  protocolVersion: MCP_PROTOCOL_VERSION,
80
80
  capabilities: {},
81
- clientInfo: { name: 'open-claude-code', version: '2.0.0' },
81
+ clientInfo: { name: 'bahulam-code', version: '2.0.0' },
82
82
  });
83
83
 
84
84
  this.serverInfo = initResult;
@@ -115,7 +115,7 @@ export class McpClient {
115
115
  this.serverInfo = await this.transport.request('initialize', {
116
116
  protocolVersion: MCP_PROTOCOL_VERSION,
117
117
  capabilities: {},
118
- clientInfo: { name: 'open-claude-code', version: '2.0.0' },
118
+ clientInfo: { name: 'bahulam-code', version: '2.0.0' },
119
119
  });
120
120
  return this.serverInfo;
121
121
  }
@@ -130,7 +130,7 @@ export class McpClient {
130
130
  this.serverInfo = await this.transport.request('initialize', {
131
131
  protocolVersion: MCP_PROTOCOL_VERSION,
132
132
  capabilities: {},
133
- clientInfo: { name: 'open-claude-code', version: '2.0.0' },
133
+ clientInfo: { name: 'bahulam-code', version: '2.0.0' },
134
134
  });
135
135
  return this.serverInfo;
136
136
  }
@@ -139,7 +139,7 @@ export class McpClient {
139
139
  const result = await this._transportRequest('initialize', {
140
140
  protocolVersion: MCP_PROTOCOL_VERSION,
141
141
  capabilities: {},
142
- clientInfo: { name: 'open-claude-code', version: '2.0.0' },
142
+ clientInfo: { name: 'bahulam-code', version: '2.0.0' },
143
143
  });
144
144
  this.serverInfo = result;
145
145
  return result;
@@ -334,7 +334,7 @@ function formatRow(check) {
334
334
  * collected check results.
335
335
  *
336
336
  * @param {object} opts
337
- * @param {object} opts.auth — TarangAuth instance
337
+ * @param {object} opts.auth — BahulamAuth instance
338
338
  * @param {string} opts.cwd — working directory
339
339
  * @param {string} opts.version — package version string
340
340
  * @param {boolean} [opts.silent] — if true, do not write (useful for tests)
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { c } from './ansi.mjs';
14
- import { TarangStreamClient } from '../core/stream-client.mjs';
14
+ import { BahulamStreamClient } from '../core/stream-client.mjs';
15
15
 
16
16
  // ── Agent Definitions ────────────────────────────────────────
17
17
 
@@ -296,7 +296,7 @@ export async function runAgentDefinition(agentDefinition, instruction, ctx, sess
296
296
  projectRoot: execContext.project_root || null,
297
297
  });
298
298
 
299
- const client = new TarangStreamClient({
299
+ const client = new BahulamStreamClient({
300
300
  baseUrl: creds.backendUrl,
301
301
  token: creds.token,
302
302
  toolExecutor,
@@ -16,7 +16,7 @@ import {
16
16
  import { parseArgs } from '../config/cli-args.mjs';
17
17
  import * as telemetry from '../telemetry/index.mjs';
18
18
  import { bahulamHome } from '../core/paths.mjs';
19
- import { TarangAuth as Auth } from '../auth/tarang-auth.mjs';
19
+ import { BahulamAuth as Auth } from '../auth/bahulam-auth.mjs';
20
20
 
21
21
  // ── Subcommands ──
22
22
 
@@ -183,8 +183,8 @@ async function main() {
183
183
  }
184
184
 
185
185
  if (subcommand === 'login') {
186
- const { TarangAuth } = await import('../auth/tarang-auth.mjs');
187
- const auth = new TarangAuth();
186
+ const { BahulamAuth } = await import('../auth/bahulam-auth.mjs');
187
+ const auth = new BahulamAuth();
188
188
  try {
189
189
  telemetry.track('login_shown', { method: 'cli_subcommand' });
190
190
  await auth.login();
@@ -198,8 +198,8 @@ async function main() {
198
198
  }
199
199
 
200
200
  if (subcommand === 'logout') {
201
- const { TarangAuth } = await import('../auth/tarang-auth.mjs');
202
- const auth = new TarangAuth();
201
+ const { BahulamAuth } = await import('../auth/bahulam-auth.mjs');
202
+ const auth = new BahulamAuth();
203
203
  const success = auth.logout();
204
204
  if (success) {
205
205
  process.stderr.write('\x1b[32m✓ Signed out. Credentials cleared.\x1b[0m\n');
@@ -306,7 +306,7 @@ async function main() {
306
306
  KEPLER_RECONNECT_MAX_ELAPSED_MS
307
307
  Max reconnect window for dropped streams
308
308
  BAHULAM_TTY_MODE=stable Scrollback-safe transcript if fixed dock redraws leak
309
- KEPLER_BLOCK_SEPARATOR Tool/content separator: space, dotted, or off
309
+ BAHULAM_BLOCK_SEPARATOR Tool/content separator: space, dotted, or off
310
310
 
311
311
  \x1b[2mDocs: https://bahulam.ai\x1b[0m
312
312
  `);
@@ -133,7 +133,7 @@ import { safeCwd } from './repl-utils.mjs';
133
133
  import { transcriptHeader, transcriptLine } from '../ui/transcript-block.mjs';
134
134
 
135
135
  export function blockSeparatorMode() {
136
- return String(process.env.KEPLER_BLOCK_SEPARATOR || 'space').toLowerCase();
136
+ return String(process.env.BAHULAM_BLOCK_SEPARATOR || 'space').toLowerCase();
137
137
  }
138
138
 
139
139
  export function renderBlockBoundary(nextBlock, { compactSame = false } = {}) {
@@ -391,7 +391,7 @@ export function renderToolResult(data, eventType = 'tool_result') {
391
391
 
392
392
  if (data._blocked) session.blockedOps++;
393
393
 
394
- const { text, tone: t } = summarizeResult(tool, data);
394
+ const { text, tone: t } = summarizeResult(tool, data, data.args || {});
395
395
  // Em dash reads more like prose than a system arrow.
396
396
  const arrow = shellResultTool(tool)
397
397
  ? `${paint.text.dim('result')} ${paint.text.dim('—')}`
@@ -33,7 +33,7 @@ import {
33
33
  writeOverlayFrame,
34
34
  eraseOverlayFrame,
35
35
  } from './repl-format.mjs';
36
- import { TarangStreamClient } from '../core/stream-client.mjs';
36
+ import { BahulamStreamClient } from '../core/stream-client.mjs';
37
37
  import { getRecentSessions, getSessionDetail, buildResumeHistory, combineResumeSummaries } from '../core/local-store.mjs';
38
38
  import { decideResumeMode, projectedTokensForChoice, formatTokens as formatCtxTokens } from '../core/resume-mode.mjs';
39
39
  import { applyCompactSummary, localCompactSummary, parseCompactTailCount, prepareCompactHistory } from '../core/compact-history.mjs';
@@ -504,7 +504,7 @@ export async function summarizeResumeTranscript({
504
504
  };
505
505
  }
506
506
  try {
507
- const client = new TarangStreamClient({
507
+ const client = new BahulamStreamClient({
508
508
  baseUrl: creds.backendUrl,
509
509
  token: creds.token,
510
510
  toolExecutor,