@townco/cli 0.1.54 → 0.1.56
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 +2 -0
- package/dist/commands/create.js +10 -0
- package/dist/commands/login.js +1 -1
- package/dist/commands/run.js +70 -29
- package/dist/commands/whoami.js +1 -1
- package/dist/index.js +214 -116
- package/dist/lib/auth-fetch.d.ts +1 -1
- package/dist/lib/auth-fetch.js +1 -1
- package/package.json +20 -8
- package/dist/commands/delete.d.ts +0 -1
- package/dist/commands/delete.js +0 -60
- package/dist/commands/edit.d.ts +0 -1
- package/dist/commands/edit.js +0 -92
- package/dist/commands/list.d.ts +0 -1
- package/dist/commands/list.js +0 -55
- package/dist/commands/mcp-add.d.ts +0 -14
- package/dist/commands/mcp-add.js +0 -494
- package/dist/commands/mcp-list.d.ts +0 -3
- package/dist/commands/mcp-list.js +0 -63
- package/dist/commands/mcp-remove.d.ts +0 -3
- package/dist/commands/mcp-remove.js +0 -120
- package/dist/commands/tool-add.d.ts +0 -6
- package/dist/commands/tool-add.js +0 -349
- package/dist/commands/tool-list.d.ts +0 -3
- package/dist/commands/tool-list.js +0 -61
- package/dist/commands/tool-register.d.ts +0 -7
- package/dist/commands/tool-register.js +0 -291
- package/dist/commands/tool-remove.d.ts +0 -3
- package/dist/commands/tool-remove.js +0 -202
- package/dist/components/MergedLogsPane.d.ts +0 -11
- package/dist/components/MergedLogsPane.js +0 -205
- package/dist/lib/auth-storage.d.ts +0 -38
- package/dist/lib/auth-storage.js +0 -89
- package/dist/lib/mcp-storage.d.ts +0 -32
- package/dist/lib/mcp-storage.js +0 -111
package/README.md
CHANGED
package/dist/commands/create.js
CHANGED
|
@@ -48,6 +48,16 @@ const AVAILABLE_TOOLS = [
|
|
|
48
48
|
value: "filesystem",
|
|
49
49
|
description: "Read, write, and search files in the project directory",
|
|
50
50
|
},
|
|
51
|
+
{
|
|
52
|
+
label: "Generate Image",
|
|
53
|
+
value: "generate_image",
|
|
54
|
+
description: "Generate images using Google's Gemini model (requires GEMINI_API_KEY)",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: "Browser",
|
|
58
|
+
value: "browser",
|
|
59
|
+
description: "Cloud browser automation (requires KERNEL_API_KEY)",
|
|
60
|
+
},
|
|
51
61
|
];
|
|
52
62
|
function NameInput({ nameInput, setNameInput, onSubmit }) {
|
|
53
63
|
useInput((_input, key) => {
|
package/dist/commands/login.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { clearAuthCredentials, getAuthFilePath, getShedUrl, loadAuthCredentials, saveAuthCredentials, } from "@townco/core/auth";
|
|
1
2
|
import inquirer from "inquirer";
|
|
2
|
-
import { clearAuthCredentials, getAuthFilePath, getShedUrl, loadAuthCredentials, saveAuthCredentials, } from "../lib/auth-storage.js";
|
|
3
3
|
// ============================================================================
|
|
4
4
|
// Command Implementation
|
|
5
5
|
// ============================================================================
|
package/dist/commands/run.js
CHANGED
|
@@ -159,27 +159,54 @@ export async function runCommand(options) {
|
|
|
159
159
|
}
|
|
160
160
|
// Load environment variables from project .env
|
|
161
161
|
const configEnvVars = await loadEnvVars(projectRoot);
|
|
162
|
+
// Resolve agent path to load agent definition
|
|
163
|
+
const agentPath = join(projectRoot, "agents", name);
|
|
164
|
+
// Load agent definition to get displayName by parsing the file
|
|
165
|
+
let agentDisplayName = name; // Fallback to agent directory name
|
|
166
|
+
try {
|
|
167
|
+
const agentIndexPath = join(agentPath, "index.ts");
|
|
168
|
+
const content = await readFile(agentIndexPath, "utf-8");
|
|
169
|
+
// Match displayName in the agent definition object
|
|
170
|
+
// Looking for patterns like: displayName: "Researcher" or displayName: 'Researcher'
|
|
171
|
+
const match = content.match(/displayName:\s*["']([^"']+)["']/);
|
|
172
|
+
if (match?.[1]) {
|
|
173
|
+
agentDisplayName = match[1];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
// If we can't read the agent definition, just use the directory name
|
|
178
|
+
// Silently fail - the directory name is a reasonable fallback
|
|
179
|
+
}
|
|
162
180
|
// Start the debugger server as subprocess (OTLP collector + UI)
|
|
163
181
|
const debuggerPkgPath = require.resolve("@townco/debugger/package.json");
|
|
164
182
|
const debuggerDir = dirname(debuggerPkgPath);
|
|
165
183
|
const debuggerProcess = spawn("bun", ["src/index.ts"], {
|
|
166
184
|
cwd: debuggerDir,
|
|
167
185
|
stdio: "inherit",
|
|
186
|
+
detached: true,
|
|
168
187
|
env: {
|
|
169
188
|
...process.env,
|
|
170
189
|
DB_PATH: join(projectRoot, ".traces.db"),
|
|
190
|
+
AGENT_NAME: agentDisplayName,
|
|
171
191
|
},
|
|
172
192
|
});
|
|
173
193
|
console.log(`Debugger UI: http://localhost:4000`);
|
|
174
194
|
// Cleanup debugger process on exit
|
|
195
|
+
let isDebuggerCleaningUp = false;
|
|
175
196
|
const cleanupDebugger = () => {
|
|
176
|
-
|
|
197
|
+
if (isDebuggerCleaningUp)
|
|
198
|
+
return;
|
|
199
|
+
isDebuggerCleaningUp = true;
|
|
200
|
+
if (debuggerProcess.pid) {
|
|
201
|
+
try {
|
|
202
|
+
process.kill(-debuggerProcess.pid, "SIGKILL");
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
// Process may already be dead
|
|
206
|
+
}
|
|
207
|
+
}
|
|
177
208
|
};
|
|
178
209
|
process.on("exit", cleanupDebugger);
|
|
179
|
-
process.on("SIGINT", cleanupDebugger);
|
|
180
|
-
process.on("SIGTERM", cleanupDebugger);
|
|
181
|
-
// Resolve agent path within the project
|
|
182
|
-
const agentPath = join(projectRoot, "agents", name);
|
|
183
210
|
// Check if agent exists
|
|
184
211
|
try {
|
|
185
212
|
const { stat } = await import("node:fs/promises");
|
|
@@ -260,11 +287,15 @@ export async function runCommand(options) {
|
|
|
260
287
|
},
|
|
261
288
|
});
|
|
262
289
|
// Setup cleanup handlers for agent and GUI processes
|
|
290
|
+
let isCleaningUp = false;
|
|
263
291
|
const cleanupProcesses = () => {
|
|
292
|
+
if (isCleaningUp)
|
|
293
|
+
return;
|
|
294
|
+
isCleaningUp = true;
|
|
264
295
|
// Kill entire process group by using negative PID
|
|
265
296
|
if (agentProcess.pid) {
|
|
266
297
|
try {
|
|
267
|
-
process.kill(-agentProcess.pid, "
|
|
298
|
+
process.kill(-agentProcess.pid, "SIGKILL");
|
|
268
299
|
}
|
|
269
300
|
catch (e) {
|
|
270
301
|
// Process may already be dead
|
|
@@ -272,35 +303,26 @@ export async function runCommand(options) {
|
|
|
272
303
|
}
|
|
273
304
|
if (guiProcess.pid) {
|
|
274
305
|
try {
|
|
275
|
-
process.kill(-guiProcess.pid, "
|
|
306
|
+
process.kill(-guiProcess.pid, "SIGKILL");
|
|
276
307
|
}
|
|
277
308
|
catch (e) {
|
|
278
309
|
// Process may already be dead
|
|
279
310
|
}
|
|
280
311
|
}
|
|
312
|
+
// Also cleanup debugger
|
|
313
|
+
cleanupDebugger();
|
|
281
314
|
};
|
|
282
315
|
process.on("exit", cleanupProcesses);
|
|
283
|
-
|
|
284
|
-
|
|
316
|
+
// Handle SIGINT (Control-C) explicitly and exit
|
|
317
|
+
const handleSigint = () => {
|
|
318
|
+
cleanupProcesses();
|
|
319
|
+
process.exit(0);
|
|
320
|
+
};
|
|
321
|
+
process.on("SIGINT", handleSigint);
|
|
322
|
+
process.on("SIGTERM", handleSigint);
|
|
285
323
|
// Render the tabbed UI with dynamic port detection
|
|
286
324
|
const { waitUntilExit } = render(_jsx(GuiRunner, { agentProcess: agentProcess, guiProcess: guiProcess, agentPort: availablePort, agentPath: agentPath, logger: logger, onExit: () => {
|
|
287
|
-
|
|
288
|
-
if (agentProcess.pid) {
|
|
289
|
-
try {
|
|
290
|
-
process.kill(-agentProcess.pid, "SIGTERM");
|
|
291
|
-
}
|
|
292
|
-
catch (e) {
|
|
293
|
-
// Process may already be dead
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
if (guiProcess.pid) {
|
|
297
|
-
try {
|
|
298
|
-
process.kill(-guiProcess.pid, "SIGTERM");
|
|
299
|
-
}
|
|
300
|
-
catch (e) {
|
|
301
|
-
// Process may already be dead
|
|
302
|
-
}
|
|
303
|
-
}
|
|
325
|
+
cleanupProcesses();
|
|
304
326
|
} }));
|
|
305
327
|
await waitUntilExit();
|
|
306
328
|
process.exit(0);
|
|
@@ -336,19 +358,30 @@ export async function runCommand(options) {
|
|
|
336
358
|
},
|
|
337
359
|
});
|
|
338
360
|
// Setup cleanup handler for agent process
|
|
361
|
+
let isCleaningUp = false;
|
|
339
362
|
const cleanupAgentProcess = () => {
|
|
363
|
+
if (isCleaningUp)
|
|
364
|
+
return;
|
|
365
|
+
isCleaningUp = true;
|
|
340
366
|
if (agentProcess.pid) {
|
|
341
367
|
try {
|
|
342
|
-
process.kill(-agentProcess.pid, "
|
|
368
|
+
process.kill(-agentProcess.pid, "SIGKILL");
|
|
343
369
|
}
|
|
344
370
|
catch (e) {
|
|
345
371
|
// Process may already be dead
|
|
346
372
|
}
|
|
347
373
|
}
|
|
374
|
+
// Also cleanup debugger
|
|
375
|
+
cleanupDebugger();
|
|
348
376
|
};
|
|
349
377
|
process.on("exit", cleanupAgentProcess);
|
|
350
|
-
|
|
351
|
-
|
|
378
|
+
// Handle SIGINT (Control-C) explicitly and exit
|
|
379
|
+
const handleSigint = () => {
|
|
380
|
+
cleanupAgentProcess();
|
|
381
|
+
process.exit(0);
|
|
382
|
+
};
|
|
383
|
+
process.on("SIGINT", handleSigint);
|
|
384
|
+
process.on("SIGTERM", handleSigint);
|
|
352
385
|
agentProcess.on("error", (error) => {
|
|
353
386
|
logger.error("Failed to start agent", { error: error.message });
|
|
354
387
|
console.error(`Failed to start agent: ${error.message}`);
|
|
@@ -373,9 +406,17 @@ export async function runCommand(options) {
|
|
|
373
406
|
if (process.stdin.isTTY) {
|
|
374
407
|
process.stdin.setRawMode(true);
|
|
375
408
|
}
|
|
409
|
+
// Setup signal handlers for TUI mode
|
|
410
|
+
const handleTuiSigint = () => {
|
|
411
|
+
cleanupDebugger();
|
|
412
|
+
process.exit(0);
|
|
413
|
+
};
|
|
414
|
+
process.on("SIGINT", handleTuiSigint);
|
|
415
|
+
process.on("SIGTERM", handleTuiSigint);
|
|
376
416
|
// Render the tabbed UI with Chat and Logs
|
|
377
417
|
const { waitUntilExit } = render(_jsx(TuiRunner, { agentPath: binPath, workingDir: agentPath, noSession: noSession, onExit: () => {
|
|
378
418
|
// Cleanup is handled by the ACP client disconnect
|
|
419
|
+
cleanupDebugger();
|
|
379
420
|
} }));
|
|
380
421
|
await waitUntilExit();
|
|
381
422
|
process.exit(0);
|
package/dist/commands/whoami.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { loadAuthCredentials } from "@townco/core/auth";
|
|
1
2
|
import { authGet } from "../lib/auth-fetch.js";
|
|
2
|
-
import { loadAuthCredentials } from "../lib/auth-storage.js";
|
|
3
3
|
export async function whoamiCommand() {
|
|
4
4
|
const credentials = loadAuthCredentials();
|
|
5
5
|
if (!credentials) {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import fs from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { argument, command, constant, flag, multiple, object, option, optional, or, } from "@optique/core";
|
|
4
5
|
import { message } from "@optique/core/message";
|
|
@@ -6,8 +7,16 @@ import { integer, string } from "@optique/core/valueparser";
|
|
|
6
7
|
import { run } from "@optique/run";
|
|
7
8
|
import { initForClaudeCode } from "@townco/agent/scaffold";
|
|
8
9
|
import { isInsideTownProject } from "@townco/agent/storage";
|
|
9
|
-
import {
|
|
10
|
+
import { router } from "@townco/api";
|
|
11
|
+
import { findRoot } from "@townco/core";
|
|
12
|
+
import { updateSchema as updateEnvSchema } from "@townco/env/update-schema";
|
|
13
|
+
import { createSecret, deleteSecret, genenv, listSecrets, updateSecret, } from "@townco/secret";
|
|
14
|
+
import { createTRPCClient, httpLink, httpSubscriptionLink, splitLink, } from "@trpc/client";
|
|
15
|
+
import archiver from "archiver";
|
|
16
|
+
import { EventSource } from "eventsource";
|
|
17
|
+
import walk from "ignore-walk";
|
|
10
18
|
import inquirer from "inquirer";
|
|
19
|
+
import superjson from "superjson";
|
|
11
20
|
import { match } from "ts-pattern";
|
|
12
21
|
import { configureCommand } from "./commands/configure.js";
|
|
13
22
|
import { createCommand } from "./commands/create.js";
|
|
@@ -19,7 +28,7 @@ import { whoamiCommand } from "./commands/whoami.js";
|
|
|
19
28
|
/**
|
|
20
29
|
* Securely prompt for a secret value without echoing to the terminal
|
|
21
30
|
*/
|
|
22
|
-
async
|
|
31
|
+
const promptSecret = async (secretName) => {
|
|
23
32
|
const answers = await inquirer.prompt([
|
|
24
33
|
{
|
|
25
34
|
type: "password",
|
|
@@ -29,8 +38,11 @@ async function promptSecret(secretName) {
|
|
|
29
38
|
},
|
|
30
39
|
]);
|
|
31
40
|
return answers.value;
|
|
32
|
-
}
|
|
33
|
-
const parser = or(command("deploy",
|
|
41
|
+
};
|
|
42
|
+
const parser = or(command("deploy", object({ command: constant("deploy") }), {
|
|
43
|
+
brief: message `Deploy agents.`,
|
|
44
|
+
description: message `Deploy agents to the Town cloud.`,
|
|
45
|
+
}), command("configure", constant("configure"), {
|
|
34
46
|
brief: message `Configure environment variables.`,
|
|
35
47
|
}), command("login", constant("login"), {
|
|
36
48
|
brief: message `Log in to Town Shed.`,
|
|
@@ -57,14 +69,23 @@ const parser = or(command("deploy", constant("deploy"), { brief: message `Deploy
|
|
|
57
69
|
noSession: optional(flag("--no-session")),
|
|
58
70
|
}), { brief: message `Run an agent.` }), command("secret", object({
|
|
59
71
|
command: constant("secret"),
|
|
60
|
-
subcommand: or(command("list", constant("list")
|
|
72
|
+
subcommand: or(command("list", object({ action: constant("list") }), {
|
|
73
|
+
brief: message `List secrets.`,
|
|
74
|
+
}), command("add", object({
|
|
61
75
|
action: constant("add"),
|
|
62
76
|
name: argument(string({ metavar: "NAME" })),
|
|
63
77
|
value: optional(argument(string({ metavar: "VALUE" }))),
|
|
64
|
-
}), { brief: message `Add a secret.` }), command("
|
|
78
|
+
}), { brief: message `Add a secret.` }), command("update", object({
|
|
79
|
+
action: constant("update"),
|
|
80
|
+
name: optional(argument(string({ metavar: "NAME" }))),
|
|
81
|
+
value: optional(argument(string({ metavar: "VALUE" }))),
|
|
82
|
+
genenv: optional(flag("-g", "--genenv", {
|
|
83
|
+
description: message `Regenerate .env file.`,
|
|
84
|
+
})),
|
|
85
|
+
}), { brief: message `Update a secret.` }), command("remove", object({
|
|
65
86
|
action: constant("remove"),
|
|
66
87
|
name: argument(string({ metavar: "NAME" })),
|
|
67
|
-
}), { brief: message `Remove a secret.` }), command("genenv", constant("genenv"), {
|
|
88
|
+
}), { brief: message `Remove a secret.` }), command("genenv", object({ action: constant("genenv") }), {
|
|
68
89
|
brief: message `Generate .env file.`,
|
|
69
90
|
})),
|
|
70
91
|
}), { brief: message `Secrets management.` }));
|
|
@@ -76,136 +97,213 @@ const meta = {
|
|
|
76
97
|
brief: message `Your one-stop shop for all things Town in the terminal\n`,
|
|
77
98
|
description: message `Town CLI is a first-class command-line experience for working with Town Agents.`,
|
|
78
99
|
};
|
|
79
|
-
async
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
console.log("\n✓ Claude Code workspace initialized successfully!");
|
|
100
|
+
const main = async (parser, meta) => await match(run(parser, meta))
|
|
101
|
+
.with("login", loginCommand)
|
|
102
|
+
.with("whoami", whoamiCommand)
|
|
103
|
+
.with({ command: "deploy" }, async () => {
|
|
104
|
+
const url = "http://localhost:3000/api/trpc";
|
|
105
|
+
const client = createTRPCClient({
|
|
106
|
+
links: [
|
|
107
|
+
splitLink({
|
|
108
|
+
condition: (op) => op.type === "subscription",
|
|
109
|
+
true: httpSubscriptionLink({
|
|
110
|
+
url,
|
|
111
|
+
transformer: superjson,
|
|
112
|
+
EventSource,
|
|
113
|
+
}),
|
|
114
|
+
false: splitLink({
|
|
115
|
+
condition: (op) => op.path === router.uploadArchive.name,
|
|
116
|
+
true: httpLink({
|
|
117
|
+
url,
|
|
118
|
+
transformer: { serialize: (o) => o, deserialize: (o) => o },
|
|
119
|
+
}),
|
|
120
|
+
false: httpLink({ url, transformer: superjson }),
|
|
121
|
+
}),
|
|
122
|
+
}),
|
|
123
|
+
],
|
|
124
|
+
});
|
|
125
|
+
console.log("Creating archive...");
|
|
126
|
+
const root = await findRoot();
|
|
127
|
+
const arc = archiver("tar", { gzip: true });
|
|
128
|
+
(await walk({ path: root, ignoreFiles: [".gitignore"] }))
|
|
129
|
+
.filter((path) => path.split("/")[0] !== ".git")
|
|
130
|
+
.forEach((path) => {
|
|
131
|
+
if (!fs.statSync(path).isFile())
|
|
112
132
|
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
133
|
+
arc.append(path, { name: path });
|
|
134
|
+
});
|
|
135
|
+
arc.finalize();
|
|
136
|
+
console.log("Uploading archive...");
|
|
137
|
+
const { sha256, cached } = await client.uploadArchive.mutate(Buffer.from(await Array.fromAsync(arc)));
|
|
138
|
+
console.log(`Archive uploaded: ${sha256} (${cached ? "cached" : "new"})`);
|
|
139
|
+
console.log("Deploying...");
|
|
140
|
+
client.deploy.subscribe({ sha256 }, {
|
|
141
|
+
onData: ({ status }) => console.log(` ${status}`),
|
|
142
|
+
onError: (err) => console.error(err),
|
|
143
|
+
onComplete: () => console.log("\n✓ Deployment complete!"),
|
|
144
|
+
});
|
|
145
|
+
})
|
|
146
|
+
.with("configure", configureCommand)
|
|
147
|
+
.with("upgrade", upgradeCommand)
|
|
148
|
+
.with({ command: "create" }, async ({ name, model, tools, systemPrompt, init, claude }) => {
|
|
149
|
+
// Handle --claude flag (initialize .claude in existing project)
|
|
150
|
+
if (claude === true) {
|
|
115
151
|
if (init !== null && init !== undefined) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
path: init,
|
|
119
|
-
});
|
|
152
|
+
console.error("Error: --claude flag is redundant with --init (projects include .claude by default)");
|
|
153
|
+
process.exit(1);
|
|
120
154
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
//
|
|
156
|
-
// Create command starts a long-running Ink session
|
|
155
|
+
// Check if we're in a Town project
|
|
156
|
+
const projectRoot = await isInsideTownProject();
|
|
157
|
+
if (projectRoot === null) {
|
|
158
|
+
console.error("Error: Not inside a Town project. Use 'town create --init <path>' to create a new project.");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
// Initialize .claude directory only
|
|
162
|
+
await initForClaudeCode(projectRoot);
|
|
163
|
+
console.log("\n✓ Claude Code workspace initialized successfully!");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Check if --init flag is present for project scaffolding
|
|
167
|
+
if (init !== null && init !== undefined) {
|
|
168
|
+
// Project mode - scaffold a standalone project
|
|
169
|
+
await createProjectCommand({
|
|
170
|
+
path: init,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Check if we're inside a Town project
|
|
175
|
+
const projectRoot = await isInsideTownProject();
|
|
176
|
+
if (projectRoot === null) {
|
|
177
|
+
// Not in a project - prompt user to initialize
|
|
178
|
+
const answer = await inquirer.prompt([
|
|
179
|
+
{
|
|
180
|
+
type: "confirm",
|
|
181
|
+
name: "initProject",
|
|
182
|
+
message: "Not inside a Town project. Initialize project in current directory?",
|
|
183
|
+
default: true,
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
if (answer.initProject) {
|
|
187
|
+
// Initialize project first
|
|
188
|
+
await createProjectCommand({ path: process.cwd() });
|
|
189
|
+
// Then create agent
|
|
157
190
|
await createCommand({
|
|
158
191
|
...(name !== undefined && { name }),
|
|
159
192
|
...(model !== undefined && { model }),
|
|
160
193
|
...(tools.length > 0 && { tools }),
|
|
161
194
|
...(systemPrompt !== undefined && { systemPrompt }),
|
|
162
|
-
agentsDir: join(
|
|
195
|
+
agentsDir: join(process.cwd(), "agents"),
|
|
163
196
|
});
|
|
164
197
|
}
|
|
198
|
+
else {
|
|
199
|
+
// User declined
|
|
200
|
+
console.log("\nPlease run 'town create' inside a project directory, or run:\n" +
|
|
201
|
+
" town create --init <path>\n" +
|
|
202
|
+
"to create a project.");
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
165
205
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
206
|
+
else {
|
|
207
|
+
// Agent mode - create agent in existing project
|
|
208
|
+
// Create command starts a long-running Ink session
|
|
209
|
+
await createCommand({
|
|
210
|
+
...(name !== undefined && { name }),
|
|
211
|
+
...(model !== undefined && { model }),
|
|
212
|
+
...(tools.length > 0 && { tools }),
|
|
213
|
+
...(systemPrompt !== undefined && { systemPrompt }),
|
|
214
|
+
agentsDir: join(projectRoot, "agents"),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
.with({ command: "run" }, async ({ name, http, gui, port, noSession }) => {
|
|
220
|
+
const options = {
|
|
221
|
+
name,
|
|
222
|
+
http: http === true,
|
|
223
|
+
gui: gui === true,
|
|
224
|
+
noSession: noSession === true,
|
|
225
|
+
};
|
|
226
|
+
if (port !== null && port !== undefined) {
|
|
227
|
+
options.port = port;
|
|
228
|
+
}
|
|
229
|
+
await runCommand(options);
|
|
230
|
+
})
|
|
231
|
+
.with({ command: "secret" }, async ({ subcommand }) => {
|
|
232
|
+
await match(subcommand)
|
|
233
|
+
.with({ action: "list" }, async () => {
|
|
234
|
+
const truncate = (str, maxLength = 50) => {
|
|
235
|
+
if (str.length <= maxLength)
|
|
236
|
+
return str;
|
|
237
|
+
return `${str.slice(0, maxLength - 3)}...`;
|
|
173
238
|
};
|
|
174
|
-
|
|
175
|
-
|
|
239
|
+
console.table((await listSecrets()).map((secret) => ({
|
|
240
|
+
Key: secret.key,
|
|
241
|
+
Valid: secret.valid ? "✓" : "✗",
|
|
242
|
+
Error: truncate(secret.error ?? ""),
|
|
243
|
+
})));
|
|
244
|
+
})
|
|
245
|
+
.with({ action: "add" }, async ({ name, value }) => {
|
|
246
|
+
// If value is not provided, prompt securely
|
|
247
|
+
const secretValue = value ?? (await promptSecret(name));
|
|
248
|
+
if (!secretValue) {
|
|
249
|
+
console.error("Error: Secret value cannot be empty");
|
|
250
|
+
process.exit(1);
|
|
176
251
|
}
|
|
177
|
-
await
|
|
252
|
+
await createSecret(name, secretValue);
|
|
253
|
+
await updateEnvSchema();
|
|
254
|
+
console.log(`Secret '${name}' added successfully (& @packages/env schema updated).`);
|
|
178
255
|
})
|
|
179
|
-
.with({
|
|
180
|
-
|
|
181
|
-
|
|
256
|
+
.with({ action: "update" }, async ({ name, value, genenv: regen }) => {
|
|
257
|
+
let secretName = name;
|
|
258
|
+
// If name is not provided, show a list prompt to select from existing secrets
|
|
259
|
+
if (!secretName) {
|
|
182
260
|
const secrets = await listSecrets();
|
|
183
|
-
|
|
184
|
-
console.
|
|
185
|
-
}
|
|
186
|
-
})
|
|
187
|
-
.with({ action: "add" }, async ({ name, value }) => {
|
|
188
|
-
// If value is not provided, prompt securely
|
|
189
|
-
const secretValue = value ?? (await promptSecret(name));
|
|
190
|
-
if (!secretValue) {
|
|
191
|
-
console.error("Error: Secret value cannot be empty");
|
|
261
|
+
if (secrets.length === 0) {
|
|
262
|
+
console.error("No secrets found to update.");
|
|
192
263
|
process.exit(1);
|
|
193
264
|
}
|
|
194
|
-
await
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
265
|
+
const answer = await inquirer.prompt([
|
|
266
|
+
{
|
|
267
|
+
type: "list",
|
|
268
|
+
name: "selectedSecret",
|
|
269
|
+
message: "Select a secret to update:",
|
|
270
|
+
choices: secrets.map((s) => ({
|
|
271
|
+
name: `${s.key} ${s.valid ? "✓" : "✗"}`,
|
|
272
|
+
value: s.key,
|
|
273
|
+
})),
|
|
274
|
+
},
|
|
275
|
+
]);
|
|
276
|
+
secretName = answer.selectedSecret;
|
|
277
|
+
}
|
|
278
|
+
// If value is not provided, prompt securely
|
|
279
|
+
if (!secretName) {
|
|
280
|
+
console.error("Error: Secret name is required");
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
const secretValue = value ?? (await promptSecret(secretName));
|
|
284
|
+
if (!secretValue) {
|
|
285
|
+
console.error("Error: Secret value cannot be empty");
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
await updateSecret(secretName, secretValue);
|
|
289
|
+
console.log(`Secret '${secretName}' updated successfully.`);
|
|
290
|
+
if (regen) {
|
|
202
291
|
await genenv();
|
|
203
292
|
console.log(".env file generated successfully.");
|
|
204
|
-
}
|
|
205
|
-
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
.with({ action: "remove" }, async ({ name }) => {
|
|
296
|
+
await deleteSecret(name);
|
|
297
|
+
await updateEnvSchema();
|
|
298
|
+
console.log(`Secret '${name}' removed successfully (& @packages/env schema updated).`);
|
|
299
|
+
})
|
|
300
|
+
.with({ action: "genenv" }, async () => {
|
|
301
|
+
await genenv();
|
|
302
|
+
console.log(".env file generated successfully.");
|
|
206
303
|
})
|
|
207
304
|
.exhaustive();
|
|
208
|
-
}
|
|
305
|
+
})
|
|
306
|
+
.exhaustive();
|
|
209
307
|
main(parser, meta).catch((error) => {
|
|
210
308
|
console.error("Error:", error);
|
|
211
309
|
process.exit(1);
|
package/dist/lib/auth-fetch.d.ts
CHANGED
package/dist/lib/auth-fetch.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isTokenExpired, loadAuthCredentials, saveAuthCredentials, } from "
|
|
1
|
+
import { isTokenExpired, loadAuthCredentials, saveAuthCredentials, } from "@townco/core/auth";
|
|
2
2
|
// ============================================================================
|
|
3
3
|
// Token Refresh
|
|
4
4
|
// ============================================================================
|