@agentforge-ai/cli 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -316,8 +316,8 @@ var CloudClient = class {
|
|
|
316
316
|
*/
|
|
317
317
|
getUrl(endpoint) {
|
|
318
318
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
319
|
-
const
|
|
320
|
-
return `${base}${
|
|
319
|
+
const path14 = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
|
320
|
+
return `${base}${path14}`;
|
|
321
321
|
}
|
|
322
322
|
/**
|
|
323
323
|
* Get request headers with authentication
|
|
@@ -2770,6 +2770,268 @@ console.log('Hello from ${name}!');
|
|
|
2770
2770
|
});
|
|
2771
2771
|
}
|
|
2772
2772
|
|
|
2773
|
+
// src/commands/skill.ts
|
|
2774
|
+
import fs7 from "fs-extra";
|
|
2775
|
+
import path7 from "path";
|
|
2776
|
+
import os2 from "os";
|
|
2777
|
+
import { execFileSync } from "child_process";
|
|
2778
|
+
function getGlobalSkillsDir() {
|
|
2779
|
+
return path7.join(os2.homedir(), ".agentforge", "skills");
|
|
2780
|
+
}
|
|
2781
|
+
function parseSkillMd2(content) {
|
|
2782
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2783
|
+
if (!fmMatch) {
|
|
2784
|
+
return { name: "", description: "", version: "1.0.0" };
|
|
2785
|
+
}
|
|
2786
|
+
const frontmatter = fmMatch[1];
|
|
2787
|
+
const data = {};
|
|
2788
|
+
for (const line of frontmatter.split("\n")) {
|
|
2789
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
2790
|
+
if (match) {
|
|
2791
|
+
data[match[1]] = match[2].trim();
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
return {
|
|
2795
|
+
name: data["name"] || "",
|
|
2796
|
+
description: data["description"] || "",
|
|
2797
|
+
version: data["version"] || "1.0.0"
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
async function installSkillFromPath(sourcePath, skillsDir) {
|
|
2801
|
+
if (!await fs7.pathExists(sourcePath)) {
|
|
2802
|
+
throw new Error(`Source path not found: ${sourcePath}`);
|
|
2803
|
+
}
|
|
2804
|
+
const skillMdPath = path7.join(sourcePath, "SKILL.md");
|
|
2805
|
+
if (!await fs7.pathExists(skillMdPath)) {
|
|
2806
|
+
throw new Error(`No SKILL.md found in ${sourcePath}. Not a valid skill directory.`);
|
|
2807
|
+
}
|
|
2808
|
+
const skillName = path7.basename(sourcePath);
|
|
2809
|
+
const destPath = path7.join(skillsDir, skillName);
|
|
2810
|
+
await fs7.mkdirp(skillsDir);
|
|
2811
|
+
await fs7.copy(sourcePath, destPath, { overwrite: true });
|
|
2812
|
+
return destPath;
|
|
2813
|
+
}
|
|
2814
|
+
async function listInstalledSkills(skillsDir) {
|
|
2815
|
+
if (!await fs7.pathExists(skillsDir)) {
|
|
2816
|
+
return [];
|
|
2817
|
+
}
|
|
2818
|
+
const entries = await fs7.readdir(skillsDir);
|
|
2819
|
+
const skills = [];
|
|
2820
|
+
for (const entry of entries) {
|
|
2821
|
+
const entryPath = path7.join(skillsDir, entry);
|
|
2822
|
+
const stat = await fs7.stat(entryPath);
|
|
2823
|
+
if (!stat.isDirectory()) continue;
|
|
2824
|
+
const skillMdPath = path7.join(entryPath, "SKILL.md");
|
|
2825
|
+
if (!await fs7.pathExists(skillMdPath)) continue;
|
|
2826
|
+
const content = await fs7.readFile(skillMdPath, "utf-8");
|
|
2827
|
+
const meta = parseSkillMd2(content);
|
|
2828
|
+
skills.push({
|
|
2829
|
+
name: meta.name || entry,
|
|
2830
|
+
version: meta.version,
|
|
2831
|
+
description: meta.description
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2834
|
+
return skills;
|
|
2835
|
+
}
|
|
2836
|
+
async function removeSkill(name, skillsDir) {
|
|
2837
|
+
const skillDir = path7.join(skillsDir, name);
|
|
2838
|
+
if (!await fs7.pathExists(skillDir)) {
|
|
2839
|
+
throw new Error(`Skill "${name}" not found in ${skillsDir}`);
|
|
2840
|
+
}
|
|
2841
|
+
await fs7.remove(skillDir);
|
|
2842
|
+
}
|
|
2843
|
+
function getGitHubUrl(nameOrUrl) {
|
|
2844
|
+
if (nameOrUrl.startsWith("https://") || nameOrUrl.startsWith("http://")) {
|
|
2845
|
+
return nameOrUrl;
|
|
2846
|
+
}
|
|
2847
|
+
return `https://github.com/${nameOrUrl}`;
|
|
2848
|
+
}
|
|
2849
|
+
function getConvexUrl2() {
|
|
2850
|
+
return process.env["CONVEX_URL"] || process.env["NEXT_PUBLIC_CONVEX_URL"];
|
|
2851
|
+
}
|
|
2852
|
+
function registerSkillCommand(program2) {
|
|
2853
|
+
const skillCmd = program2.command("skill").description("Manage global skills installed in ~/.agentforge/skills/");
|
|
2854
|
+
skillCmd.command("install").argument("<name>", "Local path or GitHub owner/repo to install from").description("Install a skill to ~/.agentforge/skills/").action(async (name) => {
|
|
2855
|
+
const skillsDir = getGlobalSkillsDir();
|
|
2856
|
+
const isLocalPath = await fs7.pathExists(name);
|
|
2857
|
+
if (isLocalPath) {
|
|
2858
|
+
const sourcePath = path7.resolve(name);
|
|
2859
|
+
try {
|
|
2860
|
+
const installedPath = await installSkillFromPath(sourcePath, skillsDir);
|
|
2861
|
+
const skillName = path7.basename(installedPath);
|
|
2862
|
+
success(`Skill "${skillName}" installed from local path.`);
|
|
2863
|
+
info(`Location: ${installedPath}`);
|
|
2864
|
+
} catch (err) {
|
|
2865
|
+
error(err.message);
|
|
2866
|
+
process.exit(1);
|
|
2867
|
+
}
|
|
2868
|
+
} else {
|
|
2869
|
+
const repoUrl = getGitHubUrl(name);
|
|
2870
|
+
const repoName = name.split("/").pop().replace(/\.git$/, "");
|
|
2871
|
+
const destPath = path7.join(skillsDir, repoName);
|
|
2872
|
+
await fs7.mkdirp(skillsDir);
|
|
2873
|
+
info(`Cloning skill from ${repoUrl}...`);
|
|
2874
|
+
try {
|
|
2875
|
+
execFileSync("git", ["clone", "--depth", "1", repoUrl, destPath], {
|
|
2876
|
+
encoding: "utf-8",
|
|
2877
|
+
stdio: "pipe"
|
|
2878
|
+
});
|
|
2879
|
+
await fs7.remove(path7.join(destPath, ".git"));
|
|
2880
|
+
const skillMdPath = path7.join(destPath, "SKILL.md");
|
|
2881
|
+
if (!await fs7.pathExists(skillMdPath)) {
|
|
2882
|
+
error("Cloned repo does not contain a SKILL.md. Not a valid skill.");
|
|
2883
|
+
await fs7.remove(destPath);
|
|
2884
|
+
process.exit(1);
|
|
2885
|
+
}
|
|
2886
|
+
success(`Skill "${repoName}" installed from GitHub.`);
|
|
2887
|
+
info(`Location: ${destPath}`);
|
|
2888
|
+
} catch (err) {
|
|
2889
|
+
error(`Failed to clone: ${err.message}`);
|
|
2890
|
+
process.exit(1);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
});
|
|
2894
|
+
skillCmd.command("list").description("List skills installed in ~/.agentforge/skills/").option("--json", "Output as JSON").action(async (opts) => {
|
|
2895
|
+
const skillsDir = getGlobalSkillsDir();
|
|
2896
|
+
header("Global Skills");
|
|
2897
|
+
let skills;
|
|
2898
|
+
try {
|
|
2899
|
+
skills = await listInstalledSkills(skillsDir);
|
|
2900
|
+
} catch {
|
|
2901
|
+
skills = [];
|
|
2902
|
+
}
|
|
2903
|
+
if (skills.length === 0) {
|
|
2904
|
+
info("No global skills installed.");
|
|
2905
|
+
dim(
|
|
2906
|
+
` Install a skill with: ${colors.cyan}agentforge skill install <path-or-owner/repo>${colors.reset}`
|
|
2907
|
+
);
|
|
2908
|
+
return;
|
|
2909
|
+
}
|
|
2910
|
+
if (opts.json) {
|
|
2911
|
+
console.log(JSON.stringify(skills, null, 2));
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
table(
|
|
2915
|
+
skills.map((s) => ({
|
|
2916
|
+
Name: s.name,
|
|
2917
|
+
Version: s.version,
|
|
2918
|
+
Description: truncate(s.description, 60)
|
|
2919
|
+
}))
|
|
2920
|
+
);
|
|
2921
|
+
dim(` Skills directory: ${skillsDir}`);
|
|
2922
|
+
});
|
|
2923
|
+
skillCmd.command("remove").argument("<name>", "Skill name to remove").description("Remove a skill from ~/.agentforge/skills/").action(async (name) => {
|
|
2924
|
+
const skillsDir = getGlobalSkillsDir();
|
|
2925
|
+
try {
|
|
2926
|
+
await removeSkill(name, skillsDir);
|
|
2927
|
+
success(`Skill "${name}" removed.`);
|
|
2928
|
+
} catch (err) {
|
|
2929
|
+
error(err.message);
|
|
2930
|
+
info("List installed skills with: agentforge skill list");
|
|
2931
|
+
process.exit(1);
|
|
2932
|
+
}
|
|
2933
|
+
});
|
|
2934
|
+
skillCmd.command("search <query>").description("Search the skill marketplace").option("-c, --category <category>", "Filter by category").action(async (query, options) => {
|
|
2935
|
+
const { searchSkills: searchMarketplace } = await import("@agentforge-ai/core");
|
|
2936
|
+
const convexUrl = getConvexUrl2();
|
|
2937
|
+
if (!convexUrl) {
|
|
2938
|
+
error("No CONVEX_URL configured. Set CONVEX_URL environment variable.");
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
try {
|
|
2942
|
+
info(`Searching marketplace for "${query}"...`);
|
|
2943
|
+
const skills = await searchMarketplace(query, convexUrl, options.category);
|
|
2944
|
+
if (skills.length === 0) {
|
|
2945
|
+
info("No skills found matching your query.");
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
header(`Found ${skills.length} skill(s)`);
|
|
2949
|
+
table(
|
|
2950
|
+
skills.map((s) => ({
|
|
2951
|
+
Name: s.name,
|
|
2952
|
+
Version: s.version,
|
|
2953
|
+
Category: s.category,
|
|
2954
|
+
Downloads: s.downloads.toString(),
|
|
2955
|
+
Description: truncate(s.description, 50)
|
|
2956
|
+
}))
|
|
2957
|
+
);
|
|
2958
|
+
} catch (err) {
|
|
2959
|
+
error(`Search failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2960
|
+
}
|
|
2961
|
+
});
|
|
2962
|
+
skillCmd.command("publish").description("Publish a skill to the marketplace").option("-d, --dir <directory>", "Skill directory (default: current directory)", ".").action(async (options) => {
|
|
2963
|
+
const fsExtra = await import("fs-extra");
|
|
2964
|
+
const pathMod = await import("path");
|
|
2965
|
+
const { parseSkillManifest, publishSkill: publishToMarketplace } = await import("@agentforge-ai/core");
|
|
2966
|
+
const convexUrl = getConvexUrl2();
|
|
2967
|
+
if (!convexUrl) {
|
|
2968
|
+
error("No CONVEX_URL configured. Set CONVEX_URL environment variable.");
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
const skillDir = pathMod.resolve(options.dir);
|
|
2972
|
+
const skillMdPath = pathMod.join(skillDir, "SKILL.md");
|
|
2973
|
+
if (!await fsExtra.pathExists(skillMdPath)) {
|
|
2974
|
+
error("No SKILL.md found in the specified directory.");
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
try {
|
|
2978
|
+
const skillMdContent = await fsExtra.readFile(skillMdPath, "utf-8");
|
|
2979
|
+
const manifest = parseSkillManifest(skillMdContent);
|
|
2980
|
+
let readmeContent;
|
|
2981
|
+
const readmePath = pathMod.join(skillDir, "README.md");
|
|
2982
|
+
if (await fsExtra.pathExists(readmePath)) {
|
|
2983
|
+
readmeContent = await fsExtra.readFile(readmePath, "utf-8");
|
|
2984
|
+
}
|
|
2985
|
+
const meta = manifest.metadata ?? {};
|
|
2986
|
+
info(`Publishing "${manifest.name}" v${manifest.version}...`);
|
|
2987
|
+
await publishToMarketplace(
|
|
2988
|
+
{
|
|
2989
|
+
name: manifest.name,
|
|
2990
|
+
version: manifest.version,
|
|
2991
|
+
description: manifest.description,
|
|
2992
|
+
author: meta.author ?? "unknown",
|
|
2993
|
+
category: meta["category"] ?? "general",
|
|
2994
|
+
tags: meta.tags ?? [],
|
|
2995
|
+
skillMdContent,
|
|
2996
|
+
readmeContent,
|
|
2997
|
+
repositoryUrl: meta.repository
|
|
2998
|
+
},
|
|
2999
|
+
convexUrl
|
|
3000
|
+
);
|
|
3001
|
+
success(`Skill "${manifest.name}" published successfully!`);
|
|
3002
|
+
} catch (err) {
|
|
3003
|
+
error(`Publish failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3004
|
+
}
|
|
3005
|
+
});
|
|
3006
|
+
skillCmd.command("featured").description("Show featured skills from the marketplace").action(async () => {
|
|
3007
|
+
const { fetchFeaturedSkills } = await import("@agentforge-ai/core");
|
|
3008
|
+
const convexUrl = getConvexUrl2();
|
|
3009
|
+
if (!convexUrl) {
|
|
3010
|
+
error("No CONVEX_URL configured. Set CONVEX_URL environment variable.");
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
try {
|
|
3014
|
+
const skills = await fetchFeaturedSkills(convexUrl);
|
|
3015
|
+
if (skills.length === 0) {
|
|
3016
|
+
info("No featured skills available.");
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
header("Featured Skills");
|
|
3020
|
+
table(
|
|
3021
|
+
skills.map((s) => ({
|
|
3022
|
+
Name: s.name,
|
|
3023
|
+
Version: s.version,
|
|
3024
|
+
Category: s.category,
|
|
3025
|
+
Downloads: s.downloads.toString(),
|
|
3026
|
+
Description: truncate(s.description, 60)
|
|
3027
|
+
}))
|
|
3028
|
+
);
|
|
3029
|
+
} catch (err) {
|
|
3030
|
+
error(`Failed to fetch featured skills: ${err instanceof Error ? err.message : String(err)}`);
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
|
|
2773
3035
|
// src/commands/cron.ts
|
|
2774
3036
|
import readline4 from "readline";
|
|
2775
3037
|
function prompt3(q) {
|
|
@@ -2933,8 +3195,8 @@ function registerMcpCommand(program2) {
|
|
|
2933
3195
|
}
|
|
2934
3196
|
|
|
2935
3197
|
// src/commands/files.ts
|
|
2936
|
-
import
|
|
2937
|
-
import
|
|
3198
|
+
import fs8 from "fs-extra";
|
|
3199
|
+
import path8 from "path";
|
|
2938
3200
|
function registerFilesCommand(program2) {
|
|
2939
3201
|
const files = program2.command("files").description("Manage files");
|
|
2940
3202
|
files.command("list").argument("[folder]", "Folder ID to list files from").option("--json", "Output as JSON").description("List files").action(async (folder, opts) => {
|
|
@@ -2961,14 +3223,14 @@ function registerFilesCommand(program2) {
|
|
|
2961
3223
|
})));
|
|
2962
3224
|
});
|
|
2963
3225
|
files.command("upload").argument("<filepath>", "Path to file to upload").option("--folder <id>", "Folder ID to upload to").option("--project <id>", "Project ID to associate with").description("Upload a file").action(async (filepath, opts) => {
|
|
2964
|
-
const absPath =
|
|
2965
|
-
if (!
|
|
3226
|
+
const absPath = path8.resolve(filepath);
|
|
3227
|
+
if (!fs8.existsSync(absPath)) {
|
|
2966
3228
|
error(`File not found: ${absPath}`);
|
|
2967
3229
|
process.exit(1);
|
|
2968
3230
|
}
|
|
2969
|
-
const stat =
|
|
2970
|
-
const name =
|
|
2971
|
-
const ext =
|
|
3231
|
+
const stat = fs8.statSync(absPath);
|
|
3232
|
+
const name = path8.basename(absPath);
|
|
3233
|
+
const ext = path8.extname(absPath).toLowerCase();
|
|
2972
3234
|
const mimeTypes = {
|
|
2973
3235
|
".txt": "text/plain",
|
|
2974
3236
|
".md": "text/markdown",
|
|
@@ -3132,8 +3394,8 @@ function registerProjectsCommand(program2) {
|
|
|
3132
3394
|
}
|
|
3133
3395
|
|
|
3134
3396
|
// src/commands/config.ts
|
|
3135
|
-
import
|
|
3136
|
-
import
|
|
3397
|
+
import fs9 from "fs-extra";
|
|
3398
|
+
import path9 from "path";
|
|
3137
3399
|
import readline7 from "readline";
|
|
3138
3400
|
function prompt6(q) {
|
|
3139
3401
|
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -3149,10 +3411,10 @@ function registerConfigCommand(program2) {
|
|
|
3149
3411
|
const cwd = process.cwd();
|
|
3150
3412
|
const envFiles = [".env", ".env.local", ".env.production"];
|
|
3151
3413
|
for (const envFile of envFiles) {
|
|
3152
|
-
const envPath =
|
|
3153
|
-
if (
|
|
3414
|
+
const envPath = path9.join(cwd, envFile);
|
|
3415
|
+
if (fs9.existsSync(envPath)) {
|
|
3154
3416
|
console.log(` ${colors.cyan}${envFile}${colors.reset}`);
|
|
3155
|
-
const content =
|
|
3417
|
+
const content = fs9.readFileSync(envPath, "utf-8");
|
|
3156
3418
|
content.split("\n").forEach((line) => {
|
|
3157
3419
|
if (line.trim() && !line.startsWith("#")) {
|
|
3158
3420
|
const [key, ...rest] = line.split("=");
|
|
@@ -3164,25 +3426,25 @@ function registerConfigCommand(program2) {
|
|
|
3164
3426
|
console.log();
|
|
3165
3427
|
}
|
|
3166
3428
|
}
|
|
3167
|
-
const convexDir =
|
|
3168
|
-
if (
|
|
3429
|
+
const convexDir = path9.join(cwd, ".convex");
|
|
3430
|
+
if (fs9.existsSync(convexDir)) {
|
|
3169
3431
|
info("Convex: Configured");
|
|
3170
3432
|
} else {
|
|
3171
3433
|
info("Convex: Not configured (run `npx convex dev`)");
|
|
3172
3434
|
}
|
|
3173
|
-
const skillsDir =
|
|
3174
|
-
if (
|
|
3175
|
-
const skills =
|
|
3435
|
+
const skillsDir = path9.join(cwd, "skills");
|
|
3436
|
+
if (fs9.existsSync(skillsDir)) {
|
|
3437
|
+
const skills = fs9.readdirSync(skillsDir).filter((d) => fs9.statSync(path9.join(skillsDir, d)).isDirectory());
|
|
3176
3438
|
info(`Skills: ${skills.length} installed (${skills.join(", ")})`);
|
|
3177
3439
|
} else {
|
|
3178
3440
|
info("Skills: None installed");
|
|
3179
3441
|
}
|
|
3180
3442
|
});
|
|
3181
3443
|
config.command("set").argument("<key>", "Configuration key").argument("<value>", "Configuration value").option("--env <file>", "Environment file to update", ".env.local").description("Set a configuration value").action(async (key, value, opts) => {
|
|
3182
|
-
const envPath =
|
|
3444
|
+
const envPath = path9.join(process.cwd(), opts.env);
|
|
3183
3445
|
let content = "";
|
|
3184
|
-
if (
|
|
3185
|
-
content =
|
|
3446
|
+
if (fs9.existsSync(envPath)) {
|
|
3447
|
+
content = fs9.readFileSync(envPath, "utf-8");
|
|
3186
3448
|
}
|
|
3187
3449
|
const lines = content.split("\n");
|
|
3188
3450
|
const idx = lines.findIndex((l) => l.startsWith(`${key}=`));
|
|
@@ -3191,16 +3453,16 @@ function registerConfigCommand(program2) {
|
|
|
3191
3453
|
} else {
|
|
3192
3454
|
lines.push(`${key}=${value}`);
|
|
3193
3455
|
}
|
|
3194
|
-
|
|
3456
|
+
fs9.writeFileSync(envPath, lines.join("\n"));
|
|
3195
3457
|
success(`Set ${key} in ${opts.env}`);
|
|
3196
3458
|
});
|
|
3197
3459
|
config.command("get").argument("<key>", "Configuration key").description("Get a configuration value").action(async (key) => {
|
|
3198
3460
|
const cwd = process.cwd();
|
|
3199
3461
|
const envFiles = [".env.local", ".env", ".env.production"];
|
|
3200
3462
|
for (const envFile of envFiles) {
|
|
3201
|
-
const envPath =
|
|
3202
|
-
if (
|
|
3203
|
-
const content =
|
|
3463
|
+
const envPath = path9.join(cwd, envFile);
|
|
3464
|
+
if (fs9.existsSync(envPath)) {
|
|
3465
|
+
const content = fs9.readFileSync(envPath, "utf-8");
|
|
3204
3466
|
const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
|
|
3205
3467
|
if (match) {
|
|
3206
3468
|
console.log(match[1].trim());
|
|
@@ -3226,7 +3488,7 @@ function registerConfigCommand(program2) {
|
|
|
3226
3488
|
else if (provider === "openrouter") envContent.push(`OPENROUTER_API_KEY=${apiKey}`);
|
|
3227
3489
|
else if (provider === "anthropic") envContent.push(`ANTHROPIC_API_KEY=${apiKey}`);
|
|
3228
3490
|
else if (provider === "google") envContent.push(`GOOGLE_API_KEY=${apiKey}`);
|
|
3229
|
-
|
|
3491
|
+
fs9.writeFileSync(path9.join(process.cwd(), ".env.local"), envContent.join("\n") + "\n");
|
|
3230
3492
|
success("Configuration saved to .env.local");
|
|
3231
3493
|
info("Run `npx convex dev` to start the Convex backend.");
|
|
3232
3494
|
});
|
|
@@ -3248,9 +3510,9 @@ function registerConfigCommand(program2) {
|
|
|
3248
3510
|
error("API key is required.");
|
|
3249
3511
|
process.exit(1);
|
|
3250
3512
|
}
|
|
3251
|
-
const envPath =
|
|
3513
|
+
const envPath = path9.join(process.cwd(), ".env.local");
|
|
3252
3514
|
let content = "";
|
|
3253
|
-
if (
|
|
3515
|
+
if (fs9.existsSync(envPath)) content = fs9.readFileSync(envPath, "utf-8");
|
|
3254
3516
|
const lines = content.split("\n");
|
|
3255
3517
|
const idx = lines.findIndex((l) => l.startsWith(`${keyName}=`));
|
|
3256
3518
|
if (idx >= 0) lines[idx] = `${keyName}=${apiKey}`;
|
|
@@ -3258,7 +3520,7 @@ function registerConfigCommand(program2) {
|
|
|
3258
3520
|
const provIdx = lines.findIndex((l) => l.startsWith("LLM_PROVIDER="));
|
|
3259
3521
|
if (provIdx >= 0) lines[provIdx] = `LLM_PROVIDER=${provider}`;
|
|
3260
3522
|
else lines.push(`LLM_PROVIDER=${provider}`);
|
|
3261
|
-
|
|
3523
|
+
fs9.writeFileSync(envPath, lines.join("\n"));
|
|
3262
3524
|
success(`Provider "${provider}" configured.`);
|
|
3263
3525
|
});
|
|
3264
3526
|
}
|
|
@@ -3425,7 +3687,7 @@ function maskKey(key) {
|
|
|
3425
3687
|
}
|
|
3426
3688
|
function promptSecret2(question) {
|
|
3427
3689
|
return new Promise((resolve2) => {
|
|
3428
|
-
const
|
|
3690
|
+
const readline13 = __require("readline");
|
|
3429
3691
|
if (process.stdin.isTTY) {
|
|
3430
3692
|
process.stdout.write(question);
|
|
3431
3693
|
process.stdin.setRawMode(true);
|
|
@@ -3453,7 +3715,7 @@ function promptSecret2(question) {
|
|
|
3453
3715
|
};
|
|
3454
3716
|
process.stdin.on("data", onData);
|
|
3455
3717
|
} else {
|
|
3456
|
-
const rl =
|
|
3718
|
+
const rl = readline13.createInterface({ input: process.stdin, output: process.stdout });
|
|
3457
3719
|
rl.question(question, (ans) => {
|
|
3458
3720
|
rl.close();
|
|
3459
3721
|
resolve2(ans.trim());
|
|
@@ -3544,8 +3806,8 @@ function registerKeysCommand(program2) {
|
|
|
3544
3806
|
}
|
|
3545
3807
|
const target = items[0];
|
|
3546
3808
|
if (!opts.force) {
|
|
3547
|
-
const
|
|
3548
|
-
const rl =
|
|
3809
|
+
const readline13 = __require("readline");
|
|
3810
|
+
const rl = readline13.createInterface({ input: process.stdin, output: process.stdout });
|
|
3549
3811
|
const answer = await new Promise((resolve2) => {
|
|
3550
3812
|
rl.question(`Delete "${target.keyName}" for ${provider}? (y/N): `, (ans) => {
|
|
3551
3813
|
rl.close();
|
|
@@ -3617,19 +3879,19 @@ function registerKeysCommand(program2) {
|
|
|
3617
3879
|
|
|
3618
3880
|
// src/commands/status.ts
|
|
3619
3881
|
import { spawn as spawn2 } from "child_process";
|
|
3620
|
-
import
|
|
3621
|
-
import
|
|
3882
|
+
import path10 from "path";
|
|
3883
|
+
import fs10 from "fs-extra";
|
|
3622
3884
|
import readline9 from "readline";
|
|
3623
3885
|
function registerStatusCommand(program2) {
|
|
3624
3886
|
program2.command("status").description("Show system health and connection status").action(async () => {
|
|
3625
3887
|
header("AgentForge Status");
|
|
3626
3888
|
const cwd = process.cwd();
|
|
3627
3889
|
const checks = {};
|
|
3628
|
-
checks["Project Root"] =
|
|
3629
|
-
checks["Convex Dir"] =
|
|
3630
|
-
checks["Skills Dir"] =
|
|
3631
|
-
checks["Dashboard Dir"] =
|
|
3632
|
-
checks["Env Config"] =
|
|
3890
|
+
checks["Project Root"] = fs10.existsSync(path10.join(cwd, "package.json")) ? "\u2714 Found" : "\u2716 Not found";
|
|
3891
|
+
checks["Convex Dir"] = fs10.existsSync(path10.join(cwd, "convex")) ? "\u2714 Found" : "\u2716 Not found";
|
|
3892
|
+
checks["Skills Dir"] = fs10.existsSync(path10.join(cwd, "skills")) ? "\u2714 Found" : "\u2716 Not configured";
|
|
3893
|
+
checks["Dashboard Dir"] = fs10.existsSync(path10.join(cwd, "dashboard")) ? "\u2714 Found" : "\u2716 Not found";
|
|
3894
|
+
checks["Env Config"] = fs10.existsSync(path10.join(cwd, ".env.local")) || fs10.existsSync(path10.join(cwd, ".env")) ? "\u2714 Found" : "\u2716 Not found";
|
|
3633
3895
|
try {
|
|
3634
3896
|
const client = await createClient();
|
|
3635
3897
|
const agents = await client.query("agents:list", {});
|
|
@@ -3640,9 +3902,9 @@ function registerStatusCommand(program2) {
|
|
|
3640
3902
|
const envFiles = [".env.local", ".env"];
|
|
3641
3903
|
let provider = "Not configured";
|
|
3642
3904
|
for (const envFile of envFiles) {
|
|
3643
|
-
const envPath =
|
|
3644
|
-
if (
|
|
3645
|
-
const content =
|
|
3905
|
+
const envPath = path10.join(cwd, envFile);
|
|
3906
|
+
if (fs10.existsSync(envPath)) {
|
|
3907
|
+
const content = fs10.readFileSync(envPath, "utf-8");
|
|
3646
3908
|
const match = content.match(/LLM_PROVIDER=(.+)/);
|
|
3647
3909
|
if (match) {
|
|
3648
3910
|
provider = match[1].trim();
|
|
@@ -3664,16 +3926,16 @@ function registerStatusCommand(program2) {
|
|
|
3664
3926
|
program2.command("dashboard").description("Launch the web dashboard").option("-p, --port <port>", "Port for the dashboard", "3000").option("--install", "Install dashboard dependencies before starting").action(async (opts) => {
|
|
3665
3927
|
const cwd = process.cwd();
|
|
3666
3928
|
const searchPaths = [
|
|
3667
|
-
|
|
3929
|
+
path10.join(cwd, "dashboard"),
|
|
3668
3930
|
// 1. Bundled in project (agentforge create)
|
|
3669
|
-
|
|
3931
|
+
path10.join(cwd, "packages", "web"),
|
|
3670
3932
|
// 2. Monorepo structure
|
|
3671
|
-
|
|
3933
|
+
path10.join(cwd, "node_modules", "@agentforge-ai", "web")
|
|
3672
3934
|
// 3. Installed as dependency
|
|
3673
3935
|
];
|
|
3674
3936
|
let dashDir = "";
|
|
3675
3937
|
for (const p of searchPaths) {
|
|
3676
|
-
if (
|
|
3938
|
+
if (fs10.existsSync(path10.join(p, "package.json"))) {
|
|
3677
3939
|
dashDir = p;
|
|
3678
3940
|
break;
|
|
3679
3941
|
}
|
|
@@ -3695,10 +3957,10 @@ function registerStatusCommand(program2) {
|
|
|
3695
3957
|
console.log();
|
|
3696
3958
|
return;
|
|
3697
3959
|
}
|
|
3698
|
-
const nodeModulesExists =
|
|
3960
|
+
const nodeModulesExists = fs10.existsSync(path10.join(dashDir, "node_modules"));
|
|
3699
3961
|
if (!nodeModulesExists || opts.install) {
|
|
3700
3962
|
header("AgentForge Dashboard \u2014 Installing Dependencies");
|
|
3701
|
-
info(`Installing in ${
|
|
3963
|
+
info(`Installing in ${path10.relative(cwd, dashDir) || "."}...`);
|
|
3702
3964
|
console.log();
|
|
3703
3965
|
const installChild = spawn2("pnpm", ["install"], {
|
|
3704
3966
|
cwd: dashDir,
|
|
@@ -3716,15 +3978,15 @@ function registerStatusCommand(program2) {
|
|
|
3716
3978
|
success("Dependencies installed.");
|
|
3717
3979
|
console.log();
|
|
3718
3980
|
}
|
|
3719
|
-
const envPath =
|
|
3720
|
-
if (
|
|
3721
|
-
const envContent =
|
|
3981
|
+
const envPath = path10.join(cwd, ".env.local");
|
|
3982
|
+
if (fs10.existsSync(envPath)) {
|
|
3983
|
+
const envContent = fs10.readFileSync(envPath, "utf-8");
|
|
3722
3984
|
const convexUrlMatch = envContent.match(/CONVEX_URL=(.+)/);
|
|
3723
3985
|
if (convexUrlMatch) {
|
|
3724
|
-
const dashEnvPath =
|
|
3986
|
+
const dashEnvPath = path10.join(dashDir, ".env.local");
|
|
3725
3987
|
const dashEnvContent = `VITE_CONVEX_URL=${convexUrlMatch[1].trim()}
|
|
3726
3988
|
`;
|
|
3727
|
-
|
|
3989
|
+
fs10.writeFileSync(dashEnvPath, dashEnvContent);
|
|
3728
3990
|
}
|
|
3729
3991
|
}
|
|
3730
3992
|
header("AgentForge Dashboard");
|
|
@@ -3944,8 +4206,8 @@ function registerLoginCommand(program2) {
|
|
|
3944
4206
|
}
|
|
3945
4207
|
|
|
3946
4208
|
// src/commands/channel-telegram.ts
|
|
3947
|
-
import
|
|
3948
|
-
import
|
|
4209
|
+
import fs11 from "fs-extra";
|
|
4210
|
+
import path11 from "path";
|
|
3949
4211
|
import readline10 from "readline";
|
|
3950
4212
|
function prompt8(q) {
|
|
3951
4213
|
const rl = readline10.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -3958,9 +4220,9 @@ function readEnvValue(key) {
|
|
|
3958
4220
|
const cwd = process.cwd();
|
|
3959
4221
|
const envFiles = [".env.local", ".env", ".env.production"];
|
|
3960
4222
|
for (const envFile of envFiles) {
|
|
3961
|
-
const envPath =
|
|
3962
|
-
if (
|
|
3963
|
-
const content =
|
|
4223
|
+
const envPath = path11.join(cwd, envFile);
|
|
4224
|
+
if (fs11.existsSync(envPath)) {
|
|
4225
|
+
const content = fs11.readFileSync(envPath, "utf-8");
|
|
3964
4226
|
const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
|
|
3965
4227
|
if (match) return match[1].trim().replace(/["']/g, "");
|
|
3966
4228
|
}
|
|
@@ -3968,10 +4230,10 @@ function readEnvValue(key) {
|
|
|
3968
4230
|
return void 0;
|
|
3969
4231
|
}
|
|
3970
4232
|
function writeEnvValue(key, value, envFile = ".env.local") {
|
|
3971
|
-
const envPath =
|
|
4233
|
+
const envPath = path11.join(process.cwd(), envFile);
|
|
3972
4234
|
let content = "";
|
|
3973
|
-
if (
|
|
3974
|
-
content =
|
|
4235
|
+
if (fs11.existsSync(envPath)) {
|
|
4236
|
+
content = fs11.readFileSync(envPath, "utf-8");
|
|
3975
4237
|
}
|
|
3976
4238
|
const lines = content.split("\n");
|
|
3977
4239
|
const idx = lines.findIndex((l) => l.startsWith(`${key}=`));
|
|
@@ -3980,7 +4242,7 @@ function writeEnvValue(key, value, envFile = ".env.local") {
|
|
|
3980
4242
|
} else {
|
|
3981
4243
|
lines.push(`${key}=${value}`);
|
|
3982
4244
|
}
|
|
3983
|
-
|
|
4245
|
+
fs11.writeFileSync(envPath, lines.join("\n"));
|
|
3984
4246
|
}
|
|
3985
4247
|
function registerChannelTelegramCommand(program2) {
|
|
3986
4248
|
const channel = program2.command("channel:telegram").description("Manage the Telegram messaging channel");
|
|
@@ -4309,8 +4571,8 @@ Commands:
|
|
|
4309
4571
|
}
|
|
4310
4572
|
|
|
4311
4573
|
// src/commands/channel-whatsapp.ts
|
|
4312
|
-
import
|
|
4313
|
-
import
|
|
4574
|
+
import fs12 from "fs-extra";
|
|
4575
|
+
import path12 from "path";
|
|
4314
4576
|
import readline11 from "readline";
|
|
4315
4577
|
function prompt9(q) {
|
|
4316
4578
|
const rl = readline11.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -4323,9 +4585,9 @@ function readEnvValue2(key) {
|
|
|
4323
4585
|
const cwd = process.cwd();
|
|
4324
4586
|
const envFiles = [".env.local", ".env", ".env.production"];
|
|
4325
4587
|
for (const envFile of envFiles) {
|
|
4326
|
-
const envPath =
|
|
4327
|
-
if (
|
|
4328
|
-
const content =
|
|
4588
|
+
const envPath = path12.join(cwd, envFile);
|
|
4589
|
+
if (fs12.existsSync(envPath)) {
|
|
4590
|
+
const content = fs12.readFileSync(envPath, "utf-8");
|
|
4329
4591
|
const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
|
|
4330
4592
|
if (match) return match[1].trim().replace(/["']/g, "");
|
|
4331
4593
|
}
|
|
@@ -4333,10 +4595,10 @@ function readEnvValue2(key) {
|
|
|
4333
4595
|
return void 0;
|
|
4334
4596
|
}
|
|
4335
4597
|
function writeEnvValue2(key, value, envFile = ".env.local") {
|
|
4336
|
-
const envPath =
|
|
4598
|
+
const envPath = path12.join(process.cwd(), envFile);
|
|
4337
4599
|
let content = "";
|
|
4338
|
-
if (
|
|
4339
|
-
content =
|
|
4600
|
+
if (fs12.existsSync(envPath)) {
|
|
4601
|
+
content = fs12.readFileSync(envPath, "utf-8");
|
|
4340
4602
|
}
|
|
4341
4603
|
const lines = content.split("\n");
|
|
4342
4604
|
const idx = lines.findIndex((l) => l.startsWith(`${key}=`));
|
|
@@ -4345,7 +4607,7 @@ function writeEnvValue2(key, value, envFile = ".env.local") {
|
|
|
4345
4607
|
} else {
|
|
4346
4608
|
lines.push(`${key}=${value}`);
|
|
4347
4609
|
}
|
|
4348
|
-
|
|
4610
|
+
fs12.writeFileSync(envPath, lines.join("\n"));
|
|
4349
4611
|
}
|
|
4350
4612
|
function registerChannelWhatsAppCommand(program2) {
|
|
4351
4613
|
const channel = program2.command("channel:whatsapp").description("Manage the WhatsApp messaging channel");
|
|
@@ -4781,6 +5043,546 @@ async function runMinimalWhatsAppBot(config) {
|
|
|
4781
5043
|
});
|
|
4782
5044
|
}
|
|
4783
5045
|
|
|
5046
|
+
// src/commands/channel-slack.ts
|
|
5047
|
+
import fs13 from "fs-extra";
|
|
5048
|
+
import path13 from "path";
|
|
5049
|
+
import readline12 from "readline";
|
|
5050
|
+
function prompt10(q) {
|
|
5051
|
+
const rl = readline12.createInterface({ input: process.stdin, output: process.stdout });
|
|
5052
|
+
return new Promise((r) => rl.question(q, (a) => {
|
|
5053
|
+
rl.close();
|
|
5054
|
+
r(a.trim());
|
|
5055
|
+
}));
|
|
5056
|
+
}
|
|
5057
|
+
function readEnvValue3(key) {
|
|
5058
|
+
const cwd = process.cwd();
|
|
5059
|
+
const envFiles = [".env.local", ".env", ".env.production"];
|
|
5060
|
+
for (const envFile of envFiles) {
|
|
5061
|
+
const envPath = path13.join(cwd, envFile);
|
|
5062
|
+
if (fs13.existsSync(envPath)) {
|
|
5063
|
+
const content = fs13.readFileSync(envPath, "utf-8");
|
|
5064
|
+
const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
|
|
5065
|
+
if (match) return match[1].trim().replace(/["']/g, "");
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
return void 0;
|
|
5069
|
+
}
|
|
5070
|
+
function writeEnvValue3(key, value, envFile = ".env.local") {
|
|
5071
|
+
const envPath = path13.join(process.cwd(), envFile);
|
|
5072
|
+
let content = "";
|
|
5073
|
+
if (fs13.existsSync(envPath)) {
|
|
5074
|
+
content = fs13.readFileSync(envPath, "utf-8");
|
|
5075
|
+
}
|
|
5076
|
+
const lines = content.split("\n");
|
|
5077
|
+
const idx = lines.findIndex((l) => l.startsWith(`${key}=`));
|
|
5078
|
+
if (idx >= 0) {
|
|
5079
|
+
lines[idx] = `${key}=${value}`;
|
|
5080
|
+
} else {
|
|
5081
|
+
lines.push(`${key}=${value}`);
|
|
5082
|
+
}
|
|
5083
|
+
fs13.writeFileSync(envPath, lines.join("\n"));
|
|
5084
|
+
}
|
|
5085
|
+
function registerChannelSlackCommand(program2) {
|
|
5086
|
+
const channel = program2.command("channel:slack").description("Manage the Slack messaging channel");
|
|
5087
|
+
channel.command("start").description("Start the Slack bot and begin routing messages to an agent").option("-a, --agent <id>", "Agent ID to route messages to").option("--bot-token <token>", "Slack bot token (xoxb-...) (overrides .env)").option("--app-token <token>", "Slack app-level token (xapp-...) for socket mode (overrides .env)").option("--signing-secret <secret>", "Slack signing secret (overrides .env)").option("--socket-mode", "Enable socket mode (default: true)", true).option("--log-level <level>", "Log level: debug, info, warn, error", "info").action(async (opts) => {
|
|
5088
|
+
header("Slack Channel");
|
|
5089
|
+
const botToken = opts.botToken || readEnvValue3("SLACK_BOT_TOKEN") || process.env.SLACK_BOT_TOKEN;
|
|
5090
|
+
if (!botToken) {
|
|
5091
|
+
error("Slack Bot Token not found.");
|
|
5092
|
+
info("Set it with: agentforge channel:slack configure");
|
|
5093
|
+
info("Or pass it with: --bot-token <token>");
|
|
5094
|
+
info("Or set SLACK_BOT_TOKEN in your .env.local file");
|
|
5095
|
+
process.exit(1);
|
|
5096
|
+
}
|
|
5097
|
+
const appToken = opts.appToken || readEnvValue3("SLACK_APP_TOKEN") || process.env.SLACK_APP_TOKEN;
|
|
5098
|
+
if (opts.socketMode && !appToken) {
|
|
5099
|
+
error("Slack App Token not found (required for socket mode).");
|
|
5100
|
+
info("Set it with: agentforge channel:slack configure");
|
|
5101
|
+
info("Or pass it with: --app-token <token>");
|
|
5102
|
+
info("Or set SLACK_APP_TOKEN in your .env.local file");
|
|
5103
|
+
info("Or disable socket mode with: --no-socket-mode");
|
|
5104
|
+
process.exit(1);
|
|
5105
|
+
}
|
|
5106
|
+
const signingSecret = opts.signingSecret || readEnvValue3("SLACK_SIGNING_SECRET") || process.env.SLACK_SIGNING_SECRET;
|
|
5107
|
+
if (!signingSecret) {
|
|
5108
|
+
error("Slack Signing Secret not found.");
|
|
5109
|
+
info("Set it with: agentforge channel:slack configure");
|
|
5110
|
+
info("Or pass it with: --signing-secret <secret>");
|
|
5111
|
+
info("Or set SLACK_SIGNING_SECRET in your .env.local file");
|
|
5112
|
+
process.exit(1);
|
|
5113
|
+
}
|
|
5114
|
+
const convexUrl = readEnvValue3("CONVEX_URL") || process.env.CONVEX_URL;
|
|
5115
|
+
if (!convexUrl) {
|
|
5116
|
+
error("CONVEX_URL not found. Run `npx convex dev` first.");
|
|
5117
|
+
process.exit(1);
|
|
5118
|
+
}
|
|
5119
|
+
let agentId = opts.agent;
|
|
5120
|
+
if (!agentId) {
|
|
5121
|
+
agentId = readEnvValue3("AGENTFORGE_AGENT_ID") || process.env.AGENTFORGE_AGENT_ID;
|
|
5122
|
+
}
|
|
5123
|
+
if (!agentId) {
|
|
5124
|
+
info("No agent specified. Fetching available agents...");
|
|
5125
|
+
const client = await createClient();
|
|
5126
|
+
const agents = await safeCall(
|
|
5127
|
+
() => client.query("agents:list", {}),
|
|
5128
|
+
"Failed to list agents"
|
|
5129
|
+
);
|
|
5130
|
+
if (!agents || agents.length === 0) {
|
|
5131
|
+
error("No agents found. Create one first: agentforge agents create");
|
|
5132
|
+
process.exit(1);
|
|
5133
|
+
}
|
|
5134
|
+
console.log();
|
|
5135
|
+
agents.forEach((a, i) => {
|
|
5136
|
+
console.log(
|
|
5137
|
+
` ${colors.cyan}${i + 1}.${colors.reset} ${a.name} ${colors.dim}(${a.id})${colors.reset} \u2014 ${a.model}`
|
|
5138
|
+
);
|
|
5139
|
+
});
|
|
5140
|
+
console.log();
|
|
5141
|
+
const choice = await prompt10("Select agent (number or ID): ");
|
|
5142
|
+
const idx = parseInt(choice) - 1;
|
|
5143
|
+
agentId = idx >= 0 && idx < agents.length ? agents[idx].id : choice;
|
|
5144
|
+
}
|
|
5145
|
+
info(`Agent: ${agentId}`);
|
|
5146
|
+
info(`Convex: ${convexUrl}`);
|
|
5147
|
+
info(`Mode: ${opts.socketMode ? "Socket Mode" : "Events API"}`);
|
|
5148
|
+
info(`Log: ${opts.logLevel}`);
|
|
5149
|
+
console.log();
|
|
5150
|
+
let startSlackChannel;
|
|
5151
|
+
try {
|
|
5152
|
+
const slackPkg = "@agentforge-ai/channels-slack";
|
|
5153
|
+
const mod = await import(
|
|
5154
|
+
/* @vite-ignore */
|
|
5155
|
+
slackPkg
|
|
5156
|
+
);
|
|
5157
|
+
startSlackChannel = mod.startSlackChannel;
|
|
5158
|
+
} catch (importError) {
|
|
5159
|
+
error("Could not import @agentforge-ai/channels-slack. Using built-in Slack runner.");
|
|
5160
|
+
dim(` Error: ${importError.message}`);
|
|
5161
|
+
console.log();
|
|
5162
|
+
await runMinimalSlackBot({
|
|
5163
|
+
botToken,
|
|
5164
|
+
appToken: appToken || "",
|
|
5165
|
+
signingSecret,
|
|
5166
|
+
agentId,
|
|
5167
|
+
convexUrl,
|
|
5168
|
+
socketMode: opts.socketMode,
|
|
5169
|
+
logLevel: opts.logLevel
|
|
5170
|
+
});
|
|
5171
|
+
return;
|
|
5172
|
+
}
|
|
5173
|
+
try {
|
|
5174
|
+
await startSlackChannel({
|
|
5175
|
+
botToken,
|
|
5176
|
+
appToken,
|
|
5177
|
+
signingSecret,
|
|
5178
|
+
agentId,
|
|
5179
|
+
convexUrl,
|
|
5180
|
+
socketMode: opts.socketMode,
|
|
5181
|
+
logLevel: opts.logLevel
|
|
5182
|
+
});
|
|
5183
|
+
success("Slack bot is running!");
|
|
5184
|
+
dim(" Press Ctrl+C to stop.");
|
|
5185
|
+
await new Promise(() => {
|
|
5186
|
+
});
|
|
5187
|
+
} catch (startError) {
|
|
5188
|
+
error(`Failed to start Slack bot: ${startError.message}`);
|
|
5189
|
+
process.exit(1);
|
|
5190
|
+
}
|
|
5191
|
+
});
|
|
5192
|
+
channel.command("configure").description("Configure the Slack bot credentials and settings").action(async () => {
|
|
5193
|
+
header("Configure Slack Channel");
|
|
5194
|
+
console.log();
|
|
5195
|
+
info("To set up a Slack app:");
|
|
5196
|
+
dim(" 1. Go to https://api.slack.com/apps and create a new app");
|
|
5197
|
+
dim(" 2. Enable Socket Mode under Settings > Socket Mode");
|
|
5198
|
+
dim(" 3. Add bot scopes: chat:write, im:history, im:read, channels:history");
|
|
5199
|
+
dim(" 4. Install the app to your workspace");
|
|
5200
|
+
dim(" 5. Copy the Bot Token, App-Level Token, and Signing Secret");
|
|
5201
|
+
console.log();
|
|
5202
|
+
const currentBotToken = readEnvValue3("SLACK_BOT_TOKEN");
|
|
5203
|
+
if (currentBotToken) {
|
|
5204
|
+
const masked = currentBotToken.slice(0, 8) + "****" + currentBotToken.slice(-4);
|
|
5205
|
+
info(`Current bot token: ${masked}`);
|
|
5206
|
+
}
|
|
5207
|
+
const botToken = await prompt10("Slack Bot Token (xoxb-...): ");
|
|
5208
|
+
if (!botToken) {
|
|
5209
|
+
error("Bot token is required.");
|
|
5210
|
+
process.exit(1);
|
|
5211
|
+
}
|
|
5212
|
+
if (!botToken.startsWith("xoxb-")) {
|
|
5213
|
+
warn('Bot token should start with "xoxb-". Please verify this is correct.');
|
|
5214
|
+
}
|
|
5215
|
+
info("Validating bot token...");
|
|
5216
|
+
try {
|
|
5217
|
+
const response = await fetch("https://slack.com/api/auth.test", {
|
|
5218
|
+
method: "POST",
|
|
5219
|
+
headers: {
|
|
5220
|
+
Authorization: `Bearer ${botToken}`,
|
|
5221
|
+
"Content-Type": "application/json"
|
|
5222
|
+
}
|
|
5223
|
+
});
|
|
5224
|
+
const data = await response.json();
|
|
5225
|
+
if (!data.ok) {
|
|
5226
|
+
warn(`Token validation warning: ${data.error}`);
|
|
5227
|
+
info("Saving token anyway. You can validate later with: agentforge channel:slack status");
|
|
5228
|
+
} else {
|
|
5229
|
+
success(`Bot verified: @${data.user} in workspace "${data.team}"`);
|
|
5230
|
+
}
|
|
5231
|
+
} catch (fetchError) {
|
|
5232
|
+
warn(`Could not validate token (network error): ${fetchError.message}`);
|
|
5233
|
+
info("Saving token anyway.");
|
|
5234
|
+
}
|
|
5235
|
+
writeEnvValue3("SLACK_BOT_TOKEN", botToken);
|
|
5236
|
+
success("Bot token saved to .env.local");
|
|
5237
|
+
console.log();
|
|
5238
|
+
const currentAppToken = readEnvValue3("SLACK_APP_TOKEN");
|
|
5239
|
+
if (currentAppToken) {
|
|
5240
|
+
const masked = currentAppToken.slice(0, 8) + "****" + currentAppToken.slice(-4);
|
|
5241
|
+
info(`Current app token: ${masked}`);
|
|
5242
|
+
}
|
|
5243
|
+
const appToken = await prompt10("Slack App-Level Token (xapp-..., for socket mode): ");
|
|
5244
|
+
if (!appToken) {
|
|
5245
|
+
warn("App-level token not provided. Socket mode will not be available.");
|
|
5246
|
+
} else {
|
|
5247
|
+
if (!appToken.startsWith("xapp-")) {
|
|
5248
|
+
warn('App token should start with "xapp-". Please verify this is correct.');
|
|
5249
|
+
}
|
|
5250
|
+
writeEnvValue3("SLACK_APP_TOKEN", appToken);
|
|
5251
|
+
success("App token saved to .env.local");
|
|
5252
|
+
}
|
|
5253
|
+
console.log();
|
|
5254
|
+
const currentSigningSecret = readEnvValue3("SLACK_SIGNING_SECRET");
|
|
5255
|
+
if (currentSigningSecret) {
|
|
5256
|
+
info(`Current signing secret: ${currentSigningSecret.slice(0, 6)}****`);
|
|
5257
|
+
}
|
|
5258
|
+
const signingSecret = await prompt10("Slack Signing Secret: ");
|
|
5259
|
+
if (!signingSecret) {
|
|
5260
|
+
error("Signing secret is required.");
|
|
5261
|
+
process.exit(1);
|
|
5262
|
+
}
|
|
5263
|
+
if (signingSecret.length < 20) {
|
|
5264
|
+
warn("Signing secret looks too short. Please verify this is correct.");
|
|
5265
|
+
}
|
|
5266
|
+
writeEnvValue3("SLACK_SIGNING_SECRET", signingSecret);
|
|
5267
|
+
success("Signing secret saved to .env.local");
|
|
5268
|
+
console.log();
|
|
5269
|
+
const defaultAgent = await prompt10("Default agent ID (optional, press Enter to skip): ");
|
|
5270
|
+
if (defaultAgent) {
|
|
5271
|
+
writeEnvValue3("AGENTFORGE_AGENT_ID", defaultAgent);
|
|
5272
|
+
success(`Default agent set to: ${defaultAgent}`);
|
|
5273
|
+
}
|
|
5274
|
+
console.log();
|
|
5275
|
+
success("Configuration complete!");
|
|
5276
|
+
info("Start the bot with: agentforge channel:slack start");
|
|
5277
|
+
});
|
|
5278
|
+
channel.command("status").description("Check the Slack bot configuration and connectivity").action(async () => {
|
|
5279
|
+
header("Slack Channel Status");
|
|
5280
|
+
const botToken = readEnvValue3("SLACK_BOT_TOKEN");
|
|
5281
|
+
const appToken = readEnvValue3("SLACK_APP_TOKEN");
|
|
5282
|
+
const signingSecret = readEnvValue3("SLACK_SIGNING_SECRET");
|
|
5283
|
+
const agentId = readEnvValue3("AGENTFORGE_AGENT_ID");
|
|
5284
|
+
const convexUrl = readEnvValue3("CONVEX_URL");
|
|
5285
|
+
const statusData = {
|
|
5286
|
+
"Bot Token": botToken ? `${botToken.slice(0, 8)}****${botToken.slice(-4)}` : `${colors.red}Not configured${colors.reset}`,
|
|
5287
|
+
"App Token": appToken ? `${appToken.slice(0, 8)}****${appToken.slice(-4)}` : `${colors.dim}Not set${colors.reset}`,
|
|
5288
|
+
"Signing Secret": signingSecret ? `${signingSecret.slice(0, 6)}****` : `${colors.red}Not configured${colors.reset}`,
|
|
5289
|
+
"Default Agent": agentId || `${colors.dim}Not set${colors.reset}`,
|
|
5290
|
+
"Convex URL": convexUrl || `${colors.red}Not configured${colors.reset}`
|
|
5291
|
+
};
|
|
5292
|
+
details(statusData);
|
|
5293
|
+
if (botToken) {
|
|
5294
|
+
info("Checking Slack API connectivity...");
|
|
5295
|
+
try {
|
|
5296
|
+
const response = await fetch("https://slack.com/api/auth.test", {
|
|
5297
|
+
method: "POST",
|
|
5298
|
+
headers: {
|
|
5299
|
+
Authorization: `Bearer ${botToken}`,
|
|
5300
|
+
"Content-Type": "application/json"
|
|
5301
|
+
}
|
|
5302
|
+
});
|
|
5303
|
+
const data = await response.json();
|
|
5304
|
+
if (data.ok) {
|
|
5305
|
+
success(`Slack API connected: @${data.user} in workspace "${data.team}" (${data.team_id})`);
|
|
5306
|
+
} else {
|
|
5307
|
+
error(`Slack API error: ${data.error}`);
|
|
5308
|
+
}
|
|
5309
|
+
} catch {
|
|
5310
|
+
warn("Could not reach Slack API (network error).");
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5313
|
+
if (convexUrl) {
|
|
5314
|
+
info("Checking Convex connectivity...");
|
|
5315
|
+
try {
|
|
5316
|
+
const client = await createClient();
|
|
5317
|
+
const agents = await client.query("agents:list", {});
|
|
5318
|
+
success(`Convex connected. ${agents.length} agents available.`);
|
|
5319
|
+
} catch {
|
|
5320
|
+
warn("Could not reach Convex deployment.");
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
});
|
|
5324
|
+
}
|
|
5325
|
+
async function runMinimalSlackBot(config) {
|
|
5326
|
+
const { botToken, appToken, signingSecret, agentId, convexUrl } = config;
|
|
5327
|
+
const convexBase = convexUrl.replace(/\/$/, "");
|
|
5328
|
+
const threadMap = /* @__PURE__ */ new Map();
|
|
5329
|
+
info("Verifying Slack bot token...");
|
|
5330
|
+
try {
|
|
5331
|
+
const res = await fetch("https://slack.com/api/auth.test", {
|
|
5332
|
+
method: "POST",
|
|
5333
|
+
headers: {
|
|
5334
|
+
Authorization: `Bearer ${botToken}`,
|
|
5335
|
+
"Content-Type": "application/json"
|
|
5336
|
+
}
|
|
5337
|
+
});
|
|
5338
|
+
const data = await res.json();
|
|
5339
|
+
if (!data.ok) {
|
|
5340
|
+
error(`Slack auth error: ${data.error}`);
|
|
5341
|
+
process.exit(1);
|
|
5342
|
+
}
|
|
5343
|
+
success(`Slack bot connected: @${data.user} in "${data.team}"`);
|
|
5344
|
+
} catch (fetchError) {
|
|
5345
|
+
warn(`Could not verify bot token: ${fetchError.message}`);
|
|
5346
|
+
info("Continuing anyway...");
|
|
5347
|
+
}
|
|
5348
|
+
async function convexMutation(fn, args) {
|
|
5349
|
+
const res = await fetch(`${convexBase}/api/mutation`, {
|
|
5350
|
+
method: "POST",
|
|
5351
|
+
headers: { "Content-Type": "application/json" },
|
|
5352
|
+
body: JSON.stringify({ path: fn, args })
|
|
5353
|
+
});
|
|
5354
|
+
const data = await res.json();
|
|
5355
|
+
if (data.status === "error") throw new Error(data.errorMessage);
|
|
5356
|
+
return data.value;
|
|
5357
|
+
}
|
|
5358
|
+
async function convexAction(fn, args) {
|
|
5359
|
+
const res = await fetch(`${convexBase}/api/action`, {
|
|
5360
|
+
method: "POST",
|
|
5361
|
+
headers: { "Content-Type": "application/json" },
|
|
5362
|
+
body: JSON.stringify({ path: fn, args })
|
|
5363
|
+
});
|
|
5364
|
+
const data = await res.json();
|
|
5365
|
+
if (data.status === "error") throw new Error(data.errorMessage);
|
|
5366
|
+
return data.value;
|
|
5367
|
+
}
|
|
5368
|
+
async function sendSlackMessage(channel, text, threadTs) {
|
|
5369
|
+
const body = { channel, text };
|
|
5370
|
+
if (threadTs) body.thread_ts = threadTs;
|
|
5371
|
+
await fetch("https://slack.com/api/chat.postMessage", {
|
|
5372
|
+
method: "POST",
|
|
5373
|
+
headers: {
|
|
5374
|
+
Authorization: `Bearer ${botToken}`,
|
|
5375
|
+
"Content-Type": "application/json"
|
|
5376
|
+
},
|
|
5377
|
+
body: JSON.stringify(body)
|
|
5378
|
+
});
|
|
5379
|
+
}
|
|
5380
|
+
async function getOrCreateThread(channelThreadKey, senderName) {
|
|
5381
|
+
const cached = threadMap.get(channelThreadKey);
|
|
5382
|
+
if (cached) return cached;
|
|
5383
|
+
const threadId = await convexMutation("chat:createThread", {
|
|
5384
|
+
agentId,
|
|
5385
|
+
name: senderName ? `Slack: ${senderName}` : `Slack ${channelThreadKey}`,
|
|
5386
|
+
userId: `slack:${channelThreadKey}`
|
|
5387
|
+
});
|
|
5388
|
+
threadMap.set(channelThreadKey, threadId);
|
|
5389
|
+
return threadId;
|
|
5390
|
+
}
|
|
5391
|
+
async function handleSlackMessage(event) {
|
|
5392
|
+
if (event.bot_id || event.subtype) return;
|
|
5393
|
+
const channelId = event.channel;
|
|
5394
|
+
const userId = event.user;
|
|
5395
|
+
const text = (event.text || "").trim();
|
|
5396
|
+
const threadTs = event.thread_ts || event.ts;
|
|
5397
|
+
if (!text) return;
|
|
5398
|
+
const threadKey = `${channelId}:${userId}`;
|
|
5399
|
+
console.log(`[Slack:${channelId}] ${text}`);
|
|
5400
|
+
try {
|
|
5401
|
+
const convexThreadId = await getOrCreateThread(threadKey, `slack:${userId}`);
|
|
5402
|
+
const result = await convexAction("chat:sendMessage", {
|
|
5403
|
+
agentId,
|
|
5404
|
+
threadId: convexThreadId,
|
|
5405
|
+
content: text,
|
|
5406
|
+
userId: `slack:${userId}`
|
|
5407
|
+
});
|
|
5408
|
+
if (result?.response) {
|
|
5409
|
+
const response = result.response;
|
|
5410
|
+
if (response.length <= 4e3) {
|
|
5411
|
+
await sendSlackMessage(channelId, response, threadTs);
|
|
5412
|
+
} else {
|
|
5413
|
+
const chunks = response.match(/.{1,4000}/gs) || [];
|
|
5414
|
+
for (const chunk of chunks) {
|
|
5415
|
+
await sendSlackMessage(channelId, chunk, threadTs);
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
5418
|
+
console.log(`[Agent] ${response.substring(0, 100)}${response.length > 100 ? "..." : ""}`);
|
|
5419
|
+
} else {
|
|
5420
|
+
await sendSlackMessage(channelId, "I couldn't generate a response. Please try again.", threadTs);
|
|
5421
|
+
}
|
|
5422
|
+
} catch (routeError) {
|
|
5423
|
+
console.error(`Error: ${routeError.message}`);
|
|
5424
|
+
await sendSlackMessage(channelId, "Sorry, I encountered an error. Please try again.", threadTs);
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
if (appToken && config.socketMode !== false) {
|
|
5428
|
+
info("Starting in Socket Mode...");
|
|
5429
|
+
try {
|
|
5430
|
+
const wsUrlRes = await fetch("https://slack.com/api/apps.connections.open", {
|
|
5431
|
+
method: "POST",
|
|
5432
|
+
headers: {
|
|
5433
|
+
Authorization: `Bearer ${appToken}`,
|
|
5434
|
+
"Content-Type": "application/json"
|
|
5435
|
+
}
|
|
5436
|
+
});
|
|
5437
|
+
const wsData = await wsUrlRes.json();
|
|
5438
|
+
if (!wsData.ok || !wsData.url) {
|
|
5439
|
+
throw new Error(`Could not open WebSocket connection: ${wsData.error}`);
|
|
5440
|
+
}
|
|
5441
|
+
const { default: WebSocket } = await import("ws");
|
|
5442
|
+
const ws = new WebSocket(wsData.url);
|
|
5443
|
+
ws.on("open", () => {
|
|
5444
|
+
success("Socket Mode connected!");
|
|
5445
|
+
dim(" Listening for messages. Press Ctrl+C to stop.");
|
|
5446
|
+
console.log();
|
|
5447
|
+
});
|
|
5448
|
+
ws.on("message", async (data) => {
|
|
5449
|
+
try {
|
|
5450
|
+
const payload = JSON.parse(data.toString());
|
|
5451
|
+
if (payload.envelope_id) {
|
|
5452
|
+
ws.send(JSON.stringify({ envelope_id: payload.envelope_id }));
|
|
5453
|
+
}
|
|
5454
|
+
if (payload.type === "events_api" && payload.payload?.event) {
|
|
5455
|
+
const event = payload.payload.event;
|
|
5456
|
+
if (event.type === "message") {
|
|
5457
|
+
await handleSlackMessage(event);
|
|
5458
|
+
}
|
|
5459
|
+
}
|
|
5460
|
+
if (payload.type === "slash_commands" && payload.payload) {
|
|
5461
|
+
const slashPayload = payload.payload;
|
|
5462
|
+
const command = slashPayload.command;
|
|
5463
|
+
const channelId = slashPayload.channel_id;
|
|
5464
|
+
const userId = slashPayload.user_id;
|
|
5465
|
+
const text = slashPayload.text || "";
|
|
5466
|
+
if (command === "/start" || command === "/new") {
|
|
5467
|
+
const key = `${channelId}:${userId}`;
|
|
5468
|
+
threadMap.delete(key);
|
|
5469
|
+
await sendSlackMessage(channelId, "New conversation started! Send me a message.");
|
|
5470
|
+
} else if (command === "/help") {
|
|
5471
|
+
await sendSlackMessage(channelId, "AgentForge Slack Bot\n\nJust send me a message and I'll respond using AI.\n\nCommands:\n/start \u2014 Reset and start fresh\n/new \u2014 Start a fresh conversation\n/help \u2014 Show this help\n/ask <question> \u2014 Ask a question");
|
|
5472
|
+
} else if (command === "/ask") {
|
|
5473
|
+
await handleSlackMessage({ channel: channelId, user: userId, text, ts: Date.now().toString() });
|
|
5474
|
+
}
|
|
5475
|
+
}
|
|
5476
|
+
} catch (parseError) {
|
|
5477
|
+
console.error(`Error processing message: ${parseError.message}`);
|
|
5478
|
+
}
|
|
5479
|
+
});
|
|
5480
|
+
ws.on("close", (code) => {
|
|
5481
|
+
warn(`Socket Mode disconnected (code: ${code}). Reconnecting in 5s...`);
|
|
5482
|
+
setTimeout(() => runMinimalSlackBot(config), 5e3);
|
|
5483
|
+
});
|
|
5484
|
+
ws.on("error", (err) => {
|
|
5485
|
+
console.error(`WebSocket error: ${err.message}`);
|
|
5486
|
+
});
|
|
5487
|
+
process.on("SIGINT", () => {
|
|
5488
|
+
console.log("\nStopping...");
|
|
5489
|
+
ws.close();
|
|
5490
|
+
process.exit(0);
|
|
5491
|
+
});
|
|
5492
|
+
await new Promise(() => {
|
|
5493
|
+
});
|
|
5494
|
+
} catch (socketError) {
|
|
5495
|
+
warn(`Socket Mode failed: ${socketError.message}`);
|
|
5496
|
+
info("Falling back to HTTP Events API server...");
|
|
5497
|
+
}
|
|
5498
|
+
}
|
|
5499
|
+
info("Starting HTTP server for Events API...");
|
|
5500
|
+
const port = 3002;
|
|
5501
|
+
const path_ = "/slack/events";
|
|
5502
|
+
const http = await import("http");
|
|
5503
|
+
const { createHmac, timingSafeEqual } = await import("crypto");
|
|
5504
|
+
function verifySlackSignature(body, timestamp, signature) {
|
|
5505
|
+
const sigBaseString = `v0:${timestamp}:${body}`;
|
|
5506
|
+
const hmac = createHmac("sha256", signingSecret);
|
|
5507
|
+
hmac.update(sigBaseString);
|
|
5508
|
+
const computedSig = `v0=${hmac.digest("hex")}`;
|
|
5509
|
+
try {
|
|
5510
|
+
return timingSafeEqual(Buffer.from(computedSig), Buffer.from(signature));
|
|
5511
|
+
} catch {
|
|
5512
|
+
return false;
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
const server = http.createServer(async (req, res) => {
|
|
5516
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
5517
|
+
if (url.pathname !== path_) {
|
|
5518
|
+
res.writeHead(404);
|
|
5519
|
+
res.end("Not Found");
|
|
5520
|
+
return;
|
|
5521
|
+
}
|
|
5522
|
+
if (req.method !== "POST") {
|
|
5523
|
+
res.writeHead(405);
|
|
5524
|
+
res.end("Method Not Allowed");
|
|
5525
|
+
return;
|
|
5526
|
+
}
|
|
5527
|
+
try {
|
|
5528
|
+
const chunks = [];
|
|
5529
|
+
for await (const chunk of req) {
|
|
5530
|
+
chunks.push(chunk);
|
|
5531
|
+
}
|
|
5532
|
+
const rawBody = Buffer.concat(chunks).toString();
|
|
5533
|
+
const timestamp = req.headers["x-slack-request-timestamp"];
|
|
5534
|
+
const signature = req.headers["x-slack-signature"];
|
|
5535
|
+
if (timestamp && signature && signingSecret) {
|
|
5536
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5537
|
+
if (Math.abs(now - parseInt(timestamp)) > 300) {
|
|
5538
|
+
res.writeHead(403);
|
|
5539
|
+
res.end("Request too old");
|
|
5540
|
+
return;
|
|
5541
|
+
}
|
|
5542
|
+
if (!verifySlackSignature(rawBody, timestamp, signature)) {
|
|
5543
|
+
res.writeHead(403);
|
|
5544
|
+
res.end("Invalid signature");
|
|
5545
|
+
return;
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
const body = JSON.parse(rawBody);
|
|
5549
|
+
if (body.type === "url_verification") {
|
|
5550
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5551
|
+
res.end(JSON.stringify({ challenge: body.challenge }));
|
|
5552
|
+
return;
|
|
5553
|
+
}
|
|
5554
|
+
res.writeHead(200);
|
|
5555
|
+
res.end("OK");
|
|
5556
|
+
if (body.type === "event_callback" && body.event?.type === "message") {
|
|
5557
|
+
await handleSlackMessage(body.event);
|
|
5558
|
+
}
|
|
5559
|
+
} catch (parseError) {
|
|
5560
|
+
console.error(`Parse error: ${parseError.message}`);
|
|
5561
|
+
if (!res.headersSent) {
|
|
5562
|
+
res.writeHead(400);
|
|
5563
|
+
res.end("Bad Request");
|
|
5564
|
+
}
|
|
5565
|
+
}
|
|
5566
|
+
});
|
|
5567
|
+
process.on("SIGINT", () => {
|
|
5568
|
+
console.log("\nStopping...");
|
|
5569
|
+
server.close();
|
|
5570
|
+
process.exit(0);
|
|
5571
|
+
});
|
|
5572
|
+
server.listen(port, () => {
|
|
5573
|
+
success(`Events API server listening on port ${port}`);
|
|
5574
|
+
info(`Events API URL: http://localhost:${port}${path_}`);
|
|
5575
|
+
console.log();
|
|
5576
|
+
info("Next steps:");
|
|
5577
|
+
dim(" 1. Expose this URL publicly (e.g., ngrok http " + port + ")");
|
|
5578
|
+
dim(" 2. Configure the Events API URL in your Slack app settings");
|
|
5579
|
+
dim(' 3. Subscribe to the "message.im" and "message.channels" events');
|
|
5580
|
+
dim(" Press Ctrl+C to stop.");
|
|
5581
|
+
});
|
|
5582
|
+
await new Promise(() => {
|
|
5583
|
+
});
|
|
5584
|
+
}
|
|
5585
|
+
|
|
4784
5586
|
// src/index.ts
|
|
4785
5587
|
import { readFileSync } from "fs";
|
|
4786
5588
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -4805,6 +5607,7 @@ registerChatCommand(program);
|
|
|
4805
5607
|
registerSessionsCommand(program);
|
|
4806
5608
|
registerThreadsCommand(program);
|
|
4807
5609
|
registerSkillsCommand(program);
|
|
5610
|
+
registerSkillCommand(program);
|
|
4808
5611
|
registerCronCommand(program);
|
|
4809
5612
|
registerMcpCommand(program);
|
|
4810
5613
|
registerFilesCommand(program);
|
|
@@ -4814,6 +5617,7 @@ registerVaultCommand(program);
|
|
|
4814
5617
|
registerKeysCommand(program);
|
|
4815
5618
|
registerChannelTelegramCommand(program);
|
|
4816
5619
|
registerChannelWhatsAppCommand(program);
|
|
5620
|
+
registerChannelSlackCommand(program);
|
|
4817
5621
|
registerStatusCommand(program);
|
|
4818
5622
|
program.parse();
|
|
4819
5623
|
//# sourceMappingURL=index.js.map
|