@agentuity/cli 0.0.6
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/AGENTS.md +139 -0
- package/README.md +239 -0
- package/bin/cli.ts +71 -0
- package/dist/api.d.ts +25 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/auth.d.ts +7 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/banner.d.ts +2 -0
- package/dist/banner.d.ts.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cmd/auth/api.d.ts +9 -0
- package/dist/cmd/auth/api.d.ts.map +1 -0
- package/dist/cmd/auth/index.d.ts +2 -0
- package/dist/cmd/auth/index.d.ts.map +1 -0
- package/dist/cmd/auth/login.d.ts +3 -0
- package/dist/cmd/auth/login.d.ts.map +1 -0
- package/dist/cmd/auth/logout.d.ts +3 -0
- package/dist/cmd/auth/logout.d.ts.map +1 -0
- package/dist/cmd/bundle/ast.d.ts +2 -0
- package/dist/cmd/bundle/ast.d.ts.map +1 -0
- package/dist/cmd/bundle/bundler.d.ts +6 -0
- package/dist/cmd/bundle/bundler.d.ts.map +1 -0
- package/dist/cmd/bundle/file.d.ts +2 -0
- package/dist/cmd/bundle/file.d.ts.map +1 -0
- package/dist/cmd/bundle/index.d.ts +2 -0
- package/dist/cmd/bundle/index.d.ts.map +1 -0
- package/dist/cmd/bundle/plugin.d.ts +4 -0
- package/dist/cmd/bundle/plugin.d.ts.map +1 -0
- package/dist/cmd/dev/index.d.ts +2 -0
- package/dist/cmd/dev/index.d.ts.map +1 -0
- package/dist/cmd/example/create-user.d.ts +2 -0
- package/dist/cmd/example/create-user.d.ts.map +1 -0
- package/dist/cmd/example/create.d.ts +2 -0
- package/dist/cmd/example/create.d.ts.map +1 -0
- package/dist/cmd/example/deploy.d.ts +2 -0
- package/dist/cmd/example/deploy.d.ts.map +1 -0
- package/dist/cmd/example/index.d.ts +2 -0
- package/dist/cmd/example/index.d.ts.map +1 -0
- package/dist/cmd/example/list.d.ts +2 -0
- package/dist/cmd/example/list.d.ts.map +1 -0
- package/dist/cmd/example/run-command.d.ts +2 -0
- package/dist/cmd/example/run-command.d.ts.map +1 -0
- package/dist/cmd/example/sound.d.ts +3 -0
- package/dist/cmd/example/sound.d.ts.map +1 -0
- package/dist/cmd/example/spinner.d.ts +2 -0
- package/dist/cmd/example/spinner.d.ts.map +1 -0
- package/dist/cmd/example/steps.d.ts +2 -0
- package/dist/cmd/example/steps.d.ts.map +1 -0
- package/dist/cmd/example/version.d.ts +2 -0
- package/dist/cmd/example/version.d.ts.map +1 -0
- package/dist/cmd/index.d.ts +3 -0
- package/dist/cmd/index.d.ts.map +1 -0
- package/dist/cmd/profile/create.d.ts +2 -0
- package/dist/cmd/profile/create.d.ts.map +1 -0
- package/dist/cmd/profile/delete.d.ts +2 -0
- package/dist/cmd/profile/delete.d.ts.map +1 -0
- package/dist/cmd/profile/index.d.ts +2 -0
- package/dist/cmd/profile/index.d.ts.map +1 -0
- package/dist/cmd/profile/list.d.ts +3 -0
- package/dist/cmd/profile/list.d.ts.map +1 -0
- package/dist/cmd/profile/show.d.ts +2 -0
- package/dist/cmd/profile/show.d.ts.map +1 -0
- package/dist/cmd/profile/use.d.ts +2 -0
- package/dist/cmd/profile/use.d.ts.map +1 -0
- package/dist/cmd/project/create.d.ts +2 -0
- package/dist/cmd/project/create.d.ts.map +1 -0
- package/dist/cmd/project/delete.d.ts +2 -0
- package/dist/cmd/project/delete.d.ts.map +1 -0
- package/dist/cmd/project/index.d.ts +2 -0
- package/dist/cmd/project/index.d.ts.map +1 -0
- package/dist/cmd/project/list.d.ts +2 -0
- package/dist/cmd/project/list.d.ts.map +1 -0
- package/dist/cmd/project/show.d.ts +2 -0
- package/dist/cmd/project/show.d.ts.map +1 -0
- package/dist/cmd/version/index.d.ts +2 -0
- package/dist/cmd/version/index.d.ts.map +1 -0
- package/dist/command-prefix.d.ts +11 -0
- package/dist/command-prefix.d.ts.map +1 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/legacy-check.d.ts +6 -0
- package/dist/legacy-check.d.ts.map +1 -0
- package/dist/logger.d.ts +24 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/schema-parser.d.ts +24 -0
- package/dist/schema-parser.d.ts.map +1 -0
- package/dist/sound.d.ts +2 -0
- package/dist/sound.d.ts.map +1 -0
- package/dist/steps.d.ts +59 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/terminal.d.ts +3 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/tui.d.ts +156 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/types.d.ts +164 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +46 -0
- package/src/api-errors.md +115 -0
- package/src/api.ts +186 -0
- package/src/auth.ts +91 -0
- package/src/banner.ts +23 -0
- package/src/cli.ts +198 -0
- package/src/cmd/auth/README.md +95 -0
- package/src/cmd/auth/api.ts +71 -0
- package/src/cmd/auth/index.ts +9 -0
- package/src/cmd/auth/login.ts +76 -0
- package/src/cmd/auth/logout.ts +14 -0
- package/src/cmd/bundle/ast.ts +228 -0
- package/src/cmd/bundle/bundler.ts +88 -0
- package/src/cmd/bundle/file.ts +16 -0
- package/src/cmd/bundle/index.ts +38 -0
- package/src/cmd/bundle/plugin.ts +259 -0
- package/src/cmd/dev/index.ts +83 -0
- package/src/cmd/example/create-user.ts +38 -0
- package/src/cmd/example/create.ts +31 -0
- package/src/cmd/example/deploy.ts +36 -0
- package/src/cmd/example/index.ts +27 -0
- package/src/cmd/example/list.ts +32 -0
- package/src/cmd/example/run-command.ts +45 -0
- package/src/cmd/example/sound.ts +14 -0
- package/src/cmd/example/spinner.ts +44 -0
- package/src/cmd/example/steps.ts +66 -0
- package/src/cmd/example/version.ts +13 -0
- package/src/cmd/index.ts +46 -0
- package/src/cmd/profile/README.md +80 -0
- package/src/cmd/profile/create.ts +57 -0
- package/src/cmd/profile/delete.ts +52 -0
- package/src/cmd/profile/index.ts +12 -0
- package/src/cmd/profile/list.ts +27 -0
- package/src/cmd/profile/show.ts +54 -0
- package/src/cmd/profile/use.ts +30 -0
- package/src/cmd/project/create.ts +247 -0
- package/src/cmd/project/delete.ts +13 -0
- package/src/cmd/project/index.ts +11 -0
- package/src/cmd/project/list.ts +13 -0
- package/src/cmd/project/show.ts +12 -0
- package/src/cmd/version/index.ts +16 -0
- package/src/command-prefix.ts +43 -0
- package/src/config.ts +304 -0
- package/src/index.ts +40 -0
- package/src/legacy-check.ts +127 -0
- package/src/logger.ts +235 -0
- package/src/runtime.ts +22 -0
- package/src/schema-parser.ts +213 -0
- package/src/sound.ts +25 -0
- package/src/steps.ts +245 -0
- package/src/terminal.ts +151 -0
- package/src/tui.md +254 -0
- package/src/tui.ts +838 -0
- package/src/types.ts +243 -0
- package/src/version.ts +29 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { createSubcommand } from '@/types';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import enquirer from 'enquirer';
|
|
4
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
5
|
+
import { resolve, basename, join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export const createProjectSubcommand = createSubcommand({
|
|
8
|
+
name: 'create',
|
|
9
|
+
description: 'Create a new project',
|
|
10
|
+
aliases: ['new'],
|
|
11
|
+
toplevel: true,
|
|
12
|
+
requiresAuth: false,
|
|
13
|
+
schema: {
|
|
14
|
+
options: z.object({
|
|
15
|
+
name: z.string().optional().describe('Project name'),
|
|
16
|
+
dir: z.string().optional().describe('Directory to create the project in'),
|
|
17
|
+
install: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.default(true)
|
|
21
|
+
.describe('Run bun install after creating the project (use --no-install to skip)'),
|
|
22
|
+
confirm: z.boolean().optional().describe('Skip confirmation prompts'),
|
|
23
|
+
fromBunCreate: z
|
|
24
|
+
.boolean()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Internal: called from bun create postinstall'),
|
|
27
|
+
dev: z.boolean().optional().describe('Internal: use local template for testing'),
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
async handler(ctx) {
|
|
32
|
+
const { logger, opts } = ctx;
|
|
33
|
+
|
|
34
|
+
// Case 2: Called from bun create postinstall
|
|
35
|
+
if (opts.fromBunCreate) {
|
|
36
|
+
const projectDir = process.cwd();
|
|
37
|
+
const packageJsonPath = join(projectDir, 'package.json');
|
|
38
|
+
|
|
39
|
+
if (!existsSync(packageJsonPath)) {
|
|
40
|
+
logger.error('package.json not found in current directory');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Disable log prefixes for cleaner postinstall output
|
|
45
|
+
logger.setShowPrefix(false);
|
|
46
|
+
|
|
47
|
+
const packageJsonFile = Bun.file(packageJsonPath);
|
|
48
|
+
const packageJson = await packageJsonFile.json();
|
|
49
|
+
const projectName = packageJson.name || basename(projectDir);
|
|
50
|
+
|
|
51
|
+
logger.info(`\n🔧 Setting up ${projectName}...\n`);
|
|
52
|
+
|
|
53
|
+
// Update package.json - remove bun-create metadata
|
|
54
|
+
packageJson.name = projectName;
|
|
55
|
+
delete packageJson['bun-create'];
|
|
56
|
+
delete packageJson.bin;
|
|
57
|
+
packageJson.private = true;
|
|
58
|
+
delete packageJson.files;
|
|
59
|
+
delete packageJson.keywords;
|
|
60
|
+
delete packageJson.author;
|
|
61
|
+
delete packageJson.license;
|
|
62
|
+
delete packageJson.publishConfig;
|
|
63
|
+
delete packageJson.description;
|
|
64
|
+
|
|
65
|
+
// Remove enquirer from dependencies (only needed for setup)
|
|
66
|
+
if (packageJson.dependencies) {
|
|
67
|
+
delete packageJson.dependencies.enquirer;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, '\t'));
|
|
71
|
+
logger.info('✓ Updated package.json');
|
|
72
|
+
|
|
73
|
+
// Update README.md
|
|
74
|
+
const readmePath = join(projectDir, 'README.md');
|
|
75
|
+
if (existsSync(readmePath)) {
|
|
76
|
+
const readmeFile = Bun.file(readmePath);
|
|
77
|
+
let readme = await readmeFile.text();
|
|
78
|
+
readme = readme.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
79
|
+
await Bun.write(readmePath, readme);
|
|
80
|
+
logger.info('✓ Updated README.md');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Update AGENTS.md
|
|
84
|
+
const agentsMdPath = join(projectDir, 'AGENTS.md');
|
|
85
|
+
if (existsSync(agentsMdPath)) {
|
|
86
|
+
const agentsMdFile = Bun.file(agentsMdPath);
|
|
87
|
+
let agentsMd = await agentsMdFile.text();
|
|
88
|
+
agentsMd = agentsMd.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
89
|
+
await Bun.write(agentsMdPath, agentsMd);
|
|
90
|
+
logger.info('✓ Updated AGENTS.md');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Remove setup files
|
|
94
|
+
const filesToRemove = ['setup.ts'];
|
|
95
|
+
for (const file of filesToRemove) {
|
|
96
|
+
const filePath = join(projectDir, file);
|
|
97
|
+
if (existsSync(filePath)) {
|
|
98
|
+
await Bun.$`rm ${filePath}`;
|
|
99
|
+
logger.info('✓ Removed ${file}');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
logger.info('\n✨ Setup complete!\n');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Case 1: Normal CLI flow
|
|
108
|
+
// Relaxed validation: any reasonable name between 2-64 characters
|
|
109
|
+
const isValidProjectName = (name: string): boolean => {
|
|
110
|
+
return name.trim().length >= 2 && name.trim().length <= 64;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Transform name to URL and disk-friendly format
|
|
114
|
+
const transformToDirectoryName = (name: string): string => {
|
|
115
|
+
const result = name
|
|
116
|
+
.trim()
|
|
117
|
+
.toLowerCase()
|
|
118
|
+
.replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with hyphens
|
|
119
|
+
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
|
|
120
|
+
.replace(/-+/g, '-') // Replace consecutive hyphens with single hyphen
|
|
121
|
+
.substring(0, 64); // Ensure max length
|
|
122
|
+
|
|
123
|
+
// Validate result is non-empty (happens when name contains only special chars)
|
|
124
|
+
if (!result) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Invalid project name "${name}": must contain at least one alphanumeric character`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Get project name
|
|
134
|
+
let projectName = opts.name;
|
|
135
|
+
while (!projectName || !isValidProjectName(projectName)) {
|
|
136
|
+
const result = await enquirer.prompt<{ name: string }>({
|
|
137
|
+
type: 'input',
|
|
138
|
+
name: 'name',
|
|
139
|
+
message: 'Project name:',
|
|
140
|
+
initial: projectName,
|
|
141
|
+
validate: (value: string) => {
|
|
142
|
+
if (!value) return 'Project name is required';
|
|
143
|
+
if (!isValidProjectName(value)) {
|
|
144
|
+
return 'Project name must be between 2 and 64 characters';
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
projectName = result.name;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
projectName = projectName.trim();
|
|
153
|
+
const projectDirName = transformToDirectoryName(projectName);
|
|
154
|
+
|
|
155
|
+
// Get directory - if specified, create the project there, otherwise create in current dir
|
|
156
|
+
const baseDir = opts.dir ? resolve(opts.dir) : process.cwd();
|
|
157
|
+
const targetDir = resolve(baseDir, projectDirName);
|
|
158
|
+
|
|
159
|
+
// Check if directory exists and validate
|
|
160
|
+
let shouldProceed = true;
|
|
161
|
+
if (existsSync(targetDir)) {
|
|
162
|
+
const files = readdirSync(targetDir);
|
|
163
|
+
const hasFiles = files.length > 0;
|
|
164
|
+
|
|
165
|
+
if (hasFiles) {
|
|
166
|
+
if (opts.confirm === false) {
|
|
167
|
+
logger.error(`Directory ${targetDir} is not empty and --no-confirm was specified`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Require explicit confirmation in non-TTY environments
|
|
172
|
+
if (opts.confirm !== true && !process.stdin.isTTY) {
|
|
173
|
+
logger.error(
|
|
174
|
+
`Directory "${targetDir}" is not empty. Use --confirm flag in non-interactive environments.`
|
|
175
|
+
);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Interactive prompt in TTY environments
|
|
180
|
+
if (opts.confirm !== true && process.stdin.isTTY) {
|
|
181
|
+
const result = await enquirer.prompt<{ proceed: boolean }>({
|
|
182
|
+
type: 'confirm',
|
|
183
|
+
name: 'proceed',
|
|
184
|
+
message: `Directory "${targetDir}" is not empty. Files may be overwritten. Continue?`,
|
|
185
|
+
initial: false,
|
|
186
|
+
});
|
|
187
|
+
shouldProceed = result.proceed;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!shouldProceed) {
|
|
191
|
+
logger.info('Operation cancelled');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Print collected values
|
|
198
|
+
logger.info('\n=== Project Configuration ===');
|
|
199
|
+
logger.info(`Name: ${projectName}`);
|
|
200
|
+
logger.info(`Directory Name: ${projectDirName}`);
|
|
201
|
+
logger.info(`Target Directory: ${targetDir}`);
|
|
202
|
+
logger.info('=============================\n');
|
|
203
|
+
|
|
204
|
+
// Run bun create to scaffold the project
|
|
205
|
+
logger.info('Creating project from template...');
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
// Determine template name based on dev mode
|
|
209
|
+
const templateName = opts.dev ? 'agentuity-dev' : 'agentuity';
|
|
210
|
+
|
|
211
|
+
if (opts.dev) {
|
|
212
|
+
logger.info('🔧 Dev mode: Using local template');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Build bun create command args
|
|
216
|
+
// Note: bun create supports --no-install to skip dependency installation
|
|
217
|
+
const bunCreateArgs = ['bun', 'create'];
|
|
218
|
+
if (opts.install === false) {
|
|
219
|
+
bunCreateArgs.push('--no-install');
|
|
220
|
+
}
|
|
221
|
+
bunCreateArgs.push(templateName, projectDirName);
|
|
222
|
+
|
|
223
|
+
logger.info(`Running: ${bunCreateArgs.join(' ')}`);
|
|
224
|
+
|
|
225
|
+
const result = Bun.spawn(bunCreateArgs, {
|
|
226
|
+
cwd: baseDir,
|
|
227
|
+
stdout: 'inherit',
|
|
228
|
+
stderr: 'inherit',
|
|
229
|
+
stdin: 'inherit',
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const exitCode = await result.exited;
|
|
233
|
+
|
|
234
|
+
if (exitCode !== 0) {
|
|
235
|
+
throw new Error(`bun create exited with code ${exitCode}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
logger.info('\n✨ Project created successfully!');
|
|
239
|
+
logger.info(`\nNext steps:`);
|
|
240
|
+
logger.info(` cd ${projectDirName}`);
|
|
241
|
+
logger.info(` bun run dev`);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
logger.error('Failed to create project:', error);
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createSubcommand } from '@/types';
|
|
2
|
+
|
|
3
|
+
export const deleteSubcommand = createSubcommand({
|
|
4
|
+
name: 'delete',
|
|
5
|
+
description: 'Delete a project',
|
|
6
|
+
aliases: ['rm', 'del'],
|
|
7
|
+
requiresAuth: true,
|
|
8
|
+
|
|
9
|
+
async handler(ctx) {
|
|
10
|
+
const { logger } = ctx;
|
|
11
|
+
logger.info('TODO: Implement project delete functionality');
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createCommand } from '@/types';
|
|
2
|
+
import { createProjectSubcommand } from './create';
|
|
3
|
+
import { listSubcommand } from './list';
|
|
4
|
+
import { deleteSubcommand } from './delete';
|
|
5
|
+
import { showSubcommand } from './show';
|
|
6
|
+
|
|
7
|
+
export const command = createCommand({
|
|
8
|
+
name: 'project',
|
|
9
|
+
description: 'Manage Agentuity projects',
|
|
10
|
+
subcommands: [createProjectSubcommand, listSubcommand, deleteSubcommand, showSubcommand],
|
|
11
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createSubcommand } from '@/types';
|
|
2
|
+
|
|
3
|
+
export const listSubcommand = createSubcommand({
|
|
4
|
+
name: 'list',
|
|
5
|
+
description: 'List all projects',
|
|
6
|
+
aliases: ['ls'],
|
|
7
|
+
requiresAuth: true,
|
|
8
|
+
|
|
9
|
+
async handler(ctx) {
|
|
10
|
+
const { logger } = ctx;
|
|
11
|
+
logger.info('TODO: Implement project list functionality');
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createSubcommand } from '@/types';
|
|
2
|
+
|
|
3
|
+
export const showSubcommand = createSubcommand({
|
|
4
|
+
name: 'show',
|
|
5
|
+
description: 'Show project details',
|
|
6
|
+
requiresAuth: true,
|
|
7
|
+
|
|
8
|
+
async handler(ctx) {
|
|
9
|
+
const { logger } = ctx;
|
|
10
|
+
logger.info('TODO: Implement project show functionality');
|
|
11
|
+
},
|
|
12
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createCommand } from '@/types';
|
|
2
|
+
import { getVersion } from '@/version';
|
|
3
|
+
import { logger } from '@/logger';
|
|
4
|
+
|
|
5
|
+
export const command = createCommand({
|
|
6
|
+
name: 'version',
|
|
7
|
+
description: 'Display version information',
|
|
8
|
+
|
|
9
|
+
async handler() {
|
|
10
|
+
try {
|
|
11
|
+
console.log(getVersion());
|
|
12
|
+
} catch (error) {
|
|
13
|
+
logger.fatal('Failed to retrieve version: %s', error);
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { getPackageName } from './version';
|
|
3
|
+
|
|
4
|
+
let cachedPrefix: string | null = null;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detects how the CLI is being invoked and returns the appropriate command prefix.
|
|
8
|
+
* Returns "agentuity" if installed globally, or "bunx @agentuity/cli" if running via bunx.
|
|
9
|
+
*/
|
|
10
|
+
export function getCommandPrefix(): string {
|
|
11
|
+
if (cachedPrefix) {
|
|
12
|
+
return cachedPrefix;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Check if running from a globally installed package
|
|
16
|
+
// When installed globally, the process.argv[1] will be in a bin directory
|
|
17
|
+
const scriptPath = process.argv[1] || '';
|
|
18
|
+
const normalized = path.normalize(scriptPath);
|
|
19
|
+
|
|
20
|
+
// If the script is in node_modules/.bin or a global bin directory, it's likely global
|
|
21
|
+
const isGlobal =
|
|
22
|
+
normalized.includes(`${path.sep}bin${path.sep}`) &&
|
|
23
|
+
!normalized.includes(`${path.sep}node_modules${path.sep}`) &&
|
|
24
|
+
!normalized.includes(path.join('packages', 'cli', 'bin'));
|
|
25
|
+
|
|
26
|
+
if (isGlobal) {
|
|
27
|
+
cachedPrefix = 'agentuity';
|
|
28
|
+
} else {
|
|
29
|
+
// Running locally via bunx or from source
|
|
30
|
+
const pkgName = getPackageName();
|
|
31
|
+
cachedPrefix = `bunx ${pkgName}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return cachedPrefix;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Gets a formatted command string with the appropriate prefix.
|
|
39
|
+
* Example: getCommand('auth login') => 'agentuity auth login' or 'bunx @agentuity/cli auth login'
|
|
40
|
+
*/
|
|
41
|
+
export function getCommand(command: string): string {
|
|
42
|
+
return `${getCommandPrefix()} ${command}`;
|
|
43
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { YAML } from 'bun';
|
|
2
|
+
import { join, extname } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { mkdir, readdir, readFile, writeFile, chmod } from 'node:fs/promises';
|
|
5
|
+
import type { Config, Profile, AuthData } from './types';
|
|
6
|
+
import { ConfigSchema } from './types';
|
|
7
|
+
import * as tui from '@/tui';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
export function getDefaultConfigDir(): string {
|
|
11
|
+
return join(homedir(), '.config', 'agentuity');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getDefaultConfigPath(): string {
|
|
15
|
+
return join(getDefaultConfigDir(), 'config.yaml');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getProfilePath(): string {
|
|
19
|
+
return join(getDefaultConfigDir(), 'profile');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function ensureConfigDir(): Promise<void> {
|
|
23
|
+
const dir = getDefaultConfigDir();
|
|
24
|
+
try {
|
|
25
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
26
|
+
} catch {
|
|
27
|
+
// Ignore if already exists
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function saveProfile(path: string): Promise<void> {
|
|
32
|
+
await ensureConfigDir();
|
|
33
|
+
await writeFile(getProfilePath(), path, { mode: 0o644 });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function getProfile(): Promise<string> {
|
|
37
|
+
const profilePath = getProfilePath();
|
|
38
|
+
const defaultConfigPath = getDefaultConfigPath();
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const file = Bun.file(profilePath);
|
|
42
|
+
if (await file.exists()) {
|
|
43
|
+
const content = await file.text();
|
|
44
|
+
const savedPath = content.trim();
|
|
45
|
+
const savedFile = Bun.file(savedPath);
|
|
46
|
+
if (await savedFile.exists()) {
|
|
47
|
+
return savedPath;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// Fall back to default
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return defaultConfigPath;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function fetchProfiles(): Promise<Profile[]> {
|
|
58
|
+
const configDir = getDefaultConfigDir();
|
|
59
|
+
const currentConfigPath = await getProfile();
|
|
60
|
+
const profiles: Profile[] = [];
|
|
61
|
+
const nameRegex = /\bname:\s+["']?([\w-_]+)["']?/;
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const entries = await readdir(configDir);
|
|
65
|
+
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
if (extname(entry) === '.yaml' && !entry.includes('templates/')) {
|
|
68
|
+
const filePath = join(configDir, entry);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const content = await readFile(filePath, 'utf-8');
|
|
72
|
+
const match = nameRegex.exec(content);
|
|
73
|
+
|
|
74
|
+
if (match && match[1]) {
|
|
75
|
+
profiles.push({
|
|
76
|
+
name: match[1],
|
|
77
|
+
filename: filePath,
|
|
78
|
+
selected: filePath === currentConfigPath,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Skip files we can't read
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// Directory doesn't exist or can't be read
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return profiles;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function loadConfig(customPath?: string): Promise<Config | null> {
|
|
94
|
+
const configPath = customPath || (await getProfile());
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const file = Bun.file(configPath);
|
|
98
|
+
const exists = await file.exists();
|
|
99
|
+
|
|
100
|
+
if (!exists) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const content = await file.text();
|
|
105
|
+
const config = YAML.parse(content);
|
|
106
|
+
|
|
107
|
+
const result = ConfigSchema.safeParse(config);
|
|
108
|
+
if (!result.success) {
|
|
109
|
+
tui.error(`Invalid config in ${configPath}:`);
|
|
110
|
+
for (const issue of result.error.issues) {
|
|
111
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : 'root';
|
|
112
|
+
tui.bullet(`${path}: ${issue.message}`);
|
|
113
|
+
}
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return result.data;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error instanceof Error) {
|
|
120
|
+
console.error(`Error loading config from ${configPath}:`, error.message);
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function formatYAML(obj: unknown, indent = 0): string {
|
|
127
|
+
const spaces = ' '.repeat(indent);
|
|
128
|
+
const lines: string[] = [];
|
|
129
|
+
|
|
130
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
131
|
+
return String(obj);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
135
|
+
if (value === null || value === undefined) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
140
|
+
lines.push(`${spaces}${key}:`);
|
|
141
|
+
lines.push(formatYAML(value, indent + 1));
|
|
142
|
+
} else if (Array.isArray(value)) {
|
|
143
|
+
lines.push(`${spaces}${key}:`);
|
|
144
|
+
for (const item of value) {
|
|
145
|
+
if (typeof item === 'object') {
|
|
146
|
+
lines.push(`${spaces} -`);
|
|
147
|
+
lines.push(formatYAML(item, indent + 2));
|
|
148
|
+
} else {
|
|
149
|
+
lines.push(`${spaces} - ${item}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} else if (typeof value === 'string') {
|
|
153
|
+
if (value === '') {
|
|
154
|
+
lines.push(`${spaces}${key}: ""`);
|
|
155
|
+
} else if (value.includes(':') || value.includes('#') || value.includes(' ')) {
|
|
156
|
+
lines.push(`${spaces}${key}: "${value}"`);
|
|
157
|
+
} else {
|
|
158
|
+
lines.push(`${spaces}${key}: ${value}`);
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
lines.push(`${spaces}${key}: ${value}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return lines.join('\n');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function saveConfig(config: Config, customPath?: string): Promise<void> {
|
|
169
|
+
const configPath = customPath || (await getProfile());
|
|
170
|
+
await ensureConfigDir();
|
|
171
|
+
|
|
172
|
+
const content = formatYAML(config);
|
|
173
|
+
await writeFile(configPath, content + '\n', { mode: 0o600 });
|
|
174
|
+
// Ensure existing files get correct permissions on upgrade
|
|
175
|
+
await chmod(configPath, 0o600);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function saveAuth(auth: AuthData): Promise<void> {
|
|
179
|
+
const config = (await loadConfig()) || { name: 'default' };
|
|
180
|
+
config.auth = {
|
|
181
|
+
api_key: auth.apiKey,
|
|
182
|
+
user_id: auth.userId,
|
|
183
|
+
expires: auth.expires.getTime(),
|
|
184
|
+
};
|
|
185
|
+
config.preferences = config.preferences || {};
|
|
186
|
+
(config.preferences as Record<string, unknown>).orgId = '';
|
|
187
|
+
await saveConfig(config);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function clearAuth(): Promise<void> {
|
|
191
|
+
const config = (await loadConfig()) || { name: 'default' };
|
|
192
|
+
config.auth = {
|
|
193
|
+
api_key: '',
|
|
194
|
+
user_id: '',
|
|
195
|
+
expires: Date.now(),
|
|
196
|
+
};
|
|
197
|
+
config.preferences = config.preferences || {};
|
|
198
|
+
(config.preferences as Record<string, unknown>).orgId = '';
|
|
199
|
+
await saveConfig(config);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function getAuth(): Promise<AuthData | null> {
|
|
203
|
+
const config = await loadConfig();
|
|
204
|
+
if (!config) return null;
|
|
205
|
+
const auth = config.auth as { api_key?: string; user_id?: string; expires?: number } | undefined;
|
|
206
|
+
|
|
207
|
+
if (!auth || !auth.api_key || !auth.user_id) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
apiKey: auth.api_key,
|
|
213
|
+
userId: auth.user_id,
|
|
214
|
+
expires: new Date(auth.expires || 0),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function getSchemaDescription(schema: z.ZodTypeAny): string | undefined {
|
|
219
|
+
return (schema as unknown as { description?: string }).description;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getPlaceholderValue(schema: z.ZodTypeAny): string {
|
|
223
|
+
// Unwrap optional to get to the inner type
|
|
224
|
+
let unwrapped = schema;
|
|
225
|
+
if (schema instanceof z.ZodOptional) {
|
|
226
|
+
unwrapped = (schema._def as unknown as { innerType: z.ZodTypeAny }).innerType;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check the type using constructor name
|
|
230
|
+
const typeName = unwrapped.constructor.name;
|
|
231
|
+
|
|
232
|
+
switch (typeName) {
|
|
233
|
+
case 'ZodString':
|
|
234
|
+
return '""';
|
|
235
|
+
case 'ZodNumber':
|
|
236
|
+
return '0';
|
|
237
|
+
case 'ZodBoolean':
|
|
238
|
+
return 'false';
|
|
239
|
+
default:
|
|
240
|
+
return '""';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function generateYAMLTemplate(name: string): string {
|
|
245
|
+
const lines: string[] = [];
|
|
246
|
+
|
|
247
|
+
// Add name (required)
|
|
248
|
+
lines.push(`name: "${name}"`);
|
|
249
|
+
lines.push('');
|
|
250
|
+
|
|
251
|
+
// Get schema shape
|
|
252
|
+
const shape = ConfigSchema.shape;
|
|
253
|
+
|
|
254
|
+
// Only include user-configurable sections
|
|
255
|
+
// Skip: auth (managed by login), devmode (internal), preferences (managed by CLI)
|
|
256
|
+
const userConfigurableSections = ['overrides'];
|
|
257
|
+
|
|
258
|
+
// Process each top-level field
|
|
259
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
260
|
+
if (key === 'name') continue;
|
|
261
|
+
if (!userConfigurableSections.includes(key)) continue;
|
|
262
|
+
|
|
263
|
+
const schema = value as z.ZodTypeAny;
|
|
264
|
+
|
|
265
|
+
// Unwrap optional to get to the inner schema
|
|
266
|
+
let innerSchema = schema;
|
|
267
|
+
if (schema instanceof z.ZodOptional) {
|
|
268
|
+
innerSchema = (schema._def as unknown as { innerType: z.ZodTypeAny }).innerType;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const description = getSchemaDescription(schema);
|
|
272
|
+
|
|
273
|
+
// Add section comment
|
|
274
|
+
if (description) {
|
|
275
|
+
lines.push(`# ${description}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// For object schemas, expand their properties
|
|
279
|
+
if (innerSchema instanceof z.ZodObject) {
|
|
280
|
+
const innerShape = innerSchema.shape;
|
|
281
|
+
lines.push(`# ${key}:`);
|
|
282
|
+
|
|
283
|
+
for (const [subKey, subValue] of Object.entries(innerShape)) {
|
|
284
|
+
const subSchema = subValue as z.ZodTypeAny;
|
|
285
|
+
const subDesc = getSchemaDescription(subSchema);
|
|
286
|
+
const placeholder = getPlaceholderValue(subSchema);
|
|
287
|
+
|
|
288
|
+
if (subDesc) {
|
|
289
|
+
lines.push(`# ${subKey}: ${placeholder} # ${subDesc}`);
|
|
290
|
+
} else {
|
|
291
|
+
lines.push(`# ${subKey}: ${placeholder}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
const placeholder = getPlaceholderValue(schema);
|
|
296
|
+
lines.push(`# ${key}: ${placeholder}`);
|
|
297
|
+
}
|
|
298
|
+
lines.push('');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return lines.join('\n');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export { generateYAMLTemplate };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export { createCLI, registerCommands } from './cli';
|
|
2
|
+
export { validateRuntime, isBun } from './runtime';
|
|
3
|
+
export { getVersion, getRevision, getPackageName, getPackage } from './version';
|
|
4
|
+
export {
|
|
5
|
+
loadConfig,
|
|
6
|
+
saveConfig,
|
|
7
|
+
getDefaultConfigPath,
|
|
8
|
+
getDefaultConfigDir,
|
|
9
|
+
getProfilePath,
|
|
10
|
+
ensureConfigDir,
|
|
11
|
+
saveProfile,
|
|
12
|
+
getProfile,
|
|
13
|
+
fetchProfiles,
|
|
14
|
+
saveAuth,
|
|
15
|
+
clearAuth,
|
|
16
|
+
getAuth,
|
|
17
|
+
} from './config';
|
|
18
|
+
export { APIClient, getAPIBaseURL, getAppBaseURL, UpgradeRequiredError } from './api';
|
|
19
|
+
export { Logger, logger } from './logger';
|
|
20
|
+
export { showBanner } from './banner';
|
|
21
|
+
export { discoverCommands } from './cmd';
|
|
22
|
+
export { detectColorScheme } from './terminal';
|
|
23
|
+
export { getCommandPrefix, getCommand } from './command-prefix';
|
|
24
|
+
export * as tui from './tui';
|
|
25
|
+
export { runSteps, setStepsColorScheme, stepSuccess, stepSkipped, stepError } from './steps';
|
|
26
|
+
export { playSound } from './sound';
|
|
27
|
+
export type {
|
|
28
|
+
Config,
|
|
29
|
+
LogLevel,
|
|
30
|
+
GlobalOptions,
|
|
31
|
+
CommandContext,
|
|
32
|
+
SubcommandDefinition,
|
|
33
|
+
CommandDefinition,
|
|
34
|
+
Profile,
|
|
35
|
+
AuthData,
|
|
36
|
+
CommandSchemas,
|
|
37
|
+
} from './types';
|
|
38
|
+
export { createSubcommand, createCommand } from './types';
|
|
39
|
+
export type { ColorScheme } from './terminal';
|
|
40
|
+
export type { Step, SimpleStep, ProgressStep, StepOutcome, ProgressCallback } from './steps';
|