infrawise 0.10.3 → 0.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/index.js CHANGED
@@ -3,10 +3,9 @@ import { readFileSync } from 'fs';
3
3
  import { join } from 'path';
4
4
  import { Command } from 'commander';
5
5
  import { printBanner } from './utils.js';
6
- import { runInit } from './commands/init.js';
7
- import { runAuth } from './commands/auth.js';
8
6
  import { runAnalyze } from './commands/analyze.js';
9
- import { runDev } from './commands/dev.js';
7
+ import { runCheck } from './commands/check.js';
8
+ import { runServe } from './commands/serve.js';
10
9
  import { runDoctor } from './commands/doctor.js';
11
10
  import { runStdio } from './commands/stdio.js';
12
11
  import { runStart } from './commands/start.js';
@@ -18,33 +17,22 @@ program
18
17
  .version(version);
19
18
  program
20
19
  .command('start')
21
- .description('Set up infrawise and connect your editor — runs init + analyze + writes editor MCP config')
20
+ .description('Probe environment, generate config, analyze, and connect your editor')
22
21
  .option('-c, --config <path>', 'Path to infrawise.yaml', 'infrawise.yaml')
23
22
  .option('--claude', 'Write .mcp.json and open Claude Code')
24
23
  .option('--cursor', 'Write .cursor/mcp.json and open Cursor')
24
+ .option('--interactive', 'Run interactive setup wizard instead of auto-discovery')
25
+ .option('--rediscover', 'Delete existing infrawise.yaml and re-probe the environment')
25
26
  .action(async (options) => {
26
27
  printBanner();
27
28
  await runStart({
28
29
  config: options.config !== 'infrawise.yaml' ? options.config : undefined,
29
30
  claude: options.claude,
30
31
  cursor: options.cursor,
32
+ interactive: options.interactive,
33
+ rediscover: options.rediscover,
31
34
  });
32
35
  });
33
- program
34
- .command('init')
35
- .description('Detect AWS profile/region, ask setup questions, and generate infrawise.yaml')
36
- .option('--force', 'Overwrite existing infrawise.yaml')
37
- .action(async (options) => {
38
- printBanner();
39
- await runInit({ force: options.force });
40
- });
41
- program
42
- .command('auth')
43
- .description('Validate and select AWS profile from ~/.aws/credentials')
44
- .action(async () => {
45
- printBanner();
46
- await runAuth();
47
- });
48
36
  program
49
37
  .command('analyze')
50
38
  .description('Load config, run extractors, build graph, and run all analyzers')
@@ -64,27 +52,45 @@ program
64
52
  });
65
53
  });
66
54
  program
67
- .command('dev')
68
- .description('Start MCP server over HTTP at localhost:3000 (alternative to stdio-based start)')
55
+ .command('check')
56
+ .description('CI gate: analyze and exit non-zero if findings reach the threshold severity')
69
57
  .option('-c, --config <path>', 'Path to infrawise.yaml', 'infrawise.yaml')
70
- .option('-p, --port <number>', 'Port to listen on', '3000')
58
+ .option('-r, --repo <path>', 'Path to repository to scan', process.cwd())
59
+ .option('--fail-on <level>', 'Severity that fails the build: high | medium | low', 'high')
71
60
  .action(async (options) => {
72
61
  printBanner();
73
- await runDev({
62
+ await runCheck({
63
+ config: options.config !== 'infrawise.yaml' ? options.config : undefined,
64
+ repo: options.repo,
65
+ failOn: options.failOn,
66
+ });
67
+ });
68
+ program
69
+ .command('serve')
70
+ .description('Start the MCP server — HTTP by default, or stdio for editor integration')
71
+ .option('-c, --config <path>', 'Path to infrawise.yaml', 'infrawise.yaml')
72
+ .option('--stdio', 'Use stdio transport (for editors via .mcp.json) instead of HTTP')
73
+ .option('-p, --port <number>', 'Port to listen on (HTTP only)', '3000')
74
+ .action(async (options) => {
75
+ if (!options.stdio)
76
+ printBanner();
77
+ await runServe({
74
78
  config: options.config !== 'infrawise.yaml' ? options.config : undefined,
79
+ stdio: options.stdio,
75
80
  port: parseInt(options.port, 10),
76
81
  });
77
82
  });
83
+ // Hidden backcompat alias: editors launched from a .mcp.json generated before
84
+ // `serve` existed still invoke `infrawise stdio`. Kept out of --help.
78
85
  program
79
- .command('stdio')
80
- .description('Start MCP server on stdio transport — used by editors via .mcp.json (auto-managed)')
86
+ .command('stdio', { hidden: true })
81
87
  .option('-c, --config <path>', 'Path to infrawise.yaml', 'infrawise.yaml')
82
88
  .action(async (options) => {
83
89
  await runStdio(options.config !== 'infrawise.yaml' ? options.config : undefined);
84
90
  });
85
91
  program
86
92
  .command('doctor')
87
- .description('Validate AWS access, postgres connectivity, config, and repo scan')
93
+ .description('Diagnostic escape hatch: validate AWS/DB access, config, and repo scan')
88
94
  .option('-c, --config <path>', 'Path to infrawise.yaml', 'infrawise.yaml')
89
95
  .action(async (options) => {
90
96
  printBanner();
@@ -2,8 +2,8 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import inquirer from 'inquirer';
5
- import { generateDefaultConfig } from '../../core/index.js';
6
- import { readAWSProfiles, detectAWSRegion, detectRepoType, log, printHeader } from '../utils.js';
5
+ import { generateDefaultConfig } from '../core/index.js';
6
+ import { readAWSProfiles, detectAWSRegion, log, printHeader } from './utils.js';
7
7
  export async function runInit(options = {}) {
8
8
  const cwd = process.cwd();
9
9
  const configPath = path.join(cwd, 'infrawise.yaml');
@@ -12,12 +12,10 @@ export async function runInit(options = {}) {
12
12
  return;
13
13
  }
14
14
  printHeader('Initialize Infrawise');
15
- const repoType = detectRepoType(cwd);
16
15
  const repoName = path.basename(cwd);
17
16
  const profiles = readAWSProfiles();
18
17
  const detectedRegion = detectAWSRegion();
19
18
  log.success(`Repository detected`, repoName);
20
- log.success(`Type`, repoType);
21
19
  log.success(`AWS profiles found`, String(profiles.length));
22
20
  console.log('');
23
21
  // ── Core settings ──────────────────────────────────────────────────────────
@@ -37,7 +35,6 @@ export async function runInit(options = {}) {
37
35
  message: 'Infrastructure:',
38
36
  choices: [
39
37
  { name: 'AWS', value: 'aws' },
40
- { name: 'LocalStack (mimics AWS locally)', value: 'localstack' },
41
38
  { name: 'Local (no cloud — databases, queues, self-hosted services)', value: 'local' },
42
39
  ],
43
40
  },
@@ -59,12 +56,8 @@ export async function runInit(options = {}) {
59
56
  ]);
60
57
  awsProfile = answer.awsProfile;
61
58
  }
62
- else if (provider === 'localstack') {
63
- awsProfile = '__localstack__';
64
- }
65
- // ── Step 3: region + endpoint ─────────────────────────────────────────────
59
+ // ── Step 3: region ────────────────────────────────────────────────────────
66
60
  let region = detectedRegion;
67
- let endpoint;
68
61
  if (provider !== 'local') {
69
62
  const regionAnswer = await inquirer.prompt([
70
63
  {
@@ -75,19 +68,8 @@ export async function runInit(options = {}) {
75
68
  },
76
69
  ]);
77
70
  region = regionAnswer.region;
78
- if (provider === 'localstack') {
79
- const endpointAnswer = await inquirer.prompt([
80
- {
81
- type: 'input',
82
- name: 'endpoint',
83
- message: 'LocalStack endpoint:',
84
- default: 'http://localhost:4566',
85
- },
86
- ]);
87
- endpoint = endpointAnswer.endpoint;
88
- }
89
71
  }
90
- const core = { project, awsProfile, region, endpoint };
72
+ const core = { project, awsProfile, region };
91
73
  // ── Databases ──────────────────────────────────────────────────────────────
92
74
  console.log('\n ' + chalk.bold('Databases'));
93
75
  console.log(chalk.dim(' Self-hosted databases (PostgreSQL, MySQL, MongoDB).'));
@@ -253,15 +235,12 @@ export async function runInit(options = {}) {
253
235
  .map((p) => p.trim())
254
236
  .filter(Boolean)
255
237
  : [];
256
- const isLocalStack = core.awsProfile === '__localstack__';
257
238
  const isEnvVars = core.awsProfile === '__env__';
258
- const resolvedProfile = isLocalStack || isEnvVars ? '' : core.awsProfile;
259
- const resolvedEndpoint = isLocalStack ? (core.endpoint ?? 'http://localhost:4566') : undefined;
239
+ const resolvedProfile = isEnvVars ? '' : core.awsProfile;
260
240
  const configContent = generateDefaultConfig(core.project, {
261
241
  aws: {
262
242
  profile: resolvedProfile,
263
243
  region: core.region ?? detectedRegion,
264
- endpoint: resolvedEndpoint,
265
244
  },
266
245
  dynamodb: { enabled: services.dynamoEnabled, includeTables },
267
246
  postgres: {
@@ -296,7 +275,7 @@ export async function runInit(options = {}) {
296
275
  console.log('');
297
276
  console.log(chalk.bold(' Next steps:'));
298
277
  log.info(`Run ${chalk.cyan('infrawise start')} to analyze and connect your editor`);
299
- log.info(`Run ${chalk.cyan('infrawise doctor')} to validate your setup`);
278
+ log.info(`Run ${chalk.cyan('infrawise doctor')} if extraction comes up empty`);
300
279
  console.log('');
301
280
  }
302
281
  }
@@ -0,0 +1,44 @@
1
+ import * as net from 'net';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ export function probePort(host, port, timeoutMs = 300) {
5
+ return new Promise((resolve) => {
6
+ const socket = new net.Socket();
7
+ const cleanup = (result) => {
8
+ socket.destroy();
9
+ resolve(result);
10
+ };
11
+ socket.setTimeout(timeoutMs);
12
+ socket.on('connect', () => cleanup(true));
13
+ socket.on('timeout', () => cleanup(false));
14
+ socket.on('error', () => cleanup(false));
15
+ socket.connect(port, host);
16
+ });
17
+ }
18
+ export function scanDotEnv(cwd) {
19
+ const envPath = path.join(cwd, '.env');
20
+ if (!fs.existsSync(envPath))
21
+ return {};
22
+ const result = {};
23
+ try {
24
+ const lines = fs.readFileSync(envPath, 'utf-8').split('\n');
25
+ for (const line of lines) {
26
+ const trimmed = line.trim();
27
+ if (!trimmed || trimmed.startsWith('#'))
28
+ continue;
29
+ const eqIdx = trimmed.indexOf('=');
30
+ if (eqIdx === -1)
31
+ continue;
32
+ const key = trimmed.slice(0, eqIdx).trim();
33
+ const value = trimmed
34
+ .slice(eqIdx + 1)
35
+ .trim()
36
+ .replace(/^["']|["']$/g, '');
37
+ result[key] = value;
38
+ }
39
+ }
40
+ catch {
41
+ // silent — non-critical
42
+ }
43
+ return result;
44
+ }
package/dist/cli/utils.js CHANGED
@@ -1,49 +1,40 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import * as os from 'os';
3
+ import { execSync } from 'child_process';
4
4
  import chalk from 'chalk';
5
5
  // ─── AWS helpers ─────────────────────────────────────────────────────────────
6
6
  export function readAWSProfiles() {
7
- const credentialsPath = path.join(os.homedir(), '.aws', 'credentials');
8
- const configPath = path.join(os.homedir(), '.aws', 'config');
9
- const profiles = new Set();
10
- function parseFile(filePath) {
11
- if (!fs.existsSync(filePath))
12
- return;
13
- for (const line of fs.readFileSync(filePath, 'utf-8').split('\n')) {
14
- const match = line.match(/^\[(.+)\]$/);
15
- if (match?.[1]) {
16
- let name = match[1];
17
- if (name.startsWith('profile '))
18
- name = name.slice(8);
19
- profiles.add(name);
20
- }
21
- }
7
+ try {
8
+ const out = execSync('aws configure list-profiles', {
9
+ encoding: 'utf-8',
10
+ stdio: ['pipe', 'pipe', 'pipe'],
11
+ });
12
+ const profiles = out.trim().split('\n').filter(Boolean);
13
+ return profiles.length > 0 ? profiles : ['default'];
14
+ }
15
+ catch {
16
+ return ['default'];
22
17
  }
23
- parseFile(credentialsPath);
24
- parseFile(configPath);
25
- return profiles.size > 0 ? [...profiles] : ['default'];
26
18
  }
27
- export function detectAWSRegion() {
19
+ export function detectAWSRegion(profile) {
28
20
  if (process.env.AWS_DEFAULT_REGION)
29
21
  return process.env.AWS_DEFAULT_REGION;
30
22
  if (process.env.AWS_REGION)
31
23
  return process.env.AWS_REGION;
32
- const configPath = path.join(os.homedir(), '.aws', 'config');
33
- if (fs.existsSync(configPath)) {
34
- const match = fs.readFileSync(configPath, 'utf-8').match(/region\s*=\s*(.+)/);
35
- if (match?.[1])
36
- return match[1].trim();
24
+ const target = profile ?? process.env.AWS_PROFILE ?? 'default';
25
+ try {
26
+ const region = execSync(`aws configure get region --profile ${target}`, {
27
+ encoding: 'utf-8',
28
+ stdio: ['pipe', 'pipe', 'pipe'],
29
+ }).trim();
30
+ if (region)
31
+ return region;
32
+ }
33
+ catch {
34
+ // profile may not have region configured
37
35
  }
38
36
  return 'us-east-1';
39
37
  }
40
- export function detectRepoType(repoPath) {
41
- if (fs.existsSync(path.join(repoPath, 'tsconfig.json')))
42
- return 'typescript';
43
- if (fs.existsSync(path.join(repoPath, 'package.json')))
44
- return 'javascript';
45
- return 'unknown';
46
- }
47
38
  // ─── Banner ──────────────────────────────────────────────────────────────────
48
39
  function readVersion() {
49
40
  try {
@@ -8,7 +8,6 @@ export const InfrawiseConfigSchema = z.object({
8
8
  .object({
9
9
  profile: z.string().optional().default(''),
10
10
  region: z.string().optional().default('us-east-1'),
11
- endpoint: z.string().optional(),
12
11
  })
13
12
  .optional()
14
13
  .default({ profile: 'default', region: 'us-east-1' }),
@@ -56,7 +55,7 @@ export const InfrawiseConfigSchema = z.object({
56
55
  eventbridge: z.object({ enabled: z.boolean().optional().default(true) }).optional(),
57
56
  rds: z.object({ enabled: z.boolean().optional().default(false) }).optional(),
58
57
  s3: z.object({ enabled: z.boolean().optional().default(false) }).optional(),
59
- kafka: z.object({ enabled: z.boolean().optional().default(false) }).optional(),
58
+ apiGateway: z.object({ enabled: z.boolean().optional().default(false) }).optional(),
60
59
  cloudwatchLogs: z
61
60
  .object({
62
61
  enabled: z.boolean().optional().default(false),
@@ -67,6 +66,11 @@ export const InfrawiseConfigSchema = z.object({
67
66
  analysis: z
68
67
  .object({
69
68
  sampleSize: z.number().int().positive().optional().default(100),
69
+ hotPartitionThreshold: z.number().int().positive().optional().default(5),
70
+ hotPartitionThresholds: z
71
+ .record(z.string(), z.number().int().positive())
72
+ .optional()
73
+ .default({}),
70
74
  })
71
75
  .optional(),
72
76
  });
@@ -78,13 +82,32 @@ export class ConfigError extends Error {
78
82
  this.name = 'ConfigError';
79
83
  }
80
84
  }
85
+ const SecretsSchema = z.object({
86
+ postgres: z.object({ connectionString: z.string() }).optional(),
87
+ mysql: z.object({ connectionString: z.string() }).optional(),
88
+ mongodb: z.object({ connectionString: z.string() }).optional(),
89
+ });
90
+ export function loadSecrets(configDir) {
91
+ const secretsPath = path.join(configDir, '.infrawise', 'secrets.yaml');
92
+ if (!fs.existsSync(secretsPath))
93
+ return {};
94
+ try {
95
+ const raw = fs.readFileSync(secretsPath, 'utf-8');
96
+ const parsed = yaml.load(raw);
97
+ const result = SecretsSchema.safeParse(parsed);
98
+ return result.success ? result.data : {};
99
+ }
100
+ catch {
101
+ return {};
102
+ }
103
+ }
81
104
  export function loadConfig(configPath) {
82
105
  const resolvedPath = configPath
83
106
  ? path.resolve(configPath)
84
107
  : path.resolve(process.cwd(), 'infrawise.yaml');
85
108
  if (!fs.existsSync(resolvedPath)) {
86
109
  throw new ConfigError(`Configuration file not found at: ${resolvedPath}`, [
87
- 'Run `infrawise init` to generate a configuration file',
110
+ 'Run `infrawise start` to generate a configuration file',
88
111
  `Or specify a path with --config <path>`,
89
112
  ]);
90
113
  }
@@ -112,7 +135,19 @@ export function loadConfig(configPath) {
112
135
  const details = result.error.issues.map((e) => ` - ${e.path.join('.')}: ${e.message}`);
113
136
  throw new ConfigError('Configuration validation failed', details);
114
137
  }
115
- return result.data;
138
+ const config = result.data;
139
+ const configDir = path.dirname(resolvedPath);
140
+ const secrets = loadSecrets(configDir);
141
+ if (secrets.postgres?.connectionString && config.postgres) {
142
+ config.postgres.connectionString = secrets.postgres.connectionString;
143
+ }
144
+ if (secrets.mysql?.connectionString && config.mysql) {
145
+ config.mysql.connectionString = secrets.mysql.connectionString;
146
+ }
147
+ if (secrets.mongodb?.connectionString && config.mongodb) {
148
+ config.mongodb.connectionString = secrets.mongodb.connectionString;
149
+ }
150
+ return config;
116
151
  }
117
152
  export function generateDefaultConfig(projectName, options) {
118
153
  const config = {
@@ -120,7 +155,6 @@ export function generateDefaultConfig(projectName, options) {
120
155
  aws: {
121
156
  profile: options?.aws?.profile ?? '',
122
157
  region: options?.aws?.region ?? 'us-east-1',
123
- ...(options?.aws?.endpoint ? { endpoint: options.aws.endpoint } : {}),
124
158
  },
125
159
  dynamodb: {
126
160
  enabled: options?.dynamodb?.enabled ?? true,
@@ -72,7 +72,7 @@ export class ConfigError extends InfrawiseError {
72
72
  'infrawise.yaml not found in current directory',
73
73
  'Missing required fields in configuration',
74
74
  details ?? 'Unexpected config error',
75
- ], 'infrawise init');
75
+ ], 'infrawise start');
76
76
  this.name = 'ConfigError';
77
77
  }
78
78
  }
@@ -73,10 +73,28 @@ export function buildGraph(operations, dynamoMeta, postgresMeta, mysqlMeta = [],
73
73
  provider: 'aws',
74
74
  hasDLQ: q.hasDLQ,
75
75
  encrypted: q.encrypted,
76
+ isFifo: q.isFifo,
77
+ visibilityTimeoutSec: q.visibilityTimeoutSec,
76
78
  approximateMessages: q.approximateMessages,
77
79
  retentionDays: q.retentionDays,
78
80
  });
79
81
  }
82
+ for (const api of servicesMeta.apiGateway ?? []) {
83
+ addNode({
84
+ id: `api:aws:${api.id}`,
85
+ type: 'api',
86
+ name: api.name,
87
+ provider: 'aws',
88
+ apiType: api.type,
89
+ routes: api.routes,
90
+ });
91
+ for (const route of api.routes) {
92
+ if (route.lambdaName) {
93
+ const lambdaId = `lambda:aws:${route.lambdaName}`;
94
+ edges.push({ from: `api:aws:${api.id}`, to: lambdaId, type: 'triggers' });
95
+ }
96
+ }
97
+ }
80
98
  for (const t of servicesMeta.sns ?? []) {
81
99
  addNode({
82
100
  id: `topic:aws:${t.name}`,
@@ -402,3 +420,6 @@ export function getEdgeFrequency(graph) {
402
420
  }
403
421
  return freq;
404
422
  }
423
+ export function getAPINodes(graph) {
424
+ return graph.nodes.filter((n) => n.type === 'api');
425
+ }
@@ -8,7 +8,7 @@ import { z } from 'zod';
8
8
  import { logger } from '../core/index.js';
9
9
  const { version } = JSON.parse(readFileSync(join(import.meta.dirname, '../../package.json'), 'utf8'));
10
10
  import { summarizeFindings } from '../analyzers/index.js';
11
- import { getTableNodes, getFunctionNodes, getQueueNodes, getTopicNodes, getSecretNodes, getParameterNodes, getLogGroupNodes, getLambdaNodes, getEventBridgeRuleNodes, getBucketNodes, getScanEdges, getOutgoingEdges, } from '../graph/index.js';
11
+ import { getTableNodes, getFunctionNodes, getQueueNodes, getTopicNodes, getSecretNodes, getParameterNodes, getLogGroupNodes, getLambdaNodes, getEventBridgeRuleNodes, getBucketNodes, getAPINodes, getScanEdges, getOutgoingEdges, } from '../graph/index.js';
12
12
  // ── State ────────────────────────────────────────────────────────────────────
13
13
  let currentGraph = { nodes: [], edges: [] };
14
14
  let currentFindings = [];
@@ -244,7 +244,7 @@ export function createMcpServer() {
244
244
  });
245
245
  }));
246
246
  mcp.registerTool('get_queue_details', {
247
- description: 'Returns all SQS queues with DLQ presence, encryption status, approximate message count, and retention days. Call this when reviewing messaging architecture, investigating a message backlog, or checking DLQ coverage before adding a new consumer. Use get_infra_overview for a quick queue count only.',
247
+ description: 'Returns all SQS queues with DLQ presence, encryption status, FIFO type (isFifo), visibility timeout, approximate message count, and retention days. When isFifo is true, all SendMessage calls must include a MessageGroupId. Call this when reviewing messaging architecture, investigating a message backlog, checking DLQ coverage, or verifying visibility timeout is set correctly relative to Lambda timeout (should be 6× the Lambda timeout). Use get_infra_overview for a quick queue count only.',
248
248
  inputSchema: z.object({}),
249
249
  }, logged('get_queue_details', async () => {
250
250
  const queues = getQueueNodes(currentGraph);
@@ -256,6 +256,8 @@ export function createMcpServer() {
256
256
  provider: q.provider,
257
257
  hasDLQ: q.hasDLQ,
258
258
  encrypted: q.encrypted,
259
+ isFifo: q.isFifo ?? false,
260
+ visibilityTimeoutSec: q.visibilityTimeoutSec,
259
261
  approximateMessages: q.approximateMessages,
260
262
  retentionDays: q.retentionDays,
261
263
  findings: queueFindings
@@ -385,6 +387,24 @@ export function createMcpServer() {
385
387
  })),
386
388
  });
387
389
  }));
390
+ mcp.registerTool('get_api_routes', {
391
+ description: 'Returns all API Gateway APIs (REST, HTTP, WebSocket) with their routes, HTTP methods, paths, and the Lambda function each route invokes. Call this before writing any API handler to understand which Lambda handles a route, or when reviewing API surface area and Lambda integration coverage.',
392
+ inputSchema: z.object({}),
393
+ }, logged('get_api_routes', async () => {
394
+ const apis = getAPINodes(currentGraph);
395
+ return toText({
396
+ total: apis.length,
397
+ apis: apis.map((api) => ({
398
+ name: api.name,
399
+ type: api.apiType,
400
+ routes: (api.routes ?? []).map((r) => ({
401
+ method: r.method,
402
+ path: r.path,
403
+ lambda: r.lambdaName ?? null,
404
+ })),
405
+ })),
406
+ });
407
+ }));
388
408
  mcp.registerTool('get_log_errors', {
389
409
  description: 'Returns recent error pattern summaries from CloudWatch log groups: pattern counts and frequencies grouped by log group. Raw log messages are never returned. Use the optional logGroup filter to scope to one group by name substring. Call this when investigating errors or identifying log groups with no retention policy.',
390
410
  inputSchema: z.object({
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "infrawise",
3
- "version": "0.10.3",
3
+ "version": "0.12.0",
4
4
  "mcpName": "io.github.Sidd27/infrawise",
5
- "description": "CLI-first infrastructure intelligence platform — analyzes DynamoDB, PostgreSQL, MySQL, MongoDB, SQS, SNS, SSM, Secrets Manager, Lambda, S3, CloudWatch Logs and exposes findings as an MCP server for Claude Code",
5
+ "description": "CLI-first infrastructure intelligence platform — analyzes DynamoDB, PostgreSQL, MySQL, MongoDB, SQS, SNS, SSM, Secrets Manager, Lambda, S3, API Gateway, CloudWatch Logs and exposes findings as an MCP server for Claude Code",
6
6
  "keywords": [
7
7
  "mcp",
8
8
  "mcp-server",
@@ -32,7 +32,9 @@
32
32
  "cli",
33
33
  "terraform",
34
34
  "cloudformation",
35
- "cdk"
35
+ "cdk",
36
+ "api-gateway",
37
+ "sqs-fifo"
36
38
  ],
37
39
  "agentskills": {
38
40
  "infrawise": "./AGENTS.md"
@@ -52,7 +54,7 @@
52
54
  "node": ">=24.0.0",
53
55
  "pnpm": ">=9.0.0"
54
56
  },
55
- "packageManager": "pnpm@10.11.0",
57
+ "packageManager": "pnpm@11.6.0",
56
58
  "scripts": {
57
59
  "build": "tsc --noEmit false --outDir dist",
58
60
  "test": "vitest run --passWithNoTests",
@@ -63,14 +65,16 @@
63
65
  "build-arch": "node scripts/build-arch.mjs",
64
66
  "generate-diagrams": "node scripts/build-arch.mjs",
65
67
  "record": "bash docs/demo/record.sh",
66
- "dev": "node dist/cli/index.js dev",
68
+ "dev": "node dist/cli/index.js serve",
67
69
  "release": "node scripts/release.js",
68
70
  "prepare": "simple-git-hooks"
69
71
  },
70
72
  "simple-git-hooks": {
71
- "pre-commit": "pnpm format && git add src/ && pnpm lint && pnpm typecheck && pnpm test"
73
+ "pre-commit": "pnpm format && git add $(git diff --cached --name-only --diff-filter=ACMR) && pnpm lint && pnpm typecheck && pnpm test"
72
74
  },
73
75
  "dependencies": {
76
+ "@aws-sdk/client-api-gateway": "^3.1068.0",
77
+ "@aws-sdk/client-apigatewayv2": "^3.1068.0",
74
78
  "@aws-sdk/client-cloudwatch-logs": "^3.1048.0",
75
79
  "@aws-sdk/client-dynamodb": "^3.1048.0",
76
80
  "@aws-sdk/client-eventbridge": "^3.1051.0",
@@ -99,20 +103,16 @@
99
103
  "zod": "^4.4.3"
100
104
  },
101
105
  "devDependencies": {
102
- "@mermaid-js/mermaid-cli": "^11.15.0",
103
- "@types/inquirer": "^9.0.9",
104
106
  "@types/js-yaml": "^4.0.9",
105
107
  "@types/node": "^25.8.0",
106
108
  "@types/pg": "^8.11.0",
107
109
  "@typescript-eslint/eslint-plugin": "^8.59.3",
108
110
  "@typescript-eslint/parser": "^8.59.3",
109
111
  "@vitest/coverage-v8": "^4.1.6",
110
- "@xmldom/xmldom": "^0.9.10",
111
112
  "eslint": "^10.4.0",
112
113
  "prettier": "^3.8.3",
113
114
  "simple-git-hooks": "^2.13.1",
114
115
  "typescript": "^6.0.3",
115
- "vite": "^8.0.13",
116
116
  "vitest": "^4.1.6"
117
117
  },
118
118
  "files": [
@@ -121,15 +121,5 @@
121
121
  ],
122
122
  "publishConfig": {
123
123
  "access": "public"
124
- },
125
- "pnpm": {
126
- "overrides": {
127
- "qs": ">=6.15.2",
128
- "vite": "^7"
129
- },
130
- "allowedBuildScripts": [
131
- "puppeteer",
132
- "simple-git-hooks"
133
- ]
134
124
  }
135
125
  }
@@ -1,54 +0,0 @@
1
- import chalk from 'chalk';
2
- import inquirer from 'inquirer';
3
- import ora from 'ora';
4
- import { readAWSProfiles, log, printHeader } from '../utils.js';
5
- import { validateDynamoAccess } from '../../adapters/aws/dynamodb.js';
6
- export async function runAuth() {
7
- printHeader('AWS Authentication');
8
- const profiles = readAWSProfiles();
9
- if (profiles.length === 0) {
10
- log.fail('No AWS profiles found');
11
- console.log('');
12
- log.info('Run ' + chalk.cyan('aws configure') + ' to set up credentials');
13
- log.info('Or manually edit ' + chalk.dim('~/.aws/credentials'));
14
- console.log('');
15
- return;
16
- }
17
- log.success(`Found ${profiles.length} profile(s)`);
18
- console.log('');
19
- const { selectedProfile } = await inquirer.prompt([
20
- {
21
- type: 'list',
22
- name: 'selectedProfile',
23
- message: 'Select a profile to validate:',
24
- choices: profiles,
25
- },
26
- ]);
27
- console.log('');
28
- const spin = ora({
29
- text: chalk.dim(`Validating "${selectedProfile}"...`),
30
- color: 'cyan',
31
- }).start();
32
- const testConfig = {
33
- project: 'auth-test',
34
- aws: { profile: selectedProfile, region: 'us-east-1' },
35
- };
36
- const isValid = await validateDynamoAccess(testConfig);
37
- if (isValid) {
38
- spin.succeed(chalk.green(`Profile "${chalk.bold(selectedProfile)}" is valid`));
39
- console.log('');
40
- console.log(chalk.dim(' Update your infrawise.yaml:'));
41
- console.log(chalk.cyan(` aws:\n profile: ${selectedProfile}`));
42
- }
43
- else {
44
- spin.fail(chalk.red(`Profile "${chalk.bold(selectedProfile)}" cannot access DynamoDB`));
45
- console.log('');
46
- log.warn('Possible causes:');
47
- log.dim('Missing IAM permissions — need dynamodb:ListTables, dynamodb:DescribeTable');
48
- log.dim('Expired SSO — run: aws sso login');
49
- log.dim('Wrong region — check your AWS config');
50
- console.log('');
51
- log.info(`Run ${chalk.cyan('infrawise doctor')} for a full diagnostic`);
52
- }
53
- console.log('');
54
- }