thinkwork-cli 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +125 -28
  2. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -802,6 +802,7 @@ function registerBootstrapCommand(program2) {
802
802
  // src/commands/login.ts
803
803
  import { execSync as execSync4 } from "child_process";
804
804
  import { createInterface as createInterface2 } from "readline";
805
+ import { select, Separator } from "@inquirer/prompts";
805
806
  import chalk5 from "chalk";
806
807
 
807
808
  // src/aws-profiles.ts
@@ -1036,33 +1037,41 @@ function describeType(type) {
1036
1037
  }
1037
1038
  }
1038
1039
  async function pickProfile(profiles) {
1039
- console.log("");
1040
- console.log(chalk5.bold(" Found these profiles in ~/.aws:"));
1041
- profiles.forEach((p, i) => {
1042
- const idx = String(i + 1).padStart(2, " ");
1043
- console.log(
1044
- ` ${chalk5.cyan(idx)}. ${chalk5.bold(p.name)} ${chalk5.dim(`(${describeType(p.type)})`)}`
1040
+ if (!process.stdin.isTTY) {
1041
+ printError(
1042
+ "The profile picker needs an interactive terminal. Re-run with --keys, --sso, or --profile <name>."
1045
1043
  );
1046
- });
1047
- const newIdx = profiles.length + 1;
1048
- const ssoIdx = profiles.length + 2;
1049
- console.log(
1050
- ` ${chalk5.cyan(String(newIdx).padStart(2, " "))}. Enter new access keys`
1051
- );
1052
- console.log(
1053
- ` ${chalk5.cyan(String(ssoIdx).padStart(2, " "))}. Log in via AWS SSO`
1054
- );
1055
- console.log("");
1056
- const answer = await ask(` Pick a profile [1-${ssoIdx}] (Enter to cancel): `);
1057
- if (!answer) return { kind: "cancel" };
1058
- const n = Number.parseInt(answer, 10);
1059
- if (Number.isNaN(n) || n < 1 || n > ssoIdx) {
1060
- printError(`"${answer}" is not a valid option.`);
1061
1044
  return { kind: "cancel" };
1062
1045
  }
1063
- if (n === newIdx) return { kind: "keys" };
1064
- if (n === ssoIdx) return { kind: "sso" };
1065
- return { kind: "existing", name: profiles[n - 1].name };
1046
+ const choices = profiles.map((p) => ({
1047
+ name: `${p.name} ${chalk5.dim(`(${describeType(p.type)})`)}`,
1048
+ value: { kind: "existing", name: p.name }
1049
+ }));
1050
+ choices.push(new Separator());
1051
+ choices.push({
1052
+ name: "Enter new access keys",
1053
+ value: { kind: "keys" },
1054
+ description: "Paste an AWS Access Key ID and Secret Access Key; saved to a new profile."
1055
+ });
1056
+ choices.push({
1057
+ name: "Log in via AWS SSO",
1058
+ value: { kind: "sso" },
1059
+ description: "Run `aws sso login` against the configured SSO profile."
1060
+ });
1061
+ try {
1062
+ const picked = await select({
1063
+ message: "Pick an AWS profile for Thinkwork:",
1064
+ choices,
1065
+ loop: false,
1066
+ pageSize: Math.max(profiles.length + 2, 10)
1067
+ });
1068
+ return picked;
1069
+ } catch (err) {
1070
+ if (err instanceof Error && err.name === "ExitPromptError") {
1071
+ return { kind: "cancel" };
1072
+ }
1073
+ throw err;
1074
+ }
1066
1075
  }
1067
1076
  async function runKeyEntry(targetProfile) {
1068
1077
  console.log("");
@@ -1145,7 +1154,26 @@ function registerLoginCommand(program2) {
1145
1154
  "--profile <name>",
1146
1155
  "AWS profile name to configure (used when entering new keys or SSO)",
1147
1156
  "thinkwork"
1148
- ).option("--sso", "Skip the picker and go straight to SSO login").option("--keys", "Skip the picker and go straight to access-key entry").action(async (opts) => {
1157
+ ).option("--sso", "Skip the picker and go straight to SSO login").option("--keys", "Skip the picker and go straight to access-key entry").addHelpText(
1158
+ "after",
1159
+ `
1160
+ Examples:
1161
+ # Interactive picker \u2014 lists profiles from ~/.aws, verifies the one you pick,
1162
+ # and saves it as your Thinkwork default.
1163
+ $ thinkwork login
1164
+
1165
+ # Skip the picker, enter fresh access keys into a named profile
1166
+ $ thinkwork login --keys --profile thinkwork
1167
+
1168
+ # Skip the picker, log in via AWS SSO
1169
+ $ thinkwork login --sso --profile work-sso
1170
+
1171
+ After login, commands resolve the AWS profile in this order:
1172
+ 1. --profile <name> (per-command override)
1173
+ 2. $AWS_PROFILE env var
1174
+ 3. defaultProfile from ~/.thinkwork/config.json (set by this command)
1175
+ `
1176
+ ).action(async (opts) => {
1149
1177
  printHeader("login", opts.profile);
1150
1178
  const awsOk = await ensureAwsCli();
1151
1179
  if (!awsOk) process.exit(1);
@@ -1744,7 +1772,30 @@ function printStageDetail(info) {
1744
1772
  function registerStatusCommand(program2) {
1745
1773
  program2.command("status").alias("list").alias("ls").description(
1746
1774
  "Show all Thinkwork environments / deployments (AWS + local). Aliases: list, ls"
1747
- ).option("-s, --stage <name>", "Show details for a specific stage").option("--region <region>", "AWS region to scan", "us-east-1").action(async (opts) => {
1775
+ ).option("-s, --stage <name>", "Show details for a specific stage").option("--region <region>", "AWS region to scan", "us-east-1").addHelpText(
1776
+ "after",
1777
+ `
1778
+ Examples:
1779
+ # List every deployment in the current AWS account (us-east-1)
1780
+ $ thinkwork list
1781
+
1782
+ # Same thing, tighter verb
1783
+ $ thinkwork ls
1784
+
1785
+ # Deep-dive on one stage (same info but scoped)
1786
+ $ thinkwork list -s dev
1787
+
1788
+ # Scan a different region
1789
+ $ thinkwork list --region us-west-2
1790
+
1791
+ # Use a specific AWS profile for this call only
1792
+ $ thinkwork --profile work-sso list
1793
+
1794
+ Discovers stages by looking for \`thinkwork-<stage>-api-graphql-http\`
1795
+ Lambdas and fans out to API Gateway, AppSync, S3, RDS, ECS, CloudFront,
1796
+ and AgentCore for per-stage detail.
1797
+ `
1798
+ ).action(async (opts) => {
1748
1799
  const identity = getAwsIdentity();
1749
1800
  printHeader("status", opts.stage || "all", identity);
1750
1801
  if (!identity) {
@@ -2342,7 +2393,38 @@ function registerUserCommand(program2) {
2342
2393
  const user = program2.command("user").description("User-management utilities for a deployed Thinkwork stack");
2343
2394
  user.command("invite <email>").description(
2344
2395
  "Invite a teammate to a tenant. Creates the Cognito user (Cognito emails a temporary password) and adds them as a tenant member."
2345
- ).requiredOption("-s, --stage <name>", "Deployment stage").requiredOption("--tenant <slug>", "Tenant slug").option("--name <name>", "Display name for the invited user").option("--role <role>", "Tenant member role", "member").action(
2396
+ ).requiredOption("-s, --stage <name>", "Deployment stage (e.g. dev, prod)").requiredOption(
2397
+ "--tenant <slug>",
2398
+ "Tenant slug (the URL-safe tenant id, e.g. acme)"
2399
+ ).option("--name <name>", "Display name for the invited user").option(
2400
+ "--role <role>",
2401
+ 'Tenant member role: "member", "admin", or "owner"',
2402
+ "member"
2403
+ ).addHelpText(
2404
+ "after",
2405
+ `
2406
+ Examples:
2407
+ # Invite a teammate as a regular member
2408
+ $ thinkwork user invite alice@example.com --tenant acme -s dev
2409
+
2410
+ # Invite with a display name and admin role
2411
+ $ thinkwork user invite bob@example.com --tenant acme -s dev \\
2412
+ --name "Bob Smith" --role admin
2413
+
2414
+ # Re-inviting someone who's already a member is a no-op (no second email)
2415
+ $ thinkwork user invite alice@example.com --tenant acme -s dev
2416
+ \u26A0 alice@example.com is already a member of "acme" (role: member). No email sent.
2417
+
2418
+ What happens:
2419
+ 1. A Cognito user is created (or reused if the email already exists).
2420
+ 2. Cognito emails the user a temporary password.
2421
+ 3. The user is added to the tenant with the given role.
2422
+ 4. On first sign-in they're prompted to set a real password.
2423
+
2424
+ Requires the stack to be deployed (the CLI discovers the API Gateway URL
2425
+ and reads api_auth_secret from terraform.tfvars for the stage).
2426
+ `
2427
+ ).action(
2346
2428
  async (email, opts) => {
2347
2429
  const stageCheck = validateStage(opts.stage);
2348
2430
  if (!stageCheck.valid) {
@@ -2402,9 +2484,24 @@ function registerUserCommand(program2) {
2402
2484
  );
2403
2485
  user.command("reset-password <email>").description(
2404
2486
  "Trigger Cognito's forgot-password flow for a user (admin-initiated). Sends them a verification code email."
2405
- ).option("-p, --profile <name>", "AWS profile").requiredOption("-s, --stage <name>", "Deployment stage").option(
2487
+ ).option("-p, --profile <name>", "AWS profile").requiredOption("-s, --stage <name>", "Deployment stage (e.g. dev, prod)").option(
2406
2488
  "-r, --region <name>",
2407
2489
  "AWS region (defaults to AWS CLI default / AWS_REGION)"
2490
+ ).addHelpText(
2491
+ "after",
2492
+ `
2493
+ Examples:
2494
+ # Admin-triggered password reset \u2014 works even if the account is locked
2495
+ $ thinkwork user reset-password alice@example.com -s dev
2496
+
2497
+ # Target a specific AWS profile + region
2498
+ $ thinkwork user reset-password alice@example.com -s prod \\
2499
+ --profile thinkwork --region us-east-1
2500
+
2501
+ Cognito emails the user a verification code; they set a new password on
2502
+ next sign-in. Use this instead of \`forgot-password\` when the user is in
2503
+ FORCE_CHANGE_PASSWORD or has been disabled.
2504
+ `
2408
2505
  ).action(
2409
2506
  async (email, opts) => {
2410
2507
  const stageCheck = validateStage(opts.stage);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkwork-cli",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Thinkwork CLI — deploy, manage, and interact with your Thinkwork stack",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,6 +19,7 @@
19
19
  "prepublishOnly": "npm run build && npm run typecheck"
20
20
  },
21
21
  "dependencies": {
22
+ "@inquirer/prompts": "^8.4.1",
22
23
  "chalk": "^5.6.2",
23
24
  "commander": "^12.0.0",
24
25
  "ora": "^9.3.0"