towns-agent 2.0.5 → 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.js CHANGED
@@ -35,9 +35,18 @@ var jsonc = __toESM(require("jsonc-parser"));
35
35
  // src/modules/utils.ts
36
36
  var fs = __toESM(require("fs"));
37
37
  var path = __toESM(require("path"));
38
+ var import_node_url = require("url");
38
39
  var import_cross_spawn = __toESM(require("cross-spawn"));
39
40
  var import_prompts = __toESM(require("prompts"));
40
41
  var import_picocolors = __toESM(require("picocolors"));
42
+ var import_dotenv = require("dotenv");
43
+ var import_sdk = require("@towns-labs/sdk");
44
+
45
+ // package.json
46
+ var version = "2.0.6";
47
+
48
+ // src/modules/utils.ts
49
+ var import_meta = {};
41
50
  var getPackageManager = () => {
42
51
  if (process.env.npm_config_user_agent) {
43
52
  const agent = process.env.npm_config_user_agent;
@@ -85,7 +94,7 @@ function getDlxCommand(packageManager) {
85
94
  }
86
95
  }
87
96
  function runCommand(command, args, opts = { cwd: void 0, silent: false }) {
88
- return new Promise((resolve2, reject) => {
97
+ return new Promise((resolve3, reject) => {
89
98
  const child = (0, import_cross_spawn.default)(command, args, {
90
99
  stdio: opts.silent ? "ignore" : "inherit",
91
100
  cwd: opts.cwd,
@@ -95,118 +104,58 @@ function runCommand(command, args, opts = { cwd: void 0, silent: false }) {
95
104
  if (code !== 0) {
96
105
  reject(new Error(`Command failed with exit code ${code}`));
97
106
  } else {
98
- resolve2();
99
- }
100
- });
101
- child.on("error", reject);
102
- });
103
- }
104
- async function getLatestTownsProtocolVersion() {
105
- return new Promise((resolve2, reject) => {
106
- const child = (0, import_cross_spawn.default)("npm", ["view", "@towns-labs/agent", "version"], {
107
- stdio: ["ignore", "pipe", "ignore"]
108
- });
109
- let output = "";
110
- child.stdout?.on("data", (data) => {
111
- output += data.toString();
112
- });
113
- child.on("close", (code) => {
114
- if (code !== 0) {
115
- reject(new Error("Failed to fetch latest @towns-labs/agent version"));
116
- } else {
117
- resolve2(output.trim());
107
+ resolve3();
118
108
  }
119
109
  });
120
110
  child.on("error", reject);
121
111
  });
122
112
  }
123
- function getLatestSdkTag() {
124
- const tagsResult = import_cross_spawn.default.sync(
125
- "git",
126
- ["ls-remote", "--tags", "https://github.com/HereNotThere/chat.git", "@towns-labs/sdk@*"],
127
- { encoding: "utf8" }
128
- );
129
- if (tagsResult.status !== 0 || !tagsResult.stdout) return null;
130
- const tags = tagsResult.stdout.split("\n").filter(Boolean).map((line) => {
131
- const [_hash, ref] = line.split(" ");
132
- const tag = ref.replace("refs/tags/", "").replace(/\^{}$/, "");
133
- const match = tag.match(/^@towns-protocol\/sdk@(\d+)\.(\d+)\.(\d+)$/);
134
- if (!match) return null;
135
- return {
136
- tag,
137
- version: [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])]
138
- };
139
- }).filter(
140
- (item) => item !== null && Array.isArray(item.version) && item.version.length === 3
141
- ).sort((a, b) => {
142
- for (let i = 0; i < 3; i++) {
143
- if (a.version[i] !== b.version[i]) {
144
- return b.version[i] - a.version[i];
145
- }
146
- }
147
- return 0;
148
- });
149
- return tags.length > 0 ? tags[0].tag : null;
113
+ function getTemplatesDir() {
114
+ const currentDir = typeof __dirname !== "undefined" ? __dirname : path.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
115
+ const fromDist = path.resolve(currentDir, "..", "templates");
116
+ if (fs.existsSync(fromDist)) return fromDist;
117
+ const fromSrc = path.resolve(currentDir, "..", "..", "templates");
118
+ if (fs.existsSync(fromSrc)) return fromSrc;
119
+ throw new Error("Templates directory not found");
150
120
  }
151
- async function cloneTemplate(packagePath, targetDir) {
152
- console.log(import_picocolors.default.blue("Cloning template from GitHub..."));
153
- const tempDir = `${targetDir}-temp`;
154
- const fullTemplatePath = `packages/examples/${packagePath}`;
155
- const latestSdkTag = getLatestSdkTag();
156
- if (!latestSdkTag) {
157
- console.error(import_picocolors.default.red("Failed to get latest SDK tag."));
158
- return false;
159
- }
160
- const cloneResult = import_cross_spawn.default.sync(
161
- "git",
162
- [
163
- "clone",
164
- "--no-checkout",
165
- "--depth",
166
- "1",
167
- "--sparse",
168
- "--branch",
169
- latestSdkTag,
170
- "https://github.com/towns-protocol/towns.git",
171
- tempDir
172
- ],
173
- { stdio: "pipe" }
174
- );
175
- if (cloneResult.status !== 0) return false;
176
- const sparseResult = import_cross_spawn.default.sync("git", ["sparse-checkout", "set", fullTemplatePath], {
177
- stdio: "pipe",
178
- cwd: tempDir
179
- });
180
- if (sparseResult.status !== 0) {
181
- fs.rmSync(tempDir, { recursive: true, force: true });
182
- return false;
183
- }
184
- const checkoutResult = import_cross_spawn.default.sync("git", ["checkout"], {
185
- stdio: "pipe",
186
- cwd: tempDir
187
- });
188
- if (checkoutResult.status !== 0) {
189
- fs.rmSync(tempDir, { recursive: true, force: true });
190
- return false;
191
- }
192
- const sourceDir = path.join(tempDir, fullTemplatePath);
121
+ function copyTemplate(templateName, targetDir) {
122
+ console.log(import_picocolors.default.blue("Copying template..."));
123
+ const templatesDir = getTemplatesDir();
124
+ const sourceDir = path.join(templatesDir, templateName);
193
125
  if (!fs.existsSync(sourceDir)) {
194
- console.error(import_picocolors.default.red(`
195
- Template directory not found at ${sourceDir}`));
196
- fs.rmSync(tempDir, { recursive: true, force: true });
126
+ console.error(import_picocolors.default.red(`Template "${templateName}" not found at ${sourceDir}`));
197
127
  return false;
198
128
  }
199
129
  fs.mkdirSync(targetDir, { recursive: true });
200
130
  fs.cpSync(sourceDir, targetDir, {
201
131
  recursive: true,
202
- filter: () => {
203
- return true;
132
+ filter: (source) => {
133
+ const basename2 = path.basename(source);
134
+ return basename2 !== "node_modules" && basename2 !== "dist";
204
135
  }
205
136
  });
206
- fs.rmSync(tempDir, { recursive: true, force: true });
207
- console.log(import_picocolors.default.green("\u2713"), "Template cloned successfully!");
137
+ const gitignoreSrc = path.join(targetDir, "_gitignore");
138
+ const gitignoreDest = path.join(targetDir, ".gitignore");
139
+ if (fs.existsSync(gitignoreSrc)) {
140
+ fs.renameSync(gitignoreSrc, gitignoreDest);
141
+ }
142
+ console.log(import_picocolors.default.green("\u2713"), "Template copied successfully!");
208
143
  return true;
209
144
  }
145
+ function copyAgentsMd(projectDir) {
146
+ try {
147
+ const templatesDir = getTemplatesDir();
148
+ const sourceFile = path.join(templatesDir, "quickstart", "AGENTS.md");
149
+ if (!fs.existsSync(sourceFile)) {
150
+ return false;
151
+ }
152
+ const destFile = path.join(projectDir, "AGENTS.md");
153
+ fs.copyFileSync(sourceFile, destFile);
154
+ return true;
155
+ } catch {
156
+ return false;
157
+ }
158
+ }
210
159
  function applyReplacements(targetDir, replacements) {
211
160
  function processDirectory(dir) {
212
161
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -332,70 +281,34 @@ async function installTownsSkills(projectDir) {
332
281
  return false;
333
282
  }
334
283
  }
335
- async function downloadAgentsMd(projectDir) {
336
- const tempDir = `${projectDir}-agents-md-temp`;
284
+ function parseDotenv() {
285
+ return (0, import_dotenv.config)({ override: false }).parsed;
286
+ }
287
+ function envFromAppPrivateData(parsed) {
288
+ const appPrivateData = parsed?.APP_PRIVATE_DATA;
289
+ if (!appPrivateData) {
290
+ return void 0;
291
+ }
337
292
  try {
338
- const agentsMdPath = "packages/examples/agent-quickstart/AGENTS.md";
339
- const latestSdkTag = getLatestSdkTag();
340
- if (!latestSdkTag) {
341
- return false;
342
- }
343
- const cloneResult = import_cross_spawn.default.sync(
344
- "git",
345
- [
346
- "clone",
347
- "--no-checkout",
348
- "--depth",
349
- "1",
350
- "--sparse",
351
- "--branch",
352
- latestSdkTag,
353
- "https://github.com/towns-protocol/towns.git",
354
- tempDir
355
- ],
356
- { stdio: "pipe" }
357
- );
358
- if (cloneResult.status !== 0) {
359
- if (fs.existsSync(tempDir)) {
360
- fs.rmSync(tempDir, { recursive: true, force: true });
361
- }
362
- return false;
363
- }
364
- const sparseResult = import_cross_spawn.default.sync("git", ["sparse-checkout", "set", agentsMdPath], {
365
- stdio: "pipe",
366
- cwd: tempDir
367
- });
368
- if (sparseResult.status !== 0) {
369
- fs.rmSync(tempDir, { recursive: true, force: true });
370
- return false;
371
- }
372
- const checkoutResult = import_cross_spawn.default.sync("git", ["checkout"], {
373
- stdio: "pipe",
374
- cwd: tempDir
375
- });
376
- if (checkoutResult.status !== 0) {
377
- fs.rmSync(tempDir, { recursive: true, force: true });
378
- return false;
379
- }
380
- const sourceFile = path.join(tempDir, agentsMdPath);
381
- if (!fs.existsSync(sourceFile)) {
382
- fs.rmSync(tempDir, { recursive: true, force: true });
383
- return false;
384
- }
385
- const destFile = path.join(projectDir, "AGENTS.md");
386
- fs.copyFileSync(sourceFile, destFile);
387
- fs.rmSync(tempDir, { recursive: true, force: true });
388
- return true;
389
- } catch (error) {
390
- console.error(
391
- import_picocolors.default.red("Error downloading AGENTS.md:"),
392
- error instanceof Error ? error.message : error
393
- );
394
- if (fs.existsSync(tempDir)) {
395
- fs.rmSync(tempDir, { recursive: true, force: true });
396
- }
397
- return false;
293
+ return (0, import_sdk.parseAppPrivateData)(appPrivateData);
294
+ } catch {
295
+ return void 0;
296
+ }
297
+ }
298
+ function resolveAppAddress(positionalArg) {
299
+ if (positionalArg) {
300
+ return positionalArg;
301
+ }
302
+ const parsed = parseDotenv();
303
+ const appAddress = parsed?.APP_ADDRESS;
304
+ if (appAddress) {
305
+ return appAddress;
398
306
  }
307
+ return envFromAppPrivateData(parsed)?.appAddress;
308
+ }
309
+ function resolveRiverEnv() {
310
+ const parsed = parseDotenv();
311
+ return parsed?.RIVER_ENV ?? envFromAppPrivateData(parsed)?.env;
399
312
  }
400
313
  async function promptAuth() {
401
314
  const { method } = await (0, import_prompts.default)({
@@ -424,7 +337,7 @@ var TEMPLATES = {
424
337
  quickstart: {
425
338
  name: "Agent Quickstart",
426
339
  description: "Simple starter agent with basic commands",
427
- packagePath: "agent-quickstart"
340
+ packagePath: "quickstart"
428
341
  }
429
342
  };
430
343
  async function init(argv) {
@@ -461,12 +374,12 @@ async function init(argv) {
461
374
  const packageManager = getPackageManager();
462
375
  const selectedTemplate = TEMPLATES[template];
463
376
  try {
464
- const success = await cloneTemplate(selectedTemplate.packagePath, targetDir);
377
+ const success = copyTemplate(selectedTemplate.packagePath, targetDir);
465
378
  if (!success) {
466
- console.error((0, import_picocolors2.red)("Failed to clone template"));
379
+ console.error((0, import_picocolors2.red)("Failed to copy template"));
467
380
  process.exit(1);
468
381
  }
469
- const latestVersion = await getLatestTownsProtocolVersion();
382
+ const latestVersion = version;
470
383
  const replacements = /* @__PURE__ */ new Map([
471
384
  ["workspace:\\^", `^${latestVersion}`],
472
385
  ["workspace:\\*", `^${latestVersion}`]
@@ -491,19 +404,12 @@ async function init(argv) {
491
404
  if (skillSuccess) {
492
405
  console.log((0, import_picocolors2.green)("\u2713"), "Towns Agent Skills installed successfully!");
493
406
  } else {
494
- console.log(
495
- (0, import_picocolors2.yellow)("\u26A0"),
496
- "Failed to install Towns Agent Skills. You can install them later with:"
497
- );
407
+ console.log((0, import_picocolors2.yellow)("\u26A0"), "Skipping Towns Agent Skills. Install later with:");
498
408
  console.log((0, import_picocolors2.yellow)(` cd ${projectName} && towns-agent install-skill`));
499
409
  }
500
- } catch (error) {
501
- console.log(
502
- (0, import_picocolors2.yellow)("\u26A0"),
503
- "Error installing skills:",
504
- error instanceof Error ? error.message : error
505
- );
506
- console.log((0, import_picocolors2.yellow)(` You can install them later with: towns-agent install-skill`));
410
+ } catch {
411
+ console.log((0, import_picocolors2.yellow)("\u26A0"), "Skipping Towns Agent Skills. Install later with:");
412
+ console.log((0, import_picocolors2.yellow)(` cd ${projectName} && towns-agent install-skill`));
507
413
  }
508
414
  await initializeGitRepository(targetDir);
509
415
  printSuccess(projectName, packageManager);
@@ -522,9 +428,9 @@ function getTownsVersions(packageJson) {
522
428
  const versions = {};
523
429
  for (const deps of [packageJson.dependencies, packageJson.devDependencies]) {
524
430
  if (deps) {
525
- for (const [pkg, version] of Object.entries(deps)) {
431
+ for (const [pkg, version2] of Object.entries(deps)) {
526
432
  if (pkg.startsWith("@towns-labs/") || pkg.startsWith("@towns-protocol/")) {
527
- versions[pkg] = version;
433
+ versions[pkg] = version2;
528
434
  }
529
435
  }
530
436
  }
@@ -607,7 +513,7 @@ async function update(_argv) {
607
513
  console.log();
608
514
  console.log((0, import_picocolors3.cyan)("Updating AGENTS.md..."));
609
515
  try {
610
- const agentsMdSuccess = await downloadAgentsMd(projectDir);
516
+ const agentsMdSuccess = copyAgentsMd(projectDir);
611
517
  if (agentsMdSuccess) {
612
518
  console.log((0, import_picocolors3.green)("\u2713"), "AGENTS.md updated successfully!");
613
519
  } else {
@@ -661,7 +567,7 @@ var import_picocolors5 = require("picocolors");
661
567
  var import_viem = require("viem");
662
568
  var import_accounts = require("viem/accounts");
663
569
  var import_relayer_client = require("@towns-labs/relayer-client");
664
- var import_sdk = require("@towns-labs/sdk");
570
+ var import_sdk2 = require("@towns-labs/sdk");
665
571
  var import_deployments = require("@towns-labs/contracts/deployments");
666
572
  async function create(argv) {
667
573
  let ownerPrivateKey = argv.ownerPrivateKey;
@@ -694,12 +600,12 @@ async function create(argv) {
694
600
  process.exit(1);
695
601
  }
696
602
  const imageUrl = argv.imageUrl ?? await promptText("Bot image URL (optional)", true);
697
- const owner = ownerPrivateKey ? await (0, import_sdk.makeSignerContextFromViem)(
603
+ const owner = ownerPrivateKey ? await (0, import_sdk2.makeSignerContextFromViem)(
698
604
  (0, import_accounts.privateKeyToAccount)(ownerPrivateKey),
699
605
  (0, import_accounts.generatePrivateKey)(),
700
606
  { days: 1 }
701
- ) : await (0, import_sdk.makeSignerContextFromBearerToken)(bearerToken);
702
- const townsConfig = (0, import_sdk.townsEnv)().makeTownsConfig();
607
+ ) : await (0, import_sdk2.makeSignerContextFromBearerToken)(bearerToken);
608
+ const townsConfig = (0, import_sdk2.townsEnv)().makeTownsConfig();
703
609
  const chainId = townsConfig.base.chainConfig.chainId;
704
610
  const addresses = (0, import_deployments.getAddressesWithFallback)(townsConfig.environmentId, chainId);
705
611
  if (!addresses?.accountProxy) {
@@ -715,7 +621,7 @@ async function create(argv) {
715
621
  },
716
622
  transport: (0, import_viem.http)(townsConfig.base.rpcUrl)
717
623
  }).extend((0, import_relayer_client.relayerActions)({ relayerUrl }));
718
- const result = await (0, import_sdk.createApp)({
624
+ const result = await (0, import_sdk2.createApp)({
719
625
  owner,
720
626
  metadata: {
721
627
  username,
@@ -747,7 +653,7 @@ async function promptText(message, optional = false) {
747
653
  var import_prompts4 = __toESM(require("prompts"));
748
654
  var import_picocolors6 = require("picocolors");
749
655
  var import_accounts2 = require("viem/accounts");
750
- var import_sdk2 = require("@towns-labs/sdk");
656
+ var import_sdk3 = require("@towns-labs/sdk");
751
657
  var import_utils5 = require("@towns-labs/utils");
752
658
  var FIELD_DEFS = [
753
659
  { flag: "username", label: "USERNAME", mask: "username", prompt: "Username" },
@@ -765,21 +671,25 @@ var FIELD_DEFS = [
765
671
  ];
766
672
  async function metadata(argv) {
767
673
  const subcommand = argv._[1];
768
- const appAddress = argv._[2];
674
+ const appAddress = resolveAppAddress(argv._[2]);
769
675
  if (!subcommand || !["view", "update"].includes(subcommand)) {
770
- console.error((0, import_picocolors6.red)("Usage: towns-agent metadata <view|update> <appAddress> [options]"));
676
+ console.error((0, import_picocolors6.red)("Usage: towns-agent metadata <view|update> [appAddress] [options]"));
771
677
  process.exit(1);
772
678
  }
773
679
  if (!appAddress) {
774
- console.error((0, import_picocolors6.red)("App address is required."));
680
+ console.error(
681
+ (0, import_picocolors6.red)(
682
+ "App address is required. Provide it as an argument, or set APP_ADDRESS or APP_PRIVATE_DATA in .env."
683
+ )
684
+ );
775
685
  process.exit(1);
776
686
  }
777
- const env = (0, import_sdk2.townsEnv)();
687
+ const env = (0, import_sdk3.townsEnv)();
778
688
  const townsConfig = env.makeTownsConfig();
779
689
  const appRegistryUrl = env.getAppRegistryUrl(townsConfig.environmentId);
780
690
  const appId = (0, import_utils5.bin_fromHexString)(appAddress);
781
691
  if (subcommand === "view") {
782
- const client = (0, import_sdk2.makeAppRegistryRpcClient)(appRegistryUrl, "");
692
+ const client = (0, import_sdk3.makeAppRegistryRpcClient)(appRegistryUrl, "");
783
693
  const { metadata: meta } = await client.getAppMetadata({ appId });
784
694
  if (!meta) {
785
695
  console.error((0, import_picocolors6.red)("No metadata found for this app."));
@@ -807,12 +717,12 @@ async function metadata(argv) {
807
717
  bearerToken = auth.value;
808
718
  }
809
719
  }
810
- const signerContext = ownerPrivateKey ? await (0, import_sdk2.makeSignerContextFromViem)(
720
+ const signerContext = ownerPrivateKey ? await (0, import_sdk3.makeSignerContextFromViem)(
811
721
  (0, import_accounts2.privateKeyToAccount)(ownerPrivateKey),
812
722
  (0, import_accounts2.generatePrivateKey)(),
813
723
  { days: 1 }
814
- ) : await (0, import_sdk2.makeSignerContextFromBearerToken)(bearerToken);
815
- const { appRegistryRpcClient } = await import_sdk2.AppRegistryService.authenticate(
724
+ ) : await (0, import_sdk3.makeSignerContextFromBearerToken)(bearerToken);
725
+ const { appRegistryRpcClient } = await import_sdk3.AppRegistryService.authenticate(
816
726
  signerContext,
817
727
  appRegistryUrl
818
728
  );
@@ -826,7 +736,7 @@ async function metadata(argv) {
826
736
  }
827
737
  }
828
738
  } else {
829
- const unauthClient = (0, import_sdk2.makeAppRegistryRpcClient)(appRegistryUrl, "");
739
+ const unauthClient = (0, import_sdk3.makeAppRegistryRpcClient)(appRegistryUrl, "");
830
740
  const { metadata: current } = await unauthClient.getAppMetadata({ appId });
831
741
  values = {};
832
742
  for (const field of FIELD_DEFS) {
@@ -874,7 +784,7 @@ async function metadata(argv) {
874
784
  var import_prompts5 = __toESM(require("prompts"));
875
785
  var import_picocolors7 = require("picocolors");
876
786
  var import_accounts3 = require("viem/accounts");
877
- var import_sdk3 = require("@towns-labs/sdk");
787
+ var import_sdk4 = require("@towns-labs/sdk");
878
788
  var import_proto = require("@towns-labs/proto");
879
789
  var import_utils7 = require("@towns-labs/utils");
880
790
  var NOTIFY_MAP = {
@@ -888,9 +798,13 @@ var NOTIFY_LABELS = {
888
798
  NONE: "No messages"
889
799
  };
890
800
  async function setup(argv) {
891
- const appAddress = argv._[1];
801
+ const appAddress = resolveAppAddress(argv._[1]);
892
802
  if (!appAddress) {
893
- console.error((0, import_picocolors7.red)("Usage: towns-agent setup <appAddress> [options]"));
803
+ console.error(
804
+ (0, import_picocolors7.red)(
805
+ "App address is required. Provide it as an argument, or set APP_ADDRESS or APP_PRIVATE_DATA in .env."
806
+ )
807
+ );
894
808
  process.exit(1);
895
809
  }
896
810
  let ownerPrivateKey = argv.ownerPrivateKey;
@@ -907,11 +821,18 @@ async function setup(argv) {
907
821
  bearerToken = auth.value;
908
822
  }
909
823
  }
910
- const webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
824
+ let webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
911
825
  if (!webhookUrl) {
912
826
  console.error((0, import_picocolors7.red)("Webhook URL is required."));
913
827
  process.exit(1);
914
828
  }
829
+ if (argv.webhookUrl) {
830
+ const urlError = validateWebhookUrl(webhookUrl);
831
+ if (urlError !== true) {
832
+ console.error((0, import_picocolors7.red)(urlError));
833
+ process.exit(1);
834
+ }
835
+ }
915
836
  let notifyKey;
916
837
  if (argv.notify) {
917
838
  notifyKey = argv.notify.toUpperCase();
@@ -927,20 +848,39 @@ async function setup(argv) {
927
848
  );
928
849
  process.exit(1);
929
850
  }
930
- const env = (0, import_sdk3.townsEnv)();
851
+ const env = (0, import_sdk4.townsEnv)();
931
852
  const townsConfig = env.makeTownsConfig();
932
853
  const appRegistryUrl = env.getAppRegistryUrl(townsConfig.environmentId);
933
854
  const appId = (0, import_utils7.bin_fromHexString)(appAddress);
934
- const signerContext = ownerPrivateKey ? await (0, import_sdk3.makeSignerContextFromViem)(
855
+ const signerContext = ownerPrivateKey ? await (0, import_sdk4.makeSignerContextFromViem)(
935
856
  (0, import_accounts3.privateKeyToAccount)(ownerPrivateKey),
936
857
  (0, import_accounts3.generatePrivateKey)(),
937
858
  { days: 1 }
938
- ) : await (0, import_sdk3.makeSignerContextFromBearerToken)(bearerToken);
939
- const { appRegistryRpcClient } = await import_sdk3.AppRegistryService.authenticate(
859
+ ) : await (0, import_sdk4.makeSignerContextFromBearerToken)(bearerToken);
860
+ const { appRegistryRpcClient } = await import_sdk4.AppRegistryService.authenticate(
940
861
  signerContext,
941
862
  appRegistryUrl
942
863
  );
943
- await appRegistryRpcClient.registerWebhook({ appId, webhookUrl });
864
+ try {
865
+ await appRegistryRpcClient.registerWebhook({ appId, webhookUrl });
866
+ } catch (error) {
867
+ if (!hasWebhookPath(webhookUrl)) {
868
+ const webhookUrlWithPath = appendWebhookPath(webhookUrl);
869
+ try {
870
+ await appRegistryRpcClient.registerWebhook({
871
+ appId,
872
+ webhookUrl: webhookUrlWithPath
873
+ });
874
+ console.log();
875
+ console.log((0, import_picocolors7.yellow)(`Registration succeeded with ${webhookUrlWithPath}`));
876
+ webhookUrl = webhookUrlWithPath;
877
+ } catch {
878
+ throw error;
879
+ }
880
+ } else {
881
+ throw error;
882
+ }
883
+ }
944
884
  await appRegistryRpcClient.setAppSettings({
945
885
  appId,
946
886
  settings: { forwardSetting }
@@ -953,12 +893,48 @@ async function setup(argv) {
953
893
  console.log();
954
894
  process.exit(0);
955
895
  }
896
+ function hasWebhookPath(url) {
897
+ const trimmed = url.trim();
898
+ try {
899
+ const parsed = new URL(trimmed);
900
+ const normalizedPath = parsed.pathname.endsWith("/") ? parsed.pathname.slice(0, -1) : parsed.pathname;
901
+ return normalizedPath.endsWith("/webhook");
902
+ } catch {
903
+ return trimmed.replace(/\s*$/, "").endsWith("/webhook");
904
+ }
905
+ }
906
+ function appendWebhookPath(url) {
907
+ const trimmed = url.trim();
908
+ const parsed = new URL(trimmed);
909
+ const normalizedPath = parsed.pathname.endsWith("/") ? parsed.pathname.slice(0, -1) : parsed.pathname;
910
+ parsed.pathname = normalizedPath === "/" ? "/webhook" : `${normalizedPath}/webhook`;
911
+ return parsed.toString();
912
+ }
913
+ function validateWebhookUrl(url) {
914
+ const trimmed = url.trim();
915
+ if (!trimmed) {
916
+ return "Webhook URL is required";
917
+ }
918
+ try {
919
+ const parsed = new URL(trimmed);
920
+ const isLocalhost = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "0.0.0.0";
921
+ if (isLocalhost && parsed.protocol === "http:") {
922
+ return "Local bots must use HTTPS. Use https:// instead of http://";
923
+ }
924
+ if (isLocalhost && !parsed.port) {
925
+ return "Localhost URL is missing a port. Example: https://localhost:3000";
926
+ }
927
+ return true;
928
+ } catch {
929
+ return "Invalid URL format. Example: https://localhost:3000/webhook";
930
+ }
931
+ }
956
932
  async function promptWebhookUrl() {
957
933
  const { value } = await (0, import_prompts5.default)({
958
934
  type: "text",
959
935
  name: "value",
960
936
  message: "Webhook URL",
961
- validate: (v) => v.trim() ? true : "Webhook URL is required"
937
+ validate: validateWebhookUrl
962
938
  });
963
939
  return value;
964
940
  }
@@ -981,7 +957,7 @@ async function promptNotify() {
981
957
  }
982
958
 
983
959
  // src/index.ts
984
- var import_sdk4 = require("@towns-labs/sdk");
960
+ var import_sdk5 = require("@towns-labs/sdk");
985
961
 
986
962
  // src/parser.ts
987
963
  var import_minimist = __toESM(require("minimist"));
@@ -1100,20 +1076,31 @@ async function main() {
1100
1076
  showHelp();
1101
1077
  return;
1102
1078
  }
1103
- if (args.env) {
1104
- process.env.RIVER_ENV = args.env;
1105
- }
1106
- if (!process.env.RIVER_ENV) {
1107
- console.error(
1108
- (0, import_picocolors8.red)("Environment is required. Use --env <local_dev|stage|prod> or set RIVER_ENV.")
1109
- );
1110
- process.exit(1);
1111
- }
1112
- try {
1113
- (0, import_sdk4.townsEnv)().makeTownsConfig();
1114
- } catch {
1115
- console.error((0, import_picocolors8.red)(`Invalid environment: ${process.env.RIVER_ENV}`));
1116
- process.exit(1);
1079
+ const requiresEnv = ["create", "setup", "metadata"].includes(command);
1080
+ if (requiresEnv) {
1081
+ if (args.env) {
1082
+ process.env.RIVER_ENV = args.env;
1083
+ }
1084
+ if (!process.env.RIVER_ENV) {
1085
+ const resolvedEnv = resolveRiverEnv();
1086
+ if (resolvedEnv) {
1087
+ process.env.RIVER_ENV = resolvedEnv;
1088
+ }
1089
+ }
1090
+ if (!process.env.RIVER_ENV) {
1091
+ console.error(
1092
+ (0, import_picocolors8.red)(
1093
+ "Environment is required. Use --env <local_dev|stage|prod>, set RIVER_ENV, or set APP_PRIVATE_DATA in .env."
1094
+ )
1095
+ );
1096
+ process.exit(1);
1097
+ }
1098
+ try {
1099
+ (0, import_sdk5.townsEnv)().makeTownsConfig();
1100
+ } catch {
1101
+ console.error((0, import_picocolors8.red)(`Invalid environment: ${process.env.RIVER_ENV}`));
1102
+ process.exit(1);
1103
+ }
1117
1104
  }
1118
1105
  try {
1119
1106
  switch (command) {
@@ -1167,8 +1154,8 @@ ${(0, import_picocolors8.yellow)("Usage:")}
1167
1154
  ${(0, import_picocolors8.yellow)("Commands:")}
1168
1155
  ${(0, import_picocolors8.green)("init")} [project-name] Create a new agent project
1169
1156
  ${(0, import_picocolors8.green)("create")} Create a new bot/app account interactively
1170
- ${(0, import_picocolors8.green)("setup")} <appAddress> Register webhook and notification settings
1171
- ${(0, import_picocolors8.green)("metadata")} <view|update> View or update app metadata
1157
+ ${(0, import_picocolors8.green)("setup")} [appAddress] Register webhook and notification settings
1158
+ ${(0, import_picocolors8.green)("metadata")} <view|update> [appAddress] View or update app metadata
1172
1159
  ${(0, import_picocolors8.green)("update")} Update @towns-labs dependencies and skills
1173
1160
  ${(0, import_picocolors8.green)("install-skill")} Install Towns Agent Skills to current project
1174
1161
 
@@ -1188,8 +1175,10 @@ ${(0, import_picocolors8.yellow)("Update Commands Options:")}
1188
1175
  -e, --envFile <path> Path to .env file (default: .env)
1189
1176
  --skip-agents-md Skip updating AGENTS.md file
1190
1177
 
1191
- ${(0, import_picocolors8.yellow)("Global Options:")}
1178
+ ${(0, import_picocolors8.yellow)("Environment Options (create, setup, metadata):")}
1192
1179
  --env <name> Environment to use: local_dev, stage, prod
1180
+
1181
+ ${(0, import_picocolors8.yellow)("Global Options:")}
1193
1182
  -h, --help Show this help message
1194
1183
 
1195
1184
  ${(0, import_picocolors8.yellow)("Examples:")}