thinkwork-cli 0.12.6 → 0.12.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import {
|
|
3
|
+
__export,
|
|
4
|
+
apiFetch,
|
|
5
|
+
apiFetchRaw,
|
|
6
|
+
ensureInit,
|
|
7
|
+
ensureWorkspace,
|
|
8
|
+
getApiEndpoint,
|
|
9
|
+
listDeployedStages,
|
|
10
|
+
listEnvironments,
|
|
11
|
+
loadEnvironment,
|
|
12
|
+
printError,
|
|
13
|
+
printHeader,
|
|
14
|
+
printMissingApiSessionError,
|
|
15
|
+
printSuccess,
|
|
16
|
+
printSummary,
|
|
17
|
+
printTierHeader,
|
|
18
|
+
printWarning,
|
|
19
|
+
resolveApiConfig,
|
|
20
|
+
resolveTerraformDir,
|
|
21
|
+
resolveTierDir,
|
|
22
|
+
runTerraform,
|
|
23
|
+
saveEnterpriseDeployment,
|
|
24
|
+
saveEnvironment
|
|
25
|
+
} from "./chunk-34NMB5AK.js";
|
|
7
26
|
|
|
8
27
|
// src/cli.ts
|
|
9
28
|
import { Command } from "commander";
|
|
@@ -22,20 +41,20 @@ function getCliConfigPath(override) {
|
|
|
22
41
|
return override ?? join(homedir(), ".thinkwork", "config.json");
|
|
23
42
|
}
|
|
24
43
|
function loadCliConfig(pathOverride) {
|
|
25
|
-
const
|
|
26
|
-
if (!existsSync(
|
|
44
|
+
const path = getCliConfigPath(pathOverride);
|
|
45
|
+
if (!existsSync(path)) return {};
|
|
27
46
|
try {
|
|
28
|
-
return JSON.parse(readFileSync(
|
|
47
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
29
48
|
} catch {
|
|
30
49
|
return {};
|
|
31
50
|
}
|
|
32
51
|
}
|
|
33
52
|
function saveCliConfig(next, pathOverride) {
|
|
34
|
-
const
|
|
35
|
-
const dir = dirname(
|
|
53
|
+
const path = getCliConfigPath(pathOverride);
|
|
54
|
+
const dir = dirname(path);
|
|
36
55
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
37
56
|
const merged = { ...loadCliConfig(pathOverride), ...next };
|
|
38
|
-
writeFileSync(
|
|
57
|
+
writeFileSync(path, JSON.stringify(merged, null, 2) + "\n");
|
|
39
58
|
}
|
|
40
59
|
function saveStageSession(stage, session, pathOverride) {
|
|
41
60
|
const current = loadCliConfig(pathOverride);
|
|
@@ -168,190 +187,9 @@ function getAwsIdentity() {
|
|
|
168
187
|
}
|
|
169
188
|
}
|
|
170
189
|
|
|
171
|
-
// src/terraform.ts
|
|
172
|
-
import { spawn } from "child_process";
|
|
173
|
-
import { existsSync as existsSync2 } from "fs";
|
|
174
|
-
import path from "path";
|
|
175
|
-
function resolveTierDir(terraformDir, stage, tier) {
|
|
176
|
-
const envDir = path.join(terraformDir, "environments", stage, tier);
|
|
177
|
-
if (existsSync2(envDir)) {
|
|
178
|
-
return envDir;
|
|
179
|
-
}
|
|
180
|
-
const greenfield = path.join(terraformDir, "examples", "greenfield");
|
|
181
|
-
if (existsSync2(greenfield)) {
|
|
182
|
-
return greenfield;
|
|
183
|
-
}
|
|
184
|
-
const flat = path.join(terraformDir);
|
|
185
|
-
if (existsSync2(path.join(flat, "main.tf"))) {
|
|
186
|
-
return flat;
|
|
187
|
-
}
|
|
188
|
-
const cwdTf = path.join(process.cwd(), "terraform");
|
|
189
|
-
if (existsSync2(path.join(cwdTf, "main.tf"))) {
|
|
190
|
-
return cwdTf;
|
|
191
|
-
}
|
|
192
|
-
return terraformDir;
|
|
193
|
-
}
|
|
194
|
-
async function ensureWorkspace(cwd, stage) {
|
|
195
|
-
const list = await runTerraformRaw(cwd, ["workspace", "list"]);
|
|
196
|
-
const workspaces = list.split("\n").map((l) => l.replace("*", "").trim()).filter(Boolean);
|
|
197
|
-
if (!workspaces.includes(stage)) {
|
|
198
|
-
await runTerraformRaw(cwd, ["workspace", "new", stage]);
|
|
199
|
-
} else {
|
|
200
|
-
await runTerraformRaw(cwd, ["workspace", "select", stage]);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
function runTerraformRaw(cwd, args) {
|
|
204
|
-
return new Promise((resolve7, reject) => {
|
|
205
|
-
const proc = spawn("terraform", args, {
|
|
206
|
-
cwd,
|
|
207
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
208
|
-
});
|
|
209
|
-
let stdout = "";
|
|
210
|
-
let stderr = "";
|
|
211
|
-
proc.stdout.on("data", (d) => stdout += d);
|
|
212
|
-
proc.stderr.on("data", (d) => stderr += d);
|
|
213
|
-
proc.on("close", (code) => {
|
|
214
|
-
if (code === 0) resolve7(stdout);
|
|
215
|
-
else reject(new Error(`terraform ${args.join(" ")} failed (exit ${code}): ${stderr}`));
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
function runTerraform(cwd, args) {
|
|
220
|
-
return new Promise((resolve7) => {
|
|
221
|
-
console.log(`
|
|
222
|
-
\u2192 terraform ${args.join(" ")}
|
|
223
|
-
`);
|
|
224
|
-
const proc = spawn("terraform", args, {
|
|
225
|
-
cwd,
|
|
226
|
-
stdio: "inherit"
|
|
227
|
-
});
|
|
228
|
-
proc.on("close", (code) => resolve7(code ?? 1));
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
async function ensureInit(cwd) {
|
|
232
|
-
const dotTerraform = path.join(cwd, ".terraform");
|
|
233
|
-
if (!existsSync2(dotTerraform)) {
|
|
234
|
-
const code = await runTerraform(cwd, ["init"]);
|
|
235
|
-
if (code !== 0) {
|
|
236
|
-
throw new Error("terraform init failed");
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// src/ui.ts
|
|
242
|
-
import chalk2 from "chalk";
|
|
243
|
-
import ora from "ora";
|
|
244
|
-
var TIER_LABELS = {
|
|
245
|
-
foundation: "Foundation",
|
|
246
|
-
data: "Data",
|
|
247
|
-
app: "App"
|
|
248
|
-
};
|
|
249
|
-
function printHeader(command, stage, identity) {
|
|
250
|
-
console.log("");
|
|
251
|
-
console.log(chalk2.bold.cyan(" \u2B21 Thinkwork") + chalk2.dim(` \u2014 ${command}`));
|
|
252
|
-
console.log(chalk2.dim(` Stage: ${chalk2.white(stage)}`));
|
|
253
|
-
if (identity) {
|
|
254
|
-
console.log(chalk2.dim(` AWS: ${chalk2.white(identity.account)} / ${chalk2.white(identity.region)}`));
|
|
255
|
-
}
|
|
256
|
-
console.log("");
|
|
257
|
-
}
|
|
258
|
-
function printTierHeader(tier, index, total) {
|
|
259
|
-
const label = TIER_LABELS[tier] ?? tier;
|
|
260
|
-
const progress = chalk2.dim(`[${index + 1}/${total}]`);
|
|
261
|
-
console.log(` ${progress} ${chalk2.bold(label)}`);
|
|
262
|
-
}
|
|
263
|
-
function printSuccess(message) {
|
|
264
|
-
console.log(`
|
|
265
|
-
${chalk2.green("\u2713")} ${chalk2.bold(message)}`);
|
|
266
|
-
}
|
|
267
|
-
function printError(message) {
|
|
268
|
-
console.log(`
|
|
269
|
-
${chalk2.red("\u2717")} ${chalk2.bold.red(message)}`);
|
|
270
|
-
}
|
|
271
|
-
function printMissingApiSessionError(stage, hasSession) {
|
|
272
|
-
if (!hasSession) {
|
|
273
|
-
printError(`No API session for stage "${stage}".`);
|
|
274
|
-
console.log("");
|
|
275
|
-
console.log(` ${chalk2.bold("To fix:")} thinkwork login --stage ${stage}`);
|
|
276
|
-
console.log(
|
|
277
|
-
chalk2.dim(
|
|
278
|
-
` (the deploy-side \`thinkwork login\` only configures an AWS profile \u2014
|
|
279
|
-
it does NOT open an API session.)`
|
|
280
|
-
)
|
|
281
|
-
);
|
|
282
|
-
console.log("");
|
|
283
|
-
} else {
|
|
284
|
-
printError(`Session for stage "${stage}" has no tenant cached.`);
|
|
285
|
-
console.log("");
|
|
286
|
-
console.log(` ${chalk2.bold("To fix:")} thinkwork login --stage ${stage}`);
|
|
287
|
-
console.log(
|
|
288
|
-
chalk2.dim(
|
|
289
|
-
` Or pass --tenant <slug>, or set THINKWORK_TENANT.`
|
|
290
|
-
)
|
|
291
|
-
);
|
|
292
|
-
console.log("");
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
function printWarning(message) {
|
|
296
|
-
console.log(` ${chalk2.yellow("\u26A0")} ${message}`);
|
|
297
|
-
}
|
|
298
|
-
function printSummary(command, stage, tiers, startTime) {
|
|
299
|
-
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
300
|
-
console.log("");
|
|
301
|
-
console.log(chalk2.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"));
|
|
302
|
-
console.log(` ${chalk2.bold("Command:")} ${command}`);
|
|
303
|
-
console.log(` ${chalk2.bold("Stage:")} ${stage}`);
|
|
304
|
-
console.log(` ${chalk2.bold("Tiers:")} ${tiers.map((t) => TIER_LABELS[t] ?? t).join(" \u2192 ")}`);
|
|
305
|
-
console.log(` ${chalk2.bold("Time:")} ${elapsed}s`);
|
|
306
|
-
console.log(chalk2.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"));
|
|
307
|
-
}
|
|
308
|
-
|
|
309
190
|
// src/lib/resolve-stage.ts
|
|
310
191
|
import { select } from "@inquirer/prompts";
|
|
311
192
|
|
|
312
|
-
// src/aws-discovery.ts
|
|
313
|
-
import { execSync as execSync2 } from "child_process";
|
|
314
|
-
function runAws(cmd) {
|
|
315
|
-
try {
|
|
316
|
-
return execSync2(`aws ${cmd}`, {
|
|
317
|
-
encoding: "utf-8",
|
|
318
|
-
timeout: 15e3,
|
|
319
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
320
|
-
}).trim();
|
|
321
|
-
} catch {
|
|
322
|
-
return null;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
function listDeployedStages(region) {
|
|
326
|
-
const raw = runAws(
|
|
327
|
-
`lambda list-functions --region ${region} --query "Functions[?starts_with(FunctionName, 'thinkwork-')].FunctionName" --output json`
|
|
328
|
-
);
|
|
329
|
-
if (!raw) return [];
|
|
330
|
-
try {
|
|
331
|
-
const functions = JSON.parse(raw);
|
|
332
|
-
const stages = /* @__PURE__ */ new Set();
|
|
333
|
-
for (const fn of functions) {
|
|
334
|
-
const m = fn.match(/^thinkwork-(.+?)-api-graphql-http$/);
|
|
335
|
-
if (m) stages.add(m[1]);
|
|
336
|
-
}
|
|
337
|
-
return [...stages].sort();
|
|
338
|
-
} catch {
|
|
339
|
-
return [];
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
function getApiEndpoint(stage, region) {
|
|
343
|
-
const raw = runAws(
|
|
344
|
-
`apigatewayv2 get-apis --region ${region} --query "Items[?Name=='thinkwork-${stage}-api'].ApiEndpoint|[0]" --output text`
|
|
345
|
-
);
|
|
346
|
-
return raw && raw !== "None" ? raw : null;
|
|
347
|
-
}
|
|
348
|
-
function getApiAuthSecretFromLambda(stage, region) {
|
|
349
|
-
const raw = runAws(
|
|
350
|
-
`lambda get-function-configuration --function-name thinkwork-${stage}-api-tenants --region ${region} --query "Environment.Variables.API_AUTH_SECRET" --output text`
|
|
351
|
-
);
|
|
352
|
-
return raw && raw !== "None" ? raw : null;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
193
|
// src/lib/interactive.ts
|
|
356
194
|
function isCancellation(err) {
|
|
357
195
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
@@ -457,8 +295,8 @@ function registerPlanCommand(program2) {
|
|
|
457
295
|
}
|
|
458
296
|
|
|
459
297
|
// src/commands/deploy.ts
|
|
460
|
-
import { spawn
|
|
461
|
-
import { existsSync as
|
|
298
|
+
import { spawn } from "child_process";
|
|
299
|
+
import { existsSync as existsSync2 } from "fs";
|
|
462
300
|
import { dirname as dirname2, resolve as pathResolve } from "path";
|
|
463
301
|
import { fileURLToPath } from "url";
|
|
464
302
|
|
|
@@ -554,7 +392,7 @@ async function runPostDeployProbe(stage) {
|
|
|
554
392
|
return;
|
|
555
393
|
}
|
|
556
394
|
await new Promise((resolve7) => {
|
|
557
|
-
const proc =
|
|
395
|
+
const proc = spawn("bash", [scriptPath, "--stage", stage], {
|
|
558
396
|
stdio: "inherit",
|
|
559
397
|
env: process.env
|
|
560
398
|
});
|
|
@@ -584,7 +422,7 @@ function locatePostDeployScript() {
|
|
|
584
422
|
)
|
|
585
423
|
];
|
|
586
424
|
for (const candidate of candidates) {
|
|
587
|
-
if (
|
|
425
|
+
if (existsSync2(candidate)) return candidate;
|
|
588
426
|
}
|
|
589
427
|
return null;
|
|
590
428
|
}
|
|
@@ -650,14 +488,14 @@ function registerDestroyCommand(program2) {
|
|
|
650
488
|
}
|
|
651
489
|
|
|
652
490
|
// src/commands/doctor.ts
|
|
653
|
-
import
|
|
654
|
-
import { execSync as
|
|
491
|
+
import chalk2 from "chalk";
|
|
492
|
+
import { execSync as execSync2 } from "child_process";
|
|
655
493
|
function checkAwsCli() {
|
|
656
494
|
return {
|
|
657
495
|
name: "AWS CLI installed",
|
|
658
496
|
run: () => {
|
|
659
497
|
try {
|
|
660
|
-
const v =
|
|
498
|
+
const v = execSync2("aws --version", { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
661
499
|
return { pass: true, detail: v.split(" ")[0] ?? v };
|
|
662
500
|
} catch {
|
|
663
501
|
return { pass: false, detail: "aws CLI not found. Install: https://aws.amazon.com/cli/" };
|
|
@@ -670,7 +508,7 @@ function checkTerraformCli() {
|
|
|
670
508
|
name: "Terraform CLI installed",
|
|
671
509
|
run: () => {
|
|
672
510
|
try {
|
|
673
|
-
const v =
|
|
511
|
+
const v = execSync2("terraform version -json", { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] });
|
|
674
512
|
const parsed = JSON.parse(v);
|
|
675
513
|
return { pass: true, detail: `v${parsed.terraform_version}` };
|
|
676
514
|
} catch {
|
|
@@ -696,7 +534,7 @@ function checkBedrockAccess() {
|
|
|
696
534
|
name: "Bedrock model access",
|
|
697
535
|
run: () => {
|
|
698
536
|
try {
|
|
699
|
-
|
|
537
|
+
execSync2(
|
|
700
538
|
"aws bedrock get-foundation-model --model-identifier anthropic.claude-3-haiku-20240307-v1:0 --output json --region us-east-1",
|
|
701
539
|
{ encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
|
|
702
540
|
);
|
|
@@ -729,17 +567,17 @@ function registerDoctorCommand(program2) {
|
|
|
729
567
|
let allPass = true;
|
|
730
568
|
for (const check of checks) {
|
|
731
569
|
const result = check.run();
|
|
732
|
-
const icon = result.pass ?
|
|
733
|
-
const detail = result.pass ?
|
|
570
|
+
const icon = result.pass ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
571
|
+
const detail = result.pass ? chalk2.dim(result.detail) : chalk2.yellow(result.detail);
|
|
734
572
|
console.log(` ${icon} ${check.name} ${detail}`);
|
|
735
573
|
if (!result.pass) allPass = false;
|
|
736
574
|
}
|
|
737
575
|
if (allPass) {
|
|
738
576
|
console.log(`
|
|
739
|
-
${
|
|
577
|
+
${chalk2.green.bold("All checks passed.")}`);
|
|
740
578
|
} else {
|
|
741
579
|
console.log(`
|
|
742
|
-
${
|
|
580
|
+
${chalk2.yellow.bold("Some checks failed.")} Fix the issues above before deploying.`);
|
|
743
581
|
}
|
|
744
582
|
process.exit(allPass ? 0 : 1);
|
|
745
583
|
});
|
|
@@ -778,75 +616,11 @@ function registerOutputsCommand(program2) {
|
|
|
778
616
|
}
|
|
779
617
|
|
|
780
618
|
// src/commands/config.ts
|
|
781
|
-
import { readFileSync as
|
|
782
|
-
import
|
|
783
|
-
|
|
784
|
-
// src/environments.ts
|
|
785
|
-
import {
|
|
786
|
-
existsSync as existsSync4,
|
|
787
|
-
mkdirSync as mkdirSync2,
|
|
788
|
-
writeFileSync as writeFileSync2,
|
|
789
|
-
readFileSync as readFileSync2,
|
|
790
|
-
readdirSync
|
|
791
|
-
} from "fs";
|
|
792
|
-
import { join as join2 } from "path";
|
|
793
|
-
import { homedir as homedir2 } from "os";
|
|
794
|
-
var THINKWORK_HOME = join2(homedir2(), ".thinkwork");
|
|
795
|
-
var ENVIRONMENTS_DIR = join2(THINKWORK_HOME, "environments");
|
|
796
|
-
var ENTERPRISE_DEPLOYMENTS_DIR = join2(
|
|
797
|
-
THINKWORK_HOME,
|
|
798
|
-
"enterprise-deployments"
|
|
799
|
-
);
|
|
800
|
-
function ensureDir(dir) {
|
|
801
|
-
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
802
|
-
}
|
|
803
|
-
function saveEnvironment(config) {
|
|
804
|
-
ensureDir(ENVIRONMENTS_DIR);
|
|
805
|
-
const envDir = join2(ENVIRONMENTS_DIR, config.stage);
|
|
806
|
-
ensureDir(envDir);
|
|
807
|
-
writeFileSync2(
|
|
808
|
-
join2(envDir, "config.json"),
|
|
809
|
-
JSON.stringify(config, null, 2) + "\n"
|
|
810
|
-
);
|
|
811
|
-
}
|
|
812
|
-
function saveEnterpriseDeployment(config) {
|
|
813
|
-
ensureDir(ENTERPRISE_DEPLOYMENTS_DIR);
|
|
814
|
-
writeFileSync2(
|
|
815
|
-
join2(ENTERPRISE_DEPLOYMENTS_DIR, `${config.customerSlug}.json`),
|
|
816
|
-
JSON.stringify(config, null, 2) + "\n"
|
|
817
|
-
);
|
|
818
|
-
}
|
|
819
|
-
function loadEnvironment(stage) {
|
|
820
|
-
const configPath = join2(ENVIRONMENTS_DIR, stage, "config.json");
|
|
821
|
-
if (!existsSync4(configPath)) return null;
|
|
822
|
-
return JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
823
|
-
}
|
|
824
|
-
function listEnvironments() {
|
|
825
|
-
if (!existsSync4(ENVIRONMENTS_DIR)) return [];
|
|
826
|
-
return readdirSync(ENVIRONMENTS_DIR).filter((name) => {
|
|
827
|
-
return existsSync4(join2(ENVIRONMENTS_DIR, name, "config.json"));
|
|
828
|
-
}).map((name) => {
|
|
829
|
-
return JSON.parse(
|
|
830
|
-
readFileSync2(join2(ENVIRONMENTS_DIR, name, "config.json"), "utf-8")
|
|
831
|
-
);
|
|
832
|
-
}).sort((a, b) => a.stage.localeCompare(b.stage));
|
|
833
|
-
}
|
|
834
|
-
function resolveTerraformDir(stage) {
|
|
835
|
-
const env = loadEnvironment(stage);
|
|
836
|
-
if (env?.terraformDir && existsSync4(env.terraformDir)) {
|
|
837
|
-
return env.terraformDir;
|
|
838
|
-
}
|
|
839
|
-
const envVar = process.env.THINKWORK_TERRAFORM_DIR;
|
|
840
|
-
if (envVar && existsSync4(envVar)) return envVar;
|
|
841
|
-
const cwdTf = join2(process.cwd(), "terraform");
|
|
842
|
-
if (existsSync4(join2(cwdTf, "main.tf"))) return cwdTf;
|
|
843
|
-
return null;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
// src/commands/config.ts
|
|
619
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
|
|
620
|
+
import chalk3 from "chalk";
|
|
847
621
|
function readTfVar(tfvarsPath, key) {
|
|
848
|
-
if (!
|
|
849
|
-
const content =
|
|
622
|
+
if (!existsSync3(tfvarsPath)) return null;
|
|
623
|
+
const content = readFileSync2(tfvarsPath, "utf-8");
|
|
850
624
|
const quoted = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
|
|
851
625
|
if (quoted) return quoted[1];
|
|
852
626
|
const bare = content.match(new RegExp(`^${key}\\s*=\\s*([^\\s#]+)`, "m"));
|
|
@@ -854,10 +628,10 @@ function readTfVar(tfvarsPath, key) {
|
|
|
854
628
|
}
|
|
855
629
|
var BARE_KEYS = /* @__PURE__ */ new Set(["enable_hindsight"]);
|
|
856
630
|
function setTfVar(tfvarsPath, key, value) {
|
|
857
|
-
if (!
|
|
631
|
+
if (!existsSync3(tfvarsPath)) {
|
|
858
632
|
throw new Error(`terraform.tfvars not found at ${tfvarsPath}`);
|
|
859
633
|
}
|
|
860
|
-
let content =
|
|
634
|
+
let content = readFileSync2(tfvarsPath, "utf-8");
|
|
861
635
|
const bare = BARE_KEYS.has(key);
|
|
862
636
|
const newLine = bare ? `${key} = ${value}` : `${key} = "${value}"`;
|
|
863
637
|
const existingRegex = new RegExp(`^${key}\\s*=\\s*(?:"[^"]*"|[^\\s#]+)`, "m");
|
|
@@ -868,13 +642,13 @@ function setTfVar(tfvarsPath, key, value) {
|
|
|
868
642
|
${newLine}
|
|
869
643
|
`;
|
|
870
644
|
}
|
|
871
|
-
|
|
645
|
+
writeFileSync2(tfvarsPath, content);
|
|
872
646
|
}
|
|
873
647
|
function resolveTfvarsPath(stage) {
|
|
874
648
|
const tfDir = resolveTerraformDir(stage);
|
|
875
649
|
if (tfDir) {
|
|
876
650
|
const direct = `${tfDir}/terraform.tfvars`;
|
|
877
|
-
if (
|
|
651
|
+
if (existsSync3(direct)) return direct;
|
|
878
652
|
}
|
|
879
653
|
const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
|
|
880
654
|
const cwd = resolveTierDir(terraformDir, stage, "app");
|
|
@@ -890,21 +664,21 @@ function registerConfigCommand(program2) {
|
|
|
890
664
|
process.exit(1);
|
|
891
665
|
}
|
|
892
666
|
console.log("");
|
|
893
|
-
console.log(
|
|
894
|
-
console.log(
|
|
895
|
-
console.log(` ${
|
|
896
|
-
console.log(` ${
|
|
897
|
-
console.log(` ${
|
|
898
|
-
console.log(` ${
|
|
899
|
-
console.log(` ${
|
|
900
|
-
console.log(` ${
|
|
901
|
-
console.log(` ${
|
|
902
|
-
console.log(
|
|
667
|
+
console.log(chalk3.bold.cyan(` \u2B21 ${env.stage}`));
|
|
668
|
+
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"));
|
|
669
|
+
console.log(` ${chalk3.bold("Region:")} ${env.region}`);
|
|
670
|
+
console.log(` ${chalk3.bold("Account:")} ${env.accountId}`);
|
|
671
|
+
console.log(` ${chalk3.bold("Database:")} ${env.databaseEngine}`);
|
|
672
|
+
console.log(` ${chalk3.bold("Memory:")} managed (always on)${env.enableHindsight ? " + hindsight" : ""}`);
|
|
673
|
+
console.log(` ${chalk3.bold("Terraform dir:")} ${env.terraformDir}`);
|
|
674
|
+
console.log(` ${chalk3.bold("Created:")} ${env.createdAt}`);
|
|
675
|
+
console.log(` ${chalk3.bold("Updated:")} ${env.updatedAt}`);
|
|
676
|
+
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"));
|
|
903
677
|
const tfvarsPath = `${env.terraformDir}/terraform.tfvars`;
|
|
904
|
-
if (
|
|
678
|
+
if (existsSync3(tfvarsPath)) {
|
|
905
679
|
console.log("");
|
|
906
|
-
console.log(
|
|
907
|
-
const content =
|
|
680
|
+
console.log(chalk3.dim(" terraform.tfvars:"));
|
|
681
|
+
const content = readFileSync2(tfvarsPath, "utf-8");
|
|
908
682
|
for (const line of content.split("\n")) {
|
|
909
683
|
if (line.trim() && !line.trim().startsWith("#")) {
|
|
910
684
|
const masked = line.replace(
|
|
@@ -917,7 +691,7 @@ function registerConfigCommand(program2) {
|
|
|
917
691
|
/^(google_oauth_client_secret\s*=\s*)".*"/,
|
|
918
692
|
'$1"********"'
|
|
919
693
|
);
|
|
920
|
-
console.log(` ${
|
|
694
|
+
console.log(` ${chalk3.dim(masked)}`);
|
|
921
695
|
}
|
|
922
696
|
}
|
|
923
697
|
}
|
|
@@ -928,24 +702,24 @@ function registerConfigCommand(program2) {
|
|
|
928
702
|
if (envs.length === 0) {
|
|
929
703
|
console.log("");
|
|
930
704
|
console.log(" No environments found.");
|
|
931
|
-
console.log(` Run ${
|
|
705
|
+
console.log(` Run ${chalk3.cyan("thinkwork init -s <stage>")} to create one.`);
|
|
932
706
|
console.log("");
|
|
933
707
|
return;
|
|
934
708
|
}
|
|
935
709
|
console.log("");
|
|
936
|
-
console.log(
|
|
937
|
-
console.log(
|
|
710
|
+
console.log(chalk3.bold(" Environments"));
|
|
711
|
+
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"));
|
|
938
712
|
for (const env of envs) {
|
|
939
|
-
const memBadge = env.enableHindsight ?
|
|
940
|
-
const dbBadge = env.databaseEngine === "rds-postgres" ?
|
|
713
|
+
const memBadge = env.enableHindsight ? chalk3.magenta("managed+hindsight") : chalk3.dim("managed");
|
|
714
|
+
const dbBadge = env.databaseEngine === "rds-postgres" ? chalk3.yellow("rds") : chalk3.dim("aurora");
|
|
941
715
|
console.log(
|
|
942
|
-
` ${
|
|
716
|
+
` ${chalk3.bold.cyan(env.stage.padEnd(16))}${env.region.padEnd(14)}${env.accountId.padEnd(16)}${dbBadge.padEnd(20)}${memBadge}`
|
|
943
717
|
);
|
|
944
718
|
}
|
|
945
|
-
console.log(
|
|
946
|
-
console.log(
|
|
719
|
+
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"));
|
|
720
|
+
console.log(chalk3.dim(` ${envs.length} environment(s)`));
|
|
947
721
|
console.log("");
|
|
948
|
-
console.log(` Show details: ${
|
|
722
|
+
console.log(` Show details: ${chalk3.cyan("thinkwork config list -s <stage>")}`);
|
|
949
723
|
console.log("");
|
|
950
724
|
});
|
|
951
725
|
config.command("get <key>").description("Get a configuration value (e.g. enable-hindsight). Prompts for stage in a TTY when omitted.").option("-s, --stage <name>", "Deployment stage").action(async (key, opts) => {
|
|
@@ -1022,11 +796,11 @@ function registerConfigCommand(program2) {
|
|
|
1022
796
|
}
|
|
1023
797
|
|
|
1024
798
|
// src/commands/bootstrap.ts
|
|
1025
|
-
import { spawn as
|
|
799
|
+
import { spawn as spawn2 } from "child_process";
|
|
1026
800
|
import { resolve } from "path";
|
|
1027
801
|
function getTerraformOutput(cwd, key) {
|
|
1028
802
|
return new Promise((resolve7, reject) => {
|
|
1029
|
-
const proc =
|
|
803
|
+
const proc = spawn2("terraform", ["output", "-raw", key], {
|
|
1030
804
|
cwd,
|
|
1031
805
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1032
806
|
});
|
|
@@ -1040,7 +814,7 @@ function getTerraformOutput(cwd, key) {
|
|
|
1040
814
|
}
|
|
1041
815
|
function runScript(scriptPath, args) {
|
|
1042
816
|
return new Promise((resolve7) => {
|
|
1043
|
-
const proc =
|
|
817
|
+
const proc = spawn2("bash", [scriptPath, ...args], {
|
|
1044
818
|
stdio: "inherit"
|
|
1045
819
|
});
|
|
1046
820
|
proc.on("close", (code) => resolve7(code ?? 1));
|
|
@@ -1068,8 +842,8 @@ function registerBootstrapCommand(program2) {
|
|
|
1068
842
|
bucket = await getTerraformOutput(cwd, "bucket_name");
|
|
1069
843
|
dbEndpoint = await getTerraformOutput(cwd, "db_cluster_endpoint");
|
|
1070
844
|
const secretArn = await getTerraformOutput(cwd, "db_secret_arn");
|
|
1071
|
-
const { execSync:
|
|
1072
|
-
const secretJson =
|
|
845
|
+
const { execSync: execSync9 } = await import("child_process");
|
|
846
|
+
const secretJson = execSync9(
|
|
1073
847
|
`aws secretsmanager get-secret-value --secret-id "${secretArn}" --query SecretString --output text`,
|
|
1074
848
|
{ encoding: "utf-8" }
|
|
1075
849
|
).trim();
|
|
@@ -1092,17 +866,17 @@ function registerBootstrapCommand(program2) {
|
|
|
1092
866
|
}
|
|
1093
867
|
|
|
1094
868
|
// src/commands/login.ts
|
|
1095
|
-
import { execSync as
|
|
869
|
+
import { execSync as execSync5 } from "child_process";
|
|
1096
870
|
import { createInterface as createInterface2 } from "readline";
|
|
1097
871
|
import { select as select2, Separator } from "@inquirer/prompts";
|
|
1098
|
-
import
|
|
872
|
+
import chalk6 from "chalk";
|
|
1099
873
|
|
|
1100
874
|
// src/aws-profiles.ts
|
|
1101
|
-
import { existsSync as
|
|
1102
|
-
import { homedir as
|
|
1103
|
-
import { join as
|
|
1104
|
-
var CREDENTIALS_PATH =
|
|
1105
|
-
var CONFIG_PATH =
|
|
875
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
876
|
+
import { homedir as homedir2 } from "os";
|
|
877
|
+
import { join as join2 } from "path";
|
|
878
|
+
var CREDENTIALS_PATH = join2(homedir2(), ".aws", "credentials");
|
|
879
|
+
var CONFIG_PATH = join2(homedir2(), ".aws", "config");
|
|
1106
880
|
function parseIni(content) {
|
|
1107
881
|
const sections = {};
|
|
1108
882
|
let current = null;
|
|
@@ -1141,8 +915,8 @@ function classify(fields) {
|
|
|
1141
915
|
}
|
|
1142
916
|
function listAwsProfiles() {
|
|
1143
917
|
const byName = /* @__PURE__ */ new Map();
|
|
1144
|
-
if (
|
|
1145
|
-
const sections = parseIni(
|
|
918
|
+
if (existsSync4(CREDENTIALS_PATH)) {
|
|
919
|
+
const sections = parseIni(readFileSync3(CREDENTIALS_PATH, "utf-8"));
|
|
1146
920
|
for (const [section, fields] of Object.entries(sections)) {
|
|
1147
921
|
byName.set(section, {
|
|
1148
922
|
name: section,
|
|
@@ -1151,8 +925,8 @@ function listAwsProfiles() {
|
|
|
1151
925
|
});
|
|
1152
926
|
}
|
|
1153
927
|
}
|
|
1154
|
-
if (
|
|
1155
|
-
const sections = parseIni(
|
|
928
|
+
if (existsSync4(CONFIG_PATH)) {
|
|
929
|
+
const sections = parseIni(readFileSync3(CONFIG_PATH, "utf-8"));
|
|
1156
930
|
for (const [section, fields] of Object.entries(sections)) {
|
|
1157
931
|
const name = normalizeConfigSection(section);
|
|
1158
932
|
if (!name) continue;
|
|
@@ -1174,14 +948,14 @@ function listAwsProfiles() {
|
|
|
1174
948
|
}
|
|
1175
949
|
|
|
1176
950
|
// src/prerequisites.ts
|
|
1177
|
-
import { execSync as
|
|
1178
|
-
import { mkdirSync as
|
|
1179
|
-
import { join as
|
|
1180
|
-
import { homedir as
|
|
1181
|
-
import
|
|
951
|
+
import { execSync as execSync3 } from "child_process";
|
|
952
|
+
import { mkdirSync as mkdirSync2, createWriteStream, chmodSync } from "fs";
|
|
953
|
+
import { join as join3 } from "path";
|
|
954
|
+
import { homedir as homedir3, platform, arch } from "os";
|
|
955
|
+
import chalk4 from "chalk";
|
|
1182
956
|
function run(cmd, opts) {
|
|
1183
957
|
try {
|
|
1184
|
-
return
|
|
958
|
+
return execSync3(cmd, {
|
|
1185
959
|
encoding: "utf-8",
|
|
1186
960
|
timeout: 3e4,
|
|
1187
961
|
stdio: opts?.silent ? ["pipe", "pipe", "pipe"] : void 0
|
|
@@ -1198,27 +972,27 @@ function hasBrew() {
|
|
|
1198
972
|
}
|
|
1199
973
|
async function ensureAwsCli() {
|
|
1200
974
|
if (isInstalled("aws")) return true;
|
|
1201
|
-
console.log(` ${
|
|
975
|
+
console.log(` ${chalk4.yellow("\u2192")} AWS CLI not found. Installing...`);
|
|
1202
976
|
const os = platform();
|
|
1203
977
|
if (os === "darwin" && hasBrew()) {
|
|
1204
978
|
const result = run("brew install awscli");
|
|
1205
979
|
if (result !== null && isInstalled("aws")) {
|
|
1206
|
-
console.log(` ${
|
|
980
|
+
console.log(` ${chalk4.green("\u2713")} AWS CLI installed via Homebrew`);
|
|
1207
981
|
return true;
|
|
1208
982
|
}
|
|
1209
983
|
}
|
|
1210
984
|
if (os === "linux") {
|
|
1211
985
|
try {
|
|
1212
|
-
const tmpDir =
|
|
1213
|
-
|
|
1214
|
-
const zipPath =
|
|
986
|
+
const tmpDir = join3(homedir3(), ".thinkwork", "tmp");
|
|
987
|
+
mkdirSync2(tmpDir, { recursive: true });
|
|
988
|
+
const zipPath = join3(tmpDir, "awscliv2.zip");
|
|
1215
989
|
console.log(" Downloading AWS CLI...");
|
|
1216
990
|
run(`curl -sL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "${zipPath}"`);
|
|
1217
991
|
run(`cd "${tmpDir}" && unzip -qo "${zipPath}"`);
|
|
1218
|
-
run(`"${tmpDir}/aws/install" --install-dir "${
|
|
1219
|
-
process.env.PATH = `${
|
|
992
|
+
run(`"${tmpDir}/aws/install" --install-dir "${homedir3()}/.thinkwork/aws-cli" --bin-dir "${homedir3()}/.local/bin" --update`);
|
|
993
|
+
process.env.PATH = `${homedir3()}/.local/bin:${process.env.PATH}`;
|
|
1220
994
|
if (isInstalled("aws")) {
|
|
1221
|
-
console.log(` ${
|
|
995
|
+
console.log(` ${chalk4.green("\u2713")} AWS CLI installed to ~/.local/bin/aws`);
|
|
1222
996
|
return true;
|
|
1223
997
|
}
|
|
1224
998
|
} catch {
|
|
@@ -1226,31 +1000,31 @@ async function ensureAwsCli() {
|
|
|
1226
1000
|
}
|
|
1227
1001
|
if (os === "darwin") {
|
|
1228
1002
|
try {
|
|
1229
|
-
const tmpDir =
|
|
1230
|
-
|
|
1231
|
-
const pkgPath =
|
|
1003
|
+
const tmpDir = join3(homedir3(), ".thinkwork", "tmp");
|
|
1004
|
+
mkdirSync2(tmpDir, { recursive: true });
|
|
1005
|
+
const pkgPath = join3(tmpDir, "AWSCLIV2.pkg");
|
|
1232
1006
|
console.log(" Downloading AWS CLI...");
|
|
1233
1007
|
run(`curl -sL "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "${pkgPath}"`);
|
|
1234
1008
|
run(`installer -pkg "${pkgPath}" -target CurrentUserHomeDirectory 2>/dev/null || sudo installer -pkg "${pkgPath}" -target /`);
|
|
1235
1009
|
if (isInstalled("aws")) {
|
|
1236
|
-
console.log(` ${
|
|
1010
|
+
console.log(` ${chalk4.green("\u2713")} AWS CLI installed`);
|
|
1237
1011
|
return true;
|
|
1238
1012
|
}
|
|
1239
1013
|
} catch {
|
|
1240
1014
|
}
|
|
1241
1015
|
}
|
|
1242
|
-
console.log(` ${
|
|
1243
|
-
console.log(` Install manually: ${
|
|
1016
|
+
console.log(` ${chalk4.red("\u2717")} Could not auto-install AWS CLI.`);
|
|
1017
|
+
console.log(` Install manually: ${chalk4.cyan("https://aws.amazon.com/cli/")}`);
|
|
1244
1018
|
return false;
|
|
1245
1019
|
}
|
|
1246
1020
|
async function ensureTerraform() {
|
|
1247
1021
|
if (isInstalled("terraform")) return true;
|
|
1248
|
-
console.log(` ${
|
|
1022
|
+
console.log(` ${chalk4.yellow("\u2192")} Terraform not found. Installing...`);
|
|
1249
1023
|
const os = platform();
|
|
1250
1024
|
if ((os === "darwin" || os === "linux") && hasBrew()) {
|
|
1251
1025
|
const result = run("brew install hashicorp/tap/terraform");
|
|
1252
1026
|
if (result !== null && isInstalled("terraform")) {
|
|
1253
|
-
console.log(` ${
|
|
1027
|
+
console.log(` ${chalk4.green("\u2713")} Terraform installed via Homebrew`);
|
|
1254
1028
|
return true;
|
|
1255
1029
|
}
|
|
1256
1030
|
}
|
|
@@ -1259,30 +1033,30 @@ async function ensureTerraform() {
|
|
|
1259
1033
|
const archName = arch() === "arm64" ? "arm64" : "amd64";
|
|
1260
1034
|
const url = `https://releases.hashicorp.com/terraform/${tfVersion}/terraform_${tfVersion}_${osName}_${archName}.zip`;
|
|
1261
1035
|
try {
|
|
1262
|
-
const tmpDir =
|
|
1263
|
-
const binDir =
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
const zipPath =
|
|
1036
|
+
const tmpDir = join3(homedir3(), ".thinkwork", "tmp");
|
|
1037
|
+
const binDir = join3(homedir3(), ".local", "bin");
|
|
1038
|
+
mkdirSync2(tmpDir, { recursive: true });
|
|
1039
|
+
mkdirSync2(binDir, { recursive: true });
|
|
1040
|
+
const zipPath = join3(tmpDir, "terraform.zip");
|
|
1267
1041
|
console.log(` Downloading Terraform ${tfVersion}...`);
|
|
1268
1042
|
run(`curl -sL "${url}" -o "${zipPath}"`);
|
|
1269
1043
|
run(`unzip -qo "${zipPath}" -d "${binDir}"`);
|
|
1270
|
-
chmodSync(
|
|
1044
|
+
chmodSync(join3(binDir, "terraform"), 493);
|
|
1271
1045
|
if (!process.env.PATH?.includes(binDir)) {
|
|
1272
1046
|
process.env.PATH = `${binDir}:${process.env.PATH}`;
|
|
1273
1047
|
}
|
|
1274
1048
|
if (isInstalled("terraform")) {
|
|
1275
|
-
console.log(` ${
|
|
1049
|
+
console.log(` ${chalk4.green("\u2713")} Terraform ${tfVersion} installed to ~/.local/bin/terraform`);
|
|
1276
1050
|
return true;
|
|
1277
1051
|
}
|
|
1278
1052
|
} catch {
|
|
1279
1053
|
}
|
|
1280
|
-
console.log(` ${
|
|
1281
|
-
console.log(` Install manually: ${
|
|
1054
|
+
console.log(` ${chalk4.red("\u2717")} Could not auto-install Terraform.`);
|
|
1055
|
+
console.log(` Install manually: ${chalk4.cyan("https://developer.hashicorp.com/terraform/install")}`);
|
|
1282
1056
|
return false;
|
|
1283
1057
|
}
|
|
1284
1058
|
async function ensurePrerequisites() {
|
|
1285
|
-
console.log(
|
|
1059
|
+
console.log(chalk4.dim(" Checking prerequisites...\n"));
|
|
1286
1060
|
const awsOk2 = await ensureAwsCli();
|
|
1287
1061
|
const tfOk = await ensureTerraform();
|
|
1288
1062
|
if (awsOk2 && tfOk) {
|
|
@@ -1290,15 +1064,15 @@ async function ensurePrerequisites() {
|
|
|
1290
1064
|
return true;
|
|
1291
1065
|
}
|
|
1292
1066
|
console.log("");
|
|
1293
|
-
console.log(` ${
|
|
1067
|
+
console.log(` ${chalk4.red("Missing prerequisites.")} Install them and try again.`);
|
|
1294
1068
|
return false;
|
|
1295
1069
|
}
|
|
1296
1070
|
|
|
1297
1071
|
// src/cognito-discovery.ts
|
|
1298
|
-
import { execSync as
|
|
1299
|
-
function
|
|
1072
|
+
import { execSync as execSync4 } from "child_process";
|
|
1073
|
+
function runAws(cmd) {
|
|
1300
1074
|
try {
|
|
1301
|
-
return
|
|
1075
|
+
return execSync4(`aws ${cmd}`, {
|
|
1302
1076
|
encoding: "utf-8",
|
|
1303
1077
|
timeout: 15e3,
|
|
1304
1078
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -1318,7 +1092,7 @@ function tryTerraformOutput(stage) {
|
|
|
1318
1092
|
}
|
|
1319
1093
|
const read = (key) => {
|
|
1320
1094
|
try {
|
|
1321
|
-
return
|
|
1095
|
+
return execSync4(`terraform output -raw ${key}`, {
|
|
1322
1096
|
cwd,
|
|
1323
1097
|
encoding: "utf-8",
|
|
1324
1098
|
timeout: 15e3,
|
|
@@ -1334,7 +1108,7 @@ function tryTerraformOutput(stage) {
|
|
|
1334
1108
|
return { userPoolId, clientId, domain };
|
|
1335
1109
|
}
|
|
1336
1110
|
function tryAwsDiscovery(stage, region) {
|
|
1337
|
-
const listRaw =
|
|
1111
|
+
const listRaw = runAws(
|
|
1338
1112
|
`cognito-idp list-user-pools --max-results 60 --region ${region} --output json`
|
|
1339
1113
|
);
|
|
1340
1114
|
if (!listRaw) return {};
|
|
@@ -1346,7 +1120,7 @@ function tryAwsDiscovery(stage, region) {
|
|
|
1346
1120
|
)
|
|
1347
1121
|
);
|
|
1348
1122
|
if (!pool) return {};
|
|
1349
|
-
const clientsRaw =
|
|
1123
|
+
const clientsRaw = runAws(
|
|
1350
1124
|
`cognito-idp list-user-pool-clients --user-pool-id ${pool.Id} --region ${region} --output json`
|
|
1351
1125
|
);
|
|
1352
1126
|
let clientId;
|
|
@@ -1379,8 +1153,8 @@ function discoverCognitoConfig(stage, region) {
|
|
|
1379
1153
|
// src/cognito-oauth.ts
|
|
1380
1154
|
import { createServer } from "http";
|
|
1381
1155
|
import { randomBytes } from "crypto";
|
|
1382
|
-
import { spawn as
|
|
1383
|
-
import
|
|
1156
|
+
import { spawn as spawn3 } from "child_process";
|
|
1157
|
+
import chalk5 from "chalk";
|
|
1384
1158
|
var CLI_LOOPBACK_PORT = 42010;
|
|
1385
1159
|
var CALLBACK_PATH = "/callback";
|
|
1386
1160
|
var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -1396,9 +1170,9 @@ async function loginWithCognito(opts) {
|
|
|
1396
1170
|
timeoutMs,
|
|
1397
1171
|
onListening: () => {
|
|
1398
1172
|
logStderr("");
|
|
1399
|
-
logStderr(` ${
|
|
1400
|
-
logStderr(` ${
|
|
1401
|
-
logStderr(` ${
|
|
1173
|
+
logStderr(` ${chalk5.cyan("Opening browser to sign in\u2026")}`);
|
|
1174
|
+
logStderr(` ${chalk5.dim("If it doesn't open automatically, visit:")}`);
|
|
1175
|
+
logStderr(` ${chalk5.dim(authorizeUrl)}`);
|
|
1402
1176
|
logStderr("");
|
|
1403
1177
|
if (opts.openBrowser !== false) {
|
|
1404
1178
|
(opts.launchBrowser ?? openInBrowser)(authorizeUrl);
|
|
@@ -1558,7 +1332,7 @@ function openInBrowser(url) {
|
|
|
1558
1332
|
const cmd = platform2 === "darwin" ? "open" : platform2 === "win32" ? "cmd" : "xdg-open";
|
|
1559
1333
|
const args = platform2 === "win32" ? ["/c", "start", "", url] : [url];
|
|
1560
1334
|
try {
|
|
1561
|
-
|
|
1335
|
+
spawn3(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
1562
1336
|
} catch {
|
|
1563
1337
|
}
|
|
1564
1338
|
}
|
|
@@ -1629,7 +1403,7 @@ function ask(prompt) {
|
|
|
1629
1403
|
}
|
|
1630
1404
|
function verifyProfile(profile) {
|
|
1631
1405
|
try {
|
|
1632
|
-
const raw =
|
|
1406
|
+
const raw = execSync5(
|
|
1633
1407
|
`aws sts get-caller-identity --profile ${profile} --output json`,
|
|
1634
1408
|
{ encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
1635
1409
|
);
|
|
@@ -1659,7 +1433,7 @@ async function pickProfile(profiles) {
|
|
|
1659
1433
|
return { kind: "cancel" };
|
|
1660
1434
|
}
|
|
1661
1435
|
const choices = profiles.map((p) => ({
|
|
1662
|
-
name: `${p.name} ${
|
|
1436
|
+
name: `${p.name} ${chalk6.dim(`(${describeType(p.type)})`)}`,
|
|
1663
1437
|
value: { kind: "existing", name: p.name }
|
|
1664
1438
|
}));
|
|
1665
1439
|
choices.push(new Separator());
|
|
@@ -1706,15 +1480,15 @@ async function runKeyEntry(targetProfile) {
|
|
|
1706
1480
|
const region = await ask(" Default region [us-east-1]: ");
|
|
1707
1481
|
const finalRegion = region || "us-east-1";
|
|
1708
1482
|
try {
|
|
1709
|
-
|
|
1483
|
+
execSync5(
|
|
1710
1484
|
`aws configure set aws_access_key_id "${accessKeyId}" --profile ${targetProfile}`,
|
|
1711
1485
|
{ stdio: "pipe" }
|
|
1712
1486
|
);
|
|
1713
|
-
|
|
1487
|
+
execSync5(
|
|
1714
1488
|
`aws configure set aws_secret_access_key "${secretAccessKey}" --profile ${targetProfile}`,
|
|
1715
1489
|
{ stdio: "pipe" }
|
|
1716
1490
|
);
|
|
1717
|
-
|
|
1491
|
+
execSync5(
|
|
1718
1492
|
`aws configure set region "${finalRegion}" --profile ${targetProfile}`,
|
|
1719
1493
|
{ stdio: "pipe" }
|
|
1720
1494
|
);
|
|
@@ -1728,7 +1502,7 @@ function runSsoLogin(targetProfile) {
|
|
|
1728
1502
|
console.log(" Launching AWS SSO login...");
|
|
1729
1503
|
console.log("");
|
|
1730
1504
|
try {
|
|
1731
|
-
|
|
1505
|
+
execSync5(`aws sso login --profile ${targetProfile}`, { stdio: "inherit" });
|
|
1732
1506
|
return true;
|
|
1733
1507
|
} catch {
|
|
1734
1508
|
printError(
|
|
@@ -1757,7 +1531,7 @@ function finalizeAws(profile, mode) {
|
|
|
1757
1531
|
` (\`thinkwork list\`, \`thinkwork deploy\`, \u2026) will use it automatically.`
|
|
1758
1532
|
);
|
|
1759
1533
|
console.log(
|
|
1760
|
-
|
|
1534
|
+
chalk6.dim(
|
|
1761
1535
|
` Override per-command with --profile <other>, or unset with \`rm ~/.thinkwork/config.json\`.`
|
|
1762
1536
|
)
|
|
1763
1537
|
);
|
|
@@ -1771,10 +1545,10 @@ async function offerApiLoginChain(opts) {
|
|
|
1771
1545
|
if (candidates.length === 0 || !isInteractive()) {
|
|
1772
1546
|
console.log("");
|
|
1773
1547
|
console.log(
|
|
1774
|
-
` ${
|
|
1548
|
+
` ${chalk6.bold("Next:")} run ${chalk6.cyan("thinkwork login --stage <stage>")} if you also need`
|
|
1775
1549
|
);
|
|
1776
1550
|
console.log(
|
|
1777
|
-
` an API session (required for ${
|
|
1551
|
+
` an API session (required for ${chalk6.cyan("eval")}, ${chalk6.cyan("agent")}, ${chalk6.cyan("thread")}, etc.).`
|
|
1778
1552
|
);
|
|
1779
1553
|
return;
|
|
1780
1554
|
}
|
|
@@ -1808,8 +1582,8 @@ async function offerApiLoginChain(opts) {
|
|
|
1808
1582
|
if (!chosen) {
|
|
1809
1583
|
console.log("");
|
|
1810
1584
|
console.log(
|
|
1811
|
-
|
|
1812
|
-
` Skipped. Run ${
|
|
1585
|
+
chalk6.dim(
|
|
1586
|
+
` Skipped. Run ${chalk6.cyan("thinkwork login --stage <stage>")} later for an API session.`
|
|
1813
1587
|
)
|
|
1814
1588
|
);
|
|
1815
1589
|
return;
|
|
@@ -1906,7 +1680,7 @@ async function doCognitoLogin(opts) {
|
|
|
1906
1680
|
}
|
|
1907
1681
|
console.log("");
|
|
1908
1682
|
console.log(
|
|
1909
|
-
|
|
1683
|
+
chalk6.dim(
|
|
1910
1684
|
` Token expires: ${new Date(tokens.expiresAt * 1e3).toISOString()}. Refreshed automatically.`
|
|
1911
1685
|
)
|
|
1912
1686
|
);
|
|
@@ -2060,9 +1834,9 @@ Registered callback URL:
|
|
|
2060
1834
|
const profiles = listAwsProfiles();
|
|
2061
1835
|
if (profiles.length === 0) {
|
|
2062
1836
|
console.log("");
|
|
2063
|
-
console.log(
|
|
1837
|
+
console.log(chalk6.dim(" No AWS profiles found in ~/.aws/."));
|
|
2064
1838
|
console.log(
|
|
2065
|
-
|
|
1839
|
+
chalk6.dim(" Falling through to access-key entry for a new profile.")
|
|
2066
1840
|
);
|
|
2067
1841
|
if (!await runKeyEntry(targetProfile)) process.exit(1);
|
|
2068
1842
|
process.env.AWS_PROFILE = targetProfile;
|
|
@@ -2073,7 +1847,7 @@ Registered callback URL:
|
|
|
2073
1847
|
const choice = await pickProfile(profiles);
|
|
2074
1848
|
if (choice.kind === "cancel") {
|
|
2075
1849
|
console.log("");
|
|
2076
|
-
console.log(
|
|
1850
|
+
console.log(chalk6.dim(" Cancelled. No changes made."));
|
|
2077
1851
|
return;
|
|
2078
1852
|
}
|
|
2079
1853
|
if (choice.kind === "keys") {
|
|
@@ -2174,16 +1948,16 @@ Examples:
|
|
|
2174
1948
|
}
|
|
2175
1949
|
|
|
2176
1950
|
// src/commands/init.ts
|
|
2177
|
-
import { existsSync as
|
|
2178
|
-
import { resolve as resolve2, join as
|
|
2179
|
-
import { execSync as
|
|
1951
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, cpSync } from "fs";
|
|
1952
|
+
import { resolve as resolve2, join as join4, dirname as dirname3 } from "path";
|
|
1953
|
+
import { execSync as execSync6 } from "child_process";
|
|
2180
1954
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2181
1955
|
import { createInterface as createInterface3 } from "readline";
|
|
2182
|
-
import
|
|
1956
|
+
import chalk7 from "chalk";
|
|
2183
1957
|
var __dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
2184
1958
|
function ask2(prompt, defaultVal = "") {
|
|
2185
1959
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
2186
|
-
const suffix = defaultVal ?
|
|
1960
|
+
const suffix = defaultVal ? chalk7.dim(` [${defaultVal}]`) : "";
|
|
2187
1961
|
return new Promise((resolve7) => {
|
|
2188
1962
|
rl.question(` ${prompt}${suffix}: `, (answer) => {
|
|
2189
1963
|
rl.close();
|
|
@@ -2192,7 +1966,7 @@ function ask2(prompt, defaultVal = "") {
|
|
|
2192
1966
|
});
|
|
2193
1967
|
}
|
|
2194
1968
|
function choose(prompt, options, defaultVal) {
|
|
2195
|
-
const optStr = options.map((o) => o === defaultVal ?
|
|
1969
|
+
const optStr = options.map((o) => o === defaultVal ? chalk7.bold(o) : chalk7.dim(o)).join(" / ");
|
|
2196
1970
|
return ask2(`${prompt} (${optStr})`, defaultVal);
|
|
2197
1971
|
}
|
|
2198
1972
|
function generateSecret(length = 32) {
|
|
@@ -2205,9 +1979,9 @@ function generateSecret(length = 32) {
|
|
|
2205
1979
|
}
|
|
2206
1980
|
function findBundledTerraform() {
|
|
2207
1981
|
const bundled = resolve2(__dirname, "terraform");
|
|
2208
|
-
if (
|
|
1982
|
+
if (existsSync6(join4(bundled, "modules"))) return bundled;
|
|
2209
1983
|
const repoTf = resolve2(__dirname, "..", "..", "..", "terraform");
|
|
2210
|
-
if (
|
|
1984
|
+
if (existsSync6(join4(repoTf, "modules"))) return repoTf;
|
|
2211
1985
|
throw new Error(
|
|
2212
1986
|
"Terraform modules not found. The CLI package may be incomplete.\nTry reinstalling: npm install -g thinkwork-cli@latest"
|
|
2213
1987
|
);
|
|
@@ -2297,9 +2071,9 @@ function registerInitCommand(program2) {
|
|
|
2297
2071
|
process.exit(1);
|
|
2298
2072
|
}
|
|
2299
2073
|
const targetDir = resolve2(opts.dir);
|
|
2300
|
-
const tfDir =
|
|
2301
|
-
const tfvarsPath =
|
|
2302
|
-
if (
|
|
2074
|
+
const tfDir = join4(targetDir, "terraform");
|
|
2075
|
+
const tfvarsPath = join4(tfDir, "terraform.tfvars");
|
|
2076
|
+
if (existsSync6(tfvarsPath)) {
|
|
2303
2077
|
printWarning(`terraform.tfvars already exists at ${tfvarsPath}`);
|
|
2304
2078
|
const overwrite = await ask2("Overwrite?", "N");
|
|
2305
2079
|
if (overwrite.toLowerCase() !== "y") {
|
|
@@ -2323,21 +2097,21 @@ function registerInitCommand(program2) {
|
|
|
2323
2097
|
config.admin_url = "http://localhost:5174";
|
|
2324
2098
|
config.mobile_scheme = "thinkwork";
|
|
2325
2099
|
} else {
|
|
2326
|
-
console.log(
|
|
2100
|
+
console.log(chalk7.bold(" Configure your Thinkwork environment\n"));
|
|
2327
2101
|
const defaultRegion = identity.region !== "unknown" ? identity.region : "us-east-1";
|
|
2328
2102
|
config.region = await ask2("AWS Region", defaultRegion);
|
|
2329
2103
|
console.log("");
|
|
2330
|
-
console.log(
|
|
2104
|
+
console.log(chalk7.dim(" \u2500\u2500 Database \u2500\u2500"));
|
|
2331
2105
|
config.database_engine = await choose("Database engine", ["aurora-serverless", "rds-postgres"], "aurora-serverless");
|
|
2332
2106
|
console.log("");
|
|
2333
|
-
console.log(
|
|
2334
|
-
console.log(
|
|
2335
|
-
console.log(
|
|
2336
|
-
console.log(
|
|
2107
|
+
console.log(chalk7.dim(" \u2500\u2500 Memory \u2500\u2500"));
|
|
2108
|
+
console.log(chalk7.dim(" AgentCore managed memory is always on (automatic retention)."));
|
|
2109
|
+
console.log(chalk7.dim(" Hindsight is an optional add-on: ECS Fargate service for"));
|
|
2110
|
+
console.log(chalk7.dim(" semantic + entity-graph retrieval (~$75/mo)."));
|
|
2337
2111
|
const hindsightAnswer = await ask2("Enable Hindsight long-term memory add-on? (y/N)", "N");
|
|
2338
2112
|
config.enable_hindsight = hindsightAnswer.toLowerCase() === "y" ? "true" : "false";
|
|
2339
2113
|
console.log("");
|
|
2340
|
-
console.log(
|
|
2114
|
+
console.log(chalk7.dim(" \u2500\u2500 Auth \u2500\u2500"));
|
|
2341
2115
|
const useGoogle = await ask2("Enable Google OAuth login? (y/N)", "N");
|
|
2342
2116
|
if (useGoogle.toLowerCase() === "y") {
|
|
2343
2117
|
config.google_oauth_client_id = await ask2("Google OAuth Client ID");
|
|
@@ -2347,13 +2121,13 @@ function registerInitCommand(program2) {
|
|
|
2347
2121
|
config.google_oauth_client_secret = "";
|
|
2348
2122
|
}
|
|
2349
2123
|
console.log("");
|
|
2350
|
-
console.log(
|
|
2124
|
+
console.log(chalk7.dim(" \u2500\u2500 Frontend URLs \u2500\u2500"));
|
|
2351
2125
|
config.admin_url = await ask2("Admin UI URL", "http://localhost:5174");
|
|
2352
2126
|
config.mobile_scheme = await ask2("Mobile app URL scheme", "thinkwork");
|
|
2353
2127
|
console.log("");
|
|
2354
|
-
console.log(
|
|
2355
|
-
console.log(
|
|
2356
|
-
console.log(
|
|
2128
|
+
console.log(chalk7.dim(" \u2500\u2500 Secrets (auto-generated) \u2500\u2500"));
|
|
2129
|
+
console.log(chalk7.dim(` DB password: ${config.db_password.slice(0, 8)}...`));
|
|
2130
|
+
console.log(chalk7.dim(` API auth secret: ${config.api_auth_secret.slice(0, 16)}...`));
|
|
2357
2131
|
}
|
|
2358
2132
|
console.log("");
|
|
2359
2133
|
console.log(" Scaffolding Terraform modules...");
|
|
@@ -2364,24 +2138,24 @@ function registerInitCommand(program2) {
|
|
|
2364
2138
|
printError(String(err));
|
|
2365
2139
|
process.exit(1);
|
|
2366
2140
|
}
|
|
2367
|
-
|
|
2141
|
+
mkdirSync3(tfDir, { recursive: true });
|
|
2368
2142
|
const copyDirs = ["modules", "examples"];
|
|
2369
2143
|
for (const dir of copyDirs) {
|
|
2370
|
-
const src =
|
|
2371
|
-
const dst =
|
|
2372
|
-
if (
|
|
2144
|
+
const src = join4(bundledTf, dir);
|
|
2145
|
+
const dst = join4(tfDir, dir);
|
|
2146
|
+
if (existsSync6(src) && !existsSync6(dst)) {
|
|
2373
2147
|
cpSync(src, dst, { recursive: true });
|
|
2374
2148
|
}
|
|
2375
2149
|
}
|
|
2376
|
-
const schemaPath =
|
|
2377
|
-
if (
|
|
2378
|
-
cpSync(schemaPath,
|
|
2150
|
+
const schemaPath = join4(bundledTf, "schema.graphql");
|
|
2151
|
+
if (existsSync6(schemaPath) && !existsSync6(join4(tfDir, "schema.graphql"))) {
|
|
2152
|
+
cpSync(schemaPath, join4(tfDir, "schema.graphql"));
|
|
2379
2153
|
}
|
|
2380
2154
|
const tfvars = buildTfvars(config);
|
|
2381
|
-
|
|
2382
|
-
const mainTfPath =
|
|
2383
|
-
if (!
|
|
2384
|
-
|
|
2155
|
+
writeFileSync3(tfvarsPath, tfvars);
|
|
2156
|
+
const mainTfPath = join4(tfDir, "main.tf");
|
|
2157
|
+
if (!existsSync6(mainTfPath)) {
|
|
2158
|
+
writeFileSync3(mainTfPath, `################################################################################
|
|
2385
2159
|
# Thinkwork \u2014 ${config.stage}
|
|
2386
2160
|
# Generated by: thinkwork init -s ${config.stage}
|
|
2387
2161
|
################################################################################
|
|
@@ -2558,20 +2332,20 @@ output "agentcore_memory_id" {
|
|
|
2558
2332
|
}
|
|
2559
2333
|
`);
|
|
2560
2334
|
}
|
|
2561
|
-
console.log(` Wrote ${
|
|
2335
|
+
console.log(` Wrote ${chalk7.cyan(tfDir + "/")}`);
|
|
2562
2336
|
console.log("");
|
|
2563
|
-
console.log(
|
|
2564
|
-
console.log(` ${
|
|
2565
|
-
console.log(` ${
|
|
2566
|
-
console.log(` ${
|
|
2567
|
-
console.log(` ${
|
|
2568
|
-
console.log(` ${
|
|
2569
|
-
console.log(` ${
|
|
2570
|
-
console.log(` ${
|
|
2571
|
-
console.log(
|
|
2337
|
+
console.log(chalk7.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"));
|
|
2338
|
+
console.log(` ${chalk7.bold("Stage:")} ${config.stage}`);
|
|
2339
|
+
console.log(` ${chalk7.bold("Region:")} ${config.region}`);
|
|
2340
|
+
console.log(` ${chalk7.bold("Account:")} ${config.account_id}`);
|
|
2341
|
+
console.log(` ${chalk7.bold("Database:")} ${config.database_engine}`);
|
|
2342
|
+
console.log(` ${chalk7.bold("Memory:")} managed (always on)${config.enable_hindsight === "true" ? " + hindsight" : ""}`);
|
|
2343
|
+
console.log(` ${chalk7.bold("Google OAuth:")} ${config.google_oauth_client_id ? "enabled" : "disabled"}`);
|
|
2344
|
+
console.log(` ${chalk7.bold("Directory:")} ${tfDir}`);
|
|
2345
|
+
console.log(chalk7.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"));
|
|
2572
2346
|
console.log("\n Initializing Terraform...\n");
|
|
2573
2347
|
try {
|
|
2574
|
-
|
|
2348
|
+
execSync6("terraform init", { cwd: tfDir, stdio: "inherit" });
|
|
2575
2349
|
} catch {
|
|
2576
2350
|
printWarning("Terraform init failed. Run `thinkwork doctor -s " + stage + "` to check prerequisites.");
|
|
2577
2351
|
return;
|
|
@@ -2590,24 +2364,24 @@ output "agentcore_memory_id" {
|
|
|
2590
2364
|
printSuccess(`Environment "${stage}" initialized`);
|
|
2591
2365
|
console.log("");
|
|
2592
2366
|
console.log(" Next steps:");
|
|
2593
|
-
console.log(` ${
|
|
2594
|
-
console.log(` ${
|
|
2595
|
-
console.log(` ${
|
|
2596
|
-
console.log(` ${
|
|
2367
|
+
console.log(` ${chalk7.cyan("1.")} thinkwork plan -s ${stage} ${chalk7.dim("# Review infrastructure plan")}`);
|
|
2368
|
+
console.log(` ${chalk7.cyan("2.")} thinkwork deploy -s ${stage} ${chalk7.dim("# Deploy to AWS (~5 min)")}`);
|
|
2369
|
+
console.log(` ${chalk7.cyan("3.")} thinkwork bootstrap -s ${stage} ${chalk7.dim("# Seed workspace files + skills")}`);
|
|
2370
|
+
console.log(` ${chalk7.cyan("4.")} thinkwork outputs -s ${stage} ${chalk7.dim("# Show API URL, Cognito IDs, etc.")}`);
|
|
2597
2371
|
console.log("");
|
|
2598
2372
|
});
|
|
2599
2373
|
}
|
|
2600
2374
|
|
|
2601
2375
|
// src/commands/status.ts
|
|
2602
|
-
import { execSync as
|
|
2603
|
-
import
|
|
2376
|
+
import { execSync as execSync7 } from "child_process";
|
|
2377
|
+
import chalk8 from "chalk";
|
|
2604
2378
|
function link(url, label) {
|
|
2605
2379
|
const text = label || url;
|
|
2606
2380
|
return `\x1B]8;;${url}\x1B\\${text}\x1B]8;;\x1B\\`;
|
|
2607
2381
|
}
|
|
2608
|
-
function
|
|
2382
|
+
function runAws2(cmd) {
|
|
2609
2383
|
try {
|
|
2610
|
-
return
|
|
2384
|
+
return execSync7(`aws ${cmd}`, {
|
|
2611
2385
|
encoding: "utf-8",
|
|
2612
2386
|
timeout: 15e3,
|
|
2613
2387
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2618,7 +2392,7 @@ function runAws3(cmd) {
|
|
|
2618
2392
|
}
|
|
2619
2393
|
function discoverAwsStages(region) {
|
|
2620
2394
|
const stages = /* @__PURE__ */ new Map();
|
|
2621
|
-
const raw =
|
|
2395
|
+
const raw = runAws2(
|
|
2622
2396
|
`lambda list-functions --region ${region} --query "Functions[?starts_with(FunctionName, 'thinkwork-')].FunctionName" --output json`
|
|
2623
2397
|
);
|
|
2624
2398
|
if (!raw) return stages;
|
|
@@ -2633,38 +2407,38 @@ function discoverAwsStages(region) {
|
|
|
2633
2407
|
for (const [stage, info] of stages) {
|
|
2634
2408
|
const count = functions.filter((f) => f.startsWith(`thinkwork-${stage}-`)).length;
|
|
2635
2409
|
info.lambdaCount = count;
|
|
2636
|
-
const apiRaw =
|
|
2410
|
+
const apiRaw = runAws2(
|
|
2637
2411
|
`apigatewayv2 get-apis --region ${region} --query "Items[?Name=='thinkwork-${stage}-api'].ApiEndpoint|[0]" --output text`
|
|
2638
2412
|
);
|
|
2639
2413
|
if (apiRaw && apiRaw !== "None") info.apiEndpoint = apiRaw;
|
|
2640
|
-
const appsyncRaw =
|
|
2414
|
+
const appsyncRaw = runAws2(
|
|
2641
2415
|
`appsync list-graphql-apis --region ${region} --query "graphqlApis[?name=='thinkwork-${stage}-subscriptions'].uris.REALTIME|[0]" --output text`
|
|
2642
2416
|
);
|
|
2643
2417
|
if (appsyncRaw && appsyncRaw !== "None") info.appsyncUrl = appsyncRaw;
|
|
2644
|
-
const appsyncApiRaw =
|
|
2418
|
+
const appsyncApiRaw = runAws2(
|
|
2645
2419
|
`appsync list-graphql-apis --region ${region} --query "graphqlApis[?name=='thinkwork-${stage}-subscriptions'].uris.GRAPHQL|[0]" --output text`
|
|
2646
2420
|
);
|
|
2647
2421
|
if (appsyncApiRaw && appsyncApiRaw !== "None") info.appsyncApiUrl = appsyncApiRaw;
|
|
2648
|
-
const acRaw =
|
|
2422
|
+
const acRaw = runAws2(
|
|
2649
2423
|
`lambda get-function --function-name thinkwork-${stage}-agentcore --region ${region} --query "Configuration.State" --output text 2>/dev/null`
|
|
2650
2424
|
);
|
|
2651
2425
|
info.agentcoreStatus = acRaw || "not deployed";
|
|
2652
|
-
const bucketRaw =
|
|
2426
|
+
const bucketRaw = runAws2(
|
|
2653
2427
|
`s3api head-bucket --bucket thinkwork-${stage}-storage --region ${region} 2>/dev/null && echo "exists"`
|
|
2654
2428
|
);
|
|
2655
2429
|
info.bucketName = bucketRaw ? `thinkwork-${stage}-storage` : void 0;
|
|
2656
|
-
const ecsRaw =
|
|
2430
|
+
const ecsRaw = runAws2(
|
|
2657
2431
|
`ecs describe-services --cluster thinkwork-${stage}-cluster --services thinkwork-${stage}-hindsight --region ${region} --query "services[0].runningCount" --output text 2>/dev/null`
|
|
2658
2432
|
);
|
|
2659
2433
|
if (ecsRaw && ecsRaw !== "None" && ecsRaw !== "0") {
|
|
2660
2434
|
info.hindsightEnabled = true;
|
|
2661
|
-
const albRaw =
|
|
2435
|
+
const albRaw = runAws2(
|
|
2662
2436
|
`elbv2 describe-load-balancers --region ${region} --query "LoadBalancers[?contains(LoadBalancerName, 'tw-${stage}-hindsight')].DNSName|[0]" --output text`
|
|
2663
2437
|
);
|
|
2664
2438
|
if (albRaw && albRaw !== "None") {
|
|
2665
2439
|
info.hindsightEndpoint = `http://${albRaw}`;
|
|
2666
2440
|
try {
|
|
2667
|
-
const health =
|
|
2441
|
+
const health = execSync7(`curl -s --max-time 3 http://${albRaw}/health`, { encoding: "utf-8" }).trim();
|
|
2668
2442
|
info.hindsightHealth = health.includes("healthy") ? "healthy" : "unhealthy";
|
|
2669
2443
|
} catch {
|
|
2670
2444
|
info.hindsightHealth = "unreachable";
|
|
@@ -2673,15 +2447,15 @@ function discoverAwsStages(region) {
|
|
|
2673
2447
|
} else {
|
|
2674
2448
|
info.hindsightEnabled = false;
|
|
2675
2449
|
}
|
|
2676
|
-
const dbRaw =
|
|
2450
|
+
const dbRaw = runAws2(
|
|
2677
2451
|
`rds describe-db-clusters --region ${region} --query "DBClusters[?starts_with(DBClusterIdentifier, 'thinkwork-${stage}')].Endpoint|[0]" --output text`
|
|
2678
2452
|
);
|
|
2679
2453
|
if (dbRaw && dbRaw !== "None") info.dbEndpoint = dbRaw;
|
|
2680
|
-
const ecrRaw =
|
|
2454
|
+
const ecrRaw = runAws2(
|
|
2681
2455
|
`ecr describe-repositories --region ${region} --query "repositories[?repositoryName=='thinkwork-${stage}-agentcore'].repositoryUri|[0]" --output text`
|
|
2682
2456
|
);
|
|
2683
2457
|
if (ecrRaw && ecrRaw !== "None") info.ecrUrl = ecrRaw;
|
|
2684
|
-
const cfJson =
|
|
2458
|
+
const cfJson = runAws2(
|
|
2685
2459
|
`cloudfront list-distributions --query "DistributionList.Items[?contains(Origins.Items[0].DomainName, 'thinkwork-${stage}-')].{Origin:Origins.Items[0].DomainName,Domain:DomainName}" --output json`
|
|
2686
2460
|
);
|
|
2687
2461
|
if (cfJson) {
|
|
@@ -2698,33 +2472,33 @@ function discoverAwsStages(region) {
|
|
|
2698
2472
|
return stages;
|
|
2699
2473
|
}
|
|
2700
2474
|
function printStageDetail(info) {
|
|
2701
|
-
console.log(
|
|
2702
|
-
console.log(
|
|
2703
|
-
console.log(` ${
|
|
2704
|
-
console.log(` ${
|
|
2705
|
-
console.log(` ${
|
|
2706
|
-
console.log(` ${
|
|
2707
|
-
console.log(` ${
|
|
2708
|
-
console.log(` ${
|
|
2475
|
+
console.log(chalk8.bold.cyan(` \u2B21 ${info.stage}`));
|
|
2476
|
+
console.log(chalk8.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"));
|
|
2477
|
+
console.log(` ${chalk8.bold("Source:")} ${info.source === "both" ? "AWS + local config" : info.source === "aws" ? "AWS (no local config)" : "local only (not in AWS)"}`);
|
|
2478
|
+
console.log(` ${chalk8.bold("Region:")} ${info.region}`);
|
|
2479
|
+
console.log(` ${chalk8.bold("Account:")} ${info.accountId}`);
|
|
2480
|
+
console.log(` ${chalk8.bold("Lambda fns:")} ${info.lambdaCount || "\u2014"}`);
|
|
2481
|
+
console.log(` ${chalk8.bold("AgentCore:")} ${info.agentcoreStatus || "unknown"}`);
|
|
2482
|
+
console.log(` ${chalk8.bold("Memory:")} managed (always on)`);
|
|
2709
2483
|
const hindsightLabel = info.hindsightEnabled === void 0 ? "unknown" : info.hindsightEnabled ? info.hindsightHealth || "running" : "disabled";
|
|
2710
|
-
console.log(` ${
|
|
2711
|
-
if (info.bucketName) console.log(` ${
|
|
2712
|
-
if (info.dbEndpoint) console.log(` ${
|
|
2713
|
-
if (info.ecrUrl) console.log(` ${
|
|
2484
|
+
console.log(` ${chalk8.bold("Hindsight:")} ${hindsightLabel}`);
|
|
2485
|
+
if (info.bucketName) console.log(` ${chalk8.bold("S3 bucket:")} ${info.bucketName}`);
|
|
2486
|
+
if (info.dbEndpoint) console.log(` ${chalk8.bold("Database:")} ${info.dbEndpoint}`);
|
|
2487
|
+
if (info.ecrUrl) console.log(` ${chalk8.bold("ECR:")} ${info.ecrUrl}`);
|
|
2714
2488
|
console.log("");
|
|
2715
|
-
console.log(
|
|
2489
|
+
console.log(chalk8.bold(" URLs:"));
|
|
2716
2490
|
if (info.adminUrl) console.log(` Admin: ${link(info.adminUrl)}`);
|
|
2717
2491
|
if (info.docsUrl) console.log(` Docs: ${link(info.docsUrl)}`);
|
|
2718
2492
|
if (info.apiEndpoint) console.log(` API: ${link(info.apiEndpoint)}`);
|
|
2719
2493
|
if (info.appsyncApiUrl) console.log(` AppSync: ${link(info.appsyncApiUrl)}`);
|
|
2720
2494
|
if (info.appsyncUrl) console.log(` WebSocket: ${link(info.appsyncUrl)}`);
|
|
2721
2495
|
if (info.hindsightEndpoint) console.log(` Hindsight: ${link(info.hindsightEndpoint)}`);
|
|
2722
|
-
console.log(
|
|
2496
|
+
console.log(chalk8.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"));
|
|
2723
2497
|
const local = loadEnvironment(info.stage);
|
|
2724
2498
|
if (local) {
|
|
2725
|
-
console.log(
|
|
2499
|
+
console.log(chalk8.dim(` Terraform dir: ${local.terraformDir}`));
|
|
2726
2500
|
} else {
|
|
2727
|
-
console.log(
|
|
2501
|
+
console.log(chalk8.dim(` No local config. Run: thinkwork init -s ${info.stage}`));
|
|
2728
2502
|
}
|
|
2729
2503
|
console.log("");
|
|
2730
2504
|
}
|
|
@@ -2761,7 +2535,7 @@ and AgentCore for per-stage detail.
|
|
|
2761
2535
|
printError("AWS credentials not configured. Run `thinkwork login` first.");
|
|
2762
2536
|
process.exit(1);
|
|
2763
2537
|
}
|
|
2764
|
-
console.log(
|
|
2538
|
+
console.log(chalk8.dim(" Scanning AWS account for Thinkwork deployments...\n"));
|
|
2765
2539
|
const awsStages = discoverAwsStages(opts.region);
|
|
2766
2540
|
const localEnvs = listEnvironments();
|
|
2767
2541
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -2796,7 +2570,7 @@ and AgentCore for per-stage detail.
|
|
|
2796
2570
|
}
|
|
2797
2571
|
if (merged.size === 0) {
|
|
2798
2572
|
console.log(" No Thinkwork environments found.");
|
|
2799
|
-
console.log(` Run ${
|
|
2573
|
+
console.log(` Run ${chalk8.cyan("thinkwork init -s <stage>")} to create one.`);
|
|
2800
2574
|
console.log("");
|
|
2801
2575
|
return;
|
|
2802
2576
|
}
|
|
@@ -2807,7 +2581,7 @@ and AgentCore for per-stage detail.
|
|
|
2807
2581
|
}
|
|
2808
2582
|
|
|
2809
2583
|
// src/commands/mcp.ts
|
|
2810
|
-
import
|
|
2584
|
+
import chalk9 from "chalk";
|
|
2811
2585
|
|
|
2812
2586
|
// ../../packages/admin-ops/src/client.ts
|
|
2813
2587
|
var AdminOpsError = class extends Error {
|
|
@@ -2823,7 +2597,7 @@ var AdminOpsError = class extends Error {
|
|
|
2823
2597
|
function createClient(config) {
|
|
2824
2598
|
const fetchImpl = config.fetchImpl ?? fetch;
|
|
2825
2599
|
const base = config.apiUrl.replace(/\/+$/, "");
|
|
2826
|
-
const doFetch = async (
|
|
2600
|
+
const doFetch = async (path, init = {}) => {
|
|
2827
2601
|
const headers = {
|
|
2828
2602
|
"Content-Type": "application/json",
|
|
2829
2603
|
Authorization: `Bearer ${config.authSecret}`,
|
|
@@ -2833,7 +2607,7 @@ function createClient(config) {
|
|
|
2833
2607
|
if (config.principalEmail) headers["x-principal-email"] = config.principalEmail;
|
|
2834
2608
|
if (config.tenantId) headers["x-tenant-id"] = config.tenantId;
|
|
2835
2609
|
if (config.agentId) headers["x-agent-id"] = config.agentId;
|
|
2836
|
-
const res = await fetchImpl(`${base}${
|
|
2610
|
+
const res = await fetchImpl(`${base}${path}`, {
|
|
2837
2611
|
...init,
|
|
2838
2612
|
headers: { ...headers, ...init.headers }
|
|
2839
2613
|
});
|
|
@@ -2919,87 +2693,6 @@ async function revokeAdminKey(client, tenantIdOrSlug, keyId) {
|
|
|
2919
2693
|
);
|
|
2920
2694
|
}
|
|
2921
2695
|
|
|
2922
|
-
// src/api-client.ts
|
|
2923
|
-
import { readFileSync as readFileSync5, existsSync as existsSync9 } from "fs";
|
|
2924
|
-
import { execSync as execSync9 } from "child_process";
|
|
2925
|
-
function readTfVar2(tfvarsPath, key) {
|
|
2926
|
-
if (!existsSync9(tfvarsPath)) return null;
|
|
2927
|
-
const content = readFileSync5(tfvarsPath, "utf-8");
|
|
2928
|
-
const match = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m"));
|
|
2929
|
-
return match ? match[1] : null;
|
|
2930
|
-
}
|
|
2931
|
-
function resolveTfvarsPath2(stage) {
|
|
2932
|
-
const tfDir = resolveTerraformDir(stage);
|
|
2933
|
-
if (tfDir) {
|
|
2934
|
-
const direct = `${tfDir}/terraform.tfvars`;
|
|
2935
|
-
if (existsSync9(direct)) return direct;
|
|
2936
|
-
}
|
|
2937
|
-
const terraformDir = process.env.THINKWORK_TERRAFORM_DIR || process.cwd();
|
|
2938
|
-
const cwd = resolveTierDir(terraformDir, stage, "app");
|
|
2939
|
-
return `${cwd}/terraform.tfvars`;
|
|
2940
|
-
}
|
|
2941
|
-
function getApiEndpoint2(stage, region) {
|
|
2942
|
-
try {
|
|
2943
|
-
const raw = execSync9(
|
|
2944
|
-
`aws apigatewayv2 get-apis --region ${region} --query "Items[?Name=='thinkwork-${stage}-api'].ApiEndpoint|[0]" --output text`,
|
|
2945
|
-
{ encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
2946
|
-
).trim();
|
|
2947
|
-
return raw && raw !== "None" ? raw : null;
|
|
2948
|
-
} catch {
|
|
2949
|
-
return null;
|
|
2950
|
-
}
|
|
2951
|
-
}
|
|
2952
|
-
async function apiFetch(apiUrl, authSecret, path2, options = {}, extraHeaders = {}) {
|
|
2953
|
-
const res = await fetch(`${apiUrl}${path2}`, {
|
|
2954
|
-
...options,
|
|
2955
|
-
headers: {
|
|
2956
|
-
"Content-Type": "application/json",
|
|
2957
|
-
Authorization: `Bearer ${authSecret}`,
|
|
2958
|
-
...extraHeaders,
|
|
2959
|
-
...options.headers
|
|
2960
|
-
}
|
|
2961
|
-
});
|
|
2962
|
-
if (!res.ok) {
|
|
2963
|
-
const body = await res.json().catch(() => ({}));
|
|
2964
|
-
throw new Error(body.error || `HTTP ${res.status}`);
|
|
2965
|
-
}
|
|
2966
|
-
return res.json();
|
|
2967
|
-
}
|
|
2968
|
-
async function apiFetchRaw(apiUrl, authSecret, path2, options = {}, extraHeaders = {}) {
|
|
2969
|
-
const res = await fetch(`${apiUrl}${path2}`, {
|
|
2970
|
-
...options,
|
|
2971
|
-
headers: {
|
|
2972
|
-
"Content-Type": "application/json",
|
|
2973
|
-
Authorization: `Bearer ${authSecret}`,
|
|
2974
|
-
...extraHeaders,
|
|
2975
|
-
...options.headers
|
|
2976
|
-
}
|
|
2977
|
-
});
|
|
2978
|
-
const body = await res.json().catch(() => ({}));
|
|
2979
|
-
return { ok: res.ok, status: res.status, body };
|
|
2980
|
-
}
|
|
2981
|
-
function resolveApiConfig(stage, regionOverride) {
|
|
2982
|
-
const tfvarsPath = resolveTfvarsPath2(stage);
|
|
2983
|
-
const tfAuthSecret = readTfVar2(tfvarsPath, "api_auth_secret");
|
|
2984
|
-
const tfRegion = readTfVar2(tfvarsPath, "region");
|
|
2985
|
-
const region = regionOverride || tfRegion || "us-east-1";
|
|
2986
|
-
const apiUrl = getApiEndpoint2(stage, region);
|
|
2987
|
-
if (!apiUrl) {
|
|
2988
|
-
printError(
|
|
2989
|
-
`Cannot discover API endpoint for stage "${stage}" in ${region}. Is the stack deployed?`
|
|
2990
|
-
);
|
|
2991
|
-
return null;
|
|
2992
|
-
}
|
|
2993
|
-
const authSecret = tfAuthSecret ?? getApiAuthSecretFromLambda(stage, region);
|
|
2994
|
-
if (!authSecret) {
|
|
2995
|
-
printError(
|
|
2996
|
-
`Cannot read api_auth_secret. Tried terraform.tfvars at ${tfvarsPath} and the \`thinkwork-${stage}-api-tenants\` Lambda env. Deploy the stack or set --profile to a role with lambda:GetFunctionConfiguration.`
|
|
2997
|
-
);
|
|
2998
|
-
return null;
|
|
2999
|
-
}
|
|
3000
|
-
return { apiUrl, authSecret };
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
2696
|
// src/lib/resolve-tenant.ts
|
|
3004
2697
|
import { select as select4 } from "@inquirer/prompts";
|
|
3005
2698
|
async function resolveTenant(opts) {
|
|
@@ -3173,7 +2866,7 @@ async function resolveServer(identifier, api, tenantSlug) {
|
|
|
3173
2866
|
getId: (s) => s.id,
|
|
3174
2867
|
getAliases: (s) => [s.slug, s.name],
|
|
3175
2868
|
resourceLabel: "MCP server",
|
|
3176
|
-
pickerLabel: (s) => `${s.name} ${
|
|
2869
|
+
pickerLabel: (s) => `${s.name} ${chalk9.dim(`(${s.slug}, ${s.id})`)}`
|
|
3177
2870
|
});
|
|
3178
2871
|
}
|
|
3179
2872
|
function formatAuth(s) {
|
|
@@ -3206,13 +2899,13 @@ Examples:
|
|
|
3206
2899
|
{ "x-tenant-slug": tenant.slug }
|
|
3207
2900
|
);
|
|
3208
2901
|
if (!servers || servers.length === 0) {
|
|
3209
|
-
console.log(
|
|
2902
|
+
console.log(chalk9.dim(" No MCP servers registered."));
|
|
3210
2903
|
return;
|
|
3211
2904
|
}
|
|
3212
2905
|
console.log("");
|
|
3213
2906
|
for (const s of servers) {
|
|
3214
|
-
const status = s.enabled ?
|
|
3215
|
-
console.log(` ${
|
|
2907
|
+
const status = s.enabled ? chalk9.green("enabled") : chalk9.dim("disabled");
|
|
2908
|
+
console.log(` ${chalk9.bold(s.name)} ${chalk9.dim(s.slug)} ${status}`);
|
|
3216
2909
|
console.log(` ID: ${s.id}`);
|
|
3217
2910
|
console.log(` URL: ${s.url}`);
|
|
3218
2911
|
console.log(` Transport: ${s.transport}`);
|
|
@@ -3393,12 +3086,12 @@ Examples:
|
|
|
3393
3086
|
if (result.ok) {
|
|
3394
3087
|
printSuccess(`Connection successful: ${server.name}.`);
|
|
3395
3088
|
if (result.tools?.length) {
|
|
3396
|
-
console.log(
|
|
3089
|
+
console.log(chalk9.bold(`
|
|
3397
3090
|
Discovered tools (${result.tools.length}):
|
|
3398
3091
|
`));
|
|
3399
3092
|
for (const t of result.tools) {
|
|
3400
3093
|
console.log(
|
|
3401
|
-
` ${
|
|
3094
|
+
` ${chalk9.cyan(t.name)}${t.description ? chalk9.dim(` - ${t.description}`) : ""}`
|
|
3402
3095
|
);
|
|
3403
3096
|
}
|
|
3404
3097
|
console.log("");
|
|
@@ -3503,8 +3196,8 @@ old one.
|
|
|
3503
3196
|
});
|
|
3504
3197
|
printJson(created);
|
|
3505
3198
|
printWarning("This token will NOT be shown again. Copy it now.");
|
|
3506
|
-
printSuccess(`MCP admin key created: ${
|
|
3507
|
-
console.log(` ${
|
|
3199
|
+
printSuccess(`MCP admin key created: ${chalk9.bold(created.name)} (${created.id})`);
|
|
3200
|
+
console.log(` ${chalk9.dim("Token:")} ${chalk9.cyan(created.token)}`);
|
|
3508
3201
|
} catch (err) {
|
|
3509
3202
|
if (isCancellation(err)) return;
|
|
3510
3203
|
printError(err instanceof Error ? err.message : String(err));
|
|
@@ -3650,7 +3343,7 @@ SPA (or a future \`thinkwork mcp assign\` CLI pass).
|
|
|
3650
3343
|
|
|
3651
3344
|
// src/commands/tools.ts
|
|
3652
3345
|
import { select as select6, password } from "@inquirer/prompts";
|
|
3653
|
-
import
|
|
3346
|
+
import chalk10 from "chalk";
|
|
3654
3347
|
var TOOL_PROVIDERS = {
|
|
3655
3348
|
"web-search": ["exa", "serpapi"]
|
|
3656
3349
|
};
|
|
@@ -3691,16 +3384,16 @@ Examples:
|
|
|
3691
3384
|
{ "x-tenant-slug": tenant.slug }
|
|
3692
3385
|
);
|
|
3693
3386
|
if (!rows || rows.length === 0) {
|
|
3694
|
-
console.log(
|
|
3695
|
-
console.log(
|
|
3387
|
+
console.log(chalk10.dim(" No built-in tools configured."));
|
|
3388
|
+
console.log(chalk10.dim(" Try: thinkwork tools web-search set"));
|
|
3696
3389
|
return;
|
|
3697
3390
|
}
|
|
3698
3391
|
console.log("");
|
|
3699
3392
|
for (const r of rows) {
|
|
3700
|
-
const status = r.enabled ?
|
|
3701
|
-
const key = r.hasSecret ?
|
|
3702
|
-
const provider = r.provider ??
|
|
3703
|
-
console.log(` ${
|
|
3393
|
+
const status = r.enabled ? chalk10.green("enabled") : chalk10.dim("disabled");
|
|
3394
|
+
const key = r.hasSecret ? chalk10.green("yes") : chalk10.red("no");
|
|
3395
|
+
const provider = r.provider ?? chalk10.dim("\u2014");
|
|
3396
|
+
console.log(` ${chalk10.bold(r.toolSlug)} ${status}`);
|
|
3704
3397
|
console.log(` Provider: ${provider}`);
|
|
3705
3398
|
console.log(` Has key: ${key}`);
|
|
3706
3399
|
if (r.lastTestedAt) console.log(` Tested: ${new Date(r.lastTestedAt).toLocaleString()}`);
|
|
@@ -3831,12 +3524,12 @@ Examples:
|
|
|
3831
3524
|
}
|
|
3832
3525
|
|
|
3833
3526
|
// src/commands/update.ts
|
|
3834
|
-
import { execSync as
|
|
3527
|
+
import { execSync as execSync8 } from "child_process";
|
|
3835
3528
|
import { realpathSync } from "fs";
|
|
3836
|
-
import
|
|
3529
|
+
import chalk11 from "chalk";
|
|
3837
3530
|
function getLatestVersion() {
|
|
3838
3531
|
try {
|
|
3839
|
-
return
|
|
3532
|
+
return execSync8("npm view thinkwork-cli version", {
|
|
3840
3533
|
encoding: "utf-8",
|
|
3841
3534
|
timeout: 1e4,
|
|
3842
3535
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3847,7 +3540,7 @@ function getLatestVersion() {
|
|
|
3847
3540
|
}
|
|
3848
3541
|
function detectInstallMethod() {
|
|
3849
3542
|
try {
|
|
3850
|
-
const which =
|
|
3543
|
+
const which = execSync8("which thinkwork", {
|
|
3851
3544
|
encoding: "utf-8",
|
|
3852
3545
|
timeout: 5e3,
|
|
3853
3546
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3875,28 +3568,28 @@ function compareVersions(a, b) {
|
|
|
3875
3568
|
function registerUpdateCommand(program2) {
|
|
3876
3569
|
program2.command("update").description("Check for and install CLI updates").option("--check", "Only check for updates, don't install").action(async (opts) => {
|
|
3877
3570
|
printHeader("update", "", null);
|
|
3878
|
-
console.log(` Current version: ${
|
|
3571
|
+
console.log(` Current version: ${chalk11.bold(VERSION)}`);
|
|
3879
3572
|
const latest = getLatestVersion();
|
|
3880
3573
|
if (!latest) {
|
|
3881
|
-
console.log(
|
|
3574
|
+
console.log(chalk11.yellow(" Could not check npm registry for updates."));
|
|
3882
3575
|
return;
|
|
3883
3576
|
}
|
|
3884
|
-
console.log(` Latest version: ${
|
|
3577
|
+
console.log(` Latest version: ${chalk11.bold(latest)}`);
|
|
3885
3578
|
console.log("");
|
|
3886
3579
|
const cmp = compareVersions(VERSION, latest);
|
|
3887
3580
|
if (cmp >= 0) {
|
|
3888
|
-
console.log(
|
|
3581
|
+
console.log(chalk11.green(" \u2713 You're on the latest version."));
|
|
3889
3582
|
console.log("");
|
|
3890
3583
|
return;
|
|
3891
3584
|
}
|
|
3892
|
-
console.log(
|
|
3585
|
+
console.log(chalk11.cyan(` Update available: ${VERSION} \u2192 ${latest}`));
|
|
3893
3586
|
console.log("");
|
|
3894
3587
|
if (opts.check) {
|
|
3895
3588
|
const method2 = detectInstallMethod();
|
|
3896
3589
|
if (method2 === "homebrew") {
|
|
3897
|
-
console.log(` Run: ${
|
|
3590
|
+
console.log(` Run: ${chalk11.cyan("brew upgrade thinkwork-ai/tap/thinkwork")}`);
|
|
3898
3591
|
} else {
|
|
3899
|
-
console.log(` Run: ${
|
|
3592
|
+
console.log(` Run: ${chalk11.cyan(`npm install -g thinkwork-cli@${latest}`)}`);
|
|
3900
3593
|
}
|
|
3901
3594
|
console.log("");
|
|
3902
3595
|
return;
|
|
@@ -3904,27 +3597,27 @@ function registerUpdateCommand(program2) {
|
|
|
3904
3597
|
const method = detectInstallMethod();
|
|
3905
3598
|
const cmd = method === "homebrew" ? "brew upgrade thinkwork-ai/tap/thinkwork" : `npm install -g thinkwork-cli@${latest}`;
|
|
3906
3599
|
console.log(` Installing via ${method}...`);
|
|
3907
|
-
console.log(
|
|
3600
|
+
console.log(chalk11.dim(` $ ${cmd}`));
|
|
3908
3601
|
console.log("");
|
|
3909
3602
|
try {
|
|
3910
|
-
|
|
3603
|
+
execSync8(cmd, { stdio: "inherit", timeout: 12e4 });
|
|
3911
3604
|
console.log("");
|
|
3912
|
-
console.log(
|
|
3605
|
+
console.log(chalk11.green(` \u2713 Upgraded to thinkwork-cli@${latest}`));
|
|
3913
3606
|
} catch {
|
|
3914
3607
|
console.log("");
|
|
3915
|
-
console.log(
|
|
3916
|
-
console.log(` ${
|
|
3608
|
+
console.log(chalk11.red(` Failed to upgrade. Try manually:`));
|
|
3609
|
+
console.log(` ${chalk11.cyan(cmd)}`);
|
|
3917
3610
|
}
|
|
3918
3611
|
console.log("");
|
|
3919
3612
|
});
|
|
3920
3613
|
}
|
|
3921
3614
|
|
|
3922
3615
|
// src/commands/user.ts
|
|
3923
|
-
import { spawn as
|
|
3616
|
+
import { spawn as spawn4 } from "child_process";
|
|
3924
3617
|
import { input as input2, select as select7 } from "@inquirer/prompts";
|
|
3925
3618
|
function getTerraformOutput2(cwd, key) {
|
|
3926
3619
|
return new Promise((resolve7, reject) => {
|
|
3927
|
-
const proc =
|
|
3620
|
+
const proc = spawn4("terraform", ["output", "-raw", key], {
|
|
3928
3621
|
cwd,
|
|
3929
3622
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3930
3623
|
});
|
|
@@ -3956,7 +3649,7 @@ function runAwsCognitoReset(userPoolId, username, region) {
|
|
|
3956
3649
|
"json"
|
|
3957
3650
|
];
|
|
3958
3651
|
if (region) args.push("--region", region);
|
|
3959
|
-
const proc =
|
|
3652
|
+
const proc = spawn4("aws", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
3960
3653
|
let stdout = "";
|
|
3961
3654
|
let stderr = "";
|
|
3962
3655
|
proc.stdout.on("data", (d) => stdout += d);
|
|
@@ -4704,6 +4397,8 @@ var CliUpdateWebhookDocument = { "kind": "Document", "definitions": [{ "kind": "
|
|
|
4704
4397
|
var CliDeleteWebhookDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "CliDeleteWebhook" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "deleteWebhook" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }] }] } }] };
|
|
4705
4398
|
var CliRegenerateWebhookTokenDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "CliRegenerateWebhookToken" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "regenerateWebhookToken" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "token" } }] } }] } }] };
|
|
4706
4399
|
var CliWebhookDeliveriesDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliWebhookDeliveries" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "webhookId" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }, { "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "webhookDeliveries" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "webhookId" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "webhookId" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "limit" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "limit" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "providerName" } }, { "kind": "Field", "name": { "kind": "Name", "value": "providerEventId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "normalizedKind" } }, { "kind": "Field", "name": { "kind": "Name", "value": "receivedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "signatureStatus" } }, { "kind": "Field", "name": { "kind": "Name", "value": "resolutionStatus" } }, { "kind": "Field", "name": { "kind": "Name", "value": "statusCode" } }, { "kind": "Field", "name": { "kind": "Name", "value": "durationMs" } }, { "kind": "Field", "name": { "kind": "Name", "value": "threadId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "threadCreated" } }, { "kind": "Field", "name": { "kind": "Name", "value": "retryCount" } }, { "kind": "Field", "name": { "kind": "Name", "value": "isReplay" } }, { "kind": "Field", "name": { "kind": "Name", "value": "errorMessage" } }] } }] } }] };
|
|
4400
|
+
var CliTestWebhookDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "CliTestWebhook" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "testWebhook" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "webhookId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "tenantId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "receivedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "resolutionStatus" } }, { "kind": "Field", "name": { "kind": "Name", "value": "signatureStatus" } }, { "kind": "Field", "name": { "kind": "Name", "value": "statusCode" } }, { "kind": "Field", "name": { "kind": "Name", "value": "bodyPreview" } }] } }] } }] };
|
|
4401
|
+
var CliWebhookForTestDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliWebhookForTest" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "webhook" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "id" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "id" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "token" } }] } }] } }] };
|
|
4707
4402
|
var CliWebhookTenantBySlugDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliWebhookTenantBySlug" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "tenantBySlug" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "slug" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }] } }] } }] };
|
|
4708
4403
|
var CliWikiTenantBySlugDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliWikiTenantBySlug" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "String" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "tenantBySlug" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "slug" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "slug" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "slug" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }] } }] } }] };
|
|
4709
4404
|
var CliAllTenantAgentsForWikiDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "CliAllTenantAgentsForWiki" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "tenantId" } }, "type": { "kind": "NonNullType", "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "ID" } } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "allTenantAgents" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "tenantId" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "tenantId" } } }, { "kind": "Argument", "name": { "kind": "Name", "value": "includeSystem" }, "value": { "kind": "BooleanValue", "value": false } }, { "kind": "Argument", "name": { "kind": "Name", "value": "includeSubAgents" }, "value": { "kind": "BooleanValue", "value": false } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "id" } }, { "kind": "Field", "name": { "kind": "Name", "value": "name" } }, { "kind": "Field", "name": { "kind": "Name", "value": "slug" } }, { "kind": "Field", "name": { "kind": "Name", "value": "type" } }, { "kind": "Field", "name": { "kind": "Name", "value": "status" } }] } }] } }] };
|
|
@@ -4883,6 +4578,8 @@ var documents = {
|
|
|
4883
4578
|
"\n mutation CliDeleteWebhook($id: ID!) {\n deleteWebhook(id: $id)\n }\n": CliDeleteWebhookDocument,
|
|
4884
4579
|
"\n mutation CliRegenerateWebhookToken($id: ID!) {\n regenerateWebhookToken(id: $id) {\n id\n token\n }\n }\n": CliRegenerateWebhookTokenDocument,
|
|
4885
4580
|
"\n query CliWebhookDeliveries($webhookId: ID!, $limit: Int) {\n webhookDeliveries(webhookId: $webhookId, limit: $limit) {\n id\n providerName\n providerEventId\n normalizedKind\n receivedAt\n signatureStatus\n resolutionStatus\n statusCode\n durationMs\n threadId\n threadCreated\n retryCount\n isReplay\n errorMessage\n }\n }\n": CliWebhookDeliveriesDocument,
|
|
4581
|
+
"\n mutation CliTestWebhook($id: ID!) {\n testWebhook(id: $id) {\n id\n webhookId\n tenantId\n receivedAt\n resolutionStatus\n signatureStatus\n statusCode\n bodyPreview\n }\n }\n": CliTestWebhookDocument,
|
|
4582
|
+
"\n query CliWebhookForTest($id: ID!) {\n webhook(id: $id) {\n id\n token\n }\n }\n": CliWebhookForTestDocument,
|
|
4886
4583
|
"\n query CliWebhookTenantBySlug($slug: String!) {\n tenantBySlug(slug: $slug) {\n id\n }\n }\n": CliWebhookTenantBySlugDocument,
|
|
4887
4584
|
"\n query CliWikiTenantBySlug($slug: String!) {\n tenantBySlug(slug: $slug) {\n id\n slug\n name\n }\n }\n": CliWikiTenantBySlugDocument,
|
|
4888
4585
|
"\n query CliAllTenantAgentsForWiki($tenantId: ID!) {\n allTenantAgents(tenantId: $tenantId, includeSystem: false, includeSubAgents: false) {\n id\n name\n slug\n type\n status\n }\n }\n": CliAllTenantAgentsForWikiDocument,
|
|
@@ -7485,7 +7182,7 @@ Examples:
|
|
|
7485
7182
|
}
|
|
7486
7183
|
|
|
7487
7184
|
// src/commands/computer.ts
|
|
7488
|
-
import
|
|
7185
|
+
import chalk12 from "chalk";
|
|
7489
7186
|
async function resolveComputerContext(opts) {
|
|
7490
7187
|
const stage = await resolveStage({ flag: opts.stage });
|
|
7491
7188
|
const api = resolveApiConfig(stage);
|
|
@@ -7525,7 +7222,7 @@ function printMigrationReport(response) {
|
|
|
7525
7222
|
if (!response.report) return;
|
|
7526
7223
|
const summary = response.report.summary ?? {};
|
|
7527
7224
|
console.log("");
|
|
7528
|
-
console.log(
|
|
7225
|
+
console.log(chalk12.bold(" Summary"));
|
|
7529
7226
|
for (const [status, count] of Object.entries(summary)) {
|
|
7530
7227
|
if (!count) continue;
|
|
7531
7228
|
console.log(` ${status.padEnd(28)} ${count}`);
|
|
@@ -7606,7 +7303,7 @@ function registerComputerCommand(program2) {
|
|
|
7606
7303
|
if (response.status === 409 && response.body.blockers) {
|
|
7607
7304
|
if (!isJsonMode()) {
|
|
7608
7305
|
console.log("");
|
|
7609
|
-
console.log(
|
|
7306
|
+
console.log(chalk12.bold(" Blockers"));
|
|
7610
7307
|
console.log(JSON.stringify(response.body.blockers, null, 2));
|
|
7611
7308
|
}
|
|
7612
7309
|
}
|
|
@@ -10734,6 +10431,28 @@ var WebhookDeliveriesDoc = graphql(`
|
|
|
10734
10431
|
}
|
|
10735
10432
|
}
|
|
10736
10433
|
`);
|
|
10434
|
+
var TestWebhookDoc = graphql(`
|
|
10435
|
+
mutation CliTestWebhook($id: ID!) {
|
|
10436
|
+
testWebhook(id: $id) {
|
|
10437
|
+
id
|
|
10438
|
+
webhookId
|
|
10439
|
+
tenantId
|
|
10440
|
+
receivedAt
|
|
10441
|
+
resolutionStatus
|
|
10442
|
+
signatureStatus
|
|
10443
|
+
statusCode
|
|
10444
|
+
bodyPreview
|
|
10445
|
+
}
|
|
10446
|
+
}
|
|
10447
|
+
`);
|
|
10448
|
+
var WebhookForTestDoc = graphql(`
|
|
10449
|
+
query CliWebhookForTest($id: ID!) {
|
|
10450
|
+
webhook(id: $id) {
|
|
10451
|
+
id
|
|
10452
|
+
token
|
|
10453
|
+
}
|
|
10454
|
+
}
|
|
10455
|
+
`);
|
|
10737
10456
|
var WebhookTenantBySlugDoc = graphql(`
|
|
10738
10457
|
query CliWebhookTenantBySlug($slug: String!) {
|
|
10739
10458
|
tenantBySlug(slug: $slug) {
|
|
@@ -11011,12 +10730,39 @@ async function runWebhookDeliveries(id, opts) {
|
|
|
11011
10730
|
]
|
|
11012
10731
|
);
|
|
11013
10732
|
}
|
|
11014
|
-
function
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
10733
|
+
async function runWebhookTest(id, _opts) {
|
|
10734
|
+
const ctx = await resolveWebhookContext(_opts);
|
|
10735
|
+
const data = await gqlMutate(ctx.client, TestWebhookDoc, { id });
|
|
10736
|
+
const delivery = data.testWebhook;
|
|
10737
|
+
let curlHint = null;
|
|
10738
|
+
try {
|
|
10739
|
+
const tokenData = await gqlQuery(ctx.client, WebhookForTestDoc, { id });
|
|
10740
|
+
if (tokenData.webhook?.token) {
|
|
10741
|
+
const { resolveApiConfig: resolveApiConfig2 } = await import("./api-client-MC4NOZ4L.js");
|
|
10742
|
+
const api = resolveApiConfig2(ctx.stage);
|
|
10743
|
+
if (api?.apiUrl) {
|
|
10744
|
+
const base = api.apiUrl.replace(/\/$/, "");
|
|
10745
|
+
curlHint = `${base}/webhooks/${tokenData.webhook.token}`;
|
|
10746
|
+
}
|
|
10747
|
+
}
|
|
10748
|
+
} catch {
|
|
10749
|
+
}
|
|
10750
|
+
if (isJsonMode()) {
|
|
10751
|
+
printJson({ delivery, publicUrl: curlHint });
|
|
10752
|
+
return;
|
|
10753
|
+
}
|
|
10754
|
+
printSuccess(
|
|
10755
|
+
`Recorded synthetic delivery ${delivery.id} (resolution=${delivery.resolutionStatus}).`
|
|
11018
10756
|
);
|
|
11019
|
-
|
|
10757
|
+
if (curlHint) {
|
|
10758
|
+
console.log("");
|
|
10759
|
+
console.log(" For end-to-end reachability check, curl the public URL:");
|
|
10760
|
+
console.log(
|
|
10761
|
+
` curl -X POST -H 'content-type: application/json' \\
|
|
10762
|
+
-d '{"hello":"world"}' \\
|
|
10763
|
+
${curlHint}`
|
|
10764
|
+
);
|
|
10765
|
+
}
|
|
11020
10766
|
}
|
|
11021
10767
|
function registerWebhookCommand(program2) {
|
|
11022
10768
|
const wh = program2.command("webhook").alias("webhooks").description("Manage inbound webhooks that dispatch to agents or routines.");
|
|
@@ -11031,7 +10777,9 @@ Examples:
|
|
|
11031
10777
|
).action(runWebhookCreate);
|
|
11032
10778
|
wh.command("update <id>").description("Update a webhook's target, rate limit, or enabled state.").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--target-type <t>").option("--target-id <id>").option("--rate-limit <rpm>").option("--allowed-ips <csv>").option("--enable").option("--disable").action(runWebhookUpdate);
|
|
11033
10779
|
wh.command("delete <id>").description("Delete a webhook (its URL stops working immediately).").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("-y, --yes", "Skip confirmation").action(runWebhookDelete);
|
|
11034
|
-
wh.command("test <id>").description(
|
|
10780
|
+
wh.command("test <id>").description(
|
|
10781
|
+
"Record a synthetic test delivery row for the webhook (visible via `webhook deliveries`). Does NOT trigger downstream dispatch; prints a curl one-liner for end-to-end reachability against the public URL."
|
|
10782
|
+
).option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").action(runWebhookTest);
|
|
11035
10783
|
wh.command("rotate <id>").description("Generate a new token for an existing webhook.").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("-y, --yes", "Skip confirmation").action(runWebhookRotate);
|
|
11036
10784
|
wh.command("deliveries <id>").description(
|
|
11037
10785
|
"Show recent delivery attempts for a webhook (newest first). Default 25, max 500."
|
|
@@ -11040,7 +10788,7 @@ Examples:
|
|
|
11040
10788
|
|
|
11041
10789
|
// src/lib/plugin-zip.ts
|
|
11042
10790
|
import { createReadStream, promises as fsp, statSync } from "fs";
|
|
11043
|
-
import { basename, join as
|
|
10791
|
+
import { basename, join as join5, relative, resolve as resolve3, sep } from "path";
|
|
11044
10792
|
import JSZip from "jszip";
|
|
11045
10793
|
var PluginZipError = class extends Error {
|
|
11046
10794
|
constructor(message, kind) {
|
|
@@ -11067,7 +10815,7 @@ async function buildPluginZip(pluginDir) {
|
|
|
11067
10815
|
"missing-directory"
|
|
11068
10816
|
);
|
|
11069
10817
|
}
|
|
11070
|
-
const manifestPath =
|
|
10818
|
+
const manifestPath = join5(root, "plugin.json");
|
|
11071
10819
|
let manifestRaw;
|
|
11072
10820
|
try {
|
|
11073
10821
|
manifestRaw = await fsp.readFile(manifestPath, "utf8");
|
|
@@ -11146,7 +10894,7 @@ async function walkDir(rootDir, currentDir) {
|
|
|
11146
10894
|
const out = [];
|
|
11147
10895
|
const dirents = await fsp.readdir(currentDir, { withFileTypes: true });
|
|
11148
10896
|
for (const ent of dirents) {
|
|
11149
|
-
const abs =
|
|
10897
|
+
const abs = join5(currentDir, ent.name);
|
|
11150
10898
|
const rel = relative(rootDir, abs);
|
|
11151
10899
|
if (ent.name === ".git" || ent.name === ".DS_Store" || ent.name === "node_modules") {
|
|
11152
10900
|
continue;
|
|
@@ -11339,7 +11087,7 @@ async function runSkillList(opts) {
|
|
|
11339
11087
|
]
|
|
11340
11088
|
);
|
|
11341
11089
|
}
|
|
11342
|
-
function
|
|
11090
|
+
function notYetImplementedAtApi(verb) {
|
|
11343
11091
|
printError(
|
|
11344
11092
|
`\`skill ${verb}\` is not yet implemented at the GraphQL API.
|
|
11345
11093
|
The current schema exposes skillCatalog (read), per-computer enableSkill/disableSkill,
|
|
@@ -11355,11 +11103,11 @@ function registerSkillCommand(program2) {
|
|
|
11355
11103
|
);
|
|
11356
11104
|
skill.command("catalog").description("Browse the skill catalog. Client-side filters --search and --tag are applied locally.").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--search <q>", "Filter by keyword").option("--tag <t>", "Filter by category").action(runSkillCatalog);
|
|
11357
11105
|
skill.command("list").alias("ls").description("List skills available to the tenant.").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--custom-only", "Only show tenant-owned custom skills (source=tenant)").action(runSkillList);
|
|
11358
|
-
skill.command("install <slug>").description("Install a public skill. (API surface pending \u2014 toggle per-agent via `agent skills set`.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--version <v>", "Pin to a specific version").action(() =>
|
|
11359
|
-
skill.command("upgrade <slug>").description("Upgrade an installed skill. (API surface pending.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").action(() =>
|
|
11360
|
-
skill.command("create [slug]").description("Publish a custom tenant-scoped skill. (Use `skill push <folder>` for now.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--name <n>").option("--description <text>").option("--manifest-file <path>", "Path to the MCP server manifest JSON").option("--endpoint <url>", "MCP server HTTP/SSE endpoint").action(() =>
|
|
11361
|
-
skill.command("update <slug>").description("Update a custom skill. (API surface pending.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--name <n>").option("--description <text>").option("--manifest-file <path>").option("--endpoint <url>").action(() =>
|
|
11362
|
-
skill.command("delete <slug>").description("Delete a custom skill. (API surface pending.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("-y, --yes", "Skip confirmation").action(() =>
|
|
11106
|
+
skill.command("install <slug>").description("Install a public skill. (API surface pending \u2014 toggle per-agent via `agent skills set`.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--version <v>", "Pin to a specific version").action(() => notYetImplementedAtApi("install"));
|
|
11107
|
+
skill.command("upgrade <slug>").description("Upgrade an installed skill. (API surface pending.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").action(() => notYetImplementedAtApi("upgrade"));
|
|
11108
|
+
skill.command("create [slug]").description("Publish a custom tenant-scoped skill. (Use `skill push <folder>` for now.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--name <n>").option("--description <text>").option("--manifest-file <path>", "Path to the MCP server manifest JSON").option("--endpoint <url>", "MCP server HTTP/SSE endpoint").action(() => notYetImplementedAtApi("create"));
|
|
11109
|
+
skill.command("update <slug>").description("Update a custom skill. (API surface pending.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--name <n>").option("--description <text>").option("--manifest-file <path>").option("--endpoint <url>").action(() => notYetImplementedAtApi("update"));
|
|
11110
|
+
skill.command("delete <slug>").description("Delete a custom skill. (API surface pending.)").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("-y, --yes", "Skip confirmation").action(() => notYetImplementedAtApi("delete"));
|
|
11363
11111
|
skill.command("push <folder>").description(
|
|
11364
11112
|
"Zip a local plugin folder and upload it to the tenant as a pending plugin."
|
|
11365
11113
|
).option("-s, --stage <name>", "Deployment stage").option("--region <name>", "AWS region", "us-east-1").addHelpText(
|
|
@@ -12789,7 +12537,7 @@ Examples:
|
|
|
12789
12537
|
|
|
12790
12538
|
// src/commands/eval/run.ts
|
|
12791
12539
|
import { checkbox, confirm as confirm17 } from "@inquirer/prompts";
|
|
12792
|
-
import
|
|
12540
|
+
import ora from "ora";
|
|
12793
12541
|
|
|
12794
12542
|
// src/commands/eval/gql.ts
|
|
12795
12543
|
var EvalRunsDoc = graphql(`
|
|
@@ -13174,7 +12922,7 @@ async function runEvalRun(opts) {
|
|
|
13174
12922
|
}
|
|
13175
12923
|
async function pollUntilTerminal(client, runId, intervalSec, timeoutSec) {
|
|
13176
12924
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
13177
|
-
const spinner = isJsonMode() ? null :
|
|
12925
|
+
const spinner = isJsonMode() ? null : ora({ text: "Waiting for run to complete\u2026" }).start();
|
|
13178
12926
|
try {
|
|
13179
12927
|
while (Date.now() < deadline) {
|
|
13180
12928
|
const data = await gqlQuery(client, EvalRunDoc, { id: runId });
|
|
@@ -13304,13 +13052,13 @@ async function runEvalGet(runId, opts) {
|
|
|
13304
13052
|
}
|
|
13305
13053
|
|
|
13306
13054
|
// src/commands/eval/watch.ts
|
|
13307
|
-
import
|
|
13055
|
+
import ora2 from "ora";
|
|
13308
13056
|
async function runEvalWatch(runId, opts) {
|
|
13309
13057
|
const ctx = await resolveEvalContext(opts);
|
|
13310
13058
|
const intervalSec = Number.parseInt(opts.interval ?? "3", 10);
|
|
13311
13059
|
const timeoutSec = Number.parseInt(opts.timeout ?? "900", 10);
|
|
13312
13060
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
13313
|
-
const spinner = isJsonMode() ? null :
|
|
13061
|
+
const spinner = isJsonMode() ? null : ora2({ text: `Watching run ${runId}\u2026` }).start();
|
|
13314
13062
|
try {
|
|
13315
13063
|
while (Date.now() < deadline) {
|
|
13316
13064
|
const data = await gqlQuery(ctx.client, EvalRunDoc, { id: runId });
|
|
@@ -13515,7 +13263,7 @@ async function runEvalTestCaseGet(id, opts) {
|
|
|
13515
13263
|
}
|
|
13516
13264
|
|
|
13517
13265
|
// src/commands/eval/test-case/create.ts
|
|
13518
|
-
import { readFileSync as
|
|
13266
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
13519
13267
|
import { input as input19, select as select8, checkbox as checkbox2 } from "@inquirer/prompts";
|
|
13520
13268
|
var DEFAULT_EVALUATORS = [
|
|
13521
13269
|
"Builtin.Helpfulness",
|
|
@@ -13588,7 +13336,7 @@ async function runEvalTestCaseCreate(opts) {
|
|
|
13588
13336
|
}
|
|
13589
13337
|
let assertions = null;
|
|
13590
13338
|
if (opts.assertionsFile) {
|
|
13591
|
-
const parsed = JSON.parse(
|
|
13339
|
+
const parsed = JSON.parse(readFileSync4(opts.assertionsFile, "utf8"));
|
|
13592
13340
|
if (!Array.isArray(parsed)) {
|
|
13593
13341
|
printError(`--assertions-file must contain a JSON array.`);
|
|
13594
13342
|
process.exit(1);
|
|
@@ -13619,7 +13367,7 @@ async function runEvalTestCaseCreate(opts) {
|
|
|
13619
13367
|
}
|
|
13620
13368
|
|
|
13621
13369
|
// src/commands/eval/test-case/update.ts
|
|
13622
|
-
import { readFileSync as
|
|
13370
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
13623
13371
|
async function runEvalTestCaseUpdate(id, opts) {
|
|
13624
13372
|
const ctx = await resolveEvalContext(opts);
|
|
13625
13373
|
const input20 = {};
|
|
@@ -13632,7 +13380,7 @@ async function runEvalTestCaseUpdate(id, opts) {
|
|
|
13632
13380
|
if (opts.tag !== void 0) input20.tags = opts.tag;
|
|
13633
13381
|
if (opts.enabled !== void 0) input20.enabled = opts.enabled;
|
|
13634
13382
|
if (opts.assertionsFile) {
|
|
13635
|
-
const parsed = JSON.parse(
|
|
13383
|
+
const parsed = JSON.parse(readFileSync5(opts.assertionsFile, "utf8"));
|
|
13636
13384
|
if (!Array.isArray(parsed)) {
|
|
13637
13385
|
printError(`--assertions-file must contain a JSON array.`);
|
|
13638
13386
|
process.exit(1);
|
|
@@ -13763,7 +13511,7 @@ Examples:
|
|
|
13763
13511
|
}
|
|
13764
13512
|
|
|
13765
13513
|
// src/commands/wiki/compile.ts
|
|
13766
|
-
import
|
|
13514
|
+
import ora3 from "ora";
|
|
13767
13515
|
|
|
13768
13516
|
// src/commands/wiki/gql.ts
|
|
13769
13517
|
var TenantBySlugDoc2 = graphql(`
|
|
@@ -14033,7 +13781,7 @@ async function runWikiCompile(opts) {
|
|
|
14033
13781
|
const errors = [];
|
|
14034
13782
|
let forbiddenHit = false;
|
|
14035
13783
|
for (const target of targets) {
|
|
14036
|
-
const spinner = isJsonMode() || scope.mode === "all" ? null :
|
|
13784
|
+
const spinner = isJsonMode() || scope.mode === "all" ? null : ora3({
|
|
14037
13785
|
text: `Enqueuing compile for ${target.label}\u2026`,
|
|
14038
13786
|
prefixText: " "
|
|
14039
13787
|
}).start();
|
|
@@ -14131,7 +13879,7 @@ async function runWikiCompile(opts) {
|
|
|
14131
13879
|
}
|
|
14132
13880
|
}
|
|
14133
13881
|
async function watchSingleJob(ctx, target) {
|
|
14134
|
-
const spinner = isJsonMode() ? null :
|
|
13882
|
+
const spinner = isJsonMode() ? null : ora3({
|
|
14135
13883
|
text: `Watching job ${shortJobId(target.jobId)} for ${target.agentLabel}\u2026`,
|
|
14136
13884
|
prefixText: " "
|
|
14137
13885
|
}).start();
|
|
@@ -14180,7 +13928,7 @@ async function watchSingleJob(ctx, target) {
|
|
|
14180
13928
|
|
|
14181
13929
|
// src/commands/wiki/rebuild.ts
|
|
14182
13930
|
import { confirm as confirm20 } from "@inquirer/prompts";
|
|
14183
|
-
import
|
|
13931
|
+
import ora4 from "ora";
|
|
14184
13932
|
async function runWikiRebuild(opts) {
|
|
14185
13933
|
if (opts.all) {
|
|
14186
13934
|
printError(
|
|
@@ -14211,7 +13959,7 @@ async function runWikiRebuild(opts) {
|
|
|
14211
13959
|
process.exit(0);
|
|
14212
13960
|
}
|
|
14213
13961
|
}
|
|
14214
|
-
const resetSpinner = isJsonMode() ? null :
|
|
13962
|
+
const resetSpinner = isJsonMode() ? null : ora4({
|
|
14215
13963
|
text: `Archiving active pages for ${agentLabel}\u2026`,
|
|
14216
13964
|
prefixText: " "
|
|
14217
13965
|
}).start();
|
|
@@ -14248,7 +13996,7 @@ async function runWikiRebuild(opts) {
|
|
|
14248
13996
|
}
|
|
14249
13997
|
process.exit(1);
|
|
14250
13998
|
}
|
|
14251
|
-
const compileSpinner = isJsonMode() ? null :
|
|
13999
|
+
const compileSpinner = isJsonMode() ? null : ora4({
|
|
14252
14000
|
text: `Enqueuing fresh compile for ${agentLabel}\u2026`,
|
|
14253
14001
|
prefixText: " "
|
|
14254
14002
|
}).start();
|
|
@@ -14321,7 +14069,7 @@ async function runWikiRebuild(opts) {
|
|
|
14321
14069
|
}
|
|
14322
14070
|
}
|
|
14323
14071
|
async function watchRebuildJob(ctx, target) {
|
|
14324
|
-
const spinner = isJsonMode() ? null :
|
|
14072
|
+
const spinner = isJsonMode() ? null : ora4({
|
|
14325
14073
|
text: `Watching rebuild job ${shortJobId(target.jobId)}\u2026`,
|
|
14326
14074
|
prefixText: " "
|
|
14327
14075
|
}).start();
|
|
@@ -14357,7 +14105,7 @@ async function watchRebuildJob(ctx, target) {
|
|
|
14357
14105
|
}
|
|
14358
14106
|
|
|
14359
14107
|
// src/commands/wiki/status.ts
|
|
14360
|
-
import
|
|
14108
|
+
import ora5 from "ora";
|
|
14361
14109
|
var DEFAULT_WATCH_INTERVAL_MS = 3e3;
|
|
14362
14110
|
async function runWikiStatus(opts) {
|
|
14363
14111
|
const ctx = await resolveWikiContext(opts);
|
|
@@ -14409,7 +14157,7 @@ async function runWikiStatus(opts) {
|
|
|
14409
14157
|
process.exit(0);
|
|
14410
14158
|
}
|
|
14411
14159
|
const latestId = jobs[0].id;
|
|
14412
|
-
const spinner = isJsonMode() ? null :
|
|
14160
|
+
const spinner = isJsonMode() ? null : ora5({
|
|
14413
14161
|
text: `Watching job ${shortJobId(latestId)}\u2026`,
|
|
14414
14162
|
prefixText: " "
|
|
14415
14163
|
}).start();
|
|
@@ -14626,14 +14374,14 @@ import { resolve as resolve5 } from "path";
|
|
|
14626
14374
|
|
|
14627
14375
|
// src/commands/enterprise/template.ts
|
|
14628
14376
|
import {
|
|
14629
|
-
existsSync as
|
|
14630
|
-
mkdirSync as
|
|
14631
|
-
readFileSync as
|
|
14632
|
-
readdirSync
|
|
14377
|
+
existsSync as existsSync7,
|
|
14378
|
+
mkdirSync as mkdirSync4,
|
|
14379
|
+
readFileSync as readFileSync6,
|
|
14380
|
+
readdirSync,
|
|
14633
14381
|
statSync as statSync2,
|
|
14634
|
-
writeFileSync as
|
|
14382
|
+
writeFileSync as writeFileSync4
|
|
14635
14383
|
} from "fs";
|
|
14636
|
-
import { dirname as dirname4, join as
|
|
14384
|
+
import { dirname as dirname4, join as join6, relative as relative2, resolve as resolve4 } from "path";
|
|
14637
14385
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14638
14386
|
var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
|
|
14639
14387
|
var MANAGED_MARKER = "thinkwork-managed: enterprise-deploy-template";
|
|
@@ -14649,17 +14397,17 @@ function renderEnterpriseDeployRepoTemplate(options) {
|
|
|
14649
14397
|
stages
|
|
14650
14398
|
});
|
|
14651
14399
|
const templateFiles = listTemplateFiles(templateRoot).filter(
|
|
14652
|
-
(
|
|
14653
|
-
relative2(templateRoot,
|
|
14400
|
+
(path) => !relative2(templateRoot, path).startsWith("terraform/stages/") && !/^terraform\/backend-[^.]+\.hcl$/.test(
|
|
14401
|
+
relative2(templateRoot, path).split("\\").join("/")
|
|
14654
14402
|
)
|
|
14655
14403
|
);
|
|
14656
14404
|
const written = [];
|
|
14657
14405
|
const preserved = [];
|
|
14658
14406
|
for (const templatePath of templateFiles) {
|
|
14659
14407
|
const relativePath = relative2(templateRoot, templatePath);
|
|
14660
|
-
const outputPath =
|
|
14408
|
+
const outputPath = join6(targetDir, relativePath);
|
|
14661
14409
|
let content = applyTemplate(
|
|
14662
|
-
|
|
14410
|
+
readFileSync6(templatePath, "utf8"),
|
|
14663
14411
|
replacements
|
|
14664
14412
|
);
|
|
14665
14413
|
if (relativePath.split("\\").join("/") === "customer/deployment.json") {
|
|
@@ -14667,30 +14415,30 @@ function renderEnterpriseDeployRepoTemplate(options) {
|
|
|
14667
14415
|
}
|
|
14668
14416
|
writeManagedFile(outputPath, content, written, preserved);
|
|
14669
14417
|
}
|
|
14670
|
-
const stageTemplateRoot =
|
|
14671
|
-
const backendTemplatePath =
|
|
14418
|
+
const stageTemplateRoot = join6(templateRoot, "terraform", "stages");
|
|
14419
|
+
const backendTemplatePath = join6(
|
|
14672
14420
|
templateRoot,
|
|
14673
14421
|
"terraform",
|
|
14674
14422
|
"backend-dev.hcl"
|
|
14675
14423
|
);
|
|
14676
14424
|
for (const stage of stages) {
|
|
14677
|
-
const explicitTemplate =
|
|
14678
|
-
const fallbackTemplate =
|
|
14679
|
-
const templatePath =
|
|
14680
|
-
const outputPath =
|
|
14425
|
+
const explicitTemplate = join6(stageTemplateRoot, `${stage}.tfvars`);
|
|
14426
|
+
const fallbackTemplate = join6(stageTemplateRoot, "dev.tfvars");
|
|
14427
|
+
const templatePath = existsSync7(explicitTemplate) ? explicitTemplate : fallbackTemplate;
|
|
14428
|
+
const outputPath = join6(
|
|
14681
14429
|
targetDir,
|
|
14682
14430
|
"terraform",
|
|
14683
14431
|
"stages",
|
|
14684
14432
|
`${stage}.tfvars`
|
|
14685
14433
|
);
|
|
14686
|
-
const content = applyTemplate(
|
|
14434
|
+
const content = applyTemplate(readFileSync6(templatePath, "utf8"), {
|
|
14687
14435
|
...replacements,
|
|
14688
14436
|
STAGE: stage
|
|
14689
14437
|
});
|
|
14690
14438
|
writeManagedFile(outputPath, content, written, preserved);
|
|
14691
|
-
const backendPath =
|
|
14439
|
+
const backendPath = join6(targetDir, "terraform", `backend-${stage}.hcl`);
|
|
14692
14440
|
const backendContent = applyTemplate(
|
|
14693
|
-
|
|
14441
|
+
readFileSync6(backendTemplatePath, "utf8"),
|
|
14694
14442
|
{
|
|
14695
14443
|
...replacements,
|
|
14696
14444
|
STAGE: stage
|
|
@@ -14730,7 +14478,7 @@ function findEnterpriseTemplateRoot() {
|
|
|
14730
14478
|
resolve4(__dirname2, "commands/enterprise/templates/deploy-repo")
|
|
14731
14479
|
];
|
|
14732
14480
|
for (const candidate of candidates) {
|
|
14733
|
-
if (
|
|
14481
|
+
if (existsSync7(join6(candidate, "thinkwork.lock"))) return candidate;
|
|
14734
14482
|
}
|
|
14735
14483
|
throw new Error(
|
|
14736
14484
|
"Enterprise deployment repo template not found. The CLI package may be incomplete."
|
|
@@ -14771,13 +14519,13 @@ function renderCustomerDeploymentJson(content, customerSlug, stages) {
|
|
|
14771
14519
|
}
|
|
14772
14520
|
function listTemplateFiles(root) {
|
|
14773
14521
|
const out = [];
|
|
14774
|
-
for (const entry of
|
|
14775
|
-
const
|
|
14776
|
-
const stat = statSync2(
|
|
14522
|
+
for (const entry of readdirSync(root)) {
|
|
14523
|
+
const path = join6(root, entry);
|
|
14524
|
+
const stat = statSync2(path);
|
|
14777
14525
|
if (stat.isDirectory()) {
|
|
14778
|
-
out.push(...listTemplateFiles(
|
|
14526
|
+
out.push(...listTemplateFiles(path));
|
|
14779
14527
|
} else if (stat.isFile()) {
|
|
14780
|
-
out.push(
|
|
14528
|
+
out.push(path);
|
|
14781
14529
|
}
|
|
14782
14530
|
}
|
|
14783
14531
|
return out.sort();
|
|
@@ -14790,24 +14538,24 @@ function applyTemplate(source, replacements) {
|
|
|
14790
14538
|
return replacements[key];
|
|
14791
14539
|
});
|
|
14792
14540
|
}
|
|
14793
|
-
function writeManagedFile(
|
|
14794
|
-
|
|
14795
|
-
if (
|
|
14796
|
-
const current =
|
|
14541
|
+
function writeManagedFile(path, content, written, preserved) {
|
|
14542
|
+
mkdirSync4(dirname4(path), { recursive: true });
|
|
14543
|
+
if (existsSync7(path)) {
|
|
14544
|
+
const current = readFileSync6(path, "utf8");
|
|
14797
14545
|
if (!current.includes(MANAGED_MARKER)) {
|
|
14798
|
-
preserved.push(
|
|
14546
|
+
preserved.push(path);
|
|
14799
14547
|
return;
|
|
14800
14548
|
}
|
|
14801
14549
|
}
|
|
14802
|
-
|
|
14803
|
-
written.push(
|
|
14550
|
+
writeFileSync4(path, content);
|
|
14551
|
+
written.push(path);
|
|
14804
14552
|
}
|
|
14805
14553
|
|
|
14806
14554
|
// src/commands/enterprise/aws-bootstrap.ts
|
|
14807
14555
|
import { execFileSync } from "child_process";
|
|
14808
|
-
import { mkdtempSync, writeFileSync as
|
|
14556
|
+
import { mkdtempSync, writeFileSync as writeFileSync5 } from "fs";
|
|
14809
14557
|
import { tmpdir } from "os";
|
|
14810
|
-
import { join as
|
|
14558
|
+
import { join as join7 } from "path";
|
|
14811
14559
|
function buildEnterpriseAwsBootstrapPlan(config) {
|
|
14812
14560
|
const oidcProviderArn = `arn:aws:iam::${config.accountId}:oidc-provider/token.actions.githubusercontent.com`;
|
|
14813
14561
|
return {
|
|
@@ -15026,9 +14774,9 @@ var AwsCliEnterpriseBootstrapClient = class {
|
|
|
15026
14774
|
message: `Deploy role ${role.roleName} already exists; updated inline deploy policy ${role.deployPolicyName}.`
|
|
15027
14775
|
};
|
|
15028
14776
|
}
|
|
15029
|
-
const dir = mkdtempSync(
|
|
15030
|
-
const trustPath =
|
|
15031
|
-
|
|
14777
|
+
const dir = mkdtempSync(join7(tmpdir(), "thinkwork-enterprise-role-"));
|
|
14778
|
+
const trustPath = join7(dir, "trust.json");
|
|
14779
|
+
writeFileSync5(trustPath, JSON.stringify(role.trustPolicy));
|
|
15032
14780
|
execFileSync("aws", [
|
|
15033
14781
|
"iam",
|
|
15034
14782
|
"create-role",
|
|
@@ -15046,9 +14794,9 @@ var AwsCliEnterpriseBootstrapClient = class {
|
|
|
15046
14794
|
}
|
|
15047
14795
|
};
|
|
15048
14796
|
function putRolePolicy(role) {
|
|
15049
|
-
const dir = mkdtempSync(
|
|
15050
|
-
const policyPath =
|
|
15051
|
-
|
|
14797
|
+
const dir = mkdtempSync(join7(tmpdir(), "thinkwork-enterprise-policy-"));
|
|
14798
|
+
const policyPath = join7(dir, "policy.json");
|
|
14799
|
+
writeFileSync5(policyPath, JSON.stringify(role.deployPolicy));
|
|
15052
14800
|
execFileSync("aws", [
|
|
15053
14801
|
"iam",
|
|
15054
14802
|
"put-role-policy",
|
|
@@ -15506,14 +15254,14 @@ function printBootstrapSummary(result) {
|
|
|
15506
15254
|
import { resolve as resolve6 } from "path";
|
|
15507
15255
|
|
|
15508
15256
|
// src/commands/enterprise/overlay-schema.ts
|
|
15509
|
-
import { existsSync as
|
|
15510
|
-
import { join as
|
|
15257
|
+
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
|
|
15258
|
+
import { join as join8, relative as relative3, sep as sep2 } from "path";
|
|
15511
15259
|
function loadEnterpriseOverlayDefinition(repoRoot) {
|
|
15512
|
-
const
|
|
15513
|
-
if (!
|
|
15514
|
-
throw new Error(`Missing customer overlay definition: ${
|
|
15260
|
+
const path = join8(repoRoot, "customer", "deployment.json");
|
|
15261
|
+
if (!existsSync8(path)) {
|
|
15262
|
+
throw new Error(`Missing customer overlay definition: ${path}`);
|
|
15515
15263
|
}
|
|
15516
|
-
const parsed = JSON.parse(
|
|
15264
|
+
const parsed = JSON.parse(readFileSync7(path, "utf8"));
|
|
15517
15265
|
if (parsed.schemaVersion !== 1) {
|
|
15518
15266
|
throw new Error(
|
|
15519
15267
|
`Unsupported customer/deployment.json schemaVersion ${parsed.schemaVersion}`
|
|
@@ -15545,11 +15293,11 @@ function stageOverlay(definition, stage) {
|
|
|
15545
15293
|
}
|
|
15546
15294
|
function readCustomerEvalPack(repoRoot, packName) {
|
|
15547
15295
|
assertPackName(packName, "eval");
|
|
15548
|
-
const
|
|
15549
|
-
if (!
|
|
15550
|
-
throw new Error(`Eval pack "${packName}" is missing: ${
|
|
15296
|
+
const path = join8(repoRoot, "customer", "evals", `${packName}.json`);
|
|
15297
|
+
if (!existsSync8(path)) {
|
|
15298
|
+
throw new Error(`Eval pack "${packName}" is missing: ${path}`);
|
|
15551
15299
|
}
|
|
15552
|
-
const parsed = JSON.parse(
|
|
15300
|
+
const parsed = JSON.parse(readFileSync7(path, "utf8"));
|
|
15553
15301
|
if (!Array.isArray(parsed)) {
|
|
15554
15302
|
throw new Error(`Eval pack "${packName}" must be a JSON array`);
|
|
15555
15303
|
}
|
|
@@ -15559,27 +15307,27 @@ function readCustomerEvalPack(repoRoot, packName) {
|
|
|
15559
15307
|
}
|
|
15560
15308
|
function readJsonSeedPack(repoRoot, packName) {
|
|
15561
15309
|
assertPackName(packName, "seed");
|
|
15562
|
-
const
|
|
15563
|
-
if (!
|
|
15564
|
-
throw new Error(`Seed pack "${packName}" is missing: ${
|
|
15310
|
+
const path = join8(repoRoot, "customer", "seeds", `${packName}.json`);
|
|
15311
|
+
if (!existsSync8(path)) {
|
|
15312
|
+
throw new Error(`Seed pack "${packName}" is missing: ${path}`);
|
|
15565
15313
|
}
|
|
15566
|
-
return JSON.parse(
|
|
15314
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
15567
15315
|
}
|
|
15568
15316
|
function collectOverlayFiles(repoRoot, family, packName) {
|
|
15569
15317
|
assertPackName(packName, family);
|
|
15570
|
-
const root =
|
|
15571
|
-
if (!
|
|
15318
|
+
const root = join8(repoRoot, "customer", family, packName);
|
|
15319
|
+
if (!existsSync8(root) || !statSync3(root).isDirectory()) {
|
|
15572
15320
|
throw new Error(`Overlay pack "${family}/${packName}" is missing: ${root}`);
|
|
15573
15321
|
}
|
|
15574
15322
|
const files = [];
|
|
15575
|
-
walkFiles(root, (
|
|
15576
|
-
const relativePath = relative3(root,
|
|
15323
|
+
walkFiles(root, (path) => {
|
|
15324
|
+
const relativePath = relative3(root, path).split(sep2).join("/");
|
|
15577
15325
|
if (relativePath === ".DS_Store" || relativePath.endsWith("/.DS_Store")) {
|
|
15578
15326
|
return;
|
|
15579
15327
|
}
|
|
15580
15328
|
files.push({
|
|
15581
15329
|
relativePath,
|
|
15582
|
-
content:
|
|
15330
|
+
content: readFileSync7(path, "utf8")
|
|
15583
15331
|
});
|
|
15584
15332
|
});
|
|
15585
15333
|
if (family === "skills" && !files.some((file) => file.relativePath === "SKILL.md")) {
|
|
@@ -15680,12 +15428,12 @@ function assertPackName(value, label) {
|
|
|
15680
15428
|
}
|
|
15681
15429
|
}
|
|
15682
15430
|
function walkFiles(root, visit) {
|
|
15683
|
-
for (const entry of
|
|
15684
|
-
const
|
|
15431
|
+
for (const entry of readdirSync2(root, { withFileTypes: true })) {
|
|
15432
|
+
const path = join8(root, entry.name);
|
|
15685
15433
|
if (entry.isDirectory()) {
|
|
15686
|
-
walkFiles(
|
|
15434
|
+
walkFiles(path, visit);
|
|
15687
15435
|
} else if (entry.isFile()) {
|
|
15688
|
-
visit(
|
|
15436
|
+
visit(path);
|
|
15689
15437
|
}
|
|
15690
15438
|
}
|
|
15691
15439
|
}
|