@raindrop-ai/wizard 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +47 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +117 -0
- package/dist/bin.js.map +1 -0
- package/dist/src/docs/browser.md +105 -0
- package/dist/src/docs/python.md +618 -0
- package/dist/src/docs/typescript.md +584 -0
- package/dist/src/docs/vercel-ai-sdk.md +304 -0
- package/dist/src/lib/agent-interface.d.ts +46 -0
- package/dist/src/lib/agent-interface.js +292 -0
- package/dist/src/lib/agent-interface.js.map +1 -0
- package/dist/src/lib/agent-prompts.d.ts +10 -0
- package/dist/src/lib/agent-prompts.js +49 -0
- package/dist/src/lib/agent-prompts.js.map +1 -0
- package/dist/src/lib/config.d.ts +39 -0
- package/dist/src/lib/config.js +549 -0
- package/dist/src/lib/config.js.map +1 -0
- package/dist/src/lib/constants.d.ts +27 -0
- package/dist/src/lib/constants.js +165 -0
- package/dist/src/lib/constants.js.map +1 -0
- package/dist/src/lib/handlers.d.ts +68 -0
- package/dist/src/lib/handlers.js +420 -0
- package/dist/src/lib/handlers.js.map +1 -0
- package/dist/src/lib/integration-testing.d.ts +44 -0
- package/dist/src/lib/integration-testing.js +123 -0
- package/dist/src/lib/integration-testing.js.map +1 -0
- package/dist/src/lib/mcp.d.ts +14 -0
- package/dist/src/lib/mcp.js +134 -0
- package/dist/src/lib/mcp.js.map +1 -0
- package/dist/src/lib/sdk-messages.d.ts +17 -0
- package/dist/src/lib/sdk-messages.js +278 -0
- package/dist/src/lib/sdk-messages.js.map +1 -0
- package/dist/src/lib/wizard.d.ts +6 -0
- package/dist/src/lib/wizard.js +131 -0
- package/dist/src/lib/wizard.js.map +1 -0
- package/dist/src/run.d.ts +8 -0
- package/dist/src/run.js +53 -0
- package/dist/src/run.js.map +1 -0
- package/dist/src/ui/App.d.ts +15 -0
- package/dist/src/ui/App.js +27 -0
- package/dist/src/ui/App.js.map +1 -0
- package/dist/src/ui/cancellation.d.ts +14 -0
- package/dist/src/ui/cancellation.js +17 -0
- package/dist/src/ui/cancellation.js.map +1 -0
- package/dist/src/ui/components/ClarifyingQuestionsPrompt.d.ts +17 -0
- package/dist/src/ui/components/ClarifyingQuestionsPrompt.js +359 -0
- package/dist/src/ui/components/ClarifyingQuestionsPrompt.js.map +1 -0
- package/dist/src/ui/components/ContinuePrompt.d.ts +14 -0
- package/dist/src/ui/components/ContinuePrompt.js +23 -0
- package/dist/src/ui/components/ContinuePrompt.js.map +1 -0
- package/dist/src/ui/components/DiffDisplay.d.ts +18 -0
- package/dist/src/ui/components/DiffDisplay.js +110 -0
- package/dist/src/ui/components/DiffDisplay.js.map +1 -0
- package/dist/src/ui/components/FeedbackSelectPrompt.d.ts +20 -0
- package/dist/src/ui/components/FeedbackSelectPrompt.js +132 -0
- package/dist/src/ui/components/FeedbackSelectPrompt.js.map +1 -0
- package/dist/src/ui/components/HistoryItemDisplay.d.ts +14 -0
- package/dist/src/ui/components/HistoryItemDisplay.js +140 -0
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -0
- package/dist/src/ui/components/Logo.d.ts +10 -0
- package/dist/src/ui/components/Logo.js +47 -0
- package/dist/src/ui/components/Logo.js.map +1 -0
- package/dist/src/ui/components/OrgInfoBox.d.ts +11 -0
- package/dist/src/ui/components/OrgInfoBox.js +16 -0
- package/dist/src/ui/components/OrgInfoBox.js.map +1 -0
- package/dist/src/ui/components/PendingPrompt.d.ts +18 -0
- package/dist/src/ui/components/PendingPrompt.js +57 -0
- package/dist/src/ui/components/PendingPrompt.js.map +1 -0
- package/dist/src/ui/components/PersistentTextInput.d.ts +21 -0
- package/dist/src/ui/components/PersistentTextInput.js +117 -0
- package/dist/src/ui/components/PersistentTextInput.js.map +1 -0
- package/dist/src/ui/components/PlanApprovalPrompt.d.ts +19 -0
- package/dist/src/ui/components/PlanApprovalPrompt.js +62 -0
- package/dist/src/ui/components/PlanApprovalPrompt.js.map +1 -0
- package/dist/src/ui/components/PromptContainer.d.ts +14 -0
- package/dist/src/ui/components/PromptContainer.js +18 -0
- package/dist/src/ui/components/PromptContainer.js.map +1 -0
- package/dist/src/ui/components/SelectPrompt.d.ts +14 -0
- package/dist/src/ui/components/SelectPrompt.js +62 -0
- package/dist/src/ui/components/SelectPrompt.js.map +1 -0
- package/dist/src/ui/components/SpinnerDisplay.d.ts +13 -0
- package/dist/src/ui/components/SpinnerDisplay.js +11 -0
- package/dist/src/ui/components/SpinnerDisplay.js.map +1 -0
- package/dist/src/ui/components/ToolApprovalPrompt.d.ts +14 -0
- package/dist/src/ui/components/ToolApprovalPrompt.js +142 -0
- package/dist/src/ui/components/ToolApprovalPrompt.js.map +1 -0
- package/dist/src/ui/components/ToolCallDisplay.d.ts +14 -0
- package/dist/src/ui/components/ToolCallDisplay.js +83 -0
- package/dist/src/ui/components/ToolCallDisplay.js.map +1 -0
- package/dist/src/ui/components/WriteKeyDisplay.d.ts +15 -0
- package/dist/src/ui/components/WriteKeyDisplay.js +13 -0
- package/dist/src/ui/components/WriteKeyDisplay.js.map +1 -0
- package/dist/src/ui/contexts/WizardContext.d.ts +210 -0
- package/dist/src/ui/contexts/WizardContext.js +362 -0
- package/dist/src/ui/contexts/WizardContext.js.map +1 -0
- package/dist/src/ui/hooks/useCancellation.d.ts +15 -0
- package/dist/src/ui/hooks/useCancellation.js +25 -0
- package/dist/src/ui/hooks/useCancellation.js.map +1 -0
- package/dist/src/ui/render.d.ts +34 -0
- package/dist/src/ui/render.js +94 -0
- package/dist/src/ui/render.js.map +1 -0
- package/dist/src/ui/types.d.ts +184 -0
- package/dist/src/ui/types.js +6 -0
- package/dist/src/ui/types.js.map +1 -0
- package/dist/src/utils/clack-utils.d.ts +13 -0
- package/dist/src/utils/clack-utils.js +131 -0
- package/dist/src/utils/clack-utils.js.map +1 -0
- package/dist/src/utils/debug.d.ts +13 -0
- package/dist/src/utils/debug.js +47 -0
- package/dist/src/utils/debug.js.map +1 -0
- package/dist/src/utils/environment.d.ts +5 -0
- package/dist/src/utils/environment.js +131 -0
- package/dist/src/utils/environment.js.map +1 -0
- package/dist/src/utils/logging.d.ts +9 -0
- package/dist/src/utils/logging.js +38 -0
- package/dist/src/utils/logging.js.map +1 -0
- package/dist/src/utils/oauth.d.ts +12 -0
- package/dist/src/utils/oauth.js +497 -0
- package/dist/src/utils/oauth.js.map +1 -0
- package/dist/src/utils/package-json-types.d.ts +44 -0
- package/dist/src/utils/package-json-types.js +6 -0
- package/dist/src/utils/package-json-types.js.map +1 -0
- package/dist/src/utils/package-json.d.ts +19 -0
- package/dist/src/utils/package-json.js +22 -0
- package/dist/src/utils/package-json.js.map +1 -0
- package/dist/src/utils/session.d.ts +2 -0
- package/dist/src/utils/session.js +87 -0
- package/dist/src/utils/session.js.map +1 -0
- package/dist/src/utils/types.d.ts +61 -0
- package/dist/src/utils/types.js +2 -0
- package/dist/src/utils/types.js.map +1 -0
- package/dist/src/utils/ui.d.ts +120 -0
- package/dist/src/utils/ui.js +164 -0
- package/dist/src/utils/ui.js.map +1 -0
- package/package.json +140 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
export var Integration;
|
|
2
|
+
(function (Integration) {
|
|
3
|
+
Integration["python"] = "python";
|
|
4
|
+
Integration["typescript"] = "typescript";
|
|
5
|
+
Integration["vercelAiSdk"] = "vercel-ai-sdk";
|
|
6
|
+
})(Integration || (Integration = {}));
|
|
7
|
+
export function getIntegrationDescription(type) {
|
|
8
|
+
switch (type) {
|
|
9
|
+
case Integration.python:
|
|
10
|
+
return 'a Python SDK';
|
|
11
|
+
case Integration.typescript:
|
|
12
|
+
return 'a TypeScript SDK';
|
|
13
|
+
case Integration.vercelAiSdk:
|
|
14
|
+
return 'the Vercel AI SDK';
|
|
15
|
+
default:
|
|
16
|
+
throw new Error(`Unknown integration ${type}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export const IS_DEV = ['test', 'development'].includes(process.env.NODE_ENV ?? '');
|
|
20
|
+
export const ISSUES_URL = 'https://github.com/raindrop/wizard/issues';
|
|
21
|
+
export const CALLBACK_PORT = 8259;
|
|
22
|
+
const RAINDROP_APP_URL = 'https://app.raindrop.ai';
|
|
23
|
+
const RAINDROP_API_URL = 'https://api.dawnai.com';
|
|
24
|
+
export const API_BASE_URL = RAINDROP_API_URL;
|
|
25
|
+
export const APP_URL = RAINDROP_APP_URL;
|
|
26
|
+
// For local dev:
|
|
27
|
+
// export const API_BASE_URL = 'http://localhost:3000';
|
|
28
|
+
// export const APP_URL = 'http://localhost:5173';
|
|
29
|
+
export const WRITE_KEY_ENDPOINT = `${API_BASE_URL}/api/cli/users/key`;
|
|
30
|
+
export const EVENTS_LIST_ENDPOINT = `${API_BASE_URL}/api/cli/events/list`;
|
|
31
|
+
export const ANTHROPIC_BASE_URL = `${API_BASE_URL}/api/cli`;
|
|
32
|
+
export const SESSION_START_ENDPOINT = `${API_BASE_URL}/api/cli/session/init`;
|
|
33
|
+
export const SESSION_UPDATE_ENDPOINT = `${API_BASE_URL}/api/cli/session/update`;
|
|
34
|
+
/**
|
|
35
|
+
* Spinner message shown during wizard execution
|
|
36
|
+
*/
|
|
37
|
+
export const SPINNER_MESSAGE = 'Raindrop wizard is working...';
|
|
38
|
+
/**
|
|
39
|
+
* Safe bash command patterns that can be auto-approved without user confirmation.
|
|
40
|
+
* Uses simple prefix matching with optional * wildcard at the end.
|
|
41
|
+
*
|
|
42
|
+
* NOTE: Package manager install/build/run commands are NOT auto-approved and require user confirmation.
|
|
43
|
+
*/
|
|
44
|
+
export const SAFE_BASH_PATTERNS = [
|
|
45
|
+
// === Package Managers - Read-only commands ===
|
|
46
|
+
'npm ls*',
|
|
47
|
+
'npm outdated*',
|
|
48
|
+
'npm audit*',
|
|
49
|
+
'pip list*',
|
|
50
|
+
'pip show*',
|
|
51
|
+
'pip freeze*',
|
|
52
|
+
'poetry show*',
|
|
53
|
+
'uv pip list*',
|
|
54
|
+
'cargo check*',
|
|
55
|
+
'cargo clippy*',
|
|
56
|
+
'cargo fmt*',
|
|
57
|
+
'go fmt*',
|
|
58
|
+
// === Type Checking / Linting / Formatting ===
|
|
59
|
+
'tsc*',
|
|
60
|
+
'eslint *',
|
|
61
|
+
'prettier *',
|
|
62
|
+
'biome *',
|
|
63
|
+
'mypy *',
|
|
64
|
+
'python -m mypy*',
|
|
65
|
+
'ruff *',
|
|
66
|
+
'black *',
|
|
67
|
+
'flake8 *',
|
|
68
|
+
'pylint *',
|
|
69
|
+
'pyright*',
|
|
70
|
+
'rubocop*',
|
|
71
|
+
'rustfmt*',
|
|
72
|
+
'gofmt*',
|
|
73
|
+
'swiftlint*',
|
|
74
|
+
'ktlint*',
|
|
75
|
+
// === Build Tools ===
|
|
76
|
+
'make',
|
|
77
|
+
'make build*',
|
|
78
|
+
'make test*',
|
|
79
|
+
'make lint*',
|
|
80
|
+
'make check*',
|
|
81
|
+
'cmake *',
|
|
82
|
+
'gradle build*',
|
|
83
|
+
'gradle test*',
|
|
84
|
+
'./gradlew build*',
|
|
85
|
+
'./gradlew test*',
|
|
86
|
+
'mvn compile*',
|
|
87
|
+
'mvn test*',
|
|
88
|
+
'mvn package*',
|
|
89
|
+
// === Testing ===
|
|
90
|
+
'jest*',
|
|
91
|
+
'vitest*',
|
|
92
|
+
'mocha*',
|
|
93
|
+
'pytest*',
|
|
94
|
+
'python -m pytest*',
|
|
95
|
+
'npx playwright test*',
|
|
96
|
+
'npx cypress run*',
|
|
97
|
+
'npx jest*',
|
|
98
|
+
'npx vitest*',
|
|
99
|
+
// === Git (read-only operations) ===
|
|
100
|
+
'git status*',
|
|
101
|
+
'git diff*',
|
|
102
|
+
'git log*',
|
|
103
|
+
'git show*',
|
|
104
|
+
'git branch*',
|
|
105
|
+
'git fetch*',
|
|
106
|
+
'git remote -v*',
|
|
107
|
+
'git ls-files*',
|
|
108
|
+
'git rev-parse*',
|
|
109
|
+
// === File/Directory Info (read-only) ===
|
|
110
|
+
'ls *',
|
|
111
|
+
'ls',
|
|
112
|
+
'cat *',
|
|
113
|
+
'head *',
|
|
114
|
+
'tail *',
|
|
115
|
+
'less *',
|
|
116
|
+
'find *',
|
|
117
|
+
'grep *',
|
|
118
|
+
'rg *',
|
|
119
|
+
'ag *',
|
|
120
|
+
'tree*',
|
|
121
|
+
'pwd',
|
|
122
|
+
'wc *',
|
|
123
|
+
'file *',
|
|
124
|
+
'stat *',
|
|
125
|
+
'du *',
|
|
126
|
+
'df *',
|
|
127
|
+
// === Environment/System Info ===
|
|
128
|
+
'which *',
|
|
129
|
+
'whereis *',
|
|
130
|
+
'type *',
|
|
131
|
+
'command -v *',
|
|
132
|
+
'node --version*',
|
|
133
|
+
'node -v*',
|
|
134
|
+
'npm --version*',
|
|
135
|
+
'npm -v*',
|
|
136
|
+
'python --version*',
|
|
137
|
+
'python -V*',
|
|
138
|
+
'python3 --version*',
|
|
139
|
+
'*--version',
|
|
140
|
+
'*-v',
|
|
141
|
+
'*-V',
|
|
142
|
+
'env',
|
|
143
|
+
'printenv*',
|
|
144
|
+
'echo *',
|
|
145
|
+
'uname*',
|
|
146
|
+
'hostname',
|
|
147
|
+
'whoami',
|
|
148
|
+
'date',
|
|
149
|
+
'uptime',
|
|
150
|
+
// === Docker (read-only / safe) ===
|
|
151
|
+
'docker ps*',
|
|
152
|
+
'docker images*',
|
|
153
|
+
'docker logs*',
|
|
154
|
+
'docker inspect*',
|
|
155
|
+
'docker-compose ps*',
|
|
156
|
+
'docker-compose logs*',
|
|
157
|
+
'docker build*',
|
|
158
|
+
'docker compose build*',
|
|
159
|
+
'docker compose up*',
|
|
160
|
+
// === Directory Navigation ===
|
|
161
|
+
'cd *',
|
|
162
|
+
'pushd *',
|
|
163
|
+
'popd',
|
|
164
|
+
];
|
|
165
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":"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","sourcesContent":["export enum Integration {\n python = 'python',\n typescript = 'typescript',\n vercelAiSdk = 'vercel-ai-sdk',\n}\n\nexport function getIntegrationDescription(type: string): string {\n switch (type) {\n case Integration.python:\n return 'a Python SDK';\n case Integration.typescript:\n return 'a TypeScript SDK';\n case Integration.vercelAiSdk:\n return 'the Vercel AI SDK';\n default:\n throw new Error(`Unknown integration ${type}`);\n }\n}\n\nexport const IS_DEV = ['test', 'development'].includes(\n process.env.NODE_ENV ?? '',\n);\n\nexport const ISSUES_URL = 'https://github.com/raindrop/wizard/issues';\n\nexport const CALLBACK_PORT = 8259;\nconst RAINDROP_APP_URL = 'https://app.raindrop.ai';\nconst RAINDROP_API_URL = 'https://api.dawnai.com';\nexport const API_BASE_URL = RAINDROP_API_URL;\nexport const APP_URL = RAINDROP_APP_URL;\n// For local dev:\n// export const API_BASE_URL = 'http://localhost:3000';\n// export const APP_URL = 'http://localhost:5173';\nexport const WRITE_KEY_ENDPOINT = `${API_BASE_URL}/api/cli/users/key`;\nexport const EVENTS_LIST_ENDPOINT = `${API_BASE_URL}/api/cli/events/list`;\nexport const ANTHROPIC_BASE_URL = `${API_BASE_URL}/api/cli`;\nexport const SESSION_START_ENDPOINT = `${API_BASE_URL}/api/cli/session/init`;\nexport const SESSION_UPDATE_ENDPOINT = `${API_BASE_URL}/api/cli/session/update`;\n\n/**\n * Spinner message shown during wizard execution\n */\nexport const SPINNER_MESSAGE = 'Raindrop wizard is working...';\n\n/**\n * Safe bash command patterns that can be auto-approved without user confirmation.\n * Uses simple prefix matching with optional * wildcard at the end.\n *\n * NOTE: Package manager install/build/run commands are NOT auto-approved and require user confirmation.\n */\nexport const SAFE_BASH_PATTERNS: string[] = [\n // === Package Managers - Read-only commands ===\n 'npm ls*',\n 'npm outdated*',\n 'npm audit*',\n\n 'pip list*',\n 'pip show*',\n 'pip freeze*',\n\n 'poetry show*',\n\n 'uv pip list*',\n\n 'cargo check*',\n 'cargo clippy*',\n 'cargo fmt*',\n\n 'go fmt*',\n\n // === Type Checking / Linting / Formatting ===\n 'tsc*',\n 'eslint *',\n 'prettier *',\n 'biome *',\n 'mypy *',\n 'python -m mypy*',\n 'ruff *',\n 'black *',\n 'flake8 *',\n 'pylint *',\n 'pyright*',\n 'rubocop*',\n 'rustfmt*',\n 'gofmt*',\n 'swiftlint*',\n 'ktlint*',\n\n // === Build Tools ===\n 'make',\n 'make build*',\n 'make test*',\n 'make lint*',\n 'make check*',\n 'cmake *',\n 'gradle build*',\n 'gradle test*',\n './gradlew build*',\n './gradlew test*',\n 'mvn compile*',\n 'mvn test*',\n 'mvn package*',\n\n // === Testing ===\n 'jest*',\n 'vitest*',\n 'mocha*',\n 'pytest*',\n 'python -m pytest*',\n 'npx playwright test*',\n 'npx cypress run*',\n 'npx jest*',\n 'npx vitest*',\n\n // === Git (read-only operations) ===\n 'git status*',\n 'git diff*',\n 'git log*',\n 'git show*',\n 'git branch*',\n 'git fetch*',\n 'git remote -v*',\n 'git ls-files*',\n 'git rev-parse*',\n\n // === File/Directory Info (read-only) ===\n 'ls *',\n 'ls',\n 'cat *',\n 'head *',\n 'tail *',\n 'less *',\n 'find *',\n 'grep *',\n 'rg *',\n 'ag *',\n 'tree*',\n 'pwd',\n 'wc *',\n 'file *',\n 'stat *',\n 'du *',\n 'df *',\n\n // === Environment/System Info ===\n 'which *',\n 'whereis *',\n 'type *',\n 'command -v *',\n 'node --version*',\n 'node -v*',\n 'npm --version*',\n 'npm -v*',\n 'python --version*',\n 'python -V*',\n 'python3 --version*',\n '*--version',\n '*-v',\n '*-V',\n 'env',\n 'printenv*',\n 'echo *',\n 'uname*',\n 'hostname',\n 'whoami',\n 'date',\n 'uptime',\n\n // === Docker (read-only / safe) ===\n 'docker ps*',\n 'docker images*',\n 'docker logs*',\n 'docker inspect*',\n 'docker-compose ps*',\n 'docker-compose logs*',\n 'docker build*',\n 'docker compose build*',\n 'docker compose up*',\n\n // === Directory Navigation ===\n 'cd *',\n 'pushd *',\n 'popd',\n];\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool approval handlers for the Claude agent
|
|
3
|
+
* Handles UI integration for tool approvals and clarifying questions
|
|
4
|
+
*/
|
|
5
|
+
import type { ToolApprovalResult, AgentQueryHandle } from '../ui/types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Clear all session-wide file approvals.
|
|
8
|
+
* Useful when starting a new session or resetting permissions.
|
|
9
|
+
*/
|
|
10
|
+
export declare function clearApprovedFiles(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Clear all session-wide command pattern approvals.
|
|
13
|
+
* Useful when starting a new session or resetting permissions.
|
|
14
|
+
*/
|
|
15
|
+
export declare function clearApprovedCommandPatterns(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Get the list of currently approved files (for debugging/display purposes)
|
|
18
|
+
*/
|
|
19
|
+
export declare function getApprovedFiles(): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Get the list of currently approved command patterns (for debugging/display purposes)
|
|
22
|
+
*/
|
|
23
|
+
export declare function getApprovedCommandPatterns(): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Pending tool call info stored while waiting for result
|
|
26
|
+
*/
|
|
27
|
+
export interface PendingToolCall {
|
|
28
|
+
toolName: string;
|
|
29
|
+
input: Record<string, unknown>;
|
|
30
|
+
description?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Dependencies required for creating an AgentQueryHandle
|
|
34
|
+
*/
|
|
35
|
+
export interface AgentQueryHandleDeps {
|
|
36
|
+
isInterruptingRef: {
|
|
37
|
+
value: boolean;
|
|
38
|
+
};
|
|
39
|
+
waitingForUserInputRef: {
|
|
40
|
+
value: boolean;
|
|
41
|
+
};
|
|
42
|
+
pendingToolCalls: Map<string, PendingToolCall>;
|
|
43
|
+
getQueryObject: () => {
|
|
44
|
+
interrupt?: () => Promise<void>;
|
|
45
|
+
} | null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create an AgentQueryHandle for external control of the agent.
|
|
49
|
+
* Handles interrupts by marking pending tool calls (keeps persistent input running).
|
|
50
|
+
*/
|
|
51
|
+
export declare function createAgentQueryHandle(deps: AgentQueryHandleDeps): AgentQueryHandle;
|
|
52
|
+
/**
|
|
53
|
+
* Session info for notifications
|
|
54
|
+
*/
|
|
55
|
+
export interface SessionInfo {
|
|
56
|
+
sessionId: string;
|
|
57
|
+
accessToken: string;
|
|
58
|
+
orgId: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create a canUseTool handler that integrates with the UI for approvals.
|
|
62
|
+
* - Handles AskUserQuestion by showing clarifying questions UI
|
|
63
|
+
* - Handles ExitPlanMode by showing plan approval UI
|
|
64
|
+
* - Handles WebSearch by restricting to allowed domains
|
|
65
|
+
* - Shows approval UI for other tools
|
|
66
|
+
* - Supports caching file approvals for "allow all edits to this file"
|
|
67
|
+
*/
|
|
68
|
+
export declare function createCanUseToolHandler(sessionInfo?: SessionInfo, approvedFilesCache?: Set<string>): (toolName: string, input: unknown) => Promise<ToolApprovalResult>;
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool approval handlers for the Claude agent
|
|
3
|
+
* Handles UI integration for tool approvals and clarifying questions
|
|
4
|
+
*/
|
|
5
|
+
import ui from '../utils/ui.js';
|
|
6
|
+
import { logToFile } from '../utils/debug.js';
|
|
7
|
+
import { createTwoFilesPatch } from 'diff';
|
|
8
|
+
import { SAFE_BASH_PATTERNS } from './constants.js';
|
|
9
|
+
import { sendSessionUpdate } from '../utils/session.js';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Session-wide Approval Tracking
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Set of file paths that have been granted blanket approval for the session.
|
|
15
|
+
* When a file is in this set, Edit/Write operations on it are auto-approved.
|
|
16
|
+
*/
|
|
17
|
+
const approvedFilesForSession = new Set();
|
|
18
|
+
/**
|
|
19
|
+
* Set of command patterns that have been granted blanket approval for the session.
|
|
20
|
+
* When a command matches a pattern in this set, Bash operations are auto-approved.
|
|
21
|
+
* Patterns use simple prefix matching (e.g., "npm install" matches "npm install axios").
|
|
22
|
+
*/
|
|
23
|
+
const approvedCommandPatternsForSession = new Set();
|
|
24
|
+
/**
|
|
25
|
+
* Clear all session-wide file approvals.
|
|
26
|
+
* Useful when starting a new session or resetting permissions.
|
|
27
|
+
*/
|
|
28
|
+
export function clearApprovedFiles() {
|
|
29
|
+
approvedFilesForSession.clear();
|
|
30
|
+
logToFile('Cleared all approved files for session');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Clear all session-wide command pattern approvals.
|
|
34
|
+
* Useful when starting a new session or resetting permissions.
|
|
35
|
+
*/
|
|
36
|
+
export function clearApprovedCommandPatterns() {
|
|
37
|
+
approvedCommandPatternsForSession.clear();
|
|
38
|
+
logToFile('Cleared all approved command patterns for session');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get the list of currently approved files (for debugging/display purposes)
|
|
42
|
+
*/
|
|
43
|
+
export function getApprovedFiles() {
|
|
44
|
+
return Array.from(approvedFilesForSession);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the list of currently approved command patterns (for debugging/display purposes)
|
|
48
|
+
*/
|
|
49
|
+
export function getApprovedCommandPatterns() {
|
|
50
|
+
return Array.from(approvedCommandPatternsForSession);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extract the command pattern from a bash command for session approval.
|
|
54
|
+
* Returns the base command with the first argument(s) to match similar commands.
|
|
55
|
+
* Examples:
|
|
56
|
+
* "npm install axios" -> "npm install"
|
|
57
|
+
* "npm run build" -> "npm run"
|
|
58
|
+
* "pip install requests" -> "pip install"
|
|
59
|
+
* "yarn add lodash" -> "yarn add"
|
|
60
|
+
*/
|
|
61
|
+
function extractCommandPattern(command) {
|
|
62
|
+
const trimmed = command.trim();
|
|
63
|
+
const parts = trimmed.split(/\s+/);
|
|
64
|
+
// For package managers with subcommands, include the subcommand
|
|
65
|
+
if (parts.length >= 2) {
|
|
66
|
+
const [cmd, subcmd] = parts;
|
|
67
|
+
// Common package managers
|
|
68
|
+
if (['npm', 'yarn', 'bun', 'pip', 'pip3', 'poetry', 'cargo', 'go'].includes(cmd)) {
|
|
69
|
+
return `${cmd} ${subcmd}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// For other commands, just return the first part
|
|
73
|
+
return parts[0] || trimmed;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if a bash command matches any approved command pattern for the session.
|
|
77
|
+
*/
|
|
78
|
+
function isCommandApprovedForSession(command) {
|
|
79
|
+
const pattern = extractCommandPattern(command);
|
|
80
|
+
return approvedCommandPatternsForSession.has(pattern);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create an AgentQueryHandle for external control of the agent.
|
|
84
|
+
* Handles interrupts by marking pending tool calls (keeps persistent input running).
|
|
85
|
+
*/
|
|
86
|
+
export function createAgentQueryHandle(deps) {
|
|
87
|
+
const { isInterruptingRef, waitingForUserInputRef, pendingToolCalls, getQueryObject, } = deps;
|
|
88
|
+
return {
|
|
89
|
+
interrupt: async () => {
|
|
90
|
+
logToFile('Soft interrupt requested (Esc)');
|
|
91
|
+
// If already interrupted, don't add another "Interrupted" item
|
|
92
|
+
if (isInterruptingRef.value || waitingForUserInputRef.value) {
|
|
93
|
+
logToFile('Already interrupted - ignoring duplicate interrupt request');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
isInterruptingRef.value = true;
|
|
97
|
+
waitingForUserInputRef.value = true;
|
|
98
|
+
// Mark all pending tool calls as interrupted and show them in history
|
|
99
|
+
if (pendingToolCalls.size > 0) {
|
|
100
|
+
for (const [toolUseId, pendingCall] of pendingToolCalls) {
|
|
101
|
+
ui.addItem({
|
|
102
|
+
type: 'tool-call',
|
|
103
|
+
text: pendingCall.toolName,
|
|
104
|
+
toolCall: {
|
|
105
|
+
toolName: pendingCall.toolName,
|
|
106
|
+
status: 'interrupted',
|
|
107
|
+
input: pendingCall.input,
|
|
108
|
+
description: pendingCall.description,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
pendingToolCalls.delete(toolUseId);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// Only show generic "Interrupted" if no pending tool calls to display
|
|
116
|
+
ui.addItem({
|
|
117
|
+
type: 'error',
|
|
118
|
+
text: 'Interrupted',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
// Note: Don't stop persistent input here - keep it visible for user to type their message
|
|
122
|
+
// The spinner will be stopped separately, and the input remains for user to submit
|
|
123
|
+
// Call SDK interrupt
|
|
124
|
+
const queryObject = getQueryObject();
|
|
125
|
+
if (queryObject?.interrupt) {
|
|
126
|
+
logToFile('Calling queryObject.interrupt()');
|
|
127
|
+
await queryObject.interrupt();
|
|
128
|
+
logToFile('queryObject.interrupt() completed');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
logToFile('No queryObject.interrupt available');
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
sendMessage: () => {
|
|
135
|
+
// No-op: use interrupt + resume pattern instead of streaming messages
|
|
136
|
+
logToFile('sendMessage called but not supported - use interrupt and resume');
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Enhanced canUseTool Handler with UI Integration
|
|
142
|
+
// ============================================================================
|
|
143
|
+
/**
|
|
144
|
+
* Generate a unified diff for Edit tool inputs (old_string -> new_string)
|
|
145
|
+
*/
|
|
146
|
+
function generateEditDiff(filePath, oldString, newString) {
|
|
147
|
+
// createTwoFilesPatch generates a unified diff
|
|
148
|
+
return createTwoFilesPatch(filePath, filePath, oldString, newString, '', // old header
|
|
149
|
+
'', // new header
|
|
150
|
+
{ context: 3 });
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Handle tool approval request by showing approval UI
|
|
154
|
+
*/
|
|
155
|
+
async function handleToolApproval(toolName, input, approvedFilesCache) {
|
|
156
|
+
logToFile('Showing tool approval UI:', { toolName, input });
|
|
157
|
+
// Extract file path
|
|
158
|
+
const fileName = typeof input.file_path === 'string'
|
|
159
|
+
? input.file_path
|
|
160
|
+
: typeof input.path === 'string'
|
|
161
|
+
? input.path
|
|
162
|
+
: undefined;
|
|
163
|
+
// Check if this file has been approved for all edits
|
|
164
|
+
if (approvedFilesCache &&
|
|
165
|
+
fileName &&
|
|
166
|
+
(toolName === 'Edit' || toolName === 'Write') &&
|
|
167
|
+
approvedFilesCache.has(fileName)) {
|
|
168
|
+
logToFile('Auto-approving edit to previously approved file:', fileName);
|
|
169
|
+
return {
|
|
170
|
+
behavior: 'allow',
|
|
171
|
+
updatedInput: input,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// Stop spinner while waiting for user approval
|
|
175
|
+
const spinner = ui.spinner();
|
|
176
|
+
spinner.stop();
|
|
177
|
+
// Generate diff content for file modification tools
|
|
178
|
+
let diffContent;
|
|
179
|
+
if (toolName === 'Edit' &&
|
|
180
|
+
typeof input.old_string === 'string' &&
|
|
181
|
+
typeof input.new_string === 'string' &&
|
|
182
|
+
fileName) {
|
|
183
|
+
// Edit: show old -> new diff
|
|
184
|
+
diffContent = generateEditDiff(fileName, input.old_string, input.new_string);
|
|
185
|
+
}
|
|
186
|
+
else if (toolName === 'Write' &&
|
|
187
|
+
typeof input.content === 'string' &&
|
|
188
|
+
fileName) {
|
|
189
|
+
// Write: show content as all additions (empty -> content)
|
|
190
|
+
diffContent = generateEditDiff(fileName, '', input.content);
|
|
191
|
+
}
|
|
192
|
+
else if (typeof input.file_diff === 'string') {
|
|
193
|
+
// Use pre-generated diff if available
|
|
194
|
+
diffContent = input.file_diff;
|
|
195
|
+
}
|
|
196
|
+
// Build props for the approval prompt
|
|
197
|
+
const props = {
|
|
198
|
+
toolName,
|
|
199
|
+
input,
|
|
200
|
+
description: typeof input.description === 'string' ? input.description : undefined,
|
|
201
|
+
diffContent,
|
|
202
|
+
fileName,
|
|
203
|
+
};
|
|
204
|
+
try {
|
|
205
|
+
const result = await ui.toolApproval(props);
|
|
206
|
+
logToFile('Tool approval result:', result);
|
|
207
|
+
// If user chose "allow all edits to this file", add to both caches
|
|
208
|
+
if (result.behavior === 'allow' && 'allowAllForFile' in result && result.allowAllForFile) {
|
|
209
|
+
approvedFilesForSession.add(result.allowAllForFile);
|
|
210
|
+
logToFile('Added file to approved list:', result.allowAllForFile);
|
|
211
|
+
}
|
|
212
|
+
if (result.behavior === 'allow' &&
|
|
213
|
+
result.allowAllEdits &&
|
|
214
|
+
fileName &&
|
|
215
|
+
approvedFilesCache) {
|
|
216
|
+
logToFile('Adding file to approved files cache:', fileName);
|
|
217
|
+
approvedFilesCache.add(fileName);
|
|
218
|
+
}
|
|
219
|
+
// If user selected "allow all for this command pattern", add to approved set
|
|
220
|
+
if (result.behavior === 'allow' &&
|
|
221
|
+
'allowAllForCommandPattern' in result &&
|
|
222
|
+
result.allowAllForCommandPattern) {
|
|
223
|
+
approvedCommandPatternsForSession.add(result.allowAllForCommandPattern);
|
|
224
|
+
logToFile('Added command pattern to approved list:', result.allowAllForCommandPattern);
|
|
225
|
+
}
|
|
226
|
+
// Restart spinner after user responds
|
|
227
|
+
spinner.start();
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
logToFile('Error in tool approval:', error);
|
|
232
|
+
return {
|
|
233
|
+
behavior: 'deny',
|
|
234
|
+
message: 'Failed to get user approval',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Handle the AskUserQuestion tool by showing clarifying questions UI.
|
|
240
|
+
* Input already contains { questions: [...] } in the correct format.
|
|
241
|
+
* Returns { questions, answers } as expected by the SDK.
|
|
242
|
+
*/
|
|
243
|
+
async function handleClarifyingQuestions(input) {
|
|
244
|
+
logToFile('Handling AskUserQuestion:', input);
|
|
245
|
+
// Stop spinner while waiting for user input
|
|
246
|
+
const spinner = ui.spinner();
|
|
247
|
+
spinner.stop();
|
|
248
|
+
try {
|
|
249
|
+
// Input is already in ClarifyingQuestionsProps format
|
|
250
|
+
const result = await ui.clarifyingQuestions(input);
|
|
251
|
+
logToFile('Clarifying questions result:', result);
|
|
252
|
+
// Restart spinner after user responds
|
|
253
|
+
spinner.start();
|
|
254
|
+
// Return questions + answers as expected by SDK
|
|
255
|
+
return {
|
|
256
|
+
behavior: 'allow',
|
|
257
|
+
updatedInput: {
|
|
258
|
+
questions: result.questions,
|
|
259
|
+
answers: result.answers,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
logToFile('Error in clarifying questions:', error);
|
|
265
|
+
return {
|
|
266
|
+
behavior: 'deny',
|
|
267
|
+
message: 'Failed to get user answers',
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Handle the ExitPlanMode tool by showing plan approval UI.
|
|
273
|
+
* Input contains { plan: "..." } with the plan in markdown format.
|
|
274
|
+
* If user approves, returns allow. If user rejects, returns deny with feedback.
|
|
275
|
+
*/
|
|
276
|
+
async function handlePlanApproval(input, sessionInfo) {
|
|
277
|
+
logToFile('Handling ExitPlanMode:', input);
|
|
278
|
+
const planContent = typeof input.plan === 'string' ? input.plan : '';
|
|
279
|
+
// Stop spinner while waiting for user approval
|
|
280
|
+
const spinner = ui.spinner();
|
|
281
|
+
spinner.stop();
|
|
282
|
+
try {
|
|
283
|
+
const result = await ui.planApproval({ planContent });
|
|
284
|
+
logToFile('Plan approval result:', result);
|
|
285
|
+
// Restart spinner after user responds
|
|
286
|
+
spinner.start();
|
|
287
|
+
if (result.approved) {
|
|
288
|
+
// Send session update with approved plan
|
|
289
|
+
if (sessionInfo) {
|
|
290
|
+
sendSessionUpdate(sessionInfo.sessionId, planContent, sessionInfo.accessToken, sessionInfo.orgId);
|
|
291
|
+
}
|
|
292
|
+
// User approved the plan - allow the tool
|
|
293
|
+
return {
|
|
294
|
+
behavior: 'allow',
|
|
295
|
+
updatedInput: input,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// User rejected with feedback - deny the tool
|
|
300
|
+
return {
|
|
301
|
+
behavior: 'deny',
|
|
302
|
+
message: result.feedback || 'User rejected plan',
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
logToFile('Error in plan approval:', error);
|
|
308
|
+
return {
|
|
309
|
+
behavior: 'deny',
|
|
310
|
+
message: 'Failed to get user response',
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Tools that are automatically approved without user confirmation
|
|
316
|
+
*/
|
|
317
|
+
const AUTO_APPROVED_TOOLS = new Set([
|
|
318
|
+
'mcp__raindrop-wizard__CompleteIntegration',
|
|
319
|
+
'mcp__raindrop-wizard__LoadPythonDocumentation',
|
|
320
|
+
'mcp__raindrop-wizard__LoadTypeScriptDocumentation',
|
|
321
|
+
'mcp__raindrop-wizard__LoadVercelAiSdkDocumentation',
|
|
322
|
+
'mcp__raindrop-wizard__LoadBrowserDocumentation',
|
|
323
|
+
'mcp__raindrop-wizard__InitializeSession',
|
|
324
|
+
'EnterPlanMode',
|
|
325
|
+
]);
|
|
326
|
+
/**
|
|
327
|
+
* Check if a bash command matches a safe pattern and can be auto-approved.
|
|
328
|
+
*/
|
|
329
|
+
function isSafeBashCommand(command) {
|
|
330
|
+
const trimmed = command.trim();
|
|
331
|
+
return SAFE_BASH_PATTERNS.some((pattern) => {
|
|
332
|
+
if (pattern.startsWith('*')) {
|
|
333
|
+
// Suffix match (e.g., '*--version')
|
|
334
|
+
return trimmed.endsWith(pattern.slice(1));
|
|
335
|
+
}
|
|
336
|
+
else if (pattern.endsWith('*')) {
|
|
337
|
+
// Prefix match (e.g., 'npm install*')
|
|
338
|
+
return trimmed.startsWith(pattern.slice(0, -1));
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
// Exact match
|
|
342
|
+
return trimmed === pattern;
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Create a canUseTool handler that integrates with the UI for approvals.
|
|
348
|
+
* - Handles AskUserQuestion by showing clarifying questions UI
|
|
349
|
+
* - Handles ExitPlanMode by showing plan approval UI
|
|
350
|
+
* - Handles WebSearch by restricting to allowed domains
|
|
351
|
+
* - Shows approval UI for other tools
|
|
352
|
+
* - Supports caching file approvals for "allow all edits to this file"
|
|
353
|
+
*/
|
|
354
|
+
export function createCanUseToolHandler(sessionInfo, approvedFilesCache) {
|
|
355
|
+
return async (toolName, input) => {
|
|
356
|
+
const inputRecord = input;
|
|
357
|
+
logToFile('canUseTool called:', { toolName, input: inputRecord });
|
|
358
|
+
if (AUTO_APPROVED_TOOLS.has(toolName)) {
|
|
359
|
+
return {
|
|
360
|
+
behavior: 'allow',
|
|
361
|
+
updatedInput: inputRecord,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
// Auto-approve safe bash commands
|
|
365
|
+
if (toolName === 'Bash' && typeof inputRecord.command === 'string') {
|
|
366
|
+
// Check if command matches safe patterns
|
|
367
|
+
if (isSafeBashCommand(inputRecord.command)) {
|
|
368
|
+
logToFile('Auto-approving safe bash command:', inputRecord.command);
|
|
369
|
+
return {
|
|
370
|
+
behavior: 'allow',
|
|
371
|
+
updatedInput: inputRecord,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
// Check if command pattern has been approved for the session
|
|
375
|
+
if (isCommandApprovedForSession(inputRecord.command)) {
|
|
376
|
+
logToFile('Auto-approving bash command based on session approval:', inputRecord.command);
|
|
377
|
+
return {
|
|
378
|
+
behavior: 'allow',
|
|
379
|
+
updatedInput: inputRecord,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Handle WebSearch by adding allowed domains restriction
|
|
384
|
+
if (toolName === 'WebSearch') {
|
|
385
|
+
return {
|
|
386
|
+
behavior: 'allow',
|
|
387
|
+
updatedInput: {
|
|
388
|
+
...inputRecord,
|
|
389
|
+
allowed_domains: ['raindrop.ai/docs', 'ai-sdk.dev/docs/'],
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
// Handle AskUserQuestion specially
|
|
394
|
+
if (toolName === 'AskUserQuestion') {
|
|
395
|
+
return handleClarifyingQuestions(inputRecord);
|
|
396
|
+
}
|
|
397
|
+
// Handle ExitPlanMode specially
|
|
398
|
+
if (toolName === 'ExitPlanMode') {
|
|
399
|
+
return handlePlanApproval(inputRecord, sessionInfo);
|
|
400
|
+
}
|
|
401
|
+
// Check if file has been granted blanket approval for this session
|
|
402
|
+
if (toolName === 'Edit' || toolName === 'Write') {
|
|
403
|
+
const fileName = typeof inputRecord.file_path === 'string'
|
|
404
|
+
? inputRecord.file_path
|
|
405
|
+
: typeof inputRecord.path === 'string'
|
|
406
|
+
? inputRecord.path
|
|
407
|
+
: undefined;
|
|
408
|
+
if (fileName && approvedFilesForSession.has(fileName)) {
|
|
409
|
+
logToFile('Auto-approving edit to previously approved file:', fileName);
|
|
410
|
+
return {
|
|
411
|
+
behavior: 'allow',
|
|
412
|
+
updatedInput: inputRecord,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Show approval UI for other tools
|
|
417
|
+
return handleToolApproval(toolName, inputRecord, approvedFilesCache);
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
//# sourceMappingURL=handlers.js.map
|