@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.
Files changed (3) hide show
  1. package/README.md +68 -17
  2. package/dist/index.js +71 -20
  3. 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
- # Login to SecretStash
37
- sstash auth login
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
- # Login with service token (for CI/CD)
40
- export SECRETSTASH_TOKEN=your-service-token
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
- | `sstash projects list` | List available projects |
302
- | `sstash projects use <name>` | Switch project context |
303
- | `sstash environments list` | List environments |
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
- | `sstash teams list` | List teams |
306
- | `sstash teams switch <name>` | Switch team context |
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 as dirname2, join as join2 } from "path";
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 data = await response.json();
320
+ const json = await response.json();
321
+ const tokens = json.data?.tokens || json.data || json;
321
322
  configManager.setTokens(
322
- data.accessToken,
323
- data.refreshToken,
324
- data.expiresIn || 900
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 data = await response.json();
362
+ const json = await response.json();
362
363
  if (!response.ok) {
363
364
  return {
364
365
  error: {
365
- code: data.code || "API_ERROR",
366
- message: data.message || `Request failed with status ${response.status}`,
367
- details: data.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/verify", {
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/enable", {
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/disable", {
525
- method: "POST",
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.qrCodeUrl, { small: true });
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
- const { confirmPassword } = await prompt({
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(environment);
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(environment);
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 = dirname2(fileURLToPath(import.meta.url));
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"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secretstash/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.8",
4
4
  "description": "CLI tool for SecretStash - secure team secrets management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",