duocode 0.1.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/.env.example +36 -0
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/ast/context.d.ts +16 -0
- package/dist/ast/context.js +37 -0
- package/dist/ast/context.js.map +1 -0
- package/dist/ast/diff.d.ts +27 -0
- package/dist/ast/diff.js +44 -0
- package/dist/ast/diff.js.map +1 -0
- package/dist/ast/locks.d.ts +47 -0
- package/dist/ast/locks.js +88 -0
- package/dist/ast/locks.js.map +1 -0
- package/dist/ast/merge.d.ts +22 -0
- package/dist/ast/merge.js +120 -0
- package/dist/ast/merge.js.map +1 -0
- package/dist/ast/ownership.d.ts +31 -0
- package/dist/ast/ownership.js +111 -0
- package/dist/ast/ownership.js.map +1 -0
- package/dist/ast/parser.d.ts +44 -0
- package/dist/ast/parser.js +134 -0
- package/dist/ast/parser.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +423 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +63 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/duo.d.ts +9 -0
- package/dist/commands/duo.js +285 -0
- package/dist/commands/duo.js.map +1 -0
- package/dist/commands/github.d.ts +2 -0
- package/dist/commands/github.js +85 -0
- package/dist/commands/github.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +33 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/negotiation.d.ts +2 -0
- package/dist/commands/negotiation.js +160 -0
- package/dist/commands/negotiation.js.map +1 -0
- package/dist/commands/repl_commands.d.ts +26 -0
- package/dist/commands/repl_commands.js +226 -0
- package/dist/commands/repl_commands.js.map +1 -0
- package/dist/commands/shell.d.ts +1 -0
- package/dist/commands/shell.js +110 -0
- package/dist/commands/shell.js.map +1 -0
- package/dist/commands/start.d.ts +2 -0
- package/dist/commands/start.js +231 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/task.d.ts +2 -0
- package/dist/commands/task.js +215 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/config/loader.d.ts +193 -0
- package/dist/config/loader.js +106 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/context/project_context.d.ts +79 -0
- package/dist/context/project_context.js +292 -0
- package/dist/context/project_context.js.map +1 -0
- package/dist/context/token_budget.d.ts +35 -0
- package/dist/context/token_budget.js +81 -0
- package/dist/context/token_budget.js.map +1 -0
- package/dist/db/queries.d.ts +121 -0
- package/dist/db/queries.js +109 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.d.ts +110 -0
- package/dist/db/schema.js +346 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/duo/duo_orchestrator.d.ts +50 -0
- package/dist/duo/duo_orchestrator.js +510 -0
- package/dist/duo/duo_orchestrator.js.map +1 -0
- package/dist/duo/duo_session.d.ts +47 -0
- package/dist/duo/duo_session.js +127 -0
- package/dist/duo/duo_session.js.map +1 -0
- package/dist/duo/duo_types.d.ts +168 -0
- package/dist/duo/duo_types.js +53 -0
- package/dist/duo/duo_types.js.map +1 -0
- package/dist/duo/session_store.d.ts +71 -0
- package/dist/duo/session_store.js +177 -0
- package/dist/duo/session_store.js.map +1 -0
- package/dist/git/worktree.d.ts +21 -0
- package/dist/git/worktree.js +86 -0
- package/dist/git/worktree.js.map +1 -0
- package/dist/github/cache.d.ts +23 -0
- package/dist/github/cache.js +67 -0
- package/dist/github/cache.js.map +1 -0
- package/dist/github/issues.d.ts +17 -0
- package/dist/github/issues.js +93 -0
- package/dist/github/issues.js.map +1 -0
- package/dist/github/mcp_client.d.ts +57 -0
- package/dist/github/mcp_client.js +214 -0
- package/dist/github/mcp_client.js.map +1 -0
- package/dist/github/sync.d.ts +11 -0
- package/dist/github/sync.js +65 -0
- package/dist/github/sync.js.map +1 -0
- package/dist/github/webhook.d.ts +25 -0
- package/dist/github/webhook.js +197 -0
- package/dist/github/webhook.js.map +1 -0
- package/dist/negotiation/index.d.ts +1 -0
- package/dist/negotiation/index.js +2 -0
- package/dist/negotiation/index.js.map +1 -0
- package/dist/negotiation/protocol.d.ts +62 -0
- package/dist/negotiation/protocol.js +188 -0
- package/dist/negotiation/protocol.js.map +1 -0
- package/dist/orchestrator/complexity_scorer.d.ts +2 -0
- package/dist/orchestrator/complexity_scorer.js +79 -0
- package/dist/orchestrator/complexity_scorer.js.map +1 -0
- package/dist/orchestrator/dependency_graph.d.ts +7 -0
- package/dist/orchestrator/dependency_graph.js +73 -0
- package/dist/orchestrator/dependency_graph.js.map +1 -0
- package/dist/orchestrator/intent_parser.d.ts +11 -0
- package/dist/orchestrator/intent_parser.js +116 -0
- package/dist/orchestrator/intent_parser.js.map +1 -0
- package/dist/orchestrator/task_runner.d.ts +56 -0
- package/dist/orchestrator/task_runner.js +181 -0
- package/dist/orchestrator/task_runner.js.map +1 -0
- package/dist/orchestrator/types.d.ts +44 -0
- package/dist/orchestrator/types.js +21 -0
- package/dist/orchestrator/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts +12 -0
- package/dist/providers/anthropic.js +258 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/auction.d.ts +42 -0
- package/dist/providers/auction.js +190 -0
- package/dist/providers/auction.js.map +1 -0
- package/dist/providers/base.d.ts +103 -0
- package/dist/providers/base.js +2 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/cost_tracker.d.ts +45 -0
- package/dist/providers/cost_tracker.js +111 -0
- package/dist/providers/cost_tracker.js.map +1 -0
- package/dist/providers/duo_pair_router.d.ts +11 -0
- package/dist/providers/duo_pair_router.js +67 -0
- package/dist/providers/duo_pair_router.js.map +1 -0
- package/dist/providers/factory.d.ts +7 -0
- package/dist/providers/factory.js +130 -0
- package/dist/providers/factory.js.map +1 -0
- package/dist/providers/grading_rubric.d.ts +37 -0
- package/dist/providers/grading_rubric.js +238 -0
- package/dist/providers/grading_rubric.js.map +1 -0
- package/dist/providers/openai.d.ts +12 -0
- package/dist/providers/openai.js +229 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +14 -0
- package/dist/providers/openrouter.js +178 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/performance_tracker.d.ts +21 -0
- package/dist/providers/performance_tracker.js +63 -0
- package/dist/providers/performance_tracker.js.map +1 -0
- package/dist/providers/registry_loader.d.ts +6 -0
- package/dist/providers/registry_loader.js +54 -0
- package/dist/providers/registry_loader.js.map +1 -0
- package/dist/providers/retry.d.ts +66 -0
- package/dist/providers/retry.js +203 -0
- package/dist/providers/retry.js.map +1 -0
- package/dist/providers/role_scorer.d.ts +16 -0
- package/dist/providers/role_scorer.js +16 -0
- package/dist/providers/role_scorer.js.map +1 -0
- package/dist/providers/router.d.ts +84 -0
- package/dist/providers/router.js +542 -0
- package/dist/providers/router.js.map +1 -0
- package/dist/security/credentials.d.ts +6 -0
- package/dist/security/credentials.js +16 -0
- package/dist/security/credentials.js.map +1 -0
- package/dist/setup/browser.d.ts +1 -0
- package/dist/setup/browser.js +12 -0
- package/dist/setup/browser.js.map +1 -0
- package/dist/setup/global_config.d.ts +14 -0
- package/dist/setup/global_config.js +54 -0
- package/dist/setup/global_config.js.map +1 -0
- package/dist/setup/wizard.d.ts +2 -0
- package/dist/setup/wizard.js +206 -0
- package/dist/setup/wizard.js.map +1 -0
- package/dist/tools/agent_loop.d.ts +38 -0
- package/dist/tools/agent_loop.js +72 -0
- package/dist/tools/agent_loop.js.map +1 -0
- package/dist/tools/approval.d.ts +64 -0
- package/dist/tools/approval.js +172 -0
- package/dist/tools/approval.js.map +1 -0
- package/dist/tools/checkpoint.d.ts +65 -0
- package/dist/tools/checkpoint.js +342 -0
- package/dist/tools/checkpoint.js.map +1 -0
- package/dist/tools/definitions.d.ts +13 -0
- package/dist/tools/definitions.js +103 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/diff_display.d.ts +46 -0
- package/dist/tools/diff_display.js +298 -0
- package/dist/tools/diff_display.js.map +1 -0
- package/dist/tools/executor.d.ts +12 -0
- package/dist/tools/executor.js +340 -0
- package/dist/tools/executor.js.map +1 -0
- package/dist/tools/permissions.d.ts +17 -0
- package/dist/tools/permissions.js +139 -0
- package/dist/tools/permissions.js.map +1 -0
- package/dist/tools/tool_types.d.ts +48 -0
- package/dist/tools/tool_types.js +7 -0
- package/dist/tools/tool_types.js.map +1 -0
- package/dist/ui/banner.d.ts +4 -0
- package/dist/ui/banner.js +104 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/callbacks.d.ts +30 -0
- package/dist/ui/callbacks.js +132 -0
- package/dist/ui/callbacks.js.map +1 -0
- package/dist/ui/colors.d.ts +14 -0
- package/dist/ui/colors.js +28 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/dashboard.d.ts +51 -0
- package/dist/ui/dashboard.js +181 -0
- package/dist/ui/dashboard.js.map +1 -0
- package/dist/ui/leaderboard.d.ts +16 -0
- package/dist/ui/leaderboard.js +43 -0
- package/dist/ui/leaderboard.js.map +1 -0
- package/dist/ui/logger.d.ts +28 -0
- package/dist/ui/logger.js +117 -0
- package/dist/ui/logger.js.map +1 -0
- package/dist/ui/progress.d.ts +16 -0
- package/dist/ui/progress.js +62 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/tokenizer.d.ts +5 -0
- package/dist/ui/tokenizer.js +54 -0
- package/dist/ui/tokenizer.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* Pre-action hook: ensures credentials are loaded from ~/.duocode/config.yaml.
|
|
4
|
+
* If no global config exists yet, runs the interactive setup wizard.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ensureInteractiveCredentials(_command: Command): Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { loadGlobalConfig, applyCredentialsToEnv, } from "../setup/global_config.js";
|
|
2
|
+
import { runSetupIfNeeded } from "../setup/wizard.js";
|
|
3
|
+
/**
|
|
4
|
+
* Pre-action hook: ensures credentials are loaded from ~/.duocode/config.yaml.
|
|
5
|
+
* If no global config exists yet, runs the interactive setup wizard.
|
|
6
|
+
*/
|
|
7
|
+
export async function ensureInteractiveCredentials(_command) {
|
|
8
|
+
const config = loadGlobalConfig();
|
|
9
|
+
if (config) {
|
|
10
|
+
applyCredentialsToEnv(config);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
// No saved config — run the wizard inline
|
|
14
|
+
await runSetupIfNeeded(process.cwd());
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/security/credentials.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,QAAiB;IAClE,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAElC,IAAI,MAAM,EAAE,CAAC;QACX,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,0CAA0C;IAC1C,MAAM,gBAAgB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function openBrowser(url: string): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
export function openBrowser(url) {
|
|
3
|
+
const cmd = process.platform === "darwin"
|
|
4
|
+
? `open "${url}"`
|
|
5
|
+
: process.platform === "win32"
|
|
6
|
+
? `start "" "${url}"`
|
|
7
|
+
: `xdg-open "${url}"`;
|
|
8
|
+
exec(cmd, () => {
|
|
9
|
+
// fire-and-forget — ignore errors (user sees the URL in terminal)
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/setup/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE1C,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,GAAG,GACP,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAC3B,CAAC,CAAC,SAAS,GAAG,GAAG;QACjB,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;YAC5B,CAAC,CAAC,aAAa,GAAG,GAAG;YACrB,CAAC,CAAC,aAAa,GAAG,GAAG,CAAC;IAE5B,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;QACb,kEAAkE;IACpE,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface GlobalConfig {
|
|
2
|
+
version: string;
|
|
3
|
+
credentials: {
|
|
4
|
+
openrouter_api_key?: string;
|
|
5
|
+
openai_api_key?: string;
|
|
6
|
+
anthropic_api_key?: string;
|
|
7
|
+
github_token?: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare function globalConfigPath(): string;
|
|
11
|
+
export declare function globalConfigExists(): boolean;
|
|
12
|
+
export declare function loadGlobalConfig(): GlobalConfig | null;
|
|
13
|
+
export declare function saveGlobalConfig(config: GlobalConfig): void;
|
|
14
|
+
export declare function applyCredentialsToEnv(config: GlobalConfig): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import YAML from "yaml";
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), ".duocode");
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.yaml");
|
|
7
|
+
export function globalConfigPath() {
|
|
8
|
+
return CONFIG_FILE;
|
|
9
|
+
}
|
|
10
|
+
export function globalConfigExists() {
|
|
11
|
+
return fs.existsSync(CONFIG_FILE);
|
|
12
|
+
}
|
|
13
|
+
export function loadGlobalConfig() {
|
|
14
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf8");
|
|
19
|
+
const parsed = YAML.parse(raw);
|
|
20
|
+
return parsed ?? null;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function saveGlobalConfig(config) {
|
|
27
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
28
|
+
const content = YAML.stringify(config);
|
|
29
|
+
fs.writeFileSync(CONFIG_FILE, content, { mode: 0o600 });
|
|
30
|
+
}
|
|
31
|
+
export function applyCredentialsToEnv(config) {
|
|
32
|
+
// OpenRouter uses the OpenAI-compatible API — set OPENAI_API_KEY + OPENAI_BASE_URL
|
|
33
|
+
if (config.credentials.openrouter_api_key) {
|
|
34
|
+
if (!process.env.OPENROUTER_API_KEY) {
|
|
35
|
+
process.env.OPENROUTER_API_KEY = config.credentials.openrouter_api_key;
|
|
36
|
+
}
|
|
37
|
+
if (!process.env.OPENAI_API_KEY) {
|
|
38
|
+
process.env.OPENAI_API_KEY = config.credentials.openrouter_api_key;
|
|
39
|
+
}
|
|
40
|
+
if (!process.env.OPENAI_BASE_URL) {
|
|
41
|
+
process.env.OPENAI_BASE_URL = "https://openrouter.ai/api/v1";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (config.credentials.openai_api_key && !process.env.OPENAI_API_KEY) {
|
|
45
|
+
process.env.OPENAI_API_KEY = config.credentials.openai_api_key;
|
|
46
|
+
}
|
|
47
|
+
if (config.credentials.anthropic_api_key && !process.env.ANTHROPIC_API_KEY) {
|
|
48
|
+
process.env.ANTHROPIC_API_KEY = config.credentials.anthropic_api_key;
|
|
49
|
+
}
|
|
50
|
+
if (config.credentials.github_token && !process.env.GITHUB_TOKEN) {
|
|
51
|
+
process.env.GITHUB_TOKEN = config.credentials.github_token;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=global_config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global_config.js","sourceRoot":"","sources":["../../src/setup/global_config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,MAAM,CAAC;AAYxB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AACvD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,UAAU,gBAAgB;IAC9B,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;QACtD,OAAO,MAAM,IAAI,IAAI,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAoB;IACnD,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACvC,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAoB;IACxD,mFAAmF;IACnF,IAAI,MAAM,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC;QAC1C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC;QACzE,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;YAChC,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC;QACrE,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,eAAe,GAAG,8BAA8B,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,CAAC,cAAc,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;QACrE,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,MAAM,CAAC,WAAW,CAAC,cAAc,CAAC;IACjE,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,CAAC,iBAAiB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC;QAC3E,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,MAAM,CAAC,WAAW,CAAC,iBAAiB,CAAC;IACvE,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,CAAC,YAAY,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QACjE,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC,YAAY,CAAC;IAC7D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { globalConfigExists, globalConfigPath, loadGlobalConfig, saveGlobalConfig, applyCredentialsToEnv, } from "./global_config.js";
|
|
5
|
+
import { findConfigPath, defaultConfigYaml, loadConfig } from "../config/loader.js";
|
|
6
|
+
import { initDatabase } from "../db/schema.js";
|
|
7
|
+
import { openBrowser } from "./browser.js";
|
|
8
|
+
import { R, B, DM, DCY, GRN, YLW, MAG } from "../ui/colors.js";
|
|
9
|
+
import { createLogger } from "../ui/logger.js";
|
|
10
|
+
const RESET = R;
|
|
11
|
+
const BOLD = B;
|
|
12
|
+
const DIM = DM;
|
|
13
|
+
const GREEN = GRN;
|
|
14
|
+
const CYAN = DCY;
|
|
15
|
+
const YELLOW = YLW;
|
|
16
|
+
const MAGENTA = MAG;
|
|
17
|
+
const log = createLogger("wizard");
|
|
18
|
+
function write(text) {
|
|
19
|
+
log.raw(text);
|
|
20
|
+
}
|
|
21
|
+
function ok(msg) {
|
|
22
|
+
log.raw(` ${GREEN}${BOLD}\u2713${RESET} ${msg}\n`);
|
|
23
|
+
}
|
|
24
|
+
function info(msg) {
|
|
25
|
+
log.raw(` ${DIM}${msg}${RESET}\n`);
|
|
26
|
+
}
|
|
27
|
+
function heading(msg) {
|
|
28
|
+
log.raw(`\n ${BOLD}${msg}${RESET}\n\n`);
|
|
29
|
+
}
|
|
30
|
+
async function ask(question, secret = false) {
|
|
31
|
+
if (secret && process.stdin.isTTY && process.stdout.isTTY) {
|
|
32
|
+
return askSecret(question);
|
|
33
|
+
}
|
|
34
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
35
|
+
try {
|
|
36
|
+
const answer = await rl.question(` ${question}`);
|
|
37
|
+
return answer.trim();
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
rl.close();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function askSecret(question) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const stdin = process.stdin;
|
|
46
|
+
const stdout = process.stdout;
|
|
47
|
+
let value = "";
|
|
48
|
+
const cleanup = () => {
|
|
49
|
+
stdin.off("data", onData);
|
|
50
|
+
if (stdin.isTTY)
|
|
51
|
+
stdin.setRawMode(false);
|
|
52
|
+
stdin.pause();
|
|
53
|
+
stdout.write("\n");
|
|
54
|
+
};
|
|
55
|
+
const onData = (chunk) => {
|
|
56
|
+
const chars = chunk.toString("utf8");
|
|
57
|
+
for (const ch of chars) {
|
|
58
|
+
if (ch === "\u0003") {
|
|
59
|
+
cleanup();
|
|
60
|
+
reject(new Error("Setup cancelled."));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (ch === "\r" || ch === "\n") {
|
|
64
|
+
cleanup();
|
|
65
|
+
resolve(value.trim());
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (ch === "\u007f") {
|
|
69
|
+
if (value.length > 0) {
|
|
70
|
+
value = value.slice(0, -1);
|
|
71
|
+
stdout.write("\b \b");
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (ch >= " ") {
|
|
76
|
+
value += ch;
|
|
77
|
+
stdout.write("\u2022");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
stdout.write(` ${question}`);
|
|
82
|
+
stdin.setRawMode(true);
|
|
83
|
+
stdin.setEncoding("utf8");
|
|
84
|
+
stdin.resume();
|
|
85
|
+
stdin.on("data", onData);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async function runCredentialSetup() {
|
|
89
|
+
heading("API Setup");
|
|
90
|
+
info("Keys are saved locally to ~/.duocode/config.yaml (mode 600).");
|
|
91
|
+
info("Environment variables override saved keys.\n");
|
|
92
|
+
write(` ${MAGENTA}Opening${RESET} ${CYAN}openrouter.ai/keys${RESET} ${DIM}— grab a key and paste it below${RESET}\n`);
|
|
93
|
+
info("If the browser didn't open: https://openrouter.ai/keys\n");
|
|
94
|
+
openBrowser("https://openrouter.ai/keys");
|
|
95
|
+
const openRouterKey = await ask(`${CYAN}OpenRouter API Key${RESET} ${DIM}(sk-or-...)${RESET}: `, true);
|
|
96
|
+
if (!openRouterKey) {
|
|
97
|
+
throw new Error("OpenRouter API Key is required.");
|
|
98
|
+
}
|
|
99
|
+
let openaiKey = "";
|
|
100
|
+
let anthropicKey = "";
|
|
101
|
+
const fallbackAnswer = await ask(`${CYAN}Add fallback provider keys now?${RESET} ${DIM}(OpenAI/Anthropic) (y/N)${RESET}: `);
|
|
102
|
+
const wantsFallback = fallbackAnswer.toLowerCase() === "y" || fallbackAnswer.toLowerCase() === "yes";
|
|
103
|
+
if (wantsFallback) {
|
|
104
|
+
openaiKey = await ask(`${CYAN}OpenAI API Key${RESET} ${DIM}(optional, sk-... or Enter to skip)${RESET}: `, true);
|
|
105
|
+
anthropicKey = await ask(`${CYAN}Anthropic API Key${RESET} ${DIM}(optional, sk-ant-... or Enter to skip)${RESET}: `, true);
|
|
106
|
+
}
|
|
107
|
+
const githubToken = await ask(`${CYAN}GitHub Token${RESET} ${DIM}(optional, Enter to skip)${RESET}: `, true);
|
|
108
|
+
const config = {
|
|
109
|
+
version: "1",
|
|
110
|
+
credentials: {
|
|
111
|
+
...(openRouterKey ? { openrouter_api_key: openRouterKey } : {}),
|
|
112
|
+
...(openaiKey ? { openai_api_key: openaiKey } : {}),
|
|
113
|
+
...(anthropicKey ? { anthropic_api_key: anthropicKey } : {}),
|
|
114
|
+
...(githubToken ? { github_token: githubToken } : {}),
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
saveGlobalConfig(config);
|
|
118
|
+
write("\n");
|
|
119
|
+
ok(`Saved to ${DIM}${globalConfigPath()}${RESET}`);
|
|
120
|
+
return config;
|
|
121
|
+
}
|
|
122
|
+
function isGitRepo(dir) {
|
|
123
|
+
try {
|
|
124
|
+
let current = dir;
|
|
125
|
+
while (true) {
|
|
126
|
+
if (fs.existsSync(path.join(current, ".git")))
|
|
127
|
+
return true;
|
|
128
|
+
const parent = path.dirname(current);
|
|
129
|
+
if (parent === current)
|
|
130
|
+
return false;
|
|
131
|
+
current = parent;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function maybeInitProject(cwd) {
|
|
139
|
+
if (findConfigPath(cwd)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// Only offer project init when inside a git repo — otherwise skip silently
|
|
143
|
+
if (!isGitRepo(cwd)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
write("\n");
|
|
147
|
+
const answer = await ask(`${YELLOW}No duocode.yaml found.${RESET} Initialize this repo? ${DIM}(y/N)${RESET} `);
|
|
148
|
+
if (!answer || (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes")) {
|
|
149
|
+
info(`Skipped. Run ${CYAN}duocode init${RESET} when ready.`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const configPath = path.join(cwd, "duocode.yaml");
|
|
153
|
+
fs.writeFileSync(configPath, defaultConfigYaml(), "utf8");
|
|
154
|
+
const config = loadConfig(cwd, configPath);
|
|
155
|
+
const duocodeDir = path.join(cwd, ".duocode");
|
|
156
|
+
const worktreesDir = path.resolve(cwd, config.worktrees.root);
|
|
157
|
+
const dbPath = path.resolve(cwd, config.database.path);
|
|
158
|
+
fs.mkdirSync(duocodeDir, { recursive: true });
|
|
159
|
+
fs.mkdirSync(worktreesDir, { recursive: true });
|
|
160
|
+
const db = initDatabase(dbPath);
|
|
161
|
+
db.close();
|
|
162
|
+
ok(`Initialized in ${DIM}${cwd}${RESET}`);
|
|
163
|
+
info(`Config: ${configPath}`);
|
|
164
|
+
info(`Database: ${dbPath}`);
|
|
165
|
+
}
|
|
166
|
+
export async function runSetupIfNeeded(cwd) {
|
|
167
|
+
// Step 1: Load or create global credentials
|
|
168
|
+
let config = loadGlobalConfig();
|
|
169
|
+
if (!config || !globalConfigExists()) {
|
|
170
|
+
config = await runCredentialSetup();
|
|
171
|
+
}
|
|
172
|
+
applyCredentialsToEnv(config);
|
|
173
|
+
// Step 2: Offer project init if in a git repo without duocode.yaml
|
|
174
|
+
await maybeInitProject(cwd);
|
|
175
|
+
// Step 3: Show ready state
|
|
176
|
+
const hasProject = !!findConfigPath(cwd);
|
|
177
|
+
write("\n");
|
|
178
|
+
ok("Ready");
|
|
179
|
+
write("\n");
|
|
180
|
+
write(` ${DIM}$${RESET} ${CYAN}duocode${RESET} start ${DIM}"describe what you want built"${RESET}\n`);
|
|
181
|
+
write(` ${DIM}$${RESET} ${CYAN}duocode${RESET} man ${DIM}Show all commands and usage${RESET}\n`);
|
|
182
|
+
if (hasProject) {
|
|
183
|
+
write(` ${DIM}$${RESET} ${CYAN}duocode${RESET} task create ${DIM}"my task"${RESET}\n`);
|
|
184
|
+
write(` ${DIM}$${RESET} ${CYAN}duocode${RESET} github sync ${DIM}--owner org --repo name${RESET}\n`);
|
|
185
|
+
}
|
|
186
|
+
write("\n");
|
|
187
|
+
}
|
|
188
|
+
export async function runReconfigure() {
|
|
189
|
+
heading("Reconfigure API Keys");
|
|
190
|
+
const existing = loadGlobalConfig();
|
|
191
|
+
const fresh = await runCredentialSetup();
|
|
192
|
+
// Merge: new values override, but keep existing keys that weren't re-entered
|
|
193
|
+
if (existing) {
|
|
194
|
+
const merged = {
|
|
195
|
+
...fresh,
|
|
196
|
+
credentials: { ...existing.credentials, ...fresh.credentials },
|
|
197
|
+
};
|
|
198
|
+
saveGlobalConfig(merged);
|
|
199
|
+
applyCredentialsToEnv(merged);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
applyCredentialsToEnv(fresh);
|
|
203
|
+
}
|
|
204
|
+
write("\n");
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=wizard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/setup/wizard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agentic tool-use loop.
|
|
3
|
+
*
|
|
4
|
+
* Sends messages + tool definitions to a model, executes any tool calls
|
|
5
|
+
* the model requests, appends results, and repeats until the model
|
|
6
|
+
* produces a final text response with no tool calls.
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolCall, ToolDefinition } from "../providers/base.js";
|
|
9
|
+
import type { ProviderRouter } from "../providers/router.js";
|
|
10
|
+
import type { ToolResult, FileChange } from "./tool_types.js";
|
|
11
|
+
import type { PermissionConfig } from "./permissions.js";
|
|
12
|
+
export interface AgentLoopOptions {
|
|
13
|
+
providerRouter: ProviderRouter;
|
|
14
|
+
/** Explicit model to use (bypasses capability routing) */
|
|
15
|
+
model: string;
|
|
16
|
+
systemPrompt: string;
|
|
17
|
+
userPrompt: string;
|
|
18
|
+
projectRoot: string;
|
|
19
|
+
permissions: PermissionConfig;
|
|
20
|
+
tools?: ToolDefinition[];
|
|
21
|
+
/** Max generate→tool→generate iterations (default 25) */
|
|
22
|
+
maxIterations?: number;
|
|
23
|
+
temperature?: number;
|
|
24
|
+
maxOutputTokens?: number;
|
|
25
|
+
/** Called after each tool execution */
|
|
26
|
+
onToolCall?: (call: ToolCall, result: ToolResult) => void;
|
|
27
|
+
/** Called when the model emits text */
|
|
28
|
+
onText?: (text: string) => void;
|
|
29
|
+
}
|
|
30
|
+
export interface AgentLoopResult {
|
|
31
|
+
/** Concatenated text output from all model turns */
|
|
32
|
+
text: string;
|
|
33
|
+
toolCallCount: number;
|
|
34
|
+
fileChanges: FileChange[];
|
|
35
|
+
totalCostUsd: number;
|
|
36
|
+
iterations: number;
|
|
37
|
+
}
|
|
38
|
+
export declare function runAgentLoop(options: AgentLoopOptions): Promise<AgentLoopResult>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ALL_TOOLS } from "./definitions.js";
|
|
2
|
+
import { executeTool } from "./executor.js";
|
|
3
|
+
import { createLogger } from "../ui/logger.js";
|
|
4
|
+
const log = createLogger("agent-loop");
|
|
5
|
+
// ── Loop implementation ──────────────────────────────────────────
|
|
6
|
+
export async function runAgentLoop(options) {
|
|
7
|
+
const { providerRouter, model, systemPrompt, projectRoot, permissions, maxIterations = 25, temperature = 0.2, maxOutputTokens, } = options;
|
|
8
|
+
const tools = options.tools ?? ALL_TOOLS;
|
|
9
|
+
const fileChanges = [];
|
|
10
|
+
const messages = [
|
|
11
|
+
{ role: "system", content: systemPrompt },
|
|
12
|
+
{ role: "user", content: options.userPrompt },
|
|
13
|
+
];
|
|
14
|
+
let totalCostUsd = 0;
|
|
15
|
+
let toolCallCount = 0;
|
|
16
|
+
let iterations = 0;
|
|
17
|
+
const textParts = [];
|
|
18
|
+
while (iterations < maxIterations) {
|
|
19
|
+
iterations++;
|
|
20
|
+
const result = await providerRouter.generate({
|
|
21
|
+
model,
|
|
22
|
+
messages,
|
|
23
|
+
tools,
|
|
24
|
+
toolChoice: "auto",
|
|
25
|
+
temperature,
|
|
26
|
+
maxOutputTokens,
|
|
27
|
+
});
|
|
28
|
+
totalCostUsd += result.costUsd;
|
|
29
|
+
const response = result.response;
|
|
30
|
+
if (response.text) {
|
|
31
|
+
textParts.push(response.text);
|
|
32
|
+
if (options.onText)
|
|
33
|
+
options.onText(response.text);
|
|
34
|
+
}
|
|
35
|
+
// No tool calls → model is done
|
|
36
|
+
if (response.toolCalls.length === 0) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
// Append the assistant turn (with tool calls attached)
|
|
40
|
+
messages.push({
|
|
41
|
+
role: "assistant",
|
|
42
|
+
content: response.text || "",
|
|
43
|
+
toolCalls: response.toolCalls,
|
|
44
|
+
});
|
|
45
|
+
// Execute each requested tool
|
|
46
|
+
for (const call of response.toolCalls) {
|
|
47
|
+
toolCallCount++;
|
|
48
|
+
log.debug("Tool call", { tool: call.name });
|
|
49
|
+
const toolResult = await executeTool({ projectRoot, permissions, fileChanges }, call);
|
|
50
|
+
log.debug("Tool result", { tool: call.name, success: toolResult.success, durationMs: toolResult.durationMs });
|
|
51
|
+
if (options.onToolCall)
|
|
52
|
+
options.onToolCall(call, toolResult);
|
|
53
|
+
// Feed the result back to the model
|
|
54
|
+
messages.push({
|
|
55
|
+
role: "tool",
|
|
56
|
+
content: toolResult.output,
|
|
57
|
+
toolCallId: call.id,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (iterations >= maxIterations) {
|
|
62
|
+
log.warn("Agent loop reached max iterations", { maxIterations });
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
text: textParts.join(""),
|
|
66
|
+
toolCallCount,
|
|
67
|
+
fileChanges,
|
|
68
|
+
totalCostUsd,
|
|
69
|
+
iterations,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=agent_loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent_loop.js","sourceRoot":"","sources":["../../src/tools/agent_loop.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;AAgCvC,oEAAoE;AAEpE,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAyB;IAEzB,MAAM,EACJ,cAAc,EACd,KAAK,EACL,YAAY,EACZ,WAAW,EACX,WAAW,EACX,aAAa,GAAG,EAAE,EAClB,WAAW,GAAG,GAAG,EACjB,eAAe,GAChB,GAAG,OAAO,CAAC;IAEZ,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC;IACzC,MAAM,WAAW,GAAiB,EAAE,CAAC;IAErC,MAAM,QAAQ,GAAkB;QAC9B,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;QACzC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE;KAC9C,CAAC;IAEF,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,OAAO,UAAU,GAAG,aAAa,EAAE,CAAC;QAClC,UAAU,EAAE,CAAC;QAEb,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC;YAC3C,KAAK;YACL,QAAQ;YACR,KAAK;YACL,UAAU,EAAE,MAAM;YAClB,WAAW;YACX,eAAe;SAChB,CAAC,CAAC;QAEH,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC;QAC/B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAEjC,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;YAClB,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,OAAO,CAAC,MAAM;gBAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpD,CAAC;QAED,gCAAgC;QAChC,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpC,MAAM;QACR,CAAC;QAED,uDAAuD;QACvD,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,QAAQ,CAAC,IAAI,IAAI,EAAE;YAC5B,SAAS,EAAE,QAAQ,CAAC,SAAS;SAC9B,CAAC,CAAC;QAEH,8BAA8B;QAC9B,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;YACtC,aAAa,EAAE,CAAC;YAChB,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAE5C,MAAM,UAAU,GAAG,MAAM,WAAW,CAClC,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,EACzC,IAAI,CACL,CAAC;YAEF,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC;YAE9G,IAAI,OAAO,CAAC,UAAU;gBAAE,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YAE7D,oCAAoC;YACpC,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,UAAU,CAAC,MAAM;gBAC1B,UAAU,EAAE,IAAI,CAAC,EAAE;aACpB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,UAAU,IAAI,aAAa,EAAE,CAAC;QAChC,GAAG,CAAC,IAAI,CAAC,mCAAmC,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,aAAa;QACb,WAAW;QACX,YAAY;QACZ,UAAU;KACX,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool approval system — controls which tool actions require user confirmation.
|
|
3
|
+
* Configurable per-category and per-tool, with session-level overrides.
|
|
4
|
+
*/
|
|
5
|
+
import type { ToolAction, ToolCategory, FileChange } from "./tool_types.js";
|
|
6
|
+
import { type RenderOptions } from "./diff_display.js";
|
|
7
|
+
export type ApprovalDecision = "allow" | "deny" | "ask";
|
|
8
|
+
export interface ApprovalRule {
|
|
9
|
+
/** Match tool name (exact) or "*" for all tools in category */
|
|
10
|
+
tool: string;
|
|
11
|
+
/** Category to match */
|
|
12
|
+
category?: ToolCategory;
|
|
13
|
+
/** What to do when this rule matches */
|
|
14
|
+
decision: ApprovalDecision;
|
|
15
|
+
/** Optional path glob for file-based rules (e.g. "*.env" → deny) */
|
|
16
|
+
pathPattern?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ApprovalPolicy {
|
|
19
|
+
/** Default decision when no rule matches */
|
|
20
|
+
defaultDecision: ApprovalDecision;
|
|
21
|
+
/** Category-level defaults (overrides defaultDecision) */
|
|
22
|
+
categoryDefaults: Partial<Record<ToolCategory, ApprovalDecision>>;
|
|
23
|
+
/** Specific rules (evaluated in order, first match wins) */
|
|
24
|
+
rules: ApprovalRule[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sensible default: reads auto-approved, writes/executes require user approval.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_APPROVAL_POLICY: ApprovalPolicy;
|
|
30
|
+
export type UserPromptFn = (action: ToolAction, displayText: string) => Promise<boolean>;
|
|
31
|
+
export declare class ApprovalEngine {
|
|
32
|
+
private policy;
|
|
33
|
+
private sessionOverrides;
|
|
34
|
+
private autoApproveAll;
|
|
35
|
+
constructor(policy?: ApprovalPolicy);
|
|
36
|
+
/**
|
|
37
|
+
* Toggle auto-approve mode (skips all checks, allows everything).
|
|
38
|
+
*/
|
|
39
|
+
setAutoApprove(value: boolean): void;
|
|
40
|
+
getAutoApprove(): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Set a session-level override for a specific tool.
|
|
43
|
+
* "allow-always" persists for the session.
|
|
44
|
+
*/
|
|
45
|
+
setToolOverride(toolName: string, decision: ApprovalDecision): void;
|
|
46
|
+
clearOverrides(): void;
|
|
47
|
+
getPolicy(): Readonly<ApprovalPolicy>;
|
|
48
|
+
updatePolicy(patch: Partial<ApprovalPolicy>): void;
|
|
49
|
+
/**
|
|
50
|
+
* Evaluate whether an action should proceed without asking the user.
|
|
51
|
+
* Returns the decision without prompting.
|
|
52
|
+
*/
|
|
53
|
+
evaluate(action: ToolAction): ApprovalDecision;
|
|
54
|
+
/**
|
|
55
|
+
* Check approval and prompt user if needed.
|
|
56
|
+
* Returns true if the action should proceed, false if denied.
|
|
57
|
+
*/
|
|
58
|
+
check(action: ToolAction, promptFn: UserPromptFn, fileChange?: FileChange, renderOpts?: RenderOptions): Promise<boolean>;
|
|
59
|
+
}
|
|
60
|
+
export declare function formatActionSummary(action: ToolAction): string;
|
|
61
|
+
/**
|
|
62
|
+
* Format a batch of actions into a compact approval prompt.
|
|
63
|
+
*/
|
|
64
|
+
export declare function formatBatchSummary(actions: ToolAction[]): string;
|