towns-agent 2.0.5 → 2.0.7
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/README.md +28 -8
- package/dist/index.js +206 -220
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +195 -210
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -8
- package/templates/quickstart/.env.sample +4 -0
- package/templates/quickstart/.turbo/turbo-build.log +2 -0
- package/templates/quickstart/AGENTS.md +267 -0
- package/templates/quickstart/README.md +95 -0
- package/templates/quickstart/_gitignore +33 -0
- package/templates/quickstart/package.json +34 -0
- package/templates/quickstart/src/commands.ts +12 -0
- package/templates/quickstart/src/index.ts +50 -0
- package/templates/quickstart/tsconfig.json +25 -0
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { green as green5, red as red7, yellow as
|
|
2
|
+
import { green as green5, red as red7, yellow as yellow4, cyan as cyan4 } from "picocolors";
|
|
3
3
|
|
|
4
4
|
// src/modules/init.ts
|
|
5
5
|
import * as fs2 from "fs";
|
|
@@ -11,9 +11,17 @@ import * as jsonc from "jsonc-parser";
|
|
|
11
11
|
// src/modules/utils.ts
|
|
12
12
|
import * as fs from "fs";
|
|
13
13
|
import * as path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
14
15
|
import { default as spawn } from "cross-spawn";
|
|
15
16
|
import { default as prompts } from "prompts";
|
|
16
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
|
|
17
25
|
var getPackageManager = () => {
|
|
18
26
|
if (process.env.npm_config_user_agent) {
|
|
19
27
|
const agent = process.env.npm_config_user_agent;
|
|
@@ -61,7 +69,7 @@ function getDlxCommand(packageManager) {
|
|
|
61
69
|
}
|
|
62
70
|
}
|
|
63
71
|
function runCommand(command, args, opts = { cwd: void 0, silent: false }) {
|
|
64
|
-
return new Promise((
|
|
72
|
+
return new Promise((resolve3, reject) => {
|
|
65
73
|
const child = spawn(command, args, {
|
|
66
74
|
stdio: opts.silent ? "ignore" : "inherit",
|
|
67
75
|
cwd: opts.cwd,
|
|
@@ -71,118 +79,58 @@ function runCommand(command, args, opts = { cwd: void 0, silent: false }) {
|
|
|
71
79
|
if (code !== 0) {
|
|
72
80
|
reject(new Error(`Command failed with exit code ${code}`));
|
|
73
81
|
} else {
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
child.on("error", reject);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
async function getLatestTownsProtocolVersion() {
|
|
81
|
-
return new Promise((resolve2, reject) => {
|
|
82
|
-
const child = spawn("npm", ["view", "@towns-labs/agent", "version"], {
|
|
83
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
84
|
-
});
|
|
85
|
-
let output = "";
|
|
86
|
-
child.stdout?.on("data", (data) => {
|
|
87
|
-
output += data.toString();
|
|
88
|
-
});
|
|
89
|
-
child.on("close", (code) => {
|
|
90
|
-
if (code !== 0) {
|
|
91
|
-
reject(new Error("Failed to fetch latest @towns-labs/agent version"));
|
|
92
|
-
} else {
|
|
93
|
-
resolve2(output.trim());
|
|
82
|
+
resolve3();
|
|
94
83
|
}
|
|
95
84
|
});
|
|
96
85
|
child.on("error", reject);
|
|
97
86
|
});
|
|
98
87
|
}
|
|
99
|
-
function
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const tags = tagsResult.stdout.split("\n").filter(Boolean).map((line) => {
|
|
107
|
-
const [_hash, ref] = line.split(" ");
|
|
108
|
-
const tag = ref.replace("refs/tags/", "").replace(/\^{}$/, "");
|
|
109
|
-
const match = tag.match(/^@towns-protocol\/sdk@(\d+)\.(\d+)\.(\d+)$/);
|
|
110
|
-
if (!match) return null;
|
|
111
|
-
return {
|
|
112
|
-
tag,
|
|
113
|
-
version: [parseInt(match[1]), parseInt(match[2]), parseInt(match[3])]
|
|
114
|
-
};
|
|
115
|
-
}).filter(
|
|
116
|
-
(item) => item !== null && Array.isArray(item.version) && item.version.length === 3
|
|
117
|
-
).sort((a, b) => {
|
|
118
|
-
for (let i = 0; i < 3; i++) {
|
|
119
|
-
if (a.version[i] !== b.version[i]) {
|
|
120
|
-
return b.version[i] - a.version[i];
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return 0;
|
|
124
|
-
});
|
|
125
|
-
return tags.length > 0 ? tags[0].tag : null;
|
|
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");
|
|
126
95
|
}
|
|
127
|
-
|
|
128
|
-
console.log(picocolors.blue("
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
const latestSdkTag = getLatestSdkTag();
|
|
132
|
-
if (!latestSdkTag) {
|
|
133
|
-
console.error(picocolors.red("Failed to get latest SDK tag."));
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
const cloneResult = spawn.sync(
|
|
137
|
-
"git",
|
|
138
|
-
[
|
|
139
|
-
"clone",
|
|
140
|
-
"--no-checkout",
|
|
141
|
-
"--depth",
|
|
142
|
-
"1",
|
|
143
|
-
"--sparse",
|
|
144
|
-
"--branch",
|
|
145
|
-
latestSdkTag,
|
|
146
|
-
"https://github.com/towns-protocol/towns.git",
|
|
147
|
-
tempDir
|
|
148
|
-
],
|
|
149
|
-
{ stdio: "pipe" }
|
|
150
|
-
);
|
|
151
|
-
if (cloneResult.status !== 0) return false;
|
|
152
|
-
const sparseResult = spawn.sync("git", ["sparse-checkout", "set", fullTemplatePath], {
|
|
153
|
-
stdio: "pipe",
|
|
154
|
-
cwd: tempDir
|
|
155
|
-
});
|
|
156
|
-
if (sparseResult.status !== 0) {
|
|
157
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
const checkoutResult = spawn.sync("git", ["checkout"], {
|
|
161
|
-
stdio: "pipe",
|
|
162
|
-
cwd: tempDir
|
|
163
|
-
});
|
|
164
|
-
if (checkoutResult.status !== 0) {
|
|
165
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
const sourceDir = path.join(tempDir, fullTemplatePath);
|
|
96
|
+
function copyTemplate(templateName, targetDir) {
|
|
97
|
+
console.log(picocolors.blue("Copying template..."));
|
|
98
|
+
const templatesDir = getTemplatesDir();
|
|
99
|
+
const sourceDir = path.join(templatesDir, templateName);
|
|
169
100
|
if (!fs.existsSync(sourceDir)) {
|
|
170
|
-
console.error(picocolors.red(`
|
|
171
|
-
Template directory not found at ${sourceDir}`));
|
|
172
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
101
|
+
console.error(picocolors.red(`Template "${templateName}" not found at ${sourceDir}`));
|
|
173
102
|
return false;
|
|
174
103
|
}
|
|
175
104
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
176
105
|
fs.cpSync(sourceDir, targetDir, {
|
|
177
106
|
recursive: true,
|
|
178
|
-
filter: () => {
|
|
179
|
-
|
|
107
|
+
filter: (source) => {
|
|
108
|
+
const basename2 = path.basename(source);
|
|
109
|
+
return basename2 !== "node_modules" && basename2 !== "dist";
|
|
180
110
|
}
|
|
181
111
|
});
|
|
182
|
-
|
|
183
|
-
|
|
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!");
|
|
184
118
|
return true;
|
|
185
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
|
+
}
|
|
186
134
|
function applyReplacements(targetDir, replacements) {
|
|
187
135
|
function processDirectory(dir) {
|
|
188
136
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -308,71 +256,35 @@ async function installTownsSkills(projectDir) {
|
|
|
308
256
|
return false;
|
|
309
257
|
}
|
|
310
258
|
}
|
|
311
|
-
|
|
312
|
-
|
|
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
|
+
}
|
|
313
267
|
try {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
return false;
|
|
318
|
-
}
|
|
319
|
-
const cloneResult = spawn.sync(
|
|
320
|
-
"git",
|
|
321
|
-
[
|
|
322
|
-
"clone",
|
|
323
|
-
"--no-checkout",
|
|
324
|
-
"--depth",
|
|
325
|
-
"1",
|
|
326
|
-
"--sparse",
|
|
327
|
-
"--branch",
|
|
328
|
-
latestSdkTag,
|
|
329
|
-
"https://github.com/towns-protocol/towns.git",
|
|
330
|
-
tempDir
|
|
331
|
-
],
|
|
332
|
-
{ stdio: "pipe" }
|
|
333
|
-
);
|
|
334
|
-
if (cloneResult.status !== 0) {
|
|
335
|
-
if (fs.existsSync(tempDir)) {
|
|
336
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
337
|
-
}
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
const sparseResult = spawn.sync("git", ["sparse-checkout", "set", agentsMdPath], {
|
|
341
|
-
stdio: "pipe",
|
|
342
|
-
cwd: tempDir
|
|
343
|
-
});
|
|
344
|
-
if (sparseResult.status !== 0) {
|
|
345
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
346
|
-
return false;
|
|
347
|
-
}
|
|
348
|
-
const checkoutResult = spawn.sync("git", ["checkout"], {
|
|
349
|
-
stdio: "pipe",
|
|
350
|
-
cwd: tempDir
|
|
351
|
-
});
|
|
352
|
-
if (checkoutResult.status !== 0) {
|
|
353
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
const sourceFile = path.join(tempDir, agentsMdPath);
|
|
357
|
-
if (!fs.existsSync(sourceFile)) {
|
|
358
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
359
|
-
return false;
|
|
360
|
-
}
|
|
361
|
-
const destFile = path.join(projectDir, "AGENTS.md");
|
|
362
|
-
fs.copyFileSync(sourceFile, destFile);
|
|
363
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
364
|
-
return true;
|
|
365
|
-
} catch (error) {
|
|
366
|
-
console.error(
|
|
367
|
-
picocolors.red("Error downloading AGENTS.md:"),
|
|
368
|
-
error instanceof Error ? error.message : error
|
|
369
|
-
);
|
|
370
|
-
if (fs.existsSync(tempDir)) {
|
|
371
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
372
|
-
}
|
|
373
|
-
return false;
|
|
268
|
+
return parseAppPrivateData(appPrivateData);
|
|
269
|
+
} catch {
|
|
270
|
+
return void 0;
|
|
374
271
|
}
|
|
375
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
|
+
}
|
|
376
288
|
async function promptAuth() {
|
|
377
289
|
const { method } = await prompts({
|
|
378
290
|
type: "select",
|
|
@@ -400,7 +312,7 @@ var TEMPLATES = {
|
|
|
400
312
|
quickstart: {
|
|
401
313
|
name: "Agent Quickstart",
|
|
402
314
|
description: "Simple starter agent with basic commands",
|
|
403
|
-
packagePath: "
|
|
315
|
+
packagePath: "quickstart"
|
|
404
316
|
}
|
|
405
317
|
};
|
|
406
318
|
async function init(argv) {
|
|
@@ -437,12 +349,12 @@ async function init(argv) {
|
|
|
437
349
|
const packageManager = getPackageManager();
|
|
438
350
|
const selectedTemplate = TEMPLATES[template];
|
|
439
351
|
try {
|
|
440
|
-
const success =
|
|
352
|
+
const success = copyTemplate(selectedTemplate.packagePath, targetDir);
|
|
441
353
|
if (!success) {
|
|
442
|
-
console.error(red("Failed to
|
|
354
|
+
console.error(red("Failed to copy template"));
|
|
443
355
|
process.exit(1);
|
|
444
356
|
}
|
|
445
|
-
const latestVersion =
|
|
357
|
+
const latestVersion = version;
|
|
446
358
|
const replacements = /* @__PURE__ */ new Map([
|
|
447
359
|
["workspace:\\^", `^${latestVersion}`],
|
|
448
360
|
["workspace:\\*", `^${latestVersion}`]
|
|
@@ -467,19 +379,12 @@ async function init(argv) {
|
|
|
467
379
|
if (skillSuccess) {
|
|
468
380
|
console.log(green("\u2713"), "Towns Agent Skills installed successfully!");
|
|
469
381
|
} else {
|
|
470
|
-
console.log(
|
|
471
|
-
yellow("\u26A0"),
|
|
472
|
-
"Failed to install Towns Agent Skills. You can install them later with:"
|
|
473
|
-
);
|
|
382
|
+
console.log(yellow("\u26A0"), "Skipping Towns Agent Skills. Install later with:");
|
|
474
383
|
console.log(yellow(` cd ${projectName} && towns-agent install-skill`));
|
|
475
384
|
}
|
|
476
|
-
} catch
|
|
477
|
-
console.log(
|
|
478
|
-
|
|
479
|
-
"Error installing skills:",
|
|
480
|
-
error instanceof Error ? error.message : error
|
|
481
|
-
);
|
|
482
|
-
console.log(yellow(` You can install them later with: towns-agent install-skill`));
|
|
385
|
+
} catch {
|
|
386
|
+
console.log(yellow("\u26A0"), "Skipping Towns Agent Skills. Install later with:");
|
|
387
|
+
console.log(yellow(` cd ${projectName} && towns-agent install-skill`));
|
|
483
388
|
}
|
|
484
389
|
await initializeGitRepository(targetDir);
|
|
485
390
|
printSuccess(projectName, packageManager);
|
|
@@ -498,9 +403,9 @@ function getTownsVersions(packageJson) {
|
|
|
498
403
|
const versions = {};
|
|
499
404
|
for (const deps of [packageJson.dependencies, packageJson.devDependencies]) {
|
|
500
405
|
if (deps) {
|
|
501
|
-
for (const [pkg,
|
|
406
|
+
for (const [pkg, version2] of Object.entries(deps)) {
|
|
502
407
|
if (pkg.startsWith("@towns-labs/") || pkg.startsWith("@towns-protocol/")) {
|
|
503
|
-
versions[pkg] =
|
|
408
|
+
versions[pkg] = version2;
|
|
504
409
|
}
|
|
505
410
|
}
|
|
506
411
|
}
|
|
@@ -583,7 +488,7 @@ async function update(_argv) {
|
|
|
583
488
|
console.log();
|
|
584
489
|
console.log(cyan2("Updating AGENTS.md..."));
|
|
585
490
|
try {
|
|
586
|
-
const agentsMdSuccess =
|
|
491
|
+
const agentsMdSuccess = copyAgentsMd(projectDir);
|
|
587
492
|
if (agentsMdSuccess) {
|
|
588
493
|
console.log(green2("\u2713"), "AGENTS.md updated successfully!");
|
|
589
494
|
} else {
|
|
@@ -752,13 +657,17 @@ var FIELD_DEFS = [
|
|
|
752
657
|
];
|
|
753
658
|
async function metadata(argv) {
|
|
754
659
|
const subcommand = argv._[1];
|
|
755
|
-
const appAddress = argv._[2];
|
|
660
|
+
const appAddress = resolveAppAddress(argv._[2]);
|
|
756
661
|
if (!subcommand || !["view", "update"].includes(subcommand)) {
|
|
757
|
-
console.error(red5("Usage: towns-agent metadata <view|update>
|
|
662
|
+
console.error(red5("Usage: towns-agent metadata <view|update> [appAddress] [options]"));
|
|
758
663
|
process.exit(1);
|
|
759
664
|
}
|
|
760
665
|
if (!appAddress) {
|
|
761
|
-
console.error(
|
|
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
|
+
);
|
|
762
671
|
process.exit(1);
|
|
763
672
|
}
|
|
764
673
|
const env = townsEnv2();
|
|
@@ -859,7 +768,7 @@ async function metadata(argv) {
|
|
|
859
768
|
|
|
860
769
|
// src/modules/setup.ts
|
|
861
770
|
import { default as prompts5 } from "prompts";
|
|
862
|
-
import { red as red6, dim as dim2, green as green4 } from "picocolors";
|
|
771
|
+
import { red as red6, dim as dim2, green as green4, yellow as yellow3 } from "picocolors";
|
|
863
772
|
import { privateKeyToAccount as privateKeyToAccount3, generatePrivateKey as generatePrivateKey3 } from "viem/accounts";
|
|
864
773
|
import {
|
|
865
774
|
AppRegistryService as AppRegistryService2,
|
|
@@ -880,9 +789,13 @@ var NOTIFY_LABELS = {
|
|
|
880
789
|
NONE: "No messages"
|
|
881
790
|
};
|
|
882
791
|
async function setup(argv) {
|
|
883
|
-
const appAddress = argv._[1];
|
|
792
|
+
const appAddress = resolveAppAddress(argv._[1]);
|
|
884
793
|
if (!appAddress) {
|
|
885
|
-
console.error(
|
|
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
|
+
);
|
|
886
799
|
process.exit(1);
|
|
887
800
|
}
|
|
888
801
|
let ownerPrivateKey = argv.ownerPrivateKey;
|
|
@@ -899,11 +812,18 @@ async function setup(argv) {
|
|
|
899
812
|
bearerToken = auth.value;
|
|
900
813
|
}
|
|
901
814
|
}
|
|
902
|
-
|
|
815
|
+
let webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
|
|
903
816
|
if (!webhookUrl) {
|
|
904
817
|
console.error(red6("Webhook URL is required."));
|
|
905
818
|
process.exit(1);
|
|
906
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
|
+
}
|
|
907
827
|
let notifyKey;
|
|
908
828
|
if (argv.notify) {
|
|
909
829
|
notifyKey = argv.notify.toUpperCase();
|
|
@@ -932,7 +852,26 @@ async function setup(argv) {
|
|
|
932
852
|
signerContext,
|
|
933
853
|
appRegistryUrl
|
|
934
854
|
);
|
|
935
|
-
|
|
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
|
+
}
|
|
936
875
|
await appRegistryRpcClient.setAppSettings({
|
|
937
876
|
appId,
|
|
938
877
|
settings: { forwardSetting }
|
|
@@ -945,12 +884,45 @@ async function setup(argv) {
|
|
|
945
884
|
console.log();
|
|
946
885
|
process.exit(0);
|
|
947
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
|
+
}
|
|
948
920
|
async function promptWebhookUrl() {
|
|
949
921
|
const { value } = await prompts5({
|
|
950
922
|
type: "text",
|
|
951
923
|
name: "value",
|
|
952
924
|
message: "Webhook URL",
|
|
953
|
-
validate:
|
|
925
|
+
validate: validateWebhookUrl
|
|
954
926
|
});
|
|
955
927
|
return value;
|
|
956
928
|
}
|
|
@@ -1092,20 +1064,31 @@ async function main() {
|
|
|
1092
1064
|
showHelp();
|
|
1093
1065
|
return;
|
|
1094
1066
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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
|
+
}
|
|
1109
1092
|
}
|
|
1110
1093
|
try {
|
|
1111
1094
|
switch (command) {
|
|
@@ -1153,38 +1136,40 @@ function showHelp() {
|
|
|
1153
1136
|
console.log(`
|
|
1154
1137
|
${cyan4("towns-agent")} - CLI for creating and managing Towns Protocol agent projects
|
|
1155
1138
|
|
|
1156
|
-
${
|
|
1139
|
+
${yellow4("Usage:")}
|
|
1157
1140
|
towns-agent <command> [options]
|
|
1158
1141
|
|
|
1159
|
-
${
|
|
1142
|
+
${yellow4("Commands:")}
|
|
1160
1143
|
${green5("init")} [project-name] Create a new agent project
|
|
1161
1144
|
${green5("create")} Create a new bot/app account interactively
|
|
1162
|
-
${green5("setup")}
|
|
1163
|
-
${green5("metadata")} <view|update> View or update app metadata
|
|
1145
|
+
${green5("setup")} [appAddress] Register webhook and notification settings
|
|
1146
|
+
${green5("metadata")} <view|update> [appAddress] View or update app metadata
|
|
1164
1147
|
${green5("update")} Update @towns-labs dependencies and skills
|
|
1165
1148
|
${green5("install-skill")} Install Towns Agent Skills to current project
|
|
1166
1149
|
|
|
1167
|
-
${
|
|
1150
|
+
${yellow4("Init Options:")}
|
|
1168
1151
|
-t, --template <name> Template to use:
|
|
1169
1152
|
${Object.entries(TEMPLATES).map(
|
|
1170
1153
|
([key, template]) => ` ${key} - ${template.description}`
|
|
1171
1154
|
).join("\n")}
|
|
1172
1155
|
Default: quickstart
|
|
1173
1156
|
|
|
1174
|
-
${
|
|
1157
|
+
${yellow4("List Commands Options:")}
|
|
1175
1158
|
-f, --file <path> Path to commands file
|
|
1176
1159
|
|
|
1177
|
-
${
|
|
1160
|
+
${yellow4("Update Commands Options:")}
|
|
1178
1161
|
-f, --file <path> Path to commands file
|
|
1179
1162
|
-t, --bearerToken <token> Bearer token for authentication
|
|
1180
1163
|
-e, --envFile <path> Path to .env file (default: .env)
|
|
1181
1164
|
--skip-agents-md Skip updating AGENTS.md file
|
|
1182
1165
|
|
|
1183
|
-
${
|
|
1166
|
+
${yellow4("Environment Options (create, setup, metadata):")}
|
|
1184
1167
|
--env <name> Environment to use: local_dev, stage, prod
|
|
1168
|
+
|
|
1169
|
+
${yellow4("Global Options:")}
|
|
1185
1170
|
-h, --help Show this help message
|
|
1186
1171
|
|
|
1187
|
-
${
|
|
1172
|
+
${yellow4("Examples:")}
|
|
1188
1173
|
${cyan4("# Create a new agent project")}
|
|
1189
1174
|
towns-agent init my-agent
|
|
1190
1175
|
towns-agent init my-ai-agent --template quickstart
|