@secretstash/cli 0.1.8 → 0.1.9

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 (2) hide show
  1. package/dist/index.js +395 -162
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -314,7 +314,9 @@ var ApiClient = class {
314
314
  body: JSON.stringify({ refreshToken })
315
315
  });
316
316
  if (!response.ok) {
317
- configManager.logout();
317
+ if (response.status === 401 || response.status === 403) {
318
+ configManager.logout();
319
+ }
318
320
  return false;
319
321
  }
320
322
  const json = await response.json();
@@ -322,11 +324,10 @@ var ApiClient = class {
322
324
  configManager.setTokens(
323
325
  tokens.accessToken,
324
326
  tokens.refreshToken,
325
- tokens.expiresIn || 900
327
+ tokens.expiresIn || 3600
326
328
  );
327
329
  return true;
328
330
  } catch {
329
- configManager.logout();
330
331
  return false;
331
332
  }
332
333
  }
@@ -891,9 +892,21 @@ async function handleBrowserLogin() {
891
892
  configManager.setUser(user.id, user.email);
892
893
  ui.br();
893
894
  ui.keyValue("Email", user.email);
894
- ui.keyValue("Config", configManager.getConfigPath());
895
- ui.br();
896
- ui.info(`Run ${ui.code("sstash teams")} to see your teams`);
895
+ const hasTeam = configManager.getCurrentTeam();
896
+ const hasProject = configManager.getCurrentProject();
897
+ if (hasTeam && hasProject) {
898
+ ui.br();
899
+ ui.subheading("Current Context");
900
+ ui.keyValue("Team", hasTeam.name);
901
+ ui.keyValue("Project", hasProject.name);
902
+ const env = configManager.getCurrentEnvironment();
903
+ if (env) ui.keyValue("Environment", ui.envBadge(env));
904
+ } else {
905
+ ui.br();
906
+ ui.box("Get Started", [
907
+ `Run ${colors.highlight("stash init")} to set up a project in this directory`
908
+ ]);
909
+ }
897
910
  return;
898
911
  }
899
912
  if (status === "expired") {
@@ -949,7 +962,7 @@ async function handleServiceTokenLogin(token, fromEnvVar = false) {
949
962
  ` ${colors.highlight("export SECRETSTASH_TOKEN=" + token.substring(0, 16) + "...")}`,
950
963
  "",
951
964
  ` ${colors.muted("# Then run CLI commands normally")}`,
952
- ` ${colors.highlight("sstash pull -q")}`
965
+ ` ${colors.highlight("stash pull -q")}`
953
966
  ]);
954
967
  }
955
968
  function registerAuthCommands(program2) {
@@ -986,7 +999,7 @@ function registerAuthCommands(program2) {
986
999
  });
987
1000
  program2.command("whoami").description("Display current user information").action(async () => {
988
1001
  if (!configManager.isAuthenticated()) {
989
- ui.error("Not authenticated. Run `sstash login` first.");
1002
+ ui.error("Not authenticated. Run `stash login` first.");
990
1003
  process.exit(1);
991
1004
  }
992
1005
  if (configManager.isServiceTokenAuth()) {
@@ -1032,7 +1045,7 @@ function registerAuthCommands(program2) {
1032
1045
  });
1033
1046
  program2.command("2fa").description("Manage two-factor authentication").command("setup").description("Set up two-factor authentication").action(async () => {
1034
1047
  if (!configManager.isAuthenticated()) {
1035
- ui.error("Not authenticated. Run `sstash login` first.");
1048
+ ui.error("Not authenticated. Run `stash login` first.");
1036
1049
  process.exit(1);
1037
1050
  }
1038
1051
  const spinner = ui.spinner("Setting up 2FA...");
@@ -1130,8 +1143,10 @@ function registerAuthCommands(program2) {
1130
1143
  ui.br();
1131
1144
  ui.keyValue("Email", email);
1132
1145
  ui.br();
1133
- ui.info("We recommend setting up 2FA for added security");
1134
- ui.info(`Run ${ui.code("sstash 2fa setup")} to enable two-factor authentication`);
1146
+ ui.box("Get Started", [
1147
+ `${colors.highlight("stash init")} - Set up your first project`,
1148
+ `${colors.muted("stash 2fa setup")} - Enable two-factor authentication (recommended)`
1149
+ ]);
1135
1150
  });
1136
1151
  }
1137
1152
 
@@ -1142,7 +1157,7 @@ function registerTeamCommands(program2) {
1142
1157
  const teams = program2.command("teams").description("Manage teams");
1143
1158
  teams.command("list").alias("ls").description("List all teams you belong to").action(async () => {
1144
1159
  if (!configManager.isAuthenticated()) {
1145
- ui.error("Not authenticated. Run `sstash login` first.");
1160
+ ui.error("Not authenticated. Run `stash login` first.");
1146
1161
  process.exit(1);
1147
1162
  }
1148
1163
  const spinner = ui.spinner("Fetching teams...");
@@ -1155,7 +1170,7 @@ function registerTeamCommands(program2) {
1155
1170
  spinner.stop();
1156
1171
  if (!result.data?.teams.length) {
1157
1172
  ui.warning("You are not a member of any teams");
1158
- ui.info(`Create one with ${ui.code("sstash teams create")}`);
1173
+ ui.info(`Create one with ${ui.code("stash teams create")}`);
1159
1174
  return;
1160
1175
  }
1161
1176
  ui.heading("Your Teams");
@@ -1170,11 +1185,11 @@ function registerTeamCommands(program2) {
1170
1185
  header: ["", "Name", "Slug", "Role"]
1171
1186
  });
1172
1187
  ui.br();
1173
- ui.info(`Switch teams with ${ui.code("sstash teams use <slug>")}`);
1188
+ ui.info(`Switch teams with ${ui.code("stash teams use <slug>")}`);
1174
1189
  });
1175
1190
  teams.command("create").description("Create a new team").option("-n, --name <name>", "Team name").action(async (options) => {
1176
1191
  if (!configManager.isAuthenticated()) {
1177
- ui.error("Not authenticated. Run `sstash login` first.");
1192
+ ui.error("Not authenticated. Run `stash login` first.");
1178
1193
  process.exit(1);
1179
1194
  }
1180
1195
  let name = options.name;
@@ -1198,21 +1213,22 @@ function registerTeamCommands(program2) {
1198
1213
  ui.error(result.error.message);
1199
1214
  process.exit(1);
1200
1215
  }
1216
+ const team = result.data.team;
1201
1217
  configManager.setCurrentTeam({
1202
- id: result.data.id,
1203
- name: result.data.name,
1204
- slug: result.data.slug
1218
+ id: team.id,
1219
+ name: team.name,
1220
+ slug: team.slug
1205
1221
  });
1206
1222
  spinner.succeed(`Team "${name}" created successfully`);
1207
1223
  ui.br();
1208
- ui.keyValue("ID", result.data.id);
1209
- ui.keyValue("Slug", result.data.slug);
1224
+ ui.keyValue("ID", team.id);
1225
+ ui.keyValue("Slug", team.slug);
1210
1226
  ui.br();
1211
- ui.info(`Create your first project with ${ui.code("sstash projects create")}`);
1227
+ ui.info(`Create your first project with ${ui.code("stash projects create")}`);
1212
1228
  });
1213
1229
  teams.command("use <slug>").description("Switch to a different team").action(async (slug) => {
1214
1230
  if (!configManager.isAuthenticated()) {
1215
- ui.error("Not authenticated. Run `sstash login` first.");
1231
+ ui.error("Not authenticated. Run `stash login` first.");
1216
1232
  process.exit(1);
1217
1233
  }
1218
1234
  const spinner = ui.spinner("Fetching teams...");
@@ -1226,6 +1242,15 @@ function registerTeamCommands(program2) {
1226
1242
  if (!team) {
1227
1243
  spinner.fail();
1228
1244
  ui.error(`Team "${slug}" not found or you don't have access`);
1245
+ if (result.data?.teams.length) {
1246
+ ui.br();
1247
+ ui.info("Your teams:");
1248
+ result.data.teams.forEach((t) => {
1249
+ ui.info(` ${t.slug} - ${t.name}`);
1250
+ });
1251
+ } else {
1252
+ ui.info(`You're not a member of any teams. Create one with ${ui.code("stash teams create")}`);
1253
+ }
1229
1254
  process.exit(1);
1230
1255
  }
1231
1256
  configManager.setCurrentTeam({
@@ -1241,7 +1266,7 @@ function registerTeamCommands(program2) {
1241
1266
  const currentTeam = configManager.getCurrentTeam();
1242
1267
  if (!currentTeam) {
1243
1268
  ui.warning("No team selected");
1244
- ui.info(`Select one with ${ui.code("sstash teams use <slug>")}`);
1269
+ ui.info(`Select one with ${ui.code("stash teams use <slug>")}`);
1245
1270
  return;
1246
1271
  }
1247
1272
  ui.heading("Current Team");
@@ -1251,12 +1276,12 @@ function registerTeamCommands(program2) {
1251
1276
  });
1252
1277
  teams.command("invite <email>").description("Invite a member to the current team").option("-r, --role <role>", "Role for the invited member (member|admin)", "member").action(async (email, options) => {
1253
1278
  if (!configManager.isAuthenticated()) {
1254
- ui.error("Not authenticated. Run `sstash login` first.");
1279
+ ui.error("Not authenticated. Run `stash login` first.");
1255
1280
  process.exit(1);
1256
1281
  }
1257
1282
  const currentTeam = configManager.getCurrentTeam();
1258
1283
  if (!currentTeam) {
1259
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
1284
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
1260
1285
  process.exit(1);
1261
1286
  }
1262
1287
  const role = options.role.toLowerCase();
@@ -1282,12 +1307,12 @@ function registerTeamCommands(program2) {
1282
1307
  });
1283
1308
  teams.command("members").description("List members of the current team").action(async () => {
1284
1309
  if (!configManager.isAuthenticated()) {
1285
- ui.error("Not authenticated. Run `sstash login` first.");
1310
+ ui.error("Not authenticated. Run `stash login` first.");
1286
1311
  process.exit(1);
1287
1312
  }
1288
1313
  const currentTeam = configManager.getCurrentTeam();
1289
1314
  if (!currentTeam) {
1290
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
1315
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
1291
1316
  process.exit(1);
1292
1317
  }
1293
1318
  const spinner = ui.spinner("Fetching team members...");
@@ -1319,12 +1344,12 @@ function registerTeamCommands(program2) {
1319
1344
  });
1320
1345
  teams.command("remove <email>").description("Remove a member from the current team").option("-f, --force", "Skip confirmation prompt").action(async (email, options) => {
1321
1346
  if (!configManager.isAuthenticated()) {
1322
- ui.error("Not authenticated. Run `sstash login` first.");
1347
+ ui.error("Not authenticated. Run `stash login` first.");
1323
1348
  process.exit(1);
1324
1349
  }
1325
1350
  const currentTeam = configManager.getCurrentTeam();
1326
1351
  if (!currentTeam) {
1327
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
1352
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
1328
1353
  process.exit(1);
1329
1354
  }
1330
1355
  const spinner = ui.spinner("Looking up member...");
@@ -1377,12 +1402,12 @@ function registerTeamCommands(program2) {
1377
1402
  });
1378
1403
  teams.command("role <email> <role>").description("Change a member's role in the current team").option("-f, --force", "Skip confirmation prompt").action(async (email, role, options) => {
1379
1404
  if (!configManager.isAuthenticated()) {
1380
- ui.error("Not authenticated. Run `sstash login` first.");
1405
+ ui.error("Not authenticated. Run `stash login` first.");
1381
1406
  process.exit(1);
1382
1407
  }
1383
1408
  const currentTeam = configManager.getCurrentTeam();
1384
1409
  if (!currentTeam) {
1385
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
1410
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
1386
1411
  process.exit(1);
1387
1412
  }
1388
1413
  const validRoles = ["admin", "member"];
@@ -1457,7 +1482,7 @@ var { prompt: prompt3 } = enquirer3;
1457
1482
  function requireTeam() {
1458
1483
  const team = configManager.getCurrentTeam();
1459
1484
  if (!team) {
1460
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
1485
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
1461
1486
  process.exit(1);
1462
1487
  }
1463
1488
  return team;
@@ -1466,7 +1491,7 @@ function registerProjectCommands(program2) {
1466
1491
  const projects = program2.command("projects").description("Manage projects");
1467
1492
  projects.command("list").alias("ls").description("List all projects in the current team").action(async () => {
1468
1493
  if (!configManager.isAuthenticated()) {
1469
- ui.error("Not authenticated. Run `sstash login` first.");
1494
+ ui.error("Not authenticated. Run `stash login` first.");
1470
1495
  process.exit(1);
1471
1496
  }
1472
1497
  const team = requireTeam();
@@ -1480,7 +1505,7 @@ function registerProjectCommands(program2) {
1480
1505
  spinner.stop();
1481
1506
  if (!result.data?.projects.length) {
1482
1507
  ui.warning(`No projects in team "${team.name}"`);
1483
- ui.info(`Create one with ${ui.code("sstash projects create")}`);
1508
+ ui.info(`Create one with ${ui.code("stash projects create")}`);
1484
1509
  return;
1485
1510
  }
1486
1511
  ui.heading(`Projects in ${team.name}`);
@@ -1496,11 +1521,11 @@ function registerProjectCommands(program2) {
1496
1521
  header: ["", "Name", "Slug", "Environments", "Secrets"]
1497
1522
  });
1498
1523
  ui.br();
1499
- ui.info(`Switch projects with ${ui.code("sstash projects use <slug>")}`);
1524
+ ui.info(`Switch projects with ${ui.code("stash projects use <slug>")}`);
1500
1525
  });
1501
1526
  projects.command("create").description("Create a new project").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").action(async (options) => {
1502
1527
  if (!configManager.isAuthenticated()) {
1503
- ui.error("Not authenticated. Run `sstash login` first.");
1528
+ ui.error("Not authenticated. Run `stash login` first.");
1504
1529
  process.exit(1);
1505
1530
  }
1506
1531
  const team = requireTeam();
@@ -1534,21 +1559,22 @@ function registerProjectCommands(program2) {
1534
1559
  ui.error(result.error.message);
1535
1560
  process.exit(1);
1536
1561
  }
1562
+ const project = result.data.project;
1537
1563
  configManager.setCurrentProject({
1538
- id: result.data.id,
1539
- name: result.data.name,
1540
- slug: result.data.slug
1564
+ id: project.id,
1565
+ name: project.name,
1566
+ slug: project.slug
1541
1567
  });
1542
1568
  spinner.succeed(`Project "${name}" created successfully`);
1543
1569
  ui.br();
1544
- ui.keyValue("ID", result.data.id);
1545
- ui.keyValue("Slug", result.data.slug);
1570
+ ui.keyValue("ID", project.id);
1571
+ ui.keyValue("Slug", project.slug);
1546
1572
  ui.br();
1547
- ui.info(`Create environments with ${ui.code("sstash environments create")}`);
1573
+ ui.info(`Create environments with ${ui.code("stash environments create")}`);
1548
1574
  });
1549
1575
  projects.command("use <slug>").description("Switch to a different project").action(async (slug) => {
1550
1576
  if (!configManager.isAuthenticated()) {
1551
- ui.error("Not authenticated. Run `sstash login` first.");
1577
+ ui.error("Not authenticated. Run `stash login` first.");
1552
1578
  process.exit(1);
1553
1579
  }
1554
1580
  const team = requireTeam();
@@ -1563,6 +1589,15 @@ function registerProjectCommands(program2) {
1563
1589
  if (!project) {
1564
1590
  spinner.fail();
1565
1591
  ui.error(`Project "${slug}" not found in team "${team.name}"`);
1592
+ if (result.data?.projects.length) {
1593
+ ui.br();
1594
+ ui.info("Available projects:");
1595
+ result.data.projects.forEach((p) => {
1596
+ ui.info(` ${p.slug} - ${p.name}`);
1597
+ });
1598
+ } else {
1599
+ ui.info(`No projects exist yet. Create one with ${ui.code("stash projects create")}`);
1600
+ }
1566
1601
  process.exit(1);
1567
1602
  }
1568
1603
  configManager.setCurrentProject({
@@ -1577,7 +1612,7 @@ function registerProjectCommands(program2) {
1577
1612
  const currentProject = configManager.getCurrentProject();
1578
1613
  if (!currentProject) {
1579
1614
  ui.warning("No project selected");
1580
- ui.info(`Select one with ${ui.code("sstash projects use <slug>")}`);
1615
+ ui.info(`Select one with ${ui.code("stash projects use <slug>")}`);
1581
1616
  return;
1582
1617
  }
1583
1618
  ui.heading("Current Project");
@@ -1587,7 +1622,7 @@ function registerProjectCommands(program2) {
1587
1622
  });
1588
1623
  projects.command("delete <slug>").alias("rm").description("Delete a project and all its environments and secrets").option("-f, --force", "Skip confirmation prompt").action(async (slug, options) => {
1589
1624
  if (!configManager.isAuthenticated()) {
1590
- ui.error("Not authenticated. Run `sstash login` first.");
1625
+ ui.error("Not authenticated. Run `stash login` first.");
1591
1626
  process.exit(1);
1592
1627
  }
1593
1628
  const team = requireTeam();
@@ -1653,7 +1688,7 @@ var { prompt: prompt4 } = enquirer4;
1653
1688
  function requireProject() {
1654
1689
  const project = configManager.getCurrentProject();
1655
1690
  if (!project) {
1656
- ui.error("No project selected. Run `sstash projects use <slug>` first.");
1691
+ ui.error("No project selected. Run `stash projects use <slug>` first.");
1657
1692
  process.exit(1);
1658
1693
  }
1659
1694
  return project;
@@ -1662,7 +1697,7 @@ function registerEnvironmentCommands(program2) {
1662
1697
  const envs = program2.command("environments").alias("envs").description("Manage environments");
1663
1698
  envs.command("list").alias("ls").description("List all environments in the current project").action(async () => {
1664
1699
  if (!configManager.isAuthenticated()) {
1665
- ui.error("Not authenticated. Run `sstash login` first.");
1700
+ ui.error("Not authenticated. Run `stash login` first.");
1666
1701
  process.exit(1);
1667
1702
  }
1668
1703
  const project = requireProject();
@@ -1676,7 +1711,7 @@ function registerEnvironmentCommands(program2) {
1676
1711
  spinner.stop();
1677
1712
  if (!result.data?.environments.length) {
1678
1713
  ui.warning(`No environments in project "${project.name}"`);
1679
- ui.info(`Create one with ${ui.code("sstash environments create")}`);
1714
+ ui.info(`Create one with ${ui.code("stash environments create")}`);
1680
1715
  return;
1681
1716
  }
1682
1717
  ui.heading(`Environments in ${project.name}`);
@@ -1696,11 +1731,11 @@ function registerEnvironmentCommands(program2) {
1696
1731
  header: ["", "Name", "Slug", "Secrets", "Inherits From"]
1697
1732
  });
1698
1733
  ui.br();
1699
- ui.info(`Switch environments with ${ui.code("sstash environments use <slug>")}`);
1734
+ ui.info(`Switch environments with ${ui.code("stash environments use <slug>")}`);
1700
1735
  });
1701
1736
  envs.command("create").description("Create a new environment").option("-n, --name <name>", "Environment name").option("--inherit-from <slug>", "Inherit secrets from parent environment (e.g., development)").action(async (options) => {
1702
1737
  if (!configManager.isAuthenticated()) {
1703
- ui.error("Not authenticated. Run `sstash login` first.");
1738
+ ui.error("Not authenticated. Run `stash login` first.");
1704
1739
  process.exit(1);
1705
1740
  }
1706
1741
  const project = requireProject();
@@ -1751,20 +1786,21 @@ function registerEnvironmentCommands(program2) {
1751
1786
  ui.error(result.error.message);
1752
1787
  process.exit(1);
1753
1788
  }
1754
- configManager.setCurrentEnvironment(result.data.slug);
1789
+ const environment = result.data.environment;
1790
+ configManager.setCurrentEnvironment(environment.slug);
1755
1791
  spinner.succeed(`Environment "${name}" created successfully`);
1756
1792
  ui.br();
1757
- ui.keyValue("ID", result.data.id);
1758
- ui.keyValue("Slug", result.data.slug);
1793
+ ui.keyValue("ID", environment.id);
1794
+ ui.keyValue("Slug", environment.slug);
1759
1795
  if (options.inheritFrom) {
1760
1796
  ui.keyValue("Inherits from", options.inheritFrom);
1761
1797
  }
1762
1798
  ui.br();
1763
- ui.info(`Add secrets with ${ui.code("sstash secrets create")} or ${ui.code("sstash push")}`);
1799
+ ui.info(`Add secrets with ${ui.code("stash secrets create")} or ${ui.code("stash push")}`);
1764
1800
  });
1765
1801
  envs.command("use <slug>").description("Switch to a different environment").action(async (slug) => {
1766
1802
  if (!configManager.isAuthenticated()) {
1767
- ui.error("Not authenticated. Run `sstash login` first.");
1803
+ ui.error("Not authenticated. Run `stash login` first.");
1768
1804
  process.exit(1);
1769
1805
  }
1770
1806
  const project = requireProject();
@@ -1779,6 +1815,15 @@ function registerEnvironmentCommands(program2) {
1779
1815
  if (!env) {
1780
1816
  spinner.fail();
1781
1817
  ui.error(`Environment "${slug}" not found in project "${project.name}"`);
1818
+ if (result.data?.environments.length) {
1819
+ ui.br();
1820
+ ui.info("Available environments:");
1821
+ result.data.environments.forEach((e) => {
1822
+ ui.info(` ${e.slug} - ${e.name}`);
1823
+ });
1824
+ } else {
1825
+ ui.info(`No environments exist yet. Create one with ${ui.code("stash environments create")}`);
1826
+ }
1782
1827
  process.exit(1);
1783
1828
  }
1784
1829
  configManager.setCurrentEnvironment(env.slug);
@@ -1788,7 +1833,7 @@ function registerEnvironmentCommands(program2) {
1788
1833
  const currentEnv = configManager.getCurrentEnvironment();
1789
1834
  if (!currentEnv) {
1790
1835
  ui.warning("No environment selected");
1791
- ui.info(`Select one with ${ui.code("sstash environments use <slug>")}`);
1836
+ ui.info(`Select one with ${ui.code("stash environments use <slug>")}`);
1792
1837
  return;
1793
1838
  }
1794
1839
  ui.heading("Current Environment");
@@ -1796,17 +1841,17 @@ function registerEnvironmentCommands(program2) {
1796
1841
  });
1797
1842
  envs.command("clone <source> <target>").description("Clone an environment with all its secrets").action(async (source, target) => {
1798
1843
  if (!configManager.isAuthenticated()) {
1799
- ui.error("Not authenticated. Run `sstash login` first.");
1844
+ ui.error("Not authenticated. Run `stash login` first.");
1800
1845
  process.exit(1);
1801
1846
  }
1802
1847
  const teamSlug = projectConfig.getEffectiveTeam();
1803
1848
  const projectSlug = projectConfig.getEffectiveProject();
1804
1849
  if (!teamSlug) {
1805
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
1850
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
1806
1851
  process.exit(1);
1807
1852
  }
1808
1853
  if (!projectSlug) {
1809
- ui.error("No project selected. Run `sstash projects use <slug>` first.");
1854
+ ui.error("No project selected. Run `stash projects use <slug>` first.");
1810
1855
  process.exit(1);
1811
1856
  }
1812
1857
  const { password } = await prompt4({
@@ -1875,11 +1920,11 @@ function registerEnvironmentCommands(program2) {
1875
1920
  console.log(` ${colors.success("+")} ${secret.key}`);
1876
1921
  }
1877
1922
  ui.br();
1878
- ui.info(`Switch to the new environment with ${ui.code(`sstash environments use ${target}`)}`);
1923
+ ui.info(`Switch to the new environment with ${ui.code(`stash environments use ${target}`)}`);
1879
1924
  });
1880
1925
  envs.command("delete <slug>").description("Delete an environment").option("-f, --force", "Skip confirmation prompts").action(async (slug, options) => {
1881
1926
  if (!configManager.isAuthenticated()) {
1882
- ui.error("Not authenticated. Run `sstash login` first.");
1927
+ ui.error("Not authenticated. Run `stash login` first.");
1883
1928
  process.exit(1);
1884
1929
  }
1885
1930
  const project = requireProject();
@@ -1957,16 +2002,26 @@ function requireContext() {
1957
2002
  const teamSlug = projectConfig.getEffectiveTeam();
1958
2003
  const projectSlug = projectConfig.getEffectiveProject();
1959
2004
  const environment = projectConfig.getEffectiveEnvironment();
1960
- if (!teamSlug) {
1961
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
1962
- process.exit(1);
1963
- }
1964
- if (!projectSlug) {
1965
- ui.error("No project selected. Run `sstash projects use <slug>` first.");
1966
- process.exit(1);
1967
- }
1968
- if (!environment) {
1969
- ui.error("No environment selected. Run `sstash environments use <slug>` first.");
2005
+ const missing = [];
2006
+ if (!teamSlug) missing.push("team");
2007
+ if (!projectSlug) missing.push("project");
2008
+ if (!environment) missing.push("environment");
2009
+ if (missing.length > 0) {
2010
+ ui.error(`Missing context: ${missing.join(", ")}`);
2011
+ ui.br();
2012
+ ui.info("Set up your context with one of these options:");
2013
+ ui.br();
2014
+ ui.keyValue("Quick setup", ui.code("stash init"));
2015
+ ui.keyValue("View context", ui.code("stash context"));
2016
+ if (missing.includes("team")) {
2017
+ ui.keyValue("Set team", ui.code("stash teams use <slug>"));
2018
+ }
2019
+ if (missing.includes("project")) {
2020
+ ui.keyValue("Set project", ui.code("stash projects use <slug>"));
2021
+ }
2022
+ if (missing.includes("environment")) {
2023
+ ui.keyValue("Set environment", ui.code("stash environments use <slug>"));
2024
+ }
1970
2025
  process.exit(1);
1971
2026
  }
1972
2027
  return { teamSlug, projectSlug, environment };
@@ -2014,7 +2069,7 @@ function registerSecretCommands(program2) {
2014
2069
  process.exit(1);
2015
2070
  }
2016
2071
  if (!configManager.isAuthenticated()) {
2017
- ui.error("Not authenticated. Run `sstash login` first.");
2072
+ ui.error("Not authenticated. Run `stash login` first.");
2018
2073
  process.exit(1);
2019
2074
  }
2020
2075
  const ctx = requireContext();
@@ -2038,7 +2093,7 @@ function registerSecretCommands(program2) {
2038
2093
  } else if (options.format === "env") {
2039
2094
  } else {
2040
2095
  ui.warning("No secrets in this environment");
2041
- ui.info(`Add secrets with ${ui.code("sstash secrets create")} or ${ui.code("sstash push")}`);
2096
+ ui.info(`Add secrets with ${ui.code("stash secrets create")} or ${ui.code("stash push")}`);
2042
2097
  }
2043
2098
  return;
2044
2099
  }
@@ -2101,7 +2156,7 @@ function registerSecretCommands(program2) {
2101
2156
  });
2102
2157
  secrets.command("set <key> [value]").description("Create or update a single secret").option("-e, --env <environment>", "Environment to use").option("-p, --project <project>", "Project slug to use").option("-d, --description <description>", "Secret description").option("--from-file <file>", "Read value from file").option("--expires <date>", "Set expiration date (YYYY-MM-DD format)").action(async (key, value, options) => {
2103
2158
  if (!configManager.isAuthenticated()) {
2104
- ui.error("Not authenticated. Run `sstash login` first.");
2159
+ ui.error("Not authenticated. Run `stash login` first.");
2105
2160
  process.exit(1);
2106
2161
  }
2107
2162
  const ctx = requireContext();
@@ -2177,7 +2232,7 @@ function registerSecretCommands(program2) {
2177
2232
  });
2178
2233
  secrets.command("get <key>").description("Get a single secret value").option("-e, --env <environment>", "Environment to use").option("-p, --project <project>", "Project slug to use").option("-r, --reveal", "Show unmasked value").action(async (key, options) => {
2179
2234
  if (!configManager.isAuthenticated()) {
2180
- ui.error("Not authenticated. Run `sstash login` first.");
2235
+ ui.error("Not authenticated. Run `stash login` first.");
2181
2236
  process.exit(1);
2182
2237
  }
2183
2238
  const ctx = requireContext();
@@ -2215,7 +2270,7 @@ function registerSecretCommands(program2) {
2215
2270
  });
2216
2271
  secrets.command("delete <key>").alias("rm").description("Delete a single secret").option("-e, --env <environment>", "Environment to use").option("-p, --project <project>", "Project slug to use").option("-f, --force", "Skip confirmation").action(async (key, options) => {
2217
2272
  if (!configManager.isAuthenticated()) {
2218
- ui.error("Not authenticated. Run `sstash login` first.");
2273
+ ui.error("Not authenticated. Run `stash login` first.");
2219
2274
  process.exit(1);
2220
2275
  }
2221
2276
  const ctx = requireContext();
@@ -2266,7 +2321,7 @@ function registerSecretCommands(program2) {
2266
2321
  });
2267
2322
  secrets.command("history <key>").description("Show version history for a secret").option("-e, --env <environment>", "Environment to use").option("-p, --project <project>", "Project slug to use").option("-l, --limit <n>", "Number of versions to show", "10").action(async (key, options) => {
2268
2323
  if (!configManager.isAuthenticated()) {
2269
- ui.error("Not authenticated. Run `sstash login` first.");
2324
+ ui.error("Not authenticated. Run `stash login` first.");
2270
2325
  process.exit(1);
2271
2326
  }
2272
2327
  const ctx = requireContext();
@@ -2354,16 +2409,16 @@ function registerSecretCommands(program2) {
2354
2409
  });
2355
2410
  ui.br();
2356
2411
  ui.info(`Showing ${versions.length} version(s)`);
2357
- ui.info(`Use ${ui.code(`sstash secrets rollback ${key} --version <n>`)} to restore a previous version`);
2412
+ ui.info(`Use ${ui.code(`stash secrets rollback ${key} --version <n>`)} to restore a previous version`);
2358
2413
  });
2359
2414
  secrets.command("rollback <key>").description("Restore a secret to a previous version").option("-e, --env <environment>", "Environment to use").option("-p, --project <project>", "Project slug to use").option("-v, --version <n>", "Version number to restore (required)").action(async (key, options) => {
2360
2415
  if (!configManager.isAuthenticated()) {
2361
- ui.error("Not authenticated. Run `sstash login` first.");
2416
+ ui.error("Not authenticated. Run `stash login` first.");
2362
2417
  process.exit(1);
2363
2418
  }
2364
2419
  if (!options.version) {
2365
2420
  ui.error("Version number is required. Use --version <n>");
2366
- ui.info(`Use ${ui.code(`sstash secrets history ${key}`)} to see available versions`);
2421
+ ui.info(`Use ${ui.code(`stash secrets history ${key}`)} to see available versions`);
2367
2422
  process.exit(1);
2368
2423
  }
2369
2424
  const version2 = parseInt(options.version, 10);
@@ -2482,7 +2537,7 @@ function registerSecretCommands(program2) {
2482
2537
  });
2483
2538
  program2.command("pull").description("Pull secrets from SecretStash to a local .env file").option("-e, --env <environment>", "Environment to pull from").option("-o, --output <file>", "Output file path").option("-f, --force", "Overwrite existing file without confirmation").action(async (options) => {
2484
2539
  if (!configManager.isAuthenticated()) {
2485
- ui.error("Not authenticated. Run `sstash login` first.");
2540
+ ui.error("Not authenticated. Run `stash login` first.");
2486
2541
  process.exit(1);
2487
2542
  }
2488
2543
  const ctx = requireContext();
@@ -2525,7 +2580,7 @@ function registerSecretCommands(program2) {
2525
2580
  });
2526
2581
  program2.command("export").description("Export secrets in various formats (env, json, yaml)").option("-e, --env <environment>", "Environment to export from").option("-f, --format <format>", "Output format (env, json, yaml)", "env").option("-o, --output <file>", "Output file path (default: stdout)").action(async (options) => {
2527
2582
  if (!configManager.isAuthenticated()) {
2528
- ui.error("Not authenticated. Run `sstash login` first.");
2583
+ ui.error("Not authenticated. Run `stash login` first.");
2529
2584
  process.exit(1);
2530
2585
  }
2531
2586
  const ctx = requireContext();
@@ -2603,7 +2658,7 @@ function registerSecretCommands(program2) {
2603
2658
  });
2604
2659
  program2.command("push").description("Push secrets from a local .env file to SecretStash").option("-e, --env <environment>", "Environment to push to").option("-i, --input <file>", "Input file path").option("--dry-run", "Show what would be pushed without making changes").action(async (options) => {
2605
2660
  if (!configManager.isAuthenticated()) {
2606
- ui.error("Not authenticated. Run `sstash login` first.");
2661
+ ui.error("Not authenticated. Run `stash login` first.");
2607
2662
  process.exit(1);
2608
2663
  }
2609
2664
  const ctx = requireContext();
@@ -2685,7 +2740,7 @@ function registerSecretCommands(program2) {
2685
2740
  });
2686
2741
  program2.command("run").description("Run a command with secrets injected as environment variables").option("-e, --env <environment>", "Environment to use").argument("<command...>", "Command to run").action(async (commandArgs, options) => {
2687
2742
  if (!configManager.isAuthenticated()) {
2688
- ui.error("Not authenticated. Run `sstash login` first.");
2743
+ ui.error("Not authenticated. Run `stash login` first.");
2689
2744
  process.exit(1);
2690
2745
  }
2691
2746
  const ctx = requireContext();
@@ -2728,7 +2783,7 @@ function registerSecretCommands(program2) {
2728
2783
  });
2729
2784
  program2.command("init").description("Initialize SecretStash in the current directory").action(async () => {
2730
2785
  if (!configManager.isAuthenticated()) {
2731
- ui.error("Not authenticated. Run `sstash login` first.");
2786
+ ui.error("Not authenticated. Run `stash login` first.");
2732
2787
  process.exit(1);
2733
2788
  }
2734
2789
  ui.heading("Initialize SecretStash");
@@ -2740,20 +2795,46 @@ function registerSecretCommands(program2) {
2740
2795
  process.exit(1);
2741
2796
  }
2742
2797
  teamsSpinner.stop();
2798
+ let selectedTeam;
2743
2799
  if (!teamsResult.data?.teams.length) {
2744
- ui.error("No teams found. Create one first with `sstash teams create`");
2745
- process.exit(1);
2800
+ ui.info("No teams found. Let's create your first team.");
2801
+ const { name } = await prompt5({
2802
+ type: "input",
2803
+ name: "name",
2804
+ message: "Team name:",
2805
+ initial: "My Team",
2806
+ validate: (value) => value ? true : "Name is required"
2807
+ });
2808
+ const createTeamSpinner = ui.spinner("Creating team...");
2809
+ const createTeamResult = await apiClient.createTeam(name);
2810
+ if (createTeamResult.error) {
2811
+ createTeamSpinner.fail();
2812
+ ui.error(createTeamResult.error.message);
2813
+ process.exit(1);
2814
+ }
2815
+ createTeamSpinner.succeed(`Team "${name}" created`);
2816
+ selectedTeam = createTeamResult.data.team;
2817
+ configManager.setCurrentTeam({
2818
+ id: selectedTeam.id,
2819
+ name: selectedTeam.name,
2820
+ slug: selectedTeam.slug
2821
+ });
2822
+ } else if (teamsResult.data.teams.length === 1) {
2823
+ selectedTeam = teamsResult.data.teams[0];
2824
+ ui.info(`Using team: ${selectedTeam.name}`);
2825
+ } else {
2826
+ const { teamSlug: teamSlug2 } = await prompt5({
2827
+ type: "select",
2828
+ name: "teamSlug",
2829
+ message: "Select team:",
2830
+ choices: teamsResult.data.teams.map((t) => ({
2831
+ name: t.slug,
2832
+ message: `${t.name} (${t.slug})`
2833
+ }))
2834
+ });
2835
+ selectedTeam = teamsResult.data.teams.find((t) => t.slug === teamSlug2);
2746
2836
  }
2747
- const { teamSlug } = await prompt5({
2748
- type: "select",
2749
- name: "teamSlug",
2750
- message: "Select team:",
2751
- choices: teamsResult.data.teams.map((t) => ({
2752
- name: t.slug,
2753
- message: `${t.name} (${t.slug})`
2754
- }))
2755
- });
2756
- const selectedTeam = teamsResult.data.teams.find((t) => t.slug === teamSlug);
2837
+ const teamSlug = selectedTeam.slug;
2757
2838
  const projectsSpinner = ui.spinner("Fetching projects...");
2758
2839
  const projectsResult = await apiClient.getProjects(selectedTeam.id);
2759
2840
  projectsSpinner.stop();
@@ -2771,7 +2852,7 @@ function registerSecretCommands(program2) {
2771
2852
  ui.error(createResult.error.message);
2772
2853
  process.exit(1);
2773
2854
  }
2774
- projectSlug = createResult.data.slug;
2855
+ projectSlug = createResult.data.project.slug;
2775
2856
  } else {
2776
2857
  const response = await prompt5({
2777
2858
  type: "select",
@@ -2797,7 +2878,7 @@ function registerSecretCommands(program2) {
2797
2878
  ui.error(createResult.error.message);
2798
2879
  process.exit(1);
2799
2880
  }
2800
- projectSlug = createResult.data.slug;
2881
+ projectSlug = createResult.data.project.slug;
2801
2882
  } else {
2802
2883
  projectSlug = response.projectSlug;
2803
2884
  }
@@ -2820,9 +2901,9 @@ function registerSecretCommands(program2) {
2820
2901
  ui.success(`Created ${configPath}`);
2821
2902
  ui.br();
2822
2903
  ui.box("Next Steps", [
2823
- `1. Add secrets: ${ui.code("sstash push .env")}`,
2824
- `2. Pull secrets: ${ui.code("sstash pull")}`,
2825
- `3. Run with secrets: ${ui.code("sstash run npm start")}`,
2904
+ `1. Add secrets: ${ui.code("stash push .env")}`,
2905
+ `2. Pull secrets: ${ui.code("stash pull")}`,
2906
+ `3. Run with secrets: ${ui.code("stash run npm start")}`,
2826
2907
  "",
2827
2908
  `Add ${ui.code(".secretstash.json")} to version control`,
2828
2909
  `Add ${ui.code(".env*")} to .gitignore`
@@ -2830,7 +2911,7 @@ function registerSecretCommands(program2) {
2830
2911
  });
2831
2912
  secrets.command("tag <key> <tagname>").description("Add a tag to a secret").option("-e, --env <environment>", "Environment to use").option("-p, --project <project>", "Project slug to use").action(async (key, tagname, options) => {
2832
2913
  if (!configManager.isAuthenticated()) {
2833
- ui.error("Not authenticated. Run `sstash login` first.");
2914
+ ui.error("Not authenticated. Run `stash login` first.");
2834
2915
  process.exit(1);
2835
2916
  }
2836
2917
  const ctx = requireContext();
@@ -2838,7 +2919,7 @@ function registerSecretCommands(program2) {
2838
2919
  const projectSlug = options.project || ctx.projectSlug;
2839
2920
  const currentTeam = configManager.getCurrentTeam();
2840
2921
  if (!currentTeam) {
2841
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
2922
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
2842
2923
  process.exit(1);
2843
2924
  }
2844
2925
  const spinner = ui.spinner("Looking up secret and tag...");
@@ -2878,7 +2959,7 @@ function registerSecretCommands(program2) {
2878
2959
  if (!tag) {
2879
2960
  spinner.fail();
2880
2961
  ui.error(`Tag "${tagname}" not found`);
2881
- ui.info(`Create it with ${ui.code(`sstash tags create ${tagname}`)}`);
2962
+ ui.info(`Create it with ${ui.code(`stash tags create ${tagname}`)}`);
2882
2963
  process.exit(1);
2883
2964
  }
2884
2965
  const secretsResult = await apiClient.getSecrets(env.id);
@@ -2910,7 +2991,7 @@ function registerSecretCommands(program2) {
2910
2991
  });
2911
2992
  secrets.command("untag <key> <tagname>").description("Remove a tag from a secret").option("-e, --env <environment>", "Environment to use").option("-p, --project <project>", "Project slug to use").action(async (key, tagname, options) => {
2912
2993
  if (!configManager.isAuthenticated()) {
2913
- ui.error("Not authenticated. Run `sstash login` first.");
2994
+ ui.error("Not authenticated. Run `stash login` first.");
2914
2995
  process.exit(1);
2915
2996
  }
2916
2997
  const ctx = requireContext();
@@ -2918,7 +2999,7 @@ function registerSecretCommands(program2) {
2918
2999
  const projectSlug = options.project || ctx.projectSlug;
2919
3000
  const currentTeam = configManager.getCurrentTeam();
2920
3001
  if (!currentTeam) {
2921
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
3002
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
2922
3003
  process.exit(1);
2923
3004
  }
2924
3005
  const spinner = ui.spinner("Looking up secret and tag...");
@@ -2985,12 +3066,12 @@ function registerSecretCommands(program2) {
2985
3066
  });
2986
3067
  secrets.command("expiring").description("List secrets that are expiring soon").option("--days <days>", "Number of days to look ahead", "7").action(async (options) => {
2987
3068
  if (!configManager.isAuthenticated()) {
2988
- ui.error("Not authenticated. Run `sstash login` first.");
3069
+ ui.error("Not authenticated. Run `stash login` first.");
2989
3070
  process.exit(1);
2990
3071
  }
2991
3072
  const teamSlug = projectConfig.getEffectiveTeam();
2992
3073
  if (!teamSlug) {
2993
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
3074
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
2994
3075
  process.exit(1);
2995
3076
  }
2996
3077
  const days = parseInt(options.days) || 7;
@@ -3139,11 +3220,11 @@ function requirePartialContext() {
3139
3220
  const teamSlug = projectConfig.getEffectiveTeam();
3140
3221
  const projectSlug = projectConfig.getEffectiveProject();
3141
3222
  if (!teamSlug) {
3142
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
3223
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
3143
3224
  process.exit(1);
3144
3225
  }
3145
3226
  if (!projectSlug) {
3146
- ui.error("No project selected. Run `sstash projects use <slug>` first.");
3227
+ ui.error("No project selected. Run `stash projects use <slug>` first.");
3147
3228
  process.exit(1);
3148
3229
  }
3149
3230
  return { teamSlug, projectSlug };
@@ -3151,7 +3232,7 @@ function requirePartialContext() {
3151
3232
  function registerDiffCommand(program2) {
3152
3233
  program2.command("diff <env1> <env2>").description("Compare secrets between two environments").option("-p, --project <slug>", "Use specific project").option("--show-unchanged", "Include secrets that are the same in both environments").option("-f, --format <format>", "Output format: table or json", "table").action(async (env1, env2, options) => {
3153
3234
  if (!configManager.isAuthenticated()) {
3154
- ui.error("Not authenticated. Run `sstash login` first.");
3235
+ ui.error("Not authenticated. Run `stash login` first.");
3155
3236
  process.exit(1);
3156
3237
  }
3157
3238
  const ctx = requirePartialContext();
@@ -3205,15 +3286,15 @@ function requireContext2() {
3205
3286
  const projectSlug = projectConfig.getEffectiveProject();
3206
3287
  const environment = projectConfig.getEffectiveEnvironment();
3207
3288
  if (!teamSlug) {
3208
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
3289
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
3209
3290
  process.exit(1);
3210
3291
  }
3211
3292
  if (!projectSlug) {
3212
- ui.error("No project selected. Run `sstash projects use <slug>` first.");
3293
+ ui.error("No project selected. Run `stash projects use <slug>` first.");
3213
3294
  process.exit(1);
3214
3295
  }
3215
3296
  if (!environment) {
3216
- ui.error("No environment selected. Run `sstash environments use <slug>` first.");
3297
+ ui.error("No environment selected. Run `stash environments use <slug>` first.");
3217
3298
  process.exit(1);
3218
3299
  }
3219
3300
  return { teamSlug, projectSlug, environment };
@@ -3249,7 +3330,7 @@ function registerShareCommands(program2) {
3249
3330
  const share = program2.command("share").description("Share secrets via temporary links");
3250
3331
  share.command("create <key>").alias("new").description("Create a share link for a secret").option("-e, --expires <time>", "Expiration time (e.g., 1h, 24h, 7d, 30m)", "24h").option("--one-time", "Link can only be viewed once").option("--env <environment>", "Environment (overrides current)").action(async (key, options) => {
3251
3332
  if (!configManager.isAuthenticated()) {
3252
- ui.error("Not authenticated. Run `sstash login` first.");
3333
+ ui.error("Not authenticated. Run `stash login` first.");
3253
3334
  process.exit(1);
3254
3335
  }
3255
3336
  const ctx = requireContext2();
@@ -3293,7 +3374,7 @@ function registerShareCommands(program2) {
3293
3374
  });
3294
3375
  share.command("list").alias("ls").description("List active share links").option("-a, --all", "Include expired and revoked links").action(async (options) => {
3295
3376
  if (!configManager.isAuthenticated()) {
3296
- ui.error("Not authenticated. Run `sstash login` first.");
3377
+ ui.error("Not authenticated. Run `stash login` first.");
3297
3378
  process.exit(1);
3298
3379
  }
3299
3380
  const spinner = ui.spinner("Fetching share links...");
@@ -3308,7 +3389,7 @@ function registerShareCommands(program2) {
3308
3389
  spinner.stop();
3309
3390
  if (!result.data?.shares.length) {
3310
3391
  ui.warning("No share links found");
3311
- ui.info(`Create one with ${ui.code("sstash share create <KEY>")}`);
3392
+ ui.info(`Create one with ${ui.code("stash share create <KEY>")}`);
3312
3393
  return;
3313
3394
  }
3314
3395
  ui.heading("Share Links");
@@ -3335,11 +3416,11 @@ function registerShareCommands(program2) {
3335
3416
  });
3336
3417
  ui.br();
3337
3418
  ui.info(`${result.data.shares.length} share link(s) total`);
3338
- ui.info(`Revoke with ${ui.code("sstash share revoke <id>")}`);
3419
+ ui.info(`Revoke with ${ui.code("stash share revoke <id>")}`);
3339
3420
  });
3340
3421
  share.command("revoke <id>").description("Revoke a share link").option("-f, --force", "Skip confirmation prompt").action(async (id, options) => {
3341
3422
  if (!configManager.isAuthenticated()) {
3342
- ui.error("Not authenticated. Run `sstash login` first.");
3423
+ ui.error("Not authenticated. Run `stash login` first.");
3343
3424
  process.exit(1);
3344
3425
  }
3345
3426
  if (!options.force) {
@@ -3472,7 +3553,7 @@ async function checkAuthentication() {
3472
3553
  name: "Authentication",
3473
3554
  status: "fail",
3474
3555
  message: "Not authenticated",
3475
- hint: "Run: sstash login"
3556
+ hint: "Run: stash login"
3476
3557
  };
3477
3558
  }
3478
3559
  const user = configManager.getUser();
@@ -3503,7 +3584,7 @@ function checkTokenExpiration() {
3503
3584
  name: "Token expiration",
3504
3585
  status: "fail",
3505
3586
  message: "No token found",
3506
- hint: "Run: sstash login"
3587
+ hint: "Run: stash login"
3507
3588
  };
3508
3589
  }
3509
3590
  if (configManager.isTokenExpired()) {
@@ -3511,7 +3592,7 @@ function checkTokenExpiration() {
3511
3592
  name: "Token expiration",
3512
3593
  status: "warn",
3513
3594
  message: "Token expired or expiring soon",
3514
- hint: "Run: sstash login"
3595
+ hint: "Run: stash login"
3515
3596
  };
3516
3597
  }
3517
3598
  return {
@@ -3535,7 +3616,7 @@ function checkTeamSelected() {
3535
3616
  name: "Team selected",
3536
3617
  status: "fail",
3537
3618
  message: "No team selected",
3538
- hint: "Run: sstash teams use <slug>"
3619
+ hint: "Run: stash teams use <slug>"
3539
3620
  };
3540
3621
  }
3541
3622
  function checkProjectSelected() {
@@ -3553,7 +3634,7 @@ function checkProjectSelected() {
3553
3634
  name: "Project selected",
3554
3635
  status: "fail",
3555
3636
  message: "No project selected",
3556
- hint: "Run: sstash projects use <slug>"
3637
+ hint: "Run: stash projects use <slug>"
3557
3638
  };
3558
3639
  }
3559
3640
  function checkEnvironmentSelected() {
@@ -3570,7 +3651,7 @@ function checkEnvironmentSelected() {
3570
3651
  name: "Environment selected",
3571
3652
  status: "fail",
3572
3653
  message: "No environment selected",
3573
- hint: "Run: sstash environments use <name>"
3654
+ hint: "Run: stash environments use <name>"
3574
3655
  };
3575
3656
  }
3576
3657
  function checkLocalConfigExists() {
@@ -3585,7 +3666,7 @@ function checkLocalConfigExists() {
3585
3666
  name: "Local config file",
3586
3667
  status: "warn",
3587
3668
  message: ".secretstash.json not found",
3588
- hint: "Run: sstash init (optional, for project-specific config)"
3669
+ hint: "Run: stash init (optional, for project-specific config)"
3589
3670
  };
3590
3671
  }
3591
3672
  function checkLocalConfigValid() {
@@ -3758,12 +3839,12 @@ function registerTagCommands(program2) {
3758
3839
  const tags = program2.command("tags").description("Manage tags for organizing secrets");
3759
3840
  tags.command("list").alias("ls").description("List all tags in the current team").action(async () => {
3760
3841
  if (!configManager.isAuthenticated()) {
3761
- ui.error("Not authenticated. Run `sstash login` first.");
3842
+ ui.error("Not authenticated. Run `stash login` first.");
3762
3843
  process.exit(1);
3763
3844
  }
3764
3845
  const currentTeam = configManager.getCurrentTeam();
3765
3846
  if (!currentTeam) {
3766
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
3847
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
3767
3848
  process.exit(1);
3768
3849
  }
3769
3850
  const spinner = ui.spinner("Fetching tags...");
@@ -3776,7 +3857,7 @@ function registerTagCommands(program2) {
3776
3857
  spinner.stop();
3777
3858
  if (!result.data?.tags.length) {
3778
3859
  ui.warning("No tags found");
3779
- ui.info(`Create one with ${ui.code("sstash tags create <name>")}`);
3860
+ ui.info(`Create one with ${ui.code("stash tags create <name>")}`);
3780
3861
  return;
3781
3862
  }
3782
3863
  ui.heading(`Tags in ${currentTeam.name}`);
@@ -3793,12 +3874,12 @@ function registerTagCommands(program2) {
3793
3874
  });
3794
3875
  tags.command("create <name>").description("Create a new tag").option("-c, --color <hex>", "Hex color for the tag (e.g., #FF5733)").action(async (name, options) => {
3795
3876
  if (!configManager.isAuthenticated()) {
3796
- ui.error("Not authenticated. Run `sstash login` first.");
3877
+ ui.error("Not authenticated. Run `stash login` first.");
3797
3878
  process.exit(1);
3798
3879
  }
3799
3880
  const currentTeam = configManager.getCurrentTeam();
3800
3881
  if (!currentTeam) {
3801
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
3882
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
3802
3883
  process.exit(1);
3803
3884
  }
3804
3885
  if (options.color && !/^#[0-9A-Fa-f]{6}$/.test(options.color)) {
@@ -3821,12 +3902,12 @@ function registerTagCommands(program2) {
3821
3902
  });
3822
3903
  tags.command("delete <name>").alias("rm").description("Delete a tag").option("-f, --force", "Skip confirmation prompt").action(async (name, options) => {
3823
3904
  if (!configManager.isAuthenticated()) {
3824
- ui.error("Not authenticated. Run `sstash login` first.");
3905
+ ui.error("Not authenticated. Run `stash login` first.");
3825
3906
  process.exit(1);
3826
3907
  }
3827
3908
  const currentTeam = configManager.getCurrentTeam();
3828
3909
  if (!currentTeam) {
3829
- ui.error("No team selected. Run `sstash teams use <slug>` first.");
3910
+ ui.error("No team selected. Run `stash teams use <slug>` first.");
3830
3911
  process.exit(1);
3831
3912
  }
3832
3913
  const spinner = ui.spinner("Looking up tag...");
@@ -4034,10 +4115,10 @@ function registerExecCommand(program2) {
4034
4115
  program2.command("exec").description("Run a command with secrets injected as environment variables (CI/CD friendly)").requiredOption("--project <slug>", "Project slug").requiredOption("--env <environment>", "Environment name (e.g., production, staging)").option("--token <token>", "Service token (defaults to SECRETS_TOKEN env var)").option("--api-url <url>", "API URL (defaults to VAULT_API_URL env var or configured URL)").argument("<command...>", "Command to run").allowExcessArguments(true).action(async (commandArgs, options) => {
4035
4116
  if (!commandArgs || commandArgs.length === 0) {
4036
4117
  ui.error("No command provided");
4037
- ui.info("Usage: sstash exec --project=<slug> --env=<environment> -- <command> [args...]");
4118
+ ui.info("Usage: stash exec --project=<slug> --env=<environment> -- <command> [args...]");
4038
4119
  ui.info("");
4039
4120
  ui.info("Example:");
4040
- ui.info(" sstash exec --project=myapp --env=production -- npm run build");
4121
+ ui.info(" stash exec --project=myapp --env=production -- npm run build");
4041
4122
  process.exit(1);
4042
4123
  }
4043
4124
  const token = options.token || process.env.SECRETS_TOKEN;
@@ -4046,7 +4127,7 @@ function registerExecCommand(program2) {
4046
4127
  ui.info("Provide a token via --token flag or SECRETS_TOKEN environment variable");
4047
4128
  ui.info("");
4048
4129
  ui.info("Generate a service token from the SecretStash dashboard or CLI:");
4049
- ui.info(' sstash tokens create --name "CI Token" --env production');
4130
+ ui.info(' stash tokens create --name "CI Token" --env production');
4050
4131
  process.exit(1);
4051
4132
  }
4052
4133
  if (!token.startsWith("stk_")) {
@@ -4126,6 +4207,154 @@ function registerExecCommand(program2) {
4126
4207
  });
4127
4208
  }
4128
4209
 
4210
+ // src/commands/context.ts
4211
+ function registerContextCommands(program2) {
4212
+ const context = program2.command("context").alias("ctx").description("View and manage current context (team, project, environment)");
4213
+ context.action(async () => {
4214
+ ui.heading("Current Context");
4215
+ const team = configManager.getCurrentTeam();
4216
+ const project = configManager.getCurrentProject();
4217
+ const environment = configManager.getCurrentEnvironment();
4218
+ const localTeam = projectConfig.getEffectiveTeam();
4219
+ const localProject = projectConfig.getEffectiveProject();
4220
+ const localEnv = projectConfig.getEffectiveEnvironment();
4221
+ const hasLocalConfig = localTeam || localProject || localEnv;
4222
+ if (team) {
4223
+ ui.keyValue("Team", `${team.name} ${colors.muted(`(${team.slug})`)}`);
4224
+ } else {
4225
+ ui.keyValue("Team", colors.warning("Not set"));
4226
+ }
4227
+ if (project) {
4228
+ ui.keyValue("Project", `${project.name} ${colors.muted(`(${project.slug})`)}`);
4229
+ } else {
4230
+ ui.keyValue("Project", colors.warning("Not set"));
4231
+ }
4232
+ if (environment) {
4233
+ ui.keyValue("Environment", ui.envBadge(environment));
4234
+ } else {
4235
+ ui.keyValue("Environment", colors.warning("Not set"));
4236
+ }
4237
+ if (hasLocalConfig) {
4238
+ ui.br();
4239
+ ui.subheading("Local Config (.secretstash.json)");
4240
+ if (localTeam) ui.keyValue("Team", localTeam);
4241
+ if (localProject) ui.keyValue("Project", localProject);
4242
+ if (localEnv) ui.keyValue("Environment", localEnv);
4243
+ }
4244
+ const missing = [];
4245
+ if (!team && !localTeam) missing.push("team");
4246
+ if (!project && !localProject) missing.push("project");
4247
+ if (!environment && !localEnv) missing.push("environment");
4248
+ if (missing.length > 0) {
4249
+ ui.br();
4250
+ ui.warning(`Missing: ${missing.join(", ")}`);
4251
+ ui.info(`Run ${ui.code("stash init")} to set up your context`);
4252
+ } else {
4253
+ ui.br();
4254
+ ui.success("Context is fully configured");
4255
+ }
4256
+ });
4257
+ context.command("show").description("Show current context").action(async () => {
4258
+ await context.parseAsync([], { from: "user" });
4259
+ });
4260
+ context.command("clear").description("Clear all context (team, project, environment)").option("--team", "Clear only team context").option("--project", "Clear only project context").option("--env", "Clear only environment context").action((options) => {
4261
+ if (options.team) {
4262
+ configManager.clearCurrentTeam();
4263
+ configManager.clearCurrentProject();
4264
+ configManager.clearCurrentEnvironment();
4265
+ ui.success("Team context cleared (project and environment also cleared)");
4266
+ } else if (options.project) {
4267
+ configManager.clearCurrentProject();
4268
+ configManager.clearCurrentEnvironment();
4269
+ ui.success("Project context cleared (environment also cleared)");
4270
+ } else if (options.env) {
4271
+ configManager.clearCurrentEnvironment();
4272
+ ui.success("Environment context cleared");
4273
+ } else {
4274
+ configManager.clearCurrentTeam();
4275
+ configManager.clearCurrentProject();
4276
+ configManager.clearCurrentEnvironment();
4277
+ ui.success("All context cleared");
4278
+ }
4279
+ });
4280
+ context.command("set").description("Interactively set context (team, project, environment)").action(async () => {
4281
+ if (!configManager.isAuthenticated()) {
4282
+ ui.error("Not authenticated. Run `stash login` first.");
4283
+ process.exit(1);
4284
+ }
4285
+ ui.info("Use `stash init` for full interactive setup");
4286
+ ui.info("Or use these commands to set context individually:");
4287
+ ui.br();
4288
+ ui.keyValue("Team", ui.code("stash teams use <slug>"));
4289
+ ui.keyValue("Project", ui.code("stash projects use <slug>"));
4290
+ ui.keyValue("Environment", ui.code("stash environments use <slug>"));
4291
+ });
4292
+ context.command("verify").description("Verify current context is valid and accessible").action(async () => {
4293
+ if (!configManager.isAuthenticated()) {
4294
+ ui.error("Not authenticated. Run `stash login` first.");
4295
+ process.exit(1);
4296
+ }
4297
+ const team = configManager.getCurrentTeam();
4298
+ const project = configManager.getCurrentProject();
4299
+ const environment = configManager.getCurrentEnvironment();
4300
+ let allValid = true;
4301
+ const spinner = ui.spinner("Verifying context...");
4302
+ if (team) {
4303
+ const teamsResult = await apiClient.getTeams();
4304
+ const teamExists = teamsResult.data?.teams.some((t) => t.id === team.id);
4305
+ if (teamExists) {
4306
+ spinner.text = `Team "${team.name}" \u2713`;
4307
+ } else {
4308
+ spinner.stop();
4309
+ ui.error(`Team "${team.name}" not found or no longer accessible`);
4310
+ allValid = false;
4311
+ }
4312
+ } else {
4313
+ spinner.stop();
4314
+ ui.warning("No team set");
4315
+ allValid = false;
4316
+ }
4317
+ if (team && project) {
4318
+ const projectsResult = await apiClient.getProjects(team.id);
4319
+ const projectExists = projectsResult.data?.projects.some((p) => p.id === project.id);
4320
+ if (projectExists) {
4321
+ spinner.text = `Project "${project.name}" \u2713`;
4322
+ } else {
4323
+ spinner.stop();
4324
+ ui.error(`Project "${project.name}" not found in team "${team.name}"`);
4325
+ allValid = false;
4326
+ }
4327
+ } else if (!project) {
4328
+ spinner.stop();
4329
+ ui.warning("No project set");
4330
+ allValid = false;
4331
+ }
4332
+ if (team && project && environment) {
4333
+ const envsResult = await apiClient.getEnvironments(project.id);
4334
+ const envExists = envsResult.data?.environments.some((e) => e.slug === environment);
4335
+ if (envExists) {
4336
+ spinner.succeed("Context verified");
4337
+ } else {
4338
+ spinner.stop();
4339
+ ui.error(`Environment "${environment}" not found in project "${project.name}"`);
4340
+ allValid = false;
4341
+ }
4342
+ } else if (!environment) {
4343
+ spinner.stop();
4344
+ ui.warning("No environment set");
4345
+ allValid = false;
4346
+ }
4347
+ if (allValid) {
4348
+ ui.br();
4349
+ ui.success("All context is valid and accessible");
4350
+ } else {
4351
+ ui.br();
4352
+ ui.info(`Run ${ui.code("stash init")} to fix context issues`);
4353
+ process.exit(1);
4354
+ }
4355
+ });
4356
+ }
4357
+
4129
4358
  // src/index.ts
4130
4359
  var __dirname = dirname(fileURLToPath(import.meta.url));
4131
4360
  var version = "0.1.0";
@@ -4150,7 +4379,7 @@ var banner = chalk3.hex("#6366f1")(`
4150
4379
  \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
4151
4380
  `);
4152
4381
  var program = new Command();
4153
- program.name("sstash").description("SecretStash CLI - Ultra-secure team secrets management").version(version, "-v, --version", "Output the current version").option("--no-banner", "Disable the banner").option("-q, --quiet", "Suppress non-essential output (useful for CI/CD)").hook("preAction", (thisCommand) => {
4382
+ program.name("stash").description("SecretStash CLI - Ultra-secure team secrets management").version(version, "-v, --version", "Output the current version").option("--no-banner", "Disable the banner").option("-q, --quiet", "Suppress non-essential output (useful for CI/CD)").hook("preAction", (thisCommand) => {
4154
4383
  const opts = thisCommand.opts();
4155
4384
  if (opts.quiet) {
4156
4385
  configManager.setQuiet(true);
@@ -4164,6 +4393,7 @@ registerTeamCommands(program);
4164
4393
  registerProjectCommands(program);
4165
4394
  registerEnvironmentCommands(program);
4166
4395
  registerSecretCommands(program);
4396
+ registerContextCommands(program);
4167
4397
  registerDiffCommand(program);
4168
4398
  registerShareCommands(program);
4169
4399
  registerDoctorCommand(program);
@@ -4173,44 +4403,47 @@ registerExecCommand(program);
4173
4403
  program.addHelpText("after", `
4174
4404
  ${chalk3.bold("Examples:")}
4175
4405
  ${chalk3.dim("# Authenticate")}
4176
- $ sstash login
4177
- $ sstash whoami
4406
+ $ stash login
4407
+ $ stash whoami
4408
+
4409
+ ${chalk3.dim("# View current context")}
4410
+ $ stash context
4178
4411
 
4179
- ${chalk3.dim("# Set up context")}
4180
- $ sstash teams use my-team
4181
- $ sstash projects use my-project
4182
- $ sstash environments use development
4412
+ ${chalk3.dim("# Set up context interactively")}
4413
+ $ stash init
4183
4414
 
4184
- ${chalk3.dim("# Or initialize in current directory")}
4185
- $ sstash init
4415
+ ${chalk3.dim("# Or set context manually")}
4416
+ $ stash teams use my-team
4417
+ $ stash projects use my-project
4418
+ $ stash environments use development
4186
4419
 
4187
4420
  ${chalk3.dim("# Manage secrets")}
4188
- $ sstash pull # Pull secrets to .env
4189
- $ sstash push # Push secrets from .env
4190
- $ sstash run npm start # Run with secrets injected
4421
+ $ stash pull # Pull secrets to .env
4422
+ $ stash push # Push secrets from .env
4423
+ $ stash run npm start # Run with secrets injected
4191
4424
 
4192
4425
  ${chalk3.dim("# View secrets")}
4193
- $ sstash secrets list
4194
- $ sstash secrets list --reveal
4426
+ $ stash secrets list
4427
+ $ stash secrets list --reveal
4195
4428
 
4196
4429
  ${chalk3.dim("# Compare environments")}
4197
- $ sstash diff development production
4430
+ $ stash diff development production
4198
4431
 
4199
4432
  ${chalk3.dim("# Share secrets")}
4200
- $ sstash share create DATABASE_URL --expires 24h --one-time
4201
- $ sstash share list
4202
- $ sstash share revoke <id>
4433
+ $ stash share create DATABASE_URL --expires 24h --one-time
4434
+ $ stash share list
4435
+ $ stash share revoke <id>
4203
4436
 
4204
4437
  ${chalk3.dim("# Organize with tags")}
4205
- $ sstash tags create database --color #FF5733
4206
- $ sstash tags list
4207
- $ sstash secrets tag DATABASE_URL database
4438
+ $ stash tags create database --color #FF5733
4439
+ $ stash tags list
4440
+ $ stash secrets tag DATABASE_URL database
4208
4441
 
4209
4442
  ${chalk3.dim("# CI/CD - pull secrets or run commands with secrets injected")}
4210
- $ SECRETS_TOKEN=stk_xxx sstash ci-pull -p myapp -e production
4211
- $ SECRETS_TOKEN=stk_xxx sstash ci-pull -p myapp -e production --format json
4212
- $ eval "$(SECRETS_TOKEN=stk_xxx sstash ci-pull -p myapp -e production --format shell)"
4213
- $ SECRETS_TOKEN=stk_... sstash exec --project=myapp --env=production -- npm run build
4443
+ $ SECRETS_TOKEN=stk_xxx stash ci-pull -p myapp -e production
4444
+ $ SECRETS_TOKEN=stk_xxx stash ci-pull -p myapp -e production --format json
4445
+ $ eval "$(SECRETS_TOKEN=stk_xxx stash ci-pull -p myapp -e production --format shell)"
4446
+ $ SECRETS_TOKEN=stk_... stash exec --project=myapp --env=production -- npm run build
4214
4447
 
4215
4448
  ${chalk3.bold("Documentation:")}
4216
4449
  https://secretstash.dev/docs
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@secretstash/cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "CLI tool for SecretStash - secure team secrets management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
+ "stash": "./bin/vault.js",
8
9
  "sstash": "./bin/vault.js"
9
10
  },
10
11
  "scripts": {