@dotenc/cli 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +317 -158
- package/package.json +1 -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.5.
|
|
10
|
+
version: "0.5.2",
|
|
11
11
|
description: "🔐 Git-native encrypted environments powered by your SSH keys",
|
|
12
12
|
author: "Ivan Filho <i@ivanfilho.com>",
|
|
13
13
|
license: "MIT",
|
|
@@ -1011,9 +1011,10 @@ import fs7 from "node:fs/promises";
|
|
|
1011
1011
|
import os2 from "node:os";
|
|
1012
1012
|
import path6 from "node:path";
|
|
1013
1013
|
import { z as z2 } from "zod";
|
|
1014
|
-
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) => {
|
|
1015
1015
|
const parsedConfig = homeConfigSchema.parse(config);
|
|
1016
1016
|
const configPath = getConfigPath();
|
|
1017
|
+
await fs7.mkdir(path6.dirname(configPath), { recursive: true });
|
|
1017
1018
|
await fs7.writeFile(configPath, JSON.stringify(parsedConfig, null, 2), "utf-8");
|
|
1018
1019
|
}, getHomeConfig = async () => {
|
|
1019
1020
|
const configPath = getConfigPath();
|
|
@@ -1024,8 +1025,14 @@ var homeConfigSchema, getConfigPath = () => path6.join(os2.homedir(), ".dotenc",
|
|
|
1024
1025
|
return {};
|
|
1025
1026
|
};
|
|
1026
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
|
+
});
|
|
1027
1033
|
homeConfigSchema = z2.object({
|
|
1028
|
-
editor: z2.string().nullish()
|
|
1034
|
+
editor: z2.string().nullish(),
|
|
1035
|
+
update: updateConfigSchema.nullish()
|
|
1029
1036
|
});
|
|
1030
1037
|
});
|
|
1031
1038
|
|
|
@@ -1354,8 +1361,9 @@ var defaultDecryptCommandDeps, ansiPattern, stripAnsi = (value) => value.replace
|
|
|
1354
1361
|
try {
|
|
1355
1362
|
const environment = await deps.getEnvironmentByName(environmentName);
|
|
1356
1363
|
const plaintext = await deps.decryptEnvironmentData(environment);
|
|
1364
|
+
const grantedUsers = Array.from(new Set(environment.keys.map((key) => key.name.trim()).filter((name) => name.length > 0)));
|
|
1357
1365
|
if (options.json) {
|
|
1358
|
-
writeJson({ ok: true, content: plaintext }, deps);
|
|
1366
|
+
writeJson({ ok: true, content: plaintext, grantedUsers }, deps);
|
|
1359
1367
|
} else {
|
|
1360
1368
|
deps.writeStdout(plaintext);
|
|
1361
1369
|
}
|
|
@@ -2092,7 +2100,7 @@ Some useful tips:`);
|
|
|
2092
2100
|
const devCmd = chalk13.gray("dotenc dev <command>");
|
|
2093
2101
|
console.log(`- To run with your encrypted env: ${devCmd}`);
|
|
2094
2102
|
if (existsSync8(".claude") || existsSync8("CLAUDE.md")) {
|
|
2095
|
-
console.log(`- Install the
|
|
2103
|
+
console.log(`- Install the agent skill: ${chalk13.gray("dotenc tools install-agent-skill")}`);
|
|
2096
2104
|
}
|
|
2097
2105
|
if (existsSync8(".vscode") || existsSync8(".cursor") || existsSync8(".windsurf")) {
|
|
2098
2106
|
console.log(`- Add the editor extension: ${chalk13.gray("dotenc tools install-vscode-extension")}`);
|
|
@@ -2218,133 +2226,23 @@ var init_textconv = __esm(() => {
|
|
|
2218
2226
|
init_getEnvironmentByPath();
|
|
2219
2227
|
});
|
|
2220
2228
|
|
|
2221
|
-
// src/commands/tools/install-
|
|
2222
|
-
import {
|
|
2223
|
-
import fs16 from "node:fs/promises";
|
|
2224
|
-
import os6 from "node:os";
|
|
2225
|
-
import path16 from "node:path";
|
|
2229
|
+
// src/commands/tools/install-agent-skill.ts
|
|
2230
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2226
2231
|
import chalk15 from "chalk";
|
|
2227
2232
|
import inquirer10 from "inquirer";
|
|
2228
|
-
var
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
**Available environments:**
|
|
2243
|
-
!\`dotenc env list 2>&1 || true\`
|
|
2244
|
-
|
|
2245
|
-
**Project public keys:**
|
|
2246
|
-
!\`dotenc key list 2>&1 || true\`
|
|
2247
|
-
|
|
2248
|
-
## What is dotenc?
|
|
2249
|
-
|
|
2250
|
-
dotenc is a Git-native encrypted environment management tool powered by SSH keys. It encrypts environment variables using AES-256-GCM and manages per-user access control using existing SSH key infrastructure. Private keys never leave \`~/.ssh/\`; only public keys are stored in the \`.dotenc/\` project folder. Encrypted files are safe to commit to Git.
|
|
2251
|
-
|
|
2252
|
-
## Common workflows
|
|
2253
|
-
|
|
2254
|
-
**First-time setup**
|
|
2255
|
-
\`\`\`bash
|
|
2256
|
-
dotenc init --name alice
|
|
2257
|
-
dotenc key add alice --from-ssh ~/.ssh/id_ed25519
|
|
2258
|
-
dotenc env create development alice
|
|
2259
|
-
dotenc env edit development # opens editor to add secrets
|
|
2260
|
-
\`\`\`
|
|
2261
|
-
|
|
2262
|
-
**Onboard a new teammate**
|
|
2263
|
-
\`\`\`bash
|
|
2264
|
-
dotenc key add bob --from-ssh /path/to/bob_key.pub
|
|
2265
|
-
dotenc auth grant development bob
|
|
2266
|
-
dotenc auth grant production bob # only if they need it
|
|
2267
|
-
\`\`\`
|
|
2268
|
-
|
|
2269
|
-
**Run the app locally with secrets**
|
|
2270
|
-
\`\`\`bash
|
|
2271
|
-
dotenc dev npm start # injects development + personal env
|
|
2272
|
-
dotenc run -e production node app.js
|
|
2273
|
-
\`\`\`
|
|
2274
|
-
|
|
2275
|
-
**Rotate keys before someone leaves**
|
|
2276
|
-
\`\`\`bash
|
|
2277
|
-
dotenc auth revoke production alice
|
|
2278
|
-
dotenc auth revoke development alice
|
|
2279
|
-
dotenc key remove alice
|
|
2280
|
-
dotenc env rotate production # re-encrypts with remaining keys
|
|
2281
|
-
\`\`\`
|
|
2282
|
-
|
|
2283
|
-
**Add a CI/CD key**
|
|
2284
|
-
\`\`\`bash
|
|
2285
|
-
dotenc key add ci --from-file ci_key.pub
|
|
2286
|
-
dotenc auth grant production ci
|
|
2287
|
-
\`\`\`
|
|
2288
|
-
|
|
2289
|
-
## CLI command reference
|
|
2290
|
-
|
|
2291
|
-
### Initialization & identity
|
|
2292
|
-
|
|
2293
|
-
| Command | Description |
|
|
2294
|
-
|---------|-------------|
|
|
2295
|
-
| \`dotenc init [--name <name>]\` | Initialize a dotenc project in the current directory |
|
|
2296
|
-
| \`dotenc whoami\` | Show your identity, active SSH key, fingerprint, and environment access |
|
|
2297
|
-
| \`dotenc config <key> [value] [--remove]\` | Get, set, or remove a global configuration key |
|
|
2298
|
-
|
|
2299
|
-
### Environment management
|
|
2300
|
-
|
|
2301
|
-
| Command | Description |
|
|
2302
|
-
|---------|-------------|
|
|
2303
|
-
| \`dotenc env list\` | List all encrypted environments |
|
|
2304
|
-
| \`dotenc env create [environment] [publicKey]\` | Create a new encrypted environment |
|
|
2305
|
-
| \`dotenc env edit [environment]\` | Edit an environment in your configured editor |
|
|
2306
|
-
| \`dotenc env rotate [environment]\` | Rotate the data key for an environment |
|
|
2307
|
-
| \`dotenc env decrypt <environment> [--json]\` | Decrypt an environment to stdout |
|
|
2308
|
-
| \`dotenc env encrypt <environment> [--stdin] [--json]\` | Encrypt plaintext into an environment file |
|
|
2309
|
-
|
|
2310
|
-
### Access control
|
|
2311
|
-
|
|
2312
|
-
| Command | Description |
|
|
2313
|
-
|---------|-------------|
|
|
2314
|
-
| \`dotenc auth list [environment]\` | List public keys with access to an environment |
|
|
2315
|
-
| \`dotenc auth grant [environment] [publicKey]\` | Grant a key access to an environment |
|
|
2316
|
-
| \`dotenc auth revoke [environment] [publicKey]\` | Revoke a key's access from an environment |
|
|
2317
|
-
|
|
2318
|
-
### Key management
|
|
2319
|
-
|
|
2320
|
-
| Command | Description |
|
|
2321
|
-
|---------|-------------|
|
|
2322
|
-
| \`dotenc key list\` | List all public keys in the project |
|
|
2323
|
-
| \`dotenc key add [name] [--from-ssh <path>] [-f <file>] [-s <string>]\` | Add a public key to the project |
|
|
2324
|
-
| \`dotenc key remove [name]\` | Remove a public key and revoke from all environments |
|
|
2325
|
-
|
|
2326
|
-
### Running commands with decrypted variables
|
|
2327
|
-
|
|
2328
|
-
| Command | Description |
|
|
2329
|
-
|---------|-------------|
|
|
2330
|
-
| \`dotenc run -e <env1>[,env2] <command> [args...]\` | Run a command with decrypted environment variables |
|
|
2331
|
-
| \`dotenc dev <command> [args...]\` | Shortcut: run with \`development\` + your personal environment |
|
|
2332
|
-
|
|
2333
|
-
### Git integration
|
|
2334
|
-
|
|
2335
|
-
| Command | Description |
|
|
2336
|
-
|---------|-------------|
|
|
2337
|
-
| \`dotenc textconv <filepath>\` | Decrypt an environment file for \`git diff\` |
|
|
2338
|
-
|
|
2339
|
-
## Guidelines
|
|
2340
|
-
|
|
2341
|
-
- **Never expose decrypted secrets in chat output.** Do not run \`dotenc env decrypt\` and display the result. If the user needs to inspect values, suggest \`dotenc env edit\` which opens their editor.
|
|
2342
|
-
- **Prefer \`dotenc dev\` / \`dotenc run\`** over manually decrypting and exporting variables.
|
|
2343
|
-
- **Warn before destructive operations.** Confirm with the user before running \`dotenc key remove\`, \`dotenc auth revoke\`, or \`dotenc env rotate\`, as these can lock users out of environments.
|
|
2344
|
-
- **Always pass arguments explicitly.** All commands support interactive mode when arguments are omitted, but Claude Code is non-interactive — always provide the full command with arguments.
|
|
2345
|
-
- **Encrypted files are Git-safe.** Files like \`.env.production.enc\` are meant to be committed; never instruct the user to add them to \`.gitignore\`.
|
|
2346
|
-
`, installClaudeCodeSkillCommand = async (options) => {
|
|
2347
|
-
const { scope } = await inquirer10.prompt([
|
|
2233
|
+
var SKILL_SOURCE = "ivanfilhoz/dotenc", SKILL_NAME = "dotenc", runNpx = (args) => new Promise((resolve, reject) => {
|
|
2234
|
+
const child = spawn2("npx", args, {
|
|
2235
|
+
stdio: "inherit",
|
|
2236
|
+
shell: process.platform === "win32"
|
|
2237
|
+
});
|
|
2238
|
+
child.on("error", reject);
|
|
2239
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
2240
|
+
}), defaultDeps, _runInstallAgentSkillCommand = async (options, depsOverrides = {}) => {
|
|
2241
|
+
const deps = {
|
|
2242
|
+
...defaultDeps,
|
|
2243
|
+
...depsOverrides
|
|
2244
|
+
};
|
|
2245
|
+
const { scope } = await deps.prompt([
|
|
2348
2246
|
{
|
|
2349
2247
|
type: "list",
|
|
2350
2248
|
name: "scope",
|
|
@@ -2355,48 +2253,68 @@ dotenc auth grant production ci
|
|
|
2355
2253
|
]
|
|
2356
2254
|
}
|
|
2357
2255
|
]);
|
|
2358
|
-
const
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2256
|
+
const args = ["skills", "add", SKILL_SOURCE, "--skill", SKILL_NAME];
|
|
2257
|
+
if (scope === "global") {
|
|
2258
|
+
args.push("-g");
|
|
2259
|
+
}
|
|
2260
|
+
if (options.force) {
|
|
2261
|
+
args.push("-y");
|
|
2262
|
+
}
|
|
2263
|
+
const npxCommand = `npx ${args.join(" ")}`;
|
|
2264
|
+
let exitCode = 0;
|
|
2265
|
+
try {
|
|
2266
|
+
exitCode = await deps.runNpx(args);
|
|
2267
|
+
} catch (error) {
|
|
2268
|
+
deps.logError(`${chalk15.red("Error:")} failed to run ${chalk15.gray(npxCommand)}.`);
|
|
2269
|
+
deps.logError(`${chalk15.red("Details:")} ${error instanceof Error ? error.message : String(error)}`);
|
|
2270
|
+
deps.exit(1);
|
|
2271
|
+
}
|
|
2272
|
+
if (exitCode !== 0) {
|
|
2273
|
+
deps.logError(`${chalk15.red("Error:")} skill installation command exited with code ${exitCode}.`);
|
|
2274
|
+
deps.exit(exitCode);
|
|
2365
2275
|
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2276
|
+
deps.log(`${chalk15.green("✓")} Agent skill installation completed via ${chalk15.gray(npxCommand)}.`);
|
|
2277
|
+
deps.log(`Run ${chalk15.gray("/dotenc")} in your agent to use it.`);
|
|
2278
|
+
}, installAgentSkillCommand = async (options) => {
|
|
2279
|
+
await _runInstallAgentSkillCommand(options);
|
|
2370
2280
|
};
|
|
2371
|
-
var
|
|
2281
|
+
var init_install_agent_skill = __esm(() => {
|
|
2282
|
+
defaultDeps = {
|
|
2283
|
+
prompt: inquirer10.prompt,
|
|
2284
|
+
runNpx,
|
|
2285
|
+
log: console.log,
|
|
2286
|
+
logError: console.error,
|
|
2287
|
+
exit: process.exit
|
|
2288
|
+
};
|
|
2289
|
+
});
|
|
2372
2290
|
|
|
2373
2291
|
// src/commands/tools/install-vscode-extension.ts
|
|
2374
2292
|
import { exec, execFile } from "node:child_process";
|
|
2375
|
-
import { existsSync as
|
|
2376
|
-
import
|
|
2377
|
-
import
|
|
2293
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
2294
|
+
import fs16 from "node:fs/promises";
|
|
2295
|
+
import path16 from "node:path";
|
|
2378
2296
|
import { promisify } from "node:util";
|
|
2379
2297
|
import chalk16 from "chalk";
|
|
2380
2298
|
import inquirer11 from "inquirer";
|
|
2381
2299
|
async function addToExtensionsJson() {
|
|
2382
|
-
const extensionsJsonPath =
|
|
2300
|
+
const extensionsJsonPath = path16.join(process.cwd(), ".vscode", "extensions.json");
|
|
2383
2301
|
let json = {};
|
|
2384
|
-
if (
|
|
2385
|
-
const content = await
|
|
2302
|
+
if (existsSync10(extensionsJsonPath)) {
|
|
2303
|
+
const content = await fs16.readFile(extensionsJsonPath, "utf-8");
|
|
2386
2304
|
try {
|
|
2387
2305
|
json = JSON.parse(content);
|
|
2388
2306
|
} catch {
|
|
2389
2307
|
json = {};
|
|
2390
2308
|
}
|
|
2391
2309
|
} else {
|
|
2392
|
-
await
|
|
2310
|
+
await fs16.mkdir(path16.join(process.cwd(), ".vscode"), { recursive: true });
|
|
2393
2311
|
}
|
|
2394
2312
|
if (!Array.isArray(json.recommendations)) {
|
|
2395
2313
|
json.recommendations = [];
|
|
2396
2314
|
}
|
|
2397
2315
|
if (!json.recommendations.includes(EXTENSION_ID)) {
|
|
2398
2316
|
json.recommendations.push(EXTENSION_ID);
|
|
2399
|
-
await
|
|
2317
|
+
await fs16.writeFile(extensionsJsonPath, JSON.stringify(json, null, 2), "utf-8");
|
|
2400
2318
|
console.log(`${chalk16.green("✓")} Added dotenc to ${chalk16.gray(".vscode/extensions.json")}`);
|
|
2401
2319
|
} else {
|
|
2402
2320
|
console.log(`${chalk16.green("✓")} dotenc already in ${chalk16.gray(".vscode/extensions.json")}`);
|
|
@@ -2412,11 +2330,11 @@ async function which(bin) {
|
|
|
2412
2330
|
}
|
|
2413
2331
|
async function detectEditors() {
|
|
2414
2332
|
const detected = [];
|
|
2415
|
-
if (
|
|
2333
|
+
if (existsSync10(path16.join(process.cwd(), ".cursor")))
|
|
2416
2334
|
detected.push("cursor");
|
|
2417
|
-
if (
|
|
2335
|
+
if (existsSync10(path16.join(process.cwd(), ".windsurf")))
|
|
2418
2336
|
detected.push("windsurf");
|
|
2419
|
-
if (
|
|
2337
|
+
if (existsSync10(path16.join(process.cwd(), ".vscode")))
|
|
2420
2338
|
detected.push("vscode");
|
|
2421
2339
|
const checks = [
|
|
2422
2340
|
{ key: "cursor", bins: ["cursor"] },
|
|
@@ -2432,7 +2350,7 @@ async function detectEditors() {
|
|
|
2432
2350
|
vscodium: "/Applications/VSCodium.app"
|
|
2433
2351
|
};
|
|
2434
2352
|
for (const [key, appPath] of Object.entries(macApps)) {
|
|
2435
|
-
if (!detected.includes(key) &&
|
|
2353
|
+
if (!detected.includes(key) && existsSync10(appPath)) {
|
|
2436
2354
|
detected.push(key);
|
|
2437
2355
|
}
|
|
2438
2356
|
}
|
|
@@ -2517,6 +2435,162 @@ var init_install_vscode_extension = __esm(() => {
|
|
|
2517
2435
|
};
|
|
2518
2436
|
});
|
|
2519
2437
|
|
|
2438
|
+
// src/helpers/update.ts
|
|
2439
|
+
import { realpathSync } from "node:fs";
|
|
2440
|
+
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) => {
|
|
2441
|
+
try {
|
|
2442
|
+
return resolveRealPath(filePath);
|
|
2443
|
+
} catch {
|
|
2444
|
+
return filePath;
|
|
2445
|
+
}
|
|
2446
|
+
}, detectInstallMethod = (options = {}) => {
|
|
2447
|
+
const execPath = options.execPath ?? process.execPath;
|
|
2448
|
+
const argv = options.argv ?? process.argv;
|
|
2449
|
+
const platform = options.platform ?? process.platform;
|
|
2450
|
+
const resolveRealPath = options.resolveRealPath ?? realpathSync;
|
|
2451
|
+
const scriptPath = argv[1] ?? "";
|
|
2452
|
+
const resolvedPaths = [execPath, scriptPath].filter(Boolean).map((value) => normalizePath(safeRealPath(resolveRealPath, value)));
|
|
2453
|
+
const allPaths = resolvedPaths.join(" ");
|
|
2454
|
+
const normalizedScriptPath = normalizePath(scriptPath);
|
|
2455
|
+
if (allPaths.includes("/cellar/dotenc/") || allPaths.includes("/homebrew/cellar/dotenc/")) {
|
|
2456
|
+
return "homebrew";
|
|
2457
|
+
}
|
|
2458
|
+
if (allPaths.includes("/scoop/apps/dotenc/") || allPaths.includes("/scoop/shims/")) {
|
|
2459
|
+
return "scoop";
|
|
2460
|
+
}
|
|
2461
|
+
if (allPaths.includes("/node_modules/@dotenc/cli/")) {
|
|
2462
|
+
return "npm";
|
|
2463
|
+
}
|
|
2464
|
+
if (normalizedScriptPath.endsWith("/src/cli.ts") || normalizedScriptPath.endsWith("/dist/cli.js") && !allPaths.includes("/node_modules/@dotenc/cli/")) {
|
|
2465
|
+
return "unknown";
|
|
2466
|
+
}
|
|
2467
|
+
if (platform === "win32" && allPaths.includes("/scoop/")) {
|
|
2468
|
+
return "scoop";
|
|
2469
|
+
}
|
|
2470
|
+
return "binary";
|
|
2471
|
+
}, parseVersionParts = (version) => {
|
|
2472
|
+
const cleaned = version.trim().replace(/^v/i, "").split("-")[0];
|
|
2473
|
+
if (!cleaned)
|
|
2474
|
+
return null;
|
|
2475
|
+
const parts = cleaned.split(".").map((part) => Number.parseInt(part, 10));
|
|
2476
|
+
if (parts.some((part) => Number.isNaN(part))) {
|
|
2477
|
+
return null;
|
|
2478
|
+
}
|
|
2479
|
+
return parts;
|
|
2480
|
+
}, compareVersions = (left, right) => {
|
|
2481
|
+
const leftParts = parseVersionParts(left);
|
|
2482
|
+
const rightParts = parseVersionParts(right);
|
|
2483
|
+
if (!leftParts || !rightParts)
|
|
2484
|
+
return 0;
|
|
2485
|
+
const maxLen = Math.max(leftParts.length, rightParts.length);
|
|
2486
|
+
for (let i = 0;i < maxLen; i += 1) {
|
|
2487
|
+
const leftPart = leftParts[i] ?? 0;
|
|
2488
|
+
const rightPart = rightParts[i] ?? 0;
|
|
2489
|
+
if (leftPart > rightPart)
|
|
2490
|
+
return 1;
|
|
2491
|
+
if (leftPart < rightPart)
|
|
2492
|
+
return -1;
|
|
2493
|
+
}
|
|
2494
|
+
return 0;
|
|
2495
|
+
}, isVersionNewer = (candidate, current) => compareVersions(candidate, current) > 0, fetchLatestVersion = async (options = {}) => {
|
|
2496
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
2497
|
+
const timeoutMs = options.timeoutMs ?? 1500;
|
|
2498
|
+
const controller = new AbortController;
|
|
2499
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2500
|
+
try {
|
|
2501
|
+
const response = await fetchImpl(NPM_LATEST_URL, {
|
|
2502
|
+
headers: {
|
|
2503
|
+
accept: "application/json"
|
|
2504
|
+
},
|
|
2505
|
+
signal: controller.signal
|
|
2506
|
+
});
|
|
2507
|
+
if (!response.ok) {
|
|
2508
|
+
return null;
|
|
2509
|
+
}
|
|
2510
|
+
const payload = await response.json();
|
|
2511
|
+
return typeof payload.version === "string" ? payload.version : null;
|
|
2512
|
+
} catch {
|
|
2513
|
+
return null;
|
|
2514
|
+
} finally {
|
|
2515
|
+
clearTimeout(timer);
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
var init_update = () => {};
|
|
2519
|
+
|
|
2520
|
+
// src/commands/update.ts
|
|
2521
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2522
|
+
import chalk17 from "chalk";
|
|
2523
|
+
var runPackageManagerCommand = (command, args) => new Promise((resolve, reject) => {
|
|
2524
|
+
const child = spawn3(command, args, {
|
|
2525
|
+
stdio: "inherit",
|
|
2526
|
+
shell: process.platform === "win32"
|
|
2527
|
+
});
|
|
2528
|
+
child.on("error", reject);
|
|
2529
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
2530
|
+
}), defaultDeps2, updateCommands, _runUpdateCommand = async (depsOverrides = {}) => {
|
|
2531
|
+
const deps = {
|
|
2532
|
+
...defaultDeps2,
|
|
2533
|
+
...depsOverrides
|
|
2534
|
+
};
|
|
2535
|
+
const method = deps.detectInstallMethod();
|
|
2536
|
+
if (method === "binary") {
|
|
2537
|
+
deps.log(`Standalone binary detected. Download the latest release at ${chalk17.cyan(GITHUB_RELEASES_URL)}.`);
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
if (method === "unknown") {
|
|
2541
|
+
deps.log("Could not determine installation method automatically.");
|
|
2542
|
+
deps.log(`Try one of these commands:`);
|
|
2543
|
+
deps.log(` ${chalk17.gray("brew upgrade dotenc")}`);
|
|
2544
|
+
deps.log(` ${chalk17.gray("scoop update dotenc")}`);
|
|
2545
|
+
deps.log(` ${chalk17.gray("npm install -g @dotenc/cli")}`);
|
|
2546
|
+
deps.log(`Or download from ${chalk17.cyan(GITHUB_RELEASES_URL)}.`);
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
const updater = updateCommands[method];
|
|
2550
|
+
deps.log(`Updating dotenc via ${updater.label}...`);
|
|
2551
|
+
let exitCode = 0;
|
|
2552
|
+
try {
|
|
2553
|
+
exitCode = await deps.runPackageManagerCommand(updater.command, updater.args);
|
|
2554
|
+
} catch (error) {
|
|
2555
|
+
deps.logError(`${chalk17.red("Error:")} failed to run ${chalk17.gray([updater.command, ...updater.args].join(" "))}.`);
|
|
2556
|
+
deps.logError(`${chalk17.red("Details:")} ${error instanceof Error ? error.message : String(error)}`);
|
|
2557
|
+
deps.exit(1);
|
|
2558
|
+
}
|
|
2559
|
+
if (exitCode !== 0) {
|
|
2560
|
+
deps.logError(`${chalk17.red("Error:")} update command exited with code ${exitCode}.`);
|
|
2561
|
+
deps.exit(exitCode);
|
|
2562
|
+
}
|
|
2563
|
+
}, updateCommand = async () => {
|
|
2564
|
+
await _runUpdateCommand();
|
|
2565
|
+
};
|
|
2566
|
+
var init_update2 = __esm(() => {
|
|
2567
|
+
init_update();
|
|
2568
|
+
defaultDeps2 = {
|
|
2569
|
+
detectInstallMethod,
|
|
2570
|
+
runPackageManagerCommand,
|
|
2571
|
+
log: console.log,
|
|
2572
|
+
logError: console.error,
|
|
2573
|
+
exit: process.exit
|
|
2574
|
+
};
|
|
2575
|
+
updateCommands = {
|
|
2576
|
+
homebrew: {
|
|
2577
|
+
command: "brew",
|
|
2578
|
+
args: ["upgrade", "dotenc"],
|
|
2579
|
+
label: "Homebrew"
|
|
2580
|
+
},
|
|
2581
|
+
scoop: {
|
|
2582
|
+
command: "scoop",
|
|
2583
|
+
args: ["update", "dotenc"],
|
|
2584
|
+
label: "Scoop"
|
|
2585
|
+
},
|
|
2586
|
+
npm: {
|
|
2587
|
+
command: "npm",
|
|
2588
|
+
args: ["install", "-g", "@dotenc/cli"],
|
|
2589
|
+
label: "npm"
|
|
2590
|
+
}
|
|
2591
|
+
};
|
|
2592
|
+
});
|
|
2593
|
+
|
|
2520
2594
|
// src/commands/whoami.ts
|
|
2521
2595
|
var whoamiCommand = async () => {
|
|
2522
2596
|
const { keys: privateKeys, passphraseProtectedKeys } = await getPrivateKeys();
|
|
@@ -2569,11 +2643,92 @@ var init_whoami = __esm(() => {
|
|
|
2569
2643
|
init_getPublicKeys();
|
|
2570
2644
|
});
|
|
2571
2645
|
|
|
2646
|
+
// src/helpers/updateNotifier.ts
|
|
2647
|
+
import chalk18 from "chalk";
|
|
2648
|
+
var CHECK_INTERVAL_MS, defaultDeps3, shouldSkipCheck = (args, env) => {
|
|
2649
|
+
if (env.DOTENC_SKIP_UPDATE_CHECK === "1") {
|
|
2650
|
+
return true;
|
|
2651
|
+
}
|
|
2652
|
+
const firstArg = args[0];
|
|
2653
|
+
if (!firstArg)
|
|
2654
|
+
return false;
|
|
2655
|
+
return ["update", "--help", "-h", "--version", "-V", "help"].includes(firstArg);
|
|
2656
|
+
}, parseTimestamp = (value) => {
|
|
2657
|
+
if (!value)
|
|
2658
|
+
return 0;
|
|
2659
|
+
const parsed = Date.parse(value);
|
|
2660
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
2661
|
+
}, persistUpdateState = async (config, updateState, deps) => {
|
|
2662
|
+
try {
|
|
2663
|
+
await deps.setHomeConfig({
|
|
2664
|
+
...config,
|
|
2665
|
+
update: updateState
|
|
2666
|
+
});
|
|
2667
|
+
} catch {}
|
|
2668
|
+
}, maybeNotifyAboutUpdate = async (depsOverrides = {}) => {
|
|
2669
|
+
const deps = {
|
|
2670
|
+
...defaultDeps3,
|
|
2671
|
+
...depsOverrides
|
|
2672
|
+
};
|
|
2673
|
+
if (shouldSkipCheck(deps.args, deps.env)) {
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
let config = {};
|
|
2677
|
+
try {
|
|
2678
|
+
config = await deps.getHomeConfig();
|
|
2679
|
+
} catch {
|
|
2680
|
+
config = {};
|
|
2681
|
+
}
|
|
2682
|
+
let updateState = config.update ?? {};
|
|
2683
|
+
let latestVersion = updateState.latestVersion ?? null;
|
|
2684
|
+
const now = deps.now();
|
|
2685
|
+
const lastCheckedAt = parseTimestamp(updateState.lastCheckedAt);
|
|
2686
|
+
const shouldRefresh = !latestVersion || now - lastCheckedAt >= CHECK_INTERVAL_MS;
|
|
2687
|
+
if (shouldRefresh) {
|
|
2688
|
+
const fetchedVersion = await deps.fetchLatestVersion();
|
|
2689
|
+
updateState = {
|
|
2690
|
+
...updateState,
|
|
2691
|
+
lastCheckedAt: new Date(now).toISOString(),
|
|
2692
|
+
latestVersion: fetchedVersion ?? latestVersion ?? undefined
|
|
2693
|
+
};
|
|
2694
|
+
latestVersion = updateState.latestVersion ?? null;
|
|
2695
|
+
await persistUpdateState(config, updateState, deps);
|
|
2696
|
+
}
|
|
2697
|
+
if (!latestVersion || !isVersionNewer(latestVersion, deps.currentVersion)) {
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
if (updateState.notifiedVersion === latestVersion) {
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
deps.log(`${chalk18.yellow("Update available:")} ${chalk18.gray(`dotenc ${deps.currentVersion}`)} -> ${chalk18.cyan(`dotenc ${latestVersion}`)}. Run ${chalk18.gray("dotenc update")}.`);
|
|
2704
|
+
updateState = {
|
|
2705
|
+
...updateState,
|
|
2706
|
+
notifiedVersion: latestVersion
|
|
2707
|
+
};
|
|
2708
|
+
await persistUpdateState(config, updateState, deps);
|
|
2709
|
+
};
|
|
2710
|
+
var init_updateNotifier = __esm(() => {
|
|
2711
|
+
init_package();
|
|
2712
|
+
init_homeConfig();
|
|
2713
|
+
init_update();
|
|
2714
|
+
CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
2715
|
+
defaultDeps3 = {
|
|
2716
|
+
getHomeConfig,
|
|
2717
|
+
setHomeConfig,
|
|
2718
|
+
fetchLatestVersion,
|
|
2719
|
+
currentVersion: package_default.version,
|
|
2720
|
+
now: () => Date.now(),
|
|
2721
|
+
log: console.log,
|
|
2722
|
+
args: process.argv.slice(2),
|
|
2723
|
+
env: process.env
|
|
2724
|
+
};
|
|
2725
|
+
});
|
|
2726
|
+
|
|
2572
2727
|
// src/program.ts
|
|
2573
2728
|
var exports_program = {};
|
|
2574
2729
|
import { Command, Option } from "commander";
|
|
2575
2730
|
var program, env, auth, key, tools;
|
|
2576
|
-
var init_program = __esm(() => {
|
|
2731
|
+
var init_program = __esm(async () => {
|
|
2577
2732
|
init_package();
|
|
2578
2733
|
init_grant();
|
|
2579
2734
|
init_list();
|
|
@@ -2592,9 +2747,11 @@ var init_program = __esm(() => {
|
|
|
2592
2747
|
init_remove();
|
|
2593
2748
|
init_run();
|
|
2594
2749
|
init_textconv();
|
|
2595
|
-
|
|
2750
|
+
init_install_agent_skill();
|
|
2596
2751
|
init_install_vscode_extension();
|
|
2752
|
+
init_update2();
|
|
2597
2753
|
init_whoami();
|
|
2754
|
+
init_updateNotifier();
|
|
2598
2755
|
program = new Command;
|
|
2599
2756
|
program.name("dotenc").description(package_default.description).version(package_default.version);
|
|
2600
2757
|
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);
|
|
@@ -2616,13 +2773,15 @@ var init_program = __esm(() => {
|
|
|
2616
2773
|
key.command("list").description("list all public keys in the project").action(keyListCommand);
|
|
2617
2774
|
key.command("remove").argument("[name]", "the name of the public key to remove").description("remove a public key from the project").action(keyRemoveCommand);
|
|
2618
2775
|
tools = program.command("tools").description("install editor integrations");
|
|
2619
|
-
tools.command("install-
|
|
2776
|
+
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);
|
|
2620
2777
|
tools.command("install-vscode-extension").description("add dotenc to VS Code / Cursor / Windsurf extension recommendations").action(installVscodeExtensionCommand);
|
|
2621
2778
|
program.command("textconv", { hidden: true }).argument("<filepath>", "path to the encrypted environment file").description("decrypt an environment file for git diff").action(textconvCommand);
|
|
2779
|
+
program.command("update").description("update dotenc based on your installation method").action(updateCommand);
|
|
2622
2780
|
program.command("whoami").description("show your identity in this project").action(whoamiCommand);
|
|
2623
2781
|
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);
|
|
2782
|
+
await maybeNotifyAboutUpdate();
|
|
2624
2783
|
program.parse();
|
|
2625
2784
|
});
|
|
2626
2785
|
|
|
2627
2786
|
// src/cli.ts
|
|
2628
|
-
|
|
2787
|
+
init_program();
|