@agentuity/cli 0.0.72 → 0.0.73
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/bin/cli.ts +19 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +77 -19
- package/dist/cli.js.map +1 -1
- package/dist/cmd/auth/api.d.ts +2 -2
- package/dist/cmd/auth/api.d.ts.map +1 -1
- package/dist/cmd/auth/api.js +15 -14
- package/dist/cmd/auth/api.js.map +1 -1
- package/dist/cmd/auth/login.d.ts.map +1 -1
- package/dist/cmd/auth/login.js +37 -16
- package/dist/cmd/auth/login.js.map +1 -1
- package/dist/cmd/auth/ssh/api.d.ts.map +1 -1
- package/dist/cmd/auth/ssh/api.js +3 -2
- package/dist/cmd/auth/ssh/api.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +56 -8
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/bundler.d.ts.map +1 -1
- package/dist/cmd/build/bundler.js +2 -0
- package/dist/cmd/build/bundler.js.map +1 -1
- package/dist/cmd/build/format-schema.d.ts +6 -0
- package/dist/cmd/build/format-schema.d.ts.map +1 -0
- package/dist/cmd/build/format-schema.js +60 -0
- package/dist/cmd/build/format-schema.js.map +1 -0
- package/dist/cmd/build/index.d.ts.map +1 -1
- package/dist/cmd/build/index.js +13 -0
- package/dist/cmd/build/index.js.map +1 -1
- package/dist/cmd/build/plugin.d.ts.map +1 -1
- package/dist/cmd/build/plugin.js +72 -2
- package/dist/cmd/build/plugin.js.map +1 -1
- package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
- package/dist/cmd/cloud/deployment/show.js +34 -10
- package/dist/cmd/cloud/deployment/show.js.map +1 -1
- package/dist/cmd/dev/agents.d.ts.map +1 -1
- package/dist/cmd/dev/agents.js +2 -2
- package/dist/cmd/dev/agents.js.map +1 -1
- package/dist/cmd/dev/sync.d.ts.map +1 -1
- package/dist/cmd/dev/sync.js +2 -2
- package/dist/cmd/dev/sync.js.map +1 -1
- package/dist/cmd/project/show.d.ts.map +1 -1
- package/dist/cmd/project/show.js +8 -7
- package/dist/cmd/project/show.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +14 -2
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/tui.d.ts +20 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +85 -14
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/cli.ts +85 -25
- package/src/cmd/auth/api.ts +20 -22
- package/src/cmd/auth/login.ts +36 -17
- package/src/cmd/auth/ssh/api.ts +5 -6
- package/src/cmd/build/ast.ts +67 -8
- package/src/cmd/build/bundler.ts +2 -0
- package/src/cmd/build/format-schema.ts +66 -0
- package/src/cmd/build/index.ts +14 -0
- package/src/cmd/build/plugin.ts +86 -2
- package/src/cmd/cloud/deployment/show.ts +42 -10
- package/src/cmd/dev/agents.ts +2 -4
- package/src/cmd/dev/sync.ts +6 -8
- package/src/cmd/project/show.ts +8 -6
- package/src/cmd/project/template-flow.ts +21 -2
- package/src/config.ts +10 -0
- package/src/tui.ts +119 -16
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple formatter for schema code strings.
|
|
3
|
+
* Adds basic indentation and line breaks for readability.
|
|
4
|
+
*/
|
|
5
|
+
export function formatSchemaCode(code: string): string {
|
|
6
|
+
if (!code) return code;
|
|
7
|
+
|
|
8
|
+
let indentLevel = 0;
|
|
9
|
+
const indentSize = 2;
|
|
10
|
+
const lines: string[] = [];
|
|
11
|
+
let currentLine = '';
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < code.length; i++) {
|
|
14
|
+
const char = code[i];
|
|
15
|
+
const nextChar = code[i + 1];
|
|
16
|
+
const prevChar = i > 0 ? code[i - 1] : '';
|
|
17
|
+
|
|
18
|
+
// Skip existing whitespace/newlines
|
|
19
|
+
if (char === '\n' || char === '\r' || (char === ' ' && prevChar === ' ')) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Handle opening braces
|
|
24
|
+
if (char === '{') {
|
|
25
|
+
currentLine += char;
|
|
26
|
+
lines.push(' '.repeat(indentLevel * indentSize) + currentLine.trim());
|
|
27
|
+
indentLevel++;
|
|
28
|
+
currentLine = '';
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle closing braces
|
|
33
|
+
if (char === '}') {
|
|
34
|
+
if (currentLine.trim()) {
|
|
35
|
+
lines.push(' '.repeat(indentLevel * indentSize) + currentLine.trim());
|
|
36
|
+
currentLine = '';
|
|
37
|
+
}
|
|
38
|
+
indentLevel--;
|
|
39
|
+
// Check if next char is closing paren - if so, put on same line
|
|
40
|
+
if (nextChar === ')') {
|
|
41
|
+
currentLine = '}';
|
|
42
|
+
} else {
|
|
43
|
+
lines.push(' '.repeat(indentLevel * indentSize) + char);
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Handle commas - add line break after
|
|
49
|
+
if (char === ',') {
|
|
50
|
+
currentLine += char;
|
|
51
|
+
lines.push(' '.repeat(indentLevel * indentSize) + currentLine.trim());
|
|
52
|
+
currentLine = '';
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Accumulate characters
|
|
57
|
+
currentLine += char;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add any remaining content
|
|
61
|
+
if (currentLine.trim()) {
|
|
62
|
+
lines.push(' '.repeat(indentLevel * indentSize) + currentLine.trim());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return lines.join('\n');
|
|
66
|
+
}
|
package/src/cmd/build/index.ts
CHANGED
|
@@ -77,6 +77,20 @@ export const command = createCommand({
|
|
|
77
77
|
logger: ctx.logger,
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
// Copy profile-specific .env file AFTER bundling (bundler clears outDir first)
|
|
81
|
+
if (opts.dev && ctx.config?.name) {
|
|
82
|
+
const envSourcePath = join(absoluteProjectDir, `.env.${ctx.config.name}`);
|
|
83
|
+
const envDestPath = join(outDir, '.env');
|
|
84
|
+
|
|
85
|
+
const envFile = Bun.file(envSourcePath);
|
|
86
|
+
if (await envFile.exists()) {
|
|
87
|
+
await Bun.write(envDestPath, envFile);
|
|
88
|
+
ctx.logger.debug(`Copied ${envSourcePath} to ${envDestPath}`);
|
|
89
|
+
} else {
|
|
90
|
+
ctx.logger.debug(`No .env.${ctx.config.name} file found, skipping env copy`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
// Run TypeScript type checking after registry generation (skip in dev mode)
|
|
81
95
|
if (!opts.dev && !opts.skipTypeCheck) {
|
|
82
96
|
try {
|
package/src/cmd/build/plugin.ts
CHANGED
|
@@ -371,10 +371,30 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
373
|
const webstatic = serveStatic({ root: import.meta.dir + '/web' });
|
|
374
|
+
// In dev mode, serve from source; in prod, serve from build output
|
|
375
|
+
const publicRoot = ${isDevMode} ? ${JSON.stringify(join(srcDir, 'web', 'public'))} : import.meta.dir + '/web/public';
|
|
376
|
+
const publicstatic = serveStatic({ root: publicRoot, rewriteRequestPath: (path) => path });
|
|
374
377
|
router.get('/', (c) => c.html(index));
|
|
375
378
|
router.get('/web/chunk/*', webstatic);
|
|
376
379
|
router.get('/web/asset/*', webstatic);
|
|
377
|
-
|
|
380
|
+
// Serve public assets at root (e.g., /favicon.ico) - must be last
|
|
381
|
+
router.get('/*', async (c, next) => {
|
|
382
|
+
const path = c.req.path;
|
|
383
|
+
// Prevent directory traversal attacks
|
|
384
|
+
if (path.includes('..') || path.includes('%2e%2e')) {
|
|
385
|
+
return c.notFound();
|
|
386
|
+
}
|
|
387
|
+
// Only serve from public folder at root (skip /web/* routes and /)
|
|
388
|
+
if (path !== '/' && !path.startsWith('/web/')) {
|
|
389
|
+
try {
|
|
390
|
+
// serveStatic calls next() internally if file not found
|
|
391
|
+
return await publicstatic(c, next);
|
|
392
|
+
} catch (err) {
|
|
393
|
+
return next();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return next();
|
|
397
|
+
});
|
|
378
398
|
})();`);
|
|
379
399
|
}
|
|
380
400
|
|
|
@@ -415,7 +435,11 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
415
435
|
|
|
416
436
|
for (const subdir of subdirs) {
|
|
417
437
|
const fullPath = join(agentBaseDir, subdir);
|
|
418
|
-
if
|
|
438
|
+
// Check if this directory or any subdirectory contains agents
|
|
439
|
+
const hasAgentInTree = Array.from(agentDirs).some((agentDir) =>
|
|
440
|
+
agentDir.startsWith(fullPath)
|
|
441
|
+
);
|
|
442
|
+
if (!hasAgentInTree) {
|
|
419
443
|
throw new Error(
|
|
420
444
|
`Directory ${subdir} in src/agent must contain at least one agent (a file with a createAgent export)`
|
|
421
445
|
);
|
|
@@ -447,6 +471,56 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
447
471
|
if (statSync(apiFile).isFile()) {
|
|
448
472
|
try {
|
|
449
473
|
const routes = await parseRoute(rootDir, apiFile, projectId, deploymentId);
|
|
474
|
+
|
|
475
|
+
// Extract schemas from agents for routes that use validators
|
|
476
|
+
for (const route of routes) {
|
|
477
|
+
// Check if route has custom schema overrides from validator({ input, output })
|
|
478
|
+
const hasCustomInput = route.config?.inputSchemaVariable;
|
|
479
|
+
const hasCustomOutput = route.config?.outputSchemaVariable;
|
|
480
|
+
|
|
481
|
+
// If route uses agent.validator(), get schemas from the agent (unless overridden)
|
|
482
|
+
if (
|
|
483
|
+
route.config?.agentImportPath &&
|
|
484
|
+
(!hasCustomInput || !hasCustomOutput)
|
|
485
|
+
) {
|
|
486
|
+
const agentImportPath = route.config.agentImportPath as string;
|
|
487
|
+
// Match by import path: @agent/zod-test -> src/agent/zod-test/agent.ts
|
|
488
|
+
// Normalize import path by removing leading '@' -> agent/zod-test
|
|
489
|
+
const importPattern = agentImportPath.replace(/^@/, '');
|
|
490
|
+
// Escape regex special characters for safe pattern matching
|
|
491
|
+
const escapedPattern = importPattern.replace(
|
|
492
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
493
|
+
'\\$&'
|
|
494
|
+
);
|
|
495
|
+
// Match as complete path segment to avoid false positives (e.g., "agent/hello" matching "agent/hello-world")
|
|
496
|
+
const segmentPattern = new RegExp(`(^|/)${escapedPattern}(/|$)`);
|
|
497
|
+
|
|
498
|
+
for (const [, agentMd] of agentMetadata) {
|
|
499
|
+
const agentFilename = agentMd.get('filename');
|
|
500
|
+
if (agentFilename && segmentPattern.test(agentFilename)) {
|
|
501
|
+
// Use agent schemas unless overridden
|
|
502
|
+
const inputSchemaCode = hasCustomInput
|
|
503
|
+
? undefined
|
|
504
|
+
: agentMd.get('inputSchemaCode');
|
|
505
|
+
const outputSchemaCode = hasCustomOutput
|
|
506
|
+
? undefined
|
|
507
|
+
: agentMd.get('outputSchemaCode');
|
|
508
|
+
|
|
509
|
+
if (inputSchemaCode || outputSchemaCode) {
|
|
510
|
+
route.schema = {
|
|
511
|
+
input: inputSchemaCode,
|
|
512
|
+
output: outputSchemaCode,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// TODO: Extract inline schema code from custom validator({ input: z.string(), output: ... })
|
|
521
|
+
// For now, custom schema overrides with inline code are not extracted (would require parsing the validator call's object expression)
|
|
522
|
+
}
|
|
523
|
+
|
|
450
524
|
apiRoutesMetadata.push(...routes);
|
|
451
525
|
|
|
452
526
|
// Collect route info for RouteRegistry generation
|
|
@@ -594,6 +668,16 @@ await (async() => {
|
|
|
594
668
|
projectId,
|
|
595
669
|
};
|
|
596
670
|
|
|
671
|
+
// Extract schema codes if available
|
|
672
|
+
const inputSchemaCode = v.get('inputSchemaCode');
|
|
673
|
+
const outputSchemaCode = v.get('outputSchemaCode');
|
|
674
|
+
if (inputSchemaCode || outputSchemaCode) {
|
|
675
|
+
agentData.schema = {
|
|
676
|
+
input: inputSchemaCode,
|
|
677
|
+
output: outputSchemaCode,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
597
681
|
const evalsStr = v.get('evals');
|
|
598
682
|
if (evalsStr) {
|
|
599
683
|
logger.trace(
|
|
@@ -15,6 +15,10 @@ const DeploymentShowResponseSchema = z.object({
|
|
|
15
15
|
tags: z.array(z.string()).describe('Deployment tags'),
|
|
16
16
|
customDomains: z.array(z.string()).optional().describe('Custom domains'),
|
|
17
17
|
cloudRegion: z.string().optional().describe('Cloud region'),
|
|
18
|
+
resourceDb: z.string().nullable().optional().describe('the database name'),
|
|
19
|
+
resourceStorage: z.string().nullable().optional().describe('the storage name'),
|
|
20
|
+
deploymentLogsURL: z.string().nullable().optional().describe('the url to the deployment logs'),
|
|
21
|
+
buildLogsURL: z.string().nullable().optional().describe('the url to the build logs'),
|
|
18
22
|
metadata: z
|
|
19
23
|
.object({
|
|
20
24
|
git: z
|
|
@@ -87,27 +91,51 @@ export const showSubcommand = createSubcommand({
|
|
|
87
91
|
|
|
88
92
|
// Skip TUI output in JSON mode
|
|
89
93
|
if (!options.json) {
|
|
90
|
-
|
|
91
|
-
console.log(tui.bold('
|
|
92
|
-
console.log(tui.bold('
|
|
93
|
-
console.log(tui.bold('
|
|
94
|
-
console.log(tui.bold('
|
|
94
|
+
const maxWidth = 18;
|
|
95
|
+
console.log(tui.bold('ID:'.padEnd(maxWidth)) + deployment.id);
|
|
96
|
+
console.log(tui.bold('Project:'.padEnd(maxWidth)) + projectId);
|
|
97
|
+
console.log(tui.bold('State:'.padEnd(maxWidth)) + (deployment.state || 'unknown'));
|
|
98
|
+
console.log(tui.bold('Active:'.padEnd(maxWidth)) + (deployment.active ? 'Yes' : 'No'));
|
|
99
|
+
console.log(
|
|
100
|
+
tui.bold('Created:'.padEnd(maxWidth)) +
|
|
101
|
+
new Date(deployment.createdAt).toLocaleString()
|
|
102
|
+
);
|
|
95
103
|
if (deployment.updatedAt) {
|
|
96
104
|
console.log(
|
|
97
|
-
tui.bold('Updated:
|
|
105
|
+
tui.bold('Updated:'.padEnd(maxWidth)) +
|
|
106
|
+
new Date(deployment.updatedAt).toLocaleString()
|
|
98
107
|
);
|
|
99
108
|
}
|
|
100
109
|
if (deployment.message) {
|
|
101
|
-
console.log(tui.bold('Message:
|
|
110
|
+
console.log(tui.bold('Message:'.padEnd(maxWidth)) + deployment.message);
|
|
102
111
|
}
|
|
103
112
|
if (deployment.tags.length > 0) {
|
|
104
|
-
console.log(tui.bold('Tags:
|
|
113
|
+
console.log(tui.bold('Tags:'.padEnd(maxWidth)) + deployment.tags.join(', '));
|
|
105
114
|
}
|
|
106
115
|
if (deployment.customDomains && deployment.customDomains.length > 0) {
|
|
107
|
-
console.log(
|
|
116
|
+
console.log(
|
|
117
|
+
tui.bold('Domains:'.padEnd(maxWidth)) + deployment.customDomains.join(', ')
|
|
118
|
+
);
|
|
108
119
|
}
|
|
109
120
|
if (deployment.cloudRegion) {
|
|
110
|
-
console.log(tui.bold('Region:
|
|
121
|
+
console.log(tui.bold('Region:'.padEnd(maxWidth)) + deployment.cloudRegion);
|
|
122
|
+
}
|
|
123
|
+
if (deployment.resourceDb) {
|
|
124
|
+
console.log(tui.bold('Database:'.padEnd(maxWidth)) + deployment.resourceDb);
|
|
125
|
+
}
|
|
126
|
+
if (deployment.resourceStorage) {
|
|
127
|
+
console.log(tui.bold('Storage:'.padEnd(maxWidth)) + deployment.resourceStorage);
|
|
128
|
+
}
|
|
129
|
+
if (deployment.deploymentLogsURL) {
|
|
130
|
+
console.log(
|
|
131
|
+
tui.bold('Deployment Logs:'.padEnd(maxWidth)) +
|
|
132
|
+
tui.link(deployment.deploymentLogsURL)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (deployment.buildLogsURL) {
|
|
136
|
+
console.log(
|
|
137
|
+
tui.bold('Build Logs:'.padEnd(maxWidth)) + tui.link(deployment.buildLogsURL)
|
|
138
|
+
);
|
|
111
139
|
}
|
|
112
140
|
|
|
113
141
|
// Git metadata
|
|
@@ -153,6 +181,10 @@ export const showSubcommand = createSubcommand({
|
|
|
153
181
|
customDomains: deployment.customDomains ?? undefined,
|
|
154
182
|
cloudRegion: deployment.cloudRegion ?? undefined,
|
|
155
183
|
metadata: deployment.metadata ?? undefined,
|
|
184
|
+
resourceDb: deployment.resourceDb ?? undefined,
|
|
185
|
+
resourceStorage: deployment.resourceStorage ?? undefined,
|
|
186
|
+
deploymentLogsURL: deployment.deploymentLogsURL ?? undefined,
|
|
187
|
+
buildLogsURL: deployment.buildLogsURL ?? undefined,
|
|
156
188
|
};
|
|
157
189
|
} catch (ex) {
|
|
158
190
|
tui.fatal(`Failed to show deployment: ${ex}`);
|
package/src/cmd/dev/agents.ts
CHANGED
|
@@ -63,14 +63,12 @@ export const agentsSubcommand = createSubcommand({
|
|
|
63
63
|
const queryParams = deploymentId ? `?deploymentId=${deploymentId}` : '';
|
|
64
64
|
|
|
65
65
|
const response = options.json
|
|
66
|
-
? await apiClient.
|
|
67
|
-
'GET',
|
|
66
|
+
? await apiClient.get(
|
|
68
67
|
`/cli/agent/${projectId}${queryParams}`,
|
|
69
68
|
AgentsResponseSchema
|
|
70
69
|
)
|
|
71
70
|
: await tui.spinner('Fetching agents', async () => {
|
|
72
|
-
return apiClient.
|
|
73
|
-
'GET',
|
|
71
|
+
return apiClient.get(
|
|
74
72
|
`/cli/agent/${projectId}${queryParams}`,
|
|
75
73
|
AgentsResponseSchema
|
|
76
74
|
);
|
package/src/cmd/dev/sync.ts
CHANGED
|
@@ -253,11 +253,10 @@ class DevmodeSyncService implements IDevmodeSyncService {
|
|
|
253
253
|
JSON.stringify(payload, null, 2)
|
|
254
254
|
);
|
|
255
255
|
|
|
256
|
-
await this.apiClient.
|
|
257
|
-
'POST',
|
|
256
|
+
await this.apiClient.post(
|
|
258
257
|
'/cli/devmode/agent',
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
payload,
|
|
259
|
+
z.object({ success: z.boolean() })
|
|
261
260
|
);
|
|
262
261
|
}
|
|
263
262
|
|
|
@@ -280,11 +279,10 @@ class DevmodeSyncService implements IDevmodeSyncService {
|
|
|
280
279
|
JSON.stringify(payload, null, 2)
|
|
281
280
|
);
|
|
282
281
|
|
|
283
|
-
await this.apiClient.
|
|
284
|
-
'POST',
|
|
282
|
+
await this.apiClient.post(
|
|
285
283
|
'/cli/devmode/eval',
|
|
286
|
-
|
|
287
|
-
|
|
284
|
+
payload,
|
|
285
|
+
z.object({ success: z.boolean() })
|
|
288
286
|
);
|
|
289
287
|
}
|
|
290
288
|
}
|
package/src/cmd/project/show.ts
CHANGED
|
@@ -6,8 +6,10 @@ import { getCommand } from '../../command-prefix';
|
|
|
6
6
|
|
|
7
7
|
const ProjectShowResponseSchema = z.object({
|
|
8
8
|
id: z.string().describe('Project ID'),
|
|
9
|
+
name: z.string().describe('Project name'),
|
|
10
|
+
description: z.string().nullable().optional().describe('Project description'),
|
|
11
|
+
tags: z.array(z.string()).nullable().optional().describe('Project tags'),
|
|
9
12
|
orgId: z.string().describe('Organization ID'),
|
|
10
|
-
name: z.string().optional().describe('Project name'),
|
|
11
13
|
secrets: z.record(z.string(), z.string()).optional().describe('Project secrets (masked)'),
|
|
12
14
|
env: z.record(z.string(), z.string()).optional().describe('Environment variables'),
|
|
13
15
|
});
|
|
@@ -45,16 +47,16 @@ export const showSubcommand = createSubcommand({
|
|
|
45
47
|
tui.fatal('Project not found');
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
if (options.json) {
|
|
49
|
-
|
|
50
|
-
} else {
|
|
51
|
-
tui.table([project], ['id', 'orgId']);
|
|
50
|
+
if (!options.json) {
|
|
51
|
+
tui.table([project], ['id', 'name', 'description', 'tags', 'orgId']);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
return {
|
|
55
55
|
id: project.id,
|
|
56
|
+
name: project.name,
|
|
57
|
+
description: project.description,
|
|
58
|
+
tags: project.tags,
|
|
56
59
|
orgId: project.orgId,
|
|
57
|
-
name: undefined,
|
|
58
60
|
secrets: project.secrets,
|
|
59
61
|
env: project.env,
|
|
60
62
|
};
|
|
@@ -177,7 +177,13 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
|
|
|
177
177
|
if (initialTemplate) {
|
|
178
178
|
const found = templates.find((t) => t.id === initialTemplate);
|
|
179
179
|
if (!found) {
|
|
180
|
-
|
|
180
|
+
const availableTemplates = templates
|
|
181
|
+
.map((t) => ` - ${t.id.padEnd(20)} ${t.description}`)
|
|
182
|
+
.join('\n');
|
|
183
|
+
logger.fatal(
|
|
184
|
+
`Template "${initialTemplate}" not found\n\nAvailable templates:\n${availableTemplates}`,
|
|
185
|
+
ErrorCode.RESOURCE_NOT_FOUND
|
|
186
|
+
);
|
|
181
187
|
return;
|
|
182
188
|
}
|
|
183
189
|
selectedTemplate = found;
|
|
@@ -232,7 +238,7 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
|
|
|
232
238
|
|
|
233
239
|
const resourceConfig: ResourcesTypes = Resources.parse({});
|
|
234
240
|
|
|
235
|
-
if (auth && apiClient && catalystClient && orgId && region) {
|
|
241
|
+
if (auth && apiClient && catalystClient && orgId && region && !skipPrompts) {
|
|
236
242
|
// Fetch resources for selected org and region using Catalyst API
|
|
237
243
|
const resources = await tui.spinner({
|
|
238
244
|
message: 'Fetching resources',
|
|
@@ -316,12 +322,25 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
|
|
|
316
322
|
|
|
317
323
|
const cloudRegion = region ?? process.env.AGENTUITY_REGION ?? 'usc';
|
|
318
324
|
|
|
325
|
+
const pkgJsonPath = resolve(dest, 'package.json');
|
|
326
|
+
let pkgJson: { description?: string; keywords?: string[] } = {};
|
|
327
|
+
if (existsSync(pkgJsonPath)) {
|
|
328
|
+
pkgJson = await Bun.file(pkgJsonPath).json();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const keywords = Array.isArray(pkgJson.keywords) ? pkgJson.keywords : [];
|
|
332
|
+
const tags = keywords.filter(
|
|
333
|
+
(tag) => tag.toLowerCase() !== 'agentuity' && !tag.toLowerCase().startsWith('agentuity')
|
|
334
|
+
);
|
|
335
|
+
|
|
319
336
|
await tui.spinner({
|
|
320
337
|
message: 'Registering your project',
|
|
321
338
|
clearOnSuccess: true,
|
|
322
339
|
callback: async () => {
|
|
323
340
|
const project = await projectCreate(apiClient, {
|
|
324
341
|
name: projectName,
|
|
342
|
+
description: pkgJson.description,
|
|
343
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
325
344
|
orgId,
|
|
326
345
|
cloudRegion,
|
|
327
346
|
});
|
package/src/config.ts
CHANGED
|
@@ -51,6 +51,16 @@ export async function saveProfile(path: string): Promise<void> {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export async function getProfile(): Promise<string> {
|
|
54
|
+
// Check environment variable first
|
|
55
|
+
if (process.env.AGENTUITY_PROFILE) {
|
|
56
|
+
const profileName = process.env.AGENTUITY_PROFILE;
|
|
57
|
+
const envProfilePath = join(getDefaultConfigDir(), `${profileName}.yaml`);
|
|
58
|
+
const envFile = Bun.file(envProfilePath);
|
|
59
|
+
if (await envFile.exists()) {
|
|
60
|
+
return envProfilePath;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
const profilePath = getProfilePath();
|
|
55
65
|
const defaultConfigPath = getDefaultConfigPath();
|
|
56
66
|
|
package/src/tui.ts
CHANGED
|
@@ -927,13 +927,34 @@ export interface LoggerSpinnerOptions<T> {
|
|
|
927
927
|
maxLines?: number;
|
|
928
928
|
}
|
|
929
929
|
|
|
930
|
+
/**
|
|
931
|
+
* Spinner options (with countdown timer)
|
|
932
|
+
*/
|
|
933
|
+
export interface CountdownSpinnerOptions<T> {
|
|
934
|
+
type: 'countdown';
|
|
935
|
+
message: string;
|
|
936
|
+
timeoutMs: number;
|
|
937
|
+
callback: () => Promise<T>;
|
|
938
|
+
/**
|
|
939
|
+
* If true, clear the spinner output on success (no icon, no message)
|
|
940
|
+
* Defaults to false
|
|
941
|
+
*/
|
|
942
|
+
clearOnSuccess?: boolean;
|
|
943
|
+
/**
|
|
944
|
+
* Optional callback to handle Enter key press
|
|
945
|
+
* Can be used to open a URL in the browser
|
|
946
|
+
*/
|
|
947
|
+
onEnterPress?: () => void;
|
|
948
|
+
}
|
|
949
|
+
|
|
930
950
|
/**
|
|
931
951
|
* Spinner options (discriminated union)
|
|
932
952
|
*/
|
|
933
953
|
export type SpinnerOptions<T> =
|
|
934
954
|
| SimpleSpinnerOptions<T>
|
|
935
955
|
| ProgressSpinnerOptions<T>
|
|
936
|
-
| LoggerSpinnerOptions<T
|
|
956
|
+
| LoggerSpinnerOptions<T>
|
|
957
|
+
| CountdownSpinnerOptions<T>;
|
|
937
958
|
|
|
938
959
|
/**
|
|
939
960
|
* Run a callback with an animated spinner (simple overload)
|
|
@@ -993,9 +1014,11 @@ export async function spinner<T>(
|
|
|
993
1014
|
// In non-TTY mode, just write logs directly to stdout
|
|
994
1015
|
process.stdout.write(logMessage + '\n');
|
|
995
1016
|
})
|
|
996
|
-
:
|
|
1017
|
+
: options.type === 'countdown'
|
|
997
1018
|
? await options.callback()
|
|
998
|
-
:
|
|
1019
|
+
: typeof options.callback === 'function'
|
|
1020
|
+
? await options.callback()
|
|
1021
|
+
: await options.callback;
|
|
999
1022
|
|
|
1000
1023
|
// If clearOnSuccess is true, don't show success message
|
|
1001
1024
|
// Also skip success message in JSON mode
|
|
@@ -1025,6 +1048,7 @@ export async function spinner<T>(
|
|
|
1025
1048
|
|
|
1026
1049
|
let frameIndex = 0;
|
|
1027
1050
|
let currentProgress: number | undefined;
|
|
1051
|
+
let remainingTime: number | undefined;
|
|
1028
1052
|
const logLines: string[] = [];
|
|
1029
1053
|
const maxLines = options.type === 'logger' ? (options.maxLines ?? 3) : 0;
|
|
1030
1054
|
const mutedColor = getColor('muted');
|
|
@@ -1045,14 +1069,19 @@ export async function spinner<T>(
|
|
|
1045
1069
|
const color = colorDef[currentColorScheme];
|
|
1046
1070
|
const frame = `${color}${bold}${frames[frameIndex % frames.length]}${reset}`;
|
|
1047
1071
|
|
|
1048
|
-
// Add progress indicator if available
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1072
|
+
// Add progress indicator or countdown timer if available
|
|
1073
|
+
let indicator = '';
|
|
1074
|
+
if (currentProgress !== undefined) {
|
|
1075
|
+
indicator = ` ${cyanColor}${Math.floor(currentProgress)}%${reset}`;
|
|
1076
|
+
} else if (remainingTime !== undefined) {
|
|
1077
|
+
const minutes = Math.floor(remainingTime / 60);
|
|
1078
|
+
const seconds = Math.floor(remainingTime % 60);
|
|
1079
|
+
const timeStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
1080
|
+
indicator = ` ${mutedColor}(${timeStr} remaining)${reset}`;
|
|
1081
|
+
}
|
|
1053
1082
|
|
|
1054
1083
|
// Render spinner line
|
|
1055
|
-
process.stderr.write(`\r\x1b[K${frame} ${message}${
|
|
1084
|
+
process.stderr.write(`\r\x1b[K${frame} ${message}${indicator}\n`);
|
|
1056
1085
|
|
|
1057
1086
|
// Render log lines if in logger mode
|
|
1058
1087
|
if (options.type === 'logger') {
|
|
@@ -1089,16 +1118,88 @@ export async function spinner<T>(
|
|
|
1089
1118
|
logLines.push(logMessage);
|
|
1090
1119
|
};
|
|
1091
1120
|
|
|
1121
|
+
// Countdown interval tracking
|
|
1122
|
+
let countdownInterval: NodeJS.Timeout | undefined;
|
|
1123
|
+
let keypressListener: ((chunk: Buffer) => void) | undefined;
|
|
1124
|
+
|
|
1125
|
+
// Helper to clean up all resources
|
|
1126
|
+
const cleanup = () => {
|
|
1127
|
+
if (countdownInterval) {
|
|
1128
|
+
clearInterval(countdownInterval);
|
|
1129
|
+
}
|
|
1130
|
+
if (keypressListener) {
|
|
1131
|
+
process.stdin.off('data', keypressListener);
|
|
1132
|
+
if (process.stdin.isTTY) {
|
|
1133
|
+
process.stdin.setRawMode(false);
|
|
1134
|
+
process.stdin.pause();
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
process.off('SIGINT', cleanupAndExit);
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
// Set up SIGINT handler for clean exit
|
|
1141
|
+
const cleanupAndExit = () => {
|
|
1142
|
+
cleanup();
|
|
1143
|
+
|
|
1144
|
+
// Stop animation
|
|
1145
|
+
clearInterval(interval);
|
|
1146
|
+
|
|
1147
|
+
// Move cursor to start of output, clear all lines
|
|
1148
|
+
if (linesRendered > 0) {
|
|
1149
|
+
process.stderr.write(`\x1b[${linesRendered}A`);
|
|
1150
|
+
}
|
|
1151
|
+
process.stderr.write('\x1b[J'); // Clear from cursor to end of screen
|
|
1152
|
+
process.stderr.write('\x1B[?25h'); // Show cursor
|
|
1153
|
+
|
|
1154
|
+
process.exit(130); // Standard exit code for SIGINT
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
process.on('SIGINT', cleanupAndExit);
|
|
1158
|
+
|
|
1092
1159
|
try {
|
|
1160
|
+
// For countdown, set up timer tracking and optional keyboard listener
|
|
1161
|
+
if (options.type === 'countdown') {
|
|
1162
|
+
const startTime = Date.now();
|
|
1163
|
+
remainingTime = options.timeoutMs / 1000;
|
|
1164
|
+
countdownInterval = setInterval(() => {
|
|
1165
|
+
const elapsed = Date.now() - startTime;
|
|
1166
|
+
remainingTime = Math.max(0, (options.timeoutMs - elapsed) / 1000);
|
|
1167
|
+
}, 100);
|
|
1168
|
+
|
|
1169
|
+
// Set up Enter key listener if callback provided
|
|
1170
|
+
if (options.onEnterPress && process.stdin.isTTY) {
|
|
1171
|
+
process.stdin.setRawMode(true);
|
|
1172
|
+
process.stdin.resume();
|
|
1173
|
+
|
|
1174
|
+
keypressListener = (chunk: Buffer) => {
|
|
1175
|
+
const key = chunk.toString();
|
|
1176
|
+
// Check for Enter key (both \r and \n)
|
|
1177
|
+
if (key === '\r' || key === '\n') {
|
|
1178
|
+
options.onEnterPress!();
|
|
1179
|
+
}
|
|
1180
|
+
// Check for Ctrl+C - let it propagate as SIGINT
|
|
1181
|
+
if (key === '\x03') {
|
|
1182
|
+
process.kill(process.pid, 'SIGINT');
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
process.stdin.on('data', keypressListener);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1093
1190
|
// Execute callback
|
|
1094
1191
|
const result =
|
|
1095
|
-
options.type === '
|
|
1096
|
-
? await options.callback(
|
|
1097
|
-
: options.type === '
|
|
1098
|
-
? await options.callback(
|
|
1099
|
-
:
|
|
1100
|
-
? await options.callback()
|
|
1101
|
-
:
|
|
1192
|
+
options.type === 'countdown'
|
|
1193
|
+
? await options.callback()
|
|
1194
|
+
: options.type === 'progress'
|
|
1195
|
+
? await options.callback(progressCallback)
|
|
1196
|
+
: options.type === 'logger'
|
|
1197
|
+
? await options.callback(logCallback)
|
|
1198
|
+
: typeof options.callback === 'function'
|
|
1199
|
+
? await options.callback()
|
|
1200
|
+
: await options.callback;
|
|
1201
|
+
|
|
1202
|
+
cleanup();
|
|
1102
1203
|
|
|
1103
1204
|
// Stop animation first
|
|
1104
1205
|
clearInterval(interval);
|
|
@@ -1119,6 +1220,8 @@ export async function spinner<T>(
|
|
|
1119
1220
|
|
|
1120
1221
|
return result;
|
|
1121
1222
|
} catch (err) {
|
|
1223
|
+
cleanup();
|
|
1224
|
+
|
|
1122
1225
|
// Stop animation first
|
|
1123
1226
|
clearInterval(interval);
|
|
1124
1227
|
|