towns-agent 2.0.4 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -8
- package/dist/index.js +214 -207
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +205 -199
- 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 +35 -0
- package/templates/quickstart/src/commands.ts +12 -0
- package/templates/quickstart/src/index.ts +56 -0
- package/templates/quickstart/tsconfig.json +25 -0
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
3
|
-
import { config as dotenvConfig } from "dotenv";
|
|
4
|
-
import { green as green5, red as red7, yellow as yellow3, cyan as cyan4 } from "picocolors";
|
|
2
|
+
import { green as green5, red as red7, yellow as yellow4, cyan as cyan4 } from "picocolors";
|
|
5
3
|
|
|
6
4
|
// src/modules/init.ts
|
|
7
5
|
import * as fs2 from "fs";
|
|
@@ -13,9 +11,17 @@ import * as jsonc from "jsonc-parser";
|
|
|
13
11
|
// src/modules/utils.ts
|
|
14
12
|
import * as fs from "fs";
|
|
15
13
|
import * as path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
16
15
|
import { default as spawn } from "cross-spawn";
|
|
17
16
|
import { default as prompts } from "prompts";
|
|
18
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.6";
|
|
23
|
+
|
|
24
|
+
// src/modules/utils.ts
|
|
19
25
|
var getPackageManager = () => {
|
|
20
26
|
if (process.env.npm_config_user_agent) {
|
|
21
27
|
const agent = process.env.npm_config_user_agent;
|
|
@@ -79,112 +85,52 @@ function runCommand(command, args, opts = { cwd: void 0, silent: false }) {
|
|
|
79
85
|
child.on("error", reject);
|
|
80
86
|
});
|
|
81
87
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
});
|
|
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");
|
|
100
95
|
}
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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);
|
|
96
|
+
function copyTemplate(templateName, targetDir) {
|
|
97
|
+
console.log(picocolors.blue("Copying template..."));
|
|
98
|
+
const templatesDir = getTemplatesDir();
|
|
99
|
+
const sourceDir = path.join(templatesDir, templateName);
|
|
171
100
|
if (!fs.existsSync(sourceDir)) {
|
|
172
|
-
console.error(picocolors.red(`
|
|
173
|
-
Template directory not found at ${sourceDir}`));
|
|
174
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
101
|
+
console.error(picocolors.red(`Template "${templateName}" not found at ${sourceDir}`));
|
|
175
102
|
return false;
|
|
176
103
|
}
|
|
177
104
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
178
105
|
fs.cpSync(sourceDir, targetDir, {
|
|
179
106
|
recursive: true,
|
|
180
|
-
filter: () => {
|
|
181
|
-
|
|
107
|
+
filter: (source) => {
|
|
108
|
+
const basename2 = path.basename(source);
|
|
109
|
+
return basename2 !== "node_modules" && basename2 !== "dist";
|
|
182
110
|
}
|
|
183
111
|
});
|
|
184
|
-
|
|
185
|
-
|
|
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!");
|
|
186
118
|
return true;
|
|
187
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
|
+
}
|
|
188
134
|
function applyReplacements(targetDir, replacements) {
|
|
189
135
|
function processDirectory(dir) {
|
|
190
136
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -310,71 +256,35 @@ async function installTownsSkills(projectDir) {
|
|
|
310
256
|
return false;
|
|
311
257
|
}
|
|
312
258
|
}
|
|
313
|
-
|
|
314
|
-
|
|
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
|
+
}
|
|
315
267
|
try {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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;
|
|
268
|
+
return parseAppPrivateData(appPrivateData);
|
|
269
|
+
} catch {
|
|
270
|
+
return void 0;
|
|
376
271
|
}
|
|
377
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
|
+
}
|
|
378
288
|
async function promptAuth() {
|
|
379
289
|
const { method } = await prompts({
|
|
380
290
|
type: "select",
|
|
@@ -402,7 +312,7 @@ var TEMPLATES = {
|
|
|
402
312
|
quickstart: {
|
|
403
313
|
name: "Agent Quickstart",
|
|
404
314
|
description: "Simple starter agent with basic commands",
|
|
405
|
-
packagePath: "
|
|
315
|
+
packagePath: "quickstart"
|
|
406
316
|
}
|
|
407
317
|
};
|
|
408
318
|
async function init(argv) {
|
|
@@ -439,12 +349,12 @@ async function init(argv) {
|
|
|
439
349
|
const packageManager = getPackageManager();
|
|
440
350
|
const selectedTemplate = TEMPLATES[template];
|
|
441
351
|
try {
|
|
442
|
-
const success =
|
|
352
|
+
const success = copyTemplate(selectedTemplate.packagePath, targetDir);
|
|
443
353
|
if (!success) {
|
|
444
|
-
console.error(red("Failed to
|
|
354
|
+
console.error(red("Failed to copy template"));
|
|
445
355
|
process.exit(1);
|
|
446
356
|
}
|
|
447
|
-
const latestVersion =
|
|
357
|
+
const latestVersion = version;
|
|
448
358
|
const replacements = /* @__PURE__ */ new Map([
|
|
449
359
|
["workspace:\\^", `^${latestVersion}`],
|
|
450
360
|
["workspace:\\*", `^${latestVersion}`]
|
|
@@ -469,19 +379,12 @@ async function init(argv) {
|
|
|
469
379
|
if (skillSuccess) {
|
|
470
380
|
console.log(green("\u2713"), "Towns Agent Skills installed successfully!");
|
|
471
381
|
} else {
|
|
472
|
-
console.log(
|
|
473
|
-
yellow("\u26A0"),
|
|
474
|
-
"Failed to install Towns Agent Skills. You can install them later with:"
|
|
475
|
-
);
|
|
382
|
+
console.log(yellow("\u26A0"), "Skipping Towns Agent Skills. Install later with:");
|
|
476
383
|
console.log(yellow(` cd ${projectName} && towns-agent install-skill`));
|
|
477
384
|
}
|
|
478
|
-
} catch
|
|
479
|
-
console.log(
|
|
480
|
-
|
|
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`));
|
|
385
|
+
} catch {
|
|
386
|
+
console.log(yellow("\u26A0"), "Skipping Towns Agent Skills. Install later with:");
|
|
387
|
+
console.log(yellow(` cd ${projectName} && towns-agent install-skill`));
|
|
485
388
|
}
|
|
486
389
|
await initializeGitRepository(targetDir);
|
|
487
390
|
printSuccess(projectName, packageManager);
|
|
@@ -500,9 +403,9 @@ function getTownsVersions(packageJson) {
|
|
|
500
403
|
const versions = {};
|
|
501
404
|
for (const deps of [packageJson.dependencies, packageJson.devDependencies]) {
|
|
502
405
|
if (deps) {
|
|
503
|
-
for (const [pkg,
|
|
406
|
+
for (const [pkg, version2] of Object.entries(deps)) {
|
|
504
407
|
if (pkg.startsWith("@towns-labs/") || pkg.startsWith("@towns-protocol/")) {
|
|
505
|
-
versions[pkg] =
|
|
408
|
+
versions[pkg] = version2;
|
|
506
409
|
}
|
|
507
410
|
}
|
|
508
411
|
}
|
|
@@ -585,7 +488,7 @@ async function update(_argv) {
|
|
|
585
488
|
console.log();
|
|
586
489
|
console.log(cyan2("Updating AGENTS.md..."));
|
|
587
490
|
try {
|
|
588
|
-
const agentsMdSuccess =
|
|
491
|
+
const agentsMdSuccess = copyAgentsMd(projectDir);
|
|
589
492
|
if (agentsMdSuccess) {
|
|
590
493
|
console.log(green2("\u2713"), "AGENTS.md updated successfully!");
|
|
591
494
|
} else {
|
|
@@ -688,7 +591,7 @@ async function create(argv) {
|
|
|
688
591
|
if (!addresses?.accountProxy) {
|
|
689
592
|
throw new Error(`No accountProxy address found for ${townsConfig.environmentId}/${chainId}`);
|
|
690
593
|
}
|
|
691
|
-
const relayerUrl =
|
|
594
|
+
const relayerUrl = townsConfig.services.relayer.url;
|
|
692
595
|
const relayerClient = createPublicClient({
|
|
693
596
|
chain: {
|
|
694
597
|
id: chainId,
|
|
@@ -754,13 +657,17 @@ var FIELD_DEFS = [
|
|
|
754
657
|
];
|
|
755
658
|
async function metadata(argv) {
|
|
756
659
|
const subcommand = argv._[1];
|
|
757
|
-
const appAddress = argv._[2];
|
|
660
|
+
const appAddress = resolveAppAddress(argv._[2]);
|
|
758
661
|
if (!subcommand || !["view", "update"].includes(subcommand)) {
|
|
759
|
-
console.error(red5("Usage: towns-agent metadata <view|update>
|
|
662
|
+
console.error(red5("Usage: towns-agent metadata <view|update> [appAddress] [options]"));
|
|
760
663
|
process.exit(1);
|
|
761
664
|
}
|
|
762
665
|
if (!appAddress) {
|
|
763
|
-
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
|
+
);
|
|
764
671
|
process.exit(1);
|
|
765
672
|
}
|
|
766
673
|
const env = townsEnv2();
|
|
@@ -861,7 +768,7 @@ async function metadata(argv) {
|
|
|
861
768
|
|
|
862
769
|
// src/modules/setup.ts
|
|
863
770
|
import { default as prompts5 } from "prompts";
|
|
864
|
-
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";
|
|
865
772
|
import { privateKeyToAccount as privateKeyToAccount3, generatePrivateKey as generatePrivateKey3 } from "viem/accounts";
|
|
866
773
|
import {
|
|
867
774
|
AppRegistryService as AppRegistryService2,
|
|
@@ -882,9 +789,13 @@ var NOTIFY_LABELS = {
|
|
|
882
789
|
NONE: "No messages"
|
|
883
790
|
};
|
|
884
791
|
async function setup(argv) {
|
|
885
|
-
const appAddress = argv._[1];
|
|
792
|
+
const appAddress = resolveAppAddress(argv._[1]);
|
|
886
793
|
if (!appAddress) {
|
|
887
|
-
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
|
+
);
|
|
888
799
|
process.exit(1);
|
|
889
800
|
}
|
|
890
801
|
let ownerPrivateKey = argv.ownerPrivateKey;
|
|
@@ -901,11 +812,18 @@ async function setup(argv) {
|
|
|
901
812
|
bearerToken = auth.value;
|
|
902
813
|
}
|
|
903
814
|
}
|
|
904
|
-
|
|
815
|
+
let webhookUrl = argv.webhookUrl ?? await promptWebhookUrl();
|
|
905
816
|
if (!webhookUrl) {
|
|
906
817
|
console.error(red6("Webhook URL is required."));
|
|
907
818
|
process.exit(1);
|
|
908
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
|
+
}
|
|
909
827
|
let notifyKey;
|
|
910
828
|
if (argv.notify) {
|
|
911
829
|
notifyKey = argv.notify.toUpperCase();
|
|
@@ -934,7 +852,26 @@ async function setup(argv) {
|
|
|
934
852
|
signerContext,
|
|
935
853
|
appRegistryUrl
|
|
936
854
|
);
|
|
937
|
-
|
|
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
|
+
}
|
|
938
875
|
await appRegistryRpcClient.setAppSettings({
|
|
939
876
|
appId,
|
|
940
877
|
settings: { forwardSetting }
|
|
@@ -947,12 +884,48 @@ async function setup(argv) {
|
|
|
947
884
|
console.log();
|
|
948
885
|
process.exit(0);
|
|
949
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.protocol === "http:") {
|
|
913
|
+
return "Local bots must use HTTPS. Use https:// instead of http://";
|
|
914
|
+
}
|
|
915
|
+
if (isLocalhost && !parsed.port) {
|
|
916
|
+
return "Localhost URL is missing a port. Example: https://localhost:3000";
|
|
917
|
+
}
|
|
918
|
+
return true;
|
|
919
|
+
} catch {
|
|
920
|
+
return "Invalid URL format. Example: https://localhost:3000/webhook";
|
|
921
|
+
}
|
|
922
|
+
}
|
|
950
923
|
async function promptWebhookUrl() {
|
|
951
924
|
const { value } = await prompts5({
|
|
952
925
|
type: "text",
|
|
953
926
|
name: "value",
|
|
954
927
|
message: "Webhook URL",
|
|
955
|
-
validate:
|
|
928
|
+
validate: validateWebhookUrl
|
|
956
929
|
});
|
|
957
930
|
return value;
|
|
958
931
|
}
|
|
@@ -974,6 +947,9 @@ async function promptNotify() {
|
|
|
974
947
|
return value;
|
|
975
948
|
}
|
|
976
949
|
|
|
950
|
+
// src/index.ts
|
|
951
|
+
import { townsEnv as townsEnv4 } from "@towns-labs/sdk";
|
|
952
|
+
|
|
977
953
|
// src/parser.ts
|
|
978
954
|
import minimist from "minimist";
|
|
979
955
|
var COMMAND_CONFIGS = {
|
|
@@ -1042,6 +1018,7 @@ var COMMAND_CONFIGS = {
|
|
|
1042
1018
|
function parseArgs(args) {
|
|
1043
1019
|
const initial = minimist(args, {
|
|
1044
1020
|
stopEarly: true,
|
|
1021
|
+
string: ["env"],
|
|
1045
1022
|
boolean: ["help"],
|
|
1046
1023
|
alias: { h: "help" }
|
|
1047
1024
|
});
|
|
@@ -1051,8 +1028,10 @@ function parseArgs(args) {
|
|
|
1051
1028
|
}
|
|
1052
1029
|
const commandConfig = COMMAND_CONFIGS[command] || {};
|
|
1053
1030
|
const booleanOptions = Array.isArray(commandConfig.boolean) ? ["help", ...commandConfig.boolean] : ["help"];
|
|
1031
|
+
const stringOptions = Array.isArray(commandConfig.string) ? ["env", ...commandConfig.string] : ["env"];
|
|
1054
1032
|
const parsed = minimist(args, {
|
|
1055
1033
|
...commandConfig,
|
|
1034
|
+
string: stringOptions,
|
|
1056
1035
|
boolean: booleanOptions,
|
|
1057
1036
|
alias: {
|
|
1058
1037
|
...commandConfig.alias,
|
|
@@ -1081,8 +1060,6 @@ function isSetupArgs(args) {
|
|
|
1081
1060
|
}
|
|
1082
1061
|
|
|
1083
1062
|
// src/index.ts
|
|
1084
|
-
dotenvConfig({ path: resolve2(__dirname, "../../generated/deployments/local_dev/.env") });
|
|
1085
|
-
dotenvConfig({ path: resolve2(__dirname, "../../contracts/deployments/envs/local/.env") });
|
|
1086
1063
|
async function main() {
|
|
1087
1064
|
const args = parseArgs(process.argv.slice(2));
|
|
1088
1065
|
const command = args._[0];
|
|
@@ -1090,6 +1067,32 @@ async function main() {
|
|
|
1090
1067
|
showHelp();
|
|
1091
1068
|
return;
|
|
1092
1069
|
}
|
|
1070
|
+
const requiresEnv = ["create", "setup", "metadata"].includes(command);
|
|
1071
|
+
if (requiresEnv) {
|
|
1072
|
+
if (args.env) {
|
|
1073
|
+
process.env.RIVER_ENV = args.env;
|
|
1074
|
+
}
|
|
1075
|
+
if (!process.env.RIVER_ENV) {
|
|
1076
|
+
const resolvedEnv = resolveRiverEnv();
|
|
1077
|
+
if (resolvedEnv) {
|
|
1078
|
+
process.env.RIVER_ENV = resolvedEnv;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (!process.env.RIVER_ENV) {
|
|
1082
|
+
console.error(
|
|
1083
|
+
red7(
|
|
1084
|
+
"Environment is required. Use --env <local_dev|stage|prod>, set RIVER_ENV, or set APP_PRIVATE_DATA in .env."
|
|
1085
|
+
)
|
|
1086
|
+
);
|
|
1087
|
+
process.exit(1);
|
|
1088
|
+
}
|
|
1089
|
+
try {
|
|
1090
|
+
townsEnv4().makeTownsConfig();
|
|
1091
|
+
} catch {
|
|
1092
|
+
console.error(red7(`Invalid environment: ${process.env.RIVER_ENV}`));
|
|
1093
|
+
process.exit(1);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1093
1096
|
try {
|
|
1094
1097
|
switch (command) {
|
|
1095
1098
|
case "init":
|
|
@@ -1136,37 +1139,40 @@ function showHelp() {
|
|
|
1136
1139
|
console.log(`
|
|
1137
1140
|
${cyan4("towns-agent")} - CLI for creating and managing Towns Protocol agent projects
|
|
1138
1141
|
|
|
1139
|
-
${
|
|
1142
|
+
${yellow4("Usage:")}
|
|
1140
1143
|
towns-agent <command> [options]
|
|
1141
1144
|
|
|
1142
|
-
${
|
|
1145
|
+
${yellow4("Commands:")}
|
|
1143
1146
|
${green5("init")} [project-name] Create a new agent project
|
|
1144
1147
|
${green5("create")} Create a new bot/app account interactively
|
|
1145
|
-
${green5("setup")}
|
|
1146
|
-
${green5("metadata")} <view|update> View or update app metadata
|
|
1148
|
+
${green5("setup")} [appAddress] Register webhook and notification settings
|
|
1149
|
+
${green5("metadata")} <view|update> [appAddress] View or update app metadata
|
|
1147
1150
|
${green5("update")} Update @towns-labs dependencies and skills
|
|
1148
1151
|
${green5("install-skill")} Install Towns Agent Skills to current project
|
|
1149
1152
|
|
|
1150
|
-
${
|
|
1153
|
+
${yellow4("Init Options:")}
|
|
1151
1154
|
-t, --template <name> Template to use:
|
|
1152
1155
|
${Object.entries(TEMPLATES).map(
|
|
1153
1156
|
([key, template]) => ` ${key} - ${template.description}`
|
|
1154
1157
|
).join("\n")}
|
|
1155
1158
|
Default: quickstart
|
|
1156
1159
|
|
|
1157
|
-
${
|
|
1160
|
+
${yellow4("List Commands Options:")}
|
|
1158
1161
|
-f, --file <path> Path to commands file
|
|
1159
1162
|
|
|
1160
|
-
${
|
|
1163
|
+
${yellow4("Update Commands Options:")}
|
|
1161
1164
|
-f, --file <path> Path to commands file
|
|
1162
1165
|
-t, --bearerToken <token> Bearer token for authentication
|
|
1163
1166
|
-e, --envFile <path> Path to .env file (default: .env)
|
|
1164
1167
|
--skip-agents-md Skip updating AGENTS.md file
|
|
1165
1168
|
|
|
1166
|
-
${
|
|
1169
|
+
${yellow4("Environment Options (create, setup, metadata):")}
|
|
1170
|
+
--env <name> Environment to use: local_dev, stage, prod
|
|
1171
|
+
|
|
1172
|
+
${yellow4("Global Options:")}
|
|
1167
1173
|
-h, --help Show this help message
|
|
1168
1174
|
|
|
1169
|
-
${
|
|
1175
|
+
${yellow4("Examples:")}
|
|
1170
1176
|
${cyan4("# Create a new agent project")}
|
|
1171
1177
|
towns-agent init my-agent
|
|
1172
1178
|
towns-agent init my-ai-agent --template quickstart
|