envsec 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -4
- package/dist/cli/load.js +49 -0
- package/dist/cli/run.js +4 -1
- package/dist/implementations/LinuxSecretServiceAccess.js +96 -0
- package/dist/implementations/PlatformKeychainAccess.js +23 -0
- package/dist/implementations/WindowsCredentialManagerAccess.js +118 -0
- package/dist/main.js +2 -1
- package/dist/services/SecretStore.js +2 -2
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# secenv
|
|
2
2
|
|
|
3
|
-
Secure environment secrets management
|
|
3
|
+
Secure environment secrets management using native OS credential stores.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- Store secrets in
|
|
7
|
+
- Store secrets in your OS native credential store (not plain text files)
|
|
8
|
+
- Cross-platform: macOS, Linux, Windows
|
|
8
9
|
- Organize secrets by environment (dev, staging, prod, etc.)
|
|
9
10
|
- Track secret types (string, number, boolean) and metadata via SQLite
|
|
10
11
|
- Search secrets with glob patterns
|
|
@@ -13,9 +14,33 @@ Secure environment secrets management for macOS using the native Keychain.
|
|
|
13
14
|
|
|
14
15
|
## Requirements
|
|
15
16
|
|
|
16
|
-
- macOS
|
|
17
17
|
- Node.js >= 18
|
|
18
18
|
|
|
19
|
+
### macOS
|
|
20
|
+
|
|
21
|
+
No extra dependencies. Uses the built-in Keychain via the `security` CLI tool.
|
|
22
|
+
|
|
23
|
+
### Linux
|
|
24
|
+
|
|
25
|
+
Requires `libsecret-tools` (provides the `secret-tool` command), which talks to GNOME Keyring, KDE Wallet, or any Secret Service API provider via D-Bus.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Debian / Ubuntu
|
|
29
|
+
sudo apt install libsecret-tools
|
|
30
|
+
|
|
31
|
+
# Fedora
|
|
32
|
+
sudo dnf install libsecret
|
|
33
|
+
|
|
34
|
+
# Arch
|
|
35
|
+
sudo pacman -S libsecret
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A running D-Bus session and a keyring daemon (e.g. `gnome-keyring-daemon`) must be active. Most desktop environments handle this automatically.
|
|
39
|
+
|
|
40
|
+
### Windows
|
|
41
|
+
|
|
42
|
+
No extra dependencies. Uses the built-in Windows Credential Manager via `cmdkey` and PowerShell.
|
|
43
|
+
|
|
19
44
|
## Installation
|
|
20
45
|
|
|
21
46
|
```bash
|
|
@@ -94,7 +119,15 @@ secenv -e dev del api.key
|
|
|
94
119
|
|
|
95
120
|
## How it works
|
|
96
121
|
|
|
97
|
-
Secrets are stored in the
|
|
122
|
+
Secrets are stored in the native OS credential store. The backend is selected automatically based on the platform:
|
|
123
|
+
|
|
124
|
+
| OS | Backend | Tool / API |
|
|
125
|
+
|---------|--------------------------------|-------------------------------------|
|
|
126
|
+
| macOS | Keychain | `security` CLI |
|
|
127
|
+
| Linux | Secret Service API (D-Bus) | `secret-tool` (libsecret) |
|
|
128
|
+
| Windows | Credential Manager | `cmdkey` + PowerShell (advapi32) |
|
|
129
|
+
|
|
130
|
+
Metadata (key names, types, timestamps) is kept in a SQLite database at `~/.secenv/store.sqlite`. Keys must contain at least one dot separator (e.g., `service.account`) which maps to the credential store's service/account structure.
|
|
98
131
|
|
|
99
132
|
## License
|
|
100
133
|
|
package/dist/cli/load.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Command, Options } from "@effect/cli";
|
|
2
|
+
import { Effect, Console } from "effect";
|
|
3
|
+
import { SecretStore } from "../services/SecretStore.js";
|
|
4
|
+
import { rootCommand } from "./root.js";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
const input = Options.text("input").pipe(Options.withAlias("i"), Options.withDescription("Input .env file path (default: .env)"), Options.withDefault(".env"));
|
|
7
|
+
const force = Options.boolean("force").pipe(Options.withAlias("f"), Options.withDescription("Overwrite existing secrets without prompting"));
|
|
8
|
+
const parseLine = (line) => {
|
|
9
|
+
const trimmed = line.trim();
|
|
10
|
+
if (trimmed === "" || trimmed.startsWith("#"))
|
|
11
|
+
return null;
|
|
12
|
+
const eqIndex = trimmed.indexOf("=");
|
|
13
|
+
if (eqIndex === -1)
|
|
14
|
+
return null;
|
|
15
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
16
|
+
const value = trimmed.slice(eqIndex + 1).trim().replace(/^["']|["']$/g, "");
|
|
17
|
+
return { key, value };
|
|
18
|
+
};
|
|
19
|
+
export const loadCommand = Command.make("load", { input, force }, ({ input, force }) => Effect.gen(function* () {
|
|
20
|
+
const { env } = yield* rootCommand;
|
|
21
|
+
const content = yield* Effect.try({
|
|
22
|
+
try: () => readFileSync(input, "utf-8"),
|
|
23
|
+
catch: () => new Error(`Cannot read file: ${input}`),
|
|
24
|
+
});
|
|
25
|
+
const lines = content.split("\n");
|
|
26
|
+
let added = 0;
|
|
27
|
+
let skipped = 0;
|
|
28
|
+
let overwritten = 0;
|
|
29
|
+
for (const line of lines) {
|
|
30
|
+
const parsed = parseLine(line);
|
|
31
|
+
if (!parsed)
|
|
32
|
+
continue;
|
|
33
|
+
const secretKey = parsed.key.toLowerCase().replaceAll("_", ".");
|
|
34
|
+
const exists = yield* SecretStore.list(env).pipe(Effect.map((items) => items.some((item) => item.key === secretKey)));
|
|
35
|
+
if (exists && !force) {
|
|
36
|
+
yield* Console.log(`⚠ Skipped "${secretKey}": already exists (use --force to overwrite)`);
|
|
37
|
+
skipped++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (exists) {
|
|
41
|
+
overwritten++;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
added++;
|
|
45
|
+
}
|
|
46
|
+
yield* SecretStore.set(env, secretKey, parsed.value, "string");
|
|
47
|
+
}
|
|
48
|
+
yield* Effect.log(`Done: ${added} added, ${overwritten} overwritten, ${skipped} skipped`);
|
|
49
|
+
}));
|
package/dist/cli/run.js
CHANGED
|
@@ -18,7 +18,10 @@ export const runCommand = Command.make("run", { cmd }, ({ cmd }) => Effect.gen(f
|
|
|
18
18
|
yield* Effect.log(`Resolved ${placeholders.length} secret(s)`);
|
|
19
19
|
}
|
|
20
20
|
try {
|
|
21
|
-
execSync(resolved, {
|
|
21
|
+
execSync(resolved, {
|
|
22
|
+
stdio: "inherit",
|
|
23
|
+
shell: process.platform === "win32" ? "cmd.exe" : "/bin/sh",
|
|
24
|
+
});
|
|
22
25
|
}
|
|
23
26
|
catch (e) {
|
|
24
27
|
yield* Effect.fail(new Error(`Command exited with code ${e.status ?? 1}`));
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
import { KeychainAccess } from "../services/KeychainAccess.js";
|
|
4
|
+
import { KeychainError, SecretNotFoundError } from "../errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Linux implementation using `secret-tool` (libsecret).
|
|
7
|
+
*
|
|
8
|
+
* Stores secrets via the freedesktop.org Secret Service API (D-Bus),
|
|
9
|
+
* backed by GNOME Keyring, KDE Wallet, or any compatible provider.
|
|
10
|
+
*
|
|
11
|
+
* Requires: `libsecret-tools` package
|
|
12
|
+
* - Debian/Ubuntu: sudo apt install libsecret-tools
|
|
13
|
+
* - Fedora: sudo dnf install libsecret
|
|
14
|
+
* - Arch: sudo pacman -S libsecret
|
|
15
|
+
*/
|
|
16
|
+
const run = (args, stdin) => Effect.async((resume) => {
|
|
17
|
+
const child = execFile("secret-tool", args, (error, stdout, stderr) => {
|
|
18
|
+
if (error && error.code === "ENOENT") {
|
|
19
|
+
resume(Effect.fail(new KeychainError({
|
|
20
|
+
command: args[0] ?? "unknown",
|
|
21
|
+
stderr: "secret-tool not found. Install libsecret-tools.",
|
|
22
|
+
message: "secret-tool is not installed. Install it with your package manager (e.g. apt install libsecret-tools).",
|
|
23
|
+
})));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
resume(Effect.succeed({
|
|
27
|
+
exitCode: error ? error.code ?? 1 : 0,
|
|
28
|
+
stdout,
|
|
29
|
+
stderr,
|
|
30
|
+
}));
|
|
31
|
+
});
|
|
32
|
+
// secret-tool store reads the password from stdin
|
|
33
|
+
if (stdin !== undefined) {
|
|
34
|
+
child.stdin?.write(stdin);
|
|
35
|
+
child.stdin?.end();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const make = KeychainAccess.of({
|
|
39
|
+
set: Effect.fn("LinuxSecretServiceAccess.set")(function* (service, account, password) {
|
|
40
|
+
// secret-tool store --label="<label>" <attribute> <value> ...
|
|
41
|
+
// Password is read from stdin
|
|
42
|
+
const result = yield* run([
|
|
43
|
+
"store",
|
|
44
|
+
"--label",
|
|
45
|
+
`secenv:${service}/${account}`,
|
|
46
|
+
"service",
|
|
47
|
+
service,
|
|
48
|
+
"account",
|
|
49
|
+
account,
|
|
50
|
+
], password);
|
|
51
|
+
if (result.exitCode !== 0) {
|
|
52
|
+
return yield* new KeychainError({
|
|
53
|
+
command: "store",
|
|
54
|
+
stderr: result.stderr,
|
|
55
|
+
message: `Failed to store secret: ${service}/${account}`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
get: Effect.fn("LinuxSecretServiceAccess.get")(function* (service, account) {
|
|
60
|
+
// secret-tool lookup <attribute> <value> ...
|
|
61
|
+
const result = yield* run([
|
|
62
|
+
"lookup",
|
|
63
|
+
"service",
|
|
64
|
+
service,
|
|
65
|
+
"account",
|
|
66
|
+
account,
|
|
67
|
+
]);
|
|
68
|
+
// secret-tool returns exit 0 with empty stdout when not found
|
|
69
|
+
if (result.exitCode !== 0 || result.stdout === "") {
|
|
70
|
+
return yield* new SecretNotFoundError({
|
|
71
|
+
key: account,
|
|
72
|
+
env: service,
|
|
73
|
+
message: `Secret not found: ${service}/${account}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return result.stdout.trimEnd();
|
|
77
|
+
}),
|
|
78
|
+
remove: Effect.fn("LinuxSecretServiceAccess.remove")(function* (service, account) {
|
|
79
|
+
// secret-tool clear <attribute> <value> ...
|
|
80
|
+
const result = yield* run([
|
|
81
|
+
"clear",
|
|
82
|
+
"service",
|
|
83
|
+
service,
|
|
84
|
+
"account",
|
|
85
|
+
account,
|
|
86
|
+
]);
|
|
87
|
+
if (result.exitCode !== 0) {
|
|
88
|
+
return yield* new KeychainError({
|
|
89
|
+
command: "clear",
|
|
90
|
+
stderr: result.stderr,
|
|
91
|
+
message: `Failed to remove secret: ${service}/${account}`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
export const LinuxSecretServiceAccessLive = Layer.succeed(KeychainAccess, make);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { platform } from "node:os";
|
|
2
|
+
import { MacOsKeychainAccessLive } from "./MacOsKeychainAccess.js";
|
|
3
|
+
import { LinuxSecretServiceAccessLive } from "./LinuxSecretServiceAccess.js";
|
|
4
|
+
import { WindowsCredentialManagerAccessLive } from "./WindowsCredentialManagerAccess.js";
|
|
5
|
+
/**
|
|
6
|
+
* Auto-detects the current OS and provides the appropriate KeychainAccess layer.
|
|
7
|
+
*
|
|
8
|
+
* - macOS: uses `security` CLI (Keychain)
|
|
9
|
+
* - Linux: uses `secret-tool` (libsecret / Secret Service API)
|
|
10
|
+
* - Windows: uses PowerShell + Credential Manager (advapi32 CredRead/cmdkey)
|
|
11
|
+
*/
|
|
12
|
+
export const PlatformKeychainAccessLive = (() => {
|
|
13
|
+
switch (platform()) {
|
|
14
|
+
case "darwin":
|
|
15
|
+
return MacOsKeychainAccessLive;
|
|
16
|
+
case "linux":
|
|
17
|
+
return LinuxSecretServiceAccessLive;
|
|
18
|
+
case "win32":
|
|
19
|
+
return WindowsCredentialManagerAccessLive;
|
|
20
|
+
default:
|
|
21
|
+
throw new Error(`Unsupported platform: ${platform()}. Supported: macOS, Linux, Windows.`);
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
import { KeychainAccess } from "../services/KeychainAccess.js";
|
|
4
|
+
import { KeychainError, SecretNotFoundError } from "../errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Windows implementation using PowerShell + Windows Credential Manager.
|
|
7
|
+
*
|
|
8
|
+
* Uses the built-in `cmdkey` for basic operations and PowerShell's
|
|
9
|
+
* `System.Net.NetworkCredential` / `CredentialManager` for read/write.
|
|
10
|
+
*
|
|
11
|
+
* No extra dependencies required — uses only built-in Windows APIs via PowerShell.
|
|
12
|
+
*
|
|
13
|
+
* Credential target format: "secenv:<service>/<account>"
|
|
14
|
+
*/
|
|
15
|
+
const runPowerShell = (script) => Effect.async((resume) => {
|
|
16
|
+
execFile("powershell.exe", [
|
|
17
|
+
"-NoProfile",
|
|
18
|
+
"-NonInteractive",
|
|
19
|
+
"-Command",
|
|
20
|
+
script,
|
|
21
|
+
], (error, stdout, stderr) => {
|
|
22
|
+
if (error && error.code === "ENOENT") {
|
|
23
|
+
resume(Effect.fail(new KeychainError({
|
|
24
|
+
command: "powershell",
|
|
25
|
+
stderr: "powershell.exe not found",
|
|
26
|
+
message: "PowerShell is not available. Ensure you are running on Windows.",
|
|
27
|
+
})));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
resume(Effect.succeed({
|
|
31
|
+
exitCode: error ? error.code ?? 1 : 0,
|
|
32
|
+
stdout,
|
|
33
|
+
stderr,
|
|
34
|
+
}));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
const escapePS = (s) => s.replaceAll("'", "''");
|
|
38
|
+
const targetName = (service, account) => `secenv:${service}/${account}`;
|
|
39
|
+
const make = KeychainAccess.of({
|
|
40
|
+
set: Effect.fn("WindowsCredentialManagerAccess.set")(function* (service, account, password) {
|
|
41
|
+
const target = escapePS(targetName(service, account));
|
|
42
|
+
const user = escapePS(account);
|
|
43
|
+
const pass = escapePS(password);
|
|
44
|
+
// Use cmdkey for simplicity — it's built-in and handles generic credentials
|
|
45
|
+
const script = `cmdkey /generic:'${target}' /user:'${user}' /pass:'${pass}'`;
|
|
46
|
+
const result = yield* runPowerShell(script);
|
|
47
|
+
if (result.exitCode !== 0) {
|
|
48
|
+
return yield* new KeychainError({
|
|
49
|
+
command: "cmdkey /add",
|
|
50
|
+
stderr: result.stderr || result.stdout,
|
|
51
|
+
message: `Failed to store credential: ${service}/${account}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
get: Effect.fn("WindowsCredentialManagerAccess.get")(function* (service, account) {
|
|
56
|
+
const target = escapePS(targetName(service, account));
|
|
57
|
+
// Read credential using .NET CredentialManager API via PowerShell
|
|
58
|
+
// This is the only reliable way to read the password back from Credential Manager
|
|
59
|
+
const script = [
|
|
60
|
+
`Add-Type -AssemblyName System.Runtime.InteropServices`,
|
|
61
|
+
`$cred = [System.Runtime.InteropServices.Marshal]`,
|
|
62
|
+
`$target = '${target}'`,
|
|
63
|
+
// Use P/Invoke to call CredReadW
|
|
64
|
+
`Add-Type @'`,
|
|
65
|
+
`using System;`,
|
|
66
|
+
`using System.Runtime.InteropServices;`,
|
|
67
|
+
`public class CredManager {`,
|
|
68
|
+
` [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]`,
|
|
69
|
+
` public static extern bool CredRead(string target, int type, int flags, out IntPtr cred);`,
|
|
70
|
+
` [DllImport("advapi32.dll")]`,
|
|
71
|
+
` public static extern void CredFree(IntPtr cred);`,
|
|
72
|
+
` [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]`,
|
|
73
|
+
` public struct CREDENTIAL {`,
|
|
74
|
+
` public int Flags; public int Type;`,
|
|
75
|
+
` public string TargetName; public string Comment;`,
|
|
76
|
+
` public long LastWritten; public int CredentialBlobSize;`,
|
|
77
|
+
` public IntPtr CredentialBlob; public int Persist;`,
|
|
78
|
+
` public int AttributeCount; public IntPtr Attributes;`,
|
|
79
|
+
` public string TargetAlias; public string UserName;`,
|
|
80
|
+
` }`,
|
|
81
|
+
` public static string Read(string target) {`,
|
|
82
|
+
` IntPtr ptr;`,
|
|
83
|
+
` if (!CredRead(target, 1, 0, out ptr)) return null;`,
|
|
84
|
+
` var c = (CREDENTIAL)Marshal.PtrToStructure(ptr, typeof(CREDENTIAL));`,
|
|
85
|
+
` var pw = Marshal.PtrToStringUni(c.CredentialBlob, c.CredentialBlobSize / 2);`,
|
|
86
|
+
` CredFree(ptr);`,
|
|
87
|
+
` return pw;`,
|
|
88
|
+
` }`,
|
|
89
|
+
`}`,
|
|
90
|
+
`'@`,
|
|
91
|
+
`$result = [CredManager]::Read('${target}')`,
|
|
92
|
+
`if ($result -eq $null) { exit 1 }`,
|
|
93
|
+
`Write-Output $result`,
|
|
94
|
+
].join("\n");
|
|
95
|
+
const result = yield* runPowerShell(script);
|
|
96
|
+
if (result.exitCode !== 0) {
|
|
97
|
+
return yield* new SecretNotFoundError({
|
|
98
|
+
key: account,
|
|
99
|
+
env: service,
|
|
100
|
+
message: `Secret not found: ${service}/${account}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return result.stdout.trim();
|
|
104
|
+
}),
|
|
105
|
+
remove: Effect.fn("WindowsCredentialManagerAccess.remove")(function* (service, account) {
|
|
106
|
+
const target = escapePS(targetName(service, account));
|
|
107
|
+
const script = `cmdkey /delete:'${target}'`;
|
|
108
|
+
const result = yield* runPowerShell(script);
|
|
109
|
+
if (result.exitCode !== 0) {
|
|
110
|
+
return yield* new KeychainError({
|
|
111
|
+
command: "cmdkey /delete",
|
|
112
|
+
stderr: result.stderr || result.stdout,
|
|
113
|
+
message: `Failed to remove credential: ${service}/${account}`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
export const WindowsCredentialManagerAccessLive = Layer.succeed(KeychainAccess, make);
|
package/dist/main.js
CHANGED
|
@@ -11,10 +11,11 @@ import { searchCommand } from "./cli/search.js";
|
|
|
11
11
|
import { listCommand } from "./cli/list.js";
|
|
12
12
|
import { runCommand } from "./cli/run.js";
|
|
13
13
|
import { envFileCommand } from "./cli/env-file.js";
|
|
14
|
+
import { loadCommand } from "./cli/load.js";
|
|
14
15
|
import { SecretStore } from "./services/SecretStore.js";
|
|
15
16
|
const require = createRequire(import.meta.url);
|
|
16
17
|
const pkg = require("../package.json");
|
|
17
|
-
const command = rootCommand.pipe(Command.withSubcommands([addCommand, getCommand, deleteCommand, delCommand, searchCommand, listCommand, runCommand, envFileCommand]));
|
|
18
|
+
const command = rootCommand.pipe(Command.withSubcommands([addCommand, getCommand, deleteCommand, delCommand, searchCommand, listCommand, runCommand, envFileCommand, loadCommand]));
|
|
18
19
|
const cli = Command.run(command, {
|
|
19
20
|
name: "secenv",
|
|
20
21
|
version: pkg.version,
|
|
@@ -2,11 +2,11 @@ import { Effect } from "effect";
|
|
|
2
2
|
import { KeychainAccess } from "./KeychainAccess.js";
|
|
3
3
|
import { MetadataStore } from "./MetadataStore.js";
|
|
4
4
|
import * as SecretKey from "../domain/SecretKey.js";
|
|
5
|
-
import {
|
|
5
|
+
import { PlatformKeychainAccessLive } from "../implementations/PlatformKeychainAccess.js";
|
|
6
6
|
import { SqliteMetadataStoreLive } from "../implementations/SqliteMetadataStore.js";
|
|
7
7
|
export class SecretStore extends Effect.Service()("SecretStore", {
|
|
8
8
|
accessors: true,
|
|
9
|
-
dependencies: [
|
|
9
|
+
dependencies: [PlatformKeychainAccessLive, SqliteMetadataStoreLive],
|
|
10
10
|
effect: Effect.gen(function* () {
|
|
11
11
|
const keychain = yield* KeychainAccess;
|
|
12
12
|
const metadata = yield* MetadataStore;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envsec",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Secure environment secrets management using
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "Secure environment secrets management using native OS credential stores",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"secenv": "./dist/main.js"
|
|
@@ -12,9 +12,6 @@
|
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=18.0.0"
|
|
14
14
|
},
|
|
15
|
-
"os": [
|
|
16
|
-
"darwin"
|
|
17
|
-
],
|
|
18
15
|
"scripts": {
|
|
19
16
|
"build": "tsc",
|
|
20
17
|
"prepublishOnly": "npm run build"
|
|
@@ -23,7 +20,12 @@
|
|
|
23
20
|
"secrets",
|
|
24
21
|
"environment-variables",
|
|
25
22
|
"keychain",
|
|
23
|
+
"credential-manager",
|
|
24
|
+
"libsecret",
|
|
25
|
+
"cross-platform",
|
|
26
26
|
"macos",
|
|
27
|
+
"linux",
|
|
28
|
+
"windows",
|
|
27
29
|
"effect",
|
|
28
30
|
"cli"
|
|
29
31
|
],
|