@aifabrix/builder 2.36.2 → 2.37.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/README.md CHANGED
@@ -1,9 +1,13 @@
1
- # AI Fabrix - Builder SDK
1
+ # AI Fabrix - Builder
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@aifabrix/builder.svg)](https://www.npmjs.com/package/@aifabrix/builder)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- Local development infrastructure + Azure deployment tool.
6
+ Install the AI Fabrix platform and test it locally. Then add external integrations or build your own applications.
7
+
8
+ ← **Full documentation:** [docs/README.md](docs/README.md) (table of contents for all guides)
9
+
10
+ ---
7
11
 
8
12
  ## Install
9
13
 
@@ -11,126 +15,86 @@ Local development infrastructure + Azure deployment tool.
11
15
  npm install -g @aifabrix/builder
12
16
  ```
13
17
 
14
- ## Quick Start
18
+ **Alias:** You can use `aifx` instead of `aifabrix` in any command.
15
19
 
16
- ```bash
17
- aifabrix up # Start Postgres + Redis
18
- aifabrix create myapp # Create your app
19
- aifabrix build myapp # Build Docker image
20
- aifabrix run myapp # Run locally
21
- # Stop the app (optionally remove its data volume)
22
- aifabrix down myapp
23
- # aifabrix down myapp --volumes
24
- ```
20
+ ---
25
21
 
26
- [Full Guide](docs/quick-start.md) | [CLI Commands](docs/cli-reference.md)
22
+ ## Goal 1: Start and test the AI Fabrix platform
27
23
 
28
- ## What You Get
24
+ Get the platform running locally so you can try it.
29
25
 
30
- - **Local Postgres + Redis infrastructure** - Runs in Docker
31
- - **Auto-generated Dockerfiles** - TypeScript and Python templates
32
- - **Environment variable management** - Secret resolution with kv:// references
33
- - **Azure deployment pipeline** - Push to ACR and deploy via controller
26
+ 1. **Start local infrastructure** (Postgres, Redis, optional Traefik):
34
27
 
35
- ## Optional Platform Apps
28
+ ```bash
29
+ aifabrix up-infra
30
+ ```
36
31
 
37
- Want authentication or deployment controller?
32
+ 2. **Start the platform** (Keycloak, Miso Controller, Dataplane) from community images:
38
33
 
39
- **Quick install from images (no build):**
40
- ```bash
41
- aifabrix up # Start Postgres + Redis first
42
- aifabrix up-miso # Install Keycloak + Miso Controller from images (auto-generated secrets for testing)
43
- ```
34
+ ```bash
35
+ aifabrix up-platform
36
+ ```
44
37
 
45
- **Or create and build from templates:**
46
- ```bash
47
- # Keycloak for authentication
48
- aifabrix create keycloak --port 8082 --database --template keycloak
49
- aifabrix build keycloak
50
- aifabrix run keycloak
51
-
52
- # Miso Controller for Azure deployments
53
- aifabrix create miso-controller --port 3000 --database --redis --template miso-controller
54
- aifabrix build miso-controller
55
- aifabrix run miso-controller
56
- ```
38
+ Or run platform apps separately: `aifabrix up-miso` then `aifabrix up-dataplane`. Infra must be up first.
57
39
 
58
- **Dataplane in dev (after login):**
59
- ```bash
60
- aifabrix login --environment dev
61
- aifabrix up-dataplane # Register or rotate, run, and deploy dataplane in dev
62
- ```
40
+ 3. **Configure secrets** You need either **OpenAI** or **Azure OpenAI**:
41
+
42
+ - **OpenAI:** set your API key:
43
+ ```bash
44
+ aifabrix secrets set secrets-openaiApiKeyVault <your-openai-secret-key>
45
+ ```
46
+ - **Azure OpenAI:** set endpoint and API key:
47
+ ```bash
48
+ aifabrix secrets set azure-openaiapi-urlKeyVault <your-azure-openai-endpoint-url>
49
+ aifabrix secrets set secrets-azureOpenaiApiKeyVault <your-azure-openai-secret-key>
50
+ ```
51
+
52
+ Secrets are stored in `~/.aifabrix/secrets.local.yaml` or the file from `aifabrix-secrets` in your config (e.g. `builder/secrets.local.yaml`).
53
+
54
+ → [Infrastructure guide](docs/infrastructure.md)
63
55
 
64
- → [Infrastructure Guide](docs/infrastructure.md)
56
+ ---
57
+
58
+ ## Goal 2: External system integration
59
+
60
+ Create and deploy an external system (e.g. HubSpot): wizard or manual setup, then validate and deploy.
61
+
62
+ **Example: HubSpot**
63
+
64
+ - Create: `aifabrix create hubspot-test --type external` (or `aifabrix wizard` for guided setup).
65
+ - Configure auth and datasources under `integration/hubspot-test/`.
66
+ - Validate: `aifabrix validate hubspot-test`
67
+ - Deploy: `aifabrix deploy hubspot-test`
68
+
69
+ → [External systems guide](docs/external-systems.md) · [Wizard](docs/wizard.md)
70
+
71
+ ---
72
+
73
+ ## Goal 3: Build your own application
74
+
75
+ Create, configure, and run your own AI Fabrix application locally or deploy it (create app → configure → build → run / deploy).
76
+
77
+ → [Your own applications](docs/your-own-applications.md)
78
+
79
+ ---
65
80
 
66
81
  ## Documentation
67
82
 
68
- - [Quick Start](docs/quick-start.md) - Get running in 5 minutes
69
- - [Infrastructure](docs/infrastructure.md) - What runs and why
70
- - [Configuration](docs/configuration.md) - Config file reference
71
- - [Building](docs/building.md) - Build process explained
72
- - [Running](docs/running.md) - Run apps locally
73
- - [Deploying](docs/deploying.md) - Deploy to Azure
74
- - [CLI Reference](docs/cli-reference.md) - All commands
75
-
76
- ## How It Works
77
-
78
- 1. **Infrastructure** - Minimal baseline (Postgres + Redis)
79
- 2. **Create** - Generate config files for your app
80
- 3. **Build** - Auto-detect runtime and build Docker image
81
- 4. **Run** - Start locally, connected to infrastructure
82
- 5. **Deploy** - Push to ACR and deploy via controller
83
-
84
- ```mermaid
85
- %%{init: {
86
- "theme": "base",
87
- "themeVariables": {
88
- "fontFamily": "Poppins, Arial Rounded MT Bold, Arial, sans-serif",
89
- "fontSize": "16px",
90
- "background": "#FFFFFF",
91
- "primaryColor": "#F8FAFC",
92
- "primaryTextColor": "#0B0E15",
93
- "primaryBorderColor": "#E2E8F0",
94
- "lineColor": "#E2E8F0",
95
- "textColor": "#0B0E15",
96
- "borderRadius": 16
97
- },
98
- "flowchart": {
99
- "curve": "linear",
100
- "nodeSpacing": 34,
101
- "rankSpacing": 34,
102
- "padding": 10
103
- }
104
- }}%%
105
-
106
- flowchart TD
107
-
108
- %% =======================
109
- %% Styles
110
- %% =======================
111
- classDef base fill:#FFFFFF,color:#0B0E15,stroke:#E2E8F0,stroke-width:1.5px;
112
- classDef primary fill:#0062FF,color:#ffffff,stroke-width:0px;
113
-
114
- %% =======================
115
- %% Flow
116
- %% =======================
117
- Install[Install CLI]:::primary --> Up[Start Infrastructure]:::base
118
- Up --> Create[Create App]:::base
119
- Create --> Build[Build Image]:::base
120
- Build --> Run[Run Locally]:::base
121
- Run --> Deploy[Deploy to Azure]:::primary
122
- ```
83
+ All guides and references are listed in **[docs/README.md](docs/README.md)** (table of contents).
123
84
 
124
- ## Development
85
+ - [CLI Reference](docs/cli-reference.md) – All commands
86
+ - [Infrastructure](docs/infrastructure.md) – What runs and why
87
+ - [Configuration](docs/configuration.md) – Config files
125
88
 
126
- - **Tests**: `npm test` (runs via wrapper; handles known Jest/Node exit issues).
127
- - **Coverage**: `npm run test:coverage` — runs tests with coverage through the same wrapper. May take 3–5 minutes for the full suite. If the process exits with a signal after "Ran all test suites", the wrapper treats it as success and coverage is written to `coverage/`. Use `test:coverage:nyc` only if you need nyc-specific reporters.
89
+ ---
128
90
 
129
91
  ## Requirements
130
92
 
131
- - **Docker Desktop** - For running containers
132
- - **Node.js 18+** - For running the CLI
133
- - **Azure CLI** - For deploying to Azure (optional)
93
+ - **Docker Desktop** For running containers
94
+ - **Node.js 18+** For running the CLI
95
+ - **Azure CLI** For deploying to Azure (optional)
96
+
97
+ ---
134
98
 
135
99
  ## License
136
100
 
@@ -50,7 +50,7 @@ function displayWebappSuccess(appName, config, envConversionMessage) {
50
50
 
51
51
  logger.log(chalk.green('\nNext steps:'));
52
52
  logger.log(chalk.white('1. Copy env.template to .env and fill in your values'));
53
- logger.log(chalk.white('2. Run: aifabrix up'));
53
+ logger.log(chalk.white('2. Run: aifabrix up-infra'));
54
54
  logger.log(chalk.white('3. Run: aifabrix build ' + appName));
55
55
  logger.log(chalk.white('4. Run: aifabrix run ' + appName));
56
56
  }
@@ -177,7 +177,7 @@ async function checkPrerequisites(appName, appConfig, debug = false, skipInfraCh
177
177
  .map(([service, _]) => service);
178
178
 
179
179
  if (unhealthyServices.length > 0) {
180
- throw new Error(`Infrastructure services not healthy: ${unhealthyServices.join(', ')}\nRun 'aifabrix up' first`);
180
+ throw new Error(`Infrastructure services not healthy: ${unhealthyServices.join(', ')}\nRun 'aifabrix up-infra' first`);
181
181
  }
182
182
  logger.log(chalk.green('✓ Infrastructure is running'));
183
183
  }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * AI Fabrix Builder CLI Command Definitions
3
+ *
4
+ * This module wires all CLI command setup and re-exports setupCommands,
5
+ * validateCommand, and handleCommandError for backward compatibility.
6
+ *
7
+ * @fileoverview CLI entry for AI Fabrix Builder; command setup orchestration
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ const { validateCommand, handleCommandError } = require('../utils/cli-utils');
13
+ const { setupAuthCommands } = require('./setup-auth');
14
+ const { setupInfraCommands } = require('./setup-infra');
15
+ const { setupAppCommands } = require('./setup-app');
16
+ const { setupEnvironmentCommands } = require('./setup-environment');
17
+ const { setupUtilityCommands } = require('./setup-utility');
18
+ const { setupDevCommands } = require('./setup-dev');
19
+ const { setupSecretsCommands } = require('./setup-secrets');
20
+ const { setupExternalSystemCommands } = require('./setup-external-system');
21
+ const { setupAppCommands: setupAppManagementCommands } = require('../commands/app');
22
+ const { setupDatasourceCommands } = require('../commands/datasource');
23
+
24
+ /**
25
+ * Sets up all CLI commands on the Commander program instance
26
+ * @param {Command} program - Commander program instance
27
+ */
28
+ function setupCommands(program) {
29
+ setupInfraCommands(program);
30
+ setupAuthCommands(program);
31
+ setupAppCommands(program);
32
+ setupEnvironmentCommands(program);
33
+ setupAppManagementCommands(program);
34
+ setupDatasourceCommands(program);
35
+ setupUtilityCommands(program);
36
+ setupExternalSystemCommands(program);
37
+ setupDevCommands(program);
38
+ setupSecretsCommands(program);
39
+ }
40
+
41
+ module.exports = {
42
+ setupCommands,
43
+ validateCommand,
44
+ handleCommandError
45
+ };
@@ -0,0 +1,229 @@
1
+ /**
2
+ * CLI application lifecycle command setup (create, wizard, build, run, push, deploy, dockerfile).
3
+ *
4
+ * @fileoverview Application command definitions for AI Fabrix Builder CLI
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const chalk = require('chalk');
10
+ const path = require('path');
11
+ const app = require('../app');
12
+ const logger = require('../utils/logger');
13
+ const { handleCommandError } = require('../utils/cli-utils');
14
+
15
+ /**
16
+ * Normalize options for external system creation
17
+ * @param {Object} options - Raw CLI options
18
+ * @returns {Object} Normalized options
19
+ */
20
+ function normalizeExternalOptions(options) {
21
+ const normalized = { ...options };
22
+ if (options.displayName) normalized.systemDisplayName = options.displayName;
23
+ if (options.description) normalized.systemDescription = options.description;
24
+ if (options.systemType) normalized.systemType = options.systemType;
25
+ if (options.authType) normalized.authType = options.authType;
26
+ if (options.datasources !== undefined) {
27
+ const parsedCount = parseInt(options.datasources, 10);
28
+ if (Number.isNaN(parsedCount) || parsedCount < 1 || parsedCount > 10) {
29
+ throw new Error('Datasources count must be a number between 1 and 10');
30
+ }
31
+ normalized.datasourceCount = parsedCount;
32
+ }
33
+ if (options.controller) {
34
+ normalized.controller = true;
35
+ normalized.controllerUrl = options.controller;
36
+ }
37
+ return normalized;
38
+ }
39
+
40
+ /**
41
+ * Validate required options for non-interactive external creation
42
+ * @param {Object} normalizedOptions - Normalized options
43
+ * @throws {Error} If required options are missing
44
+ */
45
+ function validateNonInteractiveExternalOptions(normalizedOptions) {
46
+ const missing = [];
47
+ if (!normalizedOptions.systemDisplayName) missing.push('--display-name');
48
+ if (!normalizedOptions.systemDescription) missing.push('--description');
49
+ if (!normalizedOptions.systemType) missing.push('--system-type');
50
+ if (!normalizedOptions.authType) missing.push('--auth-type');
51
+ if (!normalizedOptions.datasourceCount) missing.push('--datasources');
52
+ if (missing.length > 0) {
53
+ throw new Error(`Missing required options for non-interactive external create: ${missing.join(', ')}`);
54
+ }
55
+ if (!Object.prototype.hasOwnProperty.call(normalizedOptions, 'github')) {
56
+ normalizedOptions.github = false;
57
+ }
58
+ if (!Object.prototype.hasOwnProperty.call(normalizedOptions, 'controller')) {
59
+ normalizedOptions.controller = false;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Handle create command execution
65
+ * @async
66
+ * @param {string} appName - Application name
67
+ * @param {Object} options - CLI options
68
+ */
69
+ async function handleCreateCommand(appName, options) {
70
+ const validTypes = ['webapp', 'api', 'service', 'functionapp', 'external'];
71
+ if (options.type && !validTypes.includes(options.type)) {
72
+ throw new Error(`Invalid type: ${options.type}. Must be one of: ${validTypes.join(', ')}`);
73
+ }
74
+
75
+ const wizardOptions = { app: appName, ...options };
76
+ const normalizedOptions = normalizeExternalOptions(options);
77
+
78
+ const isExternalType = options.type === 'external';
79
+ const isNonInteractive = process.stdin && process.stdin.isTTY === false;
80
+
81
+ if (isExternalType && !options.wizard && isNonInteractive) {
82
+ validateNonInteractiveExternalOptions(normalizedOptions);
83
+ }
84
+
85
+ const shouldUseWizard = options.wizard && (options.type === 'external' || (!options.type && validTypes.includes('external')));
86
+ if (shouldUseWizard) {
87
+ const { handleWizard } = require('../commands/wizard');
88
+ await handleWizard(wizardOptions);
89
+ } else {
90
+ await app.createApp(appName, normalizedOptions);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Sets up application lifecycle commands
96
+ * @param {Command} program - Commander program instance
97
+ */
98
+ function setupAppCommands(program) {
99
+ program.command('create <app>')
100
+ .description('Create new application with configuration files')
101
+ .option('-p, --port <port>', 'Application port', '3000')
102
+ .option('-d, --database', 'Requires database')
103
+ .option('-r, --redis', 'Requires Redis')
104
+ .option('-s, --storage', 'Requires file storage')
105
+ .option('-a, --authentication', 'Requires authentication/RBAC')
106
+ .option('-l, --language <lang>', 'Runtime language (typescript/python)')
107
+ .option('-t, --template <name>', 'Template to use (e.g., miso-controller, keycloak)')
108
+ .option('--type <type>', 'Application type (webapp, api, service, functionapp, external)', 'webapp')
109
+ .option('--app', 'Generate minimal application files (package.json, index.ts or requirements.txt, main.py)')
110
+ .option('-g, --github', 'Generate GitHub Actions workflows')
111
+ .option('--github-steps <steps>', 'Extra GitHub workflow steps (comma-separated, e.g., npm,test)')
112
+ .option('--main-branch <branch>', 'Main branch name for workflows', 'main')
113
+ .option('--wizard', 'Use interactive wizard for external system creation')
114
+ .option('--display-name <name>', 'External system display name')
115
+ .option('--description <desc>', 'External system description')
116
+ .option('--system-type <type>', 'External system type (openapi, mcp, custom)')
117
+ .option('--auth-type <type>', 'External system auth type (oauth2, apikey, basic)')
118
+ .option('--datasources <count>', 'Number of datasources to create')
119
+ .action(async(appName, options) => {
120
+ try {
121
+ await handleCreateCommand(appName, options);
122
+ } catch (error) {
123
+ handleCommandError(error, 'create');
124
+ process.exit(1);
125
+ }
126
+ });
127
+
128
+ program.command('wizard [appName]')
129
+ .description('Create or extend external systems (OpenAPI, MCP, or known platforms like HubSpot) via guided steps or a config file')
130
+ .option('-a, --app <app>', 'Application name (synonym for positional appName)')
131
+ .option('--config <file>', 'Run headless using a wizard.yaml file (appName, mode, source, credential, preferences)')
132
+ .option('--silent', 'Run with saved integration/<app>/wizard.yaml only; no prompts (requires app name and existing wizard.yaml)')
133
+ .addHelpText('after', `
134
+ Examples:
135
+ $ aifabrix wizard Run interactively (mode first, then prompts)
136
+ $ aifabrix wizard my-integration Load wizard.yaml if present → show summary → "Run with saved config?" or start from step 1
137
+ $ aifabrix wizard my-integration --silent Run headless with integration/my-integration/wizard.yaml (no prompts)
138
+ $ aifabrix wizard -a my-integration Same as above (app name set)
139
+ $ aifabrix wizard --config wizard.yaml Run headless from a wizard config file
140
+
141
+ Config path: When appName is provided, integration/<appName>/wizard.yaml is used for load/save and error.log.
142
+ To change settings after a run, edit that file and run "aifabrix wizard <app>" again.
143
+ Headless config must include: appName, mode (create-system|add-datasource), source (type + filePath/url/platform).
144
+ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
145
+ .action(async(positionalAppName, options) => {
146
+ try {
147
+ const appName = positionalAppName || options.app;
148
+ const configPath = appName ? path.join(process.cwd(), 'integration', appName, 'wizard.yaml') : null;
149
+ const { handleWizard } = require('../commands/wizard');
150
+ await handleWizard({ ...options, app: appName, config: options.config, configPath });
151
+ } catch (error) {
152
+ handleCommandError(error, 'wizard');
153
+ process.exit(1);
154
+ }
155
+ });
156
+
157
+ program.command('build <app>')
158
+ .description('Build container image (auto-detects runtime)')
159
+ .option('-l, --language <lang>', 'Override language detection')
160
+ .option('-f, --force-template', 'Force rebuild from template')
161
+ .option('-t, --tag <tag>', 'Image tag (default: latest). Set image.tag in variables.yaml to match for deploy.')
162
+ .action(async(appName, options) => {
163
+ try {
164
+ const imageTag = await app.buildApp(appName, options);
165
+ logger.log(`✅ Built image: ${imageTag}`);
166
+ } catch (error) {
167
+ handleCommandError(error, 'build');
168
+ process.exit(1);
169
+ }
170
+ });
171
+
172
+ program.command('run <app>')
173
+ .description('Run application locally')
174
+ .option('-p, --port <port>', 'Override local port')
175
+ .option('-d, --debug', 'Enable debug output with detailed container information')
176
+ .action(async(appName, options) => {
177
+ try {
178
+ await app.runApp(appName, options);
179
+ } catch (error) {
180
+ handleCommandError(error, 'run');
181
+ process.exit(1);
182
+ }
183
+ });
184
+
185
+ program.command('push <app>')
186
+ .description('Push image to Azure Container Registry')
187
+ .option('-r, --registry <registry>', 'ACR registry URL (overrides variables.yaml)')
188
+ .option('-t, --tag <tag>', 'Image tag(s) - comma-separated for multiple (default: latest)')
189
+ .action(async(appName, options) => {
190
+ try {
191
+ await app.pushApp(appName, options);
192
+ } catch (error) {
193
+ handleCommandError(error, 'push');
194
+ process.exit(1);
195
+ }
196
+ });
197
+
198
+ program.command('deploy <app>')
199
+ .description('Deploy to Azure via Miso Controller')
200
+ .option('--client-id <id>', 'Client ID (overrides config)')
201
+ .option('--client-secret <secret>', 'Client Secret (overrides config)')
202
+ .option('--poll', 'Poll for deployment status', true)
203
+ .option('--no-poll', 'Do not poll for status')
204
+ .action(async(appName, options) => {
205
+ try {
206
+ await app.deployApp(appName, options);
207
+ } catch (error) {
208
+ handleCommandError(error, 'deploy');
209
+ process.exit(1);
210
+ }
211
+ });
212
+
213
+ program.command('dockerfile <app>')
214
+ .description('Generate Dockerfile for an application')
215
+ .option('-l, --language <lang>', 'Override language detection')
216
+ .option('-f, --force', 'Overwrite existing Dockerfile')
217
+ .action(async(appName, options) => {
218
+ try {
219
+ const dockerfilePath = await app.generateDockerfileForApp(appName, options);
220
+ logger.log(chalk.green('\n✅ Dockerfile generated successfully!'));
221
+ logger.log(chalk.gray(`Location: ${dockerfilePath}`));
222
+ } catch (error) {
223
+ handleCommandError(error, 'dockerfile');
224
+ process.exit(1);
225
+ }
226
+ });
227
+ }
228
+
229
+ module.exports = { setupAppCommands };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * CLI authentication command setup (login, logout, auth status/config).
3
+ *
4
+ * @fileoverview Authentication command definitions for AI Fabrix Builder CLI
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const chalk = require('chalk');
10
+ const logger = require('../utils/logger');
11
+ const { handleCommandError } = require('../utils/cli-utils');
12
+ const { handleLogin } = require('../commands/login');
13
+ const { handleLogout } = require('../commands/logout');
14
+ const { handleAuthStatus } = require('../commands/auth-status');
15
+ const { handleAuthConfig } = require('../commands/auth-config');
16
+
17
+ /**
18
+ * Sets up authentication commands
19
+ * @param {Command} program - Commander program instance
20
+ */
21
+ function setupAuthCommands(program) {
22
+ program.command('login')
23
+ .description('Authenticate with Miso Controller')
24
+ .option('-c, --controller <url>', 'Controller URL (default: from config or developer ID, e.g. http://localhost:3000)')
25
+ .option('-m, --method <method>', 'Authentication method (device|credentials)', 'device')
26
+ .option('-a, --app <app>', 'Application name (required for credentials method, reads from secrets.local.yaml)')
27
+ .option('--client-id <id>', 'Client ID (for credentials method, overrides secrets.local.yaml)')
28
+ .option('--client-secret <secret>', 'Client Secret (for credentials method, overrides secrets.local.yaml)')
29
+ .option('-e, --environment <env>', 'Environment key (updates root-level environment in config.yaml, e.g., miso, dev, tst, pro)')
30
+ .option('--online', 'Request online-only token (excludes offline_access scope, device flow only)')
31
+ .option('--scope <scopes>', 'Custom OAuth2 scope string (device flow only, default: "openid profile email offline_access")')
32
+ .action(async(options) => {
33
+ try {
34
+ await handleLogin(options);
35
+ } catch (error) {
36
+ logger.error(chalk.red('\n❌ Login failed:'), error.message);
37
+ process.exit(1);
38
+ }
39
+ });
40
+
41
+ program.command('logout')
42
+ .description('Clear authentication tokens')
43
+ .option('-c, --controller <url>', 'Clear device tokens for specific controller')
44
+ .option('-e, --environment <env>', 'Clear client tokens for specific environment')
45
+ .option('-a, --app <app>', 'Clear client tokens for specific app (requires --environment)')
46
+ .action(async(options) => {
47
+ try {
48
+ await handleLogout(options);
49
+ } catch (error) {
50
+ handleCommandError(error, 'logout');
51
+ process.exit(1);
52
+ }
53
+ });
54
+
55
+ const authStatusHandler = async(options) => {
56
+ try {
57
+ await handleAuthStatus(options);
58
+ } catch (error) {
59
+ handleCommandError(error, 'auth status');
60
+ process.exit(1);
61
+ }
62
+ };
63
+
64
+ const auth = program
65
+ .command('auth')
66
+ .description('Authentication commands');
67
+
68
+ auth
69
+ .command('status')
70
+ .description('Display authentication status for current controller and environment')
71
+ .action(authStatusHandler);
72
+
73
+ auth
74
+ .command('config')
75
+ .description('Configure authentication settings (controller, environment)')
76
+ .option('--set-controller <url>', 'Set default controller URL')
77
+ .option('--set-environment <env>', 'Set default environment')
78
+ .action(async(options) => {
79
+ try {
80
+ await handleAuthConfig(options);
81
+ } catch (error) {
82
+ handleCommandError(error, 'auth config');
83
+ process.exit(1);
84
+ }
85
+ });
86
+ }
87
+
88
+ module.exports = { setupAuthCommands };