shennian 0.3.86 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +32 -27
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude-managed-permissions.js +1 -248
- package/dist/src/agents/claude.js +8 -393
- package/dist/src/agents/codex-managed-permissions.js +1 -294
- package/dist/src/agents/codex-managed-room-tools.js +1 -249
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.d.ts +7 -1
- package/dist/src/agents/codex.js +17 -1352
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor-managed-permissions.js +2 -299
- package/dist/src/agents/cursor.js +1 -314
- package/dist/src/agents/custom.js +4 -418
- package/dist/src/agents/external-channel-instructions.js +11 -94
- package/dist/src/agents/gemini.js +1 -180
- package/dist/src/agents/manager.js +18 -250
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -188
- package/dist/src/agents/model-registry/parsers.js +4 -521
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -115
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -19
- package/dist/src/agents/native-capabilities.js +1 -237
- package/dist/src/agents/openclaw.js +2 -293
- package/dist/src/agents/opencode-managed-permissions.js +2 -187
- package/dist/src/agents/opencode.js +1 -298
- package/dist/src/agents/platform-instructions.js +17 -46
- package/dist/src/agents/runtime-compatibility.js +1 -44
- package/dist/src/agents/workbuddy-managed-permissions.js +2 -329
- package/dist/src/agents/workbuddy.js +3 -444
- package/dist/src/commands/logout.d.ts +3 -0
- package/dist/src/commands/logout.js +1 -0
- package/dist/src/daemon-ipc/paths.js +1 -1
- package/dist/src/fs/boundary.js +1 -185
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.js +1 -110
- package/dist/src/index.js +3 -587
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +1 -317
- package/dist/src/manager/retired-external-channel-compat.js +1 -40
- package/dist/src/manager/runtime.js +23 -1514
- package/dist/src/native-fusion/codex-parser.d.ts +3 -1
- package/dist/src/native-fusion/codex-parser.js +4 -660
- package/dist/src/native-fusion/codex-user-input.js +5 -102
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/file-watcher.js +1 -191
- package/dist/src/native-fusion/local-repository.js +2 -569
- package/dist/src/native-fusion/opencode-parser.js +3 -130
- package/dist/src/native-fusion/parser-common.d.ts +6 -1
- package/dist/src/native-fusion/parser-common.js +4 -4
- package/dist/src/native-fusion/parsers.d.ts +4 -2
- package/dist/src/native-fusion/parsers.js +3 -140
- package/dist/src/native-fusion/service.js +2 -739
- package/dist/src/native-fusion/session-header.js +2 -101
- package/dist/src/native-fusion/state.js +2 -104
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/personal-sync/machine-authority.js +1 -34
- package/dist/src/room/activation-audit.js +2 -45
- package/dist/src/room/activation-policy.js +1 -30
- package/dist/src/room/activation-service.js +8 -408
- package/dist/src/room/invite-bootstrap.js +1 -156
- package/dist/src/room/managed-prompt-composer.d.ts +1 -1
- package/dist/src/room/managed-prompt-composer.js +17 -16
- package/dist/src/room/managed-room-binding.js +1 -167
- package/dist/src/room/managed-room-ipc.d.ts +4 -0
- package/dist/src/room/managed-room-ipc.js +2 -2
- package/dist/src/room/mirror-sync-service.d.ts +1 -0
- package/dist/src/room/mirror-sync-service.js +1 -183
- package/dist/src/room/room-binding-client.js +1 -151
- package/dist/src/session/archive-zip.js +1 -220
- package/dist/src/session/deletion-sync-state.js +2 -136
- package/dist/src/session/handlers/agent-capabilities.js +1 -14
- package/dist/src/session/handlers/agent-config.js +1 -150
- package/dist/src/session/handlers/agents.js +1 -55
- package/dist/src/session/handlers/chat.js +2 -1030
- package/dist/src/session/handlers/control.js +1 -61
- package/dist/src/session/handlers/fs.js +1 -594
- package/dist/src/session/handlers/message-status.js +1 -15
- package/dist/src/session/handlers/remote-access.js +1 -43
- package/dist/src/session/handlers/room-managed.js +1 -101
- package/dist/src/session/handlers/session-refresh.js +1 -35
- package/dist/src/session/handlers/title.js +1 -79
- package/dist/src/session/handlers/tool-detail.js +1 -149
- package/dist/src/session/handlers/voice-window.js +1 -62
- package/dist/src/session/history-subscriptions.js +1 -470
- package/dist/src/session/manager.d.ts +1 -0
- package/dist/src/session/manager.js +1 -658
- package/dist/src/session/native-cleanup-outbox.js +2 -287
- package/dist/src/session/native-session-lifecycle.js +6 -327
- package/dist/src/session/queue.d.ts +8 -0
- package/dist/src/session/queue.js +6 -6
- package/dist/src/upgrade/scheduler.js +1 -126
- package/node_modules/@shennian/wire/dist/agent-workspace.js +2 -2
- package/package.json +1 -1
|
@@ -1,226 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/agent-config-status.test.ts
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { readLatestUserEnv } from '../agent-env.js';
|
|
7
|
-
import { loadConfig, resolveShennianPath } from '../config/index.js';
|
|
8
|
-
const MANAGED_CONFIG_PATH = resolveShennianPath('agent-provider-config.json');
|
|
9
|
-
const TOKEN_SUFFIX_LENGTH = 4;
|
|
10
|
-
function isConfigurableAgent(agent) {
|
|
11
|
-
return agent === 'codex' || agent === 'claude' || agent === 'pi';
|
|
12
|
-
}
|
|
13
|
-
const AGENT_ENV_KEYS = {
|
|
14
|
-
codex: {
|
|
15
|
-
baseUrl: ['OPENAI_BASE_URL', 'OPENAI_API_BASE', 'OPENAI_API_URL'],
|
|
16
|
-
token: ['OPENAI_API_KEY', 'OPENAI_TOKEN'],
|
|
17
|
-
},
|
|
18
|
-
claude: {
|
|
19
|
-
baseUrl: ['ANTHROPIC_BASE_URL', 'ANTHROPIC_API_URL'],
|
|
20
|
-
token: ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN'],
|
|
21
|
-
},
|
|
22
|
-
pi: {
|
|
23
|
-
baseUrl: ['DASHSCOPE_BASE_URL', 'DASHSCOPE_API_BASE', 'DASHSCOPE_API_URL'],
|
|
24
|
-
token: ['DASHSCOPE_API_KEY', 'DASHSCOPE_TOKEN'],
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
export function getManagedAgentProviderConfig(agent) {
|
|
28
|
-
return loadManagedConfig().configs[agent];
|
|
29
|
-
}
|
|
30
|
-
export function upsertManagedAgentProviderConfig(input) {
|
|
31
|
-
const file = loadManagedConfig();
|
|
32
|
-
const existing = file.configs[input.agent];
|
|
33
|
-
const record = {
|
|
34
|
-
agent: input.agent,
|
|
35
|
-
baseUrl: input.baseUrl?.trim() || existing?.baseUrl,
|
|
36
|
-
token: input.token?.trim() || existing?.token,
|
|
37
|
-
updatedAt: new Date().toISOString(),
|
|
38
|
-
};
|
|
39
|
-
file.configs[input.agent] = record;
|
|
40
|
-
saveManagedConfig(file);
|
|
41
|
-
return record;
|
|
42
|
-
}
|
|
43
|
-
export function deleteManagedAgentProviderConfig(agent) {
|
|
44
|
-
const file = loadManagedConfig();
|
|
45
|
-
delete file.configs[agent];
|
|
46
|
-
saveManagedConfig(file);
|
|
47
|
-
}
|
|
48
|
-
export function buildManagedAgentEnv(agent) {
|
|
49
|
-
if (!isConfigurableAgent(agent))
|
|
50
|
-
return {};
|
|
51
|
-
const config = getManagedAgentProviderConfig(agent);
|
|
52
|
-
if (!config)
|
|
53
|
-
return {};
|
|
54
|
-
if (agent === 'codex') {
|
|
55
|
-
return compactEnv({
|
|
56
|
-
OPENAI_BASE_URL: config.baseUrl,
|
|
57
|
-
OPENAI_API_KEY: config.token,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
if (agent === 'claude') {
|
|
61
|
-
return compactEnv({
|
|
62
|
-
ANTHROPIC_BASE_URL: config.baseUrl,
|
|
63
|
-
ANTHROPIC_API_KEY: config.token,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
return compactEnv({
|
|
67
|
-
DASHSCOPE_BASE_URL: config.baseUrl,
|
|
68
|
-
DASHSCOPE_API_KEY: config.token,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
export function getAgentConfigSummary(agent, env = readLatestUserEnv()) {
|
|
72
|
-
if (!isConfigurableAgent(agent))
|
|
73
|
-
return undefined;
|
|
74
|
-
const managed = getManagedAgentProviderConfig(agent);
|
|
75
|
-
if (managed?.token || managed?.baseUrl) {
|
|
76
|
-
return toSummary({
|
|
77
|
-
agent,
|
|
78
|
-
source: 'shennian',
|
|
79
|
-
status: managed.token ? 'managed' : 'missing',
|
|
80
|
-
baseUrl: managed.baseUrl,
|
|
81
|
-
token: managed.token,
|
|
82
|
-
tokenPresent: !!managed.token,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
const envConfig = detectFromEnv(agent, env);
|
|
86
|
-
if (envConfig.tokenPresent || envConfig.baseUrl) {
|
|
87
|
-
return toSummary({
|
|
88
|
-
agent,
|
|
89
|
-
source: 'env',
|
|
90
|
-
status: envConfig.tokenPresent ? 'detected' : 'missing',
|
|
91
|
-
baseUrl: envConfig.baseUrl,
|
|
92
|
-
token: envConfig.token,
|
|
93
|
-
tokenPresent: envConfig.tokenPresent,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
const fileConfig = detectFromKnownConfigFiles(agent);
|
|
97
|
-
if (fileConfig.tokenPresent || fileConfig.baseUrl) {
|
|
98
|
-
return toSummary({
|
|
99
|
-
agent,
|
|
100
|
-
source: 'config-file',
|
|
101
|
-
status: fileConfig.tokenPresent ? 'detected' : 'missing',
|
|
102
|
-
baseUrl: fileConfig.baseUrl,
|
|
103
|
-
token: fileConfig.token,
|
|
104
|
-
tokenPresent: fileConfig.tokenPresent,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
return toSummary({
|
|
108
|
-
agent,
|
|
109
|
-
source: 'unknown',
|
|
110
|
-
status: 'missing',
|
|
111
|
-
tokenPresent: false,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
function loadManagedConfig() {
|
|
115
|
-
try {
|
|
116
|
-
const parsed = JSON.parse(fs.readFileSync(MANAGED_CONFIG_PATH, 'utf-8'));
|
|
117
|
-
return { configs: parsed.configs ?? {} };
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return { configs: {} };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
function saveManagedConfig(file) {
|
|
124
|
-
fs.mkdirSync(path.dirname(MANAGED_CONFIG_PATH), { recursive: true });
|
|
125
|
-
fs.writeFileSync(MANAGED_CONFIG_PATH, JSON.stringify(file, null, 2), { mode: 0o600 });
|
|
126
|
-
try {
|
|
127
|
-
fs.chmodSync(MANAGED_CONFIG_PATH, 0o600);
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
// Best effort on filesystems without POSIX modes.
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function compactEnv(env) {
|
|
134
|
-
const compacted = {};
|
|
135
|
-
for (const [key, value] of Object.entries(env)) {
|
|
136
|
-
if (value)
|
|
137
|
-
compacted[key] = value;
|
|
138
|
-
}
|
|
139
|
-
return compacted;
|
|
140
|
-
}
|
|
141
|
-
function detectFromEnv(agent, env) {
|
|
142
|
-
const keys = AGENT_ENV_KEYS[agent];
|
|
143
|
-
const baseUrl = firstEnv(env, keys.baseUrl);
|
|
144
|
-
const token = firstEnv(env, keys.token);
|
|
145
|
-
return { baseUrl, token, tokenPresent: !!token };
|
|
146
|
-
}
|
|
147
|
-
function firstEnv(env, keys) {
|
|
148
|
-
for (const key of keys) {
|
|
149
|
-
const value = env[key]?.trim();
|
|
150
|
-
if (value)
|
|
151
|
-
return value;
|
|
152
|
-
}
|
|
153
|
-
return undefined;
|
|
154
|
-
}
|
|
155
|
-
function detectFromKnownConfigFiles(agent) {
|
|
156
|
-
const home = os.homedir();
|
|
157
|
-
if (agent === 'pi') {
|
|
158
|
-
const config = loadConfig();
|
|
159
|
-
const token = config.apiKeys?.dashscope?.trim();
|
|
160
|
-
if (token)
|
|
161
|
-
return { token, tokenPresent: true };
|
|
162
|
-
}
|
|
163
|
-
const candidates = agent === 'codex'
|
|
164
|
-
? [path.join(home, '.codex', 'config.toml'), path.join(home, '.codex', 'auth.json')]
|
|
165
|
-
: agent === 'claude'
|
|
166
|
-
? [path.join(home, '.claude', 'settings.json'), path.join(home, '.claude.json')]
|
|
167
|
-
: [path.join(home, '.dashscope', 'config.json')];
|
|
168
|
-
for (const file of candidates) {
|
|
169
|
-
const text = readSmallTextFile(file);
|
|
170
|
-
if (!text)
|
|
171
|
-
continue;
|
|
172
|
-
const token = extractTokenLikeValue(text, agent);
|
|
173
|
-
const baseUrl = extractBaseUrl(text);
|
|
174
|
-
if (token || baseUrl)
|
|
175
|
-
return { token, baseUrl, tokenPresent: !!token };
|
|
176
|
-
}
|
|
177
|
-
return { tokenPresent: false };
|
|
178
|
-
}
|
|
179
|
-
function readSmallTextFile(file) {
|
|
180
|
-
try {
|
|
181
|
-
const stat = fs.statSync(file);
|
|
182
|
-
if (!stat.isFile() || stat.size > 1024 * 1024)
|
|
183
|
-
return null;
|
|
184
|
-
return fs.readFileSync(file, 'utf-8');
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
function extractBaseUrl(text) {
|
|
191
|
-
const match = text.match(/(?:base_url|baseURL|api_url|apiUrl|ANTHROPIC_BASE_URL|OPENAI_BASE_URL|DASHSCOPE_BASE_URL)["'\s:=]+([^"'\s,}]+)/i);
|
|
192
|
-
return match?.[1]?.trim();
|
|
193
|
-
}
|
|
194
|
-
function extractTokenLikeValue(text, agent) {
|
|
195
|
-
const prefix = agent === 'claude' ? 'sk-ant-' : agent === 'pi' ? 'sk-' : 'sk-';
|
|
196
|
-
const direct = text.match(new RegExp(`${prefix.replace(/-/g, '\\-')}[A-Za-z0-9_\\-]{8,}`));
|
|
197
|
-
if (direct?.[0])
|
|
198
|
-
return direct[0];
|
|
199
|
-
const keyMatch = text.match(/(?:api[_-]?key|auth[_-]?token|token|dashscope)["'\s:=]+([A-Za-z0-9_\-.]{12,})/i);
|
|
200
|
-
return keyMatch?.[1]?.trim();
|
|
201
|
-
}
|
|
202
|
-
function toSummary(input) {
|
|
203
|
-
return {
|
|
204
|
-
status: input.status,
|
|
205
|
-
source: input.source,
|
|
206
|
-
tokenPresent: input.tokenPresent,
|
|
207
|
-
...(input.baseUrl ? { baseUrlHost: formatBaseUrlHost(input.baseUrl) } : {}),
|
|
208
|
-
...(input.token ? { tokenHint: maskToken(input.token) } : {}),
|
|
209
|
-
updatedAt: new Date().toISOString(),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
export function maskToken(token) {
|
|
213
|
-
const trimmed = token.trim();
|
|
214
|
-
if (!trimmed)
|
|
215
|
-
return '';
|
|
216
|
-
const suffix = trimmed.slice(-TOKEN_SUFFIX_LENGTH);
|
|
217
|
-
return `••••${suffix}`;
|
|
218
|
-
}
|
|
219
|
-
function formatBaseUrlHost(baseUrl) {
|
|
220
|
-
try {
|
|
221
|
-
return new URL(baseUrl).host || baseUrl;
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
return baseUrl.replace(/^https?:\/\//i, '').replace(/\/.*$/, '') || baseUrl;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
1
|
+
import s from"node:fs";import m from"node:os";import i from"node:path";import{readLatestUserEnv as E}from"../agent-env.js";import{loadConfig as S,resolveShennianPath as g}from"../config/index.js";const c=g("agent-provider-config.json"),U=4;function A(e){return e==="codex"||e==="claude"||e==="pi"}const O={codex:{baseUrl:["OPENAI_BASE_URL","OPENAI_API_BASE","OPENAI_API_URL"],token:["OPENAI_API_KEY","OPENAI_TOKEN"]},claude:{baseUrl:["ANTHROPIC_BASE_URL","ANTHROPIC_API_URL"],token:["ANTHROPIC_API_KEY","ANTHROPIC_AUTH_TOKEN"]},pi:{baseUrl:["DASHSCOPE_BASE_URL","DASHSCOPE_API_BASE","DASHSCOPE_API_URL"],token:["DASHSCOPE_API_KEY","DASHSCOPE_TOKEN"]}};function k(e){return u().configs[e]}function v(e){const n=u(),o=n.configs[e.agent],t={agent:e.agent,baseUrl:e.baseUrl?.trim()||o?.baseUrl,token:e.token?.trim()||o?.token,updatedAt:new Date().toISOString()};return n.configs[e.agent]=t,_(n),t}function B(e){const n=u();delete n.configs[e],_(n)}function K(e){if(!A(e))return{};const n=k(e);return n?l(e==="codex"?{OPENAI_BASE_URL:n.baseUrl,OPENAI_API_KEY:n.token}:e==="claude"?{ANTHROPIC_BASE_URL:n.baseUrl,ANTHROPIC_API_KEY:n.token}:{DASHSCOPE_BASE_URL:n.baseUrl,DASHSCOPE_API_KEY:n.token}):{}}function j(e,n=E()){if(!A(e))return;const o=k(e);if(o?.token||o?.baseUrl)return a({agent:e,source:"shennian",status:o.token?"managed":"missing",baseUrl:o.baseUrl,token:o.token,tokenPresent:!!o.token});const t=I(e,n);if(t.tokenPresent||t.baseUrl)return a({agent:e,source:"env",status:t.tokenPresent?"detected":"missing",baseUrl:t.baseUrl,token:t.token,tokenPresent:t.tokenPresent});const r=p(e);return r.tokenPresent||r.baseUrl?a({agent:e,source:"config-file",status:r.tokenPresent?"detected":"missing",baseUrl:r.baseUrl,token:r.token,tokenPresent:r.tokenPresent}):a({agent:e,source:"unknown",status:"missing",tokenPresent:!1})}function u(){try{return{configs:JSON.parse(s.readFileSync(c,"utf-8")).configs??{}}}catch{return{configs:{}}}}function _(e){s.mkdirSync(i.dirname(c),{recursive:!0}),s.writeFileSync(c,JSON.stringify(e,null,2),{mode:384});try{s.chmodSync(c,384)}catch{}}function l(e){const n={};for(const[o,t]of Object.entries(e))t&&(n[o]=t);return n}function I(e,n){const o=O[e],t=P(n,o.baseUrl),r=P(n,o.token);return{baseUrl:t,token:r,tokenPresent:!!r}}function P(e,n){for(const o of n){const t=e[o]?.trim();if(t)return t}}function p(e){const n=m.homedir();if(e==="pi"){const r=S().apiKeys?.dashscope?.trim();if(r)return{token:r,tokenPresent:!0}}const o=e==="codex"?[i.join(n,".codex","config.toml"),i.join(n,".codex","auth.json")]:e==="claude"?[i.join(n,".claude","settings.json"),i.join(n,".claude.json")]:[i.join(n,".dashscope","config.json")];for(const t of o){const r=C(t);if(!r)continue;const f=N(r,e),d=b(r);if(f||d)return{token:f,baseUrl:d,tokenPresent:!!f}}return{tokenPresent:!1}}function C(e){try{const n=s.statSync(e);return!n.isFile()||n.size>1024*1024?null:s.readFileSync(e,"utf-8")}catch{return null}}function b(e){return e.match(/(?:base_url|baseURL|api_url|apiUrl|ANTHROPIC_BASE_URL|OPENAI_BASE_URL|DASHSCOPE_BASE_URL)["'\s:=]+([^"'\s,}]+)/i)?.[1]?.trim()}function N(e,n){const o=n==="claude"?"sk-ant-":"sk-",t=e.match(new RegExp(`${o.replace(/-/g,"\\-")}[A-Za-z0-9_\\-]{8,}`));return t?.[0]?t[0]:e.match(/(?:api[_-]?key|auth[_-]?token|token|dashscope)["'\s:=]+([A-Za-z0-9_\-.]{12,})/i)?.[1]?.trim()}function a(e){return{status:e.status,source:e.source,tokenPresent:e.tokenPresent,...e.baseUrl?{baseUrlHost:R(e.baseUrl)}:{},...e.token?{tokenHint:h(e.token)}:{},updatedAt:new Date().toISOString()}}function h(e){const n=e.trim();return n?`\u2022\u2022\u2022\u2022${n.slice(-U)}`:""}function R(e){try{return new URL(e).host||e}catch{return e.replace(/^https?:\/\//i,"").replace(/\/.*$/,"")||e}}export{K as buildManagedAgentEnv,B as deleteManagedAgentProviderConfig,j as getAgentConfigSummary,k as getManagedAgentProviderConfig,h as maskToken,v as upsertManagedAgentProviderConfig};
|
|
@@ -1,299 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import fsPromises from 'node:fs/promises';
|
|
5
|
-
import os from 'node:os';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import { resolveProtectedShennianDirectories } from '../security/managed-agent-credential-paths.js';
|
|
8
|
-
const CURSOR_MANAGED_CONFIG_PREFIX = 'shennian-cursor-managed-';
|
|
9
|
-
const MAX_PROJECT_SANDBOX_CONFIG_BYTES = 1024 * 1024;
|
|
10
|
-
const BASE_SENSITIVE_READ_RULES = [
|
|
11
|
-
'Read(.env)',
|
|
12
|
-
'Read(.env.*)',
|
|
13
|
-
'Read(**/.env)',
|
|
14
|
-
'Read(**/.env.*)',
|
|
15
|
-
'Read(*.key)',
|
|
16
|
-
'Read(**/*.key)',
|
|
17
|
-
'Read(*.pem)',
|
|
18
|
-
'Read(**/*.pem)',
|
|
19
|
-
'Read(~/.shennian/**)',
|
|
20
|
-
'Read($HOME/.shennian/**)',
|
|
21
|
-
];
|
|
22
|
-
const supportedExternalCapabilities = {
|
|
23
|
-
// Cursor's shell can request a one-off network permission and retry a
|
|
24
|
-
// denied command in non-interactive mode. Managed mode therefore keeps the
|
|
25
|
-
// entire Shell tool disabled; it cannot safely offer command networking.
|
|
26
|
-
commandNetwork: { allow: false, deny: true },
|
|
27
|
-
webSearch: { allow: true, deny: true },
|
|
28
|
-
// Cursor CLI does not expose non-interactive Browser or computer-use
|
|
29
|
-
// permission switches. Keeping them unavailable is safer than pretending
|
|
30
|
-
// that MCP/WebFetch rules control the native tools.
|
|
31
|
-
browser: { allow: false, deny: true },
|
|
32
|
-
computerUse: { allow: false, deny: true },
|
|
33
|
-
// Project MCP discovery happens outside the invocation-only config and can
|
|
34
|
-
// start before a tool permission is evaluated.
|
|
35
|
-
mcp: { allow: false, deny: false },
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* Evidence baseline: Cursor Agent 2026.08.31 exposes --sandbox, --mode=ask,
|
|
39
|
-
* CURSOR_CONFIG_DIR, sandbox.json network policy, and CLI permission tokens.
|
|
40
|
-
*
|
|
41
|
-
* The profile is deliberately partial. Real 2026.08.31 probes showed that
|
|
42
|
-
* Read can cross the workspace boundary, and that an allowed Shell can read
|
|
43
|
-
* outside files, write temporary files, and request network access despite a
|
|
44
|
-
* deny policy. Project MCP discovery also lacks a strict invocation-scoped
|
|
45
|
-
* disable switch. Shell is therefore disabled in managed mode.
|
|
46
|
-
*/
|
|
47
|
-
export const CURSOR_MANAGED_RUNTIME_SECURITY = {
|
|
48
|
-
minimumRuntimeVersion: '2026.08.31',
|
|
49
|
-
maximumRuntimeVersionExclusive: '2026.09.01',
|
|
50
|
-
platforms: {
|
|
51
|
-
darwin: {
|
|
52
|
-
level: 'partial',
|
|
53
|
-
fileAccess: { 'workspace-read': false, 'workspace-write': false, 'host-full': true },
|
|
54
|
-
externalCapabilities: supportedExternalCapabilities,
|
|
55
|
-
knownGaps: [
|
|
56
|
-
'fileAccess.workspaceBoundaryUnenforced',
|
|
57
|
-
'fileAccess.outsideWorkspaceRead',
|
|
58
|
-
'sandbox.shellDisabled',
|
|
59
|
-
'externalCapabilities.webSearchToolGranularity',
|
|
60
|
-
'externalCapabilities.projectMcpStartup',
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
linux: {
|
|
64
|
-
level: 'partial',
|
|
65
|
-
fileAccess: { 'workspace-read': false, 'workspace-write': false, 'host-full': true },
|
|
66
|
-
externalCapabilities: supportedExternalCapabilities,
|
|
67
|
-
knownGaps: [
|
|
68
|
-
'fileAccess.workspaceBoundaryUnenforced',
|
|
69
|
-
'fileAccess.outsideWorkspaceRead',
|
|
70
|
-
'sandbox.shellDisabled',
|
|
71
|
-
'externalCapabilities.webSearchToolGranularity',
|
|
72
|
-
'externalCapabilities.projectMcpStartup',
|
|
73
|
-
],
|
|
74
|
-
},
|
|
75
|
-
wsl: {
|
|
76
|
-
level: 'partial',
|
|
77
|
-
fileAccess: { 'workspace-read': false, 'workspace-write': false, 'host-full': true },
|
|
78
|
-
externalCapabilities: supportedExternalCapabilities,
|
|
79
|
-
knownGaps: [
|
|
80
|
-
'fileAccess.workspaceBoundaryUnenforced',
|
|
81
|
-
'fileAccess.outsideWorkspaceRead',
|
|
82
|
-
'sandbox.shellDisabled',
|
|
83
|
-
'externalCapabilities.webSearchToolGranularity',
|
|
84
|
-
'externalCapabilities.projectMcpStartup',
|
|
85
|
-
],
|
|
86
|
-
},
|
|
87
|
-
win32: {
|
|
88
|
-
level: 'partial',
|
|
89
|
-
fileAccess: { 'workspace-read': false, 'workspace-write': false, 'host-full': true },
|
|
90
|
-
externalCapabilities: {
|
|
91
|
-
commandNetwork: { allow: true, deny: false },
|
|
92
|
-
webSearch: { allow: true, deny: true },
|
|
93
|
-
browser: { allow: false, deny: true },
|
|
94
|
-
computerUse: { allow: false, deny: true },
|
|
95
|
-
mcp: { allow: true, deny: true },
|
|
96
|
-
},
|
|
97
|
-
knownGaps: [
|
|
98
|
-
'sandbox.nativeWindowsUnverified',
|
|
99
|
-
'sandbox.shellDisabled',
|
|
100
|
-
'externalCapabilities.projectMcpStartup',
|
|
101
|
-
],
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
/**
|
|
106
|
-
* Compiles one Cursor invocation policy. The returned JSON is written only to
|
|
107
|
-
* an isolated CURSOR_CONFIG_DIR created for the child process; neither the
|
|
108
|
-
* user's ~/.cursor directory nor the workspace is modified.
|
|
109
|
-
*/
|
|
110
|
-
export function compileCursorManagedPermissions(enforcement, resolvedWorkDir) {
|
|
111
|
-
const workDir = validateResolvedWorkDir(resolvedWorkDir);
|
|
112
|
-
const policy = enforcement.effectivePolicy;
|
|
113
|
-
const hostFull = policy.fileAccess === 'host-full';
|
|
114
|
-
const workspaceReadOnly = policy.fileAccess === 'workspace-read';
|
|
115
|
-
const sensitiveReadRules = resolveSensitiveReadRules();
|
|
116
|
-
const sensitiveWriteRules = sensitiveReadRules.map((rule) => rule.replace(/^Read\(/, 'Write('));
|
|
117
|
-
if (policy.externalCapabilities.commandNetwork) {
|
|
118
|
-
throw new Error('cursor_managed_command_network_unavailable');
|
|
119
|
-
}
|
|
120
|
-
if (policy.externalCapabilities.mcp) {
|
|
121
|
-
throw new Error('cursor_managed_mcp_isolation_unavailable');
|
|
122
|
-
}
|
|
123
|
-
if (policy.externalCapabilities.browser || policy.externalCapabilities.computerUse) {
|
|
124
|
-
throw new Error('cursor_managed_native_interactive_tool_control_unavailable');
|
|
125
|
-
}
|
|
126
|
-
if (enforcement.platform === 'win32' && !hostFull) {
|
|
127
|
-
throw new Error('cursor_managed_native_windows_sandbox_unavailable');
|
|
128
|
-
}
|
|
129
|
-
if (!hostFull)
|
|
130
|
-
assertProjectSandboxCannotBroadenPolicy(workDir, workspaceReadOnly);
|
|
131
|
-
const allow = ['Read(**)'];
|
|
132
|
-
if (!workspaceReadOnly)
|
|
133
|
-
allow.push('Write(**)');
|
|
134
|
-
if (policy.externalCapabilities.webSearch)
|
|
135
|
-
allow.push('WebFetch(*)');
|
|
136
|
-
if (policy.externalCapabilities.mcp)
|
|
137
|
-
allow.push('Mcp(*:*)');
|
|
138
|
-
const deny = [
|
|
139
|
-
...sensitiveReadRules,
|
|
140
|
-
...sensitiveWriteRules,
|
|
141
|
-
'Shell(*)',
|
|
142
|
-
...(workspaceReadOnly ? ['Write(**)'] : []),
|
|
143
|
-
...(!policy.externalCapabilities.webSearch ? ['WebFetch(*)'] : []),
|
|
144
|
-
...(!policy.externalCapabilities.mcp ? ['Mcp(*:*)'] : []),
|
|
145
|
-
];
|
|
146
|
-
const networkAllowed = policy.externalCapabilities.commandNetwork;
|
|
147
|
-
const cliConfig = {
|
|
148
|
-
version: 1,
|
|
149
|
-
editor: { vimMode: false },
|
|
150
|
-
approvalMode: hostFull ? 'unrestricted' : 'allowlist',
|
|
151
|
-
sandbox: {
|
|
152
|
-
mode: hostFull ? 'disabled' : 'enabled',
|
|
153
|
-
networkAccess: networkAllowed ? 'allow_all' : 'user_config_only',
|
|
154
|
-
readBoundary: hostFull ? 'system' : 'workspace',
|
|
155
|
-
},
|
|
156
|
-
permissions: {
|
|
157
|
-
allow: [...new Set(allow)].sort(),
|
|
158
|
-
deny: [...new Set(deny)].sort(),
|
|
159
|
-
},
|
|
160
|
-
notifications: false,
|
|
161
|
-
hints: false,
|
|
162
|
-
suggestNextPrompt: false,
|
|
163
|
-
attribution: {
|
|
164
|
-
attributeCommitsToAgent: false,
|
|
165
|
-
attributePRsToAgent: false,
|
|
166
|
-
},
|
|
167
|
-
};
|
|
168
|
-
const sandboxConfig = {
|
|
169
|
-
type: hostFull
|
|
170
|
-
? 'insecure_none'
|
|
171
|
-
: workspaceReadOnly
|
|
172
|
-
? 'workspace_readonly'
|
|
173
|
-
: 'workspace_readwrite',
|
|
174
|
-
additionalReadwritePaths: [],
|
|
175
|
-
additionalReadonlyPaths: [],
|
|
176
|
-
disableTmpWrite: true,
|
|
177
|
-
enableSharedBuildCache: false,
|
|
178
|
-
networkPolicy: networkAllowed
|
|
179
|
-
? { default: 'allow', allow: [], deny: [] }
|
|
180
|
-
: { default: 'deny', allow: [], deny: ['*'] },
|
|
181
|
-
networkPolicyStrict: !networkAllowed,
|
|
182
|
-
readBoundary: hostFull ? 'system' : 'workspace',
|
|
183
|
-
};
|
|
184
|
-
return {
|
|
185
|
-
args: [
|
|
186
|
-
'--trust',
|
|
187
|
-
'--workspace',
|
|
188
|
-
workDir,
|
|
189
|
-
'--sandbox',
|
|
190
|
-
hostFull ? 'disabled' : 'enabled',
|
|
191
|
-
...(workspaceReadOnly ? ['--mode', 'ask'] : []),
|
|
192
|
-
...(policy.externalCapabilities.mcp ? ['--approve-mcps'] : []),
|
|
193
|
-
...(hostFull ? ['--force'] : []),
|
|
194
|
-
],
|
|
195
|
-
cliConfig,
|
|
196
|
-
sandboxConfig,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
export async function createCursorManagedConfigDirectory(config) {
|
|
200
|
-
const configDir = await fsPromises.mkdtemp(path.join(os.tmpdir(), CURSOR_MANAGED_CONFIG_PREFIX));
|
|
201
|
-
try {
|
|
202
|
-
await fsPromises.chmod(configDir, 0o700);
|
|
203
|
-
await Promise.all([
|
|
204
|
-
writePrivateJson(path.join(configDir, 'cli-config.json'), config.cliConfig),
|
|
205
|
-
writePrivateJson(path.join(configDir, 'sandbox.json'), config.sandboxConfig),
|
|
206
|
-
]);
|
|
207
|
-
return configDir;
|
|
208
|
-
}
|
|
209
|
-
catch (error) {
|
|
210
|
-
await disposeCursorManagedConfigDirectory(configDir).catch(() => { });
|
|
211
|
-
throw new Error('cursor_managed_config_initialization_failed', { cause: error });
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
export async function disposeCursorManagedConfigDirectory(configDir) {
|
|
215
|
-
const resolved = path.resolve(configDir);
|
|
216
|
-
const tempRoot = path.resolve(os.tmpdir());
|
|
217
|
-
if (path.dirname(resolved) !== tempRoot ||
|
|
218
|
-
!path.basename(resolved).startsWith(CURSOR_MANAGED_CONFIG_PREFIX)) {
|
|
219
|
-
throw new Error('cursor_managed_config_cleanup_refused');
|
|
220
|
-
}
|
|
221
|
-
await fsPromises.rm(resolved, { recursive: true, force: true });
|
|
222
|
-
}
|
|
223
|
-
function assertProjectSandboxCannotBroadenPolicy(workDir, workspaceReadOnly) {
|
|
224
|
-
const projectConfigPath = path.join(workDir, '.cursor', 'sandbox.json');
|
|
225
|
-
if (!fs.existsSync(projectConfigPath))
|
|
226
|
-
return;
|
|
227
|
-
let raw;
|
|
228
|
-
try {
|
|
229
|
-
const stat = fs.statSync(projectConfigPath);
|
|
230
|
-
if (!stat.isFile() || stat.size > MAX_PROJECT_SANDBOX_CONFIG_BYTES) {
|
|
231
|
-
throw new Error('invalid_project_sandbox');
|
|
232
|
-
}
|
|
233
|
-
raw = fs.readFileSync(projectConfigPath, 'utf8');
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
throw new Error('cursor_managed_project_sandbox_unverifiable', { cause: error });
|
|
237
|
-
}
|
|
238
|
-
let parsed;
|
|
239
|
-
try {
|
|
240
|
-
const value = JSON.parse(raw);
|
|
241
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
242
|
-
throw new Error('invalid_project_sandbox');
|
|
243
|
-
}
|
|
244
|
-
parsed = value;
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
throw new Error('cursor_managed_project_sandbox_unverifiable', { cause: error });
|
|
248
|
-
}
|
|
249
|
-
if (parsed.type === 'insecure_none') {
|
|
250
|
-
throw new Error('cursor_managed_project_sandbox_broadens_access');
|
|
251
|
-
}
|
|
252
|
-
if (workspaceReadOnly && parsed.type === 'workspace_readwrite') {
|
|
253
|
-
throw new Error('cursor_managed_project_sandbox_broadens_access');
|
|
254
|
-
}
|
|
255
|
-
if (hasConfiguredPaths(parsed.additionalReadwritePaths) || hasConfiguredPaths(parsed.additionalReadonlyPaths)) {
|
|
256
|
-
throw new Error('cursor_managed_project_sandbox_broadens_access');
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
function hasConfiguredPaths(value) {
|
|
260
|
-
if (value == null)
|
|
261
|
-
return false;
|
|
262
|
-
return !Array.isArray(value) || value.length > 0;
|
|
263
|
-
}
|
|
264
|
-
function normalizeForCursor(value) {
|
|
265
|
-
return value.replaceAll('\\', '/');
|
|
266
|
-
}
|
|
267
|
-
function resolveSensitiveReadRules() {
|
|
268
|
-
const shennianDirectories = resolveProtectedShennianDirectories()
|
|
269
|
-
.map((entry) => normalizeForCursor(path.normalize(entry)))
|
|
270
|
-
.filter((entry) => path.isAbsolute(entry));
|
|
271
|
-
return [...new Set([
|
|
272
|
-
...BASE_SENSITIVE_READ_RULES,
|
|
273
|
-
...shennianDirectories.map((entry) => `Read(${entry}/**)`),
|
|
274
|
-
])];
|
|
275
|
-
}
|
|
276
|
-
function validateResolvedWorkDir(value) {
|
|
277
|
-
const normalized = path.normalize(value.trim());
|
|
278
|
-
if (!normalized || !path.isAbsolute(normalized)) {
|
|
279
|
-
throw new Error('cursor_managed_workdir_invalid');
|
|
280
|
-
}
|
|
281
|
-
let realPath;
|
|
282
|
-
try {
|
|
283
|
-
realPath = fs.realpathSync(normalized);
|
|
284
|
-
}
|
|
285
|
-
catch {
|
|
286
|
-
throw new Error('cursor_managed_workdir_unavailable');
|
|
287
|
-
}
|
|
288
|
-
if (path.normalize(realPath) !== normalized) {
|
|
289
|
-
throw new Error('cursor_managed_workdir_not_realpath');
|
|
290
|
-
}
|
|
291
|
-
return normalized;
|
|
292
|
-
}
|
|
293
|
-
async function writePrivateJson(filePath, value) {
|
|
294
|
-
await fsPromises.writeFile(filePath, `${JSON.stringify(value)}\n`, {
|
|
295
|
-
encoding: 'utf8',
|
|
296
|
-
mode: 0o600,
|
|
297
|
-
flag: 'wx',
|
|
298
|
-
});
|
|
299
|
-
}
|
|
1
|
+
import l from"node:fs";import c from"node:fs/promises";import u from"node:os";import s from"node:path";import{resolveProtectedShennianDirectories as C}from"../security/managed-agent-credential-paths.js";const f="shennian-cursor-managed-",R=1024*1024,v=["Read(.env)","Read(.env.*)","Read(**/.env)","Read(**/.env.*)","Read(*.key)","Read(**/*.key)","Read(*.pem)","Read(**/*.pem)","Read(~/.shennian/**)","Read($HOME/.shennian/**)"],p={commandNetwork:{allow:!1,deny:!0},webSearch:{allow:!0,deny:!0},browser:{allow:!1,deny:!0},computerUse:{allow:!1,deny:!0},mcp:{allow:!1,deny:!1}},U={minimumRuntimeVersion:"2026.08.31",maximumRuntimeVersionExclusive:"2026.09.01",platforms:{darwin:{level:"partial",fileAccess:{"workspace-read":!1,"workspace-write":!1,"host-full":!0},externalCapabilities:p,knownGaps:["fileAccess.workspaceBoundaryUnenforced","fileAccess.outsideWorkspaceRead","sandbox.shellDisabled","externalCapabilities.webSearchToolGranularity","externalCapabilities.projectMcpStartup"]},linux:{level:"partial",fileAccess:{"workspace-read":!1,"workspace-write":!1,"host-full":!0},externalCapabilities:p,knownGaps:["fileAccess.workspaceBoundaryUnenforced","fileAccess.outsideWorkspaceRead","sandbox.shellDisabled","externalCapabilities.webSearchToolGranularity","externalCapabilities.projectMcpStartup"]},wsl:{level:"partial",fileAccess:{"workspace-read":!1,"workspace-write":!1,"host-full":!0},externalCapabilities:p,knownGaps:["fileAccess.workspaceBoundaryUnenforced","fileAccess.outsideWorkspaceRead","sandbox.shellDisabled","externalCapabilities.webSearchToolGranularity","externalCapabilities.projectMcpStartup"]},win32:{level:"partial",fileAccess:{"workspace-read":!1,"workspace-write":!1,"host-full":!0},externalCapabilities:{commandNetwork:{allow:!0,deny:!1},webSearch:{allow:!0,deny:!0},browser:{allow:!1,deny:!0},computerUse:{allow:!1,deny:!0},mcp:{allow:!0,deny:!0}},knownGaps:["sandbox.nativeWindowsUnverified","sandbox.shellDisabled","externalCapabilities.projectMcpStartup"]}}};function G(a,e){const n=j(e),o=a.effectivePolicy,t=o.fileAccess==="host-full",r=o.fileAccess==="workspace-read",w=E(),b=w.map(k=>k.replace(/^Read\(/,"Write("));if(o.externalCapabilities.commandNetwork)throw new Error("cursor_managed_command_network_unavailable");if(o.externalCapabilities.mcp)throw new Error("cursor_managed_mcp_isolation_unavailable");if(o.externalCapabilities.browser||o.externalCapabilities.computerUse)throw new Error("cursor_managed_native_interactive_tool_control_unavailable");if(a.platform==="win32"&&!t)throw new Error("cursor_managed_native_windows_sandbox_unavailable");t||S(n,r);const i=["Read(**)"];r||i.push("Write(**)"),o.externalCapabilities.webSearch&&i.push("WebFetch(*)"),o.externalCapabilities.mcp&&i.push("Mcp(*:*)");const h=[...w,...b,"Shell(*)",...r?["Write(**)"]:[],...o.externalCapabilities.webSearch?[]:["WebFetch(*)"],...o.externalCapabilities.mcp?[]:["Mcp(*:*)"]],d=o.externalCapabilities.commandNetwork,y={version:1,editor:{vimMode:!1},approvalMode:t?"unrestricted":"allowlist",sandbox:{mode:t?"disabled":"enabled",networkAccess:d?"allow_all":"user_config_only",readBoundary:t?"system":"workspace"},permissions:{allow:[...new Set(i)].sort(),deny:[...new Set(h)].sort()},notifications:!1,hints:!1,suggestNextPrompt:!1,attribution:{attributeCommitsToAgent:!1,attributePRsToAgent:!1}},x={type:t?"insecure_none":r?"workspace_readonly":"workspace_readwrite",additionalReadwritePaths:[],additionalReadonlyPaths:[],disableTmpWrite:!0,enableSharedBuildCache:!1,networkPolicy:d?{default:"allow",allow:[],deny:[]}:{default:"deny",allow:[],deny:["*"]},networkPolicyStrict:!d,readBoundary:t?"system":"workspace"};return{args:["--trust","--workspace",n,"--sandbox",t?"disabled":"enabled",...r?["--mode","ask"]:[],...o.externalCapabilities.mcp?["--approve-mcps"]:[],...t?["--force"]:[]],cliConfig:y,sandboxConfig:x}}async function T(a){const e=await c.mkdtemp(s.join(u.tmpdir(),f));try{return await c.chmod(e,448),await Promise.all([_(s.join(e,"cli-config.json"),a.cliConfig),_(s.join(e,"sandbox.json"),a.sandboxConfig)]),e}catch(n){throw await g(e).catch(()=>{}),new Error("cursor_managed_config_initialization_failed",{cause:n})}}async function g(a){const e=s.resolve(a),n=s.resolve(u.tmpdir());if(s.dirname(e)!==n||!s.basename(e).startsWith(f))throw new Error("cursor_managed_config_cleanup_refused");await c.rm(e,{recursive:!0,force:!0})}function S(a,e){const n=s.join(a,".cursor","sandbox.json");if(!l.existsSync(n))return;let o;try{const r=l.statSync(n);if(!r.isFile()||r.size>R)throw new Error("invalid_project_sandbox");o=l.readFileSync(n,"utf8")}catch(r){throw new Error("cursor_managed_project_sandbox_unverifiable",{cause:r})}let t;try{const r=JSON.parse(o);if(!r||typeof r!="object"||Array.isArray(r))throw new Error("invalid_project_sandbox");t=r}catch(r){throw new Error("cursor_managed_project_sandbox_unverifiable",{cause:r})}if(t.type==="insecure_none")throw new Error("cursor_managed_project_sandbox_broadens_access");if(e&&t.type==="workspace_readwrite")throw new Error("cursor_managed_project_sandbox_broadens_access");if(m(t.additionalReadwritePaths)||m(t.additionalReadonlyPaths))throw new Error("cursor_managed_project_sandbox_broadens_access")}function m(a){return a==null?!1:!Array.isArray(a)||a.length>0}function A(a){return a.replaceAll("\\","/")}function E(){const a=C().map(e=>A(s.normalize(e))).filter(e=>s.isAbsolute(e));return[...new Set([...v,...a.map(e=>`Read(${e}/**)`)])]}function j(a){const e=s.normalize(a.trim());if(!e||!s.isAbsolute(e))throw new Error("cursor_managed_workdir_invalid");let n;try{n=l.realpathSync(e)}catch{throw new Error("cursor_managed_workdir_unavailable")}if(s.normalize(n)!==e)throw new Error("cursor_managed_workdir_not_realpath");return e}async function _(a,e){await c.writeFile(a,`${JSON.stringify(e)}
|
|
2
|
+
`,{encoding:"utf8",mode:384,flag:"wx"})}export{U as CURSOR_MANAGED_RUNTIME_SECURITY,G as compileCursorManagedPermissions,T as createCursorManagedConfigDirectory,g as disposeCursorManagedConfigDirectory};
|