Migrate from SAM to CDK per Sea Haven conventions
SAM is for simple serverless stacks; this project has ECS, VPC, and multi-service composition which requires CDK. Migration brings ContainerImage.fromAsset() for automatic Docker builds on deploy, matching the seahaven-slack-bot pattern. Also fixes: Bedrock model ID (add version suffix), Gmail history API parameter (labelId not labelIds), classify JSON extraction (handle markdown fences), and digest block limit (cap sections at 5 items to stay under Slack's 50-block limit).
This commit is contained in:
parent
e7da56270a
commit
460cf200c7
15 changed files with 1138 additions and 473 deletions
15
.gitignore
vendored
15
.gitignore
vendored
|
|
@ -1,7 +1,20 @@
|
|||
.aws-sam/
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.env
|
||||
|
||||
# CDK
|
||||
cdk.out/
|
||||
node_modules/
|
||||
dist/
|
||||
*.js
|
||||
*.d.ts
|
||||
!cdk.json
|
||||
|
||||
# SAM (legacy)
|
||||
.aws-sam/
|
||||
samconfig.toml
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
|
|
|||
14
bin/exec-aide.ts
Normal file
14
bin/exec-aide.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env node
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import { ExecAideStack } from '../lib/exec-aide-stack';
|
||||
|
||||
const app = new cdk.App();
|
||||
|
||||
new ExecAideStack(app, 'ExecAideStack', {
|
||||
env: {
|
||||
account: '328440206208',
|
||||
region: 'us-east-1',
|
||||
},
|
||||
stackName: 'exec-aide',
|
||||
description: 'exec-aide - Gmail inbox monitor with AI classification and Slack alerts',
|
||||
});
|
||||
10
cdk.context.json
Normal file
10
cdk.context.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"availability-zones:account=328440206208:region=us-east-1": [
|
||||
"us-east-1a",
|
||||
"us-east-1b",
|
||||
"us-east-1c",
|
||||
"us-east-1d",
|
||||
"us-east-1e",
|
||||
"us-east-1f"
|
||||
]
|
||||
}
|
||||
23
cdk.json
Normal file
23
cdk.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"app": "npx ts-node --prefer-ts-exts bin/exec-aide.ts",
|
||||
"watch": {
|
||||
"include": ["**"],
|
||||
"exclude": [
|
||||
"README.md",
|
||||
"cdk*.json",
|
||||
"**/*.d.ts",
|
||||
"**/*.js",
|
||||
"tsconfig.json",
|
||||
".git",
|
||||
"node_modules",
|
||||
"src",
|
||||
"listener"
|
||||
]
|
||||
},
|
||||
"context": {
|
||||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
|
||||
"@aws-cdk/core:checkSecretUsage": true,
|
||||
"@aws-cdk/aws-iam:minimizePolicies": true,
|
||||
"@aws-cdk/core:enablePartitionLiterals": true
|
||||
}
|
||||
}
|
||||
152
lib/constructs/email-pipeline.ts
Normal file
152
lib/constructs/email-pipeline.ts
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import { Construct } from 'constructs';
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
||||
import * as lambda from 'aws-cdk-lib/aws-lambda';
|
||||
import * as events from 'aws-cdk-lib/aws-events';
|
||||
import * as targets from 'aws-cdk-lib/aws-events-targets';
|
||||
import * as scheduler from 'aws-cdk-lib/aws-scheduler';
|
||||
import * as iam from 'aws-cdk-lib/aws-iam';
|
||||
import * as logs from 'aws-cdk-lib/aws-logs';
|
||||
import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha';
|
||||
import * as path from 'path';
|
||||
|
||||
export class EmailPipelineConstruct extends Construct {
|
||||
public readonly table: dynamodb.Table;
|
||||
|
||||
constructor(scope: Construct, id: string) {
|
||||
super(scope, id);
|
||||
|
||||
const account = cdk.Stack.of(this).account;
|
||||
const region = cdk.Stack.of(this).region;
|
||||
|
||||
// ── DynamoDB ──────────────────────────────────────────────
|
||||
|
||||
this.table = new dynamodb.Table(this, 'Table', {
|
||||
tableName: 'exec-aide',
|
||||
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
||||
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
|
||||
sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
|
||||
timeToLiveAttribute: 'ttl',
|
||||
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
||||
});
|
||||
|
||||
this.table.addGlobalSecondaryIndex({
|
||||
indexName: 'by-date',
|
||||
partitionKey: { name: 'classified_date', type: dynamodb.AttributeType.STRING },
|
||||
sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
|
||||
projectionType: dynamodb.ProjectionType.ALL,
|
||||
});
|
||||
|
||||
// ── Shared environment + IAM ──────────────────────────────
|
||||
|
||||
const lambdaEnv = {
|
||||
TABLE_NAME: this.table.tableName,
|
||||
SECRET_GMAIL: 'exec-aide/gmail-oauth',
|
||||
SECRET_SLACK: 'exec-aide/slack-credentials',
|
||||
SSM_PREFIX: '/exec-aide',
|
||||
};
|
||||
|
||||
const secretsReadPolicy = new iam.PolicyStatement({
|
||||
actions: ['secretsmanager:GetSecretValue'],
|
||||
resources: [
|
||||
`arn:aws:secretsmanager:${region}:${account}:secret:exec-aide/gmail-oauth-*`,
|
||||
`arn:aws:secretsmanager:${region}:${account}:secret:exec-aide/slack-credentials-*`,
|
||||
],
|
||||
});
|
||||
|
||||
const secretsWritePolicy = new iam.PolicyStatement({
|
||||
actions: ['secretsmanager:PutSecretValue'],
|
||||
resources: [
|
||||
`arn:aws:secretsmanager:${region}:${account}:secret:exec-aide/gmail-oauth-*`,
|
||||
],
|
||||
});
|
||||
|
||||
const ssmPolicy = new iam.PolicyStatement({
|
||||
actions: ['ssm:GetParametersByPath', 'ssm:GetParameter'],
|
||||
resources: [
|
||||
`arn:aws:ssm:${region}:${account}:parameter/exec-aide`,
|
||||
`arn:aws:ssm:${region}:${account}:parameter/exec-aide/*`,
|
||||
],
|
||||
});
|
||||
|
||||
// ── Fetch & Classify Lambda ───────────────────────────────
|
||||
|
||||
const fetchClassify = new PythonFunction(this, 'FetchClassify', {
|
||||
functionName: 'exec-aide-fetch-classify',
|
||||
entry: path.join(__dirname, '../../src'),
|
||||
index: 'fetch_classify/app.py',
|
||||
handler: 'lambda_handler',
|
||||
runtime: lambda.Runtime.PYTHON_3_12,
|
||||
architecture: lambda.Architecture.ARM_64,
|
||||
memorySize: 256,
|
||||
timeout: cdk.Duration.seconds(120),
|
||||
environment: lambdaEnv,
|
||||
logRetention: logs.RetentionDays.TWO_MONTHS,
|
||||
});
|
||||
|
||||
this.table.grantReadWriteData(fetchClassify);
|
||||
fetchClassify.addToRolePolicy(secretsReadPolicy);
|
||||
fetchClassify.addToRolePolicy(secretsWritePolicy);
|
||||
fetchClassify.addToRolePolicy(ssmPolicy);
|
||||
fetchClassify.addToRolePolicy(new iam.PolicyStatement({
|
||||
actions: ['bedrock:InvokeModel'],
|
||||
resources: [
|
||||
'arn:aws:bedrock:*::foundation-model/anthropic.*',
|
||||
`arn:aws:bedrock:${region}:${account}:inference-profile/us.anthropic.*`,
|
||||
],
|
||||
}));
|
||||
|
||||
new events.Rule(this, 'PollSchedule', {
|
||||
ruleName: 'exec-aide-fetch-classify-poll',
|
||||
description: 'Poll Gmail for new messages',
|
||||
schedule: events.Schedule.rate(cdk.Duration.minutes(15)),
|
||||
targets: [new targets.LambdaFunction(fetchClassify)],
|
||||
});
|
||||
|
||||
// ── Daily Digest Lambda ───────────────────────────────────
|
||||
|
||||
const dailyDigest = new PythonFunction(this, 'DailyDigest', {
|
||||
functionName: 'exec-aide-daily-digest',
|
||||
entry: path.join(__dirname, '../../src'),
|
||||
index: 'daily_digest/app.py',
|
||||
handler: 'lambda_handler',
|
||||
runtime: lambda.Runtime.PYTHON_3_12,
|
||||
architecture: lambda.Architecture.ARM_64,
|
||||
memorySize: 256,
|
||||
timeout: cdk.Duration.seconds(120),
|
||||
environment: lambdaEnv,
|
||||
logRetention: logs.RetentionDays.TWO_MONTHS,
|
||||
});
|
||||
|
||||
this.table.grantReadWriteData(dailyDigest);
|
||||
dailyDigest.addToRolePolicy(secretsReadPolicy);
|
||||
dailyDigest.addToRolePolicy(secretsWritePolicy);
|
||||
dailyDigest.addToRolePolicy(ssmPolicy);
|
||||
|
||||
// ── EventBridge Scheduler (DST-aware 5 PM ET) ─────────────
|
||||
|
||||
const schedulerRole = new iam.Role(this, 'DigestSchedulerRole', {
|
||||
roleName: 'exec-aide-digest-scheduler',
|
||||
assumedBy: new iam.ServicePrincipal('scheduler.amazonaws.com'),
|
||||
});
|
||||
dailyDigest.grantInvoke(schedulerRole);
|
||||
|
||||
const schedule = new scheduler.CfnSchedule(this, 'DigestSchedule', {
|
||||
name: 'exec-aide-daily-digest',
|
||||
description: 'Daily 5 PM ET inbox digest',
|
||||
scheduleExpression: 'cron(0 17 ? * MON-FRI *)',
|
||||
scheduleExpressionTimezone: 'America/New_York',
|
||||
flexibleTimeWindow: { mode: 'OFF' },
|
||||
state: 'ENABLED',
|
||||
target: {
|
||||
arn: dailyDigest.functionArn,
|
||||
roleArn: schedulerRole.roleArn,
|
||||
},
|
||||
});
|
||||
|
||||
dailyDigest.addPermission('SchedulerInvoke', {
|
||||
principal: new iam.ServicePrincipal('scheduler.amazonaws.com'),
|
||||
sourceArn: `arn:aws:scheduler:${region}:${account}:schedule/default/${schedule.name}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
125
lib/constructs/socket-mode.ts
Normal file
125
lib/constructs/socket-mode.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import { Construct } from 'constructs';
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
||||
import * as ecs from 'aws-cdk-lib/aws-ecs';
|
||||
import * as ecr from 'aws-cdk-lib/aws-ecr';
|
||||
import * as logs from 'aws-cdk-lib/aws-logs';
|
||||
import * as iam from 'aws-cdk-lib/aws-iam';
|
||||
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface SocketModeProps {
|
||||
table: dynamodb.ITable;
|
||||
}
|
||||
|
||||
export class SocketModeConstruct extends Construct {
|
||||
constructor(scope: Construct, id: string, props: SocketModeProps) {
|
||||
super(scope, id);
|
||||
|
||||
const account = cdk.Stack.of(this).account;
|
||||
const region = cdk.Stack.of(this).region;
|
||||
|
||||
// ── VPC ────────────────────────────────────────────────────
|
||||
|
||||
const vpc = new ec2.Vpc(this, 'Vpc', {
|
||||
vpcName: 'exec-aide',
|
||||
ipAddresses: ec2.IpAddresses.cidr('10.30.0.0/16'),
|
||||
maxAzs: 1,
|
||||
natGateways: 0,
|
||||
subnetConfiguration: [
|
||||
{
|
||||
cidrMask: 24,
|
||||
name: 'exec-aide-listener',
|
||||
subnetType: ec2.SubnetType.PUBLIC,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const sg = new ec2.SecurityGroup(this, 'ListenerSG', {
|
||||
vpc,
|
||||
description: 'exec-aide listener - outbound only',
|
||||
allowAllOutbound: true,
|
||||
});
|
||||
|
||||
// ── ECR ────────────────────────────────────────────────────
|
||||
|
||||
new ecr.Repository(this, 'ListenerRepo', {
|
||||
repositoryName: 'exec-aide-listener',
|
||||
imageScanOnPush: true,
|
||||
lifecycleRules: [
|
||||
{
|
||||
maxImageCount: 5,
|
||||
description: 'Keep last 5 images',
|
||||
},
|
||||
],
|
||||
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
||||
});
|
||||
|
||||
// ── ECS Cluster + Task Definition ──────────────────────────
|
||||
|
||||
const cluster = new ecs.Cluster(this, 'Cluster', {
|
||||
clusterName: 'exec-aide',
|
||||
vpc,
|
||||
});
|
||||
|
||||
const taskDef = new ecs.FargateTaskDefinition(this, 'TaskDef', {
|
||||
family: 'exec-aide-listener',
|
||||
cpu: 256,
|
||||
memoryLimitMiB: 512,
|
||||
runtimePlatform: {
|
||||
cpuArchitecture: ecs.CpuArchitecture.ARM64,
|
||||
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
|
||||
},
|
||||
});
|
||||
|
||||
taskDef.addContainer('listener', {
|
||||
image: ecs.ContainerImage.fromAsset(
|
||||
path.join(__dirname, '../../listener'),
|
||||
),
|
||||
essential: true,
|
||||
environment: {
|
||||
TABLE_NAME: props.table.tableName,
|
||||
SECRET_SLACK: 'exec-aide/slack-credentials',
|
||||
SSM_PREFIX: '/exec-aide',
|
||||
},
|
||||
logging: ecs.LogDrivers.awsLogs({
|
||||
streamPrefix: 'listener',
|
||||
logGroup: new logs.LogGroup(this, 'ListenerLogGroup', {
|
||||
logGroupName: '/ecs/exec-aide-listener',
|
||||
retention: logs.RetentionDays.TWO_MONTHS,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// ── Task role IAM ──────────────────────────────────────────
|
||||
|
||||
props.table.grantReadWriteData(taskDef.taskRole);
|
||||
|
||||
taskDef.taskRole.addToPrincipalPolicy(new iam.PolicyStatement({
|
||||
actions: ['secretsmanager:GetSecretValue'],
|
||||
resources: [
|
||||
`arn:aws:secretsmanager:${region}:${account}:secret:exec-aide/slack-credentials-*`,
|
||||
],
|
||||
}));
|
||||
|
||||
taskDef.taskRole.addToPrincipalPolicy(new iam.PolicyStatement({
|
||||
actions: ['ssm:GetParametersByPath', 'ssm:GetParameter'],
|
||||
resources: [
|
||||
`arn:aws:ssm:${region}:${account}:parameter/exec-aide`,
|
||||
`arn:aws:ssm:${region}:${account}:parameter/exec-aide/*`,
|
||||
],
|
||||
}));
|
||||
|
||||
// ── Fargate Service ────────────────────────────────────────
|
||||
|
||||
new ecs.FargateService(this, 'Service', {
|
||||
serviceName: 'exec-aide-listener',
|
||||
cluster,
|
||||
taskDefinition: taskDef,
|
||||
desiredCount: 0,
|
||||
assignPublicIp: true,
|
||||
securityGroups: [sg],
|
||||
vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
|
||||
});
|
||||
}
|
||||
}
|
||||
21
lib/exec-aide-stack.ts
Normal file
21
lib/exec-aide-stack.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import * as cdk from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
import { EmailPipelineConstruct } from './constructs/email-pipeline';
|
||||
import { SocketModeConstruct } from './constructs/socket-mode';
|
||||
|
||||
export class ExecAideStack extends cdk.Stack {
|
||||
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const emailPipeline = new EmailPipelineConstruct(this, 'EmailPipeline');
|
||||
|
||||
new SocketModeConstruct(this, 'SocketMode', {
|
||||
table: emailPipeline.table,
|
||||
});
|
||||
|
||||
new cdk.CfnOutput(this, 'TableName', {
|
||||
value: emailPipeline.table.tableName,
|
||||
description: 'DynamoDB table name',
|
||||
});
|
||||
}
|
||||
}
|
||||
699
package-lock.json
generated
Normal file
699
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,699 @@
|
|||
{
|
||||
"name": "exec-aide",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "exec-aide",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@aws-cdk/aws-lambda-python-alpha": "^2.180.0-alpha.0",
|
||||
"aws-cdk-lib": "^2.180.0",
|
||||
"constructs": "^10.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"aws-cdk": "^2.180.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "~5.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-cdk/asset-awscli-v1": {
|
||||
"version": "2.2.273",
|
||||
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.273.tgz",
|
||||
"integrity": "sha512-X57HYUtHt9BQrlrzUNcMyRsDUCoakYNnY6qh5lNwRCHPtQoTfXmuISkfLk0AjLkcbS5lw1LLTQFiQhTDXfiTvg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@aws-cdk/asset-node-proxy-agent-v6": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.1.tgz",
|
||||
"integrity": "sha512-We4bmHaowOPHr+IQR4/FyTGjRfjgBj4ICMjtqmJeBDWad3Q/6St12NT07leNtyuukv2qMhtSZJQorD8KpKTwRA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@aws-cdk/aws-lambda-python-alpha": {
|
||||
"version": "2.180.0-alpha.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-cdk/aws-lambda-python-alpha/-/aws-lambda-python-alpha-2.180.0-alpha.0.tgz",
|
||||
"integrity": "sha512-OZIViZAIESuoUpZaGFpljI62Ypemncp2SbFxkzSnLjKnCO/Bt1uOEnHFtIFKb5UlDwdFNHHO9h8m6gFmtz8HMA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 14.15.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"aws-cdk-lib": "^2.180.0",
|
||||
"constructs": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-cdk/cloud-assembly-schema": {
|
||||
"version": "53.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-53.20.0.tgz",
|
||||
"integrity": "sha512-4kLAUO+I8b4nlk1Z2P4n3Ye8UtqCiXk0kJMLUThBnyHLbdz06rwAb+qlb9WZOie7NtPluemVS243ifcBh/NVsQ==",
|
||||
"bundleDependencies": [
|
||||
"jsonschema",
|
||||
"semver"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"jsonschema": "~1.4.1",
|
||||
"semver": "^7.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": {
|
||||
"version": "1.4.1",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"inBundle": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node14": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node16": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
|
||||
"integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.5",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
|
||||
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.1120.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1120.0.tgz",
|
||||
"integrity": "sha512-vDVa0IX0FhizARdY/GLSParFglKbdHCIhM8IDmynrAv9w8uLLljzWMeLUOhC1XpMErDZ/npYEihAOjfKxTaMIw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"cdk": "bin/cdk"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib": {
|
||||
"version": "2.252.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.252.0.tgz",
|
||||
"integrity": "sha512-bRLyTtJxhVgsx2JrL2B/KYYWf+Rg0s68UgQp+VRZK0h5fXeaPqDVEJGPMr7FiOgrmYwEadjfbxsTsZKNAloAvg==",
|
||||
"bundleDependencies": [
|
||||
"@balena/dockerignore",
|
||||
"@aws-cdk/cloud-assembly-api",
|
||||
"case",
|
||||
"fs-extra",
|
||||
"ignore",
|
||||
"jsonschema",
|
||||
"minimatch",
|
||||
"punycode",
|
||||
"semver",
|
||||
"table",
|
||||
"yaml",
|
||||
"mime-types"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-cdk/asset-awscli-v1": "2.2.273",
|
||||
"@aws-cdk/asset-node-proxy-agent-v6": "^2.1.1",
|
||||
"@aws-cdk/cloud-assembly-api": "^2.2.2",
|
||||
"@aws-cdk/cloud-assembly-schema": "^53.18.0",
|
||||
"@balena/dockerignore": "^1.0.2",
|
||||
"case": "1.6.3",
|
||||
"fs-extra": "^11.3.3",
|
||||
"ignore": "^5.3.2",
|
||||
"jsonschema": "^1.5.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"minimatch": "^10.2.3",
|
||||
"punycode": "^2.3.1",
|
||||
"semver": "^7.7.4",
|
||||
"table": "^6.9.0",
|
||||
"yaml": "1.10.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"constructs": "^10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": {
|
||||
"version": "2.2.2",
|
||||
"bundleDependencies": [
|
||||
"jsonschema",
|
||||
"semver"
|
||||
],
|
||||
"inBundle": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jsonschema": "~1.4.1",
|
||||
"semver": "^7.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@aws-cdk/cloud-assembly-schema": ">=53.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": {
|
||||
"version": "1.0.2",
|
||||
"inBundle": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/ajv": {
|
||||
"version": "8.18.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/astral-regex": {
|
||||
"version": "2.0.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/balanced-match": {
|
||||
"version": "4.0.4",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/brace-expansion": {
|
||||
"version": "5.0.5",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/case": {
|
||||
"version": "1.6.3",
|
||||
"inBundle": true,
|
||||
"license": "(MIT OR GPL-3.0-or-later)",
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/fast-uri": {
|
||||
"version": "3.1.0",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"inBundle": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/fs-extra": {
|
||||
"version": "11.3.3",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"inBundle": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/jsonfile": {
|
||||
"version": "6.2.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/jsonschema": {
|
||||
"version": "1.5.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/lodash.truncate": {
|
||||
"version": "4.4.2",
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/minimatch": {
|
||||
"version": "10.2.5",
|
||||
"inBundle": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"inBundle": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/slice-ansi": {
|
||||
"version": "4.0.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"astral-regex": "^2.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/table": {
|
||||
"version": "6.9.0",
|
||||
"inBundle": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.1",
|
||||
"lodash.truncate": "^4.4.2",
|
||||
"slice-ansi": "^4.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk-lib/node_modules/yaml": {
|
||||
"version": "1.10.3",
|
||||
"inBundle": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/constructs": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/constructs/-/constructs-10.6.0.tgz",
|
||||
"integrity": "sha512-TxHOnBO5zMo/G76ykzGF/wMpEHu257TbWiIxP9K0Yv/+t70UzgBQiTqjkAsWOPC6jW91DzJI0+ehQV6xDRNBuQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@tsconfig/node16": "^1.0.2",
|
||||
"acorn": "^8.4.1",
|
||||
"acorn-walk": "^8.1.1",
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"v8-compile-cache-lib": "^3.0.1",
|
||||
"yn": "3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-node": "dist/bin.js",
|
||||
"ts-node-cwd": "dist/bin-cwd.js",
|
||||
"ts-node-esm": "dist/bin-esm.js",
|
||||
"ts-node-script": "dist/bin-script.js",
|
||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||
"ts-script": "dist/bin-script-deprecated.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/core": ">=1.2.50",
|
||||
"@swc/wasm": ">=1.2.50",
|
||||
"@types/node": "*",
|
||||
"typescript": ">=2.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@swc/wasm": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
package.json
Normal file
24
package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "exec-aide",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"cdk": "cdk",
|
||||
"deploy": "cdk deploy",
|
||||
"diff": "cdk diff",
|
||||
"synth": "cdk synth"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"aws-cdk": "^2.180.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "~5.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-cdk/aws-lambda-python-alpha": "^2.180.0-alpha.0",
|
||||
"aws-cdk-lib": "^2.180.0",
|
||||
"constructs": "^10.4.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
version = 0.1
|
||||
|
||||
[default.deploy.parameters]
|
||||
stack_name = "exec-aide"
|
||||
resolve_s3 = true
|
||||
s3_prefix = "exec-aide"
|
||||
region = "us-east-1"
|
||||
confirm_changeset = true
|
||||
capabilities = "CAPABILITY_NAMED_IAM"
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
import boto3
|
||||
|
||||
|
|
@ -7,7 +8,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
_bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")
|
||||
|
||||
MODEL_ID = "us.anthropic.claude-haiku-4-5-20251001"
|
||||
MODEL_ID = "us.anthropic.claude-haiku-4-5-20251001-v1:0"
|
||||
|
||||
SYSTEM_PROMPT = """\
|
||||
You are an email triage assistant for Adam Moussa, President of Sea Haven Industries \
|
||||
|
|
@ -54,7 +55,8 @@ def classify_email(message, vip_senders, vip_domains):
|
|||
inferenceConfig={"temperature": 0.0, "maxTokens": 256},
|
||||
)
|
||||
raw = resp["output"]["message"]["content"][0]["text"]
|
||||
result = json.loads(raw)
|
||||
match = re.search(r'\{[^{}]*\}', raw)
|
||||
result = json.loads(match.group()) if match else json.loads(raw)
|
||||
return {
|
||||
"classification": result.get("classification", "NORMAL"),
|
||||
"reason": result.get("reason", ""),
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ def fetch_history(service, history_id):
|
|||
userId="me",
|
||||
startHistoryId=history_id,
|
||||
historyTypes=["messageAdded"],
|
||||
labelIds=["INBOX"],
|
||||
labelId="INBOX",
|
||||
pageToken=page_token,
|
||||
)
|
||||
.execute()
|
||||
|
|
|
|||
|
|
@ -96,78 +96,48 @@ def build_daily_digest(high_items, bypassed, unanswered, normal_count, low_count
|
|||
{"type": "divider"},
|
||||
]
|
||||
|
||||
max_items = 5
|
||||
|
||||
if high_items:
|
||||
blocks.append({
|
||||
"type": "section",
|
||||
"text": {"type": "mrkdwn", "text": f":rotating_light: *High Priority ({len(high_items)})*"},
|
||||
})
|
||||
for item in high_items:
|
||||
lines = [f":rotating_light: *High Priority ({len(high_items)})*"]
|
||||
for item in high_items[:max_items]:
|
||||
time_ago = _format_time_ago(item["internal_date"])
|
||||
gmail_url = f"https://mail.google.com/mail/u/0/#inbox/{item['pk'].replace('MSG#', '')}"
|
||||
blocks.append({
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": (
|
||||
f"*{item['subject']}*\n"
|
||||
f"From: {item.get('from_name') or item['from_email']} · {time_ago}\n"
|
||||
f"_{item.get('classification_reason', '')}_"
|
||||
),
|
||||
},
|
||||
"accessory": {
|
||||
"type": "button",
|
||||
"text": {"type": "plain_text", "text": "Open"},
|
||||
"url": gmail_url,
|
||||
"action_id": f"open_{item['pk']}",
|
||||
},
|
||||
})
|
||||
lines.append(
|
||||
f" · *{item['subject']}* — "
|
||||
f"{item.get('from_name') or item['from_email']} · {time_ago}"
|
||||
)
|
||||
if len(high_items) > max_items:
|
||||
lines.append(f" _...and {len(high_items) - max_items} more_")
|
||||
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": "\n".join(lines)}})
|
||||
blocks.append({"type": "divider"})
|
||||
|
||||
if bypassed:
|
||||
blocks.append({
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": (
|
||||
f":warning: *Bypassed Work Orders ({len(bypassed)})*\n"
|
||||
"_Sent directly to you without CC'ing work-orders@_"
|
||||
),
|
||||
},
|
||||
})
|
||||
for item in bypassed:
|
||||
blocks.append({
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": f" · *{item['subject']}* from {item.get('from_name') or item['from_email']}",
|
||||
},
|
||||
})
|
||||
lines = [
|
||||
f":warning: *Bypassed Work Orders ({len(bypassed)})*",
|
||||
"_Sent directly to you without CC'ing work-orders@_",
|
||||
]
|
||||
for item in bypassed[:max_items]:
|
||||
lines.append(f" · *{item['subject']}* from {item.get('from_name') or item['from_email']}")
|
||||
if len(bypassed) > max_items:
|
||||
lines.append(f" _...and {len(bypassed) - max_items} more_")
|
||||
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": "\n".join(lines)}})
|
||||
blocks.append({"type": "divider"})
|
||||
|
||||
if unanswered:
|
||||
blocks.append({
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": (
|
||||
f":hourglass: *Unanswered Threads ({len(unanswered)})*\n"
|
||||
"_Awaiting your reply for 24h+_"
|
||||
),
|
||||
},
|
||||
})
|
||||
for thread in unanswered:
|
||||
lines = [
|
||||
f":hourglass: *Unanswered Threads ({len(unanswered)})*",
|
||||
"_Awaiting your reply for 24h+_",
|
||||
]
|
||||
for thread in unanswered[:max_items]:
|
||||
hours = _hours_since_iso(thread.get("unanswered_since", ""))
|
||||
blocks.append({
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": (
|
||||
f" · *{thread.get('subject', '(no subject)')}*"
|
||||
f" — last from {thread.get('last_message_from', 'unknown')}"
|
||||
f" · {hours}h waiting"
|
||||
),
|
||||
},
|
||||
})
|
||||
lines.append(
|
||||
f" · *{thread.get('subject', '(no subject)')}*"
|
||||
f" — last from {thread.get('last_message_from', 'unknown')}"
|
||||
f" · {hours}h waiting"
|
||||
)
|
||||
if len(unanswered) > max_items:
|
||||
lines.append(f" _...and {len(unanswered) - max_items} more_")
|
||||
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": "\n".join(lines)}})
|
||||
blocks.append({"type": "divider"})
|
||||
|
||||
blocks.append({
|
||||
|
|
|
|||
397
template.yaml
397
template.yaml
|
|
@ -1,397 +0,0 @@
|
|||
AWSTemplateFormatVersion: '2010-09-09'
|
||||
Transform: AWS::Serverless-2016-10-31
|
||||
Description: >
|
||||
exec-aide — Gmail inbox monitor with AI classification and Slack alerts
|
||||
|
||||
Globals:
|
||||
Function:
|
||||
Runtime: python3.12
|
||||
Architectures:
|
||||
- arm64
|
||||
MemorySize: 256
|
||||
Timeout: 120
|
||||
Environment:
|
||||
Variables:
|
||||
TABLE_NAME: !Ref ExecAideTable
|
||||
SECRET_GMAIL: exec-aide/gmail-oauth
|
||||
SECRET_SLACK: exec-aide/slack-credentials
|
||||
SSM_PREFIX: /exec-aide
|
||||
|
||||
Resources:
|
||||
|
||||
# ── DynamoDB ──────────────────────────────────────────────
|
||||
|
||||
ExecAideTable:
|
||||
Type: AWS::DynamoDB::Table
|
||||
Properties:
|
||||
TableName: exec-aide
|
||||
BillingMode: PAY_PER_REQUEST
|
||||
AttributeDefinitions:
|
||||
- AttributeName: pk
|
||||
AttributeType: S
|
||||
- AttributeName: sk
|
||||
AttributeType: S
|
||||
- AttributeName: classified_date
|
||||
AttributeType: S
|
||||
KeySchema:
|
||||
- AttributeName: pk
|
||||
KeyType: HASH
|
||||
- AttributeName: sk
|
||||
KeyType: RANGE
|
||||
GlobalSecondaryIndexes:
|
||||
- IndexName: by-date
|
||||
KeySchema:
|
||||
- AttributeName: classified_date
|
||||
KeyType: HASH
|
||||
- AttributeName: sk
|
||||
KeyType: RANGE
|
||||
Projection:
|
||||
ProjectionType: ALL
|
||||
TimeToLiveSpecification:
|
||||
AttributeName: ttl
|
||||
Enabled: true
|
||||
|
||||
# ── CloudWatch Log Groups ────────────────────────────────
|
||||
|
||||
FetchClassifyLogGroup:
|
||||
Type: AWS::Logs::LogGroup
|
||||
Properties:
|
||||
LogGroupName: /aws/lambda/exec-aide-fetch-classify
|
||||
RetentionInDays: 60
|
||||
|
||||
DailyDigestLogGroup:
|
||||
Type: AWS::Logs::LogGroup
|
||||
Properties:
|
||||
LogGroupName: /aws/lambda/exec-aide-daily-digest
|
||||
RetentionInDays: 60
|
||||
|
||||
# ── Lambda Functions ─────────────────────────────────────
|
||||
|
||||
FetchClassifyFunction:
|
||||
Type: AWS::Serverless::Function
|
||||
DependsOn: FetchClassifyLogGroup
|
||||
Properties:
|
||||
FunctionName: exec-aide-fetch-classify
|
||||
Handler: fetch_classify.app.lambda_handler
|
||||
CodeUri: src/
|
||||
Events:
|
||||
PollSchedule:
|
||||
Type: Schedule
|
||||
Properties:
|
||||
Schedule: rate(15 minutes)
|
||||
Description: Poll Gmail for new messages
|
||||
Enabled: true
|
||||
Policies:
|
||||
- DynamoDBCrudPolicy:
|
||||
TableName: !Ref ExecAideTable
|
||||
- Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- secretsmanager:GetSecretValue
|
||||
Resource:
|
||||
- !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:exec-aide/gmail-oauth-*
|
||||
- !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:exec-aide/slack-credentials-*
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- secretsmanager:PutSecretValue
|
||||
Resource:
|
||||
- !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:exec-aide/gmail-oauth-*
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- ssm:GetParametersByPath
|
||||
- ssm:GetParameter
|
||||
Resource:
|
||||
- !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/exec-aide/*
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- bedrock:InvokeModel
|
||||
Resource:
|
||||
- arn:aws:bedrock:*::foundation-model/anthropic.*
|
||||
- !Sub arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:inference-profile/us.anthropic.*
|
||||
|
||||
DailyDigestFunction:
|
||||
Type: AWS::Serverless::Function
|
||||
DependsOn: DailyDigestLogGroup
|
||||
Properties:
|
||||
FunctionName: exec-aide-daily-digest
|
||||
Handler: daily_digest.app.lambda_handler
|
||||
CodeUri: src/
|
||||
Policies:
|
||||
- DynamoDBCrudPolicy:
|
||||
TableName: !Ref ExecAideTable
|
||||
- Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- secretsmanager:GetSecretValue
|
||||
Resource:
|
||||
- !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:exec-aide/gmail-oauth-*
|
||||
- !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:exec-aide/slack-credentials-*
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- secretsmanager:PutSecretValue
|
||||
Resource:
|
||||
- !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:exec-aide/gmail-oauth-*
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- ssm:GetParametersByPath
|
||||
- ssm:GetParameter
|
||||
Resource:
|
||||
- !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/exec-aide/*
|
||||
|
||||
# ── ECS / Fargate (Socket Mode listener) ──────────────────
|
||||
|
||||
ListenerLogGroup:
|
||||
Type: AWS::Logs::LogGroup
|
||||
Properties:
|
||||
LogGroupName: /ecs/exec-aide-listener
|
||||
RetentionInDays: 60
|
||||
|
||||
EcsCluster:
|
||||
Type: AWS::ECS::Cluster
|
||||
Properties:
|
||||
ClusterName: exec-aide
|
||||
|
||||
ListenerTaskDefinition:
|
||||
Type: AWS::ECS::TaskDefinition
|
||||
Properties:
|
||||
Family: exec-aide-listener
|
||||
Cpu: "256"
|
||||
Memory: "512"
|
||||
NetworkMode: awsvpc
|
||||
RequiresCompatibilities:
|
||||
- FARGATE
|
||||
RuntimePlatform:
|
||||
CpuArchitecture: ARM64
|
||||
OperatingSystemFamily: LINUX
|
||||
ExecutionRoleArn: !GetAtt ListenerExecutionRole.Arn
|
||||
TaskRoleArn: !GetAtt ListenerTaskRole.Arn
|
||||
ContainerDefinitions:
|
||||
- Name: listener
|
||||
Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/exec-aide-listener:latest
|
||||
Essential: true
|
||||
Environment:
|
||||
- Name: TABLE_NAME
|
||||
Value: !Ref ExecAideTable
|
||||
- Name: SECRET_SLACK
|
||||
Value: exec-aide/slack-credentials
|
||||
- Name: SSM_PREFIX
|
||||
Value: /exec-aide
|
||||
LogConfiguration:
|
||||
LogDriver: awslogs
|
||||
Options:
|
||||
awslogs-group: /ecs/exec-aide-listener
|
||||
awslogs-region: !Ref AWS::Region
|
||||
awslogs-stream-prefix: listener
|
||||
|
||||
ListenerService:
|
||||
Type: AWS::ECS::Service
|
||||
Properties:
|
||||
ServiceName: exec-aide-listener
|
||||
Cluster: !Ref EcsCluster
|
||||
TaskDefinition: !Ref ListenerTaskDefinition
|
||||
DesiredCount: 1
|
||||
LaunchType: FARGATE
|
||||
NetworkConfiguration:
|
||||
AwsvpcConfiguration:
|
||||
AssignPublicIp: ENABLED
|
||||
Subnets:
|
||||
- !Ref ListenerSubnet
|
||||
SecurityGroups:
|
||||
- !Ref ListenerSecurityGroup
|
||||
|
||||
ListenerSubnet:
|
||||
Type: AWS::EC2::Subnet
|
||||
Properties:
|
||||
VpcId: !Ref ListenerVpc
|
||||
CidrBlock: 10.30.0.0/24
|
||||
MapPublicIpOnLaunch: true
|
||||
AvailabilityZone: !Select [0, !GetAZs ""]
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: exec-aide-listener
|
||||
|
||||
ListenerVpc:
|
||||
Type: AWS::EC2::VPC
|
||||
Properties:
|
||||
CidrBlock: 10.30.0.0/16
|
||||
EnableDnsHostnames: true
|
||||
EnableDnsSupport: true
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: exec-aide
|
||||
|
||||
ListenerIgw:
|
||||
Type: AWS::EC2::InternetGateway
|
||||
Properties:
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: exec-aide
|
||||
|
||||
ListenerIgwAttachment:
|
||||
Type: AWS::EC2::VPCGatewayAttachment
|
||||
Properties:
|
||||
VpcId: !Ref ListenerVpc
|
||||
InternetGatewayId: !Ref ListenerIgw
|
||||
|
||||
ListenerRouteTable:
|
||||
Type: AWS::EC2::RouteTable
|
||||
Properties:
|
||||
VpcId: !Ref ListenerVpc
|
||||
|
||||
ListenerRoute:
|
||||
Type: AWS::EC2::Route
|
||||
DependsOn: ListenerIgwAttachment
|
||||
Properties:
|
||||
RouteTableId: !Ref ListenerRouteTable
|
||||
DestinationCidrBlock: 0.0.0.0/0
|
||||
GatewayId: !Ref ListenerIgw
|
||||
|
||||
ListenerSubnetRouteTableAssoc:
|
||||
Type: AWS::EC2::SubnetRouteTableAssociation
|
||||
Properties:
|
||||
SubnetId: !Ref ListenerSubnet
|
||||
RouteTableId: !Ref ListenerRouteTable
|
||||
|
||||
ListenerSecurityGroup:
|
||||
Type: AWS::EC2::SecurityGroup
|
||||
Properties:
|
||||
GroupDescription: exec-aide listener — outbound only
|
||||
VpcId: !Ref ListenerVpc
|
||||
SecurityGroupEgress:
|
||||
- IpProtocol: "-1"
|
||||
CidrIp: 0.0.0.0/0
|
||||
|
||||
ListenerExecutionRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
RoleName: exec-aide-listener-execution
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: ecs-tasks.amazonaws.com
|
||||
Action: sts:AssumeRole
|
||||
ManagedPolicyArns:
|
||||
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
|
||||
|
||||
ListenerTaskRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
RoleName: exec-aide-listener-task
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: ecs-tasks.amazonaws.com
|
||||
Action: sts:AssumeRole
|
||||
Policies:
|
||||
- PolicyName: ExecAideListenerAccess
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- secretsmanager:GetSecretValue
|
||||
Resource:
|
||||
- !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:exec-aide/slack-credentials-*
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- ssm:GetParametersByPath
|
||||
- ssm:GetParameter
|
||||
Resource:
|
||||
- !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/exec-aide/*
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- dynamodb:GetItem
|
||||
- dynamodb:PutItem
|
||||
- dynamodb:UpdateItem
|
||||
- dynamodb:Query
|
||||
Resource:
|
||||
- !GetAtt ExecAideTable.Arn
|
||||
- !Sub ${ExecAideTable.Arn}/index/*
|
||||
|
||||
ListenerEcrRepo:
|
||||
Type: AWS::ECR::Repository
|
||||
Properties:
|
||||
RepositoryName: exec-aide-listener
|
||||
ImageScanningConfiguration:
|
||||
ScanOnPush: true
|
||||
LifecyclePolicy:
|
||||
LifecyclePolicyText: |
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"rulePriority": 1,
|
||||
"description": "Keep last 5 images",
|
||||
"selection": {
|
||||
"tagStatus": "any",
|
||||
"countType": "imageCountMoreThan",
|
||||
"countNumber": 5
|
||||
},
|
||||
"action": { "type": "expire" }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# ── EventBridge Scheduler (daily digest, DST-aware) ──────
|
||||
|
||||
DailyDigestSchedule:
|
||||
Type: AWS::Scheduler::Schedule
|
||||
Properties:
|
||||
Name: exec-aide-daily-digest
|
||||
Description: Daily 5 PM ET inbox digest
|
||||
ScheduleExpression: cron(0 17 ? * MON-FRI *)
|
||||
ScheduleExpressionTimezone: America/New_York
|
||||
FlexibleTimeWindow:
|
||||
Mode: "OFF"
|
||||
State: ENABLED
|
||||
Target:
|
||||
Arn: !GetAtt DailyDigestFunction.Arn
|
||||
RoleArn: !GetAtt DailyDigestSchedulerRole.Arn
|
||||
|
||||
DailyDigestSchedulerRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
RoleName: exec-aide-digest-scheduler
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: scheduler.amazonaws.com
|
||||
Action: sts:AssumeRole
|
||||
Policies:
|
||||
- PolicyName: InvokeLambda
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action: lambda:InvokeFunction
|
||||
Resource: !GetAtt DailyDigestFunction.Arn
|
||||
|
||||
DailyDigestSchedulePermission:
|
||||
Type: AWS::Lambda::Permission
|
||||
Properties:
|
||||
FunctionName: !Ref DailyDigestFunction
|
||||
Action: lambda:InvokeFunction
|
||||
Principal: scheduler.amazonaws.com
|
||||
SourceArn: !GetAtt DailyDigestSchedule.Arn
|
||||
|
||||
Outputs:
|
||||
FetchClassifyFunctionArn:
|
||||
Description: Fetch & classify Lambda ARN
|
||||
Value: !GetAtt FetchClassifyFunction.Arn
|
||||
DailyDigestFunctionArn:
|
||||
Description: Daily digest Lambda ARN
|
||||
Value: !GetAtt DailyDigestFunction.Arn
|
||||
ExecAideTableName:
|
||||
Description: DynamoDB table name
|
||||
Value: !Ref ExecAideTable
|
||||
ListenerEcrRepoUri:
|
||||
Description: ECR repo for the Socket Mode listener
|
||||
Value: !GetAtt ListenerEcrRepo.RepositoryUri
|
||||
EcsClusterName:
|
||||
Description: ECS cluster name
|
||||
Value: !Ref EcsCluster
|
||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020"],
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./",
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "src", "listener", "scripts", "cdk.out"]
|
||||
}
|
||||
Reference in a new issue