@dotenc/cli 0.4.5 → 0.5.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/dist/cli.js +377 -42
- 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.0",
|
|
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",
|
|
@@ -1052,8 +1053,8 @@ var getCurrentKeyName = async (deps = { getPrivateKeys, getPublicKeys }) => {
|
|
|
1052
1053
|
const { keys: privateKeys } = await deps.getPrivateKeys();
|
|
1053
1054
|
const publicKeys = await deps.getPublicKeys();
|
|
1054
1055
|
const privateFingerprints = new Set(privateKeys.map((k) => k.fingerprint));
|
|
1055
|
-
const
|
|
1056
|
-
return
|
|
1056
|
+
const matches = publicKeys.filter((pub) => privateFingerprints.has(pub.fingerprint));
|
|
1057
|
+
return matches.map((m) => m.name);
|
|
1057
1058
|
};
|
|
1058
1059
|
var init_getCurrentKeyName = __esm(() => {
|
|
1059
1060
|
init_getPrivateKeys();
|
|
@@ -1150,12 +1151,29 @@ var init_run = __esm(() => {
|
|
|
1150
1151
|
|
|
1151
1152
|
// src/commands/dev.ts
|
|
1152
1153
|
import chalk8 from "chalk";
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1154
|
+
import inquirer3 from "inquirer";
|
|
1155
|
+
var defaultSelect = async (message, choices) => {
|
|
1156
|
+
const { selected } = await inquirer3.prompt([
|
|
1157
|
+
{
|
|
1158
|
+
type: "list",
|
|
1159
|
+
name: "selected",
|
|
1160
|
+
message,
|
|
1161
|
+
choices
|
|
1162
|
+
}
|
|
1163
|
+
]);
|
|
1164
|
+
return selected;
|
|
1165
|
+
}, defaultDevCommandDeps, devCommand = async (command, args, deps = defaultDevCommandDeps) => {
|
|
1166
|
+
const keyNames = await deps.getCurrentKeyName();
|
|
1167
|
+
if (keyNames.length === 0) {
|
|
1156
1168
|
deps.logError(`${chalk8.red("Error:")} could not resolve your identity. Run ${chalk8.gray("dotenc init")} first.`);
|
|
1157
1169
|
deps.exit(1);
|
|
1158
1170
|
}
|
|
1171
|
+
let keyName;
|
|
1172
|
+
if (keyNames.length === 1) {
|
|
1173
|
+
keyName = keyNames[0];
|
|
1174
|
+
} else {
|
|
1175
|
+
keyName = await deps.select("Multiple identities found. Which one do you want to use?", keyNames.map((name) => ({ name, value: name })));
|
|
1176
|
+
}
|
|
1159
1177
|
await deps.runCommand(command, args, { env: `development,${keyName}` });
|
|
1160
1178
|
};
|
|
1161
1179
|
var init_dev = __esm(() => {
|
|
@@ -1165,7 +1183,8 @@ var init_dev = __esm(() => {
|
|
|
1165
1183
|
getCurrentKeyName,
|
|
1166
1184
|
runCommand,
|
|
1167
1185
|
logError: console.error,
|
|
1168
|
-
exit: process.exit
|
|
1186
|
+
exit: process.exit,
|
|
1187
|
+
select: defaultSelect
|
|
1169
1188
|
};
|
|
1170
1189
|
});
|
|
1171
1190
|
|
|
@@ -1207,9 +1226,9 @@ var init_projectConfig = __esm(() => {
|
|
|
1207
1226
|
});
|
|
1208
1227
|
|
|
1209
1228
|
// src/prompts/createEnvironment.ts
|
|
1210
|
-
import
|
|
1229
|
+
import inquirer4 from "inquirer";
|
|
1211
1230
|
var createEnvironmentPrompt = async (message, defaultValue) => {
|
|
1212
|
-
const result = await
|
|
1231
|
+
const result = await inquirer4.prompt([
|
|
1213
1232
|
{
|
|
1214
1233
|
type: "input",
|
|
1215
1234
|
name: "environment",
|
|
@@ -1704,9 +1723,9 @@ var setupGitDiff = () => {
|
|
|
1704
1723
|
var init_setupGitDiff = () => {};
|
|
1705
1724
|
|
|
1706
1725
|
// src/prompts/inputName.ts
|
|
1707
|
-
import
|
|
1726
|
+
import inquirer5 from "inquirer";
|
|
1708
1727
|
var inputNamePrompt = async (message, defaultValue) => {
|
|
1709
|
-
const result = await
|
|
1728
|
+
const result = await inquirer5.prompt([
|
|
1710
1729
|
{
|
|
1711
1730
|
type: "input",
|
|
1712
1731
|
name: "name",
|
|
@@ -1791,9 +1810,9 @@ function validatePublicKey(key) {
|
|
|
1791
1810
|
}
|
|
1792
1811
|
|
|
1793
1812
|
// src/prompts/inputKey.ts
|
|
1794
|
-
import
|
|
1813
|
+
import inquirer6 from "inquirer";
|
|
1795
1814
|
var inputKeyPrompt = async (message, defaultValue) => {
|
|
1796
|
-
const result = await
|
|
1815
|
+
const result = await inquirer6.prompt([
|
|
1797
1816
|
{
|
|
1798
1817
|
type: "password",
|
|
1799
1818
|
name: "key",
|
|
@@ -1813,7 +1832,7 @@ import fs12 from "node:fs/promises";
|
|
|
1813
1832
|
import os4 from "node:os";
|
|
1814
1833
|
import path12 from "node:path";
|
|
1815
1834
|
import chalk12 from "chalk";
|
|
1816
|
-
import
|
|
1835
|
+
import inquirer7 from "inquirer";
|
|
1817
1836
|
var keyAddCommand = async (nameArg, options) => {
|
|
1818
1837
|
const { projectId } = await getProjectConfig();
|
|
1819
1838
|
if (!projectId) {
|
|
@@ -1911,7 +1930,7 @@ ${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
|
|
|
1911
1930
|
name: `${key.name} (${key.algorithm})`,
|
|
1912
1931
|
value: key.name
|
|
1913
1932
|
}));
|
|
1914
|
-
const modePrompt = await
|
|
1933
|
+
const modePrompt = await inquirer7.prompt({
|
|
1915
1934
|
type: "list",
|
|
1916
1935
|
name: "mode",
|
|
1917
1936
|
message: "Would you like to add one of your SSH keys or paste a public key?",
|
|
@@ -1934,7 +1953,7 @@ ${passphraseProtectedKeys.map((k) => ` - ${k}`).join(`
|
|
|
1934
1953
|
process.exit(1);
|
|
1935
1954
|
}
|
|
1936
1955
|
} else {
|
|
1937
|
-
const keyPrompt = await
|
|
1956
|
+
const keyPrompt = await inquirer7.prompt({
|
|
1938
1957
|
type: "list",
|
|
1939
1958
|
name: "key",
|
|
1940
1959
|
message: "Which SSH key do you want to add?",
|
|
@@ -1994,7 +2013,7 @@ import fs13 from "node:fs/promises";
|
|
|
1994
2013
|
import os5 from "node:os";
|
|
1995
2014
|
import path13 from "node:path";
|
|
1996
2015
|
import chalk13 from "chalk";
|
|
1997
|
-
import
|
|
2016
|
+
import inquirer8 from "inquirer";
|
|
1998
2017
|
var initCommand = async (options) => {
|
|
1999
2018
|
const { keys: privateKeys, passphraseProtectedKeys } = await getPrivateKeys();
|
|
2000
2019
|
if (!privateKeys.length) {
|
|
@@ -2025,7 +2044,7 @@ var initCommand = async (options) => {
|
|
|
2025
2044
|
if (privateKeys.length === 1) {
|
|
2026
2045
|
keyToAdd = privateKeys[0].name;
|
|
2027
2046
|
} else {
|
|
2028
|
-
const result = await
|
|
2047
|
+
const result = await inquirer8.prompt([
|
|
2029
2048
|
{
|
|
2030
2049
|
type: "list",
|
|
2031
2050
|
name: "key",
|
|
@@ -2072,6 +2091,12 @@ Some useful tips:`);
|
|
|
2072
2091
|
console.log(`- To edit your personal environment: ${editCmd}`);
|
|
2073
2092
|
const devCmd = chalk13.gray("dotenc dev <command>");
|
|
2074
2093
|
console.log(`- To run with your encrypted env: ${devCmd}`);
|
|
2094
|
+
if (existsSync8(".claude") || existsSync8("CLAUDE.md")) {
|
|
2095
|
+
console.log(`- Install the Claude Code skill: ${chalk13.gray("dotenc tools install-claude-code-skill")}`);
|
|
2096
|
+
}
|
|
2097
|
+
if (existsSync8(".vscode") || existsSync8(".cursor") || existsSync8(".windsurf")) {
|
|
2098
|
+
console.log(`- Add the editor extension: ${chalk13.gray("dotenc tools install-vscode-extension")}`);
|
|
2099
|
+
}
|
|
2075
2100
|
};
|
|
2076
2101
|
var init_init = __esm(() => {
|
|
2077
2102
|
init_createProject();
|
|
@@ -2099,9 +2124,9 @@ var init_list3 = __esm(() => {
|
|
|
2099
2124
|
});
|
|
2100
2125
|
|
|
2101
2126
|
// src/prompts/confirm.ts
|
|
2102
|
-
import
|
|
2127
|
+
import inquirer9 from "inquirer";
|
|
2103
2128
|
var confirmPrompt = async (message) => {
|
|
2104
|
-
const result = await
|
|
2129
|
+
const result = await inquirer9.prompt([
|
|
2105
2130
|
{
|
|
2106
2131
|
type: "confirm",
|
|
2107
2132
|
name: "confirm",
|
|
@@ -2193,13 +2218,312 @@ var init_textconv = __esm(() => {
|
|
|
2193
2218
|
init_getEnvironmentByPath();
|
|
2194
2219
|
});
|
|
2195
2220
|
|
|
2221
|
+
// src/commands/tools/install-claude-code-skill.ts
|
|
2222
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
2223
|
+
import fs16 from "node:fs/promises";
|
|
2224
|
+
import os6 from "node:os";
|
|
2225
|
+
import path16 from "node:path";
|
|
2226
|
+
import chalk15 from "chalk";
|
|
2227
|
+
import inquirer10 from "inquirer";
|
|
2228
|
+
var SKILL_MD_CONTENT = `---
|
|
2229
|
+
name: dotenc
|
|
2230
|
+
description: Manage dotenc encrypted environments. Use when the user asks about environment variables, secrets, dotenc setup, encrypted environments, adding teammates, rotating keys, or running commands with secrets injected.
|
|
2231
|
+
allowed-tools: Bash, Read, Glob, Grep
|
|
2232
|
+
argument-hint: [command]
|
|
2233
|
+
---
|
|
2234
|
+
|
|
2235
|
+
## Project context
|
|
2236
|
+
|
|
2237
|
+
> If any command below shows an error, dotenc is likely not initialized — suggest running \`dotenc init\` first.
|
|
2238
|
+
|
|
2239
|
+
**Current identity:**
|
|
2240
|
+
!\`dotenc whoami 2>&1 || true\`
|
|
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([
|
|
2348
|
+
{
|
|
2349
|
+
type: "list",
|
|
2350
|
+
name: "scope",
|
|
2351
|
+
message: "Install locally or globally?",
|
|
2352
|
+
choices: [
|
|
2353
|
+
{ name: "Locally (this project)", value: "local" },
|
|
2354
|
+
{ name: "Globally (all projects)", value: "global" }
|
|
2355
|
+
]
|
|
2356
|
+
}
|
|
2357
|
+
]);
|
|
2358
|
+
const baseDir = scope === "global" ? path16.join(os6.homedir(), ".claude") : path16.join(process.cwd(), ".claude");
|
|
2359
|
+
const skillDir = path16.join(baseDir, "skills", "dotenc");
|
|
2360
|
+
const skillPath = path16.join(skillDir, "SKILL.md");
|
|
2361
|
+
if (existsSync10(skillPath) && !options.force) {
|
|
2362
|
+
console.error(`${chalk15.red("Error:")} Claude Code skill already exists at ${chalk15.gray(skillPath)}.`);
|
|
2363
|
+
console.error(`Run with ${chalk15.gray("--force")} to overwrite.`);
|
|
2364
|
+
process.exit(1);
|
|
2365
|
+
}
|
|
2366
|
+
await fs16.mkdir(skillDir, { recursive: true });
|
|
2367
|
+
await fs16.writeFile(skillPath, SKILL_MD_CONTENT, "utf-8");
|
|
2368
|
+
console.log(`${chalk15.green("✓")} Claude Code skill installed at ${chalk15.gray(skillPath)}`);
|
|
2369
|
+
console.log(`Run ${chalk15.gray("/dotenc")} in Claude Code to use it.`);
|
|
2370
|
+
};
|
|
2371
|
+
var init_install_claude_code_skill = () => {};
|
|
2372
|
+
|
|
2373
|
+
// src/commands/tools/install-vscode-extension.ts
|
|
2374
|
+
import { exec, execFile } from "node:child_process";
|
|
2375
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
2376
|
+
import fs17 from "node:fs/promises";
|
|
2377
|
+
import path17 from "node:path";
|
|
2378
|
+
import { promisify } from "node:util";
|
|
2379
|
+
import chalk16 from "chalk";
|
|
2380
|
+
import inquirer11 from "inquirer";
|
|
2381
|
+
async function addToExtensionsJson() {
|
|
2382
|
+
const extensionsJsonPath = path17.join(process.cwd(), ".vscode", "extensions.json");
|
|
2383
|
+
let json = {};
|
|
2384
|
+
if (existsSync11(extensionsJsonPath)) {
|
|
2385
|
+
const content = await fs17.readFile(extensionsJsonPath, "utf-8");
|
|
2386
|
+
try {
|
|
2387
|
+
json = JSON.parse(content);
|
|
2388
|
+
} catch {
|
|
2389
|
+
json = {};
|
|
2390
|
+
}
|
|
2391
|
+
} else {
|
|
2392
|
+
await fs17.mkdir(path17.join(process.cwd(), ".vscode"), { recursive: true });
|
|
2393
|
+
}
|
|
2394
|
+
if (!Array.isArray(json.recommendations)) {
|
|
2395
|
+
json.recommendations = [];
|
|
2396
|
+
}
|
|
2397
|
+
if (!json.recommendations.includes(EXTENSION_ID)) {
|
|
2398
|
+
json.recommendations.push(EXTENSION_ID);
|
|
2399
|
+
await fs17.writeFile(extensionsJsonPath, JSON.stringify(json, null, 2), "utf-8");
|
|
2400
|
+
console.log(`${chalk16.green("✓")} Added dotenc to ${chalk16.gray(".vscode/extensions.json")}`);
|
|
2401
|
+
} else {
|
|
2402
|
+
console.log(`${chalk16.green("✓")} dotenc already in ${chalk16.gray(".vscode/extensions.json")}`);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
async function which(bin) {
|
|
2406
|
+
try {
|
|
2407
|
+
await execFileAsync("which", [bin]);
|
|
2408
|
+
return true;
|
|
2409
|
+
} catch {
|
|
2410
|
+
return false;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
async function detectEditors() {
|
|
2414
|
+
const detected = [];
|
|
2415
|
+
if (existsSync11(path17.join(process.cwd(), ".cursor")))
|
|
2416
|
+
detected.push("cursor");
|
|
2417
|
+
if (existsSync11(path17.join(process.cwd(), ".windsurf")))
|
|
2418
|
+
detected.push("windsurf");
|
|
2419
|
+
if (existsSync11(path17.join(process.cwd(), ".vscode")))
|
|
2420
|
+
detected.push("vscode");
|
|
2421
|
+
const checks = [
|
|
2422
|
+
{ key: "cursor", bins: ["cursor"] },
|
|
2423
|
+
{ key: "windsurf", bins: ["windsurf"] },
|
|
2424
|
+
{ key: "vscode", bins: ["code"] },
|
|
2425
|
+
{ key: "vscodium", bins: ["codium", "vscodium"] }
|
|
2426
|
+
];
|
|
2427
|
+
if (process.platform === "darwin") {
|
|
2428
|
+
const macApps = {
|
|
2429
|
+
cursor: "/Applications/Cursor.app",
|
|
2430
|
+
windsurf: "/Applications/Windsurf.app",
|
|
2431
|
+
vscode: "/Applications/Visual Studio Code.app",
|
|
2432
|
+
vscodium: "/Applications/VSCodium.app"
|
|
2433
|
+
};
|
|
2434
|
+
for (const [key, appPath] of Object.entries(macApps)) {
|
|
2435
|
+
if (!detected.includes(key) && existsSync11(appPath)) {
|
|
2436
|
+
detected.push(key);
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
for (const { key, bins } of checks) {
|
|
2441
|
+
if (detected.includes(key))
|
|
2442
|
+
continue;
|
|
2443
|
+
for (const bin of bins) {
|
|
2444
|
+
if (await which(bin)) {
|
|
2445
|
+
detected.push(key);
|
|
2446
|
+
break;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return detected;
|
|
2451
|
+
}
|
|
2452
|
+
async function openUrl(url) {
|
|
2453
|
+
if (process.platform === "darwin") {
|
|
2454
|
+
await execFileAsync("open", [url]);
|
|
2455
|
+
} else if (process.platform === "win32") {
|
|
2456
|
+
await execAsync(`start ${url}`);
|
|
2457
|
+
} else {
|
|
2458
|
+
await execFileAsync("xdg-open", [url]);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
async function _runInstallVscodeExtension(getEditors = detectEditors, _openUrl = openUrl) {
|
|
2462
|
+
const editors = await getEditors();
|
|
2463
|
+
await addToExtensionsJson();
|
|
2464
|
+
if (editors.length === 0) {
|
|
2465
|
+
console.log(`
|
|
2466
|
+
Install the extension in VS Code: ${chalk16.cyan(EDITOR_PROTOCOL_URLS.vscode)}`);
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
if (editors.length === 1) {
|
|
2470
|
+
const editor = editors[0];
|
|
2471
|
+
const url = EDITOR_PROTOCOL_URLS[editor];
|
|
2472
|
+
const name = EDITOR_NAMES[editor];
|
|
2473
|
+
const { open } = await inquirer11.prompt([
|
|
2474
|
+
{
|
|
2475
|
+
type: "confirm",
|
|
2476
|
+
name: "open",
|
|
2477
|
+
message: `Open extension page in ${name} now?`,
|
|
2478
|
+
default: true
|
|
2479
|
+
}
|
|
2480
|
+
]);
|
|
2481
|
+
if (open) {
|
|
2482
|
+
try {
|
|
2483
|
+
await _openUrl(url);
|
|
2484
|
+
} catch {
|
|
2485
|
+
console.log(`Open manually: ${chalk16.cyan(url)}`);
|
|
2486
|
+
}
|
|
2487
|
+
} else {
|
|
2488
|
+
console.log(`Install manually: ${chalk16.cyan(url)}`);
|
|
2489
|
+
}
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
console.log(`
|
|
2493
|
+
Install the extension in your editor:`);
|
|
2494
|
+
for (const editor of editors) {
|
|
2495
|
+
const name = EDITOR_NAMES[editor] ?? editor;
|
|
2496
|
+
const url = EDITOR_PROTOCOL_URLS[editor];
|
|
2497
|
+
console.log(` ${name}: ${chalk16.cyan(url)}`);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
var execFileAsync, execAsync, EXTENSION_ID = "dotenc.dotenc", EDITOR_PROTOCOL_URLS, EDITOR_NAMES, installVscodeExtensionCommand = async () => {
|
|
2501
|
+
await _runInstallVscodeExtension();
|
|
2502
|
+
};
|
|
2503
|
+
var init_install_vscode_extension = __esm(() => {
|
|
2504
|
+
execFileAsync = promisify(execFile);
|
|
2505
|
+
execAsync = promisify(exec);
|
|
2506
|
+
EDITOR_PROTOCOL_URLS = {
|
|
2507
|
+
vscode: `vscode:extension/${EXTENSION_ID}`,
|
|
2508
|
+
cursor: `cursor:extension/${EXTENSION_ID}`,
|
|
2509
|
+
windsurf: `windsurf:extension/${EXTENSION_ID}`,
|
|
2510
|
+
vscodium: `vscodium:extension/${EXTENSION_ID}`
|
|
2511
|
+
};
|
|
2512
|
+
EDITOR_NAMES = {
|
|
2513
|
+
vscode: "VS Code",
|
|
2514
|
+
cursor: "Cursor",
|
|
2515
|
+
windsurf: "Windsurf",
|
|
2516
|
+
vscodium: "VSCodium"
|
|
2517
|
+
};
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2196
2520
|
// src/commands/whoami.ts
|
|
2197
2521
|
var whoamiCommand = async () => {
|
|
2198
2522
|
const { keys: privateKeys, passphraseProtectedKeys } = await getPrivateKeys();
|
|
2199
2523
|
const publicKeys = await getPublicKeys();
|
|
2200
2524
|
const privateFingerprints = new Set(privateKeys.map((k) => k.fingerprint));
|
|
2201
|
-
const
|
|
2202
|
-
if (
|
|
2525
|
+
const matchingPublicKeys = publicKeys.filter((pub) => privateFingerprints.has(pub.fingerprint));
|
|
2526
|
+
if (matchingPublicKeys.length === 0) {
|
|
2203
2527
|
if (privateKeys.length === 0 && passphraseProtectedKeys.length > 0) {
|
|
2204
2528
|
console.error(passphraseProtectedKeyError(passphraseProtectedKeys));
|
|
2205
2529
|
} else {
|
|
@@ -2207,28 +2531,34 @@ var whoamiCommand = async () => {
|
|
|
2207
2531
|
}
|
|
2208
2532
|
process.exit(1);
|
|
2209
2533
|
}
|
|
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
2534
|
const environments = await getEnvironments();
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2535
|
+
for (let i = 0;i < matchingPublicKeys.length; i++) {
|
|
2536
|
+
const matchingPublicKey = matchingPublicKeys[i];
|
|
2537
|
+
if (i > 0) {
|
|
2538
|
+
console.log("");
|
|
2539
|
+
}
|
|
2540
|
+
const matchingPrivateKey = privateKeys.find((pk) => pk.fingerprint === matchingPublicKey.fingerprint);
|
|
2541
|
+
console.log(`Name: ${matchingPublicKey.name}`);
|
|
2542
|
+
console.log(`Active SSH key: ${matchingPrivateKey?.name ?? "unknown"}`);
|
|
2543
|
+
console.log(`Fingerprint: ${matchingPublicKey.fingerprint}`);
|
|
2544
|
+
const authorizedEnvironments = [];
|
|
2545
|
+
for (const envName of environments) {
|
|
2546
|
+
try {
|
|
2547
|
+
const environment = await getEnvironmentByName(envName);
|
|
2548
|
+
const hasAccess = environment.keys.some((key) => key.fingerprint === matchingPublicKey.fingerprint);
|
|
2549
|
+
if (hasAccess) {
|
|
2550
|
+
authorizedEnvironments.push(envName);
|
|
2551
|
+
}
|
|
2552
|
+
} catch {}
|
|
2553
|
+
}
|
|
2554
|
+
if (authorizedEnvironments.length > 0) {
|
|
2555
|
+
console.log("Authorized environments:");
|
|
2556
|
+
for (const env of authorizedEnvironments) {
|
|
2557
|
+
console.log(` - ${env}`);
|
|
2222
2558
|
}
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
if (authorizedEnvironments.length > 0) {
|
|
2226
|
-
console.log("Authorized environments:");
|
|
2227
|
-
for (const env of authorizedEnvironments) {
|
|
2228
|
-
console.log(` - ${env}`);
|
|
2559
|
+
} else {
|
|
2560
|
+
console.log("Authorized environments: none");
|
|
2229
2561
|
}
|
|
2230
|
-
} else {
|
|
2231
|
-
console.log("Authorized environments: none");
|
|
2232
2562
|
}
|
|
2233
2563
|
};
|
|
2234
2564
|
var init_whoami = __esm(() => {
|
|
@@ -2242,7 +2572,7 @@ var init_whoami = __esm(() => {
|
|
|
2242
2572
|
// src/program.ts
|
|
2243
2573
|
var exports_program = {};
|
|
2244
2574
|
import { Command, Option } from "commander";
|
|
2245
|
-
var program, env, auth, key;
|
|
2575
|
+
var program, env, auth, key, tools;
|
|
2246
2576
|
var init_program = __esm(() => {
|
|
2247
2577
|
init_package();
|
|
2248
2578
|
init_grant();
|
|
@@ -2262,6 +2592,8 @@ var init_program = __esm(() => {
|
|
|
2262
2592
|
init_remove();
|
|
2263
2593
|
init_run();
|
|
2264
2594
|
init_textconv();
|
|
2595
|
+
init_install_claude_code_skill();
|
|
2596
|
+
init_install_vscode_extension();
|
|
2265
2597
|
init_whoami();
|
|
2266
2598
|
program = new Command;
|
|
2267
2599
|
program.name("dotenc").description(package_default.description).version(package_default.version);
|
|
@@ -2283,6 +2615,9 @@ var init_program = __esm(() => {
|
|
|
2283
2615
|
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
2616
|
key.command("list").description("list all public keys in the project").action(keyListCommand);
|
|
2285
2617
|
key.command("remove").argument("[name]", "the name of the public key to remove").description("remove a public key from the project").action(keyRemoveCommand);
|
|
2618
|
+
tools = program.command("tools").description("install editor integrations");
|
|
2619
|
+
tools.command("install-claude-code-skill").addOption(new Option("--force", "overwrite existing installation")).description("install the Claude Code skill for this project").action(installClaudeCodeSkillCommand);
|
|
2620
|
+
tools.command("install-vscode-extension").description("add dotenc to VS Code / Cursor / Windsurf extension recommendations").action(installVscodeExtensionCommand);
|
|
2286
2621
|
program.command("textconv", { hidden: true }).argument("<filepath>", "path to the encrypted environment file").description("decrypt an environment file for git diff").action(textconvCommand);
|
|
2287
2622
|
program.command("whoami").description("show your identity in this project").action(whoamiCommand);
|
|
2288
2623
|
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);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotenc/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|