@agentuity/cli 0.0.35 → 0.0.41
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 +2 -2
- package/README.md +4 -4
- package/dist/api.d.ts +6 -22
- package/dist/api.d.ts.map +1 -1
- package/dist/auth.d.ts +0 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cmd/auth/api.d.ts.map +1 -1
- package/dist/cmd/auth/login.d.ts +1 -2
- package/dist/cmd/auth/login.d.ts.map +1 -1
- package/dist/cmd/auth/logout.d.ts +1 -2
- package/dist/cmd/auth/logout.d.ts.map +1 -1
- package/dist/cmd/auth/signup.d.ts +1 -2
- package/dist/cmd/auth/signup.d.ts.map +1 -1
- package/dist/cmd/bundle/bundler.d.ts +1 -0
- package/dist/cmd/bundle/bundler.d.ts.map +1 -1
- package/dist/cmd/bundle/patch/index.d.ts.map +1 -1
- package/dist/cmd/bundle/patch/llm.d.ts +3 -0
- package/dist/cmd/bundle/patch/llm.d.ts.map +1 -0
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/project/create.d.ts.map +1 -1
- package/dist/cmd/project/download.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.d.ts +3 -0
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/config.d.ts +10 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/sound.d.ts.map +1 -1
- package/dist/tui.d.ts +16 -7
- package/dist/tui.d.ts.map +1 -1
- package/dist/types.d.ts +36 -7
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/api.ts +27 -138
- package/src/auth.ts +87 -71
- package/src/cli.ts +6 -15
- package/src/cmd/auth/api.ts +40 -29
- package/src/cmd/auth/login.ts +5 -16
- package/src/cmd/auth/logout.ts +3 -3
- package/src/cmd/auth/signup.ts +6 -6
- package/src/cmd/bundle/bundler.ts +1 -0
- package/src/cmd/bundle/patch/index.ts +4 -0
- package/src/cmd/bundle/patch/llm.ts +36 -0
- package/src/cmd/dev/index.ts +17 -0
- package/src/cmd/example/optional-auth.ts +1 -1
- package/src/cmd/index.ts +1 -0
- package/src/cmd/profile/README.md +1 -1
- package/src/cmd/project/create.ts +11 -3
- package/src/cmd/project/download.ts +17 -0
- package/src/cmd/project/template-flow.ts +55 -1
- package/src/config.ts +54 -5
- package/src/index.ts +2 -2
- package/src/sound.ts +9 -3
- package/src/tui.ts +163 -60
- package/src/types.ts +68 -34
package/src/cmd/dev/index.ts
CHANGED
|
@@ -38,6 +38,22 @@ export const command = createCommand({
|
|
|
38
38
|
process.exit(1);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
const devmodebody = tui.muted('Local: ') + tui.link('http://127.0.0.1:3000');
|
|
42
|
+
|
|
43
|
+
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
44
|
+
padding: 2,
|
|
45
|
+
topSpacer: false,
|
|
46
|
+
bottomSpacer: false,
|
|
47
|
+
centerTitle: false,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const env = { ...process.env };
|
|
51
|
+
env.AGENTUITY_SDK_DEV_MODE = 'true';
|
|
52
|
+
env.AGENTUITY_ENV = 'development';
|
|
53
|
+
env.NODE_ENV = 'development';
|
|
54
|
+
env.PORT = '3000';
|
|
55
|
+
env.AGENTUITY_PORT = env.PORT;
|
|
56
|
+
|
|
41
57
|
const agentuityDir = resolve(rootDir, '.agentuity');
|
|
42
58
|
const appPath = resolve(agentuityDir, 'app.js');
|
|
43
59
|
|
|
@@ -208,6 +224,7 @@ export const command = createCommand({
|
|
|
208
224
|
stdout: 'inherit',
|
|
209
225
|
stderr: 'inherit',
|
|
210
226
|
stdin: 'inherit',
|
|
227
|
+
env,
|
|
211
228
|
});
|
|
212
229
|
|
|
213
230
|
running = true;
|
|
@@ -10,7 +10,7 @@ export const optionalAuthSubcommand: SubcommandDefinition = {
|
|
|
10
10
|
|
|
11
11
|
// Type guard to check if auth is present
|
|
12
12
|
const ctxWithAuth = ctx as CommandContext<true>;
|
|
13
|
-
if (
|
|
13
|
+
if (ctxWithAuth.auth) {
|
|
14
14
|
const auth = ctxWithAuth.auth as AuthData;
|
|
15
15
|
// User chose to authenticate
|
|
16
16
|
tui.success('You are authenticated!');
|
package/src/cmd/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export async function discoverCommands(): Promise<CommandDefinition[]> {
|
|
|
28
28
|
aliases: subcommand.aliases,
|
|
29
29
|
hidden: true,
|
|
30
30
|
requiresAuth: subcommand.requiresAuth,
|
|
31
|
+
optionalAuth: subcommand.optionalAuth,
|
|
31
32
|
schema: subcommand.schema,
|
|
32
33
|
handler: subcommand.handler,
|
|
33
34
|
};
|
|
@@ -77,4 +77,4 @@ The `name` field is extracted using the regex: `/\bname:\s+["']?([\w-_]+)["']?/`
|
|
|
77
77
|
- Profile files must have `.yaml` extension
|
|
78
78
|
- Profile names must match: `^[\w-_]{3,}$` (3+ chars, alphanumeric, dashes, underscores)
|
|
79
79
|
- The config loader (`loadConfig()`) automatically uses the active profile
|
|
80
|
-
- If no profile is selected or the file doesn't exist, falls back to `
|
|
80
|
+
- If no profile is selected or the file doesn't exist, falls back to `production.yaml`
|
|
@@ -7,7 +7,7 @@ export const createProjectSubcommand = createSubcommand({
|
|
|
7
7
|
description: 'Create a new project',
|
|
8
8
|
aliases: ['new'],
|
|
9
9
|
toplevel: true,
|
|
10
|
-
|
|
10
|
+
optionalAuth: true,
|
|
11
11
|
schema: {
|
|
12
12
|
options: z.object({
|
|
13
13
|
name: z.string().optional().describe('Project name'),
|
|
@@ -31,12 +31,18 @@ export const createProjectSubcommand = createSubcommand({
|
|
|
31
31
|
.optional()
|
|
32
32
|
.default(true)
|
|
33
33
|
.describe('Run bun run build after installing (use --no-build to skip)'),
|
|
34
|
-
confirm: z.boolean().optional().describe('
|
|
34
|
+
confirm: z.boolean().optional().describe('Show confirmation prompts'),
|
|
35
|
+
register: z
|
|
36
|
+
.boolean()
|
|
37
|
+
.default(true)
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Register the project, if authenticated (use --no-register to skip)'),
|
|
35
40
|
}),
|
|
36
41
|
},
|
|
37
42
|
|
|
38
43
|
async handler(ctx) {
|
|
39
|
-
const { logger, opts } = ctx;
|
|
44
|
+
const { logger, opts, auth, config } = ctx;
|
|
45
|
+
|
|
40
46
|
await runCreateFlow({
|
|
41
47
|
projectName: opts.name,
|
|
42
48
|
dir: opts.dir,
|
|
@@ -47,6 +53,8 @@ export const createProjectSubcommand = createSubcommand({
|
|
|
47
53
|
noBuild: opts.build === false,
|
|
48
54
|
skipPrompts: opts.confirm === true,
|
|
49
55
|
logger,
|
|
56
|
+
auth: opts.register === true ? auth : undefined,
|
|
57
|
+
config: config!,
|
|
50
58
|
});
|
|
51
59
|
},
|
|
52
60
|
});
|
|
@@ -225,6 +225,23 @@ export async function setupProject(options: SetupOptions): Promise<void> {
|
|
|
225
225
|
clearOnSuccess: true,
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
+
// Configure git user in CI environments (where git config may not be set)
|
|
229
|
+
if (process.env.CI) {
|
|
230
|
+
await tui.runCommand({
|
|
231
|
+
command: 'git config user.email',
|
|
232
|
+
cwd: dest,
|
|
233
|
+
cmd: ['git', 'config', 'user.email', 'agentuity@example.com'],
|
|
234
|
+
clearOnSuccess: true,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
await tui.runCommand({
|
|
238
|
+
command: 'git config user.name',
|
|
239
|
+
cwd: dest,
|
|
240
|
+
cmd: ['git', 'config', 'user.name', 'Agentuity'],
|
|
241
|
+
clearOnSuccess: true,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
228
245
|
// Add all files
|
|
229
246
|
await tui.runCommand({
|
|
230
247
|
command: 'git add .',
|
|
@@ -3,12 +3,21 @@ import { existsSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
|
3
3
|
import { cwd } from 'node:process';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import enquirer from 'enquirer';
|
|
6
|
+
import {
|
|
7
|
+
createProject,
|
|
8
|
+
projectExists,
|
|
9
|
+
listOrganizations,
|
|
10
|
+
type OrganizationList,
|
|
11
|
+
} from '@agentuity/server';
|
|
6
12
|
import type { Logger } from '../../logger';
|
|
7
13
|
import * as tui from '../../tui';
|
|
8
14
|
import { playSound } from '../../sound';
|
|
9
15
|
import { fetchTemplates, type TemplateInfo } from './templates';
|
|
10
16
|
import { downloadTemplate, setupProject } from './download';
|
|
11
17
|
import { showBanner } from '../../banner';
|
|
18
|
+
import type { AuthData, Config } from '../../types';
|
|
19
|
+
import { getAPIBaseURL, APIClient } from '../../api';
|
|
20
|
+
import { createProjectConfig } from '../../config';
|
|
12
21
|
|
|
13
22
|
interface CreateFlowOptions {
|
|
14
23
|
projectName?: string;
|
|
@@ -20,6 +29,8 @@ interface CreateFlowOptions {
|
|
|
20
29
|
noBuild: boolean;
|
|
21
30
|
skipPrompts: boolean;
|
|
22
31
|
logger: Logger;
|
|
32
|
+
auth?: AuthData;
|
|
33
|
+
config?: Config;
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
|
|
@@ -31,6 +42,8 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
|
|
|
31
42
|
templateBranch,
|
|
32
43
|
skipPrompts,
|
|
33
44
|
logger,
|
|
45
|
+
auth,
|
|
46
|
+
config,
|
|
34
47
|
} = options;
|
|
35
48
|
|
|
36
49
|
showBanner();
|
|
@@ -52,16 +65,39 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
|
|
|
52
65
|
|
|
53
66
|
// Step 2: Get project name
|
|
54
67
|
let projectName = initialProjectName;
|
|
68
|
+
|
|
69
|
+
let orgs: OrganizationList | undefined;
|
|
70
|
+
let client: APIClient | undefined;
|
|
71
|
+
let orgId: string | undefined;
|
|
72
|
+
|
|
73
|
+
if (auth) {
|
|
74
|
+
const apiUrl = getAPIBaseURL(config);
|
|
75
|
+
client = new APIClient(apiUrl, config);
|
|
76
|
+
await tui.spinner('Fetching organizations', async () => {
|
|
77
|
+
const resp = await listOrganizations(client!);
|
|
78
|
+
if (resp.data) {
|
|
79
|
+
orgs = resp.data;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
orgId = await tui.selectOrganization(orgs!, config?.preferences?.orgId);
|
|
83
|
+
}
|
|
84
|
+
|
|
55
85
|
if (!projectName && !skipPrompts) {
|
|
56
86
|
const response = await enquirer.prompt<{ name: string }>({
|
|
57
87
|
type: 'input',
|
|
58
88
|
name: 'name',
|
|
59
89
|
message: 'What is the name of your project?',
|
|
60
90
|
initial: 'My First Agent',
|
|
61
|
-
validate: (value: string) => {
|
|
91
|
+
validate: async (value: string) => {
|
|
62
92
|
if (!value || value.trim().length === 0) {
|
|
63
93
|
return 'Project name is required';
|
|
64
94
|
}
|
|
95
|
+
if (client) {
|
|
96
|
+
const exists = await projectExists(client, { name: value, organization_id: orgId! });
|
|
97
|
+
if (exists) {
|
|
98
|
+
return `Project with name '${value}' already exists in this organization`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
65
101
|
return true;
|
|
66
102
|
},
|
|
67
103
|
});
|
|
@@ -163,6 +199,24 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
|
|
|
163
199
|
logger,
|
|
164
200
|
});
|
|
165
201
|
|
|
202
|
+
if (auth && client && orgId) {
|
|
203
|
+
await tui.spinner('Registering your project', async () => {
|
|
204
|
+
const res = await createProject(client, {
|
|
205
|
+
name: projectName,
|
|
206
|
+
organization_id: orgId,
|
|
207
|
+
provider: 'bunjs',
|
|
208
|
+
});
|
|
209
|
+
if (res.success && res.data) {
|
|
210
|
+
return createProjectConfig(dest, {
|
|
211
|
+
projectId: res.data.id,
|
|
212
|
+
orgId,
|
|
213
|
+
apiKey: res.data.api_key,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
tui.fatal(res.message!);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
166
220
|
// Step 8: Show completion message
|
|
167
221
|
tui.success('✨ Project created successfully!\n');
|
|
168
222
|
tui.info('Next steps:');
|
package/src/config.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { join, extname, basename } from 'node:path';
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { mkdir, readdir, readFile, writeFile, chmod } from 'node:fs/promises';
|
|
5
5
|
import type { Config, Profile, AuthData } from './types';
|
|
6
|
-
import { ConfigSchema } from './types';
|
|
6
|
+
import { ConfigSchema, ProjectSchema } from './types';
|
|
7
7
|
import * as tui from './tui';
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
|
|
@@ -12,7 +12,7 @@ export function getDefaultConfigDir(): string {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function getDefaultConfigPath(): string {
|
|
15
|
-
return join(getDefaultConfigDir(), '
|
|
15
|
+
return join(getDefaultConfigDir(), 'production.yaml');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function getProfilePath(): string {
|
|
@@ -90,8 +90,15 @@ export async function fetchProfiles(): Promise<Profile[]> {
|
|
|
90
90
|
return profiles;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
function expandTilde(path: string): string {
|
|
94
|
+
if (path.startsWith('~/')) {
|
|
95
|
+
return join(homedir(), path.slice(2));
|
|
96
|
+
}
|
|
97
|
+
return path;
|
|
98
|
+
}
|
|
99
|
+
|
|
93
100
|
export async function loadConfig(customPath?: string): Promise<Config | null> {
|
|
94
|
-
const configPath = customPath
|
|
101
|
+
const configPath = customPath ? expandTilde(customPath) : await getProfile();
|
|
95
102
|
|
|
96
103
|
try {
|
|
97
104
|
const file = Bun.file(configPath);
|
|
@@ -248,7 +255,7 @@ function getPlaceholderValue(schema: z.ZodTypeAny): string {
|
|
|
248
255
|
}
|
|
249
256
|
}
|
|
250
257
|
|
|
251
|
-
function generateYAMLTemplate(name: string): string {
|
|
258
|
+
export function generateYAMLTemplate(name: string): string {
|
|
252
259
|
const lines: string[] = [];
|
|
253
260
|
|
|
254
261
|
// Add name (required)
|
|
@@ -308,4 +315,46 @@ function generateYAMLTemplate(name: string): string {
|
|
|
308
315
|
return lines.join('\n');
|
|
309
316
|
}
|
|
310
317
|
|
|
311
|
-
|
|
318
|
+
class ProjectConfigNotFoundExpection extends Error {
|
|
319
|
+
public name: string;
|
|
320
|
+
constructor() {
|
|
321
|
+
super('project not found');
|
|
322
|
+
this.name = 'ProjectConfigNotFoundExpection';
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
type ProjectConfig = z.infer<typeof ProjectSchema>;
|
|
327
|
+
|
|
328
|
+
export async function loadProjectConfig(dir: string): Promise<ProjectConfig> {
|
|
329
|
+
const configPath = join(dir, 'agentuity.json');
|
|
330
|
+
const file = Bun.file(configPath);
|
|
331
|
+
if (!(await file.exists())) {
|
|
332
|
+
throw new ProjectConfigNotFoundExpection();
|
|
333
|
+
}
|
|
334
|
+
const text = await file.text();
|
|
335
|
+
const config = JSON.parse(text);
|
|
336
|
+
const result = ProjectSchema.safeParse(config);
|
|
337
|
+
if (!result.success) {
|
|
338
|
+
tui.error(`Invalid project config at ${configPath}:`);
|
|
339
|
+
for (const issue of result.error.issues) {
|
|
340
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : 'root';
|
|
341
|
+
tui.bullet(`${path}: ${issue.message}`);
|
|
342
|
+
}
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
return result.data;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
type InitialProjectConfig = ProjectConfig & {
|
|
349
|
+
apiKey: string;
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
export async function createProjectConfig(dir: string, config: InitialProjectConfig) {
|
|
353
|
+
const configPath = join(dir, 'agentuity.json');
|
|
354
|
+
const configFile = Bun.file(configPath);
|
|
355
|
+
await configFile.write(JSON.stringify(config, null, 2));
|
|
356
|
+
|
|
357
|
+
const envPath = join(dir, '.env');
|
|
358
|
+
const envFile = Bun.file(envPath);
|
|
359
|
+
await envFile.write(`AGENTUITY_SDK_KEY=${config.apiKey}`);
|
|
360
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { createCLI, registerCommands } from './cli';
|
|
2
2
|
export { validateRuntime, isBun } from './runtime';
|
|
3
3
|
export { getVersion, getRevision, getPackageName, getPackage } from './version';
|
|
4
|
-
export { requireAuth, optionalAuth
|
|
4
|
+
export { requireAuth, optionalAuth } from './auth';
|
|
5
5
|
export {
|
|
6
6
|
loadConfig,
|
|
7
7
|
saveConfig,
|
|
@@ -16,7 +16,7 @@ export {
|
|
|
16
16
|
clearAuth,
|
|
17
17
|
getAuth,
|
|
18
18
|
} from './config';
|
|
19
|
-
export { APIClient, getAPIBaseURL, getAppBaseURL
|
|
19
|
+
export { APIClient, getAPIBaseURL, getAppBaseURL } from './api';
|
|
20
20
|
export { Logger, logger } from './logger';
|
|
21
21
|
export { showBanner } from './banner';
|
|
22
22
|
export { discoverCommands } from './cmd';
|
package/src/sound.ts
CHANGED
|
@@ -33,7 +33,13 @@ export function playSound(): void {
|
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
Bun.
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
if (process.stdout.isTTY && Bun.which(command[0])) {
|
|
37
|
+
try {
|
|
38
|
+
Bun.spawn(command, {
|
|
39
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
40
|
+
}).unref();
|
|
41
|
+
} catch {
|
|
42
|
+
/* ignore */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
39
45
|
}
|
package/src/tui.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Provides semantic helpers for console output with automatic icons and colors.
|
|
5
5
|
* Uses Bun's built-in color support and ANSI escape codes.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
import enquirer from 'enquirer';
|
|
8
|
+
import type { OrganizationList } from '@agentuity/server';
|
|
8
9
|
import type { ColorScheme } from './terminal';
|
|
9
10
|
|
|
10
11
|
// Icons
|
|
@@ -205,6 +206,15 @@ export function padLeft(str: string, length: number, pad = ' '): string {
|
|
|
205
206
|
return pad.repeat(length - str.length) + str;
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
interface BannerOptions {
|
|
210
|
+
padding?: number;
|
|
211
|
+
minWidth?: number;
|
|
212
|
+
topSpacer?: boolean;
|
|
213
|
+
middleSpacer?: boolean;
|
|
214
|
+
bottomSpacer?: boolean;
|
|
215
|
+
centerTitle?: boolean;
|
|
216
|
+
}
|
|
217
|
+
|
|
208
218
|
/**
|
|
209
219
|
* Display a formatted banner with title and body content
|
|
210
220
|
* Creates a bordered box around the content
|
|
@@ -212,10 +222,12 @@ export function padLeft(str: string, length: number, pad = ' '): string {
|
|
|
212
222
|
* Uses Bun.stringWidth() for accurate width calculation with ANSI codes and unicode
|
|
213
223
|
* Responsive to terminal width - adapts to narrow terminals
|
|
214
224
|
*/
|
|
215
|
-
export function banner(title: string, body: string): void {
|
|
225
|
+
export function banner(title: string, body: string, options?: BannerOptions): void {
|
|
216
226
|
// Get terminal width, default to 80 if not available, minimum 40
|
|
217
227
|
const termWidth = process.stdout.columns || 80;
|
|
218
|
-
const
|
|
228
|
+
const minWidth = options?.minWidth ?? 40;
|
|
229
|
+
const maxWidth = Math.max(minWidth, Math.min(termWidth - 2, 80)); // Between 40 and 80, with 2 char margin
|
|
230
|
+
const padding = options?.padding ?? 4;
|
|
219
231
|
|
|
220
232
|
const border = {
|
|
221
233
|
topLeft: '╭',
|
|
@@ -227,14 +239,14 @@ export function banner(title: string, body: string): void {
|
|
|
227
239
|
};
|
|
228
240
|
|
|
229
241
|
// Split body into lines and wrap if needed
|
|
230
|
-
const bodyLines = wrapText(body, maxWidth -
|
|
242
|
+
const bodyLines = wrapText(body, maxWidth - padding); // -4 for padding and borders
|
|
231
243
|
|
|
232
244
|
// Calculate width based on content
|
|
233
245
|
const titleWidth = getDisplayWidth(title);
|
|
234
246
|
const maxBodyWidth = Math.max(...bodyLines.map((line) => getDisplayWidth(line)));
|
|
235
|
-
const contentWidth = Math.max(titleWidth, maxBodyWidth);
|
|
236
|
-
const boxWidth = Math.min(contentWidth
|
|
237
|
-
const innerWidth = boxWidth -
|
|
247
|
+
const contentWidth = Math.max(minWidth, Math.max(titleWidth, maxBodyWidth) + padding);
|
|
248
|
+
const boxWidth = Math.min(contentWidth, maxWidth); // +N for padding
|
|
249
|
+
const innerWidth = boxWidth - padding;
|
|
238
250
|
|
|
239
251
|
// Colors
|
|
240
252
|
const borderColor = getColor('muted');
|
|
@@ -249,41 +261,58 @@ export function banner(title: string, body: string): void {
|
|
|
249
261
|
`${borderColor}${border.topLeft}${border.horizontal.repeat(boxWidth - 2)}${border.topRight}${reset}`
|
|
250
262
|
);
|
|
251
263
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
264
|
+
if (options?.topSpacer === true || options?.topSpacer === undefined) {
|
|
265
|
+
// Empty line
|
|
266
|
+
lines.push(
|
|
267
|
+
`${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
256
270
|
|
|
257
271
|
// Title (centered and bold)
|
|
258
272
|
const titleDisplayWidth = getDisplayWidth(title);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
273
|
+
if (options?.centerTitle === true || options?.centerTitle === undefined) {
|
|
274
|
+
const titlePadding = Math.max(0, Math.floor((innerWidth - titleDisplayWidth) / 2));
|
|
275
|
+
const titleRightPadding = Math.max(
|
|
276
|
+
0,
|
|
277
|
+
Math.max(0, innerWidth - titlePadding - titleDisplayWidth) - padding
|
|
278
|
+
);
|
|
279
|
+
const titleLine =
|
|
280
|
+
' '.repeat(titlePadding) +
|
|
281
|
+
`${titleColor}${bold(title)}${reset}` +
|
|
282
|
+
' '.repeat(titleRightPadding);
|
|
283
|
+
lines.push(
|
|
284
|
+
`${borderColor}${border.vertical} ${reset}${titleLine}${borderColor} ${border.vertical}${reset}`
|
|
285
|
+
);
|
|
286
|
+
} else {
|
|
287
|
+
const titleRightPadding = Math.max(0, Math.max(0, innerWidth - titleDisplayWidth) - padding);
|
|
288
|
+
const titleLine = `${titleColor}${bold(title)}${reset}` + ' '.repeat(titleRightPadding);
|
|
289
|
+
lines.push(
|
|
290
|
+
`${borderColor}${border.vertical} ${reset}${titleLine}${borderColor} ${border.vertical}${reset}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
268
293
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
294
|
+
if (options?.middleSpacer === true || options?.middleSpacer === undefined) {
|
|
295
|
+
// Empty line
|
|
296
|
+
lines.push(
|
|
297
|
+
`${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
273
300
|
|
|
274
301
|
// Body lines
|
|
275
302
|
for (const line of bodyLines) {
|
|
276
303
|
const lineWidth = getDisplayWidth(line);
|
|
277
|
-
const
|
|
304
|
+
const linePadding = Math.max(0, Math.max(0, innerWidth - lineWidth) - padding);
|
|
278
305
|
lines.push(
|
|
279
|
-
`${borderColor}${border.vertical} ${reset}${line}${' '.repeat(
|
|
306
|
+
`${borderColor}${border.vertical} ${reset}${line}${' '.repeat(linePadding)}${borderColor} ${border.vertical}${reset}`
|
|
280
307
|
);
|
|
281
308
|
}
|
|
282
309
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
310
|
+
if (options?.bottomSpacer === true || options?.bottomSpacer === undefined) {
|
|
311
|
+
// Empty line
|
|
312
|
+
lines.push(
|
|
313
|
+
`${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
287
316
|
|
|
288
317
|
// Bottom border
|
|
289
318
|
lines.push(
|
|
@@ -418,6 +447,30 @@ export function showSignupBenefits(): void {
|
|
|
418
447
|
console.log('');
|
|
419
448
|
}
|
|
420
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Display a message when unauthenticated to let the user know certain capabilities are disabled
|
|
452
|
+
*/
|
|
453
|
+
export function showLoggedOutMessage(): void {
|
|
454
|
+
const CYAN = Bun.color('yellow', 'ansi-16m');
|
|
455
|
+
const TEXT =
|
|
456
|
+
currentColorScheme === 'dark' ? Bun.color('white', 'ansi') : Bun.color('black', 'ansi');
|
|
457
|
+
const RESET = '\x1b[0m';
|
|
458
|
+
|
|
459
|
+
const lines = [
|
|
460
|
+
'╔══════════════════════════════════════════════╗',
|
|
461
|
+
`║ ⨺ Unauthenticated (local mode) ║`,
|
|
462
|
+
'║ ║',
|
|
463
|
+
`║ ${TEXT}Certain capabilities such as the AI services${CYAN} ║`,
|
|
464
|
+
`║ ${TEXT}and devmode remote are unavailable when${CYAN} ║`,
|
|
465
|
+
`║ ${TEXT}unauthenticated.${CYAN} ║`,
|
|
466
|
+
'╚══════════════════════════════════════════════╝',
|
|
467
|
+
];
|
|
468
|
+
|
|
469
|
+
console.log('');
|
|
470
|
+
lines.forEach((line) => console.log(CYAN + line + RESET));
|
|
471
|
+
console.log('');
|
|
472
|
+
}
|
|
473
|
+
|
|
421
474
|
/**
|
|
422
475
|
* Copy text to clipboard
|
|
423
476
|
* Returns true if successful, false otherwise
|
|
@@ -649,6 +702,28 @@ export async function spinner<T>(
|
|
|
649
702
|
}
|
|
650
703
|
|
|
651
704
|
const message = options.message;
|
|
705
|
+
const reset = COLORS.reset;
|
|
706
|
+
|
|
707
|
+
// If no TTY, just execute the callback without animation
|
|
708
|
+
if (!process.stdout.isTTY) {
|
|
709
|
+
try {
|
|
710
|
+
const result =
|
|
711
|
+
options.type === 'progress'
|
|
712
|
+
? await options.callback(() => {})
|
|
713
|
+
: typeof options.callback === 'function'
|
|
714
|
+
? await options.callback()
|
|
715
|
+
: await options.callback;
|
|
716
|
+
|
|
717
|
+
const successColor = getColor('success');
|
|
718
|
+
console.log(`${successColor}${ICONS.success} ${message}${reset}`);
|
|
719
|
+
return result;
|
|
720
|
+
} catch (err) {
|
|
721
|
+
const errorColor = getColor('error');
|
|
722
|
+
console.error(`${errorColor}${ICONS.error} ${message}${reset}`);
|
|
723
|
+
throw err;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
652
727
|
const frames = ['◐', '◓', '◑', '◒'];
|
|
653
728
|
const spinnerColors = [
|
|
654
729
|
{ light: '\x1b[36m', dark: '\x1b[96m' }, // cyan
|
|
@@ -657,7 +732,6 @@ export async function spinner<T>(
|
|
|
657
732
|
{ light: '\x1b[36m', dark: '\x1b[96m' }, // cyan
|
|
658
733
|
];
|
|
659
734
|
const bold = '\x1b[1m';
|
|
660
|
-
const reset = COLORS.reset;
|
|
661
735
|
const cyanColor = { light: '\x1b[36m', dark: '\x1b[96m' }[currentColorScheme];
|
|
662
736
|
|
|
663
737
|
let frameIndex = 0;
|
|
@@ -905,41 +979,52 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
|
|
|
905
979
|
// Wait for process to exit
|
|
906
980
|
const exitCode = await proc.exited;
|
|
907
981
|
|
|
908
|
-
//
|
|
982
|
+
// If clearOnSuccess is true and command succeeded, clear everything
|
|
983
|
+
if (clearOnSuccess && exitCode === 0) {
|
|
984
|
+
if (linesRendered > 0) {
|
|
985
|
+
// Move up to the command line
|
|
986
|
+
process.stdout.write(`\x1b[${linesRendered}A`);
|
|
987
|
+
// Clear each line (entire line) and move cursor back up
|
|
988
|
+
for (let i = 0; i < linesRendered; i++) {
|
|
989
|
+
process.stdout.write('\x1b[2K'); // Clear entire line
|
|
990
|
+
if (i < linesRendered - 1) {
|
|
991
|
+
process.stdout.write('\x1b[B'); // Move down one line
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
// Move cursor back up to original position
|
|
995
|
+
process.stdout.write(`\x1b[${linesRendered}A\r`);
|
|
996
|
+
}
|
|
997
|
+
return exitCode;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Clear all rendered lines completely
|
|
909
1001
|
if (linesRendered > 0) {
|
|
1002
|
+
// Move up to the command line (first line of our output)
|
|
910
1003
|
process.stdout.write(`\x1b[${linesRendered}A`);
|
|
1004
|
+
// Move to beginning of line and clear from cursor to end of screen
|
|
1005
|
+
process.stdout.write('\r\x1b[J');
|
|
911
1006
|
}
|
|
912
1007
|
|
|
913
|
-
//
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
for (let i = 0; i < linesRendered; i++) {
|
|
917
|
-
process.stdout.write('\r\x1b[K\n');
|
|
918
|
-
}
|
|
919
|
-
// Move cursor back up
|
|
920
|
-
process.stdout.write(`\x1b[${linesRendered}A`);
|
|
1008
|
+
// Determine icon based on exit code
|
|
1009
|
+
const icon = exitCode === 0 ? ICONS.success : ICONS.error;
|
|
1010
|
+
const statusColor = exitCode === 0 ? green : red;
|
|
921
1011
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
for (const line of finalOutputLines) {
|
|
937
|
-
let displayLine = line;
|
|
938
|
-
if (truncate && getDisplayWidth(displayLine) > maxLineWidth) {
|
|
939
|
-
displayLine = displayLine.slice(0, maxLineWidth - 3) + '...';
|
|
940
|
-
}
|
|
941
|
-
process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
|
|
1012
|
+
// Show final status: icon + command
|
|
1013
|
+
process.stdout.write(
|
|
1014
|
+
`\r\x1b[K${statusColor}${icon}${reset} ${cmdColor}${displayCmd}${reset}\n`
|
|
1015
|
+
);
|
|
1016
|
+
|
|
1017
|
+
// Determine how many lines to show in final output
|
|
1018
|
+
const finalLinesToShow = exitCode === 0 ? maxLinesOutput : maxLinesOnFailure;
|
|
1019
|
+
|
|
1020
|
+
// Show final output lines
|
|
1021
|
+
const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
|
|
1022
|
+
for (const line of finalOutputLines) {
|
|
1023
|
+
let displayLine = line;
|
|
1024
|
+
if (truncate && getDisplayWidth(displayLine) > maxLineWidth) {
|
|
1025
|
+
displayLine = displayLine.slice(0, maxLineWidth - 3) + '...';
|
|
942
1026
|
}
|
|
1027
|
+
process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
|
|
943
1028
|
}
|
|
944
1029
|
|
|
945
1030
|
return exitCode;
|
|
@@ -971,3 +1056,21 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
|
|
|
971
1056
|
process.stdout.write('\x1B[?25h');
|
|
972
1057
|
}
|
|
973
1058
|
}
|
|
1059
|
+
|
|
1060
|
+
export async function selectOrganization(
|
|
1061
|
+
orgs: OrganizationList,
|
|
1062
|
+
initial?: string
|
|
1063
|
+
): Promise<string> {
|
|
1064
|
+
if (orgs.length === 1) {
|
|
1065
|
+
return orgs[0].id;
|
|
1066
|
+
}
|
|
1067
|
+
const response = await enquirer.prompt<{ action: string }>({
|
|
1068
|
+
type: 'select',
|
|
1069
|
+
name: 'action',
|
|
1070
|
+
message: 'Select an organization',
|
|
1071
|
+
initial,
|
|
1072
|
+
choices: orgs.map((o) => ({ message: o.name, name: o.id })),
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
return response.action;
|
|
1076
|
+
}
|