gemini-coder 0.1.1 → 0.1.3
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/README.md +19 -1
- package/dist/cli.js +3 -3
- package/dist/core/ai.js +9 -30
- package/dist/core/auth.js +78 -0
- package/dist/core/orchestrator.js +26 -4
- package/dist/core/remote-client.js +49 -0
- package/dist/server.js +60 -0
- package/dist/ui/terminal.js +32 -3
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -24,7 +24,25 @@ npm start
|
|
|
24
24
|
|
|
25
25
|
## Authentication
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Server-backed auth is supported. Run the Gemini Coder server on a trusted machine that has access to Google credentials, then point client machines at it with `GEMINI_CODER_SERVER_URL`.
|
|
28
|
+
|
|
29
|
+
Server example:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export GEMINI_CODER_SERVER_TOKEN=your-shared-secret
|
|
33
|
+
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/gemini.json
|
|
34
|
+
npm run serve
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Client example:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
export GEMINI_CODER_SERVER_URL=http://your-server:8787
|
|
41
|
+
export GEMINI_CODER_SERVER_TOKEN=your-shared-secret
|
|
42
|
+
gemini-coder chat
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For local source runs, the CLI still supports a `gemini.json` file in the workspace root as a fallback.
|
|
28
46
|
|
|
29
47
|
## Notes
|
|
30
48
|
|
package/dist/cli.js
CHANGED
|
@@ -10,15 +10,15 @@ const sessionManager = new SessionManager();
|
|
|
10
10
|
program
|
|
11
11
|
.name('gemini')
|
|
12
12
|
.description('Gemini Coder Pro CLI - Advanced AI Coding Agent')
|
|
13
|
-
.version('0.1.
|
|
13
|
+
.version('0.1.3');
|
|
14
14
|
program
|
|
15
15
|
.command('chat', { isDefault: true })
|
|
16
16
|
.description('Start an interactive chat session')
|
|
17
17
|
.option('-p, --prompt <query>', 'Start with an initial prompt')
|
|
18
18
|
.option('-c, --continue', 'Continue the most recent session')
|
|
19
|
-
.option('-m, --model <name>', 'Specify the model to use', 'gemini-3.
|
|
19
|
+
.option('-m, --model <name>', 'Specify the model to use', 'gemini-3.5-flash')
|
|
20
20
|
.action(async (options) => {
|
|
21
|
-
printBootScreen('Gemini Coder Pro', 'v0.1.
|
|
21
|
+
printBootScreen('Gemini Coder Pro', 'v0.1.3');
|
|
22
22
|
let session;
|
|
23
23
|
if (options.continue) {
|
|
24
24
|
session = await sessionManager.getLatestSession();
|
package/dist/core/ai.js
CHANGED
|
@@ -1,33 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import path from 'path';
|
|
1
|
+
import { createGoogleClient } from './auth.js';
|
|
2
|
+
import { createRemoteClient } from './remote-client.js';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
let currentPath = startPath;
|
|
7
|
-
while (currentPath !== path.parse(currentPath).root) {
|
|
8
|
-
if (fs.existsSync(path.join(currentPath, 'package.json'))) {
|
|
9
|
-
return currentPath;
|
|
10
|
-
}
|
|
11
|
-
currentPath = path.dirname(currentPath);
|
|
12
|
-
}
|
|
13
|
-
throw new Error('Could not find project root containing package.json');
|
|
14
|
-
}
|
|
4
|
+
import path from 'path';
|
|
15
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
6
|
const __dirname = path.dirname(__filename);
|
|
17
|
-
const projectRoot =
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const config = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'));
|
|
24
|
-
const location = config.location ?? process.env.GOOGLE_CLOUD_LOCATION ?? 'global';
|
|
25
|
-
export const client = new GoogleGenAI({
|
|
26
|
-
project: config.project_id,
|
|
27
|
-
location,
|
|
28
|
-
vertexai: true,
|
|
29
|
-
googleAuthOptions: {
|
|
30
|
-
keyFile: credentialsPath,
|
|
31
|
-
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
|
|
32
|
-
}
|
|
33
|
-
});
|
|
7
|
+
const projectRoot = path.resolve(__dirname, '..', '..');
|
|
8
|
+
const serverUrl = process.env.GEMINI_CODER_SERVER_URL;
|
|
9
|
+
const serverToken = process.env.GEMINI_CODER_SERVER_TOKEN;
|
|
10
|
+
export const client = serverUrl
|
|
11
|
+
? createRemoteClient(serverUrl, serverToken)
|
|
12
|
+
: createGoogleClient(projectRoot);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { GoogleGenAI } from '@google/genai';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
function findFileUpward(startPath, fileName) {
|
|
5
|
+
let currentPath = startPath;
|
|
6
|
+
while (currentPath !== path.parse(currentPath).root) {
|
|
7
|
+
const candidatePath = path.join(currentPath, fileName);
|
|
8
|
+
if (fs.existsSync(candidatePath)) {
|
|
9
|
+
return candidatePath;
|
|
10
|
+
}
|
|
11
|
+
currentPath = path.dirname(currentPath);
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function loadCredentialsFromFile(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
const config = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
18
|
+
return {
|
|
19
|
+
project: config.project_id,
|
|
20
|
+
location: config.location,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function resolveAuthConfig(baseDir) {
|
|
28
|
+
const envCredentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
29
|
+
const envProject = process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCLOUD_PROJECT ?? process.env.GOOGLE_PROJECT_ID;
|
|
30
|
+
const envLocation = process.env.GOOGLE_CLOUD_LOCATION;
|
|
31
|
+
if (envCredentialsPath && fs.existsSync(envCredentialsPath)) {
|
|
32
|
+
const fromEnvFile = loadCredentialsFromFile(envCredentialsPath);
|
|
33
|
+
const project = envProject ?? fromEnvFile.project;
|
|
34
|
+
if (!project) {
|
|
35
|
+
throw new Error('GOOGLE_APPLICATION_CREDENTIALS is set, but the file does not contain project_id and no Google Cloud project env var was provided.');
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
project,
|
|
39
|
+
location: envLocation ?? fromEnvFile.location ?? 'global',
|
|
40
|
+
keyFile: envCredentialsPath,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const localCredentialsPath = findFileUpward(baseDir, 'gemini.json');
|
|
44
|
+
if (localCredentialsPath) {
|
|
45
|
+
const fromLocalFile = loadCredentialsFromFile(localCredentialsPath);
|
|
46
|
+
const project = envProject ?? fromLocalFile.project;
|
|
47
|
+
if (!project) {
|
|
48
|
+
throw new Error(`Found ${localCredentialsPath}, but it does not contain project_id and no Google Cloud project env var was provided.`);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
project,
|
|
52
|
+
location: envLocation ?? fromLocalFile.location ?? 'global',
|
|
53
|
+
keyFile: localCredentialsPath,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (envProject) {
|
|
57
|
+
return {
|
|
58
|
+
project: envProject,
|
|
59
|
+
location: envLocation ?? 'global',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
throw new Error('No auth source found. Set GOOGLE_APPLICATION_CREDENTIALS, or place gemini.json in the workspace/server root, or configure GOOGLE_CLOUD_PROJECT for ADC.');
|
|
63
|
+
}
|
|
64
|
+
export function createGoogleClient(baseDir) {
|
|
65
|
+
const auth = resolveAuthConfig(baseDir);
|
|
66
|
+
const googleAuthOptions = auth.keyFile
|
|
67
|
+
? {
|
|
68
|
+
keyFile: auth.keyFile,
|
|
69
|
+
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
|
|
70
|
+
}
|
|
71
|
+
: undefined;
|
|
72
|
+
return new GoogleGenAI({
|
|
73
|
+
project: auth.project,
|
|
74
|
+
location: auth.location,
|
|
75
|
+
vertexai: true,
|
|
76
|
+
...(googleAuthOptions ? { googleAuthOptions } : {}),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -110,7 +110,7 @@ export class Orchestrator {
|
|
|
110
110
|
mode = OrchestratorMode.NORMAL;
|
|
111
111
|
model;
|
|
112
112
|
appliedEdits = [];
|
|
113
|
-
constructor(session, sessionManager, model = 'gemini-3.
|
|
113
|
+
constructor(session, sessionManager, model = 'gemini-3.5-flash') {
|
|
114
114
|
this.session = session;
|
|
115
115
|
this.sessionManager = sessionManager;
|
|
116
116
|
this.model = model;
|
|
@@ -174,7 +174,19 @@ export class Orchestrator {
|
|
|
174
174
|
}
|
|
175
175
|
async chat() {
|
|
176
176
|
while (true) {
|
|
177
|
-
|
|
177
|
+
let userInput = '';
|
|
178
|
+
try {
|
|
179
|
+
userInput = await rl.question(getPromptText(this.mode));
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
if (error?.code === 'ABORT_ERR' ||
|
|
183
|
+
error?.name === 'AbortError' ||
|
|
184
|
+
error?.code === 'ERR_USE_AFTER_CLOSE') {
|
|
185
|
+
console.log(chalk.yellow('\nSession interrupted. Exiting...'));
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
178
190
|
if (userInput.trim() === '')
|
|
179
191
|
continue;
|
|
180
192
|
if (userInput.toLowerCase() === 'exit')
|
|
@@ -189,6 +201,7 @@ export class Orchestrator {
|
|
|
189
201
|
this.session.updatedAt = new Date().toISOString();
|
|
190
202
|
await this.sessionManager.saveSession(this.session);
|
|
191
203
|
}
|
|
204
|
+
rl.close();
|
|
192
205
|
}
|
|
193
206
|
async handleSlashCommand(command) {
|
|
194
207
|
const [cmd, ...args] = command.slice(1).split(' ');
|
|
@@ -260,12 +273,21 @@ export class Orchestrator {
|
|
|
260
273
|
contents,
|
|
261
274
|
config: {
|
|
262
275
|
tools: [{ functionDeclarations }],
|
|
276
|
+
maxOutputTokens: 8192,
|
|
263
277
|
}
|
|
264
278
|
}));
|
|
265
279
|
}
|
|
266
280
|
catch (err) {
|
|
267
281
|
spinner.fail(chalk.red('API Error'));
|
|
268
|
-
|
|
282
|
+
const message = String(err?.message || err);
|
|
283
|
+
const isOauthDnsIssue = message.includes('oauth2.googleapis.com') ||
|
|
284
|
+
message.includes('ENOTFOUND') ||
|
|
285
|
+
message.includes('EAI_AGAIN') ||
|
|
286
|
+
message.includes('ECONNRESET');
|
|
287
|
+
console.error(chalk.red(`\n[API Error]: ${message}`));
|
|
288
|
+
if (isOauthDnsIssue) {
|
|
289
|
+
console.error(chalk.yellow('\n[Hint]: The CLI could not reach oauth2.googleapis.com to exchange the service-account token. Check network access, DNS, firewall, or proxy settings on this machine, then retry.'));
|
|
290
|
+
}
|
|
269
291
|
return;
|
|
270
292
|
}
|
|
271
293
|
let responseParts = [];
|
|
@@ -283,7 +305,7 @@ export class Orchestrator {
|
|
|
283
305
|
if (parts.length > 0) {
|
|
284
306
|
responseParts.push(...parts);
|
|
285
307
|
const textParts = parts
|
|
286
|
-
.map(part => ('text' in part ? part.text : undefined))
|
|
308
|
+
.map((part) => ('text' in part ? part.text : undefined))
|
|
287
309
|
.filter((text) => typeof text === 'string' && text.length > 0)
|
|
288
310
|
.join('');
|
|
289
311
|
if (textParts) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
async function* readNdjsonStream(response) {
|
|
2
|
+
if (!response.body) {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
const reader = response.body.getReader();
|
|
6
|
+
const decoder = new TextDecoder();
|
|
7
|
+
let buffer = '';
|
|
8
|
+
while (true) {
|
|
9
|
+
const { value, done } = await reader.read();
|
|
10
|
+
if (done)
|
|
11
|
+
break;
|
|
12
|
+
buffer += decoder.decode(value, { stream: true });
|
|
13
|
+
let lineBreakIndex = buffer.indexOf('\n');
|
|
14
|
+
while (lineBreakIndex !== -1) {
|
|
15
|
+
const line = buffer.slice(0, lineBreakIndex).trim();
|
|
16
|
+
buffer = buffer.slice(lineBreakIndex + 1);
|
|
17
|
+
if (line.length > 0) {
|
|
18
|
+
yield JSON.parse(line);
|
|
19
|
+
}
|
|
20
|
+
lineBreakIndex = buffer.indexOf('\n');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const remaining = buffer.trim();
|
|
24
|
+
if (remaining.length > 0) {
|
|
25
|
+
yield JSON.parse(remaining);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function createRemoteClient(serverUrl, token) {
|
|
29
|
+
const normalizedServerUrl = serverUrl.replace(/\/+$/, '');
|
|
30
|
+
return {
|
|
31
|
+
models: {
|
|
32
|
+
generateContentStream: async (request) => {
|
|
33
|
+
const response = await fetch(`${normalizedServerUrl}/v1/generateContentStream`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify(request),
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const errorText = await response.text();
|
|
43
|
+
throw new Error(`Remote auth server error (${response.status}): ${errorText}`);
|
|
44
|
+
}
|
|
45
|
+
return readNdjsonStream(response);
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { URL } from 'url';
|
|
3
|
+
import { createGoogleClient } from './core/auth.js';
|
|
4
|
+
const serverPort = Number(process.env.GEMINI_CODER_SERVER_PORT ?? 8787);
|
|
5
|
+
const serverHost = process.env.GEMINI_CODER_SERVER_HOST ?? '127.0.0.1';
|
|
6
|
+
const requiredToken = process.env.GEMINI_CODER_SERVER_TOKEN?.trim();
|
|
7
|
+
const serverRoot = process.cwd();
|
|
8
|
+
const aiClient = createGoogleClient(serverRoot);
|
|
9
|
+
function sendJson(response, statusCode, payload) {
|
|
10
|
+
response.statusCode = statusCode;
|
|
11
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
12
|
+
response.end(JSON.stringify(payload));
|
|
13
|
+
}
|
|
14
|
+
function readRequestBody(request) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const chunks = [];
|
|
17
|
+
request.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
18
|
+
request.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
19
|
+
request.on('error', reject);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const server = createServer(async (request, response) => {
|
|
23
|
+
try {
|
|
24
|
+
const requestUrl = new URL(request.url ?? '/', `http://${request.headers.host ?? `${serverHost}:${serverPort}`}`);
|
|
25
|
+
if (requestUrl.pathname === '/health') {
|
|
26
|
+
sendJson(response, 200, { ok: true });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (requestUrl.pathname !== '/v1/generateContentStream' || request.method !== 'POST') {
|
|
30
|
+
sendJson(response, 404, { error: 'Not found' });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (requiredToken) {
|
|
34
|
+
const authHeader = request.headers.authorization ?? '';
|
|
35
|
+
if (authHeader !== `Bearer ${requiredToken}`) {
|
|
36
|
+
sendJson(response, 401, { error: 'Unauthorized' });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const rawBody = await readRequestBody(request);
|
|
41
|
+
const payload = JSON.parse(rawBody || '{}');
|
|
42
|
+
response.statusCode = 200;
|
|
43
|
+
response.setHeader('Content-Type', 'application/x-ndjson; charset=utf-8');
|
|
44
|
+
response.setHeader('Cache-Control', 'no-cache');
|
|
45
|
+
response.setHeader('Connection', 'keep-alive');
|
|
46
|
+
const stream = await aiClient.models.generateContentStream(payload);
|
|
47
|
+
for await (const chunk of stream) {
|
|
48
|
+
response.write(`${JSON.stringify(chunk)}\n`);
|
|
49
|
+
}
|
|
50
|
+
response.end();
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
sendJson(response, 500, {
|
|
54
|
+
error: error?.message || String(error),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
server.listen(serverPort, serverHost, () => {
|
|
59
|
+
console.log(`Gemini Coder server listening on http://${serverHost}:${serverPort}`);
|
|
60
|
+
});
|
package/dist/ui/terminal.js
CHANGED
|
@@ -10,6 +10,28 @@ export const QUICK_ACTIONS = [
|
|
|
10
10
|
function getBoxWidth() {
|
|
11
11
|
return Math.max(60, Math.min(process.stdout.columns || 80, 96));
|
|
12
12
|
}
|
|
13
|
+
function wrapContent(content, maxWidth) {
|
|
14
|
+
const rawLines = content.split(/\r?\n/);
|
|
15
|
+
const wrapped = [];
|
|
16
|
+
for (const rawLine of rawLines) {
|
|
17
|
+
const line = rawLine.replace(/\t/g, ' ');
|
|
18
|
+
if (line.length === 0) {
|
|
19
|
+
wrapped.push('');
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
let remaining = line;
|
|
23
|
+
while (remaining.length > maxWidth) {
|
|
24
|
+
let breakAt = remaining.lastIndexOf(' ', maxWidth);
|
|
25
|
+
if (breakAt <= 0) {
|
|
26
|
+
breakAt = maxWidth;
|
|
27
|
+
}
|
|
28
|
+
wrapped.push(remaining.slice(0, breakAt).trimEnd());
|
|
29
|
+
remaining = remaining.slice(breakAt).trimStart();
|
|
30
|
+
}
|
|
31
|
+
wrapped.push(remaining);
|
|
32
|
+
}
|
|
33
|
+
return wrapped;
|
|
34
|
+
}
|
|
13
35
|
function boxLine(content, width) {
|
|
14
36
|
const innerWidth = width - 2;
|
|
15
37
|
const text = content.length > innerWidth ? content.slice(0, innerWidth) : content;
|
|
@@ -24,8 +46,12 @@ export function printBox(title, lines, accent = 'blue') {
|
|
|
24
46
|
const left = '─'.repeat(Math.floor(titlePad / 2));
|
|
25
47
|
const right = '─'.repeat(titlePad - left.length);
|
|
26
48
|
console.log(borderColor(`┌${left}${titleText}${right}┐`));
|
|
49
|
+
const contentWidth = width - 3;
|
|
27
50
|
for (const line of lines) {
|
|
28
|
-
|
|
51
|
+
const wrappedLines = wrapContent(line, Math.max(1, contentWidth));
|
|
52
|
+
for (const wrappedLine of wrappedLines) {
|
|
53
|
+
console.log(borderColor(boxLine(` ${wrappedLine}`, width)));
|
|
54
|
+
}
|
|
29
55
|
}
|
|
30
56
|
console.log(borderColor(`└${'─'.repeat(width - 2)}┘`));
|
|
31
57
|
}
|
|
@@ -45,8 +71,11 @@ export function getPromptText(mode) {
|
|
|
45
71
|
return chalk.bold(`${promptColor('>')} `);
|
|
46
72
|
}
|
|
47
73
|
export function printAssistantResponse(text) {
|
|
48
|
-
const
|
|
49
|
-
|
|
74
|
+
const width = Math.max(40, Math.min(process.stdout.columns || 80, 80));
|
|
75
|
+
const line = '─'.repeat(width - 11);
|
|
76
|
+
console.log(chalk.blue.bold(`\n┌── Gemini ${line}`));
|
|
77
|
+
console.log(text);
|
|
78
|
+
console.log(chalk.blue.bold(`└──${'─'.repeat(width - 3)}`));
|
|
50
79
|
}
|
|
51
80
|
export function printModeChange(mode) {
|
|
52
81
|
const label = mode === OrchestratorMode.PLAN ? chalk.magenta('Plan mode') : chalk.green('Normal mode');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gemini-coder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc -p tsconfig.json",
|
|
15
15
|
"dev": "tsx src/cli.ts chat",
|
|
16
|
+
"dev:server": "tsx src/server.ts",
|
|
16
17
|
"start": "node dist/cli.js chat",
|
|
18
|
+
"serve": "node dist/server.js",
|
|
17
19
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
18
20
|
},
|
|
19
21
|
"dependencies": {
|