towns-agent 2.0.5 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { green as green5, red as red7, yellow as yellow3, cyan as cyan4 } from "picocolors";
2
+ import { green as green5, red as red7, yellow as yellow4, cyan as cyan4 } from "picocolors";
3
3
 
4
4
  // src/modules/init.ts
5
5
  import * as fs2 from "fs";
@@ -11,9 +11,17 @@ import * as jsonc from "jsonc-parser";
11
11
  // src/modules/utils.ts
12
12
  import * as fs from "fs";
13
13
  import * as path from "path";
14
+ import { fileURLToPath } from "url";
14
15
  import { default as spawn } from "cross-spawn";
15
16
  import { default as prompts } from "prompts";
16
17
  import picocolors from "picocolors";
18
+ import { config as dotenvConfig } from "dotenv";
19
+ import { parseAppPrivateData } from "@towns-labs/sdk";
20
+
21
+ // package.json
22
+ var version = "2.0.7";
23
+
24
+ // src/modules/utils.ts
17
25
  var getPackageManager = () => {
18
26
  if (process.env.npm_config_user_agent) {
19
27
  const agent = process.env.npm_config_user_agent;
@@ -61,7 +69,7 @@ function getDlxCommand(packageManager) {
61
69
  }
62
70
  }
63
71
  function runCommand(command, args, opts = { cwd: void 0, silent: false }) {
64
- return new Promise((resolve2, reject) => {
72
+ return new Promise((resolve3, reject) => {
65
73
  const child = spawn(command, args, {
66
74
  stdio: opts.silent ? "ignore" : "inherit",
67
75
  cwd: opts.cwd,
@@ -71,118 +79,58 @@ function runCommand(command, args, opts = { cwd: void 0, silent: false }) {
71
79
  if (code !== 0) {
72
80
  reject(new Error(`Command failed with exit code ${code}`));
73
81
  } else {
74
- resolve2();
75
- }
76
- });
77
- child.on("error", reject);
78
- });
79
- }
80
- async function getLatestTownsProtocolVersion() {
81
- return new Promise((resolve2, reject) => {
82
- const child = spawn("npm", ["view", "@towns-labs/agent", "version"], {
83
- stdio: ["ignore", "pipe", "ignore"]
84
- });
85
- let output = "";
86
- child.stdout?.on("data", (data) => {
87
- output += data.toString();
88
- });
89
- child.on("close", (code) => {
90
- if (code !== 0) {
91
- reject(new Error("Failed to fetch latest @towns-labs/agent version"));
92
- } else {
93
- resolve2(output.trim());
82
+ resolve3();
94
83
  }
95
84
  });
96
85
  child.on("error", reject);
97
86
  });
98
87
  }
99
- function getLatestSdkTag() {
100
- const tagsResult = spawn.sync(
101
- "git",
102
- ["ls-remote", "--tags", "https://github.com/HereNotThere/chat.git", "@towns-labs/sdk@*"],
103
- { encoding: "utf8" }
104
- );
105
- if (tagsResult.status !== 0 || !tagsResult.stdout) return null;
106
- const tags = tagsResult.stdout.split("\n").filter(Boolean).map((line) => {
107
- const [_hash, ref] = line.split(" ");
108
- const tag = ref.replace("refs/tags/", "").replace(/\^{}$/, "");
109
- const match = tag.match(/^@towns-protocol\/sdk@(\d+)\.(\d+)\.(\d+)$/);
110
- if (!match) return null;
111
- return {
112
- tag,
113
- version: [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])]
114
- };
115
- }).filter(
116
- (item) => item !== null && Array.isArray(item.version) && item.version.length === 3
117
- ).sort((a, b) => {
118
- for (let i = 0; i < 3; i++) {
119
- if (a.version[i] !== b.version[i]) {
120
- return b.version[i] - a.version[i];
121
- }
122
- }
123
- return 0;
124
- });
125
- return tags.length > 0 ? tags[0].tag : null;
88
+ function getTemplatesDir() {
89
+ const currentDir = typeof __dirname !== "undefined" ? __dirname : path.dirname(fileURLToPath(import.meta.url));
90
+ const fromDist = path.resolve(currentDir, "..", "templates");
91
+ if (fs.existsSync(fromDist)) return fromDist;
92
+ const fromSrc = path.resolve(currentDir, "..", "..", "templates");
93
+ if (fs.existsSync(fromSrc)) return fromSrc;
94
+ throw new Error("Templates directory not found");
126
95
  }
127
- async function cloneTemplate(packagePath, targetDir) {
128
- console.log(picocolors.blue("Cloning template from GitHub..."));
129
- const tempDir = `${targetDir}-temp`;
130
- const fullTemplatePath = `packages/examples/${packagePath}`;
131
- const latestSdkTag = getLatestSdkTag();
132
- if (!latestSdkTag) {
133
- console.error(picocolors.red("Failed to get latest SDK tag."));
134
- return false;
135
- }
136
- const cloneResult = spawn.sync(
137
- "git",
138
- [
139
- "clone",
140
- "--no-checkout",
141
- "--depth",
142
- "1",
143
- "--sparse",
144
- "--branch",
145
- latestSdkTag,
146
- "https://github.com/towns-protocol/towns.git",
147
- tempDir
148
- ],
149
- { stdio: "pipe" }
150
- );
151
- if (cloneResult.status !== 0) return false;
152
- const sparseResult = spawn.sync("git", ["sparse-checkout", "set", fullTemplatePath], {
153
- stdio: "pipe",
154
- cwd: tempDir
155
- });
156
- if (sparseResult.status !== 0) {
157
- fs.rmSync(tempDir, { recursive: true, force: true });
158
- return false;
159
- }
160
- const checkoutResult = spawn.sync("git", ["checkout"], {
161
- stdio: "pipe",
162
- cwd: tempDir
163
- });
164
- if (checkoutResult.status !== 0) {
165
- fs.rmSync(tempDir, { recursive: true, force: true });
166
- return false;
167
- }
168
- const sourceDir = path.join(tempDir, fullTemplatePath);
96
+ function copyTemplate(templateName, targetDir) {
97
+ console.log(picocolors.blue("Copying template..."));
98
+ const templatesDir = getTemplatesDir();
99
+ const sourceDir = path.join(templatesDir, templateName);
169
100
  if (!fs.existsSync(sourceDir)) {
170
- console.error(picocolors.red(`
171
- Template directory not found at ${sourceDir}`));
172
- fs.rmSync(tempDir, { recursive: true, force: true });
101
+ console.error(picocolors.red(`Template "${templateName}" not found at ${sourceDir}`));
173
102
  return false;
174
103
  }
175
104
  fs.mkdirSync(targetDir, { recursive: true });
176
105
  fs.cpSync(sourceDir, targetDir, {
177
106
  recursive: true,
178
- filter: () => {
179
- return true;
107
+ filter: (source) => {
108
+ const basename2 = path.basename(source);
109
+ return basename2 !== "node_modules" && basename2 !== "dist";
180
110
  }
181
111
  });
182
- fs.rmSync(tempDir, { recursive: true, force: true });
183
- console.log(picocolors.green("\u2713"), "Template cloned successfully!");
112
+ const gitignoreSrc = path.join(targetDir, "_gitignore");
113
+ const gitignoreDest = path.join(targetDir, ".gitignore");
114
+ if (fs.existsSync(gitignoreSrc)) {
115
+ fs.renameSync(gitignoreSrc, gitignoreDest);
116
+ }
117
+ console.log(picocolors.green("\u2713"), "Template copied successfully!");
184
118
  return true;
185
119
  }
120
+ function copyAgentsMd(projectDir) {
121
+ try {
122
+ const templatesDir = getTemplatesDir();
123
+ const sourceFile = path.join(templatesDir, "quickstart", "AGENTS.md");
124
+ if (!fs.existsSync(sourceFile)) {
125
+ return false;
126
+ }
127
+ const destFile = path.join(projectDir, "AGENTS.md");
128
+ fs.copyFileSync(sourceFile, destFile);
129
+ return true;
130
+ } catch {
131
+ return false;
132
+ }
133
+ }
186
134
  function applyReplacements(targetDir, replacements) {
187
135
  function processDirectory(dir) {
188
136
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -308,71 +256,35 @@ async function installTownsSkills(projectDir) {
308
256
  return false;
309
257
  }
310
258
  }
311
- async function downloadAgentsMd(projectDir) {
312
- const tempDir = `${projectDir}-agents-md-temp`;
259
+ function parseDotenv() {
260
+ return dotenvConfig({ override: false }).parsed;
261
+ }
262
+ function envFromAppPrivateData(parsed) {
263
+ const appPrivateData = parsed?.APP_PRIVATE_DATA;
264
+ if (!appPrivateData) {
265
+ return void 0;
266
+ }
313
267
  try {
314
- const agentsMdPath = "packages/examples/agent-quickstart/AGENTS.md";
315
- const latestSdkTag = getLatestSdkTag();
316
- if (!latestSdkTag) {
317
- return false;
318
- }
319
- const cloneResult = spawn.sync(
320
- "git",
321
- [
322
- "clone",
323
- "--no-checkout",
324
- "--depth",
325
- "1",
326
- "--sparse",
327
- "--branch",
328
- latestSdkTag,
329
- "https://github.com/towns-protocol/towns.git",
330
- tempDir
331
- ],
332
- { stdio: "pipe" }
333
- );
334
- if (cloneResult.status !== 0) {
335
- if (fs.existsSync(tempDir)) {
336
- fs.rmSync(tempDir, { recursive: true, force: true });
337
- }
338
- return false;
339
- }
340
- const sparseResult = spawn.sync("git", ["sparse-checkout", "set", agentsMdPath], {
341
- stdio: "pipe",
342
- cwd: tempDir
343
- });
344
- if (sparseResult.status !== 0) {
345
- fs.rmSync(tempDir, { recursive: true, force: true });
346
- return false;
347
- }
348
- const checkoutResult = spawn.sync("git", ["checkout"], {
349
- stdio: "pipe",
350
- cwd: tempDir
351
- });
352
- if (checkoutResult.status !== 0) {
353
- fs.rmSync(tempDir, { recursive: true, force: true });
354
- return false;
355
- }
356
- const sourceFile = path.join(tempDir, agentsMdPath);
357
- if (!fs.existsSync(sourceFile)) {
358
- fs.rmSync(tempDir, { recursive: true, force: true });
359
- return false;
360
- }
361
- const destFile = path.join(projectDir, "AGENTS.md");
362
- fs.copyFileSync(sourceFile, destFile);
363
- fs.rmSync(tempDir, { recursive: true, force: true });
364
- return true;
365
- } catch (error) {
366
- console.error(
367
- picocolors.red("Error downloading AGENTS.md:"),
368
- error instanceof Error ? error.message : error
369
- );
370
- if (fs.existsSync(tempDir)) {
371
- fs.rmSync(tempDir, { recursive: true, force: true });
372
- }
373
- return false;
268
+ return parseAppPrivateData(appPrivateData);
269
+ } catch {
270
+ return void 0;
374
271
  }
375
272
  }
273
+ function resolveAppAddress(positionalArg) {
274
+ if (positionalArg) {
275
+ return positionalArg;
276
+ }
277
+ const parsed = parseDotenv();
278
+ const appAddress = parsed?.APP_ADDRESS;
279
+ if (appAddress) {
280
+ return appAddress;
281
+ }
282
+ return envFromAppPrivateData(parsed)?.appAddress;
283
+ }
284
+ function resolveRiverEnv() {
285
+ const parsed = parseDotenv();
286
+ return parsed?.RIVER_ENV ?? envFromAppPrivateData(parsed)?.env;
287
+ }
376
288
  async function promptAuth() {
377
289
  const { method } = await prompts({
378
290
  type: "select",
@@ -400,7 +312,7 @@ var TEMPLATES = {
400
312
  quickstart: {
401
313
  name: "Agent Quickstart",
402
314
  description: "Simple starter agent with basic commands",
403
- packagePath: "agent-quickstart"
315
+ packagePath: "quickstart"
404
316
  }
405
317
  };
406
318
  async function init(argv) {
@@ -437,12 +349,12 @@ async function init(argv) {
437
349
  const packageManager = getPackageManager();
438
350
  const selectedTemplate = TEMPLATES[template];
439
351
  try {
440
- const success = await cloneTemplate(selectedTemplate.packagePath, targetDir);
352
+ const success = copyTemplate(selectedTemplate.packagePath, targetDir);
441
353
  if (!success) {
442
- console.error(red("Failed to clone template"));
354
+ console.error(red("Failed to copy template"));
443
355
  process.exit(1);
444
356
  }
445
- const latestVersion = await getLatestTownsProtocolVersion();
357
+ const latestVersion = version;
446
358
  const replacements = /* @__PURE__ */ new Map([
447
359
  ["workspace:\\^", `^${latestVersion}`],
448
360
  ["workspace:\\*", `^${latestVersion}`]
@@ -467,19 +379,12 @@ async function init(argv) {
467
379
  if (skillSuccess) {
468
380
  console.log(green("\u2713"), "Towns Agent Skills installed successfully!");
469
381
  } else {
470
- console.log(
471
- yellow("\u26A0"),
472
- "Failed to install Towns Agent Skills. You can install them later with:"
473
- );
382
+ console.log(yellow("\u26A0"), "Skipping Towns Agent Skills. Install later with:");
474
383
  console.log(yellow(` cd ${projectName} && towns-agent install-skill`));
475
384
  }
476
- } catch (error) {
477
- console.log(
478
- yellow("\u26A0"),
479
- "Error installing skills:",
480
- error instanceof Error ? error.message : error
481
- );
482
- console.log(yellow(` You can install them later with: towns-agent install-skill`));
385
+ } catch {
386
+ console.log(yellow("\u26A0"), "Skipping Towns Agent Skills. Install later with:");
387
+ console.log(yellow(` cd ${projectName} && towns-agent install-skill`));
483
388
  }
484
389
  await initializeGitRepository(targetDir);
485
390
  printSuccess(projectName, packageManager);
@@ -498,9 +403,9 @@ function getTownsVersions(packageJson) {
498
403
  const versions = {};
499
404
  for (const deps of [packageJson.dependencies, packageJson.devDependencies]) {
500
405
  if (deps) {
501
- for (const [pkg, version] of Object.entries(deps)) {
406
+ for (const [pkg, version2] of Object.entries(deps)) {
502
407
  if (pkg.startsWith("@towns-labs/") || pkg.startsWith("@towns-protocol/")) {
503
- versions[pkg] = version;
408
+ versions[pkg] = version2;
504
409
  }
505
410
  }
506
411
  }
@@ -583,7 +488,7 @@ async function update(_argv) {
583
488
  console.log();
584
489
  console.log(cyan2("Updating AGENTS.md..."));
585
490
  try {
586
- const agentsMdSuccess = await downloadAgentsMd(projectDir);
491
+ const agentsMdSuccess = copyAgentsMd(projectDir);
587
492
  if (agentsMdSuccess) {
588
493
  console.log(green2("\u2713"), "AGENTS.md updated successfully!");
589
494
  } else {
@@ -752,13 +657,17 @@ var FIELD_DEFS = [
752
657
  ];
753
658
  async function metadata(argv) {
754
659
  const subcommand = argv._[1];
755
- const appAddress = argv._[2];
660
+ const appAddress = resolveAppAddress(argv._[2]);
756
661
  if (!subcommand || !["view", "update"].includes(subcommand)) {
757
- console.error(red5("Usage: towns-agent metadata <view|update> <appAddress> [options]"));
662
+ console.error(red5("Usage: towns-agent metadata <view|update> [appAddress] [options]"));
758
663
  process.exit(1);
759
664
  }
760
665
  if (!appAddress) {
761
- console.error(red5("App address is required."));
666
+ console.error(
667
+ red5(
668
+ "App address is required. Provide it as an argument, or set APP_ADDRESS or APP_PRIVATE_DATA in .env."
669
+ )
670
+ );
762
671
  process.exit(1);
763
672
  }
764
673
  const env = townsEnv2();
@@ -859,7 +768,7 @@ async function metadata(argv) {
859
768
 
860
769
  // src/modules/setup.ts
861
770
  import { default as prompts5 } from "prompts";
862
- import { red as red6, dim as dim2, green as green4 } from "picocolors";
771
+ import { red as red6, dim as dim2, green as green4, yellow as yellow3 } from "picocolors";
863
772
  import { privateKeyToAccount as privateKeyToAccount3, generatePrivateKey as generatePrivateKey3 } from "viem/accounts";
864
773
  import {
865
774
  AppRegistryService as AppRegistryService2,
@@ -880,9 +789,13 @@ var NOTIFY_LABELS = {
880
789
  NONE: "No messages"
881
790
  };
882
791
  async function setup(argv) {
883
- const appAddress = argv._[1];
792
+ const appAddress = resolveAppAddress(argv._[1]);
884
793
  if (!appAddress) {
885
- console.error(red6("Usage: towns-agent setup <appAddress> [options]"));
794
+ console.error(
795
+ red6(
796
+ "App address is required. Provide it as an argument, or set APP_ADDRESS or APP_PRIVATE_DATA in .env."
797
+ )
798
+ );
886
799
  process.exit(1);
887
800
  }
888
801
  let ownerPrivateKey = argv.ownerPrivateKey;
@@ -899,11 +812,18 @@ async function setup(argv) {
899
812
  bearerToken = auth.value;
900
813
  }
901
814
  }
902
- const webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
815
+ let webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
903
816
  if (!webhookUrl) {
904
817
  console.error(red6("Webhook URL is required."));
905
818
  process.exit(1);
906
819
  }
820
+ if (argv.webhookUrl) {
821
+ const urlError = validateWebhookUrl(webhookUrl);
822
+ if (urlError !== true) {
823
+ console.error(red6(urlError));
824
+ process.exit(1);
825
+ }
826
+ }
907
827
  let notifyKey;
908
828
  if (argv.notify) {
909
829
  notifyKey = argv.notify.toUpperCase();
@@ -932,7 +852,26 @@ async function setup(argv) {
932
852
  signerContext,
933
853
  appRegistryUrl
934
854
  );
935
- await appRegistryRpcClient.registerWebhook({ appId, webhookUrl });
855
+ try {
856
+ await appRegistryRpcClient.registerWebhook({ appId, webhookUrl });
857
+ } catch (error) {
858
+ if (!hasWebhookPath(webhookUrl)) {
859
+ const webhookUrlWithPath = appendWebhookPath(webhookUrl);
860
+ try {
861
+ await appRegistryRpcClient.registerWebhook({
862
+ appId,
863
+ webhookUrl: webhookUrlWithPath
864
+ });
865
+ console.log();
866
+ console.log(yellow3(`Registration succeeded with ${webhookUrlWithPath}`));
867
+ webhookUrl = webhookUrlWithPath;
868
+ } catch {
869
+ throw error;
870
+ }
871
+ } else {
872
+ throw error;
873
+ }
874
+ }
936
875
  await appRegistryRpcClient.setAppSettings({
937
876
  appId,
938
877
  settings: { forwardSetting }
@@ -945,12 +884,45 @@ async function setup(argv) {
945
884
  console.log();
946
885
  process.exit(0);
947
886
  }
887
+ function hasWebhookPath(url) {
888
+ const trimmed = url.trim();
889
+ try {
890
+ const parsed = new URL(trimmed);
891
+ const normalizedPath = parsed.pathname.endsWith("/") ? parsed.pathname.slice(0, -1) : parsed.pathname;
892
+ return normalizedPath.endsWith("/webhook");
893
+ } catch {
894
+ return trimmed.replace(/\s*$/, "").endsWith("/webhook");
895
+ }
896
+ }
897
+ function appendWebhookPath(url) {
898
+ const trimmed = url.trim();
899
+ const parsed = new URL(trimmed);
900
+ const normalizedPath = parsed.pathname.endsWith("/") ? parsed.pathname.slice(0, -1) : parsed.pathname;
901
+ parsed.pathname = normalizedPath === "/" ? "/webhook" : `${normalizedPath}/webhook`;
902
+ return parsed.toString();
903
+ }
904
+ function validateWebhookUrl(url) {
905
+ const trimmed = url.trim();
906
+ if (!trimmed) {
907
+ return "Webhook URL is required";
908
+ }
909
+ try {
910
+ const parsed = new URL(trimmed);
911
+ const isLocalhost = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "0.0.0.0";
912
+ if (isLocalhost && !parsed.port) {
913
+ return "Localhost URL is missing a port. Example: http://localhost:3000";
914
+ }
915
+ return true;
916
+ } catch {
917
+ return "Invalid URL format. Example: http://localhost:3000/webhook";
918
+ }
919
+ }
948
920
  async function promptWebhookUrl() {
949
921
  const { value } = await prompts5({
950
922
  type: "text",
951
923
  name: "value",
952
924
  message: "Webhook URL",
953
- validate: (v) => v.trim() ? true : "Webhook URL is required"
925
+ validate: validateWebhookUrl
954
926
  });
955
927
  return value;
956
928
  }
@@ -1092,20 +1064,31 @@ async function main() {
1092
1064
  showHelp();
1093
1065
  return;
1094
1066
  }
1095
- if (args.env) {
1096
- process.env.RIVER_ENV = args.env;
1097
- }
1098
- if (!process.env.RIVER_ENV) {
1099
- console.error(
1100
- red7("Environment is required. Use --env <local_dev|stage|prod> or set RIVER_ENV.")
1101
- );
1102
- process.exit(1);
1103
- }
1104
- try {
1105
- townsEnv4().makeTownsConfig();
1106
- } catch {
1107
- console.error(red7(`Invalid environment: ${process.env.RIVER_ENV}`));
1108
- process.exit(1);
1067
+ const requiresEnv = ["create", "setup", "metadata"].includes(command);
1068
+ if (requiresEnv) {
1069
+ if (args.env) {
1070
+ process.env.RIVER_ENV = args.env;
1071
+ }
1072
+ if (!process.env.RIVER_ENV) {
1073
+ const resolvedEnv = resolveRiverEnv();
1074
+ if (resolvedEnv) {
1075
+ process.env.RIVER_ENV = resolvedEnv;
1076
+ }
1077
+ }
1078
+ if (!process.env.RIVER_ENV) {
1079
+ console.error(
1080
+ red7(
1081
+ "Environment is required. Use --env <local_dev|stage|prod>, set RIVER_ENV, or set APP_PRIVATE_DATA in .env."
1082
+ )
1083
+ );
1084
+ process.exit(1);
1085
+ }
1086
+ try {
1087
+ townsEnv4().makeTownsConfig();
1088
+ } catch {
1089
+ console.error(red7(`Invalid environment: ${process.env.RIVER_ENV}`));
1090
+ process.exit(1);
1091
+ }
1109
1092
  }
1110
1093
  try {
1111
1094
  switch (command) {
@@ -1153,38 +1136,40 @@ function showHelp() {
1153
1136
  console.log(`
1154
1137
  ${cyan4("towns-agent")} - CLI for creating and managing Towns Protocol agent projects
1155
1138
 
1156
- ${yellow3("Usage:")}
1139
+ ${yellow4("Usage:")}
1157
1140
  towns-agent <command> [options]
1158
1141
 
1159
- ${yellow3("Commands:")}
1142
+ ${yellow4("Commands:")}
1160
1143
  ${green5("init")} [project-name] Create a new agent project
1161
1144
  ${green5("create")} Create a new bot/app account interactively
1162
- ${green5("setup")} <appAddress> Register webhook and notification settings
1163
- ${green5("metadata")} <view|update> View or update app metadata
1145
+ ${green5("setup")} [appAddress] Register webhook and notification settings
1146
+ ${green5("metadata")} <view|update> [appAddress] View or update app metadata
1164
1147
  ${green5("update")} Update @towns-labs dependencies and skills
1165
1148
  ${green5("install-skill")} Install Towns Agent Skills to current project
1166
1149
 
1167
- ${yellow3("Init Options:")}
1150
+ ${yellow4("Init Options:")}
1168
1151
  -t, --template <name> Template to use:
1169
1152
  ${Object.entries(TEMPLATES).map(
1170
1153
  ([key, template]) => ` ${key} - ${template.description}`
1171
1154
  ).join("\n")}
1172
1155
  Default: quickstart
1173
1156
 
1174
- ${yellow3("List Commands Options:")}
1157
+ ${yellow4("List Commands Options:")}
1175
1158
  -f, --file <path> Path to commands file
1176
1159
 
1177
- ${yellow3("Update Commands Options:")}
1160
+ ${yellow4("Update Commands Options:")}
1178
1161
  -f, --file <path> Path to commands file
1179
1162
  -t, --bearerToken <token> Bearer token for authentication
1180
1163
  -e, --envFile <path> Path to .env file (default: .env)
1181
1164
  --skip-agents-md Skip updating AGENTS.md file
1182
1165
 
1183
- ${yellow3("Global Options:")}
1166
+ ${yellow4("Environment Options (create, setup, metadata):")}
1184
1167
  --env <name> Environment to use: local_dev, stage, prod
1168
+
1169
+ ${yellow4("Global Options:")}
1185
1170
  -h, --help Show this help message
1186
1171
 
1187
- ${yellow3("Examples:")}
1172
+ ${yellow4("Examples:")}
1188
1173
  ${cyan4("# Create a new agent project")}
1189
1174
  towns-agent init my-agent
1190
1175
  towns-agent init my-ai-agent --template quickstart