@keywaysh/cli 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,236 +1,47 @@
1
- <div align="center">
2
- <h1>Keyway CLI</h1>
3
- <p><strong>Sync .env files across your team in 30 seconds.</strong></p>
4
- <p>Stop sharing secrets on Slack. One command and everyone's in sync.</p>
5
- <br/>
1
+ # @keywaysh/cli
6
2
 
7
- [![npm version](https://img.shields.io/npm/v/@keywaysh/cli.svg?style=flat-square)](https://www.npmjs.com/package/@keywaysh/cli)
8
- [![npm downloads](https://img.shields.io/npm/dm/@keywaysh/cli.svg?style=flat-square)](https://www.npmjs.com/package/@keywaysh/cli)
9
- [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](https://opensource.org/licenses/MIT)
10
- [![.env by Keyway](https://keyway.sh/badge.svg?repo=keywaysh/cli)](https://keyway.sh)
3
+ GitHub-native secrets management. If you have repo access, you get secret access.
11
4
 
12
- <br/>
13
- <a href="https://keyway.sh">Website</a> •
14
- <a href="https://docs.keyway.sh">Docs</a> •
15
- <a href="https://keyway.sh/security">Security</a> •
16
- <a href="https://status.keyway.sh">Status</a>
17
- </div>
5
+ ## Installation
18
6
 
19
- <br/>
20
-
21
- ## Quick Start (30 seconds)
22
-
23
- **1. Setup your project:**
24
7
  ```bash
25
8
  npm install -g @keywaysh/cli
26
- keyway init
27
9
  ```
28
10
 
29
- **2. Teammates pull secrets:**
30
- ```bash
31
- keyway pull
32
- ```
11
+ Or with npx:
33
12
 
34
- **3. Deploy to Vercel:**
35
13
  ```bash
36
- keyway sync vercel
37
- ```
38
-
39
- <br/>
40
-
41
- <details>
42
- <summary><strong>See it in action</strong></summary>
43
-
44
- ```
45
- $ keyway init
46
- ✓ Logged in as @nicolas
47
- ✓ Detected repo: acme/backend
48
- ✓ Vault created
49
- ? Push your .env to the vault? Yes
50
- ✓ 12 secrets encrypted and stored
51
-
52
- $ keyway pull
53
- ✓ .env synced (12 secrets)
14
+ npx @keywaysh/cli pull
54
15
  ```
55
16
 
56
- </details>
57
-
58
- <br/>
59
-
60
- ## Why Keyway?
61
-
62
- Your secrets are probably in:
63
-
64
- - 💬 Slack DMs
65
- - 📝 Notion pages
66
- - 📧 Old emails
67
- - 🤷 The laptop of the dev who left
68
-
69
- **Keyway fixes that.** If you have GitHub repo access → you have secret access. No invites, no dashboards, no YAML.
70
-
71
- <br/>
72
-
73
- ## Install
17
+ ## Quick Start
74
18
 
75
19
  ```bash
76
- # npm
77
- npm install -g @keywaysh/cli
78
-
79
- # pnpm
80
- pnpm add -g @keywaysh/cli
81
-
82
- # yarn
83
- yarn global add @keywaysh/cli
84
-
85
- # bun
86
- bun add -g @keywaysh/cli
87
- ```
88
-
89
- <br/>
90
-
91
- ## Commands
92
-
93
- | Command | What it does |
94
- |---------|--------------|
95
- | `keyway login` | Authenticate with GitHub |
96
- | `keyway init` | Create a vault for this repo |
97
- | `keyway push` | Upload `.env` to vault |
98
- | `keyway pull` | Download secrets to `.env` |
99
- | `keyway sync vercel` | Deploy secrets to Vercel |
100
- | `keyway doctor` | Debug your setup |
101
-
102
- <br/>
20
+ # Sign in with GitHub
21
+ keyway login
103
22
 
104
- ### Push & Pull
23
+ # Initialize vault for current repo
24
+ keyway init
105
25
 
106
- ```bash
107
- # Push your local .env
26
+ # Push secrets from .env
108
27
  keyway push
109
28
 
110
- # Push to a specific environment
111
- keyway push --env production
112
-
113
- # Pull secrets
114
- keyway pull
115
-
116
- # Pull from production
117
- keyway pull --env production
118
- ```
119
-
120
- <br/>
121
-
122
- ### Sync to Providers
123
-
124
- Deploy your secrets to Vercel, Netlify, or Railway with one command:
125
-
126
- ```bash
127
- keyway sync vercel
128
- keyway sync netlify
129
- keyway sync railway
130
- ```
131
-
132
- First time: OAuth flow. After that: instant sync.
133
-
134
- <br/>
135
-
136
- ## Real-World Workflows
137
-
138
- **New dev joins the team:**
139
- ```bash
140
- git clone git@github.com:acme/app.git
141
- cd app
29
+ # Pull secrets to .env
142
30
  keyway pull
143
- npm run dev # Ready to code
144
31
  ```
145
32
 
146
- **Rotate an API key:**
147
- ```bash
148
- # Update .env locally, then:
149
- keyway push
150
- # Done. Everyone pulls the update.
151
- ```
152
-
153
- **Deploy to production:**
154
- ```bash
155
- keyway push --env production
156
- keyway sync vercel
157
- ```
158
-
159
- <br/>
160
-
161
- ## Security
162
-
163
- Keyway is a **major upgrade from Slack** without the complexity of Vault or AWS Secrets Manager.
33
+ ## Alternative Installation
164
34
 
165
- | Feature | Status |
166
- |---------|--------|
167
- | AES-256-GCM encryption | ✅ |
168
- | Encrypted at rest | ✅ |
169
- | TLS 1.3 everywhere | ✅ |
170
- | GitHub-based access control | ✅ |
171
- | No access to your code | ✅ |
172
- | SOC2 / enterprise compliance | ❌ |
173
-
174
- **We never track secret values.** Only metadata (command usage, repo names).
175
-
176
- → [Full security details](https://keyway.sh/security)
177
-
178
- <br/>
179
-
180
- ## Who is this for?
181
-
182
- **Perfect for:**
183
- - Solo devs
184
- - Small teams (2-20)
185
- - Side projects
186
- - Agencies
187
- - Early-stage startups
188
-
189
- **Not for:**
190
- - Banks, governments, enterprises needing SOC2/zero-trust
191
- - (Try Vault, Doppler, or AWS Secrets Manager)
192
-
193
- <br/>
194
-
195
- ## Configuration
35
+ Using the install script:
196
36
 
197
37
  ```bash
198
- # Use a different API
199
- export KEYWAY_API_URL=https://your-api.com
200
-
201
- # Disable telemetry
202
- export KEYWAY_DISABLE_TELEMETRY=1
38
+ curl -fsSL https://keyway.sh/install.sh | sh
203
39
  ```
204
40
 
205
- <br/>
41
+ ## Documentation
206
42
 
207
- ## Troubleshooting
208
-
209
- | Error | Fix |
210
- |-------|-----|
211
- | "Not in a git repository" | Run `git init && git remote add origin ...` |
212
- | "Vault not found" | Run `keyway init` |
213
- | "No access to repository" | Check you're a GitHub collaborator |
214
-
215
- Run `keyway doctor` for full diagnostics.
216
-
217
- <br/>
218
-
219
- ## Links
220
-
221
- - **Website:** [keyway.sh](https://keyway.sh)
222
- - **Docs:** [docs.keyway.sh](https://docs.keyway.sh)
223
- - **Issues:** [GitHub Issues](https://github.com/keywaysh/cli/issues)
224
- - **Email:** hello@keyway.sh
225
-
226
- <br/>
43
+ Visit [docs.keyway.sh](https://docs.keyway.sh) for full documentation.
227
44
 
228
45
  ## License
229
46
 
230
- MIT © [Nicolas Ritouet](https://github.com/nicolasritouet)
231
-
232
- ---
233
-
234
- <div align="center">
235
- <sub>Built with ☕ by <a href="https://keyway.sh">Keyway</a></sub>
236
- </div>
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,69 +1,33 @@
1
1
  {
2
2
  "name": "@keywaysh/cli",
3
- "version": "0.2.0",
4
- "description": "Env vars that sync like code.",
5
- "type": "module",
3
+ "version": "0.3.2",
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
- "@clack/prompts": "^0.9.1"
57
- },
58
- "devDependencies": {
59
- "@types/balanced-match": "^3.0.2",
60
- "@types/libsodium-wrappers": "^0.7.14",
61
- "@types/node": "^24.2.0",
62
- "@vitest/coverage-v8": "^3.0.0",
63
- "msw": "^2.12.4",
64
- "tsup": "^8.5.0",
65
- "tsx": "^4.20.3",
66
- "typescript": "^5.9.2",
67
- "vitest": "^3.2.4"
31
+ "node": ">=14"
68
32
  }
69
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 };