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.js CHANGED
@@ -23,8 +23,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/index.ts
26
- var import_path2 = require("path");
27
- var import_dotenv = require("dotenv");
28
26
  var import_picocolors8 = require("picocolors");
29
27
 
30
28
  // src/modules/init.ts
@@ -37,9 +35,18 @@ var jsonc = __toESM(require("jsonc-parser"));
37
35
  // src/modules/utils.ts
38
36
  var fs = __toESM(require("fs"));
39
37
  var path = __toESM(require("path"));
38
+ var import_node_url = require("url");
40
39
  var import_cross_spawn = __toESM(require("cross-spawn"));
41
40
  var import_prompts = __toESM(require("prompts"));
42
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 = {};
43
50
  var getPackageManager = () => {
44
51
  if (process.env.npm_config_user_agent) {
45
52
  const agent = process.env.npm_config_user_agent;
@@ -103,112 +110,52 @@ function runCommand(command, args, opts = { cwd: void 0, silent: false }) {
103
110
  child.on("error", reject);
104
111
  });
105
112
  }
106
- async function getLatestTownsProtocolVersion() {
107
- return new Promise((resolve3, reject) => {
108
- const child = (0, import_cross_spawn.default)("npm", ["view", "@towns-labs/agent", "version"], {
109
- stdio: ["ignore", "pipe", "ignore"]
110
- });
111
- let output = "";
112
- child.stdout?.on("data", (data) => {
113
- output += data.toString();
114
- });
115
- child.on("close", (code) => {
116
- if (code !== 0) {
117
- reject(new Error("Failed to fetch latest @towns-labs/agent version"));
118
- } else {
119
- resolve3(output.trim());
120
- }
121
- });
122
- child.on("error", reject);
123
- });
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");
124
120
  }
125
- function getLatestSdkTag() {
126
- const tagsResult = import_cross_spawn.default.sync(
127
- "git",
128
- ["ls-remote", "--tags", "https://github.com/HereNotThere/chat.git", "@towns-labs/sdk@*"],
129
- { encoding: "utf8" }
130
- );
131
- if (tagsResult.status !== 0 || !tagsResult.stdout) return null;
132
- const tags = tagsResult.stdout.split("\n").filter(Boolean).map((line) => {
133
- const [_hash, ref] = line.split(" ");
134
- const tag = ref.replace("refs/tags/", "").replace(/\^{}$/, "");
135
- const match = tag.match(/^@towns-protocol\/sdk@(\d+)\.(\d+)\.(\d+)$/);
136
- if (!match) return null;
137
- return {
138
- tag,
139
- version: [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])]
140
- };
141
- }).filter(
142
- (item) => item !== null && Array.isArray(item.version) && item.version.length === 3
143
- ).sort((a, b) => {
144
- for (let i = 0; i < 3; i++) {
145
- if (a.version[i] !== b.version[i]) {
146
- return b.version[i] - a.version[i];
147
- }
148
- }
149
- return 0;
150
- });
151
- return tags.length > 0 ? tags[0].tag : null;
152
- }
153
- async function cloneTemplate(packagePath, targetDir) {
154
- console.log(import_picocolors.default.blue("Cloning template from GitHub..."));
155
- const tempDir = `${targetDir}-temp`;
156
- const fullTemplatePath = `packages/examples/${packagePath}`;
157
- const latestSdkTag = getLatestSdkTag();
158
- if (!latestSdkTag) {
159
- console.error(import_picocolors.default.red("Failed to get latest SDK tag."));
160
- return false;
161
- }
162
- const cloneResult = import_cross_spawn.default.sync(
163
- "git",
164
- [
165
- "clone",
166
- "--no-checkout",
167
- "--depth",
168
- "1",
169
- "--sparse",
170
- "--branch",
171
- latestSdkTag,
172
- "https://github.com/towns-protocol/towns.git",
173
- tempDir
174
- ],
175
- { stdio: "pipe" }
176
- );
177
- if (cloneResult.status !== 0) return false;
178
- const sparseResult = import_cross_spawn.default.sync("git", ["sparse-checkout", "set", fullTemplatePath], {
179
- stdio: "pipe",
180
- cwd: tempDir
181
- });
182
- if (sparseResult.status !== 0) {
183
- fs.rmSync(tempDir, { recursive: true, force: true });
184
- return false;
185
- }
186
- const checkoutResult = import_cross_spawn.default.sync("git", ["checkout"], {
187
- stdio: "pipe",
188
- cwd: tempDir
189
- });
190
- if (checkoutResult.status !== 0) {
191
- fs.rmSync(tempDir, { recursive: true, force: true });
192
- return false;
193
- }
194
- 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);
195
125
  if (!fs.existsSync(sourceDir)) {
196
- console.error(import_picocolors.default.red(`
197
- Template directory not found at ${sourceDir}`));
198
- fs.rmSync(tempDir, { recursive: true, force: true });
126
+ console.error(import_picocolors.default.red(`Template "${templateName}" not found at ${sourceDir}`));
199
127
  return false;
200
128
  }
201
129
  fs.mkdirSync(targetDir, { recursive: true });
202
130
  fs.cpSync(sourceDir, targetDir, {
203
131
  recursive: true,
204
- filter: () => {
205
- return true;
132
+ filter: (source) => {
133
+ const basename2 = path.basename(source);
134
+ return basename2 !== "node_modules" && basename2 !== "dist";
206
135
  }
207
136
  });
208
- fs.rmSync(tempDir, { recursive: true, force: true });
209
- 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!");
210
143
  return true;
211
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
+ }
212
159
  function applyReplacements(targetDir, replacements) {
213
160
  function processDirectory(dir) {
214
161
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -334,71 +281,35 @@ async function installTownsSkills(projectDir) {
334
281
  return false;
335
282
  }
336
283
  }
337
- async function downloadAgentsMd(projectDir) {
338
- 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
+ }
339
292
  try {
340
- const agentsMdPath = "packages/examples/agent-quickstart/AGENTS.md";
341
- const latestSdkTag = getLatestSdkTag();
342
- if (!latestSdkTag) {
343
- return false;
344
- }
345
- const cloneResult = import_cross_spawn.default.sync(
346
- "git",
347
- [
348
- "clone",
349
- "--no-checkout",
350
- "--depth",
351
- "1",
352
- "--sparse",
353
- "--branch",
354
- latestSdkTag,
355
- "https://github.com/towns-protocol/towns.git",
356
- tempDir
357
- ],
358
- { stdio: "pipe" }
359
- );
360
- if (cloneResult.status !== 0) {
361
- if (fs.existsSync(tempDir)) {
362
- fs.rmSync(tempDir, { recursive: true, force: true });
363
- }
364
- return false;
365
- }
366
- const sparseResult = import_cross_spawn.default.sync("git", ["sparse-checkout", "set", agentsMdPath], {
367
- stdio: "pipe",
368
- cwd: tempDir
369
- });
370
- if (sparseResult.status !== 0) {
371
- fs.rmSync(tempDir, { recursive: true, force: true });
372
- return false;
373
- }
374
- const checkoutResult = import_cross_spawn.default.sync("git", ["checkout"], {
375
- stdio: "pipe",
376
- cwd: tempDir
377
- });
378
- if (checkoutResult.status !== 0) {
379
- fs.rmSync(tempDir, { recursive: true, force: true });
380
- return false;
381
- }
382
- const sourceFile = path.join(tempDir, agentsMdPath);
383
- if (!fs.existsSync(sourceFile)) {
384
- fs.rmSync(tempDir, { recursive: true, force: true });
385
- return false;
386
- }
387
- const destFile = path.join(projectDir, "AGENTS.md");
388
- fs.copyFileSync(sourceFile, destFile);
389
- fs.rmSync(tempDir, { recursive: true, force: true });
390
- return true;
391
- } catch (error) {
392
- console.error(
393
- import_picocolors.default.red("Error downloading AGENTS.md:"),
394
- error instanceof Error ? error.message : error
395
- );
396
- if (fs.existsSync(tempDir)) {
397
- fs.rmSync(tempDir, { recursive: true, force: true });
398
- }
399
- return false;
293
+ return (0, import_sdk.parseAppPrivateData)(appPrivateData);
294
+ } catch {
295
+ return void 0;
400
296
  }
401
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;
306
+ }
307
+ return envFromAppPrivateData(parsed)?.appAddress;
308
+ }
309
+ function resolveRiverEnv() {
310
+ const parsed = parseDotenv();
311
+ return parsed?.RIVER_ENV ?? envFromAppPrivateData(parsed)?.env;
312
+ }
402
313
  async function promptAuth() {
403
314
  const { method } = await (0, import_prompts.default)({
404
315
  type: "select",
@@ -426,7 +337,7 @@ var TEMPLATES = {
426
337
  quickstart: {
427
338
  name: "Agent Quickstart",
428
339
  description: "Simple starter agent with basic commands",
429
- packagePath: "agent-quickstart"
340
+ packagePath: "quickstart"
430
341
  }
431
342
  };
432
343
  async function init(argv) {
@@ -463,12 +374,12 @@ async function init(argv) {
463
374
  const packageManager = getPackageManager();
464
375
  const selectedTemplate = TEMPLATES[template];
465
376
  try {
466
- const success = await cloneTemplate(selectedTemplate.packagePath, targetDir);
377
+ const success = copyTemplate(selectedTemplate.packagePath, targetDir);
467
378
  if (!success) {
468
- console.error((0, import_picocolors2.red)("Failed to clone template"));
379
+ console.error((0, import_picocolors2.red)("Failed to copy template"));
469
380
  process.exit(1);
470
381
  }
471
- const latestVersion = await getLatestTownsProtocolVersion();
382
+ const latestVersion = version;
472
383
  const replacements = /* @__PURE__ */ new Map([
473
384
  ["workspace:\\^", `^${latestVersion}`],
474
385
  ["workspace:\\*", `^${latestVersion}`]
@@ -493,19 +404,12 @@ async function init(argv) {
493
404
  if (skillSuccess) {
494
405
  console.log((0, import_picocolors2.green)("\u2713"), "Towns Agent Skills installed successfully!");
495
406
  } else {
496
- console.log(
497
- (0, import_picocolors2.yellow)("\u26A0"),
498
- "Failed to install Towns Agent Skills. You can install them later with:"
499
- );
407
+ console.log((0, import_picocolors2.yellow)("\u26A0"), "Skipping Towns Agent Skills. Install later with:");
500
408
  console.log((0, import_picocolors2.yellow)(` cd ${projectName} && towns-agent install-skill`));
501
409
  }
502
- } catch (error) {
503
- console.log(
504
- (0, import_picocolors2.yellow)("\u26A0"),
505
- "Error installing skills:",
506
- error instanceof Error ? error.message : error
507
- );
508
- 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`));
509
413
  }
510
414
  await initializeGitRepository(targetDir);
511
415
  printSuccess(projectName, packageManager);
@@ -524,9 +428,9 @@ function getTownsVersions(packageJson) {
524
428
  const versions = {};
525
429
  for (const deps of [packageJson.dependencies, packageJson.devDependencies]) {
526
430
  if (deps) {
527
- for (const [pkg, version] of Object.entries(deps)) {
431
+ for (const [pkg, version2] of Object.entries(deps)) {
528
432
  if (pkg.startsWith("@towns-labs/") || pkg.startsWith("@towns-protocol/")) {
529
- versions[pkg] = version;
433
+ versions[pkg] = version2;
530
434
  }
531
435
  }
532
436
  }
@@ -609,7 +513,7 @@ async function update(_argv) {
609
513
  console.log();
610
514
  console.log((0, import_picocolors3.cyan)("Updating AGENTS.md..."));
611
515
  try {
612
- const agentsMdSuccess = await downloadAgentsMd(projectDir);
516
+ const agentsMdSuccess = copyAgentsMd(projectDir);
613
517
  if (agentsMdSuccess) {
614
518
  console.log((0, import_picocolors3.green)("\u2713"), "AGENTS.md updated successfully!");
615
519
  } else {
@@ -663,7 +567,7 @@ var import_picocolors5 = require("picocolors");
663
567
  var import_viem = require("viem");
664
568
  var import_accounts = require("viem/accounts");
665
569
  var import_relayer_client = require("@towns-labs/relayer-client");
666
- var import_sdk = require("@towns-labs/sdk");
570
+ var import_sdk2 = require("@towns-labs/sdk");
667
571
  var import_deployments = require("@towns-labs/contracts/deployments");
668
572
  async function create(argv) {
669
573
  let ownerPrivateKey = argv.ownerPrivateKey;
@@ -696,18 +600,18 @@ async function create(argv) {
696
600
  process.exit(1);
697
601
  }
698
602
  const imageUrl = argv.imageUrl ?? await promptText("Bot image URL (optional)", true);
699
- const owner = ownerPrivateKey ? await (0, import_sdk.makeSignerContextFromViem)(
603
+ const owner = ownerPrivateKey ? await (0, import_sdk2.makeSignerContextFromViem)(
700
604
  (0, import_accounts.privateKeyToAccount)(ownerPrivateKey),
701
605
  (0, import_accounts.generatePrivateKey)(),
702
606
  { days: 1 }
703
- ) : await (0, import_sdk.makeSignerContextFromBearerToken)(bearerToken);
704
- const townsConfig = (0, import_sdk.townsEnv)().makeTownsConfig();
607
+ ) : await (0, import_sdk2.makeSignerContextFromBearerToken)(bearerToken);
608
+ const townsConfig = (0, import_sdk2.townsEnv)().makeTownsConfig();
705
609
  const chainId = townsConfig.base.chainConfig.chainId;
706
610
  const addresses = (0, import_deployments.getAddressesWithFallback)(townsConfig.environmentId, chainId);
707
611
  if (!addresses?.accountProxy) {
708
612
  throw new Error(`No accountProxy address found for ${townsConfig.environmentId}/${chainId}`);
709
613
  }
710
- const relayerUrl = process.env.RELAYER_URL ?? "http://127.0.0.1:8787";
614
+ const relayerUrl = townsConfig.services.relayer.url;
711
615
  const relayerClient = (0, import_viem.createPublicClient)({
712
616
  chain: {
713
617
  id: chainId,
@@ -717,7 +621,7 @@ async function create(argv) {
717
621
  },
718
622
  transport: (0, import_viem.http)(townsConfig.base.rpcUrl)
719
623
  }).extend((0, import_relayer_client.relayerActions)({ relayerUrl }));
720
- const result = await (0, import_sdk.createApp)({
624
+ const result = await (0, import_sdk2.createApp)({
721
625
  owner,
722
626
  metadata: {
723
627
  username,
@@ -749,7 +653,7 @@ async function promptText(message, optional = false) {
749
653
  var import_prompts4 = __toESM(require("prompts"));
750
654
  var import_picocolors6 = require("picocolors");
751
655
  var import_accounts2 = require("viem/accounts");
752
- var import_sdk2 = require("@towns-labs/sdk");
656
+ var import_sdk3 = require("@towns-labs/sdk");
753
657
  var import_utils5 = require("@towns-labs/utils");
754
658
  var FIELD_DEFS = [
755
659
  { flag: "username", label: "USERNAME", mask: "username", prompt: "Username" },
@@ -767,21 +671,25 @@ var FIELD_DEFS = [
767
671
  ];
768
672
  async function metadata(argv) {
769
673
  const subcommand = argv._[1];
770
- const appAddress = argv._[2];
674
+ const appAddress = resolveAppAddress(argv._[2]);
771
675
  if (!subcommand || !["view", "update"].includes(subcommand)) {
772
- 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]"));
773
677
  process.exit(1);
774
678
  }
775
679
  if (!appAddress) {
776
- 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
+ );
777
685
  process.exit(1);
778
686
  }
779
- const env = (0, import_sdk2.townsEnv)();
687
+ const env = (0, import_sdk3.townsEnv)();
780
688
  const townsConfig = env.makeTownsConfig();
781
689
  const appRegistryUrl = env.getAppRegistryUrl(townsConfig.environmentId);
782
690
  const appId = (0, import_utils5.bin_fromHexString)(appAddress);
783
691
  if (subcommand === "view") {
784
- const client = (0, import_sdk2.makeAppRegistryRpcClient)(appRegistryUrl, "");
692
+ const client = (0, import_sdk3.makeAppRegistryRpcClient)(appRegistryUrl, "");
785
693
  const { metadata: meta } = await client.getAppMetadata({ appId });
786
694
  if (!meta) {
787
695
  console.error((0, import_picocolors6.red)("No metadata found for this app."));
@@ -809,12 +717,12 @@ async function metadata(argv) {
809
717
  bearerToken = auth.value;
810
718
  }
811
719
  }
812
- const signerContext = ownerPrivateKey ? await (0, import_sdk2.makeSignerContextFromViem)(
720
+ const signerContext = ownerPrivateKey ? await (0, import_sdk3.makeSignerContextFromViem)(
813
721
  (0, import_accounts2.privateKeyToAccount)(ownerPrivateKey),
814
722
  (0, import_accounts2.generatePrivateKey)(),
815
723
  { days: 1 }
816
- ) : await (0, import_sdk2.makeSignerContextFromBearerToken)(bearerToken);
817
- const { appRegistryRpcClient } = await import_sdk2.AppRegistryService.authenticate(
724
+ ) : await (0, import_sdk3.makeSignerContextFromBearerToken)(bearerToken);
725
+ const { appRegistryRpcClient } = await import_sdk3.AppRegistryService.authenticate(
818
726
  signerContext,
819
727
  appRegistryUrl
820
728
  );
@@ -828,7 +736,7 @@ async function metadata(argv) {
828
736
  }
829
737
  }
830
738
  } else {
831
- const unauthClient = (0, import_sdk2.makeAppRegistryRpcClient)(appRegistryUrl, "");
739
+ const unauthClient = (0, import_sdk3.makeAppRegistryRpcClient)(appRegistryUrl, "");
832
740
  const { metadata: current } = await unauthClient.getAppMetadata({ appId });
833
741
  values = {};
834
742
  for (const field of FIELD_DEFS) {
@@ -876,7 +784,7 @@ async function metadata(argv) {
876
784
  var import_prompts5 = __toESM(require("prompts"));
877
785
  var import_picocolors7 = require("picocolors");
878
786
  var import_accounts3 = require("viem/accounts");
879
- var import_sdk3 = require("@towns-labs/sdk");
787
+ var import_sdk4 = require("@towns-labs/sdk");
880
788
  var import_proto = require("@towns-labs/proto");
881
789
  var import_utils7 = require("@towns-labs/utils");
882
790
  var NOTIFY_MAP = {
@@ -890,9 +798,13 @@ var NOTIFY_LABELS = {
890
798
  NONE: "No messages"
891
799
  };
892
800
  async function setup(argv) {
893
- const appAddress = argv._[1];
801
+ const appAddress = resolveAppAddress(argv._[1]);
894
802
  if (!appAddress) {
895
- 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
+ );
896
808
  process.exit(1);
897
809
  }
898
810
  let ownerPrivateKey = argv.ownerPrivateKey;
@@ -909,11 +821,18 @@ async function setup(argv) {
909
821
  bearerToken = auth.value;
910
822
  }
911
823
  }
912
- const webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
824
+ let webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
913
825
  if (!webhookUrl) {
914
826
  console.error((0, import_picocolors7.red)("Webhook URL is required."));
915
827
  process.exit(1);
916
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
+ }
917
836
  let notifyKey;
918
837
  if (argv.notify) {
919
838
  notifyKey = argv.notify.toUpperCase();
@@ -929,20 +848,39 @@ async function setup(argv) {
929
848
  );
930
849
  process.exit(1);
931
850
  }
932
- const env = (0, import_sdk3.townsEnv)();
851
+ const env = (0, import_sdk4.townsEnv)();
933
852
  const townsConfig = env.makeTownsConfig();
934
853
  const appRegistryUrl = env.getAppRegistryUrl(townsConfig.environmentId);
935
854
  const appId = (0, import_utils7.bin_fromHexString)(appAddress);
936
- const signerContext = ownerPrivateKey ? await (0, import_sdk3.makeSignerContextFromViem)(
855
+ const signerContext = ownerPrivateKey ? await (0, import_sdk4.makeSignerContextFromViem)(
937
856
  (0, import_accounts3.privateKeyToAccount)(ownerPrivateKey),
938
857
  (0, import_accounts3.generatePrivateKey)(),
939
858
  { days: 1 }
940
- ) : await (0, import_sdk3.makeSignerContextFromBearerToken)(bearerToken);
941
- const { appRegistryRpcClient } = await import_sdk3.AppRegistryService.authenticate(
859
+ ) : await (0, import_sdk4.makeSignerContextFromBearerToken)(bearerToken);
860
+ const { appRegistryRpcClient } = await import_sdk4.AppRegistryService.authenticate(
942
861
  signerContext,
943
862
  appRegistryUrl
944
863
  );
945
- 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
+ }
946
884
  await appRegistryRpcClient.setAppSettings({
947
885
  appId,
948
886
  settings: { forwardSetting }
@@ -955,12 +893,48 @@ async function setup(argv) {
955
893
  console.log();
956
894
  process.exit(0);
957
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
+ }
958
932
  async function promptWebhookUrl() {
959
933
  const { value } = await (0, import_prompts5.default)({
960
934
  type: "text",
961
935
  name: "value",
962
936
  message: "Webhook URL",
963
- validate: (v) => v.trim() ? true : "Webhook URL is required"
937
+ validate: validateWebhookUrl
964
938
  });
965
939
  return value;
966
940
  }
@@ -982,6 +956,9 @@ async function promptNotify() {
982
956
  return value;
983
957
  }
984
958
 
959
+ // src/index.ts
960
+ var import_sdk5 = require("@towns-labs/sdk");
961
+
985
962
  // src/parser.ts
986
963
  var import_minimist = __toESM(require("minimist"));
987
964
  var COMMAND_CONFIGS = {
@@ -1050,6 +1027,7 @@ var COMMAND_CONFIGS = {
1050
1027
  function parseArgs(args) {
1051
1028
  const initial = (0, import_minimist.default)(args, {
1052
1029
  stopEarly: true,
1030
+ string: ["env"],
1053
1031
  boolean: ["help"],
1054
1032
  alias: { h: "help" }
1055
1033
  });
@@ -1059,8 +1037,10 @@ function parseArgs(args) {
1059
1037
  }
1060
1038
  const commandConfig = COMMAND_CONFIGS[command] || {};
1061
1039
  const booleanOptions = Array.isArray(commandConfig.boolean) ? ["help", ...commandConfig.boolean] : ["help"];
1040
+ const stringOptions = Array.isArray(commandConfig.string) ? ["env", ...commandConfig.string] : ["env"];
1062
1041
  const parsed = (0, import_minimist.default)(args, {
1063
1042
  ...commandConfig,
1043
+ string: stringOptions,
1064
1044
  boolean: booleanOptions,
1065
1045
  alias: {
1066
1046
  ...commandConfig.alias,
@@ -1089,8 +1069,6 @@ function isSetupArgs(args) {
1089
1069
  }
1090
1070
 
1091
1071
  // src/index.ts
1092
- (0, import_dotenv.config)({ path: (0, import_path2.resolve)(__dirname, "../../generated/deployments/local_dev/.env") });
1093
- (0, import_dotenv.config)({ path: (0, import_path2.resolve)(__dirname, "../../contracts/deployments/envs/local/.env") });
1094
1072
  async function main() {
1095
1073
  const args = parseArgs(process.argv.slice(2));
1096
1074
  const command = args._[0];
@@ -1098,6 +1076,32 @@ async function main() {
1098
1076
  showHelp();
1099
1077
  return;
1100
1078
  }
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
+ }
1104
+ }
1101
1105
  try {
1102
1106
  switch (command) {
1103
1107
  case "init":
@@ -1150,8 +1154,8 @@ ${(0, import_picocolors8.yellow)("Usage:")}
1150
1154
  ${(0, import_picocolors8.yellow)("Commands:")}
1151
1155
  ${(0, import_picocolors8.green)("init")} [project-name] Create a new agent project
1152
1156
  ${(0, import_picocolors8.green)("create")} Create a new bot/app account interactively
1153
- ${(0, import_picocolors8.green)("setup")} <appAddress> Register webhook and notification settings
1154
- ${(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
1155
1159
  ${(0, import_picocolors8.green)("update")} Update @towns-labs dependencies and skills
1156
1160
  ${(0, import_picocolors8.green)("install-skill")} Install Towns Agent Skills to current project
1157
1161
 
@@ -1171,6 +1175,9 @@ ${(0, import_picocolors8.yellow)("Update Commands Options:")}
1171
1175
  -e, --envFile <path> Path to .env file (default: .env)
1172
1176
  --skip-agents-md Skip updating AGENTS.md file
1173
1177
 
1178
+ ${(0, import_picocolors8.yellow)("Environment Options (create, setup, metadata):")}
1179
+ --env <name> Environment to use: local_dev, stage, prod
1180
+
1174
1181
  ${(0, import_picocolors8.yellow)("Global Options:")}
1175
1182
  -h, --help Show this help message
1176
1183