@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.
- package/LICENSE +1 -1
- package/NOTICE +6 -6
- package/README.md +10 -17
- package/package.json +9 -9
- package/src/agents/loader.mjs +1 -1
- package/src/agents/workflow_loader.mjs +2 -2
- package/src/auth/tarang-auth.mjs +3 -2
- package/src/config/model-catalog-default.json +4 -3
- package/src/config/model-catalog.mjs +2 -0
- package/src/context/prose-chunker.mjs +162 -5
- package/src/core/attachments.mjs +65 -3
- package/src/core/bundled-runtime.mjs +1 -1
- package/src/core/headless.mjs +14 -0
- package/src/core/jsonl-writer.mjs +3 -3
- package/src/core/lint-resolver.mjs +472 -0
- package/src/core/local-agent.mjs +15 -4
- package/src/core/local-store.mjs +4 -0
- package/src/core/output-filter.mjs +21 -25
- package/src/core/pricing.mjs +10 -3
- package/src/core/stream-client.mjs +9 -4
- package/src/core/tool-executor.mjs +105 -29
- package/src/daemon/http-dashboard.mjs +400 -0
- package/src/daemon/session-core.mjs +1 -0
- package/src/local-service/agent-relay.mjs +868 -0
- package/src/local-service/approval-bridge.mjs +222 -0
- package/src/local-service/browser.mjs +24 -0
- package/src/local-service/command.mjs +155 -0
- package/src/local-service/file-access.mjs +393 -0
- package/src/local-service/machine.mjs +86 -0
- package/src/local-service/paths.mjs +29 -0
- package/src/local-service/preview-converters.mjs +260 -0
- package/src/local-service/server.mjs +2644 -0
- package/src/local-service/session-store.mjs +221 -0
- package/src/onboarding/preflight.mjs +1 -1
- package/src/terminal/analytics.mjs +2 -6
- package/src/terminal/ansi.mjs +11 -3
- package/src/terminal/main.mjs +37 -11
- package/src/terminal/model-catalog-display.mjs +111 -0
- package/src/terminal/repl-format.mjs +4 -1
- package/src/terminal/repl-model-form.mjs +81 -14
- package/src/terminal/repl-render.mjs +35 -5
- package/src/terminal/repl-state.mjs +12 -0
- package/src/terminal/repl.mjs +231 -53
- package/src/terminal/tool-display.mjs +32 -0
- package/src/terminal/watch-state.mjs +118 -0
- package/src/tools/analyze-image.mjs +158 -0
- package/src/tools/bash.mjs +11 -9
- package/src/tools/generate-image.mjs +411 -0
- package/src/tools/lint.mjs +24 -43
- package/src/tools/registry.mjs +4 -0
- package/src/ui/icons.mjs +2 -0
- package/src/ui/palette.mjs +1 -1
- package/src/ui/slash-commands.mjs +4 -6
- package/src/ui/tool-card.mjs +7 -3
- package/pulse/app/activity/page.tsx +0 -190
- package/pulse/app/api/activity/route.ts +0 -138
- package/pulse/app/api/benchmark/route.ts +0 -113
- package/pulse/app/api/benchmarks/route.ts +0 -195
- package/pulse/app/api/costs/route.ts +0 -88
- package/pulse/app/api/export/route.ts +0 -77
- package/pulse/app/api/history/route.ts +0 -11
- package/pulse/app/api/import/route.ts +0 -31
- package/pulse/app/api/memory/route.ts +0 -50
- package/pulse/app/api/plans/route.ts +0 -9
- package/pulse/app/api/projects/[slug]/route.ts +0 -96
- package/pulse/app/api/projects/route.ts +0 -121
- package/pulse/app/api/sessions/[id]/replay/route.ts +0 -20
- package/pulse/app/api/sessions/[id]/route.ts +0 -31
- package/pulse/app/api/sessions/route.ts +0 -112
- package/pulse/app/api/settings/route.ts +0 -14
- package/pulse/app/api/stats/route.ts +0 -143
- package/pulse/app/api/todos/route.ts +0 -9
- package/pulse/app/api/tools/route.ts +0 -160
- package/pulse/app/benchmarks/page.tsx +0 -224
- package/pulse/app/costs/page.tsx +0 -179
- package/pulse/app/export/page.tsx +0 -465
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +0 -263
- package/pulse/app/help/page.tsx +0 -143
- package/pulse/app/history/page.tsx +0 -157
- package/pulse/app/layout.tsx +0 -46
- package/pulse/app/memory/page.tsx +0 -365
- package/pulse/app/overview-client.tsx +0 -393
- package/pulse/app/page.tsx +0 -14
- package/pulse/app/plans/page.tsx +0 -308
- package/pulse/app/projects/[slug]/page.tsx +0 -390
- package/pulse/app/projects/page.tsx +0 -110
- package/pulse/app/sessions/[id]/page.tsx +0 -243
- package/pulse/app/sessions/page.tsx +0 -39
- package/pulse/app/settings/page.tsx +0 -188
- package/pulse/app/todos/page.tsx +0 -211
- package/pulse/app/tools/page.tsx +0 -249
- package/pulse/cli.js +0 -164
- package/pulse/components/activity/day-of-week-chart.tsx +0 -35
- package/pulse/components/activity/streak-card.tsx +0 -36
- package/pulse/components/costs/cache-efficiency-panel.tsx +0 -76
- package/pulse/components/costs/cost-by-project-chart.tsx +0 -48
- package/pulse/components/costs/cost-over-time-chart.tsx +0 -95
- package/pulse/components/costs/model-token-table.tsx +0 -60
- package/pulse/components/global-search.tsx +0 -193
- package/pulse/components/keyboard-nav-provider.tsx +0 -23
- package/pulse/components/layout/bottom-nav.tsx +0 -53
- package/pulse/components/layout/client-layout.tsx +0 -31
- package/pulse/components/layout/sidebar-context.tsx +0 -50
- package/pulse/components/layout/sidebar.tsx +0 -183
- package/pulse/components/layout/top-bar.tsx +0 -121
- package/pulse/components/overview/activity-heatmap.tsx +0 -107
- package/pulse/components/overview/conversation-table.tsx +0 -148
- package/pulse/components/overview/model-breakdown-donut.tsx +0 -95
- package/pulse/components/overview/peak-hours-chart.tsx +0 -87
- package/pulse/components/overview/project-activity-donut.tsx +0 -96
- package/pulse/components/overview/stat-card.tsx +0 -102
- package/pulse/components/overview/usage-over-time-chart.tsx +0 -166
- package/pulse/components/projects/project-card.tsx +0 -175
- package/pulse/components/sessions/replay/assistant-markdown.tsx +0 -94
- package/pulse/components/sessions/replay/compaction-card.tsx +0 -25
- package/pulse/components/sessions/replay/session-sidebar.tsx +0 -231
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +0 -98
- package/pulse/components/sessions/replay/tool-call-badge.tsx +0 -127
- package/pulse/components/sessions/replay/turn-cards.tsx +0 -220
- package/pulse/components/sessions/replay/user-tool-result.tsx +0 -158
- package/pulse/components/sessions/session-badges.tsx +0 -49
- package/pulse/components/sessions/session-table.tsx +0 -299
- package/pulse/components/theme-provider.tsx +0 -44
- package/pulse/components/tools/feature-adoption-table.tsx +0 -58
- package/pulse/components/tools/mcp-server-panel.tsx +0 -45
- package/pulse/components/tools/tool-ranking-chart.tsx +0 -57
- package/pulse/components/tools/version-history-table.tsx +0 -32
- package/pulse/components/ui/alert.tsx +0 -66
- package/pulse/components/ui/badge.tsx +0 -48
- package/pulse/components/ui/breadcrumb.tsx +0 -109
- package/pulse/components/ui/button.tsx +0 -64
- package/pulse/components/ui/calendar.tsx +0 -220
- package/pulse/components/ui/card.tsx +0 -92
- package/pulse/components/ui/command.tsx +0 -158
- package/pulse/components/ui/dialog.tsx +0 -158
- package/pulse/components/ui/input.tsx +0 -21
- package/pulse/components/ui/popover.tsx +0 -89
- package/pulse/components/ui/progress.tsx +0 -31
- package/pulse/components/ui/select.tsx +0 -190
- package/pulse/components/ui/separator.tsx +0 -28
- package/pulse/components/ui/sheet.tsx +0 -143
- package/pulse/components/ui/skeleton.tsx +0 -13
- package/pulse/components/ui/table.tsx +0 -116
- package/pulse/components/ui/tabs.tsx +0 -91
- package/pulse/components/ui/tooltip.tsx +0 -57
- package/pulse/components/use-global-keyboard-nav.ts +0 -79
- package/pulse/components.json +0 -23
- package/pulse/eslint.config.mjs +0 -18
- package/pulse/lib/bahulam-paths.ts +0 -23
- package/pulse/lib/claude-reader.ts +0 -592
- package/pulse/lib/decode.ts +0 -129
- package/pulse/lib/pricing.ts +0 -102
- package/pulse/lib/replay-parser.ts +0 -165
- package/pulse/lib/tool-categories.ts +0 -140
- package/pulse/lib/utils.ts +0 -6
- package/pulse/next-env.d.ts +0 -6
- package/pulse/next.config.ts +0 -16
- package/pulse/package.json +0 -45
- package/pulse/postcss.config.mjs +0 -7
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +0 -1
- package/pulse/public/globe.svg +0 -1
- package/pulse/public/next.svg +0 -1
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +0 -1
- package/pulse/public/window.svg +0 -1
- package/pulse/tsconfig.json +0 -34
- package/pulse/types/claude.ts +0 -294
package/LICENSE
CHANGED
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright 2026
|
|
189
|
+
Copyright 2026 Bahulam
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
package/NOTICE
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Bahulam Code
|
|
2
|
-
Copyright 2026
|
|
2
|
+
Copyright 2026 Bahulam
|
|
3
3
|
|
|
4
|
-
This product includes software developed by
|
|
5
|
-
(https://
|
|
4
|
+
This product includes software developed by Bahulam
|
|
5
|
+
(https://bahulam.ai).
|
|
6
6
|
|
|
7
7
|
------------------------------------------------------------------------
|
|
8
8
|
Upstream and third-party attribution
|
|
@@ -11,12 +11,12 @@ Upstream and third-party attribution
|
|
|
11
11
|
The CLI runtime derives, in part, from the open-source open-claude-code
|
|
12
12
|
project. Original work bears its own copyright and license terms; those
|
|
13
13
|
files remain governed by their upstream licenses, and any modifications
|
|
14
|
-
to them by
|
|
14
|
+
to them by Bahulam are additionally licensed under Apache License,
|
|
15
15
|
Version 2.0 as declared in the LICENSE file at the root of this project.
|
|
16
16
|
|
|
17
17
|
The Bahulam runtime bundle (@bahulam/runtime-<platform>-<arch>) ships
|
|
18
18
|
with a PyArmor-obfuscated Python payload that is proprietary to
|
|
19
|
-
|
|
19
|
+
Bahulam and is NOT covered by the Apache License. It is distributed
|
|
20
20
|
under a separate end-user license embedded in the runtime as a signed
|
|
21
21
|
license JWT. Use of the runtime requires accepting that license.
|
|
22
22
|
|
|
@@ -30,7 +30,7 @@ Trademarks
|
|
|
30
30
|
------------------------------------------------------------------------
|
|
31
31
|
|
|
32
32
|
"Bahulam", the Bahulam wordmark, the ∞ mark, and "Orca" are trademarks
|
|
33
|
-
of
|
|
33
|
+
of Bahulam. The Apache License, Section 6, does not grant permission
|
|
34
34
|
to use these names, marks, or logos, except as required to describe the
|
|
35
35
|
origin of the software or to reproduce the content of this NOTICE file.
|
|
36
36
|
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @bahulam/code
|
|
2
2
|
|
|
3
3
|
**Bahulam Code** is Bahulam's AI coding agent for terminal-first software work.
|
|
4
4
|
It inspects a repo, plans changes, runs tools, asks for human approval, resumes
|
|
@@ -19,13 +19,13 @@ ship with are design decisions — ones we chose not to make.
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm install -g @
|
|
22
|
+
npm install -g @bahulam/code@latest
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
Run without global install:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
npx @
|
|
28
|
+
npx @bahulam/code@latest
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## Start the CLI
|
|
@@ -59,19 +59,13 @@ bahulam-code --help Full command reference
|
|
|
59
59
|
Inside the REPL, type `/help` for slash commands (models, cache, approvals,
|
|
60
60
|
skills, workflows, session tools).
|
|
61
61
|
|
|
62
|
-
##
|
|
62
|
+
## 0.1.7 Highlights
|
|
63
63
|
|
|
64
|
-
- Published
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- Rebrand: `@bahulamai/b0` → `@bahulamai/code`; binary `b0` → `bahulam-code`.
|
|
71
|
-
- Redesigned fixed input dock — labeled "Bahulam Code" identity, colored
|
|
72
|
-
border, horizontal and vertical margins, meta row showing project, git
|
|
73
|
-
branch, turn count, and token usage.
|
|
74
|
-
- Extended session-context meta line under the input dock.
|
|
64
|
+
- Published as `@bahulam/code`.
|
|
65
|
+
- Local browser workspaces through `bahulam workspace open`.
|
|
66
|
+
- Image analysis and image generation tools for agent workflows.
|
|
67
|
+
- Shared project-aware lint resolver for TS/TSX, JS/JSX, MDX, Python, Go, and Rust.
|
|
68
|
+
- Full shell output is preserved for the agent while terminal cards stay compact.
|
|
75
69
|
|
|
76
70
|
## Development
|
|
77
71
|
|
|
@@ -85,9 +79,8 @@ See [RELEASE.md](./RELEASE.md) for the merge, PR, and npm publish checklist.
|
|
|
85
79
|
## Links
|
|
86
80
|
|
|
87
81
|
- Website: https://bahulam.ai
|
|
88
|
-
- Company: https://axplusb.tech
|
|
89
82
|
- Repository: https://github.com/raviakasapu/codekepler-npm
|
|
90
83
|
|
|
91
84
|
## License
|
|
92
85
|
|
|
93
|
-
|
|
86
|
+
Apache-2.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bahulam/code",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Bahulam Code \u2014 abundance, in your terminal. CLI-first, reliability-first, sub-agents, 65.6% SWE-bench Verified.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,14 +10,13 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"src/",
|
|
13
|
-
"pulse/",
|
|
14
13
|
"README.md",
|
|
15
14
|
"LICENSE",
|
|
16
15
|
"NOTICE"
|
|
17
16
|
],
|
|
18
17
|
"scripts": {
|
|
19
18
|
"start": "node src/terminal/main.mjs",
|
|
20
|
-
"test": "BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-backend-url.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-agent-history.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-sse-client.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-tool-executor.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-project-artifacts.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-work-scope.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-skills.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-callback.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-rate-limit-display.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-preflight.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-formatter.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-terminal-rendering.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-render-queue.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-input-dock.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-live-steering-client.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-slash-commands.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-approval.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-approval-log.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-kepler-contract.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-session-manager.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-safety.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-jsonl-writer.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-analytics.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-stagnation.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-attachments.mjs",
|
|
19
|
+
"test": "BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-backend-url.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-agent-history.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-sse-client.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-tool-executor.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-generate-image.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-analyze-image.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-project-artifacts.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-work-scope.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-lint-resolver.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-local-service.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-skills.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-callback.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-rate-limit-display.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-preflight.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-formatter.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-terminal-rendering.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-render-queue.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-input-dock.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-live-steering-client.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-slash-commands.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-approval.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-approval-log.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-kepler-contract.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-session-manager.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-safety.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-jsonl-writer.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-analytics.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-stagnation.mjs && BAHULAM_HOME=/tmp/bahulam-code-test-home node test/test-attachments.mjs",
|
|
21
20
|
"sync-catalog": "node scripts/sync-catalog.mjs",
|
|
22
21
|
"prepublishOnly": "node scripts/sync-catalog.mjs"
|
|
23
22
|
},
|
|
@@ -38,20 +37,21 @@
|
|
|
38
37
|
"deepseek",
|
|
39
38
|
"claude",
|
|
40
39
|
"gpt",
|
|
41
|
-
"gemini"
|
|
42
|
-
"axplusb"
|
|
40
|
+
"gemini"
|
|
43
41
|
],
|
|
44
42
|
"license": "Apache-2.0",
|
|
45
|
-
"author": "
|
|
43
|
+
"author": "Bahulam <hello@bahulam.ai>",
|
|
46
44
|
"homepage": "https://bahulam.ai/code",
|
|
47
45
|
"repository": {
|
|
48
46
|
"type": "git",
|
|
49
47
|
"url": "git+https://github.com/raviakasapu/codekepler-npm.git"
|
|
50
48
|
},
|
|
51
49
|
"dependencies": {
|
|
50
|
+
"mermaid": "^11.17.2",
|
|
51
|
+
"monaco-editor": "^0.56.0",
|
|
52
52
|
"pdf-parse": "^1.1.1",
|
|
53
53
|
"tree-sitter-wasms": "^0.1.13",
|
|
54
|
-
"web-tree-sitter": "^0.26.9"
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
"web-tree-sitter": "^0.26.9",
|
|
55
|
+
"xlsx": "^0.18.5"
|
|
56
|
+
}
|
|
57
57
|
}
|
package/src/agents/loader.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Agent Loader — loads custom agent definitions from .bahulam/agents/
|
|
3
3
|
*
|
|
4
4
|
* Supports two formats:
|
|
5
|
-
* - YAML: .bahulam/agents/*.yaml / *.yml (native
|
|
5
|
+
* - YAML: .bahulam/agents/*.yaml / *.yml (native Bahulam SubAgent config)
|
|
6
6
|
* - JSON: .bahulam/agents/*.json
|
|
7
7
|
* - Markdown with YAML frontmatter: .bahulam/agents/*.md
|
|
8
8
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow Loader — loads single-agent workflow YAML files from .bahulam/workflows/
|
|
3
3
|
*
|
|
4
|
-
* Workflow YAML files follow the same structure as
|
|
4
|
+
* Workflow YAML files follow the same structure as Bahulam SubAgent YAML:
|
|
5
5
|
* metadata: { id, name, description, icon, tags }
|
|
6
6
|
* agent: { system_prompt, model, max_iterations }
|
|
7
7
|
* tools: { include: [...], exclude: [...] }
|
|
@@ -119,4 +119,4 @@ export function workflowToTemplatePayload(agent, sourcePath) {
|
|
|
119
119
|
params,
|
|
120
120
|
channel: raw.channel || 'server',
|
|
121
121
|
};
|
|
122
|
-
}
|
|
122
|
+
}
|
package/src/auth/tarang-auth.mjs
CHANGED
|
@@ -206,9 +206,10 @@ export class TarangAuth {
|
|
|
206
206
|
process.stderr.write(` Gateway: ${DIM}${creds.gatewayType}${RESET}\n`);
|
|
207
207
|
|
|
208
208
|
const models = creds.models || {};
|
|
209
|
-
|
|
209
|
+
const planningModel = models.planning || models.orchestrator;
|
|
210
|
+
if (planningModel || models.reasoning || models.local) {
|
|
210
211
|
process.stderr.write(`\n${BOLD} Models${RESET}\n`);
|
|
211
|
-
if (
|
|
212
|
+
if (planningModel) process.stderr.write(` Planning: ${DIM}${planningModel}${RESET}\n`);
|
|
212
213
|
if (models.reasoning) process.stderr.write(` Coding: ${DIM}${models.reasoning}${RESET}\n`);
|
|
213
214
|
if (models.local) process.stderr.write(` Local: ${DIM}${models.local}${RESET}\n`);
|
|
214
215
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schema": "bahulam.models_catalog.snapshot.
|
|
2
|
+
"schema": "bahulam.models_catalog.snapshot.v2",
|
|
3
3
|
"source": "delivered_static_catalog",
|
|
4
4
|
"generated_at": "2026-08-23T20:06:08.903480Z",
|
|
5
5
|
"deactivate_missing": false,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{"value": "us.anthropic.claude-opus-4-6-v1", "label": "Claude Opus 4.6 (Bedrock)", "provider": "anthropic", "inputCost": 15, "outputCost": 75, "context": 200000, "maxOutput": 32000, "supportsTools": true, "supportsReasoning": true, "harnessValidated": false},
|
|
11
11
|
{"value": "us.anthropic.claude-sonnet-4-6", "label": "Claude Sonnet 4.6 (Bedrock)", "provider": "anthropic", "inputCost": 3, "outputCost": 15, "context": 200000, "maxOutput": 64000, "supportsTools": true, "supportsReasoning": true, "harnessValidated": false},
|
|
12
12
|
{"value": "us.anthropic.claude-haiku-4-5-20251001-v1:0", "label": "Claude Haiku 4.5 (Bedrock)", "provider": "anthropic", "inputCost": 0.8, "outputCost": 4, "context": 200000, "maxOutput": 8192, "supportsTools": true, "supportsReasoning": false, "harnessValidated": false},
|
|
13
|
-
{"value": "google/gemini-2.5-pro", "label": "Gemini 2.5 Pro", "provider": "google", "inputCost": 1.25, "outputCost": 10, "context": 1048576, "maxOutput": 65536, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
13
|
+
{"value": "google/gemini-2.5-pro", "label": "Gemini 2.5 Pro", "provider": "google", "category": "image", "inputCost": 1.25, "outputCost": 10, "context": 1048576, "maxOutput": 65536, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
14
14
|
{"value": "google/gemini-2.5-flash", "label": "Gemini 2.5 Flash", "provider": "google", "inputCost": 0.15, "outputCost": 0.6, "context": 1048576, "maxOutput": 65535, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
15
15
|
{"value": "google/gemini-2.5-flash-lite", "label": "Gemini 2.5 Flash Lite", "provider": "google", "inputCost": 0.075, "outputCost": 0.3, "context": 1048576, "maxOutput": 65535, "supportsTools": true, "supportsReasoning": false, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
16
16
|
{"value": "openai/gpt-4.1", "label": "GPT-4.1", "provider": "openai", "inputCost": 2, "outputCost": 8, "context": 1047576, "maxOutput": 32768, "supportsTools": true, "supportsReasoning": false, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
{"value": "openai/codex-mini", "label": "Codex Mini", "provider": "openai", "inputCost": 1.5, "outputCost": 6, "context": 200000, "maxOutput": 100000, "supportsTools": true, "supportsReasoning": true, "harnessValidated": false},
|
|
20
20
|
{"value": "deepseek/deepseek-v4-flash", "label": "DeepSeek V4 Flash", "provider": "deepseek", "inputCost": 0.098, "outputCost": 0.197, "context": 1048576, "maxOutput": 384000, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
21
21
|
{"value": "deepseek/deepseek-v4-flash-0731", "label": "DeepSeek V4 Flash (0731)", "provider": "deepseek", "inputCost": 0.098, "outputCost": 0.197, "context": 1310720, "maxOutput": 384000, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "cacheProfile": "{\"type\": \"prefix_hash\", \"provider_hint\": \"DeepSeek\"}", "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
22
|
+
{"value": "deepseek/deepseek-v4-flash-vision-exp", "label": "DeepSeek V4 Flash Vision Exp", "provider": "deepseek", "category": "image", "inputCost": 0.22, "outputCost": 0.66, "context": 1048576, "maxOutput": 384000, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["free", "pro", "tier_49", "tier_99"]},
|
|
22
23
|
{"value": "deepseek/deepseek-v4-pro", "label": "DeepSeek V4 Pro", "provider": "deepseek", "inputCost": 0.435, "outputCost": 0.87, "context": 1048576, "maxOutput": 384000, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "cacheProfile": "{\"type\": \"prefix_hash\", \"provider_hint\": \"DeepSeek\"}", "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
23
24
|
{"value": "deepseek/deepseek-chat-v3-0324", "label": "DeepSeek V3", "provider": "deepseek", "inputCost": 0.27, "outputCost": 1.1, "context": 163840, "maxOutput": 163840, "supportsTools": true, "supportsReasoning": false, "harnessValidated": false},
|
|
24
25
|
{"value": "deepseek/deepseek-r1-0528", "label": "DeepSeek R1 0528", "provider": "deepseek", "inputCost": 0.8, "outputCost": 2.4, "context": 163840, "maxOutput": 32768, "supportsTools": true, "supportsReasoning": true, "harnessValidated": false},
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
{"value": "google/gemini-2.5-pro-preview", "label": "Google: Gemini 2.5 Pro Preview 06-05", "provider": "google", "inputCost": 1.25, "outputCost": 10.0, "context": 1048576, "maxOutput": 65536, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
94
95
|
{"value": "google/gemini-2.5-pro-preview-05-06", "label": "Google: Gemini 2.5 Pro Preview 05-06", "provider": "google", "inputCost": 1.25, "outputCost": 10.0, "context": 1048576, "maxOutput": 65535, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
95
96
|
{"value": "google/gemini-3-flash-preview", "label": "Google: Gemini 3 Flash Preview", "provider": "google", "inputCost": 0.5, "outputCost": 3.0, "context": 1048576, "maxOutput": 65536, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
96
|
-
{"value": "google/gemini-3-pro-image", "label": "Google: Nano Banana Pro (Gemini 3 Pro Image)", "provider": "google", "inputCost": 2.0, "outputCost": 12.0, "context": 131072, "maxOutput": 32768, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
97
|
+
{"value": "google/gemini-3-pro-image", "label": "Google: Nano Banana Pro (Gemini 3 Pro Image)", "provider": "google", "category": "image", "inputCost": 2.0, "outputCost": 12.0, "context": 131072, "maxOutput": 32768, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
97
98
|
{"value": "google/gemini-3.1-flash-lite", "label": "Google: Gemini 3.1 Flash Lite", "provider": "google", "inputCost": 0.25, "outputCost": 1.5, "context": 1048576, "maxOutput": 65536, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
98
99
|
{"value": "google/gemini-3.1-flash-lite-preview", "label": "Google: Gemini 3.1 Flash Lite Preview", "provider": "google", "inputCost": 0.25, "outputCost": 1.5, "context": 1048576, "maxOutput": 65536, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
99
100
|
{"value": "google/gemini-3.1-pro-preview", "label": "Google: Gemini 3.1 Pro Preview", "provider": "google", "inputCost": 2.0, "outputCost": 12.0, "context": 1048576, "maxOutput": 65536, "supportsTools": true, "supportsReasoning": true, "harnessValidated": true, "platformAccessTier": ["pro", "tier_49", "tier_99"]},
|
|
@@ -31,10 +31,12 @@ let _cache = null;
|
|
|
31
31
|
function normalizeSnapshotRow(row) {
|
|
32
32
|
const id = row.value || row.id;
|
|
33
33
|
if (!id) return null;
|
|
34
|
+
const category = row.category || row.modelCategory || row.model_category || 'text';
|
|
34
35
|
return {
|
|
35
36
|
id,
|
|
36
37
|
provider: row.provider || (id.includes('/') ? id.split('/', 1)[0] : 'unknown'),
|
|
37
38
|
label: row.label || id,
|
|
39
|
+
category,
|
|
38
40
|
input_cost_usd_per_m: row.inputCost ?? row.input_cost_usd_per_m ?? null,
|
|
39
41
|
output_cost_usd_per_m: row.outputCost ?? row.output_cost_usd_per_m ?? null,
|
|
40
42
|
context_length: row.context ?? row.context_length ?? null,
|
|
@@ -62,7 +62,8 @@ export const TEXT_MIMES = new Set([
|
|
|
62
62
|
'text/tab-separated-values',
|
|
63
63
|
]);
|
|
64
64
|
export const PDF_MIMES = new Set(['application/pdf']);
|
|
65
|
-
export const
|
|
65
|
+
export const NOTEBOOK_MIMES = new Set(['application/x-ipynb+json']);
|
|
66
|
+
export const DOCUMENT_MIMES = new Set([...TEXT_MIMES, ...PDF_MIMES, ...NOTEBOOK_MIMES]);
|
|
66
67
|
|
|
67
68
|
// Wider set the CLI already supports at the `read_attachment` tool layer.
|
|
68
69
|
// Chunking works fine on any UTF-8 text — retrieval quality on structured
|
|
@@ -72,17 +73,26 @@ export const TEXT_LIKE_MIMES = new Set([
|
|
|
72
73
|
'application/json',
|
|
73
74
|
'application/x-yaml',
|
|
74
75
|
'application/toml',
|
|
76
|
+
'application/xml',
|
|
77
|
+
'application/sql',
|
|
75
78
|
'text/yaml',
|
|
76
79
|
'text/html',
|
|
80
|
+
'text/xml',
|
|
77
81
|
'text/x-log',
|
|
78
82
|
'text/x-rst',
|
|
79
83
|
'text/x-restructuredtext',
|
|
84
|
+
'text/x-sql',
|
|
85
|
+
'text/x-sh',
|
|
86
|
+
'text/x-ini',
|
|
87
|
+
'text/x-env',
|
|
88
|
+
'text/x-dockerfile',
|
|
80
89
|
]);
|
|
81
90
|
|
|
82
91
|
// Extension → mime map. Kept small and explicit — Python's
|
|
83
92
|
// mimetypes.guess_type varies by OS registry; this table is stable.
|
|
84
93
|
const EXT_TO_MIME = new Map([
|
|
85
94
|
['.txt', 'text/plain'],
|
|
95
|
+
['.text', 'text/plain'],
|
|
86
96
|
['.log', 'text/x-log'],
|
|
87
97
|
['.md', 'text/markdown'],
|
|
88
98
|
['.markdown', 'text/markdown'],
|
|
@@ -90,15 +100,114 @@ const EXT_TO_MIME = new Map([
|
|
|
90
100
|
['.csv', 'text/csv'],
|
|
91
101
|
['.tsv', 'text/tab-separated-values'],
|
|
92
102
|
['.json', 'application/json'],
|
|
103
|
+
['.ipynb', 'application/x-ipynb+json'],
|
|
93
104
|
['.yaml', 'application/x-yaml'],
|
|
94
105
|
['.yml', 'application/x-yaml'],
|
|
95
106
|
['.toml', 'application/toml'],
|
|
96
107
|
['.html', 'text/html'],
|
|
97
108
|
['.htm', 'text/html'],
|
|
109
|
+
['.xml', 'application/xml'],
|
|
98
110
|
['.rst', 'text/x-rst'],
|
|
99
111
|
['.pdf', 'application/pdf'],
|
|
112
|
+
// Config + shell + database extensions users actually paste in as
|
|
113
|
+
// attachments (Supabase seeds, .env for triage, Dockerfile review, …).
|
|
114
|
+
['.sql', 'text/x-sql'],
|
|
115
|
+
['.env', 'text/x-env'],
|
|
116
|
+
['.ini', 'text/x-ini'],
|
|
117
|
+
['.conf', 'text/plain'],
|
|
118
|
+
['.cfg', 'text/plain'],
|
|
119
|
+
['.properties', 'text/plain'],
|
|
120
|
+
['.editorconfig', 'text/plain'],
|
|
121
|
+
['.gitignore', 'text/plain'],
|
|
122
|
+
['.gitattributes', 'text/plain'],
|
|
123
|
+
['.dockerignore', 'text/plain'],
|
|
124
|
+
['.dockerfile', 'text/x-dockerfile'],
|
|
125
|
+
['.sh', 'text/x-sh'],
|
|
126
|
+
['.bash', 'text/x-sh'],
|
|
127
|
+
['.zsh', 'text/x-sh'],
|
|
100
128
|
]);
|
|
101
129
|
|
|
130
|
+
function sourceToText(value) {
|
|
131
|
+
if (Array.isArray(value)) return value.map(part => String(part ?? '')).join('');
|
|
132
|
+
if (typeof value === 'string') return value;
|
|
133
|
+
return '';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function outputText(output) {
|
|
137
|
+
if (!output || typeof output !== 'object') return '';
|
|
138
|
+
if (output.output_type === 'error') {
|
|
139
|
+
const traceback = Array.isArray(output.traceback)
|
|
140
|
+
? output.traceback.map(line => String(line ?? '')).join('\n')
|
|
141
|
+
: '';
|
|
142
|
+
const header = [output.ename, output.evalue].filter(Boolean).join(': ');
|
|
143
|
+
return [header, traceback].filter(Boolean).join('\n');
|
|
144
|
+
}
|
|
145
|
+
if (output.text) return sourceToText(output.text);
|
|
146
|
+
const data = output.data && typeof output.data === 'object' ? output.data : null;
|
|
147
|
+
if (data?.['text/plain']) return sourceToText(data['text/plain']);
|
|
148
|
+
if (data?.['text/markdown']) return sourceToText(data['text/markdown']);
|
|
149
|
+
if (data?.['text/html']) return sourceToText(data['text/html']).replace(/<[^>]*>/g, ' ');
|
|
150
|
+
return '';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function notebookLanguage(notebook) {
|
|
154
|
+
const languageInfo = notebook?.metadata?.language_info;
|
|
155
|
+
const kernelspec = notebook?.metadata?.kernelspec;
|
|
156
|
+
return String(languageInfo?.name || kernelspec?.language || '').trim() || 'python';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function formatNotebookText(rawNotebook, { maxOutputChars = 12_000 } = {}) {
|
|
160
|
+
let notebook;
|
|
161
|
+
try {
|
|
162
|
+
notebook = typeof rawNotebook === 'string'
|
|
163
|
+
? JSON.parse(rawNotebook)
|
|
164
|
+
: JSON.parse(Buffer.from(rawNotebook || '').toString('utf8'));
|
|
165
|
+
} catch {
|
|
166
|
+
return '';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const cells = Array.isArray(notebook?.cells) ? notebook.cells : [];
|
|
170
|
+
if (!cells.length) return '';
|
|
171
|
+
|
|
172
|
+
const language = notebookLanguage(notebook);
|
|
173
|
+
const parts = [
|
|
174
|
+
`Jupyter notebook (${cells.length} cell${cells.length === 1 ? '' : 's'}, language=${language})`,
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
cells.forEach((cell, index) => {
|
|
178
|
+
const type = String(cell?.cell_type || 'raw').toLowerCase();
|
|
179
|
+
const execution = cell?.execution_count != null ? ` execution_count=${cell.execution_count}` : '';
|
|
180
|
+
const source = sourceToText(cell?.source).trimEnd();
|
|
181
|
+
parts.push('');
|
|
182
|
+
parts.push(`Cell ${index + 1} [${type}${execution}]`);
|
|
183
|
+
if (type === 'code') {
|
|
184
|
+
parts.push(`\`\`\`${language}`);
|
|
185
|
+
parts.push(source);
|
|
186
|
+
parts.push('```');
|
|
187
|
+
} else {
|
|
188
|
+
parts.push(source || '(empty)');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const outputs = Array.isArray(cell?.outputs) ? cell.outputs : [];
|
|
192
|
+
const renderedOutputs = outputs
|
|
193
|
+
.map(outputText)
|
|
194
|
+
.map(text => text.trimEnd())
|
|
195
|
+
.filter(Boolean);
|
|
196
|
+
if (renderedOutputs.length) {
|
|
197
|
+
let output = renderedOutputs.join('\n\n');
|
|
198
|
+
if (output.length > maxOutputChars) {
|
|
199
|
+
output = `${output.slice(0, maxOutputChars)}\n... [output truncated]`;
|
|
200
|
+
}
|
|
201
|
+
parts.push('Output:');
|
|
202
|
+
parts.push('```text');
|
|
203
|
+
parts.push(output);
|
|
204
|
+
parts.push('```');
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return parts.join('\n').trim();
|
|
209
|
+
}
|
|
210
|
+
|
|
102
211
|
/**
|
|
103
212
|
* Best-effort mime for a local file (extension-only, no magic-byte sniff).
|
|
104
213
|
* Files with no matching extension return 'application/octet-stream',
|
|
@@ -200,6 +309,16 @@ export async function extractFromBytes(buffer, mime, opts = {}) {
|
|
|
200
309
|
}));
|
|
201
310
|
}
|
|
202
311
|
|
|
312
|
+
if (NOTEBOOK_MIMES.has(normalizedMime)) {
|
|
313
|
+
const text = formatNotebookText(buffer);
|
|
314
|
+
return chunkText(text).map(c => ({
|
|
315
|
+
page: null,
|
|
316
|
+
chunk_no: c.chunk_no,
|
|
317
|
+
text: c.text,
|
|
318
|
+
tokens: c.tokens,
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
|
|
203
322
|
if (PDF_MIMES.has(normalizedMime)) {
|
|
204
323
|
const pages = await extractPdfPages(buffer);
|
|
205
324
|
const out = [];
|
|
@@ -217,6 +336,32 @@ export async function extractFromBytes(buffer, mime, opts = {}) {
|
|
|
217
336
|
return [];
|
|
218
337
|
}
|
|
219
338
|
|
|
339
|
+
// Sniff whether a buffer is UTF-8 text vs binary. Cheap heuristic used
|
|
340
|
+
// only as a fallback when the file extension didn't map to a known text
|
|
341
|
+
// mime — the goal is to let obscure but genuinely-textual files (.sql,
|
|
342
|
+
// .env, Dockerfile, .terraformrc, whatever) through instead of dropping
|
|
343
|
+
// them at the mime gate.
|
|
344
|
+
//
|
|
345
|
+
// Rules:
|
|
346
|
+
// * NUL byte in the first probe window → binary
|
|
347
|
+
// * Not decodable as strict UTF-8 → binary
|
|
348
|
+
// * Otherwise → text
|
|
349
|
+
//
|
|
350
|
+
// Probe window bounded to 8KB — enough to catch a binary header on
|
|
351
|
+
// large files, small enough to be a no-op on the hot path.
|
|
352
|
+
const _TEXT_PROBE_BYTES = 8192;
|
|
353
|
+
function looksLikeText(buffer) {
|
|
354
|
+
if (!buffer || buffer.length === 0) return false;
|
|
355
|
+
const probe = buffer.length > _TEXT_PROBE_BYTES ? buffer.subarray(0, _TEXT_PROBE_BYTES) : buffer;
|
|
356
|
+
if (probe.includes(0x00)) return false;
|
|
357
|
+
try {
|
|
358
|
+
new TextDecoder('utf-8', { fatal: true }).decode(probe);
|
|
359
|
+
return true;
|
|
360
|
+
} catch {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
220
365
|
/**
|
|
221
366
|
* Read a local file and chunk it. Returns { mime, chunks }; both empty
|
|
222
367
|
* on unresolvable path or unsupported mime — same contract as Python's
|
|
@@ -237,12 +382,11 @@ export async function extractFromPath(absPath, opts = {}) {
|
|
|
237
382
|
}
|
|
238
383
|
if (!stat.isFile()) return { mime: '', chunks: [] };
|
|
239
384
|
|
|
240
|
-
|
|
385
|
+
let mime = guessMime(absPath);
|
|
241
386
|
const textMimes = opts.textMimes || TEXT_LIKE_MIMES;
|
|
242
|
-
if (!textMimes.has(mime) && !PDF_MIMES.has(mime)) {
|
|
243
|
-
return { mime, chunks: [] };
|
|
244
|
-
}
|
|
245
387
|
|
|
388
|
+
// Read the buffer up front so the text-sniff fallback can see the
|
|
389
|
+
// actual bytes when the extension didn't map to a known mime.
|
|
246
390
|
let buffer;
|
|
247
391
|
try {
|
|
248
392
|
buffer = fs.readFileSync(absPath);
|
|
@@ -250,6 +394,19 @@ export async function extractFromPath(absPath, opts = {}) {
|
|
|
250
394
|
return { mime, chunks: [] };
|
|
251
395
|
}
|
|
252
396
|
|
|
397
|
+
// Fallback for unknown extensions: if guessMime returned octet-stream
|
|
398
|
+
// (or any mime not in our allow-list), sniff the buffer. If the bytes
|
|
399
|
+
// look like valid UTF-8 text with no NULs in the first probe window,
|
|
400
|
+
// treat as text/plain. This unblocks .sql, .env, Dockerfile, and any
|
|
401
|
+
// other text file we haven't explicitly listed.
|
|
402
|
+
if (!textMimes.has(mime) && !PDF_MIMES.has(mime) && !NOTEBOOK_MIMES.has(mime)) {
|
|
403
|
+
if (looksLikeText(buffer)) {
|
|
404
|
+
mime = 'text/plain';
|
|
405
|
+
} else {
|
|
406
|
+
return { mime, chunks: [] };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
253
410
|
const chunks = await extractFromBytes(buffer, mime, opts);
|
|
254
411
|
return { mime, chunks };
|
|
255
412
|
}
|
package/src/core/attachments.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from 'node:path';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import { createHash, randomUUID } from 'node:crypto';
|
|
5
5
|
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import { formatNotebookText } from '../context/prose-chunker.mjs';
|
|
6
7
|
|
|
7
8
|
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif']);
|
|
8
9
|
// Text-first document formats we can extract client-side and inline into the
|
|
@@ -12,7 +13,7 @@ const DOCUMENT_EXTENSIONS = new Set([
|
|
|
12
13
|
'.pdf',
|
|
13
14
|
'.txt', '.md', '.mdx', '.rst',
|
|
14
15
|
'.csv', '.tsv', '.log',
|
|
15
|
-
'.json', '.yaml', '.yml', '.toml', '.ini', '.env',
|
|
16
|
+
'.json', '.ipynb', '.yaml', '.yml', '.toml', '.ini', '.env',
|
|
16
17
|
'.html', '.htm', '.xml',
|
|
17
18
|
]);
|
|
18
19
|
const DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
@@ -143,6 +144,53 @@ function sniffImage(buffer) {
|
|
|
143
144
|
return null;
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
export function detectImageFile(filePath, { cwd = process.cwd() } = {}) {
|
|
148
|
+
const resolved = resolveAttachmentPath(filePath, cwd);
|
|
149
|
+
let stat;
|
|
150
|
+
try {
|
|
151
|
+
stat = fs.statSync(resolved);
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
if (!stat.isFile() || stat.size <= 0) return null;
|
|
156
|
+
|
|
157
|
+
const ext = path.extname(resolved).toLowerCase();
|
|
158
|
+
const probe = Buffer.alloc(Math.min(64, stat.size));
|
|
159
|
+
let fd = null;
|
|
160
|
+
try {
|
|
161
|
+
fd = fs.openSync(resolved, 'r');
|
|
162
|
+
fs.readSync(fd, probe, 0, probe.length, 0);
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
} finally {
|
|
166
|
+
if (fd !== null) {
|
|
167
|
+
try { fs.closeSync(fd); } catch {}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const sniffed = sniffImage(probe);
|
|
172
|
+
if (sniffed) {
|
|
173
|
+
return {
|
|
174
|
+
path: resolved,
|
|
175
|
+
mime_type: sniffed.mime_type,
|
|
176
|
+
bytes: stat.size,
|
|
177
|
+
ext: sniffed.ext,
|
|
178
|
+
verified: true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (!IMAGE_EXTENSIONS.has(ext)) return null;
|
|
182
|
+
const mimeType = ext === '.jpg' || ext === '.jpeg'
|
|
183
|
+
? 'image/jpeg'
|
|
184
|
+
: `image/${ext.slice(1)}`;
|
|
185
|
+
return {
|
|
186
|
+
path: resolved,
|
|
187
|
+
mime_type: mimeType,
|
|
188
|
+
bytes: stat.size,
|
|
189
|
+
ext,
|
|
190
|
+
verified: false,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
146
194
|
function pngDimensions(buffer) {
|
|
147
195
|
if (buffer.length < 24) return {};
|
|
148
196
|
return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20) };
|
|
@@ -510,6 +558,17 @@ function extractPlainText(filePath, { maxChars }) {
|
|
|
510
558
|
};
|
|
511
559
|
}
|
|
512
560
|
|
|
561
|
+
function extractNotebookText(filePath, { maxChars }) {
|
|
562
|
+
const buffer = fs.readFileSync(filePath);
|
|
563
|
+
const raw = formatNotebookText(buffer);
|
|
564
|
+
const truncated = raw.length > maxChars;
|
|
565
|
+
return {
|
|
566
|
+
text: truncated ? raw.slice(0, maxChars) : raw,
|
|
567
|
+
pages: 0,
|
|
568
|
+
truncated,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
513
572
|
/**
|
|
514
573
|
* Load a single document attachment. Reads locally, extracts text, returns
|
|
515
574
|
* a public metadata block plus the extracted text. Errors bubble as
|
|
@@ -537,9 +596,12 @@ export async function loadDocumentAttachment(filePath, {
|
|
|
537
596
|
|
|
538
597
|
const ext = path.extname(resolved).toLowerCase();
|
|
539
598
|
const isPdf = ext === '.pdf';
|
|
599
|
+
const isNotebook = ext === '.ipynb';
|
|
540
600
|
const { text, pages, truncated } = isPdf
|
|
541
601
|
? await extractPdfText(resolved, { maxChars })
|
|
542
|
-
:
|
|
602
|
+
: isNotebook
|
|
603
|
+
? extractNotebookText(resolved, { maxChars })
|
|
604
|
+
: extractPlainText(resolved, { maxChars });
|
|
543
605
|
|
|
544
606
|
const buffer = fs.readFileSync(resolved);
|
|
545
607
|
const sha256 = createHash('sha256').update(buffer).digest('hex');
|
|
@@ -549,7 +611,7 @@ export async function loadDocumentAttachment(filePath, {
|
|
|
549
611
|
path: resolved,
|
|
550
612
|
name: path.basename(resolved),
|
|
551
613
|
ext,
|
|
552
|
-
kind: isPdf ? 'pdf' : 'text',
|
|
614
|
+
kind: isPdf ? 'pdf' : isNotebook ? 'notebook' : 'text',
|
|
553
615
|
bytes: stat.size,
|
|
554
616
|
chars: text.length,
|
|
555
617
|
pages,
|
|
@@ -165,7 +165,7 @@ function _clearLicenseActivationState() {
|
|
|
165
165
|
if (process.platform === 'darwin') {
|
|
166
166
|
try {
|
|
167
167
|
spawnSync('security',
|
|
168
|
-
['delete-generic-password', '-s', '
|
|
168
|
+
['delete-generic-password', '-s', 'ai.bahulam.agent.runtime', '-a', 'af_state'],
|
|
169
169
|
{ stdio: 'ignore' });
|
|
170
170
|
} catch { /* best-effort */ }
|
|
171
171
|
}
|
package/src/core/headless.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import { ApprovalManager } from './approval.mjs';
|
|
|
22
22
|
// is invisible to attach clients and to paired mobile devices.
|
|
23
23
|
import { tapSseEvent, registerBroadcaster } from '../daemon/event-tap.mjs';
|
|
24
24
|
import { startSocketServer } from '../daemon/socket-server.mjs';
|
|
25
|
+
import { startHttpDashboard } from '../daemon/http-dashboard.mjs';
|
|
25
26
|
import { resolvePending } from '../daemon/approval-store.mjs';
|
|
26
27
|
import { startRelayBridge } from '../daemon/relay-client.mjs';
|
|
27
28
|
import { loadRemoteConfig } from '../commands/remote.mjs';
|
|
@@ -348,6 +349,18 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
|
|
|
348
349
|
});
|
|
349
350
|
registerBroadcaster(evt => server.broadcastEvent(evt));
|
|
350
351
|
|
|
352
|
+
// Optional HTTP browser dashboard (opt-in with BAHULAM_DASHBOARD=1)
|
|
353
|
+
let dashboardUrl = null;
|
|
354
|
+
if (process.env.BAHULAM_DASHBOARD === '1') {
|
|
355
|
+
try {
|
|
356
|
+
const dashboard = await startHttpDashboard({ sessionId: _sid });
|
|
357
|
+
dashboardUrl = dashboard.url;
|
|
358
|
+
process.stderr.write(`[prd-092] dashboard: ${dashboard.url}\n`);
|
|
359
|
+
} catch (err) {
|
|
360
|
+
try { process.stderr.write(`[prd-092] dashboard start: ${err.message}\n`); } catch {}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
351
364
|
// Write meta.json + daemon.pid so `bahulam list` /
|
|
352
365
|
// `bahulam stop` and the mobile session directory
|
|
353
366
|
// can find this session.
|
|
@@ -361,6 +374,7 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
|
|
|
361
374
|
sock_path: server.sockPath,
|
|
362
375
|
opened_at: new Date().toISOString(),
|
|
363
376
|
headless: true,
|
|
377
|
+
dashboard_url: dashboardUrl,
|
|
364
378
|
},
|
|
365
379
|
});
|
|
366
380
|
fsSync.writeFileSync(
|