@bahulam/code 0.1.5 → 0.1.7

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 (179) hide show
  1. package/LICENSE +1 -1
  2. package/NOTICE +6 -6
  3. package/README.md +10 -17
  4. package/package.json +9 -9
  5. package/src/agents/loader.mjs +1 -1
  6. package/src/agents/workflow_loader.mjs +2 -2
  7. package/src/auth/tarang-auth.mjs +3 -2
  8. package/src/config/model-catalog-default.json +4 -3
  9. package/src/config/model-catalog.mjs +2 -0
  10. package/src/context/prose-chunker.mjs +162 -5
  11. package/src/core/attachments.mjs +65 -3
  12. package/src/core/bundled-runtime.mjs +1 -1
  13. package/src/core/headless.mjs +14 -0
  14. package/src/core/jsonl-writer.mjs +3 -3
  15. package/src/core/lint-resolver.mjs +472 -0
  16. package/src/core/local-agent.mjs +15 -4
  17. package/src/core/local-store.mjs +4 -0
  18. package/src/core/output-filter.mjs +21 -25
  19. package/src/core/pricing.mjs +10 -3
  20. package/src/core/stream-client.mjs +112 -16
  21. package/src/core/tool-executor.mjs +123 -31
  22. package/src/daemon/http-dashboard.mjs +400 -0
  23. package/src/daemon/session-core.mjs +1 -0
  24. package/src/local-service/agent-relay.mjs +868 -0
  25. package/src/local-service/approval-bridge.mjs +222 -0
  26. package/src/local-service/browser.mjs +24 -0
  27. package/src/local-service/command.mjs +155 -0
  28. package/src/local-service/file-access.mjs +393 -0
  29. package/src/local-service/machine.mjs +86 -0
  30. package/src/local-service/paths.mjs +29 -0
  31. package/src/local-service/preview-converters.mjs +260 -0
  32. package/src/local-service/server.mjs +2644 -0
  33. package/src/local-service/session-store.mjs +221 -0
  34. package/src/onboarding/preflight.mjs +1 -1
  35. package/src/terminal/analytics.mjs +2 -6
  36. package/src/terminal/ansi.mjs +11 -3
  37. package/src/terminal/main.mjs +37 -11
  38. package/src/terminal/model-catalog-display.mjs +111 -0
  39. package/src/terminal/repl-format.mjs +4 -1
  40. package/src/terminal/repl-model-form.mjs +81 -14
  41. package/src/terminal/repl-render.mjs +35 -5
  42. package/src/terminal/repl-state.mjs +12 -0
  43. package/src/terminal/repl.mjs +255 -55
  44. package/src/terminal/tool-display.mjs +32 -0
  45. package/src/terminal/watch-state.mjs +118 -0
  46. package/src/tools/analyze-image.mjs +158 -0
  47. package/src/tools/bash.mjs +11 -9
  48. package/src/tools/generate-image.mjs +411 -0
  49. package/src/tools/lint.mjs +24 -43
  50. package/src/tools/project-overview.mjs +5 -2
  51. package/src/tools/registry.mjs +4 -0
  52. package/src/ui/icons.mjs +2 -0
  53. package/src/ui/palette.mjs +1 -1
  54. package/src/ui/slash-commands.mjs +4 -6
  55. package/src/ui/tool-card.mjs +7 -3
  56. package/pulse/app/activity/page.tsx +0 -190
  57. package/pulse/app/api/activity/route.ts +0 -138
  58. package/pulse/app/api/benchmark/route.ts +0 -113
  59. package/pulse/app/api/benchmarks/route.ts +0 -195
  60. package/pulse/app/api/costs/route.ts +0 -88
  61. package/pulse/app/api/export/route.ts +0 -77
  62. package/pulse/app/api/history/route.ts +0 -11
  63. package/pulse/app/api/import/route.ts +0 -31
  64. package/pulse/app/api/memory/route.ts +0 -50
  65. package/pulse/app/api/plans/route.ts +0 -9
  66. package/pulse/app/api/projects/[slug]/route.ts +0 -96
  67. package/pulse/app/api/projects/route.ts +0 -121
  68. package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
  69. package/pulse/app/api/sessions/[id]/route.ts +0 -31
  70. package/pulse/app/api/sessions/route.ts +0 -112
  71. package/pulse/app/api/settings/route.ts +0 -14
  72. package/pulse/app/api/stats/route.ts +0 -143
  73. package/pulse/app/api/todos/route.ts +0 -9
  74. package/pulse/app/api/tools/route.ts +0 -160
  75. package/pulse/app/benchmarks/page.tsx +0 -224
  76. package/pulse/app/costs/page.tsx +0 -179
  77. package/pulse/app/export/page.tsx +0 -465
  78. package/pulse/app/favicon.ico +0 -0
  79. package/pulse/app/globals.css +0 -263
  80. package/pulse/app/help/page.tsx +0 -143
  81. package/pulse/app/history/page.tsx +0 -157
  82. package/pulse/app/layout.tsx +0 -46
  83. package/pulse/app/memory/page.tsx +0 -365
  84. package/pulse/app/overview-client.tsx +0 -393
  85. package/pulse/app/page.tsx +0 -14
  86. package/pulse/app/plans/page.tsx +0 -308
  87. package/pulse/app/projects/[slug]/page.tsx +0 -390
  88. package/pulse/app/projects/page.tsx +0 -110
  89. package/pulse/app/sessions/[id]/page.tsx +0 -243
  90. package/pulse/app/sessions/page.tsx +0 -39
  91. package/pulse/app/settings/page.tsx +0 -188
  92. package/pulse/app/todos/page.tsx +0 -211
  93. package/pulse/app/tools/page.tsx +0 -249
  94. package/pulse/cli.js +0 -164
  95. package/pulse/components/activity/day-of-week-chart.tsx +0 -35
  96. package/pulse/components/activity/streak-card.tsx +0 -36
  97. package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
  98. package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
  99. package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
  100. package/pulse/components/costs/model-token-table.tsx +0 -60
  101. package/pulse/components/global-search.tsx +0 -193
  102. package/pulse/components/keyboard-nav-provider.tsx +0 -23
  103. package/pulse/components/layout/bottom-nav.tsx +0 -53
  104. package/pulse/components/layout/client-layout.tsx +0 -31
  105. package/pulse/components/layout/sidebar-context.tsx +0 -50
  106. package/pulse/components/layout/sidebar.tsx +0 -183
  107. package/pulse/components/layout/top-bar.tsx +0 -121
  108. package/pulse/components/overview/activity-heatmap.tsx +0 -107
  109. package/pulse/components/overview/conversation-table.tsx +0 -148
  110. package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
  111. package/pulse/components/overview/peak-hours-chart.tsx +0 -87
  112. package/pulse/components/overview/project-activity-donut.tsx +0 -96
  113. package/pulse/components/overview/stat-card.tsx +0 -102
  114. package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
  115. package/pulse/components/projects/project-card.tsx +0 -175
  116. package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
  117. package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
  118. package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
  119. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
  120. package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
  121. package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
  122. package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
  123. package/pulse/components/sessions/session-badges.tsx +0 -49
  124. package/pulse/components/sessions/session-table.tsx +0 -299
  125. package/pulse/components/theme-provider.tsx +0 -44
  126. package/pulse/components/tools/feature-adoption-table.tsx +0 -58
  127. package/pulse/components/tools/mcp-server-panel.tsx +0 -45
  128. package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
  129. package/pulse/components/tools/version-history-table.tsx +0 -32
  130. package/pulse/components/ui/alert.tsx +0 -66
  131. package/pulse/components/ui/badge.tsx +0 -48
  132. package/pulse/components/ui/breadcrumb.tsx +0 -109
  133. package/pulse/components/ui/button.tsx +0 -64
  134. package/pulse/components/ui/calendar.tsx +0 -220
  135. package/pulse/components/ui/card.tsx +0 -92
  136. package/pulse/components/ui/command.tsx +0 -158
  137. package/pulse/components/ui/dialog.tsx +0 -158
  138. package/pulse/components/ui/input.tsx +0 -21
  139. package/pulse/components/ui/popover.tsx +0 -89
  140. package/pulse/components/ui/progress.tsx +0 -31
  141. package/pulse/components/ui/select.tsx +0 -190
  142. package/pulse/components/ui/separator.tsx +0 -28
  143. package/pulse/components/ui/sheet.tsx +0 -143
  144. package/pulse/components/ui/skeleton.tsx +0 -13
  145. package/pulse/components/ui/table.tsx +0 -116
  146. package/pulse/components/ui/tabs.tsx +0 -91
  147. package/pulse/components/ui/tooltip.tsx +0 -57
  148. package/pulse/components/use-global-keyboard-nav.ts +0 -79
  149. package/pulse/components.json +0 -23
  150. package/pulse/eslint.config.mjs +0 -18
  151. package/pulse/lib/bahulam-paths.ts +0 -23
  152. package/pulse/lib/claude-reader.ts +0 -592
  153. package/pulse/lib/decode.ts +0 -129
  154. package/pulse/lib/pricing.ts +0 -102
  155. package/pulse/lib/replay-parser.ts +0 -165
  156. package/pulse/lib/tool-categories.ts +0 -140
  157. package/pulse/lib/utils.ts +0 -6
  158. package/pulse/next-env.d.ts +0 -6
  159. package/pulse/next.config.ts +0 -16
  160. package/pulse/package.json +0 -45
  161. package/pulse/postcss.config.mjs +0 -7
  162. package/pulse/public/activity.png +0 -0
  163. package/pulse/public/cc-lens.png +0 -0
  164. package/pulse/public/command-k.png +0 -0
  165. package/pulse/public/costs.png +0 -0
  166. package/pulse/public/dashboard-dark.png +0 -0
  167. package/pulse/public/dashboard-white.png +0 -0
  168. package/pulse/public/export.png +0 -0
  169. package/pulse/public/file.svg +0 -1
  170. package/pulse/public/globe.svg +0 -1
  171. package/pulse/public/next.svg +0 -1
  172. package/pulse/public/projects.png +0 -0
  173. package/pulse/public/session-chat.png +0 -0
  174. package/pulse/public/todos.png +0 -0
  175. package/pulse/public/tools.png +0 -0
  176. package/pulse/public/vercel.svg +0 -1
  177. package/pulse/public/window.svg +0 -1
  178. package/pulse/tsconfig.json +0 -34
  179. 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
+ }