@woosau/dsh-custom-env 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +132 -2
- package/README.zh-CN.md +133 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +864 -0
- package/lib/index.js +1053 -0
- package/lib/typert.host.d.ts +5 -0
- package/lib/typert.host.js +295 -0
- package/lib/typert.remote-client.d.ts +15 -0
- package/lib/typert.remote-client.js +231 -0
- package/lib/types/cli-path/index.d.ts +12 -0
- package/lib/types/cli-path/inspect-active.d.ts +33 -0
- package/lib/types/cli-path/macos.d.ts +21 -0
- package/lib/types/cli-path/paths.d.ts +46 -0
- package/lib/types/cli-path/resolve-expected.d.ts +24 -0
- package/lib/types/cli-path/status.d.ts +32 -0
- package/lib/types/cli-path/windows.d.ts +26 -0
- package/lib/types/client/EnvSettingsSection.d.ts +20 -0
- package/lib/types/client/index.d.ts +17 -0
- package/lib/types/client/locales.d.ts +9 -0
- package/lib/types/client/mount.d.ts +11 -0
- package/lib/types/env-file.d.ts +40 -0
- package/lib/types/index.d.ts +60 -0
- package/lib/types/names.d.ts +16 -0
- package/lib/types/os-user-env/index.d.ts +13 -0
- package/lib/types/os-user-env/macos.d.ts +15 -0
- package/lib/types/os-user-env/windows.d.ts +37 -0
- package/lib/types/types.d.ts +44 -0
- package/locale/en.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +87 -4
- package/scripts/command-path.ps1 +150 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,1053 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
5
|
+
var __typeError = (msg) => {
|
|
6
|
+
throw TypeError(msg);
|
|
7
|
+
};
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
10
|
+
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
11
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
12
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
13
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
14
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
15
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
16
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
17
|
+
return value;
|
|
18
|
+
};
|
|
19
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
20
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
21
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
22
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
23
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
|
|
24
|
+
return __privateGet(this, extra);
|
|
25
|
+
}, set [name](x) {
|
|
26
|
+
return __privateSet(this, extra, x);
|
|
27
|
+
} }, name));
|
|
28
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
29
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
30
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
31
|
+
if (k) {
|
|
32
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
|
|
33
|
+
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
34
|
+
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
35
|
+
}
|
|
36
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
|
|
37
|
+
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
38
|
+
else if (typeof it !== "object" || it === null) __typeError("Object expected");
|
|
39
|
+
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
40
|
+
}
|
|
41
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
42
|
+
};
|
|
43
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
44
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
45
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
46
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
47
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
48
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
49
|
+
|
|
50
|
+
// src/index.ts
|
|
51
|
+
import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
52
|
+
|
|
53
|
+
// src/cli-path/inspect-active.ts
|
|
54
|
+
import { accessSync, constants, existsSync as existsSync2, lstatSync, readlinkSync } from "node:fs";
|
|
55
|
+
import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
|
|
56
|
+
|
|
57
|
+
// src/cli-path/paths.ts
|
|
58
|
+
import { existsSync } from "node:fs";
|
|
59
|
+
import { basename, dirname, isAbsolute, join, normalize, resolve, sep } from "node:path";
|
|
60
|
+
function launcherName(platform = process.platform) {
|
|
61
|
+
return platform === "win32" ? "dsh.cmd" : "dsh";
|
|
62
|
+
}
|
|
63
|
+
function normalizeComparable(path, platform = process.platform) {
|
|
64
|
+
const expanded = path.trim().replace(/^"|"$/gu, "");
|
|
65
|
+
try {
|
|
66
|
+
const full = isAbsolute(expanded) ? resolve(expanded) : expanded;
|
|
67
|
+
const trimmed = full.replace(/[/\\]+$/u, "");
|
|
68
|
+
return platform === "win32" ? trimmed.toUpperCase() : trimmed;
|
|
69
|
+
} catch {
|
|
70
|
+
return platform === "win32" ? expanded.toUpperCase() : expanded;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function findElectronResources(execPath, platform = process.platform, exists = existsSync) {
|
|
74
|
+
const dir = dirname(execPath);
|
|
75
|
+
const candidates = [
|
|
76
|
+
join(dir, "resources"),
|
|
77
|
+
join(dir, "..", "Resources"),
|
|
78
|
+
join(dir, "..", "resources")
|
|
79
|
+
];
|
|
80
|
+
const name = launcherName(platform);
|
|
81
|
+
for (const candidate of candidates) {
|
|
82
|
+
const cliBin = join(candidate, "runtime", "cli", "bin", name);
|
|
83
|
+
if (exists(cliBin)) return resolve(candidate);
|
|
84
|
+
}
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
function findLauncherDirectory(start, platform = process.platform, maxHops = 8, exists = existsSync) {
|
|
88
|
+
let current = resolve(start);
|
|
89
|
+
const name = launcherName(platform);
|
|
90
|
+
for (let i = 0; i < maxHops; i += 1) {
|
|
91
|
+
const asFile = join(dirname(current), name);
|
|
92
|
+
if (exists(asFile) && basename(asFile) === name) return dirname(asFile);
|
|
93
|
+
const asDir = join(current, name);
|
|
94
|
+
if (exists(asDir)) return current;
|
|
95
|
+
const inBin = join(current, "bin", name);
|
|
96
|
+
if (exists(inBin)) return join(current, "bin");
|
|
97
|
+
const parent = dirname(current);
|
|
98
|
+
if (parent === current) break;
|
|
99
|
+
current = parent;
|
|
100
|
+
}
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
function looksLikeCliEntry(argv1) {
|
|
104
|
+
if (argv1 === void 0 || argv1 === "") return false;
|
|
105
|
+
const base = basename(argv1).toLowerCase();
|
|
106
|
+
return base === "dsh" || base === "dsh.cmd" || base === "dsh.js" || base === "cli.js" || base === "cli.mjs" || base.endsWith(".js") && argv1.replaceAll("\\", "/").includes("/dsh");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/cli-path/inspect-active.ts
|
|
110
|
+
var MAC_DESTINATION = "/usr/local/bin/dsh";
|
|
111
|
+
function inspectActiveCli(options = {}) {
|
|
112
|
+
const platform = options.platform ?? process.platform;
|
|
113
|
+
const exists = options.exists ?? existsSync2;
|
|
114
|
+
const isExecutable = options.isExecutable ?? ((path) => {
|
|
115
|
+
try {
|
|
116
|
+
accessSync(path, constants.X_OK);
|
|
117
|
+
return true;
|
|
118
|
+
} catch {
|
|
119
|
+
return exists(path);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const readLink = options.readLink ?? ((path) => {
|
|
123
|
+
try {
|
|
124
|
+
if (!lstatSync(path).isSymbolicLink()) return void 0;
|
|
125
|
+
return readlinkSync(path);
|
|
126
|
+
} catch {
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
if (platform === "win32") {
|
|
131
|
+
return inspectWindows(options, exists);
|
|
132
|
+
}
|
|
133
|
+
return inspectPosix(platform, options, exists, isExecutable, readLink);
|
|
134
|
+
}
|
|
135
|
+
function inspectWindows(options, exists) {
|
|
136
|
+
const machine = options.machinePath ?? readWindowsMachinePath();
|
|
137
|
+
const user = options.pathEnv ?? readWindowsUserPath();
|
|
138
|
+
const combined = `${machine};${user}`;
|
|
139
|
+
const extensions = (options.pathext ?? process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((ext) => ext.trim()).filter((ext) => ext !== "");
|
|
140
|
+
const searchExt = [".ps1", ...extensions];
|
|
141
|
+
for (const part of combined.split(";")) {
|
|
142
|
+
const directory = expandWindowsPath(part.trim().replace(/^"|"$/gu, ""));
|
|
143
|
+
if (directory === "" || !isAbsolute2(directory)) continue;
|
|
144
|
+
for (const extension of searchExt) {
|
|
145
|
+
const candidate = join2(directory, `dsh${extension}`);
|
|
146
|
+
try {
|
|
147
|
+
if (exists(candidate)) return { path: candidate, symlinkTarget: void 0 };
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return { path: void 0, symlinkTarget: void 0 };
|
|
153
|
+
}
|
|
154
|
+
function inspectPosix(platform, options, exists, isExecutable, readLink) {
|
|
155
|
+
const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
|
|
156
|
+
const name = launcherName(platform);
|
|
157
|
+
for (const part of pathEnv.split(":")) {
|
|
158
|
+
const directory = part.trim();
|
|
159
|
+
if (directory === "") continue;
|
|
160
|
+
const candidate = join2(directory, name);
|
|
161
|
+
try {
|
|
162
|
+
if (!exists(candidate)) continue;
|
|
163
|
+
if (!isExecutable(candidate) && platform !== "darwin") continue;
|
|
164
|
+
const symlinkTarget = readLink(candidate);
|
|
165
|
+
return { path: candidate, symlinkTarget };
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (platform === "darwin" && exists(MAC_DESTINATION)) {
|
|
170
|
+
return { path: MAC_DESTINATION, symlinkTarget: readLink(MAC_DESTINATION) };
|
|
171
|
+
}
|
|
172
|
+
return { path: void 0, symlinkTarget: void 0 };
|
|
173
|
+
}
|
|
174
|
+
function readWindowsUserPath() {
|
|
175
|
+
return process.env.Path ?? process.env.PATH ?? "";
|
|
176
|
+
}
|
|
177
|
+
function readWindowsMachinePath() {
|
|
178
|
+
return "";
|
|
179
|
+
}
|
|
180
|
+
function expandWindowsPath(value) {
|
|
181
|
+
return value.replace(/%([^%]+)%/gu, (_match, name) => process.env[name] ?? `%${name}%`);
|
|
182
|
+
}
|
|
183
|
+
function activeMatchesExpected(active, expectedLauncher, platform = process.platform) {
|
|
184
|
+
if (active.path === void 0) return false;
|
|
185
|
+
if (platform === "darwin") {
|
|
186
|
+
const target = active.symlinkTarget;
|
|
187
|
+
if (target !== void 0 && target !== "") {
|
|
188
|
+
const resolved = target.startsWith("/") ? target : join2(active.path, "..", target);
|
|
189
|
+
if (normalizeComparable(resolved, platform) === normalizeComparable(expectedLauncher, platform)) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return normalizeComparable(active.path, platform) === normalizeComparable(expectedLauncher, platform);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/cli-path/macos.ts
|
|
198
|
+
import { execFile } from "node:child_process";
|
|
199
|
+
import { existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync, readFileSync, readlinkSync as readlinkSync2, renameSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
200
|
+
import { dirname as dirname2, isAbsolute as isAbsolute3, join as join3 } from "node:path";
|
|
201
|
+
import { promisify } from "node:util";
|
|
202
|
+
var execFileAsync = promisify(execFile);
|
|
203
|
+
var MAC_CLI_DESTINATION = "/usr/local/bin/dsh";
|
|
204
|
+
var MAC_CLI_RECEIPT = "/usr/local/bin/.dsh-custom-env-command.json";
|
|
205
|
+
var MacosCommandError = class extends Error {
|
|
206
|
+
constructor(code, message) {
|
|
207
|
+
super(message);
|
|
208
|
+
this.code = code;
|
|
209
|
+
this.name = "MacosCommandError";
|
|
210
|
+
}
|
|
211
|
+
code;
|
|
212
|
+
};
|
|
213
|
+
function readMacosCliLink(destination = MAC_CLI_DESTINATION) {
|
|
214
|
+
try {
|
|
215
|
+
if (!lstatSync2(destination).isSymbolicLink()) return void 0;
|
|
216
|
+
const target = readlinkSync2(destination);
|
|
217
|
+
return isAbsolute3(target) ? target : join3(dirname2(destination), target);
|
|
218
|
+
} catch {
|
|
219
|
+
return void 0;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function installMacosCliLink(launcher) {
|
|
223
|
+
if (process.platform !== "darwin") {
|
|
224
|
+
throw new MacosCommandError("EUNSUPPORTED", "macOS command install is only available on darwin.");
|
|
225
|
+
}
|
|
226
|
+
if (!isAbsolute3(launcher) || !existsSync3(launcher)) {
|
|
227
|
+
throw new MacosCommandError("ENOENT", "The expected launcher is unavailable.");
|
|
228
|
+
}
|
|
229
|
+
const current = readMacosCliLink();
|
|
230
|
+
if (current !== void 0 && same(current, launcher)) {
|
|
231
|
+
writeReceipt(launcher);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
await writeLink(launcher);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (error.code !== "EACCES") throw error;
|
|
238
|
+
await writeLinkElevated(launcher);
|
|
239
|
+
}
|
|
240
|
+
writeReceipt(launcher);
|
|
241
|
+
}
|
|
242
|
+
async function writeLink(launcher) {
|
|
243
|
+
mkdirSync(dirname2(MAC_CLI_DESTINATION), { recursive: true });
|
|
244
|
+
const pending = `${MAC_CLI_DESTINATION}.dsh-custom-env-new`;
|
|
245
|
+
try {
|
|
246
|
+
unlinkSync(pending);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if (error.code !== "ENOENT") throw error;
|
|
249
|
+
}
|
|
250
|
+
symlinkSync(launcher, pending);
|
|
251
|
+
try {
|
|
252
|
+
try {
|
|
253
|
+
unlinkSync(MAC_CLI_DESTINATION);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (error.code !== "ENOENT") throw error;
|
|
256
|
+
}
|
|
257
|
+
renameSync(pending, MAC_CLI_DESTINATION);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
try {
|
|
260
|
+
unlinkSync(pending);
|
|
261
|
+
} catch (cleanup) {
|
|
262
|
+
if (cleanup.code !== "ENOENT") {
|
|
263
|
+
throw new AggregateError([error, cleanup], "Failed to install macOS dsh link");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function writeLinkElevated(launcher) {
|
|
270
|
+
const escapedLauncher = launcher.replace(/'/gu, `'\\''`);
|
|
271
|
+
const escapedDest = MAC_CLI_DESTINATION.replace(/'/gu, `'\\''`);
|
|
272
|
+
const shell = `mkdir -p /usr/local/bin && ln -sfn '${escapedLauncher}' '${escapedDest}'`;
|
|
273
|
+
const script = `do shell script ${JSON.stringify(shell)} with administrator privileges`;
|
|
274
|
+
try {
|
|
275
|
+
await execFileAsync("/usr/bin/osascript", ["-e", script]);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
278
|
+
throw new MacosCommandError("EACCES", `Elevated symlink failed: ${message}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function writeReceipt(launcher) {
|
|
282
|
+
const receipt = { schemaVersion: 1, launcher };
|
|
283
|
+
try {
|
|
284
|
+
writeFileSync(MAC_CLI_RECEIPT, `${JSON.stringify(receipt)}
|
|
285
|
+
`, { mode: 420 });
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (error.code !== "EACCES") {
|
|
288
|
+
try {
|
|
289
|
+
const existing = readFileSync(MAC_CLI_RECEIPT, "utf8");
|
|
290
|
+
if (existing.includes(launcher)) return;
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function same(left, right) {
|
|
297
|
+
return left === right;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/cli-path/resolve-expected.ts
|
|
301
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
302
|
+
import { basename as basename2, dirname as dirname3, join as join4, resolve as resolve2 } from "node:path";
|
|
303
|
+
function basenameLower(path) {
|
|
304
|
+
return basename2(path).toLowerCase();
|
|
305
|
+
}
|
|
306
|
+
function resolveExpectedCli(options = {}) {
|
|
307
|
+
const platform = options.platform ?? process.platform;
|
|
308
|
+
const execPath = options.execPath ?? process.execPath;
|
|
309
|
+
const argv = options.argv ?? process.argv;
|
|
310
|
+
const electron = options.electronRunAsNode ?? process.env.ELECTRON_RUN_AS_NODE;
|
|
311
|
+
const exists = options.exists ?? existsSync4;
|
|
312
|
+
const name = launcherName(platform);
|
|
313
|
+
if (electron !== void 0 && electron !== "") {
|
|
314
|
+
const resources = findElectronResources(execPath, platform, exists);
|
|
315
|
+
if (resources !== void 0) {
|
|
316
|
+
const directory = join4(resources, "runtime", "cli", "bin");
|
|
317
|
+
const launcher = join4(directory, name);
|
|
318
|
+
if (exists(launcher)) return { launcher: resolve2(launcher), directory: resolve2(directory), kind: "electron-resources" };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const argv1 = argv[1];
|
|
322
|
+
if (looksLikeCliEntry(argv1) && argv1 !== void 0) {
|
|
323
|
+
const file = resolve2(argv1);
|
|
324
|
+
if (exists(file)) {
|
|
325
|
+
const base = basenameLower(file);
|
|
326
|
+
if (base === name.toLowerCase() || base === "dsh" || base === "dsh.cmd") {
|
|
327
|
+
return { launcher: file, directory: dirname3(file), kind: "argv" };
|
|
328
|
+
}
|
|
329
|
+
const directory = findLauncherDirectory(file, platform, 8, exists);
|
|
330
|
+
if (directory !== void 0) {
|
|
331
|
+
const launcher = join4(directory, name);
|
|
332
|
+
if (exists(launcher)) return { launcher: resolve2(launcher), directory: resolve2(directory), kind: "argv" };
|
|
333
|
+
}
|
|
334
|
+
const siblingBin = join4(dirname3(file), "bin", name);
|
|
335
|
+
if (exists(siblingBin)) {
|
|
336
|
+
return { launcher: resolve2(siblingBin), directory: resolve2(dirname3(siblingBin)), kind: "argv" };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (argv1 !== void 0 && argv1 !== "") {
|
|
341
|
+
const walked = findLauncherDirectory(resolve2(argv1), platform, 8, exists);
|
|
342
|
+
if (walked !== void 0) {
|
|
343
|
+
const launcher = join4(walked, name);
|
|
344
|
+
if (exists(launcher)) return { launcher: resolve2(launcher), directory: resolve2(walked), kind: "walk" };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return void 0;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/cli-path/status.ts
|
|
351
|
+
function buildCliStatus(options = {}) {
|
|
352
|
+
const platform = options.platform ?? process.platform;
|
|
353
|
+
const expected = options.expected !== void 0 ? options.expected : resolveExpectedCli({ platform });
|
|
354
|
+
const active = options.active ?? inspectActiveCli({ platform });
|
|
355
|
+
if (expected === void 0) {
|
|
356
|
+
return {
|
|
357
|
+
platform,
|
|
358
|
+
activePath: active.path ?? null,
|
|
359
|
+
expectedPath: null,
|
|
360
|
+
expectedDirectory: null,
|
|
361
|
+
matches: false,
|
|
362
|
+
canInstall: false,
|
|
363
|
+
reason: options.resolveFailedReason ?? "could-not-resolve-expected-cli"
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
const matches = activeMatchesExpected(active, expected.launcher, platform);
|
|
367
|
+
const canInstall = !matches && (platform === "win32" || platform === "darwin");
|
|
368
|
+
return {
|
|
369
|
+
platform,
|
|
370
|
+
activePath: active.path ?? null,
|
|
371
|
+
expectedPath: expected.launcher,
|
|
372
|
+
expectedDirectory: expected.directory,
|
|
373
|
+
matches,
|
|
374
|
+
canInstall,
|
|
375
|
+
reason: matches ? null : platform !== "win32" && platform !== "darwin" ? "unsupported-platform" : null
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/cli-path/windows.ts
|
|
380
|
+
import { spawn } from "node:child_process";
|
|
381
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
382
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
383
|
+
import { fileURLToPath } from "node:url";
|
|
384
|
+
var WindowsCommandError = class extends Error {
|
|
385
|
+
constructor(code, message) {
|
|
386
|
+
super(message);
|
|
387
|
+
this.code = code;
|
|
388
|
+
this.name = "WindowsCommandError";
|
|
389
|
+
}
|
|
390
|
+
code;
|
|
391
|
+
};
|
|
392
|
+
function commandPathScript(fromUrl = import.meta.url) {
|
|
393
|
+
const here = dirname4(fileURLToPath(fromUrl));
|
|
394
|
+
const candidates = [
|
|
395
|
+
join5(here, "..", "scripts", "command-path.ps1"),
|
|
396
|
+
join5(here, "..", "..", "scripts", "command-path.ps1"),
|
|
397
|
+
join5(here, "scripts", "command-path.ps1")
|
|
398
|
+
];
|
|
399
|
+
for (const candidate of candidates) {
|
|
400
|
+
if (existsSync5(candidate)) return candidate;
|
|
401
|
+
}
|
|
402
|
+
return candidates[0];
|
|
403
|
+
}
|
|
404
|
+
async function runWindowsCommandPath(operation, directory, expected) {
|
|
405
|
+
if (process.platform !== "win32") {
|
|
406
|
+
throw new WindowsCommandError("EUNSUPPORTED", "Windows command PATH install is only available on win32.");
|
|
407
|
+
}
|
|
408
|
+
const systemRoot = process.env.SystemRoot ?? process.env.WINDIR;
|
|
409
|
+
if (systemRoot === void 0) {
|
|
410
|
+
throw new WindowsCommandError("ENOENT", "Windows system directory is unavailable.");
|
|
411
|
+
}
|
|
412
|
+
const shell = join5(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
413
|
+
const script = commandPathScript();
|
|
414
|
+
const child = spawn(
|
|
415
|
+
shell,
|
|
416
|
+
["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script],
|
|
417
|
+
{ stdio: ["pipe", "pipe", "pipe"], windowsHide: true }
|
|
418
|
+
);
|
|
419
|
+
const chunks = [];
|
|
420
|
+
const errChunks = [];
|
|
421
|
+
child.stdout.on("data", (chunk) => {
|
|
422
|
+
chunks.push(chunk);
|
|
423
|
+
});
|
|
424
|
+
child.stderr.on("data", (chunk) => {
|
|
425
|
+
errChunks.push(chunk);
|
|
426
|
+
});
|
|
427
|
+
const exited = new Promise((accept, reject) => {
|
|
428
|
+
child.once("error", reject);
|
|
429
|
+
child.once("close", accept);
|
|
430
|
+
});
|
|
431
|
+
child.stdin.end(JSON.stringify({
|
|
432
|
+
operation,
|
|
433
|
+
directory,
|
|
434
|
+
...expected === void 0 ? {} : { expected }
|
|
435
|
+
}));
|
|
436
|
+
const code = await exited;
|
|
437
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
438
|
+
let parsed;
|
|
439
|
+
try {
|
|
440
|
+
parsed = JSON.parse(raw);
|
|
441
|
+
} catch (error) {
|
|
442
|
+
const detail = Buffer.concat(errChunks).toString("utf8").trim() || raw;
|
|
443
|
+
throw new WindowsCommandError(
|
|
444
|
+
"EIO",
|
|
445
|
+
`command-path.ps1 returned invalid JSON (exit ${String(code)})${detail === "" ? "" : `: ${detail}`}`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
if (parsed.ok !== true || parsed.state === void 0) {
|
|
449
|
+
throw new WindowsCommandError(
|
|
450
|
+
typeof parsed.code === "string" ? parsed.code : "EIO",
|
|
451
|
+
typeof parsed.message === "string" ? parsed.message : "command-path.ps1 failed"
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const state = parsed.state;
|
|
455
|
+
if (typeof state.fingerprint !== "string" || typeof state.directory !== "string" || typeof state.available !== "boolean" || typeof state.managed !== "boolean") {
|
|
456
|
+
throw new WindowsCommandError("EIO", "command-path.ps1 returned an incomplete state.");
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
fingerprint: state.fingerprint,
|
|
460
|
+
directory: state.directory,
|
|
461
|
+
available: state.available,
|
|
462
|
+
managed: state.managed,
|
|
463
|
+
...typeof state.activeCommand === "string" ? { activeCommand: state.activeCommand } : {}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/cli-path/index.ts
|
|
468
|
+
async function cliStatus() {
|
|
469
|
+
const platform = process.platform;
|
|
470
|
+
const expected = resolveExpectedCli({ platform });
|
|
471
|
+
if (platform === "win32" && expected !== void 0) {
|
|
472
|
+
try {
|
|
473
|
+
const state = await runWindowsCommandPath("inspect", expected.directory);
|
|
474
|
+
return buildCliStatus({
|
|
475
|
+
platform,
|
|
476
|
+
expected,
|
|
477
|
+
active: { path: state.activeCommand, symlinkTarget: void 0 }
|
|
478
|
+
});
|
|
479
|
+
} catch {
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (platform === "darwin" && expected !== void 0) {
|
|
483
|
+
const link = readMacosCliLink();
|
|
484
|
+
const active = inspectActiveCli({ platform });
|
|
485
|
+
const path = active.path ?? (link !== void 0 ? "/usr/local/bin/dsh" : void 0);
|
|
486
|
+
return buildCliStatus({
|
|
487
|
+
platform,
|
|
488
|
+
expected,
|
|
489
|
+
active: {
|
|
490
|
+
path,
|
|
491
|
+
symlinkTarget: link ?? active.symlinkTarget
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
return buildCliStatus({ platform, expected });
|
|
496
|
+
}
|
|
497
|
+
async function cliInstall() {
|
|
498
|
+
const before = await cliStatus();
|
|
499
|
+
if (!before.canInstall || before.expectedPath === null || before.expectedDirectory === null) {
|
|
500
|
+
const error = new Error(before.reason ?? "CLI install is not available");
|
|
501
|
+
error.code = "EUNSUPPORTED";
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
const platform = before.platform;
|
|
505
|
+
try {
|
|
506
|
+
if (platform === "win32") {
|
|
507
|
+
const inspect = await runWindowsCommandPath("inspect", before.expectedDirectory);
|
|
508
|
+
await runWindowsCommandPath("install", before.expectedDirectory, inspect.fingerprint);
|
|
509
|
+
} else if (platform === "darwin") {
|
|
510
|
+
await installMacosCliLink(before.expectedPath);
|
|
511
|
+
} else {
|
|
512
|
+
throw new Error("CLI install is not supported on this platform");
|
|
513
|
+
}
|
|
514
|
+
} catch (error) {
|
|
515
|
+
if (error instanceof WindowsCommandError || error instanceof MacosCommandError) throw error;
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
return cliStatus();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/env-file.ts
|
|
522
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
523
|
+
import { dirname as dirname5, join as join6 } from "node:path";
|
|
524
|
+
import { parseEnv } from "node:util";
|
|
525
|
+
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
526
|
+
|
|
527
|
+
// src/names.ts
|
|
528
|
+
var BOOTSTRAP_NAMES = /* @__PURE__ */ new Set([
|
|
529
|
+
"PATH",
|
|
530
|
+
"HOME",
|
|
531
|
+
"USERPROFILE",
|
|
532
|
+
"SHELL",
|
|
533
|
+
"NODE_OPTIONS",
|
|
534
|
+
"NODE_PATH",
|
|
535
|
+
"NODE_EXTRA_CA_CERTS",
|
|
536
|
+
"LD_PRELOAD",
|
|
537
|
+
"LD_LIBRARY_PATH",
|
|
538
|
+
"LD_AUDIT",
|
|
539
|
+
"BASH_ENV",
|
|
540
|
+
"ENV",
|
|
541
|
+
"SHELLOPTS",
|
|
542
|
+
"BASHOPTS",
|
|
543
|
+
"PERL5OPT",
|
|
544
|
+
"PERL5LIB",
|
|
545
|
+
"PYTHONSTARTUP",
|
|
546
|
+
"PYTHONPATH",
|
|
547
|
+
"RUBYOPT",
|
|
548
|
+
"RUBYLIB",
|
|
549
|
+
"JAVA_TOOL_OPTIONS",
|
|
550
|
+
"_JAVA_OPTIONS",
|
|
551
|
+
"JDK_JAVA_OPTIONS",
|
|
552
|
+
"PYTHONHOME",
|
|
553
|
+
"GIT_SSH",
|
|
554
|
+
"GIT_SSH_COMMAND",
|
|
555
|
+
"GIT_EXTERNAL_DIFF",
|
|
556
|
+
"GIT_PAGER",
|
|
557
|
+
"GIT_EDITOR",
|
|
558
|
+
"GIT_ASKPASS",
|
|
559
|
+
"SSH_ASKPASS",
|
|
560
|
+
"GIT_CONFIG_GLOBAL",
|
|
561
|
+
"GIT_CONFIG_SYSTEM",
|
|
562
|
+
"GIT_CONFIG_COUNT",
|
|
563
|
+
"EDITOR",
|
|
564
|
+
"VISUAL",
|
|
565
|
+
"PAGER",
|
|
566
|
+
"BROWSER",
|
|
567
|
+
"DEEPSEEK_BASE_URL",
|
|
568
|
+
"DEEPSEEK_SEARCH_BASE_URL",
|
|
569
|
+
"SSL_CERT_FILE",
|
|
570
|
+
"SSL_CERT_DIR",
|
|
571
|
+
"HTTP_PROXY",
|
|
572
|
+
"HTTPS_PROXY",
|
|
573
|
+
"ALL_PROXY",
|
|
574
|
+
"NO_PROXY",
|
|
575
|
+
"REQUESTS_CA_BUNDLE",
|
|
576
|
+
"CURL_CA_BUNDLE",
|
|
577
|
+
"NODE_TLS_REJECT_UNAUTHORIZED"
|
|
578
|
+
]);
|
|
579
|
+
var BOOTSTRAP_PREFIXES = ["DSH_", "XDG_", "DYLD_", "BASH_FUNC_"];
|
|
580
|
+
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
581
|
+
function isBootstrapOnly(name) {
|
|
582
|
+
const upper = name.toUpperCase();
|
|
583
|
+
return BOOTSTRAP_NAMES.has(upper) || BOOTSTRAP_PREFIXES.some((prefix) => upper.startsWith(prefix));
|
|
584
|
+
}
|
|
585
|
+
function isWritableEnvName(name) {
|
|
586
|
+
return ENV_NAME.test(name) && !isBootstrapOnly(name);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/env-file.ts
|
|
590
|
+
function envFilePath() {
|
|
591
|
+
return join6(resolveDshHome(), ".env");
|
|
592
|
+
}
|
|
593
|
+
function readRaw() {
|
|
594
|
+
const path = envFilePath();
|
|
595
|
+
return existsSync6(path) ? readFileSync2(path, "utf8") : "";
|
|
596
|
+
}
|
|
597
|
+
function writeEnvFile(content) {
|
|
598
|
+
const path = envFilePath();
|
|
599
|
+
mkdirSync2(dirname5(path), { recursive: true });
|
|
600
|
+
writeFileSync2(path, content, "utf8");
|
|
601
|
+
}
|
|
602
|
+
function encodeValue(value) {
|
|
603
|
+
if (/^[A-Za-z0-9_./:@+-]*$/u.test(value)) return value;
|
|
604
|
+
return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"').replace(/\n/gu, "\\n")}"`;
|
|
605
|
+
}
|
|
606
|
+
function parseLines(content) {
|
|
607
|
+
const text = content.replace(/^\uFEFF/u, "");
|
|
608
|
+
if (text === "") return [];
|
|
609
|
+
return text.split(/\r?\n/u).map((raw) => {
|
|
610
|
+
const trimmed = raw.trim();
|
|
611
|
+
if (trimmed === "" || trimmed.startsWith("#")) return { kind: "other", raw };
|
|
612
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/u.exec(trimmed);
|
|
613
|
+
if (match?.[1] === void 0) return { kind: "other", raw };
|
|
614
|
+
return { kind: "assignment", raw, name: match[1] };
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
function serializeLines(lines) {
|
|
618
|
+
const kept = lines.filter((line) => !(line.kind === "other" && line.raw.trim() === ""));
|
|
619
|
+
return kept.length === 0 ? "" : `${kept.map((line) => line.raw).join("\n")}
|
|
620
|
+
`;
|
|
621
|
+
}
|
|
622
|
+
function assertWritable(name, value) {
|
|
623
|
+
if (!isWritableEnvName(name)) {
|
|
624
|
+
throw new Error(`custom-env: refused bootstrap or invalid name "${name}"`);
|
|
625
|
+
}
|
|
626
|
+
if (value === "") {
|
|
627
|
+
throw new Error(`custom-env: empty value for "${name}"`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function loadEnvMap() {
|
|
631
|
+
const raw = readRaw().replace(/^\uFEFF/u, "").trim();
|
|
632
|
+
if (raw === "") return {};
|
|
633
|
+
let parsed;
|
|
634
|
+
try {
|
|
635
|
+
parsed = parseEnv(raw);
|
|
636
|
+
} catch (error) {
|
|
637
|
+
throw new Error(`custom-env: invalid dotenv syntax in ${envFilePath()}`, { cause: error });
|
|
638
|
+
}
|
|
639
|
+
const out = {};
|
|
640
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
641
|
+
if (typeof value === "string" && value !== "" && isWritableEnvName(key)) out[key] = value;
|
|
642
|
+
}
|
|
643
|
+
return out;
|
|
644
|
+
}
|
|
645
|
+
function listEnvEntries() {
|
|
646
|
+
const path = envFilePath();
|
|
647
|
+
const exists = existsSync6(path);
|
|
648
|
+
if (!exists) return { path, exists, entries: [] };
|
|
649
|
+
const map = loadEnvMap();
|
|
650
|
+
const entries = [];
|
|
651
|
+
const seen = /* @__PURE__ */ new Set();
|
|
652
|
+
for (const line of parseLines(readRaw())) {
|
|
653
|
+
if (line.kind !== "assignment" || line.name === void 0 || seen.has(line.name)) continue;
|
|
654
|
+
const value = map[line.name];
|
|
655
|
+
if (value === void 0) continue;
|
|
656
|
+
seen.add(line.name);
|
|
657
|
+
entries.push({ name: line.name, value });
|
|
658
|
+
}
|
|
659
|
+
return { path, exists, entries };
|
|
660
|
+
}
|
|
661
|
+
function writeEnvEntry(replaceName, name, value) {
|
|
662
|
+
assertWritable(replaceName, value);
|
|
663
|
+
assertWritable(name, value);
|
|
664
|
+
const assignment = `${name}=${encodeValue(value)}`;
|
|
665
|
+
let replaced = false;
|
|
666
|
+
const next = [];
|
|
667
|
+
for (const line of parseLines(readRaw())) {
|
|
668
|
+
if (line.kind === "assignment" && line.name === replaceName) {
|
|
669
|
+
replaced = true;
|
|
670
|
+
next.push({ kind: "assignment", raw: assignment, name });
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (replaceName !== name && line.kind === "assignment" && line.name === name) continue;
|
|
674
|
+
next.push(line);
|
|
675
|
+
}
|
|
676
|
+
if (!replaced) next.push({ kind: "assignment", raw: assignment, name });
|
|
677
|
+
writeEnvFile(serializeLines(next));
|
|
678
|
+
}
|
|
679
|
+
function removeEnvEntry(name) {
|
|
680
|
+
if (!isWritableEnvName(name)) {
|
|
681
|
+
throw new Error(`custom-env: refused bootstrap or invalid name "${name}"`);
|
|
682
|
+
}
|
|
683
|
+
if (!existsSync6(envFilePath())) return false;
|
|
684
|
+
const lines = parseLines(readRaw());
|
|
685
|
+
const next = lines.filter((line) => !(line.kind === "assignment" && line.name === name));
|
|
686
|
+
if (next.length === lines.length) return false;
|
|
687
|
+
writeEnvFile(serializeLines(next));
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/os-user-env/macos.ts
|
|
692
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, existsSync as existsSync7 } from "node:fs";
|
|
693
|
+
import { homedir } from "node:os";
|
|
694
|
+
import { join as join7 } from "node:path";
|
|
695
|
+
import { spawnSync } from "node:child_process";
|
|
696
|
+
var AGENT_LABEL = "ai.dsh.custom-env";
|
|
697
|
+
var AGENT_DIR = () => join7(homedir(), "Library", "LaunchAgents");
|
|
698
|
+
var AGENT_PATH = () => join7(AGENT_DIR(), `${AGENT_LABEL}.plist`);
|
|
699
|
+
var cached;
|
|
700
|
+
function escapeXml(value) {
|
|
701
|
+
return value.replace(/&/gu, "&").replace(/</gu, "<").replace(/>/gu, ">").replace(/"/gu, """);
|
|
702
|
+
}
|
|
703
|
+
function buildPlist(entries) {
|
|
704
|
+
const pairs = Object.entries(entries).map(([key, value]) => ` <key>${escapeXml(key)}</key>
|
|
705
|
+
<string>${escapeXml(value)}</string>`).join("\n");
|
|
706
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
707
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
708
|
+
<plist version="1.0">
|
|
709
|
+
<dict>
|
|
710
|
+
<key>Label</key>
|
|
711
|
+
<string>${AGENT_LABEL}</string>
|
|
712
|
+
<key>ProgramArguments</key>
|
|
713
|
+
<array>
|
|
714
|
+
<string>/bin/true</string>
|
|
715
|
+
</array>
|
|
716
|
+
<key>RunAtLoad</key>
|
|
717
|
+
<true/>
|
|
718
|
+
<key>EnvironmentVariables</key>
|
|
719
|
+
<dict>
|
|
720
|
+
${pairs}
|
|
721
|
+
</dict>
|
|
722
|
+
</dict>
|
|
723
|
+
</plist>
|
|
724
|
+
`;
|
|
725
|
+
}
|
|
726
|
+
function launchctl(args) {
|
|
727
|
+
const result = spawnSync("launchctl", args, { encoding: "utf8" });
|
|
728
|
+
if (result.error) throw result.error;
|
|
729
|
+
if (result.status !== 0) {
|
|
730
|
+
const detail = (result.stderr || result.stdout || "").trim();
|
|
731
|
+
throw new Error(`custom-env: launchctl ${args.join(" ")} failed${detail === "" ? "" : `: ${detail}`}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function ensureCache() {
|
|
735
|
+
if (cached !== void 0) return cached;
|
|
736
|
+
cached = {};
|
|
737
|
+
return cached;
|
|
738
|
+
}
|
|
739
|
+
function writeAgent(entries) {
|
|
740
|
+
const path = AGENT_PATH();
|
|
741
|
+
if (Object.keys(entries).length === 0) {
|
|
742
|
+
if (existsSync7(path)) {
|
|
743
|
+
try {
|
|
744
|
+
launchctl(["unload", path]);
|
|
745
|
+
} catch (_error) {
|
|
746
|
+
}
|
|
747
|
+
unlinkSync2(path);
|
|
748
|
+
}
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
mkdirSync3(AGENT_DIR(), { recursive: true });
|
|
752
|
+
writeFileSync3(path, buildPlist(entries), "utf8");
|
|
753
|
+
try {
|
|
754
|
+
launchctl(["unload", path]);
|
|
755
|
+
} catch (_error) {
|
|
756
|
+
}
|
|
757
|
+
launchctl(["load", path]);
|
|
758
|
+
}
|
|
759
|
+
function persistMacosUserEnv(entries, platform = process.platform) {
|
|
760
|
+
if (platform !== "darwin") return;
|
|
761
|
+
const pairs = Object.entries(entries);
|
|
762
|
+
if (pairs.length === 0) return;
|
|
763
|
+
const map = ensureCache();
|
|
764
|
+
for (const [name, value] of pairs) {
|
|
765
|
+
map[name] = value;
|
|
766
|
+
launchctl(["setenv", name, value]);
|
|
767
|
+
}
|
|
768
|
+
writeAgent(map);
|
|
769
|
+
}
|
|
770
|
+
function removeMacosUserEnv(name, platform = process.platform) {
|
|
771
|
+
if (platform !== "darwin") return;
|
|
772
|
+
const map = ensureCache();
|
|
773
|
+
delete map[name];
|
|
774
|
+
try {
|
|
775
|
+
launchctl(["unsetenv", name]);
|
|
776
|
+
} catch (_error) {
|
|
777
|
+
}
|
|
778
|
+
writeAgent(map);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/os-user-env/windows.ts
|
|
782
|
+
import { createRequire } from "node:module";
|
|
783
|
+
var HKEY_CURRENT_USER = 2147483649;
|
|
784
|
+
var KEY_SET_VALUE = 2;
|
|
785
|
+
var REG_SZ = 1;
|
|
786
|
+
var ERROR_SUCCESS = 0;
|
|
787
|
+
var ERROR_FILE_NOT_FOUND = 2;
|
|
788
|
+
var HWND_BROADCAST = 65535;
|
|
789
|
+
var WM_SETTINGCHANGE = 26;
|
|
790
|
+
var SMTO_ABORTIFHUNG = 2;
|
|
791
|
+
var bindings;
|
|
792
|
+
function loadBindings() {
|
|
793
|
+
if (bindings !== void 0) return bindings;
|
|
794
|
+
const require2 = createRequire(import.meta.url);
|
|
795
|
+
const koffi = require2("koffi");
|
|
796
|
+
const advapi32 = koffi.load("advapi32.dll");
|
|
797
|
+
const user32 = koffi.load("user32.dll");
|
|
798
|
+
bindings = {
|
|
799
|
+
openKey: advapi32.func(
|
|
800
|
+
"long __stdcall RegOpenKeyExW(uintptr_t hKey, const char16_t *subKey, uint32_t options, uint32_t samDesired, _Out_ void **result)"
|
|
801
|
+
),
|
|
802
|
+
setValue: advapi32.func(
|
|
803
|
+
"long __stdcall RegSetValueExW(void *hKey, const char16_t *valueName, uint32_t reserved, uint32_t type, const uint8_t *data, uint32_t cbData)"
|
|
804
|
+
),
|
|
805
|
+
deleteValue: advapi32.func(
|
|
806
|
+
"long __stdcall RegDeleteValueW(void *hKey, const char16_t *valueName)"
|
|
807
|
+
),
|
|
808
|
+
closeKey: advapi32.func("long __stdcall RegCloseKey(void *hKey)"),
|
|
809
|
+
sendMessageTimeout: user32.func(
|
|
810
|
+
"intptr_t __stdcall SendMessageTimeoutW(uintptr_t hWnd, uint32_t msg, uintptr_t wParam, const char16_t *lParam, uint32_t flags, uint32_t timeout, _Out_ uintptr_t *result)"
|
|
811
|
+
)
|
|
812
|
+
};
|
|
813
|
+
return bindings;
|
|
814
|
+
}
|
|
815
|
+
function encodeRegSz(value) {
|
|
816
|
+
return Buffer.from(`${value}\0`, "utf16le");
|
|
817
|
+
}
|
|
818
|
+
function broadcastEnvironmentChange(api) {
|
|
819
|
+
const broadcastResult = [0];
|
|
820
|
+
api.sendMessageTimeout(
|
|
821
|
+
HWND_BROADCAST,
|
|
822
|
+
WM_SETTINGCHANGE,
|
|
823
|
+
0,
|
|
824
|
+
"Environment",
|
|
825
|
+
SMTO_ABORTIFHUNG,
|
|
826
|
+
1e3,
|
|
827
|
+
broadcastResult
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
function persistWindowsUserEnv(entries, platform = process.platform) {
|
|
831
|
+
if (platform !== "win32") return;
|
|
832
|
+
const pairs = Object.entries(entries);
|
|
833
|
+
if (pairs.length === 0) return;
|
|
834
|
+
const api = loadBindings();
|
|
835
|
+
const keySlot = [null];
|
|
836
|
+
const openStatus = api.openKey(HKEY_CURRENT_USER, "Environment", 0, KEY_SET_VALUE, keySlot);
|
|
837
|
+
if (openStatus !== ERROR_SUCCESS) {
|
|
838
|
+
throw new Error(`custom-env: RegOpenKeyExW failed (Win32 ${openStatus})`);
|
|
839
|
+
}
|
|
840
|
+
const hKey = keySlot[0];
|
|
841
|
+
try {
|
|
842
|
+
for (const [name, value] of pairs) {
|
|
843
|
+
const data = encodeRegSz(value);
|
|
844
|
+
const status = api.setValue(hKey, name, 0, REG_SZ, data, data.length);
|
|
845
|
+
if (status !== ERROR_SUCCESS) {
|
|
846
|
+
throw new Error(`custom-env: RegSetValueExW(${name}) failed (Win32 ${status})`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
} finally {
|
|
850
|
+
api.closeKey(hKey);
|
|
851
|
+
}
|
|
852
|
+
broadcastEnvironmentChange(api);
|
|
853
|
+
}
|
|
854
|
+
function removeWindowsUserEnv(name, platform = process.platform) {
|
|
855
|
+
if (platform !== "win32") return;
|
|
856
|
+
const api = loadBindings();
|
|
857
|
+
const keySlot = [null];
|
|
858
|
+
const openStatus = api.openKey(HKEY_CURRENT_USER, "Environment", 0, KEY_SET_VALUE, keySlot);
|
|
859
|
+
if (openStatus !== ERROR_SUCCESS) {
|
|
860
|
+
throw new Error(`custom-env: RegOpenKeyExW failed (Win32 ${openStatus})`);
|
|
861
|
+
}
|
|
862
|
+
const hKey = keySlot[0];
|
|
863
|
+
try {
|
|
864
|
+
const status = api.deleteValue(hKey, name);
|
|
865
|
+
if (status !== ERROR_SUCCESS && status !== ERROR_FILE_NOT_FOUND) {
|
|
866
|
+
throw new Error(`custom-env: RegDeleteValueW(${name}) failed (Win32 ${status})`);
|
|
867
|
+
}
|
|
868
|
+
} finally {
|
|
869
|
+
api.closeKey(hKey);
|
|
870
|
+
}
|
|
871
|
+
broadcastEnvironmentChange(api);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/os-user-env/index.ts
|
|
875
|
+
function persistUserEnv(entries) {
|
|
876
|
+
persistWindowsUserEnv(entries);
|
|
877
|
+
persistMacosUserEnv(entries);
|
|
878
|
+
}
|
|
879
|
+
function removeUserEnv(name) {
|
|
880
|
+
removeWindowsUserEnv(name);
|
|
881
|
+
removeMacosUserEnv(name);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// src/types.ts
|
|
885
|
+
function classifyEntry(id, name, value, baselineById) {
|
|
886
|
+
const base = baselineById.get(id);
|
|
887
|
+
if (base === void 0) return { kind: "added", previous: null };
|
|
888
|
+
if (base.name !== name || base.value !== value) return { kind: "modified", previous: base.value };
|
|
889
|
+
return { kind: "unchanged", previous: null };
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/index.ts
|
|
893
|
+
function asMessage(error) {
|
|
894
|
+
return error instanceof Error ? error.message : String(error);
|
|
895
|
+
}
|
|
896
|
+
function applyLive(entries) {
|
|
897
|
+
for (const [key, value] of Object.entries(entries)) process.env[key] = value;
|
|
898
|
+
try {
|
|
899
|
+
persistUserEnv(entries);
|
|
900
|
+
} catch (error) {
|
|
901
|
+
process.stderr.write(`custom-env: failed to persist OS user environment: ${asMessage(error)}
|
|
902
|
+
`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
function clearLive(name) {
|
|
906
|
+
delete process.env[name];
|
|
907
|
+
try {
|
|
908
|
+
removeUserEnv(name);
|
|
909
|
+
} catch (error) {
|
|
910
|
+
process.stderr.write(`custom-env: failed to remove OS user environment ${name}: ${asMessage(error)}
|
|
911
|
+
`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
var _cliInstall_dec, _cliStatus_dec, _restore_dec, _deleteEntry_dec, _upsert_dec, _list_dec, _a, _init;
|
|
915
|
+
var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [Remote("list")], _upsert_dec = [Remote("upsert")], _deleteEntry_dec = [Remote("deleteEntry")], _restore_dec = [Remote("restore")], _cliStatus_dec = [Remote("cliStatus")], _cliInstall_dec = [Remote("cliInstall")], _a) {
|
|
916
|
+
constructor(ctx) {
|
|
917
|
+
super(ctx, "customEnv");
|
|
918
|
+
__runInitializers(_init, 5, this);
|
|
919
|
+
__publicField(this, "baselineById", /* @__PURE__ */ new Map());
|
|
920
|
+
__publicField(this, "idByName", /* @__PURE__ */ new Map());
|
|
921
|
+
__publicField(this, "nameById", /* @__PURE__ */ new Map());
|
|
922
|
+
__publicField(this, "idSeq", 0);
|
|
923
|
+
const loaded = loadEnvMap();
|
|
924
|
+
for (const [name, value] of Object.entries(loaded)) {
|
|
925
|
+
const id = this.allocId();
|
|
926
|
+
this.bind(id, name);
|
|
927
|
+
this.baselineById.set(id, { name, value });
|
|
928
|
+
}
|
|
929
|
+
applyLive(loaded);
|
|
930
|
+
}
|
|
931
|
+
allocId() {
|
|
932
|
+
return `env-${++this.idSeq}`;
|
|
933
|
+
}
|
|
934
|
+
bind(id, name) {
|
|
935
|
+
const previous = this.nameById.get(id);
|
|
936
|
+
if (previous !== void 0) this.idByName.delete(previous);
|
|
937
|
+
this.nameById.set(id, name);
|
|
938
|
+
this.idByName.set(name, id);
|
|
939
|
+
}
|
|
940
|
+
unbind(id) {
|
|
941
|
+
const name = this.nameById.get(id);
|
|
942
|
+
if (name !== void 0) this.idByName.delete(name);
|
|
943
|
+
this.nameById.delete(id);
|
|
944
|
+
}
|
|
945
|
+
requireName(id, forError) {
|
|
946
|
+
const name = this.nameById.get(id);
|
|
947
|
+
if (name === void 0) {
|
|
948
|
+
throw new RemoteError("custom-env/invalid-name", `unknown row id "${id}"`, { name: forError });
|
|
949
|
+
}
|
|
950
|
+
return name;
|
|
951
|
+
}
|
|
952
|
+
/** Persist `name=value` replacing `from` in place; sync process.env / OS. */
|
|
953
|
+
commit(from, name, value) {
|
|
954
|
+
if (!isWritableEnvName(name) || from !== name && !isWritableEnvName(from)) {
|
|
955
|
+
throw new RemoteError("custom-env/invalid-name", `refused name "${name}"`, { name });
|
|
956
|
+
}
|
|
957
|
+
if (value === "") {
|
|
958
|
+
throw new RemoteError("custom-env/invalid-value", `empty value for "${name}"`, { name });
|
|
959
|
+
}
|
|
960
|
+
try {
|
|
961
|
+
writeEnvEntry(from, name, value);
|
|
962
|
+
} catch (error) {
|
|
963
|
+
throw new RemoteError("custom-env/invalid-name", asMessage(error), { name }, { cause: error });
|
|
964
|
+
}
|
|
965
|
+
if (from !== name) clearLive(from);
|
|
966
|
+
applyLive({ [name]: value });
|
|
967
|
+
}
|
|
968
|
+
async list() {
|
|
969
|
+
const { path, exists, entries: raw } = listEnvEntries();
|
|
970
|
+
const present = new Set(raw.map((entry) => entry.name));
|
|
971
|
+
for (const [name, id] of [...this.idByName]) {
|
|
972
|
+
if (!present.has(name)) this.unbind(id);
|
|
973
|
+
}
|
|
974
|
+
const entries = raw.map(({ name, value }) => {
|
|
975
|
+
let id = this.idByName.get(name);
|
|
976
|
+
if (id === void 0) {
|
|
977
|
+
id = this.allocId();
|
|
978
|
+
this.bind(id, name);
|
|
979
|
+
}
|
|
980
|
+
const classified = classifyEntry(id, name, value, this.baselineById);
|
|
981
|
+
return { id, name, value, kind: classified.kind, previous: classified.previous };
|
|
982
|
+
});
|
|
983
|
+
return { path, exists, entries };
|
|
984
|
+
}
|
|
985
|
+
async upsert(id, name, value) {
|
|
986
|
+
if (id === "") {
|
|
987
|
+
this.commit(name, name, value);
|
|
988
|
+
if (!this.idByName.has(name)) this.bind(this.allocId(), name);
|
|
989
|
+
return this.list();
|
|
990
|
+
}
|
|
991
|
+
const from = this.requireName(id, name);
|
|
992
|
+
this.commit(from, name, value);
|
|
993
|
+
this.bind(id, name);
|
|
994
|
+
return this.list();
|
|
995
|
+
}
|
|
996
|
+
async deleteEntry(id) {
|
|
997
|
+
const name = this.requireName(id, id);
|
|
998
|
+
if (!isWritableEnvName(name)) {
|
|
999
|
+
throw new RemoteError("custom-env/invalid-name", `refused name "${name}"`, { name });
|
|
1000
|
+
}
|
|
1001
|
+
try {
|
|
1002
|
+
removeEnvEntry(name);
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
throw new RemoteError("custom-env/invalid-name", asMessage(error), { name }, { cause: error });
|
|
1005
|
+
}
|
|
1006
|
+
clearLive(name);
|
|
1007
|
+
this.unbind(id);
|
|
1008
|
+
return this.list();
|
|
1009
|
+
}
|
|
1010
|
+
async restore(id) {
|
|
1011
|
+
const name = this.nameById.get(id);
|
|
1012
|
+
if (name === void 0) {
|
|
1013
|
+
throw new RemoteError("custom-env/restore-failed", `unknown row id "${id}"`, { name: id });
|
|
1014
|
+
}
|
|
1015
|
+
const base = this.baselineById.get(id);
|
|
1016
|
+
if (base === void 0) return this.deleteEntry(id);
|
|
1017
|
+
if (name === base.name && loadEnvMap()[base.name] === base.value) return this.list();
|
|
1018
|
+
try {
|
|
1019
|
+
this.commit(name, base.name, base.value);
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
if (error instanceof RemoteError) {
|
|
1022
|
+
throw new RemoteError("custom-env/restore-failed", error.message, { name }, { cause: error });
|
|
1023
|
+
}
|
|
1024
|
+
throw new RemoteError("custom-env/restore-failed", asMessage(error), { name }, { cause: error });
|
|
1025
|
+
}
|
|
1026
|
+
this.bind(id, base.name);
|
|
1027
|
+
return this.list();
|
|
1028
|
+
}
|
|
1029
|
+
async cliStatus() {
|
|
1030
|
+
return cliStatus();
|
|
1031
|
+
}
|
|
1032
|
+
async cliInstall() {
|
|
1033
|
+
try {
|
|
1034
|
+
return await cliInstall();
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
const code = typeof error.code === "string" ? error.code : "EIO";
|
|
1037
|
+
throw new RemoteError("custom-env/cli-install-failed", asMessage(error), { code }, { cause: error });
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
_init = __decoratorStart(_a);
|
|
1042
|
+
__decorateElement(_init, 1, "list", _list_dec, CustomEnvController);
|
|
1043
|
+
__decorateElement(_init, 1, "upsert", _upsert_dec, CustomEnvController);
|
|
1044
|
+
__decorateElement(_init, 1, "deleteEntry", _deleteEntry_dec, CustomEnvController);
|
|
1045
|
+
__decorateElement(_init, 1, "restore", _restore_dec, CustomEnvController);
|
|
1046
|
+
__decorateElement(_init, 1, "cliStatus", _cliStatus_dec, CustomEnvController);
|
|
1047
|
+
__decorateElement(_init, 1, "cliInstall", _cliInstall_dec, CustomEnvController);
|
|
1048
|
+
__decoratorMetadata(_init, CustomEnvController);
|
|
1049
|
+
export {
|
|
1050
|
+
classifyEntry,
|
|
1051
|
+
CustomEnvController as default,
|
|
1052
|
+
envFilePath
|
|
1053
|
+
};
|