azp-cli 1.2.0 → 1.4.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/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [1.4.0](https://github.com/tapanmeena/azp-cli/compare/v1.3.1...v1.4.0) (2026-01-15)
6
+
7
+
8
+ ### Features
9
+
10
+ * add presets management functionality to CLI ([253c66c](https://github.com/tapanmeena/azp-cli/commit/253c66c3691d31f7bc032c293b069667a6a148b5))
11
+ * add subscription reader support for role activation and deactivation ([182027b](https://github.com/tapanmeena/azp-cli/commit/182027be73accf0d9f3b13385b28da1701b50297))
12
+ * utilize cached update check result if within interval ([b4c999d](https://github.com/tapanmeena/azp-cli/commit/b4c999d37fcbbb23ba9d2f637833ba48dd7a38e9))
13
+
14
+ ### [1.3.1](https://github.com/tapanmeena/azp-cli/compare/v1.3.0...v1.3.1) (2026-01-14)
15
+
16
+ ## [1.3.0](https://github.com/tapanmeena/azp-cli/compare/v1.2.0...v1.3.0) (2026-01-14)
17
+
18
+
19
+ ### Features
20
+
21
+ * add Azure CLI installation check before authentication ([c291268](https://github.com/tapanmeena/azp-cli/commit/c291268359854e7da74a0cea046741c0449609d7))
22
+ * update package.json with repository type; rename CLI command to 'check-update' and adjust header formatting ([8ef92d4](https://github.com/tapanmeena/azp-cli/commit/8ef92d4da233459f7631f2bf4f85ed5a4113c918))
23
+ * update README with new features and installation instructions; refactor CLI command name to 'azp' ([24510f7](https://github.com/tapanmeena/azp-cli/commit/24510f78f44f9e1570d93c8b750aa6a41b27b62e))
24
+
5
25
  ## [1.2.0](https://github.com/tapanmeena/azp-cli/compare/v1.1.0...v1.2.0) (2026-01-13)
6
26
 
7
27
 
package/README.md CHANGED
@@ -14,6 +14,10 @@ A command-line interface tool for managing Azure Privileged Identity Management
14
14
  - ✨ **Beautiful UI** - Polished terminal experience with spinners and colors
15
15
  - 🔄 **Multi-role Support** - Activate or deactivate multiple roles at once
16
16
  - 📊 **Status Tracking** - Real-time feedback on activation/deactivation status
17
+ - 💾 **Presets** - Save and reuse activation/deactivation configurations
18
+ - 🚀 **Non-interactive Mode** - CLI flags for scripting and automation
19
+ - 🔔 **Update Notifications** - Automatic update checks with configurable behavior
20
+ - 📤 **JSON Output** - Machine-readable output for integration with other tools
17
21
 
18
22
  ## Prerequisites
19
23
 
@@ -38,7 +42,22 @@ az account show
38
42
 
39
43
  ## Installation
40
44
 
41
- ### From Source
45
+ ### Global Installation (Recommended)
46
+
47
+ ```bash
48
+ # Using npm
49
+ npm install -g azp-cli
50
+
51
+ # Using pnpm
52
+ pnpm add -g azp-cli
53
+
54
+ # Using yarn
55
+ yarn global add azp-cli
56
+ ```
57
+
58
+ After installation, the `azp` command will be available globally.
59
+
60
+ ### From Source (Development)
42
61
 
43
62
  ```bash
44
63
  # Clone the repository
@@ -47,13 +66,12 @@ cd azp-cli
47
66
 
48
67
  # Install dependencies
49
68
  pnpm install
50
- # or
51
- npm install
52
69
 
53
70
  # Build the project
54
71
  pnpm build
55
- # or
56
- npm run build
72
+
73
+ # Link globally for development
74
+ npm link
57
75
  ```
58
76
 
59
77
  ## Usage
@@ -61,24 +79,38 @@ npm run build
61
79
  ### Running the CLI
62
80
 
63
81
  ```bash
64
- # Development mode
65
- pnpm dev
66
- # or
67
- npm run dev
82
+ # After global installation
83
+ azp
84
+
85
+ # Or with specific commands
86
+ azp activate
87
+ azp deactivate
88
+ azp preset list
89
+ azp update
68
90
 
69
- # After building
70
- node dist/index.js
91
+ # Development mode (from source)
92
+ pnpm dev
71
93
  ```
72
94
 
73
95
  ### Commands
74
96
 
75
- | Command | Alias | Description |
76
- | ------------ | ----- | -------------------------------------- |
77
- | `activate` | `a` | Activate a role in Azure PIM (default) |
78
- | `deactivate` | `d` | Deactivate a role in Azure PIM |
79
- | `update` | - | Check for a newer version |
80
- | `preset` | - | Manage reusable presets |
81
- | `help` | - | Display help information |
97
+ | Command | Alias | Description |
98
+ | ------------ | --------- | -------------------------------------- |
99
+ | `activate` | `a` | Activate a role in Azure PIM (default) |
100
+ | `deactivate` | `d` | Deactivate a role in Azure PIM |
101
+ | `preset` | - | Manage reusable presets |
102
+ | `update` | `upgrade` | Check for a newer version |
103
+ | `help` | - | Display help information |
104
+
105
+ #### Preset Subcommands
106
+
107
+ | Command | Description |
108
+ | --------------- | -------------------------------------------- |
109
+ | `preset list` | List all available presets |
110
+ | `preset show` | Show details of a specific preset |
111
+ | `preset add` | Add a new preset (interactive wizard) |
112
+ | `preset edit` | Edit an existing preset (interactive wizard) |
113
+ | `preset remove` | Remove a preset |
82
114
 
83
115
  ### Updates
84
116
 
@@ -102,9 +134,11 @@ The update-check cache is stored alongside presets in your config directory:
102
134
  - macOS/Linux: `~/.config/azp-cli/update-check.json` (or `$XDG_CONFIG_HOME/azp-cli/update-check.json`)
103
135
  - Windows: `%APPDATA%\azp-cli\update-check.json`
104
136
 
105
- ### One-command (non-interactive) activation
137
+ ### Non-interactive Mode (Automation)
106
138
 
107
- Use flags to activate PIM roles directly without going through the main menu.
139
+ Use flags to activate or deactivate PIM roles directly without going through the interactive menu, perfect for scripting and CI/CD workflows.
140
+
141
+ #### Activation Examples
108
142
 
109
143
  ```bash
110
144
  # Activate a single role by name (non-interactive)
@@ -135,6 +169,43 @@ azp activate --no-interactive --dry-run \
135
169
  --output json
136
170
  ```
137
171
 
172
+ #### Deactivation Examples
173
+
174
+ ```bash
175
+ # Deactivate specific roles
176
+ azp deactivate --no-interactive --yes \
177
+ --subscription-id <SUBSCRIPTION_GUID> \
178
+ --role-name "Owner" \
179
+ --justification "Task completed"
180
+
181
+ # Deactivate across all subscriptions (omit subscription-id)
182
+ azp deactivate --no-interactive --yes \
183
+ --role-name "Contributor" \
184
+ --allow-multiple
185
+ ```
186
+
187
+ #### Available Flags
188
+
189
+ **Common flags (activate/deactivate):**
190
+
191
+ - `--no-interactive` - Disable interactive prompts
192
+ - `-y, --yes` - Skip confirmation prompts
193
+ - `--subscription-id <id>` - Target subscription (optional for deactivate)
194
+ - `--role-name <name>` - Role name(s) to target (can be repeated)
195
+ - `--allow-multiple` - Allow multiple role matches
196
+ - `--dry-run` - Preview without submitting
197
+ - `--output <text|json>` - Output format (default: text)
198
+ - `--quiet` - Suppress non-essential output
199
+
200
+ **Activation-specific:**
201
+
202
+ - `--duration-hours <n>` - Duration (1-8 hours, default varies by role)
203
+ - `--justification <text>` - Justification for activation
204
+
205
+ **Deactivation-specific:**
206
+
207
+ - `--justification <text>` - Justification for deactivation (optional)
208
+
138
209
  ## Presets
139
210
 
140
211
  Presets let you save your daily activation/deactivation routines (subscription + role names + duration + justification) and reuse them with `--preset <name>`.
@@ -163,24 +234,27 @@ A preset can define one or both blocks:
163
234
  - `${datetime}` → ISO timestamp
164
235
  - `${userPrincipalName}` → resolved from Microsoft Graph `/me`
165
236
 
166
- ### Common workflows
237
+ ### Common Workflows
167
238
 
168
239
  ```bash
169
240
  # Create a preset (interactive wizard)
170
241
  azp preset add daily-ops
171
242
 
243
+ # Create a preset with Azure integration (fetches subscriptions/roles)
244
+ azp preset add daily-ops --from-azure
245
+
172
246
  # Edit a preset (interactive wizard)
173
247
  azp preset edit daily-ops
174
248
 
175
- # You can also re-run add to overwrite an existing preset
176
- azp preset add daily-ops
177
-
178
- # List presets
249
+ # List all presets
179
250
  azp preset list
180
251
 
181
- # Show one preset
252
+ # Show one preset details
182
253
  azp preset show daily-ops
183
254
 
255
+ # Remove a preset
256
+ azp preset remove daily-ops
257
+
184
258
  # Use a preset (flags still override preset values)
185
259
  azp activate --preset daily-ops --yes
186
260
 
@@ -293,6 +367,8 @@ git push --follow-tags
293
367
  4. Publish to npm (if desired):
294
368
 
295
369
  ```bash
370
+ npm publish
371
+ # or
296
372
  pnpm publish
297
373
  ```
298
374
 
@@ -301,11 +377,14 @@ pnpm publish
301
377
  ```
302
378
  azp-cli/
303
379
  ├── src/
304
- │ ├── index.ts # CLI entry point and command definitions
305
- │ ├── auth.ts # Azure authentication handling
306
- │ ├── azure-pim.ts # Azure PIM API operations
307
- │ ├── cli.ts # Interactive menu and user flows
308
- └── ui.ts # Terminal UI utilities (spinners, formatting)
380
+ │ ├── index.ts # CLI entry point and command definitions
381
+ │ ├── auth.ts # Azure authentication handling
382
+ │ ├── azure-pim.ts # Azure PIM API operations
383
+ │ ├── cli.ts # Interactive menu and user flows
384
+ ├── presets.ts # Preset configuration and storage
385
+ │ ├── presets-cli.ts # Preset wizard flows
386
+ │ ├── ui.ts # Terminal UI utilities (spinners, formatting)
387
+ │ └── update-check.ts # Update notification system
309
388
  ├── package.json
310
389
  ├── tsconfig.json
311
390
  └── README.md
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAQ3D,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAWD,eAAO,MAAM,YAAY,QAAa,OAAO,CAAC,WAAW,CAgCxD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAU,YAAY,kBAAkB,KAAG,OAAO,CAAC,MAAM,CAMhF,CAAC"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAU3D,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,kBAAkB,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAqBD,eAAO,MAAM,YAAY,QAAa,OAAO,CAAC,WAAW,CA8BxD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAU,YAAY,kBAAkB,KAAG,OAAO,CAAC,MAAM,CAMhF,CAAC"}
package/dist/auth.js CHANGED
@@ -4,6 +4,8 @@ exports.getArmToken = exports.authenticate = void 0;
4
4
  const identity_1 = require("@azure/identity");
5
5
  const microsoft_graph_client_1 = require("@microsoft/microsoft-graph-client");
6
6
  const azureTokenCredentials_1 = require("@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials");
7
+ const child_process_1 = require("child_process");
8
+ const util_1 = require("util");
7
9
  const ui_1 = require("./ui");
8
10
  const GRAPH_SCOPES = ["https://graph.microsoft.com/.default"];
9
11
  const ARM_SCOPES = ["https://management.azure.com/.default"];
@@ -14,7 +16,17 @@ const getCredential = () => {
14
16
  }
15
17
  return cachedCredential;
16
18
  };
19
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
20
+ const checkAzureCliInstalled = async () => {
21
+ try {
22
+ await execAsync("az --version");
23
+ }
24
+ catch (error) {
25
+ throw new Error("Azure CLI is not installed or not in PATH. Please install it to use this tool.");
26
+ }
27
+ };
17
28
  const authenticate = async () => {
29
+ await checkAzureCliInstalled();
18
30
  (0, ui_1.startSpinner)("Authenticating with Azure CLI...");
19
31
  try {
20
32
  const credential = getCredential();
@@ -24,10 +36,7 @@ const authenticate = async () => {
24
36
  const userId = user.id;
25
37
  const userPrincipalName = user.userPrincipalName;
26
38
  (0, ui_1.succeedSpinner)("Authentication successful");
27
- (0, ui_1.showSummary)("User Information", [
28
- { label: "Name", value: user.displayName },
29
- { label: "Email", value: userPrincipalName },
30
- ]);
39
+ (0, ui_1.showUserInfo)(user.displayName, userPrincipalName);
31
40
  return {
32
41
  credential,
33
42
  graphClient,
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":";;;AAAA,8CAAqD;AACrD,8EAA2D;AAC3D,iHAA8H;AAC9H,6BAA8E;AAE9E,MAAM,YAAY,GAAG,CAAC,sCAAsC,CAAC,CAAC;AAE9D,MAAM,UAAU,GAAG,CAAC,uCAAuC,CAAC,CAAC;AAS7D,IAAI,gBAAgB,GAA8B,IAAI,CAAC;AAEvD,MAAM,aAAa,GAAG,GAAuB,EAAE;IAC7C,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,gBAAgB,GAAG,IAAI,6BAAkB,EAAE,CAAC;IAC9C,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC,CAAC;AAEK,MAAM,YAAY,GAAG,KAAK,IAA0B,EAAE;IAC3D,IAAA,iBAAY,EAAC,kCAAkC,CAAC,CAAC;IAEjD,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;QAGnC,MAAM,YAAY,GAAG,IAAI,6DAAqC,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;QAErG,MAAM,WAAW,GAAG,+BAAM,CAAC,kBAAkB,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;QAGxF,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,kCAAkC,CAAC,CAAC,GAAG,EAAE,CAAC;QAC9H,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAC;QAEjD,IAAA,mBAAc,EAAC,2BAA2B,CAAC,CAAC;QAC5C,IAAA,gBAAW,EAAC,kBAAkB,EAAE;YAC9B,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE;YAC1C,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE;SAC7C,CAAC,CAAC;QAEH,OAAO;YACL,UAAU;YACV,WAAW;YACX,MAAM;YACN,iBAAiB;SAClB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAA,gBAAW,EAAC,uBAAuB,CAAC,CAAC;QACrC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAhCW,QAAA,YAAY,gBAgCvB;AAEK,MAAM,WAAW,GAAG,KAAK,EAAE,UAA8B,EAAmB,EAAE;IACnF,MAAM,aAAa,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC5D,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,aAAa,CAAC,KAAK,CAAC;AAC7B,CAAC,CAAC;AANW,QAAA,WAAW,eAMtB"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":";;;AAAA,8CAAqD;AACrD,8EAA2D;AAC3D,iHAA8H;AAC9H,iDAAqC;AACrC,+BAAiC;AACjC,6BAA+E;AAE/E,MAAM,YAAY,GAAG,CAAC,sCAAsC,CAAC,CAAC;AAE9D,MAAM,UAAU,GAAG,CAAC,uCAAuC,CAAC,CAAC;AAS7D,IAAI,gBAAgB,GAA8B,IAAI,CAAC;AAEvD,MAAM,aAAa,GAAG,GAAuB,EAAE;IAC7C,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,gBAAgB,GAAG,IAAI,6BAAkB,EAAE,CAAC;IAC9C,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,IAAA,gBAAS,EAAC,oBAAI,CAAC,CAAC;AAElC,MAAM,sBAAsB,GAAG,KAAK,IAAmB,EAAE;IACvD,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,cAAc,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,gFAAgF,CAAC,CAAC;IACpG,CAAC;AACH,CAAC,CAAC;AAEK,MAAM,YAAY,GAAG,KAAK,IAA0B,EAAE;IAC3D,MAAM,sBAAsB,EAAE,CAAC;IAC/B,IAAA,iBAAY,EAAC,kCAAkC,CAAC,CAAC;IAEjD,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;QAGnC,MAAM,YAAY,GAAG,IAAI,6DAAqC,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;QAErG,MAAM,WAAW,GAAG,+BAAM,CAAC,kBAAkB,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;QAGxF,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,kCAAkC,CAAC,CAAC,GAAG,EAAE,CAAC;QAC9H,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAC;QAEjD,IAAA,mBAAc,EAAC,2BAA2B,CAAC,CAAC;QAC5C,IAAA,iBAAY,EAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;QAElD,OAAO;YACL,UAAU;YACV,WAAW;YACX,MAAM;YACN,iBAAiB;SAClB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAA,gBAAW,EAAC,uBAAuB,CAAC,CAAC;QACrC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AA9BW,QAAA,YAAY,gBA8BvB;AAEK,MAAM,WAAW,GAAG,KAAK,EAAE,UAA8B,EAAmB,EAAE;IACnF,MAAM,aAAa,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC5D,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,aAAa,CAAC,KAAK,CAAC;AAC7B,CAAC,CAAC;AANW,QAAA,WAAW,eAMtB"}
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ const chalk_1 = __importDefault(require("chalk"));
8
8
  const inquirer_1 = __importDefault(require("inquirer"));
9
9
  const azure_pim_1 = require("./azure-pim");
10
10
  const ui_1 = require("./ui");
11
+ const presets_cli_1 = require("./presets-cli");
11
12
  const normalizeRoleName = (value) => value.trim().toLowerCase();
12
13
  const validateDurationHours = (value) => {
13
14
  if (!Number.isFinite(value) || value < 1 || value > 8) {
@@ -30,11 +31,10 @@ const activateOnce = async (authContext, options) => {
30
31
  }
31
32
  (0, ui_1.logBlank)();
32
33
  (0, ui_1.logInfo)("Resolving subscription and eligible roles...");
33
- const subscriptions = await (0, azure_pim_1.fetchSubscriptions)(authContext.credential);
34
- const selectedSubscription = subscriptions.find((s) => s.subscriptionId === options.subscriptionId);
35
- if (!selectedSubscription) {
36
- throw new Error(`Subscription not found for --subscription-id=${options.subscriptionId}`);
37
- }
34
+ const selectedSubscription = {
35
+ subscriptionId: options.subscriptionId,
36
+ displayName: `${options.subscriptionId} (name unavailable)`,
37
+ };
38
38
  const eligibleRoles = await (0, azure_pim_1.fetchEligibleRolesForSubscription)(authContext.credential, selectedSubscription.subscriptionId, selectedSubscription.displayName, authContext.userId);
39
39
  const eligibleByName = new Map();
40
40
  for (const role of eligibleRoles) {
@@ -219,17 +219,21 @@ const deactivateOnce = async (authContext, options) => {
219
219
  }
220
220
  (0, ui_1.logBlank)();
221
221
  (0, ui_1.logInfo)("Resolving subscriptions and active roles...");
222
- const subscriptions = await (0, azure_pim_1.fetchSubscriptions)(authContext.credential);
223
- if (subscriptions.length === 0) {
224
- throw new Error("No subscriptions found.");
225
- }
226
- let targetSubscriptions = subscriptions;
222
+ let targetSubscriptions;
227
223
  if (options.subscriptionId?.trim()) {
228
- const selectedSubscription = subscriptions.find((s) => s.subscriptionId === options.subscriptionId);
229
- if (!selectedSubscription) {
230
- throw new Error(`Subscription not found for --subscription-id=${options.subscriptionId}`);
224
+ targetSubscriptions = [
225
+ {
226
+ subscriptionId: options.subscriptionId,
227
+ displayName: `${options.subscriptionId} (name unavailable)`,
228
+ },
229
+ ];
230
+ }
231
+ else {
232
+ const subscriptions = await (0, azure_pim_1.fetchSubscriptions)(authContext.credential);
233
+ if (subscriptions.length === 0) {
234
+ throw new Error("No subscriptions found. use --subscription-id to specify a subscription.");
231
235
  }
232
- targetSubscriptions = [selectedSubscription];
236
+ targetSubscriptions = subscriptions;
233
237
  }
234
238
  let allActiveRoles = [];
235
239
  for (const sub of targetSubscriptions) {
@@ -443,6 +447,7 @@ const showMainMenu = async (authContext) => {
443
447
  choices: [
444
448
  { name: chalk_1.default.green("▶ Activate Role(s)"), value: "activate" },
445
449
  { name: chalk_1.default.yellow("◼ Deactivate Role(s)"), value: "deactivate" },
450
+ { name: chalk_1.default.magenta("⚙ Presets..."), value: "presets" },
446
451
  { name: chalk_1.default.red("✕ Exit"), value: "exit" },
447
452
  ],
448
453
  default: "activate",
@@ -455,6 +460,9 @@ const showMainMenu = async (authContext) => {
455
460
  case "deactivate":
456
461
  await (0, exports.handleDeactivation)(authContext);
457
462
  break;
463
+ case "presets":
464
+ await (0, presets_cli_1.runPresetsManager)();
465
+ break;
458
466
  case "exit":
459
467
  (0, ui_1.logBlank)();
460
468
  (0, ui_1.logDim)("Goodbye! 👋");
@@ -469,38 +477,76 @@ const handleActivation = async (authContext) => {
469
477
  (0, ui_1.logBlank)();
470
478
  (0, ui_1.logInfo)("Starting role activation flow...");
471
479
  (0, ui_1.logBlank)();
472
- const subscriptions = await (0, azure_pim_1.fetchSubscriptions)(authContext.credential);
480
+ let subscriptions = await (0, azure_pim_1.fetchSubscriptions)(authContext.credential);
481
+ let selectedSubscription;
473
482
  if (subscriptions.length === 0) {
474
483
  (0, ui_1.logWarning)("No subscriptions found.");
475
- await promptBackToMainMenuOrExit("What would you like to do?");
476
- return;
477
- }
478
- const BACK_VALUE = "__BACK__";
479
- const subscriptionChoices = subscriptions
480
- .map((sub) => ({
481
- name: (0, ui_1.formatSubscription)(sub.displayName, sub.subscriptionId),
482
- value: sub.subscriptionId,
483
- }))
484
- .concat([{ name: chalk_1.default.dim("↩ Back to Main Menu"), value: BACK_VALUE }]);
485
- (0, ui_1.logBlank)();
486
- const { selectedSubscriptionId } = await inquirer_1.default.prompt([
487
- {
488
- type: "select",
489
- name: "selectedSubscriptionId",
490
- message: chalk_1.default.cyan("Select a subscription:"),
491
- choices: subscriptionChoices,
492
- pageSize: 15,
493
- default: subscriptionChoices[0]?.value,
494
- },
495
- ]);
496
- if (selectedSubscriptionId === BACK_VALUE) {
497
- (0, ui_1.logDim)("Returning to main menu...");
498
- return;
484
+ const { action } = await inquirer_1.default.prompt([
485
+ {
486
+ type: "select",
487
+ name: "action",
488
+ message: chalk_1.default.yellow("No subscriptions found. What would you like to do?"),
489
+ choices: [
490
+ { name: chalk_1.default.cyan("Enter subscription ID manually"), value: "enter" },
491
+ { name: chalk_1.default.cyan("↩ Back to Main Menu"), value: "back" },
492
+ { name: chalk_1.default.red("✕ Exit"), value: "exit" },
493
+ ],
494
+ default: "enter",
495
+ },
496
+ ]);
497
+ if (action === "back") {
498
+ return;
499
+ }
500
+ else if (action === "exit") {
501
+ (0, ui_1.logBlank)();
502
+ (0, ui_1.logDim)("Goodbye! 👋");
503
+ process.exit(0);
504
+ }
505
+ else {
506
+ const { manualId } = await inquirer_1.default.prompt([
507
+ {
508
+ type: "input",
509
+ name: "manualId",
510
+ message: chalk_1.default.cyan("Subscription ID:"),
511
+ validate: (value) => {
512
+ if (!value || !value.trim())
513
+ return chalk_1.default.red("Please enter a subscription ID.");
514
+ return true;
515
+ },
516
+ },
517
+ ]);
518
+ selectedSubscription = { subscriptionId: manualId.trim(), displayName: manualId.trim() };
519
+ }
499
520
  }
500
- const selectedSubscription = subscriptions.find((sub) => sub.subscriptionId === selectedSubscriptionId);
501
- if (!selectedSubscription) {
502
- (0, ui_1.logError)("Selected subscription not found.");
503
- return;
521
+ else {
522
+ const BACK_VALUE = "__BACK__";
523
+ const subscriptionChoices = subscriptions
524
+ .map((sub) => ({
525
+ name: (0, ui_1.formatSubscription)(sub.displayName, sub.subscriptionId),
526
+ value: sub.subscriptionId,
527
+ }))
528
+ .concat([{ name: chalk_1.default.dim("↩ Back to Main Menu"), value: BACK_VALUE }]);
529
+ (0, ui_1.logBlank)();
530
+ const { selectedSubscriptionId } = await inquirer_1.default.prompt([
531
+ {
532
+ type: "select",
533
+ name: "selectedSubscriptionId",
534
+ message: chalk_1.default.cyan("Select a subscription:"),
535
+ choices: subscriptionChoices,
536
+ pageSize: 15,
537
+ default: subscriptionChoices[0]?.value,
538
+ },
539
+ ]);
540
+ if (selectedSubscriptionId === BACK_VALUE) {
541
+ (0, ui_1.logDim)("Returning to main menu...");
542
+ return;
543
+ }
544
+ const found = subscriptions.find((sub) => sub.subscriptionId === selectedSubscriptionId);
545
+ if (!found) {
546
+ (0, ui_1.logError)("Selected subscription not found.");
547
+ return;
548
+ }
549
+ selectedSubscription = { subscriptionId: found.subscriptionId, displayName: found.displayName };
504
550
  }
505
551
  const eligibleRoles = await (0, azure_pim_1.fetchEligibleRolesForSubscription)(authContext.credential, selectedSubscription.subscriptionId, selectedSubscription.displayName, authContext.userId);
506
552
  if (eligibleRoles.length === 0) {
@@ -631,12 +677,46 @@ const handleDeactivation = async (authContext) => {
631
677
  (0, ui_1.logBlank)();
632
678
  (0, ui_1.logInfo)("Starting role deactivation flow...");
633
679
  (0, ui_1.logBlank)();
634
- const subscriptions = await (0, azure_pim_1.fetchSubscriptions)(authContext.credential);
680
+ let subscriptions = await (0, azure_pim_1.fetchSubscriptions)(authContext.credential);
635
681
  let activeAzureRoles = [];
636
682
  if (subscriptions.length === 0) {
637
683
  (0, ui_1.logWarning)("No subscriptions found.");
638
- await promptBackToMainMenuOrExit("What would you like to do?");
639
- return;
684
+ const { action } = await inquirer_1.default.prompt([
685
+ {
686
+ type: "select",
687
+ name: "action",
688
+ message: chalk_1.default.yellow("No subscriptions found. What would you like to do?"),
689
+ choices: [
690
+ { name: chalk_1.default.cyan("Enter subscription ID manually"), value: "enter" },
691
+ { name: chalk_1.default.cyan("↩ Back to Main Menu"), value: "back" },
692
+ { name: chalk_1.default.red("✕ Exit"), value: "exit" },
693
+ ],
694
+ default: "enter",
695
+ },
696
+ ]);
697
+ if (action === "back") {
698
+ return;
699
+ }
700
+ else if (action === "exit") {
701
+ (0, ui_1.logBlank)();
702
+ (0, ui_1.logDim)("Goodbye! 👋");
703
+ process.exit(0);
704
+ }
705
+ else {
706
+ const { manualId } = await inquirer_1.default.prompt([
707
+ {
708
+ type: "input",
709
+ name: "manualId",
710
+ message: chalk_1.default.cyan("Subscription ID to inspect (for active roles):"),
711
+ validate: (value) => {
712
+ if (!value || !value.trim())
713
+ return chalk_1.default.red("Please enter a subscription ID.");
714
+ return true;
715
+ },
716
+ },
717
+ ]);
718
+ subscriptions.push({ subscriptionId: manualId.trim(), displayName: manualId.trim(), tenantId: "" });
719
+ }
640
720
  }
641
721
  for (const sub of subscriptions) {
642
722
  const roles = await (0, azure_pim_1.listActiveAzureRoles)(authContext.credential, sub.subscriptionId, sub.displayName, authContext.userId);