@agentuity/cli 0.0.43 → 0.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/README.md +1 -1
- package/dist/api.d.ts +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/auth.d.ts +10 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/banner.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cmd/auth/api.d.ts +4 -4
- package/dist/cmd/auth/api.d.ts.map +1 -1
- package/dist/cmd/auth/index.d.ts.map +1 -1
- package/dist/cmd/auth/login.d.ts.map +1 -1
- package/dist/cmd/auth/signup.d.ts.map +1 -1
- package/dist/cmd/auth/ssh/add.d.ts +2 -0
- package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/api.d.ts +16 -0
- package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/delete.d.ts +2 -0
- package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/index.d.ts +3 -0
- package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
- package/dist/cmd/auth/ssh/list.d.ts +2 -0
- package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
- package/dist/cmd/auth/whoami.d.ts.map +1 -1
- package/dist/cmd/bundle/ast.d.ts +14 -3
- package/dist/cmd/bundle/ast.d.ts.map +1 -1
- package/dist/cmd/bundle/ast.test.d.ts +2 -0
- package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
- package/dist/cmd/bundle/bundler.d.ts +6 -1
- package/dist/cmd/bundle/bundler.d.ts.map +1 -1
- package/dist/cmd/bundle/file.d.ts.map +1 -1
- package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
- package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
- package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
- package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
- package/dist/cmd/bundle/plugin.d.ts +2 -0
- package/dist/cmd/bundle/plugin.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/domain.d.ts +17 -0
- package/dist/cmd/cloud/domain.d.ts.map +1 -0
- package/dist/cmd/cloud/index.d.ts.map +1 -1
- package/dist/cmd/cloud/resource/add.d.ts +2 -0
- package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/delete.d.ts +2 -0
- package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/index.d.ts +3 -0
- package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
- package/dist/cmd/cloud/resource/list.d.ts +2 -0
- package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
- package/dist/cmd/cloud/scp/download.d.ts +2 -0
- package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
- package/dist/cmd/cloud/scp/index.d.ts +3 -0
- package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
- package/dist/cmd/cloud/scp/upload.d.ts +2 -0
- package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
- package/dist/cmd/cloud/ssh.d.ts +2 -0
- package/dist/cmd/cloud/ssh.d.ts.map +1 -0
- package/dist/cmd/dev/api.d.ts +18 -0
- package/dist/cmd/dev/api.d.ts.map +1 -0
- package/dist/cmd/dev/download.d.ts +11 -0
- package/dist/cmd/dev/download.d.ts.map +1 -0
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/templates.d.ts +3 -0
- package/dist/cmd/dev/templates.d.ts.map +1 -0
- package/dist/cmd/env/delete.d.ts.map +1 -1
- package/dist/cmd/env/get.d.ts.map +1 -1
- package/dist/cmd/env/import.d.ts.map +1 -1
- package/dist/cmd/env/list.d.ts.map +1 -1
- package/dist/cmd/env/pull.d.ts.map +1 -1
- package/dist/cmd/env/push.d.ts.map +1 -1
- package/dist/cmd/env/set.d.ts.map +1 -1
- package/dist/cmd/profile/show.d.ts.map +1 -1
- package/dist/cmd/project/create.d.ts.map +1 -1
- package/dist/cmd/project/delete.d.ts.map +1 -1
- package/dist/cmd/project/list.d.ts.map +1 -1
- package/dist/cmd/project/show.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.d.ts +4 -0
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/secret/delete.d.ts.map +1 -1
- package/dist/cmd/secret/get.d.ts.map +1 -1
- package/dist/cmd/secret/import.d.ts.map +1 -1
- package/dist/cmd/secret/list.d.ts.map +1 -1
- package/dist/cmd/secret/pull.d.ts.map +1 -1
- package/dist/cmd/secret/push.d.ts.map +1 -1
- package/dist/cmd/secret/set.d.ts.map +1 -1
- package/dist/config.d.ts +9 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/crypto/box.d.ts +65 -0
- package/dist/crypto/box.d.ts.map +1 -0
- package/dist/crypto/box.test.d.ts +2 -0
- package/dist/crypto/box.test.d.ts.map +1 -0
- package/dist/download.d.ts.map +1 -1
- package/dist/steps.d.ts +4 -1
- package/dist/steps.d.ts.map +1 -1
- package/dist/terminal.d.ts.map +1 -1
- package/dist/tui.d.ts +31 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/types.d.ts +249 -126
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/detectSubagent.d.ts +15 -0
- package/dist/utils/detectSubagent.d.ts.map +1 -0
- package/dist/utils/zip.d.ts +7 -0
- package/dist/utils/zip.d.ts.map +1 -0
- package/package.json +11 -3
- package/src/api-errors.md +2 -2
- package/src/api.ts +12 -7
- package/src/auth.ts +116 -7
- package/src/banner.ts +13 -6
- package/src/cli.ts +695 -63
- package/src/cmd/auth/api.ts +10 -16
- package/src/cmd/auth/index.ts +2 -1
- package/src/cmd/auth/login.ts +24 -8
- package/src/cmd/auth/signup.ts +15 -11
- package/src/cmd/auth/ssh/add.ts +263 -0
- package/src/cmd/auth/ssh/api.ts +94 -0
- package/src/cmd/auth/ssh/delete.ts +102 -0
- package/src/cmd/auth/ssh/index.ts +10 -0
- package/src/cmd/auth/ssh/list.ts +74 -0
- package/src/cmd/auth/whoami.ts +13 -13
- package/src/cmd/bundle/ast.test.ts +565 -0
- package/src/cmd/bundle/ast.ts +457 -44
- package/src/cmd/bundle/bundler.ts +255 -57
- package/src/cmd/bundle/file.ts +6 -12
- package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
- package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
- package/src/cmd/bundle/index.ts +9 -9
- package/src/cmd/bundle/patch/aisdk.ts +1 -1
- package/src/cmd/bundle/plugin.ts +373 -53
- package/src/cmd/cloud/deploy.ts +300 -93
- package/src/cmd/cloud/domain.ts +92 -0
- package/src/cmd/cloud/index.ts +4 -1
- package/src/cmd/cloud/resource/add.ts +56 -0
- package/src/cmd/cloud/resource/delete.ts +120 -0
- package/src/cmd/cloud/resource/index.ts +11 -0
- package/src/cmd/cloud/resource/list.ts +69 -0
- package/src/cmd/cloud/scp/download.ts +59 -0
- package/src/cmd/cloud/scp/index.ts +9 -0
- package/src/cmd/cloud/scp/upload.ts +62 -0
- package/src/cmd/cloud/ssh.ts +68 -0
- package/src/cmd/dev/api.ts +46 -0
- package/src/cmd/dev/download.ts +111 -0
- package/src/cmd/dev/index.ts +360 -34
- package/src/cmd/dev/templates.ts +84 -0
- package/src/cmd/env/delete.ts +5 -20
- package/src/cmd/env/get.ts +5 -18
- package/src/cmd/env/import.ts +5 -20
- package/src/cmd/env/list.ts +5 -18
- package/src/cmd/env/pull.ts +10 -23
- package/src/cmd/env/push.ts +5 -23
- package/src/cmd/env/set.ts +5 -20
- package/src/cmd/index.ts +2 -2
- package/src/cmd/profile/show.ts +15 -6
- package/src/cmd/project/create.ts +7 -2
- package/src/cmd/project/delete.ts +75 -18
- package/src/cmd/project/download.ts +2 -2
- package/src/cmd/project/list.ts +8 -8
- package/src/cmd/project/show.ts +3 -7
- package/src/cmd/project/template-flow.ts +170 -72
- package/src/cmd/secret/delete.ts +5 -20
- package/src/cmd/secret/get.ts +5 -18
- package/src/cmd/secret/import.ts +5 -20
- package/src/cmd/secret/list.ts +5 -18
- package/src/cmd/secret/pull.ts +10 -23
- package/src/cmd/secret/push.ts +5 -23
- package/src/cmd/secret/set.ts +5 -20
- package/src/config.ts +224 -24
- package/src/crypto/box.test.ts +431 -0
- package/src/crypto/box.ts +477 -0
- package/src/download.ts +1 -0
- package/src/env-util.test.ts +1 -1
- package/src/steps.ts +65 -6
- package/src/terminal.ts +24 -23
- package/src/tui.ts +192 -61
- package/src/types.ts +291 -201
- package/src/utils/detectSubagent.ts +31 -0
- package/src/utils/zip.ts +38 -0
- package/dist/cmd/example/create-user.d.ts +0 -2
- package/dist/cmd/example/create-user.d.ts.map +0 -1
- package/dist/cmd/example/create.d.ts +0 -2
- package/dist/cmd/example/create.d.ts.map +0 -1
- package/dist/cmd/example/deploy.d.ts +0 -2
- package/dist/cmd/example/deploy.d.ts.map +0 -1
- package/dist/cmd/example/index.d.ts +0 -2
- package/dist/cmd/example/index.d.ts.map +0 -1
- package/dist/cmd/example/list.d.ts +0 -2
- package/dist/cmd/example/list.d.ts.map +0 -1
- package/dist/cmd/example/optional-auth.d.ts +0 -3
- package/dist/cmd/example/optional-auth.d.ts.map +0 -1
- package/dist/cmd/example/run-command.d.ts +0 -2
- package/dist/cmd/example/run-command.d.ts.map +0 -1
- package/dist/cmd/example/sound.d.ts +0 -3
- package/dist/cmd/example/sound.d.ts.map +0 -1
- package/dist/cmd/example/spinner.d.ts +0 -2
- package/dist/cmd/example/spinner.d.ts.map +0 -1
- package/dist/cmd/example/steps.d.ts +0 -2
- package/dist/cmd/example/steps.d.ts.map +0 -1
- package/dist/cmd/example/version.d.ts +0 -2
- package/dist/cmd/example/version.d.ts.map +0 -1
- package/src/cmd/example/create-user.ts +0 -38
- package/src/cmd/example/create.ts +0 -31
- package/src/cmd/example/deploy.ts +0 -36
- package/src/cmd/example/index.ts +0 -29
- package/src/cmd/example/list.ts +0 -32
- package/src/cmd/example/optional-auth.ts +0 -38
- package/src/cmd/example/run-command.ts +0 -45
- package/src/cmd/example/sound.ts +0 -14
- package/src/cmd/example/spinner.ts +0 -44
- package/src/cmd/example/steps.ts +0 -66
- package/src/cmd/example/version.ts +0 -13
package/src/cmd/dev/index.ts
CHANGED
|
@@ -1,26 +1,49 @@
|
|
|
1
|
-
|
|
1
|
+
/** biome-ignore-all lint/style/useTemplate: its easier */
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import type { BuildMetadata } from '@agentuity/server';
|
|
3
4
|
import { resolve, join } from 'node:path';
|
|
4
5
|
import { bundle } from '../bundle/bundler';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
6
|
+
import { getBuildMetadata } from '../bundle/plugin';
|
|
7
|
+
import { existsSync, type FSWatcher, watch, statSync, readdirSync } from 'node:fs';
|
|
8
|
+
import {
|
|
9
|
+
getDefaultConfigDir,
|
|
10
|
+
loadDevelopmentProjectSDKKey,
|
|
11
|
+
saveProjectDir,
|
|
12
|
+
saveConfig,
|
|
13
|
+
} from '../../config';
|
|
14
|
+
import { type Config, createCommand } from '../../types';
|
|
8
15
|
import * as tui from '../../tui';
|
|
16
|
+
import { createAgentTemplates, createAPITemplates } from './templates';
|
|
17
|
+
import { generateEndpoint, type DevmodeResponse } from './api';
|
|
18
|
+
import { APIClient, getAPIBaseURL } from '../../api';
|
|
19
|
+
import { download } from './download';
|
|
9
20
|
|
|
10
21
|
export const command = createCommand({
|
|
11
22
|
name: 'dev',
|
|
12
23
|
description: 'Build and run the development server',
|
|
13
24
|
schema: {
|
|
14
25
|
options: z.object({
|
|
15
|
-
|
|
26
|
+
local: z.boolean().optional().describe('Turn on local services (instead of cloud)'),
|
|
27
|
+
public: z
|
|
28
|
+
.boolean()
|
|
29
|
+
.optional()
|
|
30
|
+
.default(!process.env.CI)
|
|
31
|
+
.describe('Turn on or off the public url'),
|
|
32
|
+
port: z
|
|
33
|
+
.number()
|
|
34
|
+
.min(1024) // should we allow a lower root port? probably not?
|
|
35
|
+
.max(65535)
|
|
36
|
+
.default(3500)
|
|
37
|
+
.describe('The TCP port to start the dev start'),
|
|
16
38
|
}),
|
|
17
39
|
},
|
|
18
|
-
|
|
40
|
+
optional: { auth: 'Continue without an account (local only)', project: true },
|
|
19
41
|
|
|
20
42
|
async handler(ctx) {
|
|
21
|
-
const { opts, logger, options } = ctx;
|
|
43
|
+
const { opts, logger, options, project, projectDir, auth } = ctx;
|
|
44
|
+
let { config } = ctx;
|
|
22
45
|
|
|
23
|
-
const rootDir =
|
|
46
|
+
const rootDir = projectDir;
|
|
24
47
|
const appTs = join(rootDir, 'app.ts');
|
|
25
48
|
const srcDir = join(rootDir, 'src');
|
|
26
49
|
const mustHaves = [join(rootDir, 'package.json'), appTs, srcDir];
|
|
@@ -42,13 +65,67 @@ export const command = createCommand({
|
|
|
42
65
|
|
|
43
66
|
await saveProjectDir(rootDir);
|
|
44
67
|
|
|
68
|
+
let devmode: DevmodeResponse | undefined;
|
|
69
|
+
let gravityBin: string | undefined;
|
|
70
|
+
|
|
71
|
+
if (auth && project && opts.public) {
|
|
72
|
+
// Only create apiClient if auth is available
|
|
73
|
+
const apiClient = new APIClient(getAPIBaseURL(config), logger, config);
|
|
74
|
+
const endpoint = await tui.spinner({
|
|
75
|
+
message: 'Connecting to Gravity',
|
|
76
|
+
callback: () => {
|
|
77
|
+
return generateEndpoint(apiClient, project.projectId, config?.devmode?.hostname);
|
|
78
|
+
},
|
|
79
|
+
clearOnSuccess: true,
|
|
80
|
+
});
|
|
81
|
+
const _config = { ...config } as Config;
|
|
82
|
+
_config.devmode = {
|
|
83
|
+
hostname: endpoint.hostname,
|
|
84
|
+
};
|
|
85
|
+
await saveConfig(_config);
|
|
86
|
+
config = _config;
|
|
87
|
+
devmode = endpoint;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (devmode) {
|
|
91
|
+
const configDir = getDefaultConfigDir();
|
|
92
|
+
const gravityDir = join(configDir, 'gravity');
|
|
93
|
+
let mustCheck = true;
|
|
94
|
+
if (
|
|
95
|
+
config?.gravity?.version &&
|
|
96
|
+
existsSync(join(gravityDir, config.gravity.version, 'gravity')) &&
|
|
97
|
+
config?.gravity?.checked
|
|
98
|
+
) {
|
|
99
|
+
if (Date.now() - config.gravity.checked < 3.6e6) {
|
|
100
|
+
mustCheck = false;
|
|
101
|
+
gravityBin = join(gravityDir, config.gravity.version, 'gravity');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (mustCheck) {
|
|
105
|
+
const res = await download(gravityDir);
|
|
106
|
+
gravityBin = res.filename;
|
|
107
|
+
const _config = { ...config } as Config;
|
|
108
|
+
_config.gravity = {
|
|
109
|
+
checked: Date.now(),
|
|
110
|
+
version: res.version,
|
|
111
|
+
};
|
|
112
|
+
await saveConfig(_config);
|
|
113
|
+
config = _config;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const canDoInput = !!(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
|
|
118
|
+
|
|
45
119
|
const devmodebody =
|
|
46
|
-
tui.muted('Local:
|
|
47
|
-
tui.link(
|
|
48
|
-
'\n
|
|
49
|
-
tui.muted('
|
|
50
|
-
tui.
|
|
51
|
-
|
|
120
|
+
tui.muted(tui.padRight('Local:', 10)) +
|
|
121
|
+
tui.link(`http://127.0.0.1:${opts.port}`) +
|
|
122
|
+
'\n' +
|
|
123
|
+
tui.muted(tui.padRight('Public:', 10)) +
|
|
124
|
+
(devmode?.hostname ? tui.link(`https://${devmode.hostname}`) : tui.warn('Disabled')) +
|
|
125
|
+
'\n' +
|
|
126
|
+
(canDoInput
|
|
127
|
+
? '\n' + tui.muted('Press ') + tui.bold('h') + tui.muted(' for keyboard shortcuts')
|
|
128
|
+
: '');
|
|
52
129
|
|
|
53
130
|
function showBanner() {
|
|
54
131
|
tui.banner('⨺ Agentuity DevMode', devmodebody, {
|
|
@@ -65,8 +142,39 @@ export const command = createCommand({
|
|
|
65
142
|
env.AGENTUITY_SDK_DEV_MODE = 'true';
|
|
66
143
|
env.AGENTUITY_ENV = 'development';
|
|
67
144
|
env.NODE_ENV = 'development';
|
|
68
|
-
env.PORT =
|
|
145
|
+
env.PORT = Number(opts.port).toFixed();
|
|
69
146
|
env.AGENTUITY_PORT = env.PORT;
|
|
147
|
+
if (options.logLevel !== undefined) env.AGENTUITY_LOG_LEVEL = options.logLevel;
|
|
148
|
+
// Pass through AGENTUITY_SDK_LOG_LEVEL for internal SDK logger
|
|
149
|
+
if (process.env.AGENTUITY_SDK_LOG_LEVEL) {
|
|
150
|
+
env.AGENTUITY_SDK_LOG_LEVEL = process.env.AGENTUITY_SDK_LOG_LEVEL;
|
|
151
|
+
}
|
|
152
|
+
env.AGENTUITY_FORCE_LOCAL_SERVICES = opts.local === true ? 'true' : 'false';
|
|
153
|
+
if (config?.overrides?.transport_url) {
|
|
154
|
+
env.AGENTUITY_TRANSPORT_URL = config.overrides.transport_url;
|
|
155
|
+
}
|
|
156
|
+
if (config?.overrides?.catalyst_url) {
|
|
157
|
+
env.AGENTUITY_CATALYST_URL = config.overrides.catalyst_url;
|
|
158
|
+
}
|
|
159
|
+
if (config?.overrides?.vector_url) {
|
|
160
|
+
env.AGENTUITY_VECTOR_URL = config.overrides.vector_url;
|
|
161
|
+
}
|
|
162
|
+
if (config?.overrides?.object_url) {
|
|
163
|
+
env.AGENTUITY_OBJECTSTORE_URL = config.overrides.object_url;
|
|
164
|
+
}
|
|
165
|
+
if (config?.overrides?.kv_url) {
|
|
166
|
+
env.AGENTUITY_KEYVALUE_URL = config.overrides.kv_url;
|
|
167
|
+
}
|
|
168
|
+
if (config?.overrides?.stream_url) {
|
|
169
|
+
env.AGENTUITY_STREAM_URL = config.overrides.stream_url;
|
|
170
|
+
}
|
|
171
|
+
if (project) {
|
|
172
|
+
env.AGENTUITY_CLOUD_ORG_ID = project.orgId;
|
|
173
|
+
env.AGENTUITY_CLOUD_PROJECT_ID = project.projectId;
|
|
174
|
+
}
|
|
175
|
+
if (!process.stdout.isTTY) {
|
|
176
|
+
env.NO_COLOR = '1';
|
|
177
|
+
}
|
|
70
178
|
|
|
71
179
|
const agentuityDir = resolve(rootDir, '.agentuity');
|
|
72
180
|
const appPath = resolve(agentuityDir, 'app.js');
|
|
@@ -84,7 +192,57 @@ export const command = createCommand({
|
|
|
84
192
|
let shuttingDownForRestart = false;
|
|
85
193
|
let pendingRestart = false;
|
|
86
194
|
let building = false;
|
|
87
|
-
let
|
|
195
|
+
let buildCompletedAt = 0;
|
|
196
|
+
const BUILD_COOLDOWN_MS = 500; // Ignore file changes for 500ms after build completes
|
|
197
|
+
let metadata: Partial<BuildMetadata> | undefined;
|
|
198
|
+
let showInitialReadyMessage = true;
|
|
199
|
+
let serverStartTime = 0;
|
|
200
|
+
let gravityClient: Bun.Subprocess | undefined;
|
|
201
|
+
|
|
202
|
+
if (gravityBin && devmode && project) {
|
|
203
|
+
const sdkKey = await loadDevelopmentProjectSDKKey(rootDir);
|
|
204
|
+
if (!sdkKey) {
|
|
205
|
+
tui.warning(`Couldn't find the AGENTUITY_SDK_KEY in ${rootDir} .env file`);
|
|
206
|
+
} else {
|
|
207
|
+
const gravityBinExists = await Bun.file(gravityBin).exists();
|
|
208
|
+
if (!gravityBinExists) {
|
|
209
|
+
logger.error(
|
|
210
|
+
`Gravity binary not found at ${gravityBin}, skipping gravity client startup`
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
try {
|
|
214
|
+
gravityClient = Bun.spawn(
|
|
215
|
+
[
|
|
216
|
+
gravityBin,
|
|
217
|
+
'--endpoint-id',
|
|
218
|
+
devmode.id,
|
|
219
|
+
'--port',
|
|
220
|
+
env.PORT,
|
|
221
|
+
'--url',
|
|
222
|
+
config?.overrides?.gravity_url ?? 'grpc://devmode.agentuity.com',
|
|
223
|
+
'--log-level',
|
|
224
|
+
'error',
|
|
225
|
+
],
|
|
226
|
+
{
|
|
227
|
+
cwd: rootDir,
|
|
228
|
+
stdout: 'inherit',
|
|
229
|
+
stderr: 'inherit',
|
|
230
|
+
stdin: 'ignore',
|
|
231
|
+
env: { ...env, AGENTUITY_SDK_KEY: sdkKey },
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
gravityClient.exited.then(() => {
|
|
235
|
+
logger.debug('gravity client exited');
|
|
236
|
+
});
|
|
237
|
+
} catch (err) {
|
|
238
|
+
logger.error(
|
|
239
|
+
'Failed to spawn gravity client: %s',
|
|
240
|
+
err instanceof Error ? err.message : String(err)
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
88
246
|
|
|
89
247
|
// Track restart timestamps to detect restart loops
|
|
90
248
|
const restartTimestamps: number[] = [];
|
|
@@ -96,7 +254,7 @@ export const command = createCommand({
|
|
|
96
254
|
restartTimestamps.push(now);
|
|
97
255
|
|
|
98
256
|
// Remove timestamps older than the time window
|
|
99
|
-
while (restartTimestamps.length > 0 && now - restartTimestamps[0]
|
|
257
|
+
while (restartTimestamps.length > 0 && now - restartTimestamps[0] > TIME_WINDOW_MS) {
|
|
100
258
|
restartTimestamps.shift();
|
|
101
259
|
}
|
|
102
260
|
|
|
@@ -160,7 +318,13 @@ export const command = createCommand({
|
|
|
160
318
|
};
|
|
161
319
|
|
|
162
320
|
// Handle signals to ensure entire process tree is killed
|
|
163
|
-
const cleanup = () => {
|
|
321
|
+
const cleanup = (exitCode = 0) => {
|
|
322
|
+
logger.trace('cleanup() called with exitCode=%d', exitCode);
|
|
323
|
+
if (gravityClient) {
|
|
324
|
+
logger.debug('calling kill on gravity client with pid: %d', gravityClient.pid);
|
|
325
|
+
gravityClient.kill('SIGINT');
|
|
326
|
+
gravityClient = undefined;
|
|
327
|
+
}
|
|
164
328
|
if (pid && running) {
|
|
165
329
|
kill();
|
|
166
330
|
}
|
|
@@ -168,11 +332,22 @@ export const command = createCommand({
|
|
|
168
332
|
watcher.close();
|
|
169
333
|
}
|
|
170
334
|
watchers.length = 0;
|
|
171
|
-
process.exit(
|
|
335
|
+
process.exit(exitCode);
|
|
172
336
|
};
|
|
173
337
|
|
|
174
338
|
process.on('SIGINT', cleanup);
|
|
175
339
|
process.on('SIGTERM', cleanup);
|
|
340
|
+
process.on('SIGQUIT', cleanup);
|
|
341
|
+
process.on('exit', () => {
|
|
342
|
+
// Synchronous cleanup on exit
|
|
343
|
+
if (gravityClient) {
|
|
344
|
+
try {
|
|
345
|
+
gravityClient.kill('SIGINT');
|
|
346
|
+
} catch {
|
|
347
|
+
// Ignore errors
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
176
351
|
|
|
177
352
|
async function restart() {
|
|
178
353
|
// Queue restart if already restarting
|
|
@@ -197,6 +372,7 @@ export const command = createCommand({
|
|
|
197
372
|
} else {
|
|
198
373
|
logger.trace('Initial server start');
|
|
199
374
|
}
|
|
375
|
+
logger.trace('Starting typecheck and build...');
|
|
200
376
|
await Promise.all([
|
|
201
377
|
tui.runCommand({
|
|
202
378
|
command: 'tsc',
|
|
@@ -209,35 +385,44 @@ export const command = createCommand({
|
|
|
209
385
|
}),
|
|
210
386
|
tui.spinner('Building project', async () => {
|
|
211
387
|
try {
|
|
388
|
+
logger.trace('Bundle starting...');
|
|
212
389
|
building = true;
|
|
213
390
|
await bundle({
|
|
214
391
|
rootDir,
|
|
215
392
|
dev: true,
|
|
216
393
|
});
|
|
217
394
|
building = false;
|
|
218
|
-
|
|
395
|
+
buildCompletedAt = Date.now();
|
|
396
|
+
logger.trace('Bundle completed successfully');
|
|
397
|
+
} catch (error) {
|
|
219
398
|
building = false;
|
|
399
|
+
logger.trace('Bundle failed: %s', error);
|
|
220
400
|
failure('Build failed');
|
|
221
401
|
}
|
|
222
402
|
}),
|
|
223
403
|
]);
|
|
404
|
+
logger.trace('Typecheck and build completed');
|
|
224
405
|
|
|
225
406
|
if (failed) {
|
|
407
|
+
logger.trace('Restart failed, returning early');
|
|
226
408
|
return;
|
|
227
409
|
}
|
|
228
410
|
|
|
411
|
+
logger.trace('Checking if app file exists: %s', appPath);
|
|
229
412
|
if (!existsSync(appPath)) {
|
|
413
|
+
logger.trace('App file not found: %s', appPath);
|
|
230
414
|
failure(`App file not found: ${appPath}`);
|
|
231
415
|
return;
|
|
232
416
|
}
|
|
417
|
+
logger.trace('App file exists, getting build metadata...');
|
|
233
418
|
|
|
234
|
-
metadata =
|
|
235
|
-
|
|
236
|
-
env.AGENTUITY_LOG_LEVEL = options.logLevel;
|
|
419
|
+
metadata = getBuildMetadata();
|
|
420
|
+
logger.trace('Build metadata retrieved');
|
|
237
421
|
|
|
238
422
|
logger.trace('Starting dev server: %s', appPath);
|
|
239
423
|
// Use shell to run in a process group for proper cleanup
|
|
240
424
|
// The 'exec' ensures the shell is replaced by the actual process
|
|
425
|
+
logger.trace('Spawning dev server process...');
|
|
241
426
|
devServer = Bun.spawn(['sh', '-c', `exec bun run "${appPath}"`], {
|
|
242
427
|
cwd: rootDir,
|
|
243
428
|
stdout: 'inherit',
|
|
@@ -246,33 +431,57 @@ export const command = createCommand({
|
|
|
246
431
|
env,
|
|
247
432
|
});
|
|
248
433
|
|
|
434
|
+
logger.trace('Dev server process spawned, setting up state...');
|
|
249
435
|
running = true;
|
|
250
436
|
failed = false;
|
|
251
437
|
pid = devServer.pid;
|
|
252
438
|
exitPromise = devServer.exited;
|
|
439
|
+
serverStartTime = Date.now();
|
|
253
440
|
logger.trace('Dev server started (pid: %d)', pid);
|
|
254
441
|
|
|
442
|
+
if (showInitialReadyMessage) {
|
|
443
|
+
showInitialReadyMessage = false;
|
|
444
|
+
logger.info('DevMode ready 🚀');
|
|
445
|
+
logger.trace('Initial ready message logged');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
logger.trace('Attaching exit handler to dev server process...');
|
|
255
449
|
// Attach non-blocking exit handler
|
|
256
450
|
exitPromise
|
|
257
451
|
.then((exitCode) => {
|
|
452
|
+
const runtime = Date.now() - serverStartTime;
|
|
258
453
|
logger.trace(
|
|
259
|
-
'Dev server exited with code %d (shuttingDownForRestart=%s)',
|
|
454
|
+
'Dev server exited with code %d (shuttingDownForRestart=%s, runtime=%dms)',
|
|
260
455
|
exitCode,
|
|
261
|
-
shuttingDownForRestart
|
|
456
|
+
shuttingDownForRestart,
|
|
457
|
+
runtime
|
|
262
458
|
);
|
|
263
459
|
running = false;
|
|
264
460
|
devServer = undefined;
|
|
265
461
|
exitPromise = undefined;
|
|
266
|
-
//
|
|
267
|
-
if (
|
|
462
|
+
// If server exited immediately after starting (< 2 seconds), treat as failure and restart
|
|
463
|
+
if (runtime < 2000 && !shuttingDownForRestart) {
|
|
464
|
+
logger.trace('Server exited too quickly, treating as failure and restarting');
|
|
465
|
+
failure('Server exited immediately after starting');
|
|
466
|
+
// Trigger a restart after a short delay
|
|
467
|
+
setTimeout(() => {
|
|
468
|
+
if (!running && !restarting) {
|
|
469
|
+
logger.trace('Triggering restart after quick exit');
|
|
470
|
+
restart();
|
|
471
|
+
}
|
|
472
|
+
}, 100);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
// Only exit the CLI if this is a clean exit AND not a restart AND server ran for a while
|
|
476
|
+
if (exitCode === 0 && !shuttingDownForRestart && runtime >= 2000) {
|
|
268
477
|
logger.trace('Clean exit, stopping CLI');
|
|
269
|
-
|
|
478
|
+
cleanup(exitCode);
|
|
270
479
|
}
|
|
271
480
|
// Non-zero exit codes are treated as restartable failures
|
|
272
481
|
// But if it's exit code 1 (common error exit), also exit the CLI
|
|
273
|
-
if (exitCode === 1 && !shuttingDownForRestart) {
|
|
482
|
+
if (exitCode === 1 && !shuttingDownForRestart && runtime >= 2000) {
|
|
274
483
|
logger.trace('Server exited with error code 1, stopping CLI');
|
|
275
|
-
|
|
484
|
+
cleanup(exitCode);
|
|
276
485
|
}
|
|
277
486
|
})
|
|
278
487
|
.catch((error) => {
|
|
@@ -293,14 +502,18 @@ export const command = createCommand({
|
|
|
293
502
|
}
|
|
294
503
|
});
|
|
295
504
|
} catch (error) {
|
|
505
|
+
logger.trace('Restart caught error: %s', error);
|
|
296
506
|
if (error instanceof Error) {
|
|
507
|
+
logger.trace('Error message: %s, stack: %s', error.message, error.stack);
|
|
297
508
|
failure(`Dev server failed: ${error.message}`);
|
|
298
509
|
} else {
|
|
510
|
+
logger.trace('Non-Error exception: %s', String(error));
|
|
299
511
|
failure('Dev server failed');
|
|
300
512
|
}
|
|
301
513
|
running = false;
|
|
302
514
|
devServer = undefined;
|
|
303
515
|
} finally {
|
|
516
|
+
logger.trace('Entering restart() finally block...');
|
|
304
517
|
const hadPendingRestart = pendingRestart;
|
|
305
518
|
restarting = false;
|
|
306
519
|
pendingRestart = false;
|
|
@@ -323,7 +536,7 @@ export const command = createCommand({
|
|
|
323
536
|
logger.trace('Initial restart completed, setting up watchers');
|
|
324
537
|
|
|
325
538
|
// Setup keyboard shortcuts (only if we have a TTY)
|
|
326
|
-
if (
|
|
539
|
+
if (canDoInput) {
|
|
327
540
|
logger.trace('Setting up keyboard shortcuts');
|
|
328
541
|
process.stdin.setRawMode(true);
|
|
329
542
|
process.stdin.resume();
|
|
@@ -385,6 +598,23 @@ export const command = createCommand({
|
|
|
385
598
|
});
|
|
386
599
|
|
|
387
600
|
logger.trace('✓ Keyboard shortcuts enabled');
|
|
601
|
+
} else {
|
|
602
|
+
if (process.stdin) {
|
|
603
|
+
// still need to monitor stdin in case we are pipeing into another process or file etc
|
|
604
|
+
if (typeof process.stdin.setRawMode === 'function') {
|
|
605
|
+
process.stdin.setRawMode(true);
|
|
606
|
+
}
|
|
607
|
+
process.stdin.resume();
|
|
608
|
+
process.stdin.on('data', (data) => {
|
|
609
|
+
const key = data.toString();
|
|
610
|
+
// Handle Ctrl+C
|
|
611
|
+
if (key === '\u0003') {
|
|
612
|
+
cleanup();
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
logger.trace('❌ Keyboard shortcuts disabled');
|
|
388
618
|
}
|
|
389
619
|
|
|
390
620
|
// Patterns to ignore (generated files that change during build)
|
|
@@ -393,14 +623,25 @@ export const command = createCommand({
|
|
|
393
623
|
/registry\.generated\.ts$/,
|
|
394
624
|
/types\.generated\.d\.ts$/,
|
|
395
625
|
/client\.generated\.js$/,
|
|
626
|
+
/\.tmp$/,
|
|
627
|
+
/\.tsbuildinfo$/,
|
|
628
|
+
/\.agentuity\//,
|
|
629
|
+
// Ignore temporary files created by sed (e.g., sedUprJj0)
|
|
630
|
+
/\/sed[A-Za-z0-9]+$/,
|
|
396
631
|
];
|
|
397
632
|
|
|
633
|
+
// Helper to check if a file is a temporary file created by sed
|
|
634
|
+
const isSedTempFile = (filePath: string): boolean => {
|
|
635
|
+
const basename = filePath.split('/').pop() || '';
|
|
636
|
+
return /^sed[A-Za-z0-9]+$/.test(basename);
|
|
637
|
+
};
|
|
638
|
+
|
|
398
639
|
logger.trace('Setting up file watchers for: %s', watches.join(', '));
|
|
399
640
|
for (const watchDir of watches) {
|
|
400
641
|
try {
|
|
401
642
|
logger.trace('Setting up watcher for %s', watchDir);
|
|
402
643
|
const watcher = watch(watchDir, { recursive: true }, (eventType, changedFile) => {
|
|
403
|
-
const absPath = changedFile ?
|
|
644
|
+
const absPath = changedFile ? resolve(watchDir, changedFile) : watchDir;
|
|
404
645
|
|
|
405
646
|
// Ignore file changes during active build to prevent loops
|
|
406
647
|
if (building) {
|
|
@@ -413,6 +654,18 @@ export const command = createCommand({
|
|
|
413
654
|
return;
|
|
414
655
|
}
|
|
415
656
|
|
|
657
|
+
// Ignore file changes immediately after build completes (cooldown period)
|
|
658
|
+
// This prevents restarts from build output files that are written asynchronously
|
|
659
|
+
if (buildCompletedAt > 0 && Date.now() - buildCompletedAt < BUILD_COOLDOWN_MS) {
|
|
660
|
+
logger.trace(
|
|
661
|
+
'File change ignored (build cooldown): %s (event: %s, file: %s)',
|
|
662
|
+
watchDir,
|
|
663
|
+
eventType,
|
|
664
|
+
changedFile
|
|
665
|
+
);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
416
669
|
// Ignore node_modules folder
|
|
417
670
|
if (absPath.includes('node_modules')) {
|
|
418
671
|
logger.trace(
|
|
@@ -425,7 +678,12 @@ export const command = createCommand({
|
|
|
425
678
|
}
|
|
426
679
|
|
|
427
680
|
// Ignore changes in .agentuity directory (build output)
|
|
428
|
-
|
|
681
|
+
// Check both relative path and normalized absolute path
|
|
682
|
+
const isInAgentuityDir =
|
|
683
|
+
(changedFile &&
|
|
684
|
+
(changedFile === '.agentuity' || changedFile.startsWith('.agentuity/'))) ||
|
|
685
|
+
resolve(absPath).startsWith(agentuityDir);
|
|
686
|
+
if (isInAgentuityDir) {
|
|
429
687
|
logger.trace(
|
|
430
688
|
'File change ignored (.agentuity dir): %s (event: %s, file: %s)',
|
|
431
689
|
watchDir,
|
|
@@ -446,8 +704,61 @@ export const command = createCommand({
|
|
|
446
704
|
return;
|
|
447
705
|
}
|
|
448
706
|
|
|
707
|
+
// Check for .tmp file renames that replace watched files (BEFORE ignoring)
|
|
708
|
+
// This handles cases like sed -i.tmp where agent.ts.tmp is renamed to agent.ts
|
|
709
|
+
if (eventType === 'rename' && changedFile && changedFile.endsWith('.tmp')) {
|
|
710
|
+
const targetFile = changedFile.slice(0, -4); // Remove .tmp suffix
|
|
711
|
+
const targetAbsPath = resolve(watchDir, targetFile);
|
|
712
|
+
|
|
713
|
+
// Only trigger restart for source files (ts, tsx, js, jsx, etc.)
|
|
714
|
+
const isSourceFile = /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(targetFile);
|
|
715
|
+
|
|
716
|
+
// Check if target file exists and is not in ignored directories
|
|
717
|
+
const targetExists = existsSync(targetAbsPath);
|
|
718
|
+
const inNodeModules = targetAbsPath.includes('node_modules');
|
|
719
|
+
const inAgentuityDir =
|
|
720
|
+
(targetFile &&
|
|
721
|
+
(targetFile === '.agentuity' || targetFile.startsWith('.agentuity/'))) ||
|
|
722
|
+
resolve(targetAbsPath).startsWith(agentuityDir);
|
|
723
|
+
let isDirectory = false;
|
|
724
|
+
if (targetExists) {
|
|
725
|
+
try {
|
|
726
|
+
isDirectory = statSync(targetAbsPath).isDirectory();
|
|
727
|
+
} catch (err) {
|
|
728
|
+
logger.trace('Failed to stat target file: %s', err);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (
|
|
733
|
+
isSourceFile &&
|
|
734
|
+
targetExists &&
|
|
735
|
+
!inNodeModules &&
|
|
736
|
+
!inAgentuityDir &&
|
|
737
|
+
!isDirectory
|
|
738
|
+
) {
|
|
739
|
+
logger.trace(
|
|
740
|
+
'File change detected (temp file rename): %s -> %s',
|
|
741
|
+
absPath,
|
|
742
|
+
targetAbsPath
|
|
743
|
+
);
|
|
744
|
+
restart();
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
449
749
|
// Ignore generated files to prevent restart loops
|
|
450
750
|
if (changedFile) {
|
|
751
|
+
// Check for sed temporary files
|
|
752
|
+
if (isSedTempFile(changedFile)) {
|
|
753
|
+
logger.trace(
|
|
754
|
+
'File change ignored (sed temp file): %s (event: %s, file: %s)',
|
|
755
|
+
watchDir,
|
|
756
|
+
eventType,
|
|
757
|
+
changedFile
|
|
758
|
+
);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
// Check other ignore patterns
|
|
451
762
|
for (const pattern of ignorePatterns) {
|
|
452
763
|
if (pattern.test(changedFile)) {
|
|
453
764
|
logger.trace(
|
|
@@ -461,6 +772,21 @@ export const command = createCommand({
|
|
|
461
772
|
}
|
|
462
773
|
}
|
|
463
774
|
|
|
775
|
+
if (
|
|
776
|
+
eventType === 'rename' &&
|
|
777
|
+
existsSync(absPath) &&
|
|
778
|
+
statSync(absPath).isDirectory() &&
|
|
779
|
+
readdirSync(absPath).length === 0
|
|
780
|
+
) {
|
|
781
|
+
if (changedFile?.startsWith('src/agents/')) {
|
|
782
|
+
logger.debug('agent directory created: %s', changedFile);
|
|
783
|
+
createAgentTemplates(absPath);
|
|
784
|
+
} else if (changedFile?.startsWith('src/apis/')) {
|
|
785
|
+
logger.debug('api directory created: %s', changedFile);
|
|
786
|
+
createAPITemplates(absPath);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
464
790
|
logger.trace(
|
|
465
791
|
'File change detected: %s (event: %s, file: %s)',
|
|
466
792
|
absPath,
|
|
@@ -478,6 +804,6 @@ export const command = createCommand({
|
|
|
478
804
|
logger.debug('Dev server watching for changes');
|
|
479
805
|
|
|
480
806
|
// Keep the handler alive indefinitely
|
|
481
|
-
await new Promise(() => {});
|
|
807
|
+
await new Promise(() => {}).catch(() => cleanup());
|
|
482
808
|
},
|
|
483
809
|
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* eslint-disable no-control-regex */
|
|
2
|
+
import { writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const newAgentTemplate = (name: string) => `import { createAgent } from '@agentuity/runtime';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
const agent = createAgent({
|
|
9
|
+
metadata: {
|
|
10
|
+
name: '${name}',
|
|
11
|
+
description: 'Add my agent description here',
|
|
12
|
+
},
|
|
13
|
+
schema: {
|
|
14
|
+
input: z.string(),
|
|
15
|
+
output: z.string(),
|
|
16
|
+
},
|
|
17
|
+
handler: async (_c, input) => {
|
|
18
|
+
// TODO: add your code here
|
|
19
|
+
return input;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export default agent;
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const newAgentRouteTemplate = (name: string) => `import { createRouter } from '@agentuity/runtime';
|
|
27
|
+
|
|
28
|
+
const router = createRouter();
|
|
29
|
+
|
|
30
|
+
router.get('/', async (c) => {
|
|
31
|
+
// TODO: add your code here
|
|
32
|
+
const output = await c.agent.${name}.run('hello world');
|
|
33
|
+
return c.text(output);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export default router;
|
|
37
|
+
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const newAPIRouteTemplate = (_name: string) => `import { createRouter } from '@agentuity/runtime';
|
|
41
|
+
|
|
42
|
+
const router = createRouter();
|
|
43
|
+
|
|
44
|
+
router.get('/', async (c) => {
|
|
45
|
+
// TODO: add your code here
|
|
46
|
+
return c.text('Hello');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export default router;
|
|
50
|
+
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const invalidDirRegex = /[<>:"/\\|?*]/;
|
|
54
|
+
|
|
55
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: checking for invalid control characters in directory names
|
|
56
|
+
const invalidControlChars = /[\u0000-\u001F]/;
|
|
57
|
+
const reservedWindowsNames = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
|
|
58
|
+
const invalidTrailing = /[. ]$/;
|
|
59
|
+
|
|
60
|
+
function isValidDirectoryName(name: string): boolean {
|
|
61
|
+
return (
|
|
62
|
+
!invalidDirRegex.test(name) &&
|
|
63
|
+
!invalidControlChars.test(name) &&
|
|
64
|
+
!reservedWindowsNames.test(name) &&
|
|
65
|
+
!invalidTrailing.test(name)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createAgentTemplates(dir: string) {
|
|
70
|
+
const name = basename(dir);
|
|
71
|
+
if (!isValidDirectoryName(name)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
writeFileSync(join(dir, 'agent.ts'), newAgentTemplate(name));
|
|
75
|
+
writeFileSync(join(dir, 'route.ts'), newAgentRouteTemplate(name));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createAPITemplates(dir: string) {
|
|
79
|
+
const name = basename(dir);
|
|
80
|
+
if (!isValidDirectoryName(name)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
writeFileSync(join(dir, 'route.ts'), newAPIRouteTemplate(name));
|
|
84
|
+
}
|