gencode-ai 0.1.0 → 0.1.2
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/.gencode/settings.local.json +7 -0
- package/README.md +20 -102
- package/dist/agent/agent.d.ts +43 -2
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +90 -17
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/types.d.ts +9 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/cli/components/AllModelsSelector.d.ts +11 -0
- package/dist/cli/components/AllModelsSelector.d.ts.map +1 -0
- package/dist/cli/components/AllModelsSelector.js +153 -0
- package/dist/cli/components/AllModelsSelector.js.map +1 -0
- package/dist/cli/components/App.d.ts +8 -1
- package/dist/cli/components/App.d.ts.map +1 -1
- package/dist/cli/components/App.js +276 -40
- package/dist/cli/components/App.js.map +1 -1
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
- package/dist/cli/components/CommandSuggestions.js +3 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -1
- package/dist/cli/components/Header.d.ts +1 -1
- package/dist/cli/components/Header.d.ts.map +1 -1
- package/dist/cli/components/Header.js +4 -6
- package/dist/cli/components/Header.js.map +1 -1
- package/dist/cli/components/Logo.d.ts +1 -0
- package/dist/cli/components/Logo.d.ts.map +1 -1
- package/dist/cli/components/Logo.js +16 -3
- package/dist/cli/components/Logo.js.map +1 -1
- package/dist/cli/components/Messages.d.ts +17 -3
- package/dist/cli/components/Messages.d.ts.map +1 -1
- package/dist/cli/components/Messages.js +70 -18
- package/dist/cli/components/Messages.js.map +1 -1
- package/dist/cli/components/ModelSelector.d.ts +7 -7
- package/dist/cli/components/ModelSelector.d.ts.map +1 -1
- package/dist/cli/components/ModelSelector.js +116 -33
- package/dist/cli/components/ModelSelector.js.map +1 -1
- package/dist/cli/components/PermissionPrompt.d.ts +60 -0
- package/dist/cli/components/PermissionPrompt.d.ts.map +1 -0
- package/dist/cli/components/PermissionPrompt.js +192 -0
- package/dist/cli/components/PermissionPrompt.js.map +1 -0
- package/dist/cli/components/ProviderManager.d.ts +8 -0
- package/dist/cli/components/ProviderManager.d.ts.map +1 -0
- package/dist/cli/components/ProviderManager.js +280 -0
- package/dist/cli/components/ProviderManager.js.map +1 -0
- package/dist/cli/components/Spinner.d.ts +7 -2
- package/dist/cli/components/Spinner.d.ts.map +1 -1
- package/dist/cli/components/Spinner.js +116 -25
- package/dist/cli/components/Spinner.js.map +1 -1
- package/dist/cli/components/TodoList.d.ts +7 -0
- package/dist/cli/components/TodoList.d.ts.map +1 -0
- package/dist/cli/components/TodoList.js +34 -0
- package/dist/cli/components/TodoList.js.map +1 -0
- package/dist/cli/components/index.d.ts +1 -0
- package/dist/cli/components/index.d.ts.map +1 -1
- package/dist/cli/components/index.js +1 -0
- package/dist/cli/components/index.js.map +1 -1
- package/dist/cli/components/markdown.d.ts +9 -0
- package/dist/cli/components/markdown.d.ts.map +1 -0
- package/dist/cli/components/markdown.js +129 -0
- package/dist/cli/components/markdown.js.map +1 -0
- package/dist/cli/components/theme.d.ts +5 -0
- package/dist/cli/components/theme.d.ts.map +1 -1
- package/dist/cli/components/theme.js +7 -0
- package/dist/cli/components/theme.js.map +1 -1
- package/dist/cli/index.js +66 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +14 -4
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +19 -3
- package/dist/config/index.js.map +1 -1
- package/dist/config/levels.d.ts +49 -0
- package/dist/config/levels.d.ts.map +1 -0
- package/dist/config/levels.js +222 -0
- package/dist/config/levels.js.map +1 -0
- package/dist/config/loader.d.ts +46 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +153 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/manager.d.ts +115 -15
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +260 -34
- package/dist/config/manager.js.map +1 -1
- package/dist/config/manager.test.d.ts +5 -0
- package/dist/config/manager.test.d.ts.map +1 -0
- package/dist/config/manager.test.js +192 -0
- package/dist/config/manager.test.js.map +1 -0
- package/dist/config/merger.d.ts +56 -0
- package/dist/config/merger.d.ts.map +1 -0
- package/dist/config/merger.js +177 -0
- package/dist/config/merger.js.map +1 -0
- package/dist/config/providers-config.d.ts +28 -0
- package/dist/config/providers-config.d.ts.map +1 -0
- package/dist/config/providers-config.js +79 -0
- package/dist/config/providers-config.js.map +1 -0
- package/dist/config/test-utils.d.ts +24 -0
- package/dist/config/test-utils.d.ts.map +1 -0
- package/dist/config/test-utils.js +55 -0
- package/dist/config/test-utils.js.map +1 -0
- package/dist/config/types.d.ts +108 -9
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +53 -2
- package/dist/config/types.js.map +1 -1
- package/dist/memory/import-resolver.d.ts +46 -0
- package/dist/memory/import-resolver.d.ts.map +1 -0
- package/dist/memory/import-resolver.js +117 -0
- package/dist/memory/import-resolver.js.map +1 -0
- package/dist/memory/index.d.ts +7 -6
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +7 -5
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/init-prompt.d.ts +22 -0
- package/dist/memory/init-prompt.d.ts.map +1 -0
- package/dist/memory/init-prompt.js +103 -0
- package/dist/memory/init-prompt.js.map +1 -0
- package/dist/memory/memory-manager.d.ts +119 -0
- package/dist/memory/memory-manager.d.ts.map +1 -0
- package/dist/memory/memory-manager.js +587 -0
- package/dist/memory/memory-manager.js.map +1 -0
- package/dist/memory/rules-parser.d.ts +38 -0
- package/dist/memory/rules-parser.d.ts.map +1 -0
- package/dist/memory/rules-parser.js +69 -0
- package/dist/memory/rules-parser.js.map +1 -0
- package/dist/memory/test-utils.d.ts +20 -0
- package/dist/memory/test-utils.d.ts.map +1 -0
- package/dist/memory/test-utils.js +44 -0
- package/dist/memory/test-utils.js.map +1 -0
- package/dist/memory/types.d.ts +70 -63
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +42 -2
- package/dist/memory/types.js.map +1 -1
- package/dist/permissions/audit.d.ts +82 -0
- package/dist/permissions/audit.d.ts.map +1 -0
- package/dist/permissions/audit.js +229 -0
- package/dist/permissions/audit.js.map +1 -0
- package/dist/permissions/index.d.ts +11 -1
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +15 -0
- package/dist/permissions/index.js.map +1 -1
- package/dist/permissions/manager.d.ts +149 -13
- package/dist/permissions/manager.d.ts.map +1 -1
- package/dist/permissions/manager.js +480 -35
- package/dist/permissions/manager.js.map +1 -1
- package/dist/permissions/manager.test.d.ts +5 -0
- package/dist/permissions/manager.test.d.ts.map +1 -0
- package/dist/permissions/manager.test.js +213 -0
- package/dist/permissions/manager.test.js.map +1 -0
- package/dist/permissions/persistence.d.ts +74 -0
- package/dist/permissions/persistence.d.ts.map +1 -0
- package/dist/permissions/persistence.js +248 -0
- package/dist/permissions/persistence.js.map +1 -0
- package/dist/permissions/persistence.test.d.ts +5 -0
- package/dist/permissions/persistence.test.d.ts.map +1 -0
- package/dist/permissions/persistence.test.js +171 -0
- package/dist/permissions/persistence.test.js.map +1 -0
- package/dist/permissions/prompt-matcher.d.ts +64 -0
- package/dist/permissions/prompt-matcher.d.ts.map +1 -0
- package/dist/permissions/prompt-matcher.js +415 -0
- package/dist/permissions/prompt-matcher.js.map +1 -0
- package/dist/permissions/prompt-matcher.test.d.ts +5 -0
- package/dist/permissions/prompt-matcher.test.d.ts.map +1 -0
- package/dist/permissions/prompt-matcher.test.js +107 -0
- package/dist/permissions/prompt-matcher.test.js.map +1 -0
- package/dist/permissions/types.d.ts +157 -0
- package/dist/permissions/types.d.ts.map +1 -1
- package/dist/permissions/types.js +43 -8
- package/dist/permissions/types.js.map +1 -1
- package/dist/prompts/index.d.ts +92 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +241 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +14 -3
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +5 -3
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +13 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/registry.d.ts +66 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +158 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/search/brave.d.ts +14 -0
- package/dist/providers/search/brave.d.ts.map +1 -0
- package/dist/providers/search/brave.js +87 -0
- package/dist/providers/search/brave.js.map +1 -0
- package/dist/providers/search/exa.d.ts +12 -0
- package/dist/providers/search/exa.d.ts.map +1 -0
- package/dist/providers/search/exa.js +158 -0
- package/dist/providers/search/exa.js.map +1 -0
- package/dist/providers/search/index.d.ts +31 -0
- package/dist/providers/search/index.d.ts.map +1 -0
- package/dist/providers/search/index.js +75 -0
- package/dist/providers/search/index.js.map +1 -0
- package/dist/providers/search/serper.d.ts +14 -0
- package/dist/providers/search/serper.d.ts.map +1 -0
- package/dist/providers/search/serper.js +87 -0
- package/dist/providers/search/serper.js.map +1 -0
- package/dist/providers/search/types.d.ts +21 -0
- package/dist/providers/search/types.d.ts.map +1 -0
- package/dist/providers/search/types.js +5 -0
- package/dist/providers/search/types.js.map +1 -0
- package/dist/providers/store.d.ts +104 -0
- package/dist/providers/store.d.ts.map +1 -0
- package/dist/providers/store.js +171 -0
- package/dist/providers/store.js.map +1 -0
- package/dist/providers/types.d.ts +7 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/vertex-ai.d.ts +33 -0
- package/dist/providers/vertex-ai.d.ts.map +1 -0
- package/dist/providers/vertex-ai.js +407 -0
- package/dist/providers/vertex-ai.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -1
- package/dist/tools/builtin/bash.js +2 -1
- package/dist/tools/builtin/bash.js.map +1 -1
- package/dist/tools/builtin/edit.d.ts.map +1 -1
- package/dist/tools/builtin/edit.js +2 -1
- package/dist/tools/builtin/edit.js.map +1 -1
- package/dist/tools/builtin/glob.d.ts.map +1 -1
- package/dist/tools/builtin/glob.js +2 -1
- package/dist/tools/builtin/glob.js.map +1 -1
- package/dist/tools/builtin/grep.d.ts.map +1 -1
- package/dist/tools/builtin/grep.js +2 -1
- package/dist/tools/builtin/grep.js.map +1 -1
- package/dist/tools/builtin/read.d.ts.map +1 -1
- package/dist/tools/builtin/read.js +2 -1
- package/dist/tools/builtin/read.js.map +1 -1
- package/dist/tools/builtin/todowrite.d.ts +15 -0
- package/dist/tools/builtin/todowrite.d.ts.map +1 -0
- package/dist/tools/builtin/todowrite.js +88 -0
- package/dist/tools/builtin/todowrite.js.map +1 -0
- package/dist/tools/builtin/webfetch.d.ts +20 -0
- package/dist/tools/builtin/webfetch.d.ts.map +1 -0
- package/dist/tools/builtin/webfetch.js +228 -0
- package/dist/tools/builtin/webfetch.js.map +1 -0
- package/dist/tools/builtin/websearch.d.ts +17 -0
- package/dist/tools/builtin/websearch.d.ts.map +1 -0
- package/dist/tools/builtin/websearch.js +87 -0
- package/dist/tools/builtin/websearch.js.map +1 -0
- package/dist/tools/builtin/write.d.ts.map +1 -1
- package/dist/tools/builtin/write.js +2 -1
- package/dist/tools/builtin/write.js.map +1 -1
- package/dist/tools/index.d.ts +18 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +28 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/types.d.ts +41 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +16 -0
- package/dist/tools/types.js.map +1 -1
- package/dist/tools/utils/ssrf.d.ts +18 -0
- package/dist/tools/utils/ssrf.d.ts.map +1 -0
- package/dist/tools/utils/ssrf.js +70 -0
- package/dist/tools/utils/ssrf.js.map +1 -0
- package/docs/README.md +5 -4
- package/docs/config-system-comparison.md +707 -0
- package/docs/memory-system.md +238 -0
- package/docs/permissions.md +368 -0
- package/docs/proposals/0001-web-fetch-tool.md +32 -2
- package/docs/proposals/0002-web-search-tool.md +59 -2
- package/docs/proposals/0005-todo-system.md +350 -85
- package/docs/proposals/0006-memory-system.md +11 -10
- package/docs/proposals/0012-ask-user-question.md +941 -206
- package/docs/proposals/0023-permission-enhancements.md +61 -2
- package/docs/proposals/0041-configuration-system.md +587 -0
- package/docs/proposals/0042-prompt-optimization.md +866 -0
- package/docs/proposals/README.md +8 -6
- package/docs/providers.md +220 -0
- package/jest.config.js +26 -0
- package/package.json +14 -3
- package/src/agent/agent.ts +120 -18
- package/src/agent/types.ts +9 -1
- package/src/cli/components/App.tsx +369 -47
- package/src/cli/components/CommandSuggestions.tsx +3 -0
- package/src/cli/components/Header.tsx +11 -17
- package/src/cli/components/Logo.tsx +76 -9
- package/src/cli/components/Messages.tsx +146 -38
- package/src/cli/components/ModelSelector.tsx +169 -52
- package/src/cli/components/PermissionPrompt.tsx +388 -0
- package/src/cli/components/ProviderManager.tsx +534 -0
- package/src/cli/components/Spinner.tsx +138 -25
- package/src/cli/components/TodoList.tsx +54 -0
- package/src/cli/components/index.ts +6 -0
- package/src/cli/components/markdown.ts +157 -0
- package/src/cli/components/theme.ts +7 -0
- package/src/cli/index.tsx +76 -13
- package/src/config/index.ts +79 -4
- package/src/config/levels.test.ts +163 -0
- package/src/config/levels.ts +285 -0
- package/src/config/loader.test.ts +120 -0
- package/src/config/loader.ts +178 -0
- package/src/config/manager.test.ts +215 -0
- package/src/config/manager.ts +328 -40
- package/src/config/merger.test.ts +360 -0
- package/src/config/merger.ts +221 -0
- package/src/config/providers-config.ts +85 -0
- package/src/config/test-utils.ts +79 -0
- package/src/config/types.ts +186 -9
- package/src/memory/import-resolver.test.ts +117 -0
- package/src/memory/import-resolver.ts +149 -0
- package/src/memory/index.ts +11 -0
- package/src/memory/init-prompt.ts +113 -0
- package/src/memory/memory-manager.test.ts +198 -0
- package/src/memory/memory-manager.ts +716 -0
- package/src/memory/rules-parser.test.ts +182 -0
- package/src/memory/rules-parser.ts +82 -0
- package/src/memory/test-utils.ts +60 -0
- package/src/memory/types.ts +119 -0
- package/src/permissions/audit.ts +284 -0
- package/src/permissions/index.ts +20 -1
- package/src/permissions/manager.test.ts +260 -0
- package/src/permissions/manager.ts +592 -40
- package/src/permissions/persistence.test.ts +220 -0
- package/src/permissions/persistence.ts +301 -0
- package/src/permissions/prompt-matcher.test.ts +213 -0
- package/src/permissions/prompt-matcher.ts +472 -0
- package/src/permissions/types.ts +236 -8
- package/src/prompts/index.test.ts +279 -0
- package/src/prompts/index.ts +306 -0
- package/src/prompts/system/anthropic.txt +29 -0
- package/src/prompts/system/base.txt +124 -0
- package/src/prompts/system/gemini.txt +35 -0
- package/src/prompts/system/generic.txt +128 -0
- package/src/prompts/system/openai.txt +29 -0
- package/src/prompts/tools/bash.txt +60 -0
- package/src/prompts/tools/edit.txt +29 -0
- package/src/prompts/tools/glob.txt +35 -0
- package/src/prompts/tools/grep.txt +43 -0
- package/src/prompts/tools/read.txt +22 -0
- package/src/prompts/tools/todowrite.txt +71 -0
- package/src/prompts/tools/webfetch.txt +34 -0
- package/src/prompts/tools/websearch.txt +41 -0
- package/src/prompts/tools/write.txt +23 -0
- package/src/providers/gemini.ts +20 -4
- package/src/providers/index.ts +18 -3
- package/src/providers/registry.ts +198 -0
- package/src/providers/search/brave.ts +132 -0
- package/src/providers/search/exa.ts +217 -0
- package/src/providers/search/index.ts +79 -0
- package/src/providers/search/serper.ts +133 -0
- package/src/providers/search/types.ts +24 -0
- package/src/providers/store.ts +216 -0
- package/src/providers/types.ts +9 -1
- package/src/providers/vertex-ai.ts +594 -0
- package/src/tools/builtin/bash.ts +2 -1
- package/src/tools/builtin/edit.ts +2 -1
- package/src/tools/builtin/glob.ts +2 -1
- package/src/tools/builtin/grep.ts +2 -1
- package/src/tools/builtin/read.ts +2 -1
- package/src/tools/builtin/todowrite.ts +102 -0
- package/src/tools/builtin/webfetch.ts +261 -0
- package/src/tools/builtin/websearch.ts +103 -0
- package/src/tools/builtin/write.ts +2 -1
- package/src/tools/index.ts +28 -2
- package/src/tools/types.ts +32 -0
- package/src/tools/utils/ssrf.ts +79 -0
- package/tsconfig.json +1 -1
- package/CLAUDE.md +0 -70
|
@@ -1,44 +1,311 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Permission Manager -
|
|
2
|
+
* Permission Manager - Enhanced permission control with pattern matching,
|
|
3
|
+
* prompt-based approvals, persistence, and audit logging.
|
|
4
|
+
*
|
|
5
|
+
* Claude Code compatible design with:
|
|
6
|
+
* - Pattern-based rules (e.g., "Bash(git add:*)")
|
|
7
|
+
* - Prompt-based permissions (e.g., { tool: "Bash", prompt: "run tests" })
|
|
8
|
+
* - Session/project/global scopes
|
|
9
|
+
* - Persistent allowlists
|
|
10
|
+
* - Audit trail
|
|
3
11
|
*/
|
|
4
12
|
|
|
5
|
-
import type {
|
|
6
|
-
|
|
13
|
+
import type {
|
|
14
|
+
PermissionConfig,
|
|
15
|
+
PermissionContext,
|
|
16
|
+
PermissionDecision,
|
|
17
|
+
PermissionMode,
|
|
18
|
+
PermissionRule,
|
|
19
|
+
PromptPermission,
|
|
20
|
+
ApprovalAction,
|
|
21
|
+
ApprovalSuggestion,
|
|
22
|
+
ConfirmCallback,
|
|
23
|
+
SimpleConfirmCallback,
|
|
24
|
+
PermissionSettings,
|
|
25
|
+
AuditDecision,
|
|
26
|
+
} from './types.js';
|
|
27
|
+
import {
|
|
28
|
+
DEFAULT_PERMISSION_CONFIG,
|
|
29
|
+
DEFAULT_SUGGESTIONS,
|
|
30
|
+
} from './types.js';
|
|
31
|
+
import { PromptMatcher, matchesPatternString } from './prompt-matcher.js';
|
|
32
|
+
import { PermissionPersistence } from './persistence.js';
|
|
33
|
+
import { PermissionAudit } from './audit.js';
|
|
7
34
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Session approval cache entry
|
|
37
|
+
*/
|
|
38
|
+
interface SessionApproval {
|
|
39
|
+
tool: string;
|
|
40
|
+
pattern?: string;
|
|
41
|
+
approvedAt: Date;
|
|
42
|
+
}
|
|
12
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Enhanced Permission Manager
|
|
46
|
+
*/
|
|
13
47
|
export class PermissionManager {
|
|
14
48
|
private config: PermissionConfig;
|
|
49
|
+
private promptMatcher: PromptMatcher;
|
|
50
|
+
private persistence: PermissionPersistence;
|
|
51
|
+
private audit: PermissionAudit;
|
|
52
|
+
|
|
53
|
+
// Session-scoped caches
|
|
54
|
+
private sessionApprovals: Map<string, SessionApproval> = new Map();
|
|
55
|
+
private sessionRejections: Set<string> = new Set();
|
|
56
|
+
|
|
57
|
+
// Callbacks
|
|
15
58
|
private confirmCallback?: ConfirmCallback;
|
|
16
|
-
private
|
|
59
|
+
private simpleConfirmCallback?: SimpleConfirmCallback;
|
|
60
|
+
private saveRuleCallback?: (tool: string, pattern?: string) => Promise<void>;
|
|
61
|
+
|
|
62
|
+
// Current context
|
|
63
|
+
private sessionId?: string;
|
|
64
|
+
private projectPath?: string;
|
|
17
65
|
|
|
18
|
-
constructor(
|
|
66
|
+
constructor(options: {
|
|
67
|
+
config?: Partial<PermissionConfig>;
|
|
68
|
+
projectPath?: string;
|
|
69
|
+
enableAudit?: boolean;
|
|
70
|
+
} = {}) {
|
|
19
71
|
this.config = {
|
|
20
72
|
...DEFAULT_PERMISSION_CONFIG,
|
|
21
|
-
...config,
|
|
73
|
+
...options.config,
|
|
74
|
+
rules: [
|
|
75
|
+
...DEFAULT_PERMISSION_CONFIG.rules,
|
|
76
|
+
...(options.config?.rules ?? []),
|
|
77
|
+
],
|
|
22
78
|
};
|
|
79
|
+
|
|
80
|
+
this.projectPath = options.projectPath;
|
|
81
|
+
this.promptMatcher = new PromptMatcher();
|
|
82
|
+
this.persistence = new PermissionPersistence(options.projectPath);
|
|
83
|
+
this.audit = new PermissionAudit({
|
|
84
|
+
persistToFile: options.enableAudit ?? false,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Initialize - load persisted rules and settings
|
|
90
|
+
*/
|
|
91
|
+
async initialize(settings?: PermissionSettings): Promise<void> {
|
|
92
|
+
// Load persisted rules
|
|
93
|
+
const persistedRules = await this.persistence.getAllRules();
|
|
94
|
+
const runtimeRules = this.persistence.persistedToRuntime(persistedRules);
|
|
95
|
+
this.config.rules.push(...runtimeRules);
|
|
96
|
+
|
|
97
|
+
// Parse settings permissions
|
|
98
|
+
if (settings) {
|
|
99
|
+
const settingsRules = this.persistence.parseSettingsPermissions(settings);
|
|
100
|
+
this.config.rules.push(...settingsRules);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Set session ID for tracking
|
|
106
|
+
*/
|
|
107
|
+
setSessionId(sessionId: string): void {
|
|
108
|
+
this.sessionId = sessionId;
|
|
23
109
|
}
|
|
24
110
|
|
|
25
111
|
/**
|
|
26
|
-
* Set
|
|
112
|
+
* Set enhanced confirmation callback
|
|
27
113
|
*/
|
|
28
114
|
setConfirmCallback(callback: ConfirmCallback): void {
|
|
29
115
|
this.confirmCallback = callback;
|
|
30
116
|
}
|
|
31
117
|
|
|
32
118
|
/**
|
|
33
|
-
*
|
|
119
|
+
* Set simple yes/no confirmation callback (backward compatible)
|
|
120
|
+
*/
|
|
121
|
+
setSimpleConfirmCallback(callback: SimpleConfirmCallback): void {
|
|
122
|
+
this.simpleConfirmCallback = callback;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Set callback to save permission rules to settings
|
|
127
|
+
* This allows integration with SettingsManager for settings.local.json persistence
|
|
128
|
+
*/
|
|
129
|
+
setSaveRuleCallback(callback: (tool: string, pattern?: string) => Promise<void>): void {
|
|
130
|
+
this.saveRuleCallback = callback;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Add prompt-based permissions (Claude Code ExitPlanMode style)
|
|
135
|
+
*/
|
|
136
|
+
addAllowedPrompts(prompts: PromptPermission[]): void {
|
|
137
|
+
this.config.allowedPrompts.push(...prompts);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Clear prompt-based permissions
|
|
142
|
+
*/
|
|
143
|
+
clearAllowedPrompts(): void {
|
|
144
|
+
this.config.allowedPrompts = [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get current allowed prompts
|
|
149
|
+
*/
|
|
150
|
+
getAllowedPrompts(): PromptPermission[] {
|
|
151
|
+
return [...this.config.allowedPrompts];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get all rules
|
|
156
|
+
*/
|
|
157
|
+
getRules(): PermissionRule[] {
|
|
158
|
+
return [...this.config.rules];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check permission (without prompting user)
|
|
163
|
+
*
|
|
164
|
+
* Flow matches Claude Code official design:
|
|
165
|
+
* 1. DENY rules → block immediately
|
|
166
|
+
* 2. ALLOW rules → auto-approve (includes prompt-based & session cache)
|
|
167
|
+
* 3. ASK rules → force prompt
|
|
168
|
+
* 4. Default behavior (read-only → auto, write → prompt)
|
|
169
|
+
*/
|
|
170
|
+
async checkPermission(context: PermissionContext): Promise<PermissionDecision> {
|
|
171
|
+
const { tool, input } = context;
|
|
172
|
+
|
|
173
|
+
// ========================================================================
|
|
174
|
+
// Step 1: Check DENY rules (highest priority - block immediately)
|
|
175
|
+
// ========================================================================
|
|
176
|
+
const denyRule = this.findMatchingRule(context, 'deny');
|
|
177
|
+
if (denyRule) {
|
|
178
|
+
await this.logAudit(context, 'denied', `Denied by rule: ${this.describeRule(denyRule)}`);
|
|
179
|
+
return {
|
|
180
|
+
allowed: false,
|
|
181
|
+
reason: `Blocked: ${denyRule.description ?? denyRule.pattern ?? tool}`,
|
|
182
|
+
matchedRule: denyRule,
|
|
183
|
+
requiresConfirmation: false,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ========================================================================
|
|
188
|
+
// Step 2: Check ALLOW rules (auto-approve if matched)
|
|
189
|
+
// Includes: settings.allow[], prompt-based permissions, session cache
|
|
190
|
+
// ========================================================================
|
|
191
|
+
|
|
192
|
+
// 2a. Check settings.allow rules
|
|
193
|
+
const autoRule = this.findMatchingRule(context, 'auto');
|
|
194
|
+
if (autoRule) {
|
|
195
|
+
await this.logAudit(context, 'allowed', `Auto-approved: ${this.describeRule(autoRule)}`);
|
|
196
|
+
return {
|
|
197
|
+
allowed: true,
|
|
198
|
+
reason: 'Auto-approved',
|
|
199
|
+
matchedRule: autoRule,
|
|
200
|
+
requiresConfirmation: false,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 2b. Check prompt-based permissions (Plan mode allowedPrompts)
|
|
205
|
+
const promptMatch = this.matchPrompt(tool, input);
|
|
206
|
+
if (promptMatch) {
|
|
207
|
+
await this.logAudit(context, 'allowed', `Prompt match: ${promptMatch.prompt}`);
|
|
208
|
+
return {
|
|
209
|
+
allowed: true,
|
|
210
|
+
reason: `Approved: ${promptMatch.prompt}`,
|
|
211
|
+
matchedRule: promptMatch,
|
|
212
|
+
requiresConfirmation: false,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 2c. Check session approval cache
|
|
217
|
+
const cacheKey = this.getCacheKey(tool, input);
|
|
218
|
+
if (this.sessionApprovals.has(cacheKey)) {
|
|
219
|
+
await this.logAudit(context, 'allowed', 'Session cache hit');
|
|
220
|
+
return {
|
|
221
|
+
allowed: true,
|
|
222
|
+
reason: 'Previously approved',
|
|
223
|
+
requiresConfirmation: false,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ========================================================================
|
|
228
|
+
// Step 3: Check ASK rules (force confirmation)
|
|
229
|
+
// ========================================================================
|
|
230
|
+
const askRule = this.findMatchingRule(context, 'confirm');
|
|
231
|
+
if (askRule) {
|
|
232
|
+
await this.logAudit(context, 'confirmed', `ASK rule matched: ${this.describeRule(askRule)}`);
|
|
233
|
+
return {
|
|
234
|
+
allowed: false,
|
|
235
|
+
reason: `Requires confirmation: ${askRule.description ?? askRule.pattern ?? tool}`,
|
|
236
|
+
matchedRule: askRule,
|
|
237
|
+
requiresConfirmation: true,
|
|
238
|
+
suggestions: this.getSuggestions(context),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ========================================================================
|
|
243
|
+
// Step 4: Default behavior (read-only → auto, write → prompt)
|
|
244
|
+
// ========================================================================
|
|
245
|
+
|
|
246
|
+
// Check session rejection cache
|
|
247
|
+
if (this.sessionRejections.has(cacheKey)) {
|
|
248
|
+
await this.logAudit(context, 'denied', 'Session rejection cache');
|
|
249
|
+
return {
|
|
250
|
+
allowed: false,
|
|
251
|
+
reason: 'Previously denied',
|
|
252
|
+
requiresConfirmation: false,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Default: requires confirmation for write operations
|
|
257
|
+
return {
|
|
258
|
+
allowed: false,
|
|
259
|
+
reason: 'Requires approval',
|
|
260
|
+
requiresConfirmation: true,
|
|
261
|
+
suggestions: this.getSuggestions(context),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Request permission (prompts user if needed)
|
|
267
|
+
*/
|
|
268
|
+
async requestPermission(tool: string, input: unknown): Promise<boolean> {
|
|
269
|
+
const context: PermissionContext = {
|
|
270
|
+
tool,
|
|
271
|
+
input,
|
|
272
|
+
sessionId: this.sessionId,
|
|
273
|
+
projectPath: this.projectPath,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const decision = await this.checkPermission(context);
|
|
277
|
+
|
|
278
|
+
if (decision.allowed) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!decision.requiresConfirmation) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Prompt user for confirmation
|
|
287
|
+
const action = await this.promptUser(tool, input, decision.suggestions);
|
|
288
|
+
|
|
289
|
+
return this.handleApprovalAction(action, context);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Backward-compatible check method
|
|
294
|
+
*/
|
|
295
|
+
async check(tool: string, input: unknown): Promise<boolean> {
|
|
296
|
+
return this.requestPermission(tool, input);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get permission mode for a tool (for simple queries)
|
|
34
301
|
*/
|
|
35
302
|
getModeForTool(tool: string): PermissionMode {
|
|
36
303
|
for (const rule of this.config.rules) {
|
|
37
304
|
if (typeof rule.tool === 'string') {
|
|
38
|
-
if (rule.tool === tool) {
|
|
305
|
+
if (rule.tool === tool && !rule.pattern) {
|
|
39
306
|
return rule.mode;
|
|
40
307
|
}
|
|
41
|
-
} else if (rule.tool.test(tool)) {
|
|
308
|
+
} else if (rule.tool.test(tool) && !rule.pattern) {
|
|
42
309
|
return rule.mode;
|
|
43
310
|
}
|
|
44
311
|
}
|
|
@@ -46,35 +313,288 @@ export class PermissionManager {
|
|
|
46
313
|
}
|
|
47
314
|
|
|
48
315
|
/**
|
|
49
|
-
*
|
|
316
|
+
* Approve a tool for this session
|
|
50
317
|
*/
|
|
51
|
-
|
|
52
|
-
const
|
|
318
|
+
approveForSession(tool: string, pattern?: string): void {
|
|
319
|
+
const key = pattern ? `${tool}:${pattern}` : tool;
|
|
320
|
+
this.sessionApprovals.set(key, {
|
|
321
|
+
tool,
|
|
322
|
+
pattern,
|
|
323
|
+
approvedAt: new Date(),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
53
326
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Add a persistent allow rule
|
|
329
|
+
*/
|
|
330
|
+
async addAllowRule(
|
|
331
|
+
tool: string,
|
|
332
|
+
pattern?: string,
|
|
333
|
+
scope: 'project' | 'global' = 'global'
|
|
334
|
+
): Promise<void> {
|
|
335
|
+
await this.persistence.addRule(tool, 'auto', {
|
|
336
|
+
pattern,
|
|
337
|
+
scope,
|
|
338
|
+
description: `User approved: ${tool}${pattern ? `(${pattern})` : ''}`,
|
|
339
|
+
});
|
|
57
340
|
|
|
58
|
-
|
|
59
|
-
|
|
341
|
+
// Also add to runtime config
|
|
342
|
+
this.config.rules.push({
|
|
343
|
+
tool,
|
|
344
|
+
mode: 'auto',
|
|
345
|
+
pattern,
|
|
346
|
+
scope,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
60
349
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
350
|
+
/**
|
|
351
|
+
* Add a persistent deny rule
|
|
352
|
+
*/
|
|
353
|
+
async addDenyRule(
|
|
354
|
+
tool: string,
|
|
355
|
+
pattern?: string,
|
|
356
|
+
scope: 'project' | 'global' = 'global'
|
|
357
|
+
): Promise<void> {
|
|
358
|
+
await this.persistence.addRule(tool, 'deny', {
|
|
359
|
+
pattern,
|
|
360
|
+
scope,
|
|
361
|
+
description: `User blocked: ${tool}${pattern ? `(${pattern})` : ''}`,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Also add to runtime config
|
|
365
|
+
this.config.rules.push({
|
|
366
|
+
tool,
|
|
367
|
+
mode: 'deny',
|
|
368
|
+
pattern,
|
|
369
|
+
scope,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Clear session approvals
|
|
375
|
+
*/
|
|
376
|
+
clearSessionApprovals(): void {
|
|
377
|
+
this.sessionApprovals.clear();
|
|
378
|
+
this.sessionRejections.clear();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Get audit log
|
|
383
|
+
*/
|
|
384
|
+
getAuditLog(count?: number): ReturnType<PermissionAudit['getRecent']> {
|
|
385
|
+
return this.audit.getRecent(count);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get audit statistics
|
|
390
|
+
*/
|
|
391
|
+
getAuditStats(): ReturnType<PermissionAudit['getStats']> {
|
|
392
|
+
return this.audit.getStats();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Get persistence manager (for direct access)
|
|
397
|
+
*/
|
|
398
|
+
getPersistence(): PermissionPersistence {
|
|
399
|
+
return this.persistence;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ============================================================================
|
|
403
|
+
// Private Methods
|
|
404
|
+
// ============================================================================
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Find a matching rule for the given context and mode
|
|
408
|
+
*/
|
|
409
|
+
private findMatchingRule(
|
|
410
|
+
context: PermissionContext,
|
|
411
|
+
mode: PermissionMode
|
|
412
|
+
): PermissionRule | undefined {
|
|
413
|
+
for (const rule of this.config.rules) {
|
|
414
|
+
if (rule.mode !== mode) continue;
|
|
415
|
+
if (!this.matchesTool(rule.tool, context.tool)) continue;
|
|
416
|
+
|
|
417
|
+
// If rule has pattern, check it (pass tool name for path matching)
|
|
418
|
+
if (rule.pattern) {
|
|
419
|
+
if (!this.matchesPattern(rule.pattern, context.input, context.tool)) {
|
|
420
|
+
continue;
|
|
66
421
|
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Check expiration
|
|
425
|
+
if (rule.expiresAt && new Date() > rule.expiresAt) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return rule;
|
|
430
|
+
}
|
|
67
431
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
432
|
+
return undefined;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Check if tool matches rule
|
|
437
|
+
*/
|
|
438
|
+
private matchesTool(rulePattern: string | RegExp, tool: string): boolean {
|
|
439
|
+
if (typeof rulePattern === 'string') {
|
|
440
|
+
return rulePattern === tool || rulePattern === '*';
|
|
441
|
+
}
|
|
442
|
+
return rulePattern.test(tool);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Check if input matches pattern
|
|
447
|
+
*/
|
|
448
|
+
private matchesPattern(pattern: string | RegExp, input: unknown, tool?: string): boolean {
|
|
449
|
+
if (typeof pattern === 'string') {
|
|
450
|
+
return matchesPatternString(pattern, input, tool);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const inputStr = typeof input === 'string'
|
|
454
|
+
? input
|
|
455
|
+
: JSON.stringify(input);
|
|
456
|
+
|
|
457
|
+
return pattern.test(inputStr);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Match against prompt-based permissions
|
|
462
|
+
*/
|
|
463
|
+
private matchPrompt(tool: string, input: unknown): PromptPermission | null {
|
|
464
|
+
for (const permission of this.config.allowedPrompts) {
|
|
465
|
+
if (permission.tool !== tool) continue;
|
|
466
|
+
|
|
467
|
+
if (this.promptMatcher.matches(permission.prompt, input)) {
|
|
468
|
+
return permission;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Generate cache key for session approvals
|
|
476
|
+
*/
|
|
477
|
+
private getCacheKey(tool: string, input: unknown): string {
|
|
478
|
+
// For Bash, use the command as the key
|
|
479
|
+
if (tool === 'Bash' && input && typeof input === 'object') {
|
|
480
|
+
const cmd = (input as { command?: string }).command;
|
|
481
|
+
if (cmd) {
|
|
482
|
+
// Use first few tokens as pattern
|
|
483
|
+
const tokens = cmd.split(/\s+/).slice(0, 3).join(' ');
|
|
484
|
+
return `${tool}:${tokens}`;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// For file operations, use the path
|
|
489
|
+
if (['Read', 'Write', 'Edit', 'Glob'].includes(tool)) {
|
|
490
|
+
if (input && typeof input === 'object') {
|
|
491
|
+
const path = (input as { file_path?: string; path?: string }).file_path
|
|
492
|
+
?? (input as { file_path?: string; path?: string }).path;
|
|
493
|
+
if (path) {
|
|
494
|
+
return `${tool}:${path}`;
|
|
71
495
|
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Default: use full input JSON
|
|
500
|
+
return `${tool}:${JSON.stringify(input)}`;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Get approval suggestions for a context
|
|
505
|
+
*/
|
|
506
|
+
private getSuggestions(context: PermissionContext): ApprovalSuggestion[] {
|
|
507
|
+
// Start with default suggestions
|
|
508
|
+
const suggestions = [...DEFAULT_SUGGESTIONS];
|
|
509
|
+
|
|
510
|
+
// Customize "Always allow" label based on tool/input
|
|
511
|
+
const alwaysIndex = suggestions.findIndex((s) => s.action === 'allow_always');
|
|
512
|
+
if (alwaysIndex >= 0 && context.tool === 'Bash') {
|
|
513
|
+
const input = context.input as { command?: string };
|
|
514
|
+
if (input?.command) {
|
|
515
|
+
const prefix = input.command.split(/\s+/).slice(0, 2).join(' ');
|
|
516
|
+
suggestions[alwaysIndex] = {
|
|
517
|
+
...suggestions[alwaysIndex],
|
|
518
|
+
label: `Always allow "${prefix}*"`,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return suggestions;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Prompt user for approval
|
|
528
|
+
*/
|
|
529
|
+
private async promptUser(
|
|
530
|
+
tool: string,
|
|
531
|
+
input: unknown,
|
|
532
|
+
suggestions?: ApprovalSuggestion[]
|
|
533
|
+
): Promise<ApprovalAction> {
|
|
534
|
+
const effectiveSuggestions = suggestions ?? DEFAULT_SUGGESTIONS;
|
|
535
|
+
|
|
536
|
+
// Use enhanced callback if available
|
|
537
|
+
if (this.confirmCallback) {
|
|
538
|
+
return this.confirmCallback(tool, input, effectiveSuggestions);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Fall back to simple callback
|
|
542
|
+
if (this.simpleConfirmCallback) {
|
|
543
|
+
const confirmed = await this.simpleConfirmCallback(tool, input);
|
|
544
|
+
return confirmed ? 'allow_session' : 'deny';
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// No callback - auto-approve (non-interactive mode)
|
|
548
|
+
return 'allow_once';
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Handle the user's approval action
|
|
553
|
+
*/
|
|
554
|
+
private async handleApprovalAction(
|
|
555
|
+
action: ApprovalAction,
|
|
556
|
+
context: PermissionContext
|
|
557
|
+
): Promise<boolean> {
|
|
558
|
+
const { tool, input } = context;
|
|
559
|
+
const cacheKey = this.getCacheKey(tool, input);
|
|
560
|
+
|
|
561
|
+
switch (action) {
|
|
562
|
+
case 'allow_once':
|
|
563
|
+
await this.logAudit(context, 'confirmed', 'User approved once');
|
|
564
|
+
return true;
|
|
72
565
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
566
|
+
case 'allow_session':
|
|
567
|
+
this.sessionApprovals.set(cacheKey, {
|
|
568
|
+
tool,
|
|
569
|
+
approvedAt: new Date(),
|
|
570
|
+
});
|
|
571
|
+
await this.logAudit(context, 'confirmed', 'User approved for session');
|
|
572
|
+
return true;
|
|
573
|
+
|
|
574
|
+
case 'allow_always':
|
|
575
|
+
// Extract pattern for persistent rule
|
|
576
|
+
const pattern = this.extractPattern(tool, input);
|
|
577
|
+
// Use saveRuleCallback if available (saves to settings.local.json)
|
|
578
|
+
// Otherwise fallback to addAllowRule (saves to permissions.json)
|
|
579
|
+
if (this.saveRuleCallback) {
|
|
580
|
+
await this.saveRuleCallback(tool, pattern);
|
|
581
|
+
} else {
|
|
582
|
+
await this.addAllowRule(tool, pattern, 'project');
|
|
76
583
|
}
|
|
77
|
-
|
|
584
|
+
// Also add to runtime config for immediate effect
|
|
585
|
+
this.config.rules.push({
|
|
586
|
+
tool,
|
|
587
|
+
mode: 'auto',
|
|
588
|
+
pattern,
|
|
589
|
+
scope: 'project',
|
|
590
|
+
});
|
|
591
|
+
await this.logAudit(context, 'confirmed', 'User approved for project');
|
|
592
|
+
return true;
|
|
593
|
+
|
|
594
|
+
case 'deny':
|
|
595
|
+
this.sessionRejections.add(cacheKey);
|
|
596
|
+
await this.logAudit(context, 'rejected', 'User denied');
|
|
597
|
+
return false;
|
|
78
598
|
|
|
79
599
|
default:
|
|
80
600
|
return false;
|
|
@@ -82,16 +602,48 @@ export class PermissionManager {
|
|
|
82
602
|
}
|
|
83
603
|
|
|
84
604
|
/**
|
|
85
|
-
*
|
|
605
|
+
* Extract a pattern from tool input for persistent rules
|
|
86
606
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
607
|
+
private extractPattern(tool: string, input: unknown): string | undefined {
|
|
608
|
+
if (tool === 'Bash' && input && typeof input === 'object') {
|
|
609
|
+
const cmd = (input as { command?: string }).command;
|
|
610
|
+
if (cmd) {
|
|
611
|
+
// Use first two tokens + wildcard
|
|
612
|
+
const tokens = cmd.split(/\s+/).slice(0, 2);
|
|
613
|
+
return tokens.join(' ') + ':*';
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return undefined;
|
|
89
618
|
}
|
|
90
619
|
|
|
91
620
|
/**
|
|
92
|
-
*
|
|
621
|
+
* Describe a rule for logging
|
|
93
622
|
*/
|
|
94
|
-
|
|
95
|
-
|
|
623
|
+
private describeRule(rule: PermissionRule): string {
|
|
624
|
+
const parts = [rule.tool.toString()];
|
|
625
|
+
if (rule.pattern) {
|
|
626
|
+
parts.push(`(${rule.pattern})`);
|
|
627
|
+
}
|
|
628
|
+
if (rule.description) {
|
|
629
|
+
parts.push(`- ${rule.description}`);
|
|
630
|
+
}
|
|
631
|
+
return parts.join('');
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Log audit entry
|
|
636
|
+
*/
|
|
637
|
+
private async logAudit(
|
|
638
|
+
context: PermissionContext,
|
|
639
|
+
decision: AuditDecision,
|
|
640
|
+
reason: string
|
|
641
|
+
): Promise<void> {
|
|
642
|
+
await this.audit.log(context.tool, context.input, decision, reason, {
|
|
643
|
+
sessionId: context.sessionId,
|
|
644
|
+
});
|
|
96
645
|
}
|
|
97
646
|
}
|
|
647
|
+
|
|
648
|
+
// Re-export types and utilities
|
|
649
|
+
export type { ConfirmCallback, SimpleConfirmCallback };
|