@agentuity/cli 0.0.9 → 0.0.11

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.
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/create.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,uBAAuB,wCAgPlC,CAAC"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/create.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,wCA8ClC,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":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Logger } from '@/logger';
2
+ interface CreateFlowOptions {
3
+ projectName?: string;
4
+ template?: string;
5
+ templateDir?: string;
6
+ templateBranch?: string;
7
+ noInstall: boolean;
8
+ noBuild: boolean;
9
+ skipPrompts: boolean;
10
+ logger: Logger;
11
+ }
12
+ export declare function runCreateFlow(options: CreateFlowOptions): Promise<void>;
13
+ export {};
14
+ //# 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":"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"}
@@ -0,0 +1,8 @@
1
+ export interface TemplateInfo {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ directory: string;
6
+ }
7
+ export declare function fetchTemplates(localDir?: string, branch?: string): Promise<TemplateInfo[]>;
8
+ //# sourceMappingURL=templates.d.ts.map
@@ -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';
@@ -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/tui.d.ts CHANGED
@@ -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":"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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.9",
3
+ "version": "0.0.11",
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-integration.ts",
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.9",
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": {
@@ -1,8 +1,6 @@
1
1
  import { createSubcommand } from '@/types';
2
2
  import { z } from 'zod';
3
- import enquirer from 'enquirer';
4
- import { existsSync, readdirSync } from 'node:fs';
5
- import { resolve, basename, join } from 'node:path';
3
+ import { runCreateFlow } from './template-flow';
6
4
 
7
5
  export const createProjectSubcommand = createSubcommand({
8
6
  name: 'create',
@@ -14,234 +12,40 @@ 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
- confirm: z.boolean().optional().describe('Skip confirmation prompts'),
23
- fromBunCreate: z
29
+ build: z
24
30
  .boolean()
25
31
  .optional()
26
- .describe('Internal: called from bun create postinstall'),
27
- dev: z.boolean().optional().describe('Internal: use local template for testing'),
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
- // Case 2: Called from bun create postinstall
35
- if (opts.fromBunCreate) {
36
- const projectDir = process.cwd();
37
- const packageJsonPath = join(projectDir, 'package.json');
38
-
39
- if (!existsSync(packageJsonPath)) {
40
- logger.error('package.json not found in current directory');
41
- return;
42
- }
43
-
44
- // Disable log prefixes for cleaner postinstall output
45
- logger.setShowPrefix(false);
46
-
47
- const packageJsonFile = Bun.file(packageJsonPath);
48
- const packageJson = await packageJsonFile.json();
49
- const projectName = packageJson.name || basename(projectDir);
50
-
51
- logger.info(`\n🔧 Setting up ${projectName}...\n`);
52
-
53
- // Update package.json - remove bun-create metadata
54
- packageJson.name = projectName;
55
- delete packageJson['bun-create'];
56
- delete packageJson.bin;
57
- packageJson.private = true;
58
- delete packageJson.files;
59
- delete packageJson.keywords;
60
- delete packageJson.author;
61
- delete packageJson.license;
62
- delete packageJson.publishConfig;
63
- delete packageJson.description;
64
-
65
- // Remove enquirer from dependencies (only needed for setup)
66
- if (packageJson.dependencies) {
67
- delete packageJson.dependencies.enquirer;
68
- }
69
-
70
- await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, '\t'));
71
- logger.info('✓ Updated package.json');
72
-
73
- // Update README.md
74
- const readmePath = join(projectDir, 'README.md');
75
- if (existsSync(readmePath)) {
76
- const readmeFile = Bun.file(readmePath);
77
- let readme = await readmeFile.text();
78
- readme = readme.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
79
- await Bun.write(readmePath, readme);
80
- logger.info('✓ Updated README.md');
81
- }
82
-
83
- // Update AGENTS.md
84
- const agentsMdPath = join(projectDir, 'AGENTS.md');
85
- if (existsSync(agentsMdPath)) {
86
- const agentsMdFile = Bun.file(agentsMdPath);
87
- let agentsMd = await agentsMdFile.text();
88
- agentsMd = agentsMd.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
89
- await Bun.write(agentsMdPath, agentsMd);
90
- logger.info('✓ Updated AGENTS.md');
91
- }
92
-
93
- // Remove setup files
94
- const filesToRemove = ['setup.ts'];
95
- for (const file of filesToRemove) {
96
- const filePath = join(projectDir, file);
97
- if (existsSync(filePath)) {
98
- await Bun.$`rm ${filePath}`;
99
- logger.info('✓ Removed ${file}');
100
- }
101
- }
102
-
103
- logger.info('\n✨ Setup complete!\n');
104
- return;
105
- }
106
-
107
- // Case 1: Normal CLI flow
108
- // Relaxed validation: any reasonable name between 2-64 characters
109
- const isValidProjectName = (name: string): boolean => {
110
- return name.trim().length >= 2 && name.trim().length <= 64;
111
- };
112
-
113
- // Transform name to URL and disk-friendly format
114
- const transformToDirectoryName = (name: string): string => {
115
- const result = name
116
- .trim()
117
- .toLowerCase()
118
- .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with hyphens
119
- .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
120
- .replace(/-+/g, '-') // Replace consecutive hyphens with single hyphen
121
- .substring(0, 64); // Ensure max length
122
-
123
- // Validate result is non-empty (happens when name contains only special chars)
124
- if (!result) {
125
- throw new Error(
126
- `Invalid project name "${name}": must contain at least one alphanumeric character`
127
- );
128
- }
129
-
130
- return result;
131
- };
132
-
133
- // Get project name
134
- let projectName = opts.name;
135
- while (!projectName || !isValidProjectName(projectName)) {
136
- const result = await enquirer.prompt<{ name: string }>({
137
- type: 'input',
138
- name: 'name',
139
- message: 'Project name:',
140
- initial: projectName,
141
- validate: (value: string) => {
142
- if (!value) return 'Project name is required';
143
- if (!isValidProjectName(value)) {
144
- return 'Project name must be between 2 and 64 characters';
145
- }
146
- return true;
147
- },
148
- });
149
- projectName = result.name;
150
- }
151
-
152
- projectName = projectName.trim();
153
- const projectDirName = transformToDirectoryName(projectName);
154
-
155
- // Get directory - if specified, create the project there, otherwise create in current dir
156
- const baseDir = opts.dir ? resolve(opts.dir) : process.cwd();
157
- const targetDir = resolve(baseDir, projectDirName);
158
-
159
- // Check if directory exists and validate
160
- let shouldProceed = true;
161
- if (existsSync(targetDir)) {
162
- const files = readdirSync(targetDir);
163
- const hasFiles = files.length > 0;
164
-
165
- if (hasFiles) {
166
- if (opts.confirm === false) {
167
- logger.error(`Directory ${targetDir} is not empty and --no-confirm was specified`);
168
- return;
169
- }
170
-
171
- // Require explicit confirmation in non-TTY environments
172
- if (opts.confirm !== true && !process.stdin.isTTY) {
173
- logger.error(
174
- `Directory "${targetDir}" is not empty. Use --confirm flag in non-interactive environments.`
175
- );
176
- return;
177
- }
178
-
179
- // Interactive prompt in TTY environments
180
- if (opts.confirm !== true && process.stdin.isTTY) {
181
- const result = await enquirer.prompt<{ proceed: boolean }>({
182
- type: 'confirm',
183
- name: 'proceed',
184
- message: `Directory "${targetDir}" is not empty. Files may be overwritten. Continue?`,
185
- initial: false,
186
- });
187
- shouldProceed = result.proceed;
188
- }
189
-
190
- if (!shouldProceed) {
191
- logger.info('Operation cancelled');
192
- return;
193
- }
194
- }
195
- }
196
-
197
- // Print collected values
198
- logger.info('\n=== Project Configuration ===');
199
- logger.info(`Name: ${projectName}`);
200
- logger.info(`Directory Name: ${projectDirName}`);
201
- logger.info(`Target Directory: ${targetDir}`);
202
- logger.info('=============================\n');
203
-
204
- // Run bun create to scaffold the project
205
- logger.info('Creating project from template...');
206
-
207
- try {
208
- // Determine template name based on dev mode
209
- const templateName = opts.dev ? 'agentuity-dev' : 'agentuity';
210
-
211
- if (opts.dev) {
212
- logger.info('🔧 Dev mode: Using local template');
213
- }
214
-
215
- // Build bun create command args
216
- // Note: bun create supports --no-install to skip dependency installation
217
- const bunCreateArgs = ['bun', 'create'];
218
- if (opts.install === false) {
219
- bunCreateArgs.push('--no-install');
220
- }
221
- bunCreateArgs.push(templateName, projectDirName);
222
-
223
- logger.info(`Running: ${bunCreateArgs.join(' ')}`);
224
-
225
- const result = Bun.spawn(bunCreateArgs, {
226
- cwd: baseDir,
227
- stdout: 'inherit',
228
- stderr: 'inherit',
229
- stdin: 'inherit',
230
- });
231
-
232
- const exitCode = await result.exited;
233
-
234
- if (exitCode !== 0) {
235
- throw new Error(`bun create exited with code ${exitCode}`);
236
- }
237
-
238
- logger.info('\n✨ Project created successfully!');
239
- logger.info(`\nNext steps:`);
240
- logger.info(` cd ${projectDirName}`);
241
- logger.info(` bun run dev`);
242
- } catch (error) {
243
- logger.error('Failed to create project:', error);
244
- throw error;
245
- }
40
+ await runCreateFlow({
41
+ projectName: opts.name,
42
+ template: opts.template,
43
+ templateDir: opts.templateDir,
44
+ templateBranch: opts.templateBranch,
45
+ noInstall: opts.install === false,
46
+ noBuild: opts.build === false,
47
+ skipPrompts: opts.confirm === true,
48
+ logger,
49
+ });
246
50
  },
247
51
  });
@@ -0,0 +1,174 @@
1
+ import { join } from 'node:path';
2
+ import { existsSync, mkdirSync, renameSync, readdirSync, cpSync, rmSync } from 'node:fs';
3
+ import { pipeline } from 'node:stream/promises';
4
+ import { createGunzip } from 'node:zlib';
5
+ import { extract } from 'tar-fs';
6
+ import type { Logger } from '@/logger';
7
+ import * as tui from '@/tui';
8
+ import { downloadWithSpinner } from '@/download';
9
+ import type { TemplateInfo } from './templates';
10
+
11
+ const GITHUB_REPO = 'agentuity/sdk';
12
+ const GITHUB_BRANCH = 'main';
13
+
14
+ interface DownloadOptions {
15
+ dest: string;
16
+ template: TemplateInfo;
17
+ templateDir?: string;
18
+ templateBranch?: string;
19
+ }
20
+
21
+ interface SetupOptions {
22
+ dest: string;
23
+ projectName: string;
24
+ dirName: string;
25
+ noInstall: boolean;
26
+ noBuild: boolean;
27
+ logger: Logger;
28
+ }
29
+
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}...`);
45
+
46
+ // Copy all files from source to dest
47
+ const files = readdirSync(sourceDir);
48
+ for (const file of files) {
49
+ cpSync(join(sourceDir, file), join(dest, file), { recursive: true });
50
+ }
51
+
52
+ // Rename gitignore -> .gitignore
53
+ const gi = join(dest, 'gitignore');
54
+ if (existsSync(gi)) {
55
+ renameSync(gi, join(dest, '.gitignore'));
56
+ }
57
+
58
+ return;
59
+ }
60
+
61
+ // Download from GitHub
62
+ const branch = templateBranch || GITHUB_BRANCH;
63
+ const templatePath = `templates/${template.directory}`;
64
+ const url = `https://codeload.github.com/${GITHUB_REPO}/tar.gz/${branch}`;
65
+ const tempDir = join(dest, '.temp-download');
66
+ mkdirSync(tempDir, { recursive: true });
67
+
68
+ await downloadWithSpinner(
69
+ {
70
+ url,
71
+ message: templateBranch
72
+ ? `Downloading template files from branch ${branch}...`
73
+ : 'Downloading template files...',
74
+ },
75
+ async (stream) => {
76
+ // Extract only the template directory from tarball
77
+ await pipeline(
78
+ stream,
79
+ createGunzip(),
80
+ 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;
89
+ },
90
+ })
91
+ );
92
+ }
93
+ );
94
+
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 });
101
+
102
+ // Rename gitignore -> .gitignore
103
+ const gi = join(dest, 'gitignore');
104
+ if (existsSync(gi)) {
105
+ renameSync(gi, join(dest, '.gitignore'));
106
+ }
107
+ }
108
+
109
+ export async function setupProject(options: SetupOptions): Promise<void> {
110
+ const { dest, projectName, dirName, noInstall, noBuild, logger } = options;
111
+
112
+ process.chdir(dest);
113
+
114
+ // Replace {{PROJECT_NAME}} in files
115
+ tui.info(`🔧 Setting up ${projectName}...`);
116
+ await replaceInFiles(dest, projectName, dirName);
117
+
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
+ // Install dependencies
133
+ if (!noInstall) {
134
+ const exitCode = await tui.runCommand({
135
+ command: 'bun install',
136
+ cmd: ['bun', 'install'],
137
+ clearOnSuccess: true,
138
+ });
139
+ if (exitCode !== 0) {
140
+ logger.error('Failed to install dependencies');
141
+ }
142
+ }
143
+
144
+ // Build project
145
+ if (!noBuild) {
146
+ const exitCode = await tui.runCommand({
147
+ command: 'bun run build',
148
+ cmd: ['bun', 'run', 'build'],
149
+ clearOnSuccess: true,
150
+ });
151
+ if (exitCode !== 0) {
152
+ logger.error('Failed to build project');
153
+ }
154
+ }
155
+ }
156
+
157
+ async function replaceInFiles(dir: string, projectName: string, dirName: string): Promise<void> {
158
+ const filesToReplace = ['package.json', 'README.md', 'AGENTS.md'];
159
+
160
+ for (const file of filesToReplace) {
161
+ const filePath = join(dir, file);
162
+ const bunFile = Bun.file(filePath);
163
+ if (await bunFile.exists()) {
164
+ let content = await bunFile.text();
165
+ // Replace human-readable name in most places
166
+ content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
167
+ // Replace with directory name for package.json "name" field (npm package name)
168
+ if (file === 'package.json') {
169
+ content = content.replace(/"name":\s*".*?"/, `"name": "${dirName}"`);
170
+ }
171
+ await Bun.write(filePath, content);
172
+ }
173
+ }
174
+ }
@@ -0,0 +1,182 @@
1
+ import { basename, resolve } from 'node:path';
2
+ import { existsSync, readdirSync, rmSync } from 'node:fs';
3
+ import enquirer from 'enquirer';
4
+ import type { Logger } from '@/logger';
5
+ import * as tui from '@/tui';
6
+ import { fetchTemplates, type TemplateInfo } from './templates';
7
+ import { downloadTemplate, setupProject } from './download';
8
+
9
+ interface CreateFlowOptions {
10
+ projectName?: string;
11
+ template?: string;
12
+ templateDir?: string;
13
+ templateBranch?: string;
14
+ noInstall: boolean;
15
+ noBuild: boolean;
16
+ skipPrompts: boolean;
17
+ logger: Logger;
18
+ }
19
+
20
+ export async function runCreateFlow(options: CreateFlowOptions): Promise<void> {
21
+ const {
22
+ projectName: initialProjectName,
23
+ template: initialTemplate,
24
+ templateDir,
25
+ templateBranch,
26
+ skipPrompts,
27
+ logger,
28
+ } = options;
29
+
30
+ // Step 1: Fetch available templates
31
+ if (templateDir) {
32
+ tui.info(`📋 Loading templates from local directory: ${templateDir}...\n`);
33
+ } else if (templateBranch) {
34
+ tui.info(`📋 Fetching available templates from branch: ${templateBranch}...\n`);
35
+ } else {
36
+ tui.info('📋 Fetching available templates...\n');
37
+ }
38
+ const templates = await fetchTemplates(templateDir, templateBranch);
39
+
40
+ if (templates.length === 0) {
41
+ logger.fatal('No templates available');
42
+ }
43
+
44
+ // Step 2: Get project name
45
+ let projectName = initialProjectName;
46
+ if (!projectName && !skipPrompts) {
47
+ const response = await enquirer.prompt<{ name: string }>({
48
+ type: 'input',
49
+ name: 'name',
50
+ message: 'What is the name of your project?',
51
+ initial: 'My First Agent',
52
+ validate: (value: string) => {
53
+ if (!value || value.trim().length === 0) {
54
+ return 'Project name is required';
55
+ }
56
+ return true;
57
+ },
58
+ });
59
+ projectName = response.name;
60
+ }
61
+ projectName = projectName || 'My First Agent';
62
+
63
+ // Step 3: Generate disk-friendly directory name
64
+ const dirName = projectName === '.' ? '.' : sanitizeDirectoryName(projectName);
65
+
66
+ // Step 4: Determine destination directory
67
+ const dest = dirName === '.' ? process.cwd() : resolve(process.cwd(), dirName);
68
+ const destExists = existsSync(dest);
69
+ const destEmpty = destExists ? readdirSync(dest).length === 0 : true;
70
+
71
+ if (destExists && !destEmpty && dirName !== '.') {
72
+ // In TTY mode, ask if they want to overwrite
73
+ if (process.stdin.isTTY && !skipPrompts) {
74
+ tui.warning(`Directory ${dirName} already exists and is not empty.\n`);
75
+ const response = await enquirer.prompt<{ overwrite: boolean }>({
76
+ type: 'confirm',
77
+ name: 'overwrite',
78
+ message: 'Delete and overwrite the directory?',
79
+ initial: false,
80
+ });
81
+
82
+ if (!response.overwrite) {
83
+ tui.info('Operation cancelled');
84
+ process.exit(0);
85
+ }
86
+
87
+ // Delete the existing directory
88
+ rmSync(dest, { recursive: true, force: true });
89
+ tui.success(`Deleted ${dirName}\n`);
90
+ } else {
91
+ logger.fatal(`Directory ${dirName} already exists and is not empty.`);
92
+ }
93
+ }
94
+
95
+ // Show directory and name confirmation
96
+ if (!skipPrompts) {
97
+ const displayPath = dirName === '.' ? basename(dest) : dirName;
98
+ tui.info(`📁 Project: ${tui.bold(projectName)}`);
99
+ tui.info(`📂 Directory: ${tui.bold(displayPath)}\n`);
100
+ }
101
+
102
+ // Step 5: Select template
103
+ let selectedTemplate: TemplateInfo;
104
+ if (initialTemplate) {
105
+ const found = templates.find((t) => t.id === initialTemplate);
106
+ if (!found) {
107
+ logger.fatal(`Template "${initialTemplate}" not found`);
108
+ return;
109
+ }
110
+ selectedTemplate = found;
111
+ } else if (skipPrompts) {
112
+ selectedTemplate = templates[0];
113
+ } else {
114
+ const response = await enquirer.prompt<{ template: string }>({
115
+ type: 'select',
116
+ name: 'template',
117
+ message: 'Select a template:',
118
+ choices: templates.map((t) => ({
119
+ name: t.id,
120
+ message: `${t.name.padEnd(15, ' ')} ${tui.muted(t.description)}`,
121
+ })),
122
+ });
123
+ const found = templates.find((t) => t.id === response.template);
124
+ if (!found) {
125
+ logger.fatal('Template selection failed');
126
+ return;
127
+ }
128
+ selectedTemplate = found;
129
+ }
130
+
131
+ tui.info(`✨ Using template: ${tui.bold(selectedTemplate.name)}`);
132
+
133
+ // Step 6: Download template
134
+ await downloadTemplate({
135
+ dest,
136
+ template: selectedTemplate,
137
+ templateDir,
138
+ templateBranch,
139
+ });
140
+
141
+ // Step 7: Setup project (replace placeholders, install deps, build)
142
+ await setupProject({
143
+ dest,
144
+ projectName: projectName === '.' ? basename(dest) : projectName,
145
+ dirName: dirName === '.' ? basename(dest) : dirName,
146
+ noInstall: options.noInstall,
147
+ noBuild: options.noBuild,
148
+ logger,
149
+ });
150
+
151
+ // Step 8: Show completion message
152
+ tui.success('✨ Project created successfully!\n');
153
+ tui.info('Next steps:');
154
+ if (dirName !== '.') {
155
+ tui.newline();
156
+ console.log(` 1. ${tui.bold(`cd ${dirName}`)}`);
157
+ console.log(` 2. ${tui.bold('bun run dev')}`);
158
+ } else {
159
+ console.log(` 1. ${tui.bold('bun run dev')}`);
160
+ }
161
+ tui.newline();
162
+ console.log(`Your agents will be running at ${tui.link('http://localhost:3000')}`);
163
+ }
164
+
165
+ /**
166
+ * Sanitize a project name to create a safe directory/package name
167
+ * - Converts to lowercase
168
+ * - Replaces spaces and underscores with hyphens
169
+ * - Removes unsafe characters
170
+ * - Ensures it starts with a letter or number
171
+ */
172
+ function sanitizeDirectoryName(name: string): string {
173
+ return name
174
+ .toLowerCase()
175
+ .trim()
176
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
177
+ .replace(/_+/g, '-') // Replace underscores with hyphens
178
+ .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric except hyphens
179
+ .replace(/-+/g, '-') // Collapse multiple hyphens
180
+ .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
181
+ .replace(/^[^a-z0-9]+/, ''); // Remove leading non-alphanumeric
182
+ }
@@ -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
+ }
@@ -0,0 +1,101 @@
1
+ import { Transform } 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 = response.body as unknown as NodeJS.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/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('#DDDDDD', 'ansi') || '\x1b[37m', // light gray
40
+ dark: Bun.color('#888888', 'ansi') || '\x1b[90m', // darker gray
41
41
  },
42
42
  bold: {
43
43
  light: '\x1b[1m',
@@ -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,39 @@ 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
- // Show final status with appropriate color
797
- const statusColor = exitCode === 0 ? green : red;
798
- process.stdout.write(`\r\x1b[K${statusColor}$${reset} ${cmdColor}${displayCmd}${reset}\n`);
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
- // Show final output lines
801
- const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
802
- for (const line of finalOutputLines) {
803
- let displayLine = line;
804
- if (getDisplayWidth(displayLine) > maxLineWidth) {
805
- displayLine = displayLine.slice(0, maxLineWidth - 3) + '...';
807
+ // Show compact success: ✓ command
808
+ process.stdout.write(`\r\x1b[K${green}${ICONS.success}${reset} ${cmdColor}${displayCmd}${reset}\n`);
809
+ } else {
810
+ // Determine how many lines to show in final output
811
+ const finalLinesToShow = exitCode === 0 ? 3 : 10;
812
+
813
+ // Show final status with appropriate color
814
+ const statusColor = exitCode === 0 ? green : red;
815
+ process.stdout.write(`\r\x1b[K${statusColor}$${reset} ${cmdColor}${displayCmd}${reset}\n`);
816
+
817
+ // Show final output lines
818
+ const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
819
+ for (const line of finalOutputLines) {
820
+ let displayLine = line;
821
+ if (getDisplayWidth(displayLine) > maxLineWidth) {
822
+ displayLine = displayLine.slice(0, maxLineWidth - 3) + '...';
823
+ }
824
+ process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
806
825
  }
807
- process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
808
826
  }
809
827
 
810
828
  return exitCode;