@agentlang/cli 0.11.10 → 0.12.1

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/out/main.js CHANGED
@@ -1,4 +1,3 @@
1
- import chalk from 'chalk';
2
1
  import { Command } from 'commander';
3
2
  import { NodeFileSystem } from 'langium/node';
4
3
  import * as path from 'node:path';
@@ -9,8 +8,30 @@ import { initializeProject } from './utils/projectInitializer.js';
9
8
  import { existsSync, readFileSync } from 'node:fs';
10
9
  import { fileURLToPath } from 'node:url';
11
10
  import { dirname, join } from 'node:path';
11
+ import { renderToString } from 'ink';
12
+ import React from 'react';
13
+ import Help from './ui/components/Help.js';
14
+ import { ui, ansi } from './ui/index.js';
12
15
  const __filename = fileURLToPath(import.meta.url);
13
16
  const __dirname = dirname(__filename);
17
+ // Read version before heavy imports so --version works without agentlang loaded
18
+ let packageVersion = '0.0.0';
19
+ try {
20
+ const packagePath = join(__dirname, '..', 'package.json');
21
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
22
+ packageVersion = packageJson.version || '0.0.0';
23
+ }
24
+ catch (_a) {
25
+ // Fallback to default version
26
+ }
27
+ // Early exit for --version/-V — intercepts before heavy agentlang imports
28
+ if (process.argv.includes('--version') || process.argv.includes('-V')) {
29
+ ui.row([
30
+ { text: 'agent', bold: true, color: 'cyan' },
31
+ { text: ` v${packageVersion}`, color: 'white' },
32
+ ]);
33
+ process.exit(0);
34
+ }
14
35
  let agPath = 'agentlang';
15
36
  // Check if ./node_modules/agentlang exists in the current directory, add to agPath
16
37
  const nodeModulesPath = path.resolve(process.cwd(), 'node_modules/agentlang');
@@ -41,16 +62,6 @@ import { findSpecFile } from './ui-generator/specFinder.js';
41
62
  import { startStudio } from './studio.js';
42
63
  import { OpenAPIClientAxios } from 'openapi-client-axios';
43
64
  import { forkApp } from './utils/forkApp.js';
44
- // Read package.json for version
45
- let packageVersion = '0.0.0';
46
- try {
47
- const packagePath = join(__dirname, '..', 'package.json');
48
- const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
49
- packageVersion = packageJson.version || '0.0.0';
50
- }
51
- catch (_a) {
52
- // Fallback to a default version
53
- }
54
65
  function getDefaultRepoUrl(appName) {
55
66
  const username = os.userInfo().username || 'username';
56
67
  const repoName = appName.replace(/\s+/g, '');
@@ -58,8 +69,7 @@ function getDefaultRepoUrl(appName) {
58
69
  }
59
70
  async function promptAndPushRepository(git, appName) {
60
71
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
61
- // eslint-disable-next-line no-console
62
- console.log(chalk.dim('Skipping git push prompt (non-interactive terminal).'));
72
+ ui.dim('Skipping git push prompt (non-interactive terminal).');
63
73
  return;
64
74
  }
65
75
  const rl = createInterface({
@@ -67,14 +77,14 @@ async function promptAndPushRepository(git, appName) {
67
77
  output: process.stdout,
68
78
  });
69
79
  try {
70
- const pushAnswer = (await rl.question(chalk.cyan('Would you like to push this repo now? (y/N) ')))
80
+ const pushAnswer = (await rl.question(ansi.cyan('Would you like to push this repo now? (y/N) ')))
71
81
  .trim()
72
82
  .toLowerCase();
73
83
  if (pushAnswer !== 'y' && pushAnswer !== 'yes') {
74
84
  return;
75
85
  }
76
86
  const defaultRepoUrl = getDefaultRepoUrl(appName);
77
- const repoUrlInputPromise = rl.question(chalk.cyan('Repository URL: '));
87
+ const repoUrlInputPromise = rl.question(ansi.cyan('Repository URL: '));
78
88
  rl.write(defaultRepoUrl);
79
89
  const repoUrlInput = await repoUrlInputPromise;
80
90
  const repoUrl = repoUrlInput.trim() || defaultRepoUrl;
@@ -88,12 +98,10 @@ async function promptAndPushRepository(git, appName) {
88
98
  }
89
99
  const currentBranch = (await git.branch()).current || 'main';
90
100
  await git.push(['-u', 'origin', currentBranch]);
91
- // eslint-disable-next-line no-console
92
- console.log(`${chalk.green('✓')} Pushed to ${chalk.cyan(repoUrl)}`);
101
+ ui.step('✓', 'Pushed to', repoUrl);
93
102
  }
94
103
  catch (error) {
95
- // eslint-disable-next-line no-console
96
- console.log(chalk.yellow(`⚠️ Skipped pushing repository: ${error instanceof Error ? error.message : String(error)}`));
104
+ ui.warn(`Skipped pushing repository: ${error instanceof Error ? error.message : String(error)}`);
97
105
  }
98
106
  finally {
99
107
  rl.close();
@@ -103,33 +111,41 @@ async function promptAndPushRepository(git, appName) {
103
111
  export const initCommand = async (appName, options) => {
104
112
  const currentDir = process.cwd();
105
113
  const targetDir = join(currentDir, appName);
114
+ ui.blank();
115
+ ui.banner('Initialize App');
116
+ ui.blank();
117
+ ui.label('App', appName, 'cyan');
118
+ ui.label('Location', targetDir);
119
+ ui.blank();
106
120
  try {
107
121
  await initializeProject(targetDir, appName, {
108
122
  prompt: options === null || options === void 0 ? void 0 : options.prompt,
109
- silent: false, // Maintain logs for CLI
123
+ silent: false,
110
124
  });
111
- // Change to the app directory (for CLI context)
112
125
  try {
113
126
  process.chdir(targetDir);
114
- // eslint-disable-next-line no-console
115
- console.log(chalk.cyan(`\n📂 Changed directory to ${chalk.bold(appName)}`));
116
127
  }
117
128
  catch (_a) {
118
129
  // Ignore if can't change directory
119
130
  }
120
- // eslint-disable-next-line no-console
121
- console.log(chalk.green('\n✨ Successfully initialized Agentlang application!'));
122
- // eslint-disable-next-line no-console
123
- console.log(chalk.dim('\nNext steps:'));
124
- // eslint-disable-next-line no-console
125
- console.log(chalk.dim(' 1. Add your application logic to src/core.al'));
126
- // eslint-disable-next-line no-console
127
- console.log(chalk.dim(' 2. Run your app with: ') + chalk.cyan('agent run'));
128
- // eslint-disable-next-line no-console
129
- console.log(chalk.dim(' 3. Or start Studio UI with: ') + chalk.cyan('agent studio'));
131
+ ui.blank();
132
+ ui.divider(50);
133
+ ui.success(`${appName} initialized successfully!`);
134
+ ui.blank();
135
+ ui.dim('Next steps:');
136
+ ui.dim(' 1. Add your application logic to src/core.al');
137
+ ui.row([
138
+ { text: ' 2. Run your app with: ', dimColor: true },
139
+ { text: 'agent run', color: 'cyan' },
140
+ ]);
141
+ ui.row([
142
+ { text: ' 3. Or start Studio UI with: ', dimColor: true },
143
+ { text: 'agent studio', color: 'cyan' },
144
+ ]);
145
+ ui.divider(50);
146
+ ui.blank();
130
147
  // Handle interactive git push
131
148
  const git = simpleGit(targetDir);
132
- // Check if git is initialized (initializeProject does it, but let's be safe)
133
149
  if (await git.checkIsRepo()) {
134
150
  await promptAndPushRepository(git, appName);
135
151
  }
@@ -138,121 +154,36 @@ export const initCommand = async (appName, options) => {
138
154
  }
139
155
  }
140
156
  catch (error) {
141
- // eslint-disable-next-line no-console
142
- console.error(chalk.red('❌ Error initializing application:'), error instanceof Error ? error.message : error);
157
+ ui.error(`Error initializing application: ${error instanceof Error ? error.message : String(error)}`);
143
158
  process.exit(1);
144
159
  }
145
160
  };
146
- // Custom help formatter
147
- function customHelp() {
148
- const gradient = [chalk.hex('#00D9FF'), chalk.hex('#00C4E6'), chalk.hex('#00AFCC'), chalk.hex('#009AB3')];
149
- const header = `
150
- ${gradient[0]('█████╗')} ${gradient[1](' ██████╗')} ${gradient[2]('███████╗')}${gradient[3]('███╗ ██╗')}${gradient[0]('████████╗')}
151
- ${gradient[0]('██╔══██╗')}${gradient[1]('██╔════╝')} ${gradient[2]('██╔════╝')}${gradient[3]('████╗ ██║')}${gradient[0]('╚══██╔══╝')}
152
- ${gradient[0]('███████║')}${gradient[1]('██║ ███╗')}${gradient[2]('█████╗')} ${gradient[3]('██╔██╗ ██║')}${gradient[0](' ██║')}
153
- ${gradient[0]('██╔══██║')}${gradient[1]('██║ ██║')}${gradient[2]('██╔══╝')} ${gradient[3]('██║╚██╗██║')}${gradient[0](' ██║')}
154
- ${gradient[0]('██║ ██║')}${gradient[1]('╚██████╔╝')}${gradient[2]('███████╗')}${gradient[3]('██║ ╚████║')}${gradient[0](' ██║')}
155
- ${gradient[0]('╚═╝ ╚═╝')} ${gradient[1]('╚═════╝')} ${gradient[2]('╚══════╝')}${gradient[3]('╚═╝ ╚═══╝')}${gradient[0](' ╚═╝')}
156
-
157
- ${chalk.bold.white('Agentlang CLI')} ${chalk.dim(`v${packageVersion}`)}
158
- ${chalk.dim('CLI for all things Agentlang')}
159
- `;
160
- const usage = `
161
- ${chalk.bold.white('USAGE')}
162
- ${chalk.dim('$')} ${chalk.cyan('agent')} ${chalk.yellow('<command>')} ${chalk.dim('[options]')}
163
-
164
- ${chalk.bold.white('COMMANDS')}
165
-
166
- ${chalk.cyan.bold('init')} ${chalk.dim('<appname>')}
167
- ${chalk.white('▸')} Initialize a new Agentlang application
168
- ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
169
- ${chalk.yellow('OPTIONS')}
170
- ${chalk.cyan('-p, --prompt')} ${chalk.dim('<description>')} Description or prompt for the application
171
-
172
- ${chalk.cyan.bold('run')} ${chalk.dim('[file]')}
173
- ${chalk.white('▸')} Load and execute an Agentlang module
174
- ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
175
- ${chalk.yellow('OPTIONS')}
176
- ${chalk.cyan('-c, --config')} ${chalk.dim('<file>')} Configuration file path
177
-
178
- ${chalk.cyan.bold('repl')} ${chalk.dim('[directory]')}
179
- ${chalk.white('▸')} Start interactive REPL environment
180
- ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
181
- ${chalk.yellow('OPTIONS')}
182
- ${chalk.cyan('-w, --watch')} Watch files and reload automatically
183
- ${chalk.cyan('-q, --quiet')} Suppress startup messages
184
-
185
- ${chalk.cyan.bold('doc')} ${chalk.dim('[file]')}
186
- ${chalk.white('▸')} Generate API documentation (Swagger/OpenAPI)
187
- ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
188
- ${chalk.yellow('OPTIONS')}
189
- ${chalk.cyan('-h, --outputHtml')} ${chalk.dim('<file>')} Generate HTML documentation
190
- ${chalk.cyan('-p, --outputPostman')} ${chalk.dim('<file>')} Generate Postman collection
191
-
192
- ${chalk.cyan.bold('parseAndValidate')} ${chalk.dim('<file>')}
193
- ${chalk.white('▸')} Parse and validate Agentlang source code
194
- ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
195
- ${chalk.yellow('OPTIONS')}
196
- ${chalk.cyan('-d, --destination')} ${chalk.dim('<dir>')} Output directory
197
-
198
- ${chalk.cyan.bold('ui-gen')} ${chalk.dim('[spec-file]')}
199
- ${chalk.white('▸')} Generate UI from specification ${chalk.dim('(requires Anthropic API key)')}
200
- ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
201
- ${chalk.yellow('OPTIONS')}
202
- ${chalk.cyan('-d, --directory')} ${chalk.dim('<dir>')} Target directory
203
- ${chalk.cyan('-k, --api-key')} ${chalk.dim('<key>')} Anthropic API key
204
- ${chalk.cyan('-p, --push')} Commit and push to git
205
- ${chalk.cyan('-m, --message')} ${chalk.dim('<text>')} Update instructions
206
-
207
- ${chalk.cyan.bold('fork')} ${chalk.dim('<source> [name]')}
208
- ${chalk.white('▸')} Fork an app from a local directory or git repository
209
- ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
210
- ${chalk.yellow('OPTIONS')}
211
- ${chalk.cyan('-b, --branch')} ${chalk.dim('<branch>')} Git branch to clone (for git URLs)
212
- ${chalk.cyan('-u, --username')} ${chalk.dim('<username>')} GitHub username for authenticated access
213
- ${chalk.cyan('-t, --token')} ${chalk.dim('<token>')} GitHub token for authenticated access
214
-
215
- ${chalk.cyan.bold('import')} ${chalk.dim('<source> [name]')}
216
- ${chalk.white('▸')} Import an app (alias for fork)
217
- ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
218
- ${chalk.yellow('OPTIONS')}
219
- ${chalk.cyan('-b, --branch')} ${chalk.dim('<branch>')} Git branch to clone (for git URLs)
220
- ${chalk.cyan('-u, --username')} ${chalk.dim('<username>')} GitHub username for authenticated access
221
- ${chalk.cyan('-t, --token')} ${chalk.dim('<token>')} GitHub token for authenticated access
222
-
223
- ${chalk.cyan.bold('studio')} ${chalk.dim('[path]')}
224
- ${chalk.white('▸')} Start Agentlang Studio with local server
225
- ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
226
- ${chalk.yellow('OPTIONS')}
227
- ${chalk.cyan('-p, --port')} ${chalk.dim('<port>')} Port to run Studio server on (default: 4000)
228
-
229
- ${chalk.bold.white('GLOBAL OPTIONS')}
230
- ${chalk.cyan('-h, --help')} Display help information
231
- ${chalk.cyan('-V, --version')} Display version number
232
-
233
- ${chalk.bold.white('LEARN MORE')}
234
- ${chalk.white('Docs')} ${chalk.cyan('https://github.com/agentlang/agentlang-cli')}
235
- ${chalk.white('Issues')} ${chalk.cyan('https://github.com/agentlang/agentlang-cli/issues')}
236
-
237
- ${chalk.dim('Run')} ${chalk.cyan('agent <command> --help')} ${chalk.dim('for detailed command information')}
238
- `;
239
- return header + usage;
240
- }
241
161
  export default function () {
242
162
  const program = new Command();
243
163
  // Configure program
244
164
  program
245
165
  .name('agent')
246
- .description(chalk.gray('CLI for all things Agentlang'))
166
+ .description('CLI for all things Agentlang')
247
167
  .version(packageVersion, '-V, --version', 'Display version number')
248
- .helpOption('-h, --help', 'Show help information')
168
+ .helpOption(false)
249
169
  .helpCommand(false)
250
170
  .configureHelp({
251
171
  sortSubcommands: true,
252
172
  sortOptions: true,
253
173
  });
254
- // Override help display
255
- program.helpInformation = customHelp;
174
+ // Use ink-rendered help via renderToString
175
+ program.helpInformation = () => {
176
+ return renderToString(React.createElement(Help, { version: packageVersion }), {
177
+ columns: process.stdout.columns || 80,
178
+ });
179
+ };
180
+ // Add explicit help flag since we disabled the built-in one
181
+ program.option('-h, --help', 'Show help information');
182
+ program.on('option:help', () => {
183
+ // eslint-disable-next-line no-console
184
+ console.log(program.helpInformation());
185
+ process.exit(0);
186
+ });
256
187
  const fileExtensions = AgentlangLanguageMetaData.fileExtensions.join(', ');
257
188
  program
258
189
  .command('init')
@@ -260,7 +191,7 @@ export default function () {
260
191
  .option('-p, --prompt <description>', 'Description or prompt for the application')
261
192
  .description('Initialize a new Agentlang application')
262
193
  .addHelpText('after', `
263
- ${chalk.bold.white('DESCRIPTION')}
194
+ ${ui.format.boldWhite('DESCRIPTION')}
264
195
  Creates a new Agentlang application with the necessary project structure.
265
196
  This command will create:
266
197
  • package.json with your app name and version
@@ -270,18 +201,18 @@ ${chalk.bold.white('DESCRIPTION')}
270
201
  The command checks if the directory is already initialized by looking for
271
202
  existing package.json or .al files (excluding config.al).
272
203
 
273
- ${chalk.bold.white('EXAMPLES')}
274
- ${chalk.dim('Initialize a new app called CarDealership')}
275
- ${chalk.dim('$')} ${chalk.cyan('agent init CarDealership')}
204
+ ${ui.format.boldWhite('EXAMPLES')}
205
+ ${ui.format.dim('Initialize a new app called CarDealership')}
206
+ ${ui.format.dim('$')} ${ui.format.cyan('agent init CarDealership')}
276
207
 
277
- ${chalk.dim('Initialize a new e-commerce app')}
278
- ${chalk.dim('$')} ${chalk.cyan('agent init MyShop')}
208
+ ${ui.format.dim('Initialize a new e-commerce app')}
209
+ ${ui.format.dim('$')} ${ui.format.cyan('agent init MyShop')}
279
210
 
280
- ${chalk.dim('Initialize with multiple words (use PascalCase)')}
281
- ${chalk.dim('$')} ${chalk.cyan('agent init InventoryManagement')}
211
+ ${ui.format.dim('Initialize with multiple words (use PascalCase)')}
212
+ ${ui.format.dim('$')} ${ui.format.cyan('agent init InventoryManagement')}
282
213
 
283
- ${chalk.dim('Initialize with a description/prompt')}
284
- ${chalk.dim('$')} ${chalk.cyan('agent init ShowroomApp --prompt "a showroom app"')}
214
+ ${ui.format.dim('Initialize with a description/prompt')}
215
+ ${ui.format.dim('$')} ${ui.format.cyan('agent init ShowroomApp --prompt "a showroom app"')}
285
216
  `)
286
217
  .action(initCommand);
287
218
  program
@@ -290,22 +221,22 @@ ${chalk.bold.white('EXAMPLES')}
290
221
  .option('-c, --config <config>', 'Path to configuration file')
291
222
  .description('Load and execute an Agentlang module')
292
223
  .addHelpText('after', `
293
- ${chalk.bold.white('DESCRIPTION')}
224
+ ${ui.format.boldWhite('DESCRIPTION')}
294
225
  Loads and executes an Agentlang module, starting the runtime environment
295
226
  and initializing all configured services, databases, and integrations.
296
227
 
297
- ${chalk.bold.white('EXAMPLES')}
298
- ${chalk.dim('Run module in current directory')}
299
- ${chalk.dim('$')} ${chalk.cyan('agent run')}
228
+ ${ui.format.boldWhite('EXAMPLES')}
229
+ ${ui.format.dim('Run module in current directory')}
230
+ ${ui.format.dim('$')} ${ui.format.cyan('agent run')}
300
231
 
301
- ${chalk.dim('Run specific module file')}
302
- ${chalk.dim('$')} ${chalk.cyan('agent run ./my-app/main.al')}
232
+ ${ui.format.dim('Run specific module file')}
233
+ ${ui.format.dim('$')} ${ui.format.cyan('agent run ./my-app/main.al')}
303
234
 
304
- ${chalk.dim('Run with custom configuration')}
305
- ${chalk.dim('$')} ${chalk.cyan('agent run ./my-app -c config.json')}
235
+ ${ui.format.dim('Run with custom configuration')}
236
+ ${ui.format.dim('$')} ${ui.format.cyan('agent run ./my-app -c config.json')}
306
237
 
307
- ${chalk.dim('Run module from specific directory')}
308
- ${chalk.dim('$')} ${chalk.cyan('agent run ~/projects/erp-system')}
238
+ ${ui.format.dim('Run module from specific directory')}
239
+ ${ui.format.dim('$')} ${ui.format.cyan('agent run ~/projects/erp-system')}
309
240
  `)
310
241
  .action(runModule);
311
242
  program
@@ -315,26 +246,26 @@ ${chalk.bold.white('EXAMPLES')}
315
246
  .option('-q, --quiet', 'Suppress startup messages')
316
247
  .description('Start interactive REPL environment')
317
248
  .addHelpText('after', `
318
- ${chalk.bold.white('DESCRIPTION')}
249
+ ${ui.format.boldWhite('DESCRIPTION')}
319
250
  Starts an interactive Read-Eval-Print Loop (REPL) environment for
320
251
  Agentlang, allowing you to execute code interactively, test functions,
321
252
  and explore your application in real-time.
322
253
 
323
- ${chalk.bold.white('EXAMPLES')}
324
- ${chalk.dim('Start REPL in current directory')}
325
- ${chalk.dim('$')} ${chalk.cyan('agent repl')}
254
+ ${ui.format.boldWhite('EXAMPLES')}
255
+ ${ui.format.dim('Start REPL in current directory')}
256
+ ${ui.format.dim('$')} ${ui.format.cyan('agent repl')}
326
257
 
327
- ${chalk.dim('Start REPL in specific directory')}
328
- ${chalk.dim('$')} ${chalk.cyan('agent repl ./my-app')}
258
+ ${ui.format.dim('Start REPL in specific directory')}
259
+ ${ui.format.dim('$')} ${ui.format.cyan('agent repl ./my-app')}
329
260
 
330
- ${chalk.dim('Start with file watching enabled')}
331
- ${chalk.dim('$')} ${chalk.cyan('agent repl --watch')}
261
+ ${ui.format.dim('Start with file watching enabled')}
262
+ ${ui.format.dim('$')} ${ui.format.cyan('agent repl --watch')}
332
263
 
333
- ${chalk.dim('Start in quiet mode (no startup messages)')}
334
- ${chalk.dim('$')} ${chalk.cyan('agent repl --quiet')}
264
+ ${ui.format.dim('Start in quiet mode (no startup messages)')}
265
+ ${ui.format.dim('$')} ${ui.format.cyan('agent repl --quiet')}
335
266
 
336
- ${chalk.dim('Combine options for development workflow')}
337
- ${chalk.dim('$')} ${chalk.cyan('agent repl . --watch')}
267
+ ${ui.format.dim('Combine options for development workflow')}
268
+ ${ui.format.dim('$')} ${ui.format.cyan('agent repl . --watch')}
338
269
  `)
339
270
  .action(replCommand);
340
271
  program
@@ -344,26 +275,26 @@ ${chalk.bold.white('EXAMPLES')}
344
275
  .option('-p, --outputPostman <outputPostman>', 'Generate Postman collection')
345
276
  .description('Generate API documentation (Swagger/OpenAPI)')
346
277
  .addHelpText('after', `
347
- ${chalk.bold.white('DESCRIPTION')}
278
+ ${ui.format.boldWhite('DESCRIPTION')}
348
279
  Generates comprehensive API documentation from your Agentlang module
349
280
  in Swagger/OpenAPI format. Supports both HTML and Postman collection
350
281
  output formats for easy API exploration and testing.
351
282
 
352
- ${chalk.bold.white('EXAMPLES')}
353
- ${chalk.dim('Generate OpenAPI spec (outputs to console)')}
354
- ${chalk.dim('$')} ${chalk.cyan('agent doc')}
283
+ ${ui.format.boldWhite('EXAMPLES')}
284
+ ${ui.format.dim('Generate OpenAPI spec (outputs to console)')}
285
+ ${ui.format.dim('$')} ${ui.format.cyan('agent doc')}
355
286
 
356
- ${chalk.dim('Generate HTML documentation')}
357
- ${chalk.dim('$')} ${chalk.cyan('agent doc --outputHtml api-docs.html')}
287
+ ${ui.format.dim('Generate HTML documentation')}
288
+ ${ui.format.dim('$')} ${ui.format.cyan('agent doc --outputHtml api-docs.html')}
358
289
 
359
- ${chalk.dim('Generate Postman collection')}
360
- ${chalk.dim('$')} ${chalk.cyan('agent doc --outputPostman collection.json')}
290
+ ${ui.format.dim('Generate Postman collection')}
291
+ ${ui.format.dim('$')} ${ui.format.cyan('agent doc --outputPostman collection.json')}
361
292
 
362
- ${chalk.dim('Generate both HTML and Postman')}
363
- ${chalk.dim('$')} ${chalk.cyan('agent doc -h docs.html -p collection.json')}
293
+ ${ui.format.dim('Generate both HTML and Postman')}
294
+ ${ui.format.dim('$')} ${ui.format.cyan('agent doc -h docs.html -p collection.json')}
364
295
 
365
- ${chalk.dim('Generate docs for specific module')}
366
- ${chalk.dim('$')} ${chalk.cyan('agent doc ./my-api -h api.html')}
296
+ ${ui.format.dim('Generate docs for specific module')}
297
+ ${ui.format.dim('$')} ${ui.format.cyan('agent doc ./my-api -h api.html')}
367
298
  `)
368
299
  .action(generateDoc);
369
300
  program
@@ -372,20 +303,20 @@ ${chalk.bold.white('EXAMPLES')}
372
303
  .option('-d, --destination <dir>', 'Output directory for generated files')
373
304
  .description('Parse and validate Agentlang source code')
374
305
  .addHelpText('after', `
375
- ${chalk.bold.white('DESCRIPTION')}
306
+ ${ui.format.boldWhite('DESCRIPTION')}
376
307
  Parses and validates an Agentlang source file, checking for syntax
377
308
  errors, lexer issues, and semantic validation problems. Useful for
378
309
  CI/CD pipelines and pre-deployment validation.
379
310
 
380
- ${chalk.bold.white('EXAMPLES')}
381
- ${chalk.dim('Validate a source file')}
382
- ${chalk.dim('$')} ${chalk.cyan('agent parseAndValidate ./src/main.al')}
311
+ ${ui.format.boldWhite('EXAMPLES')}
312
+ ${ui.format.dim('Validate a source file')}
313
+ ${ui.format.dim('$')} ${ui.format.cyan('agent parseAndValidate ./src/main.al')}
383
314
 
384
- ${chalk.dim('Parse and validate with output directory')}
385
- ${chalk.dim('$')} ${chalk.cyan('agent parseAndValidate main.al -d ./out')}
315
+ ${ui.format.dim('Parse and validate with output directory')}
316
+ ${ui.format.dim('$')} ${ui.format.cyan('agent parseAndValidate main.al -d ./out')}
386
317
 
387
- ${chalk.dim('Validate in CI/CD pipeline')}
388
- ${chalk.dim('$')} ${chalk.cyan('agent parseAndValidate app.al && npm run deploy')}
318
+ ${ui.format.dim('Validate in CI/CD pipeline')}
319
+ ${ui.format.dim('$')} ${ui.format.cyan('agent parseAndValidate app.al && npm run deploy')}
389
320
  `)
390
321
  .action(parseAndValidate);
391
322
  program
@@ -397,36 +328,36 @@ ${chalk.bold.white('EXAMPLES')}
397
328
  .option('-m, --message <message>', 'User message for incremental updates')
398
329
  .description('Generate UI from specification (requires Anthropic API key)')
399
330
  .addHelpText('after', `
400
- ${chalk.bold.white('DESCRIPTION')}
331
+ ${ui.format.boldWhite('DESCRIPTION')}
401
332
  Generates a complete UI application from a ui-spec.json specification
402
333
  using AI. Supports incremental updates, allowing you to evolve your UI
403
334
  over time with natural language instructions.
404
335
 
405
- ${chalk.yellow.bold('API KEY REQUIRED')}
406
- Set ${chalk.cyan('ANTHROPIC_API_KEY')} environment variable or use ${chalk.cyan('--api-key')} flag
407
- ${chalk.dim('Get your key at: https://console.anthropic.com')}
336
+ ${ui.format.row([{ text: 'API KEY REQUIRED', color: 'yellow', bold: true }])}
337
+ Set ${ui.format.cyan('ANTHROPIC_API_KEY')} environment variable or use ${ui.format.cyan('--api-key')} flag
338
+ ${ui.format.dim('Get your key at: https://console.anthropic.com')}
408
339
 
409
- ${chalk.bold.white('EXAMPLES')}
410
- ${chalk.dim('Generate UI with auto-detected spec')}
411
- ${chalk.dim('$')} ${chalk.cyan('agent ui-gen')}
340
+ ${ui.format.boldWhite('EXAMPLES')}
341
+ ${ui.format.dim('Generate UI with auto-detected spec')}
342
+ ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen')}
412
343
 
413
- ${chalk.dim('Generate from specific spec file')}
414
- ${chalk.dim('$')} ${chalk.cyan('agent ui-gen ui-spec.json')}
344
+ ${ui.format.dim('Generate from specific spec file')}
345
+ ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen ui-spec.json')}
415
346
 
416
- ${chalk.dim('Generate and commit to git')}
417
- ${chalk.dim('$')} ${chalk.cyan('agent ui-gen --push')}
347
+ ${ui.format.dim('Generate and commit to git')}
348
+ ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen --push')}
418
349
 
419
- ${chalk.dim('Generate in specific directory')}
420
- ${chalk.dim('$')} ${chalk.cyan('agent ui-gen -d ./frontend')}
350
+ ${ui.format.dim('Generate in specific directory')}
351
+ ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen -d ./frontend')}
421
352
 
422
- ${chalk.dim('Update existing UI with changes')}
423
- ${chalk.dim('$')} ${chalk.cyan('agent ui-gen -m "Add dark mode toggle"')}
353
+ ${ui.format.dim('Update existing UI with changes')}
354
+ ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen -m "Add dark mode toggle"')}
424
355
 
425
- ${chalk.dim('Incremental update with git push')}
426
- ${chalk.dim('$')} ${chalk.cyan('agent ui-gen -m "Fix login validation" -p')}
356
+ ${ui.format.dim('Incremental update with git push')}
357
+ ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen -m "Fix login validation" -p')}
427
358
 
428
- ${chalk.dim('Use custom API key')}
429
- ${chalk.dim('$')} ${chalk.cyan('agent ui-gen --api-key sk-ant-...')}
359
+ ${ui.format.dim('Use custom API key')}
360
+ ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen --api-key sk-ant-...')}
430
361
  `)
431
362
  .action(generateUICommand);
432
363
  program
@@ -438,26 +369,26 @@ ${chalk.bold.white('EXAMPLES')}
438
369
  .option('-t, --token <token>', 'GitHub token for authenticated access')
439
370
  .description('Fork an app from a local directory or git repository')
440
371
  .addHelpText('after', `
441
- ${chalk.bold.white('DESCRIPTION')}
372
+ ${ui.format.boldWhite('DESCRIPTION')}
442
373
  Forks an Agentlang application from a source path (local directory or git URL)
443
374
  into the current workspace. The forked app will be initialized with dependencies
444
375
  installed and a fresh git repository.
445
376
 
446
- ${chalk.bold.white('EXAMPLES')}
447
- ${chalk.dim('Fork from local directory')}
448
- ${chalk.dim('$')} ${chalk.cyan('agent fork ./my-app MyForkedApp')}
377
+ ${ui.format.boldWhite('EXAMPLES')}
378
+ ${ui.format.dim('Fork from local directory')}
379
+ ${ui.format.dim('$')} ${ui.format.cyan('agent fork ./my-app MyForkedApp')}
449
380
 
450
- ${chalk.dim('Fork from GitHub repository')}
451
- ${chalk.dim('$')} ${chalk.cyan('agent fork https://github.com/user/repo.git MyApp')}
381
+ ${ui.format.dim('Fork from GitHub repository')}
382
+ ${ui.format.dim('$')} ${ui.format.cyan('agent fork https://github.com/user/repo.git MyApp')}
452
383
 
453
- ${chalk.dim('Fork from GitHub with specific branch')}
454
- ${chalk.dim('$')} ${chalk.cyan('agent fork https://github.com/user/repo.git MyApp --branch develop')}
384
+ ${ui.format.dim('Fork from GitHub with specific branch')}
385
+ ${ui.format.dim('$')} ${ui.format.cyan('agent fork https://github.com/user/repo.git MyApp --branch develop')}
455
386
 
456
- ${chalk.dim('Fork private repository with authentication')}
457
- ${chalk.dim('$')} ${chalk.cyan('agent fork https://github.com/user/repo.git MyApp -u username -t token')}
387
+ ${ui.format.dim('Fork private repository with authentication')}
388
+ ${ui.format.dim('$')} ${ui.format.cyan('agent fork https://github.com/user/repo.git MyApp -u username -t token')}
458
389
 
459
- ${chalk.dim('Fork using git@ URL')}
460
- ${chalk.dim('$')} ${chalk.cyan('agent fork git@github.com:user/repo.git MyApp')}
390
+ ${ui.format.dim('Fork using git@ URL')}
391
+ ${ui.format.dim('$')} ${ui.format.cyan('agent fork git@github.com:user/repo.git MyApp')}
461
392
  `)
462
393
  .action(forkCommand);
463
394
  program
@@ -469,16 +400,16 @@ ${chalk.bold.white('EXAMPLES')}
469
400
  .option('-t, --token <token>', 'GitHub token for authenticated access')
470
401
  .description('Import an app from a local directory or git repository (alias for fork)')
471
402
  .addHelpText('after', `
472
- ${chalk.bold.white('DESCRIPTION')}
403
+ ${ui.format.boldWhite('DESCRIPTION')}
473
404
  Imports an Agentlang application from a source path. This is an alias for the
474
405
  'fork' command and uses the same functionality.
475
406
 
476
- ${chalk.bold.white('EXAMPLES')}
477
- ${chalk.dim('Import from local directory')}
478
- ${chalk.dim('$')} ${chalk.cyan('agent import ./my-app MyImportedApp')}
407
+ ${ui.format.boldWhite('EXAMPLES')}
408
+ ${ui.format.dim('Import from local directory')}
409
+ ${ui.format.dim('$')} ${ui.format.cyan('agent import ./my-app MyImportedApp')}
479
410
 
480
- ${chalk.dim('Import from GitHub repository')}
481
- ${chalk.dim('$')} ${chalk.cyan('agent import https://github.com/user/repo.git MyApp')}
411
+ ${ui.format.dim('Import from GitHub repository')}
412
+ ${ui.format.dim('$')} ${ui.format.cyan('agent import https://github.com/user/repo.git MyApp')}
482
413
  `)
483
414
  .action(forkCommand);
484
415
  program
@@ -488,7 +419,7 @@ ${chalk.bold.white('EXAMPLES')}
488
419
  .option('--server-only', 'Start only the backend server without opening the UI')
489
420
  .description('Start Agentlang Studio with local server')
490
421
  .addHelpText('after', `
491
- ${chalk.bold.white('DESCRIPTION')}
422
+ ${ui.format.boldWhite('DESCRIPTION')}
492
423
  Starts the Agentlang Design Studio locally for your project. This command:
493
424
  • Starts the Agentlang server (via 'agent run')
494
425
  • Serves the Studio UI on a local web server
@@ -497,21 +428,21 @@ ${chalk.bold.white('DESCRIPTION')}
497
428
  The Studio UI allows you to visually edit Agents, Data Models, and Workflows,
498
429
  with changes saved directly to your project files (.al files, package.json, etc.).
499
430
 
500
- ${chalk.bold.white('EXAMPLES')}
501
- ${chalk.dim('Start Studio in current directory')}
502
- ${chalk.dim('$')} ${chalk.cyan('agent studio')}
431
+ ${ui.format.boldWhite('EXAMPLES')}
432
+ ${ui.format.dim('Start Studio in current directory')}
433
+ ${ui.format.dim('$')} ${ui.format.cyan('agent studio')}
503
434
 
504
- ${chalk.dim('Start Studio for specific project')}
505
- ${chalk.dim('$')} ${chalk.cyan('agent studio ./my-project')}
435
+ ${ui.format.dim('Start Studio for specific project')}
436
+ ${ui.format.dim('$')} ${ui.format.cyan('agent studio ./my-project')}
506
437
 
507
- ${chalk.dim('Start Studio on custom port')}
508
- ${chalk.dim('$')} ${chalk.cyan('agent studio --port 5000')}
438
+ ${ui.format.dim('Start Studio on custom port')}
439
+ ${ui.format.dim('$')} ${ui.format.cyan('agent studio --port 5000')}
509
440
 
510
- ${chalk.dim('Start Studio with path and custom port')}
511
- ${chalk.dim('$')} ${chalk.cyan('agent studio ./monitoring -p 5000')}
441
+ ${ui.format.dim('Start Studio with path and custom port')}
442
+ ${ui.format.dim('$')} ${ui.format.cyan('agent studio ./monitoring -p 5000')}
512
443
 
513
- ${chalk.dim('Start only the backend server (for development)')}
514
- ${chalk.dim('$')} ${chalk.cyan('agent studio --server-only')}
444
+ ${ui.format.dim('Start only the backend server (for development)')}
445
+ ${ui.format.dim('$')} ${ui.format.cyan('agent studio --server-only')}
515
446
  `)
516
447
  .action(studioCommand);
517
448
  program.parse(process.argv);
@@ -532,12 +463,10 @@ export const parseAndValidate = async (fileName) => {
532
463
  const parseResult = document.parseResult;
533
464
  // verify no lexer, parser, or general diagnostic errors show up
534
465
  if (parseResult.lexerErrors.length === 0 && parseResult.parserErrors.length === 0) {
535
- // eslint-disable-next-line no-console
536
- console.log(chalk.green(`Parsed and validated ${fileName} successfully!`));
466
+ ui.success(`Parsed and validated ${fileName} successfully!`);
537
467
  }
538
468
  else {
539
- // eslint-disable-next-line no-console
540
- console.log(chalk.red(`Failed to parse and validate ${fileName}!`));
469
+ ui.error(`Failed to parse and validate ${fileName}!`);
541
470
  }
542
471
  };
543
472
  export const runModule = async (fileName) => {
@@ -559,9 +488,8 @@ export const runModule = async (fileName) => {
559
488
  });
560
489
  }
561
490
  catch (err) {
562
- if (isNodeEnv && chalk) {
563
- // eslint-disable-next-line no-console
564
- console.error(chalk.red(String(err)));
491
+ if (isNodeEnv) {
492
+ ui.error(String(err));
565
493
  }
566
494
  else {
567
495
  // eslint-disable-next-line no-console
@@ -587,8 +515,7 @@ export const replCommand = async (directory, options) => {
587
515
  });
588
516
  }
589
517
  catch (error) {
590
- // eslint-disable-next-line no-console
591
- console.log(chalk.red(`Failed to start REPL: ${error instanceof Error ? error.message : String(error)}`));
518
+ ui.error(`Failed to start REPL: ${error instanceof Error ? error.message : String(error)}`);
592
519
  process.exit(1);
593
520
  }
594
521
  };
@@ -601,20 +528,23 @@ export async function internAndRunModule(module, appSpec) {
601
528
  await runPostInitTasks(appSpec);
602
529
  return rm;
603
530
  }
604
- /* eslint-disable no-console */
605
531
  export const generateUICommand = async (specFile, options) => {
606
532
  try {
607
- console.log(chalk.blue('🚀 Agentlang UI Generator\n'));
533
+ ui.blank();
534
+ ui.banner('UI Generator');
535
+ ui.blank();
608
536
  // Get API key from options or environment
609
537
  const apiKey = (options === null || options === void 0 ? void 0 : options.apiKey) || process.env.ANTHROPIC_API_KEY;
610
538
  if (!apiKey) {
611
- console.error(chalk.red('❌ Error: Anthropic API key is required.'));
612
- console.log(chalk.yellow(' Set ANTHROPIC_API_KEY environment variable or use --api-key flag.'));
613
- console.log(chalk.gray('\n Example:'));
614
- console.log(chalk.gray(' $ export ANTHROPIC_API_KEY=sk-ant-...'));
615
- console.log(chalk.gray(' $ agent ui-gen'));
616
- console.log(chalk.gray('\n Or:'));
617
- console.log(chalk.gray(' $ agent ui-gen --api-key sk-ant-...'));
539
+ ui.error('Anthropic API key is required.');
540
+ ui.warn('Set ANTHROPIC_API_KEY environment variable or use --api-key flag.');
541
+ ui.blank();
542
+ ui.gray(' Example:');
543
+ ui.gray(' $ export ANTHROPIC_API_KEY=sk-ant-...');
544
+ ui.gray(' $ agent ui-gen');
545
+ ui.blank();
546
+ ui.gray(' Or:');
547
+ ui.gray(' $ agent ui-gen --api-key sk-ant-...');
618
548
  process.exit(1);
619
549
  }
620
550
  // Set target directory
@@ -623,52 +553,51 @@ export const generateUICommand = async (specFile, options) => {
623
553
  // Auto-detect spec file if not provided
624
554
  let specFilePath;
625
555
  if (!specFile) {
626
- console.log(chalk.cyan('📄 Searching for UI spec file...'));
556
+ ui.dim('Searching for UI spec file...');
627
557
  specFilePath = await findSpecFile(absoluteTargetDir);
628
558
  }
629
559
  else {
630
560
  specFilePath = path.resolve(process.cwd(), specFile);
631
561
  }
632
562
  // Load the UI spec
633
- console.log(chalk.cyan(`📄 Loading UI spec from: ${specFilePath}`));
634
563
  const uiSpec = await loadUISpec(specFilePath);
635
- console.log(chalk.cyan(`📂 Target directory: ${absoluteTargetDir}`));
636
- console.log(chalk.cyan(`📦 Output will be created in: ${path.join(absoluteTargetDir, 'ui')}`));
564
+ ui.label('Spec', specFilePath, 'cyan');
565
+ ui.label('Target', absoluteTargetDir);
566
+ ui.label('Output', path.join(absoluteTargetDir, 'ui'));
567
+ ui.blank();
637
568
  // Generate or update the UI
638
569
  await generateUI(uiSpec, absoluteTargetDir, apiKey, (options === null || options === void 0 ? void 0 : options.push) || false, options === null || options === void 0 ? void 0 : options.message);
639
- console.log(chalk.green('\n✅ UI generation completed successfully!'));
570
+ ui.blank();
571
+ ui.divider(50);
572
+ ui.success('UI generation completed!');
573
+ ui.divider(50);
574
+ ui.blank();
640
575
  }
641
576
  catch (error) {
642
- console.error(chalk.red('\n❌ Error:'), error instanceof Error ? error.message : error);
577
+ ui.error(error instanceof Error ? error.message : String(error));
643
578
  process.exit(1);
644
579
  }
645
580
  };
646
- /* eslint-enable no-console */
647
- /* eslint-disable no-console */
648
581
  export const studioCommand = async (projectPath, options) => {
649
582
  try {
650
583
  const port = parseInt((options === null || options === void 0 ? void 0 : options.port) || '4000', 10);
651
584
  if (isNaN(port) || port < 1 || port > 65535) {
652
- console.error(chalk.red('Invalid port number. Port must be between 1 and 65535.'));
585
+ ui.error('Invalid port number. Port must be between 1 and 65535.');
653
586
  process.exit(1);
654
587
  }
655
588
  await startStudio(projectPath || '.', port, options === null || options === void 0 ? void 0 : options.serverOnly);
656
589
  }
657
590
  catch (error) {
658
- console.error(chalk.red(`Failed to start Studio: ${error instanceof Error ? error.message : String(error)}`));
591
+ ui.error(`Failed to start Studio: ${error instanceof Error ? error.message : String(error)}`);
659
592
  process.exit(1);
660
593
  }
661
594
  };
662
- /* eslint-enable no-console */
663
- /* eslint-disable no-console */
664
595
  export const forkCommand = async (source, name, options) => {
665
596
  try {
666
- console.log(chalk.blue('🚀 Forking Agentlang application...\n'));
667
597
  // Determine destination name
668
598
  let appName = name;
669
599
  if (!appName) {
670
600
  if (source.startsWith('http') || source.startsWith('git@')) {
671
- // Try to infer from URL
672
601
  const parts = source.split('/');
673
602
  const lastPart = parts[parts.length - 1].replace('.git', '');
674
603
  appName = lastPart;
@@ -690,24 +619,41 @@ export const forkCommand = async (source, name, options) => {
690
619
  token: options.token,
691
620
  };
692
621
  }
693
- console.log(chalk.cyan(`📦 Source: ${source}`));
694
- console.log(chalk.cyan(`📂 Destination: ${destPath}`));
622
+ ui.blank();
623
+ ui.banner('Fork App');
624
+ ui.blank();
625
+ ui.label('Source', source, 'cyan');
626
+ ui.label('Destination', destPath);
695
627
  if (options === null || options === void 0 ? void 0 : options.branch) {
696
- console.log(chalk.cyan(`🌿 Branch: ${options.branch}`));
628
+ ui.label('Branch', options.branch, 'cyan');
697
629
  }
698
630
  if (forkOptions.credentials) {
699
- console.log(chalk.cyan(`🔐 Authenticated as: ${forkOptions.credentials.username}`));
631
+ ui.label('Auth', forkOptions.credentials.username, 'cyan');
700
632
  }
633
+ ui.blank();
701
634
  // Perform the fork
702
635
  const result = await forkApp(source, destPath, forkOptions);
703
- console.log(chalk.green(`\n✅ Successfully forked app "${result.name}"!`));
704
- console.log(chalk.dim('\nNext steps:'));
705
- console.log(chalk.dim(' 1. Change directory: ') + chalk.cyan(`cd ${result.name}`));
706
- console.log(chalk.dim(' 2. Run your app: ') + chalk.cyan('agent run'));
707
- console.log(chalk.dim(' 3. Or start Studio: ') + chalk.cyan('agent studio'));
636
+ ui.divider(50);
637
+ ui.success(`Forked "${result.name}" successfully!`);
638
+ ui.blank();
639
+ ui.dim('Next steps:');
640
+ ui.row([
641
+ { text: ' 1. Change directory: ', dimColor: true },
642
+ { text: `cd ${result.name}`, color: 'cyan' },
643
+ ]);
644
+ ui.row([
645
+ { text: ' 2. Run your app: ', dimColor: true },
646
+ { text: 'agent run', color: 'cyan' },
647
+ ]);
648
+ ui.row([
649
+ { text: ' 3. Or start Studio: ', dimColor: true },
650
+ { text: 'agent studio', color: 'cyan' },
651
+ ]);
652
+ ui.divider(50);
653
+ ui.blank();
708
654
  }
709
655
  catch (error) {
710
- console.error(chalk.red('\n❌ Error:'), error instanceof Error ? error.message : error);
656
+ ui.error(error instanceof Error ? error.message : String(error));
711
657
  process.exit(1);
712
658
  }
713
659
  };