@wraps.dev/cli 3.11.1 → 3.12.0
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 +479 -57
- package/dist/cli.js.map +1 -1
- package/dist/console/assets/{index-75ahdKRk.js → index-D_beYvNq.js} +1 -1
- package/dist/console/index.html +1 -1
- package/dist/lambda/agent-enforcer/.bundled +1 -1
- package/dist/lambda/event-processor/.bundled +1 -1
- package/dist/lambda/inbound-processor/.bundled +1 -1
- package/dist/lambda/sms-event-processor/.bundled +1 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -660,6 +660,9 @@ function parsePulumiError(error) {
|
|
|
660
660
|
if (message.includes("stack is currently locked")) {
|
|
661
661
|
return { code: "STACK_LOCKED" };
|
|
662
662
|
}
|
|
663
|
+
if (message.includes("pending operations") || message.includes("pending_operations")) {
|
|
664
|
+
return { code: "PENDING_OPERATIONS" };
|
|
665
|
+
}
|
|
663
666
|
if (/ENOENT[:\s].*\bpulumi\b|spawn pulumi ENOENT/.test(message)) {
|
|
664
667
|
return { code: "NOT_INSTALLED" };
|
|
665
668
|
}
|
|
@@ -955,6 +958,8 @@ function pulumiErrorToWrapsError(code, iamAction, service, resourceName, resourc
|
|
|
955
958
|
);
|
|
956
959
|
case "STACK_LOCKED":
|
|
957
960
|
return errors.stackLocked();
|
|
961
|
+
case "PENDING_OPERATIONS":
|
|
962
|
+
return errors.pendingOperations();
|
|
958
963
|
case "NOT_INSTALLED":
|
|
959
964
|
return errors.pulumiNotInstalled();
|
|
960
965
|
case "SES_PERMISSION_DENIED":
|
|
@@ -1071,7 +1076,7 @@ Saved regions: ${savedRegions.join(", ")}` : "Pass --region or set AWS_REGION.\n
|
|
|
1071
1076
|
pulumiError: (message) => new WrapsError(
|
|
1072
1077
|
`Infrastructure deployment failed: ${message}`,
|
|
1073
1078
|
"PULUMI_ERROR",
|
|
1074
|
-
"
|
|
1079
|
+
"The message above is Pulumi's own output. If it is not self-explanatory, re-run the command \u2014 some failures are transient \u2014 and see the troubleshooting guide if it persists.",
|
|
1075
1080
|
"https://wraps.dev/docs/guides/aws-setup/troubleshooting"
|
|
1076
1081
|
),
|
|
1077
1082
|
noStack: () => new WrapsError(
|
|
@@ -1092,6 +1097,12 @@ Saved regions: ${savedRegions.join(", ")}` : "Pass --region or set AWS_REGION.\n
|
|
|
1092
1097
|
"This happens when a previous deployment was interrupted.\n\nFor local state, run:\n rm -rf ~/.wraps/pulumi/.pulumi/locks\n\nFor S3 state, delete the lock object in your wraps-state-* bucket under .pulumi/locks/\n\nThen try your command again.",
|
|
1093
1098
|
"https://wraps.dev/docs/guides/aws-setup/troubleshooting"
|
|
1094
1099
|
),
|
|
1100
|
+
pendingOperations: () => new WrapsError(
|
|
1101
|
+
"The Pulumi stack has unresolved operations from a previous deployment",
|
|
1102
|
+
"PENDING_OPERATIONS",
|
|
1103
|
+
"This happens when a previous deployment was interrupted (Ctrl-C, a crashed CI job, lost network) before it could record whether its changes finished.\n\nThose operations may have partially completed, so the safe first move is to reconcile state against what actually exists in AWS:\n pulumi refresh\n\nTo inspect what Pulumi thinks is pending before doing anything:\n pulumi stack export\n\nThen try your command again.\n\nIf a deployment is genuinely still running elsewhere, wait for it to finish instead of clearing anything.",
|
|
1104
|
+
"https://wraps.dev/docs/guides/aws-setup/troubleshooting"
|
|
1105
|
+
),
|
|
1095
1106
|
// SMS-specific errors
|
|
1096
1107
|
smsNotConfigured: () => new WrapsError(
|
|
1097
1108
|
"SMS infrastructure not found",
|
|
@@ -32844,6 +32855,7 @@ var init_dynamodb_metrics = __esm({
|
|
|
32844
32855
|
// src/commands/update.ts
|
|
32845
32856
|
var update_exports = {};
|
|
32846
32857
|
__export(update_exports, {
|
|
32858
|
+
installRelease: () => installRelease,
|
|
32847
32859
|
update: () => update
|
|
32848
32860
|
});
|
|
32849
32861
|
import { execFileSync } from "child_process";
|
|
@@ -32852,6 +32864,7 @@ import {
|
|
|
32852
32864
|
createWriteStream,
|
|
32853
32865
|
mkdirSync as mkdirSync3,
|
|
32854
32866
|
mkdtempSync,
|
|
32867
|
+
readdirSync as readdirSync3,
|
|
32855
32868
|
rmSync
|
|
32856
32869
|
} from "fs";
|
|
32857
32870
|
import { homedir as homedir6, tmpdir as tmpdir2 } from "os";
|
|
@@ -32882,6 +32895,15 @@ async function fetchLatestVersion() {
|
|
|
32882
32895
|
}
|
|
32883
32896
|
return null;
|
|
32884
32897
|
}
|
|
32898
|
+
function installRelease(source, installDir) {
|
|
32899
|
+
for (const entry of readdirSync3(source)) {
|
|
32900
|
+
const target = join24(installDir, entry);
|
|
32901
|
+
rmSync(target, { recursive: true, force: true });
|
|
32902
|
+
execFileSync("cp", ["-R", join24(source, entry), target]);
|
|
32903
|
+
}
|
|
32904
|
+
chmodSync(join24(installDir, "bin", "wraps"), 493);
|
|
32905
|
+
chmodSync(join24(installDir, "runtime", "node"), 493);
|
|
32906
|
+
}
|
|
32885
32907
|
function detectPlatformArch() {
|
|
32886
32908
|
const platform3 = process.platform === "darwin" ? "darwin" : "linux";
|
|
32887
32909
|
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
|
@@ -32978,13 +33000,7 @@ async function update(currentVersion) {
|
|
|
32978
33000
|
const extractDir = join24(tmp, "extract");
|
|
32979
33001
|
mkdirSync3(extractDir, { recursive: true });
|
|
32980
33002
|
execFileSync("tar", ["xzf", tarballPath, "-C", extractDir]);
|
|
32981
|
-
|
|
32982
|
-
for (const dir of ["bin", "runtime", "lib"]) {
|
|
32983
|
-
rmSync(join24(INSTALL_DIR, dir), { recursive: true, force: true });
|
|
32984
|
-
execFileSync("cp", ["-R", join24(source, dir), join24(INSTALL_DIR, dir)]);
|
|
32985
|
-
}
|
|
32986
|
-
chmodSync(join24(INSTALL_DIR, "bin", "wraps"), 493);
|
|
32987
|
-
chmodSync(join24(INSTALL_DIR, "runtime", "node"), 493);
|
|
33003
|
+
installRelease(join24(extractDir, "wraps"), INSTALL_DIR);
|
|
32988
33004
|
});
|
|
32989
33005
|
console.log();
|
|
32990
33006
|
progress.succeed(
|
|
@@ -38204,7 +38220,7 @@ Run ${pc17.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
38204
38220
|
`IAM role ${pc17.cyan(roleName)} not found \u2014 will create it using stored externalId`
|
|
38205
38221
|
);
|
|
38206
38222
|
}
|
|
38207
|
-
if (!options.force) {
|
|
38223
|
+
if (!(options.force || options.yes)) {
|
|
38208
38224
|
progress.stop();
|
|
38209
38225
|
const actionLabel = roleExists2 ? "Update" : "Create";
|
|
38210
38226
|
const shouldContinue = await confirm6({
|
|
@@ -38224,6 +38240,8 @@ Run ${pc17.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
38224
38240
|
const smsEnabled = !!smsConfig;
|
|
38225
38241
|
const smsSendingEnabled = smsConfig && smsConfig.sendingEnabled !== false;
|
|
38226
38242
|
const smsEventTracking = smsConfig?.eventTracking;
|
|
38243
|
+
let permissionsUpdated = false;
|
|
38244
|
+
let trustPolicyRepaired = false;
|
|
38227
38245
|
if (!roleExists2 && externalId) {
|
|
38228
38246
|
await progress.execute("Creating IAM role", async () => {
|
|
38229
38247
|
const trustPolicy = {
|
|
@@ -38263,6 +38281,8 @@ Run ${pc17.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
38263
38281
|
})
|
|
38264
38282
|
);
|
|
38265
38283
|
});
|
|
38284
|
+
permissionsUpdated = true;
|
|
38285
|
+
trustPolicyRepaired = true;
|
|
38266
38286
|
} else {
|
|
38267
38287
|
await progress.execute("Updating IAM role permissions", async () => {
|
|
38268
38288
|
const { PutRolePolicyCommand: PutRolePolicyCommand2 } = await import("@aws-sdk/client-iam");
|
|
@@ -38274,6 +38294,7 @@ Run ${pc17.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
38274
38294
|
})
|
|
38275
38295
|
);
|
|
38276
38296
|
});
|
|
38297
|
+
permissionsUpdated = true;
|
|
38277
38298
|
if (externalId) {
|
|
38278
38299
|
const trustPolicy = {
|
|
38279
38300
|
Version: "2012-10-17",
|
|
@@ -38300,10 +38321,19 @@ Run ${pc17.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
38300
38321
|
})
|
|
38301
38322
|
);
|
|
38302
38323
|
});
|
|
38324
|
+
trustPolicyRepaired = true;
|
|
38325
|
+
} else {
|
|
38326
|
+
log15.warn(
|
|
38327
|
+
`Trust policy was ${pc17.bold("not")} touched: no stored externalId found for this control plane.`
|
|
38328
|
+
);
|
|
38329
|
+
console.log(
|
|
38330
|
+
` If ${pc17.cyan(selfhosted ? "wraps selfhost connect" : "wraps platform connect")} still cannot assume ${pc17.cyan(roleName)}, re-run it to repair the trust policy.`
|
|
38331
|
+
);
|
|
38303
38332
|
}
|
|
38304
38333
|
}
|
|
38305
38334
|
progress.stop();
|
|
38306
38335
|
const actionVerb = roleExists2 ? "updated" : "created";
|
|
38336
|
+
const externalIdPresent = !!externalId;
|
|
38307
38337
|
trackCommand("platform:update-role", {
|
|
38308
38338
|
success: true,
|
|
38309
38339
|
duration_ms: Date.now() - startTime,
|
|
@@ -38313,12 +38343,23 @@ Run ${pc17.cyan("wraps email init")} to deploy infrastructure first.
|
|
|
38313
38343
|
jsonSuccess("platform.update-role", {
|
|
38314
38344
|
updated: true,
|
|
38315
38345
|
action: actionVerb,
|
|
38316
|
-
roleName
|
|
38346
|
+
roleName,
|
|
38347
|
+
permissionsUpdated,
|
|
38348
|
+
trustPolicyRepaired,
|
|
38349
|
+
externalIdPresent
|
|
38317
38350
|
});
|
|
38318
38351
|
return;
|
|
38319
38352
|
}
|
|
38320
38353
|
outro10(pc17.green(`\u2713 Platform access role ${actionVerb} successfully`));
|
|
38321
38354
|
console.log(`
|
|
38355
|
+
${pc17.bold("Summary:")}`);
|
|
38356
|
+
console.log(
|
|
38357
|
+
` ${pc17.green("\u2713")} permissions: ${permissionsUpdated ? "updated" : "unchanged"}`
|
|
38358
|
+
);
|
|
38359
|
+
console.log(
|
|
38360
|
+
trustPolicyRepaired ? ` ${pc17.green("\u2713")} trust policy: repaired` : ` ${pc17.yellow("!")} trust policy: unchanged (no stored externalId)`
|
|
38361
|
+
);
|
|
38362
|
+
console.log(`
|
|
38322
38363
|
${pc17.bold("Permissions:")}`);
|
|
38323
38364
|
console.log(`
|
|
38324
38365
|
${pc17.bold(pc17.cyan("Email:"))}`);
|
|
@@ -39338,6 +39379,7 @@ async function wrapsDoctor(options) {
|
|
|
39338
39379
|
|
|
39339
39380
|
// src/commands/email/agent.ts
|
|
39340
39381
|
init_esm_shims();
|
|
39382
|
+
import { promises as dns } from "dns";
|
|
39341
39383
|
import * as clack18 from "@clack/prompts";
|
|
39342
39384
|
import * as pulumi16 from "@pulumi/pulumi";
|
|
39343
39385
|
import pc20 from "picocolors";
|
|
@@ -40770,7 +40812,12 @@ async function createAgentApiClient(tokenFlag) {
|
|
|
40770
40812
|
},
|
|
40771
40813
|
...body ? { body: JSON.stringify(body) } : {}
|
|
40772
40814
|
});
|
|
40773
|
-
return {
|
|
40815
|
+
return {
|
|
40816
|
+
ok: true,
|
|
40817
|
+
get: call("GET"),
|
|
40818
|
+
post: call("POST"),
|
|
40819
|
+
patch: call("PATCH")
|
|
40820
|
+
};
|
|
40774
40821
|
}
|
|
40775
40822
|
async function parseAgentApiError(resp) {
|
|
40776
40823
|
const data = await resp.json().catch(() => ({}));
|
|
@@ -40825,6 +40872,31 @@ var DEFAULT_AGENT_POLICY = {
|
|
|
40825
40872
|
allowedRecipientDomains: []
|
|
40826
40873
|
};
|
|
40827
40874
|
var AGENT_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,51}$/;
|
|
40875
|
+
var MX_LOOKUP_TIMEOUT_MS = 3e3;
|
|
40876
|
+
async function domainAcceptsMail(domain) {
|
|
40877
|
+
let timer;
|
|
40878
|
+
const timedOut = /* @__PURE__ */ Symbol("mx-lookup-timeout");
|
|
40879
|
+
const timeout = new Promise((resolve) => {
|
|
40880
|
+
timer = setTimeout(() => resolve(timedOut), MX_LOOKUP_TIMEOUT_MS);
|
|
40881
|
+
});
|
|
40882
|
+
try {
|
|
40883
|
+
const result = await Promise.race([dns.resolveMx(domain), timeout]);
|
|
40884
|
+
if (result === timedOut) {
|
|
40885
|
+
return null;
|
|
40886
|
+
}
|
|
40887
|
+
return result.length > 0;
|
|
40888
|
+
} catch (error) {
|
|
40889
|
+
const code = error?.code;
|
|
40890
|
+
if (code === "ENODATA" || code === "ENOTFOUND") {
|
|
40891
|
+
return false;
|
|
40892
|
+
}
|
|
40893
|
+
return null;
|
|
40894
|
+
} finally {
|
|
40895
|
+
if (timer) {
|
|
40896
|
+
clearTimeout(timer);
|
|
40897
|
+
}
|
|
40898
|
+
}
|
|
40899
|
+
}
|
|
40828
40900
|
async function agentCreate(options) {
|
|
40829
40901
|
if (!isJsonMode()) {
|
|
40830
40902
|
clack18.intro(pc20.bold("Create Agent Mailbox"));
|
|
@@ -41202,6 +41274,12 @@ ${suggestion}
|
|
|
41202
41274
|
return await resp.json();
|
|
41203
41275
|
});
|
|
41204
41276
|
const syncFailed = sync.syncStatus === "failed";
|
|
41277
|
+
let mailAccepted = null;
|
|
41278
|
+
try {
|
|
41279
|
+
mailAccepted = await domainAcceptsMail(domain);
|
|
41280
|
+
} catch {
|
|
41281
|
+
mailAccepted = null;
|
|
41282
|
+
}
|
|
41205
41283
|
if (isJsonMode()) {
|
|
41206
41284
|
jsonSuccess("email.agent.create", {
|
|
41207
41285
|
id: created.id,
|
|
@@ -41222,7 +41300,11 @@ ${suggestion}
|
|
|
41222
41300
|
// the credential doesn't have to be smuggled through an error payload a
|
|
41223
41301
|
// scripted caller isn't looking at. syncStatus is the field to branch on.
|
|
41224
41302
|
syncStatus: sync.syncStatus ?? "unknown",
|
|
41225
|
-
warning: sync.warning ?? null
|
|
41303
|
+
warning: sync.warning ?? null,
|
|
41304
|
+
// true/false when the apex MX lookup resolved definitively, null when it
|
|
41305
|
+
// could not be determined (timeout, network issue, …) — null must never
|
|
41306
|
+
// be read as "no MX".
|
|
41307
|
+
domainAcceptsMail: mailAccepted
|
|
41226
41308
|
});
|
|
41227
41309
|
return;
|
|
41228
41310
|
}
|
|
@@ -41238,6 +41320,15 @@ ${suggestion}
|
|
|
41238
41320
|
console.log(` ${pc20.dim("Agent ID:")} ${pc20.cyan(created.id)}`);
|
|
41239
41321
|
console.log(` ${pc20.dim("Enforcer:")} ${pc20.cyan(aliasArn)}`);
|
|
41240
41322
|
console.log();
|
|
41323
|
+
if (mailAccepted === false) {
|
|
41324
|
+
clack18.log.warn(
|
|
41325
|
+
`Replies to ${pc20.cyan(emailAddress)} will bounce \u2014 ${pc20.cyan(domain)} has no MX record.`
|
|
41326
|
+
);
|
|
41327
|
+
console.log(
|
|
41328
|
+
` Set ${pc20.cyan("replyTo")} on every send to a mailbox that can receive, or run ${pc20.cyan("wraps email inbound")} to make ${domain} receive mail.`
|
|
41329
|
+
);
|
|
41330
|
+
console.log();
|
|
41331
|
+
}
|
|
41241
41332
|
const mcpEnv = [
|
|
41242
41333
|
`WRAPS_AGENT_ID=${created.id}`,
|
|
41243
41334
|
`WRAPS_AGENT_ENFORCER_ARN=${aliasArn}`,
|
|
@@ -41400,6 +41491,166 @@ async function agentKill(options) {
|
|
|
41400
41491
|
}
|
|
41401
41492
|
clack18.log.success(`Killed ${pc20.cyan(target.emailAddress)}.`);
|
|
41402
41493
|
}
|
|
41494
|
+
function toList(value) {
|
|
41495
|
+
if (value === void 0) {
|
|
41496
|
+
return [];
|
|
41497
|
+
}
|
|
41498
|
+
return Array.isArray(value) ? value.map(String) : [String(value)];
|
|
41499
|
+
}
|
|
41500
|
+
function dedupePreserveOrder(base, additions) {
|
|
41501
|
+
const seen = new Set(base);
|
|
41502
|
+
const result = [...base];
|
|
41503
|
+
for (const item of additions) {
|
|
41504
|
+
if (!seen.has(item)) {
|
|
41505
|
+
seen.add(item);
|
|
41506
|
+
result.push(item);
|
|
41507
|
+
}
|
|
41508
|
+
}
|
|
41509
|
+
return result;
|
|
41510
|
+
}
|
|
41511
|
+
function sameStringArray(a6, b6) {
|
|
41512
|
+
return a6.length === b6.length && a6.every((v, i6) => v === b6[i6]);
|
|
41513
|
+
}
|
|
41514
|
+
async function agentPolicy(options) {
|
|
41515
|
+
const api = await createAgentApiClient(options.token);
|
|
41516
|
+
if (!api.ok) {
|
|
41517
|
+
if (isJsonMode()) {
|
|
41518
|
+
jsonError("email.agent.policy", {
|
|
41519
|
+
code: "NOT_AUTHENTICATED",
|
|
41520
|
+
message: "No API token found.",
|
|
41521
|
+
suggestion: "Run: wraps auth login"
|
|
41522
|
+
});
|
|
41523
|
+
} else {
|
|
41524
|
+
clack18.log.error("No API token found. Run: wraps auth login");
|
|
41525
|
+
}
|
|
41526
|
+
return;
|
|
41527
|
+
}
|
|
41528
|
+
const listResp = await api.get("/v1/agents");
|
|
41529
|
+
if (!listResp.ok) {
|
|
41530
|
+
const message = await parseAgentApiError(listResp);
|
|
41531
|
+
if (isJsonMode()) {
|
|
41532
|
+
jsonError("email.agent.policy", { code: "API_ERROR", message });
|
|
41533
|
+
} else {
|
|
41534
|
+
clack18.log.error(`Failed to list agents: ${message}`);
|
|
41535
|
+
}
|
|
41536
|
+
return;
|
|
41537
|
+
}
|
|
41538
|
+
const { agents } = await listResp.json();
|
|
41539
|
+
const target = options.name ? agents.find((a6) => a6.name === options.name || a6.id === options.name) : void 0;
|
|
41540
|
+
if (!target) {
|
|
41541
|
+
const message = options.name ? `No agent named "${options.name}".` : "Agent name is required.";
|
|
41542
|
+
if (isJsonMode()) {
|
|
41543
|
+
jsonError("email.agent.policy", { code: "NOT_FOUND", message });
|
|
41544
|
+
} else {
|
|
41545
|
+
clack18.log.error(message);
|
|
41546
|
+
}
|
|
41547
|
+
return;
|
|
41548
|
+
}
|
|
41549
|
+
if (target.status === "KILLED") {
|
|
41550
|
+
const message = `Agent "${target.name}" is killed \u2014 its policy can no longer be changed.`;
|
|
41551
|
+
if (isJsonMode()) {
|
|
41552
|
+
jsonError("email.agent.policy", { code: "AGENT_KILLED", message });
|
|
41553
|
+
} else {
|
|
41554
|
+
clack18.log.error(message);
|
|
41555
|
+
}
|
|
41556
|
+
return;
|
|
41557
|
+
}
|
|
41558
|
+
const clearAllowlist = options.clearAllowlist === true;
|
|
41559
|
+
const allowRecipientAdditions = toList(options.allowRecipient);
|
|
41560
|
+
const allowDomainAdditions = toList(options.allowDomain);
|
|
41561
|
+
if (clearAllowlist && (allowRecipientAdditions.length > 0 || allowDomainAdditions.length > 0)) {
|
|
41562
|
+
const message = "--clear-allowlist cannot be combined with --allow-recipient or --allow-domain.";
|
|
41563
|
+
if (isJsonMode()) {
|
|
41564
|
+
jsonError("email.agent.policy", { code: "VALIDATION", message });
|
|
41565
|
+
} else {
|
|
41566
|
+
clack18.log.error(message);
|
|
41567
|
+
}
|
|
41568
|
+
return;
|
|
41569
|
+
}
|
|
41570
|
+
const current = target.policy;
|
|
41571
|
+
const maxPerHour = options.maxPerHour === void 0 ? current.maxPerHour : Number(options.maxPerHour);
|
|
41572
|
+
const maxPerDay = options.maxPerDay === void 0 ? current.maxPerDay : Number(options.maxPerDay);
|
|
41573
|
+
if (!(Number.isInteger(maxPerHour) && maxPerHour >= 0)) {
|
|
41574
|
+
const message = "--max-per-hour must be a whole number >= 0.";
|
|
41575
|
+
if (isJsonMode()) {
|
|
41576
|
+
jsonError("email.agent.policy", { code: "VALIDATION", message });
|
|
41577
|
+
} else {
|
|
41578
|
+
clack18.log.error(message);
|
|
41579
|
+
}
|
|
41580
|
+
return;
|
|
41581
|
+
}
|
|
41582
|
+
if (!(Number.isInteger(maxPerDay) && maxPerDay >= 0)) {
|
|
41583
|
+
const message = "--max-per-day must be a whole number >= 0.";
|
|
41584
|
+
if (isJsonMode()) {
|
|
41585
|
+
jsonError("email.agent.policy", { code: "VALIDATION", message });
|
|
41586
|
+
} else {
|
|
41587
|
+
clack18.log.error(message);
|
|
41588
|
+
}
|
|
41589
|
+
return;
|
|
41590
|
+
}
|
|
41591
|
+
const allowedRecipients = clearAllowlist ? [] : dedupePreserveOrder(current.allowedRecipients, allowRecipientAdditions);
|
|
41592
|
+
const allowedRecipientDomains = clearAllowlist ? [] : dedupePreserveOrder(
|
|
41593
|
+
current.allowedRecipientDomains,
|
|
41594
|
+
allowDomainAdditions
|
|
41595
|
+
);
|
|
41596
|
+
const nextPolicy = {
|
|
41597
|
+
maxPerHour,
|
|
41598
|
+
maxPerDay,
|
|
41599
|
+
allowedRecipients,
|
|
41600
|
+
allowedRecipientDomains
|
|
41601
|
+
};
|
|
41602
|
+
const noChange = nextPolicy.maxPerHour === current.maxPerHour && nextPolicy.maxPerDay === current.maxPerDay && sameStringArray(nextPolicy.allowedRecipients, current.allowedRecipients) && sameStringArray(
|
|
41603
|
+
nextPolicy.allowedRecipientDomains,
|
|
41604
|
+
current.allowedRecipientDomains
|
|
41605
|
+
);
|
|
41606
|
+
if (noChange) {
|
|
41607
|
+
if (isJsonMode()) {
|
|
41608
|
+
jsonSuccess("email.agent.policy", {
|
|
41609
|
+
agent: target,
|
|
41610
|
+
syncStatus: "skipped",
|
|
41611
|
+
noChange: true
|
|
41612
|
+
});
|
|
41613
|
+
} else {
|
|
41614
|
+
clack18.log.info("No changes.");
|
|
41615
|
+
}
|
|
41616
|
+
return;
|
|
41617
|
+
}
|
|
41618
|
+
const resp = await api.patch(`/v1/agents/${target.id}/policy`, nextPolicy);
|
|
41619
|
+
if (!resp.ok) {
|
|
41620
|
+
const message = await parseAgentApiError(resp);
|
|
41621
|
+
if (isJsonMode()) {
|
|
41622
|
+
jsonError("email.agent.policy", { code: "API_ERROR", message });
|
|
41623
|
+
} else {
|
|
41624
|
+
clack18.log.error(`Failed to update policy: ${message}`);
|
|
41625
|
+
}
|
|
41626
|
+
return;
|
|
41627
|
+
}
|
|
41628
|
+
const body = await resp.json();
|
|
41629
|
+
const syncFailed = body.syncStatus === "failed";
|
|
41630
|
+
if (isJsonMode()) {
|
|
41631
|
+
jsonSuccess("email.agent.policy", {
|
|
41632
|
+
agent: body.agent,
|
|
41633
|
+
syncStatus: body.syncStatus,
|
|
41634
|
+
warning: body.warning ?? null
|
|
41635
|
+
});
|
|
41636
|
+
if (syncFailed) {
|
|
41637
|
+
process.exit(1);
|
|
41638
|
+
}
|
|
41639
|
+
return;
|
|
41640
|
+
}
|
|
41641
|
+
const before = `${current.maxPerHour}/hr \xB7 ${current.maxPerDay}/day`;
|
|
41642
|
+
const after = `${body.agent.policy.maxPerHour}/hr \xB7 ${body.agent.policy.maxPerDay}/day`;
|
|
41643
|
+
clack18.log.success(`${before} \u2192 ${after}`);
|
|
41644
|
+
console.log(
|
|
41645
|
+
` Allowlist: ${body.agent.policy.allowedRecipients.length} recipient(s), ${body.agent.policy.allowedRecipientDomains.length} domain(s)`
|
|
41646
|
+
);
|
|
41647
|
+
if (syncFailed) {
|
|
41648
|
+
if (body.warning) {
|
|
41649
|
+
clack18.log.warn(body.warning);
|
|
41650
|
+
}
|
|
41651
|
+
process.exit(1);
|
|
41652
|
+
}
|
|
41653
|
+
}
|
|
41403
41654
|
|
|
41404
41655
|
// src/commands/email/check.ts
|
|
41405
41656
|
init_esm_shims();
|
|
@@ -41408,7 +41659,7 @@ import * as clack19 from "@clack/prompts";
|
|
|
41408
41659
|
|
|
41409
41660
|
// ../email-check/dist/index.js
|
|
41410
41661
|
init_esm_shims();
|
|
41411
|
-
import { promises as
|
|
41662
|
+
import { promises as dns2, Resolver } from "dns";
|
|
41412
41663
|
import { URL as URL2 } from "url";
|
|
41413
41664
|
import * as net from "net";
|
|
41414
41665
|
import * as tls from "tls";
|
|
@@ -41731,7 +41982,7 @@ function createNodeDnsProvider(options = {}) {
|
|
|
41731
41982
|
async function resolveTxt(domain) {
|
|
41732
41983
|
try {
|
|
41733
41984
|
const records = await withTimeout2(
|
|
41734
|
-
|
|
41985
|
+
dns2.resolveTxt(domain),
|
|
41735
41986
|
`TXT lookup for ${domain}`
|
|
41736
41987
|
);
|
|
41737
41988
|
return records;
|
|
@@ -41745,7 +41996,7 @@ function createNodeDnsProvider(options = {}) {
|
|
|
41745
41996
|
async function resolveMx(domain) {
|
|
41746
41997
|
try {
|
|
41747
41998
|
const records = await withTimeout2(
|
|
41748
|
-
|
|
41999
|
+
dns2.resolveMx(domain),
|
|
41749
42000
|
`MX lookup for ${domain}`
|
|
41750
42001
|
);
|
|
41751
42002
|
return records.sort((a6, b6) => a6.priority - b6.priority);
|
|
@@ -41759,7 +42010,7 @@ function createNodeDnsProvider(options = {}) {
|
|
|
41759
42010
|
async function resolveA(domain) {
|
|
41760
42011
|
try {
|
|
41761
42012
|
const records = await withTimeout2(
|
|
41762
|
-
|
|
42013
|
+
dns2.resolve4(domain),
|
|
41763
42014
|
`A lookup for ${domain}`
|
|
41764
42015
|
);
|
|
41765
42016
|
return records;
|
|
@@ -41773,7 +42024,7 @@ function createNodeDnsProvider(options = {}) {
|
|
|
41773
42024
|
async function resolveAaaa(domain) {
|
|
41774
42025
|
try {
|
|
41775
42026
|
const records = await withTimeout2(
|
|
41776
|
-
|
|
42027
|
+
dns2.resolve6(domain),
|
|
41777
42028
|
`AAAA lookup for ${domain}`
|
|
41778
42029
|
);
|
|
41779
42030
|
return records;
|
|
@@ -41787,7 +42038,7 @@ function createNodeDnsProvider(options = {}) {
|
|
|
41787
42038
|
async function resolvePtr(ip) {
|
|
41788
42039
|
try {
|
|
41789
42040
|
const records = await withTimeout2(
|
|
41790
|
-
|
|
42041
|
+
dns2.reverse(ip),
|
|
41791
42042
|
`PTR lookup for ${ip}`
|
|
41792
42043
|
);
|
|
41793
42044
|
return records;
|
|
@@ -41801,7 +42052,7 @@ function createNodeDnsProvider(options = {}) {
|
|
|
41801
42052
|
async function resolveCaa(domain) {
|
|
41802
42053
|
try {
|
|
41803
42054
|
const records = await withTimeout2(
|
|
41804
|
-
|
|
42055
|
+
dns2.resolveCaa(domain),
|
|
41805
42056
|
`CAA lookup for ${domain}`
|
|
41806
42057
|
);
|
|
41807
42058
|
return records.map((r6) => {
|
|
@@ -41833,7 +42084,7 @@ function createNodeDnsProvider(options = {}) {
|
|
|
41833
42084
|
async function resolveCname(domain) {
|
|
41834
42085
|
try {
|
|
41835
42086
|
const records = await withTimeout2(
|
|
41836
|
-
|
|
42087
|
+
dns2.resolveCname(domain),
|
|
41837
42088
|
`CNAME lookup for ${domain}`
|
|
41838
42089
|
);
|
|
41839
42090
|
return records;
|
|
@@ -48939,7 +49190,7 @@ ${pc27.dim(`Distribution ${trackingDistributionId} is disabled; delete it from t
|
|
|
48939
49190
|
// src/commands/email/inbound.ts
|
|
48940
49191
|
init_esm_shims();
|
|
48941
49192
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
48942
|
-
import { promises as
|
|
49193
|
+
import { promises as dns3 } from "dns";
|
|
48943
49194
|
import * as clack27 from "@clack/prompts";
|
|
48944
49195
|
import * as pulumi20 from "@pulumi/pulumi";
|
|
48945
49196
|
import pc28 from "picocolors";
|
|
@@ -49837,7 +50088,7 @@ Enable it: ${pc28.cyan("wraps email inbound init")}
|
|
|
49837
50088
|
`Checking MX record for ${receivingDomain}`,
|
|
49838
50089
|
async () => {
|
|
49839
50090
|
try {
|
|
49840
|
-
const records = await
|
|
50091
|
+
const records = await dns3.resolveMx(receivingDomain);
|
|
49841
50092
|
const hasSES = records.some(
|
|
49842
50093
|
(r6) => r6.exchange.includes("inbound-smtp")
|
|
49843
50094
|
);
|
|
@@ -49866,7 +50117,7 @@ Enable it: ${pc28.cyan("wraps email inbound init")}
|
|
|
49866
50117
|
`Checking SPF record for ${receivingDomain}`,
|
|
49867
50118
|
async () => {
|
|
49868
50119
|
try {
|
|
49869
|
-
const records = await
|
|
50120
|
+
const records = await dns3.resolveTxt(receivingDomain);
|
|
49870
50121
|
const flat = records.map((r6) => r6.join(""));
|
|
49871
50122
|
const spf = flat.find((r6) => r6.startsWith("v=spf1"));
|
|
49872
50123
|
const hasSES = spf?.includes("amazonses.com") ?? false;
|
|
@@ -51863,7 +52114,7 @@ async function emailPlan(options) {
|
|
|
51863
52114
|
init_esm_shims();
|
|
51864
52115
|
init_dist();
|
|
51865
52116
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
51866
|
-
import { promises as
|
|
52117
|
+
import { promises as dns4 } from "dns";
|
|
51867
52118
|
import {
|
|
51868
52119
|
GetParameterCommand,
|
|
51869
52120
|
ParameterNotFound,
|
|
@@ -52486,7 +52737,7 @@ Enable it: ${pc35.cyan("wraps email reply init --domain yourapp.com")}
|
|
|
52486
52737
|
const receiptRuleHasDomain = ruleDomains.includes(`r.mail.${entry.domain}`);
|
|
52487
52738
|
let mxResolves = false;
|
|
52488
52739
|
try {
|
|
52489
|
-
const records = await
|
|
52740
|
+
const records = await dns4.resolveMx(`r.mail.${entry.domain}`);
|
|
52490
52741
|
mxResolves = records.some((r6) => r6.exchange.includes("inbound-smtp"));
|
|
52491
52742
|
} catch (err) {
|
|
52492
52743
|
const code = err?.code;
|
|
@@ -59916,6 +60167,50 @@ async function deployEventBridge(metadata, region, identity, webhookSecret, prog
|
|
|
59916
60167
|
progress.succeed("Event streaming configured");
|
|
59917
60168
|
}
|
|
59918
60169
|
var WRAPS_PLATFORM_ACCOUNT_ID = "905130073023";
|
|
60170
|
+
async function verifyTrustPolicyExternalId(iam13, roleName, externalId, progress) {
|
|
60171
|
+
const readCurrentExternalId = async () => {
|
|
60172
|
+
const roleResult = await iam13.send(
|
|
60173
|
+
new GetRoleCommand2({ RoleName: roleName })
|
|
60174
|
+
);
|
|
60175
|
+
const rawDocument = roleResult.Role?.AssumeRolePolicyDocument;
|
|
60176
|
+
if (!rawDocument) {
|
|
60177
|
+
throw new Error(
|
|
60178
|
+
"GetRole response did not include AssumeRolePolicyDocument"
|
|
60179
|
+
);
|
|
60180
|
+
}
|
|
60181
|
+
const parsed = JSON.parse(decodeURIComponent(rawDocument));
|
|
60182
|
+
return parsed.Statement?.[0]?.Condition?.StringEquals?.["sts:ExternalId"];
|
|
60183
|
+
};
|
|
60184
|
+
const reportUnverifiable = (error) => {
|
|
60185
|
+
const errName = error && typeof error === "object" && "name" in error ? error.name : "Unknown";
|
|
60186
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
60187
|
+
progress.info(
|
|
60188
|
+
`Could not verify the trust policy write (${errName}: ${errMsg}) \u2014 assuming it succeeded.`
|
|
60189
|
+
);
|
|
60190
|
+
};
|
|
60191
|
+
let current;
|
|
60192
|
+
try {
|
|
60193
|
+
current = await readCurrentExternalId();
|
|
60194
|
+
} catch (error) {
|
|
60195
|
+
reportUnverifiable(error);
|
|
60196
|
+
return;
|
|
60197
|
+
}
|
|
60198
|
+
if (current === externalId) {
|
|
60199
|
+
return;
|
|
60200
|
+
}
|
|
60201
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
60202
|
+
try {
|
|
60203
|
+
current = await readCurrentExternalId();
|
|
60204
|
+
} catch (error) {
|
|
60205
|
+
reportUnverifiable(error);
|
|
60206
|
+
return;
|
|
60207
|
+
}
|
|
60208
|
+
if (current !== externalId) {
|
|
60209
|
+
throw new Error(
|
|
60210
|
+
`Trust policy verification failed: role ${roleName} does not carry the expected externalId after the write.`
|
|
60211
|
+
);
|
|
60212
|
+
}
|
|
60213
|
+
}
|
|
59919
60214
|
async function updatePlatformRole(metadata, progress, externalId, selfhosted) {
|
|
59920
60215
|
const roleName = selfhosted ? SELFHOST_CONSOLE_ACCESS_ROLE_NAME : CONSOLE_ACCESS_ROLE_NAME;
|
|
59921
60216
|
const iam13 = new IAMClient4({ region: "us-east-1" });
|
|
@@ -59972,6 +60267,10 @@ async function updatePlatformRole(metadata, progress, externalId, selfhosted) {
|
|
|
59972
60267
|
})
|
|
59973
60268
|
);
|
|
59974
60269
|
});
|
|
60270
|
+
await progress.execute(
|
|
60271
|
+
"Verifying trust policy",
|
|
60272
|
+
() => verifyTrustPolicyExternalId(iam13, roleName, externalId, progress)
|
|
60273
|
+
);
|
|
59975
60274
|
}
|
|
59976
60275
|
progress.succeed("Platform access role updated");
|
|
59977
60276
|
} else if (externalId) {
|
|
@@ -60013,6 +60312,10 @@ async function updatePlatformRole(metadata, progress, externalId, selfhosted) {
|
|
|
60013
60312
|
})
|
|
60014
60313
|
);
|
|
60015
60314
|
});
|
|
60315
|
+
await progress.execute(
|
|
60316
|
+
"Verifying trust policy",
|
|
60317
|
+
() => verifyTrustPolicyExternalId(iam13, roleName, externalId, progress)
|
|
60318
|
+
);
|
|
60016
60319
|
progress.succeed("Platform access role created");
|
|
60017
60320
|
} else {
|
|
60018
60321
|
progress.info(
|
|
@@ -60020,13 +60323,52 @@ async function updatePlatformRole(metadata, progress, externalId, selfhosted) {
|
|
|
60020
60323
|
);
|
|
60021
60324
|
}
|
|
60022
60325
|
}
|
|
60023
|
-
async function resolveOrganization(orgs) {
|
|
60326
|
+
async function resolveOrganization(orgs, options) {
|
|
60024
60327
|
if (!orgs || orgs.length === 0) {
|
|
60025
60328
|
return null;
|
|
60026
60329
|
}
|
|
60330
|
+
if (options.org) {
|
|
60331
|
+
const needle = options.org.toLowerCase();
|
|
60332
|
+
const match = orgs.find((o3) => o3.slug.toLowerCase() === needle) || orgs.find((o3) => o3.id.toLowerCase() === needle);
|
|
60333
|
+
if (match) {
|
|
60334
|
+
return match;
|
|
60335
|
+
}
|
|
60336
|
+
const available = orgs.map((o3) => o3.slug).join(", ");
|
|
60337
|
+
if (isJsonMode()) {
|
|
60338
|
+
jsonError("platform.connect", {
|
|
60339
|
+
code: "ORG_NOT_FOUND",
|
|
60340
|
+
message: `No organization matches "${options.org}".`,
|
|
60341
|
+
suggestion: `Available organizations: ${available}`
|
|
60342
|
+
});
|
|
60343
|
+
} else {
|
|
60344
|
+
log43.error(`No organization matches ${pc48.cyan(options.org)}.`);
|
|
60345
|
+
console.log(`
|
|
60346
|
+
Available organizations: ${available}
|
|
60347
|
+
`);
|
|
60348
|
+
}
|
|
60349
|
+
process.exit(1);
|
|
60350
|
+
}
|
|
60027
60351
|
if (orgs.length === 1) {
|
|
60028
60352
|
return orgs[0];
|
|
60029
60353
|
}
|
|
60354
|
+
if (options.json || options.yes) {
|
|
60355
|
+
const available = orgs.map((o3) => o3.slug).join(", ");
|
|
60356
|
+
if (isJsonMode()) {
|
|
60357
|
+
jsonError("platform.connect", {
|
|
60358
|
+
code: "ORG_AMBIGUOUS",
|
|
60359
|
+
message: "Multiple organizations found \u2014 pass --org <slug> to choose one non-interactively.",
|
|
60360
|
+
suggestion: `Available organizations: ${available}`
|
|
60361
|
+
});
|
|
60362
|
+
} else {
|
|
60363
|
+
log43.error(
|
|
60364
|
+
"Multiple organizations found. Pass --org <slug> to choose one non-interactively."
|
|
60365
|
+
);
|
|
60366
|
+
console.log(`
|
|
60367
|
+
Available organizations: ${available}
|
|
60368
|
+
`);
|
|
60369
|
+
}
|
|
60370
|
+
process.exit(1);
|
|
60371
|
+
}
|
|
60030
60372
|
const selected = await select21({
|
|
60031
60373
|
message: "Which organization should this AWS account connect to?",
|
|
60032
60374
|
options: orgs.map((org) => ({
|
|
@@ -60135,7 +60477,11 @@ Run ${pc48.cyan("wraps selfhost login")} first.
|
|
|
60135
60477
|
token = saasToken;
|
|
60136
60478
|
organizations = (await readAuthConfig())?.auth?.organizations;
|
|
60137
60479
|
}
|
|
60138
|
-
const org = await resolveOrganization(organizations
|
|
60480
|
+
const org = await resolveOrganization(organizations, {
|
|
60481
|
+
org: options.org,
|
|
60482
|
+
json: options.json,
|
|
60483
|
+
yes: options.yes
|
|
60484
|
+
});
|
|
60139
60485
|
if (!org) {
|
|
60140
60486
|
progress.stop();
|
|
60141
60487
|
log43.error(
|
|
@@ -60213,6 +60559,7 @@ You can try the manual flow: ${pc48.cyan("wraps auth logout")} then ${pc48.cyan(
|
|
|
60213
60559
|
process.exit(1);
|
|
60214
60560
|
}
|
|
60215
60561
|
progress.succeed("Connection registered");
|
|
60562
|
+
const connectionRegistered = true;
|
|
60216
60563
|
if (selfhosted) {
|
|
60217
60564
|
metadata.selfhostPlatform = {
|
|
60218
60565
|
externalId: result.externalId,
|
|
@@ -60245,16 +60592,7 @@ You can try the manual flow: ${pc48.cyan("wraps auth logout")} then ${pc48.cyan(
|
|
|
60245
60592
|
}
|
|
60246
60593
|
}
|
|
60247
60594
|
await saveConnectionMetadata(metadata);
|
|
60248
|
-
|
|
60249
|
-
await deployEventBridge(
|
|
60250
|
-
metadata,
|
|
60251
|
-
region,
|
|
60252
|
-
identity,
|
|
60253
|
-
result.webhookSecret,
|
|
60254
|
-
progress,
|
|
60255
|
-
selfhosted ? { url: apiBaseUrl } : void 0
|
|
60256
|
-
);
|
|
60257
|
-
}
|
|
60595
|
+
let roleUpdated = false;
|
|
60258
60596
|
try {
|
|
60259
60597
|
await updatePlatformRole(
|
|
60260
60598
|
metadata,
|
|
@@ -60262,6 +60600,7 @@ You can try the manual flow: ${pc48.cyan("wraps auth logout")} then ${pc48.cyan(
|
|
|
60262
60600
|
result.externalId,
|
|
60263
60601
|
selfhosted
|
|
60264
60602
|
);
|
|
60603
|
+
roleUpdated = true;
|
|
60265
60604
|
} catch (error) {
|
|
60266
60605
|
const errName = error && typeof error === "object" && "name" in error ? error.name : "Unknown";
|
|
60267
60606
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -60271,6 +60610,29 @@ You can try the manual flow: ${pc48.cyan("wraps auth logout")} then ${pc48.cyan(
|
|
|
60271
60610
|
Run ${pc48.cyan("wraps platform update-role")} to retry.`
|
|
60272
60611
|
);
|
|
60273
60612
|
}
|
|
60613
|
+
let eventStreamingOk = null;
|
|
60614
|
+
if (hasEmail && !adopted) {
|
|
60615
|
+
try {
|
|
60616
|
+
await deployEventBridge(
|
|
60617
|
+
metadata,
|
|
60618
|
+
region,
|
|
60619
|
+
identity,
|
|
60620
|
+
result.webhookSecret,
|
|
60621
|
+
progress,
|
|
60622
|
+
selfhosted ? { url: apiBaseUrl } : void 0
|
|
60623
|
+
);
|
|
60624
|
+
eventStreamingOk = true;
|
|
60625
|
+
} catch (error) {
|
|
60626
|
+
eventStreamingOk = false;
|
|
60627
|
+
const errName = error && typeof error === "object" && "name" in error ? error.name : "Unknown";
|
|
60628
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
60629
|
+
log43.warn(
|
|
60630
|
+
`Could not configure event streaming (${errName}): ${errMsg}
|
|
60631
|
+
The connection is registered and the access role is up to date \u2014 the dashboard will work.
|
|
60632
|
+
Only SES event delivery is missing. Run ${pc48.cyan("wraps email config")} to retry.`
|
|
60633
|
+
);
|
|
60634
|
+
}
|
|
60635
|
+
}
|
|
60274
60636
|
await saveConnectionMetadata(metadata);
|
|
60275
60637
|
progress.stop();
|
|
60276
60638
|
if (isJsonMode()) {
|
|
@@ -60281,16 +60643,32 @@ You can try the manual flow: ${pc48.cyan("wraps auth logout")} then ${pc48.cyan(
|
|
|
60281
60643
|
connectionId: result.connectionId,
|
|
60282
60644
|
webhookConnected: !adopted,
|
|
60283
60645
|
selfhosted,
|
|
60284
|
-
adopted
|
|
60646
|
+
adopted,
|
|
60647
|
+
connectionRegistered,
|
|
60648
|
+
roleUpdated,
|
|
60649
|
+
eventStreamingOk
|
|
60285
60650
|
});
|
|
60286
60651
|
} else if (adopted) {
|
|
60287
|
-
|
|
60288
|
-
|
|
60289
|
-
|
|
60290
|
-
|
|
60291
|
-
|
|
60292
|
-
|
|
60293
|
-
|
|
60652
|
+
if (roleUpdated) {
|
|
60653
|
+
outro27(pc48.green("AWS account adopted!"));
|
|
60654
|
+
console.log();
|
|
60655
|
+
console.log(
|
|
60656
|
+
pc48.dim(
|
|
60657
|
+
"Registered this account and repaired the IAM trust policy so the dashboard can reach it."
|
|
60658
|
+
)
|
|
60659
|
+
);
|
|
60660
|
+
} else {
|
|
60661
|
+
outro27(
|
|
60662
|
+
pc48.yellow("AWS account adopted, but the IAM role was NOT repaired.")
|
|
60663
|
+
);
|
|
60664
|
+
console.log();
|
|
60665
|
+
console.log(
|
|
60666
|
+
pc48.dim(
|
|
60667
|
+
"Registered this account, but the dashboard CANNOT reach it yet \u2014 the IAM trust policy was not repaired."
|
|
60668
|
+
)
|
|
60669
|
+
);
|
|
60670
|
+
console.log(` Run ${pc48.cyan("wraps platform update-role")} to retry.`);
|
|
60671
|
+
}
|
|
60294
60672
|
console.log(
|
|
60295
60673
|
pc48.dim(
|
|
60296
60674
|
"No infrastructure was deployed and no event wiring was changed."
|
|
@@ -60299,17 +60677,38 @@ You can try the manual flow: ${pc48.cyan("wraps auth logout")} then ${pc48.cyan(
|
|
|
60299
60677
|
console.log(` Dashboard: ${pc48.cyan(dashboardUrl)}`);
|
|
60300
60678
|
console.log();
|
|
60301
60679
|
} else {
|
|
60680
|
+
const fullySucceeded = roleUpdated && eventStreamingOk !== false;
|
|
60302
60681
|
outro27(
|
|
60303
|
-
pc48.green(
|
|
60682
|
+
fullySucceeded ? pc48.green(
|
|
60304
60683
|
selfhosted ? "Self-hosted connection complete!" : "Platform connection complete!"
|
|
60684
|
+
) : pc48.yellow(
|
|
60685
|
+
selfhosted ? "Self-hosted connection registered, with issues." : "Platform connection registered, with issues."
|
|
60305
60686
|
)
|
|
60306
60687
|
);
|
|
60307
60688
|
console.log();
|
|
60308
60689
|
console.log(
|
|
60309
|
-
pc48.
|
|
60310
|
-
"Events from your AWS infrastructure will stream to the dashboard."
|
|
60311
|
-
)
|
|
60690
|
+
` ${roleUpdated ? pc48.green("\u2713") : pc48.red("\u2717")} Access role: ${roleUpdated ? "up to date" : "NOT updated \u2014 dashboard cannot reach this account"}`
|
|
60312
60691
|
);
|
|
60692
|
+
if (eventStreamingOk !== null) {
|
|
60693
|
+
console.log(
|
|
60694
|
+
` ${eventStreamingOk ? pc48.green("\u2713") : pc48.yellow("!")} Event streaming: ${eventStreamingOk ? "configured" : "not configured"}`
|
|
60695
|
+
);
|
|
60696
|
+
}
|
|
60697
|
+
if (!roleUpdated) {
|
|
60698
|
+
console.log(` Run ${pc48.cyan("wraps platform update-role")} to retry.`);
|
|
60699
|
+
}
|
|
60700
|
+
if (eventStreamingOk === false) {
|
|
60701
|
+
console.log(
|
|
60702
|
+
` Run ${pc48.cyan("wraps email config")} to retry event streaming.`
|
|
60703
|
+
);
|
|
60704
|
+
}
|
|
60705
|
+
if (fullySucceeded) {
|
|
60706
|
+
console.log(
|
|
60707
|
+
pc48.dim(
|
|
60708
|
+
"Events from your AWS infrastructure will stream to the dashboard."
|
|
60709
|
+
)
|
|
60710
|
+
);
|
|
60711
|
+
}
|
|
60313
60712
|
console.log(` Dashboard: ${pc48.cyan(dashboardUrl)}`);
|
|
60314
60713
|
console.log();
|
|
60315
60714
|
}
|
|
@@ -60319,6 +60718,9 @@ You can try the manual flow: ${pc48.cyan("wraps auth logout")} then ${pc48.cyan(
|
|
|
60319
60718
|
duration_ms: duration,
|
|
60320
60719
|
authenticated: true
|
|
60321
60720
|
});
|
|
60721
|
+
if (!roleUpdated) {
|
|
60722
|
+
process.exit(1);
|
|
60723
|
+
}
|
|
60322
60724
|
} catch (error) {
|
|
60323
60725
|
progress.stop();
|
|
60324
60726
|
const duration = Date.now() - startTime;
|
|
@@ -63413,7 +63815,7 @@ function createMetricsRouter(config2) {
|
|
|
63413
63815
|
init_esm_shims();
|
|
63414
63816
|
init_config_set_slug();
|
|
63415
63817
|
init_metadata();
|
|
63416
|
-
import
|
|
63818
|
+
import dns5 from "dns/promises";
|
|
63417
63819
|
import { Router as createRouter6 } from "express";
|
|
63418
63820
|
|
|
63419
63821
|
// src/console/services/settings-service.ts
|
|
@@ -63577,7 +63979,7 @@ function createSettingsRouter(config2) {
|
|
|
63577
63979
|
}
|
|
63578
63980
|
console.log(`[Verify] Checking CNAME for: ${domain}`);
|
|
63579
63981
|
console.log(`[Verify] Expected target: ${expectedTarget}`);
|
|
63580
|
-
const records = await
|
|
63982
|
+
const records = await dns5.resolveCname(domain);
|
|
63581
63983
|
console.log("[Verify] CNAME records found:", records);
|
|
63582
63984
|
const verified = records.some(
|
|
63583
63985
|
(record) => record.toLowerCase().includes(expectedTarget.toLowerCase())
|
|
@@ -63610,7 +64012,7 @@ function createSettingsRouter(config2) {
|
|
|
63610
64012
|
}
|
|
63611
64013
|
const dmarcDomain = `_dmarc.${domain}`;
|
|
63612
64014
|
console.log(`[Verify] Checking DMARC for: ${dmarcDomain}`);
|
|
63613
|
-
const records = await
|
|
64015
|
+
const records = await dns5.resolveTxt(dmarcDomain);
|
|
63614
64016
|
console.log("[Verify] TXT records found:", records);
|
|
63615
64017
|
const hasDmarc = records.some((record) => {
|
|
63616
64018
|
const value = record.join("");
|
|
@@ -69282,7 +69684,7 @@ async function support() {
|
|
|
69282
69684
|
console.log();
|
|
69283
69685
|
console.log(` ${pc68.bold("Email:")} ${pc68.cyan("hey@wraps.dev")}`);
|
|
69284
69686
|
console.log(
|
|
69285
|
-
` ${pc68.bold("GitHub:")} ${pc68.cyan("https://github.com/wraps-
|
|
69687
|
+
` ${pc68.bold("GitHub:")} ${pc68.cyan("https://github.com/wraps-team/wraps/issues")}`
|
|
69286
69688
|
);
|
|
69287
69689
|
console.log(` ${pc68.bold("Docs:")} ${pc68.cyan("https://wraps.dev/docs")}`);
|
|
69288
69690
|
console.log(
|
|
@@ -69818,7 +70220,10 @@ function showHelp() {
|
|
|
69818
70220
|
);
|
|
69819
70221
|
console.log(` ${pc72.cyan("email agent list")} List agents`);
|
|
69820
70222
|
console.log(
|
|
69821
|
-
` ${pc72.cyan("email agent kill")} Kill an agent (revoke sending)
|
|
70223
|
+
` ${pc72.cyan("email agent kill")} Kill an agent (revoke sending)`
|
|
70224
|
+
);
|
|
70225
|
+
console.log(
|
|
70226
|
+
` ${pc72.cyan("email agent policy")} Change an agent's caps or allowlist
|
|
69822
70227
|
`
|
|
69823
70228
|
);
|
|
69824
70229
|
console.log("Template Commands:");
|
|
@@ -70377,11 +70782,24 @@ async function run() {
|
|
|
70377
70782
|
json: flags.json
|
|
70378
70783
|
});
|
|
70379
70784
|
break;
|
|
70785
|
+
case "policy":
|
|
70786
|
+
await agentPolicy({
|
|
70787
|
+
// baseline:allow-no-region — API-backed like agentList/agentKill, not direct AWS
|
|
70788
|
+
name: sub[3] || flags.name,
|
|
70789
|
+
maxPerHour: flags["max-per-hour"],
|
|
70790
|
+
maxPerDay: flags["max-per-day"],
|
|
70791
|
+
allowRecipient: flags["allow-recipient"],
|
|
70792
|
+
allowDomain: flags["allow-domain"],
|
|
70793
|
+
clearAllowlist: flags["clear-allowlist"] === true,
|
|
70794
|
+
token: flags.token,
|
|
70795
|
+
json: flags.json
|
|
70796
|
+
});
|
|
70797
|
+
break;
|
|
70380
70798
|
default:
|
|
70381
70799
|
throw errors.unknownCommand(
|
|
70382
70800
|
"agent command",
|
|
70383
70801
|
agentSubCommand,
|
|
70384
|
-
"Available commands: create, list, kill"
|
|
70802
|
+
"Available commands: create, list, kill, policy"
|
|
70385
70803
|
);
|
|
70386
70804
|
}
|
|
70387
70805
|
break;
|
|
@@ -70734,13 +71152,15 @@ async function run() {
|
|
|
70734
71152
|
force: flags.force,
|
|
70735
71153
|
yes: flags.yes,
|
|
70736
71154
|
json: flags.json,
|
|
70737
|
-
selfhosted: true
|
|
71155
|
+
selfhosted: true,
|
|
71156
|
+
org: flags.org
|
|
70738
71157
|
});
|
|
70739
71158
|
break;
|
|
70740
71159
|
case "update-role":
|
|
70741
71160
|
await updateRole({
|
|
70742
71161
|
region: flags.region,
|
|
70743
71162
|
force: flags.force,
|
|
71163
|
+
yes: flags.yes,
|
|
70744
71164
|
json: flags.json,
|
|
70745
71165
|
selfhosted: true
|
|
70746
71166
|
});
|
|
@@ -70946,7 +71366,8 @@ async function run() {
|
|
|
70946
71366
|
region: flags.region,
|
|
70947
71367
|
force: flags.force,
|
|
70948
71368
|
yes: flags.yes,
|
|
70949
|
-
json: flags.json
|
|
71369
|
+
json: flags.json,
|
|
71370
|
+
org: flags.org
|
|
70950
71371
|
});
|
|
70951
71372
|
break;
|
|
70952
71373
|
case "disconnect":
|
|
@@ -70961,6 +71382,7 @@ async function run() {
|
|
|
70961
71382
|
await updateRole({
|
|
70962
71383
|
region: flags.region,
|
|
70963
71384
|
force: flags.force,
|
|
71385
|
+
yes: flags.yes,
|
|
70964
71386
|
json: flags.json
|
|
70965
71387
|
});
|
|
70966
71388
|
break;
|