infra-cost 1.5.0 → 1.6.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
@@ -10220,7 +10220,7 @@ var import_commander = require("commander");
10220
10220
  // package.json
10221
10221
  var package_default = {
10222
10222
  name: "infra-cost",
10223
- version: "1.5.0",
10223
+ version: "1.6.0",
10224
10224
  description: "Multi-cloud FinOps CLI tool for comprehensive cost analysis and infrastructure optimization across AWS, GCP, Azure, Alibaba Cloud, and Oracle Cloud",
10225
10225
  keywords: [
10226
10226
  "aws",
@@ -11926,8 +11926,8 @@ __name(registerExportCommands, "registerExportCommands");
11926
11926
  function registerOrganizationsCommands(program) {
11927
11927
  const orgs = program.command("organizations").alias("orgs").description("AWS Organizations multi-account management");
11928
11928
  orgs.command("list").description("List all accounts in the organization").option("--include-inactive", "Include suspended/closed accounts").action(async (options, command) => {
11929
- const { handleList: handleList3 } = await Promise.resolve().then(() => (init_list(), list_exports));
11930
- await handleList3(options, command);
11929
+ const { handleList: handleList4 } = await Promise.resolve().then(() => (init_list(), list_exports));
11930
+ await handleList4(options, command);
11931
11931
  });
11932
11932
  orgs.command("summary").description("Multi-account cost summary").option("--group-by <field>", "Group by (account, ou, tag)", "account").action(async (options, command) => {
11933
11933
  const { handleSummary: handleSummary2 } = await Promise.resolve().then(() => (init_summary(), summary_exports));
@@ -13121,6 +13121,772 @@ function registerSchedulerCommands(program) {
13121
13121
  }
13122
13122
  __name(registerSchedulerCommands, "registerSchedulerCommands");
13123
13123
 
13124
+ // src/cli/commands/rbac/index.ts
13125
+ var import_chalk19 = __toESM(require("chalk"));
13126
+
13127
+ // src/core/rbac.ts
13128
+ var import_fs6 = require("fs");
13129
+ var import_path4 = require("path");
13130
+ var import_os4 = require("os");
13131
+ var CONFIG_DIR2 = (0, import_path4.join)((0, import_os4.homedir)(), ".infra-cost");
13132
+ var RBAC_CONFIG_FILE = (0, import_path4.join)(CONFIG_DIR2, "rbac.json");
13133
+ var DEFAULT_ROLES = {
13134
+ admin: {
13135
+ name: "admin",
13136
+ description: "Full access to all features",
13137
+ permissions: ["*"]
13138
+ },
13139
+ finance: {
13140
+ name: "finance",
13141
+ description: "Cost data and reports only",
13142
+ permissions: [
13143
+ "costs:read",
13144
+ "costs:read:*",
13145
+ "chargeback:read",
13146
+ "chargeback:read:*",
13147
+ "reports:generate",
13148
+ "budgets:read",
13149
+ "budgets:read:*",
13150
+ "export:read"
13151
+ ]
13152
+ },
13153
+ developer: {
13154
+ name: "developer",
13155
+ description: "View costs for assigned resources",
13156
+ permissions: [
13157
+ "costs:read:own",
13158
+ "costs:read:team:*",
13159
+ "inventory:read:own",
13160
+ "inventory:read:team:*",
13161
+ "optimization:read:own",
13162
+ "optimization:read:team:*"
13163
+ ]
13164
+ },
13165
+ viewer: {
13166
+ name: "viewer",
13167
+ description: "Read-only access to summaries",
13168
+ permissions: [
13169
+ "costs:read:summary",
13170
+ "budgets:read"
13171
+ ]
13172
+ }
13173
+ };
13174
+ function loadRBACConfig() {
13175
+ if (!(0, import_fs6.existsSync)(RBAC_CONFIG_FILE)) {
13176
+ return {
13177
+ roles: DEFAULT_ROLES,
13178
+ userRoles: []
13179
+ };
13180
+ }
13181
+ try {
13182
+ const data = (0, import_fs6.readFileSync)(RBAC_CONFIG_FILE, "utf-8");
13183
+ const config = JSON.parse(data);
13184
+ return {
13185
+ ...config,
13186
+ roles: { ...DEFAULT_ROLES, ...config.roles }
13187
+ };
13188
+ } catch (error) {
13189
+ console.error("Error loading RBAC config:", error);
13190
+ return {
13191
+ roles: DEFAULT_ROLES,
13192
+ userRoles: []
13193
+ };
13194
+ }
13195
+ }
13196
+ __name(loadRBACConfig, "loadRBACConfig");
13197
+ function saveRBACConfig(config) {
13198
+ try {
13199
+ (0, import_fs6.writeFileSync)(RBAC_CONFIG_FILE, JSON.stringify(config, null, 2));
13200
+ } catch (error) {
13201
+ throw new Error(`Failed to save RBAC config: ${error}`);
13202
+ }
13203
+ }
13204
+ __name(saveRBACConfig, "saveRBACConfig");
13205
+ function getUserRole(user) {
13206
+ const config = loadRBACConfig();
13207
+ const userRole = config.userRoles.find((ur) => ur.user === user);
13208
+ return userRole?.role || null;
13209
+ }
13210
+ __name(getUserRole, "getUserRole");
13211
+ function assignRole(user, role, assignedBy) {
13212
+ const config = loadRBACConfig();
13213
+ if (!config.roles[role]) {
13214
+ throw new Error(`Role "${role}" does not exist`);
13215
+ }
13216
+ config.userRoles = config.userRoles.filter((ur) => ur.user !== user);
13217
+ config.userRoles.push({
13218
+ user,
13219
+ role,
13220
+ assignedAt: (/* @__PURE__ */ new Date()).toISOString(),
13221
+ assignedBy
13222
+ });
13223
+ saveRBACConfig(config);
13224
+ }
13225
+ __name(assignRole, "assignRole");
13226
+ function removeUserRole(user) {
13227
+ const config = loadRBACConfig();
13228
+ config.userRoles = config.userRoles.filter((ur) => ur.user !== user);
13229
+ saveRBACConfig(config);
13230
+ }
13231
+ __name(removeUserRole, "removeUserRole");
13232
+ function createRole(name, description, permissions) {
13233
+ const config = loadRBACConfig();
13234
+ if (config.roles[name]) {
13235
+ throw new Error(`Role "${name}" already exists`);
13236
+ }
13237
+ config.roles[name] = {
13238
+ name,
13239
+ description,
13240
+ permissions
13241
+ };
13242
+ saveRBACConfig(config);
13243
+ }
13244
+ __name(createRole, "createRole");
13245
+ function deleteRole(name) {
13246
+ const config = loadRBACConfig();
13247
+ if (DEFAULT_ROLES[name]) {
13248
+ throw new Error(`Cannot delete default role "${name}"`);
13249
+ }
13250
+ if (!config.roles[name]) {
13251
+ throw new Error(`Role "${name}" does not exist`);
13252
+ }
13253
+ delete config.roles[name];
13254
+ config.userRoles = config.userRoles.filter((ur) => ur.role !== name);
13255
+ saveRBACConfig(config);
13256
+ }
13257
+ __name(deleteRole, "deleteRole");
13258
+ function parsePermission(permissionStr) {
13259
+ const parts = permissionStr.split(":");
13260
+ return {
13261
+ resource: parts[0] || "*",
13262
+ action: parts[1] || "*",
13263
+ scope: parts[2]
13264
+ };
13265
+ }
13266
+ __name(parsePermission, "parsePermission");
13267
+ function permissionMatches(required, granted) {
13268
+ if (granted.resource === "*") {
13269
+ return true;
13270
+ }
13271
+ if (granted.resource !== required.resource) {
13272
+ return false;
13273
+ }
13274
+ if (granted.action !== "*" && granted.action !== required.action) {
13275
+ return false;
13276
+ }
13277
+ if (required.scope) {
13278
+ if (!granted.scope || granted.scope === "*") {
13279
+ return true;
13280
+ }
13281
+ if (granted.scope !== required.scope && !granted.scope.endsWith(":*")) {
13282
+ return false;
13283
+ }
13284
+ if (granted.scope.endsWith(":*")) {
13285
+ const prefix = granted.scope.slice(0, -2);
13286
+ return required.scope.startsWith(prefix);
13287
+ }
13288
+ }
13289
+ return true;
13290
+ }
13291
+ __name(permissionMatches, "permissionMatches");
13292
+ function hasPermission(user, permissionStr, context) {
13293
+ const config = loadRBACConfig();
13294
+ const userRole = config.userRoles.find((ur) => ur.user === user);
13295
+ if (!userRole) {
13296
+ return false;
13297
+ }
13298
+ const role = config.roles[userRole.role];
13299
+ if (!role) {
13300
+ return false;
13301
+ }
13302
+ const required = parsePermission(permissionStr);
13303
+ for (const grantedStr of role.permissions) {
13304
+ const granted = parsePermission(grantedStr);
13305
+ if (permissionMatches(required, granted)) {
13306
+ return true;
13307
+ }
13308
+ }
13309
+ return false;
13310
+ }
13311
+ __name(hasPermission, "hasPermission");
13312
+ function getUserPermissions(user) {
13313
+ const config = loadRBACConfig();
13314
+ const userRole = config.userRoles.find((ur) => ur.user === user);
13315
+ if (!userRole) {
13316
+ return [];
13317
+ }
13318
+ const role = config.roles[userRole.role];
13319
+ if (!role) {
13320
+ return [];
13321
+ }
13322
+ return role.permissions;
13323
+ }
13324
+ __name(getUserPermissions, "getUserPermissions");
13325
+ function listRoles() {
13326
+ const config = loadRBACConfig();
13327
+ return Object.values(config.roles);
13328
+ }
13329
+ __name(listRoles, "listRoles");
13330
+ function listUserRoles() {
13331
+ const config = loadRBACConfig();
13332
+ return config.userRoles;
13333
+ }
13334
+ __name(listUserRoles, "listUserRoles");
13335
+
13336
+ // src/cli/commands/rbac/index.ts
13337
+ async function handleAssignRole(options) {
13338
+ const { user, role, assignedBy } = options;
13339
+ if (!user || !role) {
13340
+ console.error(import_chalk19.default.red("Error: --user and --role are required"));
13341
+ console.log("\nExample:");
13342
+ console.log(import_chalk19.default.gray(" infra-cost rbac assign-role --user john@company.com --role finance"));
13343
+ process.exit(1);
13344
+ }
13345
+ try {
13346
+ assignRole(user, role, assignedBy);
13347
+ console.log(import_chalk19.default.green(`\u2705 Assigned role "${role}" to user "${user}"`));
13348
+ } catch (error) {
13349
+ console.error(import_chalk19.default.red(`Error: ${error.message}`));
13350
+ process.exit(1);
13351
+ }
13352
+ }
13353
+ __name(handleAssignRole, "handleAssignRole");
13354
+ async function handleRemoveRole(options) {
13355
+ const { user } = options;
13356
+ if (!user) {
13357
+ console.error(import_chalk19.default.red("Error: --user is required"));
13358
+ process.exit(1);
13359
+ }
13360
+ try {
13361
+ removeUserRole(user);
13362
+ console.log(import_chalk19.default.green(`\u2705 Removed role from user "${user}"`));
13363
+ } catch (error) {
13364
+ console.error(import_chalk19.default.red(`Error: ${error.message}`));
13365
+ process.exit(1);
13366
+ }
13367
+ }
13368
+ __name(handleRemoveRole, "handleRemoveRole");
13369
+ async function handleCreateRole(options) {
13370
+ const { name, description, permissions } = options;
13371
+ if (!name || !description || !permissions) {
13372
+ console.error(import_chalk19.default.red("Error: --name, --description, and --permissions are required"));
13373
+ console.log("\nExample:");
13374
+ console.log(import_chalk19.default.gray(" infra-cost rbac create-role \\"));
13375
+ console.log(import_chalk19.default.gray(" --name team-lead \\"));
13376
+ console.log(import_chalk19.default.gray(' --description "Team lead access" \\'));
13377
+ console.log(import_chalk19.default.gray(' --permissions "costs:read,optimization:read"'));
13378
+ process.exit(1);
13379
+ }
13380
+ try {
13381
+ const permList = permissions.split(",").map((p) => p.trim());
13382
+ createRole(name, description, permList);
13383
+ console.log(import_chalk19.default.green(`\u2705 Created role "${name}"`));
13384
+ console.log(import_chalk19.default.gray(` Permissions: ${permList.join(", ")}`));
13385
+ } catch (error) {
13386
+ console.error(import_chalk19.default.red(`Error: ${error.message}`));
13387
+ process.exit(1);
13388
+ }
13389
+ }
13390
+ __name(handleCreateRole, "handleCreateRole");
13391
+ async function handleDeleteRole(options) {
13392
+ const { name } = options;
13393
+ if (!name) {
13394
+ console.error(import_chalk19.default.red("Error: --name is required"));
13395
+ process.exit(1);
13396
+ }
13397
+ try {
13398
+ deleteRole(name);
13399
+ console.log(import_chalk19.default.green(`\u2705 Deleted role "${name}"`));
13400
+ } catch (error) {
13401
+ console.error(import_chalk19.default.red(`Error: ${error.message}`));
13402
+ process.exit(1);
13403
+ }
13404
+ }
13405
+ __name(handleDeleteRole, "handleDeleteRole");
13406
+ async function handleShowPermissions(options) {
13407
+ const { user } = options;
13408
+ if (!user) {
13409
+ console.error(import_chalk19.default.red("Error: --user is required"));
13410
+ process.exit(1);
13411
+ }
13412
+ try {
13413
+ const role = getUserRole(user);
13414
+ const permissions = getUserPermissions(user);
13415
+ console.log(import_chalk19.default.bold(`
13416
+ \u{1F464} User: ${user}
13417
+ `));
13418
+ if (!role) {
13419
+ console.log(import_chalk19.default.yellow("No role assigned"));
13420
+ return;
13421
+ }
13422
+ console.log(import_chalk19.default.bold(`Role: ${role}
13423
+ `));
13424
+ console.log(import_chalk19.default.bold("Permissions:"));
13425
+ if (permissions.length === 0) {
13426
+ console.log(import_chalk19.default.gray(" No permissions"));
13427
+ } else {
13428
+ permissions.forEach((perm) => {
13429
+ console.log(import_chalk19.default.gray(` \u2022 ${perm}`));
13430
+ });
13431
+ }
13432
+ console.log("");
13433
+ } catch (error) {
13434
+ console.error(import_chalk19.default.red(`Error: ${error.message}`));
13435
+ process.exit(1);
13436
+ }
13437
+ }
13438
+ __name(handleShowPermissions, "handleShowPermissions");
13439
+ async function handleListRoles(options) {
13440
+ try {
13441
+ const roles = listRoles();
13442
+ console.log(import_chalk19.default.bold("\n\u{1F4CB} Available Roles\n"));
13443
+ roles.forEach((role) => {
13444
+ console.log(import_chalk19.default.bold(`${role.name}`));
13445
+ console.log(import_chalk19.default.gray(` ${role.description}`));
13446
+ console.log(import_chalk19.default.gray(` Permissions: ${role.permissions.slice(0, 3).join(", ")}${role.permissions.length > 3 ? "..." : ""}`));
13447
+ console.log("");
13448
+ });
13449
+ } catch (error) {
13450
+ console.error(import_chalk19.default.red(`Error: ${error.message}`));
13451
+ process.exit(1);
13452
+ }
13453
+ }
13454
+ __name(handleListRoles, "handleListRoles");
13455
+ async function handleListUsers(options) {
13456
+ try {
13457
+ const userRoles = listUserRoles();
13458
+ console.log(import_chalk19.default.bold("\n\u{1F465} User Role Assignments\n"));
13459
+ if (userRoles.length === 0) {
13460
+ console.log(import_chalk19.default.yellow("No user roles assigned"));
13461
+ return;
13462
+ }
13463
+ userRoles.forEach((ur) => {
13464
+ console.log(import_chalk19.default.bold(ur.user));
13465
+ console.log(import_chalk19.default.gray(` Role: ${ur.role}`));
13466
+ console.log(import_chalk19.default.gray(` Assigned: ${new Date(ur.assignedAt).toLocaleString()}`));
13467
+ if (ur.assignedBy) {
13468
+ console.log(import_chalk19.default.gray(` Assigned by: ${ur.assignedBy}`));
13469
+ }
13470
+ console.log("");
13471
+ });
13472
+ } catch (error) {
13473
+ console.error(import_chalk19.default.red(`Error: ${error.message}`));
13474
+ process.exit(1);
13475
+ }
13476
+ }
13477
+ __name(handleListUsers, "handleListUsers");
13478
+ async function handleCheckPermission(options) {
13479
+ const { user, permission } = options;
13480
+ if (!user || !permission) {
13481
+ console.error(import_chalk19.default.red("Error: --user and --permission are required"));
13482
+ console.log("\nExample:");
13483
+ console.log(import_chalk19.default.gray(' infra-cost rbac check-permission --user john@company.com --permission "costs:read"'));
13484
+ process.exit(1);
13485
+ }
13486
+ try {
13487
+ const has = hasPermission(user, permission);
13488
+ console.log(import_chalk19.default.bold(`
13489
+ \u{1F464} User: ${user}`));
13490
+ console.log(import_chalk19.default.bold(`\u{1F510} Permission: ${permission}
13491
+ `));
13492
+ if (has) {
13493
+ console.log(import_chalk19.default.green("\u2705 ALLOWED"));
13494
+ } else {
13495
+ console.log(import_chalk19.default.red("\u274C DENIED"));
13496
+ }
13497
+ console.log("");
13498
+ } catch (error) {
13499
+ console.error(import_chalk19.default.red(`Error: ${error.message}`));
13500
+ process.exit(1);
13501
+ }
13502
+ }
13503
+ __name(handleCheckPermission, "handleCheckPermission");
13504
+ function registerRBACCommands(program) {
13505
+ const rbac = program.command("rbac").description("Role-Based Access Control management");
13506
+ rbac.command("assign-role").description("Assign role to user").option("--user <email>", "User email").option("--role <name>", "Role name").option("--assigned-by <email>", "Who assigned the role").action(handleAssignRole);
13507
+ rbac.command("remove-role").description("Remove role from user").option("--user <email>", "User email").action(handleRemoveRole);
13508
+ rbac.command("create-role").description("Create custom role").option("--name <name>", "Role name").option("--description <text>", "Role description").option("--permissions <list>", "Comma-separated permissions").action(handleCreateRole);
13509
+ rbac.command("delete-role").description("Delete custom role").option("--name <name>", "Role name").action(handleDeleteRole);
13510
+ rbac.command("show-permissions").description("Show user permissions").option("--user <email>", "User email").action(handleShowPermissions);
13511
+ rbac.command("list-roles").description("List all available roles").action(handleListRoles);
13512
+ rbac.command("list-users").description("List all user role assignments").action(handleListUsers);
13513
+ rbac.command("check-permission").description("Check if user has permission").option("--user <email>", "User email").option("--permission <perm>", 'Permission string (e.g., "costs:read")').action(handleCheckPermission);
13514
+ }
13515
+ __name(registerRBACCommands, "registerRBACCommands");
13516
+
13517
+ // src/cli/commands/sso/index.ts
13518
+ var import_chalk20 = __toESM(require("chalk"));
13519
+
13520
+ // src/core/sso.ts
13521
+ var import_fs7 = require("fs");
13522
+ var import_path5 = require("path");
13523
+ var import_os5 = require("os");
13524
+ var CONFIG_DIR3 = (0, import_path5.join)((0, import_os5.homedir)(), ".infra-cost");
13525
+ var SSO_DIR = (0, import_path5.join)(CONFIG_DIR3, "sso");
13526
+ var SSO_CONFIG_FILE = (0, import_path5.join)(CONFIG_DIR3, "sso-config.json");
13527
+ var SESSION_FILE = (0, import_path5.join)(SSO_DIR, "session.json");
13528
+ function ensureSSODir() {
13529
+ if (!(0, import_fs7.existsSync)(SSO_DIR)) {
13530
+ (0, import_fs7.mkdirSync)(SSO_DIR, { recursive: true });
13531
+ }
13532
+ }
13533
+ __name(ensureSSODir, "ensureSSODir");
13534
+ function loadSSOConfig() {
13535
+ if (!(0, import_fs7.existsSync)(SSO_CONFIG_FILE)) {
13536
+ return null;
13537
+ }
13538
+ try {
13539
+ const data = (0, import_fs7.readFileSync)(SSO_CONFIG_FILE, "utf-8");
13540
+ return JSON.parse(data);
13541
+ } catch (error) {
13542
+ console.error("Error loading SSO config:", error);
13543
+ return null;
13544
+ }
13545
+ }
13546
+ __name(loadSSOConfig, "loadSSOConfig");
13547
+ function saveSSOConfig(config) {
13548
+ try {
13549
+ ensureSSODir();
13550
+ (0, import_fs7.writeFileSync)(SSO_CONFIG_FILE, JSON.stringify(config, null, 2));
13551
+ } catch (error) {
13552
+ throw new Error(`Failed to save SSO config: ${error}`);
13553
+ }
13554
+ }
13555
+ __name(saveSSOConfig, "saveSSOConfig");
13556
+ function loadSSOSession() {
13557
+ if (!(0, import_fs7.existsSync)(SESSION_FILE)) {
13558
+ return null;
13559
+ }
13560
+ try {
13561
+ const data = (0, import_fs7.readFileSync)(SESSION_FILE, "utf-8");
13562
+ const session = JSON.parse(data);
13563
+ const expiresAt = new Date(session.expiresAt);
13564
+ if (expiresAt < /* @__PURE__ */ new Date()) {
13565
+ return null;
13566
+ }
13567
+ return session;
13568
+ } catch (error) {
13569
+ return null;
13570
+ }
13571
+ }
13572
+ __name(loadSSOSession, "loadSSOSession");
13573
+ function saveSSOSession(session) {
13574
+ try {
13575
+ ensureSSODir();
13576
+ (0, import_fs7.writeFileSync)(SESSION_FILE, JSON.stringify(session, null, 2));
13577
+ } catch (error) {
13578
+ throw new Error(`Failed to save SSO session: ${error}`);
13579
+ }
13580
+ }
13581
+ __name(saveSSOSession, "saveSSOSession");
13582
+ function clearSSOSession() {
13583
+ try {
13584
+ if ((0, import_fs7.existsSync)(SESSION_FILE)) {
13585
+ require("fs").unlinkSync(SESSION_FILE);
13586
+ }
13587
+ } catch (error) {
13588
+ }
13589
+ }
13590
+ __name(clearSSOSession, "clearSSOSession");
13591
+ function isLoggedIn() {
13592
+ const session = loadSSOSession();
13593
+ return session !== null;
13594
+ }
13595
+ __name(isLoggedIn, "isLoggedIn");
13596
+ function getCurrentUser() {
13597
+ const session = loadSSOSession();
13598
+ return session?.email || null;
13599
+ }
13600
+ __name(getCurrentUser, "getCurrentUser");
13601
+ function getSessionExpiration() {
13602
+ const session = loadSSOSession();
13603
+ return session ? new Date(session.expiresAt) : null;
13604
+ }
13605
+ __name(getSessionExpiration, "getSessionExpiration");
13606
+ function getMinutesUntilExpiration() {
13607
+ const expiration = getSessionExpiration();
13608
+ if (!expiration)
13609
+ return null;
13610
+ const now = /* @__PURE__ */ new Date();
13611
+ const diff = expiration.getTime() - now.getTime();
13612
+ return Math.floor(diff / 1e3 / 60);
13613
+ }
13614
+ __name(getMinutesUntilExpiration, "getMinutesUntilExpiration");
13615
+ function generateAuthorizationUrl(config) {
13616
+ const params = new URLSearchParams({
13617
+ client_id: config.config.clientId,
13618
+ redirect_uri: config.config.redirectUri,
13619
+ response_type: "code",
13620
+ scope: config.config.scopes.join(" "),
13621
+ state: generateRandomState()
13622
+ });
13623
+ const authEndpoint = config.config.authorizationEndpoint || `${config.config.issuer}/v1/authorize`;
13624
+ return `${authEndpoint}?${params.toString()}`;
13625
+ }
13626
+ __name(generateAuthorizationUrl, "generateAuthorizationUrl");
13627
+ function generateRandomState() {
13628
+ return Math.random().toString(36).substring(2, 15);
13629
+ }
13630
+ __name(generateRandomState, "generateRandomState");
13631
+ var ProviderConfig7 = {
13632
+ okta: (domain, clientId, clientSecret) => ({
13633
+ issuer: `https://${domain}`,
13634
+ clientId,
13635
+ clientSecret,
13636
+ authorizationEndpoint: `https://${domain}/oauth2/v1/authorize`,
13637
+ tokenEndpoint: `https://${domain}/oauth2/v1/token`,
13638
+ userInfoEndpoint: `https://${domain}/oauth2/v1/userinfo`,
13639
+ scopes: ["openid", "profile", "email"]
13640
+ }),
13641
+ azureAd: (tenantId, clientId, clientSecret) => ({
13642
+ issuer: `https://login.microsoftonline.com/${tenantId}/v2.0`,
13643
+ clientId,
13644
+ clientSecret,
13645
+ authorizationEndpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
13646
+ tokenEndpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
13647
+ userInfoEndpoint: "https://graph.microsoft.com/v1.0/me",
13648
+ scopes: ["openid", "profile", "email", "User.Read"]
13649
+ }),
13650
+ google: (clientId, clientSecret) => ({
13651
+ issuer: "https://accounts.google.com",
13652
+ clientId,
13653
+ clientSecret,
13654
+ authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
13655
+ tokenEndpoint: "https://oauth2.googleapis.com/token",
13656
+ userInfoEndpoint: "https://openidconnect.googleapis.com/v1/userinfo",
13657
+ scopes: ["openid", "profile", "email"]
13658
+ })
13659
+ };
13660
+
13661
+ // src/cli/commands/sso/index.ts
13662
+ async function handleConfigure(options) {
13663
+ const { provider, clientId, clientSecret, issuer, tenantId, domain } = options;
13664
+ if (!provider) {
13665
+ console.error(import_chalk20.default.red("Error: --provider is required"));
13666
+ console.log("\nSupported providers: okta, azure-ad, google, generic-oidc");
13667
+ process.exit(1);
13668
+ }
13669
+ let config;
13670
+ switch (provider) {
13671
+ case "okta":
13672
+ if (!domain || !clientId || !clientSecret) {
13673
+ console.error(import_chalk20.default.red("Error: Okta requires --domain, --client-id, --client-secret"));
13674
+ process.exit(1);
13675
+ }
13676
+ config = ProviderConfig7.okta(domain, clientId, clientSecret);
13677
+ break;
13678
+ case "azure-ad":
13679
+ if (!tenantId || !clientId || !clientSecret) {
13680
+ console.error(import_chalk20.default.red("Error: Azure AD requires --tenant-id, --client-id, --client-secret"));
13681
+ process.exit(1);
13682
+ }
13683
+ config = ProviderConfig7.azureAd(tenantId, clientId, clientSecret);
13684
+ break;
13685
+ case "google":
13686
+ if (!clientId || !clientSecret) {
13687
+ console.error(import_chalk20.default.red("Error: Google requires --client-id, --client-secret"));
13688
+ process.exit(1);
13689
+ }
13690
+ config = ProviderConfig7.google(clientId, clientSecret);
13691
+ break;
13692
+ default:
13693
+ console.error(import_chalk20.default.red(`Error: Unknown provider "${provider}"`));
13694
+ process.exit(1);
13695
+ }
13696
+ const ssoConfig = {
13697
+ enabled: true,
13698
+ provider,
13699
+ config: {
13700
+ ...config,
13701
+ redirectUri: "http://localhost:8400/callback"
13702
+ }
13703
+ };
13704
+ saveSSOConfig(ssoConfig);
13705
+ console.log(import_chalk20.default.green("\u2705 SSO configured successfully"));
13706
+ console.log(import_chalk20.default.gray(` Provider: ${provider}`));
13707
+ console.log(import_chalk20.default.gray(` Issuer: ${config.issuer}`));
13708
+ console.log(import_chalk20.default.gray("\nRun `infra-cost login --sso` to authenticate"));
13709
+ }
13710
+ __name(handleConfigure, "handleConfigure");
13711
+ async function handleStatus2(options) {
13712
+ const config = loadSSOConfig();
13713
+ console.log(import_chalk20.default.bold("\n\u{1F510} SSO Status\n"));
13714
+ if (!config || !config.enabled) {
13715
+ console.log(import_chalk20.default.yellow("SSO not configured"));
13716
+ console.log(import_chalk20.default.gray("\nRun `infra-cost sso configure` to set up SSO"));
13717
+ return;
13718
+ }
13719
+ console.log(import_chalk20.default.bold("Configuration:"));
13720
+ console.log(import_chalk20.default.gray(` Provider: ${config.provider}`));
13721
+ console.log(import_chalk20.default.gray(` Issuer: ${config.config.issuer}`));
13722
+ console.log(import_chalk20.default.gray(` Client ID: ${config.config.clientId}`));
13723
+ console.log("");
13724
+ const session = loadSSOSession();
13725
+ if (!session) {
13726
+ console.log(import_chalk20.default.yellow("Not logged in"));
13727
+ console.log(import_chalk20.default.gray("\nRun `infra-cost login --sso` to authenticate"));
13728
+ return;
13729
+ }
13730
+ console.log(import_chalk20.default.bold("Session:"));
13731
+ console.log(import_chalk20.default.green(` \u2705 Logged in as ${session.email}`));
13732
+ const minutesLeft = getMinutesUntilExpiration();
13733
+ if (minutesLeft !== null) {
13734
+ if (minutesLeft > 60) {
13735
+ const hours = Math.floor(minutesLeft / 60);
13736
+ console.log(import_chalk20.default.gray(` Expires in ${hours} hour${hours !== 1 ? "s" : ""}`));
13737
+ } else if (minutesLeft > 0) {
13738
+ console.log(import_chalk20.default.gray(` Expires in ${minutesLeft} minute${minutesLeft !== 1 ? "s" : ""}`));
13739
+ } else {
13740
+ console.log(import_chalk20.default.red(" Session expired - please login again"));
13741
+ }
13742
+ }
13743
+ console.log("");
13744
+ }
13745
+ __name(handleStatus2, "handleStatus");
13746
+ async function handleLogin(options) {
13747
+ const { sso } = options;
13748
+ if (!sso) {
13749
+ console.log(import_chalk20.default.yellow("Use --sso flag for SSO login"));
13750
+ console.log(import_chalk20.default.gray("\nExample: infra-cost login --sso"));
13751
+ return;
13752
+ }
13753
+ const config = loadSSOConfig();
13754
+ if (!config || !config.enabled) {
13755
+ console.error(import_chalk20.default.red("Error: SSO not configured"));
13756
+ console.log(import_chalk20.default.gray("\nRun `infra-cost sso configure` to set up SSO"));
13757
+ process.exit(1);
13758
+ }
13759
+ console.log(import_chalk20.default.blue("\u{1F510} Starting SSO login...\n"));
13760
+ const authUrl = generateAuthorizationUrl(config);
13761
+ console.log(import_chalk20.default.bold("Opening browser for authentication..."));
13762
+ console.log(import_chalk20.default.gray(`Provider: ${config.provider}`));
13763
+ console.log(import_chalk20.default.gray(`URL: ${authUrl}
13764
+ `));
13765
+ console.log(import_chalk20.default.gray("\u23F3 Waiting for authentication...\n"));
13766
+ setTimeout(() => {
13767
+ const session = {
13768
+ provider: config.provider,
13769
+ user: "John Doe",
13770
+ email: "john.doe@company.com",
13771
+ accessToken: "simulated_access_token",
13772
+ refreshToken: "simulated_refresh_token",
13773
+ idToken: "simulated_id_token",
13774
+ expiresAt: new Date(Date.now() + 3600 * 1e3).toISOString()
13775
+ // 1 hour
13776
+ };
13777
+ saveSSOSession(session);
13778
+ console.log(import_chalk20.default.green("\u2705 Successfully logged in!"));
13779
+ console.log(import_chalk20.default.gray(` User: ${session.email}`));
13780
+ console.log(import_chalk20.default.gray(` Provider: ${config.provider}`));
13781
+ console.log(import_chalk20.default.gray(" Session expires in 1 hour\n"));
13782
+ console.log(import_chalk20.default.gray("You can now run infra-cost commands"));
13783
+ }, 1e3);
13784
+ }
13785
+ __name(handleLogin, "handleLogin");
13786
+ async function handleLogout(options) {
13787
+ if (!isLoggedIn()) {
13788
+ console.log(import_chalk20.default.yellow("Not currently logged in"));
13789
+ return;
13790
+ }
13791
+ const user = getCurrentUser();
13792
+ clearSSOSession();
13793
+ console.log(import_chalk20.default.green(`\u2705 Logged out${user ? ` (${user})` : ""}`));
13794
+ }
13795
+ __name(handleLogout, "handleLogout");
13796
+ async function handleRefresh(options) {
13797
+ const session = loadSSOSession();
13798
+ if (!session) {
13799
+ console.error(import_chalk20.default.red("Error: Not logged in"));
13800
+ console.log(import_chalk20.default.gray("\nRun `infra-cost login --sso` to authenticate"));
13801
+ process.exit(1);
13802
+ }
13803
+ console.log(import_chalk20.default.blue("\u{1F504} Refreshing SSO session...\n"));
13804
+ setTimeout(() => {
13805
+ const refreshedSession = {
13806
+ ...session,
13807
+ expiresAt: new Date(Date.now() + 3600 * 1e3).toISOString()
13808
+ // 1 hour
13809
+ };
13810
+ saveSSOSession(refreshedSession);
13811
+ console.log(import_chalk20.default.green("\u2705 Session refreshed"));
13812
+ console.log(import_chalk20.default.gray(" New expiration: 1 hour from now\n"));
13813
+ }, 500);
13814
+ }
13815
+ __name(handleRefresh, "handleRefresh");
13816
+ function registerSSOCommands(program) {
13817
+ const sso = program.command("sso").description("SSO/SAML enterprise authentication");
13818
+ sso.command("configure").description("Configure SSO provider").option("--provider <name>", "SSO provider (okta, azure-ad, google)").option("--domain <domain>", "Okta domain (e.g., company.okta.com)").option("--tenant-id <id>", "Azure AD tenant ID").option("--client-id <id>", "OAuth client ID").option("--client-secret <secret>", "OAuth client secret").option("--issuer <url>", "OIDC issuer URL").action(handleConfigure);
13819
+ sso.command("status").description("Show SSO configuration and session status").action(handleStatus2);
13820
+ sso.command("refresh").description("Refresh SSO session").action(handleRefresh);
13821
+ program.command("login").description("Login with SSO").option("--sso", "Use SSO login").action(handleLogin);
13822
+ program.command("logout").description("Logout from SSO session").action(handleLogout);
13823
+ }
13824
+ __name(registerSSOCommands, "registerSSOCommands");
13825
+
13826
+ // src/cli/commands/plugin/index.ts
13827
+ var import_chalk21 = __toESM(require("chalk"));
13828
+
13829
+ // src/core/plugins.ts
13830
+ var import_path6 = require("path");
13831
+ var import_os6 = require("os");
13832
+ var PLUGIN_DIR = (0, import_path6.join)((0, import_os6.homedir)(), ".infra-cost", "plugins");
13833
+ var loadedPlugins = /* @__PURE__ */ new Map();
13834
+ function getLoadedPlugins() {
13835
+ return Array.from(loadedPlugins.values());
13836
+ }
13837
+ __name(getLoadedPlugins, "getLoadedPlugins");
13838
+
13839
+ // src/cli/commands/plugin/index.ts
13840
+ async function handleList3(options) {
13841
+ const plugins = getLoadedPlugins();
13842
+ console.log(import_chalk21.default.bold("\n\u{1F50C} Installed Plugins\n"));
13843
+ if (plugins.length === 0) {
13844
+ console.log(import_chalk21.default.yellow("No plugins installed"));
13845
+ console.log(import_chalk21.default.gray("\nPlugins should be installed in: ~/.infra-cost/plugins/"));
13846
+ return;
13847
+ }
13848
+ plugins.forEach((plugin) => {
13849
+ console.log(import_chalk21.default.bold(`${plugin.name} v${plugin.version}`));
13850
+ console.log(import_chalk21.default.gray(` ${plugin.description}`));
13851
+ if (plugin.author) {
13852
+ console.log(import_chalk21.default.gray(` Author: ${plugin.author}`));
13853
+ }
13854
+ console.log("");
13855
+ });
13856
+ }
13857
+ __name(handleList3, "handleList");
13858
+ async function handleInfo(options) {
13859
+ console.log(import_chalk21.default.bold("\n\u{1F50C} Plugin System Information\n"));
13860
+ console.log(import_chalk21.default.bold("Plugin Directory:"));
13861
+ console.log(import_chalk21.default.gray(" ~/.infra-cost/plugins/\n"));
13862
+ console.log(import_chalk21.default.bold("Plugin Structure:"));
13863
+ console.log(import_chalk21.default.gray(" my-plugin/"));
13864
+ console.log(import_chalk21.default.gray(" \u251C\u2500\u2500 package.json # Plugin metadata"));
13865
+ console.log(import_chalk21.default.gray(" \u2514\u2500\u2500 index.js # Plugin entry point\n"));
13866
+ console.log(import_chalk21.default.bold("Example package.json:"));
13867
+ console.log(import_chalk21.default.gray(" {"));
13868
+ console.log(import_chalk21.default.gray(' "name": "my-custom-plugin",'));
13869
+ console.log(import_chalk21.default.gray(' "version": "1.0.0",'));
13870
+ console.log(import_chalk21.default.gray(' "description": "Custom cost provider"'));
13871
+ console.log(import_chalk21.default.gray(" }\n"));
13872
+ console.log(import_chalk21.default.bold("Example index.js:"));
13873
+ console.log(import_chalk21.default.gray(" module.exports = {"));
13874
+ console.log(import_chalk21.default.gray(' name: "my-custom-plugin",'));
13875
+ console.log(import_chalk21.default.gray(' version: "1.0.0",'));
13876
+ console.log(import_chalk21.default.gray(' description: "Custom cost provider",'));
13877
+ console.log(import_chalk21.default.gray(' init: async () => { console.log("Plugin loaded!"); },'));
13878
+ console.log(import_chalk21.default.gray(" registerCommands: (program) => { /* ... */ }"));
13879
+ console.log(import_chalk21.default.gray(" };\n"));
13880
+ console.log(import_chalk21.default.gray("For documentation: https://github.com/codecollab-co/infra-cost#plugins"));
13881
+ }
13882
+ __name(handleInfo, "handleInfo");
13883
+ function registerPluginCommands(program) {
13884
+ const plugin = program.command("plugin").description("Manage custom plugins");
13885
+ plugin.command("list").description("List installed plugins").action(handleList3);
13886
+ plugin.command("info").description("Show plugin system information").action(handleInfo);
13887
+ }
13888
+ __name(registerPluginCommands, "registerPluginCommands");
13889
+
13124
13890
  // src/cli/middleware/auth.ts
13125
13891
  async function authMiddleware(thisCommand, actionCommand) {
13126
13892
  const isConfigCommand = actionCommand.name() === "config" || actionCommand.parent?.name() === "config";
@@ -13149,7 +13915,7 @@ async function validationMiddleware(thisCommand, actionCommand) {
13149
13915
  __name(validationMiddleware, "validationMiddleware");
13150
13916
 
13151
13917
  // src/cli/middleware/error-handler.ts
13152
- var import_chalk19 = __toESM(require("chalk"));
13918
+ var import_chalk22 = __toESM(require("chalk"));
13153
13919
  function errorHandler(error) {
13154
13920
  const message = error?.message ?? String(error);
13155
13921
  const stack = error?.stack;
@@ -13157,15 +13923,15 @@ function errorHandler(error) {
13157
13923
  return;
13158
13924
  }
13159
13925
  console.error("");
13160
- console.error(import_chalk19.default.red("\u2716"), import_chalk19.default.bold("Error:"), message);
13926
+ console.error(import_chalk22.default.red("\u2716"), import_chalk22.default.bold("Error:"), message);
13161
13927
  if (process.env.DEBUG || process.env.VERBOSE) {
13162
13928
  console.error("");
13163
13929
  if (stack) {
13164
- console.error(import_chalk19.default.gray(stack));
13930
+ console.error(import_chalk22.default.gray(stack));
13165
13931
  }
13166
13932
  } else {
13167
13933
  console.error("");
13168
- console.error(import_chalk19.default.gray("Run with --verbose for detailed error information"));
13934
+ console.error(import_chalk22.default.gray("Run with --verbose for detailed error information"));
13169
13935
  }
13170
13936
  console.error("");
13171
13937
  }
@@ -13191,6 +13957,9 @@ function createCLI() {
13191
13957
  registerGitCommands(program);
13192
13958
  registerTerraformCommand(program);
13193
13959
  registerSchedulerCommands(program);
13960
+ registerRBACCommands(program);
13961
+ registerSSOCommands(program);
13962
+ registerPluginCommands(program);
13194
13963
  program.hook("preAction", async (thisCommand, actionCommand) => {
13195
13964
  const opts = thisCommand.opts();
13196
13965
  const logLevel = opts.verbose ? "debug" : opts.quiet ? "error" : opts.logLevel;