towns-agent 2.0.4

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 ADDED
@@ -0,0 +1,1182 @@
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";
5
+
6
+ // src/modules/init.ts
7
+ import * as fs2 from "fs";
8
+ import * as path2 from "path";
9
+ import { default as prompts2 } from "prompts";
10
+ import { red, yellow, cyan, green } from "picocolors";
11
+ import * as jsonc from "jsonc-parser";
12
+
13
+ // src/modules/utils.ts
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ import { default as spawn } from "cross-spawn";
17
+ import { default as prompts } from "prompts";
18
+ import picocolors from "picocolors";
19
+ var getPackageManager = () => {
20
+ if (process.env.npm_config_user_agent) {
21
+ const agent = process.env.npm_config_user_agent;
22
+ if (agent.startsWith("yarn")) return "yarn";
23
+ if (agent.startsWith("npm")) return "npm";
24
+ if (agent.startsWith("pnpm")) return "pnpm";
25
+ if (agent.startsWith("bun")) return "bun";
26
+ }
27
+ return "npm";
28
+ };
29
+ function getInstallCommand(packageManager) {
30
+ switch (packageManager) {
31
+ case "bun":
32
+ return "bun install";
33
+ case "yarn":
34
+ return "yarn";
35
+ case "pnpm":
36
+ return "pnpm install";
37
+ default:
38
+ return "npm install";
39
+ }
40
+ }
41
+ function getRunCommand(packageManager, script) {
42
+ switch (packageManager) {
43
+ case "bun":
44
+ return `bun run ${script}`;
45
+ case "yarn":
46
+ return `yarn ${script}`;
47
+ case "pnpm":
48
+ return `pnpm ${script}`;
49
+ default:
50
+ return `npm run ${script}`;
51
+ }
52
+ }
53
+ function getDlxCommand(packageManager) {
54
+ switch (packageManager) {
55
+ case "bun":
56
+ return "bunx";
57
+ case "yarn":
58
+ return "yarn dlx";
59
+ case "pnpm":
60
+ return "pnpm dlx";
61
+ default:
62
+ return "npx";
63
+ }
64
+ }
65
+ function runCommand(command, args, opts = { cwd: void 0, silent: false }) {
66
+ return new Promise((resolve3, reject) => {
67
+ const child = spawn(command, args, {
68
+ stdio: opts.silent ? "ignore" : "inherit",
69
+ cwd: opts.cwd,
70
+ shell: process.platform === "win32"
71
+ });
72
+ child.on("close", (code) => {
73
+ if (code !== 0) {
74
+ reject(new Error(`Command failed with exit code ${code}`));
75
+ } else {
76
+ resolve3();
77
+ }
78
+ });
79
+ child.on("error", reject);
80
+ });
81
+ }
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
+ });
100
+ }
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);
171
+ if (!fs.existsSync(sourceDir)) {
172
+ console.error(picocolors.red(`
173
+ Template directory not found at ${sourceDir}`));
174
+ fs.rmSync(tempDir, { recursive: true, force: true });
175
+ return false;
176
+ }
177
+ fs.mkdirSync(targetDir, { recursive: true });
178
+ fs.cpSync(sourceDir, targetDir, {
179
+ recursive: true,
180
+ filter: () => {
181
+ return true;
182
+ }
183
+ });
184
+ fs.rmSync(tempDir, { recursive: true, force: true });
185
+ console.log(picocolors.green("\u2713"), "Template cloned successfully!");
186
+ return true;
187
+ }
188
+ function applyReplacements(targetDir, replacements) {
189
+ function processDirectory(dir) {
190
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
191
+ for (const entry of entries) {
192
+ const fullPath = path.join(dir, entry.name);
193
+ if (entry.isDirectory()) {
194
+ if (entry.name === "node_modules" || entry.name === "dist") {
195
+ continue;
196
+ }
197
+ processDirectory(fullPath);
198
+ } else {
199
+ let content = fs.readFileSync(fullPath, "utf-8");
200
+ let modified = false;
201
+ if (entry.name === "package.json" || entry.name.endsWith(".ts") || entry.name.endsWith(".js")) {
202
+ for (const [search, replace] of replacements) {
203
+ const regex = new RegExp(search, "g");
204
+ if (regex.test(content)) {
205
+ content = content.replace(regex, replace);
206
+ modified = true;
207
+ }
208
+ }
209
+ if (modified) {
210
+ fs.writeFileSync(fullPath, content);
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+ processDirectory(targetDir);
217
+ }
218
+ function printSuccess(projectName, packageManager) {
219
+ console.log(picocolors.green("\u2713"), "Bot project created successfully!");
220
+ console.log();
221
+ console.log("Next steps:");
222
+ console.log(picocolors.cyan(` cd ${projectName}`));
223
+ console.log(picocolors.cyan(` ${getInstallCommand(packageManager)}`));
224
+ console.log("Set up your environment variables:");
225
+ console.log(picocolors.cyan(" cp .env.sample .env"));
226
+ console.log(" Edit .env with your bot credentials");
227
+ console.log("Start your bot:");
228
+ console.log(picocolors.cyan(` ${getRunCommand(packageManager, "dev")}`));
229
+ }
230
+ async function initializeGitRepository(targetDir) {
231
+ try {
232
+ await runCommand("git", ["init"], { cwd: targetDir, silent: true });
233
+ await runCommand("git", ["add", "."], { cwd: targetDir, silent: true });
234
+ await runCommand("git", ["commit", "-m", "feat: towns bot scaffolding"], {
235
+ cwd: targetDir,
236
+ silent: true
237
+ });
238
+ return true;
239
+ } catch (error) {
240
+ console.log(
241
+ picocolors.yellow("\u26A0"),
242
+ "Failed to initialize git repository:",
243
+ error instanceof Error ? error.message : "Unknown error"
244
+ );
245
+ console.log(picocolors.yellow(" You can manually initialize git later with: git init"));
246
+ return false;
247
+ }
248
+ }
249
+ var TOWNS_SKILL_REPO = "https://github.com/towns-protocol/skills.git";
250
+ var AGENTS_SKILL_FOLDERS = [".claude/skills", ".codex/skills"];
251
+ async function installTownsSkills(projectDir) {
252
+ const tempDir = `${projectDir}-skills-temp`;
253
+ try {
254
+ const cloneResult = spawn.sync(
255
+ "git",
256
+ ["clone", "--depth", "1", "--filter=blob:none", "--sparse", TOWNS_SKILL_REPO, tempDir],
257
+ { stdio: "pipe" }
258
+ );
259
+ if (cloneResult.status !== 0) {
260
+ if (fs.existsSync(tempDir)) {
261
+ fs.rmSync(tempDir, { recursive: true, force: true });
262
+ }
263
+ return false;
264
+ }
265
+ const sparseResult = spawn.sync("git", ["sparse-checkout", "set", "skills"], {
266
+ stdio: "pipe",
267
+ cwd: tempDir
268
+ });
269
+ if (sparseResult.status !== 0) {
270
+ fs.rmSync(tempDir, { recursive: true, force: true });
271
+ return false;
272
+ }
273
+ const checkoutResult = spawn.sync("git", ["checkout"], {
274
+ stdio: "pipe",
275
+ cwd: tempDir
276
+ });
277
+ if (checkoutResult.status !== 0) {
278
+ fs.rmSync(tempDir, { recursive: true, force: true });
279
+ return false;
280
+ }
281
+ const sourceSkillsDir = path.join(tempDir, "skills");
282
+ if (!fs.existsSync(sourceSkillsDir)) {
283
+ fs.rmSync(tempDir, { recursive: true, force: true });
284
+ return false;
285
+ }
286
+ const skillDirs = fs.readdirSync(sourceSkillsDir, { withFileTypes: true });
287
+ for (const skillFolder of AGENTS_SKILL_FOLDERS) {
288
+ const targetDir = path.join(projectDir, skillFolder);
289
+ if (!fs.existsSync(targetDir)) {
290
+ fs.mkdirSync(targetDir, { recursive: true });
291
+ }
292
+ for (const skillDir of skillDirs) {
293
+ if (skillDir.isDirectory()) {
294
+ const sourcePath = path.join(sourceSkillsDir, skillDir.name);
295
+ const destPath = path.join(targetDir, skillDir.name);
296
+ fs.cpSync(sourcePath, destPath, { recursive: true });
297
+ }
298
+ }
299
+ }
300
+ fs.rmSync(tempDir, { recursive: true, force: true });
301
+ return true;
302
+ } catch (error) {
303
+ console.error(
304
+ picocolors.red("Error installing skills:"),
305
+ error instanceof Error ? error.message : error
306
+ );
307
+ if (fs.existsSync(tempDir)) {
308
+ fs.rmSync(tempDir, { recursive: true, force: true });
309
+ }
310
+ return false;
311
+ }
312
+ }
313
+ async function downloadAgentsMd(projectDir) {
314
+ const tempDir = `${projectDir}-agents-md-temp`;
315
+ 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;
376
+ }
377
+ }
378
+ async function promptAuth() {
379
+ const { method } = await prompts({
380
+ type: "select",
381
+ name: "method",
382
+ message: "Login method",
383
+ choices: [
384
+ { title: "Bearer Token", value: "bearerToken" },
385
+ { title: "Private Key", value: "privateKey" }
386
+ ]
387
+ });
388
+ if (method === void 0) return void 0;
389
+ const promptMessage = method === "bearerToken" ? "Enter your bearer token" : "Enter your private key";
390
+ const { value } = await prompts({
391
+ type: "password",
392
+ name: "value",
393
+ message: promptMessage,
394
+ validate: (v) => v.trim() ? true : "This field is required"
395
+ });
396
+ if (!value) return void 0;
397
+ return { method, value };
398
+ }
399
+
400
+ // src/modules/init.ts
401
+ var TEMPLATES = {
402
+ quickstart: {
403
+ name: "Agent Quickstart",
404
+ description: "Simple starter agent with basic commands",
405
+ packagePath: "agent-quickstart"
406
+ }
407
+ };
408
+ async function init(argv) {
409
+ const projectName = argv._[1];
410
+ const template = argv.template || "quickstart";
411
+ if (!projectName) {
412
+ console.error(red("Error: Please provide a project name"));
413
+ console.log(yellow("Usage: towns-agent init <project-name>"));
414
+ process.exit(1);
415
+ }
416
+ if (!TEMPLATES[template]) {
417
+ console.error(red(`Error: Unknown template "${template}"`));
418
+ console.log(yellow("Available templates:"), Object.keys(TEMPLATES).join(", "));
419
+ process.exit(1);
420
+ }
421
+ const targetDir = path2.resolve(process.cwd(), projectName);
422
+ if (fs2.existsSync(targetDir)) {
423
+ const { overwrite } = await prompts2({
424
+ type: "confirm",
425
+ name: "overwrite",
426
+ message: `Directory ${projectName} already exists. Overwrite?`,
427
+ initial: false
428
+ });
429
+ if (!overwrite) {
430
+ console.log(yellow("Operation cancelled"));
431
+ process.exit(0);
432
+ }
433
+ fs2.rmSync(targetDir, { recursive: true, force: true });
434
+ }
435
+ console.log(cyan(`Creating a new Towns Protocol agent in ${targetDir}`));
436
+ if (template !== "quickstart") {
437
+ console.log(cyan(`Using template: ${TEMPLATES[template].name}`));
438
+ }
439
+ const packageManager = getPackageManager();
440
+ const selectedTemplate = TEMPLATES[template];
441
+ try {
442
+ const success = await cloneTemplate(selectedTemplate.packagePath, targetDir);
443
+ if (!success) {
444
+ console.error(red("Failed to clone template"));
445
+ process.exit(1);
446
+ }
447
+ const latestVersion = await getLatestTownsProtocolVersion();
448
+ const replacements = /* @__PURE__ */ new Map([
449
+ ["workspace:\\^", `^${latestVersion}`],
450
+ ["workspace:\\*", `^${latestVersion}`]
451
+ ]);
452
+ applyReplacements(targetDir, replacements);
453
+ const packageJsonPath = path2.join(targetDir, "package.json");
454
+ if (fs2.existsSync(packageJsonPath)) {
455
+ const content = fs2.readFileSync(packageJsonPath, "utf-8");
456
+ const edits = [
457
+ jsonc.modify(content, ["name"], projectName, {}),
458
+ jsonc.modify(content, ["version"], "0.0.1", {})
459
+ ];
460
+ let modifiedContent = jsonc.applyEdits(content, edits.flat());
461
+ const parsed = jsonc.parse(modifiedContent);
462
+ delete parsed.private;
463
+ modifiedContent = JSON.stringify(parsed, null, 2);
464
+ fs2.writeFileSync(packageJsonPath, modifiedContent);
465
+ }
466
+ console.log(cyan("Installing Towns Agent Skills..."));
467
+ try {
468
+ const skillSuccess = await installTownsSkills(targetDir);
469
+ if (skillSuccess) {
470
+ console.log(green("\u2713"), "Towns Agent Skills installed successfully!");
471
+ } else {
472
+ console.log(
473
+ yellow("\u26A0"),
474
+ "Failed to install Towns Agent Skills. You can install them later with:"
475
+ );
476
+ console.log(yellow(` cd ${projectName} && towns-agent install-skill`));
477
+ }
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`));
485
+ }
486
+ await initializeGitRepository(targetDir);
487
+ printSuccess(projectName, packageManager);
488
+ } catch (error) {
489
+ console.error(red("Error:"), error instanceof Error ? error.message : error);
490
+ console.error(red(`Please delete the directory ${targetDir} and try again.`));
491
+ process.exit(1);
492
+ }
493
+ }
494
+
495
+ // src/modules/update.ts
496
+ import fs3 from "fs";
497
+ import path3 from "path";
498
+ import { green as green2, red as red2, yellow as yellow2, cyan as cyan2 } from "picocolors";
499
+ function getTownsVersions(packageJson) {
500
+ const versions = {};
501
+ for (const deps of [packageJson.dependencies, packageJson.devDependencies]) {
502
+ if (deps) {
503
+ for (const [pkg, version] of Object.entries(deps)) {
504
+ if (pkg.startsWith("@towns-labs/") || pkg.startsWith("@towns-protocol/")) {
505
+ versions[pkg] = version;
506
+ }
507
+ }
508
+ }
509
+ }
510
+ return versions;
511
+ }
512
+ async function update(_argv) {
513
+ const packageJsonPath = path3.join(process.cwd(), "package.json");
514
+ if (!fs3.existsSync(packageJsonPath)) {
515
+ console.error(red2("Error: No package.json found in the current directory"));
516
+ console.log(yellow2("Please run this command from a Towns Protocol bot project directory"));
517
+ process.exit(1);
518
+ }
519
+ const packageManager = getPackageManager();
520
+ const dlxCommand = getDlxCommand(packageManager);
521
+ console.log(cyan2("Checking for @towns-protocol updates..."));
522
+ try {
523
+ const [dlxBin, ...dlxArgs] = dlxCommand.split(" ");
524
+ const packageJsonBefore = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
525
+ const versionsBefore = getTownsVersions(packageJsonBefore);
526
+ await runCommand(dlxBin, [...dlxArgs, "npm-check-updates", "-u", "-f", "@towns-labs/*"], {
527
+ silent: true
528
+ });
529
+ await runCommand(
530
+ dlxBin,
531
+ [...dlxArgs, "npm-check-updates", "-u", "-f", "@towns-protocol/*"],
532
+ {
533
+ silent: true
534
+ }
535
+ );
536
+ const packageJsonAfter = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
537
+ const versionsAfter = getTownsVersions(packageJsonAfter);
538
+ const updates = [];
539
+ for (const [pkg, newVersion] of Object.entries(versionsAfter)) {
540
+ const oldVersion = versionsBefore[pkg];
541
+ if (oldVersion && oldVersion !== newVersion) {
542
+ updates.push({ package: pkg, from: oldVersion, to: newVersion });
543
+ }
544
+ }
545
+ if (updates.length === 0) {
546
+ console.log(green2("\u2713"), "All @towns-protocol and @towns-labs packages are up to date!");
547
+ } else {
548
+ console.log();
549
+ for (const update2 of updates) {
550
+ console.log(green2("\u2713"), `${update2.package} ${update2.from} \u2192 ${update2.to}`);
551
+ }
552
+ console.log();
553
+ console.log(cyan2(`Installing dependencies with ${packageManager}...`));
554
+ const installCmd = getInstallCommand(packageManager);
555
+ const [installBin, ...installArgs] = installCmd.split(" ");
556
+ await runCommand(installBin, installArgs.length > 0 ? installArgs : []);
557
+ console.log();
558
+ console.log(green2("\u2713"), "Dependencies updated successfully!");
559
+ }
560
+ const projectDir = process.cwd();
561
+ const claudeSkillsDir = path3.join(projectDir, ".claude", "skills");
562
+ const codexSkillsDir = path3.join(projectDir, ".codex", "skills");
563
+ if (fs3.existsSync(claudeSkillsDir) || fs3.existsSync(codexSkillsDir)) {
564
+ console.log();
565
+ console.log(cyan2("Updating Towns Agent Skills..."));
566
+ try {
567
+ const skillSuccess = await installTownsSkills(projectDir);
568
+ if (skillSuccess) {
569
+ console.log(green2("\u2713"), "Towns Agent Skills updated successfully!");
570
+ } else {
571
+ console.log(
572
+ yellow2("\u26A0"),
573
+ "Failed to update skills. You can reinstall them with: towns-agent install-skill"
574
+ );
575
+ }
576
+ } catch (error) {
577
+ console.log(
578
+ yellow2("\u26A0"),
579
+ "Error updating skills:",
580
+ error instanceof Error ? error.message : error
581
+ );
582
+ }
583
+ }
584
+ if (!_argv.skipAgentsMd) {
585
+ console.log();
586
+ console.log(cyan2("Updating AGENTS.md..."));
587
+ try {
588
+ const agentsMdSuccess = await downloadAgentsMd(projectDir);
589
+ if (agentsMdSuccess) {
590
+ console.log(green2("\u2713"), "AGENTS.md updated successfully!");
591
+ } else {
592
+ console.log(
593
+ yellow2("\u26A0"),
594
+ "Failed to update AGENTS.md. You can update it manually or run update again."
595
+ );
596
+ }
597
+ } catch (error) {
598
+ console.log(
599
+ yellow2("\u26A0"),
600
+ "Error updating AGENTS.md:",
601
+ error instanceof Error ? error.message : error
602
+ );
603
+ }
604
+ }
605
+ } catch {
606
+ console.error(red2("Error:"), "Failed to update dependencies");
607
+ process.exit(1);
608
+ }
609
+ }
610
+
611
+ // src/modules/install-skill.ts
612
+ import { red as red3, cyan as cyan3, green as green3 } from "picocolors";
613
+ async function skill(_argv) {
614
+ const cwd = process.cwd();
615
+ await installSkill(cwd);
616
+ }
617
+ async function installSkill(projectDir) {
618
+ console.log(cyan3("Installing Towns Agent Skills..."));
619
+ try {
620
+ const success = await installTownsSkills(projectDir);
621
+ if (success) {
622
+ console.log(green3("\u2713"), "Towns Agent Skills installed successfully!");
623
+ console.log();
624
+ console.log("Skills have been installed to .claude/skills/ and .codex/skills/");
625
+ console.log("They will be available when you open this project in your AI assistant");
626
+ } else {
627
+ console.error(red3("Failed to install Towns Agent Skills"));
628
+ process.exit(1);
629
+ }
630
+ } catch (error) {
631
+ console.error(red3("Error:"), error instanceof Error ? error.message : error);
632
+ process.exit(1);
633
+ }
634
+ }
635
+
636
+ // src/modules/create.ts
637
+ import { default as prompts3 } from "prompts";
638
+ import { red as red4 } from "picocolors";
639
+ import { createPublicClient, http } from "viem";
640
+ import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
641
+ import { relayerActions } from "@towns-labs/relayer-client";
642
+ import {
643
+ createApp,
644
+ makeSignerContextFromBearerToken,
645
+ makeSignerContextFromViem,
646
+ townsEnv
647
+ } from "@towns-labs/sdk";
648
+ import { getAddressesWithFallback } from "@towns-labs/contracts/deployments";
649
+ async function create(argv) {
650
+ let ownerPrivateKey = argv.ownerPrivateKey;
651
+ let bearerToken = argv.bearerToken;
652
+ if (!ownerPrivateKey && !bearerToken) {
653
+ const auth = await promptAuth();
654
+ if (!auth) {
655
+ console.error(red4("Authentication is required."));
656
+ process.exit(1);
657
+ }
658
+ if (auth.method === "privateKey") {
659
+ ownerPrivateKey = auth.value;
660
+ } else {
661
+ bearerToken = auth.value;
662
+ }
663
+ }
664
+ const username = argv.username ?? await promptText("Bot username");
665
+ if (!username) {
666
+ console.error(red4("Username is required."));
667
+ process.exit(1);
668
+ }
669
+ const displayName = argv.displayName ?? await promptText("Bot display name");
670
+ if (!displayName) {
671
+ console.error(red4("Display name is required."));
672
+ process.exit(1);
673
+ }
674
+ const description = argv.description ?? await promptText("Bot description");
675
+ if (!description) {
676
+ console.error(red4("Description is required."));
677
+ process.exit(1);
678
+ }
679
+ const imageUrl = argv.imageUrl ?? await promptText("Bot image URL (optional)", true);
680
+ const owner = ownerPrivateKey ? await makeSignerContextFromViem(
681
+ privateKeyToAccount(ownerPrivateKey),
682
+ generatePrivateKey(),
683
+ { days: 1 }
684
+ ) : await makeSignerContextFromBearerToken(bearerToken);
685
+ const townsConfig = townsEnv().makeTownsConfig();
686
+ const chainId = townsConfig.base.chainConfig.chainId;
687
+ const addresses = getAddressesWithFallback(townsConfig.environmentId, chainId);
688
+ if (!addresses?.accountProxy) {
689
+ throw new Error(`No accountProxy address found for ${townsConfig.environmentId}/${chainId}`);
690
+ }
691
+ const relayerUrl = process.env.RELAYER_URL ?? "http://127.0.0.1:8787";
692
+ const relayerClient = createPublicClient({
693
+ chain: {
694
+ id: chainId,
695
+ name: "Local",
696
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
697
+ rpcUrls: { default: { http: [townsConfig.base.rpcUrl] } }
698
+ },
699
+ transport: http(townsConfig.base.rpcUrl)
700
+ }).extend(relayerActions({ relayerUrl }));
701
+ const result = await createApp({
702
+ owner,
703
+ metadata: {
704
+ username,
705
+ displayName,
706
+ description,
707
+ imageUrl: imageUrl || ""
708
+ },
709
+ relayerClient,
710
+ accountProxy: addresses.accountProxy,
711
+ townsConfig
712
+ });
713
+ console.log(`APP_ADDRESS=${result.appAddress}`);
714
+ console.log(`APP_PRIVATE_KEY=${result.appPrivateKey}`);
715
+ console.log(`APP_PRIVATE_DATA=${result.appPrivateData}`);
716
+ console.log(`JWT_SECRET=${result.jwtSecretBase64}`);
717
+ process.exit(0);
718
+ }
719
+ async function promptText(message, optional = false) {
720
+ const { value } = await prompts3({
721
+ type: "text",
722
+ name: "value",
723
+ message,
724
+ validate: optional ? void 0 : (v) => v.trim() ? true : "This field is required"
725
+ });
726
+ return value;
727
+ }
728
+
729
+ // src/modules/metadata.ts
730
+ import { default as prompts4 } from "prompts";
731
+ import { red as red5, dim } from "picocolors";
732
+ import { privateKeyToAccount as privateKeyToAccount2, generatePrivateKey as generatePrivateKey2 } from "viem/accounts";
733
+ import {
734
+ AppRegistryService,
735
+ makeAppRegistryRpcClient,
736
+ makeSignerContextFromBearerToken as makeSignerContextFromBearerToken2,
737
+ makeSignerContextFromViem as makeSignerContextFromViem2,
738
+ townsEnv as townsEnv2
739
+ } from "@towns-labs/sdk";
740
+ import { bin_fromHexString } from "@towns-labs/utils";
741
+ var FIELD_DEFS = [
742
+ { flag: "username", label: "USERNAME", mask: "username", prompt: "Username" },
743
+ { flag: "displayName", label: "DISPLAY_NAME", mask: "display_name", prompt: "Display name" },
744
+ { flag: "description", label: "DESCRIPTION", mask: "description", prompt: "Description" },
745
+ { flag: "imageUrl", label: "IMAGE_URL", mask: "image_url", prompt: "Image URL" },
746
+ { flag: "avatarUrl", label: "AVATAR_URL", mask: "avatar_url", prompt: "Avatar URL" },
747
+ {
748
+ flag: "externalUrl",
749
+ label: "EXTERNAL_URL",
750
+ mask: "external_url",
751
+ prompt: "External URL"
752
+ },
753
+ { flag: "motto", label: "MOTTO", mask: "motto", prompt: "Motto" }
754
+ ];
755
+ async function metadata(argv) {
756
+ const subcommand = argv._[1];
757
+ const appAddress = argv._[2];
758
+ if (!subcommand || !["view", "update"].includes(subcommand)) {
759
+ console.error(red5("Usage: towns-agent metadata <view|update> <appAddress> [options]"));
760
+ process.exit(1);
761
+ }
762
+ if (!appAddress) {
763
+ console.error(red5("App address is required."));
764
+ process.exit(1);
765
+ }
766
+ const env = townsEnv2();
767
+ const townsConfig = env.makeTownsConfig();
768
+ const appRegistryUrl = env.getAppRegistryUrl(townsConfig.environmentId);
769
+ const appId = bin_fromHexString(appAddress);
770
+ if (subcommand === "view") {
771
+ const client = makeAppRegistryRpcClient(appRegistryUrl, "");
772
+ const { metadata: meta } = await client.getAppMetadata({ appId });
773
+ if (!meta) {
774
+ console.error(red5("No metadata found for this app."));
775
+ process.exit(1);
776
+ }
777
+ console.log();
778
+ for (const field of FIELD_DEFS) {
779
+ const value = meta[field.flag] ?? "";
780
+ console.log(` ${dim(field.prompt.padEnd(14))} ${value || dim("\u2014")}`);
781
+ }
782
+ console.log();
783
+ process.exit(0);
784
+ }
785
+ let ownerPrivateKey = argv.ownerPrivateKey;
786
+ let bearerToken = ownerPrivateKey ? void 0 : argv.bearerToken;
787
+ if (!ownerPrivateKey && !bearerToken) {
788
+ const auth = await promptAuth();
789
+ if (!auth) {
790
+ console.error(red5("Authentication is required for update."));
791
+ process.exit(1);
792
+ }
793
+ if (auth.method === "privateKey") {
794
+ ownerPrivateKey = auth.value;
795
+ } else {
796
+ bearerToken = auth.value;
797
+ }
798
+ }
799
+ const signerContext = ownerPrivateKey ? await makeSignerContextFromViem2(
800
+ privateKeyToAccount2(ownerPrivateKey),
801
+ generatePrivateKey2(),
802
+ { days: 1 }
803
+ ) : await makeSignerContextFromBearerToken2(bearerToken);
804
+ const { appRegistryRpcClient } = await AppRegistryService.authenticate(
805
+ signerContext,
806
+ appRegistryUrl
807
+ );
808
+ const hasAnyFlag = FIELD_DEFS.some((f) => argv[f.flag] !== void 0);
809
+ let values;
810
+ if (hasAnyFlag) {
811
+ values = {};
812
+ for (const field of FIELD_DEFS) {
813
+ if (argv[field.flag] !== void 0) {
814
+ values[field.flag] = argv[field.flag];
815
+ }
816
+ }
817
+ } else {
818
+ const unauthClient = makeAppRegistryRpcClient(appRegistryUrl, "");
819
+ const { metadata: current } = await unauthClient.getAppMetadata({ appId });
820
+ values = {};
821
+ for (const field of FIELD_DEFS) {
822
+ const { value } = await prompts4({
823
+ type: "text",
824
+ name: "value",
825
+ message: field.prompt,
826
+ initial: current?.[field.flag] ?? ""
827
+ });
828
+ if (value === void 0) {
829
+ process.exit(1);
830
+ }
831
+ if (value !== (current?.[field.flag] ?? "")) {
832
+ values[field.flag] = value;
833
+ }
834
+ }
835
+ if (Object.keys(values).length === 0) {
836
+ console.log("No changes.");
837
+ process.exit(0);
838
+ }
839
+ }
840
+ const updateMask = [];
841
+ const metadataUpdate = {};
842
+ for (const field of FIELD_DEFS) {
843
+ if (values[field.flag] !== void 0) {
844
+ updateMask.push(field.mask);
845
+ metadataUpdate[field.flag] = values[field.flag];
846
+ }
847
+ }
848
+ await appRegistryRpcClient.updateAppMetadata({
849
+ appId,
850
+ updateMask,
851
+ metadata: metadataUpdate
852
+ });
853
+ for (const field of FIELD_DEFS) {
854
+ if (values[field.flag] !== void 0) {
855
+ console.log(` ${dim(field.prompt.padEnd(14))} ${values[field.flag]}`);
856
+ }
857
+ }
858
+ console.log();
859
+ process.exit(0);
860
+ }
861
+
862
+ // src/modules/setup.ts
863
+ import { default as prompts5 } from "prompts";
864
+ import { red as red6, dim as dim2, green as green4 } from "picocolors";
865
+ import { privateKeyToAccount as privateKeyToAccount3, generatePrivateKey as generatePrivateKey3 } from "viem/accounts";
866
+ import {
867
+ AppRegistryService as AppRegistryService2,
868
+ makeSignerContextFromBearerToken as makeSignerContextFromBearerToken3,
869
+ makeSignerContextFromViem as makeSignerContextFromViem3,
870
+ townsEnv as townsEnv3
871
+ } from "@towns-labs/sdk";
872
+ import { ForwardSettingValue } from "@towns-labs/proto";
873
+ import { bin_fromHexString as bin_fromHexString2 } from "@towns-labs/utils";
874
+ var NOTIFY_MAP = {
875
+ ALL: ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES,
876
+ NONE: ForwardSettingValue.FORWARD_SETTING_NO_MESSAGES,
877
+ MENTION_REPLY_REACTION: ForwardSettingValue.FORWARD_SETTING_MENTIONS_REPLIES_REACTIONS
878
+ };
879
+ var NOTIFY_LABELS = {
880
+ ALL: "All messages",
881
+ MENTION_REPLY_REACTION: "Mentions, replies & reactions",
882
+ NONE: "No messages"
883
+ };
884
+ async function setup(argv) {
885
+ const appAddress = argv._[1];
886
+ if (!appAddress) {
887
+ console.error(red6("Usage: towns-agent setup <appAddress> [options]"));
888
+ process.exit(1);
889
+ }
890
+ let ownerPrivateKey = argv.ownerPrivateKey;
891
+ let bearerToken = argv.bearerToken;
892
+ if (!ownerPrivateKey && !bearerToken) {
893
+ const auth = await promptAuth();
894
+ if (!auth) {
895
+ console.error(red6("Authentication is required."));
896
+ process.exit(1);
897
+ }
898
+ if (auth.method === "privateKey") {
899
+ ownerPrivateKey = auth.value;
900
+ } else {
901
+ bearerToken = auth.value;
902
+ }
903
+ }
904
+ const webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
905
+ if (!webhookUrl) {
906
+ console.error(red6("Webhook URL is required."));
907
+ process.exit(1);
908
+ }
909
+ let notifyKey;
910
+ if (argv.notify) {
911
+ notifyKey = argv.notify.toUpperCase();
912
+ } else if (argv.webhookUrl) {
913
+ notifyKey = "ALL";
914
+ } else {
915
+ notifyKey = await promptNotify();
916
+ }
917
+ const forwardSetting = NOTIFY_MAP[notifyKey];
918
+ if (forwardSetting === void 0) {
919
+ console.error(
920
+ red6(`Invalid --notify value: ${notifyKey}. Use ALL, NONE, or MENTION_REPLY_REACTION.`)
921
+ );
922
+ process.exit(1);
923
+ }
924
+ const env = townsEnv3();
925
+ const townsConfig = env.makeTownsConfig();
926
+ const appRegistryUrl = env.getAppRegistryUrl(townsConfig.environmentId);
927
+ const appId = bin_fromHexString2(appAddress);
928
+ const signerContext = ownerPrivateKey ? await makeSignerContextFromViem3(
929
+ privateKeyToAccount3(ownerPrivateKey),
930
+ generatePrivateKey3(),
931
+ { days: 1 }
932
+ ) : await makeSignerContextFromBearerToken3(bearerToken);
933
+ const { appRegistryRpcClient } = await AppRegistryService2.authenticate(
934
+ signerContext,
935
+ appRegistryUrl
936
+ );
937
+ await appRegistryRpcClient.registerWebhook({ appId, webhookUrl });
938
+ await appRegistryRpcClient.setAppSettings({
939
+ appId,
940
+ settings: { forwardSetting }
941
+ });
942
+ console.log();
943
+ console.log(green4("Setup complete!"));
944
+ console.log();
945
+ console.log(` ${dim2("Webhook URL".padEnd(14))} ${webhookUrl}`);
946
+ console.log(` ${dim2("Notify".padEnd(14))} ${NOTIFY_LABELS[notifyKey] ?? notifyKey}`);
947
+ console.log();
948
+ process.exit(0);
949
+ }
950
+ async function promptWebhookUrl() {
951
+ const { value } = await prompts5({
952
+ type: "text",
953
+ name: "value",
954
+ message: "Webhook URL",
955
+ validate: (v) => v.trim() ? true : "Webhook URL is required"
956
+ });
957
+ return value;
958
+ }
959
+ async function promptNotify() {
960
+ const { value } = await prompts5({
961
+ type: "select",
962
+ name: "value",
963
+ message: "Notify setting",
964
+ choices: [
965
+ { title: "All messages", value: "ALL" },
966
+ { title: "Mentions, replies & reactions", value: "MENTION_REPLY_REACTION" },
967
+ { title: "No messages", value: "NONE" }
968
+ ],
969
+ initial: 0
970
+ });
971
+ if (value === void 0) {
972
+ process.exit(1);
973
+ }
974
+ return value;
975
+ }
976
+
977
+ // src/parser.ts
978
+ import minimist from "minimist";
979
+ var COMMAND_CONFIGS = {
980
+ init: {
981
+ string: ["template"],
982
+ alias: { t: "template" },
983
+ default: { template: "quickstart" }
984
+ },
985
+ update: {
986
+ boolean: ["skipAgentsMd"],
987
+ alias: { "skip-agents-md": "skipAgentsMd" }
988
+ },
989
+ "install-skill": {},
990
+ create: {
991
+ string: [
992
+ "bearerToken",
993
+ "ownerPrivateKey",
994
+ "username",
995
+ "displayName",
996
+ "description",
997
+ "imageUrl"
998
+ ],
999
+ alias: {
1000
+ b: "bearerToken",
1001
+ k: "ownerPrivateKey",
1002
+ u: "username",
1003
+ n: "displayName",
1004
+ d: "description",
1005
+ i: "imageUrl"
1006
+ }
1007
+ },
1008
+ metadata: {
1009
+ string: [
1010
+ "_",
1011
+ "ownerPrivateKey",
1012
+ "bearerToken",
1013
+ "username",
1014
+ "displayName",
1015
+ "description",
1016
+ "imageUrl",
1017
+ "avatarUrl",
1018
+ "externalUrl",
1019
+ "motto"
1020
+ ],
1021
+ alias: {
1022
+ k: "ownerPrivateKey",
1023
+ b: "bearerToken",
1024
+ u: "username",
1025
+ n: "displayName",
1026
+ d: "description",
1027
+ i: "imageUrl",
1028
+ a: "avatarUrl",
1029
+ e: "externalUrl",
1030
+ m: "motto"
1031
+ }
1032
+ },
1033
+ setup: {
1034
+ string: ["_", "ownerPrivateKey", "bearerToken", "webhookUrl", "notify"],
1035
+ alias: {
1036
+ k: "ownerPrivateKey",
1037
+ b: "bearerToken",
1038
+ w: "webhookUrl"
1039
+ }
1040
+ }
1041
+ };
1042
+ function parseArgs(args) {
1043
+ const initial = minimist(args, {
1044
+ stopEarly: true,
1045
+ boolean: ["help"],
1046
+ alias: { h: "help" }
1047
+ });
1048
+ const command = initial._[0];
1049
+ if (!command || initial.help) {
1050
+ return initial;
1051
+ }
1052
+ const commandConfig = COMMAND_CONFIGS[command] || {};
1053
+ const booleanOptions = Array.isArray(commandConfig.boolean) ? ["help", ...commandConfig.boolean] : ["help"];
1054
+ const parsed = minimist(args, {
1055
+ ...commandConfig,
1056
+ boolean: booleanOptions,
1057
+ alias: {
1058
+ ...commandConfig.alias,
1059
+ h: "help"
1060
+ }
1061
+ });
1062
+ return parsed;
1063
+ }
1064
+ function isInitArgs(args) {
1065
+ return args._[0] === "init";
1066
+ }
1067
+ function isUpdateArgs(args) {
1068
+ return args._[0] === "update";
1069
+ }
1070
+ function isSkillArgs(args) {
1071
+ return args._[0] === "install-skill";
1072
+ }
1073
+ function isCreateArgs(args) {
1074
+ return args._[0] === "create";
1075
+ }
1076
+ function isMetadataArgs(args) {
1077
+ return args._[0] === "metadata";
1078
+ }
1079
+ function isSetupArgs(args) {
1080
+ return args._[0] === "setup";
1081
+ }
1082
+
1083
+ // src/index.ts
1084
+ dotenvConfig({ path: resolve2(__dirname, "../../generated/deployments/local_dev/.env") });
1085
+ dotenvConfig({ path: resolve2(__dirname, "../../contracts/deployments/envs/local/.env") });
1086
+ async function main() {
1087
+ const args = parseArgs(process.argv.slice(2));
1088
+ const command = args._[0];
1089
+ if (args.help || !command) {
1090
+ showHelp();
1091
+ return;
1092
+ }
1093
+ try {
1094
+ switch (command) {
1095
+ case "init":
1096
+ if (isInitArgs(args)) {
1097
+ await init(args);
1098
+ }
1099
+ break;
1100
+ case "update":
1101
+ if (isUpdateArgs(args)) {
1102
+ await update(args);
1103
+ }
1104
+ break;
1105
+ case "install-skill":
1106
+ if (isSkillArgs(args)) {
1107
+ await skill(args);
1108
+ }
1109
+ break;
1110
+ case "create":
1111
+ if (isCreateArgs(args)) {
1112
+ await create(args);
1113
+ }
1114
+ break;
1115
+ case "metadata":
1116
+ if (isMetadataArgs(args)) {
1117
+ await metadata(args);
1118
+ }
1119
+ break;
1120
+ case "setup":
1121
+ if (isSetupArgs(args)) {
1122
+ await setup(args);
1123
+ }
1124
+ break;
1125
+ default:
1126
+ console.error(red7(`Unknown command: ${command}`));
1127
+ showHelp();
1128
+ process.exit(1);
1129
+ }
1130
+ } catch (error) {
1131
+ console.error(red7("Error:"), error instanceof Error ? error.message : error);
1132
+ process.exit(1);
1133
+ }
1134
+ }
1135
+ function showHelp() {
1136
+ console.log(`
1137
+ ${cyan4("towns-agent")} - CLI for creating and managing Towns Protocol agent projects
1138
+
1139
+ ${yellow3("Usage:")}
1140
+ towns-agent <command> [options]
1141
+
1142
+ ${yellow3("Commands:")}
1143
+ ${green5("init")} [project-name] Create a new agent project
1144
+ ${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
1147
+ ${green5("update")} Update @towns-labs dependencies and skills
1148
+ ${green5("install-skill")} Install Towns Agent Skills to current project
1149
+
1150
+ ${yellow3("Init Options:")}
1151
+ -t, --template <name> Template to use:
1152
+ ${Object.entries(TEMPLATES).map(
1153
+ ([key, template]) => ` ${key} - ${template.description}`
1154
+ ).join("\n")}
1155
+ Default: quickstart
1156
+
1157
+ ${yellow3("List Commands Options:")}
1158
+ -f, --file <path> Path to commands file
1159
+
1160
+ ${yellow3("Update Commands Options:")}
1161
+ -f, --file <path> Path to commands file
1162
+ -t, --bearerToken <token> Bearer token for authentication
1163
+ -e, --envFile <path> Path to .env file (default: .env)
1164
+ --skip-agents-md Skip updating AGENTS.md file
1165
+
1166
+ ${yellow3("Global Options:")}
1167
+ -h, --help Show this help message
1168
+
1169
+ ${yellow3("Examples:")}
1170
+ ${cyan4("# Create a new agent project")}
1171
+ towns-agent init my-agent
1172
+ towns-agent init my-ai-agent --template quickstart
1173
+
1174
+ ${cyan4("# Update dependencies")}
1175
+ towns-agent update
1176
+ `);
1177
+ }
1178
+ main().catch((error) => {
1179
+ console.error(red7("Unexpected error:"), error);
1180
+ process.exit(1);
1181
+ });
1182
+ //# sourceMappingURL=index.mjs.map