@toolforge-js/sdk 0.8.0 → 0.8.2
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/cli/index.js +530 -76
- package/dist/components/index.d.ts +161 -55
- package/dist/components/index.js +1 -1
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.js +5 -2
- package/dist/config-schema-DcLVggqh.js +19 -0
- package/dist/{tool-UVAGtnlr.js → tool-DDDEH8M3.js} +98 -45
- package/package.json +9 -4
- package/dist/config-schema-CcWOtgOv.js +0 -12
package/dist/cli/index.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
import { C as __toESM, S as __require, _ as
|
|
2
|
-
import {
|
|
1
|
+
import { C as __toESM, S as __require, _ as stopToolMessageSchema, a as AGENT_TAG_NAME, b as invariant, c as runWithRetries, d as heartbeatAckMessageSchema, f as initCommunicationMessageSchema, g as stopAgentMessageSchema, h as startToolMessageSchema, i as AGENT_STEP_TAG_NAME, l as ackMessageSchema, m as startAgentMessageSchema, n as TOOL_TAG_NAME, o as Agent, p as runnerId, r as Tool, s as exponentialBackoff, t as TOOL_HANDLER_TAG_NAME, u as baseMessageSchema, v as convertToWords, x as __commonJS, y as getErrorMessage } from "../tool-DDDEH8M3.js";
|
|
2
|
+
import { n as toolForgeConfigSchema, r as TOKEN_PREFIX, t as TF_CONFIG_TAG_NAME } from "../config-schema-DcLVggqh.js";
|
|
3
3
|
import * as z$1 from "zod";
|
|
4
4
|
import z from "zod";
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
5
|
+
import { createReadStream, readFileSync } from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { EventEmitter } from "node:events";
|
|
8
8
|
import ora from "ora";
|
|
9
9
|
import { pino } from "pino";
|
|
10
|
-
import
|
|
11
|
-
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
12
|
-
import chokidar from "chokidar";
|
|
13
|
-
import { nanoid } from "nanoid";
|
|
14
|
-
import picocolors from "picocolors";
|
|
10
|
+
import * as crypto from "node:crypto";
|
|
15
11
|
import * as fs from "node:fs/promises";
|
|
12
|
+
import { create } from "tar";
|
|
16
13
|
import * as os from "node:os";
|
|
17
14
|
import { camelCase, capitalize, kebabCase } from "es-toolkit/string";
|
|
18
15
|
import esbuild from "esbuild";
|
|
16
|
+
import { nanoid } from "nanoid";
|
|
17
|
+
import { P, match } from "ts-pattern";
|
|
18
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
19
|
+
import * as prompts from "@clack/prompts";
|
|
20
|
+
import picocolors from "picocolors";
|
|
21
|
+
import { createAuthClient } from "better-auth/client";
|
|
22
|
+
import { deviceAuthorizationClient } from "better-auth/client/plugins";
|
|
23
|
+
import chokidar from "chokidar";
|
|
19
24
|
|
|
20
25
|
//#region ../../node_modules/.bun/commander@14.0.2/node_modules/commander/lib/error.js
|
|
21
26
|
var require_error = /* @__PURE__ */ __commonJS({ "../../node_modules/.bun/commander@14.0.2/node_modules/commander/lib/error.js": ((exports) => {
|
|
@@ -3066,13 +3071,17 @@ async function gatherForgeItems(toolsDir, mode, logger) {
|
|
|
3066
3071
|
return await import(bundleFilePath);
|
|
3067
3072
|
}
|
|
3068
3073
|
const { forgeItems, toolEntries, agentEntries } = await gatherToolAndAgentEntries(toolsDir, mode, logger);
|
|
3069
|
-
if (forgeItems.length || toolEntries.length || agentEntries.length)
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3074
|
+
if (forgeItems.length || toolEntries.length || agentEntries.length) {
|
|
3075
|
+
const { bundleFilePath } = await bundleForge({
|
|
3076
|
+
toolEntries,
|
|
3077
|
+
agentEntries,
|
|
3078
|
+
forgeItems,
|
|
3079
|
+
toolsDir,
|
|
3080
|
+
logger
|
|
3081
|
+
});
|
|
3082
|
+
logger.debug("loading bundled tools from %s", bundleFilePath);
|
|
3083
|
+
return await import(`${bundleFilePath}?t=${Date.now()}`);
|
|
3084
|
+
}
|
|
3076
3085
|
return {
|
|
3077
3086
|
forgeItems: [],
|
|
3078
3087
|
tools: {},
|
|
@@ -3196,11 +3205,11 @@ ${agentEntries.map(({ variableName, id }) => ` '${id}': agent_${variableName},`
|
|
|
3196
3205
|
}
|
|
3197
3206
|
const entryFileContent = entryFileContentGroups.join("\n\n");
|
|
3198
3207
|
logger.debug(`generate file content\n${"-".repeat(70)}\n%s\n${"-".repeat(70)}`, entryFileContent);
|
|
3199
|
-
const
|
|
3200
|
-
const bundleFilePath = path.resolve(
|
|
3208
|
+
const bundleDirPath = path.resolve(process.cwd(), TOOL_FORGE_BUILD_DIR);
|
|
3209
|
+
const bundleFilePath = path.resolve(bundleDirPath, TOOL_FORGE_BUILD_BUNDLE_FILE);
|
|
3201
3210
|
logger.debug("bundling tools to %s", bundleFilePath);
|
|
3202
|
-
await setupOutputDir(
|
|
3203
|
-
logger.debug("ensured output directory exists at %s",
|
|
3211
|
+
await setupOutputDir(bundleDirPath);
|
|
3212
|
+
logger.debug("ensured output directory exists at %s", bundleDirPath);
|
|
3204
3213
|
logger.debug("starting esbuild bundling...");
|
|
3205
3214
|
await esbuild.build({
|
|
3206
3215
|
stdin: {
|
|
@@ -3212,11 +3221,20 @@ ${agentEntries.map(({ variableName, id }) => ` '${id}': agent_${variableName},`
|
|
|
3212
3221
|
platform: "node",
|
|
3213
3222
|
outfile: bundleFilePath,
|
|
3214
3223
|
logLevel: "error",
|
|
3215
|
-
format: "esm"
|
|
3224
|
+
format: "esm",
|
|
3225
|
+
banner: { js: [
|
|
3226
|
+
"/* eslint-disable */",
|
|
3227
|
+
"// @ts-nocheck",
|
|
3228
|
+
"// noinspection JSUnusedGlobalSymbols",
|
|
3229
|
+
"// This file is auto-generated by ToolForge. Do not edit manually."
|
|
3230
|
+
].join("\n\n") }
|
|
3216
3231
|
});
|
|
3217
3232
|
logger.debug("esbuild bundling completed");
|
|
3218
|
-
return
|
|
3219
|
-
|
|
3233
|
+
return {
|
|
3234
|
+
bundleFilePath,
|
|
3235
|
+
bundleDirPath
|
|
3236
|
+
};
|
|
3237
|
+
} else throw new Error("no tools or agents found to bundle");
|
|
3220
3238
|
}
|
|
3221
3239
|
function isToolDefinition(obj) {
|
|
3222
3240
|
return "__tf__tag__name__" in obj && obj.__tf__tag__name__ === TOOL_TAG_NAME && "handler" in obj && typeof obj.handler === "function" && "__tf__tag__name__" in obj.handler && obj.handler.__tf__tag__name__ === TOOL_HANDLER_TAG_NAME;
|
|
@@ -3260,6 +3278,417 @@ function getMemoryUsage() {
|
|
|
3260
3278
|
};
|
|
3261
3279
|
}
|
|
3262
3280
|
|
|
3281
|
+
//#endregion
|
|
3282
|
+
//#region src/cli/actions/utils.ts
|
|
3283
|
+
async function getToolForgeConfig(configRelPath) {
|
|
3284
|
+
try {
|
|
3285
|
+
const configPath = path.resolve(process.cwd(), configRelPath);
|
|
3286
|
+
return {
|
|
3287
|
+
config: toolForgeConfigSchema.extend({ __tf__tag__name__: z$1.symbol(TF_CONFIG_TAG_NAME.description) }).parse(await import(configPath).then((mod) => mod.default)),
|
|
3288
|
+
configPath
|
|
3289
|
+
};
|
|
3290
|
+
} catch (error) {
|
|
3291
|
+
if (error instanceof z$1.ZodError) throw new Error("Invalid tool forge config.\n\nPlease refer the documentation [https://docs.tool-forge.ai/sdk/configuration] for the correct configuration schema");
|
|
3292
|
+
throw error;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
/**
|
|
3296
|
+
* Opens the given URL in the default web browser.
|
|
3297
|
+
*/
|
|
3298
|
+
function openInBrowser(url) {
|
|
3299
|
+
const platform = process.platform;
|
|
3300
|
+
let command;
|
|
3301
|
+
if (platform === "win32") command = `start '${url}'`;
|
|
3302
|
+
else if (platform === "darwin") command = `open '${url}'`;
|
|
3303
|
+
else command = `xdg-open '${url}'`;
|
|
3304
|
+
Bun.spawn({
|
|
3305
|
+
cmd: [
|
|
3306
|
+
"sh",
|
|
3307
|
+
"-c",
|
|
3308
|
+
command
|
|
3309
|
+
],
|
|
3310
|
+
stdout: "ignore",
|
|
3311
|
+
stderr: "ignore"
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
const TOOL_FORGE_CLI_DOMAIN = "ai.tool-forge.cli";
|
|
3315
|
+
const TOOL_FORGE_ACCESS_TOKEN_KEY = "access-token";
|
|
3316
|
+
async function buildForge(config, logger) {
|
|
3317
|
+
const toolsDir = path.resolve(process.cwd(), config.toolsDir);
|
|
3318
|
+
const { toolEntries, agentEntries, forgeItems } = await gatherToolAndAgentEntries(toolsDir, "production", logger);
|
|
3319
|
+
if (forgeItems.length === 0) {
|
|
3320
|
+
logger.warn("no tools or agents found to build");
|
|
3321
|
+
process.exit(0);
|
|
3322
|
+
}
|
|
3323
|
+
logger.info("building %d tools and agents...", forgeItems.length);
|
|
3324
|
+
const { bundleFilePath, bundleDirPath } = await bundleForge({
|
|
3325
|
+
toolEntries,
|
|
3326
|
+
agentEntries,
|
|
3327
|
+
forgeItems,
|
|
3328
|
+
toolsDir,
|
|
3329
|
+
logger
|
|
3330
|
+
});
|
|
3331
|
+
logger.info("tools built successfully");
|
|
3332
|
+
return {
|
|
3333
|
+
bundleFilePath,
|
|
3334
|
+
bundleDirPath
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
const FILES_TO_COPY = [
|
|
3338
|
+
"package.json",
|
|
3339
|
+
"toolforge.config.ts",
|
|
3340
|
+
"tsconfig.json",
|
|
3341
|
+
"bun.lock",
|
|
3342
|
+
"bun.lockb"
|
|
3343
|
+
];
|
|
3344
|
+
const DIRS_TO_COPY = ["prisma"];
|
|
3345
|
+
async function createCompleteBundle({ bundleDirPath, rootPath }, logger) {
|
|
3346
|
+
for (const file of FILES_TO_COPY) {
|
|
3347
|
+
const srcPath = path.resolve(rootPath, file);
|
|
3348
|
+
if (!await fs.exists(srcPath)) continue;
|
|
3349
|
+
if (file === "package.json") {
|
|
3350
|
+
logger.debug("cleaning package.json before adding to bundle");
|
|
3351
|
+
const cleanedPackageJSON = await cleanPackageJSON(srcPath, logger);
|
|
3352
|
+
const destPath = path.resolve(bundleDirPath, "package.json");
|
|
3353
|
+
logger.debug("cleaned package.json: %o", cleanedPackageJSON);
|
|
3354
|
+
logger.debug("writing cleaned package.json to %s", destPath);
|
|
3355
|
+
try {
|
|
3356
|
+
await fs.writeFile(destPath, JSON.stringify(cleanedPackageJSON, null, 2), "utf-8");
|
|
3357
|
+
} catch (error) {
|
|
3358
|
+
logger.warn("failed to write cleaned package.json: %s", error.message);
|
|
3359
|
+
}
|
|
3360
|
+
} else {
|
|
3361
|
+
const srcPath$1 = path.resolve(rootPath, file);
|
|
3362
|
+
const destPath = path.resolve(bundleDirPath, file);
|
|
3363
|
+
logger.debug("copying %s to %s", srcPath$1, destPath);
|
|
3364
|
+
try {
|
|
3365
|
+
await fs.copyFile(srcPath$1, destPath);
|
|
3366
|
+
} catch (error) {
|
|
3367
|
+
logger.warn("failed to copy %s: %s", srcPath$1, error.message);
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
for (const dir of DIRS_TO_COPY) {
|
|
3372
|
+
const srcPath = path.join(rootPath, dir);
|
|
3373
|
+
const destPath = path.join(bundleDirPath, dir);
|
|
3374
|
+
logger.debug("copying %s to %s", srcPath, destPath);
|
|
3375
|
+
try {
|
|
3376
|
+
if (!(await fs.stat(srcPath)).isDirectory()) continue;
|
|
3377
|
+
await fs.cp(srcPath, destPath, { recursive: true });
|
|
3378
|
+
} catch (error) {
|
|
3379
|
+
logger.warn("failed to copy %s: %s", srcPath, error.message);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
const tarballPath = path.resolve(rootPath, ".toolforge-bundle.tar.gz");
|
|
3383
|
+
logger.info("creating tarball at %s", tarballPath);
|
|
3384
|
+
await create({
|
|
3385
|
+
gzip: true,
|
|
3386
|
+
file: tarballPath,
|
|
3387
|
+
cwd: bundleDirPath
|
|
3388
|
+
}, ["."]);
|
|
3389
|
+
const sha256 = await getFileSha256(tarballPath);
|
|
3390
|
+
logger.debug("tarball sha256: %s", sha256);
|
|
3391
|
+
logger.info("tarball created successfully");
|
|
3392
|
+
return {
|
|
3393
|
+
tarballPath,
|
|
3394
|
+
sha256
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
const VALID_TOOL_FORGE_PACKAGES = ["@toolforge-js/sdk"];
|
|
3398
|
+
/**
|
|
3399
|
+
* Cleans the package.json file by removing toolforge internal dependencies
|
|
3400
|
+
* and updating other workspace dependencies to their latest versions.
|
|
3401
|
+
*/
|
|
3402
|
+
async function cleanPackageJSON(filePath, logger) {
|
|
3403
|
+
const packageJSON = z$1.object({
|
|
3404
|
+
devDependencies: z$1.record(z$1.string(), z$1.string()).optional().default({}),
|
|
3405
|
+
dependencies: z$1.record(z$1.string(), z$1.string()).optional().default({})
|
|
3406
|
+
}).loose().parse(JSON.parse(await fs.readFile(filePath, "utf-8")));
|
|
3407
|
+
for (const key of ["devDependencies", "dependencies"]) {
|
|
3408
|
+
const deps = packageJSON[key];
|
|
3409
|
+
for (const dep of Object.keys(deps)) {
|
|
3410
|
+
logger.trace("processing dependency %s", dep);
|
|
3411
|
+
if (!dep.startsWith("@toolforge-js/")) {
|
|
3412
|
+
logger.trace("skipping non-toolforge package %s", dep);
|
|
3413
|
+
continue;
|
|
3414
|
+
}
|
|
3415
|
+
if (!VALID_TOOL_FORGE_PACKAGES.includes(dep)) {
|
|
3416
|
+
logger.trace("removing invalid toolforge package %s", dep);
|
|
3417
|
+
delete deps[dep];
|
|
3418
|
+
} else {
|
|
3419
|
+
if (deps[dep].startsWith("workspace:")) {
|
|
3420
|
+
const latestVersion = await getPackageLatestVersion(dep);
|
|
3421
|
+
if (latestVersion) deps[dep] = latestVersion;
|
|
3422
|
+
}
|
|
3423
|
+
logger.trace("updated dependency %s to version %s", dep, deps[dep]);
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
return packageJSON;
|
|
3428
|
+
}
|
|
3429
|
+
/**
|
|
3430
|
+
* Gets the latest version of a package from npm registry.
|
|
3431
|
+
*/
|
|
3432
|
+
async function getPackageLatestVersion(packageName) {
|
|
3433
|
+
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
3434
|
+
return z$1.object({
|
|
3435
|
+
version: z$1.string(),
|
|
3436
|
+
name: z$1.literal(packageName)
|
|
3437
|
+
}).parse(await res.json()).version;
|
|
3438
|
+
}
|
|
3439
|
+
/**
|
|
3440
|
+
* Generates the SHA-256 hash of a file.
|
|
3441
|
+
*/
|
|
3442
|
+
function getFileSha256(filePath) {
|
|
3443
|
+
return new Promise((resolve, reject) => {
|
|
3444
|
+
const hash = crypto.createHash("sha256");
|
|
3445
|
+
const stream = createReadStream(filePath);
|
|
3446
|
+
stream.on("error", reject);
|
|
3447
|
+
stream.on("data", (chunk) => {
|
|
3448
|
+
hash.update(chunk);
|
|
3449
|
+
});
|
|
3450
|
+
stream.on("end", () => {
|
|
3451
|
+
resolve(hash.digest("hex"));
|
|
3452
|
+
});
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
//#endregion
|
|
3457
|
+
//#region src/cli/actions/build.ts
|
|
3458
|
+
async function build(configRelPath, debug) {
|
|
3459
|
+
const logger = pino({
|
|
3460
|
+
level: debug ? "debug" : "info",
|
|
3461
|
+
transport: { target: "pino-pretty" }
|
|
3462
|
+
});
|
|
3463
|
+
try {
|
|
3464
|
+
const spinner = ora("loading configuration...").start();
|
|
3465
|
+
const { config, configPath } = await getToolForgeConfig(configRelPath);
|
|
3466
|
+
spinner.stop();
|
|
3467
|
+
logger.debug({ config }, "loaded config from %s", configPath);
|
|
3468
|
+
await buildForge(config, logger);
|
|
3469
|
+
} catch (error) {
|
|
3470
|
+
logger.error(error, "failed to build tools");
|
|
3471
|
+
process.exit(1);
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
//#endregion
|
|
3476
|
+
//#region src/cli/actions/deploy.ts
|
|
3477
|
+
/**
|
|
3478
|
+
* Action to deploy the tools to Tool Forge platform.
|
|
3479
|
+
*/
|
|
3480
|
+
async function deploy(configRelPath, debug) {
|
|
3481
|
+
const logFilePath = path.resolve(process.cwd(), ".tf-logs", `deploy-${Date.now()}.log`);
|
|
3482
|
+
const logger = pino({
|
|
3483
|
+
level: debug ? "debug" : "info",
|
|
3484
|
+
transport: {
|
|
3485
|
+
target: "pino/file",
|
|
3486
|
+
options: {
|
|
3487
|
+
destination: logFilePath,
|
|
3488
|
+
mkdir: true
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
});
|
|
3492
|
+
prompts.intro("Deploying to Tool Forge");
|
|
3493
|
+
prompts.log.info(`Saving logs to ${logFilePath}`);
|
|
3494
|
+
const spinner = prompts.spinner();
|
|
3495
|
+
spinner.start("Checking authentication...");
|
|
3496
|
+
const key = await Bun.secrets.get({
|
|
3497
|
+
service: TOOL_FORGE_CLI_DOMAIN,
|
|
3498
|
+
name: TOOL_FORGE_ACCESS_TOKEN_KEY
|
|
3499
|
+
});
|
|
3500
|
+
spinner.stop("Authentication checked.");
|
|
3501
|
+
if (!key) {
|
|
3502
|
+
prompts.outro("You are not logged in. Please run \"tool-forge login\" to login.");
|
|
3503
|
+
process.exit(1);
|
|
3504
|
+
}
|
|
3505
|
+
const loginSpinner = prompts.spinner();
|
|
3506
|
+
loginSpinner.start("Loading configuration...");
|
|
3507
|
+
const { config } = await getToolForgeConfig(configRelPath).then((data) => {
|
|
3508
|
+
loginSpinner.stop("Configuration loaded.");
|
|
3509
|
+
return data;
|
|
3510
|
+
}).catch((error) => {
|
|
3511
|
+
loginSpinner.stop("Failed to load configuration.");
|
|
3512
|
+
prompts.outro(getErrorMessage(error));
|
|
3513
|
+
process.exit(1);
|
|
3514
|
+
});
|
|
3515
|
+
const envSpinner = prompts.spinner();
|
|
3516
|
+
envSpinner.start("Fetching production environments...");
|
|
3517
|
+
const { environments } = await fetch(new URL("/api/cli/production-environments", config.appServerUrl), { headers: { "x-tf-token": key } }).then(async (res) => {
|
|
3518
|
+
if (!res.ok) throw new Error(`Failed to fetch production environments: ${res.status} ${res.statusText}`);
|
|
3519
|
+
const data = z$1.object({ environments: z$1.object({
|
|
3520
|
+
id: z$1.string(),
|
|
3521
|
+
environmentName: z$1.string(),
|
|
3522
|
+
workspaceName: z$1.string()
|
|
3523
|
+
}).array() }).parse(await res.json());
|
|
3524
|
+
envSpinner.stop("Production environments fetched.");
|
|
3525
|
+
return data;
|
|
3526
|
+
}).catch((error) => {
|
|
3527
|
+
envSpinner.stop("Failed to fetch production environments.");
|
|
3528
|
+
prompts.outro(getErrorMessage(error));
|
|
3529
|
+
process.exit(1);
|
|
3530
|
+
});
|
|
3531
|
+
const environmentSelect = await prompts.select({
|
|
3532
|
+
message: "Select the production environment to deploy to:",
|
|
3533
|
+
options: environments.map((env) => ({
|
|
3534
|
+
label: `${env.environmentName} (Workspace: ${env.workspaceName})`,
|
|
3535
|
+
value: env.id
|
|
3536
|
+
}))
|
|
3537
|
+
});
|
|
3538
|
+
if (prompts.isCancel(environmentSelect)) {
|
|
3539
|
+
prompts.outro("Deployment cancelled.");
|
|
3540
|
+
process.exit(0);
|
|
3541
|
+
}
|
|
3542
|
+
const name = await prompts.text({
|
|
3543
|
+
message: "Enter a name for this deployment:",
|
|
3544
|
+
validate(value) {
|
|
3545
|
+
if (value.length === 0) return "Deployment name cannot be empty";
|
|
3546
|
+
}
|
|
3547
|
+
});
|
|
3548
|
+
if (prompts.isCancel(name)) {
|
|
3549
|
+
prompts.outro("Deployment cancelled.");
|
|
3550
|
+
process.exit(0);
|
|
3551
|
+
}
|
|
3552
|
+
const buildSpinner = prompts.spinner();
|
|
3553
|
+
buildSpinner.start("Building tools...");
|
|
3554
|
+
const { tarballPath, sha256 } = await buildForge(config, logger).then(async ({ bundleDirPath }) => {
|
|
3555
|
+
buildSpinner.stop("Tools built successfully.");
|
|
3556
|
+
const { tarballPath: tarballPath$1, sha256: sha256$1 } = await createCompleteBundle({
|
|
3557
|
+
bundleDirPath,
|
|
3558
|
+
rootPath: process.cwd()
|
|
3559
|
+
}, logger);
|
|
3560
|
+
return {
|
|
3561
|
+
tarballPath: tarballPath$1,
|
|
3562
|
+
sha256: sha256$1
|
|
3563
|
+
};
|
|
3564
|
+
}).catch((error) => {
|
|
3565
|
+
buildSpinner.stop("Failed to build tools.");
|
|
3566
|
+
prompts.outro(getErrorMessage(error));
|
|
3567
|
+
process.exit(1);
|
|
3568
|
+
});
|
|
3569
|
+
const deploySpinner = prompts.spinner();
|
|
3570
|
+
const environmentName = environments.find((env) => env.id === environmentSelect)?.environmentName;
|
|
3571
|
+
deploySpinner.start(`Deploying tools to ${environmentName}...`);
|
|
3572
|
+
const formData = new FormData();
|
|
3573
|
+
formData.append("bundle", new File([await Bun.file(tarballPath).arrayBuffer()], path.basename(tarballPath), { type: "application/gzip" }));
|
|
3574
|
+
formData.append("environmentId", environmentSelect);
|
|
3575
|
+
formData.append("sha256", sha256);
|
|
3576
|
+
formData.append("name", name);
|
|
3577
|
+
const { url } = await fetch(new URL("/api/cli/initialize-deployment", config.appServerUrl), {
|
|
3578
|
+
method: "POST",
|
|
3579
|
+
headers: { "x-tf-token": key },
|
|
3580
|
+
body: formData
|
|
3581
|
+
}).then(async (res) => {
|
|
3582
|
+
if (!res.ok) {
|
|
3583
|
+
const errorData = await res.json().catch(() => null);
|
|
3584
|
+
logger.error("deployment failed: %s %s %s", res.status, res.statusText, JSON.stringify(errorData));
|
|
3585
|
+
throw new Error(`Deployment failed: ${res.status} ${res.statusText}`);
|
|
3586
|
+
}
|
|
3587
|
+
const data = z$1.object({
|
|
3588
|
+
deploymentId: z$1.uuid(),
|
|
3589
|
+
url: z$1.string()
|
|
3590
|
+
}).parse(await res.json());
|
|
3591
|
+
deploySpinner.stop("Tools deployed successfully.");
|
|
3592
|
+
return data;
|
|
3593
|
+
}).catch((error) => {
|
|
3594
|
+
deploySpinner.stop("Failed to deploy tools.");
|
|
3595
|
+
prompts.outro(getErrorMessage(error));
|
|
3596
|
+
process.exit(1);
|
|
3597
|
+
});
|
|
3598
|
+
const deploymentUrl = new URL(url, config.appUrl).toString();
|
|
3599
|
+
prompts.outro(`Deployment initiated successfully!\n\nYou can view the deployment status at:\n${picocolors.green(deploymentUrl)}`);
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3602
|
+
//#endregion
|
|
3603
|
+
//#region src/cli/actions/login.ts
|
|
3604
|
+
async function login(configRelPath) {
|
|
3605
|
+
prompts.intro("Logging in to Tool Forge");
|
|
3606
|
+
const spinner = prompts.spinner();
|
|
3607
|
+
spinner.start("Loading configuration...");
|
|
3608
|
+
try {
|
|
3609
|
+
const { config } = await getToolForgeConfig(configRelPath);
|
|
3610
|
+
spinner.stop("Configuration loaded.");
|
|
3611
|
+
const authClient = createAuthClient({
|
|
3612
|
+
baseURL: config.appServerUrl,
|
|
3613
|
+
plugins: [deviceAuthorizationClient()]
|
|
3614
|
+
});
|
|
3615
|
+
const deviceLabel = getDeviceLabel();
|
|
3616
|
+
const clientId = `toolforge-cli:${deviceLabel}`;
|
|
3617
|
+
const { data: deviceCodeData, error } = await authClient.device.code({
|
|
3618
|
+
client_id: clientId,
|
|
3619
|
+
scope: "deployments:read deployments:write"
|
|
3620
|
+
});
|
|
3621
|
+
if (error || !deviceCodeData) {
|
|
3622
|
+
prompts.outro(`failed to initiate device authorization flow: ${error.error_description}`);
|
|
3623
|
+
process.exit(1);
|
|
3624
|
+
}
|
|
3625
|
+
const verificationUrl = new URL("/cli/login", config.appUrl);
|
|
3626
|
+
verificationUrl.searchParams.set("deviceLabel", deviceLabel);
|
|
3627
|
+
verificationUrl.searchParams.set("userCode", deviceCodeData.user_code);
|
|
3628
|
+
prompts.note(`To authenticate, please visit:\n${verificationUrl.toString()}\n\n🔐 And enter the code: ${deviceCodeData.user_code}`, "Login Instructions");
|
|
3629
|
+
openInBrowser(verificationUrl.toString());
|
|
3630
|
+
async function pollForToken() {
|
|
3631
|
+
return new Promise((resolve, reject) => {
|
|
3632
|
+
let pollInterval = 5e3;
|
|
3633
|
+
let timeout;
|
|
3634
|
+
async function poll() {
|
|
3635
|
+
const { error: error$1, data } = await authClient.device.token({
|
|
3636
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
3637
|
+
device_code: deviceCodeData.device_code,
|
|
3638
|
+
client_id: clientId
|
|
3639
|
+
});
|
|
3640
|
+
if (error$1) match(error$1.error).returnType().with("authorization_pending", () => {
|
|
3641
|
+
clearTimeout(timeout);
|
|
3642
|
+
timeout = setTimeout(poll, pollInterval);
|
|
3643
|
+
}).with("slow_down", () => {
|
|
3644
|
+
clearTimeout(timeout);
|
|
3645
|
+
pollInterval += 5e3;
|
|
3646
|
+
timeout = setTimeout(poll, pollInterval);
|
|
3647
|
+
}).with("access_denied", () => {
|
|
3648
|
+
clearTimeout(timeout);
|
|
3649
|
+
reject(/* @__PURE__ */ new Error("access denied by user"));
|
|
3650
|
+
}).with("expired_token", () => {
|
|
3651
|
+
clearTimeout(timeout);
|
|
3652
|
+
reject(/* @__PURE__ */ new Error("device code has expired"));
|
|
3653
|
+
}).with("invalid_grant", () => {
|
|
3654
|
+
clearTimeout(timeout);
|
|
3655
|
+
reject(/* @__PURE__ */ new Error("invalid grant, please try again"));
|
|
3656
|
+
}).with("invalid_request", () => {
|
|
3657
|
+
clearTimeout(timeout);
|
|
3658
|
+
reject(/* @__PURE__ */ new Error("invalid request, please try again"));
|
|
3659
|
+
}).run();
|
|
3660
|
+
if (data) {
|
|
3661
|
+
clearTimeout(timeout);
|
|
3662
|
+
resolve(data.access_token);
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
timeout = setTimeout(poll, pollInterval);
|
|
3666
|
+
});
|
|
3667
|
+
}
|
|
3668
|
+
const pollSpinner = prompts.spinner();
|
|
3669
|
+
pollSpinner.start("Waiting for authentication...");
|
|
3670
|
+
try {
|
|
3671
|
+
const token = await pollForToken();
|
|
3672
|
+
await Bun.secrets.set({
|
|
3673
|
+
service: TOOL_FORGE_CLI_DOMAIN,
|
|
3674
|
+
name: TOOL_FORGE_ACCESS_TOKEN_KEY,
|
|
3675
|
+
value: token
|
|
3676
|
+
});
|
|
3677
|
+
pollSpinner.stop("Authentication successful!");
|
|
3678
|
+
prompts.outro("Successfully authenticated and stored access token securely");
|
|
3679
|
+
} catch (error$1) {
|
|
3680
|
+
pollSpinner.stop("Authentication failed.");
|
|
3681
|
+
prompts.outro(getErrorMessage(error$1));
|
|
3682
|
+
}
|
|
3683
|
+
} catch (error) {
|
|
3684
|
+
spinner.stop("Failed to load configuration.");
|
|
3685
|
+
prompts.outro(getErrorMessage(error));
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
function getDeviceLabel() {
|
|
3689
|
+
return `${os.userInfo().username}@${os.hostname()}`;
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3263
3692
|
//#endregion
|
|
3264
3693
|
//#region src/internal/websocket-client.ts
|
|
3265
3694
|
const optionsSchema$1 = z$1.object({
|
|
@@ -3313,7 +3742,10 @@ var WebSocketClient = class extends EventEmitter {
|
|
|
3313
3742
|
this.#connectToServer();
|
|
3314
3743
|
}
|
|
3315
3744
|
#connectToServer() {
|
|
3316
|
-
if (this.#connectionState
|
|
3745
|
+
if (this.#connectionState === "closing") {
|
|
3746
|
+
this.#logger.warn("start called while closing, ignoring");
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3317
3749
|
if (this.#connectionState === "connected" || this.#connectionState === "connecting") {
|
|
3318
3750
|
this.#logger.warn("already connecting, connected, or stopped, ignoring");
|
|
3319
3751
|
return;
|
|
@@ -3530,8 +3962,8 @@ var WebSocketClient = class extends EventEmitter {
|
|
|
3530
3962
|
#stop() {
|
|
3531
3963
|
if (this.#heartbeatInterval) clearInterval(this.#heartbeatInterval);
|
|
3532
3964
|
if (this.#heartbeatTimeout) clearTimeout(this.#heartbeatTimeout);
|
|
3533
|
-
if (this.#socket) {
|
|
3534
|
-
|
|
3965
|
+
if (this.#socket && this.#socket.readyState !== WebSocket.CLOSED) {
|
|
3966
|
+
this.#socket.close(WS_ERROR_CODES.GOING_AWAY, "runner shutting down");
|
|
3535
3967
|
this.#socket.removeEventListener("open", this.#handleWebSocketOpen.bind(this));
|
|
3536
3968
|
this.#socket.removeEventListener("close", this.#handleWebSocketClose.bind(this));
|
|
3537
3969
|
this.#socket.removeEventListener("message", this.#handleWebSocketMessage.bind(this));
|
|
@@ -3572,6 +4004,8 @@ var ForgeRunner = class extends EventEmitter {
|
|
|
3572
4004
|
#logger;
|
|
3573
4005
|
#config;
|
|
3574
4006
|
#serverUrl;
|
|
4007
|
+
#apiKey;
|
|
4008
|
+
#refreshApiKeyInterval;
|
|
3575
4009
|
#forgeItems = [];
|
|
3576
4010
|
#tools = {};
|
|
3577
4011
|
#agents = {};
|
|
@@ -3583,8 +4017,9 @@ var ForgeRunner = class extends EventEmitter {
|
|
|
3583
4017
|
constructor(config, options, logger) {
|
|
3584
4018
|
super({ captureRejections: true });
|
|
3585
4019
|
this.#config = config;
|
|
4020
|
+
this.#apiKey = config.apiKey;
|
|
3586
4021
|
const url = new URL(this.#config.sdkServer);
|
|
3587
|
-
url.searchParams.set("apiKey", this.#
|
|
4022
|
+
url.searchParams.set("apiKey", this.#apiKey);
|
|
3588
4023
|
url.searchParams.set("runnerId", this.#id);
|
|
3589
4024
|
url.pathname = "/socket";
|
|
3590
4025
|
this.#serverUrl = url.toString();
|
|
@@ -3595,6 +4030,9 @@ var ForgeRunner = class extends EventEmitter {
|
|
|
3595
4030
|
runnerId: this.#id
|
|
3596
4031
|
}, this, this.#logger);
|
|
3597
4032
|
this.#webSocketClient.on(WEB_SOCKET_CLIENT_EVENTS.COMMUNICATION_INITIALIZED, this.#handleWebSocketCommunicationInitialized.bind(this));
|
|
4033
|
+
if (this.#apiKey.startsWith(TOKEN_PREFIX)) this.#refreshApiKeyInterval = setInterval(() => {
|
|
4034
|
+
this.#refreshApiKey();
|
|
4035
|
+
}, 840 * 1e3);
|
|
3598
4036
|
this.#logger.info({
|
|
3599
4037
|
mode: this.#options.mode,
|
|
3600
4038
|
id: this.#id
|
|
@@ -3612,6 +4050,8 @@ var ForgeRunner = class extends EventEmitter {
|
|
|
3612
4050
|
this.#tools = tools;
|
|
3613
4051
|
this.#agents = agents;
|
|
3614
4052
|
this.#logger.debug(`gathered ${this.#forgeItems.length} tools and directories`);
|
|
4053
|
+
this.#logger.info("websocket communication initialized, sending forge list...");
|
|
4054
|
+
await this.#sendListForge();
|
|
3615
4055
|
this.#watchToolChanges();
|
|
3616
4056
|
this.#unsubscribeMessageHandler = this.#webSocketClient.subscribeToMessages(this.#handleWebSocketMessage.bind(this));
|
|
3617
4057
|
} catch (error) {
|
|
@@ -3780,6 +4220,26 @@ var ForgeRunner = class extends EventEmitter {
|
|
|
3780
4220
|
});
|
|
3781
4221
|
}
|
|
3782
4222
|
#handleStopAgentMessage(message) {}
|
|
4223
|
+
/**
|
|
4224
|
+
* Refreshes the API key by requesting a new token from the SDK server,
|
|
4225
|
+
* when the apiKey is a (short lived) token used when runner is deployed
|
|
4226
|
+
* using Tool Forge Cloud.
|
|
4227
|
+
*/
|
|
4228
|
+
async #refreshApiKey() {
|
|
4229
|
+
const url = new URL(this.#serverUrl);
|
|
4230
|
+
url.pathname = "/token/refresh";
|
|
4231
|
+
const res = await fetch(url.toString(), {
|
|
4232
|
+
method: "POST",
|
|
4233
|
+
headers: { "Content-Type": "application/json" },
|
|
4234
|
+
body: JSON.stringify({ token: this.#apiKey })
|
|
4235
|
+
});
|
|
4236
|
+
if (!res.ok) throw new Error(`failed to refresh token: ${res.status} ${res.statusText}`);
|
|
4237
|
+
const data = await res.json();
|
|
4238
|
+
const result = z$1.object({ token: z$1.string().startsWith(TOKEN_PREFIX) }).safeParse(data);
|
|
4239
|
+
if (!result.success) throw new Error("invalid response from token refresh");
|
|
4240
|
+
this.#apiKey = result.data.token;
|
|
4241
|
+
this.#logger.info("successfully refreshed API token");
|
|
4242
|
+
}
|
|
3783
4243
|
stop() {
|
|
3784
4244
|
this.#logger.info("gracefully stopping tool runner...");
|
|
3785
4245
|
if (this.#fsWatcher) {
|
|
@@ -3794,59 +4254,17 @@ var ForgeRunner = class extends EventEmitter {
|
|
|
3794
4254
|
}
|
|
3795
4255
|
this.#webSocketClient.off(WEB_SOCKET_CLIENT_EVENTS.COMMUNICATION_INITIALIZED, this.#handleWebSocketCommunicationInitialized.bind(this));
|
|
3796
4256
|
this.#webSocketClient.stop();
|
|
4257
|
+
if (this.#refreshApiKeyInterval) {
|
|
4258
|
+
clearInterval(this.#refreshApiKeyInterval);
|
|
4259
|
+
this.#refreshApiKeyInterval = void 0;
|
|
4260
|
+
this.#logger.info("token refresh interval cleared");
|
|
4261
|
+
}
|
|
3797
4262
|
this.#logger.info("tool runner stopped");
|
|
3798
4263
|
}
|
|
3799
4264
|
};
|
|
3800
4265
|
|
|
3801
4266
|
//#endregion
|
|
3802
|
-
//#region src/cli/
|
|
3803
|
-
const version = JSON.parse(readFileSync(path.resolve(__dirname, "../../package.json"), "utf-8")).version;
|
|
3804
|
-
program.name("toolforge").description("Tool Forge SDK cli").version(version);
|
|
3805
|
-
program.command("dev").description("start the tool forge development server").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").option("-d, --debug", "enable debug logging", false).action(async function startServer() {
|
|
3806
|
-
const debug = z$1.boolean().optional().default(false).parse(this.opts().debug);
|
|
3807
|
-
await startToolForge({
|
|
3808
|
-
configRelPath: z$1.string().parse(this.opts().config),
|
|
3809
|
-
debug,
|
|
3810
|
-
mode: "development"
|
|
3811
|
-
});
|
|
3812
|
-
});
|
|
3813
|
-
program.command("start").description("start the tool forge server in production mode").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").option("-d, --debug", "enable debug logging", false).action(async function startServer() {
|
|
3814
|
-
const debug = z$1.boolean().optional().default(false).parse(this.opts().debug);
|
|
3815
|
-
await startToolForge({
|
|
3816
|
-
configRelPath: z$1.string().parse(this.opts().config),
|
|
3817
|
-
debug,
|
|
3818
|
-
mode: "production"
|
|
3819
|
-
});
|
|
3820
|
-
});
|
|
3821
|
-
program.command("build").description("build the tools for production").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").option("-d, --debug", "enable debug logging", false).action(async function() {
|
|
3822
|
-
const logger = pino({
|
|
3823
|
-
level: z$1.boolean().optional().default(false).parse(this.opts().debug) ? "debug" : "info",
|
|
3824
|
-
transport: { target: "pino-pretty" }
|
|
3825
|
-
});
|
|
3826
|
-
try {
|
|
3827
|
-
const configRelPath = z$1.string().parse(this.opts().config);
|
|
3828
|
-
const configPath = path.resolve(process.cwd(), configRelPath);
|
|
3829
|
-
const config = toolForgeConfigSchema.parse(await import(configPath).then((mod) => mod.default));
|
|
3830
|
-
logger.debug({ config }, "loaded config from %s", configPath);
|
|
3831
|
-
const toolsDir = path.resolve(process.cwd(), config.toolsDir);
|
|
3832
|
-
const { toolEntries, agentEntries, forgeItems } = await gatherToolAndAgentEntries(toolsDir, "production", logger);
|
|
3833
|
-
if (forgeItems.length === 0) {
|
|
3834
|
-
logger.warn("no tools or agents found to build");
|
|
3835
|
-
process.exit(0);
|
|
3836
|
-
}
|
|
3837
|
-
await bundleForge({
|
|
3838
|
-
toolEntries,
|
|
3839
|
-
agentEntries,
|
|
3840
|
-
forgeItems,
|
|
3841
|
-
toolsDir,
|
|
3842
|
-
logger
|
|
3843
|
-
});
|
|
3844
|
-
logger.info("tools built successfully");
|
|
3845
|
-
} catch (error) {
|
|
3846
|
-
logger.error(error, "failed to build tools");
|
|
3847
|
-
process.exit(1);
|
|
3848
|
-
}
|
|
3849
|
-
});
|
|
4267
|
+
//#region src/cli/actions/start-tool-forge.ts
|
|
3850
4268
|
async function startToolForge({ configRelPath, debug, mode }) {
|
|
3851
4269
|
const logger = pino({
|
|
3852
4270
|
level: debug ? "debug" : "info",
|
|
@@ -3854,8 +4272,7 @@ async function startToolForge({ configRelPath, debug, mode }) {
|
|
|
3854
4272
|
});
|
|
3855
4273
|
try {
|
|
3856
4274
|
const spinner = ora("loading configuration...").start();
|
|
3857
|
-
const configPath =
|
|
3858
|
-
const config = toolForgeConfigSchema.parse(await import(configPath).then((mod) => mod.default));
|
|
4275
|
+
const { config, configPath } = await getToolForgeConfig(configRelPath);
|
|
3859
4276
|
spinner.stop();
|
|
3860
4277
|
logger.debug({ config }, "loaded config from %s", configPath);
|
|
3861
4278
|
const runner = new ForgeRunner(config, { mode }, logger);
|
|
@@ -3879,6 +4296,43 @@ async function startToolForge({ configRelPath, debug, mode }) {
|
|
|
3879
4296
|
process.exit(1);
|
|
3880
4297
|
}
|
|
3881
4298
|
}
|
|
4299
|
+
|
|
4300
|
+
//#endregion
|
|
4301
|
+
//#region src/cli/index.ts
|
|
4302
|
+
const version = JSON.parse(readFileSync(path.resolve(__dirname, "../../package.json"), "utf-8")).version;
|
|
4303
|
+
program.name("toolforge").description("Tool Forge SDK cli").version(version);
|
|
4304
|
+
program.command("dev").description("start the tool forge development server").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").option("-d, --debug", "enable debug logging", false).action(async function startServer() {
|
|
4305
|
+
const debug = z$1.boolean().optional().default(false).parse(this.opts().debug);
|
|
4306
|
+
await startToolForge({
|
|
4307
|
+
configRelPath: z$1.string().parse(this.opts().config),
|
|
4308
|
+
debug,
|
|
4309
|
+
mode: "development"
|
|
4310
|
+
}).catch((error) => {
|
|
4311
|
+
if (error instanceof Error) console.warn("Failed to start development server:", error.message);
|
|
4312
|
+
process.exit(1);
|
|
4313
|
+
});
|
|
4314
|
+
});
|
|
4315
|
+
program.command("start").description("start the tool forge server in production mode").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").option("-d, --debug", "enable debug logging", false).action(async function startServer() {
|
|
4316
|
+
const debug = z$1.boolean().optional().default(false).parse(this.opts().debug);
|
|
4317
|
+
await startToolForge({
|
|
4318
|
+
configRelPath: z$1.string().parse(this.opts().config),
|
|
4319
|
+
debug,
|
|
4320
|
+
mode: "production"
|
|
4321
|
+
}).catch((error) => {
|
|
4322
|
+
if (error instanceof Error) console.warn("Failed to start server:", error.message);
|
|
4323
|
+
process.exit(1);
|
|
4324
|
+
});
|
|
4325
|
+
});
|
|
4326
|
+
program.command("build").description("build the tools for production").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").option("-d, --debug", "enable debug logging", false).action(async function() {
|
|
4327
|
+
const debug = z$1.boolean().optional().default(false).parse(this.opts().debug);
|
|
4328
|
+
await build(z$1.string().parse(this.opts().config), debug);
|
|
4329
|
+
});
|
|
4330
|
+
program.command("login").description("login to Tool Forge").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").action(async function() {
|
|
4331
|
+
await login(z$1.string().parse(this.opts().config));
|
|
4332
|
+
});
|
|
4333
|
+
program.command("deploy").description("deploy the tools to Tool Forge").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").option("-d, --debug", "enable debug logging", false).action(async function() {
|
|
4334
|
+
await deploy(z$1.string().parse(this.opts().config), z$1.boolean().optional().default(false).parse(this.opts().debug));
|
|
4335
|
+
});
|
|
3882
4336
|
program.parse(Bun.argv);
|
|
3883
4337
|
|
|
3884
4338
|
//#endregion
|