@agentuity/cli 0.0.91 → 0.0.93
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/dist/agents-docs.d.ts +22 -0
- package/dist/agents-docs.d.ts.map +1 -0
- package/dist/agents-docs.js +36 -0
- package/dist/agents-docs.js.map +1 -0
- package/dist/banner.d.ts.map +1 -1
- package/dist/banner.js +11 -4
- package/dist/banner.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/cmd/cloud/storage/upload.d.ts.map +1 -1
- package/dist/cmd/cloud/storage/upload.js +40 -26
- package/dist/cmd/cloud/storage/upload.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +3 -0
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +1 -0
- package/dist/cmd/index.js.map +1 -1
- package/dist/cmd/project/download.d.ts.map +1 -1
- package/dist/cmd/project/download.js +173 -55
- package/dist/cmd/project/download.js.map +1 -1
- package/dist/cmd/setup/index.d.ts +2 -0
- package/dist/cmd/setup/index.d.ts.map +1 -0
- package/dist/cmd/setup/index.js +37 -0
- package/dist/cmd/setup/index.js.map +1 -0
- package/dist/cmd/upgrade/index.d.ts.map +1 -1
- package/dist/cmd/upgrade/index.js +6 -1
- package/dist/cmd/upgrade/index.js.map +1 -1
- package/dist/tui.d.ts +16 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +3 -3
- package/dist/tui.js.map +1 -1
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +6 -1
- package/dist/version-check.js.map +1 -1
- package/dist/version.d.ts +17 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +24 -0
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
- package/src/agents-docs.ts +52 -0
- package/src/banner.ts +18 -4
- package/src/cli.ts +6 -0
- package/src/cmd/cloud/storage/upload.ts +43 -29
- package/src/cmd/dev/index.ts +5 -1
- package/src/cmd/index.ts +1 -0
- package/src/cmd/project/download.ts +194 -59
- package/src/cmd/setup/index.ts +42 -0
- package/src/cmd/upgrade/index.ts +8 -1
- package/src/tui.ts +3 -3
- package/src/version-check.ts +6 -1
- package/src/version.ts +28 -0
|
@@ -24,10 +24,18 @@ export const uploadSubcommand = createSubcommand({
|
|
|
24
24
|
command: `${getCommand('cloud storage put')} my-bucket file.txt --content-type text/plain`,
|
|
25
25
|
description: 'Upload file with content type',
|
|
26
26
|
},
|
|
27
|
+
{
|
|
28
|
+
command: `${getCommand('cloud storage upload')} my-bucket file.txt --key custom-name.txt`,
|
|
29
|
+
description: 'Upload file with custom object key',
|
|
30
|
+
},
|
|
27
31
|
{
|
|
28
32
|
command: `cat file.txt | ${getCommand('cloud storage upload')} my-bucket -`,
|
|
29
33
|
description: 'Upload from stdin',
|
|
30
34
|
},
|
|
35
|
+
{
|
|
36
|
+
command: `cat data.json | ${getCommand('cloud storage upload')} my-bucket - --key data.json`,
|
|
37
|
+
description: 'Upload from stdin with custom key',
|
|
38
|
+
},
|
|
31
39
|
],
|
|
32
40
|
schema: {
|
|
33
41
|
args: z.object({
|
|
@@ -35,6 +43,10 @@ export const uploadSubcommand = createSubcommand({
|
|
|
35
43
|
filename: z.string().describe('File path to upload or "-" for STDIN'),
|
|
36
44
|
}),
|
|
37
45
|
options: z.object({
|
|
46
|
+
key: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('Remote object key (defaults to basename or "stdin" for piped uploads)'),
|
|
38
50
|
contentType: z
|
|
39
51
|
.string()
|
|
40
52
|
.optional()
|
|
@@ -75,40 +87,38 @@ export const uploadSubcommand = createSubcommand({
|
|
|
75
87
|
);
|
|
76
88
|
}
|
|
77
89
|
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
// Read file content
|
|
81
|
-
let fileContent: Buffer;
|
|
82
|
-
let actualFilename: string;
|
|
90
|
+
// Prepare streaming upload - we don't buffer the entire file in memory
|
|
91
|
+
let stream: ReadableStream<Uint8Array>;
|
|
83
92
|
|
|
84
93
|
if (args.filename === '-') {
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
const arrayBuffer = await Bun.readableStreamToArrayBuffer(Bun.stdin.stream());
|
|
88
|
-
fileContent = Buffer.from(arrayBuffer);
|
|
89
|
-
actualFilename = 'stdin';
|
|
90
|
-
} catch (error) {
|
|
91
|
-
tui.fatal(
|
|
92
|
-
`Cannot read from stdin: ${error instanceof Error ? error.message : String(error)}`,
|
|
93
|
-
ErrorCode.FILE_NOT_FOUND
|
|
94
|
-
);
|
|
95
|
-
}
|
|
94
|
+
// Stream from STDIN
|
|
95
|
+
stream = Bun.stdin.stream();
|
|
96
96
|
} else {
|
|
97
|
-
//
|
|
97
|
+
// Stream from file
|
|
98
98
|
const file = Bun.file(args.filename);
|
|
99
99
|
if (!(await file.exists())) {
|
|
100
100
|
tui.fatal(`File not found: ${args.filename}`, ErrorCode.FILE_NOT_FOUND);
|
|
101
101
|
}
|
|
102
|
-
|
|
103
|
-
actualFilename = basename(args.filename);
|
|
102
|
+
stream = file.stream();
|
|
104
103
|
}
|
|
105
104
|
|
|
106
|
-
//
|
|
105
|
+
// Derive the remote object key:
|
|
106
|
+
// 1. Use --key if provided
|
|
107
|
+
// 2. For stdin (-), default to 'stdin'
|
|
108
|
+
// 3. For files, use the basename
|
|
109
|
+
const objectKey =
|
|
110
|
+
opts.key && opts.key.trim().length > 0
|
|
111
|
+
? opts.key
|
|
112
|
+
: args.filename === '-'
|
|
113
|
+
? 'stdin'
|
|
114
|
+
: basename(args.filename);
|
|
115
|
+
|
|
116
|
+
// Auto-detect content type from the object key's extension
|
|
117
|
+
// This allows content-type detection for stdin when --key is provided
|
|
107
118
|
let contentType = opts.contentType;
|
|
108
|
-
if (!contentType
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const ext = dotIndex > 0 ? filename.substring(dotIndex + 1).toLowerCase() : undefined;
|
|
119
|
+
if (!contentType) {
|
|
120
|
+
const dotIndex = objectKey.lastIndexOf('.');
|
|
121
|
+
const ext = dotIndex > 0 ? objectKey.substring(dotIndex + 1).toLowerCase() : undefined;
|
|
112
122
|
const mimeTypes: Record<string, string> = {
|
|
113
123
|
txt: 'text/plain',
|
|
114
124
|
html: 'text/html',
|
|
@@ -139,11 +149,15 @@ export const uploadSubcommand = createSubcommand({
|
|
|
139
149
|
region: bucket.region,
|
|
140
150
|
});
|
|
141
151
|
|
|
152
|
+
// Upload using streaming - wrap the stream in a Response object
|
|
153
|
+
// S3Client.write accepts Response which allows streaming without buffering in memory
|
|
154
|
+
let bytesUploaded = 0;
|
|
155
|
+
|
|
142
156
|
await tui.spinner({
|
|
143
|
-
message: `Uploading ${
|
|
157
|
+
message: `Uploading ${objectKey} to ${args.name}`,
|
|
144
158
|
clearOnSuccess: true,
|
|
145
159
|
callback: async () => {
|
|
146
|
-
await s3Client.write(
|
|
160
|
+
bytesUploaded = await s3Client.write(objectKey, new Response(stream), {
|
|
147
161
|
type: contentType,
|
|
148
162
|
});
|
|
149
163
|
},
|
|
@@ -151,15 +165,15 @@ export const uploadSubcommand = createSubcommand({
|
|
|
151
165
|
|
|
152
166
|
if (!options.json) {
|
|
153
167
|
tui.success(
|
|
154
|
-
`Uploaded ${tui.bold(
|
|
168
|
+
`Uploaded ${tui.bold(objectKey)} to ${tui.bold(args.name)} (${bytesUploaded} bytes)`
|
|
155
169
|
);
|
|
156
170
|
}
|
|
157
171
|
|
|
158
172
|
return {
|
|
159
173
|
success: true,
|
|
160
174
|
bucket: args.name,
|
|
161
|
-
filename:
|
|
162
|
-
size:
|
|
175
|
+
filename: objectKey,
|
|
176
|
+
size: bytesUploaded,
|
|
163
177
|
};
|
|
164
178
|
},
|
|
165
179
|
});
|
package/src/cmd/dev/index.ts
CHANGED
|
@@ -25,8 +25,9 @@ import { BuildMetadata } from '@agentuity/server';
|
|
|
25
25
|
import { getCommand } from '../../command-prefix';
|
|
26
26
|
import { notifyWorkbenchClients } from '../../utils/workbench-notify';
|
|
27
27
|
import { getEnvFilePaths, readEnvFile } from '../../env-util';
|
|
28
|
+
import { writeAgentsDocs } from '../../agents-docs';
|
|
28
29
|
|
|
29
|
-
const shouldDisableInteractive
|
|
30
|
+
const shouldDisableInteractive= (interactive?: boolean) => {
|
|
30
31
|
if (!interactive) {
|
|
31
32
|
return true;
|
|
32
33
|
}
|
|
@@ -102,6 +103,9 @@ export const command = createCommand({
|
|
|
102
103
|
|
|
103
104
|
await saveProjectDir(rootDir);
|
|
104
105
|
|
|
106
|
+
// Regenerate AGENTS.md files if they are missing (e.g., after node_modules reinstall)
|
|
107
|
+
await writeAgentsDocs(rootDir, { onlyIfMissing: true });
|
|
108
|
+
|
|
105
109
|
let devmode: DevmodeResponse | undefined;
|
|
106
110
|
let gravityBin: string | undefined;
|
|
107
111
|
let gravityURL: string | undefined;
|
package/src/cmd/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export async function discoverCommands(): Promise<CommandDefinition[]> {
|
|
|
13
13
|
import('./profile').then((m) => m.command),
|
|
14
14
|
import('./project').then((m) => m.command),
|
|
15
15
|
import('./repl').then((m) => m.command),
|
|
16
|
+
import('./setup').then((m) => m.command),
|
|
16
17
|
import('./upgrade').then((m) => m.command),
|
|
17
18
|
import('./version').then((m) => m.command),
|
|
18
19
|
]);
|
|
@@ -16,13 +16,11 @@ import { extract, type Headers } from 'tar-fs';
|
|
|
16
16
|
import { StructuredError, type Logger } from '@agentuity/core';
|
|
17
17
|
import * as tui from '../../tui';
|
|
18
18
|
import { downloadWithSpinner } from '../../download';
|
|
19
|
-
import {
|
|
20
|
-
import { generateLLMPrompt as generateAgentPrompt } from '../ai/prompt/agent';
|
|
21
|
-
import { generateLLMPrompt as generateWebPrompt } from '../ai/prompt/web';
|
|
22
|
-
import { generateLLMPrompt as generateAPIPrompt } from '../ai/prompt/api';
|
|
19
|
+
import { writeAgentsDocs } from '../../agents-docs';
|
|
23
20
|
import type { TemplateInfo } from './templates';
|
|
24
21
|
|
|
25
22
|
const GITHUB_BRANCH = 'main';
|
|
23
|
+
const BASE_TEMPLATE_DIR = '_base';
|
|
26
24
|
|
|
27
25
|
interface DownloadOptions {
|
|
28
26
|
dest: string;
|
|
@@ -45,7 +43,78 @@ const TemplateDirectoryNotFoundError = StructuredError('TemplateDirectoryNotFoun
|
|
|
45
43
|
directory: string;
|
|
46
44
|
}>();
|
|
47
45
|
|
|
48
|
-
async function
|
|
46
|
+
async function copyTemplateFiles(sourceDir: string, dest: string, skipGitignoreRename = false) {
|
|
47
|
+
if (!existsSync(sourceDir)) {
|
|
48
|
+
return; // Source directory doesn't exist, skip (overlay may be empty)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Copy all files from source to dest (overlay wins on conflicts)
|
|
52
|
+
const files = readdirSync(sourceDir);
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
// Skip package.overlay.json - it's handled separately for merging
|
|
55
|
+
if (file === 'package.overlay.json') {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// Skip .gitkeep files - they're just placeholders for empty directories
|
|
59
|
+
if (file === '.gitkeep') {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
cpSync(join(sourceDir, file), join(dest, file), { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Rename gitignore -> .gitignore (only do this once, after all copies)
|
|
66
|
+
if (!skipGitignoreRename) {
|
|
67
|
+
const gi = join(dest, 'gitignore');
|
|
68
|
+
if (existsSync(gi)) {
|
|
69
|
+
renameSync(gi, join(dest, '.gitignore'));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function mergePackageJson(dest: string, overlayDir: string) {
|
|
75
|
+
const basePackagePath = join(dest, 'package.json');
|
|
76
|
+
const overlayPackagePath = join(overlayDir, 'package.overlay.json');
|
|
77
|
+
|
|
78
|
+
// If no overlay package.json exists, nothing to merge
|
|
79
|
+
if (!existsSync(overlayPackagePath)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Read base package.json
|
|
84
|
+
const basePackage = JSON.parse(await Bun.file(basePackagePath).text());
|
|
85
|
+
|
|
86
|
+
// Read overlay package.json
|
|
87
|
+
const overlayPackage = JSON.parse(await Bun.file(overlayPackagePath).text());
|
|
88
|
+
|
|
89
|
+
// Merge dependencies (overlay wins on conflicts)
|
|
90
|
+
if (overlayPackage.dependencies) {
|
|
91
|
+
basePackage.dependencies = {
|
|
92
|
+
...basePackage.dependencies,
|
|
93
|
+
...overlayPackage.dependencies,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Merge devDependencies (overlay wins on conflicts)
|
|
98
|
+
if (overlayPackage.devDependencies) {
|
|
99
|
+
basePackage.devDependencies = {
|
|
100
|
+
...basePackage.devDependencies,
|
|
101
|
+
...overlayPackage.devDependencies,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Merge scripts (overlay wins on conflicts)
|
|
106
|
+
if (overlayPackage.scripts) {
|
|
107
|
+
basePackage.scripts = {
|
|
108
|
+
...basePackage.scripts,
|
|
109
|
+
...overlayPackage.scripts,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Write merged package.json
|
|
114
|
+
await Bun.write(basePackagePath, JSON.stringify(basePackage, null, '\t') + '\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function _cleanup(sourceDir: string, dest: string) {
|
|
49
118
|
if (!existsSync(sourceDir)) {
|
|
50
119
|
throw new TemplateDirectoryNotFoundError({
|
|
51
120
|
directory: sourceDir,
|
|
@@ -75,28 +144,59 @@ export async function downloadTemplate(options: DownloadOptions): Promise<void>
|
|
|
75
144
|
|
|
76
145
|
// Copy from local directory if provided
|
|
77
146
|
if (templateDir) {
|
|
78
|
-
const
|
|
147
|
+
const baseDir = resolve(join(templateDir, BASE_TEMPLATE_DIR));
|
|
148
|
+
const overlayDir = resolve(join(templateDir, template.directory));
|
|
79
149
|
|
|
80
|
-
|
|
150
|
+
// Base template must exist
|
|
151
|
+
if (!existsSync(baseDir)) {
|
|
81
152
|
throw new TemplateDirectoryNotFoundError({
|
|
82
|
-
directory:
|
|
83
|
-
message: `
|
|
153
|
+
directory: baseDir,
|
|
154
|
+
message: `Base template directory not found: ${baseDir}`,
|
|
84
155
|
});
|
|
85
156
|
}
|
|
86
157
|
|
|
87
|
-
|
|
158
|
+
// Overlay directory must exist (even if empty)
|
|
159
|
+
if (!existsSync(overlayDir)) {
|
|
160
|
+
throw new TemplateDirectoryNotFoundError({
|
|
161
|
+
directory: overlayDir,
|
|
162
|
+
message: `Template directory not found: ${overlayDir}`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await tui.spinner({
|
|
167
|
+
type: 'progress',
|
|
168
|
+
message: '📦 Copying template files...',
|
|
169
|
+
clearOnSuccess: true,
|
|
170
|
+
callback: async (progress) => {
|
|
171
|
+
// Step 1: Copy base template files (skip gitignore rename for now)
|
|
172
|
+
await copyTemplateFiles(baseDir, dest, true);
|
|
173
|
+
progress(33);
|
|
174
|
+
|
|
175
|
+
// Step 2: Copy overlay template files (overlay wins on conflicts)
|
|
176
|
+
await copyTemplateFiles(overlayDir, dest, false);
|
|
177
|
+
progress(66);
|
|
178
|
+
|
|
179
|
+
// Step 3: Merge package.json with overlay dependencies
|
|
180
|
+
await mergePackageJson(dest, overlayDir);
|
|
181
|
+
progress(100);
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return;
|
|
88
186
|
}
|
|
89
187
|
|
|
90
188
|
// Download from GitHub
|
|
91
189
|
const branch = templateBranch || GITHUB_BRANCH;
|
|
92
|
-
const
|
|
190
|
+
const basePath = `templates/${BASE_TEMPLATE_DIR}`;
|
|
191
|
+
const overlayPath = `templates/${template.directory}`;
|
|
93
192
|
const url = `https://agentuity.sh/template/sdk/${branch}/tar.gz`;
|
|
94
193
|
const tempDir = mkdtempSync(join(tmpdir(), 'agentuity-'));
|
|
95
194
|
const tarballPath = join(tempDir, 'download.tar.gz');
|
|
96
195
|
|
|
97
196
|
logger.debug('[download] URL: %s', url);
|
|
98
197
|
logger.debug('[download] Branch: %s', branch);
|
|
99
|
-
logger.debug('[download]
|
|
198
|
+
logger.debug('[download] Base path: %s', basePath);
|
|
199
|
+
logger.debug('[download] Overlay path: %s', overlayPath);
|
|
100
200
|
logger.debug('[download] Temp dir: %s', tempDir);
|
|
101
201
|
|
|
102
202
|
try {
|
|
@@ -124,37 +224,53 @@ export async function downloadTemplate(options: DownloadOptions): Promise<void>
|
|
|
124
224
|
}
|
|
125
225
|
);
|
|
126
226
|
|
|
127
|
-
// Step 2: Extract tarball
|
|
128
|
-
// We extract only the files within the template directory
|
|
227
|
+
// Step 2: Extract tarball - extract both base and overlay templates
|
|
129
228
|
// The tarball structure is: sdk-{branch}/templates/{template.directory}/...
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
229
|
+
const baseExtractDir = join(tempDir, 'base');
|
|
230
|
+
const overlayExtractDir = join(tempDir, 'overlay');
|
|
231
|
+
mkdirSync(baseExtractDir, { recursive: true });
|
|
232
|
+
mkdirSync(overlayExtractDir, { recursive: true });
|
|
233
|
+
|
|
234
|
+
const basePrefix = `sdk-${branch}/${basePath}/`;
|
|
235
|
+
const overlayPrefix = `sdk-${branch}/${overlayPath}/`;
|
|
236
|
+
logger.debug('[extract] Base extract dir: %s', baseExtractDir);
|
|
237
|
+
logger.debug('[extract] Overlay extract dir: %s', overlayExtractDir);
|
|
238
|
+
logger.debug('[extract] Base prefix: %s', basePrefix);
|
|
239
|
+
logger.debug('[extract] Overlay prefix: %s', overlayPrefix);
|
|
136
240
|
|
|
137
241
|
// Track extraction stats for debugging
|
|
138
242
|
let ignoredCount = 0;
|
|
139
|
-
let
|
|
243
|
+
let baseExtractedCount = 0;
|
|
244
|
+
let overlayExtractedCount = 0;
|
|
140
245
|
|
|
141
246
|
// Track which entries we've mapped so we don't ignore them later
|
|
142
247
|
// Note: tar-fs calls map BEFORE ignore (despite what docs say)
|
|
143
248
|
const mappedEntries = new Set<string>();
|
|
144
249
|
|
|
145
|
-
const extractor = extract(
|
|
250
|
+
const extractor = extract(tempDir, {
|
|
146
251
|
// map callback: called FIRST, allows modifying the entry before extraction
|
|
147
|
-
// We
|
|
252
|
+
// We extract base files to baseExtractDir and overlay files to overlayExtractDir
|
|
148
253
|
map: (header: Headers) => {
|
|
149
254
|
const originalName = header.name;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
logger.debug('[extract]
|
|
156
|
-
|
|
255
|
+
|
|
256
|
+
// Check if this is a base template file
|
|
257
|
+
if (header.name.startsWith(basePrefix) && header.name.length > basePrefix.length) {
|
|
258
|
+
header.name = `base/${header.name.substring(basePrefix.length)}`;
|
|
259
|
+
mappedEntries.add(header.name);
|
|
260
|
+
logger.debug('[extract] MAP BASE: %s -> %s', originalName, header.name);
|
|
261
|
+
baseExtractedCount++;
|
|
262
|
+
}
|
|
263
|
+
// Check if this is an overlay template file
|
|
264
|
+
else if (
|
|
265
|
+
header.name.startsWith(overlayPrefix) &&
|
|
266
|
+
header.name.length > overlayPrefix.length
|
|
267
|
+
) {
|
|
268
|
+
header.name = `overlay/${header.name.substring(overlayPrefix.length)}`;
|
|
269
|
+
mappedEntries.add(header.name);
|
|
270
|
+
logger.debug('[extract] MAP OVERLAY: %s -> %s', originalName, header.name);
|
|
271
|
+
overlayExtractedCount++;
|
|
157
272
|
}
|
|
273
|
+
|
|
158
274
|
return header;
|
|
159
275
|
},
|
|
160
276
|
// ignore callback: called AFTER map, receives the MAPPED name
|
|
@@ -183,10 +299,28 @@ export async function downloadTemplate(options: DownloadOptions): Promise<void>
|
|
|
183
299
|
|
|
184
300
|
logger.debug('[extract] Extraction complete');
|
|
185
301
|
logger.debug('[extract] Ignored entries: %d', ignoredCount);
|
|
186
|
-
logger.debug('[extract]
|
|
302
|
+
logger.debug('[extract] Base extracted entries: %d', baseExtractedCount);
|
|
303
|
+
logger.debug('[extract] Overlay extracted entries: %d', overlayExtractedCount);
|
|
187
304
|
|
|
188
|
-
// Step 3: Copy
|
|
189
|
-
await
|
|
305
|
+
// Step 3: Copy base template files, then overlay template files
|
|
306
|
+
await tui.spinner({
|
|
307
|
+
type: 'progress',
|
|
308
|
+
message: '📦 Copying template files...',
|
|
309
|
+
clearOnSuccess: true,
|
|
310
|
+
callback: async (progress) => {
|
|
311
|
+
// Copy base template files (skip gitignore rename for now)
|
|
312
|
+
await copyTemplateFiles(baseExtractDir, dest, true);
|
|
313
|
+
progress(33);
|
|
314
|
+
|
|
315
|
+
// Copy overlay template files (overlay wins on conflicts)
|
|
316
|
+
await copyTemplateFiles(overlayExtractDir, dest, false);
|
|
317
|
+
progress(66);
|
|
318
|
+
|
|
319
|
+
// Merge package.json with overlay dependencies
|
|
320
|
+
await mergePackageJson(dest, overlayExtractDir);
|
|
321
|
+
progress(100);
|
|
322
|
+
},
|
|
323
|
+
});
|
|
190
324
|
} finally {
|
|
191
325
|
// Clean up temp directory
|
|
192
326
|
logger.debug('[cleanup] Removing temp dir: %s', tempDir);
|
|
@@ -214,6 +348,30 @@ export async function setupProject(options: SetupOptions): Promise<void> {
|
|
|
214
348
|
}
|
|
215
349
|
}
|
|
216
350
|
|
|
351
|
+
// Run optional template setup script if it exists
|
|
352
|
+
// This allows templates to run custom setup logic after bun install
|
|
353
|
+
const setupScriptPath = join(dest, '_setup.ts');
|
|
354
|
+
if (existsSync(setupScriptPath)) {
|
|
355
|
+
try {
|
|
356
|
+
const exitCode = await tui.runCommand({
|
|
357
|
+
command: 'bun _setup.ts',
|
|
358
|
+
cwd: dest,
|
|
359
|
+
cmd: ['bun', '_setup.ts'],
|
|
360
|
+
clearOnSuccess: true,
|
|
361
|
+
});
|
|
362
|
+
if (exitCode !== 0) {
|
|
363
|
+
logger.error('Template setup script failed');
|
|
364
|
+
}
|
|
365
|
+
} finally {
|
|
366
|
+
// Always delete the setup script after running (or attempting to run)
|
|
367
|
+
try {
|
|
368
|
+
rmSync(setupScriptPath);
|
|
369
|
+
} catch {
|
|
370
|
+
// Ignore errors when deleting the setup script
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
217
375
|
// Build project
|
|
218
376
|
if (!noBuild) {
|
|
219
377
|
const exitCode = await tui.runCommand({
|
|
@@ -278,32 +436,9 @@ export async function setupProject(options: SetupOptions): Promise<void> {
|
|
|
278
436
|
});
|
|
279
437
|
}
|
|
280
438
|
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const agentFile = join(cliDir, 'AGENTS.md');
|
|
285
|
-
const prompt = generateCLIPrompt();
|
|
286
|
-
await Bun.write(agentFile, prompt);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// generate and write AGENTS.md for each of the main folders
|
|
290
|
-
const agentDir = join(dest, 'src', 'agent');
|
|
291
|
-
if (existsSync(agentDir)) {
|
|
292
|
-
const agentAPIFile = join(agentDir, 'AGENTS.md');
|
|
293
|
-
await Bun.write(agentAPIFile, generateAgentPrompt());
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const apiDir = join(dest, 'src', 'api');
|
|
297
|
-
if (existsSync(apiDir)) {
|
|
298
|
-
const agentAPIFile = join(apiDir, 'AGENTS.md');
|
|
299
|
-
await Bun.write(agentAPIFile, generateAPIPrompt());
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const webDir = join(dest, 'src', 'web');
|
|
303
|
-
if (existsSync(webDir)) {
|
|
304
|
-
const webFile = join(webDir, 'AGENTS.md');
|
|
305
|
-
await Bun.write(webFile, generateWebPrompt());
|
|
306
|
-
}
|
|
439
|
+
// Generate and write AGENTS.md files for the CLI and source folders
|
|
440
|
+
// Always overwrite during project setup to ensure fresh content
|
|
441
|
+
await writeAgentsDocs(dest);
|
|
307
442
|
}
|
|
308
443
|
|
|
309
444
|
async function replaceInFiles(dir: string, projectName: string, dirName: string): Promise<void> {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createCommand } from '../../types';
|
|
3
|
+
import { showBanner } from '../../banner';
|
|
4
|
+
import * as tui from '../../tui';
|
|
5
|
+
import { getCommand } from '../../command-prefix';
|
|
6
|
+
|
|
7
|
+
export const command = createCommand({
|
|
8
|
+
name: 'setup',
|
|
9
|
+
description: 'Display first-run setup information (internal use)',
|
|
10
|
+
hidden: true,
|
|
11
|
+
skipUpgradeCheck: true,
|
|
12
|
+
tags: ['read-only', 'fast'],
|
|
13
|
+
optional: { auth: true },
|
|
14
|
+
schema: {
|
|
15
|
+
options: z.object({
|
|
16
|
+
nonInteractive: z.boolean().optional().describe('Run in non-interactive mode'),
|
|
17
|
+
}),
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async handler(ctx) {
|
|
21
|
+
const { opts, auth } = ctx;
|
|
22
|
+
const _nonInteractive = opts.nonInteractive ?? false;
|
|
23
|
+
|
|
24
|
+
tui.newline();
|
|
25
|
+
showBanner();
|
|
26
|
+
tui.newline();
|
|
27
|
+
|
|
28
|
+
if (!auth?.expires) {
|
|
29
|
+
tui.output(`${tui.muted('To get started, run:')}`);
|
|
30
|
+
tui.newline();
|
|
31
|
+
tui.output(
|
|
32
|
+
`${getCommand('login')} ${tui.muted('Login to an existing account (or signup)')}`
|
|
33
|
+
);
|
|
34
|
+
tui.output(`${getCommand('create')} ${tui.muted('Create a project')}`);
|
|
35
|
+
tui.output(`${getCommand('help')} ${tui.muted('List commands and options')}`);
|
|
36
|
+
} else {
|
|
37
|
+
tui.success('Welcome back! 🙌');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return undefined;
|
|
41
|
+
},
|
|
42
|
+
});
|
package/src/cmd/upgrade/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createCommand } from '../../types';
|
|
2
|
-
import { getVersion } from '../../version';
|
|
2
|
+
import { getVersion, getCompareUrl, getReleaseUrl, toTag } from '../../version';
|
|
3
3
|
import { getCommand } from '../../command-prefix';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { ErrorCode, createError, exitWithError } from '../../errors';
|
|
@@ -301,6 +301,13 @@ export const command = createCommand({
|
|
|
301
301
|
tui.info(`Current version: ${tui.muted(currentVersion)}`);
|
|
302
302
|
tui.info(`Latest version: ${tui.bold(latestVersion)}`);
|
|
303
303
|
tui.newline();
|
|
304
|
+
if (toTag(currentVersion) !== toTag(latestVersion)) {
|
|
305
|
+
tui.warning(
|
|
306
|
+
`What's changed: ${tui.link(getCompareUrl(currentVersion, latestVersion))}`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
tui.success(`Release notes: ${tui.link(getReleaseUrl(latestVersion))}`);
|
|
310
|
+
tui.newline();
|
|
304
311
|
|
|
305
312
|
const shouldUpgrade = await tui.confirm('Do you want to upgrade?', true);
|
|
306
313
|
|
package/src/tui.ts
CHANGED
|
@@ -307,7 +307,7 @@ export function link(url: string, title?: string, color = getColor('link')): str
|
|
|
307
307
|
/**
|
|
308
308
|
* Check if terminal supports OSC 8 hyperlinks
|
|
309
309
|
*/
|
|
310
|
-
function supportsHyperlinks(): boolean {
|
|
310
|
+
export function supportsHyperlinks(): boolean {
|
|
311
311
|
const term = process.env.TERM || '';
|
|
312
312
|
const termProgram = process.env.TERM_PROGRAM || '';
|
|
313
313
|
const wtSession = process.env.WT_SESSION || '';
|
|
@@ -361,7 +361,7 @@ export function output(message: string): void {
|
|
|
361
361
|
* which causes incorrect alignment. We strip OSC 8 codes first, then use Bun.stringWidth()
|
|
362
362
|
* to handle regular ANSI codes and unicode characters correctly.
|
|
363
363
|
*/
|
|
364
|
-
function getDisplayWidth(str: string): number {
|
|
364
|
+
export function getDisplayWidth(str: string): number {
|
|
365
365
|
// Remove OSC-8 hyperlink sequences using Unicode escapes (\u001b = ESC, \u0007 = BEL) to satisfy linter
|
|
366
366
|
// eslint-disable-next-line no-control-regex
|
|
367
367
|
const withoutOSC8 = str.replace(/\u001b\]8;;[^\u0007]*\u0007/g, '');
|
|
@@ -371,7 +371,7 @@ function getDisplayWidth(str: string): number {
|
|
|
371
371
|
/**
|
|
372
372
|
* Strip all ANSI escape sequences from a string
|
|
373
373
|
*/
|
|
374
|
-
function stripAnsi(str: string): string {
|
|
374
|
+
export function stripAnsi(str: string): string {
|
|
375
375
|
// eslint-disable-next-line no-control-regex
|
|
376
376
|
return str.replace(/\u001b\[[0-9;]*m/g, '').replace(/\u001b\]8;;[^\u0007]*\u0007/g, '');
|
|
377
377
|
}
|
package/src/version-check.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Config, Logger, CommandDefinition } from './types';
|
|
2
2
|
import { isRunningFromExecutable, fetchLatestVersion } from './cmd/upgrade';
|
|
3
|
-
import { getVersion } from './version';
|
|
3
|
+
import { getVersion, getCompareUrl, getReleaseUrl, toTag } from './version';
|
|
4
4
|
import * as tui from './tui';
|
|
5
5
|
import { saveConfig } from './config';
|
|
6
6
|
import { $ } from 'bun';
|
|
@@ -97,6 +97,11 @@ async function promptUpgrade(currentVersion: string, latestVersion: string): Pro
|
|
|
97
97
|
tui.info(`Current version: ${tui.muted(currentVersion)}`);
|
|
98
98
|
tui.info(`Latest version: ${tui.bold(latestVersion)}`);
|
|
99
99
|
tui.newline();
|
|
100
|
+
if (toTag(currentVersion) !== toTag(latestVersion)) {
|
|
101
|
+
tui.warning(`What's changed: ${tui.link(getCompareUrl(currentVersion, latestVersion))}`);
|
|
102
|
+
}
|
|
103
|
+
tui.success(`Release notes: ${tui.link(getReleaseUrl(latestVersion))}`);
|
|
104
|
+
tui.newline();
|
|
100
105
|
|
|
101
106
|
return await tui.confirm('Would you like to upgrade now?', true);
|
|
102
107
|
}
|
package/src/version.ts
CHANGED
|
@@ -27,3 +27,31 @@ export function getRevision(): string {
|
|
|
27
27
|
// Bun provides git SHA via Bun.revision
|
|
28
28
|
return typeof Bun !== 'undefined' && Bun.revision ? Bun.revision.substring(0, 8) : 'unknown';
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
const GITHUB_REPO_URL = 'https://github.com/agentuity/sdk';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Normalize a version string to a Git tag format (with 'v' prefix)
|
|
35
|
+
*/
|
|
36
|
+
export function toTag(version: string): string {
|
|
37
|
+
return version.startsWith('v') ? version : `v${version}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the GitHub URL for comparing two versions
|
|
42
|
+
* @param fromVersion - The current/old version
|
|
43
|
+
* @param toVersion - The new/target version
|
|
44
|
+
* @returns GitHub compare URL
|
|
45
|
+
*/
|
|
46
|
+
export function getCompareUrl(fromVersion: string, toVersion: string): string {
|
|
47
|
+
return `${GITHUB_REPO_URL}/compare/${toTag(fromVersion)}...${toTag(toVersion)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the GitHub URL for a specific release
|
|
52
|
+
* @param version - The version to get the release URL for
|
|
53
|
+
* @returns GitHub release URL
|
|
54
|
+
*/
|
|
55
|
+
export function getReleaseUrl(version: string): string {
|
|
56
|
+
return `${GITHUB_REPO_URL}/releases/tag/${toTag(version)}`;
|
|
57
|
+
}
|