@townco/cli 0.1.45 → 0.1.47

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.
@@ -66,8 +66,6 @@ function CreateApp({ name: initialName, model: initialModel, tools: initialTools
66
66
  return "model";
67
67
  if (!initialTools || initialTools.length === 0)
68
68
  return "tools";
69
- if (!initialSystemPrompt)
70
- return "systemPrompt";
71
69
  return "review";
72
70
  };
73
71
  const [stage, setStage] = useState(determineInitialStage());
@@ -75,43 +73,14 @@ function CreateApp({ name: initialName, model: initialModel, tools: initialTools
75
73
  name: initialName || "",
76
74
  model: initialModel || "",
77
75
  tools: initialTools ? [...initialTools] : [],
78
- systemPrompt: initialSystemPrompt || "",
76
+ systemPrompt: initialSystemPrompt || "You are a helpful assistant.",
79
77
  });
80
78
  const [nameInput, setNameInput] = useState(initialName || "");
81
- const [systemPromptInput, setSystemPromptInput] = useState(initialSystemPrompt || "");
82
- const [isEditingPrompt, setIsEditingPrompt] = useState(false);
83
- const [promptEditMode, setPromptEditMode] = useState(null);
84
79
  const [reviewSelection, setReviewSelection] = useState(null);
85
80
  const [isEditingFromReview, setIsEditingFromReview] = useState(false);
86
81
  const [scaffoldStatus, setScaffoldStatus] = useState("pending");
87
82
  const [scaffoldError, setScaffoldError] = useState(null);
88
83
  const [agentPath, setAgentPath] = useState(null);
89
- // Handle opening editor when systemPrompt stage is entered from review
90
- useEffect(() => {
91
- (async () => {
92
- if (stage === "systemPrompt" &&
93
- isEditingFromReview &&
94
- !isEditingPrompt &&
95
- promptEditMode === null) {
96
- // Trigger editor opening
97
- setIsEditingPrompt(true);
98
- const editorContent = await openInEditor(agentDef.systemPrompt || "You are a helpful assistant.");
99
- if (editorContent !== null) {
100
- // Editor worked
101
- setAgentDef({ ...agentDef, systemPrompt: editorContent });
102
- setIsEditingPrompt(false);
103
- setIsEditingFromReview(false);
104
- setStage("review");
105
- }
106
- else {
107
- // Fallback to inline
108
- setPromptEditMode("inline");
109
- setIsEditingPrompt(false);
110
- setSystemPromptInput(agentDef.systemPrompt || "You are a helpful assistant.");
111
- }
112
- }
113
- })();
114
- }, [stage, isEditingFromReview, isEditingPrompt, promptEditMode, agentDef]);
115
84
  // Handle scaffolding when entering "done" stage
116
85
  useEffect(() => {
117
86
  if (stage === "done" && scaffoldStatus === "pending") {
@@ -126,7 +95,7 @@ function CreateApp({ name: initialName, model: initialModel, tools: initialTools
126
95
  const model = agentDef.model;
127
96
  const definition = {
128
97
  model,
129
- systemPrompt: agentDef.systemPrompt || null,
98
+ systemPrompt: agentDef.systemPrompt || "You are a helpful assistant.",
130
99
  tools: agentDef.tools || [],
131
100
  hooks: [
132
101
  {
@@ -197,62 +166,15 @@ function CreateApp({ name: initialName, model: initialModel, tools: initialTools
197
166
  }
198
167
  // Tools selection stage
199
168
  if (stage === "tools") {
200
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, children: ["Select tools for agent: ", agentDef.name] }) }), _jsx(MultiSelect, { options: AVAILABLE_TOOLS, selected: agentDef.tools || [], onChange: (tools) => setAgentDef({ ...agentDef, tools }), onSubmit: async () => {
169
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, children: ["Select tools for agent: ", agentDef.name] }) }), _jsx(MultiSelect, { options: AVAILABLE_TOOLS, selected: agentDef.tools || [], onChange: (tools) => setAgentDef({ ...agentDef, tools }), onSubmit: () => {
201
170
  // If editing from review, just go back to review
202
171
  if (isEditingFromReview) {
203
172
  setIsEditingFromReview(false);
204
- setStage("review");
205
- return;
206
- }
207
- // If systemPrompt was provided via flag, skip to review
208
- if (initialSystemPrompt) {
209
- setStage("review");
210
- return;
211
- }
212
- setStage("systemPrompt");
213
- // Attempt to open editor
214
- setIsEditingPrompt(true);
215
- const editorContent = await openInEditor(agentDef.systemPrompt || "You are a helpful assistant.");
216
- if (editorContent !== null) {
217
- // Editor worked
218
- setAgentDef({ ...agentDef, systemPrompt: editorContent });
219
- setIsEditingPrompt(false);
220
- setStage("review");
221
- }
222
- else {
223
- // Fallback to inline
224
- setPromptEditMode("inline");
225
- setIsEditingPrompt(false);
226
- setSystemPromptInput(agentDef.systemPrompt || "You are a helpful assistant.");
227
173
  }
174
+ // Go directly to review stage
175
+ setStage("review");
228
176
  }, onCancel: () => setStage("model") })] }));
229
177
  }
230
- // System prompt stage (inline fallback)
231
- if (stage === "systemPrompt") {
232
- if (isEditingPrompt) {
233
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Opening editor for system prompt..." }) }), _jsx(Text, { dimColor: true, children: "You can edit the system prompt in your preferred editor." })] }));
234
- }
235
- if (promptEditMode === "inline") {
236
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Enter system prompt (multi-line supported):" }) }), _jsx(InputBox, { value: systemPromptInput, onChange: setSystemPromptInput, onSubmit: () => {
237
- setAgentDef({ ...agentDef, systemPrompt: systemPromptInput });
238
- if (isEditingFromReview) {
239
- setIsEditingFromReview(false);
240
- setPromptEditMode(null);
241
- }
242
- setStage("review");
243
- }, onEscape: () => {
244
- if (isEditingFromReview) {
245
- setIsEditingFromReview(false);
246
- setPromptEditMode(null);
247
- setStage("review");
248
- }
249
- else {
250
- setStage("tools");
251
- }
252
- }, isSubmitting: false, attachedFiles: [] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter: Continue \u2022 Esc: Back" }) })] }));
253
- }
254
- return null;
255
- }
256
178
  // Review stage
257
179
  if (stage === "review") {
258
180
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Review Agent Configuration:" }) }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Name: " }), agentDef.name] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Model: " }), agentDef.model] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Tools: " }), agentDef.tools && agentDef.tools.length > 0
@@ -278,11 +200,6 @@ function CreateApp({ name: initialName, model: initialModel, tools: initialTools
278
200
  value: "tools",
279
201
  description: "Go back and change tool selection",
280
202
  },
281
- {
282
- label: "Edit system prompt",
283
- value: "systemPrompt",
284
- description: "Go back and change the system prompt",
285
- },
286
203
  ], selected: reviewSelection, onChange: (value) => {
287
204
  setReviewSelection(value);
288
205
  }, onSubmit: (value) => {
@@ -293,8 +210,7 @@ function CreateApp({ name: initialName, model: initialModel, tools: initialTools
293
210
  }
294
211
  else if (value === "name" ||
295
212
  value === "model" ||
296
- value === "tools" ||
297
- value === "systemPrompt") {
213
+ value === "tools") {
298
214
  // Set flag so we return to review after editing
299
215
  setIsEditingFromReview(true);
300
216
  setStage(value);
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@townco/cli",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "town": "./dist/index.js"
@@ -15,17 +15,17 @@
15
15
  "build": "tsc"
16
16
  },
17
17
  "devDependencies": {
18
- "@townco/tsconfig": "0.1.37",
18
+ "@townco/tsconfig": "0.1.39",
19
19
  "@types/bun": "^1.3.1",
20
20
  "@types/react": "^19.2.2"
21
21
  },
22
22
  "dependencies": {
23
23
  "@optique/core": "^0.6.2",
24
24
  "@optique/run": "^0.6.2",
25
- "@townco/agent": "0.1.45",
26
- "@townco/core": "0.0.18",
27
- "@townco/secret": "0.1.40",
28
- "@townco/ui": "0.1.40",
25
+ "@townco/agent": "0.1.47",
26
+ "@townco/core": "0.0.20",
27
+ "@townco/secret": "0.1.42",
28
+ "@townco/ui": "0.1.42",
29
29
  "@types/inquirer": "^9.0.9",
30
30
  "ink": "^6.4.0",
31
31
  "ink-text-input": "^6.0.0",
@@ -1 +0,0 @@
1
- export declare function deleteCommand(name: string): Promise<void>;
@@ -1,60 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { agentExists, deleteAgent } from "@townco/agent/storage";
3
- import { SingleSelect } from "@townco/ui/tui";
4
- import { Box, render, Text } from "ink";
5
- import { useState } from "react";
6
- function DeleteApp({ name }) {
7
- const [confirmed, setConfirmed] = useState(null);
8
- if (confirmed === null) {
9
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, children: ["Are you sure you want to delete agent", " ", _jsx(Text, { color: "red", children: name }), "?"] }) }), _jsx(Text, { dimColor: true, children: "This action cannot be undone." }), _jsx(Box, { marginTop: 1, children: _jsx(SingleSelect, { options: [
10
- {
11
- label: "Yes, delete",
12
- value: "yes",
13
- description: "Permanently delete this agent",
14
- },
15
- {
16
- label: "No, cancel",
17
- value: "no",
18
- description: "Keep the agent",
19
- },
20
- ], selected: null, onChange: () => { }, onSubmit: async (value) => {
21
- if (value === "yes") {
22
- setConfirmed(true);
23
- try {
24
- await deleteAgent(name);
25
- setTimeout(() => process.exit(0), 1500);
26
- }
27
- catch (error) {
28
- console.error(`Error deleting agent: ${error instanceof Error ? error.message : String(error)}`);
29
- process.exit(1);
30
- }
31
- }
32
- else {
33
- setConfirmed(false);
34
- setTimeout(() => process.exit(0), 500);
35
- }
36
- }, onCancel: () => {
37
- setConfirmed(false);
38
- setTimeout(() => process.exit(0), 500);
39
- } }) })] }));
40
- }
41
- if (confirmed) {
42
- return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: "green", children: "\u2713 Agent deleted successfully" }) }));
43
- }
44
- return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { dimColor: true, children: "Cancelled" }) }));
45
- }
46
- export async function deleteCommand(name) {
47
- // Check if agent exists
48
- const exists = await agentExists(name);
49
- if (!exists) {
50
- console.error(`Error: Agent "${name}" not found.`);
51
- console.log('\nList agents with "town list"');
52
- process.exit(1);
53
- }
54
- // Set stdin to raw mode to capture input
55
- if (process.stdin.isTTY) {
56
- process.stdin.setRawMode(true);
57
- }
58
- const { waitUntilExit } = render(_jsx(DeleteApp, { name: name }));
59
- await waitUntilExit();
60
- }
@@ -1 +0,0 @@
1
- export declare function editCommand(name: string): Promise<void>;
@@ -1,92 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { isInsideTownProject } from "@townco/agent/storage";
4
- import { createCommand } from "./create.js";
5
- async function loadAgentConfig(name, agentPath) {
6
- try {
7
- const indexPath = join(agentPath, "index.ts");
8
- const content = await readFile(indexPath, "utf-8");
9
- // Parse model
10
- const modelMatch = content.match(/model:\s*"([^"]+)"/);
11
- if (!modelMatch) {
12
- throw new Error("Failed to parse model from agent configuration");
13
- }
14
- const model = modelMatch[1];
15
- // Parse tools
16
- const toolsMatch = content.match(/tools:\s*\[([^\]]*)\]/);
17
- let tools = [];
18
- if (toolsMatch?.[1]) {
19
- const toolsStr = toolsMatch[1];
20
- tools = toolsStr
21
- .split(",")
22
- .map((t) => t.trim().replace(/["']/g, ""))
23
- .filter((t) => t.length > 0);
24
- }
25
- // Parse systemPrompt - handle multiline strings and null
26
- const systemPromptMatch = content.match(/systemPrompt:\s*(null|"([^"]*)"|`([^`]*)`)/s);
27
- let systemPrompt;
28
- if (systemPromptMatch) {
29
- if (systemPromptMatch[1] === "null") {
30
- systemPrompt = "";
31
- }
32
- else {
33
- // Get either double-quoted (group 2) or backtick-quoted (group 3) content
34
- systemPrompt = systemPromptMatch[2] || systemPromptMatch[3] || "";
35
- }
36
- }
37
- if (systemPrompt !== undefined && systemPrompt !== "") {
38
- return {
39
- name,
40
- model: model,
41
- tools,
42
- systemPrompt,
43
- };
44
- }
45
- return {
46
- name,
47
- model: model,
48
- tools,
49
- };
50
- }
51
- catch (error) {
52
- console.error(`Error loading agent config: ${error instanceof Error ? error.message : String(error)}`);
53
- return null;
54
- }
55
- }
56
- export async function editCommand(name) {
57
- // Check if we're inside a Town project
58
- const projectRoot = await isInsideTownProject();
59
- if (projectRoot === null) {
60
- console.error("Error: Not inside a Town project.\n\n" +
61
- "Please run 'town edit' inside a project directory.");
62
- process.exit(1);
63
- }
64
- const agentPath = join(projectRoot, "agents", name);
65
- // Check if the agent exists
66
- try {
67
- await readFile(join(agentPath, "agent.json"), "utf-8");
68
- }
69
- catch {
70
- console.error(`Error: Agent "${name}" not found.`);
71
- console.log('\nCreate an agent with "town create" or list agents with "town list"');
72
- process.exit(1);
73
- }
74
- // Load existing config
75
- const config = await loadAgentConfig(name, agentPath);
76
- if (!config) {
77
- console.error(`Error: Failed to load agent configuration for "${name}".`);
78
- process.exit(1);
79
- }
80
- console.log(`Editing agent: ${name}\n`);
81
- // Reuse create command with existing values and overwrite flag
82
- await createCommand({
83
- name: config.name,
84
- model: config.model,
85
- tools: config.tools,
86
- ...(config.systemPrompt !== undefined && {
87
- systemPrompt: config.systemPrompt,
88
- }),
89
- overwrite: true,
90
- agentsDir: join(projectRoot, "agents"),
91
- });
92
- }
@@ -1 +0,0 @@
1
- export declare function listCommand(): Promise<void>;
@@ -1,55 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { getAgentPath, listAgents } from "@townco/agent/storage";
4
- async function getAgentInfo(name) {
5
- try {
6
- const agentPath = getAgentPath(name);
7
- const configPath = join(agentPath, "agent.json");
8
- const content = await readFile(configPath, "utf-8");
9
- const config = JSON.parse(content);
10
- return {
11
- name,
12
- model: config.model,
13
- tools: config.tools || [],
14
- };
15
- }
16
- catch (error) {
17
- return {
18
- name,
19
- error: error instanceof Error ? error.message : "Failed to read agent",
20
- };
21
- }
22
- }
23
- export async function listCommand() {
24
- const agents = await listAgents();
25
- if (agents.length === 0) {
26
- console.log("No agents found.");
27
- console.log('\x1b[2mCreate your first agent with "town create"\x1b[0m');
28
- return;
29
- }
30
- console.log(`\x1b[1mFound ${agents.length} agent${agents.length === 1 ? "" : "s"}:\x1b[0m\n`);
31
- // Get info for all agents
32
- const agentInfos = await Promise.all(agents.map((name) => getAgentInfo(name)));
33
- // Display each agent
34
- for (const info of agentInfos) {
35
- const agentPath = getAgentPath(info.name);
36
- if (info.error) {
37
- console.log(` \x1b[31m✗\x1b[0m \x1b[1m${info.name}\x1b[0m`);
38
- console.log(` \x1b[2mError: ${info.error}\x1b[0m`);
39
- }
40
- else {
41
- const modelLabel = info.model
42
- ? info.model.replace("claude-", "")
43
- : "unknown";
44
- console.log(` \x1b[32m●\x1b[0m \x1b[1m${info.name}\x1b[0m`);
45
- console.log(` \x1b[2mModel: ${modelLabel}\x1b[0m`);
46
- if (info.tools && info.tools.length > 0) {
47
- console.log(` \x1b[2mTools: ${info.tools.join(", ")}\x1b[0m`);
48
- }
49
- console.log(` \x1b[2mPath: ${agentPath}\x1b[0m`);
50
- }
51
- console.log(); // Empty line between agents
52
- }
53
- console.log(`\x1b[2mRun an agent with: town run <name>\x1b[0m`);
54
- console.log(`\x1b[2mTUI mode (default), --gui for web interface, --http for API server\x1b[0m`);
55
- }
@@ -1,14 +0,0 @@
1
- interface MCPAddProps {
2
- name?: string;
3
- url?: string;
4
- command?: string;
5
- args?: readonly string[];
6
- }
7
- declare function MCPAddApp({
8
- name: initialName,
9
- url: initialUrl,
10
- command: initialCommand,
11
- args: initialArgs,
12
- }: MCPAddProps): import("react/jsx-runtime").JSX.Element | null;
13
- export default MCPAddApp;
14
- export declare function runMCPAdd(props?: MCPAddProps): Promise<void>;