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
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission Audit - Log permission decisions for transparency
|
|
3
|
+
*
|
|
4
|
+
* Maintains an in-memory audit log with optional file persistence.
|
|
5
|
+
* Useful for debugging, security review, and compliance.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs/promises';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import * as os from 'os';
|
|
11
|
+
import type { PermissionAuditEntry, AuditDecision } from './types.js';
|
|
12
|
+
|
|
13
|
+
const AUDIT_FILE = 'permission-audit.json';
|
|
14
|
+
const MAX_MEMORY_ENTRIES = 1000;
|
|
15
|
+
const MAX_FILE_ENTRIES = 10000;
|
|
16
|
+
const GLOBAL_DIR = path.join(os.homedir(), '.gencode');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Summarize tool input for audit (avoid storing sensitive data)
|
|
20
|
+
*/
|
|
21
|
+
function summarizeInput(tool: string, input: unknown): string {
|
|
22
|
+
if (input === null || input === undefined) return '';
|
|
23
|
+
|
|
24
|
+
if (typeof input === 'string') {
|
|
25
|
+
return input.slice(0, 100);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof input !== 'object') {
|
|
29
|
+
return String(input).slice(0, 100);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const obj = input as Record<string, unknown>;
|
|
33
|
+
|
|
34
|
+
// Tool-specific summaries
|
|
35
|
+
switch (tool) {
|
|
36
|
+
case 'Bash':
|
|
37
|
+
return (obj.command as string)?.slice(0, 100) ?? '';
|
|
38
|
+
|
|
39
|
+
case 'Read':
|
|
40
|
+
case 'Write':
|
|
41
|
+
case 'Edit':
|
|
42
|
+
case 'Glob':
|
|
43
|
+
return (obj.file_path as string) ?? (obj.path as string) ?? '';
|
|
44
|
+
|
|
45
|
+
case 'Grep':
|
|
46
|
+
return `${obj.pattern ?? ''} in ${obj.path ?? '.'}`;
|
|
47
|
+
|
|
48
|
+
case 'WebFetch':
|
|
49
|
+
return (obj.url as string)?.slice(0, 100) ?? '';
|
|
50
|
+
|
|
51
|
+
case 'WebSearch':
|
|
52
|
+
return (obj.query as string)?.slice(0, 100) ?? '';
|
|
53
|
+
|
|
54
|
+
default:
|
|
55
|
+
// Generic summary
|
|
56
|
+
const keys = Object.keys(obj).slice(0, 3);
|
|
57
|
+
return keys.map((k) => `${k}:${String(obj[k]).slice(0, 20)}`).join(', ');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Permission Audit Logger
|
|
63
|
+
*/
|
|
64
|
+
export class PermissionAudit {
|
|
65
|
+
private entries: PermissionAuditEntry[] = [];
|
|
66
|
+
private persistToFile: boolean;
|
|
67
|
+
private filePath: string;
|
|
68
|
+
|
|
69
|
+
constructor(options: { persistToFile?: boolean; auditDir?: string } = {}) {
|
|
70
|
+
this.persistToFile = options.persistToFile ?? false;
|
|
71
|
+
const dir = options.auditDir ?? GLOBAL_DIR;
|
|
72
|
+
this.filePath = path.join(dir, AUDIT_FILE);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Log a permission decision
|
|
77
|
+
*/
|
|
78
|
+
async log(
|
|
79
|
+
tool: string,
|
|
80
|
+
input: unknown,
|
|
81
|
+
decision: AuditDecision,
|
|
82
|
+
reason: string,
|
|
83
|
+
options: {
|
|
84
|
+
matchedRule?: string;
|
|
85
|
+
sessionId?: string;
|
|
86
|
+
} = {}
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
const entry: PermissionAuditEntry = {
|
|
89
|
+
timestamp: new Date(),
|
|
90
|
+
tool,
|
|
91
|
+
inputSummary: summarizeInput(tool, input),
|
|
92
|
+
decision,
|
|
93
|
+
reason,
|
|
94
|
+
matchedRule: options.matchedRule,
|
|
95
|
+
sessionId: options.sessionId,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Add to memory
|
|
99
|
+
this.entries.push(entry);
|
|
100
|
+
|
|
101
|
+
// Trim if too large
|
|
102
|
+
if (this.entries.length > MAX_MEMORY_ENTRIES) {
|
|
103
|
+
this.entries = this.entries.slice(-MAX_MEMORY_ENTRIES);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Persist if enabled
|
|
107
|
+
if (this.persistToFile) {
|
|
108
|
+
await this.appendToFile(entry);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Append entry to file
|
|
114
|
+
*/
|
|
115
|
+
private async appendToFile(entry: PermissionAuditEntry): Promise<void> {
|
|
116
|
+
try {
|
|
117
|
+
// Ensure directory exists
|
|
118
|
+
const dir = path.dirname(this.filePath);
|
|
119
|
+
await fs.mkdir(dir, { recursive: true });
|
|
120
|
+
|
|
121
|
+
// Load existing entries
|
|
122
|
+
let fileEntries: PermissionAuditEntry[] = [];
|
|
123
|
+
try {
|
|
124
|
+
const content = await fs.readFile(this.filePath, 'utf-8');
|
|
125
|
+
fileEntries = JSON.parse(content);
|
|
126
|
+
} catch {
|
|
127
|
+
// File doesn't exist
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Add new entry
|
|
131
|
+
fileEntries.push(entry);
|
|
132
|
+
|
|
133
|
+
// Trim if too large
|
|
134
|
+
if (fileEntries.length > MAX_FILE_ENTRIES) {
|
|
135
|
+
fileEntries = fileEntries.slice(-MAX_FILE_ENTRIES);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Write back
|
|
139
|
+
await fs.writeFile(
|
|
140
|
+
this.filePath,
|
|
141
|
+
JSON.stringify(fileEntries, null, 2),
|
|
142
|
+
'utf-8'
|
|
143
|
+
);
|
|
144
|
+
} catch {
|
|
145
|
+
// Silently fail - audit should not break the app
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get recent audit entries
|
|
151
|
+
*/
|
|
152
|
+
getRecent(count: number = 50): PermissionAuditEntry[] {
|
|
153
|
+
return this.entries.slice(-count);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get all entries in memory
|
|
158
|
+
*/
|
|
159
|
+
getAll(): PermissionAuditEntry[] {
|
|
160
|
+
return [...this.entries];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get entries by tool
|
|
165
|
+
*/
|
|
166
|
+
getByTool(tool: string): PermissionAuditEntry[] {
|
|
167
|
+
return this.entries.filter((e) => e.tool === tool);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get entries by decision
|
|
172
|
+
*/
|
|
173
|
+
getByDecision(decision: AuditDecision): PermissionAuditEntry[] {
|
|
174
|
+
return this.entries.filter((e) => e.decision === decision);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get entries by session
|
|
179
|
+
*/
|
|
180
|
+
getBySession(sessionId: string): PermissionAuditEntry[] {
|
|
181
|
+
return this.entries.filter((e) => e.sessionId === sessionId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get statistics
|
|
186
|
+
*/
|
|
187
|
+
getStats(): {
|
|
188
|
+
total: number;
|
|
189
|
+
allowed: number;
|
|
190
|
+
denied: number;
|
|
191
|
+
confirmed: number;
|
|
192
|
+
rejected: number;
|
|
193
|
+
byTool: Record<string, number>;
|
|
194
|
+
} {
|
|
195
|
+
const stats = {
|
|
196
|
+
total: this.entries.length,
|
|
197
|
+
allowed: 0,
|
|
198
|
+
denied: 0,
|
|
199
|
+
confirmed: 0,
|
|
200
|
+
rejected: 0,
|
|
201
|
+
byTool: {} as Record<string, number>,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
for (const entry of this.entries) {
|
|
205
|
+
// Count by decision
|
|
206
|
+
switch (entry.decision) {
|
|
207
|
+
case 'allowed':
|
|
208
|
+
stats.allowed++;
|
|
209
|
+
break;
|
|
210
|
+
case 'denied':
|
|
211
|
+
stats.denied++;
|
|
212
|
+
break;
|
|
213
|
+
case 'confirmed':
|
|
214
|
+
stats.confirmed++;
|
|
215
|
+
break;
|
|
216
|
+
case 'rejected':
|
|
217
|
+
stats.rejected++;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Count by tool
|
|
222
|
+
stats.byTool[entry.tool] = (stats.byTool[entry.tool] ?? 0) + 1;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return stats;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Clear in-memory entries
|
|
230
|
+
*/
|
|
231
|
+
clear(): void {
|
|
232
|
+
this.entries = [];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Load entries from file
|
|
237
|
+
*/
|
|
238
|
+
async loadFromFile(): Promise<PermissionAuditEntry[]> {
|
|
239
|
+
try {
|
|
240
|
+
const content = await fs.readFile(this.filePath, 'utf-8');
|
|
241
|
+
return JSON.parse(content);
|
|
242
|
+
} catch {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Clear file entries
|
|
249
|
+
*/
|
|
250
|
+
async clearFile(): Promise<void> {
|
|
251
|
+
try {
|
|
252
|
+
await fs.writeFile(this.filePath, '[]', 'utf-8');
|
|
253
|
+
} catch {
|
|
254
|
+
// Silently fail
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Format entry for display
|
|
260
|
+
*/
|
|
261
|
+
formatEntry(entry: PermissionAuditEntry): string {
|
|
262
|
+
const time = entry.timestamp.toLocaleTimeString('en-US', {
|
|
263
|
+
hour: '2-digit',
|
|
264
|
+
minute: '2-digit',
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const decision = entry.decision.toUpperCase().padEnd(9);
|
|
268
|
+
const tool = entry.tool.padEnd(10);
|
|
269
|
+
const input = entry.inputSummary.slice(0, 40).padEnd(40);
|
|
270
|
+
|
|
271
|
+
return `${time} ${decision} ${tool} ${input}`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Format entries as table
|
|
276
|
+
*/
|
|
277
|
+
formatTable(entries: PermissionAuditEntry[]): string {
|
|
278
|
+
const header = 'Time Decision Tool Input';
|
|
279
|
+
const separator = '─'.repeat(header.length);
|
|
280
|
+
const rows = entries.map((e) => this.formatEntry(e));
|
|
281
|
+
|
|
282
|
+
return [header, separator, ...rows].join('\n');
|
|
283
|
+
}
|
|
284
|
+
}
|
package/src/permissions/index.ts
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Permission System
|
|
3
|
+
*
|
|
4
|
+
* Enhanced permission management with:
|
|
5
|
+
* - Pattern-based rules (Claude Code style: "Bash(git add:*)")
|
|
6
|
+
* - Prompt-based permissions (ExitPlanMode style)
|
|
7
|
+
* - Session/project/global scopes
|
|
8
|
+
* - Persistent allowlists
|
|
9
|
+
* - Audit trail
|
|
3
10
|
*/
|
|
4
11
|
|
|
12
|
+
// Types
|
|
5
13
|
export * from './types.js';
|
|
14
|
+
|
|
15
|
+
// Core Manager
|
|
6
16
|
export { PermissionManager } from './manager.js';
|
|
7
|
-
export type { ConfirmCallback } from './manager.js';
|
|
17
|
+
export type { ConfirmCallback, SimpleConfirmCallback } from './manager.js';
|
|
18
|
+
|
|
19
|
+
// Prompt Matching
|
|
20
|
+
export { PromptMatcher, parsePatternString, matchesPatternString } from './prompt-matcher.js';
|
|
21
|
+
|
|
22
|
+
// Persistence
|
|
23
|
+
export { PermissionPersistence } from './persistence.js';
|
|
24
|
+
|
|
25
|
+
// Audit
|
|
26
|
+
export { PermissionAudit } from './audit.js';
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PermissionManager Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { jest } from '@jest/globals';
|
|
6
|
+
import { PermissionManager } from './manager.js';
|
|
7
|
+
import type { PermissionConfig, ApprovalAction } from './types.js';
|
|
8
|
+
|
|
9
|
+
describe('PermissionManager', () => {
|
|
10
|
+
describe('default behavior', () => {
|
|
11
|
+
it('should auto-approve read-only tools by default', async () => {
|
|
12
|
+
const manager = new PermissionManager();
|
|
13
|
+
|
|
14
|
+
const readDecision = await manager.checkPermission({
|
|
15
|
+
tool: 'Read',
|
|
16
|
+
input: { file_path: '/test/file.ts' },
|
|
17
|
+
});
|
|
18
|
+
expect(readDecision.allowed).toBe(true);
|
|
19
|
+
expect(readDecision.requiresConfirmation).toBe(false);
|
|
20
|
+
|
|
21
|
+
const globDecision = await manager.checkPermission({
|
|
22
|
+
tool: 'Glob',
|
|
23
|
+
input: { pattern: '*.ts' },
|
|
24
|
+
});
|
|
25
|
+
expect(globDecision.allowed).toBe(true);
|
|
26
|
+
|
|
27
|
+
const grepDecision = await manager.checkPermission({
|
|
28
|
+
tool: 'Grep',
|
|
29
|
+
input: { pattern: 'test' },
|
|
30
|
+
});
|
|
31
|
+
expect(grepDecision.allowed).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should require confirmation for write operations by default', async () => {
|
|
35
|
+
const manager = new PermissionManager();
|
|
36
|
+
|
|
37
|
+
const bashDecision = await manager.checkPermission({
|
|
38
|
+
tool: 'Bash',
|
|
39
|
+
input: { command: 'echo hello' },
|
|
40
|
+
});
|
|
41
|
+
expect(bashDecision.allowed).toBe(false);
|
|
42
|
+
expect(bashDecision.requiresConfirmation).toBe(true);
|
|
43
|
+
|
|
44
|
+
const writeDecision = await manager.checkPermission({
|
|
45
|
+
tool: 'Write',
|
|
46
|
+
input: { file_path: '/test/file.ts', content: 'test' },
|
|
47
|
+
});
|
|
48
|
+
expect(writeDecision.allowed).toBe(false);
|
|
49
|
+
expect(writeDecision.requiresConfirmation).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('allow rules', () => {
|
|
54
|
+
it('should auto-approve operations matching allow rules', async () => {
|
|
55
|
+
const manager = new PermissionManager({
|
|
56
|
+
config: {
|
|
57
|
+
defaultMode: 'confirm',
|
|
58
|
+
rules: [
|
|
59
|
+
{ tool: 'Bash', mode: 'auto', pattern: 'git add:*' },
|
|
60
|
+
],
|
|
61
|
+
allowedPrompts: [],
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const decision = await manager.checkPermission({
|
|
66
|
+
tool: 'Bash',
|
|
67
|
+
input: { command: 'git add .' },
|
|
68
|
+
});
|
|
69
|
+
expect(decision.allowed).toBe(true);
|
|
70
|
+
expect(decision.requiresConfirmation).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should still require confirmation for non-matching operations', async () => {
|
|
74
|
+
const manager = new PermissionManager({
|
|
75
|
+
config: {
|
|
76
|
+
defaultMode: 'confirm',
|
|
77
|
+
rules: [
|
|
78
|
+
{ tool: 'Bash', mode: 'auto', pattern: 'git add:*' },
|
|
79
|
+
],
|
|
80
|
+
allowedPrompts: [],
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const decision = await manager.checkPermission({
|
|
85
|
+
tool: 'Bash',
|
|
86
|
+
input: { command: 'rm -rf /' },
|
|
87
|
+
});
|
|
88
|
+
expect(decision.allowed).toBe(false);
|
|
89
|
+
expect(decision.requiresConfirmation).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('deny rules', () => {
|
|
94
|
+
it('should block operations matching deny rules', async () => {
|
|
95
|
+
const manager = new PermissionManager({
|
|
96
|
+
config: {
|
|
97
|
+
defaultMode: 'confirm',
|
|
98
|
+
rules: [
|
|
99
|
+
{ tool: 'Bash', mode: 'deny', pattern: 'rm -rf:*' },
|
|
100
|
+
],
|
|
101
|
+
allowedPrompts: [],
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const decision = await manager.checkPermission({
|
|
106
|
+
tool: 'Bash',
|
|
107
|
+
input: { command: 'rm -rf /' },
|
|
108
|
+
});
|
|
109
|
+
expect(decision.allowed).toBe(false);
|
|
110
|
+
expect(decision.requiresConfirmation).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should prioritize deny over allow rules', async () => {
|
|
114
|
+
const manager = new PermissionManager({
|
|
115
|
+
config: {
|
|
116
|
+
defaultMode: 'confirm',
|
|
117
|
+
rules: [
|
|
118
|
+
{ tool: 'Bash', mode: 'auto', pattern: 'rm:*' },
|
|
119
|
+
{ tool: 'Bash', mode: 'deny', pattern: 'rm -rf:*' },
|
|
120
|
+
],
|
|
121
|
+
allowedPrompts: [],
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const decision = await manager.checkPermission({
|
|
126
|
+
tool: 'Bash',
|
|
127
|
+
input: { command: 'rm -rf /' },
|
|
128
|
+
});
|
|
129
|
+
expect(decision.allowed).toBe(false);
|
|
130
|
+
expect(decision.requiresConfirmation).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('session caching', () => {
|
|
135
|
+
it('should cache session approvals', async () => {
|
|
136
|
+
const manager = new PermissionManager();
|
|
137
|
+
|
|
138
|
+
manager.approveForSession('Bash', 'npm test');
|
|
139
|
+
|
|
140
|
+
const decision = await manager.checkPermission({
|
|
141
|
+
tool: 'Bash',
|
|
142
|
+
input: { command: 'npm test' },
|
|
143
|
+
});
|
|
144
|
+
expect(decision.allowed).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should clear session cache', async () => {
|
|
148
|
+
const manager = new PermissionManager();
|
|
149
|
+
|
|
150
|
+
manager.approveForSession('Bash', 'npm test');
|
|
151
|
+
manager.clearSessionApprovals();
|
|
152
|
+
|
|
153
|
+
const decision = await manager.checkPermission({
|
|
154
|
+
tool: 'Bash',
|
|
155
|
+
input: { command: 'npm test' },
|
|
156
|
+
});
|
|
157
|
+
expect(decision.requiresConfirmation).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('prompt-based permissions', () => {
|
|
162
|
+
it('should approve operations matching allowed prompts', async () => {
|
|
163
|
+
const manager = new PermissionManager();
|
|
164
|
+
manager.addAllowedPrompts([
|
|
165
|
+
{ tool: 'Bash', prompt: 'run tests' },
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
const decision = await manager.checkPermission({
|
|
169
|
+
tool: 'Bash',
|
|
170
|
+
input: { command: 'npm test' },
|
|
171
|
+
});
|
|
172
|
+
expect(decision.allowed).toBe(true);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should clear allowed prompts', () => {
|
|
176
|
+
const manager = new PermissionManager();
|
|
177
|
+
manager.addAllowedPrompts([
|
|
178
|
+
{ tool: 'Bash', prompt: 'run tests' },
|
|
179
|
+
]);
|
|
180
|
+
manager.clearAllowedPrompts();
|
|
181
|
+
|
|
182
|
+
expect(manager.getAllowedPrompts()).toHaveLength(0);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('confirmation callback', () => {
|
|
187
|
+
it('should call enhanced confirm callback when confirmation required', async () => {
|
|
188
|
+
const manager = new PermissionManager();
|
|
189
|
+
const mockCallback = jest.fn().mockResolvedValue('allow_once' as ApprovalAction);
|
|
190
|
+
manager.setConfirmCallback(mockCallback);
|
|
191
|
+
|
|
192
|
+
const result = await manager.requestPermission('Bash', { command: 'echo hello' });
|
|
193
|
+
|
|
194
|
+
expect(mockCallback).toHaveBeenCalledWith(
|
|
195
|
+
'Bash',
|
|
196
|
+
{ command: 'echo hello' },
|
|
197
|
+
expect.any(Array)
|
|
198
|
+
);
|
|
199
|
+
expect(result).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should call saveRuleCallback when allow_always is selected', async () => {
|
|
203
|
+
const manager = new PermissionManager();
|
|
204
|
+
const mockSaveCallback = jest.fn().mockResolvedValue(undefined);
|
|
205
|
+
const mockConfirmCallback = jest.fn().mockResolvedValue('allow_always' as ApprovalAction);
|
|
206
|
+
|
|
207
|
+
manager.setConfirmCallback(mockConfirmCallback);
|
|
208
|
+
manager.setSaveRuleCallback(mockSaveCallback);
|
|
209
|
+
|
|
210
|
+
await manager.requestPermission('Bash', { command: 'npm run build' });
|
|
211
|
+
|
|
212
|
+
expect(mockSaveCallback).toHaveBeenCalledWith('Bash', 'npm run:*');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('getModeForTool', () => {
|
|
217
|
+
it('should return auto for read-only tools', () => {
|
|
218
|
+
const manager = new PermissionManager();
|
|
219
|
+
|
|
220
|
+
expect(manager.getModeForTool('Read')).toBe('auto');
|
|
221
|
+
expect(manager.getModeForTool('Glob')).toBe('auto');
|
|
222
|
+
expect(manager.getModeForTool('Grep')).toBe('auto');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should return confirm for write tools', () => {
|
|
226
|
+
const manager = new PermissionManager();
|
|
227
|
+
|
|
228
|
+
expect(manager.getModeForTool('Bash')).toBe('confirm');
|
|
229
|
+
expect(manager.getModeForTool('Write')).toBe('confirm');
|
|
230
|
+
expect(manager.getModeForTool('Edit')).toBe('confirm');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('audit logging', () => {
|
|
235
|
+
it('should log permission decisions', async () => {
|
|
236
|
+
const manager = new PermissionManager({ enableAudit: true });
|
|
237
|
+
|
|
238
|
+
await manager.checkPermission({
|
|
239
|
+
tool: 'Read',
|
|
240
|
+
input: { file_path: '/test/file.ts' },
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const auditLog = manager.getAuditLog(10);
|
|
244
|
+
expect(auditLog.length).toBeGreaterThan(0);
|
|
245
|
+
expect(auditLog[0].tool).toBe('Read');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should provide audit stats', async () => {
|
|
249
|
+
const manager = new PermissionManager({ enableAudit: true });
|
|
250
|
+
|
|
251
|
+
await manager.checkPermission({
|
|
252
|
+
tool: 'Read',
|
|
253
|
+
input: { file_path: '/test/file.ts' },
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const stats = manager.getAuditStats();
|
|
257
|
+
expect(stats.allowed).toBeGreaterThanOrEqual(1);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|