Repository: AIGNE-io/aigne-framework
Pull request #60: feat(cli): support run agents from remote URL
Author: login: li-yechao
Created: 2025-04-15T02:16:46Z | Merged: 2025-04-15T06:26:55Z

## Related Issue

<!-- Use keywords like fixes, closes, resolves, relates to link the issue. In principle, all PRs should be associated with an issue -->

### Major Changes
1. support run agents from remote URL
```shell
aigne run https://www.aigne.io/xxx/xxx.tgz?secret=xxx
```

### Checklist

- [x] This change requires documentation updates, and I have updated the relevant documentation. If the documentation has not been updated, please create a documentation update issue and link it here
- [x] The changes are already covered by tests, and I have adjusted the test coverage for the changed parts
- [x] The newly added code logic is also covered by tests
- [x] This change adds dependencies, and they are placed in dependencies and devDependencies
- [x] This change includes adding or updating npm dependencies, and it does not result in multiple versions of the same dependency [check the diff of pnpm-lock.yaml]

<!-- This is an auto-generated comment: release notes by AIGNE Reviewer -->
### Summary by AIGNE

**Release Notes**

New Feature:
- Added support for running AIGNE agents directly from remote URLs
- Implemented secure package download and extraction functionality for remote agents

Test:
- Enhanced test coverage for remote agent execution and package handling

This update allows users to run AIGNE agents by simply providing a URL, eliminating the need to manually download and extract agent packages. The system automatically handles downloading, verification, and cleanup of remote packages, making it easier to share and execute agents across different environments.
<!-- end of auto-generated comment: release notes by AIGNE Reviewer -->


Diff:
diff --git a/docs/cli.md b/docs/cli.md
index 2aab1d5b0..bc6889f35 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -37,7 +37,7 @@ aigne run [path] [options]
 
 #### Arguments
 
-- `path`: Path to the agents directory (defaults to current directory `.`)
+- `path`: Path to the agents directory (defaults to current directory `.`) or URL to a project from AIGNE Studio
 
 #### Options
 
@@ -64,6 +64,12 @@ Run a specific agent:
 aigne run --agent myAgent
 ```
 
+Run an agent from an AIGNE Studio project URL:
+
+```bash
+aigne run https://www.aigne.io/projects/xxx/xxx.tgz
+```
+
 ### `aigne create`
 
 Create a new AIGNE project with the required configuration files. The command will interactively prompt for a project name.
diff --git a/docs/cli.zh.md b/docs/cli.zh.md
index 119fa5ae6..b47ee24b2 100644
--- a/docs/cli.zh.md
+++ b/docs/cli.zh.md
@@ -37,7 +37,7 @@ aigne run [路径] [选项]
 
 #### 参数
 
-- `路径`：代理目录的路径（默认为当前目录 `.`）
+- `路径`：代理目录的路径（默认为当前目录 `.`）或指向 AIGNE Studio 中项目的 URL
 
 #### 选项
 
@@ -64,6 +64,12 @@ aigne run ./my-agents
 aigne run --agent myAgent
 ```
 
+从 AIGNE Studio 项目 URL 运行代理：
+
+```bash
+aigne run https://www.aigne.io/projects/xxx/xxx.tgz
+```
+
 ### `aigne create`
 
 创建一个新的 AIGNE 项目，包含所需的配置文件。该命令会以交互方式提示输入项目名称。
diff --git a/packages/cli/package.json b/packages/cli/package.json
index cb6849727..3a012c335 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -55,14 +55,17 @@
     "gradient-string": "^3.0.0",
     "inquirer": "^12.5.2",
     "openai": "^4.93.0",
+    "pretty-error": "^4.0.0",
+    "tar": "^7.4.3",
     "zod": "^3.24.2"
   },
   "devDependencies": {
-    "@aigne/test-utils": "workspace:^",
+    "@types/archiver": "^6.0.3",
     "@types/bun": "^1.2.9",
     "@types/express": "^5.0.1",
     "@types/gradient-string": "^1.1.6",
     "@types/node": "^22.14.0",
+    "archiver": "^7.0.1",
     "detect-port": "^2.1.0",
     "npm-run-all": "^4.1.5",
     "rimraf": "^6.0.1",
diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts
index 36cd1263c..05f77393a 100644
--- a/packages/cli/src/cli.ts
+++ b/packages/cli/src/cli.ts
@@ -1,5 +1,11 @@
 #!/usr/bin/env node
 
+import PrettyError from "pretty-error";
 import { createAIGNECommand } from "./commands/aigne.js";
 
-createAIGNECommand().parse();
+createAIGNECommand()
+  .parseAsync()
+  .catch((error) => {
+    console.error(new PrettyError().render(error));
+    process.exit(1);
+  });
diff --git a/packages/cli/src/commands/run.ts b/packages/cli/src/commands/run.ts
index 9e3d6066f..2e1fdc586 100644
--- a/packages/cli/src/commands/run.ts
+++ b/packages/cli/src/commands/run.ts
@@ -1,6 +1,10 @@
-import { isAbsolute, resolve } from "node:path";
+import { randomUUID } from "node:crypto";
+import { mkdir, rm } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { isAbsolute, join, resolve } from "node:path";
 import { type Agent, ExecutionEngine } from "@aigne/core";
 import { Command, type OptionValues } from "commander";
+import { downloadAndExtract } from "../utils/download.js";
 import { runChatLoopInTerminal } from "../utils/run-chat-loop.js";
 
 interface RunOptions extends OptionValues {
@@ -10,35 +14,53 @@ interface RunOptions extends OptionValues {
 export function createRunCommand(): Command {
   return new Command("run")
     .description("Run a chat loop with the specified agent")
-    .argument("[path]", "Path to the agents directory", ".")
+    .argument("[path]", "Path to the agents directory or URL to aigne project", ".")
     .option("--agent <agent>", "Name of the agent to use (defaults to the first agent found)")
     .action(async (path: string, options: RunOptions) => {
-      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
-
-      const engine = await ExecutionEngine.load({ path: absolutePath });
-
-      let agent: Agent | undefined;
-      if (options.agent) {
-        agent = engine.agents[options.agent];
-        if (!agent) {
-          console.error(`Agent "${options.agent}" not found.`);
-          console.log("Available agents:");
-          for (const agent of engine.agents) {
-            console.log(`- ${agent.name}`);
-          }
-          throw new Error(`Agent "${options.agent}" not found`);
-        }
-      } else {
-        agent = engine.agents[0];
-        if (!agent) throw new Error("No agents found in the specified path");
+      if (path.startsWith("http")) {
+        await downloadAndRunPackage(path, options);
+        return;
       }
 
-      const user = engine.call(agent);
-
-      await runChatLoopInTerminal(user, {});
+      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
 
-      await engine.shutdown();
+      await runEngine(path, absolutePath, options);
     })
     .showHelpAfterError(true)
     .showSuggestionAfterError(true);
 }
+
+async function runEngine(originalPath: string, path: string, options: RunOptions) {
+  const engine = await ExecutionEngine.load({ path });
+
+  let agent: Agent | undefined;
+  if (options.agent) {
+    agent = engine.agents[options.agent];
+    if (!agent) {
+      console.error(`Agent "${options.agent}" not found in ${originalPath}`);
+      console.log("Available agents:");
+      for (const agent of engine.agents) {
+        console.log(`- ${agent.name}`);
+      }
+      throw new Error(`Agent "${options.agent}" not found in ${originalPath}`);
+    }
+  } else {
+    agent = engine.agents[0];
+    if (!agent) throw new Error(`No agents found in ${originalPath}`);
+  }
+
+  const user = engine.call(agent);
+
+  await runChatLoopInTerminal(user, {});
+}
+
+async function downloadAndRunPackage(url: string, options: RunOptions) {
+  const dir = join(tmpdir(), randomUUID());
+  try {
+    await mkdir(dir, { recursive: true });
+    await downloadAndExtract(url, dir);
+    await runEngine(url, dir, options);
+  } finally {
+    await rm(dir, { recursive: true, force: true });
+  }
+}
diff --git a/packages/cli/src/utils/download.ts b/packages/cli/src/utils/download.ts
new file mode 100644
index 000000000..bf458518a
--- /dev/null
+++ b/packages/cli/src/utils/download.ts
@@ -0,0 +1,27 @@
+import { Readable } from "node:stream";
+import { finished } from "node:stream/promises";
+import type { ReadableStream } from "node:stream/web";
+import { x } from "tar";
+
+export async function downloadAndExtract(url: string, dir: string) {
+  const response = await fetch(url).catch((error) => {
+    throw new Error(`Failed to download package from ${url}: ${error.message}`);
+  });
+
+  if (!response.ok) {
+    throw new Error(`Failed to download package from ${url}: ${response.statusText}`);
+  }
+
+  if (!response.body) {
+    throw new Error(`Failed to download package from ${url}: Unexpected to get empty response`);
+  }
+
+  try {
+    await finished(
+      Readable.fromWeb(response.body as unknown as ReadableStream).pipe(x({ C: dir })),
+    );
+  } catch (error) {
+    error.message = `Failed to extract package from ${url}: ${error.message}`;
+    throw error;
+  }
+}
diff --git a/packages/cli/test/_mocks_/mock-aigne-package.ts b/packages/cli/test/_mocks_/mock-aigne-package.ts
new file mode 100644
index 000000000..9de876620
--- /dev/null
+++ b/packages/cli/test/_mocks_/mock-aigne-package.ts
@@ -0,0 +1,42 @@
+import archiver from "archiver";
+
+export async function mockAIGNEPackage() {
+  const mockPackage = archiver("tar", {
+    gzip: true,
+    store: false,
+  });
+  mockPackage.append(
+    `\
+  chat_model:
+  name: gpt-4o-mini
+  agents:
+  - chat.yaml
+  `,
+    { name: "aigne.yaml" },
+  );
+  mockPackage.append(
+    `\
+  name: chat
+  description: A simple chat agent
+  instructions: You are a helpful assistant.
+  `,
+    {
+      name: "chat.yaml",
+    },
+  );
+
+  await mockPackage.finalize();
+
+  return new ReadableStream({
+    async start(controller) {
+      try {
+        for await (const a of mockPackage) {
+          controller.enqueue(a);
+        }
+      } catch (error) {
+        controller.error(error);
+      }
+      controller.close();
+    },
+  });
+}
diff --git a/packages/test-utils/src/mock-module.ts b/packages/cli/test/_mocks_/mock-module.ts
similarity index 100%
rename from packages/test-utils/src/mock-module.ts
rename to packages/cli/test/_mocks_/mock-module.ts
diff --git a/packages/cli/test/cli.test.ts b/packages/cli/test/cli.test.ts
index 8fe65a7df..0298cfdc0 100644
--- a/packages/cli/test/cli.test.ts
+++ b/packages/cli/test/cli.test.ts
@@ -1,14 +1,36 @@
-import { expect, mock, test } from "bun:test";
+import { expect, mock, spyOn, test } from "bun:test";
 import { Command } from "commander";
+import { withQuery } from "ufo";
+import { mockModule } from "./_mocks_/mock-module.js";
 
 test("CLI imports and loads correctly", async () => {
   const createAIGNECommand = mock(() => new Command());
 
-  mock.module("@aigne/cli/commands/aigne.js", () => ({
+  await using _ = await mockModule("@aigne/cli/commands/aigne.js", () => ({
     createAIGNECommand,
   }));
 
-  await import("@aigne/cli/cli.js");
+  await import(withQuery("@aigne/cli/cli.js", { key: Date.now() }));
 
   expect(createAIGNECommand).toHaveBeenCalled();
 });
+
+test("aigne cli should print pretty error message", async () => {
+  const error = spyOn(console, "error").mockImplementationOnce(() => {});
+  const exit = spyOn(process, "exit").mockImplementationOnce(() => undefined as never);
+
+  await using _ = await mockModule("@aigne/cli/commands/aigne.js", () => ({
+    createAIGNECommand: () =>
+      new Command().action(async () => {
+        throw new Error("test error");
+      }),
+  }));
+
+  await import(withQuery("@aigne/cli/cli.js", { key: Date.now() }));
+
+  expect(error).toHaveBeenCalledWith(expect.stringContaining("test error"));
+  expect(exit).toHaveBeenCalledWith(1);
+
+  error.mockRestore();
+  exit.mockRestore();
+});
diff --git a/packages/cli/test/commands/run.test.ts b/packages/cli/test/commands/run.test.ts
index 9bbcefa99..342d67253 100644
--- a/packages/cli/test/commands/run.test.ts
+++ b/packages/cli/test/commands/run.test.ts
@@ -2,7 +2,8 @@ import { expect, mock, spyOn, test } from "bun:test";
 import { join, relative, resolve } from "node:path";
 import { createRunCommand } from "@aigne/cli/commands/run.js";
 import { UserAgent } from "@aigne/core";
-import { mockModule } from "@aigne/test-utils/mock-module.js";
+import { mockAIGNEPackage } from "../_mocks_/mock-aigne-package.js";
+import { mockModule } from "../_mocks_/mock-module.js";
 
 test("run command should call run chat loop correctly", async () => {
   const runChatLoopInTerminal = mock();
@@ -58,3 +59,26 @@ test("run command should call run chat loop correctly", async () => {
     "not found",
   );
 });
+
+test("run command should download package and run correctly", async () => {
+  const runChatLoopInTerminal = mock();
+
+  await using _ = await mockModule("@aigne/cli/utils/run-chat-loop.js", () => {
+    return { runChatLoopInTerminal };
+  });
+
+  spyOn(globalThis, "fetch").mockReturnValueOnce(
+    Promise.resolve(new Response(await mockAIGNEPackage())),
+  );
+
+  const command = createRunCommand();
+
+  const url = "https://www.aigne.io/projects/xxx/test-agents.tgz";
+
+  await command.parseAsync(["", "run", url]);
+
+  expect(runChatLoopInTerminal).toHaveBeenLastCalledWith(
+    expect.any(UserAgent),
+    expect.objectContaining({}),
+  );
+});
diff --git a/packages/cli/test/commands/serve.test.ts b/packages/cli/test/commands/serve.test.ts
index 4947b3808..6f3c5cc50 100644
--- a/packages/cli/test/commands/serve.test.ts
+++ b/packages/cli/test/commands/serve.test.ts
@@ -1,8 +1,8 @@
 import { expect, mock, test } from "bun:test";
 import { join } from "node:path";
 import { createServeCommand } from "@aigne/cli/commands/serve.js";
-import { mockModule } from "@aigne/test-utils/mock-module.js";
 import { detect } from "detect-port";
+import { mockModule } from "../_mocks_/mock-module.js";
 
 test("serve-mcp command should work with default options", async () => {
   const mockListen = mock().mockImplementationOnce((_, cb) => cb());
diff --git a/packages/cli/test/utils/download.test.ts b/packages/cli/test/utils/download.test.ts
new file mode 100644
index 000000000..a770e17d9
--- /dev/null
+++ b/packages/cli/test/utils/download.test.ts
@@ -0,0 +1,63 @@
+import { expect, spyOn, test } from "bun:test";
+import { randomUUID } from "node:crypto";
+import { mkdir, rm, stat } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { downloadAndExtract } from "@aigne/cli/utils/download.js";
+import { mockAIGNEPackage } from "../_mocks_/mock-aigne-package.js";
+
+test("downloadPackage should work", async () => {
+  const url = "https://www.aigne.io/projects/xxx/test-package.tgz";
+  const dir = join(tmpdir(), randomUUID());
+  await mkdir(dir, { recursive: true });
+
+  try {
+    spyOn(globalThis, "fetch").mockReturnValueOnce(
+      Promise.resolve(new Response(await mockAIGNEPackage())),
+    );
+
+    await downloadAndExtract(url, dir);
+
+    expect((await stat(join(dir, "aigne.yaml"))).isFile()).toBeTrue();
+  } finally {
+    await rm(dir, { recursive: true, force: true });
+  }
+});
+
+test("downloadPackage should raise error with custom message", async () => {
+  const url = "https://www.aigne.io/projects/xxx/test-package.tgz";
+  const dir = join(tmpdir(), randomUUID());
+  await mkdir(dir, { recursive: true });
+
+  try {
+    spyOn(globalThis, "fetch").mockReturnValueOnce(Promise.reject(new Error("Network error")));
+
+    expect(downloadAndExtract(url, dir)).rejects.toThrow(
+      "Failed to download package from https://www.aigne.io/projects/xxx/test-package.tgz: Network error",
+    );
+
+    spyOn(globalThis, "fetch").mockReturnValueOnce(
+      Promise.resolve(new Response(null, { status: 404, statusText: "Not Found" })),
+    );
+
+    expect(downloadAndExtract(url, dir)).rejects.toThrow(
+      "Failed to download package from https://www.aigne.io/projects/xxx/test-package.tgz: Not Found",
+    );
+
+    spyOn(globalThis, "fetch").mockReturnValueOnce(Promise.resolve(new Response(null)));
+
+    expect(downloadAndExtract(url, dir)).rejects.toThrow(
+      "Failed to download package from https://www.aigne.io/projects/xxx/test-package.tgz: Unexpected to get empty response",
+    );
+
+    spyOn(globalThis, "fetch").mockReturnValueOnce(
+      Promise.resolve(new Response("invalid tgz file content")),
+    );
+
+    expect(downloadAndExtract(url, dir)).rejects.toThrow(
+      "Failed to extract package from https://www.aigne.io/projects/xxx/test-package.tgz: TAR_BAD_ARCHIVE: Unrecognized archive format",
+    );
+  } finally {
+    await rm(dir, { recursive: true, force: true });
+  }
+});
diff --git a/packages/core/test/_mocks/mock-module.ts b/packages/core/test/_mocks/mock-module.ts
new file mode 100644
index 000000000..3f7480116
--- /dev/null
+++ b/packages/core/test/_mocks/mock-module.ts
@@ -0,0 +1,21 @@
+import { mock } from "bun:test";
+
+export async function mockModule(
+  modulePath: string,
+  renderMocks: () => Promise<Record<string, unknown>> | Record<string, unknown>,
+) {
+  const original = await import(modulePath).then((res) => ({ ...res })).catch(() => ({}));
+  const mocks = await Promise.resolve(renderMocks())
+    .then((modules) => ({ modules }))
+    .catch((error) => ({ error }));
+
+  await mock.module(modulePath, () => {
+    if ("error" in mocks) throw mocks.error;
+    return { ...original, ...mocks.modules };
+  });
+  return {
+    [Symbol.asyncDispose]: async () => {
+      await mock.module(modulePath, () => original);
+    },
+  };
+}
diff --git a/packages/core/test/loader/agent-js.test.ts b/packages/core/test/loader/agent-js.test.ts
index cfa43c29d..7d9e5638f 100644
--- a/packages/core/test/loader/agent-js.test.ts
+++ b/packages/core/test/loader/agent-js.test.ts
@@ -1,6 +1,6 @@
 import { expect, test } from "bun:test";
 import { loadAgentFromJsFile } from "@aigne/core/loader/agent-js.js";
-import { mockModule } from "@aigne/test-utils/mock-module.js";
+import { mockModule } from "../_mocks/mock-module.js";
 
 test("loadAgentFromJs should error if agent.js file is invalid", async () => {
   const fn = () => {};
diff --git a/packages/core/test/loader/agent-yaml.test.ts b/packages/core/test/loader/agent-yaml.test.ts
index 873cd251c..82352a2a5 100644
--- a/packages/core/test/loader/agent-yaml.test.ts
+++ b/packages/core/test/loader/agent-yaml.test.ts
@@ -5,7 +5,7 @@ import { AIAgent, FunctionAgent } from "@aigne/core";
 import { loadAgentFromYamlFile } from "@aigne/core/loader/agent-yaml.js";
 import { loadAgent } from "@aigne/core/loader/index.js";
 import { outputSchemaToResponseFormatSchema } from "@aigne/core/utils/json-schema.js";
-import { mockModule } from "@aigne/test-utils/mock-module.js";
+import { mockModule } from "../_mocks/mock-module.js";
 
 test("loadAgentFromYaml should load agent correctly", async () => {
   const agent = await loadAgent(join(import.meta.dirname, "../../test-agents/chat.yaml"));
diff --git a/packages/core/test/loader/loader.test.ts b/packages/core/test/loader/loader.test.ts
index 499180403..9cf35a9b0 100644
--- a/packages/core/test/loader/loader.test.ts
+++ b/packages/core/test/loader/loader.test.ts
@@ -6,9 +6,9 @@ import { load, loadAgent } from "@aigne/core/loader/index.js";
 import { ClaudeChatModel } from "@aigne/core/models/claude-chat-model.js";
 import { OpenAIChatModel } from "@aigne/core/models/openai-chat-model.js";
 import { XAIChatModel } from "@aigne/core/models/xai-chat-model.js";
-import { mockModule } from "@aigne/test-utils/mock-module.js";
 import { Client } from "@modelcontextprotocol/sdk/client/index.js";
 import { nanoid } from "nanoid";
+import { mockModule } from "../_mocks/mock-module.js";
 
 test("ExecutionEngine.load should load agents correctly", async () => {
   const engine = await ExecutionEngine.load({
diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ace76711c..205900cc7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -249,13 +249,19 @@ importers:
       openai:
         specifier: ^4.93.0
         version: 4.93.0(encoding@0.1.13)(ws@8.18.0)(zod@3.24.2)
+      pretty-error:
+        specifier: ^4.0.0
+        version: 4.0.0
+      tar:
+        specifier: ^7.4.3
+        version: 7.4.3
       zod:
         specifier: ^3.24.2
         version: 3.24.2
     devDependencies:
-      '@aigne/test-utils':
-        specifier: workspace:^
-        version: link:../test-utils
+      '@types/archiver':
+        specifier: ^6.0.3
+        version: 6.0.3
       '@types/bun':
         specifier: ^1.2.9
         version: 1.2.9
@@ -268,6 +274,9 @@ importers:
       '@types/node':
         specifier: ^22.14.0
         version: 22.14.0
+      archiver:
+        specifier: ^7.0.1
+        version: 7.0.1
       detect-port:
         specifier: ^2.1.0
         version: 2.1.0
@@ -575,10 +584,21 @@ packages:
     resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
     engines: {node: '>=12'}
 
+  '@isaacs/fs-minipass@4.0.1':
+    resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
+    engines: {node: '>=18.0.0'}
+
   '@modelcontextprotocol/sdk@1.9.0':
     resolution: {integrity: sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==}
     engines: {node: '>=18'}
 
+  '@pkgjs/parseargs@0.11.0':
+    resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+    engines: {node: '>=14'}
+
+  '@types/archiver@6.0.3':
+    resolution: {integrity: sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==}
+
   '@types/body-parser@1.19.5':
     resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
 
@@ -627,6 +647,9 @@ packages:
   '@types/range-parser@1.2.7':
     resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
 
+  '@types/readdir-glob@1.1.5':
+    resolution: {integrity: sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==}
+
   '@types/retry@0.12.2':
     resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==}
 
@@ -686,6 +709,14 @@ packages:
     resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
     engines: {node: '>=12'}
 
+  archiver-utils@5.0.2:
+    resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==}
+    engines: {node: '>= 14'}
+
+  archiver@7.0.1:
+    resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==}
+    engines: {node: '>= 14'}
+
   array-buffer-byte-length@1.0.2:
     resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
     engines: {node: '>= 0.4'}
@@ -694,6 +725,9 @@ packages:
     resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
     engines: {node: '>= 0.4'}
 
+  async@3.2.6:
+    resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+
   asynckit@0.4.0:
     resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
 
@@ -701,19 +735,38 @@ packages:
     resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
     engines: {node: '>= 0.4'}
 
+  b4a@1.6.7:
+    resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
+
   balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
 
+  bare-events@2.5.4:
+    resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==}
+
+  base64-js@1.5.1:
+    resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
   body-parser@2.2.0:
     resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
     engines: {node: '>=18'}
 
+  boolbase@1.0.0:
+    resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
   brace-expansion@1.1.11:
     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
 
   brace-expansion@2.0.1:
     resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
 
+  buffer-crc32@1.0.0:
+    resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
+    engines: {node: '>=8.0.0'}
+
+  buffer@6.0.3:
+    resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
   bun-types@1.2.9:
     resolution: {integrity: sha512-dk/kOEfQbajENN/D6FyiSgOKEuUi9PWfqKQJEgwKrCMWbjS/S6tEXp178mWvWAcUSYm9ArDlWHZKO3T/4cLXiw==}
 
@@ -744,6 +797,10 @@ packages:
   chardet@0.7.0:
     resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
 
+  chownr@3.0.0:
+    resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
+    engines: {node: '>=18'}
+
   cli-cursor@5.0.0:
     resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
     engines: {node: '>=18'}
@@ -780,6 +837,10 @@ packages:
     resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
     engines: {node: '>=18'}
 
+  compress-commons@6.0.2:
+    resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==}
+    engines: {node: '>= 14'}
+
   concat-map@0.0.1:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
 
@@ -799,10 +860,22 @@ packages:
     resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
     engines: {node: '>= 0.6'}
 
+  core-util-is@1.0.3:
+    resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+
   cors@2.8.5:
     resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
     engines: {node: '>= 0.10'}
 
+  crc-32@1.2.2:
+    resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
+    engines: {node: '>=0.8'}
+    hasBin: true
+
+  crc32-stream@6.0.0:
+    resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
+    engines: {node: '>= 14'}
+
   cross-spawn@6.0.6:
     resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==}
     engines: {node: '>=4.8'}
@@ -811,6 +884,13 @@ packages:
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}
 
+  css-select@4.3.0:
+    resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
+
+  css-what@6.1.0:
+    resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
+    engines: {node: '>= 6'}
+
   data-view-buffer@1.0.2:
     resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
     engines: {node: '>= 0.4'}
@@ -853,6 +933,22 @@ packages:
     engines: {node: '>= 16.0.0'}
     hasBin: true
 
+  dom-converter@0.2.0:
+    resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==}
+
+  dom-serializer@1.4.1:
+    resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
+
+  domelementtype@2.3.0:
+    resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+  domhandler@4.3.1:
+    resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
+    engines: {node: '>= 4'}
+
+  domutils@2.8.0:
+    resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
+
   dunder-proto@1.0.1:
     resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
     engines: {node: '>= 0.4'}
@@ -879,6 +975,9 @@ packages:
   encoding@0.1.13:
     resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
 
+  entities@2.2.0:
+    resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
+
   environment@1.1.0:
     resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
     engines: {node: '>=18'}
@@ -928,6 +1027,10 @@ packages:
   eventemitter3@5.0.1:
     resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
 
+  events@3.3.0:
+    resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
+    engines: {node: '>=0.8.x'}
+
   eventsource-parser@3.0.0:
     resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==}
     engines: {node: '>=18.0.0'}
@@ -950,6 +1053,9 @@ packages:
     resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
     engines: {node: '>=4'}
 
+  fast-fifo@1.3.2:
+    resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
+
   fastq@1.19.1:
     resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
 
@@ -1009,6 +1115,10 @@ packages:
     resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
     engines: {node: '>= 0.4'}
 
+  glob@10.4.5:
+    resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+    hasBin: true
+
   glob@11.0.1:
     resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==}
     engines: {node: 20 || >=22}
@@ -1059,6 +1169,9 @@ packages:
   hosted-git-info@2.8.9:
     resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
 
+  htmlparser2@6.1.0:
+    resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
+
   http-errors@2.0.0:
     resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
     engines: {node: '>= 0.8'}
@@ -1074,6 +1187,9 @@ packages:
     resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
     engines: {node: '>=0.10.0'}
 
+  ieee754@1.2.1:
+    resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
   inherits@2.0.4:
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
 
@@ -1176,6 +1292,10 @@ packages:
     resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
     engines: {node: '>= 0.4'}
 
+  is-stream@2.0.1:
+    resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+    engines: {node: '>=8'}
+
   is-string@1.1.1:
     resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
     engines: {node: '>= 0.4'}
@@ -1200,12 +1320,18 @@ packages:
     resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
     engines: {node: '>= 0.4'}
 
+  isarray@1.0.0:
+    resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+
   isarray@2.0.5:
     resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
 
   isexe@2.0.0:
     resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
 
+  jackspeak@3.4.3:
+    resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
   jackspeak@4.0.2:
     resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==}
     engines: {node: 20 || >=22}
@@ -1213,14 +1339,24 @@ packages:
   json-parse-better-errors@1.0.2:
     resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==}
 
+  lazystream@1.0.1:
+    resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
+    engines: {node: '>= 0.6.3'}
+
   load-json-file@4.0.0:
     resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
     engines: {node: '>=4'}
 
+  lodash@4.17.21:
+    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
   log-update@6.1.0:
     resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
     engines: {node: '>=18'}
 
+  lru-cache@10.4.3:
+    resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
   lru-cache@11.0.2:
     resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==}
     engines: {node: 20 || >=22}
@@ -1268,10 +1404,27 @@ packages:
   minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
 
+  minimatch@5.1.6:
+    resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
+    engines: {node: '>=10'}
+
+  minimatch@9.0.5:
+    resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+    engines: {node: '>=16 || 14 >=14.17'}
+
   minipass@7.1.2:
     resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
     engines: {node: '>=16 || 14 >=14.17'}
 
+  minizlib@3.0.2:
+    resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==}
+    engines: {node: '>= 18'}
+
+  mkdirp@3.0.1:
+    resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
+    engines: {node: '>=10'}
+    hasBin: true
+
   ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
@@ -1311,11 +1464,18 @@ packages:
   normalize-package-data@2.5.0:
     resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
 
+  normalize-path@3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+
   npm-run-all@4.1.5:
     resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==}
     engines: {node: '>= 4'}
     hasBin: true
 
+  nth-check@2.1.1:
+    resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
   object-assign@4.1.1:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}
@@ -1389,6 +1549,10 @@ packages:
   path-parse@1.0.7:
     resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
 
+  path-scurry@1.11.1:
+    resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+    engines: {node: '>=16 || 14 >=14.18'}
+
   path-scurry@2.0.0:
     resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
     engines: {node: 20 || >=22}
@@ -1418,6 +1582,16 @@ packages:
     resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
     engines: {node: '>= 0.4'}
 
+  pretty-error@4.0.0:
+    resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==}
+
+  process-nextick-args@2.0.1:
+    resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+
+  process@0.11.10:
+    resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
+    engines: {node: '>= 0.6.0'}
+
   proxy-addr@2.0.7:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
@@ -1438,6 +1612,16 @@ packages:
     resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==}
     engines: {node: '>=4'}
 
+  readable-stream@2.3.8:
+    resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
+
+  readable-stream@4.7.0:
+    resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  readdir-glob@1.1.3:
+    resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==}
+
   reflect.getprototypeof@1.0.10:
     resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
     engines: {node: '>= 0.4'}
@@ -1446,6 +1630,9 @@ packages:
     resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
     engines: {node: '>= 0.4'}
 
+  renderkid@3.0.0:
+    resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==}
+
   resolve@1.22.10:
     resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
     engines: {node: '>= 0.4'}
@@ -1486,6 +1673,9 @@ packages:
     resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
     engines: {node: '>=0.4'}
 
+  safe-buffer@5.1.2:
+    resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+
   safe-buffer@5.2.1:
     resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
 
@@ -1595,6 +1785,9 @@ packages:
     resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
     engines: {node: '>= 0.8'}
 
+  streamx@2.22.0:
+    resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==}
+
   string-width@4.2.3:
     resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
     engines: {node: '>=8'}
@@ -1623,6 +1816,12 @@ packages:
     resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
     engines: {node: '>= 0.4'}
 
+  string_decoder@1.1.1:
+    resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+
+  string_decoder@1.3.0:
+    resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
   strip-ansi@6.0.1:
     resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
     engines: {node: '>=8'}
@@ -1643,6 +1842,16 @@ packages:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
 
+  tar-stream@3.1.7:
+    resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
+
+  tar@7.4.3:
+    resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
+    engines: {node: '>=18'}
+
+  text-decoder@1.2.3:
+    resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==}
+
   tinycolor2@1.6.0:
     resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
 
@@ -1709,6 +1918,12 @@ packages:
     resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
     engines: {node: '>= 0.8'}
 
+  util-deprecate@1.0.2:
+    resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+  utila@0.4.0:
+    resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==}
+
   uuid@11.1.0:
     resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
     hasBin: true
@@ -1786,6 +2001,10 @@ packages:
       utf-8-validate:
         optional: true
 
+  yallist@5.0.0:
+    resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
+    engines: {node: '>=18'}
+
   yaml@2.7.1:
     resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==}
     engines: {node: '>= 14'}
@@ -1795,6 +2014,10 @@ packages:
     resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==}
     engines: {node: '>=18'}
 
+  zip-stream@6.0.1:
+    resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
+    engines: {node: '>= 14'}
+
   zod-to-json-schema@3.24.5:
     resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
     peerDependencies:
@@ -1997,6 +2220,10 @@ snapshots:
       wrap-ansi: 8.1.0
       wrap-ansi-cjs: wrap-ansi@7.0.0
 
+  '@isaacs/fs-minipass@4.0.1':
+    dependencies:
+      minipass: 7.1.2
+
   '@modelcontextprotocol/sdk@1.9.0':
     dependencies:
       content-type: 1.0.5
@@ -2012,6 +2239,13 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@pkgjs/parseargs@0.11.0':
+    optional: true
+
+  '@types/archiver@6.0.3':
+    dependencies:
+      '@types/readdir-glob': 1.1.5
+
   '@types/body-parser@1.19.5':
     dependencies:
       '@types/connect': 3.4.38
@@ -2071,6 +2305,10 @@ snapshots:
 
   '@types/range-parser@1.2.7': {}
 
+  '@types/readdir-glob@1.1.5':
+    dependencies:
+      '@types/node': 22.14.0
+
   '@types/retry@0.12.2': {}
 
   '@types/send@0.17.4':
@@ -2127,6 +2365,26 @@ snapshots:
 
   ansi-styles@6.2.1: {}
 
+  archiver-utils@5.0.2:
+    dependencies:
+      glob: 10.4.5
+      graceful-fs: 4.2.11
+      is-stream: 2.0.1
+      lazystream: 1.0.1
+      lodash: 4.17.21
+      normalize-path: 3.0.0
+      readable-stream: 4.7.0
+
+  archiver@7.0.1:
+    dependencies:
+      archiver-utils: 5.0.2
+      async: 3.2.6
+      buffer-crc32: 1.0.0
+      readable-stream: 4.7.0
+      readdir-glob: 1.1.3
+      tar-stream: 3.1.7
+      zip-stream: 6.0.1
+
   array-buffer-byte-length@1.0.2:
     dependencies:
       call-bound: 1.0.3
@@ -2142,14 +2400,23 @@ snapshots:
       get-intrinsic: 1.2.7
       is-array-buffer: 3.0.5
 
+  async@3.2.6: {}
+
   asynckit@0.4.0: {}
 
   available-typed-arrays@1.0.7:
     dependencies:
       possible-typed-array-names: 1.0.0
 
+  b4a@1.6.7: {}
+
   balanced-match@1.0.2: {}
 
+  bare-events@2.5.4:
+    optional: true
+
+  base64-js@1.5.1: {}
+
   body-parser@2.2.0:
     dependencies:
       bytes: 3.1.2
@@ -2164,6 +2431,8 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  boolbase@1.0.0: {}
+
   brace-expansion@1.1.11:
     dependencies:
       balanced-match: 1.0.2
@@ -2173,6 +2442,13 @@ snapshots:
     dependencies:
       balanced-match: 1.0.2
 
+  buffer-crc32@1.0.0: {}
+
+  buffer@6.0.3:
+    dependencies:
+      base64-js: 1.5.1
+      ieee754: 1.2.1
+
   bun-types@1.2.9:
     dependencies:
       '@types/node': 22.14.0
@@ -2207,6 +2483,8 @@ snapshots:
 
   chardet@0.7.0: {}
 
+  chownr@3.0.0: {}
+
   cli-cursor@5.0.0:
     dependencies:
       restore-cursor: 5.1.0
@@ -2238,6 +2516,14 @@ snapshots:
 
   commander@13.1.0: {}
 
+  compress-commons@6.0.2:
+    dependencies:
+      crc-32: 1.2.2
+      crc32-stream: 6.0.0
+      is-stream: 2.0.1
+      normalize-path: 3.0.0
+      readable-stream: 4.7.0
+
   concat-map@0.0.1: {}
 
   content-disposition@1.0.0:
@@ -2250,11 +2536,20 @@ snapshots:
 
   cookie@0.7.1: {}
 
+  core-util-is@1.0.3: {}
+
   cors@2.8.5:
     dependencies:
       object-assign: 4.1.1
       vary: 1.1.2
 
+  crc-32@1.2.2: {}
+
+  crc32-stream@6.0.0:
+    dependencies:
+      crc-32: 1.2.2
+      readable-stream: 4.7.0
+
   cross-spawn@6.0.6:
     dependencies:
       nice-try: 1.0.5
@@ -2269,6 +2564,16 @@ snapshots:
       shebang-command: 2.0.0
       which: 2.0.2
 
+  css-select@4.3.0:
+    dependencies:
+      boolbase: 1.0.0
+      css-what: 6.1.0
+      domhandler: 4.3.1
+      domutils: 2.8.0
+      nth-check: 2.1.1
+
+  css-what@6.1.0: {}
+
   data-view-buffer@1.0.2:
     dependencies:
       call-bound: 1.0.3
@@ -2311,6 +2616,28 @@ snapshots:
     dependencies:
       address: 2.0.3
 
+  dom-converter@0.2.0:
+    dependencies:
+      utila: 0.4.0
+
+  dom-serializer@1.4.1:
+    dependencies:
+      domelementtype: 2.3.0
+      domhandler: 4.3.1
+      entities: 2.2.0
+
+  domelementtype@2.3.0: {}
+
+  domhandler@4.3.1:
+    dependencies:
+      domelementtype: 2.3.0
+
+  domutils@2.8.0:
+    dependencies:
+      dom-serializer: 1.4.1
+      domelementtype: 2.3.0
+      domhandler: 4.3.1
+
   dunder-proto@1.0.1:
     dependencies:
       call-bind-apply-helpers: 1.0.1
@@ -2334,6 +2661,8 @@ snapshots:
       iconv-lite: 0.6.3
     optional: true
 
+  entities@2.2.0: {}
+
   environment@1.1.0: {}
 
   error-ex@1.3.2:
@@ -2425,6 +2754,8 @@ snapshots:
 
   eventemitter3@5.0.1: {}
 
+  events@3.3.0: {}
+
   eventsource-parser@3.0.0: {}
 
   eventsource@3.0.5:
@@ -2473,6 +2804,8 @@ snapshots:
       iconv-lite: 0.4.24
       tmp: 0.0.33
 
+  fast-fifo@1.3.2: {}
+
   fastq@1.19.1:
     dependencies:
       reusify: 1.1.0
@@ -2553,6 +2886,15 @@ snapshots:
       es-errors: 1.3.0
       get-intrinsic: 1.2.7
 
+  glob@10.4.5:
+    dependencies:
+      foreground-child: 3.3.0
+      jackspeak: 3.4.3
+      minimatch: 9.0.5
+      minipass: 7.1.2
+      package-json-from-dist: 1.0.1
+      path-scurry: 1.11.1
+
   glob@11.0.1:
     dependencies:
       foreground-child: 3.3.0
@@ -2600,6 +2942,13 @@ snapshots:
 
   hosted-git-info@2.8.9: {}
 
+  htmlparser2@6.1.0:
+    dependencies:
+      domelementtype: 2.3.0
+      domhandler: 4.3.1
+      domutils: 2.8.0
+      entities: 2.2.0
+
   http-errors@2.0.0:
     dependencies:
       depd: 2.0.0
@@ -2620,6 +2969,8 @@ snapshots:
     dependencies:
       safer-buffer: 2.1.2
 
+  ieee754@1.2.1: {}
+
   inherits@2.0.4: {}
 
   inquirer@12.5.2(@types/node@22.14.0):
@@ -2726,6 +3077,8 @@ snapshots:
     dependencies:
       call-bound: 1.0.3
 
+  is-stream@2.0.1: {}
+
   is-string@1.1.1:
     dependencies:
       call-bound: 1.0.3
@@ -2752,16 +3105,28 @@ snapshots:
       call-bound: 1.0.3
       get-intrinsic: 1.2.7
 
+  isarray@1.0.0: {}
+
   isarray@2.0.5: {}
 
   isexe@2.0.0: {}
 
+  jackspeak@3.4.3:
+    dependencies:
+      '@isaacs/cliui': 8.0.2
+    optionalDependencies:
+      '@pkgjs/parseargs': 0.11.0
+
   jackspeak@4.0.2:
     dependencies:
       '@isaacs/cliui': 8.0.2
 
   json-parse-better-errors@1.0.2: {}
 
+  lazystream@1.0.1:
+    dependencies:
+      readable-stream: 2.3.8
+
   load-json-file@4.0.0:
     dependencies:
       graceful-fs: 4.2.11
@@ -2769,6 +3134,8 @@ snapshots:
       pify: 3.0.0
       strip-bom: 3.0.0
 
+  lodash@4.17.21: {}
+
   log-update@6.1.0:
     dependencies:
       ansi-escapes: 7.0.0
@@ -2777,6 +3144,8 @@ snapshots:
       strip-ansi: 7.1.0
       wrap-ansi: 9.0.0
 
+  lru-cache@10.4.3: {}
+
   lru-cache@11.0.2: {}
 
   math-intrinsics@1.1.0: {}
@@ -2809,8 +3178,22 @@ snapshots:
     dependencies:
       brace-expansion: 1.1.11
 
+  minimatch@5.1.6:
+    dependencies:
+      brace-expansion: 2.0.1
+
+  minimatch@9.0.5:
+    dependencies:
+      brace-expansion: 2.0.1
+
   minipass@7.1.2: {}
 
+  minizlib@3.0.2:
+    dependencies:
+      minipass: 7.1.2
+
+  mkdirp@3.0.1: {}
+
   ms@2.1.3: {}
 
   mustache@4.2.0: {}
@@ -2838,6 +3221,8 @@ snapshots:
       semver: 5.7.2
       validate-npm-package-license: 3.0.4
 
+  normalize-path@3.0.0: {}
+
   npm-run-all@4.1.5:
     dependencies:
       ansi-styles: 3.2.1
@@ -2850,6 +3235,10 @@ snapshots:
       shell-quote: 1.8.2
       string.prototype.padend: 3.1.6
 
+  nth-check@2.1.1:
+    dependencies:
+      boolbase: 1.0.0
+
   object-assign@4.1.1: {}
 
   object-inspect@1.13.3: {}
@@ -2921,6 +3310,11 @@ snapshots:
 
   path-parse@1.0.7: {}
 
+  path-scurry@1.11.1:
+    dependencies:
+      lru-cache: 10.4.3
+      minipass: 7.1.2
+
   path-scurry@2.0.0:
     dependencies:
       lru-cache: 11.0.2
@@ -2940,6 +3334,15 @@ snapshots:
 
   possible-typed-array-names@1.0.0: {}
 
+  pretty-error@4.0.0:
+    dependencies:
+      lodash: 4.17.21
+      renderkid: 3.0.0
+
+  process-nextick-args@2.0.1: {}
+
+  process@0.11.10: {}
+
   proxy-addr@2.0.7:
     dependencies:
       forwarded: 0.2.0
@@ -2964,6 +3367,28 @@ snapshots:
       normalize-package-data: 2.5.0
       path-type: 3.0.0
 
+  readable-stream@2.3.8:
+    dependencies:
+      core-util-is: 1.0.3
+      inherits: 2.0.4
+      isarray: 1.0.0
+      process-nextick-args: 2.0.1
+      safe-buffer: 5.1.2
+      string_decoder: 1.1.1
+      util-deprecate: 1.0.2
+
+  readable-stream@4.7.0:
+    dependencies:
+      abort-controller: 3.0.0
+      buffer: 6.0.3
+      events: 3.3.0
+      process: 0.11.10
+      string_decoder: 1.3.0
+
+  readdir-glob@1.1.3:
+    dependencies:
+      minimatch: 5.1.6
+
   reflect.getprototypeof@1.0.10:
     dependencies:
       call-bind: 1.0.8
@@ -2984,6 +3409,14 @@ snapshots:
       gopd: 1.2.0
       set-function-name: 2.0.2
 
+  renderkid@3.0.0:
+    dependencies:
+      css-select: 4.3.0
+      dom-converter: 0.2.0
+      htmlparser2: 6.1.0
+      lodash: 4.17.21
+      strip-ansi: 6.0.1
+
   resolve@1.22.10:
     dependencies:
       is-core-module: 2.16.1
@@ -3030,6 +3463,8 @@ snapshots:
       has-symbols: 1.1.0
       isarray: 2.0.5
 
+  safe-buffer@5.1.2: {}
+
   safe-buffer@5.2.1: {}
 
   safe-push-apply@1.0.0:
@@ -3168,6 +3603,13 @@ snapshots:
 
   statuses@2.0.1: {}
 
+  streamx@2.22.0:
+    dependencies:
+      fast-fifo: 1.3.2
+      text-decoder: 1.2.3
+    optionalDependencies:
+      bare-events: 2.5.4
+
   string-width@4.2.3:
     dependencies:
       emoji-regex: 8.0.0
@@ -3216,6 +3658,14 @@ snapshots:
       define-properties: 1.2.1
       es-object-atoms: 1.1.1
 
+  string_decoder@1.1.1:
+    dependencies:
+      safe-buffer: 5.1.2
+
+  string_decoder@1.3.0:
+    dependencies:
+      safe-buffer: 5.2.1
+
   strip-ansi@6.0.1:
     dependencies:
       ansi-regex: 5.0.1
@@ -3232,6 +3682,25 @@ snapshots:
 
   supports-preserve-symlinks-flag@1.0.0: {}
 
+  tar-stream@3.1.7:
+    dependencies:
+      b4a: 1.6.7
+      fast-fifo: 1.3.2
+      streamx: 2.22.0
+
+  tar@7.4.3:
+    dependencies:
+      '@isaacs/fs-minipass': 4.0.1
+      chownr: 3.0.0
+      minipass: 7.1.2
+      minizlib: 3.0.2
+      mkdirp: 3.0.1
+      yallist: 5.0.0
+
+  text-decoder@1.2.3:
+    dependencies:
+      b4a: 1.6.7
+
   tinycolor2@1.6.0: {}
 
   tinygradient@1.1.5:
@@ -3307,6 +3776,10 @@ snapshots:
 
   unpipe@1.0.0: {}
 
+  util-deprecate@1.0.2: {}
+
+  utila@0.4.0: {}
+
   uuid@11.1.0: {}
 
   validate-npm-package-license@3.0.4:
@@ -3402,10 +3875,18 @@ snapshots:
   ws@8.18.0:
     optional: true
 
+  yallist@5.0.0: {}
+
   yaml@2.7.1: {}
 
   yoctocolors-cjs@2.1.2: {}
 
+  zip-stream@6.0.1:
+    dependencies:
+      archiver-utils: 5.0.2
+      compress-commons: 6.0.2
+      readable-stream: 4.7.0
+
   zod-to-json-schema@3.24.5(zod@3.24.2):
     dependencies:
       zod: 3.24.2


Reviews:
user: login: wangshijun
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T02:26:37Z
user: login: li-yechao
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T02:46:07Z
user: login: li-yechao
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T03:03:07Z
user: login: li-yechao
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T03:03:21Z
user: login: li-yechao
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T04:46:03Z
user: login: wangshijun
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T05:34:06Z
user: login: wangshijun
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T06:21:52Z

Review comments:
diff_hunk: @@ -10,33 +14,53 @@ interface RunOptions extends OptionValues {
 export function createRunCommand(): Command {
   return new Command("run")
     .description("Run a chat loop with the specified agent")
-    .argument("[path]", "Path to the agents directory", ".")
+    .argument("[path]", "Path to the agents directory or URL to aigne project", ".")
     .option("--agent <agent>", "Name of the agent to use (defaults to the first agent found)")
     .action(async (path: string, options: RunOptions) => {
-      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
-
-      const engine = await ExecutionEngine.load({ path: absolutePath });
-
-      let agent: Agent | undefined;
-      if (options.agent) {
-        agent = engine.agents[options.agent];
-        if (!agent) {
-          console.error(`Agent "${options.agent}" not found.`);
-          console.log("Available agents:");
-          for (const agent of engine.agents) {
-            console.log(`- ${agent.name}`);
-          }
-          throw new Error(`Agent "${options.agent}" not found`);
-        }
-      } else {
-        agent = engine.agents[0];
-        if (!agent) throw new Error("No agents found in the specified path");
+      if (path.startsWith("http")) {
+        await downloadAndRunPackage(path, options);
+        return;
       }
 
-      const user = engine.call(agent);
+      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
 
-      await runChatLoopInTerminal(user, {});
+      await runEngine(absolutePath, options);
     })
     .showHelpAfterError(true)
     .showSuggestionAfterError(true);
 }
+
+async function runEngine(path: string, options: RunOptions) {
+  const engine = await ExecutionEngine.load({ path });
+
+  let agent: Agent | undefined;
+  if (options.agent) {
+    agent = engine.agents[options.agent];
+    if (!agent) {
+      console.error(`Agent "${options.agent}" not found.`);
+      console.log("Available agents:");
+      for (const agent of engine.agents) {
+        console.log(`- ${agent.name}`);
+      }
+      throw new Error(`Agent "${options.agent}" not found`);
+    }
+  } else {
+    agent = engine.agents[0];
+    if (!agent) throw new Error("No agents found in the specified path");
path: packages/cli/src/commands/run.ts
user: login: wangshijun
body: 这里建议把目录打印到错误里面
created_at: 2025-04-15T02:24:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,33 +14,53 @@ interface RunOptions extends OptionValues {
 export function createRunCommand(): Command {
   return new Command("run")
     .description("Run a chat loop with the specified agent")
-    .argument("[path]", "Path to the agents directory", ".")
+    .argument("[path]", "Path to the agents directory or URL to aigne project", ".")
     .option("--agent <agent>", "Name of the agent to use (defaults to the first agent found)")
     .action(async (path: string, options: RunOptions) => {
-      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
-
-      const engine = await ExecutionEngine.load({ path: absolutePath });
-
-      let agent: Agent | undefined;
-      if (options.agent) {
-        agent = engine.agents[options.agent];
-        if (!agent) {
-          console.error(`Agent "${options.agent}" not found.`);
-          console.log("Available agents:");
-          for (const agent of engine.agents) {
-            console.log(`- ${agent.name}`);
-          }
-          throw new Error(`Agent "${options.agent}" not found`);
-        }
-      } else {
-        agent = engine.agents[0];
-        if (!agent) throw new Error("No agents found in the specified path");
+      if (path.startsWith("http")) {
+        await downloadAndRunPackage(path, options);
+        return;
       }
 
-      const user = engine.call(agent);
+      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
 
-      await runChatLoopInTerminal(user, {});
+      await runEngine(absolutePath, options);
     })
     .showHelpAfterError(true)
     .showSuggestionAfterError(true);
 }
+
+async function runEngine(path: string, options: RunOptions) {
+  const engine = await ExecutionEngine.load({ path });
+
+  let agent: Agent | undefined;
+  if (options.agent) {
+    agent = engine.agents[options.agent];
+    if (!agent) {
+      console.error(`Agent "${options.agent}" not found.`);
path: packages/cli/src/commands/run.ts
user: login: wangshijun
body: 同样：Agent {name} not found in {path}
created_at: 2025-04-15T02:24:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,33 +14,53 @@ interface RunOptions extends OptionValues {
 export function createRunCommand(): Command {
   return new Command("run")
     .description("Run a chat loop with the specified agent")
-    .argument("[path]", "Path to the agents directory", ".")
+    .argument("[path]", "Path to the agents directory or URL to aigne project", ".")
     .option("--agent <agent>", "Name of the agent to use (defaults to the first agent found)")
     .action(async (path: string, options: RunOptions) => {
-      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
-
-      const engine = await ExecutionEngine.load({ path: absolutePath });
-
-      let agent: Agent | undefined;
-      if (options.agent) {
-        agent = engine.agents[options.agent];
-        if (!agent) {
-          console.error(`Agent "${options.agent}" not found.`);
-          console.log("Available agents:");
-          for (const agent of engine.agents) {
-            console.log(`- ${agent.name}`);
-          }
-          throw new Error(`Agent "${options.agent}" not found`);
-        }
-      } else {
-        agent = engine.agents[0];
-        if (!agent) throw new Error("No agents found in the specified path");
+      if (path.startsWith("http")) {
+        await downloadAndRunPackage(path, options);
+        return;
       }
 
-      const user = engine.call(agent);
+      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
 
-      await runChatLoopInTerminal(user, {});
+      await runEngine(absolutePath, options);
     })
     .showHelpAfterError(true)
     .showSuggestionAfterError(true);
 }
+
+async function runEngine(path: string, options: RunOptions) {
+  const engine = await ExecutionEngine.load({ path });
+
+  let agent: Agent | undefined;
+  if (options.agent) {
+    agent = engine.agents[options.agent];
+    if (!agent) {
+      console.error(`Agent "${options.agent}" not found.`);
+      console.log("Available agents:");
+      for (const agent of engine.agents) {
+        console.log(`- ${agent.name}`);
+      }
+      throw new Error(`Agent "${options.agent}" not found`);
+    }
+  } else {
+    agent = engine.agents[0];
+    if (!agent) throw new Error("No agents found in the specified path");
+  }
+
+  const user = engine.call(agent);
+
+  await runChatLoopInTerminal(user, {});
+}
+
+async function downloadAndRunPackage(url: string, options: RunOptions) {
+  const dir = join(tmpdir(), randomUUID());
+  try {
+    await mkdir(dir, { recursive: true });
+    await downloadAndExtract(url, dir);
+    await runEngine(dir, options);
+  } finally {
path: packages/cli/src/commands/run.ts
user: login: wangshijun
body: 这里不应该吞没错误，比如格式不对，没有权限等等，不然用的人会不明所以，建议的反响测试：

1. 不返回预期结构的 URL
2. 不存在的 URL，比如 404
3. 没权限的 URL，比如 https://www.arcblock.io/?file=/etc/passwd

created_at: 2025-04-15T02:26:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,33 +14,53 @@ interface RunOptions extends OptionValues {
 export function createRunCommand(): Command {
   return new Command("run")
     .description("Run a chat loop with the specified agent")
-    .argument("[path]", "Path to the agents directory", ".")
+    .argument("[path]", "Path to the agents directory or URL to aigne project", ".")
     .option("--agent <agent>", "Name of the agent to use (defaults to the first agent found)")
     .action(async (path: string, options: RunOptions) => {
-      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
-
-      const engine = await ExecutionEngine.load({ path: absolutePath });
-
-      let agent: Agent | undefined;
-      if (options.agent) {
-        agent = engine.agents[options.agent];
-        if (!agent) {
-          console.error(`Agent "${options.agent}" not found.`);
-          console.log("Available agents:");
-          for (const agent of engine.agents) {
-            console.log(`- ${agent.name}`);
-          }
-          throw new Error(`Agent "${options.agent}" not found`);
-        }
-      } else {
-        agent = engine.agents[0];
-        if (!agent) throw new Error("No agents found in the specified path");
+      if (path.startsWith("http")) {
+        await downloadAndRunPackage(path, options);
+        return;
       }
 
-      const user = engine.call(agent);
+      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
 
-      await runChatLoopInTerminal(user, {});
+      await runEngine(absolutePath, options);
     })
     .showHelpAfterError(true)
     .showSuggestionAfterError(true);
 }
+
+async function runEngine(path: string, options: RunOptions) {
+  const engine = await ExecutionEngine.load({ path });
+
+  let agent: Agent | undefined;
+  if (options.agent) {
+    agent = engine.agents[options.agent];
+    if (!agent) {
+      console.error(`Agent "${options.agent}" not found.`);
+      console.log("Available agents:");
+      for (const agent of engine.agents) {
+        console.log(`- ${agent.name}`);
+      }
+      throw new Error(`Agent "${options.agent}" not found`);
+    }
+  } else {
+    agent = engine.agents[0];
+    if (!agent) throw new Error("No agents found in the specified path");
+  }
+
+  const user = engine.call(agent);
+
+  await runChatLoopInTerminal(user, {});
+}
+
+async function downloadAndRunPackage(url: string, options: RunOptions) {
+  const dir = join(tmpdir(), randomUUID());
+  try {
+    await mkdir(dir, { recursive: true });
+    await downloadAndExtract(url, dir);
+    await runEngine(dir, options);
+  } finally {
path: packages/cli/src/commands/run.ts
user: login: li-yechao
body: 这里没有吞没错误，仅在 finally 中清理了资源。另外在 download 中优化了 error message
created_at: 2025-04-15T02:46:06Z
author_association: COLLABORATOR
diff_hunk: @@ -10,33 +14,53 @@ interface RunOptions extends OptionValues {
 export function createRunCommand(): Command {
   return new Command("run")
     .description("Run a chat loop with the specified agent")
-    .argument("[path]", "Path to the agents directory", ".")
+    .argument("[path]", "Path to the agents directory or URL to aigne project", ".")
     .option("--agent <agent>", "Name of the agent to use (defaults to the first agent found)")
     .action(async (path: string, options: RunOptions) => {
-      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
-
-      const engine = await ExecutionEngine.load({ path: absolutePath });
-
-      let agent: Agent | undefined;
-      if (options.agent) {
-        agent = engine.agents[options.agent];
-        if (!agent) {
-          console.error(`Agent "${options.agent}" not found.`);
-          console.log("Available agents:");
-          for (const agent of engine.agents) {
-            console.log(`- ${agent.name}`);
-          }
-          throw new Error(`Agent "${options.agent}" not found`);
-        }
-      } else {
-        agent = engine.agents[0];
-        if (!agent) throw new Error("No agents found in the specified path");
+      if (path.startsWith("http")) {
+        await downloadAndRunPackage(path, options);
+        return;
       }
 
-      const user = engine.call(agent);
+      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
 
-      await runChatLoopInTerminal(user, {});
+      await runEngine(absolutePath, options);
     })
     .showHelpAfterError(true)
     .showSuggestionAfterError(true);
 }
+
+async function runEngine(path: string, options: RunOptions) {
+  const engine = await ExecutionEngine.load({ path });
+
+  let agent: Agent | undefined;
+  if (options.agent) {
+    agent = engine.agents[options.agent];
+    if (!agent) {
+      console.error(`Agent "${options.agent}" not found.`);
path: packages/cli/src/commands/run.ts
user: login: li-yechao
body: fixed
created_at: 2025-04-15T03:03:05Z
author_association: COLLABORATOR
diff_hunk: @@ -10,33 +14,53 @@ interface RunOptions extends OptionValues {
 export function createRunCommand(): Command {
   return new Command("run")
     .description("Run a chat loop with the specified agent")
-    .argument("[path]", "Path to the agents directory", ".")
+    .argument("[path]", "Path to the agents directory or URL to aigne project", ".")
     .option("--agent <agent>", "Name of the agent to use (defaults to the first agent found)")
     .action(async (path: string, options: RunOptions) => {
-      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
-
-      const engine = await ExecutionEngine.load({ path: absolutePath });
-
-      let agent: Agent | undefined;
-      if (options.agent) {
-        agent = engine.agents[options.agent];
-        if (!agent) {
-          console.error(`Agent "${options.agent}" not found.`);
-          console.log("Available agents:");
-          for (const agent of engine.agents) {
-            console.log(`- ${agent.name}`);
-          }
-          throw new Error(`Agent "${options.agent}" not found`);
-        }
-      } else {
-        agent = engine.agents[0];
-        if (!agent) throw new Error("No agents found in the specified path");
+      if (path.startsWith("http")) {
+        await downloadAndRunPackage(path, options);
+        return;
       }
 
-      const user = engine.call(agent);
+      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
 
-      await runChatLoopInTerminal(user, {});
+      await runEngine(absolutePath, options);
     })
     .showHelpAfterError(true)
     .showSuggestionAfterError(true);
 }
+
+async function runEngine(path: string, options: RunOptions) {
+  const engine = await ExecutionEngine.load({ path });
+
+  let agent: Agent | undefined;
+  if (options.agent) {
+    agent = engine.agents[options.agent];
+    if (!agent) {
+      console.error(`Agent "${options.agent}" not found.`);
+      console.log("Available agents:");
+      for (const agent of engine.agents) {
+        console.log(`- ${agent.name}`);
+      }
+      throw new Error(`Agent "${options.agent}" not found`);
+    }
+  } else {
+    agent = engine.agents[0];
+    if (!agent) throw new Error("No agents found in the specified path");
path: packages/cli/src/commands/run.ts
user: login: li-yechao
body: fixed
created_at: 2025-04-15T03:03:19Z
author_association: COLLABORATOR
diff_hunk: @@ -10,33 +14,53 @@ interface RunOptions extends OptionValues {
 export function createRunCommand(): Command {
   return new Command("run")
     .description("Run a chat loop with the specified agent")
-    .argument("[path]", "Path to the agents directory", ".")
+    .argument("[path]", "Path to the agents directory or URL to aigne project", ".")
     .option("--agent <agent>", "Name of the agent to use (defaults to the first agent found)")
     .action(async (path: string, options: RunOptions) => {
-      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
-
-      const engine = await ExecutionEngine.load({ path: absolutePath });
-
-      let agent: Agent | undefined;
-      if (options.agent) {
-        agent = engine.agents[options.agent];
-        if (!agent) {
-          console.error(`Agent "${options.agent}" not found.`);
-          console.log("Available agents:");
-          for (const agent of engine.agents) {
-            console.log(`- ${agent.name}`);
-          }
-          throw new Error(`Agent "${options.agent}" not found`);
-        }
-      } else {
-        agent = engine.agents[0];
-        if (!agent) throw new Error("No agents found in the specified path");
+      if (path.startsWith("http")) {
+        await downloadAndRunPackage(path, options);
+        return;
       }
 
-      const user = engine.call(agent);
+      const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
 
-      await runChatLoopInTerminal(user, {});
+      await runEngine(absolutePath, options);
     })
     .showHelpAfterError(true)
     .showSuggestionAfterError(true);
 }
+
+async function runEngine(path: string, options: RunOptions) {
+  const engine = await ExecutionEngine.load({ path });
+
+  let agent: Agent | undefined;
+  if (options.agent) {
+    agent = engine.agents[options.agent];
+    if (!agent) {
+      console.error(`Agent "${options.agent}" not found.`);
+      console.log("Available agents:");
+      for (const agent of engine.agents) {
+        console.log(`- ${agent.name}`);
+      }
+      throw new Error(`Agent "${options.agent}" not found`);
+    }
+  } else {
+    agent = engine.agents[0];
+    if (!agent) throw new Error("No agents found in the specified path");
+  }
+
+  const user = engine.call(agent);
+
+  await runChatLoopInTerminal(user, {});
+}
+
+async function downloadAndRunPackage(url: string, options: RunOptions) {
+  const dir = join(tmpdir(), randomUUID());
+  try {
+    await mkdir(dir, { recursive: true });
+    await downloadAndExtract(url, dir);
+    await runEngine(dir, options);
+  } finally {
path: packages/cli/src/commands/run.ts
user: login: li-yechao
body: <img width="1652" alt="Screenshot 2025-04-15 at 11 25 41" src="https://github.com/user-attachments/assets/69adc1f8-60a3-4440-98b6-8d926face9b9" />

created_at: 2025-04-15T04:46:03Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-15T02:16:57Z
body: <img src="https://avatars.githubusercontent.com/u/143965177?s=48&v=4" alt="Image description" width="15" height="15"> AIGNE Framework

## Walkthrough

This change introduces remote agent execution capabilities to the CLI by implementing a secure download and extraction system for AIGNE packages. The enhancement allows running agents from both local files and remote URLs, with proper temporary directory management and cleanup. The implementation includes comprehensive testing infrastructure with mocked packages and network calls.

## Changes

| Files | Summary |
|-------|----------|
| `packages/cli/src/utils/download.ts`<br>`packages/cli/test/utils/download.test.ts` | Core download utility implementation with stream-based remote package fetching and extraction, accompanied by tests verifying download, extraction, and cleanup processes |
| `packages/cli/src/commands/run.ts`<br>`packages/cli/test/commands/run.test.ts` | Enhanced run command supporting remote URL-based agents with temporary directory management and package cleanup, validated through comprehensive tests |
| `packages/cli/test/_mocks_/mock-aigne-package.ts` | Test utility generating mock tar.gz archives with required YAML configurations for simulating valid AIGNE packages |
<!-- This is an auto-generated comment: raw summary by AIGNE Reviewer -->
<!--

I'll group the related changes together based on their functionality and dependencies. Here are the deduplicated and grouped changesets:

---
packages/cli/src/utils/download.ts, packages/cli/test/utils/download.test.ts: Summary:
Core download utility implementation and testing. Introduces `downloadAndExtract` function for fetching and extracting remote packages, along with comprehensive tests that verify the download, extraction, and cleanup processes. The implementation uses Node.js streams and tar extraction, while tests use Bun's framework with mocked fetch API.

---
packages/cli/src/commands/run.ts, packages/cli/test/commands/run.test.ts: Summary:
Extension of CLI's "run" command to support remote URL-based agents. The command now handles both local and remote agent execution paths, with added functionality to manage temporary directories and package cleanup. Tests verify the new remote package handling capabilities through mocked fetch API and validate the chat loop initialization.

---
packages/cli/test/_mocks_/mock-aigne-package.ts: Summary:
Test utility that creates mock AIGNE packages for testing. Implements `mockAIGNEPackage()` to generate tar.gz archives containing required YAML configurations, used by both the download and run command tests to simulate valid AIGNE packages.

The changes are grouped into three logical units:
1. Download utility and its tests
2. Run command enhancement and its tests
3. Shared mock package generator for testing

This grouping reflects the dependencies between the components and their related functionality.
-->
<!-- end of auto-generated comment: raw summary by AIGNE Reviewer -->
<!-- This is an auto-generated comment: short summary by AIGNE Reviewer -->
<!--

This PR introduces remote package support for the AIGNE CLI, allowing users to execute agents from remote URLs. The changes include:

1. A new `downloadAndExtract` utility that handles fetching and extracting remote packages using Node.js streams and tar extraction

2. Enhanced "run" command functionality that supports both local and remote agent execution paths, including temporary directory management and package cleanup

3. Test infrastructure with mock AIGNE package generation capabilities that create tar.gz archives containing required YAML configurations

The implementation spans across download utilities, command handling, and testing utilities, enabling the CLI to seamlessly work with both local and remote agent packages.
-->
<!-- end of auto-generated comment: short summary by AIGNE Reviewer -->


<!-- commit_ids_reviewed_start -->
<!-- 4c68c11f55a9a032fe4afe792955f9e29af82e2e -->
<!-- commit_ids_reviewed_end -->

<!-- This is an auto-generated comment: summarize by AIGNE Reviewer -->
author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-15T02:20:10Z
body: ## [Codecov](https://app.codecov.io/gh/AIGNE-io/aigne-framework/pull/60?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AIGNE-io) Report
Attention: Patch coverage is `99.07407%` with `1 line` in your changes missing coverage. Please review.
| [Files with missing lines](https://app.codecov.io/gh/AIGNE-io/aigne-framework/pull/60?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AIGNE-io) | Patch % | Lines |
|---|---|---|
| [packages/cli/test/\_mocks\_/mock-aigne-package.ts](https://app.codecov.io/gh/AIGNE-io/aigne-framework/pull/60?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AIGNE-io#diff-cGFja2FnZXMvY2xpL3Rlc3QvX21vY2tzXy9tb2NrLWFpZ25lLXBhY2thZ2UudHM=) | 96.29% | [1 Missing :warning: ](https://app.codecov.io/gh/AIGNE-io/aigne-framework/pull/60?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=AIGNE-io) |

:loudspeaker: Thoughts on this report? [Let us know!](https://github.com/codecov/feedback/issues/255)
author_association: NONE