@bahulam/code 0.1.6 → 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 (178) 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 +9 -4
  21. package/src/core/tool-executor.mjs +105 -29
  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 +231 -53
  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/registry.mjs +4 -0
  51. package/src/ui/icons.mjs +2 -0
  52. package/src/ui/palette.mjs +1 -1
  53. package/src/ui/slash-commands.mjs +4 -6
  54. package/src/ui/tool-card.mjs +7 -3
  55. package/pulse/app/activity/page.tsx +0 -190
  56. package/pulse/app/api/activity/route.ts +0 -138
  57. package/pulse/app/api/benchmark/route.ts +0 -113
  58. package/pulse/app/api/benchmarks/route.ts +0 -195
  59. package/pulse/app/api/costs/route.ts +0 -88
  60. package/pulse/app/api/export/route.ts +0 -77
  61. package/pulse/app/api/history/route.ts +0 -11
  62. package/pulse/app/api/import/route.ts +0 -31
  63. package/pulse/app/api/memory/route.ts +0 -50
  64. package/pulse/app/api/plans/route.ts +0 -9
  65. package/pulse/app/api/projects/[slug]/route.ts +0 -96
  66. package/pulse/app/api/projects/route.ts +0 -121
  67. package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
  68. package/pulse/app/api/sessions/[id]/route.ts +0 -31
  69. package/pulse/app/api/sessions/route.ts +0 -112
  70. package/pulse/app/api/settings/route.ts +0 -14
  71. package/pulse/app/api/stats/route.ts +0 -143
  72. package/pulse/app/api/todos/route.ts +0 -9
  73. package/pulse/app/api/tools/route.ts +0 -160
  74. package/pulse/app/benchmarks/page.tsx +0 -224
  75. package/pulse/app/costs/page.tsx +0 -179
  76. package/pulse/app/export/page.tsx +0 -465
  77. package/pulse/app/favicon.ico +0 -0
  78. package/pulse/app/globals.css +0 -263
  79. package/pulse/app/help/page.tsx +0 -143
  80. package/pulse/app/history/page.tsx +0 -157
  81. package/pulse/app/layout.tsx +0 -46
  82. package/pulse/app/memory/page.tsx +0 -365
  83. package/pulse/app/overview-client.tsx +0 -393
  84. package/pulse/app/page.tsx +0 -14
  85. package/pulse/app/plans/page.tsx +0 -308
  86. package/pulse/app/projects/[slug]/page.tsx +0 -390
  87. package/pulse/app/projects/page.tsx +0 -110
  88. package/pulse/app/sessions/[id]/page.tsx +0 -243
  89. package/pulse/app/sessions/page.tsx +0 -39
  90. package/pulse/app/settings/page.tsx +0 -188
  91. package/pulse/app/todos/page.tsx +0 -211
  92. package/pulse/app/tools/page.tsx +0 -249
  93. package/pulse/cli.js +0 -164
  94. package/pulse/components/activity/day-of-week-chart.tsx +0 -35
  95. package/pulse/components/activity/streak-card.tsx +0 -36
  96. package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
  97. package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
  98. package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
  99. package/pulse/components/costs/model-token-table.tsx +0 -60
  100. package/pulse/components/global-search.tsx +0 -193
  101. package/pulse/components/keyboard-nav-provider.tsx +0 -23
  102. package/pulse/components/layout/bottom-nav.tsx +0 -53
  103. package/pulse/components/layout/client-layout.tsx +0 -31
  104. package/pulse/components/layout/sidebar-context.tsx +0 -50
  105. package/pulse/components/layout/sidebar.tsx +0 -183
  106. package/pulse/components/layout/top-bar.tsx +0 -121
  107. package/pulse/components/overview/activity-heatmap.tsx +0 -107
  108. package/pulse/components/overview/conversation-table.tsx +0 -148
  109. package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
  110. package/pulse/components/overview/peak-hours-chart.tsx +0 -87
  111. package/pulse/components/overview/project-activity-donut.tsx +0 -96
  112. package/pulse/components/overview/stat-card.tsx +0 -102
  113. package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
  114. package/pulse/components/projects/project-card.tsx +0 -175
  115. package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
  116. package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
  117. package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
  118. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
  119. package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
  120. package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
  121. package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
  122. package/pulse/components/sessions/session-badges.tsx +0 -49
  123. package/pulse/components/sessions/session-table.tsx +0 -299
  124. package/pulse/components/theme-provider.tsx +0 -44
  125. package/pulse/components/tools/feature-adoption-table.tsx +0 -58
  126. package/pulse/components/tools/mcp-server-panel.tsx +0 -45
  127. package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
  128. package/pulse/components/tools/version-history-table.tsx +0 -32
  129. package/pulse/components/ui/alert.tsx +0 -66
  130. package/pulse/components/ui/badge.tsx +0 -48
  131. package/pulse/components/ui/breadcrumb.tsx +0 -109
  132. package/pulse/components/ui/button.tsx +0 -64
  133. package/pulse/components/ui/calendar.tsx +0 -220
  134. package/pulse/components/ui/card.tsx +0 -92
  135. package/pulse/components/ui/command.tsx +0 -158
  136. package/pulse/components/ui/dialog.tsx +0 -158
  137. package/pulse/components/ui/input.tsx +0 -21
  138. package/pulse/components/ui/popover.tsx +0 -89
  139. package/pulse/components/ui/progress.tsx +0 -31
  140. package/pulse/components/ui/select.tsx +0 -190
  141. package/pulse/components/ui/separator.tsx +0 -28
  142. package/pulse/components/ui/sheet.tsx +0 -143
  143. package/pulse/components/ui/skeleton.tsx +0 -13
  144. package/pulse/components/ui/table.tsx +0 -116
  145. package/pulse/components/ui/tabs.tsx +0 -91
  146. package/pulse/components/ui/tooltip.tsx +0 -57
  147. package/pulse/components/use-global-keyboard-nav.ts +0 -79
  148. package/pulse/components.json +0 -23
  149. package/pulse/eslint.config.mjs +0 -18
  150. package/pulse/lib/bahulam-paths.ts +0 -23
  151. package/pulse/lib/claude-reader.ts +0 -592
  152. package/pulse/lib/decode.ts +0 -129
  153. package/pulse/lib/pricing.ts +0 -102
  154. package/pulse/lib/replay-parser.ts +0 -165
  155. package/pulse/lib/tool-categories.ts +0 -140
  156. package/pulse/lib/utils.ts +0 -6
  157. package/pulse/next-env.d.ts +0 -6
  158. package/pulse/next.config.ts +0 -16
  159. package/pulse/package.json +0 -45
  160. package/pulse/postcss.config.mjs +0 -7
  161. package/pulse/public/activity.png +0 -0
  162. package/pulse/public/cc-lens.png +0 -0
  163. package/pulse/public/command-k.png +0 -0
  164. package/pulse/public/costs.png +0 -0
  165. package/pulse/public/dashboard-dark.png +0 -0
  166. package/pulse/public/dashboard-white.png +0 -0
  167. package/pulse/public/export.png +0 -0
  168. package/pulse/public/file.svg +0 -1
  169. package/pulse/public/globe.svg +0 -1
  170. package/pulse/public/next.svg +0 -1
  171. package/pulse/public/projects.png +0 -0
  172. package/pulse/public/session-chat.png +0 -0
  173. package/pulse/public/todos.png +0 -0
  174. package/pulse/public/tools.png +0 -0
  175. package/pulse/public/vercel.svg +0 -1
  176. package/pulse/public/window.svg +0 -1
  177. package/pulse/tsconfig.json +0 -34
  178. 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
+ }