@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
package/README.md CHANGED
@@ -79,7 +79,7 @@ See [RELEASE.md](./RELEASE.md) for the merge, PR, and npm publish checklist.
79
79
  ## Links
80
80
 
81
81
  - Website: https://bahulam.ai
82
- - Repository: https://github.com/raviakasapu/codekepler-npm
82
+ - Repository: https://github.com/BahulamAI/BahulamCode-CLI
83
83
 
84
84
  ## License
85
85
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bahulam/code",
3
- "version": "0.1.7",
4
- "description": "Bahulam Code \u2014 abundance, in your terminal. CLI-first, reliability-first, sub-agents, 65.6% SWE-bench Verified.",
3
+ "version": "0.1.8",
4
+ "description": "Bahulam Code abundance, in your terminal. CLI-first, reliability-first, sub-agents, 65.6% SWE-bench Verified.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "bahulam": "src/terminal/main.mjs",
@@ -44,7 +44,7 @@
44
44
  "homepage": "https://bahulam.ai/code",
45
45
  "repository": {
46
46
  "type": "git",
47
- "url": "git+https://github.com/raviakasapu/codekepler-npm.git"
47
+ "url": "git+https://github.com/BahulamAI/BahulamCode-CLI.git"
48
48
  },
49
49
  "dependencies": {
50
50
  "mermaid": "^11.17.2",
@@ -38,7 +38,7 @@ function readTokenFromEnv() {
38
38
  return null;
39
39
  }
40
40
 
41
- export class TarangAuth {
41
+ export class BahulamAuth {
42
42
  constructor() {
43
43
  this._config = null;
44
44
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { resolveBackendUrl } from '../core/backend-url.mjs';
11
- import { TarangAuth } from '../auth/tarang-auth.mjs';
11
+ import { BahulamAuth } from '../auth/bahulam-auth.mjs';
12
12
  import { listLocalAgents, syncAgentsToBackend, agentToSpec, agentContentHash } from '../agents/scaffold.mjs';
13
13
  import { parseAgentDefinition } from '../agents/parser.mjs';
14
14
  import fs from 'node:fs';
@@ -56,7 +56,7 @@ function printAgentUsage() {
56
56
  // ── Helpers ────────────────────────────────────────────────────
57
57
 
58
58
  function getAuth() {
59
- const auth = new TarangAuth();
59
+ const auth = new BahulamAuth();
60
60
  const creds = auth.loadCredentials();
61
61
  if (!creds.token) {
62
62
  process.stderr.write(`${RED}✗ Not authenticated. Run bahulam-code login first.${RESET}\n`);
@@ -66,7 +66,7 @@ function getAuth() {
66
66
  }
67
67
 
68
68
  function getBaseUrl() {
69
- const auth = new TarangAuth();
69
+ const auth = new BahulamAuth();
70
70
  return auth.loadCredentials().backendUrl || resolveBackendUrl();
71
71
  }
72
72
 
@@ -10,7 +10,7 @@
10
10
  * `bahulam device list`).
11
11
  */
12
12
 
13
- import { TarangAuth } from '../auth/tarang-auth.mjs';
13
+ import { BahulamAuth } from '../auth/bahulam-auth.mjs';
14
14
 
15
15
  const GATEWAY = (process.env.BAHULAM_GATEWAY_URL || 'https://gateway.bahulam.ai').replace(/\/+$/, '').replace(/\/v1$/, '');
16
16
  const RESET = '\x1b[0m';
@@ -22,7 +22,7 @@ const YELLOW = '\x1b[33m';
22
22
 
23
23
  export async function runDeviceCommand(args = []) {
24
24
  const sub = (args[0] || '').toLowerCase();
25
- const auth = new TarangAuth();
25
+ const auth = new BahulamAuth();
26
26
  const creds = auth.loadCredentials();
27
27
  if (!creds.token) {
28
28
  process.stderr.write(`${RED}Not logged in. Run ${BOLD}bahulam login${RESET}${RED} first.${RESET}\n`);
@@ -30,7 +30,7 @@ import * as fs from 'node:fs';
30
30
  import * as os from 'node:os';
31
31
  import { generateKeyPairSync, createPrivateKey, createPublicKey } from 'node:crypto';
32
32
  import { promisify } from 'node:util';
33
- import { TarangAuth } from '../auth/tarang-auth.mjs';
33
+ import { BahulamAuth } from '../auth/bahulam-auth.mjs';
34
34
 
35
35
  const GATEWAY = (process.env.BAHULAM_GATEWAY_URL || 'https://gateway.bahulam.ai').replace(/\/+$/, '').replace(/\/v1$/, '');
36
36
  const RESET = '\x1b[0m';
@@ -42,7 +42,7 @@ const YELLOW = '\x1b[33m';
42
42
  const CYAN = '\x1b[36m';
43
43
 
44
44
  export async function runPairCommand(args = []) {
45
- const auth = new TarangAuth();
45
+ const auth = new BahulamAuth();
46
46
  const creds = auth.loadCredentials();
47
47
  if (!creds.token) {
48
48
  process.stderr.write(`${RED}Not logged in. Run ${BOLD}bahulam login${RESET}${RED} first.${RESET}\n`);
@@ -13,7 +13,7 @@
13
13
  * connection — the daemon must be running. * daemon needs a device_id + keypair to authenticate to the relay.
14
14
  */
15
15
 
16
- import { TarangAuth } from '../auth/tarang-auth.mjs';
16
+ import { BahulamAuth } from '../auth/bahulam-auth.mjs';
17
17
 
18
18
  const RESET = '\x1b[0m';
19
19
  const BOLD = '\x1b[1m';
@@ -26,7 +26,7 @@ const DEFAULT_RELAY = (process.env.BAHULAM_RELAY_URL || 'wss://relay.bahulam.ai'
26
26
 
27
27
  export async function runRemoteCommand(args = []) {
28
28
  const sub = (args[0] || '').toLowerCase();
29
- const auth = new TarangAuth();
29
+ const auth = new BahulamAuth();
30
30
  auth.loadCredentials();
31
31
 
32
32
  if (sub === 'enable') return _enable(auth);
@@ -93,7 +93,7 @@ function _status(auth) {
93
93
  * public_key, peer_pubkeys } — or null if we shouldn't dial.
94
94
  */
95
95
  export function loadRemoteConfig() {
96
- const auth = new TarangAuth();
96
+ const auth = new BahulamAuth();
97
97
  const config = auth.getRawConfig() || auth.loadCredentials() && auth.getRawConfig();
98
98
  if (!config?.remote?.enabled) return null;
99
99
  if (!config?.pairing?.device_id) return null;
@@ -16,8 +16,8 @@
16
16
  */
17
17
 
18
18
  import { resolveBackendUrl } from '../core/backend-url.mjs';
19
- import { TarangAuth } from '../auth/tarang-auth.mjs';
20
- import { TarangStreamClient } from '../core/stream-client.mjs';
19
+ import { BahulamAuth } from '../auth/bahulam-auth.mjs';
20
+ import { BahulamStreamClient } from '../core/stream-client.mjs';
21
21
  import { createToolExecutor } from '../core/tool-executor.mjs';
22
22
  import { ApprovalManager } from '../core/approval.mjs';
23
23
  import { EventFormatter } from '../ui/formatter.mjs';
@@ -87,7 +87,7 @@ function printWorkflowUsage() {
87
87
  // ── Helpers ────────────────────────────────────────────────────
88
88
 
89
89
  function getAuthHeaders() {
90
- const auth = new TarangAuth();
90
+ const auth = new BahulamAuth();
91
91
  const creds = auth.loadCredentials();
92
92
  if (!creds.token) {
93
93
  process.stderr.write(`${RED}✗ Not authenticated. Run `bahulam-code login` first.${RESET}\n`);
@@ -100,7 +100,7 @@ function getAuthHeaders() {
100
100
  }
101
101
 
102
102
  function getBaseUrl() {
103
- const auth = new TarangAuth();
103
+ const auth = new BahulamAuth();
104
104
  return auth.loadCredentials().backendUrl || resolveBackendUrl();
105
105
  }
106
106
 
@@ -540,7 +540,7 @@ async function handleRunMulti(args) {
540
540
  process.exit(1);
541
541
  }
542
542
 
543
- const client = new TarangStreamClient({
543
+ const client = new BahulamStreamClient({
544
544
  baseUrl,
545
545
  token: headers.Authorization.replace(/^Bearer\s+/i, ''),
546
546
  toolExecutor: createToolExecutor(),
@@ -27,8 +27,8 @@ export const ENV_SCHEMA = {
27
27
  CLAUDE_CODE_BRIEF: { type: 'boolean', default: false, description: 'Brief output mode' },
28
28
  CLAUDE_CODE_DISABLE_CRON: { type: 'boolean', default: false, description: 'Disable cron tasks' },
29
29
  CLAUDE_CODE_ENABLE_TASKS: { type: 'boolean', default: false, description: 'Enable task system' },
30
- KEPLER_STAGNATION_DETECTION: { type: 'boolean', default: false, description: 'Detect consecutive identical tool-call loops' },
31
- KEPLER_STAGNATION_THRESHOLD: { type: 'number', default: 3, description: 'Consecutive identical calls before warning' },
30
+ BAHULAM_STAGNATION_DETECTION: { type: 'boolean', default: false, description: 'Detect repeated tool-call or no-op edit loops' },
31
+ BAHULAM_STAGNATION_THRESHOLD: { type: 'number', default: 3, description: 'Consecutive repeated calls before warning' },
32
32
  CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: { type: 'boolean', default: false, description: 'Enable agent teams' },
33
33
  CLAUDE_CODE_DEBUG: { type: 'boolean', default: false, description: 'Debug mode' },
34
34
  CLAUDE_CODE_DISABLE_TELEMETRY: { type: 'boolean', default: false, description: 'Disable telemetry' },
@@ -44,6 +44,13 @@ export const ENV_SCHEMA = {
44
44
  // UI and Display
45
45
  SHOW_THINKING: { type: 'boolean', default: false, description: 'Show thinking blocks' },
46
46
  SHOW_TOOL_RESULTS: { type: 'boolean', default: false, description: 'Show tool results in REPL' },
47
+ BAHULAM_PLAIN: { type: 'boolean', default: false, description: 'Disable ANSI output and fixed terminal UI' },
48
+ BAHULAM_TTY_MODE: { type: 'string', default: 'rich', description: 'Terminal UI mode: rich, stable, transcript, or plain' },
49
+ BAHULAM_FIXED_INPUT: { type: 'boolean', default: true, description: 'Enable the fixed bottom input dock in rich TTY mode' },
50
+ BAHULAM_INPUT_ROWS_MAX: { type: 'number', default: 6, description: 'Maximum input rows reserved by the fixed input dock' },
51
+ BAHULAM_PROMPT_BOTTOM_PADDING: { type: 'number', default: 5, description: 'Blank rows reserved below the prompt when the fixed dock is disabled' },
52
+ BAHULAM_PASTE_FLUSH_MS: { type: 'number', default: 35, description: 'Debounce window for non-bracketed multiline paste input' },
53
+ BAHULAM_BLOCK_SEPARATOR: { type: 'string', default: 'space', description: 'Tool/content separator style: space, dotted, or off' },
47
54
  NO_COLOR: { type: 'boolean', default: false, description: 'Disable colored output' },
48
55
  TERM: { type: 'string', description: 'Terminal type' },
49
56
 
@@ -17,7 +17,7 @@ function readIfExists(filePath, maxChars = 12000) {
17
17
  }
18
18
  }
19
19
 
20
- export function loadKeplerMemory({ cwd = process.cwd() } = {}) {
20
+ export function loadBahulamMemory({ cwd = process.cwd() } = {}) {
21
21
  const files = [];
22
22
  const global = readIfExists(path.join(bahulamHome(), 'KEPLER.md'));
23
23
  if (global) files.push({ source: 'global', ...global });
@@ -118,13 +118,13 @@ function applyEnvOverrides(settings) {
118
118
  if (process.env.CLAUDE_CODE_THINKING === '1') settings.alwaysThinkingEnabled = true;
119
119
  if (process.env.CLAUDE_CODE_DISABLE_CRON === '1') settings.cronEnabled = false;
120
120
  if (process.env.CLAUDE_CODE_ENABLE_TASKS === '1') settings.enableTeams = true;
121
- const stagnationEnabled = process.env.KEPLER_STAGNATION_DETECTION;
121
+ const stagnationEnabled = process.env.BAHULAM_STAGNATION_DETECTION;
122
122
  if (stagnationEnabled !== undefined) {
123
123
  settings.stagnationDetection = ['1', 'true', 'yes', 'on'].includes(
124
124
  stagnationEnabled.toLowerCase(),
125
125
  );
126
126
  }
127
- const stagnationThreshold = process.env.KEPLER_STAGNATION_THRESHOLD;
127
+ const stagnationThreshold = process.env.BAHULAM_STAGNATION_THRESHOLD;
128
128
  if (stagnationThreshold) {
129
129
  const n = parseInt(stagnationThreshold, 10);
130
130
  if (Number.isInteger(n) && n >= 2) settings.stagnationThreshold = n;
@@ -191,6 +191,23 @@ export function createAgentLoop({ model, tools, permissions, settings, hooks })
191
191
  result = await hooks.runPostToolUse(block.name, result);
192
192
  }
193
193
 
194
+ const resultStagnation = stagnation.recordResult(block.name, block.input || {}, result);
195
+ if (resultStagnation.detected) {
196
+ yield {
197
+ type: 'stagnation',
198
+ tool: block.name,
199
+ count: resultStagnation.count,
200
+ kind: resultStagnation.kind,
201
+ target: resultStagnation.target,
202
+ };
203
+ result = {
204
+ ...(typeof result === 'object' && result !== null ? result : {}),
205
+ success: false,
206
+ output: stagnationMessage(block.name, resultStagnation.count, resultStagnation),
207
+ _stagnation: true,
208
+ };
209
+ }
210
+
194
211
  yield { type: 'result', tool: block.name, result };
195
212
 
196
213
  toolResults.push({
@@ -265,7 +265,7 @@ function powershellString(value) {
265
265
  }
266
266
 
267
267
  function ensureClipboardDir(baseDir = os.tmpdir()) {
268
- const dir = path.join(baseDir, 'kepler-clipboard-images');
268
+ const dir = path.join(baseDir, 'bahulam-clipboard-images');
269
269
  fs.mkdirSync(dir, { recursive: true });
270
270
  return dir;
271
271
  }
@@ -8,32 +8,29 @@
8
8
  */
9
9
 
10
10
  const BACKEND_URLS = {
11
- // Four supported environments:
12
- // local — Docker Compose backend on the developer's own machine.
13
- // dev — Bahulam Cloud development (Azure Container Apps, eastus).
14
- // production Bahulam Cloud live (Azure Container Apps, centralus).
15
- // bundled — CLI-local Python runtime spawned as a subprocess. URL is
16
- // overridden by bundled-runtime.mjs at spawn time; sentinel
17
- // value below is only used if the runtime isn't up yet.
11
+ // Supported environments:
12
+ // local — Docker Compose backend on the developer's own machine.
13
+ // production — Bahulam Cloud live (api.bahulam.ai).
14
+ // bundled CLI-local Python runtime spawned as a subprocess. URL is
15
+ // overridden by bundled-runtime.mjs at spawn time; sentinel
16
+ // value below is only used if the runtime isn't up yet.
17
+ //
18
+ // For custom dev/staging URLs, set TARANG_BACKEND_URL directly.
18
19
  local: 'http://127.0.0.1:8150',
19
- dev: 'https://codekepler-backend-dev.kindisland-9034322d.eastus.azurecontainerapps.io',
20
20
  production: 'https://api.bahulam.ai',
21
21
  bundled: 'http://127.0.0.1:0', // sentinel — real URL comes from bundled-runtime.mjs
22
22
  };
23
23
 
24
24
  // Aliases (backwards compat + convenience)
25
25
  BACKEND_URLS.prod = BACKEND_URLS.production;
26
- BACKEND_URLS.treetop = BACKEND_URLS.dev; // legacy alias
27
- BACKEND_URLS.docker = BACKEND_URLS.local; // convenience alias
26
+ BACKEND_URLS.docker = BACKEND_URLS.local;
28
27
 
29
28
  const WEB_URLS = {
30
29
  local: 'http://localhost:3100',
31
- dev: 'https://treetop.bahulam.ai',
32
30
  production: 'https://bahulam.ai',
33
31
  bundled: 'http://localhost:3100', // bundled mode reuses local web if user runs it
34
32
  };
35
33
  WEB_URLS.prod = WEB_URLS.production;
36
- WEB_URLS.treetop = WEB_URLS.dev; // legacy alias
37
34
  WEB_URLS.docker = WEB_URLS.local;
38
35
 
39
36
  /**
@@ -50,6 +47,8 @@ export function resolveWebUrl() {
50
47
 
51
48
  /**
52
49
  * Resolve the backend URL from environment.
50
+ *
51
+ * For dev/staging, set TARANG_BACKEND_URL=<your-url> explicitly.
53
52
  * @returns {string}
54
53
  */
55
54
  export function resolveBackendUrl() {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Callback Client — POST tool results back to Tarang backend.
2
+ * Callback Client — POST tool results back to Bahulam backend.
3
3
  * Implements retry with backoff: 2 retries, 500ms delay.
4
4
  * 4xx: no retry. 5xx/network: retry.
5
5
  */
@@ -11,11 +11,11 @@
11
11
  * bahulam-code --headless --model deepseek/deepseek-chat-v3-0324 "Add tests"
12
12
  */
13
13
 
14
- import { TarangStreamClient } from './stream-client.mjs';
14
+ import { BahulamStreamClient } from './stream-client.mjs';
15
15
  import { createToolExecutor } from './tool-executor.mjs';
16
16
  import { buildWorkScope, promptProjectRoots } from './work-scope.mjs';
17
17
  import { persistProjectArtifacts } from './project-artifacts.mjs';
18
- import { TarangAuth } from '../auth/tarang-auth.mjs';
18
+ import { BahulamAuth } from '../auth/bahulam-auth.mjs';
19
19
  import { ApprovalManager } from './approval.mjs';
20
20
  // daemon wiring — headless (and `bahulam daemonize`) also starts the socket
21
21
  // server + relay bridge when eventlog is enabled. Without this the daemon
@@ -58,7 +58,7 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
58
58
  };
59
59
 
60
60
  // ── Auth ──
61
- const auth = new TarangAuth();
61
+ const auth = new BahulamAuth();
62
62
  const creds = auth.loadCredentials();
63
63
  if (!creds.token) {
64
64
  emit({ type: 'error', error: 'Not logged in. Run: bahulam login' });
@@ -99,7 +99,7 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
99
99
  };
100
100
  log(`Local mode: ${localModel}`);
101
101
  } else {
102
- client = new TarangStreamClient({
102
+ client = new BahulamStreamClient({
103
103
  baseUrl: creds.backendUrl,
104
104
  token: creds.token,
105
105
  toolExecutor,
@@ -17,13 +17,13 @@ import { randomUUID } from 'node:crypto';
17
17
  import * as childProcessModule from 'node:child_process';
18
18
  import { bahulamHome } from './paths.mjs';
19
19
 
20
- const KEPLER_DIR = bahulamHome();
20
+ const BAHULAM_DIR = bahulamHome();
21
21
  const FLUSH_INTERVAL_MS = 500;
22
22
 
23
23
  /**
24
24
  * Sanitize a cwd path into a project slug for the directory name.
25
25
  * /Users/sree/Sites/myproject → -Users-sree-Sites-myproject
26
- * Mirrors the Claude Code / kepler-cli convention.
26
+ * Mirrors the Claude Code / Bahulam CLI convention.
27
27
  */
28
28
  function sanitizePath(p) {
29
29
  return p.replace(/\//g, '-').replace(/^-/, '-');
@@ -67,7 +67,7 @@ export class JsonlWriter {
67
67
  this.version = version;
68
68
  this.sessionId = null; // set by setSessionId() when backend assigns it
69
69
  this.slug = sanitizePath(cwd);
70
- this.projectDir = path.join(KEPLER_DIR, 'projects', this.slug);
70
+ this.projectDir = path.join(BAHULAM_DIR, 'projects', this.slug);
71
71
 
72
72
  // UUID chain for parent linking (cc-lens replay)
73
73
  this.lastUuid = null;
@@ -85,7 +85,7 @@ export class JsonlWriter {
85
85
  this._turnToolResults = []; // [{tool_use_id, content, is_error}, ...]
86
86
  this._turnUsage = null;
87
87
  this._turnModel = null;
88
- this._pendingKeplerEvents = [];
88
+ this._pendingBahulamEvents = [];
89
89
 
90
90
  // Git branch (captured once at construction)
91
91
  this._gitBranch = this._detectGitBranch();
@@ -101,10 +101,10 @@ export class JsonlWriter {
101
101
  this.sessionId = id;
102
102
  this._transcriptPath = path.join(this.projectDir, `${id}.jsonl`);
103
103
  this._ready = true;
104
- if (this._pendingKeplerEvents.length > 0) {
105
- const pending = this._pendingKeplerEvents;
106
- this._pendingKeplerEvents = [];
107
- for (const event of pending) this.writeKeplerEvent(event);
104
+ if (this._pendingBahulamEvents.length > 0) {
105
+ const pending = this._pendingBahulamEvents;
106
+ this._pendingBahulamEvents = [];
107
+ for (const event of pending) this.writeBahulamEvent(event);
108
108
  }
109
109
  // Flush any buffered entries now that we have a path
110
110
  if (this._buffer.length > 0) this._flush();
@@ -146,7 +146,7 @@ export class JsonlWriter {
146
146
  * Write a Bahulam UI/SSE event for exact-ish terminal replay on /resume.
147
147
  * This is a Bahulam extension; cc-lens readers should ignore unknown types.
148
148
  */
149
- writeKeplerEvent(event) {
149
+ writeBahulamEvent(event) {
150
150
  if (!event || !event.type) return;
151
151
  const sanitized = sanitizeEventValue({
152
152
  type: event.type,
@@ -154,12 +154,12 @@ export class JsonlWriter {
154
154
  });
155
155
 
156
156
  if (!this.sessionId) {
157
- this._pendingKeplerEvents.push(sanitized);
157
+ this._pendingBahulamEvents.push(sanitized);
158
158
  return;
159
159
  }
160
160
 
161
161
  const entry = {
162
- type: 'kepler_event',
162
+ type: 'bahulam_event',
163
163
  timestamp: new Date().toISOString(),
164
164
  cwd: this.cwd,
165
165
  sessionId: this.sessionId,
@@ -204,8 +204,8 @@ export class JsonlWriter {
204
204
  content: normalizeToolResultContent(output),
205
205
  is_error: !!isError,
206
206
  };
207
- const kepler = compactToolResultMetadata(metadata);
208
- if (kepler) entry.bahulam = kepler;
207
+ const bahulamMeta = compactToolResultMetadata(metadata);
208
+ if (bahulamMeta) entry.bahulam = bahulamMeta;
209
209
  this._turnToolResults.push(entry);
210
210
  }
211
211
 
@@ -305,7 +305,7 @@ export class JsonlWriter {
305
305
  project: this.cwd,
306
306
  sessionId: this.sessionId,
307
307
  };
308
- const historyPath = path.join(KEPLER_DIR, 'history.jsonl');
308
+ const historyPath = path.join(BAHULAM_DIR, 'history.jsonl');
309
309
  fs.promises.appendFile(historyPath, JSON.stringify(entry) + '\n', { mode: 0o600 })
310
310
  .catch(() => {}); // best effort
311
311
  }
@@ -393,7 +393,7 @@ export class JsonlWriter {
393
393
  fs.mkdirSync(this.projectDir, { recursive: true, mode: 0o700 });
394
394
  } catch { /* ignore */ }
395
395
  try {
396
- fs.mkdirSync(path.join(KEPLER_DIR, 'projects'), { recursive: true, mode: 0o700 });
396
+ fs.mkdirSync(path.join(BAHULAM_DIR, 'projects'), { recursive: true, mode: 0o700 });
397
397
  } catch { /* ignore */ }
398
398
  }
399
399
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Local Agent — T18: Direct LLM API calls, <100ms startup, offline.
3
3
  * Replaces the SSE backend for --local mode.
4
- * Yields events matching the same format as TarangStreamClient.
4
+ * Yields events matching the same format as BahulamStreamClient.
5
5
  */
6
6
 
7
7
  import { ContextRetriever } from '../context/retriever.mjs';
@@ -58,15 +58,16 @@ const TOOL_SCHEMAS = [
58
58
  },
59
59
  {
60
60
  name: 'edit_file',
61
- description: 'Search for a string in a file and replace it. The old_string must match exactly (including whitespace).',
61
+ description: 'Search for a string in a file and replace it. The search string must match exactly, including whitespace.',
62
62
  input_schema: {
63
63
  type: 'object',
64
64
  properties: {
65
65
  file_path: { type: 'string', description: 'Path to the file' },
66
- old_string: { type: 'string', description: 'Exact string to find in the file' },
67
- new_string: { type: 'string', description: 'Replacement string' },
66
+ search: { type: 'string', description: 'Exact string to find in the file' },
67
+ replace: { type: 'string', description: 'Replacement string' },
68
+ replace_all: { type: 'boolean', description: 'Replace every occurrence instead of only the first match' },
68
69
  },
69
- required: ['file_path', 'old_string', 'new_string'],
70
+ required: ['file_path', 'search', 'replace'],
70
71
  },
71
72
  },
72
73
  {
@@ -310,6 +311,26 @@ export class LocalAgent {
310
311
  result = { success: false, output: `Error: ${err.message}` };
311
312
  }
312
313
  const durationMs = Date.now() - toolStart;
314
+ const resultStagnation = stagnation.recordResult(name, input || {}, result);
315
+ if (resultStagnation.detected) {
316
+ const message = stagnationMessage(name, resultStagnation.count, resultStagnation);
317
+ result = {
318
+ ...result,
319
+ success: false,
320
+ output: message,
321
+ _stagnation: true,
322
+ };
323
+ yield {
324
+ type: 'stagnation',
325
+ data: {
326
+ tool: name,
327
+ count: resultStagnation.count,
328
+ message,
329
+ kind: resultStagnation.kind,
330
+ target: resultStagnation.target,
331
+ },
332
+ };
333
+ }
313
334
 
314
335
  yield {
315
336
  type: 'tool_done',
@@ -833,7 +833,7 @@ export function getHistory(n = 50) {
833
833
 
834
834
  export function getStorePaths() {
835
835
  return {
836
- keplerDir: KEPLER_DIR,
836
+ bahulamDir: KEPLER_DIR,
837
837
  projectsDir: PROJECTS_DIR,
838
838
  historyPath: path.join(KEPLER_DIR, 'history.jsonl'),
839
839
  };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Mode Selector
3
3
  *
4
- * remote (default): All requests go to Tarang backend.
4
+ * remote (default): All requests go to Bahulam backend.
5
5
  * Backend handles orchestration, model selection, tool routing.
6
6
  * User's provider and models configured via web Settings page.
7
7
  *
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Output Filter — Smart shell output filtering + auto-lint.
3
- * Ported from tarang-cli (Python) ws/executor.py with enhanced patterns.
3
+ * Ported from tarang-cli (Python, legacy) ws/executor.py with enhanced patterns.
4
4
  */
5
5
 
6
6
  import * as fs from 'node:fs';
@@ -23,9 +23,9 @@ export function persistProjectArtifacts(data, resources, log = () => {}) {
23
23
  for (const [field, filename] of artifacts) {
24
24
  try {
25
25
  // Resolver honors legacy .kepler/ when it's the only dir that exists.
26
- const keplerDir = projectConfigDir(resource.root);
27
- fs.mkdirSync(keplerDir, { recursive: true });
28
- const artifactPath = path.join(keplerDir, filename);
26
+ const bahulamDir = projectConfigDir(resource.root);
27
+ fs.mkdirSync(bahulamDir, { recursive: true });
28
+ const artifactPath = path.join(bahulamDir, filename);
29
29
  fs.writeFileSync(artifactPath, data[field], 'utf-8');
30
30
  resource[field] = data[field];
31
31
  written.push(artifactPath);
@@ -1,7 +1,7 @@
1
1
  import * as crypto from 'node:crypto';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
- import { loadKeplerMemory } from '../config/memory-loader.mjs';
4
+ import { loadBahulamMemory } from '../config/memory-loader.mjs';
5
5
  import { bahulamHome, projectConfigDir } from './paths.mjs';
6
6
 
7
7
  function sha(content) {
@@ -27,8 +27,8 @@ function readFile(filePath, label, maxChars = 12000) {
27
27
  }
28
28
  }
29
29
 
30
- function readTasks(keplerDir) {
31
- const tasksDir = path.join(keplerDir, 'tasks');
30
+ function readTasks(bahulamDir) {
31
+ const tasksDir = path.join(bahulamDir, 'tasks');
32
32
  const files = [];
33
33
  try {
34
34
  if (!fs.existsSync(tasksDir)) return files;
@@ -66,9 +66,9 @@ function scanSkills(dir, scope) {
66
66
  }
67
67
 
68
68
  export function loadProjectContext({ cwd = process.cwd(), previous = null } = {}) {
69
- const keplerDir = projectConfigDir(cwd);
69
+ const bahulamDir = projectConfigDir(cwd);
70
70
  const files = [];
71
- for (const file of loadKeplerMemory({ cwd })) {
71
+ for (const file of loadBahulamMemory({ cwd })) {
72
72
  const label = file.path.endsWith(path.join('.bahulam', 'KEPLER.md'))
73
73
  ? 'KEPLER.md'
74
74
  : path.basename(file.path);
@@ -83,10 +83,10 @@ export function loadProjectContext({ cwd = process.cwd(), previous = null } = {}
83
83
  }
84
84
 
85
85
  for (const name of ['config.json', 'project.md', 'style.md', 'goal.md', 'plan.md', 'hitl.md']) {
86
- const file = readFile(path.join(keplerDir, name), name, name.endsWith('.json') ? 4000 : 12000);
86
+ const file = readFile(path.join(bahulamDir, name), name, name.endsWith('.json') ? 4000 : 12000);
87
87
  if (file) files.push(file);
88
88
  }
89
- files.push(...readTasks(keplerDir));
89
+ files.push(...readTasks(bahulamDir));
90
90
 
91
91
  const previousHashes = new Map((previous?.files || []).map(f => [f.path, f.hash]));
92
92
  const changed = files.filter(f => f.hash && previousHashes.get(f.path) && previousHashes.get(f.path) !== f.hash);
@@ -102,14 +102,14 @@ export function loadProjectContext({ cwd = process.cwd(), previous = null } = {}
102
102
 
103
103
  const skills = [
104
104
  ...scanSkills(bahulamHome(), 'global'),
105
- ...scanSkills(keplerDir, 'project'),
105
+ ...scanSkills(bahulamDir, 'project'),
106
106
  ];
107
107
  const byName = new Map();
108
108
  for (const skill of skills) byName.set(skill.name, skill);
109
109
 
110
110
  return {
111
111
  root: cwd,
112
- kepler_dir: keplerDir,
112
+ kepler_dir: bahulamDir,
113
113
  files,
114
114
  loaded,
115
115
  changed,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Settings Sync — fetch user settings from Tarang web and cache locally.
2
+ * Settings Sync — fetch user settings from Bahulam web and cache locally.
3
3
  *
4
4
  * Syncs: gateway_type, model preferences, configured providers.
5
5
  * Cached in ~/.bahulam/config.json alongside auth token.