@jean2/sdk 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/dist/client.d.ts +34 -0
  2. package/dist/client.d.ts.map +1 -0
  3. package/dist/client.js +102 -0
  4. package/dist/client.js.map +1 -0
  5. package/dist/emitter.d.ts +12 -0
  6. package/dist/emitter.d.ts.map +1 -0
  7. package/dist/emitter.js +59 -0
  8. package/dist/emitter.js.map +1 -0
  9. package/dist/errors.d.ts +25 -0
  10. package/dist/errors.d.ts.map +1 -0
  11. package/dist/errors.js +49 -0
  12. package/dist/errors.js.map +1 -0
  13. package/dist/index.d.ts +36 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +30 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/namespaces/chat.d.ts +12 -0
  18. package/dist/namespaces/chat.d.ts.map +1 -0
  19. package/dist/namespaces/chat.js +18 -0
  20. package/dist/namespaces/chat.js.map +1 -0
  21. package/dist/namespaces/permissions.d.ts +26 -0
  22. package/dist/namespaces/permissions.d.ts.map +1 -0
  23. package/dist/namespaces/permissions.js +33 -0
  24. package/dist/namespaces/permissions.js.map +1 -0
  25. package/dist/namespaces/providers.d.ts +8 -0
  26. package/dist/namespaces/providers.d.ts.map +1 -0
  27. package/dist/namespaces/providers.js +13 -0
  28. package/dist/namespaces/providers.js.map +1 -0
  29. package/dist/namespaces/queue.d.ts +11 -0
  30. package/dist/namespaces/queue.d.ts.map +1 -0
  31. package/dist/namespaces/queue.js +19 -0
  32. package/dist/namespaces/queue.js.map +1 -0
  33. package/dist/namespaces/sessions.d.ts +28 -0
  34. package/dist/namespaces/sessions.d.ts.map +1 -0
  35. package/dist/namespaces/sessions.js +43 -0
  36. package/dist/namespaces/sessions.js.map +1 -0
  37. package/dist/namespaces/terminal.d.ts +100 -0
  38. package/dist/namespaces/terminal.d.ts.map +1 -0
  39. package/dist/namespaces/terminal.js +495 -0
  40. package/dist/namespaces/terminal.js.map +1 -0
  41. package/dist/rest/attachments.d.ts +40 -0
  42. package/dist/rest/attachments.d.ts.map +1 -0
  43. package/dist/rest/attachments.js +40 -0
  44. package/dist/rest/attachments.js.map +1 -0
  45. package/dist/rest/config.d.ts +88 -0
  46. package/dist/rest/config.d.ts.map +1 -0
  47. package/dist/rest/config.js +83 -0
  48. package/dist/rest/config.js.map +1 -0
  49. package/dist/rest/files.d.ts +37 -0
  50. package/dist/rest/files.d.ts.map +1 -0
  51. package/dist/rest/files.js +59 -0
  52. package/dist/rest/files.js.map +1 -0
  53. package/dist/rest/http-namespace.d.ts +54 -0
  54. package/dist/rest/http-namespace.d.ts.map +1 -0
  55. package/dist/rest/http-namespace.js +67 -0
  56. package/dist/rest/http-namespace.js.map +1 -0
  57. package/dist/rest/mcp.d.ts +43 -0
  58. package/dist/rest/mcp.d.ts.map +1 -0
  59. package/dist/rest/mcp.js +41 -0
  60. package/dist/rest/mcp.js.map +1 -0
  61. package/dist/rest/models.d.ts +12 -0
  62. package/dist/rest/models.d.ts.map +1 -0
  63. package/dist/rest/models.js +10 -0
  64. package/dist/rest/models.js.map +1 -0
  65. package/dist/rest/preconfigs.d.ts +51 -0
  66. package/dist/rest/preconfigs.d.ts.map +1 -0
  67. package/dist/rest/preconfigs.js +29 -0
  68. package/dist/rest/preconfigs.js.map +1 -0
  69. package/dist/rest/prompts.d.ts +12 -0
  70. package/dist/rest/prompts.d.ts.map +1 -0
  71. package/dist/rest/prompts.js +10 -0
  72. package/dist/rest/prompts.js.map +1 -0
  73. package/dist/rest/providers.d.ts +56 -0
  74. package/dist/rest/providers.d.ts.map +1 -0
  75. package/dist/rest/providers.js +59 -0
  76. package/dist/rest/providers.js.map +1 -0
  77. package/dist/rest/sessions.d.ts +54 -0
  78. package/dist/rest/sessions.d.ts.map +1 -0
  79. package/dist/rest/sessions.js +47 -0
  80. package/dist/rest/sessions.js.map +1 -0
  81. package/dist/rest/terminals.d.ts +28 -0
  82. package/dist/rest/terminals.d.ts.map +1 -0
  83. package/dist/rest/terminals.js +22 -0
  84. package/dist/rest/terminals.js.map +1 -0
  85. package/dist/rest/tools.d.ts +34 -0
  86. package/dist/rest/tools.d.ts.map +1 -0
  87. package/dist/rest/tools.js +37 -0
  88. package/dist/rest/tools.js.map +1 -0
  89. package/dist/rest/workspaces.d.ts +28 -0
  90. package/dist/rest/workspaces.d.ts.map +1 -0
  91. package/dist/rest/workspaces.js +34 -0
  92. package/dist/rest/workspaces.js.map +1 -0
  93. package/dist/shared-protocol/client.d.ts +148 -0
  94. package/dist/shared-protocol/client.d.ts.map +1 -0
  95. package/dist/shared-protocol/client.js +4 -0
  96. package/dist/shared-protocol/client.js.map +1 -0
  97. package/dist/shared-protocol/index.d.ts +4 -0
  98. package/dist/shared-protocol/index.d.ts.map +1 -0
  99. package/dist/shared-protocol/index.js +4 -0
  100. package/dist/shared-protocol/index.js.map +1 -0
  101. package/dist/shared-protocol/server.d.ts +233 -0
  102. package/dist/shared-protocol/server.d.ts.map +1 -0
  103. package/dist/shared-protocol/server.js +2 -0
  104. package/dist/shared-protocol/server.js.map +1 -0
  105. package/dist/shared-protocol/terminal.d.ts +55 -0
  106. package/dist/shared-protocol/terminal.d.ts.map +1 -0
  107. package/dist/shared-protocol/terminal.js +2 -0
  108. package/dist/shared-protocol/terminal.js.map +1 -0
  109. package/dist/shared-types/configuration.d.ts +100 -0
  110. package/dist/shared-types/configuration.d.ts.map +1 -0
  111. package/dist/shared-types/configuration.js +2 -0
  112. package/dist/shared-types/configuration.js.map +1 -0
  113. package/dist/shared-types/file.d.ts +47 -0
  114. package/dist/shared-types/file.d.ts.map +1 -0
  115. package/dist/shared-types/file.js +2 -0
  116. package/dist/shared-types/file.js.map +1 -0
  117. package/dist/shared-types/index.d.ts +20 -0
  118. package/dist/shared-types/index.d.ts.map +1 -0
  119. package/dist/shared-types/index.js +20 -0
  120. package/dist/shared-types/index.js.map +1 -0
  121. package/dist/shared-types/interrupt.d.ts +25 -0
  122. package/dist/shared-types/interrupt.d.ts.map +1 -0
  123. package/dist/shared-types/interrupt.js +2 -0
  124. package/dist/shared-types/interrupt.js.map +1 -0
  125. package/dist/shared-types/mcp.d.ts +49 -0
  126. package/dist/shared-types/mcp.d.ts.map +1 -0
  127. package/dist/shared-types/mcp.js +6 -0
  128. package/dist/shared-types/mcp.js.map +1 -0
  129. package/dist/shared-types/message.d.ts +166 -0
  130. package/dist/shared-types/message.d.ts.map +1 -0
  131. package/dist/shared-types/message.js +34 -0
  132. package/dist/shared-types/message.js.map +1 -0
  133. package/dist/shared-types/model.d.ts +60 -0
  134. package/dist/shared-types/model.d.ts.map +1 -0
  135. package/dist/shared-types/model.js +2 -0
  136. package/dist/shared-types/model.js.map +1 -0
  137. package/dist/shared-types/permission.d.ts +209 -0
  138. package/dist/shared-types/permission.d.ts.map +1 -0
  139. package/dist/shared-types/permission.js +787 -0
  140. package/dist/shared-types/permission.js.map +1 -0
  141. package/dist/shared-types/preconfig.d.ts +31 -0
  142. package/dist/shared-types/preconfig.d.ts.map +1 -0
  143. package/dist/shared-types/preconfig.js +2 -0
  144. package/dist/shared-types/preconfig.js.map +1 -0
  145. package/dist/shared-types/prompt.d.ts +6 -0
  146. package/dist/shared-types/prompt.d.ts.map +1 -0
  147. package/dist/shared-types/prompt.js +2 -0
  148. package/dist/shared-types/prompt.js.map +1 -0
  149. package/dist/shared-types/provider.d.ts +31 -0
  150. package/dist/shared-types/provider.d.ts.map +1 -0
  151. package/dist/shared-types/provider.js +2 -0
  152. package/dist/shared-types/provider.js.map +1 -0
  153. package/dist/shared-types/runtime.d.ts +27 -0
  154. package/dist/shared-types/runtime.d.ts.map +1 -0
  155. package/dist/shared-types/runtime.js +2 -0
  156. package/dist/shared-types/runtime.js.map +1 -0
  157. package/dist/shared-types/server.d.ts +16 -0
  158. package/dist/shared-types/server.d.ts.map +1 -0
  159. package/dist/shared-types/server.js +2 -0
  160. package/dist/shared-types/server.js.map +1 -0
  161. package/dist/shared-types/session.d.ts +24 -0
  162. package/dist/shared-types/session.d.ts.map +1 -0
  163. package/dist/shared-types/session.js +2 -0
  164. package/dist/shared-types/session.js.map +1 -0
  165. package/dist/shared-types/skill.d.ts +24 -0
  166. package/dist/shared-types/skill.d.ts.map +1 -0
  167. package/dist/shared-types/skill.js +2 -0
  168. package/dist/shared-types/skill.js.map +1 -0
  169. package/dist/shared-types/task.d.ts +45 -0
  170. package/dist/shared-types/task.d.ts.map +1 -0
  171. package/dist/shared-types/task.js +2 -0
  172. package/dist/shared-types/task.js.map +1 -0
  173. package/dist/shared-types/tool.d.ts +250 -0
  174. package/dist/shared-types/tool.d.ts.map +1 -0
  175. package/dist/shared-types/tool.js +3 -0
  176. package/dist/shared-types/tool.js.map +1 -0
  177. package/dist/shared-types/ui.d.ts +35 -0
  178. package/dist/shared-types/ui.d.ts.map +1 -0
  179. package/dist/shared-types/ui.js +52 -0
  180. package/dist/shared-types/ui.js.map +1 -0
  181. package/dist/shared-types/visualization.d.ts +208 -0
  182. package/dist/shared-types/visualization.d.ts.map +1 -0
  183. package/dist/shared-types/visualization.js +2 -0
  184. package/dist/shared-types/visualization.js.map +1 -0
  185. package/dist/shared-types/workspace.d.ts +9 -0
  186. package/dist/shared-types/workspace.d.ts.map +1 -0
  187. package/dist/shared-types/workspace.js +2 -0
  188. package/dist/shared-types/workspace.js.map +1 -0
  189. package/dist/shared-utils/index.d.ts +2 -0
  190. package/dist/shared-utils/index.d.ts.map +1 -0
  191. package/dist/shared-utils/index.js +2 -0
  192. package/dist/shared-utils/index.js.map +1 -0
  193. package/dist/shared-utils/model-context.d.ts +15 -0
  194. package/dist/shared-utils/model-context.d.ts.map +1 -0
  195. package/dist/shared-utils/model-context.js +22 -0
  196. package/dist/shared-utils/model-context.js.map +1 -0
  197. package/dist/shared.d.ts +4 -0
  198. package/dist/shared.d.ts.map +1 -0
  199. package/dist/shared.js +4 -0
  200. package/dist/shared.js.map +1 -0
  201. package/dist/transport/http.d.ts +37 -0
  202. package/dist/transport/http.d.ts.map +1 -0
  203. package/dist/transport/http.js +75 -0
  204. package/dist/transport/http.js.map +1 -0
  205. package/dist/transport/websocket.d.ts +33 -0
  206. package/dist/transport/websocket.d.ts.map +1 -0
  207. package/dist/transport/websocket.js +145 -0
  208. package/dist/transport/websocket.js.map +1 -0
  209. package/dist/types/index.d.ts +22 -0
  210. package/dist/types/index.d.ts.map +1 -0
  211. package/dist/types/index.js +3 -0
  212. package/dist/types/index.js.map +1 -0
  213. package/dist/types/rest-responses.d.ts +389 -0
  214. package/dist/types/rest-responses.d.ts.map +1 -0
  215. package/dist/types/rest-responses.js +2 -0
  216. package/dist/types/rest-responses.js.map +1 -0
  217. package/dist/types/sdk-types.d.ts +32 -0
  218. package/dist/types/sdk-types.d.ts.map +1 -0
  219. package/dist/types/sdk-types.js +2 -0
  220. package/dist/types/sdk-types.js.map +1 -0
  221. package/dist/types/server-messages.d.ts +132 -0
  222. package/dist/types/server-messages.d.ts.map +1 -0
  223. package/dist/types/server-messages.js +135 -0
  224. package/dist/types/server-messages.js.map +1 -0
  225. package/dist/version.d.ts +2 -0
  226. package/dist/version.d.ts.map +1 -0
  227. package/dist/version.js +2 -0
  228. package/dist/version.js.map +1 -0
  229. package/package.json +31 -0
@@ -0,0 +1,787 @@
1
+ // =============================================================================
2
+ // Canonical Permission Contract
3
+ //
4
+ // Design principles:
5
+ // 1. Tool-authored, platform-rendered: Tools define structured PermissionAsk
6
+ // objects; client renders generically via the same UI
7
+ // 2. Shell is first-class: Explicit shell-command permission category with
8
+ // pattern matching (command names like rm, sudo, curl)
9
+ // 3. Explicit grant semantics: Specific grant scopes (once/session/workspace)
10
+ // with duration support. No 'always' scope — use 'workspace' instead.
11
+ // 4. ask.* is the only interactive channel: No side-channel grants
12
+ // 5. Persisted grants human-readable: Structured metadata for UI review
13
+ // =============================================================================
14
+ // =============================================================================
15
+ // Shell-Specific Permission Helpers
16
+ // =============================================================================
17
+ export const SHELL_DANGEROUS_COMMANDS = [
18
+ 'rm', 'rmdir', 'del', 'erase',
19
+ 'sudo', 'su', 'doas',
20
+ 'chmod', 'chown',
21
+ 'dd', 'mkfs', 'format',
22
+ 'shutdown', 'reboot', 'halt',
23
+ 'iptables', 'ufw', 'firewall-cmd',
24
+ 'curl', 'wget', 'nc', 'netcat',
25
+ 'eval', 'exec',
26
+ ];
27
+ export const SHELL_FILESYSTEM_COMMANDS = [
28
+ 'mv', 'cp', 'mkdir', 'touch', 'ln',
29
+ 'git push', 'git reset --hard',
30
+ ];
31
+ export const SHELL_SHELL_OPERATORS = ['&&', '||', '|', '>', '>>', '`', '$(', ';'];
32
+ export function splitShellCommandSegments(command) {
33
+ const normalized = command
34
+ .replace(/&&|\|\||;|\||>>|>|`|\$\(/g, '\n')
35
+ .split('\n')
36
+ .map(part => part.trim())
37
+ .filter(Boolean);
38
+ return normalized.map((segment) => {
39
+ const parts = segment.split(/\s+/);
40
+ const baseCommand = parts[0]?.replace(/.*\//, '') || '';
41
+ const args = parts.slice(1);
42
+ const flags = args.filter(arg => arg.startsWith('-'));
43
+ return {
44
+ raw: segment,
45
+ baseCommand,
46
+ args,
47
+ flags,
48
+ };
49
+ });
50
+ }
51
+ function normalizeShellCommandIdentity(segment) {
52
+ const base = segment.baseCommand;
53
+ const firstNonFlagArg = segment.args.find(arg => !arg.startsWith('-'));
54
+ if (!base) {
55
+ return segment.raw;
56
+ }
57
+ if (base === 'git' && firstNonFlagArg) {
58
+ return `git ${firstNonFlagArg}`;
59
+ }
60
+ if (['npm', 'pnpm', 'yarn', 'bun', 'cargo'].includes(base) && firstNonFlagArg) {
61
+ return `${base} ${firstNonFlagArg}`;
62
+ }
63
+ return base;
64
+ }
65
+ function getShellCommandDangerPriority(identity) {
66
+ const lower = identity.toLowerCase();
67
+ if (lower === 'shutdown' || lower === 'reboot' || lower === 'halt')
68
+ return 5;
69
+ if (lower === 'sudo' || lower === 'su' || lower === 'doas' || lower === 'chmod' || lower === 'chown')
70
+ return 4;
71
+ if (lower === 'rm' || lower.startsWith('rm ') || lower === 'rmdir' || lower === 'del' || lower === 'erase' || lower === 'dd' || lower === 'mkfs' || lower === 'format')
72
+ return 3;
73
+ if (lower === 'mv' || lower === 'cp' || lower === 'mkdir' || lower === 'touch' || lower === 'ln' || lower === 'git push' || lower === 'git reset')
74
+ return 2;
75
+ if (lower === 'curl' || lower === 'wget' || lower === 'nc' || lower === 'netcat' || lower === 'iptables' || lower === 'ufw' || lower === 'firewall-cmd' || lower === 'eval' || lower === 'exec')
76
+ return 1;
77
+ return 0;
78
+ }
79
+ export function getEffectiveShellCommandIdentity(command) {
80
+ const segments = splitShellCommandSegments(command);
81
+ if (segments.length === 0) {
82
+ return '';
83
+ }
84
+ let selectedIdentity = normalizeShellCommandIdentity(segments[0]);
85
+ let selectedPriority = getShellCommandDangerPriority(selectedIdentity);
86
+ for (const segment of segments.slice(1)) {
87
+ const identity = normalizeShellCommandIdentity(segment);
88
+ const priority = getShellCommandDangerPriority(identity);
89
+ if (priority > selectedPriority) {
90
+ selectedIdentity = identity;
91
+ selectedPriority = priority;
92
+ }
93
+ }
94
+ return selectedIdentity;
95
+ }
96
+ function isDangerousShellIdentity(identity) {
97
+ return getShellCommandDangerPriority(identity) > 0;
98
+ }
99
+ export const SENSITIVE_FILE_PATTERNS = [
100
+ '.env', '.pem', '.key', '.ssh/', 'id_rsa', 'id_ed25519',
101
+ '.gitconfig', '.npmrc', 'credentials', 'secrets', 'password',
102
+ '.htpasswd',
103
+ ];
104
+ // =============================================================================
105
+ // Scope Policy: allowed scopes per intent type
106
+ // =============================================================================
107
+ const FILE_READ_ALLOWED_SCOPES = ['once', 'session', 'workspace'];
108
+ const FILE_WRITE_ALLOWED_SCOPES = ['once', 'session', 'workspace'];
109
+ const FILE_DELETE_ALLOWED_SCOPES = ['once', 'session'];
110
+ const NETWORK_REQUEST_ALLOWED_SCOPES = ['once', 'session', 'workspace'];
111
+ const SHELL_EXECUTE_ALLOWED_SCOPES = ['once', 'session'];
112
+ export function getAllowedScopesForIntent(intent) {
113
+ if (!intent.persistable)
114
+ return ['once'];
115
+ switch (intent.resource) {
116
+ case 'file':
117
+ switch (intent.action) {
118
+ case 'read': return FILE_READ_ALLOWED_SCOPES;
119
+ case 'write': return FILE_WRITE_ALLOWED_SCOPES;
120
+ case 'delete': return FILE_DELETE_ALLOWED_SCOPES;
121
+ default: return ['once'];
122
+ }
123
+ case 'network':
124
+ return NETWORK_REQUEST_ALLOWED_SCOPES;
125
+ case 'shell-command':
126
+ return SHELL_EXECUTE_ALLOWED_SCOPES;
127
+ default:
128
+ return ['once'];
129
+ }
130
+ }
131
+ // =============================================================================
132
+ // Shell Effect Analyzer
133
+ // =============================================================================
134
+ const FILE_READ_COMMANDS = ['cat', 'head', 'tail', 'less', 'more', 'wc', 'file', 'stat', 'ls', 'find', 'grep', 'awk', 'sed', 'sort', 'uniq', 'diff', 'comm', 'cut', 'tr', 'tee'];
135
+ const FILE_WRITE_COMMANDS = ['touch', 'mkdir'];
136
+ const FILE_DELETE_COMMANDS = ['rm', 'rmdir', 'del', 'erase'];
137
+ const NETWORK_COMMANDS = ['curl', 'wget', 'nc', 'netcat'];
138
+ function isFileReadCommand(cmd) {
139
+ return FILE_READ_COMMANDS.includes(cmd);
140
+ }
141
+ function isFileWriteCommand(cmd) {
142
+ return FILE_WRITE_COMMANDS.includes(cmd);
143
+ }
144
+ function isFileDeleteCommand(cmd) {
145
+ return FILE_DELETE_COMMANDS.includes(cmd);
146
+ }
147
+ function isNetworkCommand(cmd) {
148
+ return NETWORK_COMMANDS.includes(cmd);
149
+ }
150
+ function _isOperatorCommand(command) {
151
+ return SHELL_SHELL_OPERATORS.some(op => command.includes(op));
152
+ }
153
+ function hasDynamicExpansion(command) {
154
+ return /\$\{?[^}]+\}?/.test(command) || command.includes('$(');
155
+ }
156
+ /**
157
+ * Extract non-flag, non-command arguments from a shell command string.
158
+ * Returns the arguments that likely represent file paths or targets.
159
+ */
160
+ function _extractTargetArguments(command) {
161
+ const parts = command.trim().split(/\s+/);
162
+ const args = [];
163
+ let i = 1; // skip base command
164
+ // Skip subcommand for git/npm/etc
165
+ const base = parts[0]?.replace(/.*\//, '') || '';
166
+ if (['git'].includes(base) && parts[1] && !parts[1].startsWith('-')) {
167
+ i = 2; // skip "git" and "push"/etc
168
+ }
169
+ if (['npm', 'pnpm', 'yarn', 'bun', 'cargo'].includes(base) && parts[1] && !parts[1].startsWith('-')) {
170
+ i = 2;
171
+ }
172
+ for (; i < parts.length; i++) {
173
+ const part = parts[i];
174
+ if (!part)
175
+ continue;
176
+ // Skip flags
177
+ if (part.startsWith('-'))
178
+ continue;
179
+ // Skip output redirection targets
180
+ if (part === '>' || part === '>>') {
181
+ i++;
182
+ continue;
183
+ }
184
+ args.push(part);
185
+ }
186
+ return args;
187
+ }
188
+ /**
189
+ * Analyze a shell command into structured permission intents.
190
+ *
191
+ * This is the core of the target-based permission system.
192
+ * Instead of "allow cat", it produces "allow file read .env".
193
+ */
194
+ export function analyzeShellCommandEffects(params) {
195
+ const { command, baseCommand, resolvedPaths, hasOperators, riskCategory } = params;
196
+ const intents = [];
197
+ const hasDynamic = hasDynamicExpansion(command);
198
+ // If operators are present, the command is too complex for reliable target analysis
199
+ if (hasOperators) {
200
+ intents.push({
201
+ resource: 'shell-command',
202
+ action: 'execute',
203
+ targets: [{ target: baseCommand, matcher: 'exact' }],
204
+ persistable: false,
205
+ nonPersistableReason: 'command contains shell operators',
206
+ allowedScopes: ['once', 'session'],
207
+ });
208
+ return intents;
209
+ }
210
+ // Network commands
211
+ if (isNetworkCommand(baseCommand)) {
212
+ const targets = extractNetworkTargets(command, baseCommand);
213
+ if (targets.length > 0) {
214
+ const intent = {
215
+ resource: 'network',
216
+ action: 'request',
217
+ targets,
218
+ persistable: !hasDynamic,
219
+ nonPersistableReason: hasDynamic ? 'dynamic variable expansion' : undefined,
220
+ allowedScopes: [],
221
+ };
222
+ intent.allowedScopes = getAllowedScopesForIntent(intent);
223
+ intents.push(intent);
224
+ }
225
+ else {
226
+ // No extractable URL/host
227
+ intents.push({
228
+ resource: 'shell-command',
229
+ action: 'execute',
230
+ targets: [{ target: baseCommand, matcher: 'exact' }],
231
+ persistable: false,
232
+ nonPersistableReason: 'could not extract network target',
233
+ allowedScopes: ['once', 'session'],
234
+ });
235
+ }
236
+ return intents;
237
+ }
238
+ // File delete commands
239
+ if (isFileDeleteCommand(baseCommand)) {
240
+ if (resolvedPaths.length > 0 && !hasDynamic) {
241
+ const isRecursive = command.includes('-r') || command.includes('-R') || command.includes('-rf');
242
+ const targets = resolvedPaths.map(p => ({
243
+ target: isRecursive ? ensureTrailingSlash(p) : p,
244
+ matcher: isRecursive ? 'prefix' : 'exact',
245
+ }));
246
+ const intent = {
247
+ resource: 'file',
248
+ action: 'delete',
249
+ targets,
250
+ persistable: true,
251
+ allowedScopes: [],
252
+ };
253
+ intent.allowedScopes = getAllowedScopesForIntent(intent);
254
+ intents.push(intent);
255
+ }
256
+ else {
257
+ intents.push({
258
+ resource: 'shell-command',
259
+ action: 'execute',
260
+ targets: [{ target: baseCommand, matcher: 'exact' }],
261
+ persistable: false,
262
+ nonPersistableReason: hasDynamic ? 'dynamic variable expansion' : 'could not resolve target paths',
263
+ allowedScopes: ['once'],
264
+ });
265
+ }
266
+ return intents;
267
+ }
268
+ // File read commands
269
+ if (isFileReadCommand(baseCommand)) {
270
+ if (resolvedPaths.length > 0 && !hasDynamic) {
271
+ const targets = resolvedPaths.map(p => ({
272
+ target: p,
273
+ matcher: 'exact',
274
+ }));
275
+ const intent = {
276
+ resource: 'file',
277
+ action: 'read',
278
+ targets,
279
+ persistable: true,
280
+ allowedScopes: [],
281
+ };
282
+ intent.allowedScopes = getAllowedScopesForIntent(intent);
283
+ intents.push(intent);
284
+ }
285
+ else if (resolvedPaths.length === 0) {
286
+ // File read command with no resolved paths (e.g., "cat" alone or piping)
287
+ // This is a shell-execute fallback
288
+ intents.push({
289
+ resource: 'shell-command',
290
+ action: 'execute',
291
+ targets: [{ target: baseCommand, matcher: 'exact' }],
292
+ persistable: false,
293
+ nonPersistableReason: 'no resolvable file targets',
294
+ allowedScopes: ['once', 'session'],
295
+ });
296
+ }
297
+ else {
298
+ // Has dynamic expansion
299
+ intents.push({
300
+ resource: 'shell-command',
301
+ action: 'execute',
302
+ targets: [{ target: baseCommand, matcher: 'exact' }],
303
+ persistable: false,
304
+ nonPersistableReason: 'dynamic variable expansion',
305
+ allowedScopes: ['once'],
306
+ });
307
+ }
308
+ return intents;
309
+ }
310
+ // File write / modification commands (mv, cp, mkdir, touch, ln)
311
+ if (isFileWriteCommand(baseCommand) || ['mv', 'cp', 'ln'].includes(baseCommand)) {
312
+ if (resolvedPaths.length > 0 && !hasDynamic) {
313
+ const targets = resolvedPaths.map(p => ({
314
+ target: p,
315
+ matcher: 'exact',
316
+ }));
317
+ const intent = {
318
+ resource: 'file',
319
+ action: 'write',
320
+ targets,
321
+ persistable: true,
322
+ allowedScopes: [],
323
+ };
324
+ intent.allowedScopes = getAllowedScopesForIntent(intent);
325
+ intents.push(intent);
326
+ }
327
+ else {
328
+ intents.push({
329
+ resource: 'shell-command',
330
+ action: 'execute',
331
+ targets: [{ target: baseCommand, matcher: 'exact' }],
332
+ persistable: false,
333
+ nonPersistableReason: hasDynamic ? 'dynamic variable expansion' : 'could not resolve target paths',
334
+ allowedScopes: ['once'],
335
+ });
336
+ }
337
+ return intents;
338
+ }
339
+ // Sensitive files category — even if command isn't a known file reader,
340
+ // if it references sensitive files, produce a file-read intent
341
+ if (riskCategory === 'sensitive-files' && resolvedPaths.length > 0 && !hasDynamic) {
342
+ const targets = resolvedPaths.map(p => ({
343
+ target: p,
344
+ matcher: 'exact',
345
+ }));
346
+ const intent = {
347
+ resource: 'file',
348
+ action: 'read',
349
+ targets,
350
+ persistable: true,
351
+ allowedScopes: [],
352
+ };
353
+ intent.allowedScopes = getAllowedScopesForIntent(intent);
354
+ intents.push(intent);
355
+ return intents;
356
+ }
357
+ // All other dangerous/system commands that we can't classify
358
+ // (sudo, chmod, chown, shutdown, eval, etc.)
359
+ intents.push({
360
+ resource: 'shell-command',
361
+ action: 'execute',
362
+ targets: [{ target: baseCommand, matcher: 'exact' }],
363
+ persistable: false,
364
+ nonPersistableReason: 'unclassifiable system command',
365
+ allowedScopes: ['once', 'session'],
366
+ });
367
+ return intents;
368
+ }
369
+ function ensureTrailingSlash(path) {
370
+ if (path.endsWith('/'))
371
+ return path;
372
+ return path + '/';
373
+ }
374
+ function extractNetworkTargets(command, _baseCommand) {
375
+ const targets = [];
376
+ const urlRegex = /https?:\/\/([^\s/$.?#].[^\s]*)/gi;
377
+ let match;
378
+ while ((match = urlRegex.exec(command)) !== null) {
379
+ const fullUrl = match[0];
380
+ const host = match[1].split('/')[0];
381
+ if (host) {
382
+ targets.push({
383
+ target: host,
384
+ matcher: 'exact',
385
+ });
386
+ }
387
+ // Also store the full URL prefix as a secondary target
388
+ if (fullUrl.length > host.length + 8) { // more than just "http://host"
389
+ const pathPrefix = fullUrl.substring(0, fullUrl.indexOf('/', 8) + 1);
390
+ if (pathPrefix && pathPrefix.length > 8) {
391
+ targets.push({
392
+ target: pathPrefix,
393
+ matcher: 'prefix',
394
+ });
395
+ }
396
+ }
397
+ }
398
+ // Deduplicate
399
+ const seen = new Set();
400
+ return targets.filter(t => {
401
+ if (seen.has(t.target))
402
+ return false;
403
+ seen.add(t.target);
404
+ return true;
405
+ });
406
+ }
407
+ /**
408
+ * Create a shell permission ask with structured intent analysis.
409
+ *
410
+ * This function now runs the shell effect analyzer to produce target-based
411
+ * intents instead of command-identity patterns. For example:
412
+ * - "cat .env" → resource='file', action='read', target='/workspace/.env'
413
+ * - "rm -rf build" → resource='file', action='delete', target='build/' (prefix)
414
+ * - "curl https://api.example.com" → resource='network', action='request', target='api.example.com'
415
+ *
416
+ * Falls back to resource='shell-command' when analysis can't extract targets.
417
+ */
418
+ export function createShellPermissionAskStructured(params) {
419
+ const commandIdentity = getEffectiveShellCommandIdentity(params.command);
420
+ // Run the intent analysis
421
+ const intents = analyzeShellCommandEffects({
422
+ command: params.command,
423
+ baseCommand: params.baseCommand,
424
+ resolvedPaths: params.resolvedPaths ?? [],
425
+ hasOperators: params.hasOperators,
426
+ workspaceBound: params.workspaceBound,
427
+ riskCategory: params.riskCategory,
428
+ });
429
+ // Use the primary intent to determine resource, action, and scope
430
+ const primaryIntent = intents[0];
431
+ // Determine the effective resource and action from the primary intent
432
+ const effectiveResource = primaryIntent?.resource ?? 'shell-command';
433
+ const effectiveAction = primaryIntent?.action;
434
+ // Build patterns from intent targets (not command identity)
435
+ const patterns = [];
436
+ if (primaryIntent) {
437
+ for (const t of primaryIntent.targets) {
438
+ patterns.push(t.target);
439
+ }
440
+ }
441
+ else if (commandIdentity) {
442
+ patterns.push(commandIdentity);
443
+ }
444
+ // Compute allowed scopes from policy
445
+ const allAllowedScopes = intents.flatMap(i => i.allowedScopes);
446
+ const allowedScopes = [...new Set(allAllowedScopes)];
447
+ // Determine max suggested duration based on policy
448
+ let duration = 'workspace';
449
+ if (params.riskCategory === 'destructive' || params.riskCategory === 'network' || isDangerousShellIdentity(commandIdentity)) {
450
+ duration = 'session';
451
+ }
452
+ // Enforce: if the intent is not persistable, cap at 'session'
453
+ if (primaryIntent && !primaryIntent.persistable) {
454
+ duration = allowedScopes.includes('session') ? 'session' : 'once';
455
+ }
456
+ // Build scope definition from primary intent
457
+ const scope = buildScopeFromIntent(primaryIntent, commandIdentity, params.flags);
458
+ // Build human-readable question with target info
459
+ const question = buildShellQuestion(params, primaryIntent);
460
+ return {
461
+ type: 'permission',
462
+ question,
463
+ risk: params.risk,
464
+ resource: effectiveResource,
465
+ action: effectiveAction,
466
+ scope,
467
+ patterns,
468
+ duration,
469
+ intents,
470
+ allowedScopes,
471
+ paths: params.resolvedPaths,
472
+ metadata: {
473
+ command: params.command,
474
+ baseCommand: commandIdentity,
475
+ flags: params.flags,
476
+ riskCategory: params.riskCategory,
477
+ reason: params.reason,
478
+ resolvedPaths: params.resolvedPaths,
479
+ workspaceBound: params.workspaceBound,
480
+ hasOperators: params.hasOperators,
481
+ effectiveResource,
482
+ effectiveAction,
483
+ description: buildDescription({
484
+ baseCommand: commandIdentity,
485
+ flags: params.flags,
486
+ riskCategory: params.riskCategory,
487
+ workspaceBound: params.workspaceBound,
488
+ hasOperators: params.hasOperators,
489
+ }),
490
+ },
491
+ };
492
+ }
493
+ function buildScopeFromIntent(intent, commandIdentity, flags) {
494
+ if (!intent) {
495
+ return {
496
+ type: 'shell-command',
497
+ value: commandIdentity,
498
+ label: formatCommandLabel(commandIdentity, flags),
499
+ };
500
+ }
501
+ switch (intent.resource) {
502
+ case 'file':
503
+ return {
504
+ type: 'file',
505
+ value: intent.targets.map(t => t.target).join(', '),
506
+ label: `${intent.action} ${intent.targets.map(t => t.target.split('/').pop() || t.target).join(', ')}`,
507
+ };
508
+ case 'network':
509
+ return {
510
+ type: 'resource',
511
+ value: intent.targets.map(t => t.target).join(', '),
512
+ label: intent.targets.map(t => t.target).join(', '),
513
+ };
514
+ case 'shell-command':
515
+ return {
516
+ type: 'shell-command',
517
+ value: commandIdentity,
518
+ label: formatCommandLabel(commandIdentity, flags),
519
+ };
520
+ default:
521
+ return {
522
+ type: 'shell-command',
523
+ value: commandIdentity,
524
+ label: formatCommandLabel(commandIdentity, flags),
525
+ };
526
+ }
527
+ }
528
+ function buildShellQuestion(params, intent) {
529
+ // Build a more descriptive question when we have intent info
530
+ if (intent && intent.resource === 'file') {
531
+ const targetNames = intent.targets.map(t => t.target.split('/').pop() || t.target);
532
+ const actionLabel = intent.action === 'read' ? 'Read' : intent.action === 'write' ? 'Write to' : intent.action === 'delete' ? 'Delete' : 'Access';
533
+ let question = `${actionLabel} file "${targetNames.join('", "')}"`;
534
+ if (!params.workspaceBound) {
535
+ question += ' (outside workspace)';
536
+ }
537
+ question += `. Requires approval.`;
538
+ return question;
539
+ }
540
+ if (intent && intent.resource === 'network') {
541
+ const hosts = intent.targets.filter(t => t.matcher === 'exact').map(t => t.target);
542
+ if (hosts.length > 0) {
543
+ return `Network request to "${hosts.join('", "')}" via ${truncateCommand(params.command)}. Requires approval.`;
544
+ }
545
+ }
546
+ // Fallback: original question format
547
+ let question = `Run command "${truncateCommand(params.command)}"`;
548
+ if (params.workspaceBound) {
549
+ question += ` (within workspace)`;
550
+ }
551
+ else {
552
+ question += ` (references paths outside workspace)`;
553
+ }
554
+ question += `: ${params.reason}. Requires approval.`;
555
+ return question;
556
+ }
557
+ export function createOutsideWorkspaceAsk(params) {
558
+ const commandIdentity = getEffectiveShellCommandIdentity(params.command);
559
+ const intents = analyzeShellCommandEffects({
560
+ command: params.command,
561
+ baseCommand: commandIdentity,
562
+ resolvedPaths: params.resolvedPaths,
563
+ hasOperators: params.hasOperators ?? false,
564
+ workspaceBound: false,
565
+ riskCategory: 'outside-workspace',
566
+ });
567
+ const primaryIntent = intents[0];
568
+ const effectiveResource = primaryIntent?.resource ?? 'shell-command';
569
+ const allAllowedScopes = intents.flatMap(i => i.allowedScopes);
570
+ const allowedScopes = [...new Set(allAllowedScopes)];
571
+ const patterns = [];
572
+ if (primaryIntent) {
573
+ for (const t of primaryIntent.targets) {
574
+ patterns.push(t.target);
575
+ }
576
+ }
577
+ if (patterns.length === 0) {
578
+ patterns.push(commandIdentity || `cwd:${params.cwd}`);
579
+ }
580
+ const scope = {
581
+ type: 'path',
582
+ value: params.cwd,
583
+ label: params.cwd.split('/').pop() || params.cwd,
584
+ };
585
+ return {
586
+ type: 'permission',
587
+ question: `Command "${truncateCommand(params.command)}" runs in directory outside workspace (${params.cwd}). Requires approval.`,
588
+ risk: 'medium',
589
+ resource: effectiveResource,
590
+ action: primaryIntent?.action,
591
+ scope,
592
+ patterns,
593
+ duration: 'session',
594
+ intents,
595
+ allowedScopes,
596
+ metadata: {
597
+ command: params.command,
598
+ baseCommand: commandIdentity,
599
+ cwd: params.cwd,
600
+ resolvedPaths: params.resolvedPaths,
601
+ riskCategory: 'outside-workspace',
602
+ effectiveResource,
603
+ effectiveAction: primaryIntent?.action,
604
+ description: `External directory access: ${params.cwd}`,
605
+ hasOperators: params.hasOperators ?? false,
606
+ },
607
+ };
608
+ }
609
+ export function createWorkspaceModificationAsk(params) {
610
+ const commandIdentity = getEffectiveShellCommandIdentity(params.command);
611
+ const intents = analyzeShellCommandEffects({
612
+ command: params.command,
613
+ baseCommand: params.baseCommand,
614
+ resolvedPaths: params.resolvedPaths,
615
+ hasOperators: params.hasOperators ?? false,
616
+ workspaceBound: true,
617
+ riskCategory: 'workspace-modification',
618
+ });
619
+ const primaryIntent = intents[0];
620
+ const effectiveResource = primaryIntent?.resource ?? 'shell-command';
621
+ const effectiveAction = primaryIntent?.action;
622
+ const allAllowedScopes = intents.flatMap(i => i.allowedScopes);
623
+ const allowedScopes = [...new Set(allAllowedScopes)];
624
+ const patterns = [];
625
+ if (primaryIntent) {
626
+ for (const t of primaryIntent.targets) {
627
+ patterns.push(t.target);
628
+ }
629
+ }
630
+ if (patterns.length === 0) {
631
+ patterns.push(commandIdentity || params.baseCommand);
632
+ }
633
+ // Build better question for file-targeted operations
634
+ let question;
635
+ if (primaryIntent && primaryIntent.resource === 'file') {
636
+ const targetNames = primaryIntent.targets.map(t => t.target.split('/').pop() || t.target);
637
+ const actionLabel = primaryIntent.action === 'write' ? 'Write to' : primaryIntent.action === 'read' ? 'Read' : 'Modify';
638
+ question = `${actionLabel} "${targetNames.join('", "')}" via ${truncateCommand(params.command)}. Requires approval.`;
639
+ }
640
+ else {
641
+ question = `Run filesystem command "${truncateCommand(params.command)}" within workspace. Requires approval.`;
642
+ }
643
+ const scope = buildScopeFromIntent(primaryIntent, commandIdentity, []);
644
+ return {
645
+ type: 'permission',
646
+ question,
647
+ risk: 'medium',
648
+ resource: effectiveResource,
649
+ action: effectiveAction,
650
+ scope,
651
+ patterns,
652
+ duration: 'session',
653
+ intents,
654
+ allowedScopes,
655
+ paths: params.resolvedPaths,
656
+ metadata: {
657
+ command: params.command,
658
+ baseCommand: commandIdentity || params.baseCommand,
659
+ resolvedPaths: params.resolvedPaths,
660
+ riskCategory: 'workspace-modification',
661
+ effectiveResource,
662
+ effectiveAction,
663
+ description: `Workspace filesystem: ${commandIdentity || params.baseCommand}`,
664
+ hasOperators: params.hasOperators ?? false,
665
+ },
666
+ };
667
+ }
668
+ function truncateCommand(cmd, maxLen = 80) {
669
+ return cmd.length > maxLen ? cmd.slice(0, maxLen) + '...' : cmd;
670
+ }
671
+ function formatCommandLabel(baseCommand, flags) {
672
+ if (!baseCommand) {
673
+ return '';
674
+ }
675
+ if (flags.length === 0) {
676
+ return baseCommand;
677
+ }
678
+ const commonFlags = flags.slice(0, 3).join(' ');
679
+ return commonFlags ? `${baseCommand} ${commonFlags}` : baseCommand;
680
+ }
681
+ function buildDescription(params) {
682
+ const parts = [];
683
+ switch (params.riskCategory) {
684
+ case 'destructive':
685
+ parts.push('Destructive operation');
686
+ break;
687
+ case 'network':
688
+ parts.push('Network access');
689
+ break;
690
+ case 'side-effect':
691
+ parts.push('Side-effect command');
692
+ break;
693
+ case 'workspace-modification':
694
+ parts.push('Workspace modification');
695
+ break;
696
+ case 'outside-workspace':
697
+ parts.push('Outside workspace');
698
+ break;
699
+ }
700
+ if (!params.workspaceBound) {
701
+ parts.push('References external paths');
702
+ }
703
+ if (params.hasOperators) {
704
+ parts.push('Contains shell operators');
705
+ }
706
+ return parts.join(' • ');
707
+ }
708
+ export function createFilePermissionAsk(params) {
709
+ const operationLabel = params.operation === 'read' ? 'Reading' : params.operation === 'write' ? 'Writing' : 'Editing';
710
+ const fileName = params.path.split('/').pop() || params.path;
711
+ let question = `${operationLabel} file "${fileName}"`;
712
+ if (params.isSensitiveFile) {
713
+ question += ' (sensitive file)';
714
+ }
715
+ else if (params.isOutsideWorkspace) {
716
+ question += ' (outside workspace)';
717
+ }
718
+ question += ' requires approval.';
719
+ if (params.reason) {
720
+ question += ` ${params.reason}`;
721
+ }
722
+ const patterns = [];
723
+ patterns.push(`file:${params.path}`);
724
+ patterns.push(`file:${fileName}`);
725
+ if (params.isOutsideWorkspace) {
726
+ patterns.push('outside-workspace');
727
+ }
728
+ if (params.isSensitiveFile) {
729
+ patterns.push('sensitive-file');
730
+ }
731
+ let duration = 'workspace';
732
+ if (params.isSensitiveFile || params.risk === 'high' || params.risk === 'critical') {
733
+ duration = 'session';
734
+ }
735
+ return {
736
+ type: 'permission',
737
+ question,
738
+ risk: params.risk,
739
+ resource: 'file',
740
+ paths: [params.path],
741
+ patterns,
742
+ duration,
743
+ metadata: {
744
+ operation: params.operation,
745
+ path: params.path,
746
+ fileName,
747
+ isOutsideWorkspace: params.isOutsideWorkspace ?? false,
748
+ isSensitiveFile: params.isSensitiveFile ?? false,
749
+ reason: params.reason,
750
+ },
751
+ };
752
+ }
753
+ export function createWebfetchPermissionAsk(params) {
754
+ let question = `Fetch URL "${params.host}"`;
755
+ if (params.isHttp) {
756
+ question += ' (unencrypted HTTP)';
757
+ }
758
+ question += ' requires approval.';
759
+ if (params.reason) {
760
+ question += ` ${params.reason}`;
761
+ }
762
+ const patterns = [params.host, params.url];
763
+ let duration = 'workspace';
764
+ if (params.isHttp || params.risk === 'high' || params.risk === 'critical') {
765
+ duration = 'session';
766
+ }
767
+ return {
768
+ type: 'permission',
769
+ question,
770
+ risk: params.risk,
771
+ resource: 'network',
772
+ scope: {
773
+ type: 'resource',
774
+ value: params.url,
775
+ label: params.host,
776
+ },
777
+ patterns,
778
+ duration,
779
+ metadata: {
780
+ url: params.url,
781
+ host: params.host,
782
+ isHttp: params.isHttp ?? false,
783
+ reason: params.reason,
784
+ },
785
+ };
786
+ }
787
+ //# sourceMappingURL=permission.js.map