@agentuity/cli 0.0.10 → 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 +43 -2
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/project/create.d.ts.map +1 -1
- package/dist/cmd/project/download.d.ts +20 -0
- package/dist/cmd/project/download.d.ts.map +1 -0
- package/dist/cmd/project/template-flow.d.ts +15 -0
- package/dist/cmd/project/template-flow.d.ts.map +1 -0
- package/dist/cmd/project/templates.d.ts +8 -0
- package/dist/cmd/project/templates.d.ts.map +1 -0
- package/dist/download.d.ts +25 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/sound.d.ts +1 -1
- package/dist/sound.d.ts.map +1 -1
- package/dist/tui.d.ts +6 -1
- package/dist/tui.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/cmd/dev/index.ts +0 -2
- package/src/cmd/example/sound.ts +2 -2
- package/src/cmd/project/create.ts +25 -220
- package/src/cmd/project/download.ts +159 -0
- package/src/cmd/project/template-flow.ts +197 -0
- package/src/cmd/project/templates.ts +48 -0
- package/src/download.ts +101 -0
- package/src/index.ts +7 -0
- package/src/sound.ts +27 -13
- package/src/tui.ts +37 -17
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,
|
|
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":"
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/create.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,wCA+ClC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Logger } from '@/logger';
|
|
2
|
+
import type { TemplateInfo } from './templates';
|
|
3
|
+
interface DownloadOptions {
|
|
4
|
+
dest: string;
|
|
5
|
+
template: TemplateInfo;
|
|
6
|
+
templateDir?: string;
|
|
7
|
+
templateBranch?: string;
|
|
8
|
+
}
|
|
9
|
+
interface SetupOptions {
|
|
10
|
+
dest: string;
|
|
11
|
+
projectName: string;
|
|
12
|
+
dirName: string;
|
|
13
|
+
noInstall: boolean;
|
|
14
|
+
noBuild: boolean;
|
|
15
|
+
logger: Logger;
|
|
16
|
+
}
|
|
17
|
+
export declare function downloadTemplate(options: DownloadOptions): Promise<void>;
|
|
18
|
+
export declare function setupProject(options: SetupOptions): Promise<void>;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Logger } from '@/logger';
|
|
2
|
+
interface CreateFlowOptions {
|
|
3
|
+
projectName?: string;
|
|
4
|
+
dir?: string;
|
|
5
|
+
template?: string;
|
|
6
|
+
templateDir?: string;
|
|
7
|
+
templateBranch?: string;
|
|
8
|
+
noInstall: boolean;
|
|
9
|
+
noBuild: boolean;
|
|
10
|
+
skipPrompts: boolean;
|
|
11
|
+
logger: Logger;
|
|
12
|
+
}
|
|
13
|
+
export declare function runCreateFlow(options: CreateFlowOptions): Promise<void>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=template-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/templates.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAMD,wBAAsB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAgChG"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface DownloadOptions {
|
|
2
|
+
url: string;
|
|
3
|
+
headers?: Record<string, string>;
|
|
4
|
+
message?: string;
|
|
5
|
+
onProgress?: (percent: number, downloadedBytes: number, totalBytes: number) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Download a file with progress tracking
|
|
9
|
+
* Returns the response body stream for further processing
|
|
10
|
+
*/
|
|
11
|
+
export declare function downloadWithProgress(options: DownloadOptions): Promise<NodeJS.ReadableStream>;
|
|
12
|
+
/**
|
|
13
|
+
* Download a file with a TUI spinner showing progress
|
|
14
|
+
*/
|
|
15
|
+
export declare function downloadWithSpinner<T>(options: DownloadOptions, processor: (stream: NodeJS.ReadableStream) => Promise<T>): Promise<T>;
|
|
16
|
+
/**
|
|
17
|
+
* Download a GitHub tarball with progress tracking
|
|
18
|
+
*/
|
|
19
|
+
export interface DownloadGitHubOptions {
|
|
20
|
+
repo: string;
|
|
21
|
+
branch?: string;
|
|
22
|
+
message?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function downloadGitHubTarball(options: DownloadGitHubOptions): Promise<NodeJS.ReadableStream>;
|
|
25
|
+
//# sourceMappingURL=download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACpF;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,eAAe,GACtB,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAsChC;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAC1C,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,GACtD,OAAO,CAAC,CAAC,CAAC,CAoBZ;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,qBAAqB,CAC1C,OAAO,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAKhC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { getCommandPrefix, getCommand } from './command-prefix';
|
|
|
11
11
|
export * as tui from './tui';
|
|
12
12
|
export { runSteps, setStepsColorScheme, stepSuccess, stepSkipped, stepError } from './steps';
|
|
13
13
|
export { playSound } from './sound';
|
|
14
|
+
export { downloadWithProgress, downloadWithSpinner, downloadGitHubTarball, type DownloadOptions as DownloadOptionsType, type DownloadGitHubOptions, } from './download';
|
|
14
15
|
export type { Config, LogLevel, GlobalOptions, CommandContext, SubcommandDefinition, CommandDefinition, Profile, AuthData, CommandSchemas, } from './types';
|
|
15
16
|
export { createSubcommand, createCommand } from './types';
|
|
16
17
|
export type { ColorScheme } from './terminal';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EACN,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,WAAW,EACX,UAAU,EACV,aAAa,EACb,QAAQ,EACR,SAAS,EACT,OAAO,GACP,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,YAAY,EACX,MAAM,EACN,QAAQ,EACR,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,OAAO,EACP,QAAQ,EACR,cAAc,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EACN,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,WAAW,EACX,UAAU,EACV,aAAa,EACb,QAAQ,EACR,SAAS,EACT,OAAO,GACP,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EACN,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,eAAe,IAAI,mBAAmB,EAC3C,KAAK,qBAAqB,GAC1B,MAAM,YAAY,CAAC;AACpB,YAAY,EACX,MAAM,EACN,QAAQ,EACR,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,OAAO,EACP,QAAQ,EACR,cAAc,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/sound.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function playSound():
|
|
1
|
+
export declare function playSound(): void;
|
|
2
2
|
//# sourceMappingURL=sound.d.ts.map
|
package/dist/sound.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sound.d.ts","sourceRoot":"","sources":["../src/sound.ts"],"names":[],"mappings":"AAEA,
|
|
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
|
*/
|
|
@@ -141,6 +141,11 @@ export interface CommandRunnerOptions {
|
|
|
141
141
|
* Environment variables
|
|
142
142
|
*/
|
|
143
143
|
env?: Record<string, string>;
|
|
144
|
+
/**
|
|
145
|
+
* If true, clear output on success and only show command + success icon
|
|
146
|
+
* Defaults to false
|
|
147
|
+
*/
|
|
148
|
+
clearOnSuccess?: boolean;
|
|
144
149
|
}
|
|
145
150
|
/**
|
|
146
151
|
* Run an external command and stream its output with a live UI
|
package/dist/tui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA+C9C,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAExD;AAUD;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI7C;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI3C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,
|
|
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.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -24,20 +24,20 @@
|
|
|
24
24
|
"clean": "rm -rf dist",
|
|
25
25
|
"build": "bunx tsc --build --emitDeclarationOnly --force",
|
|
26
26
|
"typecheck": "bunx tsc --noEmit",
|
|
27
|
-
"test": "bun scripts/test-create-
|
|
28
|
-
"prepublishOnly": "bun run clean && bun run build"
|
|
29
|
-
"setup-dev-template": "bun scripts/setup-dev-template.ts",
|
|
30
|
-
"simulate-create": "bun scripts/simulate-bun-create.ts"
|
|
27
|
+
"test": "bun scripts/test-create-flow.ts",
|
|
28
|
+
"prepublishOnly": "bun run clean && bun run build"
|
|
31
29
|
},
|
|
32
30
|
"dependencies": {
|
|
33
|
-
"@agentuity/core": "0.0.
|
|
31
|
+
"@agentuity/core": "0.0.11",
|
|
34
32
|
"acorn-loose": "^8.5.2",
|
|
35
33
|
"astring": "^1.9.0",
|
|
36
34
|
"commander": "^14.0.2",
|
|
37
35
|
"enquirer": "^2.4.1",
|
|
36
|
+
"tar-fs": "^3.1.1",
|
|
38
37
|
"zod": "^4.1.12"
|
|
39
38
|
},
|
|
40
39
|
"devDependencies": {
|
|
40
|
+
"@types/tar-fs": "^2.0.4",
|
|
41
41
|
"typescript": "^5.9.0"
|
|
42
42
|
},
|
|
43
43
|
"publishConfig": {
|
package/src/cmd/dev/index.ts
CHANGED
|
@@ -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
|
package/src/cmd/example/sound.ts
CHANGED
|
@@ -6,9 +6,9 @@ export const soundSubcommand: SubcommandDefinition = {
|
|
|
6
6
|
name: 'sound',
|
|
7
7
|
description: 'Test completion sound',
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
handler() {
|
|
10
10
|
tui.info('Playing completion sound...');
|
|
11
|
-
|
|
11
|
+
playSound();
|
|
12
12
|
tui.success('Sound played!');
|
|
13
13
|
},
|
|
14
14
|
};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { createSubcommand } from '@/types';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import
|
|
4
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
5
|
-
import { resolve, basename, join } from 'node:path';
|
|
3
|
+
import { runCreateFlow } from './template-flow';
|
|
6
4
|
|
|
7
5
|
export const createProjectSubcommand = createSubcommand({
|
|
8
6
|
name: 'create',
|
|
@@ -14,234 +12,41 @@ export const createProjectSubcommand = createSubcommand({
|
|
|
14
12
|
options: z.object({
|
|
15
13
|
name: z.string().optional().describe('Project name'),
|
|
16
14
|
dir: z.string().optional().describe('Directory to create the project in'),
|
|
15
|
+
template: z.string().optional().describe('Template to use'),
|
|
16
|
+
templateDir: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Local template directory for testing (e.g., ./packages/templates)'),
|
|
20
|
+
templateBranch: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('GitHub branch to use for templates (default: main)'),
|
|
17
24
|
install: z
|
|
18
25
|
.boolean()
|
|
19
26
|
.optional()
|
|
20
27
|
.default(true)
|
|
21
28
|
.describe('Run bun install after creating the project (use --no-install to skip)'),
|
|
22
|
-
|
|
23
|
-
fromBunCreate: z
|
|
29
|
+
build: z
|
|
24
30
|
.boolean()
|
|
25
31
|
.optional()
|
|
26
|
-
.
|
|
27
|
-
|
|
32
|
+
.default(true)
|
|
33
|
+
.describe('Run bun run build after installing (use --no-build to skip)'),
|
|
34
|
+
confirm: z.boolean().optional().describe('Skip confirmation prompts'),
|
|
28
35
|
}),
|
|
29
36
|
},
|
|
30
37
|
|
|
31
38
|
async handler(ctx) {
|
|
32
39
|
const { logger, opts } = ctx;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
}
|
|
40
|
+
await runCreateFlow({
|
|
41
|
+
projectName: opts.name,
|
|
42
|
+
dir: opts.dir,
|
|
43
|
+
template: opts.template,
|
|
44
|
+
templateDir: opts.templateDir,
|
|
45
|
+
templateBranch: opts.templateBranch,
|
|
46
|
+
noInstall: opts.install === false,
|
|
47
|
+
noBuild: opts.build === false,
|
|
48
|
+
skipPrompts: opts.confirm === true,
|
|
49
|
+
logger,
|
|
50
|
+
});
|
|
246
51
|
},
|
|
247
52
|
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
2
|
+
import { existsSync, mkdirSync, renameSync, readdirSync, cpSync, rmSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { pipeline } from 'node:stream/promises';
|
|
5
|
+
import { createGunzip } from 'node:zlib';
|
|
6
|
+
import { extract, type Headers } from 'tar-fs';
|
|
7
|
+
import type { Logger } from '@/logger';
|
|
8
|
+
import * as tui from '@/tui';
|
|
9
|
+
import { downloadWithSpinner } from '@/download';
|
|
10
|
+
import type { TemplateInfo } from './templates';
|
|
11
|
+
|
|
12
|
+
const GITHUB_REPO = 'agentuity/sdk';
|
|
13
|
+
const GITHUB_BRANCH = 'main';
|
|
14
|
+
|
|
15
|
+
interface DownloadOptions {
|
|
16
|
+
dest: string;
|
|
17
|
+
template: TemplateInfo;
|
|
18
|
+
templateDir?: string;
|
|
19
|
+
templateBranch?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface SetupOptions {
|
|
23
|
+
dest: string;
|
|
24
|
+
projectName: string;
|
|
25
|
+
dirName: string;
|
|
26
|
+
noInstall: boolean;
|
|
27
|
+
noBuild: boolean;
|
|
28
|
+
logger: Logger;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function cleanup(sourceDir: string, dest: string) {
|
|
32
|
+
if (!existsSync(sourceDir)) {
|
|
33
|
+
throw new Error(`Template directory not found: ${sourceDir}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
tui.spinner(`📦 Copying template from ${sourceDir}...`, async () => {
|
|
37
|
+
// Copy all files from source to dest
|
|
38
|
+
const files = readdirSync(sourceDir);
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
cpSync(join(sourceDir, file), join(dest, file), { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Rename gitignore -> .gitignore
|
|
44
|
+
const gi = join(dest, 'gitignore');
|
|
45
|
+
if (existsSync(gi)) {
|
|
46
|
+
renameSync(gi, join(dest, '.gitignore'));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function downloadTemplate(options: DownloadOptions): Promise<void> {
|
|
52
|
+
const { dest, template, templateDir, templateBranch } = options;
|
|
53
|
+
|
|
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);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Download from GitHub
|
|
68
|
+
const branch = templateBranch || GITHUB_BRANCH;
|
|
69
|
+
const templatePath = `templates/${template.directory}`;
|
|
70
|
+
const url = `https://codeload.github.com/${GITHUB_REPO}/tar.gz/${branch}`;
|
|
71
|
+
const tempDir = join(dest, '.temp-download');
|
|
72
|
+
mkdirSync(tempDir, { recursive: true });
|
|
73
|
+
|
|
74
|
+
await downloadWithSpinner(
|
|
75
|
+
{
|
|
76
|
+
url,
|
|
77
|
+
message: templateBranch
|
|
78
|
+
? `Downloading template files from branch ${branch}...`
|
|
79
|
+
: 'Downloading template files...',
|
|
80
|
+
},
|
|
81
|
+
async (stream) => {
|
|
82
|
+
// Extract only the template directory from tarball
|
|
83
|
+
const prefix = `sdk-${branch}/${templatePath}/`;
|
|
84
|
+
await pipeline(
|
|
85
|
+
stream,
|
|
86
|
+
createGunzip(),
|
|
87
|
+
extract(tempDir, {
|
|
88
|
+
filter: (name: string) => name.startsWith(prefix),
|
|
89
|
+
map: (header: Headers) => {
|
|
90
|
+
header.name = header.name.substring(prefix.length);
|
|
91
|
+
return header;
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
await cleanup(tempDir, dest);
|
|
99
|
+
|
|
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}`);
|
|
104
|
+
}
|
|
105
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function setupProject(options: SetupOptions): Promise<void> {
|
|
109
|
+
const { dest, projectName, dirName, noInstall, noBuild, logger } = options;
|
|
110
|
+
|
|
111
|
+
// Replace {{PROJECT_NAME}} in files
|
|
112
|
+
tui.info(`🔧 Setting up ${projectName}...`);
|
|
113
|
+
await replaceInFiles(dest, projectName, dirName);
|
|
114
|
+
|
|
115
|
+
// Install dependencies
|
|
116
|
+
if (!noInstall) {
|
|
117
|
+
const exitCode = await tui.runCommand({
|
|
118
|
+
command: 'bun install',
|
|
119
|
+
cwd: dest,
|
|
120
|
+
cmd: ['bun', 'install'],
|
|
121
|
+
clearOnSuccess: true,
|
|
122
|
+
});
|
|
123
|
+
if (exitCode !== 0) {
|
|
124
|
+
logger.error('Failed to install dependencies');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Build project
|
|
129
|
+
if (!noBuild) {
|
|
130
|
+
const exitCode = await tui.runCommand({
|
|
131
|
+
command: 'bun run build',
|
|
132
|
+
cwd: dest,
|
|
133
|
+
cmd: ['bun', 'run', 'build'],
|
|
134
|
+
clearOnSuccess: true,
|
|
135
|
+
});
|
|
136
|
+
if (exitCode !== 0) {
|
|
137
|
+
logger.error('Failed to build project');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function replaceInFiles(dir: string, projectName: string, dirName: string): Promise<void> {
|
|
143
|
+
const filesToReplace = ['package.json', 'README.md', 'AGENTS.md'];
|
|
144
|
+
|
|
145
|
+
for (const file of filesToReplace) {
|
|
146
|
+
const filePath = join(dir, file);
|
|
147
|
+
const bunFile = Bun.file(filePath);
|
|
148
|
+
if (await bunFile.exists()) {
|
|
149
|
+
let content = await bunFile.text();
|
|
150
|
+
// Replace human-readable name in most places
|
|
151
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
152
|
+
// Replace with directory name for package.json "name" field (npm package name)
|
|
153
|
+
if (file === 'package.json') {
|
|
154
|
+
content = content.replace(/"name":\s*".*?"/, `"name": "${dirName}"`);
|
|
155
|
+
}
|
|
156
|
+
await Bun.write(filePath, content);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { basename, resolve } from 'node:path';
|
|
2
|
+
import { existsSync, readdirSync, rmSync } from 'node:fs';
|
|
3
|
+
import { cwd } from 'node:process';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import enquirer from 'enquirer';
|
|
6
|
+
import type { Logger } from '@/logger';
|
|
7
|
+
import * as tui from '@/tui';
|
|
8
|
+
import { playSound } from '@/sound';
|
|
9
|
+
import { fetchTemplates, type TemplateInfo } from './templates';
|
|
10
|
+
import { downloadTemplate, setupProject } from './download';
|
|
11
|
+
|
|
12
|
+
interface CreateFlowOptions {
|
|
13
|
+
projectName?: string;
|
|
14
|
+
dir?: string;
|
|
15
|
+
template?: string;
|
|
16
|
+
templateDir?: string;
|
|
17
|
+
templateBranch?: string;
|
|
18
|
+
noInstall: boolean;
|
|
19
|
+
noBuild: boolean;
|
|
20
|
+
skipPrompts: boolean;
|
|
21
|
+
logger: Logger;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
|
|
25
|
+
const {
|
|
26
|
+
projectName: initialProjectName,
|
|
27
|
+
dir: targetDir,
|
|
28
|
+
template: initialTemplate,
|
|
29
|
+
templateDir,
|
|
30
|
+
templateBranch,
|
|
31
|
+
skipPrompts,
|
|
32
|
+
logger,
|
|
33
|
+
} = options;
|
|
34
|
+
|
|
35
|
+
// Step 1: Fetch available templates
|
|
36
|
+
if (templateDir) {
|
|
37
|
+
tui.info(`📋 Loading templates from local directory: ${templateDir}...\n`);
|
|
38
|
+
} else if (templateBranch) {
|
|
39
|
+
tui.info(`📋 Fetching available templates from branch: ${templateBranch}...\n`);
|
|
40
|
+
} else {
|
|
41
|
+
tui.info('📋 Fetching available templates...\n');
|
|
42
|
+
}
|
|
43
|
+
const templates = await fetchTemplates(templateDir, templateBranch);
|
|
44
|
+
|
|
45
|
+
if (templates.length === 0) {
|
|
46
|
+
logger.fatal('No templates available');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Step 2: Get project name
|
|
50
|
+
let projectName = initialProjectName;
|
|
51
|
+
if (!projectName && !skipPrompts) {
|
|
52
|
+
const response = await enquirer.prompt<{ name: string }>({
|
|
53
|
+
type: 'input',
|
|
54
|
+
name: 'name',
|
|
55
|
+
message: 'What is the name of your project?',
|
|
56
|
+
initial: 'My First Agent',
|
|
57
|
+
validate: (value: string) => {
|
|
58
|
+
if (!value || value.trim().length === 0) {
|
|
59
|
+
return 'Project name is required';
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
projectName = response.name;
|
|
65
|
+
}
|
|
66
|
+
projectName = projectName || 'My First Agent';
|
|
67
|
+
|
|
68
|
+
// Step 3: Generate disk-friendly directory name
|
|
69
|
+
const dirName = projectName === '.' ? '.' : sanitizeDirectoryName(projectName);
|
|
70
|
+
|
|
71
|
+
// Step 4: Determine destination directory
|
|
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);
|
|
79
|
+
const destExists = existsSync(dest);
|
|
80
|
+
const destEmpty = destExists ? readdirSync(dest).length === 0 : true;
|
|
81
|
+
|
|
82
|
+
if (destExists && !destEmpty && dirName !== '.') {
|
|
83
|
+
// In TTY mode, ask if they want to overwrite
|
|
84
|
+
if (process.stdin.isTTY && !skipPrompts) {
|
|
85
|
+
tui.warning(`Directory ${dest} already exists and is not empty.`, true);
|
|
86
|
+
const response = await enquirer.prompt<{ overwrite: boolean }>({
|
|
87
|
+
type: 'confirm',
|
|
88
|
+
name: 'overwrite',
|
|
89
|
+
message: 'Delete and overwrite the directory?',
|
|
90
|
+
initial: false,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (!response.overwrite) {
|
|
94
|
+
tui.info('Operation cancelled');
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
|
|
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
|
+
}
|
|
104
|
+
rmSync(dest, { recursive: true, force: true });
|
|
105
|
+
tui.success(`Deleted ${dest}\n`);
|
|
106
|
+
} else {
|
|
107
|
+
logger.fatal(`Directory ${dest} already exists and is not empty.`, true);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Show directory and name confirmation
|
|
112
|
+
if (!skipPrompts) {
|
|
113
|
+
tui.info(`📁 Project: ${tui.bold(projectName)}`);
|
|
114
|
+
tui.info(`📂 Directory: ${tui.bold(dest)}\n`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Step 5: Select template
|
|
118
|
+
let selectedTemplate: TemplateInfo;
|
|
119
|
+
if (initialTemplate) {
|
|
120
|
+
const found = templates.find((t) => t.id === initialTemplate);
|
|
121
|
+
if (!found) {
|
|
122
|
+
logger.fatal(`Template "${initialTemplate}" not found`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
selectedTemplate = found;
|
|
126
|
+
} else if (skipPrompts) {
|
|
127
|
+
selectedTemplate = templates[0];
|
|
128
|
+
} else {
|
|
129
|
+
const response = await enquirer.prompt<{ template: string }>({
|
|
130
|
+
type: 'select',
|
|
131
|
+
name: 'template',
|
|
132
|
+
message: 'Select a template:',
|
|
133
|
+
choices: templates.map((t) => ({
|
|
134
|
+
name: t.id,
|
|
135
|
+
message: `${t.name.padEnd(15, ' ')} ${tui.muted(t.description)}`,
|
|
136
|
+
})),
|
|
137
|
+
});
|
|
138
|
+
const found = templates.find((t) => t.id === response.template);
|
|
139
|
+
if (!found) {
|
|
140
|
+
logger.fatal('Template selection failed');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
selectedTemplate = found;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
tui.info(`✨ Using template: ${tui.bold(selectedTemplate.name)}`);
|
|
147
|
+
|
|
148
|
+
// Step 6: Download template
|
|
149
|
+
await downloadTemplate({
|
|
150
|
+
dest,
|
|
151
|
+
template: selectedTemplate,
|
|
152
|
+
templateDir,
|
|
153
|
+
templateBranch,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Step 7: Setup project (replace placeholders, install deps, build)
|
|
157
|
+
await setupProject({
|
|
158
|
+
dest,
|
|
159
|
+
projectName: projectName === '.' ? basename(dest) : projectName,
|
|
160
|
+
dirName: dirName === '.' ? basename(dest) : dirName,
|
|
161
|
+
noInstall: options.noInstall,
|
|
162
|
+
noBuild: options.noBuild,
|
|
163
|
+
logger,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Step 8: Show completion message
|
|
167
|
+
tui.success('✨ Project created successfully!\n');
|
|
168
|
+
tui.info('Next steps:');
|
|
169
|
+
if (dirName !== '.') {
|
|
170
|
+
const dirDisplay = cwd() == targetDir ? basename(dirName) : dest;
|
|
171
|
+
tui.newline();
|
|
172
|
+
console.log(` 1. ${tui.bold(`cd ${dirDisplay}`)}`);
|
|
173
|
+
console.log(` 2. ${tui.bold('bun run dev')}`);
|
|
174
|
+
} else {
|
|
175
|
+
console.log(` ${tui.bold('bun run dev')}`);
|
|
176
|
+
}
|
|
177
|
+
playSound();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Sanitize a project name to create a safe directory/package name
|
|
182
|
+
* - Converts to lowercase
|
|
183
|
+
* - Replaces spaces and underscores with hyphens
|
|
184
|
+
* - Removes unsafe characters
|
|
185
|
+
* - Ensures it starts with a letter or number
|
|
186
|
+
*/
|
|
187
|
+
function sanitizeDirectoryName(name: string): string {
|
|
188
|
+
return name
|
|
189
|
+
.toLowerCase()
|
|
190
|
+
.trim()
|
|
191
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
192
|
+
.replace(/_+/g, '-') // Replace underscores with hyphens
|
|
193
|
+
.replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric except hyphens
|
|
194
|
+
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
195
|
+
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
|
|
196
|
+
.replace(/^[^a-z0-9]+/, ''); // Remove leading non-alphanumeric
|
|
197
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const GITHUB_REPO = 'agentuity/sdk';
|
|
2
|
+
const GITHUB_BRANCH = 'main';
|
|
3
|
+
const TEMPLATES_JSON_PATH = 'templates/templates.json';
|
|
4
|
+
|
|
5
|
+
export interface TemplateInfo {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
directory: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface TemplatesManifest {
|
|
13
|
+
templates: TemplateInfo[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function fetchTemplates(localDir?: string, branch?: string): Promise<TemplateInfo[]> {
|
|
17
|
+
// Load from local directory if provided
|
|
18
|
+
if (localDir) {
|
|
19
|
+
const { join } = await import('node:path');
|
|
20
|
+
const { resolve } = await import('node:path');
|
|
21
|
+
const manifestPath = resolve(join(localDir, 'templates.json'));
|
|
22
|
+
const file = Bun.file(manifestPath);
|
|
23
|
+
|
|
24
|
+
if (!(await file.exists())) {
|
|
25
|
+
throw new Error(`templates.json not found at ${manifestPath}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const manifest = (await file.json()) as TemplatesManifest;
|
|
29
|
+
return manifest.templates;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Fetch from GitHub
|
|
33
|
+
const branchToUse = branch || GITHUB_BRANCH;
|
|
34
|
+
const url = `https://raw.githubusercontent.com/${GITHUB_REPO}/${branchToUse}/${TEMPLATES_JSON_PATH}`;
|
|
35
|
+
|
|
36
|
+
const headers: Record<string, string> = {};
|
|
37
|
+
if (process.env.GITHUB_TOKEN) {
|
|
38
|
+
headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const response = await fetch(url, { headers });
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(`Failed to fetch templates: ${response.statusText}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const manifest = (await response.json()) as TemplatesManifest;
|
|
47
|
+
return manifest.templates;
|
|
48
|
+
}
|
package/src/download.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Transform, Readable } from 'node:stream';
|
|
2
|
+
import * as tui from './tui';
|
|
3
|
+
|
|
4
|
+
export interface DownloadOptions {
|
|
5
|
+
url: string;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
message?: string;
|
|
8
|
+
onProgress?: (percent: number, downloadedBytes: number, totalBytes: number) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Download a file with progress tracking
|
|
13
|
+
* Returns the response body stream for further processing
|
|
14
|
+
*/
|
|
15
|
+
export async function downloadWithProgress(
|
|
16
|
+
options: DownloadOptions
|
|
17
|
+
): Promise<NodeJS.ReadableStream> {
|
|
18
|
+
const { url, headers = {}, onProgress } = options;
|
|
19
|
+
|
|
20
|
+
// Add GITHUB_TOKEN if available and not already set
|
|
21
|
+
const requestHeaders = { ...headers };
|
|
22
|
+
if (process.env.GITHUB_TOKEN && !requestHeaders['Authorization']) {
|
|
23
|
+
requestHeaders['Authorization'] = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const response = await fetch(url, { headers: requestHeaders });
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new Error(`Download failed: ${response.statusText}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const contentLength = parseInt(response.headers.get('content-length') || '0', 10);
|
|
32
|
+
let downloadedBytes = 0;
|
|
33
|
+
|
|
34
|
+
// Create a transform stream that tracks progress
|
|
35
|
+
const progressStream = new Transform({
|
|
36
|
+
transform(chunk, _encoding, callback) {
|
|
37
|
+
downloadedBytes += chunk.length;
|
|
38
|
+
|
|
39
|
+
if (contentLength > 0) {
|
|
40
|
+
const percent = Math.min(100, Math.floor((downloadedBytes / contentLength) * 100));
|
|
41
|
+
if (onProgress) {
|
|
42
|
+
onProgress(percent, downloadedBytes, contentLength);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
callback(null, chunk);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Pipe the response through the progress tracker
|
|
51
|
+
const responseStream = Readable.fromWeb(response.body as unknown as ReadableStream);
|
|
52
|
+
responseStream.pipe(progressStream);
|
|
53
|
+
|
|
54
|
+
return progressStream;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Download a file with a TUI spinner showing progress
|
|
59
|
+
*/
|
|
60
|
+
export async function downloadWithSpinner<T>(
|
|
61
|
+
options: DownloadOptions,
|
|
62
|
+
processor: (stream: NodeJS.ReadableStream) => Promise<T>
|
|
63
|
+
): Promise<T> {
|
|
64
|
+
const { message = 'Downloading...' } = options;
|
|
65
|
+
|
|
66
|
+
return await tui.spinner({
|
|
67
|
+
type: 'progress',
|
|
68
|
+
message,
|
|
69
|
+
callback: async (updateProgress) => {
|
|
70
|
+
const stream = await downloadWithProgress({
|
|
71
|
+
...options,
|
|
72
|
+
onProgress: (percent) => updateProgress(percent),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const result = await processor(stream);
|
|
76
|
+
|
|
77
|
+
// Ensure we show 100% at the end
|
|
78
|
+
updateProgress(100);
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Download a GitHub tarball with progress tracking
|
|
87
|
+
*/
|
|
88
|
+
export interface DownloadGitHubOptions {
|
|
89
|
+
repo: string;
|
|
90
|
+
branch?: string;
|
|
91
|
+
message?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function downloadGitHubTarball(
|
|
95
|
+
options: DownloadGitHubOptions
|
|
96
|
+
): Promise<NodeJS.ReadableStream> {
|
|
97
|
+
const { repo, branch = 'main', message = 'Downloading from GitHub...' } = options;
|
|
98
|
+
const url = `https://codeload.github.com/${repo}/tar.gz/${branch}`;
|
|
99
|
+
|
|
100
|
+
return await downloadWithSpinner({ url, message }, async (stream) => stream);
|
|
101
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,13 @@ export { getCommandPrefix, getCommand } from './command-prefix';
|
|
|
24
24
|
export * as tui from './tui';
|
|
25
25
|
export { runSteps, setStepsColorScheme, stepSuccess, stepSkipped, stepError } from './steps';
|
|
26
26
|
export { playSound } from './sound';
|
|
27
|
+
export {
|
|
28
|
+
downloadWithProgress,
|
|
29
|
+
downloadWithSpinner,
|
|
30
|
+
downloadGitHubTarball,
|
|
31
|
+
type DownloadOptions as DownloadOptionsType,
|
|
32
|
+
type DownloadGitHubOptions,
|
|
33
|
+
} from './download';
|
|
27
34
|
export type {
|
|
28
35
|
Config,
|
|
29
36
|
LogLevel,
|
package/src/sound.ts
CHANGED
|
@@ -1,25 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join } from 'node:path';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export function playSound(): void {
|
|
4
4
|
const platform = process.platform;
|
|
5
5
|
|
|
6
|
-
let
|
|
6
|
+
let command: string[];
|
|
7
7
|
switch (platform) {
|
|
8
|
-
case 'darwin':
|
|
9
|
-
|
|
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
|
-
|
|
13
|
-
.quiet()
|
|
14
|
-
.nothrow();
|
|
27
|
+
command = ['paplay', '/usr/share/sounds/freedesktop/stereo/complete.oga'];
|
|
15
28
|
break;
|
|
16
29
|
case 'win32':
|
|
17
|
-
|
|
30
|
+
command = ['rundll32', 'user32.dll,MessageBeep', '0x00000040'];
|
|
18
31
|
break;
|
|
32
|
+
default:
|
|
33
|
+
return;
|
|
19
34
|
}
|
|
20
35
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
36
|
+
Bun.spawn(command, {
|
|
37
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
38
|
+
}).unref();
|
|
25
39
|
}
|
package/src/tui.ts
CHANGED
|
@@ -37,7 +37,7 @@ const COLORS = {
|
|
|
37
37
|
},
|
|
38
38
|
muted: {
|
|
39
39
|
light: Bun.color('#808080', 'ansi') || '\x1b[90m', // gray
|
|
40
|
-
dark: Bun.color('#
|
|
40
|
+
dark: Bun.color('#888888', 'ansi') || '\x1b[90m', // darker gray
|
|
41
41
|
},
|
|
42
42
|
bold: {
|
|
43
43
|
light: '\x1b[1m',
|
|
@@ -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
|
}
|
|
@@ -653,6 +653,11 @@ export interface CommandRunnerOptions {
|
|
|
653
653
|
* Environment variables
|
|
654
654
|
*/
|
|
655
655
|
env?: Record<string, string>;
|
|
656
|
+
/**
|
|
657
|
+
* If true, clear output on success and only show command + success icon
|
|
658
|
+
* Defaults to false
|
|
659
|
+
*/
|
|
660
|
+
clearOnSuccess?: boolean;
|
|
656
661
|
}
|
|
657
662
|
|
|
658
663
|
/**
|
|
@@ -666,7 +671,7 @@ export interface CommandRunnerOptions {
|
|
|
666
671
|
* Shows the last 3 lines of output as it streams.
|
|
667
672
|
*/
|
|
668
673
|
export async function runCommand(options: CommandRunnerOptions): Promise<number> {
|
|
669
|
-
const { command, cmd, cwd, env } = options;
|
|
674
|
+
const { command, cmd, cwd, env, clearOnSuccess = false } = options;
|
|
670
675
|
const isTTY = process.stdout.isTTY;
|
|
671
676
|
|
|
672
677
|
// If not a TTY, just run the command normally and log output
|
|
@@ -785,26 +790,41 @@ export async function runCommand(options: CommandRunnerOptions): Promise<number>
|
|
|
785
790
|
// Wait for process to exit
|
|
786
791
|
const exitCode = await proc.exited;
|
|
787
792
|
|
|
788
|
-
// Determine how many lines to show in final output
|
|
789
|
-
const finalLinesToShow = exitCode === 0 ? 3 : 10;
|
|
790
|
-
|
|
791
793
|
// Move cursor up to redraw final state
|
|
792
794
|
if (linesRendered > 0) {
|
|
793
795
|
process.stdout.write(`\x1b[${linesRendered}A`);
|
|
794
796
|
}
|
|
795
797
|
|
|
796
|
-
//
|
|
797
|
-
|
|
798
|
-
|
|
798
|
+
// Clear all lines if clearOnSuccess is true and command succeeded
|
|
799
|
+
if (clearOnSuccess && exitCode === 0) {
|
|
800
|
+
// Clear all rendered lines
|
|
801
|
+
for (let i = 0; i < linesRendered; i++) {
|
|
802
|
+
process.stdout.write('\r\x1b[K\n');
|
|
803
|
+
}
|
|
804
|
+
// Move cursor back up
|
|
805
|
+
process.stdout.write(`\x1b[${linesRendered}A`);
|
|
799
806
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
807
|
+
// Show compact success: ✓ command
|
|
808
|
+
process.stdout.write(
|
|
809
|
+
`\r\x1b[K${green}${ICONS.success}${reset} ${cmdColor}${displayCmd}${reset}\n`
|
|
810
|
+
);
|
|
811
|
+
} else {
|
|
812
|
+
// Determine how many lines to show in final output
|
|
813
|
+
const finalLinesToShow = exitCode === 0 ? 3 : 10;
|
|
814
|
+
|
|
815
|
+
// Show final status with appropriate color
|
|
816
|
+
const statusColor = exitCode === 0 ? green : red;
|
|
817
|
+
process.stdout.write(`\r\x1b[K${statusColor}$${reset} ${cmdColor}${displayCmd}${reset}\n`);
|
|
818
|
+
|
|
819
|
+
// Show final output lines
|
|
820
|
+
const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
|
|
821
|
+
for (const line of finalOutputLines) {
|
|
822
|
+
let displayLine = line;
|
|
823
|
+
if (getDisplayWidth(displayLine) > maxLineWidth) {
|
|
824
|
+
displayLine = displayLine.slice(0, maxLineWidth - 3) + '...';
|
|
825
|
+
}
|
|
826
|
+
process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
|
|
806
827
|
}
|
|
807
|
-
process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
|
|
808
828
|
}
|
|
809
829
|
|
|
810
830
|
return exitCode;
|