towns-agent 2.0.4 → 2.0.6

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,7 +1,5 @@
1
1
  // src/index.ts
2
- import { resolve as resolve2 } from "path";
3
- import { config as dotenvConfig } from "dotenv";
4
- 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";
5
3
 
6
4
  // src/modules/init.ts
7
5
  import * as fs2 from "fs";
@@ -13,9 +11,17 @@ import * as jsonc from "jsonc-parser";
13
11
  // src/modules/utils.ts
14
12
  import * as fs from "fs";
15
13
  import * as path from "path";
14
+ import { fileURLToPath } from "url";
16
15
  import { default as spawn } from "cross-spawn";
17
16
  import { default as prompts } from "prompts";
18
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.6";
23
+
24
+ // src/modules/utils.ts
19
25
  var getPackageManager = () => {
20
26
  if (process.env.npm_config_user_agent) {
21
27
  const agent = process.env.npm_config_user_agent;
@@ -79,112 +85,52 @@ function runCommand(command, args, opts = { cwd: void 0, silent: false }) {
79
85
  child.on("error", reject);
80
86
  });
81
87
  }
82
- async function getLatestTownsProtocolVersion() {
83
- return new Promise((resolve3, reject) => {
84
- const child = spawn("npm", ["view", "@towns-labs/agent", "version"], {
85
- stdio: ["ignore", "pipe", "ignore"]
86
- });
87
- let output = "";
88
- child.stdout?.on("data", (data) => {
89
- output += data.toString();
90
- });
91
- child.on("close", (code) => {
92
- if (code !== 0) {
93
- reject(new Error("Failed to fetch latest @towns-labs/agent version"));
94
- } else {
95
- resolve3(output.trim());
96
- }
97
- });
98
- child.on("error", reject);
99
- });
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");
100
95
  }
101
- function getLatestSdkTag() {
102
- const tagsResult = spawn.sync(
103
- "git",
104
- ["ls-remote", "--tags", "https://github.com/HereNotThere/chat.git", "@towns-labs/sdk@*"],
105
- { encoding: "utf8" }
106
- );
107
- if (tagsResult.status !== 0 || !tagsResult.stdout) return null;
108
- const tags = tagsResult.stdout.split("\n").filter(Boolean).map((line) => {
109
- const [_hash, ref] = line.split(" ");
110
- const tag = ref.replace("refs/tags/", "").replace(/\^{}$/, "");
111
- const match = tag.match(/^@towns-protocol\/sdk@(\d+)\.(\d+)\.(\d+)$/);
112
- if (!match) return null;
113
- return {
114
- tag,
115
- version: [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])]
116
- };
117
- }).filter(
118
- (item) => item !== null && Array.isArray(item.version) && item.version.length === 3
119
- ).sort((a, b) => {
120
- for (let i = 0; i < 3; i++) {
121
- if (a.version[i] !== b.version[i]) {
122
- return b.version[i] - a.version[i];
123
- }
124
- }
125
- return 0;
126
- });
127
- return tags.length > 0 ? tags[0].tag : null;
128
- }
129
- async function cloneTemplate(packagePath, targetDir) {
130
- console.log(picocolors.blue("Cloning template from GitHub..."));
131
- const tempDir = `${targetDir}-temp`;
132
- const fullTemplatePath = `packages/examples/${packagePath}`;
133
- const latestSdkTag = getLatestSdkTag();
134
- if (!latestSdkTag) {
135
- console.error(picocolors.red("Failed to get latest SDK tag."));
136
- return false;
137
- }
138
- const cloneResult = spawn.sync(
139
- "git",
140
- [
141
- "clone",
142
- "--no-checkout",
143
- "--depth",
144
- "1",
145
- "--sparse",
146
- "--branch",
147
- latestSdkTag,
148
- "https://github.com/towns-protocol/towns.git",
149
- tempDir
150
- ],
151
- { stdio: "pipe" }
152
- );
153
- if (cloneResult.status !== 0) return false;
154
- const sparseResult = spawn.sync("git", ["sparse-checkout", "set", fullTemplatePath], {
155
- stdio: "pipe",
156
- cwd: tempDir
157
- });
158
- if (sparseResult.status !== 0) {
159
- fs.rmSync(tempDir, { recursive: true, force: true });
160
- return false;
161
- }
162
- const checkoutResult = spawn.sync("git", ["checkout"], {
163
- stdio: "pipe",
164
- cwd: tempDir
165
- });
166
- if (checkoutResult.status !== 0) {
167
- fs.rmSync(tempDir, { recursive: true, force: true });
168
- return false;
169
- }
170
- 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);
171
100
  if (!fs.existsSync(sourceDir)) {
172
- console.error(picocolors.red(`
173
- Template directory not found at ${sourceDir}`));
174
- fs.rmSync(tempDir, { recursive: true, force: true });
101
+ console.error(picocolors.red(`Template "${templateName}" not found at ${sourceDir}`));
175
102
  return false;
176
103
  }
177
104
  fs.mkdirSync(targetDir, { recursive: true });
178
105
  fs.cpSync(sourceDir, targetDir, {
179
106
  recursive: true,
180
- filter: () => {
181
- return true;
107
+ filter: (source) => {
108
+ const basename2 = path.basename(source);
109
+ return basename2 !== "node_modules" && basename2 !== "dist";
182
110
  }
183
111
  });
184
- fs.rmSync(tempDir, { recursive: true, force: true });
185
- 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!");
186
118
  return true;
187
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
+ }
188
134
  function applyReplacements(targetDir, replacements) {
189
135
  function processDirectory(dir) {
190
136
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -310,71 +256,35 @@ async function installTownsSkills(projectDir) {
310
256
  return false;
311
257
  }
312
258
  }
313
- async function downloadAgentsMd(projectDir) {
314
- 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
+ }
315
267
  try {
316
- const agentsMdPath = "packages/examples/agent-quickstart/AGENTS.md";
317
- const latestSdkTag = getLatestSdkTag();
318
- if (!latestSdkTag) {
319
- return false;
320
- }
321
- const cloneResult = spawn.sync(
322
- "git",
323
- [
324
- "clone",
325
- "--no-checkout",
326
- "--depth",
327
- "1",
328
- "--sparse",
329
- "--branch",
330
- latestSdkTag,
331
- "https://github.com/towns-protocol/towns.git",
332
- tempDir
333
- ],
334
- { stdio: "pipe" }
335
- );
336
- if (cloneResult.status !== 0) {
337
- if (fs.existsSync(tempDir)) {
338
- fs.rmSync(tempDir, { recursive: true, force: true });
339
- }
340
- return false;
341
- }
342
- const sparseResult = spawn.sync("git", ["sparse-checkout", "set", agentsMdPath], {
343
- stdio: "pipe",
344
- cwd: tempDir
345
- });
346
- if (sparseResult.status !== 0) {
347
- fs.rmSync(tempDir, { recursive: true, force: true });
348
- return false;
349
- }
350
- const checkoutResult = spawn.sync("git", ["checkout"], {
351
- stdio: "pipe",
352
- cwd: tempDir
353
- });
354
- if (checkoutResult.status !== 0) {
355
- fs.rmSync(tempDir, { recursive: true, force: true });
356
- return false;
357
- }
358
- const sourceFile = path.join(tempDir, agentsMdPath);
359
- if (!fs.existsSync(sourceFile)) {
360
- fs.rmSync(tempDir, { recursive: true, force: true });
361
- return false;
362
- }
363
- const destFile = path.join(projectDir, "AGENTS.md");
364
- fs.copyFileSync(sourceFile, destFile);
365
- fs.rmSync(tempDir, { recursive: true, force: true });
366
- return true;
367
- } catch (error) {
368
- console.error(
369
- picocolors.red("Error downloading AGENTS.md:"),
370
- error instanceof Error ? error.message : error
371
- );
372
- if (fs.existsSync(tempDir)) {
373
- fs.rmSync(tempDir, { recursive: true, force: true });
374
- }
375
- return false;
268
+ return parseAppPrivateData(appPrivateData);
269
+ } catch {
270
+ return void 0;
376
271
  }
377
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
+ }
378
288
  async function promptAuth() {
379
289
  const { method } = await prompts({
380
290
  type: "select",
@@ -402,7 +312,7 @@ var TEMPLATES = {
402
312
  quickstart: {
403
313
  name: "Agent Quickstart",
404
314
  description: "Simple starter agent with basic commands",
405
- packagePath: "agent-quickstart"
315
+ packagePath: "quickstart"
406
316
  }
407
317
  };
408
318
  async function init(argv) {
@@ -439,12 +349,12 @@ async function init(argv) {
439
349
  const packageManager = getPackageManager();
440
350
  const selectedTemplate = TEMPLATES[template];
441
351
  try {
442
- const success = await cloneTemplate(selectedTemplate.packagePath, targetDir);
352
+ const success = copyTemplate(selectedTemplate.packagePath, targetDir);
443
353
  if (!success) {
444
- console.error(red("Failed to clone template"));
354
+ console.error(red("Failed to copy template"));
445
355
  process.exit(1);
446
356
  }
447
- const latestVersion = await getLatestTownsProtocolVersion();
357
+ const latestVersion = version;
448
358
  const replacements = /* @__PURE__ */ new Map([
449
359
  ["workspace:\\^", `^${latestVersion}`],
450
360
  ["workspace:\\*", `^${latestVersion}`]
@@ -469,19 +379,12 @@ async function init(argv) {
469
379
  if (skillSuccess) {
470
380
  console.log(green("\u2713"), "Towns Agent Skills installed successfully!");
471
381
  } else {
472
- console.log(
473
- yellow("\u26A0"),
474
- "Failed to install Towns Agent Skills. You can install them later with:"
475
- );
382
+ console.log(yellow("\u26A0"), "Skipping Towns Agent Skills. Install later with:");
476
383
  console.log(yellow(` cd ${projectName} && towns-agent install-skill`));
477
384
  }
478
- } catch (error) {
479
- console.log(
480
- yellow("\u26A0"),
481
- "Error installing skills:",
482
- error instanceof Error ? error.message : error
483
- );
484
- 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`));
485
388
  }
486
389
  await initializeGitRepository(targetDir);
487
390
  printSuccess(projectName, packageManager);
@@ -500,9 +403,9 @@ function getTownsVersions(packageJson) {
500
403
  const versions = {};
501
404
  for (const deps of [packageJson.dependencies, packageJson.devDependencies]) {
502
405
  if (deps) {
503
- for (const [pkg, version] of Object.entries(deps)) {
406
+ for (const [pkg, version2] of Object.entries(deps)) {
504
407
  if (pkg.startsWith("@towns-labs/") || pkg.startsWith("@towns-protocol/")) {
505
- versions[pkg] = version;
408
+ versions[pkg] = version2;
506
409
  }
507
410
  }
508
411
  }
@@ -585,7 +488,7 @@ async function update(_argv) {
585
488
  console.log();
586
489
  console.log(cyan2("Updating AGENTS.md..."));
587
490
  try {
588
- const agentsMdSuccess = await downloadAgentsMd(projectDir);
491
+ const agentsMdSuccess = copyAgentsMd(projectDir);
589
492
  if (agentsMdSuccess) {
590
493
  console.log(green2("\u2713"), "AGENTS.md updated successfully!");
591
494
  } else {
@@ -688,7 +591,7 @@ async function create(argv) {
688
591
  if (!addresses?.accountProxy) {
689
592
  throw new Error(`No accountProxy address found for ${townsConfig.environmentId}/${chainId}`);
690
593
  }
691
- const relayerUrl = process.env.RELAYER_URL ?? "http://127.0.0.1:8787";
594
+ const relayerUrl = townsConfig.services.relayer.url;
692
595
  const relayerClient = createPublicClient({
693
596
  chain: {
694
597
  id: chainId,
@@ -754,13 +657,17 @@ var FIELD_DEFS = [
754
657
  ];
755
658
  async function metadata(argv) {
756
659
  const subcommand = argv._[1];
757
- const appAddress = argv._[2];
660
+ const appAddress = resolveAppAddress(argv._[2]);
758
661
  if (!subcommand || !["view", "update"].includes(subcommand)) {
759
- console.error(red5("Usage: towns-agent metadata <view|update> <appAddress> [options]"));
662
+ console.error(red5("Usage: towns-agent metadata <view|update> [appAddress] [options]"));
760
663
  process.exit(1);
761
664
  }
762
665
  if (!appAddress) {
763
- 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
+ );
764
671
  process.exit(1);
765
672
  }
766
673
  const env = townsEnv2();
@@ -861,7 +768,7 @@ async function metadata(argv) {
861
768
 
862
769
  // src/modules/setup.ts
863
770
  import { default as prompts5 } from "prompts";
864
- 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";
865
772
  import { privateKeyToAccount as privateKeyToAccount3, generatePrivateKey as generatePrivateKey3 } from "viem/accounts";
866
773
  import {
867
774
  AppRegistryService as AppRegistryService2,
@@ -882,9 +789,13 @@ var NOTIFY_LABELS = {
882
789
  NONE: "No messages"
883
790
  };
884
791
  async function setup(argv) {
885
- const appAddress = argv._[1];
792
+ const appAddress = resolveAppAddress(argv._[1]);
886
793
  if (!appAddress) {
887
- 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
+ );
888
799
  process.exit(1);
889
800
  }
890
801
  let ownerPrivateKey = argv.ownerPrivateKey;
@@ -901,11 +812,18 @@ async function setup(argv) {
901
812
  bearerToken = auth.value;
902
813
  }
903
814
  }
904
- const webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
815
+ let webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
905
816
  if (!webhookUrl) {
906
817
  console.error(red6("Webhook URL is required."));
907
818
  process.exit(1);
908
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
+ }
909
827
  let notifyKey;
910
828
  if (argv.notify) {
911
829
  notifyKey = argv.notify.toUpperCase();
@@ -934,7 +852,26 @@ async function setup(argv) {
934
852
  signerContext,
935
853
  appRegistryUrl
936
854
  );
937
- 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
+ }
938
875
  await appRegistryRpcClient.setAppSettings({
939
876
  appId,
940
877
  settings: { forwardSetting }
@@ -947,12 +884,48 @@ async function setup(argv) {
947
884
  console.log();
948
885
  process.exit(0);
949
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.protocol === "http:") {
913
+ return "Local bots must use HTTPS. Use https:// instead of http://";
914
+ }
915
+ if (isLocalhost && !parsed.port) {
916
+ return "Localhost URL is missing a port. Example: https://localhost:3000";
917
+ }
918
+ return true;
919
+ } catch {
920
+ return "Invalid URL format. Example: https://localhost:3000/webhook";
921
+ }
922
+ }
950
923
  async function promptWebhookUrl() {
951
924
  const { value } = await prompts5({
952
925
  type: "text",
953
926
  name: "value",
954
927
  message: "Webhook URL",
955
- validate: (v) => v.trim() ? true : "Webhook URL is required"
928
+ validate: validateWebhookUrl
956
929
  });
957
930
  return value;
958
931
  }
@@ -974,6 +947,9 @@ async function promptNotify() {
974
947
  return value;
975
948
  }
976
949
 
950
+ // src/index.ts
951
+ import { townsEnv as townsEnv4 } from "@towns-labs/sdk";
952
+
977
953
  // src/parser.ts
978
954
  import minimist from "minimist";
979
955
  var COMMAND_CONFIGS = {
@@ -1042,6 +1018,7 @@ var COMMAND_CONFIGS = {
1042
1018
  function parseArgs(args) {
1043
1019
  const initial = minimist(args, {
1044
1020
  stopEarly: true,
1021
+ string: ["env"],
1045
1022
  boolean: ["help"],
1046
1023
  alias: { h: "help" }
1047
1024
  });
@@ -1051,8 +1028,10 @@ function parseArgs(args) {
1051
1028
  }
1052
1029
  const commandConfig = COMMAND_CONFIGS[command] || {};
1053
1030
  const booleanOptions = Array.isArray(commandConfig.boolean) ? ["help", ...commandConfig.boolean] : ["help"];
1031
+ const stringOptions = Array.isArray(commandConfig.string) ? ["env", ...commandConfig.string] : ["env"];
1054
1032
  const parsed = minimist(args, {
1055
1033
  ...commandConfig,
1034
+ string: stringOptions,
1056
1035
  boolean: booleanOptions,
1057
1036
  alias: {
1058
1037
  ...commandConfig.alias,
@@ -1081,8 +1060,6 @@ function isSetupArgs(args) {
1081
1060
  }
1082
1061
 
1083
1062
  // src/index.ts
1084
- dotenvConfig({ path: resolve2(__dirname, "../../generated/deployments/local_dev/.env") });
1085
- dotenvConfig({ path: resolve2(__dirname, "../../contracts/deployments/envs/local/.env") });
1086
1063
  async function main() {
1087
1064
  const args = parseArgs(process.argv.slice(2));
1088
1065
  const command = args._[0];
@@ -1090,6 +1067,32 @@ async function main() {
1090
1067
  showHelp();
1091
1068
  return;
1092
1069
  }
1070
+ const requiresEnv = ["create", "setup", "metadata"].includes(command);
1071
+ if (requiresEnv) {
1072
+ if (args.env) {
1073
+ process.env.RIVER_ENV = args.env;
1074
+ }
1075
+ if (!process.env.RIVER_ENV) {
1076
+ const resolvedEnv = resolveRiverEnv();
1077
+ if (resolvedEnv) {
1078
+ process.env.RIVER_ENV = resolvedEnv;
1079
+ }
1080
+ }
1081
+ if (!process.env.RIVER_ENV) {
1082
+ console.error(
1083
+ red7(
1084
+ "Environment is required. Use --env <local_dev|stage|prod>, set RIVER_ENV, or set APP_PRIVATE_DATA in .env."
1085
+ )
1086
+ );
1087
+ process.exit(1);
1088
+ }
1089
+ try {
1090
+ townsEnv4().makeTownsConfig();
1091
+ } catch {
1092
+ console.error(red7(`Invalid environment: ${process.env.RIVER_ENV}`));
1093
+ process.exit(1);
1094
+ }
1095
+ }
1093
1096
  try {
1094
1097
  switch (command) {
1095
1098
  case "init":
@@ -1136,37 +1139,40 @@ function showHelp() {
1136
1139
  console.log(`
1137
1140
  ${cyan4("towns-agent")} - CLI for creating and managing Towns Protocol agent projects
1138
1141
 
1139
- ${yellow3("Usage:")}
1142
+ ${yellow4("Usage:")}
1140
1143
  towns-agent <command> [options]
1141
1144
 
1142
- ${yellow3("Commands:")}
1145
+ ${yellow4("Commands:")}
1143
1146
  ${green5("init")} [project-name] Create a new agent project
1144
1147
  ${green5("create")} Create a new bot/app account interactively
1145
- ${green5("setup")} <appAddress> Register webhook and notification settings
1146
- ${green5("metadata")} <view|update> View or update app metadata
1148
+ ${green5("setup")} [appAddress] Register webhook and notification settings
1149
+ ${green5("metadata")} <view|update> [appAddress] View or update app metadata
1147
1150
  ${green5("update")} Update @towns-labs dependencies and skills
1148
1151
  ${green5("install-skill")} Install Towns Agent Skills to current project
1149
1152
 
1150
- ${yellow3("Init Options:")}
1153
+ ${yellow4("Init Options:")}
1151
1154
  -t, --template <name> Template to use:
1152
1155
  ${Object.entries(TEMPLATES).map(
1153
1156
  ([key, template]) => ` ${key} - ${template.description}`
1154
1157
  ).join("\n")}
1155
1158
  Default: quickstart
1156
1159
 
1157
- ${yellow3("List Commands Options:")}
1160
+ ${yellow4("List Commands Options:")}
1158
1161
  -f, --file <path> Path to commands file
1159
1162
 
1160
- ${yellow3("Update Commands Options:")}
1163
+ ${yellow4("Update Commands Options:")}
1161
1164
  -f, --file <path> Path to commands file
1162
1165
  -t, --bearerToken <token> Bearer token for authentication
1163
1166
  -e, --envFile <path> Path to .env file (default: .env)
1164
1167
  --skip-agents-md Skip updating AGENTS.md file
1165
1168
 
1166
- ${yellow3("Global Options:")}
1169
+ ${yellow4("Environment Options (create, setup, metadata):")}
1170
+ --env <name> Environment to use: local_dev, stage, prod
1171
+
1172
+ ${yellow4("Global Options:")}
1167
1173
  -h, --help Show this help message
1168
1174
 
1169
- ${yellow3("Examples:")}
1175
+ ${yellow4("Examples:")}
1170
1176
  ${cyan4("# Create a new agent project")}
1171
1177
  towns-agent init my-agent
1172
1178
  towns-agent init my-ai-agent --template quickstart