infrawise 0.1.2 → 0.2.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.
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IaCDriftAnalyzer = void 0;
4
+ class IaCDriftAnalyzer {
5
+ name = 'IaCDriftAnalyzer';
6
+ iacSchema = null;
7
+ setIaCSchema(schema) {
8
+ this.iacSchema = schema;
9
+ }
10
+ async analyze(graph) {
11
+ const findings = [];
12
+ if (!this.iacSchema)
13
+ return findings;
14
+ const iac = this.iacSchema;
15
+ // ── DynamoDB drift ───────────────────────────────────────────────────────
16
+ const deployedDynamo = new Set(graph.nodes
17
+ .filter((n) => n.type === 'table' && n.databaseType === 'dynamodb')
18
+ .map((n) => n.name));
19
+ const iacDynamo = new Map(iac.dynamoTables.map((t) => [t.name, t.filePath]));
20
+ for (const [name, fp] of iacDynamo) {
21
+ if (!deployedDynamo.has(name)) {
22
+ findings.push({
23
+ severity: 'medium',
24
+ issue: `IaC drift: DynamoDB table "${name}" defined in IaC but not deployed`,
25
+ description: `"${name}" is in ${fp} but not found in AWS. It may be undeployed or deleted manually.`,
26
+ recommendation: 'Run `terraform apply` / deploy your stack, or remove the definition from IaC.',
27
+ metadata: { resourceType: 'dynamodb_table', name, filePath: fp, driftType: 'defined_not_deployed' },
28
+ });
29
+ }
30
+ }
31
+ for (const name of deployedDynamo) {
32
+ if (!iacDynamo.has(name)) {
33
+ findings.push({
34
+ severity: 'medium',
35
+ issue: `IaC drift: DynamoDB table "${name}" deployed but not in IaC`,
36
+ description: `"${name}" exists in AWS DynamoDB but has no IaC definition. It may have been created manually.`,
37
+ recommendation: 'Import the table with `terraform import` or add a CloudFormation resource, then track all future changes through IaC.',
38
+ metadata: { resourceType: 'dynamodb_table', name, driftType: 'deployed_not_defined' },
39
+ });
40
+ }
41
+ }
42
+ // ── Queue drift ───────────────────────────────────────────────────────────
43
+ const deployedQueues = new Set(graph.nodes.filter((n) => n.type === 'queue').map((n) => n.name));
44
+ const iacQueues = new Map(iac.queues.map((q) => [q.name, q.filePath]));
45
+ for (const [name, fp] of iacQueues) {
46
+ if (!deployedQueues.has(name)) {
47
+ findings.push({
48
+ severity: 'medium',
49
+ issue: `IaC drift: SQS queue "${name}" defined in IaC but not deployed`,
50
+ description: `SQS queue "${name}" is defined in ${fp} but not found in the live account.`,
51
+ recommendation: 'Deploy the queue via `terraform apply` or your CFN/CDK stack.',
52
+ metadata: { resourceType: 'sqs_queue', name, filePath: fp, driftType: 'defined_not_deployed' },
53
+ });
54
+ }
55
+ }
56
+ for (const name of deployedQueues) {
57
+ if (!iacQueues.has(name)) {
58
+ findings.push({
59
+ severity: 'low',
60
+ issue: `IaC drift: SQS queue "${name}" deployed but not in IaC`,
61
+ description: `SQS queue "${name}" exists in AWS but is not tracked in IaC. Manual resources can't be audited or reproduced reliably.`,
62
+ recommendation: 'Import or define the queue in IaC to bring it under version control.',
63
+ metadata: { resourceType: 'sqs_queue', name, driftType: 'deployed_not_defined' },
64
+ });
65
+ }
66
+ }
67
+ // ── Lambda drift ──────────────────────────────────────────────────────────
68
+ const deployedLambdas = new Set(graph.nodes.filter((n) => n.type === 'lambda').map((n) => n.name));
69
+ const iacLambdas = new Map(iac.lambdas.map((l) => [l.name, l.filePath]));
70
+ for (const [name, fp] of iacLambdas) {
71
+ if (!deployedLambdas.has(name)) {
72
+ findings.push({
73
+ severity: 'medium',
74
+ issue: `IaC drift: Lambda "${name}" defined in IaC but not deployed`,
75
+ description: `Lambda function "${name}" is defined in ${fp} but not found in the live account.`,
76
+ recommendation: 'Deploy the function via `terraform apply` or your CFN/CDK stack.',
77
+ metadata: { resourceType: 'lambda_function', name, filePath: fp, driftType: 'defined_not_deployed' },
78
+ });
79
+ }
80
+ }
81
+ for (const name of deployedLambdas) {
82
+ if (!iacLambdas.has(name)) {
83
+ findings.push({
84
+ severity: 'low',
85
+ issue: `IaC drift: Lambda "${name}" deployed but not in IaC`,
86
+ description: `Lambda "${name}" exists in AWS but is not tracked in IaC.`,
87
+ recommendation: 'Import the function into IaC or add it as a resource.',
88
+ metadata: { resourceType: 'lambda_function', name, driftType: 'deployed_not_defined' },
89
+ });
90
+ }
91
+ }
92
+ return findings;
93
+ }
94
+ }
95
+ exports.IaCDriftAnalyzer = IaCDriftAnalyzer;
@@ -0,0 +1,319 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runAnalyze = runAnalyze;
40
+ const path = __importStar(require("path"));
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const ora_1 = __importDefault(require("ora"));
43
+ const core_1 = require("../../core");
44
+ const dynamodb_1 = require("../../adapters/dynamodb");
45
+ const postgres_1 = require("../../adapters/postgres");
46
+ const mysql_1 = require("../../adapters/mysql");
47
+ const mongodb_1 = require("../../adapters/mongodb");
48
+ const terraform_1 = require("../../adapters/terraform");
49
+ const aws_1 = require("../../adapters/aws");
50
+ const logs_1 = require("../../adapters/logs");
51
+ const context_1 = require("../../context");
52
+ const graph_1 = require("../../graph");
53
+ const analyzers_1 = require("../../analyzers");
54
+ const utils_1 = require("../utils");
55
+ async function runAnalyze(options = {}) {
56
+ (0, utils_1.printHeader)('Running Analysis');
57
+ let config;
58
+ try {
59
+ config = (0, core_1.loadConfig)(options.config);
60
+ utils_1.log.success('Config loaded', options.config ?? 'infrawise.yaml');
61
+ }
62
+ catch (err) {
63
+ console.error((0, core_1.formatError)(err));
64
+ process.exit(1);
65
+ }
66
+ const repoPath = options.repo ?? process.cwd();
67
+ const awsCfg = { region: config.aws?.region, profile: config.aws?.profile };
68
+ const dynamoMeta = [];
69
+ const postgresMeta = [];
70
+ const mysqlMeta = [];
71
+ const mongoMeta = [];
72
+ const servicesMeta = {};
73
+ // ── DynamoDB ────────────────────────────────────────────────────────────────
74
+ if (config.dynamodb?.enabled === true) {
75
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting DynamoDB tables...'), color: 'cyan' }).start();
76
+ try {
77
+ const result = await (0, dynamodb_1.extractDynamoMetadata)(config);
78
+ dynamoMeta.push(...result);
79
+ spin.succeed(chalk_1.default.green('DynamoDB') + chalk_1.default.dim(` ${result.length} table(s)`));
80
+ }
81
+ catch (err) {
82
+ spin.warn(chalk_1.default.yellow('DynamoDB skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
83
+ }
84
+ }
85
+ // ── PostgreSQL ──────────────────────────────────────────────────────────────
86
+ if (config.postgres?.enabled && config.postgres.connectionString) {
87
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting PostgreSQL schema...'), color: 'cyan' }).start();
88
+ try {
89
+ const result = await (0, postgres_1.extractPostgresMetadata)(config.postgres.connectionString);
90
+ postgresMeta.push(...result);
91
+ spin.succeed(chalk_1.default.green('PostgreSQL') + chalk_1.default.dim(` ${result.length} table(s)`));
92
+ }
93
+ catch (err) {
94
+ spin.warn(chalk_1.default.yellow('PostgreSQL skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
95
+ }
96
+ }
97
+ // ── MySQL ───────────────────────────────────────────────────────────────────
98
+ if (config.mysql?.enabled && config.mysql.connectionString) {
99
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting MySQL schema...'), color: 'cyan' }).start();
100
+ try {
101
+ const result = await (0, mysql_1.extractMySQLMetadata)(config.mysql.connectionString);
102
+ mysqlMeta.push(...result);
103
+ spin.succeed(chalk_1.default.green('MySQL') + chalk_1.default.dim(` ${result.length} table(s)`));
104
+ }
105
+ catch (err) {
106
+ spin.warn(chalk_1.default.yellow('MySQL skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
107
+ }
108
+ }
109
+ // ── MongoDB ─────────────────────────────────────────────────────────────────
110
+ if (config.mongodb?.enabled && config.mongodb.connectionString) {
111
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting MongoDB schema...'), color: 'cyan' }).start();
112
+ try {
113
+ const result = await (0, mongodb_1.extractMongoMetadata)(config.mongodb.connectionString, config.mongodb.databases);
114
+ mongoMeta.push(...result);
115
+ spin.succeed(chalk_1.default.green('MongoDB') + chalk_1.default.dim(` ${result.length} collection(s)`));
116
+ }
117
+ catch (err) {
118
+ spin.warn(chalk_1.default.yellow('MongoDB skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
119
+ }
120
+ }
121
+ // ── SQS ─────────────────────────────────────────────────────────────────────
122
+ if (config.sqs?.enabled === true) {
123
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting SQS queues...'), color: 'cyan' }).start();
124
+ try {
125
+ const result = await (0, aws_1.extractSQSMetadata)(awsCfg);
126
+ servicesMeta.sqs = result;
127
+ spin.succeed(chalk_1.default.green('SQS') + chalk_1.default.dim(` ${result.length} queue(s)`));
128
+ }
129
+ catch (err) {
130
+ spin.warn(chalk_1.default.yellow('SQS skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
131
+ }
132
+ }
133
+ // ── SNS ─────────────────────────────────────────────────────────────────────
134
+ if (config.sns?.enabled === true) {
135
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting SNS topics...'), color: 'cyan' }).start();
136
+ try {
137
+ const result = await (0, aws_1.extractSNSMetadata)(awsCfg);
138
+ servicesMeta.sns = result;
139
+ spin.succeed(chalk_1.default.green('SNS') + chalk_1.default.dim(` ${result.length} topic(s)`));
140
+ }
141
+ catch (err) {
142
+ spin.warn(chalk_1.default.yellow('SNS skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
143
+ }
144
+ }
145
+ // ── SSM Parameter Store ──────────────────────────────────────────────────────
146
+ if (config.ssm?.enabled === true) {
147
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting SSM parameters...'), color: 'cyan' }).start();
148
+ try {
149
+ const result = await (0, aws_1.extractSSMMetadata)({ ...awsCfg, paths: config.ssm?.paths });
150
+ servicesMeta.ssm = result;
151
+ spin.succeed(chalk_1.default.green('SSM') + chalk_1.default.dim(` ${result.length} parameter(s) `) + chalk_1.default.dim('(metadata only, no values)'));
152
+ }
153
+ catch (err) {
154
+ spin.warn(chalk_1.default.yellow('SSM skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
155
+ }
156
+ }
157
+ // ── Secrets Manager ──────────────────────────────────────────────────────────
158
+ if (config.secretsManager?.enabled === true) {
159
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting Secrets Manager metadata...'), color: 'cyan' }).start();
160
+ try {
161
+ const result = await (0, aws_1.extractSecretsMetadata)(awsCfg);
162
+ servicesMeta.secrets = result;
163
+ spin.succeed(chalk_1.default.green('Secrets Manager') + chalk_1.default.dim(` ${result.length} secret(s) `) + chalk_1.default.dim('(names/rotation only, no values)'));
164
+ }
165
+ catch (err) {
166
+ spin.warn(chalk_1.default.yellow('Secrets Manager skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
167
+ }
168
+ }
169
+ // ── Lambda ───────────────────────────────────────────────────────────────────
170
+ if (config.lambda?.enabled === true) {
171
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting Lambda functions...'), color: 'cyan' }).start();
172
+ try {
173
+ const result = await (0, aws_1.extractLambdaMetadata)(awsCfg);
174
+ servicesMeta.lambda = result;
175
+ spin.succeed(chalk_1.default.green('Lambda') + chalk_1.default.dim(` ${result.length} function(s)`));
176
+ }
177
+ catch (err) {
178
+ spin.warn(chalk_1.default.yellow('Lambda skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
179
+ }
180
+ }
181
+ // ── RDS ──────────────────────────────────────────────────────────────────────
182
+ if (config.rds?.enabled === true) {
183
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting RDS instances...'), color: 'cyan' }).start();
184
+ try {
185
+ const result = await (0, aws_1.extractRDSMetadata)(awsCfg);
186
+ servicesMeta.rds = result;
187
+ spin.succeed(chalk_1.default.green('RDS') + chalk_1.default.dim(` ${result.length} instance(s)`));
188
+ }
189
+ catch (err) {
190
+ spin.warn(chalk_1.default.yellow('RDS skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
191
+ }
192
+ }
193
+ // ── CloudWatch Logs ──────────────────────────────────────────────────────────
194
+ if (config.cloudwatchLogs?.enabled) {
195
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Sampling CloudWatch Logs (errors only, max 50 groups)...'), color: 'cyan' }).start();
196
+ try {
197
+ const result = await (0, logs_1.extractLogsSummary)({
198
+ ...awsCfg,
199
+ logGroupPrefixes: config.cloudwatchLogs.logGroupPrefixes,
200
+ windowHours: config.cloudwatchLogs.windowHours,
201
+ });
202
+ servicesMeta.logs = result;
203
+ const errorGroups = result.filter((lg) => lg.errorCount > 0).length;
204
+ spin.succeed(chalk_1.default.green('CloudWatch Logs') + chalk_1.default.dim(` ${result.length} group(s), ${errorGroups} with errors`));
205
+ }
206
+ catch (err) {
207
+ spin.warn(chalk_1.default.yellow('CloudWatch Logs skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
208
+ }
209
+ }
210
+ // ── IaC schema (Terraform / CloudFormation / CDK) ────────────────────────────
211
+ let iacDriftAnalyzer;
212
+ {
213
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Extracting IaC schema (Terraform / CloudFormation / CDK)...'), color: 'cyan' }).start();
214
+ try {
215
+ const iacSchema = await (0, terraform_1.extractIaCSchema)(repoPath);
216
+ const total = iacSchema.dynamoTables.length + iacSchema.rdsInstances.length +
217
+ iacSchema.mongoClusters.length + iacSchema.queues.length + iacSchema.topics.length +
218
+ iacSchema.lambdas.length + iacSchema.buckets.length + iacSchema.parameters.length +
219
+ iacSchema.secrets.length + iacSchema.apiGateways.length;
220
+ iacDriftAnalyzer = new analyzers_1.IaCDriftAnalyzer();
221
+ iacDriftAnalyzer.setIaCSchema(iacSchema);
222
+ spin.succeed(chalk_1.default.green('IaC schema') + chalk_1.default.dim(` ${total} resource(s) across TF/CFN/CDK`));
223
+ }
224
+ catch (err) {
225
+ spin.warn(chalk_1.default.yellow('IaC scan skipped') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
226
+ }
227
+ }
228
+ // ── Repository scan ──────────────────────────────────────────────────────────
229
+ let operations;
230
+ {
231
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim(`Scanning ${path.basename(repoPath)} for service usage...`), color: 'cyan' }).start();
232
+ try {
233
+ operations = await (0, context_1.scanRepository)(repoPath);
234
+ spin.succeed(chalk_1.default.green('Repository scanned') + chalk_1.default.dim(` ${operations.length} service operation(s) found`));
235
+ }
236
+ catch (err) {
237
+ spin.warn(chalk_1.default.yellow('Repository scan failed') + chalk_1.default.dim(` ${err instanceof Error ? err.message : String(err)}`));
238
+ operations = [];
239
+ }
240
+ }
241
+ // ── Build graph ──────────────────────────────────────────────────────────────
242
+ let graph;
243
+ {
244
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Building infrastructure graph...'), color: 'cyan' }).start();
245
+ graph = (0, graph_1.buildGraph)(operations, dynamoMeta, postgresMeta, mysqlMeta, mongoMeta, servicesMeta);
246
+ spin.succeed(chalk_1.default.green('Graph built') + chalk_1.default.dim(` ${graph.nodes.length} nodes, ${graph.edges.length} edges`));
247
+ }
248
+ // ── Run analyzers ────────────────────────────────────────────────────────────
249
+ let findings;
250
+ {
251
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Running analyzers...'), color: 'cyan' }).start();
252
+ const analyzers = [
253
+ ...(config.dynamodb?.enabled === true ? [
254
+ new analyzers_1.FullTableScanAnalyzer(),
255
+ new analyzers_1.MissingGSIAnalyzer(),
256
+ new analyzers_1.HotPartitionAnalyzer(),
257
+ ] : []),
258
+ ...(config.postgres?.enabled ? [
259
+ new analyzers_1.MissingIndexAnalyzer(),
260
+ new analyzers_1.NplusOneAnalyzer(),
261
+ new analyzers_1.LargeSelectAnalyzer(),
262
+ ] : []),
263
+ ...(config.mysql?.enabled ? [
264
+ new analyzers_1.MissingMySQLIndexAnalyzer(),
265
+ new analyzers_1.MySQLFullTableScanAnalyzer(),
266
+ ] : []),
267
+ ...(config.mongodb?.enabled ? [
268
+ new analyzers_1.MissingMongoIndexAnalyzer(),
269
+ new analyzers_1.MongoCollectionScanAnalyzer(),
270
+ ] : []),
271
+ ...(config.sqs?.enabled === true ? [
272
+ new analyzers_1.MissingDLQAnalyzer(),
273
+ new analyzers_1.UnencryptedQueueAnalyzer(),
274
+ new analyzers_1.LargeQueueBacklogAnalyzer(),
275
+ ] : []),
276
+ ...(config.secretsManager?.enabled === true ? [
277
+ new analyzers_1.MissingSecretRotationAnalyzer(),
278
+ ] : []),
279
+ ...(config.cloudwatchLogs?.enabled ? [
280
+ new analyzers_1.MissingLogRetentionAnalyzer(),
281
+ ] : []),
282
+ ...(config.lambda?.enabled === true ? [
283
+ new analyzers_1.LambdaDefaultMemoryAnalyzer(),
284
+ new analyzers_1.LambdaHighTimeoutAnalyzer(),
285
+ ] : []),
286
+ ...(config.rds?.enabled === true ? [
287
+ new analyzers_1.RDSPubliclyAccessibleAnalyzer(),
288
+ new analyzers_1.RDSNoBackupAnalyzer(),
289
+ new analyzers_1.RDSUnencryptedAnalyzer(),
290
+ new analyzers_1.RDSNoDeletionProtectionAnalyzer(),
291
+ new analyzers_1.RDSNoMultiAZAnalyzer(),
292
+ ] : []),
293
+ ...(iacDriftAnalyzer ? [iacDriftAnalyzer] : []),
294
+ ];
295
+ findings = await (0, analyzers_1.runAllAnalyzers)(graph, analyzers);
296
+ spin.succeed(chalk_1.default.green('Analysis complete') + chalk_1.default.dim(` ${findings.length} finding(s)`));
297
+ }
298
+ // ── Cache ─────────────────────────────────────────────────────────────────────
299
+ (0, core_1.writeCache)('graph', graph);
300
+ (0, core_1.writeCache)('findings', findings);
301
+ (0, core_1.writeCache)('operations', operations);
302
+ // ── Output ────────────────────────────────────────────────────────────────────
303
+ console.log('');
304
+ if (findings.length === 0) {
305
+ console.log(` ${chalk_1.default.green.bold('✓ No issues found!')} ${chalk_1.default.dim('Your infrastructure looks clean.')}`);
306
+ }
307
+ else {
308
+ console.log(chalk_1.default.bold(` Findings`) + chalk_1.default.dim(` ${findings.length} total`));
309
+ findings.forEach((f, i) => (0, utils_1.printFinding)(f, i));
310
+ (0, utils_1.printSummaryBox)(findings);
311
+ if (findings.some((f) => f.severity === 'high')) {
312
+ console.log(`\n ${chalk_1.default.red.bold('Action required:')} ${chalk_1.default.red('High severity issues detected.')}`);
313
+ }
314
+ }
315
+ console.log('');
316
+ utils_1.log.dim(`Results cached in .infrawise/cache/`);
317
+ utils_1.log.info(`Run ${chalk_1.default.cyan('infrawise dev')} to explore via the MCP server`);
318
+ console.log('');
319
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runAuth = runAuth;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const inquirer_1 = __importDefault(require("inquirer"));
9
+ const ora_1 = __importDefault(require("ora"));
10
+ const utils_1 = require("../utils");
11
+ const dynamodb_1 = require("../../adapters/dynamodb");
12
+ async function runAuth() {
13
+ (0, utils_1.printHeader)('AWS Authentication');
14
+ const profiles = (0, utils_1.readAWSProfiles)();
15
+ if (profiles.length === 0) {
16
+ utils_1.log.fail('No AWS profiles found');
17
+ console.log('');
18
+ utils_1.log.info('Run ' + chalk_1.default.cyan('aws configure') + ' to set up credentials');
19
+ utils_1.log.info('Or manually edit ' + chalk_1.default.dim('~/.aws/credentials'));
20
+ console.log('');
21
+ return;
22
+ }
23
+ utils_1.log.success(`Found ${profiles.length} profile(s)`);
24
+ console.log('');
25
+ const { selectedProfile } = await inquirer_1.default.prompt([
26
+ {
27
+ type: 'list',
28
+ name: 'selectedProfile',
29
+ message: 'Select a profile to validate:',
30
+ choices: profiles,
31
+ },
32
+ ]);
33
+ console.log('');
34
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim(`Validating "${selectedProfile}"...`), color: 'cyan' }).start();
35
+ const testConfig = {
36
+ project: 'auth-test',
37
+ aws: { profile: selectedProfile, region: 'us-east-1' },
38
+ };
39
+ const isValid = await (0, dynamodb_1.validateDynamoAccess)(testConfig);
40
+ if (isValid) {
41
+ spin.succeed(chalk_1.default.green(`Profile "${chalk_1.default.bold(selectedProfile)}" is valid`));
42
+ console.log('');
43
+ console.log(chalk_1.default.dim(' Update your infrawise.yaml:'));
44
+ console.log(chalk_1.default.cyan(` aws:\n profile: ${selectedProfile}`));
45
+ }
46
+ else {
47
+ spin.fail(chalk_1.default.red(`Profile "${chalk_1.default.bold(selectedProfile)}" cannot access DynamoDB`));
48
+ console.log('');
49
+ utils_1.log.warn('Possible causes:');
50
+ utils_1.log.dim('Missing IAM permissions — need dynamodb:ListTables, dynamodb:DescribeTable');
51
+ utils_1.log.dim('Expired SSO — run: aws sso login');
52
+ utils_1.log.dim('Wrong region — check your AWS config');
53
+ console.log('');
54
+ utils_1.log.info(`Run ${chalk_1.default.cyan('infrawise doctor')} for a full diagnostic`);
55
+ }
56
+ console.log('');
57
+ }
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runDev = runDev;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const core_1 = require("../../core");
10
+ const server_1 = require("../../server");
11
+ const utils_1 = require("../utils");
12
+ const BOX_W = 52;
13
+ const TOOL_MAP = [
14
+ { name: 'get_infra_overview' },
15
+ { name: 'get_graph_summary' },
16
+ { name: 'analyze_function' },
17
+ { name: 'suggest_gsi', service: 'dynamodb' },
18
+ { name: 'postgres_index_suggestions', service: 'postgres' },
19
+ { name: 'suggest_mongo_index', service: 'mongodb' },
20
+ { name: 'mysql_index_suggestions', service: 'mysql' },
21
+ { name: 'get_queue_details', service: 'sqs' },
22
+ { name: 'get_topic_details', service: 'sns' },
23
+ { name: 'get_secrets_overview', service: 'secretsManager' },
24
+ { name: 'get_parameter_overview', service: 'ssm' },
25
+ { name: 'get_lambda_overview', service: 'lambda' },
26
+ { name: 'get_log_errors', service: 'cloudwatchLogs' },
27
+ ];
28
+ function isEnabled(cfg, service) {
29
+ if (!service)
30
+ return true;
31
+ const svc = cfg[service];
32
+ return svc?.enabled === true;
33
+ }
34
+ function boxLine(visibleContent, coloredContent) {
35
+ const padding = ' '.repeat(Math.max(0, BOX_W - visibleContent.length));
36
+ console.log(chalk_1.default.dim(' │') + coloredContent + padding + chalk_1.default.dim('│'));
37
+ }
38
+ function boxDivider() {
39
+ console.log(chalk_1.default.dim(' ├────────────────────────────────────────────────────┤'));
40
+ }
41
+ function groupTools(tools) {
42
+ const lines = [];
43
+ let i = 0;
44
+ while (i < tools.length) {
45
+ const a = tools[i];
46
+ const b = tools[i + 1];
47
+ if (b && ` ${a} · ${b}`.length <= BOX_W) {
48
+ lines.push(`${a} · ${b}`);
49
+ i += 2;
50
+ }
51
+ else {
52
+ lines.push(a);
53
+ i++;
54
+ }
55
+ }
56
+ return lines;
57
+ }
58
+ async function runDev(options = {}) {
59
+ const port = options.port ?? 3000;
60
+ (0, utils_1.printHeader)('MCP Server');
61
+ let config;
62
+ try {
63
+ config = (0, core_1.loadConfig)(options.config);
64
+ utils_1.log.success('Config loaded', options.config ?? 'infrawise.yaml');
65
+ }
66
+ catch (err) {
67
+ console.error((0, core_1.formatError)(err));
68
+ process.exit(1);
69
+ }
70
+ // Load cached state
71
+ const cachedGraph = (0, core_1.readCache)('graph');
72
+ const cachedFindings = (0, core_1.readCache)('findings');
73
+ if (cachedGraph && cachedFindings) {
74
+ utils_1.log.success('Cached analysis loaded', `${cachedGraph.nodes.length} nodes · ${cachedGraph.edges.length} edges · ${cachedFindings.length} finding(s)`);
75
+ (0, server_1.setGraphState)(cachedGraph, cachedFindings);
76
+ }
77
+ else {
78
+ utils_1.log.warn('No cached analysis found');
79
+ utils_1.log.dim(`Run ${chalk_1.default.cyan('infrawise analyze')} first for full results`);
80
+ (0, server_1.setGraphState)({ nodes: [], edges: [] }, []);
81
+ }
82
+ console.log('');
83
+ // Start server
84
+ const spin = (0, ora_1.default)({ text: chalk_1.default.dim('Starting server...'), color: 'cyan' }).start();
85
+ const { start } = (0, server_1.createServer)(port);
86
+ await start();
87
+ spin.succeed(chalk_1.default.green('Server running'));
88
+ // Compute active/inactive tools from config
89
+ const activeTools = TOOL_MAP.filter((t) => isEnabled(config, t.service)).map((t) => t.name);
90
+ const inactiveTools = TOOL_MAP.filter((t) => !isEnabled(config, t.service)).map((t) => t.name);
91
+ // URL rows
92
+ const mcpUrl = `http://localhost:${port}/mcp`;
93
+ const toolsUrl = `http://localhost:${port}/mcp/tools`;
94
+ const healthUrl = `http://localhost:${port}/health`;
95
+ // Print box
96
+ console.log('');
97
+ console.log(chalk_1.default.dim(' ┌────────────────────────────────────────────────────┐'));
98
+ boxLine(' MCP Server', chalk_1.default.bold(' MCP Server'));
99
+ boxDivider();
100
+ boxLine(` POST ${mcpUrl}`, ` ${chalk_1.default.dim('POST')} ${chalk_1.default.cyan(mcpUrl)}`);
101
+ boxLine(` GET ${toolsUrl}`, ` ${chalk_1.default.dim('GET')} ${chalk_1.default.cyan(toolsUrl)}`);
102
+ boxLine(` GET ${healthUrl}`, ` ${chalk_1.default.dim('GET')} ${chalk_1.default.cyan(healthUrl)}`);
103
+ boxDivider();
104
+ const activeLabel = ` Tools (${activeTools.length} active${inactiveTools.length > 0 ? ` · ${inactiveTools.length} off` : ''})`;
105
+ boxLine(activeLabel, chalk_1.default.dim(activeLabel));
106
+ for (const line of groupTools(activeTools)) {
107
+ boxLine(` ${line}`, ` ${line}`);
108
+ }
109
+ if (inactiveTools.length > 0) {
110
+ boxDivider();
111
+ boxLine(' Off (enable in infrawise.yaml):', chalk_1.default.dim(' Off (enable in infrawise.yaml):'));
112
+ for (const line of groupTools(inactiveTools)) {
113
+ boxLine(` ${line}`, chalk_1.default.dim(` ${line}`));
114
+ }
115
+ }
116
+ console.log(chalk_1.default.dim(' └────────────────────────────────────────────────────┘'));
117
+ console.log('');
118
+ console.log(chalk_1.default.dim(' Add via CLI:'));
119
+ console.log(chalk_1.default.dim(` claude mcp add --transport http infrawise ${mcpUrl}`));
120
+ console.log('');
121
+ console.log(chalk_1.default.dim(' Press Ctrl+C to stop\n'));
122
+ process.on('SIGINT', () => {
123
+ console.log(chalk_1.default.dim('\n Shutting down...\n'));
124
+ process.exit(0);
125
+ });
126
+ await new Promise(() => { });
127
+ }