@keywaysh/cli 0.0.2 → 0.0.4
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 +138 -164
- package/dist/cli.js +82 -50
- package/package.json +15 -11
package/README.md
CHANGED
|
@@ -1,60 +1,82 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
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
|
+
<br/><br/>
|
|
4
10
|
|
|
5
11
|
[](https://opensource.org/licenses/MIT)
|
|
6
12
|
[](https://www.npmjs.com/package/@keywaysh/cli)
|
|
7
13
|
|
|
8
|
-
|
|
14
|
+
</div>
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
## Why Keyway?
|
|
17
|
+
|
|
18
|
+
Most devs store secrets in... chaotic places:
|
|
19
|
+
|
|
20
|
+
- Slack
|
|
21
|
+
- Notion
|
|
22
|
+
- Discord
|
|
23
|
+
- Google Docs
|
|
24
|
+
- Lost `.env` files
|
|
25
|
+
- Messages you can't find anymore
|
|
26
|
+
- Machine of the dev who left the project
|
|
13
27
|
|
|
14
|
-
|
|
28
|
+
**Keyway fixes that.**
|
|
29
|
+
|
|
30
|
+
If you have GitHub access to a repo → you have access to its secrets.
|
|
31
|
+
No invites. No dashboards. No complex config.
|
|
32
|
+
Just one command that works.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
15
35
|
|
|
16
36
|
```bash
|
|
17
|
-
|
|
37
|
+
npm install -g @keywaysh/cli
|
|
18
38
|
```
|
|
19
39
|
|
|
20
40
|
## Quick Start
|
|
21
41
|
|
|
42
|
+
Inside any project connected to GitHub:
|
|
43
|
+
|
|
22
44
|
```bash
|
|
23
|
-
# 0. Authenticate once (browser/device flow)
|
|
24
45
|
keyway login
|
|
25
|
-
|
|
26
|
-
# 1. Initialize a vault for your repository
|
|
27
46
|
keyway init
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
What happens:
|
|
28
50
|
|
|
29
|
-
|
|
51
|
+
1. Keyway authenticates via GitHub OAuth
|
|
52
|
+
2. Detects your GitHub repo
|
|
53
|
+
3. Creates a vault for this repo
|
|
54
|
+
4. Asks if you want to sync your `.env`
|
|
30
55
|
|
|
31
|
-
|
|
32
|
-
keyway push --file .env
|
|
56
|
+
Then any teammate can simply run:
|
|
33
57
|
|
|
34
|
-
|
|
35
|
-
keyway pull
|
|
58
|
+
```bash
|
|
59
|
+
keyway pull
|
|
36
60
|
```
|
|
37
61
|
|
|
62
|
+
And boom: your `.env` is recreated locally.
|
|
63
|
+
|
|
38
64
|
## Commands
|
|
39
65
|
|
|
40
66
|
### `keyway login`
|
|
41
67
|
|
|
42
|
-
Authenticate with GitHub through the Keyway OAuth/device flow
|
|
68
|
+
Authenticate with GitHub through the Keyway OAuth/device flow.
|
|
43
69
|
|
|
44
70
|
```bash
|
|
45
71
|
keyway login
|
|
46
72
|
```
|
|
47
73
|
|
|
48
|
-
If you
|
|
49
|
-
|
|
50
|
-
Fine-grained PAT alternative:
|
|
74
|
+
If you prefer using a fine-grained PAT:
|
|
51
75
|
|
|
52
76
|
```bash
|
|
53
77
|
keyway login --token
|
|
54
78
|
```
|
|
55
79
|
|
|
56
|
-
This opens GitHub to create a repo-scoped fine-grained PAT (metadata: read-only, no account permissions). Paste the `github_pat_...` token when prompted; the CLI validates and stores it.
|
|
57
|
-
|
|
58
80
|
### `keyway init`
|
|
59
81
|
|
|
60
82
|
Initialize a vault for the current repository.
|
|
@@ -63,18 +85,15 @@ Initialize a vault for the current repository.
|
|
|
63
85
|
keyway init
|
|
64
86
|
```
|
|
65
87
|
|
|
66
|
-
|
|
67
|
-
- Must be in a git repository
|
|
68
|
-
- Repository must have a GitHub remote
|
|
69
|
-
- Authenticated via `keyway login` (or provide `GITHUB_TOKEN`)
|
|
88
|
+
Creates a vault, pushes your `.env`, and sets everything up.
|
|
70
89
|
|
|
71
90
|
### `keyway push`
|
|
72
91
|
|
|
73
|
-
|
|
92
|
+
Push your local `.env` to the vault.
|
|
74
93
|
|
|
75
94
|
```bash
|
|
76
|
-
# Push
|
|
77
|
-
keyway push
|
|
95
|
+
# Push to development (default)
|
|
96
|
+
keyway push
|
|
78
97
|
|
|
79
98
|
# Push to a specific environment
|
|
80
99
|
keyway push --env production
|
|
@@ -83,35 +102,36 @@ keyway push --env production
|
|
|
83
102
|
keyway push --file .env.staging --env staging
|
|
84
103
|
```
|
|
85
104
|
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
105
|
+
Useful when:
|
|
106
|
+
- you added a new variable
|
|
107
|
+
- you rotated a key
|
|
108
|
+
- you fixed a staging/production mismatch
|
|
89
109
|
|
|
90
110
|
### `keyway pull`
|
|
91
111
|
|
|
92
|
-
|
|
112
|
+
Pull secrets from the vault and write them to `.env`.
|
|
93
113
|
|
|
94
114
|
```bash
|
|
95
|
-
# Pull development environment
|
|
96
|
-
keyway pull
|
|
115
|
+
# Pull development environment (default)
|
|
116
|
+
keyway pull
|
|
97
117
|
|
|
98
118
|
# Pull from a specific environment
|
|
99
119
|
keyway pull --env production
|
|
100
120
|
|
|
101
121
|
# Pull to a different file
|
|
102
|
-
keyway pull --file .env.local
|
|
122
|
+
keyway pull --file .env.local
|
|
103
123
|
```
|
|
104
124
|
|
|
105
|
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
125
|
+
Perfect for:
|
|
126
|
+
- onboarding a new dev
|
|
127
|
+
- syncing your local environment
|
|
128
|
+
- switching between machines
|
|
108
129
|
|
|
109
130
|
### `keyway doctor`
|
|
110
131
|
|
|
111
|
-
|
|
132
|
+
Diagnostic command to check your setup.
|
|
112
133
|
|
|
113
134
|
```bash
|
|
114
|
-
# Run all checks
|
|
115
135
|
keyway doctor
|
|
116
136
|
|
|
117
137
|
# Output as JSON (for CI/CD)
|
|
@@ -122,132 +142,106 @@ keyway doctor --strict
|
|
|
122
142
|
```
|
|
123
143
|
|
|
124
144
|
**Checks performed:**
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
|
|
131
|
-
## Configuration
|
|
145
|
+
- Node.js version (≥18.0.0 required)
|
|
146
|
+
- Git installation and repository status
|
|
147
|
+
- API connectivity
|
|
148
|
+
- File system write permissions
|
|
149
|
+
- `.gitignore` configuration
|
|
150
|
+
- System clock synchronization
|
|
132
151
|
|
|
133
|
-
###
|
|
152
|
+
### `keyway logout`
|
|
134
153
|
|
|
135
|
-
|
|
154
|
+
Clear stored Keyway credentials.
|
|
136
155
|
|
|
137
156
|
```bash
|
|
138
|
-
keyway
|
|
157
|
+
keyway logout
|
|
139
158
|
```
|
|
140
159
|
|
|
141
|
-
|
|
160
|
+
## Security
|
|
142
161
|
|
|
143
|
-
|
|
162
|
+
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.
|
|
144
163
|
|
|
145
|
-
**
|
|
164
|
+
**What we do:**
|
|
165
|
+
- AES-256-GCM encryption server-side
|
|
166
|
+
- TLS everywhere
|
|
167
|
+
- GitHub read-only permissions
|
|
168
|
+
- No access to your code
|
|
169
|
+
- Secrets stored encrypted at rest
|
|
170
|
+
- No analytics on secret values (only metadata)
|
|
146
171
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
172
|
+
**What we don't do:**
|
|
173
|
+
- No zero-trust enterprise model
|
|
174
|
+
- No access to your cloud infrastructure
|
|
175
|
+
- No access to your production deployment keys
|
|
150
176
|
|
|
151
|
-
|
|
177
|
+
More details: [keyway.sh/security](https://keyway.sh/security)
|
|
152
178
|
|
|
153
|
-
|
|
154
|
-
git config --global github.token your_github_personal_access_token
|
|
155
|
-
```
|
|
179
|
+
## Who is this for?
|
|
156
180
|
|
|
157
|
-
|
|
181
|
+
Keyway is perfect for:
|
|
182
|
+
- Solo developers
|
|
183
|
+
- Small teams
|
|
184
|
+
- Side-projects
|
|
185
|
+
- Early SaaS
|
|
186
|
+
- Agencies managing many repos
|
|
187
|
+
- Rapid prototyping
|
|
158
188
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
189
|
+
**Not designed for:**
|
|
190
|
+
- Banks
|
|
191
|
+
- Governments
|
|
192
|
+
- Enterprise zero-trust teams
|
|
193
|
+
*(you're looking for Vault, Doppler, or AWS Secrets Manager)*
|
|
163
194
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
By default, Keyway uses the production API at `https://keyway-backend-production.up.railway.app`. To point to another API:
|
|
195
|
+
## Example Workflow
|
|
167
196
|
|
|
168
197
|
```bash
|
|
169
|
-
|
|
198
|
+
git clone git@github.com:acme/backend.git
|
|
199
|
+
cd backend
|
|
200
|
+
keyway pull
|
|
201
|
+
# ✓ secrets pulled
|
|
202
|
+
npm run dev
|
|
170
203
|
```
|
|
171
204
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
Keyway uses PostHog for privacy-first analytics. To configure:
|
|
205
|
+
Add a new secret:
|
|
175
206
|
|
|
176
207
|
```bash
|
|
177
|
-
|
|
178
|
-
|
|
208
|
+
echo "STRIPE_KEY=sk_live_xxx" >> .env
|
|
209
|
+
keyway push
|
|
210
|
+
# ✓ 1 secret updated
|
|
179
211
|
```
|
|
180
212
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
export KEYWAY_DISABLE_TELEMETRY=1
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
The CLI ships with built-in analytics defaults; use the env vars above to override for development.
|
|
188
|
-
|
|
189
|
-
**Privacy:** No secret names or values are ever sent to analytics.
|
|
190
|
-
|
|
191
|
-
## How It Works
|
|
213
|
+
## Configuration
|
|
192
214
|
|
|
193
|
-
|
|
194
|
-
2. **Authorization**: Checks if you're a collaborator/admin on the repository
|
|
195
|
-
3. **Encryption**: All secrets are encrypted server-side with AES-256-GCM
|
|
196
|
-
4. **Storage**: Encrypted secrets stored in PostgreSQL
|
|
197
|
-
5. **Retrieval**: Secrets are decrypted and returned only to authorized users
|
|
215
|
+
### GitHub Token (alternative to login)
|
|
198
216
|
|
|
199
|
-
|
|
217
|
+
If you cannot use the login flow, set a GitHub token manually:
|
|
200
218
|
|
|
201
219
|
```bash
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
# Run in dev mode
|
|
206
|
-
npm run dev
|
|
220
|
+
# Environment variable
|
|
221
|
+
export GITHUB_TOKEN=your_github_personal_access_token
|
|
207
222
|
|
|
208
|
-
#
|
|
209
|
-
|
|
223
|
+
# Or via git config
|
|
224
|
+
git config --global github.token your_github_personal_access_token
|
|
225
|
+
```
|
|
210
226
|
|
|
211
|
-
|
|
212
|
-
npm run build:watch
|
|
227
|
+
### API URL
|
|
213
228
|
|
|
214
|
-
|
|
215
|
-
npm test
|
|
229
|
+
By default, Keyway uses the production API. To point to another API:
|
|
216
230
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
keyway --version
|
|
231
|
+
```bash
|
|
232
|
+
export KEYWAY_API_URL=http://localhost:3000
|
|
220
233
|
```
|
|
221
234
|
|
|
222
|
-
|
|
235
|
+
### Disable Telemetry
|
|
223
236
|
|
|
237
|
+
```bash
|
|
238
|
+
export KEYWAY_DISABLE_TELEMETRY=1
|
|
224
239
|
```
|
|
225
|
-
src/
|
|
226
|
-
├── cli.tsx # Main CLI entry point with commander
|
|
227
|
-
├── types.ts # TypeScript types and interfaces
|
|
228
|
-
├── ui/ # Ink React components
|
|
229
|
-
│ ├── Banner.tsx # Startup banner with gradient
|
|
230
|
-
│ └── Spinner.tsx # Loading spinner component
|
|
231
|
-
├── cmds/ # Command implementations
|
|
232
|
-
│ ├── init.ts # Initialize vault
|
|
233
|
-
│ ├── push.ts # Push secrets
|
|
234
|
-
│ ├── pull.ts # Pull secrets
|
|
235
|
-
│ └── doctor.tsx # Environment diagnostics
|
|
236
|
-
├── utils/ # Utility functions
|
|
237
|
-
│ ├── analytics.ts # PostHog integration
|
|
238
|
-
│ ├── api.ts # API client
|
|
239
|
-
│ └── git.ts # Git helpers
|
|
240
|
-
└── core/ # Core business logic
|
|
241
|
-
└── doctor.ts # Doctor checks implementations
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
## Privacy & Security
|
|
245
240
|
|
|
246
|
-
|
|
241
|
+
## Privacy & Analytics
|
|
247
242
|
|
|
248
243
|
**NEVER tracked:**
|
|
249
|
-
- Secret names
|
|
250
|
-
- Secret values
|
|
244
|
+
- Secret names or values
|
|
251
245
|
- Environment variable content
|
|
252
246
|
- Access tokens
|
|
253
247
|
- File contents
|
|
@@ -255,21 +249,13 @@ src/
|
|
|
255
249
|
**Only tracked:**
|
|
256
250
|
- Command usage (init, push, pull)
|
|
257
251
|
- Repository names (public info)
|
|
258
|
-
- Environment names (e.g., "production")
|
|
259
|
-
- Number of variables (count only)
|
|
260
252
|
- Error messages (sanitized)
|
|
261
|
-
- Machine-specific anonymous ID
|
|
262
|
-
|
|
263
|
-
### Distinct ID
|
|
264
|
-
|
|
265
|
-
Each machine has a unique, anonymous identifier stored in `~/.config/keyway/id.json`. This ID is randomly generated and contains no personally identifiable information.
|
|
266
253
|
|
|
267
254
|
## Troubleshooting
|
|
268
255
|
|
|
269
256
|
### "Not in a git repository"
|
|
270
257
|
|
|
271
258
|
```bash
|
|
272
|
-
# Initialize git and add a remote
|
|
273
259
|
git init
|
|
274
260
|
git remote add origin git@github.com:your-org/your-repo.git
|
|
275
261
|
```
|
|
@@ -277,14 +263,14 @@ git remote add origin git@github.com:your-org/your-repo.git
|
|
|
277
263
|
### "GitHub token not found"
|
|
278
264
|
|
|
279
265
|
```bash
|
|
280
|
-
|
|
266
|
+
keyway login
|
|
267
|
+
# or
|
|
281
268
|
export GITHUB_TOKEN=your_token
|
|
282
269
|
```
|
|
283
270
|
|
|
284
271
|
### "Vault not found"
|
|
285
272
|
|
|
286
273
|
```bash
|
|
287
|
-
# Initialize the vault first
|
|
288
274
|
keyway init
|
|
289
275
|
```
|
|
290
276
|
|
|
@@ -292,36 +278,24 @@ keyway init
|
|
|
292
278
|
|
|
293
279
|
Make sure you're a collaborator or admin on the GitHub repository.
|
|
294
280
|
|
|
295
|
-
|
|
281
|
+
## TL;DR
|
|
296
282
|
|
|
297
283
|
```bash
|
|
298
|
-
|
|
299
|
-
keyway
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
export KEYWAY_NO_BANNER=1
|
|
303
|
-
keyway doctor
|
|
284
|
+
npm i -g @keywaysh/cli
|
|
285
|
+
keyway login
|
|
286
|
+
keyway init
|
|
287
|
+
keyway pull
|
|
304
288
|
```
|
|
305
289
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
```bash
|
|
309
|
-
# Update version
|
|
310
|
-
npm version patch # or minor, or major
|
|
290
|
+
No more Slack. No more outdated `.env`.
|
|
291
|
+
Your team stays perfectly in sync.
|
|
311
292
|
|
|
312
|
-
|
|
313
|
-
npm run build
|
|
293
|
+
## Support
|
|
314
294
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
295
|
+
- **Issues**: [github.com/keywaysh/cli/issues](https://github.com/keywaysh/cli/issues)
|
|
296
|
+
- **Email**: unlock@keyway.sh
|
|
297
|
+
- **Website**: [keyway.sh](https://keyway.sh)
|
|
318
298
|
|
|
319
299
|
## License
|
|
320
300
|
|
|
321
301
|
MIT © Nicolas Ritouet
|
|
322
|
-
|
|
323
|
-
## Support
|
|
324
|
-
|
|
325
|
-
- **Issues**: https://github.com/keywaysh/cli/issues
|
|
326
|
-
- **Email**: unlock@keyway.sh
|
|
327
|
-
- **Website**: https://keyway.sh
|
package/dist/cli.js
CHANGED
|
@@ -106,47 +106,64 @@ async function initVault(repoFullName, accessToken) {
|
|
|
106
106
|
if (accessToken) {
|
|
107
107
|
headers.Authorization = `Bearer ${accessToken}`;
|
|
108
108
|
}
|
|
109
|
-
const response = await fetch(`${API_BASE_URL}/vaults
|
|
109
|
+
const response = await fetch(`${API_BASE_URL}/v1/vaults`, {
|
|
110
110
|
method: "POST",
|
|
111
111
|
headers,
|
|
112
112
|
body: JSON.stringify(body)
|
|
113
113
|
});
|
|
114
|
-
|
|
114
|
+
const result = await handleResponse(response);
|
|
115
|
+
return result.data;
|
|
116
|
+
}
|
|
117
|
+
function parseEnvContent(content) {
|
|
118
|
+
const result = {};
|
|
119
|
+
const lines = content.split("\n");
|
|
120
|
+
for (const line of lines) {
|
|
121
|
+
const trimmed = line.trim();
|
|
122
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
123
|
+
const eqIndex = trimmed.indexOf("=");
|
|
124
|
+
if (eqIndex === -1) continue;
|
|
125
|
+
const key = trimmed.substring(0, eqIndex).trim();
|
|
126
|
+
let value = trimmed.substring(eqIndex + 1);
|
|
127
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
128
|
+
value = value.slice(1, -1);
|
|
129
|
+
}
|
|
130
|
+
if (key) result[key] = value;
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
115
133
|
}
|
|
116
134
|
async function pushSecrets(repoFullName, environment, content, accessToken) {
|
|
117
|
-
const
|
|
135
|
+
const secrets = parseEnvContent(content);
|
|
136
|
+
const body = { repoFullName, environment, secrets };
|
|
118
137
|
const headers = { "Content-Type": "application/json" };
|
|
119
138
|
if (accessToken) {
|
|
120
139
|
headers.Authorization = `Bearer ${accessToken}`;
|
|
121
140
|
}
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
);
|
|
131
|
-
return handleResponse(response);
|
|
141
|
+
const response = await fetch(`${API_BASE_URL}/v1/secrets/push`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers,
|
|
144
|
+
body: JSON.stringify(body)
|
|
145
|
+
});
|
|
146
|
+
const result = await handleResponse(response);
|
|
147
|
+
return result.data;
|
|
132
148
|
}
|
|
133
149
|
async function pullSecrets(repoFullName, environment, accessToken) {
|
|
134
|
-
const encodedRepo = encodeURIComponent(repoFullName);
|
|
135
150
|
const headers = { "Content-Type": "application/json" };
|
|
136
151
|
if (accessToken) {
|
|
137
152
|
headers.Authorization = `Bearer ${accessToken}`;
|
|
138
153
|
}
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
154
|
+
const params = new URLSearchParams({
|
|
155
|
+
repo: repoFullName,
|
|
156
|
+
environment
|
|
157
|
+
});
|
|
158
|
+
const response = await fetch(`${API_BASE_URL}/v1/secrets/pull?${params}`, {
|
|
159
|
+
method: "GET",
|
|
160
|
+
headers
|
|
161
|
+
});
|
|
162
|
+
const result = await handleResponse(response);
|
|
163
|
+
return { content: result.data.content };
|
|
147
164
|
}
|
|
148
165
|
async function startDeviceLogin(repository) {
|
|
149
|
-
const response = await fetch(`${API_BASE_URL}/auth/device/start`, {
|
|
166
|
+
const response = await fetch(`${API_BASE_URL}/v1/auth/device/start`, {
|
|
150
167
|
method: "POST",
|
|
151
168
|
headers: { "Content-Type": "application/json" },
|
|
152
169
|
body: JSON.stringify(repository ? { repository } : {})
|
|
@@ -154,7 +171,7 @@ async function startDeviceLogin(repository) {
|
|
|
154
171
|
return handleResponse(response);
|
|
155
172
|
}
|
|
156
173
|
async function pollDeviceLogin(deviceCode) {
|
|
157
|
-
const response = await fetch(`${API_BASE_URL}/auth/device/poll`, {
|
|
174
|
+
const response = await fetch(`${API_BASE_URL}/v1/auth/device/poll`, {
|
|
158
175
|
method: "POST",
|
|
159
176
|
headers: { "Content-Type": "application/json" },
|
|
160
177
|
body: JSON.stringify({ deviceCode })
|
|
@@ -162,7 +179,7 @@ async function pollDeviceLogin(deviceCode) {
|
|
|
162
179
|
return handleResponse(response);
|
|
163
180
|
}
|
|
164
181
|
async function validateToken(token) {
|
|
165
|
-
const response = await fetch(`${API_BASE_URL}/auth/token/validate`, {
|
|
182
|
+
const response = await fetch(`${API_BASE_URL}/v1/auth/token/validate`, {
|
|
166
183
|
method: "POST",
|
|
167
184
|
headers: {
|
|
168
185
|
"Content-Type": "application/json",
|
|
@@ -183,7 +200,7 @@ import fs from "fs";
|
|
|
183
200
|
// package.json
|
|
184
201
|
var package_default = {
|
|
185
202
|
name: "@keywaysh/cli",
|
|
186
|
-
version: "0.0.
|
|
203
|
+
version: "0.0.4",
|
|
187
204
|
description: "One link to all your secrets",
|
|
188
205
|
type: "module",
|
|
189
206
|
bin: {
|
|
@@ -199,7 +216,10 @@ var package_default = {
|
|
|
199
216
|
"build:watch": "pnpm exec tsup --watch",
|
|
200
217
|
prepublishOnly: "pnpm run build",
|
|
201
218
|
test: "pnpm exec vitest run",
|
|
202
|
-
"test:watch": "pnpm exec vitest"
|
|
219
|
+
"test:watch": "pnpm exec vitest",
|
|
220
|
+
release: "npm version patch && git push && git push --tags",
|
|
221
|
+
"release:minor": "npm version minor && git push && git push --tags",
|
|
222
|
+
"release:major": "npm version major && git push && git push --tags"
|
|
203
223
|
},
|
|
204
224
|
keywords: [
|
|
205
225
|
"secrets",
|
|
@@ -228,8 +248,7 @@ var package_default = {
|
|
|
228
248
|
conf: "^15.0.2",
|
|
229
249
|
open: "^11.0.0",
|
|
230
250
|
"posthog-node": "^3.5.0",
|
|
231
|
-
prompts: "^2.4.2"
|
|
232
|
-
undici: "^7.13.0"
|
|
251
|
+
prompts: "^2.4.2"
|
|
233
252
|
},
|
|
234
253
|
devDependencies: {
|
|
235
254
|
"@types/node": "^24.2.0",
|
|
@@ -536,7 +555,7 @@ import path2 from "path";
|
|
|
536
555
|
import prompts2 from "prompts";
|
|
537
556
|
import chalk2 from "chalk";
|
|
538
557
|
function generateBadge(repo) {
|
|
539
|
-
return `[](https://keyway.sh/
|
|
558
|
+
return `[](https://www.keyway.sh/dashboard/vaults/${repo})`;
|
|
540
559
|
}
|
|
541
560
|
function insertBadgeIntoReadme(readmeContent, badge) {
|
|
542
561
|
if (readmeContent.includes("keyway.sh/badge.svg")) {
|
|
@@ -547,6 +566,9 @@ function insertBadgeIntoReadme(readmeContent, badge) {
|
|
|
547
566
|
if (titleIndex !== -1) {
|
|
548
567
|
const before = lines.slice(0, titleIndex + 1);
|
|
549
568
|
const after = lines.slice(titleIndex + 1);
|
|
569
|
+
while (after.length > 0 && after[0].trim() === "") {
|
|
570
|
+
after.shift();
|
|
571
|
+
}
|
|
550
572
|
const newLines = [...before, "", badge, "", ...after];
|
|
551
573
|
return newLines.join("\n");
|
|
552
574
|
}
|
|
@@ -829,6 +851,16 @@ async function pushCommand(options) {
|
|
|
829
851
|
console.log("\nUploading secrets...");
|
|
830
852
|
const response = await pushSecrets(repoFullName, environment, content, accessToken);
|
|
831
853
|
console.log(chalk4.green("\n\u2713 " + response.message));
|
|
854
|
+
if (response.stats) {
|
|
855
|
+
const { created, updated, deleted } = response.stats;
|
|
856
|
+
const parts = [];
|
|
857
|
+
if (created > 0) parts.push(chalk4.green(`+${created} created`));
|
|
858
|
+
if (updated > 0) parts.push(chalk4.yellow(`~${updated} updated`));
|
|
859
|
+
if (deleted > 0) parts.push(chalk4.red(`-${deleted} deleted`));
|
|
860
|
+
if (parts.length > 0) {
|
|
861
|
+
console.log(`Stats: ${parts.join(", ")}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
832
864
|
console.log(`
|
|
833
865
|
Your secrets are now encrypted and stored securely.`);
|
|
834
866
|
console.log(`To retrieve them, run: ${chalk4.cyan(`keyway pull --env ${environment}`)}`);
|
|
@@ -926,8 +958,7 @@ import { execSync as execSync2 } from "child_process";
|
|
|
926
958
|
import { writeFileSync, unlinkSync, readFileSync, existsSync } from "fs";
|
|
927
959
|
import { tmpdir } from "os";
|
|
928
960
|
import { join } from "path";
|
|
929
|
-
|
|
930
|
-
var API_HEALTH_URL = `${process.env.KEYWAY_API_URL || INTERNAL_API_URL}/`;
|
|
961
|
+
var API_HEALTH_URL = `${process.env.KEYWAY_API_URL || INTERNAL_API_URL}/v1/health`;
|
|
931
962
|
async function checkNode() {
|
|
932
963
|
const nodeVersion = process.versions.node;
|
|
933
964
|
const [major] = nodeVersion.split(".").map(Number);
|
|
@@ -978,10 +1009,19 @@ async function checkGit() {
|
|
|
978
1009
|
}
|
|
979
1010
|
}
|
|
980
1011
|
async function checkNetwork() {
|
|
1012
|
+
const fetchFn = globalThis.fetch;
|
|
1013
|
+
if (!fetchFn) {
|
|
1014
|
+
return {
|
|
1015
|
+
id: "network",
|
|
1016
|
+
name: "API connectivity",
|
|
1017
|
+
status: "warn",
|
|
1018
|
+
detail: "Fetch API not available in this Node.js runtime"
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
981
1021
|
try {
|
|
982
1022
|
const controller = new AbortController();
|
|
983
1023
|
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
984
|
-
const response = await
|
|
1024
|
+
const response = await fetchFn(API_HEALTH_URL, {
|
|
985
1025
|
method: "HEAD",
|
|
986
1026
|
signal: controller.signal
|
|
987
1027
|
});
|
|
@@ -1093,7 +1133,7 @@ async function checkSystemClock() {
|
|
|
1093
1133
|
try {
|
|
1094
1134
|
const controller = new AbortController();
|
|
1095
1135
|
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
1096
|
-
const response = await
|
|
1136
|
+
const response = await fetch("https://api.keyway.sh/v1/health", {
|
|
1097
1137
|
method: "HEAD",
|
|
1098
1138
|
signal: controller.signal
|
|
1099
1139
|
});
|
|
@@ -1227,7 +1267,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1227
1267
|
// package.json with { type: 'json' }
|
|
1228
1268
|
var package_default2 = {
|
|
1229
1269
|
name: "@keywaysh/cli",
|
|
1230
|
-
version: "0.0.
|
|
1270
|
+
version: "0.0.4",
|
|
1231
1271
|
description: "One link to all your secrets",
|
|
1232
1272
|
type: "module",
|
|
1233
1273
|
bin: {
|
|
@@ -1243,7 +1283,10 @@ var package_default2 = {
|
|
|
1243
1283
|
"build:watch": "pnpm exec tsup --watch",
|
|
1244
1284
|
prepublishOnly: "pnpm run build",
|
|
1245
1285
|
test: "pnpm exec vitest run",
|
|
1246
|
-
"test:watch": "pnpm exec vitest"
|
|
1286
|
+
"test:watch": "pnpm exec vitest",
|
|
1287
|
+
release: "npm version patch && git push && git push --tags",
|
|
1288
|
+
"release:minor": "npm version minor && git push && git push --tags",
|
|
1289
|
+
"release:major": "npm version major && git push && git push --tags"
|
|
1247
1290
|
},
|
|
1248
1291
|
keywords: [
|
|
1249
1292
|
"secrets",
|
|
@@ -1272,8 +1315,7 @@ var package_default2 = {
|
|
|
1272
1315
|
conf: "^15.0.2",
|
|
1273
1316
|
open: "^11.0.0",
|
|
1274
1317
|
"posthog-node": "^3.5.0",
|
|
1275
|
-
prompts: "^2.4.2"
|
|
1276
|
-
undici: "^7.13.0"
|
|
1318
|
+
prompts: "^2.4.2"
|
|
1277
1319
|
},
|
|
1278
1320
|
devDependencies: {
|
|
1279
1321
|
"@types/node": "^24.2.0",
|
|
@@ -1287,11 +1329,6 @@ var package_default2 = {
|
|
|
1287
1329
|
|
|
1288
1330
|
// src/cli.ts
|
|
1289
1331
|
var program = new Command();
|
|
1290
|
-
var shouldShowBanner = () => {
|
|
1291
|
-
if (process.env.KEYWAY_NO_BANNER === "1") return false;
|
|
1292
|
-
const argv = process.argv.slice(2);
|
|
1293
|
-
return !argv.includes("--no-banner") && argv.length > 0;
|
|
1294
|
-
};
|
|
1295
1332
|
var showBanner = () => {
|
|
1296
1333
|
const text = chalk7.cyan.bold("Keyway CLI");
|
|
1297
1334
|
const subtitle = chalk7.gray("GitHub-native secrets manager for dev teams");
|
|
@@ -1300,10 +1337,8 @@ ${text}
|
|
|
1300
1337
|
${subtitle}
|
|
1301
1338
|
`);
|
|
1302
1339
|
};
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
}
|
|
1306
|
-
program.name("keyway").description("GitHub-native secrets manager for dev teams").version(package_default2.version).option("--no-banner", "Disable the startup banner");
|
|
1340
|
+
showBanner();
|
|
1341
|
+
program.name("keyway").description("GitHub-native secrets manager for dev teams").version(package_default2.version);
|
|
1307
1342
|
program.command("init").description("Initialize a vault for the current repository").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (options) => {
|
|
1308
1343
|
await initCommand(options);
|
|
1309
1344
|
});
|
|
@@ -1322,9 +1357,6 @@ program.command("logout").description("Clear stored Keyway credentials").action(
|
|
|
1322
1357
|
program.command("doctor").description("Run environment checks to ensure Keyway runs smoothly").option("--json", "Output results as JSON for machine processing", false).option("--strict", "Treat warnings as failures", false).action(async (options) => {
|
|
1323
1358
|
await doctorCommand(options);
|
|
1324
1359
|
});
|
|
1325
|
-
program.command("readme").description("README utilities").command("add-badge").description("Insert the Keyway badge into README").action(async () => {
|
|
1326
|
-
await addBadgeToReadme();
|
|
1327
|
-
});
|
|
1328
1360
|
program.parseAsync().catch((error) => {
|
|
1329
1361
|
console.error(chalk7.red("Error:"), error.message || error);
|
|
1330
1362
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keywaysh/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "One link to all your secrets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "pnpm exec tsx src/cli.ts",
|
|
15
|
+
"build": "pnpm exec tsup",
|
|
16
|
+
"build:watch": "pnpm exec tsup --watch",
|
|
17
|
+
"prepublishOnly": "pnpm run build",
|
|
18
|
+
"test": "pnpm exec vitest run",
|
|
19
|
+
"test:watch": "pnpm exec vitest",
|
|
20
|
+
"release": "npm version patch && git push && git push --tags",
|
|
21
|
+
"release:minor": "npm version minor && git push && git push --tags",
|
|
22
|
+
"release:major": "npm version major && git push && git push --tags"
|
|
23
|
+
},
|
|
13
24
|
"keywords": [
|
|
14
25
|
"secrets",
|
|
15
26
|
"env",
|
|
@@ -27,6 +38,7 @@
|
|
|
27
38
|
"bugs": {
|
|
28
39
|
"url": "https://github.com/keywaysh/cli/issues"
|
|
29
40
|
},
|
|
41
|
+
"packageManager": "pnpm@10.6.1",
|
|
30
42
|
"engines": {
|
|
31
43
|
"node": ">=18.0.0"
|
|
32
44
|
},
|
|
@@ -36,8 +48,7 @@
|
|
|
36
48
|
"conf": "^15.0.2",
|
|
37
49
|
"open": "^11.0.0",
|
|
38
50
|
"posthog-node": "^3.5.0",
|
|
39
|
-
"prompts": "^2.4.2"
|
|
40
|
-
"undici": "^7.13.0"
|
|
51
|
+
"prompts": "^2.4.2"
|
|
41
52
|
},
|
|
42
53
|
"devDependencies": {
|
|
43
54
|
"@types/node": "^24.2.0",
|
|
@@ -46,12 +57,5 @@
|
|
|
46
57
|
"tsx": "^4.20.3",
|
|
47
58
|
"typescript": "^5.9.2",
|
|
48
59
|
"vitest": "^3.2.4"
|
|
49
|
-
},
|
|
50
|
-
"scripts": {
|
|
51
|
-
"dev": "pnpm exec tsx src/cli.ts",
|
|
52
|
-
"build": "pnpm exec tsup",
|
|
53
|
-
"build:watch": "pnpm exec tsup --watch",
|
|
54
|
-
"test": "pnpm exec vitest run",
|
|
55
|
-
"test:watch": "pnpm exec vitest"
|
|
56
60
|
}
|
|
57
|
-
}
|
|
61
|
+
}
|