@axplusb/kepler 0.0.1 → 1.0.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 +82 -0
- package/package.json +36 -4
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -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 +52 -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/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 +142 -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 +159 -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 +52 -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 +182 -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/claude-reader.ts +594 -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 +89 -0
- package/src/agents/parser.mjs +98 -0
- package/src/agents/teams.mjs +123 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +277 -0
- package/src/config/cli-args.mjs +173 -0
- package/src/config/env.mjs +263 -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 +270 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/core/agent-loop.mjs +480 -0
- package/src/core/approval.mjs +273 -0
- package/src/core/backend-url.mjs +57 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +149 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/headless.mjs +168 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +351 -0
- package/src/core/local-agent.mjs +429 -0
- package/src/core/local-store.mjs +325 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +98 -0
- package/src/core/pricing.mjs +314 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/safety.mjs +200 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +317 -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 +367 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +135 -0
- package/src/core/tool-executor.mjs +725 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +370 -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/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +573 -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/loader.mjs +147 -0
- package/src/skills/runner.mjs +55 -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 +421 -0
- package/src/terminal/main.mjs +150 -0
- package/src/terminal/repl.mjs +1484 -0
- package/src/terminal/tool-display.mjs +58 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +148 -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/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +132 -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/banner.mjs +237 -0
- package/src/ui/commands.mjs +499 -0
- package/src/ui/formatter.mjs +379 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/slash-commands.mjs +258 -0
- package/index.js +0 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook Engine — pre/post tool use and stop hooks.
|
|
3
|
+
*
|
|
4
|
+
* Based on Claude Code's hooks system (6 event types):
|
|
5
|
+
* - PreToolUse: can block tool execution
|
|
6
|
+
* - PostToolUse: can modify results
|
|
7
|
+
* - Stop: can prevent the agent from stopping
|
|
8
|
+
* - Notification: inform external systems
|
|
9
|
+
* - PrePrompt: modify user input
|
|
10
|
+
* - PostResponse: modify assistant output
|
|
11
|
+
*
|
|
12
|
+
* Hooks are defined in settings.json under the "hooks" key.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
|
|
17
|
+
export class HookEngine {
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} hooksConfig - hooks configuration from settings
|
|
20
|
+
*/
|
|
21
|
+
constructor(hooksConfig = {}) {
|
|
22
|
+
this.hooks = hooksConfig;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Run pre-tool-use hooks. Returns { allow, message }.
|
|
27
|
+
* If any hook returns deny, the tool call is blocked.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} toolName - name of the tool being called
|
|
30
|
+
* @param {object} input - tool input arguments
|
|
31
|
+
* @returns {Promise<{allow: boolean, message?: string}>}
|
|
32
|
+
*/
|
|
33
|
+
async runPreToolUse(toolName, input) {
|
|
34
|
+
const hooks = this._getHooks('PreToolUse');
|
|
35
|
+
for (const hook of hooks) {
|
|
36
|
+
// Check if hook applies to this tool
|
|
37
|
+
if (hook.toolName && hook.toolName !== toolName) continue;
|
|
38
|
+
|
|
39
|
+
const result = await this._executeHook(hook, {
|
|
40
|
+
event: 'PreToolUse',
|
|
41
|
+
toolName,
|
|
42
|
+
input,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (result?.decision === 'deny' || result?.decision === 'block') {
|
|
46
|
+
return { allow: false, message: result.message || `Blocked by hook: ${hook.name || 'unnamed'}` };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { allow: true };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Run post-tool-use hooks. Can modify the result.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} toolName - name of the tool that was called
|
|
56
|
+
* @param {*} result - tool execution result
|
|
57
|
+
* @returns {Promise<*>} possibly modified result
|
|
58
|
+
*/
|
|
59
|
+
async runPostToolUse(toolName, result) {
|
|
60
|
+
const hooks = this._getHooks('PostToolUse');
|
|
61
|
+
let current = result;
|
|
62
|
+
for (const hook of hooks) {
|
|
63
|
+
if (hook.toolName && hook.toolName !== toolName) continue;
|
|
64
|
+
|
|
65
|
+
const hookResult = await this._executeHook(hook, {
|
|
66
|
+
event: 'PostToolUse',
|
|
67
|
+
toolName,
|
|
68
|
+
result: current,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (hookResult?.modifiedResult !== undefined) {
|
|
72
|
+
current = hookResult.modifiedResult;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return current;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run stop hooks. Returns true if stop should proceed, false to continue.
|
|
80
|
+
*
|
|
81
|
+
* @returns {Promise<boolean>} whether to allow stopping
|
|
82
|
+
*/
|
|
83
|
+
async runStop() {
|
|
84
|
+
const hooks = this._getHooks('Stop');
|
|
85
|
+
for (const hook of hooks) {
|
|
86
|
+
const result = await this._executeHook(hook, { event: 'Stop' });
|
|
87
|
+
if (result?.preventStop) {
|
|
88
|
+
return false; // do not stop
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return true; // allow stop
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Run notification hooks (fire-and-forget).
|
|
96
|
+
* @param {string} event - notification event name
|
|
97
|
+
* @param {object} data - event data
|
|
98
|
+
*/
|
|
99
|
+
async runNotification(event, data) {
|
|
100
|
+
const hooks = this._getHooks('Notification');
|
|
101
|
+
for (const hook of hooks) {
|
|
102
|
+
try {
|
|
103
|
+
await this._executeHook(hook, { event, ...data });
|
|
104
|
+
} catch {
|
|
105
|
+
// Notifications are best-effort
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get hooks for a given event type.
|
|
112
|
+
* @param {string} eventType
|
|
113
|
+
* @returns {Array}
|
|
114
|
+
*/
|
|
115
|
+
_getHooks(eventType) {
|
|
116
|
+
if (!this.hooks || !this.hooks[eventType]) return [];
|
|
117
|
+
const hooks = this.hooks[eventType];
|
|
118
|
+
return Array.isArray(hooks) ? hooks : [hooks];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Execute a single hook. Supports command (shell) and function hooks.
|
|
123
|
+
*
|
|
124
|
+
* @param {object} hook - hook definition
|
|
125
|
+
* @param {object} context - execution context
|
|
126
|
+
* @returns {Promise<object|null>}
|
|
127
|
+
*/
|
|
128
|
+
async _executeHook(hook, context) {
|
|
129
|
+
try {
|
|
130
|
+
if (hook.command) {
|
|
131
|
+
const env = {
|
|
132
|
+
...process.env,
|
|
133
|
+
HOOK_EVENT: context.event,
|
|
134
|
+
HOOK_TOOL: context.toolName || '',
|
|
135
|
+
HOOK_INPUT: JSON.stringify(context.input || {}),
|
|
136
|
+
};
|
|
137
|
+
const output = execSync(hook.command, {
|
|
138
|
+
encoding: 'utf-8',
|
|
139
|
+
timeout: hook.timeout || 10000,
|
|
140
|
+
env,
|
|
141
|
+
});
|
|
142
|
+
try {
|
|
143
|
+
return JSON.parse(output.trim());
|
|
144
|
+
} catch {
|
|
145
|
+
return { output: output.trim() };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof hook.handler === 'function') {
|
|
150
|
+
return await hook.handler(context);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return null;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (hook.failOpen !== false) {
|
|
156
|
+
// Default: fail open (allow)
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return { decision: 'deny', message: `Hook error: ${err.message}` };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @devtarang/orca v1.0.1 — Orca AI Coding Agent CLI
|
|
4
|
+
*
|
|
5
|
+
* Phase 3: Hybrid local/remote/auto + advanced features.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Load .env file from cwd or ~/.orca/.env
|
|
9
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
|
|
13
|
+
for (const envPath of [join(process.cwd(), '.env'), join(homedir(), '.orca', '.env')]) {
|
|
14
|
+
if (existsSync(envPath)) {
|
|
15
|
+
for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
|
|
16
|
+
const match = line.match(/^\s*([\w]+)\s*=\s*(.+?)\s*$/);
|
|
17
|
+
if (match && !process.env[match[1]]) {
|
|
18
|
+
process.env[match[1]] = match[2];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
import { TarangStreamClient, EVENT_TYPES } from './core/stream-client.mjs';
|
|
26
|
+
import { LocalAgent } from './core/local-agent.mjs';
|
|
27
|
+
import { createToolExecutor } from './core/tool-executor.mjs';
|
|
28
|
+
import { TarangAuth } from './auth/tarang-auth.mjs';
|
|
29
|
+
import { ApprovalManager } from './core/approval.mjs';
|
|
30
|
+
import { SessionManager } from './core/session-manager.mjs';
|
|
31
|
+
import { EventFormatter } from './ui/formatter.mjs';
|
|
32
|
+
import { handleSlashCommand, COMMANDS } from './ui/slash-commands.mjs';
|
|
33
|
+
import { selectMode } from './core/mode-selector.mjs';
|
|
34
|
+
import { printBanner, printProjectInfo, printHints, printAuthStatus, printStyledConfig, printGoodbye } from './ui/banner.mjs';
|
|
35
|
+
import { ContextRetriever } from './context/retriever.mjs';
|
|
36
|
+
import { loadSettings } from './config/settings.mjs';
|
|
37
|
+
|
|
38
|
+
const VERSION = '1.0.1';
|
|
39
|
+
|
|
40
|
+
// ── Arg Parsing (consolidated from index.mjs + cli-args.mjs) ──
|
|
41
|
+
|
|
42
|
+
function parseArgs(argv) {
|
|
43
|
+
const args = {
|
|
44
|
+
// Commands
|
|
45
|
+
command: null, instruction: null,
|
|
46
|
+
// Tarang mode flags
|
|
47
|
+
verbose: false, yes: false, plan: false, strict: false,
|
|
48
|
+
local: false, remote: false, debug: false,
|
|
49
|
+
version: false, help: false,
|
|
50
|
+
// Config subcommand flags
|
|
51
|
+
showConfig: false, openRouterKey: null, anthropicKey: null,
|
|
52
|
+
backendUrl: null, mode: null,
|
|
53
|
+
// Extended flags (from cli-args.mjs)
|
|
54
|
+
permissionMode: null,
|
|
55
|
+
outputFormat: null,
|
|
56
|
+
systemPrompt: null,
|
|
57
|
+
addDirs: [],
|
|
58
|
+
maxTurns: null,
|
|
59
|
+
allowedTools: null,
|
|
60
|
+
disallowedTools: null,
|
|
61
|
+
};
|
|
62
|
+
let i = 0;
|
|
63
|
+
while (i < argv.length) {
|
|
64
|
+
const arg = argv[i];
|
|
65
|
+
switch (arg) {
|
|
66
|
+
// Version / help
|
|
67
|
+
case '--version': case '-V': args.version = true; break;
|
|
68
|
+
case '--help': case '-h': args.help = true; break;
|
|
69
|
+
// Behavior flags
|
|
70
|
+
case '--verbose': case '-v': args.verbose = true; break;
|
|
71
|
+
case '--debug': case '-d': args.debug = true; args.verbose = true; break;
|
|
72
|
+
case '--yes': case '-y': args.yes = true; break;
|
|
73
|
+
case '--plan': args.plan = true; break;
|
|
74
|
+
case '--strict': args.strict = true; break;
|
|
75
|
+
// Mode flags
|
|
76
|
+
case '--local': args.local = true; break;
|
|
77
|
+
case '--remote': args.remote = true; break;
|
|
78
|
+
case '--mode': args.mode = argv[++i]; break;
|
|
79
|
+
// Commands
|
|
80
|
+
case 'login': args.command = 'login'; break;
|
|
81
|
+
case 'resume': args.command = 'resume'; break;
|
|
82
|
+
case 'config': args.command = 'config'; break;
|
|
83
|
+
case 'configure': args.command = 'configure'; break;
|
|
84
|
+
case 'sync': args.command = 'sync'; break;
|
|
85
|
+
// Config flags
|
|
86
|
+
case '--show': args.showConfig = true; break;
|
|
87
|
+
case '--openrouter-key': case '-k': args.openRouterKey = argv[++i]; break;
|
|
88
|
+
case '--anthropic-key': args.anthropicKey = argv[++i]; break;
|
|
89
|
+
case '--openai-key': args.openaiKey = argv[++i]; break;
|
|
90
|
+
case '--google-key': args.googleKey = argv[++i]; break;
|
|
91
|
+
case '--gateway': args.gateway = argv[++i]; break;
|
|
92
|
+
// --backend-url removed: use TARANG_ENV
|
|
93
|
+
// --model removed: use tarang configure (web settings)
|
|
94
|
+
// Extended flags
|
|
95
|
+
case '--permission-mode': args.permissionMode = argv[++i]; break;
|
|
96
|
+
case '--print': case '-p': args.instruction = argv[++i]; break;
|
|
97
|
+
case '--output-format': args.outputFormat = argv[++i]; break;
|
|
98
|
+
case '--system-prompt': args.systemPrompt = argv[++i]; break;
|
|
99
|
+
case '--add-dir': args.addDirs.push(argv[++i]); break;
|
|
100
|
+
case '--max-turns': args.maxTurns = parseInt(argv[++i], 10); break;
|
|
101
|
+
case '--allowedTools': args.allowedTools = argv[++i]?.split(',').map(s => s.trim()); break;
|
|
102
|
+
case '--disallowedTools': args.disallowedTools = argv[++i]?.split(',').map(s => s.trim()); break;
|
|
103
|
+
default:
|
|
104
|
+
if (!arg.startsWith('-') && !args.command && !args.instruction) args.instruction = arg;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
if (!args.verbose && process.env.TARANG_VERBOSE === '1') args.verbose = true;
|
|
110
|
+
if (!args.yes && process.env.TARANG_YES === '1') args.yes = true;
|
|
111
|
+
return args;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function printUsage() {
|
|
115
|
+
printBanner();
|
|
116
|
+
|
|
117
|
+
const B = '\x1b[1m', C = '\x1b[36m', D = '\x1b[2m', G = '\x1b[32m', R = '\x1b[0m';
|
|
118
|
+
|
|
119
|
+
process.stderr.write(`${B}USAGE${R}\n`);
|
|
120
|
+
process.stderr.write(` ${C}orca "instruction"${R} Execute instruction\n`);
|
|
121
|
+
process.stderr.write(` ${C}orca${R} Interactive mode (REPL)\n`);
|
|
122
|
+
process.stderr.write(` ${C}orca login${R} Authenticate via GitHub OAuth\n`);
|
|
123
|
+
process.stderr.write(` ${C}orca configure${R} Open settings in browser\n`);
|
|
124
|
+
process.stderr.write(` ${C}orca config --show${R} Display local configuration\n`);
|
|
125
|
+
process.stderr.write(` ${C}orca resume${R} Resume a paused session\n`);
|
|
126
|
+
process.stderr.write('\n');
|
|
127
|
+
process.stderr.write(`${B}MODE FLAGS${R}\n`);
|
|
128
|
+
process.stderr.write(` ${G}--local${R} Direct LLM API ${D}(<100ms, offline)${R}\n`);
|
|
129
|
+
process.stderr.write(` ${G}--remote${R} SSE backend ${D}(multi-agent orchestration)${R}\n`);
|
|
130
|
+
process.stderr.write(` ${G}--mode <auto|local|remote>${R} Set mode explicitly\n`);
|
|
131
|
+
process.stderr.write(` ${D}(default: auto-select based on task complexity)${R}\n`);
|
|
132
|
+
process.stderr.write('\n');
|
|
133
|
+
process.stderr.write(`${B}MODEL FLAGS${R}\n`);
|
|
134
|
+
process.stderr.write(` ${G}--system-prompt <text>${R} Override system prompt\n`);
|
|
135
|
+
process.stderr.write(` ${G}--max-turns <n>${R} Maximum conversation turns\n`);
|
|
136
|
+
process.stderr.write(` ${D}Models are configured via: orca configure${R}\n`);
|
|
137
|
+
process.stderr.write('\n');
|
|
138
|
+
process.stderr.write(`${B}PERMISSION FLAGS${R}\n`);
|
|
139
|
+
process.stderr.write(` ${G}--yes, -y${R} Auto-approve all operations\n`);
|
|
140
|
+
process.stderr.write(` ${G}--plan${R} Read-only mode (block all writes)\n`);
|
|
141
|
+
process.stderr.write(` ${G}--strict${R} Deny tools not in allowed list\n`);
|
|
142
|
+
process.stderr.write(` ${G}--permission-mode <mode>${R} Permission mode ${D}(auto, plan, strict)${R}\n`);
|
|
143
|
+
process.stderr.write(` ${G}--allowedTools <tools>${R} Comma-separated allowed tools\n`);
|
|
144
|
+
process.stderr.write(` ${G}--disallowedTools <tools>${R} Comma-separated denied tools\n`);
|
|
145
|
+
process.stderr.write('\n');
|
|
146
|
+
process.stderr.write(`${B}OUTPUT FLAGS${R}\n`);
|
|
147
|
+
process.stderr.write(` ${G}--print, -p <prompt>${R} Non-interactive: run prompt and exit\n`);
|
|
148
|
+
process.stderr.write(` ${G}--output-format <fmt>${R} Output format: text, json, stream-json\n`);
|
|
149
|
+
process.stderr.write(` ${G}--verbose, -v${R} Show tool details and thinking\n`);
|
|
150
|
+
process.stderr.write(` ${G}--debug, -d${R} Debug mode ${D}(implies verbose)${R}\n`);
|
|
151
|
+
process.stderr.write('\n');
|
|
152
|
+
process.stderr.write(`${B}OTHER FLAGS${R}\n`);
|
|
153
|
+
process.stderr.write(` ${G}--version, -V${R} Show version\n`);
|
|
154
|
+
process.stderr.write(` ${G}--help, -h${R} Show this help\n`);
|
|
155
|
+
process.stderr.write(` ${G}--add-dir <dir>${R} Additional CLAUDE.md directory\n`);
|
|
156
|
+
process.stderr.write('\n');
|
|
157
|
+
process.stderr.write(`${B}SLASH COMMANDS${R} ${D}(interactive mode)${R}\n`);
|
|
158
|
+
for (const [k, v] of Object.entries(COMMANDS)) {
|
|
159
|
+
process.stderr.write(` ${C}${k.padEnd(14)}${R} ${v.description}\n`);
|
|
160
|
+
}
|
|
161
|
+
process.stderr.write('\n');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── Execute ─────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
async function executeInstruction(executor, instruction, formatter, sessionMgr) {
|
|
167
|
+
sessionMgr.start(instruction);
|
|
168
|
+
for await (const event of executor) {
|
|
169
|
+
formatter.render(event);
|
|
170
|
+
if (event.type === 'session_info') sessionMgr.setSessionInfo(event.data);
|
|
171
|
+
if (event.type === 'tool_call' || event.type === 'tool_request') sessionMgr.recordToolCall(event.data?.tool);
|
|
172
|
+
if (event.type === 'complete') sessionMgr.complete(event.data?.summary);
|
|
173
|
+
if (event.type === 'error' && event.data?.fatal) sessionMgr.fail(event.data?.message);
|
|
174
|
+
if (event.type === 'cancelled') sessionMgr.cancel();
|
|
175
|
+
if (event.type === 'paused') sessionMgr.pause();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── REPL ────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
// Inline REPL removed — now delegates to startTerminalRepl() from terminal/repl.mjs
|
|
182
|
+
// which has full markdown rendering, turn summaries, cost display, and ANSI UI.
|
|
183
|
+
|
|
184
|
+
import { startTerminalRepl } from './terminal/repl.mjs';
|
|
185
|
+
|
|
186
|
+
// ── Main ────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
async function main() {
|
|
189
|
+
const args = parseArgs(process.argv.slice(2));
|
|
190
|
+
if (args.version) { console.log(`@devtarang/orca ${VERSION}`); process.exit(0); }
|
|
191
|
+
if (args.help) { printUsage(); process.exit(0); }
|
|
192
|
+
|
|
193
|
+
const auth = new TarangAuth();
|
|
194
|
+
|
|
195
|
+
if (args.command === 'login') {
|
|
196
|
+
printBanner();
|
|
197
|
+
process.stderr.write('\x1b[1mAuthentication\x1b[0m\n\n');
|
|
198
|
+
await auth.login();
|
|
199
|
+
process.stderr.write('\n\x1b[32m✓ Login successful!\x1b[0m\n');
|
|
200
|
+
// Sync settings from web after login
|
|
201
|
+
try {
|
|
202
|
+
process.stderr.write('\x1b[2mSyncing settings from server...\x1b[0m\n');
|
|
203
|
+
const remote = await auth.syncSettings();
|
|
204
|
+
process.stderr.write(`\x1b[32m✓ Settings synced\x1b[0m ${remote.gateway_type ? `(gateway: ${remote.gateway_type})` : ''}\n`);
|
|
205
|
+
} catch {
|
|
206
|
+
process.stderr.write('\x1b[2mSettings sync skipped — configure at devtarang.ai/dashboard/settings\x1b[0m\n');
|
|
207
|
+
}
|
|
208
|
+
process.stderr.write('\n');
|
|
209
|
+
// Fall through to REPL — user starts working right away
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (args.command === 'sync') {
|
|
213
|
+
printBanner();
|
|
214
|
+
process.stderr.write('\x1b[1mSyncing settings...\x1b[0m\n\n');
|
|
215
|
+
try {
|
|
216
|
+
const remote = await auth.syncSettings();
|
|
217
|
+
process.stderr.write(`\x1b[32m✓ Gateway:\x1b[0m ${remote.gateway_type}\n`);
|
|
218
|
+
if (remote.models?.orchestrator) process.stderr.write(`\x1b[32m✓ Orchestrator:\x1b[0m ${remote.models.orchestrator}\n`);
|
|
219
|
+
if (remote.models?.reasoning) process.stderr.write(`\x1b[32m✓ Coding:\x1b[0m ${remote.models.reasoning}\n`);
|
|
220
|
+
if (remote.models?.local) process.stderr.write(`\x1b[32m✓ Local:\x1b[0m ${remote.models.local}\n`);
|
|
221
|
+
if (remote.configured_providers?.length) process.stderr.write(`\x1b[32m✓ Providers:\x1b[0m ${remote.configured_providers.join(', ')}\n`);
|
|
222
|
+
process.stderr.write('\n\x1b[32m✓ Settings saved to ~/.orca/config.json\x1b[0m\n');
|
|
223
|
+
} catch (err) {
|
|
224
|
+
process.stderr.write(`\x1b[31m✗ ${err.message}\x1b[0m\n`);
|
|
225
|
+
}
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (args.command === 'configure') {
|
|
230
|
+
printBanner();
|
|
231
|
+
const { resolveWebUrl } = await import('./core/backend-url.mjs');
|
|
232
|
+
const webUrl = resolveWebUrl();
|
|
233
|
+
const settingsUrl = `${webUrl}/dashboard/settings?tab=providers&source=cli`;
|
|
234
|
+
process.stderr.write('\x1b[36mOpening settings in browser...\x1b[0m\n');
|
|
235
|
+
process.stderr.write(`\x1b[2m${settingsUrl}\x1b[0m\n`);
|
|
236
|
+
const openCmd = process.platform === 'darwin' ? 'open' :
|
|
237
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
238
|
+
const { exec } = await import('node:child_process');
|
|
239
|
+
exec(`${openCmd} "${settingsUrl}"`, () => {});
|
|
240
|
+
process.stderr.write('\n\x1b[2mConfigure your provider, models, and CLI preferences in the browser.\x1b[0m\n');
|
|
241
|
+
process.stderr.write('\x1b[2mChanges sync automatically to the backend.\x1b[0m\n');
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (args.command === 'config') {
|
|
246
|
+
let changed = false;
|
|
247
|
+
if (args.openRouterKey) { auth.saveOpenRouterKey(args.openRouterKey); process.stderr.write('\x1b[32m✓ OpenRouter key saved.\x1b[0m\n'); changed = true; }
|
|
248
|
+
if (args.anthropicKey) { auth.saveAnthropicKey(args.anthropicKey); process.stderr.write('\x1b[32m✓ Anthropic key saved.\x1b[0m\n'); changed = true; }
|
|
249
|
+
if (args.openaiKey) { auth.saveOpenAIKey(args.openaiKey); process.stderr.write('\x1b[32m✓ OpenAI key saved.\x1b[0m\n'); changed = true; }
|
|
250
|
+
if (args.googleKey) { auth.saveGoogleKey(args.googleKey); process.stderr.write('\x1b[32m✓ Google AI key saved.\x1b[0m\n'); changed = true; }
|
|
251
|
+
if (args.gateway) { auth.saveCredentials({ gateway_type: args.gateway }); process.stderr.write(`\x1b[32m✓ Gateway set to ${args.gateway}\x1b[0m\n`); changed = true; }
|
|
252
|
+
if (args.mode) { auth.setMode(args.mode); process.stderr.write(`\x1b[32m✓ Mode set to ${args.mode}\x1b[0m\n`); changed = true; }
|
|
253
|
+
if (args.showConfig || !changed) {
|
|
254
|
+
auth.printConfig();
|
|
255
|
+
}
|
|
256
|
+
process.exit(0);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Load settings (user ~/.claude/settings.json + project .claude/settings.json + local)
|
|
260
|
+
const settings = await loadSettings();
|
|
261
|
+
|
|
262
|
+
/** Re-read credentials from disk (so /login updates take effect). */
|
|
263
|
+
function freshCreds() {
|
|
264
|
+
const c = auth.loadCredentials();
|
|
265
|
+
return {
|
|
266
|
+
token: process.env.TARANG_TOKEN || c.token,
|
|
267
|
+
openRouterKey: process.env.OPENROUTER_API_KEY || c.openRouterKey,
|
|
268
|
+
anthropicKey: process.env.ANTHROPIC_API_KEY || c.anthropicKey,
|
|
269
|
+
openaiKey: process.env.OPENAI_API_KEY || c.openaiKey,
|
|
270
|
+
googleKey: process.env.GOOGLE_API_KEY || c.googleKey,
|
|
271
|
+
backendUrl: c.backendUrl,
|
|
272
|
+
gatewayType: c.gatewayType,
|
|
273
|
+
models: c.models,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Initial load — used for settings and startup checks
|
|
278
|
+
const initCreds = freshCreds();
|
|
279
|
+
|
|
280
|
+
// Apply settings as defaults (CLI flags override settings)
|
|
281
|
+
if (!args.verbose && settings.debugMode) args.verbose = true;
|
|
282
|
+
if (!args.permissionMode && settings.permissions?.defaultMode !== 'default') {
|
|
283
|
+
args.permissionMode = settings.permissions.defaultMode;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const toolExecutor = createToolExecutor();
|
|
287
|
+
const approval = new ApprovalManager({ autoApprove: args.yes, planMode: args.plan });
|
|
288
|
+
const formatter = new EventFormatter({ verbose: args.verbose });
|
|
289
|
+
const sessionMgr = new SessionManager();
|
|
290
|
+
const contextRetriever = new ContextRetriever(process.cwd());
|
|
291
|
+
|
|
292
|
+
/** Create an executor (local or remote) for a given instruction. */
|
|
293
|
+
async function createExecutor(instruction, messages = null) {
|
|
294
|
+
// Always re-read credentials so /login changes are picked up
|
|
295
|
+
const creds = freshCreds();
|
|
296
|
+
const mode = await selectMode(instruction, args, { ...creds, backendUrl: creds.backendUrl });
|
|
297
|
+
|
|
298
|
+
if (mode === 'local') {
|
|
299
|
+
// Model priority: CLI flag > synced web setting > settings.json > default
|
|
300
|
+
const localModel = creds.models?.local || settings.model || 'anthropic/claude-sonnet-4-6';
|
|
301
|
+
if (args.verbose) process.stderr.write(`\x1b[2m[mode] local (${localModel})\x1b[0m\n`);
|
|
302
|
+
|
|
303
|
+
// Pick the right API key based on the model/gateway
|
|
304
|
+
let apiKey = creds.anthropicKey;
|
|
305
|
+
let orKey = creds.openRouterKey;
|
|
306
|
+
if (creds.gatewayType === 'openai') apiKey = creds.openaiKey;
|
|
307
|
+
if (creds.gatewayType === 'googleai') apiKey = creds.googleKey;
|
|
308
|
+
|
|
309
|
+
return new LocalAgent({
|
|
310
|
+
apiKey,
|
|
311
|
+
openRouterKey: orKey,
|
|
312
|
+
model: localModel,
|
|
313
|
+
toolExecutor,
|
|
314
|
+
verbose: args.verbose,
|
|
315
|
+
cwd: process.cwd(),
|
|
316
|
+
systemPromptOverride: args.systemPrompt,
|
|
317
|
+
maxTurns: args.maxTurns,
|
|
318
|
+
stagnationDetection: settings.stagnationDetection,
|
|
319
|
+
stagnationThreshold: settings.stagnationThreshold,
|
|
320
|
+
}).execute(instruction, { cwd: process.cwd() });
|
|
321
|
+
} else {
|
|
322
|
+
if (args.verbose) process.stderr.write('\x1b[2m[mode] remote\x1b[0m\n');
|
|
323
|
+
|
|
324
|
+
// Retrieve BM25 context to send to backend
|
|
325
|
+
let indexedContext = {};
|
|
326
|
+
try {
|
|
327
|
+
const chunks = contextRetriever.retrieve(instruction, 8);
|
|
328
|
+
if (chunks.length > 0) {
|
|
329
|
+
indexedContext = {
|
|
330
|
+
indexed: chunks.map(c => ({ id: c.id, score: c.score, text: c.text })),
|
|
331
|
+
};
|
|
332
|
+
if (args.verbose) process.stderr.write(`\x1b[2m[context] ${chunks.length} chunks from BM25 index\x1b[0m\n`);
|
|
333
|
+
}
|
|
334
|
+
} catch {
|
|
335
|
+
// No index available — send without context
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const client = new TarangStreamClient({
|
|
339
|
+
baseUrl: creds.backendUrl, token: creds.token, toolExecutor,
|
|
340
|
+
verbose: args.verbose, approvalManager: approval,
|
|
341
|
+
});
|
|
342
|
+
// Cancel on SIGINT but don't exit (REPL handles exit)
|
|
343
|
+
const sigHandler = async () => { await client.cancel().catch(() => {}); };
|
|
344
|
+
process.once('SIGINT', sigHandler);
|
|
345
|
+
return client.execute(instruction, { cwd: process.cwd(), ...indexedContext }, messages);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (args.command === 'resume') {
|
|
350
|
+
const state = sessionMgr.loadState();
|
|
351
|
+
if (!state || state.status === 'completed') { process.stderr.write('No resumable session.\n'); process.exit(1); }
|
|
352
|
+
const resumeCreds = freshCreds();
|
|
353
|
+
const client = new TarangStreamClient({ baseUrl: resumeCreds.backendUrl, token: resumeCreds.token, toolExecutor, verbose: args.verbose, approvalManager: approval });
|
|
354
|
+
client.currentTaskId = state.task_id;
|
|
355
|
+
await client.resume();
|
|
356
|
+
for await (const event of client.execute(state.instruction)) formatter.render(event);
|
|
357
|
+
process.exit(0);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (args.instruction) {
|
|
361
|
+
const exec = await createExecutor(args.instruction);
|
|
362
|
+
await executeInstruction(exec, args.instruction, formatter, sessionMgr);
|
|
363
|
+
process.stdout.write('\n');
|
|
364
|
+
process.exit(0);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
await startTerminalRepl();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
main().catch(err => { process.stderr.write(`\x1b[31mFatal: ${err.message}\x1b[0m\n`); process.exit(1); });
|