@bahulam/code 2.6.0
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/README.md +80 -0
- package/package.json +49 -0
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/benchmark/route.ts +113 -0
- package/pulse/app/api/benchmarks/route.ts +195 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +50 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/benchmarks/page.tsx +224 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +143 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +164 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +53 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +183 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/bahulam-paths.ts +23 -0
- package/pulse/lib/claude-reader.ts +592 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- 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 +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- 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 +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +94 -0
- package/src/agents/multi_workflow_loader.mjs +330 -0
- package/src/agents/parser.mjs +205 -0
- package/src/agents/scaffold.mjs +222 -0
- package/src/agents/teams.mjs +123 -0
- package/src/agents/workflow_loader.mjs +122 -0
- package/src/agents/workflow_scaffold.mjs +249 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +306 -0
- package/src/commands/agent.mjs +220 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +200 -0
- package/src/config/env.mjs +263 -0
- package/src/config/hook-runner.mjs +100 -0
- package/src/config/memory-loader.mjs +32 -0
- package/src/config/settings-loader.mjs +45 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +308 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/context/symbol-indexer.mjs +375 -0
- package/src/core/agent-history.mjs +111 -0
- package/src/core/agent-loop.mjs +486 -0
- package/src/core/approval-log.mjs +104 -0
- package/src/core/approval.mjs +476 -0
- package/src/core/attachments.mjs +380 -0
- package/src/core/backend-url.mjs +55 -0
- package/src/core/cache-control.mjs +92 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +180 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/compact-history.mjs +127 -0
- package/src/core/context-envelope.mjs +54 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/error-guidance.mjs +311 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +448 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +537 -0
- package/src/core/local-store.mjs +836 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +190 -0
- package/src/core/policy-resolver.mjs +156 -0
- package/src/core/pricing.mjs +336 -0
- package/src/core/project-artifacts.mjs +39 -0
- package/src/core/project-context-loader.mjs +139 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limit-display.mjs +121 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/resume-mode.mjs +192 -0
- package/src/core/risk-tier.mjs +337 -0
- package/src/core/safety.mjs +203 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +360 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +829 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +140 -0
- package/src/core/tasks.mjs +196 -0
- package/src/core/tool-executor.mjs +1950 -0
- package/src/core/trust.mjs +158 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +426 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/onboarding/preflight.mjs +360 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +652 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +252 -0
- package/src/skills/runner.mjs +55 -0
- package/src/state/orbit.mjs +263 -0
- package/src/state/verbosity.mjs +99 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +695 -0
- package/src/terminal/init.mjs +145 -0
- package/src/terminal/main.mjs +269 -0
- package/src/terminal/repl-explore.mjs +35 -0
- package/src/terminal/repl-format.mjs +257 -0
- package/src/terminal/repl-render.mjs +561 -0
- package/src/terminal/repl-resume.mjs +625 -0
- package/src/terminal/repl-state.mjs +103 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +3832 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +240 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +231 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/project-overview.mjs +641 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +116 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/approval.mjs +263 -0
- package/src/ui/banner.mjs +235 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +164 -0
- package/src/ui/input-dock.mjs +444 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/mission-report.mjs +296 -0
- package/src/ui/palette.mjs +189 -0
- package/src/ui/slash-commands.mjs +245 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +152 -0
- package/src/ui/term.mjs +159 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +463 -0
- package/src/ui/tool-details.mjs +312 -0
- package/src/ui/transcript-block.mjs +21 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bahulam Code Authentication — GitHub OAuth + config management.
|
|
3
|
+
* Reads/writes ~/.bahulam/config.json (fallback: ~/.kepler/config.json for
|
|
4
|
+
* legacy installs — see src/core/paths.mjs for the resolver).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import * as http from 'node:http';
|
|
10
|
+
import { getLoginSuccessHTML } from '../ui/banner.mjs';
|
|
11
|
+
import { resolveBackendUrl } from '../core/backend-url.mjs';
|
|
12
|
+
import { bahulamHome } from '../core/paths.mjs';
|
|
13
|
+
|
|
14
|
+
// Note: computed via a function (not a constant) so that BAHULAM_HOME /
|
|
15
|
+
// KEPLER_HOME env-var swaps mid-process still work.
|
|
16
|
+
function configDir() { return bahulamHome(); }
|
|
17
|
+
function configPath() { return path.join(configDir(), 'config.json'); }
|
|
18
|
+
|
|
19
|
+
// Legacy exports kept for backwards compat with any tests/scripts that
|
|
20
|
+
// still import CONFIG_DIR / CONFIG_PATH by name.
|
|
21
|
+
const CONFIG_DIR = configDir();
|
|
22
|
+
const CONFIG_PATH = configPath();
|
|
23
|
+
|
|
24
|
+
let _tokenEnvNoticeShown = false;
|
|
25
|
+
function readTokenFromEnv() {
|
|
26
|
+
if (process.env.B0_TOKEN) return process.env.B0_TOKEN;
|
|
27
|
+
if (process.env.KEPLER_TOKEN) {
|
|
28
|
+
if (!_tokenEnvNoticeShown && process.env.B0_QUIET_MIGRATION !== '1') {
|
|
29
|
+
_tokenEnvNoticeShown = true;
|
|
30
|
+
try {
|
|
31
|
+
process.stderr.write(
|
|
32
|
+
' \x1b[2mnote: KEPLER_TOKEN is deprecated; set B0_TOKEN instead.\x1b[0m\n'
|
|
33
|
+
);
|
|
34
|
+
} catch {}
|
|
35
|
+
}
|
|
36
|
+
return process.env.KEPLER_TOKEN;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class TarangAuth {
|
|
42
|
+
constructor() {
|
|
43
|
+
this._config = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Ensure ~/.bahulam/ (or legacy ~/.kepler/) exists with secure permissions. */
|
|
47
|
+
_ensureConfigDir() {
|
|
48
|
+
const dir = configDir();
|
|
49
|
+
if (!fs.existsSync(dir)) {
|
|
50
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Load credentials and settings from config.json. */
|
|
55
|
+
loadCredentials() {
|
|
56
|
+
const cfgPath = configPath();
|
|
57
|
+
try {
|
|
58
|
+
if (fs.existsSync(cfgPath)) {
|
|
59
|
+
const raw = fs.readFileSync(cfgPath, 'utf-8');
|
|
60
|
+
this._config = JSON.parse(raw);
|
|
61
|
+
} else {
|
|
62
|
+
this._config = {};
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
this._config = {};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
token: readTokenFromEnv() || this._config.token || null,
|
|
69
|
+
openRouterKey: this._config.openrouter_key || process.env.OPENROUTER_API_KEY || null,
|
|
70
|
+
anthropicKey: this._config.anthropic_api_key || process.env.ANTHROPIC_API_KEY || null,
|
|
71
|
+
openaiKey: this._config.openai_api_key || process.env.OPENAI_API_KEY || null,
|
|
72
|
+
googleKey: this._config.google_api_key || process.env.GOOGLE_API_KEY || null,
|
|
73
|
+
backendUrl: resolveBackendUrl(),
|
|
74
|
+
mode: this._config.mode || 'auto',
|
|
75
|
+
gatewayType: this._config.gateway_type || 'openrouter',
|
|
76
|
+
models: this._config.models || {},
|
|
77
|
+
configuredProviders: this._config.configured_providers || [],
|
|
78
|
+
gatewayConfig: this._config.gateway_config || {},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Get the raw config object. */
|
|
83
|
+
getRawConfig() {
|
|
84
|
+
if (!this._config) this.loadCredentials();
|
|
85
|
+
return this._config || {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Clear credentials — remove token and keys from config. */
|
|
89
|
+
logout() {
|
|
90
|
+
try {
|
|
91
|
+
const cfgPath = configPath();
|
|
92
|
+
if (fs.existsSync(cfgPath)) {
|
|
93
|
+
fs.unlinkSync(cfgPath);
|
|
94
|
+
}
|
|
95
|
+
this._config = null;
|
|
96
|
+
return true;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Save credentials atomically (temp-file + rename). */
|
|
103
|
+
saveCredentials(updates) {
|
|
104
|
+
this._ensureConfigDir();
|
|
105
|
+
const current = this._config || {};
|
|
106
|
+
const merged = { ...current, ...updates };
|
|
107
|
+
const cfgPath = configPath();
|
|
108
|
+
const tmpPath = `${cfgPath}.tmp.${process.pid}`;
|
|
109
|
+
fs.writeFileSync(tmpPath, JSON.stringify(merged, null, 2), { mode: 0o600 });
|
|
110
|
+
fs.renameSync(tmpPath, cfgPath);
|
|
111
|
+
this._config = merged;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Check if user has a valid auth token. */
|
|
115
|
+
isAuthenticated() {
|
|
116
|
+
const creds = this.loadCredentials();
|
|
117
|
+
return !!creds.token;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Check if OpenRouter key is configured. */
|
|
121
|
+
hasOpenRouterKey() {
|
|
122
|
+
const creds = this.loadCredentials();
|
|
123
|
+
return !!creds.openRouterKey;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Save an API key by provider name. */
|
|
127
|
+
saveProviderKey(provider, key) {
|
|
128
|
+
const keyMap = {
|
|
129
|
+
openrouter: 'openrouter_key',
|
|
130
|
+
anthropic: 'anthropic_api_key',
|
|
131
|
+
openai: 'openai_api_key',
|
|
132
|
+
googleai: 'google_api_key',
|
|
133
|
+
azureopenai: 'azure_api_key',
|
|
134
|
+
bedrock: 'aws_access_key',
|
|
135
|
+
databricks: 'databricks_token',
|
|
136
|
+
};
|
|
137
|
+
const field = keyMap[provider];
|
|
138
|
+
if (!field) throw new Error(`Unknown provider: ${provider}`);
|
|
139
|
+
this.saveCredentials({ [field]: key });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Save OpenRouter API key. */
|
|
143
|
+
saveOpenRouterKey(key) {
|
|
144
|
+
this.saveProviderKey('openrouter', key);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Save Anthropic API key. */
|
|
148
|
+
saveAnthropicKey(key) {
|
|
149
|
+
this.saveProviderKey('anthropic', key);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Save OpenAI API key. */
|
|
153
|
+
saveOpenAIKey(key) {
|
|
154
|
+
this.saveProviderKey('openai', key);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Save Google AI API key. */
|
|
158
|
+
saveGoogleKey(key) {
|
|
159
|
+
this.saveProviderKey('googleai', key);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Sync settings from web backend and save locally. */
|
|
163
|
+
async syncSettings() {
|
|
164
|
+
const { fetchRemoteSettings, mergeRemoteSettings } = await import('../core/settings-sync.mjs');
|
|
165
|
+
const creds = this.loadCredentials();
|
|
166
|
+
if (!creds.token) throw new Error('Not logged in. Run `bahulam-code login` first.');
|
|
167
|
+
|
|
168
|
+
const remote = await fetchRemoteSettings(creds.token);
|
|
169
|
+
if (!remote) throw new Error('Failed to fetch settings from server.');
|
|
170
|
+
|
|
171
|
+
const merged = mergeRemoteSettings(this.getRawConfig(), remote);
|
|
172
|
+
this.saveCredentials(merged);
|
|
173
|
+
return remote;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Set default mode. */
|
|
177
|
+
setMode(mode) {
|
|
178
|
+
const valid = ['local', 'remote', 'auto'];
|
|
179
|
+
if (!valid.includes(mode)) {
|
|
180
|
+
throw new Error(`Invalid mode: ${mode}. Must be one of: ${valid.join(', ')}`);
|
|
181
|
+
}
|
|
182
|
+
this.saveCredentials({ mode });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Display config (styled). */
|
|
186
|
+
printConfig() {
|
|
187
|
+
const creds = this.loadCredentials();
|
|
188
|
+
const GREEN = '\x1b[32m', RED = '\x1b[31m', DIM = '\x1b[2m', BOLD = '\x1b[1m', CYAN = '\x1b[36m', RESET = '\x1b[0m';
|
|
189
|
+
const check = `${GREEN}\u2713${RESET}`;
|
|
190
|
+
const cross = `${RED}\u2717${RESET}`;
|
|
191
|
+
|
|
192
|
+
const env = process.env.TARANG_ENV || process.env.NODE_ENV || 'production';
|
|
193
|
+
|
|
194
|
+
process.stderr.write(`\n${BOLD}Bahulam Code Configuration${RESET}\n`);
|
|
195
|
+
process.stderr.write(`${'─'.repeat(50)}\n`);
|
|
196
|
+
process.stderr.write(` Auth: ${creds.token ? `${check} logged in` : `${cross} not logged in ${DIM}(/login)${RESET}`}\n`);
|
|
197
|
+
process.stderr.write(` Environment: ${DIM}${env}${RESET}\n`);
|
|
198
|
+
process.stderr.write(` Backend: ${DIM}${creds.backendUrl}${RESET}\n`);
|
|
199
|
+
process.stderr.write(` Mode: ${DIM}${creds.mode || 'auto'}${RESET}\n`);
|
|
200
|
+
process.stderr.write(` Gateway: ${DIM}${creds.gatewayType}${RESET}\n`);
|
|
201
|
+
|
|
202
|
+
const models = creds.models || {};
|
|
203
|
+
if (models.orchestrator || models.reasoning || models.local) {
|
|
204
|
+
process.stderr.write(`\n${BOLD} Models${RESET}\n`);
|
|
205
|
+
if (models.orchestrator) process.stderr.write(` Orchestrator: ${DIM}${models.orchestrator}${RESET}\n`);
|
|
206
|
+
if (models.reasoning) process.stderr.write(` Coding: ${DIM}${models.reasoning}${RESET}\n`);
|
|
207
|
+
if (models.local) process.stderr.write(` Local: ${DIM}${models.local}${RESET}\n`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const providers = creds.configuredProviders || [];
|
|
211
|
+
if (providers.length > 0) {
|
|
212
|
+
process.stderr.write(` Providers: ${DIM}${providers.join(', ')}${RESET}\n`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const raw = this.getRawConfig();
|
|
216
|
+
if (raw.last_synced_at) {
|
|
217
|
+
process.stderr.write(` Last synced: ${DIM}${new Date(raw.last_synced_at).toLocaleString()}${RESET}\n`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
process.stderr.write(`\n ${DIM}Run ${RESET}${CYAN}bahulam-code sync${RESET}${DIM} to sync settings from web.${RESET}\n`);
|
|
221
|
+
process.stderr.write(` ${DIM}Run ${RESET}${CYAN}bahulam-code configure${RESET}${DIM} to open settings in browser.${RESET}\n`);
|
|
222
|
+
process.stderr.write('\n');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Run login flow via web app.
|
|
227
|
+
*
|
|
228
|
+
* Flow:
|
|
229
|
+
* 1. CLI starts local HTTP server on random port
|
|
230
|
+
* 2. Opens browser to web /auth/cli?callback=http://127.0.0.1:{port}/callback
|
|
231
|
+
* 3. Web checks Supabase session (if none → GitHub OAuth → Supabase)
|
|
232
|
+
* 4. Web generates CLI token via /api/cli/token
|
|
233
|
+
* 5. Web redirects browser to CLI callback with token
|
|
234
|
+
* 6. CLI receives token, saves to ~/.bahulam/config.json
|
|
235
|
+
*/
|
|
236
|
+
async login() {
|
|
237
|
+
const { resolveWebUrl } = await import('../core/backend-url.mjs');
|
|
238
|
+
const webUrl = resolveWebUrl();
|
|
239
|
+
|
|
240
|
+
return new Promise((resolve, reject) => {
|
|
241
|
+
const server = http.createServer(async (req, res) => {
|
|
242
|
+
const url = new URL(req.url, `http://localhost`);
|
|
243
|
+
|
|
244
|
+
// The web app redirects here with ?token=<cli_token>
|
|
245
|
+
const token = url.searchParams.get('token');
|
|
246
|
+
|
|
247
|
+
if (!token) {
|
|
248
|
+
// Maybe an error or missing token
|
|
249
|
+
const error = url.searchParams.get('error');
|
|
250
|
+
if (error) {
|
|
251
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
252
|
+
res.end(`<html><body><h2>Login failed</h2><p>${error}</p></body></html>`);
|
|
253
|
+
server.close();
|
|
254
|
+
reject(new Error(error));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// Ignore other requests (favicon, etc.)
|
|
258
|
+
res.writeHead(200);
|
|
259
|
+
res.end('');
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Save the CLI token
|
|
264
|
+
this.saveCredentials({ token });
|
|
265
|
+
|
|
266
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
267
|
+
res.end(getLoginSuccessHTML());
|
|
268
|
+
|
|
269
|
+
server.close();
|
|
270
|
+
resolve(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
server.listen(0, '127.0.0.1', () => {
|
|
274
|
+
const port = server.address().port;
|
|
275
|
+
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
276
|
+
const authUrl = `${webUrl}/auth/cli?callback=${encodeURIComponent(callbackUrl)}`;
|
|
277
|
+
|
|
278
|
+
process.stderr.write(`\n\x1b[36mOpening browser for login...\x1b[0m\n`);
|
|
279
|
+
process.stderr.write(`\x1b[2mIf browser doesn't open, visit:\x1b[0m\n \x1b[4m${authUrl}\x1b[0m\n\n`);
|
|
280
|
+
|
|
281
|
+
// Open browser
|
|
282
|
+
const openCmd = process.platform === 'darwin' ? 'open' :
|
|
283
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
284
|
+
import('node:child_process').then(({ exec }) => {
|
|
285
|
+
exec(`${openCmd} "${authUrl}"`, () => {});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Timeout after 120s
|
|
290
|
+
setTimeout(() => {
|
|
291
|
+
server.close();
|
|
292
|
+
reject(new Error('Login timed out after 120s'));
|
|
293
|
+
}, 120_000);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Ensure user is authenticated, prompt login if not.
|
|
299
|
+
*/
|
|
300
|
+
async ensureAuth() {
|
|
301
|
+
if (!this.isAuthenticated()) {
|
|
302
|
+
process.stderr.write('\x1b[33mNot logged in.\x1b[0m Starting login flow...\n');
|
|
303
|
+
await this.login();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent CLI Commands — list, get, sync user-defined agents.
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* bahulam-code agent list
|
|
6
|
+
* bahulam-code agent get <slug>
|
|
7
|
+
* bahulam-code agent sync [--dir <path>]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { resolveBackendUrl } from '../core/backend-url.mjs';
|
|
11
|
+
import { TarangAuth } from '../auth/tarang-auth.mjs';
|
|
12
|
+
import { listLocalAgents, syncAgentsToBackend, agentToSpec, agentContentHash } from '../agents/scaffold.mjs';
|
|
13
|
+
import { parseAgentDefinition } from '../agents/parser.mjs';
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
|
|
17
|
+
const RESET = '\x1b[0m';
|
|
18
|
+
const BOLD = '\x1b[1m';
|
|
19
|
+
const DIM = '\x1b[2m';
|
|
20
|
+
const RED = '\x1b[31m';
|
|
21
|
+
const GREEN = '\x1b[32m';
|
|
22
|
+
const YELLOW = '\x1b[33m';
|
|
23
|
+
const CYAN = '\x1b[36m';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Main entry point for all `bahulam-code agent` subcommands.
|
|
27
|
+
* @param {object} args - parsed CLI args
|
|
28
|
+
*/
|
|
29
|
+
export async function handleAgentCommand(args) {
|
|
30
|
+
const sub = args.agentSubcommand || 'help';
|
|
31
|
+
|
|
32
|
+
switch (sub) {
|
|
33
|
+
case 'list':
|
|
34
|
+
await handleList(args);
|
|
35
|
+
break;
|
|
36
|
+
case 'get':
|
|
37
|
+
await handleGet(args);
|
|
38
|
+
break;
|
|
39
|
+
case 'sync':
|
|
40
|
+
await handleSync(args);
|
|
41
|
+
break;
|
|
42
|
+
default:
|
|
43
|
+
printAgentUsage();
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function printAgentUsage() {
|
|
49
|
+
process.stderr.write(`${BOLD}AGENT COMMANDS${RESET}\n`);
|
|
50
|
+
process.stderr.write(` ${CYAN}bahulam-code agent list${RESET} List user-defined agents\n`);
|
|
51
|
+
process.stderr.write(` ${CYAN}bahulam-code agent get <slug>${RESET} Show agent details\n`);
|
|
52
|
+
process.stderr.write(` ${CYAN}bahulam-code agent sync [--dir <path>]${RESET} Sync agent YAML files\n`);
|
|
53
|
+
process.stderr.write('\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Helpers ────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
function getAuth() {
|
|
59
|
+
const auth = new TarangAuth();
|
|
60
|
+
const creds = auth.loadCredentials();
|
|
61
|
+
if (!creds.token) {
|
|
62
|
+
process.stderr.write(`${RED}✗ Not authenticated. Run `bahulam-code login` first.${RESET}\n`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
return creds;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getBaseUrl() {
|
|
69
|
+
const auth = new TarangAuth();
|
|
70
|
+
return auth.loadCredentials().backendUrl || resolveBackendUrl();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function apiFetch(path, options = {}) {
|
|
74
|
+
const creds = getAuth();
|
|
75
|
+
const baseUrl = getBaseUrl();
|
|
76
|
+
const headers = {
|
|
77
|
+
'Authorization': `Bearer ${creds.token}`,
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
...options.headers,
|
|
80
|
+
};
|
|
81
|
+
const url = `${baseUrl}${path}`;
|
|
82
|
+
const response = await fetch(url, { ...options, headers });
|
|
83
|
+
|
|
84
|
+
if (response.status === 401) {
|
|
85
|
+
process.stderr.write(`${RED}✗ Authentication failed. Run `bahulam-code login` to re-authenticate.${RESET}\n`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const text = await response.text();
|
|
90
|
+
let payload;
|
|
91
|
+
try {
|
|
92
|
+
payload = text ? JSON.parse(text) : {};
|
|
93
|
+
} catch {
|
|
94
|
+
payload = { detail: text };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const msg = payload?.detail || payload?.message || `HTTP ${response.status}`;
|
|
99
|
+
throw new Error(msg);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return payload;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── List ───────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
async function handleList(args) {
|
|
108
|
+
const result = await apiFetch('/api/user/agents');
|
|
109
|
+
|
|
110
|
+
const agents = result.agents || [];
|
|
111
|
+
if (agents.length === 0) {
|
|
112
|
+
process.stderr.write(`${DIM}No user-defined agents found.${RESET}\n`);
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
process.stderr.write(`${BOLD}User-defined Agents:${RESET}\n`);
|
|
117
|
+
for (const a of agents) {
|
|
118
|
+
const source = a.source === 'platform' ? `${DIM}(platform)${RESET}` : `${DIM}(user)${RESET}`;
|
|
119
|
+
process.stderr.write(` ${CYAN}${a.slug}${RESET} ${a.name || a.slug} ${source}\n`);
|
|
120
|
+
if (a.description) {
|
|
121
|
+
process.stderr.write(` ${DIM}${a.description}${RESET}\n`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
process.stdout.write(JSON.stringify(agents.map(a => ({ slug: a.slug, name: a.name, source: a.source })), null, 2) + '\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Get ────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
async function handleGet(args) {
|
|
130
|
+
const slug = args.agentSlug;
|
|
131
|
+
if (!slug) {
|
|
132
|
+
process.stderr.write(`${RED}✗ Usage: bahulam-code agent get <slug>${RESET}\n`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const result = await apiFetch(`/api/user/agents/${encodeURIComponent(slug)}`);
|
|
137
|
+
|
|
138
|
+
process.stderr.write(`${BOLD}${result.name || result.slug}${RESET} (${CYAN}${result.slug}${RESET})\n`);
|
|
139
|
+
if (result.description) process.stderr.write(` ${DIM}${result.description}${RESET}\n`);
|
|
140
|
+
process.stderr.write(` Source: ${result.source || 'user'}\n`);
|
|
141
|
+
const spec = result.spec || {};
|
|
142
|
+
const role = spec.role || 'specialist';
|
|
143
|
+
process.stderr.write(` Role: ${role}\n`);
|
|
144
|
+
const caps = spec.capabilities || [];
|
|
145
|
+
if (caps.length) process.stderr.write(` Capabilities: ${caps.join(', ')}\n`);
|
|
146
|
+
const tools = spec.tools || [];
|
|
147
|
+
if (tools.length) process.stderr.write(` Tools: ${tools.join(', ')}\n`);
|
|
148
|
+
if (spec.system_prompt || spec.prompt) {
|
|
149
|
+
const prompt = spec.system_prompt || spec.prompt || '';
|
|
150
|
+
const preview = prompt.slice(0, 200);
|
|
151
|
+
process.stderr.write(` System prompt: ${DIM}${preview}${preview.length < prompt.length ? '...' : ''}${RESET}\n`);
|
|
152
|
+
}
|
|
153
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Sync ───────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
async function handleSync(args) {
|
|
159
|
+
let localAgents;
|
|
160
|
+
try {
|
|
161
|
+
localAgents = args.agentDir
|
|
162
|
+
? listAgentsFromDir(args.agentDir)
|
|
163
|
+
: listLocalAgents(process.cwd());
|
|
164
|
+
} catch (err) {
|
|
165
|
+
process.stderr.write(`${YELLOW}⚠ ${err.message}${RESET}\n`);
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
const selected = args.agentSlug
|
|
169
|
+
? localAgents.filter(agent => agent.slug === args.agentSlug || agent.name === args.agentSlug)
|
|
170
|
+
: localAgents;
|
|
171
|
+
|
|
172
|
+
if (selected.length === 0) {
|
|
173
|
+
const target = args.agentSlug ? ` matching '${args.agentSlug}'` : '';
|
|
174
|
+
process.stderr.write(`${YELLOW}⚠ No local agents${target} found.${RESET}\n`);
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const creds = getAuth();
|
|
180
|
+
const result = await syncAgentsToBackend({
|
|
181
|
+
backendUrl: getBaseUrl(),
|
|
182
|
+
token: creds.token,
|
|
183
|
+
agents: selected,
|
|
184
|
+
});
|
|
185
|
+
const synced = result.synced ?? selected.length;
|
|
186
|
+
process.stderr.write(`${GREEN}✓ Synced ${synced} agent${synced === 1 ? '' : 's'} to Supabase.${RESET}\n`);
|
|
187
|
+
process.stdout.write(JSON.stringify({ synced, agents: result.agents || [] }, null, 2) + '\n');
|
|
188
|
+
} catch (err) {
|
|
189
|
+
process.stderr.write(`${RED}✗ Agent sync failed: ${err.message}${RESET}\n`);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function listAgentsFromDir(dir) {
|
|
195
|
+
const agents = [];
|
|
196
|
+
let entries;
|
|
197
|
+
try {
|
|
198
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
199
|
+
} catch {
|
|
200
|
+
throw new Error(`No agent directory found: ${dir}`);
|
|
201
|
+
}
|
|
202
|
+
for (const entry of entries) {
|
|
203
|
+
if (!entry.isFile()) continue;
|
|
204
|
+
const ext = path.extname(entry.name);
|
|
205
|
+
if (!['.yaml', '.yml', '.json', '.md'].includes(ext)) continue;
|
|
206
|
+
const filePath = path.join(dir, entry.name);
|
|
207
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
208
|
+
const agent = parseAgentDefinition(content, ext);
|
|
209
|
+
const spec = agentToSpec(agent);
|
|
210
|
+
agents.push({
|
|
211
|
+
...agent,
|
|
212
|
+
slug: spec.slug,
|
|
213
|
+
spec,
|
|
214
|
+
source: filePath,
|
|
215
|
+
source_scope: 'project',
|
|
216
|
+
content_hash: agentContentHash(agent),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return agents;
|
|
220
|
+
}
|