@dotenc/cli 0.4.6 → 0.5.1
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/dist/cli.js +539 -46
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ var package_default;
|
|
|
7
7
|
var init_package = __esm(() => {
|
|
8
8
|
package_default = {
|
|
9
9
|
name: "@dotenc/cli",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.5.1",
|
|
11
11
|
description: "🔐 Git-native encrypted environments powered by your SSH keys",
|
|
12
12
|
author: "Ivan Filho <i@ivanfilho.com>",
|
|
13
13
|
license: "MIT",
|
|
@@ -21,6 +21,7 @@ var init_package = __esm(() => {
|
|
|
21
21
|
scripts: {
|
|
22
22
|
dev: "bun src/cli.ts",
|
|
23
23
|
start: "node dist/cli.js",
|
|
24
|
+
typecheck: "tsc --noEmit -p tsconfig.json",
|
|
24
25
|
build: "bun build src/cli.ts --outdir dist --target node --packages external && { echo '#!/usr/bin/env node'; cat dist/cli.js; } > dist/cli.tmp && mv dist/cli.tmp dist/cli.js",
|
|
25
26
|
"build:binary": "bun run build:binary:darwin-arm64 && bun run build:binary:darwin-x64 && bun run build:binary:linux-x64 && bun run build:binary:linux-arm64 && bun run build:binary:windows-x64",
|
|
26
27
|
"build:binary:darwin-arm64": "bun build src/cli.ts --compile --target=bun-darwin-arm64 --outfile dist/dotenc-darwin-arm64",
|
|
@@ -1010,9 +1011,10 @@ import fs7 from "node:fs/promises";
|
|
|
1010
1011
|
import os2 from "node:os";
|
|
1011
1012
|
import path6 from "node:path";
|
|
1012
1013
|
import { z as z2 } from "zod";
|
|
1013
|
-
var homeConfigSchema, getConfigPath = () => path6.join(os2.homedir(), ".dotenc", "config.json"), setHomeConfig = async (config) => {
|
|
1014
|
+
var updateConfigSchema, homeConfigSchema, getConfigPath = () => path6.join(os2.homedir(), ".dotenc", "config.json"), setHomeConfig = async (config) => {
|
|
1014
1015
|
const parsedConfig = homeConfigSchema.parse(config);
|
|
1015
1016
|
const configPath = getConfigPath();
|
|
1017
|
+
await fs7.mkdir(path6.dirname(configPath), { recursive: true });
|
|
1016
1018
|
await fs7.writeFile(configPath, JSON.stringify(parsedConfig, null, 2), "utf-8");
|
|
1017
1019
|
}, getHomeConfig = async () => {
|
|
1018
1020
|
const configPath = getConfigPath();
|
|
@@ -1023,8 +1025,14 @@ var homeConfigSchema, getConfigPath = () => path6.join(os2.homedir(), ".dotenc",
|
|
|
1023
1025
|
return {};
|
|
1024
1026
|
};
|
|
1025
1027
|
var init_homeConfig = __esm(() => {
|
|
1028
|
+
updateConfigSchema = z2.object({
|
|
1029
|
+
lastCheckedAt: z2.string().nullish(),
|
|
1030
|
+
latestVersion: z2.string().nullish(),
|
|
1031
|
+
notifiedVersion: z2.string().nullish()
|
|
1032
|
+
});
|
|
1026
1033
|
homeConfigSchema = z2.object({
|
|
1027
|
-
editor: z2.string().nullish()
|
|
1034
|
+
editor: z2.string().nullish(),
|
|
1035
|
+
update: updateConfigSchema.nullish()
|
|
1028
1036
|
});
|
|
1029
1037
|
});
|
|
1030
1038
|
|
|
@@ -1052,8 +1060,8 @@ var getCurrentKeyName = async (deps = { getPrivateKeys, getPublicKeys }) => {
|
|
|
1052
1060
|
const { keys: privateKeys } = await deps.getPrivateKeys();
|
|
1053
1061
|
const publicKeys = await deps.getPublicKeys();
|
|
1054
1062
|
const privateFingerprints = new Set(privateKeys.map((k) => k.fingerprint));
|
|
1055
|
-
const
|
|
1056
|
-
return
|
|
1063
|
+
const matches = publicKeys.filter((pub) => privateFingerprints.has(pub.fingerprint));
|
|
1064
|
+
return matches.map((m) => m.name);
|
|
1057
1065
|
};
|
|
1058
1066
|
var init_getCurrentKeyName = __esm(() => {
|
|
1059
1067
|
init_getPrivateKeys();
|
|
@@ -1150,12 +1158,29 @@ var init_run = __esm(() => {
|
|
|
1150
1158
|
|
|
1151
1159
|
// src/commands/dev.ts
|
|
1152
1160
|
import chalk8 from "chalk";
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1161
|
+
import inquirer3 from "inquirer";
|
|
1162
|
+
var defaultSelect = async (message, choices) => {
|
|
1163
|
+
const { selected } = await inquirer3.prompt([
|
|
1164
|
+
{
|
|
1165
|
+
type: "list",
|
|
1166
|
+
name: "selected",
|
|
1167
|
+
message,
|
|
1168
|
+
choices
|
|
1169
|
+
}
|
|
1170
|
+
]);
|
|
1171
|
+
return selected;
|
|
1172
|
+
}, defaultDevCommandDeps, devCommand = async (command, args, deps = defaultDevCommandDeps) => {
|
|
1173
|
+
const keyNames = await deps.getCurrentKeyName();
|
|
1174
|
+
if (keyNames.length === 0) {
|
|
1156
1175
|
deps.logError(`${chalk8.red("Error:")} could not resolve your identity. Run ${chalk8.gray("dotenc init")} first.`);
|
|
1157
1176
|
deps.exit(1);
|
|
1158
1177
|
}
|
|
1178
|
+
let keyName;
|
|
1179
|
+
if (keyNames.length === 1) {
|
|
1180
|
+
keyName = keyNames[0];
|
|
1181
|
+
} else {
|
|
1182
|
+
keyName = await deps.select("Multiple identities found. Which one do you want to use?", keyNames.map((name) => ({ name, value: name })));
|
|
1183
|
+
}
|
|
1159
1184
|
await deps.runCommand(command, args, { env: `development,${keyName}` });
|
|
1160
1185
|
};
|
|
1161
1186
|
var init_dev = __esm(() => {
|
|
@@ -1165,7 +1190,8 @@ var init_dev = __esm(() => {
|
|
|
1165
1190
|
getCurrentKeyName,
|
|
1166
1191
|
runCommand,
|
|
1167
1192
|
logError: console.error,
|
|
1168
|
-
exit: process.exit
|
|
1193
|
+
exit: process.exit,
|
|
1194
|
+
select: defaultSelect
|
|
1169
1195
|
};
|
|
1170
1196
|
});
|
|
1171
1197
|
|
|
@@ -1207,9 +1233,9 @@ var init_projectConfig = __esm(() => {
|
|
|
1207
1233
|
});
|
|
1208
1234
|
|
|
1209
1235
|
// src/prompts/createEnvironment.ts
|
|
1210
|
-
import
|
|
1236
|
+
import inquirer4 from "inquirer";
|
|
1211
1237
|
var createEnvironmentPrompt = async (message, defaultValue) => {
|
|
1212
|
-
const result = await
|
|
1238
|
+
const result = await inquirer4.prompt([
|
|
1213
1239
|
{
|
|
1214
1240
|
type: "input",
|
|
1215
1241
|
name: "environment",
|
|
@@ -1704,9 +1730,9 @@ var setupGitDiff = () => {
|
|
|
1704
1730
|
var init_setupGitDiff = () => {};
|
|
1705
1731
|
|
|
1706
1732
|
// src/prompts/inputName.ts
|
|
1707
|
-
import
|
|
1733
|
+
import inquirer5 from "inquirer";
|
|
1708
1734
|
var inputNamePrompt = async (message, defaultValue) => {
|
|
1709
|
-
const result = await
|
|
1735
|
+
const result = await inquirer5.prompt([
|
|
1710
1736
|
{
|
|
1711
1737
|
type: "input",
|
|
1712
1738
|
name: "name",
|
|
@@ -1791,9 +1817,9 @@ function validatePublicKey(key) {
|
|
|
1791
1817
|
}
|
|
1792
1818
|
|
|
1793
1819
|
// src/prompts/inputKey.ts
|
|
1794
|
-
import
|
|
1820
|
+
import inquirer6 from "inquirer";
|
|
1795
1821
|
var inputKeyPrompt = async (message, defaultValue) => {
|
|
1796
|
-
const result = await
|
|
1822
|
+
const result = await inquirer6.prompt([
|
|
1797
1823
|
{
|
|
1798
1824
|
type: "password",
|
|
1799
1825
|
name: "key",
|
|
@@ -1813,7 +1839,7 @@ import fs12 from "node:fs/promises";
|
|
|
1813
1839
|
import os4 from "node:os";
|
|
1814
1840
|
import path12 from "node:path";
|
|
1815
1841
|
import chalk12 from "chalk";
|
|
1816
|
-
import
|
|
1842
|
+
import inquirer7 from "inquirer";
|
|
1817
1843
|
var keyAddCommand = async (nameArg, options) => {
|
|
1818
1844
|
const { projectId } = await getProjectConfig();
|
|
1819
1845
|
if (!projectId) {
|
|
@@ -1911,7 +1937,7 @@ ${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
|
|
|
1911
1937
|
name: `${key.name} (${key.algorithm})`,
|
|
1912
1938
|
value: key.name
|
|
1913
1939
|
}));
|
|
1914
|
-
const modePrompt = await
|
|
1940
|
+
const modePrompt = await inquirer7.prompt({
|
|
1915
1941
|
type: "list",
|
|
1916
1942
|
name: "mode",
|
|
1917
1943
|
message: "Would you like to add one of your SSH keys or paste a public key?",
|
|
@@ -1934,7 +1960,7 @@ ${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
|
|
|
1934
1960
|
process.exit(1);
|
|
1935
1961
|
}
|
|
1936
1962
|
} else {
|
|
1937
|
-
const keyPrompt = await
|
|
1963
|
+
const keyPrompt = await inquirer7.prompt({
|
|
1938
1964
|
type: "list",
|
|
1939
1965
|
name: "key",
|
|
1940
1966
|
message: "Which SSH key do you want to add?",
|
|
@@ -1994,7 +2020,7 @@ import fs13 from "node:fs/promises";
|
|
|
1994
2020
|
import os5 from "node:os";
|
|
1995
2021
|
import path13 from "node:path";
|
|
1996
2022
|
import chalk13 from "chalk";
|
|
1997
|
-
import
|
|
2023
|
+
import inquirer8 from "inquirer";
|
|
1998
2024
|
var initCommand = async (options) => {
|
|
1999
2025
|
const { keys: privateKeys, passphraseProtectedKeys } = await getPrivateKeys();
|
|
2000
2026
|
if (!privateKeys.length) {
|
|
@@ -2025,7 +2051,7 @@ var initCommand = async (options) => {
|
|
|
2025
2051
|
if (privateKeys.length === 1) {
|
|
2026
2052
|
keyToAdd = privateKeys[0].name;
|
|
2027
2053
|
} else {
|
|
2028
|
-
const result = await
|
|
2054
|
+
const result = await inquirer8.prompt([
|
|
2029
2055
|
{
|
|
2030
2056
|
type: "list",
|
|
2031
2057
|
name: "key",
|
|
@@ -2072,6 +2098,12 @@ Some useful tips:`);
|
|
|
2072
2098
|
console.log(`- To edit your personal environment: ${editCmd}`);
|
|
2073
2099
|
const devCmd = chalk13.gray("dotenc dev <command>");
|
|
2074
2100
|
console.log(`- To run with your encrypted env: ${devCmd}`);
|
|
2101
|
+
if (existsSync8(".claude") || existsSync8("CLAUDE.md")) {
|
|
2102
|
+
console.log(`- Install the agent skill: ${chalk13.gray("dotenc tools install-agent-skill")}`);
|
|
2103
|
+
}
|
|
2104
|
+
if (existsSync8(".vscode") || existsSync8(".cursor") || existsSync8(".windsurf")) {
|
|
2105
|
+
console.log(`- Add the editor extension: ${chalk13.gray("dotenc tools install-vscode-extension")}`);
|
|
2106
|
+
}
|
|
2075
2107
|
};
|
|
2076
2108
|
var init_init = __esm(() => {
|
|
2077
2109
|
init_createProject();
|
|
@@ -2099,9 +2131,9 @@ var init_list3 = __esm(() => {
|
|
|
2099
2131
|
});
|
|
2100
2132
|
|
|
2101
2133
|
// src/prompts/confirm.ts
|
|
2102
|
-
import
|
|
2134
|
+
import inquirer9 from "inquirer";
|
|
2103
2135
|
var confirmPrompt = async (message) => {
|
|
2104
|
-
const result = await
|
|
2136
|
+
const result = await inquirer9.prompt([
|
|
2105
2137
|
{
|
|
2106
2138
|
type: "confirm",
|
|
2107
2139
|
name: "confirm",
|
|
@@ -2193,13 +2225,378 @@ var init_textconv = __esm(() => {
|
|
|
2193
2225
|
init_getEnvironmentByPath();
|
|
2194
2226
|
});
|
|
2195
2227
|
|
|
2228
|
+
// src/commands/tools/install-agent-skill.ts
|
|
2229
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2230
|
+
import chalk15 from "chalk";
|
|
2231
|
+
import inquirer10 from "inquirer";
|
|
2232
|
+
var SKILL_SOURCE = "ivanfilhoz/dotenc", SKILL_NAME = "dotenc", runNpx = (args) => new Promise((resolve, reject) => {
|
|
2233
|
+
const child = spawn2("npx", args, {
|
|
2234
|
+
stdio: "inherit",
|
|
2235
|
+
shell: process.platform === "win32"
|
|
2236
|
+
});
|
|
2237
|
+
child.on("error", reject);
|
|
2238
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
2239
|
+
}), defaultDeps, _runInstallAgentSkillCommand = async (options, depsOverrides = {}) => {
|
|
2240
|
+
const deps = {
|
|
2241
|
+
...defaultDeps,
|
|
2242
|
+
...depsOverrides
|
|
2243
|
+
};
|
|
2244
|
+
const { scope } = await deps.prompt([
|
|
2245
|
+
{
|
|
2246
|
+
type: "list",
|
|
2247
|
+
name: "scope",
|
|
2248
|
+
message: "Install locally or globally?",
|
|
2249
|
+
choices: [
|
|
2250
|
+
{ name: "Locally (this project)", value: "local" },
|
|
2251
|
+
{ name: "Globally (all projects)", value: "global" }
|
|
2252
|
+
]
|
|
2253
|
+
}
|
|
2254
|
+
]);
|
|
2255
|
+
const args = ["skills", "add", SKILL_SOURCE, "--skill", SKILL_NAME];
|
|
2256
|
+
if (scope === "global") {
|
|
2257
|
+
args.push("-g");
|
|
2258
|
+
}
|
|
2259
|
+
if (options.force) {
|
|
2260
|
+
args.push("-y");
|
|
2261
|
+
}
|
|
2262
|
+
const npxCommand = `npx ${args.join(" ")}`;
|
|
2263
|
+
let exitCode = 0;
|
|
2264
|
+
try {
|
|
2265
|
+
exitCode = await deps.runNpx(args);
|
|
2266
|
+
} catch (error) {
|
|
2267
|
+
deps.logError(`${chalk15.red("Error:")} failed to run ${chalk15.gray(npxCommand)}.`);
|
|
2268
|
+
deps.logError(`${chalk15.red("Details:")} ${error instanceof Error ? error.message : String(error)}`);
|
|
2269
|
+
deps.exit(1);
|
|
2270
|
+
}
|
|
2271
|
+
if (exitCode !== 0) {
|
|
2272
|
+
deps.logError(`${chalk15.red("Error:")} skill installation command exited with code ${exitCode}.`);
|
|
2273
|
+
deps.exit(exitCode);
|
|
2274
|
+
}
|
|
2275
|
+
deps.log(`${chalk15.green("✓")} Agent skill installation completed via ${chalk15.gray(npxCommand)}.`);
|
|
2276
|
+
deps.log(`Run ${chalk15.gray("/dotenc")} in your agent to use it.`);
|
|
2277
|
+
}, installAgentSkillCommand = async (options) => {
|
|
2278
|
+
await _runInstallAgentSkillCommand(options);
|
|
2279
|
+
};
|
|
2280
|
+
var init_install_agent_skill = __esm(() => {
|
|
2281
|
+
defaultDeps = {
|
|
2282
|
+
prompt: inquirer10.prompt,
|
|
2283
|
+
runNpx,
|
|
2284
|
+
log: console.log,
|
|
2285
|
+
logError: console.error,
|
|
2286
|
+
exit: process.exit
|
|
2287
|
+
};
|
|
2288
|
+
});
|
|
2289
|
+
|
|
2290
|
+
// src/commands/tools/install-vscode-extension.ts
|
|
2291
|
+
import { exec, execFile } from "node:child_process";
|
|
2292
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
2293
|
+
import fs16 from "node:fs/promises";
|
|
2294
|
+
import path16 from "node:path";
|
|
2295
|
+
import { promisify } from "node:util";
|
|
2296
|
+
import chalk16 from "chalk";
|
|
2297
|
+
import inquirer11 from "inquirer";
|
|
2298
|
+
async function addToExtensionsJson() {
|
|
2299
|
+
const extensionsJsonPath = path16.join(process.cwd(), ".vscode", "extensions.json");
|
|
2300
|
+
let json = {};
|
|
2301
|
+
if (existsSync10(extensionsJsonPath)) {
|
|
2302
|
+
const content = await fs16.readFile(extensionsJsonPath, "utf-8");
|
|
2303
|
+
try {
|
|
2304
|
+
json = JSON.parse(content);
|
|
2305
|
+
} catch {
|
|
2306
|
+
json = {};
|
|
2307
|
+
}
|
|
2308
|
+
} else {
|
|
2309
|
+
await fs16.mkdir(path16.join(process.cwd(), ".vscode"), { recursive: true });
|
|
2310
|
+
}
|
|
2311
|
+
if (!Array.isArray(json.recommendations)) {
|
|
2312
|
+
json.recommendations = [];
|
|
2313
|
+
}
|
|
2314
|
+
if (!json.recommendations.includes(EXTENSION_ID)) {
|
|
2315
|
+
json.recommendations.push(EXTENSION_ID);
|
|
2316
|
+
await fs16.writeFile(extensionsJsonPath, JSON.stringify(json, null, 2), "utf-8");
|
|
2317
|
+
console.log(`${chalk16.green("✓")} Added dotenc to ${chalk16.gray(".vscode/extensions.json")}`);
|
|
2318
|
+
} else {
|
|
2319
|
+
console.log(`${chalk16.green("✓")} dotenc already in ${chalk16.gray(".vscode/extensions.json")}`);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
async function which(bin) {
|
|
2323
|
+
try {
|
|
2324
|
+
await execFileAsync("which", [bin]);
|
|
2325
|
+
return true;
|
|
2326
|
+
} catch {
|
|
2327
|
+
return false;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
async function detectEditors() {
|
|
2331
|
+
const detected = [];
|
|
2332
|
+
if (existsSync10(path16.join(process.cwd(), ".cursor")))
|
|
2333
|
+
detected.push("cursor");
|
|
2334
|
+
if (existsSync10(path16.join(process.cwd(), ".windsurf")))
|
|
2335
|
+
detected.push("windsurf");
|
|
2336
|
+
if (existsSync10(path16.join(process.cwd(), ".vscode")))
|
|
2337
|
+
detected.push("vscode");
|
|
2338
|
+
const checks = [
|
|
2339
|
+
{ key: "cursor", bins: ["cursor"] },
|
|
2340
|
+
{ key: "windsurf", bins: ["windsurf"] },
|
|
2341
|
+
{ key: "vscode", bins: ["code"] },
|
|
2342
|
+
{ key: "vscodium", bins: ["codium", "vscodium"] }
|
|
2343
|
+
];
|
|
2344
|
+
if (process.platform === "darwin") {
|
|
2345
|
+
const macApps = {
|
|
2346
|
+
cursor: "/Applications/Cursor.app",
|
|
2347
|
+
windsurf: "/Applications/Windsurf.app",
|
|
2348
|
+
vscode: "/Applications/Visual Studio Code.app",
|
|
2349
|
+
vscodium: "/Applications/VSCodium.app"
|
|
2350
|
+
};
|
|
2351
|
+
for (const [key, appPath] of Object.entries(macApps)) {
|
|
2352
|
+
if (!detected.includes(key) && existsSync10(appPath)) {
|
|
2353
|
+
detected.push(key);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
for (const { key, bins } of checks) {
|
|
2358
|
+
if (detected.includes(key))
|
|
2359
|
+
continue;
|
|
2360
|
+
for (const bin of bins) {
|
|
2361
|
+
if (await which(bin)) {
|
|
2362
|
+
detected.push(key);
|
|
2363
|
+
break;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
return detected;
|
|
2368
|
+
}
|
|
2369
|
+
async function openUrl(url) {
|
|
2370
|
+
if (process.platform === "darwin") {
|
|
2371
|
+
await execFileAsync("open", [url]);
|
|
2372
|
+
} else if (process.platform === "win32") {
|
|
2373
|
+
await execAsync(`start ${url}`);
|
|
2374
|
+
} else {
|
|
2375
|
+
await execFileAsync("xdg-open", [url]);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
async function _runInstallVscodeExtension(getEditors = detectEditors, _openUrl = openUrl) {
|
|
2379
|
+
const editors = await getEditors();
|
|
2380
|
+
await addToExtensionsJson();
|
|
2381
|
+
if (editors.length === 0) {
|
|
2382
|
+
console.log(`
|
|
2383
|
+
Install the extension in VS Code: ${chalk16.cyan(EDITOR_PROTOCOL_URLS.vscode)}`);
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
if (editors.length === 1) {
|
|
2387
|
+
const editor = editors[0];
|
|
2388
|
+
const url = EDITOR_PROTOCOL_URLS[editor];
|
|
2389
|
+
const name = EDITOR_NAMES[editor];
|
|
2390
|
+
const { open } = await inquirer11.prompt([
|
|
2391
|
+
{
|
|
2392
|
+
type: "confirm",
|
|
2393
|
+
name: "open",
|
|
2394
|
+
message: `Open extension page in ${name} now?`,
|
|
2395
|
+
default: true
|
|
2396
|
+
}
|
|
2397
|
+
]);
|
|
2398
|
+
if (open) {
|
|
2399
|
+
try {
|
|
2400
|
+
await _openUrl(url);
|
|
2401
|
+
} catch {
|
|
2402
|
+
console.log(`Open manually: ${chalk16.cyan(url)}`);
|
|
2403
|
+
}
|
|
2404
|
+
} else {
|
|
2405
|
+
console.log(`Install manually: ${chalk16.cyan(url)}`);
|
|
2406
|
+
}
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
console.log(`
|
|
2410
|
+
Install the extension in your editor:`);
|
|
2411
|
+
for (const editor of editors) {
|
|
2412
|
+
const name = EDITOR_NAMES[editor] ?? editor;
|
|
2413
|
+
const url = EDITOR_PROTOCOL_URLS[editor];
|
|
2414
|
+
console.log(` ${name}: ${chalk16.cyan(url)}`);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
var execFileAsync, execAsync, EXTENSION_ID = "dotenc.dotenc", EDITOR_PROTOCOL_URLS, EDITOR_NAMES, installVscodeExtensionCommand = async () => {
|
|
2418
|
+
await _runInstallVscodeExtension();
|
|
2419
|
+
};
|
|
2420
|
+
var init_install_vscode_extension = __esm(() => {
|
|
2421
|
+
execFileAsync = promisify(execFile);
|
|
2422
|
+
execAsync = promisify(exec);
|
|
2423
|
+
EDITOR_PROTOCOL_URLS = {
|
|
2424
|
+
vscode: `vscode:extension/${EXTENSION_ID}`,
|
|
2425
|
+
cursor: `cursor:extension/${EXTENSION_ID}`,
|
|
2426
|
+
windsurf: `windsurf:extension/${EXTENSION_ID}`,
|
|
2427
|
+
vscodium: `vscodium:extension/${EXTENSION_ID}`
|
|
2428
|
+
};
|
|
2429
|
+
EDITOR_NAMES = {
|
|
2430
|
+
vscode: "VS Code",
|
|
2431
|
+
cursor: "Cursor",
|
|
2432
|
+
windsurf: "Windsurf",
|
|
2433
|
+
vscodium: "VSCodium"
|
|
2434
|
+
};
|
|
2435
|
+
});
|
|
2436
|
+
|
|
2437
|
+
// src/helpers/update.ts
|
|
2438
|
+
import { realpathSync } from "node:fs";
|
|
2439
|
+
var NPM_LATEST_URL = "https://registry.npmjs.org/@dotenc%2fcli/latest", GITHUB_RELEASES_URL = "https://github.com/ivanfilhoz/dotenc/releases", normalizePath = (value) => value.replace(/\\/g, "/").toLowerCase(), safeRealPath = (resolveRealPath, filePath) => {
|
|
2440
|
+
try {
|
|
2441
|
+
return resolveRealPath(filePath);
|
|
2442
|
+
} catch {
|
|
2443
|
+
return filePath;
|
|
2444
|
+
}
|
|
2445
|
+
}, detectInstallMethod = (options = {}) => {
|
|
2446
|
+
const execPath = options.execPath ?? process.execPath;
|
|
2447
|
+
const argv = options.argv ?? process.argv;
|
|
2448
|
+
const platform = options.platform ?? process.platform;
|
|
2449
|
+
const resolveRealPath = options.resolveRealPath ?? realpathSync;
|
|
2450
|
+
const scriptPath = argv[1] ?? "";
|
|
2451
|
+
const resolvedPaths = [execPath, scriptPath].filter(Boolean).map((value) => normalizePath(safeRealPath(resolveRealPath, value)));
|
|
2452
|
+
const allPaths = resolvedPaths.join(" ");
|
|
2453
|
+
const normalizedScriptPath = normalizePath(scriptPath);
|
|
2454
|
+
if (allPaths.includes("/cellar/dotenc/") || allPaths.includes("/homebrew/cellar/dotenc/")) {
|
|
2455
|
+
return "homebrew";
|
|
2456
|
+
}
|
|
2457
|
+
if (allPaths.includes("/scoop/apps/dotenc/") || allPaths.includes("/scoop/shims/")) {
|
|
2458
|
+
return "scoop";
|
|
2459
|
+
}
|
|
2460
|
+
if (allPaths.includes("/node_modules/@dotenc/cli/")) {
|
|
2461
|
+
return "npm";
|
|
2462
|
+
}
|
|
2463
|
+
if (normalizedScriptPath.endsWith("/src/cli.ts") || normalizedScriptPath.endsWith("/dist/cli.js") && !allPaths.includes("/node_modules/@dotenc/cli/")) {
|
|
2464
|
+
return "unknown";
|
|
2465
|
+
}
|
|
2466
|
+
if (platform === "win32" && allPaths.includes("/scoop/")) {
|
|
2467
|
+
return "scoop";
|
|
2468
|
+
}
|
|
2469
|
+
return "binary";
|
|
2470
|
+
}, parseVersionParts = (version) => {
|
|
2471
|
+
const cleaned = version.trim().replace(/^v/i, "").split("-")[0];
|
|
2472
|
+
if (!cleaned)
|
|
2473
|
+
return null;
|
|
2474
|
+
const parts = cleaned.split(".").map((part) => Number.parseInt(part, 10));
|
|
2475
|
+
if (parts.some((part) => Number.isNaN(part))) {
|
|
2476
|
+
return null;
|
|
2477
|
+
}
|
|
2478
|
+
return parts;
|
|
2479
|
+
}, compareVersions = (left, right) => {
|
|
2480
|
+
const leftParts = parseVersionParts(left);
|
|
2481
|
+
const rightParts = parseVersionParts(right);
|
|
2482
|
+
if (!leftParts || !rightParts)
|
|
2483
|
+
return 0;
|
|
2484
|
+
const maxLen = Math.max(leftParts.length, rightParts.length);
|
|
2485
|
+
for (let i = 0;i < maxLen; i += 1) {
|
|
2486
|
+
const leftPart = leftParts[i] ?? 0;
|
|
2487
|
+
const rightPart = rightParts[i] ?? 0;
|
|
2488
|
+
if (leftPart > rightPart)
|
|
2489
|
+
return 1;
|
|
2490
|
+
if (leftPart < rightPart)
|
|
2491
|
+
return -1;
|
|
2492
|
+
}
|
|
2493
|
+
return 0;
|
|
2494
|
+
}, isVersionNewer = (candidate, current) => compareVersions(candidate, current) > 0, fetchLatestVersion = async (options = {}) => {
|
|
2495
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
2496
|
+
const timeoutMs = options.timeoutMs ?? 1500;
|
|
2497
|
+
const controller = new AbortController;
|
|
2498
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2499
|
+
try {
|
|
2500
|
+
const response = await fetchImpl(NPM_LATEST_URL, {
|
|
2501
|
+
headers: {
|
|
2502
|
+
accept: "application/json"
|
|
2503
|
+
},
|
|
2504
|
+
signal: controller.signal
|
|
2505
|
+
});
|
|
2506
|
+
if (!response.ok) {
|
|
2507
|
+
return null;
|
|
2508
|
+
}
|
|
2509
|
+
const payload = await response.json();
|
|
2510
|
+
return typeof payload.version === "string" ? payload.version : null;
|
|
2511
|
+
} catch {
|
|
2512
|
+
return null;
|
|
2513
|
+
} finally {
|
|
2514
|
+
clearTimeout(timer);
|
|
2515
|
+
}
|
|
2516
|
+
};
|
|
2517
|
+
var init_update = () => {};
|
|
2518
|
+
|
|
2519
|
+
// src/commands/update.ts
|
|
2520
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2521
|
+
import chalk17 from "chalk";
|
|
2522
|
+
var runPackageManagerCommand = (command, args) => new Promise((resolve, reject) => {
|
|
2523
|
+
const child = spawn3(command, args, {
|
|
2524
|
+
stdio: "inherit",
|
|
2525
|
+
shell: process.platform === "win32"
|
|
2526
|
+
});
|
|
2527
|
+
child.on("error", reject);
|
|
2528
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
2529
|
+
}), defaultDeps2, updateCommands, _runUpdateCommand = async (depsOverrides = {}) => {
|
|
2530
|
+
const deps = {
|
|
2531
|
+
...defaultDeps2,
|
|
2532
|
+
...depsOverrides
|
|
2533
|
+
};
|
|
2534
|
+
const method = deps.detectInstallMethod();
|
|
2535
|
+
if (method === "binary") {
|
|
2536
|
+
deps.log(`Standalone binary detected. Download the latest release at ${chalk17.cyan(GITHUB_RELEASES_URL)}.`);
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
if (method === "unknown") {
|
|
2540
|
+
deps.log("Could not determine installation method automatically.");
|
|
2541
|
+
deps.log(`Try one of these commands:`);
|
|
2542
|
+
deps.log(` ${chalk17.gray("brew upgrade dotenc")}`);
|
|
2543
|
+
deps.log(` ${chalk17.gray("scoop update dotenc")}`);
|
|
2544
|
+
deps.log(` ${chalk17.gray("npm install -g @dotenc/cli")}`);
|
|
2545
|
+
deps.log(`Or download from ${chalk17.cyan(GITHUB_RELEASES_URL)}.`);
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
const updater = updateCommands[method];
|
|
2549
|
+
deps.log(`Updating dotenc via ${updater.label}...`);
|
|
2550
|
+
let exitCode = 0;
|
|
2551
|
+
try {
|
|
2552
|
+
exitCode = await deps.runPackageManagerCommand(updater.command, updater.args);
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
deps.logError(`${chalk17.red("Error:")} failed to run ${chalk17.gray([updater.command, ...updater.args].join(" "))}.`);
|
|
2555
|
+
deps.logError(`${chalk17.red("Details:")} ${error instanceof Error ? error.message : String(error)}`);
|
|
2556
|
+
deps.exit(1);
|
|
2557
|
+
}
|
|
2558
|
+
if (exitCode !== 0) {
|
|
2559
|
+
deps.logError(`${chalk17.red("Error:")} update command exited with code ${exitCode}.`);
|
|
2560
|
+
deps.exit(exitCode);
|
|
2561
|
+
}
|
|
2562
|
+
}, updateCommand = async () => {
|
|
2563
|
+
await _runUpdateCommand();
|
|
2564
|
+
};
|
|
2565
|
+
var init_update2 = __esm(() => {
|
|
2566
|
+
init_update();
|
|
2567
|
+
defaultDeps2 = {
|
|
2568
|
+
detectInstallMethod,
|
|
2569
|
+
runPackageManagerCommand,
|
|
2570
|
+
log: console.log,
|
|
2571
|
+
logError: console.error,
|
|
2572
|
+
exit: process.exit
|
|
2573
|
+
};
|
|
2574
|
+
updateCommands = {
|
|
2575
|
+
homebrew: {
|
|
2576
|
+
command: "brew",
|
|
2577
|
+
args: ["upgrade", "dotenc"],
|
|
2578
|
+
label: "Homebrew"
|
|
2579
|
+
},
|
|
2580
|
+
scoop: {
|
|
2581
|
+
command: "scoop",
|
|
2582
|
+
args: ["update", "dotenc"],
|
|
2583
|
+
label: "Scoop"
|
|
2584
|
+
},
|
|
2585
|
+
npm: {
|
|
2586
|
+
command: "npm",
|
|
2587
|
+
args: ["install", "-g", "@dotenc/cli"],
|
|
2588
|
+
label: "npm"
|
|
2589
|
+
}
|
|
2590
|
+
};
|
|
2591
|
+
});
|
|
2592
|
+
|
|
2196
2593
|
// src/commands/whoami.ts
|
|
2197
2594
|
var whoamiCommand = async () => {
|
|
2198
2595
|
const { keys: privateKeys, passphraseProtectedKeys } = await getPrivateKeys();
|
|
2199
2596
|
const publicKeys = await getPublicKeys();
|
|
2200
2597
|
const privateFingerprints = new Set(privateKeys.map((k) => k.fingerprint));
|
|
2201
|
-
const
|
|
2202
|
-
if (
|
|
2598
|
+
const matchingPublicKeys = publicKeys.filter((pub) => privateFingerprints.has(pub.fingerprint));
|
|
2599
|
+
if (matchingPublicKeys.length === 0) {
|
|
2203
2600
|
if (privateKeys.length === 0 && passphraseProtectedKeys.length > 0) {
|
|
2204
2601
|
console.error(passphraseProtectedKeyError(passphraseProtectedKeys));
|
|
2205
2602
|
} else {
|
|
@@ -2207,28 +2604,34 @@ var whoamiCommand = async () => {
|
|
|
2207
2604
|
}
|
|
2208
2605
|
process.exit(1);
|
|
2209
2606
|
}
|
|
2210
|
-
const matchingPrivateKey = privateKeys.find((pk) => pk.fingerprint === matchingPublicKey.fingerprint);
|
|
2211
|
-
console.log(`Name: ${matchingPublicKey.name}`);
|
|
2212
|
-
console.log(`Active SSH key: ${matchingPrivateKey?.name ?? "unknown"}`);
|
|
2213
|
-
console.log(`Fingerprint: ${matchingPublicKey.fingerprint}`);
|
|
2214
2607
|
const environments = await getEnvironments();
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2608
|
+
for (let i = 0;i < matchingPublicKeys.length; i++) {
|
|
2609
|
+
const matchingPublicKey = matchingPublicKeys[i];
|
|
2610
|
+
if (i > 0) {
|
|
2611
|
+
console.log("");
|
|
2612
|
+
}
|
|
2613
|
+
const matchingPrivateKey = privateKeys.find((pk) => pk.fingerprint === matchingPublicKey.fingerprint);
|
|
2614
|
+
console.log(`Name: ${matchingPublicKey.name}`);
|
|
2615
|
+
console.log(`Active SSH key: ${matchingPrivateKey?.name ?? "unknown"}`);
|
|
2616
|
+
console.log(`Fingerprint: ${matchingPublicKey.fingerprint}`);
|
|
2617
|
+
const authorizedEnvironments = [];
|
|
2618
|
+
for (const envName of environments) {
|
|
2619
|
+
try {
|
|
2620
|
+
const environment = await getEnvironmentByName(envName);
|
|
2621
|
+
const hasAccess = environment.keys.some((key) => key.fingerprint === matchingPublicKey.fingerprint);
|
|
2622
|
+
if (hasAccess) {
|
|
2623
|
+
authorizedEnvironments.push(envName);
|
|
2624
|
+
}
|
|
2625
|
+
} catch {}
|
|
2626
|
+
}
|
|
2627
|
+
if (authorizedEnvironments.length > 0) {
|
|
2628
|
+
console.log("Authorized environments:");
|
|
2629
|
+
for (const env of authorizedEnvironments) {
|
|
2630
|
+
console.log(` - ${env}`);
|
|
2222
2631
|
}
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
if (authorizedEnvironments.length > 0) {
|
|
2226
|
-
console.log("Authorized environments:");
|
|
2227
|
-
for (const env of authorizedEnvironments) {
|
|
2228
|
-
console.log(` - ${env}`);
|
|
2632
|
+
} else {
|
|
2633
|
+
console.log("Authorized environments: none");
|
|
2229
2634
|
}
|
|
2230
|
-
} else {
|
|
2231
|
-
console.log("Authorized environments: none");
|
|
2232
2635
|
}
|
|
2233
2636
|
};
|
|
2234
2637
|
var init_whoami = __esm(() => {
|
|
@@ -2239,11 +2642,92 @@ var init_whoami = __esm(() => {
|
|
|
2239
2642
|
init_getPublicKeys();
|
|
2240
2643
|
});
|
|
2241
2644
|
|
|
2645
|
+
// src/helpers/updateNotifier.ts
|
|
2646
|
+
import chalk18 from "chalk";
|
|
2647
|
+
var CHECK_INTERVAL_MS, defaultDeps3, shouldSkipCheck = (args, env) => {
|
|
2648
|
+
if (env.DOTENC_SKIP_UPDATE_CHECK === "1") {
|
|
2649
|
+
return true;
|
|
2650
|
+
}
|
|
2651
|
+
const firstArg = args[0];
|
|
2652
|
+
if (!firstArg)
|
|
2653
|
+
return false;
|
|
2654
|
+
return ["update", "--help", "-h", "--version", "-V", "help"].includes(firstArg);
|
|
2655
|
+
}, parseTimestamp = (value) => {
|
|
2656
|
+
if (!value)
|
|
2657
|
+
return 0;
|
|
2658
|
+
const parsed = Date.parse(value);
|
|
2659
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
2660
|
+
}, persistUpdateState = async (config, updateState, deps) => {
|
|
2661
|
+
try {
|
|
2662
|
+
await deps.setHomeConfig({
|
|
2663
|
+
...config,
|
|
2664
|
+
update: updateState
|
|
2665
|
+
});
|
|
2666
|
+
} catch {}
|
|
2667
|
+
}, maybeNotifyAboutUpdate = async (depsOverrides = {}) => {
|
|
2668
|
+
const deps = {
|
|
2669
|
+
...defaultDeps3,
|
|
2670
|
+
...depsOverrides
|
|
2671
|
+
};
|
|
2672
|
+
if (shouldSkipCheck(deps.args, deps.env)) {
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
let config = {};
|
|
2676
|
+
try {
|
|
2677
|
+
config = await deps.getHomeConfig();
|
|
2678
|
+
} catch {
|
|
2679
|
+
config = {};
|
|
2680
|
+
}
|
|
2681
|
+
let updateState = config.update ?? {};
|
|
2682
|
+
let latestVersion = updateState.latestVersion ?? null;
|
|
2683
|
+
const now = deps.now();
|
|
2684
|
+
const lastCheckedAt = parseTimestamp(updateState.lastCheckedAt);
|
|
2685
|
+
const shouldRefresh = !latestVersion || now - lastCheckedAt >= CHECK_INTERVAL_MS;
|
|
2686
|
+
if (shouldRefresh) {
|
|
2687
|
+
const fetchedVersion = await deps.fetchLatestVersion();
|
|
2688
|
+
updateState = {
|
|
2689
|
+
...updateState,
|
|
2690
|
+
lastCheckedAt: new Date(now).toISOString(),
|
|
2691
|
+
latestVersion: fetchedVersion ?? latestVersion ?? undefined
|
|
2692
|
+
};
|
|
2693
|
+
latestVersion = updateState.latestVersion ?? null;
|
|
2694
|
+
await persistUpdateState(config, updateState, deps);
|
|
2695
|
+
}
|
|
2696
|
+
if (!latestVersion || !isVersionNewer(latestVersion, deps.currentVersion)) {
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
if (updateState.notifiedVersion === latestVersion) {
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
deps.log(`${chalk18.yellow("Update available:")} ${chalk18.gray(`dotenc ${deps.currentVersion}`)} -> ${chalk18.cyan(`dotenc ${latestVersion}`)}. Run ${chalk18.gray("dotenc update")}.`);
|
|
2703
|
+
updateState = {
|
|
2704
|
+
...updateState,
|
|
2705
|
+
notifiedVersion: latestVersion
|
|
2706
|
+
};
|
|
2707
|
+
await persistUpdateState(config, updateState, deps);
|
|
2708
|
+
};
|
|
2709
|
+
var init_updateNotifier = __esm(() => {
|
|
2710
|
+
init_package();
|
|
2711
|
+
init_homeConfig();
|
|
2712
|
+
init_update();
|
|
2713
|
+
CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
2714
|
+
defaultDeps3 = {
|
|
2715
|
+
getHomeConfig,
|
|
2716
|
+
setHomeConfig,
|
|
2717
|
+
fetchLatestVersion,
|
|
2718
|
+
currentVersion: package_default.version,
|
|
2719
|
+
now: () => Date.now(),
|
|
2720
|
+
log: console.log,
|
|
2721
|
+
args: process.argv.slice(2),
|
|
2722
|
+
env: process.env
|
|
2723
|
+
};
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2242
2726
|
// src/program.ts
|
|
2243
2727
|
var exports_program = {};
|
|
2244
2728
|
import { Command, Option } from "commander";
|
|
2245
|
-
var program, env, auth, key;
|
|
2246
|
-
var init_program = __esm(() => {
|
|
2729
|
+
var program, env, auth, key, tools;
|
|
2730
|
+
var init_program = __esm(async () => {
|
|
2247
2731
|
init_package();
|
|
2248
2732
|
init_grant();
|
|
2249
2733
|
init_list();
|
|
@@ -2262,7 +2746,11 @@ var init_program = __esm(() => {
|
|
|
2262
2746
|
init_remove();
|
|
2263
2747
|
init_run();
|
|
2264
2748
|
init_textconv();
|
|
2749
|
+
init_install_agent_skill();
|
|
2750
|
+
init_install_vscode_extension();
|
|
2751
|
+
init_update2();
|
|
2265
2752
|
init_whoami();
|
|
2753
|
+
init_updateNotifier();
|
|
2266
2754
|
program = new Command;
|
|
2267
2755
|
program.name("dotenc").description(package_default.description).version(package_default.version);
|
|
2268
2756
|
program.command("init").addOption(new Option("-n, --name <name>", "your username for the project")).description("initialize a dotenc project in the current directory").action(initCommand);
|
|
@@ -2283,11 +2771,16 @@ var init_program = __esm(() => {
|
|
|
2283
2771
|
key.command("add").argument("[name]", "the name of the public key in the project").addOption(new Option("--from-ssh <path>", "add a public key derived from an SSH key file")).addOption(new Option("-f, --from-file <file>", "add the key from a PEM file")).addOption(new Option("-s, --from-string <string>", "add a public key from a string")).description("add a public key to the project").action(keyAddCommand);
|
|
2284
2772
|
key.command("list").description("list all public keys in the project").action(keyListCommand);
|
|
2285
2773
|
key.command("remove").argument("[name]", "the name of the public key to remove").description("remove a public key from the project").action(keyRemoveCommand);
|
|
2774
|
+
tools = program.command("tools").description("install editor integrations");
|
|
2775
|
+
tools.command("install-agent-skill").addOption(new Option("--force", "run npx skills in non-interactive mode (-y)")).description("install the agent skill for this project").action(installAgentSkillCommand);
|
|
2776
|
+
tools.command("install-vscode-extension").description("add dotenc to VS Code / Cursor / Windsurf extension recommendations").action(installVscodeExtensionCommand);
|
|
2286
2777
|
program.command("textconv", { hidden: true }).argument("<filepath>", "path to the encrypted environment file").description("decrypt an environment file for git diff").action(textconvCommand);
|
|
2778
|
+
program.command("update").description("update dotenc based on your installation method").action(updateCommand);
|
|
2287
2779
|
program.command("whoami").description("show your identity in this project").action(whoamiCommand);
|
|
2288
2780
|
program.command("config").argument("<key>", "the key to get or set").argument("[value]", "the value to set the key to").addOption(new Option("-r, --remove", "remove a configuration key")).description("manage global configuration").action(configCommand);
|
|
2781
|
+
await maybeNotifyAboutUpdate();
|
|
2289
2782
|
program.parse();
|
|
2290
2783
|
});
|
|
2291
2784
|
|
|
2292
2785
|
// src/cli.ts
|
|
2293
|
-
|
|
2786
|
+
init_program();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotenc/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "🔐 Git-native encrypted environments powered by your SSH keys",
|
|
5
5
|
"author": "Ivan Filho <i@ivanfilho.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"dev": "bun src/cli.ts",
|
|
16
16
|
"start": "node dist/cli.js",
|
|
17
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
17
18
|
"build": "bun build src/cli.ts --outdir dist --target node --packages external && { echo '#!/usr/bin/env node'; cat dist/cli.js; } > dist/cli.tmp && mv dist/cli.tmp dist/cli.js",
|
|
18
19
|
"build:binary": "bun run build:binary:darwin-arm64 && bun run build:binary:darwin-x64 && bun run build:binary:linux-x64 && bun run build:binary:linux-arm64 && bun run build:binary:windows-x64",
|
|
19
20
|
"build:binary:darwin-arm64": "bun build src/cli.ts --compile --target=bun-darwin-arm64 --outfile dist/dotenc-darwin-arm64",
|