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