@secretstash/cli 0.1.5 → 0.1.8
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 +68 -17
- package/dist/index.js +71 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,13 +32,38 @@ brew install secretstash/tap/sstash
|
|
|
32
32
|
|
|
33
33
|
### Authentication
|
|
34
34
|
|
|
35
|
+
The CLI uses browser-based authentication by default, supporting passkeys, OAuth (GitHub/Google), and 2FA:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Login via browser (opens browser window)
|
|
39
|
+
sstash login
|
|
40
|
+
|
|
41
|
+
# This will:
|
|
42
|
+
# 1. Generate a session code
|
|
43
|
+
# 2. Open your default browser to complete authentication
|
|
44
|
+
# 3. Wait for you to sign in (passkey, OAuth, or email/password)
|
|
45
|
+
# 4. Automatically complete CLI authentication
|
|
46
|
+
|
|
47
|
+
# Check who you're logged in as
|
|
48
|
+
sstash whoami
|
|
49
|
+
|
|
50
|
+
# Logout
|
|
51
|
+
sstash logout
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Service Token Authentication (CI/CD)
|
|
55
|
+
|
|
56
|
+
For automated workflows, use service tokens instead of browser authentication:
|
|
57
|
+
|
|
35
58
|
```bash
|
|
36
|
-
#
|
|
37
|
-
|
|
59
|
+
# Set service token as environment variable
|
|
60
|
+
export SECRETSTASH_TOKEN=stk_your-service-token
|
|
61
|
+
|
|
62
|
+
# Or login with token directly
|
|
63
|
+
sstash login --token stk_your-service-token
|
|
38
64
|
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
sstash auth status
|
|
65
|
+
# Verify token works
|
|
66
|
+
sstash teams
|
|
42
67
|
```
|
|
43
68
|
|
|
44
69
|
### Working with Secrets
|
|
@@ -285,25 +310,51 @@ For CI/CD and automated workflows, use service tokens instead of user credential
|
|
|
285
310
|
|
|
286
311
|
## Commands Reference
|
|
287
312
|
|
|
313
|
+
### Authentication
|
|
314
|
+
| Command | Description |
|
|
315
|
+
|---------|-------------|
|
|
316
|
+
| `sstash login` | Authenticate via browser (passkeys, OAuth, 2FA) |
|
|
317
|
+
| `sstash login --token <token>` | Authenticate with service token |
|
|
318
|
+
| `sstash logout` | Clear authentication |
|
|
319
|
+
| `sstash whoami` | Show current user/token info |
|
|
320
|
+
| `sstash 2fa setup` | Set up two-factor authentication |
|
|
321
|
+
| `sstash register` | Create a new SecretStash account |
|
|
322
|
+
|
|
323
|
+
### Secrets
|
|
288
324
|
| Command | Description |
|
|
289
325
|
|---------|-------------|
|
|
290
|
-
| `sstash auth login` | Authenticate with SecretStash |
|
|
291
|
-
| `sstash auth logout` | Clear authentication |
|
|
292
|
-
| `sstash auth status` | Show authentication status |
|
|
293
326
|
| `sstash pull` | Pull secrets from SecretStash |
|
|
294
327
|
| `sstash push` | Push secrets to SecretStash |
|
|
295
|
-
| `sstash list` | List secrets in an environment |
|
|
296
|
-
| `sstash get <key>` | Get a specific secret |
|
|
297
|
-
| `sstash set <key>=<value>` | Set a specific secret |
|
|
298
|
-
| `sstash delete <key>` | Delete a specific secret |
|
|
328
|
+
| `sstash secrets list` | List secrets in an environment |
|
|
329
|
+
| `sstash secrets get <key>` | Get a specific secret |
|
|
330
|
+
| `sstash secrets set <key>=<value>` | Set a specific secret |
|
|
331
|
+
| `sstash secrets delete <key>` | Delete a specific secret |
|
|
332
|
+
| `sstash secrets history <key>` | View version history for a secret |
|
|
333
|
+
| `sstash secrets rollback <key>` | Rollback to a previous version |
|
|
334
|
+
| `sstash secrets tag <key> <tag>` | Add a tag to a secret |
|
|
335
|
+
| `sstash secrets untag <key> <tag>` | Remove a tag from a secret |
|
|
336
|
+
| `sstash secrets expiring` | List secrets expiring soon |
|
|
299
337
|
| `sstash run` | Run a command with secrets injected |
|
|
300
338
|
| `sstash diff` | Compare local and remote secrets |
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
|
339
|
+
|
|
340
|
+
### Organization
|
|
341
|
+
| Command | Description |
|
|
342
|
+
|---------|-------------|
|
|
343
|
+
| `sstash teams` | List teams |
|
|
344
|
+
| `sstash teams use <slug>` | Switch team context |
|
|
345
|
+
| `sstash projects` | List projects in current team |
|
|
346
|
+
| `sstash projects use <slug>` | Switch project context |
|
|
347
|
+
| `sstash environments` | List environments in current project |
|
|
304
348
|
| `sstash environments create <name>` | Create a new environment |
|
|
305
|
-
|
|
306
|
-
|
|
349
|
+
|
|
350
|
+
### Tags & Shares
|
|
351
|
+
| Command | Description |
|
|
352
|
+
|---------|-------------|
|
|
353
|
+
| `sstash tags` | List tags in current team |
|
|
354
|
+
| `sstash tags create <name>` | Create a new tag |
|
|
355
|
+
| `sstash share create <key>` | Create a share link for a secret |
|
|
356
|
+
| `sstash share list` | List active share links |
|
|
357
|
+
| `sstash share revoke <id>` | Revoke a share link |
|
|
307
358
|
|
|
308
359
|
Use `sstash --help` or `sstash <command> --help` for detailed usage information.
|
|
309
360
|
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
import chalk3 from "chalk";
|
|
6
6
|
import { readFileSync as readFileSync4 } from "fs";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
-
import { dirname
|
|
8
|
+
import { dirname, join as join2 } from "path";
|
|
9
9
|
|
|
10
10
|
// src/commands/auth.ts
|
|
11
11
|
import enquirer from "enquirer";
|
|
@@ -317,11 +317,12 @@ var ApiClient = class {
|
|
|
317
317
|
configManager.logout();
|
|
318
318
|
return false;
|
|
319
319
|
}
|
|
320
|
-
const
|
|
320
|
+
const json = await response.json();
|
|
321
|
+
const tokens = json.data?.tokens || json.data || json;
|
|
321
322
|
configManager.setTokens(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
tokens.accessToken,
|
|
324
|
+
tokens.refreshToken,
|
|
325
|
+
tokens.expiresIn || 900
|
|
325
326
|
);
|
|
326
327
|
return true;
|
|
327
328
|
} catch {
|
|
@@ -358,17 +359,17 @@ var ApiClient = class {
|
|
|
358
359
|
headers: requestHeaders,
|
|
359
360
|
body: body ? JSON.stringify(body) : void 0
|
|
360
361
|
});
|
|
361
|
-
const
|
|
362
|
+
const json = await response.json();
|
|
362
363
|
if (!response.ok) {
|
|
363
364
|
return {
|
|
364
365
|
error: {
|
|
365
|
-
code:
|
|
366
|
-
message:
|
|
367
|
-
details:
|
|
366
|
+
code: json.code || json.error?.code || "API_ERROR",
|
|
367
|
+
message: json.message || json.error?.message || `Request failed with status ${response.status}`,
|
|
368
|
+
details: json.details || json.error?.details
|
|
368
369
|
}
|
|
369
370
|
};
|
|
370
371
|
}
|
|
371
|
-
return { data };
|
|
372
|
+
return { data: json.data };
|
|
372
373
|
} catch (error) {
|
|
373
374
|
return {
|
|
374
375
|
error: {
|
|
@@ -399,9 +400,9 @@ var ApiClient = class {
|
|
|
399
400
|
});
|
|
400
401
|
}
|
|
401
402
|
async verify2FA(tempToken, code) {
|
|
402
|
-
return this.request("/api/v1/auth/2fa
|
|
403
|
+
return this.request("/api/v1/auth/verify-2fa", {
|
|
403
404
|
method: "POST",
|
|
404
|
-
body: { tempToken, code },
|
|
405
|
+
body: { tempToken, token: code },
|
|
405
406
|
requireAuth: false
|
|
406
407
|
});
|
|
407
408
|
}
|
|
@@ -515,14 +516,14 @@ var ApiClient = class {
|
|
|
515
516
|
});
|
|
516
517
|
}
|
|
517
518
|
async enable2FA(code) {
|
|
518
|
-
return this.request("/api/v1/users/me/2fa/
|
|
519
|
+
return this.request("/api/v1/users/me/2fa/verify", {
|
|
519
520
|
method: "POST",
|
|
520
521
|
body: { code }
|
|
521
522
|
});
|
|
522
523
|
}
|
|
523
524
|
async disable2FA(code) {
|
|
524
|
-
return this.request("/api/v1/users/me/2fa
|
|
525
|
-
method: "
|
|
525
|
+
return this.request("/api/v1/users/me/2fa", {
|
|
526
|
+
method: "DELETE",
|
|
526
527
|
body: { code }
|
|
527
528
|
});
|
|
528
529
|
}
|
|
@@ -1045,7 +1046,7 @@ function registerAuthCommands(program2) {
|
|
|
1045
1046
|
ui.heading("Two-Factor Authentication Setup");
|
|
1046
1047
|
ui.info("Scan this QR code with your authenticator app:");
|
|
1047
1048
|
ui.br();
|
|
1048
|
-
qrcode.generate(result.data.
|
|
1049
|
+
qrcode.generate(result.data.otpauthUrl, { small: true });
|
|
1049
1050
|
ui.br();
|
|
1050
1051
|
ui.subheading("Or enter this secret manually:");
|
|
1051
1052
|
console.log(colors.primary.bold(` ${result.data.secret}`));
|
|
@@ -1106,7 +1107,7 @@ function registerAuthCommands(program2) {
|
|
|
1106
1107
|
return true;
|
|
1107
1108
|
}
|
|
1108
1109
|
});
|
|
1109
|
-
|
|
1110
|
+
await prompt({
|
|
1110
1111
|
type: "password",
|
|
1111
1112
|
name: "confirmPassword",
|
|
1112
1113
|
message: "Confirm Password:",
|
|
@@ -2834,12 +2835,37 @@ function registerSecretCommands(program2) {
|
|
|
2834
2835
|
}
|
|
2835
2836
|
const ctx = requireContext();
|
|
2836
2837
|
const environment = options.env || ctx.environment;
|
|
2838
|
+
const projectSlug = options.project || ctx.projectSlug;
|
|
2837
2839
|
const currentTeam = configManager.getCurrentTeam();
|
|
2838
2840
|
if (!currentTeam) {
|
|
2839
2841
|
ui.error("No team selected. Run `sstash teams use <slug>` first.");
|
|
2840
2842
|
process.exit(1);
|
|
2841
2843
|
}
|
|
2842
2844
|
const spinner = ui.spinner("Looking up secret and tag...");
|
|
2845
|
+
const projectsResult = await apiClient.getProjects(currentTeam.id);
|
|
2846
|
+
if (projectsResult.error) {
|
|
2847
|
+
spinner.fail();
|
|
2848
|
+
ui.error(projectsResult.error.message);
|
|
2849
|
+
process.exit(1);
|
|
2850
|
+
}
|
|
2851
|
+
const project = projectsResult.data?.projects.find((p) => p.slug === projectSlug);
|
|
2852
|
+
if (!project) {
|
|
2853
|
+
spinner.fail();
|
|
2854
|
+
ui.error(`Project "${projectSlug}" not found`);
|
|
2855
|
+
process.exit(1);
|
|
2856
|
+
}
|
|
2857
|
+
const envsResult = await apiClient.getEnvironments(project.id);
|
|
2858
|
+
if (envsResult.error) {
|
|
2859
|
+
spinner.fail();
|
|
2860
|
+
ui.error(envsResult.error.message);
|
|
2861
|
+
process.exit(1);
|
|
2862
|
+
}
|
|
2863
|
+
const env = envsResult.data?.environments.find((e) => e.slug === environment || e.name.toLowerCase() === environment.toLowerCase());
|
|
2864
|
+
if (!env) {
|
|
2865
|
+
spinner.fail();
|
|
2866
|
+
ui.error(`Environment "${environment}" not found`);
|
|
2867
|
+
process.exit(1);
|
|
2868
|
+
}
|
|
2843
2869
|
const tagsResult = await apiClient.getTags(currentTeam.id);
|
|
2844
2870
|
if (tagsResult.error) {
|
|
2845
2871
|
spinner.fail();
|
|
@@ -2855,7 +2881,7 @@ function registerSecretCommands(program2) {
|
|
|
2855
2881
|
ui.info(`Create it with ${ui.code(`sstash tags create ${tagname}`)}`);
|
|
2856
2882
|
process.exit(1);
|
|
2857
2883
|
}
|
|
2858
|
-
const secretsResult = await apiClient.getSecrets(
|
|
2884
|
+
const secretsResult = await apiClient.getSecrets(env.id);
|
|
2859
2885
|
if (secretsResult.error) {
|
|
2860
2886
|
spinner.fail();
|
|
2861
2887
|
ui.error(secretsResult.error.message);
|
|
@@ -2889,12 +2915,37 @@ function registerSecretCommands(program2) {
|
|
|
2889
2915
|
}
|
|
2890
2916
|
const ctx = requireContext();
|
|
2891
2917
|
const environment = options.env || ctx.environment;
|
|
2918
|
+
const projectSlug = options.project || ctx.projectSlug;
|
|
2892
2919
|
const currentTeam = configManager.getCurrentTeam();
|
|
2893
2920
|
if (!currentTeam) {
|
|
2894
2921
|
ui.error("No team selected. Run `sstash teams use <slug>` first.");
|
|
2895
2922
|
process.exit(1);
|
|
2896
2923
|
}
|
|
2897
2924
|
const spinner = ui.spinner("Looking up secret and tag...");
|
|
2925
|
+
const projectsResult = await apiClient.getProjects(currentTeam.id);
|
|
2926
|
+
if (projectsResult.error) {
|
|
2927
|
+
spinner.fail();
|
|
2928
|
+
ui.error(projectsResult.error.message);
|
|
2929
|
+
process.exit(1);
|
|
2930
|
+
}
|
|
2931
|
+
const project = projectsResult.data?.projects.find((p) => p.slug === projectSlug);
|
|
2932
|
+
if (!project) {
|
|
2933
|
+
spinner.fail();
|
|
2934
|
+
ui.error(`Project "${projectSlug}" not found`);
|
|
2935
|
+
process.exit(1);
|
|
2936
|
+
}
|
|
2937
|
+
const envsResult = await apiClient.getEnvironments(project.id);
|
|
2938
|
+
if (envsResult.error) {
|
|
2939
|
+
spinner.fail();
|
|
2940
|
+
ui.error(envsResult.error.message);
|
|
2941
|
+
process.exit(1);
|
|
2942
|
+
}
|
|
2943
|
+
const env = envsResult.data?.environments.find((e) => e.slug === environment || e.name.toLowerCase() === environment.toLowerCase());
|
|
2944
|
+
if (!env) {
|
|
2945
|
+
spinner.fail();
|
|
2946
|
+
ui.error(`Environment "${environment}" not found`);
|
|
2947
|
+
process.exit(1);
|
|
2948
|
+
}
|
|
2898
2949
|
const tagsResult = await apiClient.getTags(currentTeam.id);
|
|
2899
2950
|
if (tagsResult.error) {
|
|
2900
2951
|
spinner.fail();
|
|
@@ -2909,7 +2960,7 @@ function registerSecretCommands(program2) {
|
|
|
2909
2960
|
ui.error(`Tag "${tagname}" not found`);
|
|
2910
2961
|
process.exit(1);
|
|
2911
2962
|
}
|
|
2912
|
-
const secretsResult = await apiClient.getSecrets(
|
|
2963
|
+
const secretsResult = await apiClient.getSecrets(env.id);
|
|
2913
2964
|
if (secretsResult.error) {
|
|
2914
2965
|
spinner.fail();
|
|
2915
2966
|
ui.error(secretsResult.error.message);
|
|
@@ -4076,7 +4127,7 @@ function registerExecCommand(program2) {
|
|
|
4076
4127
|
}
|
|
4077
4128
|
|
|
4078
4129
|
// src/index.ts
|
|
4079
|
-
var __dirname =
|
|
4130
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
4080
4131
|
var version = "0.1.0";
|
|
4081
4132
|
try {
|
|
4082
4133
|
const pkg = JSON.parse(readFileSync4(join2(__dirname, "..", "package.json"), "utf-8"));
|