@bahulam/code 0.1.6 → 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 (204) hide show
  1. package/LICENSE +1 -1
  2. package/NOTICE +6 -6
  3. package/README.md +11 -18
  4. package/package.json +11 -11
  5. package/src/agents/loader.mjs +1 -1
  6. package/src/agents/workflow_loader.mjs +2 -2
  7. package/src/auth/{tarang-auth.mjs → bahulam-auth.mjs} +4 -3
  8. package/src/commands/agent.mjs +3 -3
  9. package/src/commands/device.mjs +2 -2
  10. package/src/commands/pair.mjs +2 -2
  11. package/src/commands/remote.mjs +3 -3
  12. package/src/commands/workflow.mjs +5 -5
  13. package/src/config/env.mjs +9 -2
  14. package/src/config/memory-loader.mjs +1 -1
  15. package/src/config/model-catalog-default.json +4 -3
  16. package/src/config/model-catalog.mjs +2 -0
  17. package/src/config/settings.mjs +2 -2
  18. package/src/context/prose-chunker.mjs +162 -5
  19. package/src/core/agent-loop.mjs +17 -0
  20. package/src/core/attachments.mjs +66 -4
  21. package/src/core/backend-url.mjs +11 -12
  22. package/src/core/bundled-runtime.mjs +1 -1
  23. package/src/core/callback-client.mjs +1 -1
  24. package/src/core/headless.mjs +18 -4
  25. package/src/core/jsonl-writer.mjs +18 -18
  26. package/src/core/lint-resolver.mjs +472 -0
  27. package/src/core/local-agent.mjs +40 -8
  28. package/src/core/local-store.mjs +5 -1
  29. package/src/core/mode-selector.mjs +1 -1
  30. package/src/core/output-filter.mjs +22 -26
  31. package/src/core/pricing.mjs +10 -3
  32. package/src/core/project-artifacts.mjs +3 -3
  33. package/src/core/project-context-loader.mjs +9 -9
  34. package/src/core/settings-sync.mjs +1 -1
  35. package/src/core/stagnation.mjs +61 -1
  36. package/src/core/stream-client.mjs +13 -8
  37. package/src/core/system-prompt.mjs +2 -2
  38. package/src/core/tool-executor.mjs +178 -84
  39. package/src/daemon/http-dashboard.mjs +400 -0
  40. package/src/daemon/session-core.mjs +1 -0
  41. package/src/local-service/agent-relay.mjs +868 -0
  42. package/src/local-service/approval-bridge.mjs +222 -0
  43. package/src/local-service/browser.mjs +24 -0
  44. package/src/local-service/command.mjs +155 -0
  45. package/src/local-service/file-access.mjs +393 -0
  46. package/src/local-service/machine.mjs +86 -0
  47. package/src/local-service/paths.mjs +29 -0
  48. package/src/local-service/preview-converters.mjs +260 -0
  49. package/src/local-service/server.mjs +2644 -0
  50. package/src/local-service/session-store.mjs +221 -0
  51. package/src/mcp/client.mjs +4 -4
  52. package/src/onboarding/preflight.mjs +2 -2
  53. package/src/terminal/agents.mjs +2 -2
  54. package/src/terminal/analytics.mjs +2 -6
  55. package/src/terminal/ansi.mjs +11 -3
  56. package/src/terminal/main.mjs +43 -17
  57. package/src/terminal/model-catalog-display.mjs +111 -0
  58. package/src/terminal/repl-format.mjs +4 -1
  59. package/src/terminal/repl-model-form.mjs +81 -14
  60. package/src/terminal/repl-render.mjs +37 -7
  61. package/src/terminal/repl-resume.mjs +2 -2
  62. package/src/terminal/repl-state.mjs +12 -0
  63. package/src/terminal/repl.mjs +248 -70
  64. package/src/terminal/tool-display.mjs +32 -0
  65. package/src/terminal/watch-state.mjs +118 -0
  66. package/src/tools/agent.mjs +2 -2
  67. package/src/tools/analyze-image.mjs +158 -0
  68. package/src/tools/bash.mjs +11 -9
  69. package/src/tools/generate-image.mjs +411 -0
  70. package/src/tools/lint.mjs +24 -43
  71. package/src/tools/project-overview.mjs +12 -12
  72. package/src/tools/registry.mjs +4 -0
  73. package/src/ui/formatter.mjs +2 -2
  74. package/src/ui/icons.mjs +2 -0
  75. package/src/ui/input-dock.mjs +7 -7
  76. package/src/ui/palette.mjs +1 -1
  77. package/src/ui/slash-commands.mjs +4 -6
  78. package/src/ui/spinner.mjs +1 -1
  79. package/src/ui/term.mjs +2 -6
  80. package/src/ui/tool-card.mjs +81 -6
  81. package/pulse/app/activity/page.tsx +0 -190
  82. package/pulse/app/api/activity/route.ts +0 -138
  83. package/pulse/app/api/benchmark/route.ts +0 -113
  84. package/pulse/app/api/benchmarks/route.ts +0 -195
  85. package/pulse/app/api/costs/route.ts +0 -88
  86. package/pulse/app/api/export/route.ts +0 -77
  87. package/pulse/app/api/history/route.ts +0 -11
  88. package/pulse/app/api/import/route.ts +0 -31
  89. package/pulse/app/api/memory/route.ts +0 -50
  90. package/pulse/app/api/plans/route.ts +0 -9
  91. package/pulse/app/api/projects/[slug]/route.ts +0 -96
  92. package/pulse/app/api/projects/route.ts +0 -121
  93. package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
  94. package/pulse/app/api/sessions/[id]/route.ts +0 -31
  95. package/pulse/app/api/sessions/route.ts +0 -112
  96. package/pulse/app/api/settings/route.ts +0 -14
  97. package/pulse/app/api/stats/route.ts +0 -143
  98. package/pulse/app/api/todos/route.ts +0 -9
  99. package/pulse/app/api/tools/route.ts +0 -160
  100. package/pulse/app/benchmarks/page.tsx +0 -224
  101. package/pulse/app/costs/page.tsx +0 -179
  102. package/pulse/app/export/page.tsx +0 -465
  103. package/pulse/app/favicon.ico +0 -0
  104. package/pulse/app/globals.css +0 -263
  105. package/pulse/app/help/page.tsx +0 -143
  106. package/pulse/app/history/page.tsx +0 -157
  107. package/pulse/app/layout.tsx +0 -46
  108. package/pulse/app/memory/page.tsx +0 -365
  109. package/pulse/app/overview-client.tsx +0 -393
  110. package/pulse/app/page.tsx +0 -14
  111. package/pulse/app/plans/page.tsx +0 -308
  112. package/pulse/app/projects/[slug]/page.tsx +0 -390
  113. package/pulse/app/projects/page.tsx +0 -110
  114. package/pulse/app/sessions/[id]/page.tsx +0 -243
  115. package/pulse/app/sessions/page.tsx +0 -39
  116. package/pulse/app/settings/page.tsx +0 -188
  117. package/pulse/app/todos/page.tsx +0 -211
  118. package/pulse/app/tools/page.tsx +0 -249
  119. package/pulse/cli.js +0 -164
  120. package/pulse/components/activity/day-of-week-chart.tsx +0 -35
  121. package/pulse/components/activity/streak-card.tsx +0 -36
  122. package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
  123. package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
  124. package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
  125. package/pulse/components/costs/model-token-table.tsx +0 -60
  126. package/pulse/components/global-search.tsx +0 -193
  127. package/pulse/components/keyboard-nav-provider.tsx +0 -23
  128. package/pulse/components/layout/bottom-nav.tsx +0 -53
  129. package/pulse/components/layout/client-layout.tsx +0 -31
  130. package/pulse/components/layout/sidebar-context.tsx +0 -50
  131. package/pulse/components/layout/sidebar.tsx +0 -183
  132. package/pulse/components/layout/top-bar.tsx +0 -121
  133. package/pulse/components/overview/activity-heatmap.tsx +0 -107
  134. package/pulse/components/overview/conversation-table.tsx +0 -148
  135. package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
  136. package/pulse/components/overview/peak-hours-chart.tsx +0 -87
  137. package/pulse/components/overview/project-activity-donut.tsx +0 -96
  138. package/pulse/components/overview/stat-card.tsx +0 -102
  139. package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
  140. package/pulse/components/projects/project-card.tsx +0 -175
  141. package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
  142. package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
  143. package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
  144. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
  145. package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
  146. package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
  147. package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
  148. package/pulse/components/sessions/session-badges.tsx +0 -49
  149. package/pulse/components/sessions/session-table.tsx +0 -299
  150. package/pulse/components/theme-provider.tsx +0 -44
  151. package/pulse/components/tools/feature-adoption-table.tsx +0 -58
  152. package/pulse/components/tools/mcp-server-panel.tsx +0 -45
  153. package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
  154. package/pulse/components/tools/version-history-table.tsx +0 -32
  155. package/pulse/components/ui/alert.tsx +0 -66
  156. package/pulse/components/ui/badge.tsx +0 -48
  157. package/pulse/components/ui/breadcrumb.tsx +0 -109
  158. package/pulse/components/ui/button.tsx +0 -64
  159. package/pulse/components/ui/calendar.tsx +0 -220
  160. package/pulse/components/ui/card.tsx +0 -92
  161. package/pulse/components/ui/command.tsx +0 -158
  162. package/pulse/components/ui/dialog.tsx +0 -158
  163. package/pulse/components/ui/input.tsx +0 -21
  164. package/pulse/components/ui/popover.tsx +0 -89
  165. package/pulse/components/ui/progress.tsx +0 -31
  166. package/pulse/components/ui/select.tsx +0 -190
  167. package/pulse/components/ui/separator.tsx +0 -28
  168. package/pulse/components/ui/sheet.tsx +0 -143
  169. package/pulse/components/ui/skeleton.tsx +0 -13
  170. package/pulse/components/ui/table.tsx +0 -116
  171. package/pulse/components/ui/tabs.tsx +0 -91
  172. package/pulse/components/ui/tooltip.tsx +0 -57
  173. package/pulse/components/use-global-keyboard-nav.ts +0 -79
  174. package/pulse/components.json +0 -23
  175. package/pulse/eslint.config.mjs +0 -18
  176. package/pulse/lib/bahulam-paths.ts +0 -23
  177. package/pulse/lib/claude-reader.ts +0 -592
  178. package/pulse/lib/decode.ts +0 -129
  179. package/pulse/lib/pricing.ts +0 -102
  180. package/pulse/lib/replay-parser.ts +0 -165
  181. package/pulse/lib/tool-categories.ts +0 -140
  182. package/pulse/lib/utils.ts +0 -6
  183. package/pulse/next-env.d.ts +0 -6
  184. package/pulse/next.config.ts +0 -16
  185. package/pulse/package.json +0 -45
  186. package/pulse/postcss.config.mjs +0 -7
  187. package/pulse/public/activity.png +0 -0
  188. package/pulse/public/cc-lens.png +0 -0
  189. package/pulse/public/command-k.png +0 -0
  190. package/pulse/public/costs.png +0 -0
  191. package/pulse/public/dashboard-dark.png +0 -0
  192. package/pulse/public/dashboard-white.png +0 -0
  193. package/pulse/public/export.png +0 -0
  194. package/pulse/public/file.svg +0 -1
  195. package/pulse/public/globe.svg +0 -1
  196. package/pulse/public/next.svg +0 -1
  197. package/pulse/public/projects.png +0 -0
  198. package/pulse/public/session-chat.png +0 -0
  199. package/pulse/public/todos.png +0 -0
  200. package/pulse/public/tools.png +0 -0
  201. package/pulse/public/vercel.svg +0 -1
  202. package/pulse/public/window.svg +0 -1
  203. package/pulse/tsconfig.json +0 -34
  204. package/pulse/types/claude.ts +0 -294
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Local file preview helpers.
3
+ *
4
+ * These helpers keep formatted preview work inside the local service. Raw
5
+ * bytes remain transport-only; browser panes consume structured rows or cached
6
+ * PDF assets.
7
+ */
8
+
9
+ import * as crypto from 'node:crypto';
10
+ import * as fs from 'node:fs';
11
+ import * as path from 'node:path';
12
+ import { spawnSync } from 'node:child_process';
13
+ import * as XLSXModule from 'xlsx';
14
+ import { resolveWorkspacePath } from './file-access.mjs';
15
+ import { localServiceRoot } from './paths.mjs';
16
+
17
+ const XLSX = XLSXModule.default || XLSXModule;
18
+ const MAX_SPREADSHEET_BYTES = 50 * 1024 * 1024;
19
+ const MAX_OFFICE_BYTES = 100 * 1024 * 1024;
20
+ const DEFAULT_MAX_SHEETS = 8;
21
+ const DEFAULT_MAX_ROWS = 200;
22
+ const DEFAULT_MAX_COLS = 60;
23
+
24
+ export function createSpreadsheetPreview(session, requestedPath, {
25
+ maxSheets = DEFAULT_MAX_SHEETS,
26
+ maxRows = DEFAULT_MAX_ROWS,
27
+ maxCols = DEFAULT_MAX_COLS,
28
+ } = {}) {
29
+ const target = resolveWorkspacePath(session, requestedPath);
30
+ const stat = assertPreviewableFile(target, MAX_SPREADSHEET_BYTES);
31
+ const workbook = XLSX.readFile(target, {
32
+ cellFormula: true,
33
+ cellNF: false,
34
+ cellHTML: false,
35
+ cellText: true,
36
+ });
37
+
38
+ const root = fs.realpathSync(session.root_path);
39
+ const sheetNames = workbook.SheetNames.slice(0, maxSheets);
40
+ const sheets = sheetNames.map((name) => {
41
+ const sheet = workbook.Sheets[name];
42
+ const ref = sheet?.['!ref'] || 'A1:A1';
43
+ const range = XLSX.utils.decode_range(ref);
44
+ const rowCount = Math.max(0, range.e.r - range.s.r + 1);
45
+ const columnCount = Math.max(0, range.e.c - range.s.c + 1);
46
+ const rowLimit = Math.min(rowCount, maxRows);
47
+ const columnLimit = Math.min(columnCount, maxCols);
48
+ const columns = Array.from({ length: columnLimit }, (_, index) => (
49
+ XLSX.utils.encode_col(range.s.c + index)
50
+ ));
51
+ const rows = [];
52
+
53
+ for (let r = 0; r < rowLimit; r += 1) {
54
+ const cells = [];
55
+ for (let c = 0; c < columnLimit; c += 1) {
56
+ const address = XLSX.utils.encode_cell({ r: range.s.r + r, c: range.s.c + c });
57
+ cells.push(formatSpreadsheetCell(sheet?.[address]));
58
+ }
59
+ rows.push({ number: range.s.r + r + 1, cells });
60
+ }
61
+
62
+ return {
63
+ name,
64
+ ref,
65
+ row_count: rowCount,
66
+ column_count: columnCount,
67
+ truncated_rows: rowCount > rowLimit,
68
+ truncated_columns: columnCount > columnLimit,
69
+ columns,
70
+ rows,
71
+ };
72
+ });
73
+
74
+ return {
75
+ ok: true,
76
+ type: 'spreadsheet',
77
+ path: relativeToRoot(root, target),
78
+ size: stat.size,
79
+ sheet_count: workbook.SheetNames.length,
80
+ truncated_sheets: workbook.SheetNames.length > sheetNames.length,
81
+ sheets,
82
+ };
83
+ }
84
+
85
+ export function createOfficePdfPreview(session, requestedPath) {
86
+ const target = resolveWorkspacePath(session, requestedPath);
87
+ const stat = assertPreviewableFile(target, MAX_OFFICE_BYTES);
88
+ const libreOffice = findLibreOffice();
89
+ if (!libreOffice) {
90
+ return {
91
+ ok: false,
92
+ code: 'libreoffice_missing',
93
+ message: 'LibreOffice was not found on this machine.',
94
+ install_hint: 'Install LibreOffice or set BAHULAM_LIBREOFFICE_PATH to the soffice executable.',
95
+ };
96
+ }
97
+
98
+ const root = fs.realpathSync(session.root_path);
99
+ const cacheId = previewCacheId(target, stat);
100
+ const cacheRoot = previewCacheRoot(session.id);
101
+ const outDir = path.join(cacheRoot, cacheId);
102
+ fs.mkdirSync(outDir, { recursive: true, mode: 0o700 });
103
+
104
+ const expectedName = `${path.basename(target, path.extname(target))}.pdf`;
105
+ const expectedPath = path.join(outDir, expectedName);
106
+ if (!fs.existsSync(expectedPath)) {
107
+ const profileDir = path.join(outDir, 'lo-profile');
108
+ fs.mkdirSync(profileDir, { recursive: true, mode: 0o700 });
109
+ const args = [
110
+ '--headless',
111
+ '--nologo',
112
+ '--nodefault',
113
+ '--nofirststartwizard',
114
+ '--nolockcheck',
115
+ '--norestore',
116
+ `-env:UserInstallation=${fileUrl(profileDir)}`,
117
+ '--convert-to',
118
+ 'pdf',
119
+ '--outdir',
120
+ outDir,
121
+ target,
122
+ ];
123
+ const result = spawnSync(libreOffice, args, {
124
+ encoding: 'utf-8',
125
+ timeout: 90_000,
126
+ windowsHide: true,
127
+ });
128
+ if (result.error || result.status !== 0) {
129
+ return {
130
+ ok: false,
131
+ code: 'office_conversion_failed',
132
+ message: result.error?.message || result.stderr || result.stdout || 'LibreOffice conversion failed.',
133
+ };
134
+ }
135
+ }
136
+
137
+ const pdfPath = fs.existsSync(expectedPath) ? expectedPath : findNewestPdf(outDir);
138
+ if (!pdfPath) {
139
+ return {
140
+ ok: false,
141
+ code: 'office_conversion_missing_output',
142
+ message: 'LibreOffice completed but no PDF output was produced.',
143
+ };
144
+ }
145
+
146
+ return {
147
+ ok: true,
148
+ type: 'office_pdf',
149
+ path: relativeToRoot(root, target),
150
+ cache_id: cacheId,
151
+ file_name: path.basename(pdfPath),
152
+ size: fs.statSync(pdfPath).size,
153
+ };
154
+ }
155
+
156
+ export function resolvePreviewCacheFile(session, cachePath) {
157
+ const root = previewCacheRoot(session.id);
158
+ const rel = String(cachePath || '').replace(/^[/\\]+/, '');
159
+ const target = path.resolve(root, rel);
160
+ const rootPath = path.resolve(root);
161
+ const relative = path.relative(rootPath, target);
162
+ if (!rel || relative.startsWith('..') || path.isAbsolute(relative)) {
163
+ const err = new Error('Preview cache path is outside the local preview cache');
164
+ err.code = 'OUTSIDE_WORKSPACE';
165
+ throw err;
166
+ }
167
+
168
+ const stat = fs.statSync(target);
169
+ if (!stat.isFile()) {
170
+ const err = new Error('Preview cache path is not a file');
171
+ err.code = 'NOT_FILE';
172
+ throw err;
173
+ }
174
+ return { path: target, stat };
175
+ }
176
+
177
+ function assertPreviewableFile(target, maxBytes) {
178
+ const stat = fs.statSync(target);
179
+ if (!stat.isFile()) {
180
+ const err = new Error('Requested path is not a file');
181
+ err.code = 'NOT_FILE';
182
+ throw err;
183
+ }
184
+ if (stat.size > maxBytes) {
185
+ const err = new Error(`File is larger than ${maxBytes} bytes`);
186
+ err.code = 'FILE_TOO_LARGE';
187
+ throw err;
188
+ }
189
+ return stat;
190
+ }
191
+
192
+ function formatSpreadsheetCell(cell) {
193
+ if (!cell) return { value: '', formula: null };
194
+ const value = cell.w != null ? cell.w : cell.v != null ? String(cell.v) : '';
195
+ return {
196
+ value: String(value),
197
+ formula: cell.f ? `=${cell.f}` : null,
198
+ };
199
+ }
200
+
201
+ function findLibreOffice() {
202
+ const configured = process.env.BAHULAM_LIBREOFFICE_PATH || process.env.LIBREOFFICE_PATH;
203
+ const candidates = configured
204
+ ? [configured]
205
+ : [
206
+ '/Applications/LibreOffice.app/Contents/MacOS/soffice',
207
+ '/opt/homebrew/bin/soffice',
208
+ '/usr/local/bin/soffice',
209
+ '/usr/bin/libreoffice',
210
+ 'soffice',
211
+ 'libreoffice',
212
+ ];
213
+
214
+ for (const candidate of candidates.filter(Boolean)) {
215
+ const result = spawnSync(candidate, ['--version'], {
216
+ encoding: 'utf-8',
217
+ timeout: 5_000,
218
+ windowsHide: true,
219
+ });
220
+ if (!result.error && result.status === 0) return candidate;
221
+ }
222
+ return null;
223
+ }
224
+
225
+ function previewCacheRoot(sessionId) {
226
+ const safeSessionId = String(sessionId || 'session').replace(/[^a-zA-Z0-9_.-]/g, '_');
227
+ const root = path.join(localServiceRoot(), 'previews', safeSessionId);
228
+ fs.mkdirSync(root, { recursive: true, mode: 0o700 });
229
+ return root;
230
+ }
231
+
232
+ function previewCacheId(target, stat) {
233
+ return crypto
234
+ .createHash('sha256')
235
+ .update(`${fs.realpathSync(target)}\0${stat.size}\0${stat.mtimeMs}`)
236
+ .digest('hex')
237
+ .slice(0, 24);
238
+ }
239
+
240
+ function findNewestPdf(dir) {
241
+ try {
242
+ return fs.readdirSync(dir)
243
+ .filter((name) => name.toLowerCase().endsWith('.pdf'))
244
+ .map((name) => path.join(dir, name))
245
+ .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0] || null;
246
+ } catch {
247
+ return null;
248
+ }
249
+ }
250
+
251
+ function relativeToRoot(root, target) {
252
+ const rel = path.relative(root, target);
253
+ return rel ? rel.split(path.sep).join('/') : '.';
254
+ }
255
+
256
+ function fileUrl(filePath) {
257
+ const resolved = path.resolve(filePath);
258
+ const parts = resolved.split(path.sep).map(encodeURIComponent);
259
+ return `file://${resolved.startsWith(path.sep) ? '' : '/'}${parts.join('/')}`;
260
+ }