@keywaysh/cli 0.1.16 → 0.3.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/README.md CHANGED
@@ -1,322 +1,47 @@
1
- <div align="center">
2
- <h1>Keyway CLI</h1>
3
- <strong>The simplest way to sync your project's environment variables.</strong><br/>
4
- Stop sending <code>.env</code> files on Slack. One command and you're in sync.
5
- <br/><br/>
6
- <a href="https://keyway.sh">keyway.sh</a> ·
7
- <a href="https://github.com/keywaysh/cli">GitHub</a> ·
8
- <a href="https://www.npmjs.com/package/@keywaysh/cli">NPM</a> ·
9
- <a href="https://status.keyway.sh">Status</a>
10
- <br/><br/>
1
+ # @keywaysh/cli
11
2
 
12
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
13
- [![npm version](https://img.shields.io/npm/v/@keywaysh/cli.svg)](https://www.npmjs.com/package/@keywaysh/cli)
3
+ GitHub-native secrets management. If you have repo access, you get secret access.
14
4
 
15
- </div>
16
-
17
- ## Why Keyway?
18
-
19
- Most devs store secrets in... chaotic places:
20
-
21
- - Slack
22
- - Notion
23
- - Discord
24
- - Google Docs
25
- - Lost `.env` files
26
- - Messages you can't find anymore
27
- - Machine of the dev who left the project
28
-
29
- **Keyway fixes that.**
30
-
31
- If you have GitHub access to a repo → you have access to its secrets.
32
- No invites. No dashboards. No complex config.
33
- Just one command that works.
34
-
35
- ## Install
5
+ ## Installation
36
6
 
37
7
  ```bash
38
8
  npm install -g @keywaysh/cli
39
9
  ```
40
10
 
41
- ## Quick Start
42
-
43
- Inside any project connected to GitHub:
11
+ Or with npx:
44
12
 
45
13
  ```bash
46
- keyway login
47
- keyway init
14
+ npx @keywaysh/cli pull
48
15
  ```
49
16
 
50
- What happens:
51
-
52
- 1. Keyway authenticates via GitHub OAuth
53
- 2. Detects your GitHub repo
54
- 3. Creates a vault for this repo
55
- 4. Asks if you want to sync your `.env`
56
-
57
- Then any teammate can simply run:
58
-
59
- ```bash
60
- keyway pull
61
- ```
62
-
63
- And boom: your `.env` is recreated locally.
64
-
65
- ## Commands
66
-
67
- ### `keyway login`
68
-
69
- Authenticate with GitHub through the Keyway OAuth/device flow.
17
+ ## Quick Start
70
18
 
71
19
  ```bash
20
+ # Sign in with GitHub
72
21
  keyway login
73
- ```
74
22
 
75
- If you prefer using a fine-grained PAT:
76
-
77
- ```bash
78
- keyway login --token
79
- ```
80
-
81
- ### `keyway init`
82
-
83
- Initialize a vault for the current repository.
84
-
85
- ```bash
23
+ # Initialize vault for current repo
86
24
  keyway init
87
- ```
88
-
89
- Creates a vault, pushes your `.env`, and sets everything up.
90
25
 
91
- ### `keyway push`
92
-
93
- Push your local `.env` to the vault.
94
-
95
- ```bash
96
- # Push to development (default)
26
+ # Push secrets from .env
97
27
  keyway push
98
28
 
99
- # Push to a specific environment
100
- keyway push --env production
101
-
102
- # Push a different file
103
- keyway push --file .env.staging --env staging
104
- ```
105
-
106
- Useful when:
107
- - you added a new variable
108
- - you rotated a key
109
- - you fixed a staging/production mismatch
110
-
111
- ### `keyway pull`
112
-
113
- Pull secrets from the vault and write them to `.env`.
114
-
115
- ```bash
116
- # Pull development environment (default)
117
- keyway pull
118
-
119
- # Pull from a specific environment
120
- keyway pull --env production
121
-
122
- # Pull to a different file
123
- keyway pull --file .env.local
124
- ```
125
-
126
- Perfect for:
127
- - onboarding a new dev
128
- - syncing your local environment
129
- - switching between machines
130
-
131
- ### `keyway doctor`
132
-
133
- Diagnostic command to check your setup.
134
-
135
- ```bash
136
- keyway doctor
137
-
138
- # Output as JSON (for CI/CD)
139
- keyway doctor --json
140
-
141
- # Strict mode (treat warnings as failures)
142
- keyway doctor --strict
143
- ```
144
-
145
- **Checks performed:**
146
- - Node.js version (≥18.0.0 required)
147
- - Git installation and repository status
148
- - API connectivity
149
- - File system write permissions
150
- - `.gitignore` configuration
151
- - System clock synchronization
152
-
153
- ### `keyway logout`
154
-
155
- Clear stored Keyway credentials.
156
-
157
- ```bash
158
- keyway logout
159
- ```
160
-
161
- ### `keyway sync`
162
-
163
- **One command to deploy your secrets everywhere.**
164
-
165
- Sync your vault to Vercel, Netlify, and more. No more copy-pasting environment variables between dashboards.
166
-
167
- ```bash
168
- keyway sync vercel
169
- keyway sync netlify
170
- keyway sync railway
171
- ```
172
-
173
- The CLI handles everything:
174
- 1. OAuth connection (first time only)
175
- 2. Project auto-detection
176
- 3. Smart diff (only updates what changed)
177
-
178
- Your local `.env`, Vercel, Netlify, Railway — all in sync with one command.
179
-
180
- ## Security
181
-
182
- Keyway is designed to be **simple and secure** — a major upgrade from Slack or Notion, without the complexity of Hashicorp Vault or AWS Secrets Manager.
183
-
184
- **What we do:**
185
- - AES-256-GCM encryption server-side and client-side token storage
186
- - TLS everywhere (HTTPS enforced)
187
- - GitHub read-only permissions
188
- - No access to your code
189
- - Secrets stored encrypted at rest
190
- - No analytics on secret values (only metadata)
191
- - Encrypted token storage with file permissions
192
-
193
- **What we don't do:**
194
- - No zero-trust enterprise model
195
- - No access to your cloud infrastructure
196
- - No access to your production deployment keys
197
-
198
- For detailed security information, see [SECURITY.md](./SECURITY.md) and [keyway.sh/security](https://keyway.sh/security)
199
-
200
- ## Who is this for?
201
-
202
- Keyway is perfect for:
203
- - Solo developers
204
- - Small teams
205
- - Side-projects
206
- - Early SaaS
207
- - Agencies managing many repos
208
- - Rapid prototyping
209
-
210
- **Not designed for:**
211
- - Banks
212
- - Governments
213
- - Enterprise zero-trust teams
214
- *(you're looking for Vault, Doppler, or AWS Secrets Manager)*
215
-
216
- ## Example Workflow
217
-
218
- ```bash
219
- git clone git@github.com:acme/backend.git
220
- cd backend
29
+ # Pull secrets to .env
221
30
  keyway pull
222
- # ✓ secrets pulled
223
- npm run dev
224
- ```
225
-
226
- Add a new secret:
227
-
228
- ```bash
229
- echo "STRIPE_KEY=sk_live_xxx" >> .env
230
- keyway push
231
- # ✓ 1 secret updated
232
31
  ```
233
32
 
234
- ## Configuration
33
+ ## Alternative Installation
235
34
 
236
- ### GitHub Token (alternative to login)
237
-
238
- If you cannot use the login flow, set a GitHub token manually:
35
+ Using the install script:
239
36
 
240
37
  ```bash
241
- # Environment variable
242
- export GITHUB_TOKEN=your_github_personal_access_token
243
-
244
- # Or via git config
245
- git config --global github.token your_github_personal_access_token
38
+ curl -fsSL https://keyway.sh/install.sh | sh
246
39
  ```
247
40
 
248
- ### API URL
249
-
250
- By default, Keyway uses the production API. To point to another API:
251
-
252
- ```bash
253
- export KEYWAY_API_URL=http://localhost:3000
254
- ```
255
-
256
- ### Disable Telemetry
257
-
258
- ```bash
259
- export KEYWAY_DISABLE_TELEMETRY=1
260
- ```
261
-
262
- ## Privacy & Analytics
263
-
264
- **NEVER tracked:**
265
- - Secret names or values
266
- - Environment variable content
267
- - Access tokens
268
- - File contents
269
-
270
- **Only tracked:**
271
- - Command usage (init, push, pull)
272
- - Repository names (public info)
273
- - Error messages (sanitized)
274
-
275
- ## Troubleshooting
276
-
277
- ### "Not in a git repository"
278
-
279
- ```bash
280
- git init
281
- git remote add origin git@github.com:your-org/your-repo.git
282
- ```
283
-
284
- ### "GitHub token not found"
285
-
286
- ```bash
287
- keyway login
288
- # or
289
- export GITHUB_TOKEN=your_token
290
- ```
291
-
292
- ### "Vault not found"
293
-
294
- ```bash
295
- keyway init
296
- ```
297
-
298
- ### "You do not have access to this repository"
299
-
300
- Make sure you're a collaborator or admin on the GitHub repository.
301
-
302
- ## TL;DR
303
-
304
- ```bash
305
- npm i -g @keywaysh/cli
306
- keyway login
307
- keyway init
308
- keyway pull
309
- ```
310
-
311
- No more Slack. No more outdated `.env`.
312
- Your team stays perfectly in sync.
313
-
314
- ## Support
41
+ ## Documentation
315
42
 
316
- - **Issues**: [github.com/keywaysh/cli/issues](https://github.com/keywaysh/cli/issues)
317
- - **Email**: hello@keyway.sh
318
- - **Website**: [keyway.sh](https://keyway.sh)
43
+ Visit [docs.keyway.sh](https://docs.keyway.sh) for full documentation.
319
44
 
320
45
  ## License
321
46
 
322
- MIT © Nicolas Ritouet
47
+ MIT
package/bin/keyway ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require("child_process");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+
7
+ const binaryName = process.platform === "win32" ? "keyway-bin.exe" : "keyway-bin";
8
+ const binaryPath = path.join(__dirname, binaryName);
9
+
10
+ if (!fs.existsSync(binaryPath)) {
11
+ console.error("keyway binary not found. Try reinstalling: npm install @keywaysh/cli");
12
+ process.exit(1);
13
+ }
14
+
15
+ const result = spawnSync(binaryPath, process.argv.slice(2), {
16
+ stdio: "inherit",
17
+ env: process.env,
18
+ });
19
+
20
+ if (result.error) {
21
+ console.error("Failed to run keyway:", result.error.message);
22
+ process.exit(1);
23
+ }
24
+
25
+ process.exit(result.status || 0);
package/package.json CHANGED
@@ -1,70 +1,33 @@
1
1
  {
2
2
  "name": "@keywaysh/cli",
3
- "version": "0.1.16",
4
- "description": "One link to all your secrets",
5
- "type": "module",
3
+ "version": "0.3.1",
4
+ "description": "GitHub-native secrets management CLI",
6
5
  "bin": {
7
- "keyway": "./dist/cli.js"
6
+ "keyway": "./bin/keyway"
8
7
  },
9
- "main": "./dist/cli.js",
10
- "files": [
11
- "dist"
12
- ],
13
8
  "scripts": {
14
- "dev": "pnpm exec tsx src/cli.ts",
15
- "dev:local": "NODE_TLS_REJECT_UNAUTHORIZED=0 KEYWAY_API_URL=https://localhost/api pnpm exec tsx src/cli.ts",
16
- "build": "pnpm exec tsup",
17
- "build:watch": "pnpm exec tsup --watch",
18
- "prepublishOnly": "pnpm run build",
19
- "test": "pnpm exec vitest run",
20
- "test:watch": "pnpm exec vitest",
21
- "test:coverage": "pnpm exec vitest run --coverage",
22
- "release": "npm version patch && git push && git push --tags",
23
- "release:minor": "npm version minor && git push && git push --tags",
24
- "release:major": "npm version major && git push && git push --tags"
9
+ "postinstall": "node scripts/install.js"
25
10
  },
26
- "keywords": [
27
- "secrets",
28
- "env",
29
- "keyway",
30
- "cli",
31
- "devops"
32
- ],
33
- "author": "Nicolas Ritouet",
34
- "license": "MIT",
35
- "homepage": "https://keyway.sh",
36
11
  "repository": {
37
12
  "type": "git",
38
- "url": "https://github.com/keywaysh/cli.git"
13
+ "url": "git+https://github.com/keywaysh/cli-go.git"
39
14
  },
15
+ "homepage": "https://keyway.sh",
40
16
  "bugs": {
41
- "url": "https://github.com/keywaysh/cli/issues"
17
+ "url": "https://github.com/keywaysh/cli-go/issues"
42
18
  },
43
- "packageManager": "pnpm@10.6.1",
19
+ "keywords": [
20
+ "secrets",
21
+ "cli",
22
+ "github",
23
+ "env",
24
+ "environment-variables",
25
+ "dotenv",
26
+ "secrets-management"
27
+ ],
28
+ "author": "Keyway <hello@keyway.sh>",
29
+ "license": "MIT",
44
30
  "engines": {
45
- "node": ">=18.0.0"
46
- },
47
- "dependencies": {
48
- "@octokit/rest": "^22.0.1",
49
- "balanced-match": "^3.0.1",
50
- "commander": "^14.0.0",
51
- "conf": "^15.0.2",
52
- "libsodium-wrappers": "^0.7.15",
53
- "open": "^11.0.0",
54
- "picocolors": "^1.1.1",
55
- "posthog-node": "^3.5.0",
56
- "prompts": "^2.4.2"
57
- },
58
- "devDependencies": {
59
- "@types/balanced-match": "^3.0.2",
60
- "@types/libsodium-wrappers": "^0.7.14",
61
- "@types/node": "^24.2.0",
62
- "@types/prompts": "^2.4.9",
63
- "@vitest/coverage-v8": "^3.0.0",
64
- "msw": "^2.12.4",
65
- "tsup": "^8.5.0",
66
- "tsx": "^4.20.3",
67
- "typescript": "^5.9.2",
68
- "vitest": "^3.2.4"
31
+ "node": ">=14"
69
32
  }
70
33
  }
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+
3
+ const https = require("https");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const { execSync, spawnSync } = require("child_process");
7
+ const os = require("os");
8
+ const crypto = require("crypto");
9
+
10
+ const VERSION = require("../package.json").version;
11
+ const REPO = "keywaysh/cli-go";
12
+ const BINARY_NAME = "keyway";
13
+
14
+ const PLATFORMS = {
15
+ "darwin-arm64": "darwin_arm64",
16
+ "darwin-x64": "darwin_amd64",
17
+ "linux-arm64": "linux_arm64",
18
+ "linux-x64": "linux_amd64",
19
+ "win32-x64": "windows_amd64",
20
+ "win32-arm64": "windows_arm64",
21
+ };
22
+
23
+ function getPlatformKey() {
24
+ return `${process.platform}-${process.arch}`;
25
+ }
26
+
27
+ function getBinaryPath() {
28
+ const binDir = path.join(__dirname, "..", "bin");
29
+ // Use keyway-bin to avoid conflict with the wrapper script
30
+ const binaryName = process.platform === "win32" ? `${BINARY_NAME}-bin.exe` : `${BINARY_NAME}-bin`;
31
+ return path.join(binDir, binaryName);
32
+ }
33
+
34
+ function fetch(url) {
35
+ return new Promise((resolve, reject) => {
36
+ const request = https.get(url, (response) => {
37
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
38
+ // Follow redirect
39
+ fetch(response.headers.location).then(resolve).catch(reject);
40
+ return;
41
+ }
42
+
43
+ if (response.statusCode !== 200) {
44
+ reject(new Error(`Failed to download: ${response.statusCode}`));
45
+ return;
46
+ }
47
+
48
+ const chunks = [];
49
+ response.on("data", (chunk) => chunks.push(chunk));
50
+ response.on("end", () => resolve(Buffer.concat(chunks)));
51
+ response.on("error", reject);
52
+ });
53
+
54
+ request.on("error", reject);
55
+ request.setTimeout(60000, () => {
56
+ request.destroy();
57
+ reject(new Error("Request timeout"));
58
+ });
59
+ });
60
+ }
61
+
62
+ async function downloadChecksum() {
63
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/checksums.txt`;
64
+ try {
65
+ const data = await fetch(url);
66
+ return data.toString();
67
+ } catch (error) {
68
+ console.warn("Warning: Could not download checksums file");
69
+ return null;
70
+ }
71
+ }
72
+
73
+ function verifyChecksum(buffer, checksums, filename) {
74
+ if (!checksums) return true;
75
+
76
+ const hash = crypto.createHash("sha256").update(buffer).digest("hex");
77
+ const expectedLine = checksums.split("\n").find((line) => line.includes(filename));
78
+
79
+ if (!expectedLine) {
80
+ console.warn(`Warning: No checksum found for ${filename}`);
81
+ return true;
82
+ }
83
+
84
+ const expectedHash = expectedLine.split(/\s+/)[0];
85
+ if (hash !== expectedHash) {
86
+ throw new Error(`Checksum mismatch for ${filename}\nExpected: ${expectedHash}\nGot: ${hash}`);
87
+ }
88
+
89
+ return true;
90
+ }
91
+
92
+ async function extractTarGz(buffer, destDir) {
93
+ const tmpFile = path.join(os.tmpdir(), `keyway-${Date.now()}.tar.gz`);
94
+ fs.writeFileSync(tmpFile, buffer);
95
+
96
+ try {
97
+ execSync(`tar -xzf "${tmpFile}" -C "${destDir}"`, { stdio: "pipe" });
98
+ } finally {
99
+ fs.unlinkSync(tmpFile);
100
+ }
101
+ }
102
+
103
+ async function extractZip(buffer, destDir) {
104
+ const tmpFile = path.join(os.tmpdir(), `keyway-${Date.now()}.zip`);
105
+ fs.writeFileSync(tmpFile, buffer);
106
+
107
+ try {
108
+ // Try powershell on Windows
109
+ if (process.platform === "win32") {
110
+ execSync(
111
+ `powershell -Command "Expand-Archive -Path '${tmpFile}' -DestinationPath '${destDir}' -Force"`,
112
+ { stdio: "pipe" }
113
+ );
114
+ } else {
115
+ execSync(`unzip -o "${tmpFile}" -d "${destDir}"`, { stdio: "pipe" });
116
+ }
117
+ } finally {
118
+ fs.unlinkSync(tmpFile);
119
+ }
120
+ }
121
+
122
+ async function install() {
123
+ const platformKey = getPlatformKey();
124
+ const target = PLATFORMS[platformKey];
125
+
126
+ if (!target) {
127
+ console.error(`\nUnsupported platform: ${platformKey}`);
128
+ console.error(`Supported platforms: ${Object.keys(PLATFORMS).join(", ")}`);
129
+ console.error(`\nYou can install manually from: https://github.com/${REPO}/releases`);
130
+ process.exit(1);
131
+ }
132
+
133
+ const binDir = path.join(__dirname, "..", "bin");
134
+ const binaryPath = getBinaryPath();
135
+
136
+ // Check if binary already exists and is correct version
137
+ if (fs.existsSync(binaryPath)) {
138
+ try {
139
+ const result = spawnSync(binaryPath, ["--version"], { encoding: "utf8" });
140
+ if (result.stdout && result.stdout.includes(VERSION)) {
141
+ console.log(`keyway v${VERSION} already installed`);
142
+ return;
143
+ }
144
+ } catch (e) {
145
+ // Binary exists but couldn't run, reinstall
146
+ }
147
+ }
148
+
149
+ const isWindows = process.platform === "win32";
150
+ const ext = isWindows ? "zip" : "tar.gz";
151
+ const filename = `${BINARY_NAME}_${VERSION}_${target}.${ext}`;
152
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${filename}`;
153
+
154
+ console.log(`Downloading keyway v${VERSION} for ${target}...`);
155
+
156
+ try {
157
+ // Download checksums and binary in parallel
158
+ const [checksums, archiveBuffer] = await Promise.all([downloadChecksum(), fetch(url)]);
159
+
160
+ // Verify checksum
161
+ verifyChecksum(archiveBuffer, checksums, filename);
162
+
163
+ // Create bin directory
164
+ if (!fs.existsSync(binDir)) {
165
+ fs.mkdirSync(binDir, { recursive: true });
166
+ }
167
+
168
+ // Extract to temp dir first, then move binary
169
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "keyway-"));
170
+
171
+ try {
172
+ if (isWindows) {
173
+ await extractZip(archiveBuffer, tmpDir);
174
+ } else {
175
+ await extractTarGz(archiveBuffer, tmpDir);
176
+ }
177
+
178
+ // Find and move the binary (archive contains 'keyway', we save as 'keyway-bin')
179
+ const archiveBinaryName = isWindows ? `${BINARY_NAME}.exe` : BINARY_NAME;
180
+ const extractedBinary = path.join(tmpDir, archiveBinaryName);
181
+
182
+ if (!fs.existsSync(extractedBinary)) {
183
+ throw new Error(`Binary not found in archive: ${archiveBinaryName}`);
184
+ }
185
+
186
+ // Remove old binary if exists
187
+ if (fs.existsSync(binaryPath)) {
188
+ fs.unlinkSync(binaryPath);
189
+ }
190
+
191
+ fs.copyFileSync(extractedBinary, binaryPath);
192
+
193
+ // Make executable on Unix
194
+ if (!isWindows) {
195
+ fs.chmodSync(binaryPath, 0o755);
196
+ }
197
+
198
+ console.log(`Successfully installed keyway v${VERSION}`);
199
+ } finally {
200
+ // Cleanup temp directory
201
+ fs.rmSync(tmpDir, { recursive: true, force: true });
202
+ }
203
+ } catch (error) {
204
+ console.error(`\nFailed to install keyway: ${error.message}`);
205
+ console.error(`\nYou can install manually from: https://github.com/${REPO}/releases`);
206
+ console.error(`Or use: curl -fsSL https://keyway.sh/install.sh | sh`);
207
+ process.exit(1);
208
+ }
209
+ }
210
+
211
+ // Only run if called directly (not required as module)
212
+ if (require.main === module) {
213
+ install();
214
+ }
215
+
216
+ module.exports = { install, getBinaryPath };