thinkwork-cli 0.1.1 → 0.2.1

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 (38) hide show
  1. package/dist/cli.js +311 -54
  2. package/dist/terraform/examples/greenfield/main.tf +190 -0
  3. package/dist/terraform/examples/greenfield/terraform.tfvars.example +28 -0
  4. package/dist/terraform/modules/_internal/workspace-guard/main.tf +29 -0
  5. package/dist/terraform/modules/app/agentcore-runtime/main.tf +217 -0
  6. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +122 -0
  7. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +20 -0
  8. package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +31 -0
  9. package/dist/terraform/modules/app/crons/main.tf +55 -0
  10. package/dist/terraform/modules/app/hindsight-memory/README.md +66 -0
  11. package/dist/terraform/modules/app/hindsight-memory/main.tf +331 -0
  12. package/dist/terraform/modules/app/job-triggers/main.tf +70 -0
  13. package/dist/terraform/modules/app/lambda-api/.build/placeholder.zip +0 -0
  14. package/dist/terraform/modules/app/lambda-api/handlers.tf +311 -0
  15. package/dist/terraform/modules/app/lambda-api/main.tf +245 -0
  16. package/dist/terraform/modules/app/lambda-api/outputs.tf +24 -0
  17. package/dist/terraform/modules/app/lambda-api/variables.tf +153 -0
  18. package/dist/terraform/modules/app/ses-email/main.tf +51 -0
  19. package/dist/terraform/modules/app/static-site/main.tf +176 -0
  20. package/dist/terraform/modules/data/aurora-postgres/README.md +92 -0
  21. package/dist/terraform/modules/data/aurora-postgres/main.tf +185 -0
  22. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +30 -0
  23. package/dist/terraform/modules/data/aurora-postgres/variables.tf +114 -0
  24. package/dist/terraform/modules/data/bedrock-knowledge-base/main.tf +102 -0
  25. package/dist/terraform/modules/data/s3-buckets/main.tf +91 -0
  26. package/dist/terraform/modules/foundation/cognito/main.tf +377 -0
  27. package/dist/terraform/modules/foundation/cognito/outputs.tf +29 -0
  28. package/dist/terraform/modules/foundation/cognito/variables.tf +124 -0
  29. package/dist/terraform/modules/foundation/dns/main.tf +49 -0
  30. package/dist/terraform/modules/foundation/kms/main.tf +49 -0
  31. package/dist/terraform/modules/foundation/vpc/main.tf +137 -0
  32. package/dist/terraform/modules/foundation/vpc/outputs.tf +14 -0
  33. package/dist/terraform/modules/foundation/vpc/variables.tf +40 -0
  34. package/dist/terraform/modules/thinkwork/main.tf +212 -0
  35. package/dist/terraform/modules/thinkwork/outputs.tf +87 -0
  36. package/dist/terraform/modules/thinkwork/variables.tf +241 -0
  37. package/dist/terraform/schema.graphql +199 -0
  38. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -83,7 +83,19 @@ function resolveTierDir(terraformDir, stage, tier) {
83
83
  if (existsSync(envDir)) {
84
84
  return envDir;
85
85
  }
86
- return path.join(terraformDir, "examples", "greenfield");
86
+ const greenfield = path.join(terraformDir, "examples", "greenfield");
87
+ if (existsSync(greenfield)) {
88
+ return greenfield;
89
+ }
90
+ const flat = path.join(terraformDir);
91
+ if (existsSync(path.join(flat, "main.tf"))) {
92
+ return flat;
93
+ }
94
+ const cwdTf = path.join(process.cwd(), "terraform");
95
+ if (existsSync(path.join(cwdTf, "main.tf"))) {
96
+ return cwdTf;
97
+ }
98
+ return terraformDir;
87
99
  }
88
100
  async function ensureWorkspace(cwd, stage) {
89
101
  const list = await runTerraformRaw(cwd, ["workspace", "list"]);
@@ -473,19 +485,67 @@ function registerOutputsCommand(program2) {
473
485
  }
474
486
 
475
487
  // src/commands/config.ts
476
- import { readFileSync, writeFileSync, existsSync as existsSync2 } from "fs";
488
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
489
+ import chalk3 from "chalk";
490
+
491
+ // src/environments.ts
492
+ import { existsSync as existsSync2, mkdirSync, writeFileSync, readFileSync, readdirSync } from "fs";
493
+ import { join } from "path";
494
+ import { homedir } from "os";
495
+ var THINKWORK_HOME = join(homedir(), ".thinkwork");
496
+ var ENVIRONMENTS_DIR = join(THINKWORK_HOME, "environments");
497
+ function ensureDir(dir) {
498
+ if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
499
+ }
500
+ function saveEnvironment(config) {
501
+ ensureDir(ENVIRONMENTS_DIR);
502
+ const envDir = join(ENVIRONMENTS_DIR, config.stage);
503
+ ensureDir(envDir);
504
+ writeFileSync(
505
+ join(envDir, "config.json"),
506
+ JSON.stringify(config, null, 2) + "\n"
507
+ );
508
+ }
509
+ function loadEnvironment(stage) {
510
+ const configPath = join(ENVIRONMENTS_DIR, stage, "config.json");
511
+ if (!existsSync2(configPath)) return null;
512
+ return JSON.parse(readFileSync(configPath, "utf-8"));
513
+ }
514
+ function listEnvironments() {
515
+ if (!existsSync2(ENVIRONMENTS_DIR)) return [];
516
+ return readdirSync(ENVIRONMENTS_DIR).filter((name) => {
517
+ return existsSync2(join(ENVIRONMENTS_DIR, name, "config.json"));
518
+ }).map((name) => {
519
+ return JSON.parse(
520
+ readFileSync(join(ENVIRONMENTS_DIR, name, "config.json"), "utf-8")
521
+ );
522
+ }).sort((a, b) => a.stage.localeCompare(b.stage));
523
+ }
524
+ function resolveTerraformDir(stage) {
525
+ const env = loadEnvironment(stage);
526
+ if (env?.terraformDir && existsSync2(env.terraformDir)) {
527
+ return env.terraformDir;
528
+ }
529
+ const envVar = process.env.THINKWORK_TERRAFORM_DIR;
530
+ if (envVar && existsSync2(envVar)) return envVar;
531
+ const cwdTf = join(process.cwd(), "terraform");
532
+ if (existsSync2(join(cwdTf, "main.tf"))) return cwdTf;
533
+ return null;
534
+ }
535
+
536
+ // src/commands/config.ts
477
537
  var VALID_MEMORY_ENGINES = ["managed", "hindsight"];
478
538
  function readTfVar(tfvarsPath, key) {
479
- if (!existsSync2(tfvarsPath)) return null;
480
- const content = readFileSync(tfvarsPath, "utf-8");
539
+ if (!existsSync3(tfvarsPath)) return null;
540
+ const content = readFileSync2(tfvarsPath, "utf-8");
481
541
  const match = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
482
542
  return match ? match[1] : null;
483
543
  }
484
544
  function setTfVar(tfvarsPath, key, value) {
485
- if (!existsSync2(tfvarsPath)) {
545
+ if (!existsSync3(tfvarsPath)) {
486
546
  throw new Error(`terraform.tfvars not found at ${tfvarsPath}`);
487
547
  }
488
- let content = readFileSync(tfvarsPath, "utf-8");
548
+ let content = readFileSync2(tfvarsPath, "utf-8");
489
549
  const regex = new RegExp(`^(${key}\\s*=\\s*)"[^"]*"`, "m");
490
550
  if (regex.test(content)) {
491
551
  content = content.replace(regex, `$1"${value}"`);
@@ -494,19 +554,93 @@ function setTfVar(tfvarsPath, key, value) {
494
554
  ${key} = "${value}"
495
555
  `;
496
556
  }
497
- writeFileSync(tfvarsPath, content);
557
+ writeFileSync2(tfvarsPath, content);
558
+ }
559
+ function resolveTfvarsPath(stage) {
560
+ const tfDir = resolveTerraformDir(stage);
561
+ if (tfDir) {
562
+ const direct = `${tfDir}/terraform.tfvars`;
563
+ if (existsSync3(direct)) return direct;
564
+ }
565
+ const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
566
+ const cwd = resolveTierDir(terraformDir, stage, "app");
567
+ return `${cwd}/terraform.tfvars`;
498
568
  }
499
569
  function registerConfigCommand(program2) {
500
570
  const config = program2.command("config").description("View or change stack configuration");
571
+ config.command("list").description("List all environments, or show config for a specific stage").option("-s, --stage <name>", "Show config for a specific stage").action((opts) => {
572
+ if (opts.stage) {
573
+ const env = loadEnvironment(opts.stage);
574
+ if (!env) {
575
+ printError(`Environment "${opts.stage}" not found. Run \`thinkwork init -s ${opts.stage}\` first.`);
576
+ process.exit(1);
577
+ }
578
+ console.log("");
579
+ console.log(chalk3.bold.cyan(` \u2B21 ${env.stage}`));
580
+ console.log(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
581
+ console.log(` ${chalk3.bold("Region:")} ${env.region}`);
582
+ console.log(` ${chalk3.bold("Account:")} ${env.accountId}`);
583
+ console.log(` ${chalk3.bold("Database:")} ${env.databaseEngine}`);
584
+ console.log(` ${chalk3.bold("Memory:")} ${env.memoryEngine}`);
585
+ console.log(` ${chalk3.bold("Terraform dir:")} ${env.terraformDir}`);
586
+ console.log(` ${chalk3.bold("Created:")} ${env.createdAt}`);
587
+ console.log(` ${chalk3.bold("Updated:")} ${env.updatedAt}`);
588
+ console.log(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
589
+ const tfvarsPath = `${env.terraformDir}/terraform.tfvars`;
590
+ if (existsSync3(tfvarsPath)) {
591
+ console.log("");
592
+ console.log(chalk3.dim(" terraform.tfvars:"));
593
+ const content = readFileSync2(tfvarsPath, "utf-8");
594
+ for (const line of content.split("\n")) {
595
+ if (line.trim() && !line.trim().startsWith("#")) {
596
+ const masked = line.replace(
597
+ /^(db_password\s*=\s*)".*"/,
598
+ '$1"********"'
599
+ ).replace(
600
+ /^(api_auth_secret\s*=\s*)".*"/,
601
+ '$1"********"'
602
+ ).replace(
603
+ /^(google_oauth_client_secret\s*=\s*)".*"/,
604
+ '$1"********"'
605
+ );
606
+ console.log(` ${chalk3.dim(masked)}`);
607
+ }
608
+ }
609
+ }
610
+ console.log("");
611
+ return;
612
+ }
613
+ const envs = listEnvironments();
614
+ if (envs.length === 0) {
615
+ console.log("");
616
+ console.log(" No environments found.");
617
+ console.log(` Run ${chalk3.cyan("thinkwork init -s <stage>")} to create one.`);
618
+ console.log("");
619
+ return;
620
+ }
621
+ console.log("");
622
+ console.log(chalk3.bold(" Environments"));
623
+ console.log(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
624
+ for (const env of envs) {
625
+ const memBadge = env.memoryEngine === "hindsight" ? chalk3.magenta("hindsight") : chalk3.dim("managed");
626
+ const dbBadge = env.databaseEngine === "rds-postgres" ? chalk3.yellow("rds") : chalk3.dim("aurora");
627
+ console.log(
628
+ ` ${chalk3.bold.cyan(env.stage.padEnd(16))}${env.region.padEnd(14)}${env.accountId.padEnd(16)}${dbBadge.padEnd(20)}${memBadge}`
629
+ );
630
+ }
631
+ console.log(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
632
+ console.log(chalk3.dim(` ${envs.length} environment(s)`));
633
+ console.log("");
634
+ console.log(` Show details: ${chalk3.cyan("thinkwork config list -s <stage>")}`);
635
+ console.log("");
636
+ });
501
637
  config.command("get <key>").description("Get a configuration value (e.g. memory-engine)").requiredOption("-s, --stage <name>", "Deployment stage").action((key, opts) => {
502
638
  const stageCheck = validateStage(opts.stage);
503
639
  if (!stageCheck.valid) {
504
640
  printError(stageCheck.error);
505
641
  process.exit(1);
506
642
  }
507
- const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
508
- const cwd = resolveTierDir(terraformDir, opts.stage, "app");
509
- const tfvarsPath = `${cwd}/terraform.tfvars`;
643
+ const tfvarsPath = resolveTfvarsPath(opts.stage);
510
644
  const tfKey = key.replace(/-/g, "_");
511
645
  const value = readTfVar(tfvarsPath, tfKey);
512
646
  if (value === null) {
@@ -515,7 +649,7 @@ function registerConfigCommand(program2) {
515
649
  console.log(` ${key} = ${value}`);
516
650
  }
517
651
  });
518
- config.command("set <key> <value>").description("Set a configuration value and optionally deploy (e.g. config set memory-engine hindsight)").requiredOption("-s, --stage <name>", "Deployment stage").option("--apply", "Run terraform apply after changing the value").action(async (key, value, opts) => {
652
+ config.command("set <key> <value>").description("Set a configuration value and optionally deploy").requiredOption("-s, --stage <name>", "Deployment stage").option("--apply", "Run terraform apply after changing the value").action(async (key, value, opts) => {
519
653
  const stageCheck = validateStage(opts.stage);
520
654
  if (!stageCheck.valid) {
521
655
  printError(stageCheck.error);
@@ -528,18 +662,21 @@ function registerConfigCommand(program2) {
528
662
  }
529
663
  const identity = getAwsIdentity();
530
664
  printHeader("config set", opts.stage, identity);
531
- const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
532
- const cwd = resolveTierDir(terraformDir, opts.stage, "app");
533
- const tfvarsPath = `${cwd}/terraform.tfvars`;
665
+ const tfvarsPath = resolveTfvarsPath(opts.stage);
534
666
  const oldValue = readTfVar(tfvarsPath, tfKey);
535
667
  setTfVar(tfvarsPath, tfKey, value);
536
668
  console.log(` ${key}: ${oldValue ?? "(unset)"} \u2192 ${value}`);
537
669
  if (opts.apply) {
670
+ const tfDir = resolveTerraformDir(opts.stage);
671
+ if (!tfDir) {
672
+ printError("Cannot find terraform directory. Run `thinkwork init` first.");
673
+ process.exit(1);
674
+ }
538
675
  console.log("");
539
676
  console.log(" Applying configuration change...");
540
- await ensureInit(cwd);
541
- await ensureWorkspace(cwd, opts.stage);
542
- const code = await runTerraform(cwd, [
677
+ await ensureInit(tfDir);
678
+ await ensureWorkspace(tfDir, opts.stage);
679
+ const code = await runTerraform(tfDir, [
543
680
  "apply",
544
681
  "-auto-approve",
545
682
  `-var=stage=${opts.stage}`
@@ -709,14 +846,16 @@ function registerLoginCommand(program2) {
709
846
  }
710
847
 
711
848
  // src/commands/init.ts
712
- import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
713
- import { resolve as resolve2, join } from "path";
849
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, cpSync } from "fs";
850
+ import { resolve as resolve2, join as join2, dirname } from "path";
714
851
  import { execSync as execSync4 } from "child_process";
852
+ import { fileURLToPath } from "url";
715
853
  import { createInterface as createInterface3 } from "readline";
716
- import chalk3 from "chalk";
854
+ import chalk4 from "chalk";
855
+ var __dirname = dirname(fileURLToPath(import.meta.url));
717
856
  function ask2(prompt, defaultVal = "") {
718
857
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
719
- const suffix = defaultVal ? chalk3.dim(` [${defaultVal}]`) : "";
858
+ const suffix = defaultVal ? chalk4.dim(` [${defaultVal}]`) : "";
720
859
  return new Promise((resolve3) => {
721
860
  rl.question(` ${prompt}${suffix}: `, (answer) => {
722
861
  rl.close();
@@ -725,7 +864,7 @@ function ask2(prompt, defaultVal = "") {
725
864
  });
726
865
  }
727
866
  function choose(prompt, options, defaultVal) {
728
- const optStr = options.map((o) => o === defaultVal ? chalk3.bold(o) : chalk3.dim(o)).join(" / ");
867
+ const optStr = options.map((o) => o === defaultVal ? chalk4.bold(o) : chalk4.dim(o)).join(" / ");
729
868
  return ask2(`${prompt} (${optStr})`, defaultVal);
730
869
  }
731
870
  function generateSecret(length = 32) {
@@ -736,6 +875,15 @@ function generateSecret(length = 32) {
736
875
  for (const b of bytes) result += chars[b % chars.length];
737
876
  return result;
738
877
  }
878
+ function findBundledTerraform() {
879
+ const bundled = resolve2(__dirname, "..", "terraform");
880
+ if (existsSync4(join2(bundled, "modules"))) return bundled;
881
+ const repoTf = resolve2(__dirname, "..", "..", "..", "..", "terraform");
882
+ if (existsSync4(join2(repoTf, "modules"))) return repoTf;
883
+ throw new Error(
884
+ "Terraform modules not found. The CLI package may be incomplete.\nTry reinstalling: npm install -g thinkwork-cli@latest"
885
+ );
886
+ }
739
887
  function buildTfvars(config) {
740
888
  const lines = [
741
889
  `# Thinkwork \u2014 ${config.stage} stage`,
@@ -782,7 +930,7 @@ function buildTfvars(config) {
782
930
  return lines.join("\n");
783
931
  }
784
932
  function registerInitCommand(program2) {
785
- program2.command("init").description("Initialize a new Thinkwork environment").requiredOption("-s, --stage <name>", "Stage name (e.g. dev, staging, prod)").option("-d, --dir <path>", "Directory to initialize in", ".").option("--defaults", "Skip interactive prompts, use all defaults").action(async (opts) => {
933
+ program2.command("init").description("Initialize a new Thinkwork environment").requiredOption("-s, --stage <name>", "Stage name (e.g. dev, staging, prod)").option("-d, --dir <path>", "Target directory", ".").option("--defaults", "Skip interactive prompts, use all defaults").action(async (opts) => {
786
934
  const stageCheck = validateStage(opts.stage);
787
935
  if (!stageCheck.valid) {
788
936
  printError(stageCheck.error);
@@ -794,10 +942,10 @@ function registerInitCommand(program2) {
794
942
  printError("AWS credentials not configured. Run `thinkwork login` first.");
795
943
  process.exit(1);
796
944
  }
797
- const rootDir = resolve2(opts.dir);
798
- const tfDir = join(rootDir, "terraform", "examples", "greenfield");
799
- const tfvarsPath = join(tfDir, "terraform.tfvars");
800
- if (existsSync3(tfvarsPath)) {
945
+ const targetDir = resolve2(opts.dir);
946
+ const tfDir = join2(targetDir, "terraform");
947
+ const tfvarsPath = join2(tfDir, "terraform.tfvars");
948
+ if (existsSync4(tfvarsPath)) {
801
949
  printWarning(`terraform.tfvars already exists at ${tfvarsPath}`);
802
950
  const overwrite = await ask2("Overwrite?", "N");
803
951
  if (overwrite.toLowerCase() !== "y") {
@@ -821,19 +969,19 @@ function registerInitCommand(program2) {
821
969
  config.admin_url = "http://localhost:5174";
822
970
  config.mobile_scheme = "thinkwork";
823
971
  } else {
824
- console.log(chalk3.bold(" Configure your Thinkwork environment\n"));
972
+ console.log(chalk4.bold(" Configure your Thinkwork environment\n"));
825
973
  const defaultRegion = identity.region !== "unknown" ? identity.region : "us-east-1";
826
974
  config.region = await ask2("AWS Region", defaultRegion);
827
975
  console.log("");
828
- console.log(chalk3.dim(" \u2500\u2500 Database \u2500\u2500"));
976
+ console.log(chalk4.dim(" \u2500\u2500 Database \u2500\u2500"));
829
977
  config.database_engine = await choose("Database engine", ["aurora-serverless", "rds-postgres"], "aurora-serverless");
830
978
  console.log("");
831
- console.log(chalk3.dim(" \u2500\u2500 Memory \u2500\u2500"));
832
- console.log(chalk3.dim(" managed = Built-in AgentCore memory (remember/recall/forget)"));
833
- console.log(chalk3.dim(" hindsight = ECS Fargate service with semantic + graph retrieval"));
979
+ console.log(chalk4.dim(" \u2500\u2500 Memory \u2500\u2500"));
980
+ console.log(chalk4.dim(" managed = Built-in AgentCore memory (remember/recall/forget)"));
981
+ console.log(chalk4.dim(" hindsight = ECS Fargate service with semantic + graph retrieval"));
834
982
  config.memory_engine = await choose("Memory engine", ["managed", "hindsight"], "managed");
835
983
  console.log("");
836
- console.log(chalk3.dim(" \u2500\u2500 Auth \u2500\u2500"));
984
+ console.log(chalk4.dim(" \u2500\u2500 Auth \u2500\u2500"));
837
985
  const useGoogle = await ask2("Enable Google OAuth login? (y/N)", "N");
838
986
  if (useGoogle.toLowerCase() === "y") {
839
987
  config.google_oauth_client_id = await ask2("Google OAuth Client ID");
@@ -843,30 +991,128 @@ function registerInitCommand(program2) {
843
991
  config.google_oauth_client_secret = "";
844
992
  }
845
993
  console.log("");
846
- console.log(chalk3.dim(" \u2500\u2500 Frontend URLs \u2500\u2500"));
994
+ console.log(chalk4.dim(" \u2500\u2500 Frontend URLs \u2500\u2500"));
847
995
  config.admin_url = await ask2("Admin UI URL", "http://localhost:5174");
848
996
  config.mobile_scheme = await ask2("Mobile app URL scheme", "thinkwork");
849
997
  console.log("");
850
- console.log(chalk3.dim(" \u2500\u2500 Secrets (auto-generated) \u2500\u2500"));
851
- console.log(chalk3.dim(` DB password: ${config.db_password.slice(0, 8)}...`));
852
- console.log(chalk3.dim(` API auth secret: ${config.api_auth_secret.slice(0, 16)}...`));
998
+ console.log(chalk4.dim(" \u2500\u2500 Secrets (auto-generated) \u2500\u2500"));
999
+ console.log(chalk4.dim(` DB password: ${config.db_password.slice(0, 8)}...`));
1000
+ console.log(chalk4.dim(` API auth secret: ${config.api_auth_secret.slice(0, 16)}...`));
853
1001
  }
854
- if (!existsSync3(tfDir)) {
855
- mkdirSync(tfDir, { recursive: true });
1002
+ console.log("");
1003
+ console.log(" Scaffolding Terraform modules...");
1004
+ let bundledTf;
1005
+ try {
1006
+ bundledTf = findBundledTerraform();
1007
+ } catch (err) {
1008
+ printError(String(err));
1009
+ process.exit(1);
1010
+ }
1011
+ mkdirSync2(tfDir, { recursive: true });
1012
+ const copyDirs = ["modules", "examples"];
1013
+ for (const dir of copyDirs) {
1014
+ const src = join2(bundledTf, dir);
1015
+ const dst = join2(tfDir, dir);
1016
+ if (existsSync4(src) && !existsSync4(dst)) {
1017
+ cpSync(src, dst, { recursive: true });
1018
+ }
1019
+ }
1020
+ const schemaPath = join2(bundledTf, "schema.graphql");
1021
+ if (existsSync4(schemaPath) && !existsSync4(join2(tfDir, "schema.graphql"))) {
1022
+ cpSync(schemaPath, join2(tfDir, "schema.graphql"));
856
1023
  }
857
1024
  const tfvars = buildTfvars(config);
858
- writeFileSync2(tfvarsPath, tfvars);
859
- console.log("");
860
- console.log(` Wrote ${chalk3.cyan(tfvarsPath)}`);
1025
+ writeFileSync3(tfvarsPath, tfvars);
1026
+ const mainTfPath = join2(tfDir, "main.tf");
1027
+ if (!existsSync4(mainTfPath)) {
1028
+ writeFileSync3(mainTfPath, `################################################################################
1029
+ # Thinkwork \u2014 ${config.stage}
1030
+ # Generated by: thinkwork init -s ${config.stage}
1031
+ ################################################################################
1032
+
1033
+ terraform {
1034
+ required_version = ">= 1.5"
1035
+
1036
+ required_providers {
1037
+ aws = {
1038
+ source = "hashicorp/aws"
1039
+ version = "~> 5.0"
1040
+ }
1041
+ archive = {
1042
+ source = "hashicorp/archive"
1043
+ version = "~> 2.0"
1044
+ }
1045
+ null = {
1046
+ source = "hashicorp/null"
1047
+ version = "~> 3.0"
1048
+ }
1049
+ }
1050
+ }
1051
+
1052
+ provider "aws" {
1053
+ region = var.region
1054
+ }
1055
+
1056
+ variable "stage" { type = string }
1057
+ variable "region" { type = string; default = "us-east-1" }
1058
+ variable "account_id" { type = string }
1059
+ variable "db_password" { type = string; sensitive = true }
1060
+ variable "database_engine" { type = string; default = "aurora-serverless" }
1061
+ variable "memory_engine" { type = string; default = "managed" }
1062
+ variable "google_oauth_client_id" { type = string; default = "" }
1063
+ variable "google_oauth_client_secret" { type = string; sensitive = true; default = "" }
1064
+ variable "pre_signup_lambda_zip" { type = string; default = "" }
1065
+ variable "lambda_zips_dir" { type = string; default = "" }
1066
+ variable "api_auth_secret" { type = string; sensitive = true; default = "" }
1067
+ variable "admin_callback_urls" { type = list(string); default = ["http://localhost:5174", "http://localhost:5174/auth/callback"] }
1068
+ variable "admin_logout_urls" { type = list(string); default = ["http://localhost:5174"] }
1069
+ variable "mobile_callback_urls" { type = list(string); default = ["exp://localhost:8081", "thinkwork://", "thinkwork://auth/callback"] }
1070
+ variable "mobile_logout_urls" { type = list(string); default = ["exp://localhost:8081", "thinkwork://"] }
1071
+
1072
+ module "thinkwork" {
1073
+ source = "./modules/thinkwork"
1074
+
1075
+ stage = var.stage
1076
+ region = var.region
1077
+ account_id = var.account_id
1078
+
1079
+ db_password = var.db_password
1080
+ database_engine = var.database_engine
1081
+ memory_engine = var.memory_engine
1082
+ google_oauth_client_id = var.google_oauth_client_id
1083
+ google_oauth_client_secret = var.google_oauth_client_secret
1084
+ pre_signup_lambda_zip = var.pre_signup_lambda_zip
1085
+ lambda_zips_dir = var.lambda_zips_dir
1086
+ api_auth_secret = var.api_auth_secret
1087
+ admin_callback_urls = var.admin_callback_urls
1088
+ admin_logout_urls = var.admin_logout_urls
1089
+ mobile_callback_urls = var.mobile_callback_urls
1090
+ mobile_logout_urls = var.mobile_logout_urls
1091
+ }
1092
+
1093
+ output "api_endpoint" { value = module.thinkwork.api_endpoint }
1094
+ output "user_pool_id" { value = module.thinkwork.user_pool_id }
1095
+ output "admin_client_id" { value = module.thinkwork.admin_client_id }
1096
+ output "mobile_client_id" { value = module.thinkwork.mobile_client_id }
1097
+ output "bucket_name" { value = module.thinkwork.bucket_name }
1098
+ output "db_cluster_endpoint" { value = module.thinkwork.db_cluster_endpoint }
1099
+ output "db_secret_arn" { value = module.thinkwork.db_secret_arn; sensitive = true }
1100
+ output "ecr_repository_url" { value = module.thinkwork.ecr_repository_url }
1101
+ output "memory_engine" { value = module.thinkwork.memory_engine }
1102
+ output "hindsight_endpoint" { value = module.thinkwork.hindsight_endpoint }
1103
+ `);
1104
+ }
1105
+ console.log(` Wrote ${chalk4.cyan(tfDir + "/")}`);
861
1106
  console.log("");
862
- console.log(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
863
- console.log(` ${chalk3.bold("Stage:")} ${config.stage}`);
864
- console.log(` ${chalk3.bold("Region:")} ${config.region}`);
865
- console.log(` ${chalk3.bold("Account:")} ${config.account_id}`);
866
- console.log(` ${chalk3.bold("Database:")} ${config.database_engine}`);
867
- console.log(` ${chalk3.bold("Memory:")} ${config.memory_engine}`);
868
- console.log(` ${chalk3.bold("Google OAuth:")} ${config.google_oauth_client_id ? "enabled" : "disabled"}`);
869
- console.log(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1107
+ console.log(chalk4.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1108
+ console.log(` ${chalk4.bold("Stage:")} ${config.stage}`);
1109
+ console.log(` ${chalk4.bold("Region:")} ${config.region}`);
1110
+ console.log(` ${chalk4.bold("Account:")} ${config.account_id}`);
1111
+ console.log(` ${chalk4.bold("Database:")} ${config.database_engine}`);
1112
+ console.log(` ${chalk4.bold("Memory:")} ${config.memory_engine}`);
1113
+ console.log(` ${chalk4.bold("Google OAuth:")} ${config.google_oauth_client_id ? "enabled" : "disabled"}`);
1114
+ console.log(` ${chalk4.bold("Directory:")} ${tfDir}`);
1115
+ console.log(chalk4.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
870
1116
  console.log("\n Initializing Terraform...\n");
871
1117
  try {
872
1118
  execSync4("terraform init", { cwd: tfDir, stdio: "inherit" });
@@ -874,13 +1120,24 @@ function registerInitCommand(program2) {
874
1120
  printWarning("Terraform init failed. Run `thinkwork doctor -s " + opts.stage + "` to check prerequisites.");
875
1121
  return;
876
1122
  }
1123
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1124
+ saveEnvironment({
1125
+ stage: config.stage,
1126
+ region: config.region,
1127
+ accountId: config.account_id,
1128
+ terraformDir: tfDir,
1129
+ databaseEngine: config.database_engine,
1130
+ memoryEngine: config.memory_engine,
1131
+ createdAt: now,
1132
+ updatedAt: now
1133
+ });
877
1134
  printSuccess(`Environment "${opts.stage}" initialized`);
878
1135
  console.log("");
879
1136
  console.log(" Next steps:");
880
- console.log(` ${chalk3.cyan("1.")} thinkwork plan -s ${opts.stage} ${chalk3.dim("# Review infrastructure plan")}`);
881
- console.log(` ${chalk3.cyan("2.")} thinkwork deploy -s ${opts.stage} ${chalk3.dim("# Deploy to AWS")}`);
882
- console.log(` ${chalk3.cyan("3.")} thinkwork bootstrap -s ${opts.stage} ${chalk3.dim("# Seed workspace files + skills")}`);
883
- console.log(` ${chalk3.cyan("4.")} thinkwork outputs -s ${opts.stage} ${chalk3.dim("# Show API URL, Cognito IDs, etc.")}`);
1137
+ console.log(` ${chalk4.cyan("1.")} thinkwork plan -s ${opts.stage} ${chalk4.dim("# Review infrastructure plan")}`);
1138
+ console.log(` ${chalk4.cyan("2.")} thinkwork deploy -s ${opts.stage} ${chalk4.dim("# Deploy to AWS (~5 min)")}`);
1139
+ console.log(` ${chalk4.cyan("3.")} thinkwork bootstrap -s ${opts.stage} ${chalk4.dim("# Seed workspace files + skills")}`);
1140
+ console.log(` ${chalk4.cyan("4.")} thinkwork outputs -s ${opts.stage} ${chalk4.dim("# Show API URL, Cognito IDs, etc.")}`);
884
1141
  console.log("");
885
1142
  });
886
1143
  }