@agentuity/cli 0.0.11 → 0.0.12

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 CHANGED
@@ -8,7 +8,27 @@ import { detectColorScheme } from '../src/terminal';
8
8
  import { setColorScheme } from '../src/tui';
9
9
  import { getVersion } from '../src/version';
10
10
  import { checkLegacyCLI } from '../src/legacy-check';
11
- import type { LogLevel } from '../src/types';
11
+ import type { CommandContext, LogLevel } from '../src/types';
12
+
13
+ // Cleanup TTY state before exit
14
+ function cleanupAndExit() {
15
+ if (process.stdin.isTTY) {
16
+ process.stdin.setRawMode(false);
17
+ process.stdout.write('\x1B[?25h'); // Restore cursor
18
+ }
19
+ process.exitCode = 0;
20
+ process.exit(0);
21
+ }
22
+
23
+ // Handle Ctrl+C gracefully
24
+ process.once('SIGINT', () => {
25
+ console.log('\n');
26
+ cleanupAndExit();
27
+ });
28
+
29
+ process.once('SIGTERM', () => {
30
+ cleanupAndExit();
31
+ });
12
32
 
13
33
  validateRuntime();
14
34
 
@@ -61,11 +81,32 @@ const ctx = {
61
81
  };
62
82
 
63
83
  const commands = await discoverCommands();
64
- await registerCommands(program, commands, ctx);
84
+ await registerCommands(program, commands, ctx as unknown as CommandContext);
65
85
 
66
86
  try {
67
87
  await program.parseAsync(process.argv);
68
88
  } catch (error) {
89
+ // Don't log error if it's from Ctrl+C, user cancellation, or signal termination
90
+ if (error instanceof Error) {
91
+ const msg = error.message.toLowerCase();
92
+ if (
93
+ msg.includes('sigint') ||
94
+ msg.includes('sigterm') ||
95
+ msg.includes('user force closed') ||
96
+ msg.includes('cancelled') || // UK
97
+ msg.includes('canceled') || // US
98
+ msg === ''
99
+ ) {
100
+ process.exit(0);
101
+ }
102
+ if ('name' in error && error.name === 'AbortError') {
103
+ process.exit(0);
104
+ }
105
+ }
106
+ // Also exit cleanly if error is empty/undefined (user cancellation)
107
+ if (!error) {
108
+ process.exit(0);
109
+ }
69
110
  logger.error('CLI error:', error);
70
111
  process.exit(1);
71
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmd/dev/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,OAAO,qCA2ElB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmd/dev/index.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,OAAO,qCAyElB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/create.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,wCA8ClC,CAAC"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/create.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,wCA+ClC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/download.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,UAAU,eAAe;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,YAAY;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA6E9E;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CvE"}
1
+ {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/download.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,UAAU,eAAe;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,YAAY;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAsBD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD9E;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCvE"}
@@ -1,6 +1,7 @@
1
1
  import type { Logger } from '@/logger';
2
2
  interface CreateFlowOptions {
3
3
  projectName?: string;
4
+ dir?: string;
4
5
  template?: string;
5
6
  templateDir?: string;
6
7
  templateBranch?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"template-flow.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/template-flow.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAKvC,UAAU,iBAAiB;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+I7E"}
1
+ {"version":3,"file":"template-flow.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/template-flow.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAMvC,UAAU,iBAAiB;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0J7E"}
package/dist/sound.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare function playSound(): Promise<void>;
1
+ export declare function playSound(): void;
2
2
  //# sourceMappingURL=sound.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sound.d.ts","sourceRoot":"","sources":["../src/sound.ts"],"names":[],"mappings":"AAEA,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB/C"}
1
+ {"version":3,"file":"sound.d.ts","sourceRoot":"","sources":["../src/sound.ts"],"names":[],"mappings":"AAEA,wBAAgB,SAAS,IAAI,IAAI,CAoChC"}
package/dist/tui.d.ts CHANGED
@@ -17,7 +17,7 @@ export declare function error(message: string): void;
17
17
  /**
18
18
  * Print a warning message with a yellow warning icon
19
19
  */
20
- export declare function warning(message: string): void;
20
+ export declare function warning(message: string, asError?: boolean): void;
21
21
  /**
22
22
  * Print an info message with a cyan info icon
23
23
  */
package/dist/tui.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentuity/cli",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./dist/index.d.ts",
@@ -35,9 +35,7 @@ export const command = createCommand({
35
35
  process.exit(1);
36
36
  }
37
37
 
38
- tui.newline();
39
38
  tui.info('Starting development server...');
40
- tui.newline();
41
39
 
42
40
  // Use shell to run in a process group for proper cleanup
43
41
  // The 'exec' ensures the shell is replaced by the actual process
@@ -6,9 +6,9 @@ export const soundSubcommand: SubcommandDefinition = {
6
6
  name: 'sound',
7
7
  description: 'Test completion sound',
8
8
 
9
- async handler() {
9
+ handler() {
10
10
  tui.info('Playing completion sound...');
11
- await playSound();
11
+ playSound();
12
12
  tui.success('Sound played!');
13
13
  },
14
14
  };
@@ -39,6 +39,7 @@ export const createProjectSubcommand = createSubcommand({
39
39
  const { logger, opts } = ctx;
40
40
  await runCreateFlow({
41
41
  projectName: opts.name,
42
+ dir: opts.dir,
42
43
  template: opts.template,
43
44
  templateDir: opts.templateDir,
44
45
  templateBranch: opts.templateBranch,
@@ -1,8 +1,9 @@
1
- import { join } from 'node:path';
1
+ import { join, resolve } from 'node:path';
2
2
  import { existsSync, mkdirSync, renameSync, readdirSync, cpSync, rmSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
3
4
  import { pipeline } from 'node:stream/promises';
4
5
  import { createGunzip } from 'node:zlib';
5
- import { extract } from 'tar-fs';
6
+ import { extract, type Headers } from 'tar-fs';
6
7
  import type { Logger } from '@/logger';
7
8
  import * as tui from '@/tui';
8
9
  import { downloadWithSpinner } from '@/download';
@@ -27,22 +28,12 @@ interface SetupOptions {
27
28
  logger: Logger;
28
29
  }
29
30
 
30
- export async function downloadTemplate(options: DownloadOptions): Promise<void> {
31
- const { dest, template, templateDir, templateBranch } = options;
32
-
33
- mkdirSync(dest, { recursive: true });
34
-
35
- // Copy from local directory if provided
36
- if (templateDir) {
37
- const { resolve } = await import('node:path');
38
- const sourceDir = resolve(join(templateDir, template.directory));
39
-
40
- if (!existsSync(sourceDir)) {
41
- throw new Error(`Template directory not found: ${sourceDir}`);
42
- }
43
-
44
- tui.info(`📦 Copying template from ${sourceDir}...`);
31
+ async function cleanup(sourceDir: string, dest: string) {
32
+ if (!existsSync(sourceDir)) {
33
+ throw new Error(`Template directory not found: ${sourceDir}`);
34
+ }
45
35
 
36
+ tui.spinner(`📦 Copying template from ${sourceDir}...`, async () => {
46
37
  // Copy all files from source to dest
47
38
  const files = readdirSync(sourceDir);
48
39
  for (const file of files) {
@@ -54,8 +45,23 @@ export async function downloadTemplate(options: DownloadOptions): Promise<void>
54
45
  if (existsSync(gi)) {
55
46
  renameSync(gi, join(dest, '.gitignore'));
56
47
  }
48
+ });
49
+ }
50
+
51
+ export async function downloadTemplate(options: DownloadOptions): Promise<void> {
52
+ const { dest, template, templateDir, templateBranch } = options;
57
53
 
58
- return;
54
+ mkdirSync(dest, { recursive: true });
55
+
56
+ // Copy from local directory if provided
57
+ if (templateDir) {
58
+ const sourceDir = resolve(join(templateDir, template.directory));
59
+
60
+ if (!existsSync(sourceDir)) {
61
+ throw new Error(`Template directory not found: ${sourceDir}`);
62
+ }
63
+
64
+ return cleanup(sourceDir, dest);
59
65
  }
60
66
 
61
67
  // Download from GitHub
@@ -74,65 +80,43 @@ export async function downloadTemplate(options: DownloadOptions): Promise<void>
74
80
  },
75
81
  async (stream) => {
76
82
  // Extract only the template directory from tarball
83
+ const prefix = `sdk-${branch}/${templatePath}/`;
77
84
  await pipeline(
78
85
  stream,
79
86
  createGunzip(),
80
87
  extract(tempDir, {
81
- map: (header) => {
82
- const prefix = `sdk-${branch}/${templatePath}/`;
83
- if (header.name.startsWith(prefix)) {
84
- header.name = header.name.substring(prefix.length);
85
- return header;
86
- }
87
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
- return null as any;
88
+ filter: (name: string) => name.startsWith(prefix),
89
+ map: (header: Headers) => {
90
+ header.name = header.name.substring(prefix.length);
91
+ return header;
89
92
  },
90
93
  })
91
94
  );
92
95
  }
93
96
  );
94
97
 
95
- // Move files from temp to dest
96
- const files = readdirSync(tempDir);
97
- for (const file of files) {
98
- cpSync(join(tempDir, file), join(dest, file), { recursive: true });
99
- }
100
- rmSync(tempDir, { recursive: true, force: true });
98
+ await cleanup(tempDir, dest);
101
99
 
102
- // Rename gitignore -> .gitignore
103
- const gi = join(dest, 'gitignore');
104
- if (existsSync(gi)) {
105
- renameSync(gi, join(dest, '.gitignore'));
100
+ // Extra safety: refuse to delete root or home directories
101
+ const home = homedir();
102
+ if (tempDir === '/' || tempDir === home) {
103
+ throw new Error(`Refusing to delete protected path: ${tempDir}`);
106
104
  }
105
+ rmSync(tempDir, { recursive: true, force: true });
107
106
  }
108
107
 
109
108
  export async function setupProject(options: SetupOptions): Promise<void> {
110
109
  const { dest, projectName, dirName, noInstall, noBuild, logger } = options;
111
110
 
112
- process.chdir(dest);
113
-
114
111
  // Replace {{PROJECT_NAME}} in files
115
112
  tui.info(`🔧 Setting up ${projectName}...`);
116
113
  await replaceInFiles(dest, projectName, dirName);
117
114
 
118
- // Run setup.ts if it exists (legacy)
119
- if (await Bun.file('./setup.ts').exists()) {
120
- await tui.spinner({
121
- message: 'Running setup script...',
122
- callback: async () => {
123
- const proc = Bun.spawn(['bun', './setup.ts'], { stdio: ['pipe', 'pipe', 'pipe'] });
124
- const exitCode = await proc.exited;
125
- if (exitCode !== 0) {
126
- logger.error('Setup script failed');
127
- }
128
- },
129
- });
130
- }
131
-
132
115
  // Install dependencies
133
116
  if (!noInstall) {
134
117
  const exitCode = await tui.runCommand({
135
118
  command: 'bun install',
119
+ cwd: dest,
136
120
  cmd: ['bun', 'install'],
137
121
  clearOnSuccess: true,
138
122
  });
@@ -145,6 +129,7 @@ export async function setupProject(options: SetupOptions): Promise<void> {
145
129
  if (!noBuild) {
146
130
  const exitCode = await tui.runCommand({
147
131
  command: 'bun run build',
132
+ cwd: dest,
148
133
  cmd: ['bun', 'run', 'build'],
149
134
  clearOnSuccess: true,
150
135
  });
@@ -1,13 +1,17 @@
1
1
  import { basename, resolve } from 'node:path';
2
2
  import { existsSync, readdirSync, rmSync } from 'node:fs';
3
+ import { cwd } from 'node:process';
4
+ import { homedir } from 'node:os';
3
5
  import enquirer from 'enquirer';
4
6
  import type { Logger } from '@/logger';
5
7
  import * as tui from '@/tui';
8
+ import { playSound } from '@/sound';
6
9
  import { fetchTemplates, type TemplateInfo } from './templates';
7
10
  import { downloadTemplate, setupProject } from './download';
8
11
 
9
12
  interface CreateFlowOptions {
10
13
  projectName?: string;
14
+ dir?: string;
11
15
  template?: string;
12
16
  templateDir?: string;
13
17
  templateBranch?: string;
@@ -20,6 +24,7 @@ interface CreateFlowOptions {
20
24
  export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
21
25
  const {
22
26
  projectName: initialProjectName,
27
+ dir: targetDir,
23
28
  template: initialTemplate,
24
29
  templateDir,
25
30
  templateBranch,
@@ -64,14 +69,20 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
64
69
  const dirName = projectName === '.' ? '.' : sanitizeDirectoryName(projectName);
65
70
 
66
71
  // Step 4: Determine destination directory
67
- const dest = dirName === '.' ? process.cwd() : resolve(process.cwd(), dirName);
72
+ // Expand ~ to home directory
73
+ let expandedTargetDir = targetDir;
74
+ if (expandedTargetDir && expandedTargetDir.startsWith('~')) {
75
+ expandedTargetDir = expandedTargetDir.replace(/^~/, homedir());
76
+ }
77
+ const baseDir = expandedTargetDir ? resolve(expandedTargetDir) : process.cwd();
78
+ const dest = dirName === '.' ? baseDir : resolve(baseDir, dirName);
68
79
  const destExists = existsSync(dest);
69
80
  const destEmpty = destExists ? readdirSync(dest).length === 0 : true;
70
81
 
71
82
  if (destExists && !destEmpty && dirName !== '.') {
72
83
  // In TTY mode, ask if they want to overwrite
73
84
  if (process.stdin.isTTY && !skipPrompts) {
74
- tui.warning(`Directory ${dirName} already exists and is not empty.\n`);
85
+ tui.warning(`Directory ${dest} already exists and is not empty.`, true);
75
86
  const response = await enquirer.prompt<{ overwrite: boolean }>({
76
87
  type: 'confirm',
77
88
  name: 'overwrite',
@@ -84,19 +95,23 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
84
95
  process.exit(0);
85
96
  }
86
97
 
87
- // Delete the existing directory
98
+ // Extra safety: refuse to delete root or home directories
99
+ const home = homedir();
100
+ if (dest === '/' || dest === home) {
101
+ logger.fatal(`Refusing to delete protected path: ${dest}`);
102
+ return;
103
+ }
88
104
  rmSync(dest, { recursive: true, force: true });
89
- tui.success(`Deleted ${dirName}\n`);
105
+ tui.success(`Deleted ${dest}\n`);
90
106
  } else {
91
- logger.fatal(`Directory ${dirName} already exists and is not empty.`);
107
+ logger.fatal(`Directory ${dest} already exists and is not empty.`, true);
92
108
  }
93
109
  }
94
110
 
95
111
  // Show directory and name confirmation
96
112
  if (!skipPrompts) {
97
- const displayPath = dirName === '.' ? basename(dest) : dirName;
98
113
  tui.info(`📁 Project: ${tui.bold(projectName)}`);
99
- tui.info(`📂 Directory: ${tui.bold(displayPath)}\n`);
114
+ tui.info(`📂 Directory: ${tui.bold(dest)}\n`);
100
115
  }
101
116
 
102
117
  // Step 5: Select template
@@ -152,14 +167,14 @@ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
152
167
  tui.success('✨ Project created successfully!\n');
153
168
  tui.info('Next steps:');
154
169
  if (dirName !== '.') {
170
+ const dirDisplay = cwd() == targetDir ? basename(dirName) : dest;
155
171
  tui.newline();
156
- console.log(` 1. ${tui.bold(`cd ${dirName}`)}`);
172
+ console.log(` 1. ${tui.bold(`cd ${dirDisplay}`)}`);
157
173
  console.log(` 2. ${tui.bold('bun run dev')}`);
158
174
  } else {
159
- console.log(` 1. ${tui.bold('bun run dev')}`);
175
+ console.log(` ${tui.bold('bun run dev')}`);
160
176
  }
161
- tui.newline();
162
- console.log(`Your agents will be running at ${tui.link('http://localhost:3000')}`);
177
+ playSound();
163
178
  }
164
179
 
165
180
  /**
package/src/download.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Transform } from 'node:stream';
1
+ import { Transform, Readable } from 'node:stream';
2
2
  import * as tui from './tui';
3
3
 
4
4
  export interface DownloadOptions {
@@ -48,7 +48,7 @@ export async function downloadWithProgress(
48
48
  });
49
49
 
50
50
  // Pipe the response through the progress tracker
51
- const responseStream = response.body as unknown as NodeJS.ReadableStream;
51
+ const responseStream = Readable.fromWeb(response.body as unknown as ReadableStream);
52
52
  responseStream.pipe(progressStream);
53
53
 
54
54
  return progressStream;
package/src/sound.ts CHANGED
@@ -1,25 +1,39 @@
1
- import { $ } from 'bun';
1
+ import { join } from 'node:path';
2
2
 
3
- export async function playSound(): Promise<void> {
3
+ export function playSound(): void {
4
4
  const platform = process.platform;
5
5
 
6
- let result;
6
+ let command: string[];
7
7
  switch (platform) {
8
- case 'darwin':
9
- result = await $`afplay /System/Library/Sounds/Glass.aiff`.quiet().nothrow();
8
+ case 'darwin': {
9
+ const items = [
10
+ 'Blow.aiff',
11
+ 'Bottle.aiff',
12
+ 'Frog.aiff',
13
+ 'Funk.aiff',
14
+ 'Glass.aiff',
15
+ 'Hero.aiff',
16
+ 'Morse.aiff',
17
+ 'Ping.aiff',
18
+ 'Pop.aiff',
19
+ 'Purr.aiff',
20
+ 'Sosumi.aiff',
21
+ ] as const;
22
+ const file = items[Math.floor(Math.random() * items.length)];
23
+ command = ['afplay', join('/System/Library/Sounds', file)];
10
24
  break;
25
+ }
11
26
  case 'linux':
12
- result = await $`paplay /usr/share/sounds/freedesktop/stereo/complete.oga`
13
- .quiet()
14
- .nothrow();
27
+ command = ['paplay', '/usr/share/sounds/freedesktop/stereo/complete.oga'];
15
28
  break;
16
29
  case 'win32':
17
- result = await $`rundll32 user32.dll,MessageBeep 0x00000040`.quiet().nothrow();
30
+ command = ['rundll32', 'user32.dll,MessageBeep', '0x00000040'];
18
31
  break;
32
+ default:
33
+ return;
19
34
  }
20
35
 
21
- // Fallback to terminal bell if command failed or platform unsupported
22
- if (!result || result.exitCode !== 0) {
23
- process.stdout.write('\u0007');
24
- }
36
+ Bun.spawn(command, {
37
+ stdio: ['ignore', 'ignore', 'ignore'],
38
+ }).unref();
25
39
  }
package/src/tui.ts CHANGED
@@ -85,8 +85,8 @@ export function error(message: string): void {
85
85
  /**
86
86
  * Print a warning message with a yellow warning icon
87
87
  */
88
- export function warning(message: string): void {
89
- const color = getColor('warning');
88
+ export function warning(message: string, asError = false): void {
89
+ const color = asError ? getColor('error') : getColor('warning');
90
90
  const reset = COLORS.reset;
91
91
  console.log(`${color}${ICONS.warning} ${message}${reset}`);
92
92
  }
@@ -805,7 +805,9 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
805
805
  process.stdout.write(`\x1b[${linesRendered}A`);
806
806
 
807
807
  // Show compact success: ✓ command
808
- process.stdout.write(`\r\x1b[K${green}${ICONS.success}${reset} ${cmdColor}${displayCmd}${reset}\n`);
808
+ process.stdout.write(
809
+ `\r\x1b[K${green}${ICONS.success}${reset} ${cmdColor}${displayCmd}${reset}\n`
810
+ );
809
811
  } else {
810
812
  // Determine how many lines to show in final output
811
813
  const finalLinesToShow = exitCode === 0 ? 3 : 10;