@aigne/cli 1.0.0-8 → 1.1.0
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/CHANGELOG.md +9 -0
- package/dist/cli.d.ts +2 -0
- package/dist/commands/aigne.d.ts +2 -0
- package/dist/commands/aigne.js +6 -2
- package/dist/commands/create.d.ts +2 -0
- package/dist/commands/create.js +13 -13
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +3 -5
- package/dist/commands/serve.d.ts +2 -0
- package/dist/commands/serve.js +32 -0
- package/dist/commands/test.d.ts +2 -0
- package/dist/utils/ascii-logo.d.ts +1 -0
- package/dist/utils/ascii-logo.js +18 -0
- package/dist/utils/serve-mcp.d.ts +5 -0
- package/dist/utils/serve-mcp.js +71 -0
- package/package.json +34 -9
- package/templates/default/aigne.yaml +4 -0
- package/templates/default/filesystem.yaml +3 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.1.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.0.0...cli-v1.1.0) (2025-04-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add `serve` command for @aigne/cli ([#54](https://github.com/AIGNE-io/aigne-framework/issues/54)) ([1cca843](https://github.com/AIGNE-io/aigne-framework/commit/1cca843f1760abe832b6651108fa858130f47355))
|
|
9
|
+
* add agent library support ([#51](https://github.com/AIGNE-io/aigne-framework/issues/51)) ([1f0d34d](https://github.com/AIGNE-io/aigne-framework/commit/1f0d34ddda3154283a4bc958ddb9b68b4ac106b0))
|
package/dist/cli.d.ts
ADDED
package/dist/commands/aigne.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import
|
|
2
|
+
import pkg from "../../package.json" with { type: "json" };
|
|
3
|
+
import { asciiLogo } from "../utils/ascii-logo.js";
|
|
3
4
|
import { createCreateCommand } from "./create.js";
|
|
4
5
|
import { createRunCommand } from "./run.js";
|
|
6
|
+
import { createServeCommand } from "./serve.js";
|
|
5
7
|
import { createTestCommand } from "./test.js";
|
|
6
8
|
export function createAIGNECommand() {
|
|
9
|
+
console.log(asciiLogo);
|
|
7
10
|
return new Command()
|
|
8
11
|
.name("aigne")
|
|
9
12
|
.description("CLI for AIGNE framework")
|
|
10
|
-
.version(version)
|
|
13
|
+
.version(pkg.version)
|
|
11
14
|
.addCommand(createRunCommand())
|
|
12
15
|
.addCommand(createTestCommand())
|
|
13
16
|
.addCommand(createCreateCommand())
|
|
17
|
+
.addCommand(createServeCommand())
|
|
14
18
|
.showHelpAfterError(true)
|
|
15
19
|
.showSuggestionAfterError(true);
|
|
16
20
|
}
|
package/dist/commands/create.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
2
2
|
import { cp } from "node:fs/promises";
|
|
3
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
6
|
export function createCreateCommand() {
|
|
7
7
|
return new Command("create")
|
|
8
8
|
.description("Create a new aigne project with agent config files")
|
|
9
9
|
.argument("[path]", "Path to create the project directory", ".")
|
|
10
|
-
.action(async (
|
|
11
|
-
let
|
|
12
|
-
if (
|
|
10
|
+
.action(async (_path) => {
|
|
11
|
+
let path = _path;
|
|
12
|
+
if (path === ".") {
|
|
13
13
|
const answers = await inquirer.prompt([
|
|
14
14
|
{
|
|
15
15
|
type: "input",
|
|
16
16
|
name: "projectName",
|
|
17
17
|
message: "Project name:",
|
|
18
|
-
default:
|
|
18
|
+
default: _path !== "." ? _path : "my-aigne-project",
|
|
19
19
|
validate: (input) => {
|
|
20
20
|
if (input.trim() === "")
|
|
21
21
|
return "Project name cannot be empty.";
|
|
@@ -23,16 +23,16 @@ export function createCreateCommand() {
|
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
25
|
]);
|
|
26
|
-
|
|
26
|
+
path = answers.projectName;
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
const isPathNotEmpty = existsSync(
|
|
28
|
+
path = isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
29
|
+
const isPathNotEmpty = existsSync(path) && readdirSync(path).length > 0;
|
|
30
30
|
if (isPathNotEmpty) {
|
|
31
31
|
const answers = await inquirer.prompt([
|
|
32
32
|
{
|
|
33
33
|
type: "confirm",
|
|
34
34
|
name: "overwrite",
|
|
35
|
-
message: `The directory "${
|
|
35
|
+
message: `The directory "${path}" is not empty. Do you want to remove its contents?`,
|
|
36
36
|
default: false,
|
|
37
37
|
},
|
|
38
38
|
]);
|
|
@@ -51,18 +51,18 @@ export function createCreateCommand() {
|
|
|
51
51
|
default: "default",
|
|
52
52
|
},
|
|
53
53
|
]);
|
|
54
|
-
mkdirSync(
|
|
55
|
-
const templatePath = join(
|
|
54
|
+
mkdirSync(path, { recursive: true });
|
|
55
|
+
const templatePath = join(import.meta.dirname, "../../templates", template);
|
|
56
56
|
if (!existsSync(templatePath))
|
|
57
57
|
throw new Error(`Template "${template}" not found.`);
|
|
58
58
|
const files = readdirSync(templatePath);
|
|
59
59
|
for (const file of files) {
|
|
60
60
|
const source = join(templatePath, file);
|
|
61
|
-
const destination = join(
|
|
61
|
+
const destination = join(path, file);
|
|
62
62
|
await cp(source, destination, { recursive: true, force: true });
|
|
63
63
|
}
|
|
64
64
|
console.log("\n✅ Aigne project created successfully!");
|
|
65
|
-
console.log(`\nTo use your new agent, run:\n cd ${path} &&
|
|
65
|
+
console.log(`\nTo use your new agent, run:\n cd ${relative(process.cwd(), path)} && aigne run`);
|
|
66
66
|
})
|
|
67
67
|
.showHelpAfterError(true)
|
|
68
68
|
.showSuggestionAfterError(true);
|
package/dist/commands/run.js
CHANGED
|
@@ -19,15 +19,13 @@ export function createRunCommand() {
|
|
|
19
19
|
for (const agent of engine.agents) {
|
|
20
20
|
console.log(`- ${agent.name}`);
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
throw new Error(`Agent "${options.agent}" not found`);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
26
26
|
agent = engine.agents[0];
|
|
27
|
-
if (!agent)
|
|
28
|
-
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
27
|
+
if (!agent)
|
|
28
|
+
throw new Error("No agents found in the specified path");
|
|
31
29
|
}
|
|
32
30
|
const user = engine.call(agent);
|
|
33
31
|
await runChatLoopInTerminal(user, {});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isAbsolute, resolve } from "node:path";
|
|
2
|
+
import { ExecutionEngine } from "@aigne/core";
|
|
3
|
+
import { tryOrThrow } from "@aigne/core/utils/type-utils.js";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { serveMCPServer } from "../utils/serve-mcp.js";
|
|
6
|
+
const DEFAULT_PORT = () => tryOrThrow(() => {
|
|
7
|
+
const { PORT } = process.env;
|
|
8
|
+
if (!PORT)
|
|
9
|
+
return 3000;
|
|
10
|
+
const port = Number.parseInt(PORT);
|
|
11
|
+
if (!port || !Number.isInteger(port))
|
|
12
|
+
throw new Error(`Invalid PORT: ${PORT}`);
|
|
13
|
+
return port;
|
|
14
|
+
}, (error) => new Error(`parse PORT error ${error.message}`));
|
|
15
|
+
export function createServeCommand() {
|
|
16
|
+
return new Command("serve")
|
|
17
|
+
.description("Serve the agents in the specified directory as a MCP server")
|
|
18
|
+
.argument("[path]", "Path to the agents directory", ".")
|
|
19
|
+
.option("--mcp", "Serve the agents as a MCP server")
|
|
20
|
+
.option("--port <port>", "Port to run the MCP server on", (s) => Number.parseInt(s))
|
|
21
|
+
.action(async (path, options) => {
|
|
22
|
+
const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
23
|
+
const port = options.port || DEFAULT_PORT();
|
|
24
|
+
const engine = await ExecutionEngine.load({ path: absolutePath });
|
|
25
|
+
if (options.mcp)
|
|
26
|
+
await serveMCPServer({ engine, port });
|
|
27
|
+
else
|
|
28
|
+
throw new Error("Default server is not supported yet. Please use --mcp option");
|
|
29
|
+
})
|
|
30
|
+
.showHelpAfterError(true)
|
|
31
|
+
.showSuggestionAfterError(true);
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const asciiLogo: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import gradient from "gradient-string";
|
|
3
|
+
import pkg from "../../package.json" with { type: "json" };
|
|
4
|
+
const modernGradient = gradient(["#4facfe", "#7367f0", "#f86aad"]);
|
|
5
|
+
const logo = `
|
|
6
|
+
_ ___ ____ _ _ _____
|
|
7
|
+
/ \\ |_ _/ ___| \\ | | ____|
|
|
8
|
+
/ _ \\ | | | _| \\| | _|
|
|
9
|
+
/ ___ \\ | | |_| | |\\ | |___
|
|
10
|
+
/_/ \\_\\___\\____|_| \\_|_____|
|
|
11
|
+
`;
|
|
12
|
+
const frameworkInfo = `v${pkg.version}`;
|
|
13
|
+
const logoLines = logo.split("\n");
|
|
14
|
+
const maxLength = Math.max(...logoLines.filter((line) => line.trim()).map((line) => line.length));
|
|
15
|
+
const versionText = frameworkInfo;
|
|
16
|
+
const padding = Math.floor((maxLength - versionText.length) / 2);
|
|
17
|
+
const centeredVersion = " ".repeat(padding) + versionText;
|
|
18
|
+
export const asciiLogo = `${modernGradient(logo)}\n${chalk.cyan(centeredVersion)}\n\n`;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getMessage } from "@aigne/core";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
4
|
+
import express from "express";
|
|
5
|
+
import { ZodObject } from "zod";
|
|
6
|
+
export async function serveMCPServer({ engine, port }) {
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: engine.name || "aigne-mcp-server",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
}, {
|
|
11
|
+
capabilities: { tools: {} },
|
|
12
|
+
instructions: engine.description,
|
|
13
|
+
});
|
|
14
|
+
for (const agent of engine.agents) {
|
|
15
|
+
const schema = agent.inputSchema;
|
|
16
|
+
if (!(schema instanceof ZodObject))
|
|
17
|
+
throw new Error("Agent input schema must be a ZodObject");
|
|
18
|
+
server.tool(agent.name, agent.description || "", schema.shape, async (input) => {
|
|
19
|
+
const result = await engine.call(agent, input);
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
type: "text",
|
|
24
|
+
text: getMessage(result) || JSON.stringify(result),
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const app = express();
|
|
31
|
+
const transports = {};
|
|
32
|
+
app.get("/sse", async (req, res) => {
|
|
33
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
34
|
+
transports[transport.sessionId] = transport;
|
|
35
|
+
req.on("close", () => {
|
|
36
|
+
delete transports[transport.sessionId];
|
|
37
|
+
});
|
|
38
|
+
await server.connect(transport);
|
|
39
|
+
});
|
|
40
|
+
app.post("/messages", async (req, res) => {
|
|
41
|
+
const sessionId = req.query.sessionId;
|
|
42
|
+
const transport = transports[sessionId];
|
|
43
|
+
if (transport) {
|
|
44
|
+
await transport.handlePostMessage(req, res);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
throw new HttpError(400, "No transport found for sessionId");
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
app.use(((error, _req, res, _next) => {
|
|
51
|
+
console.error("handle route error", { error });
|
|
52
|
+
res
|
|
53
|
+
.status(error instanceof HttpError ? error.status : 500)
|
|
54
|
+
.json({ error: { message: error.message } });
|
|
55
|
+
}));
|
|
56
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
57
|
+
const httpServer = app.listen(port, (error) => {
|
|
58
|
+
if (error)
|
|
59
|
+
reject(error);
|
|
60
|
+
resolve();
|
|
61
|
+
});
|
|
62
|
+
await promise;
|
|
63
|
+
return httpServer;
|
|
64
|
+
}
|
|
65
|
+
class HttpError extends Error {
|
|
66
|
+
status;
|
|
67
|
+
constructor(status, message) {
|
|
68
|
+
super(message);
|
|
69
|
+
this.status = status;
|
|
70
|
+
}
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "cli for AIGNE framework",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -22,24 +22,49 @@
|
|
|
22
22
|
"bin": {
|
|
23
23
|
"aigne": "dist/cli.js"
|
|
24
24
|
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
"./*": "./dist/*"
|
|
28
|
+
},
|
|
29
|
+
"typesVersions": {
|
|
30
|
+
"*": {
|
|
31
|
+
"*": [
|
|
32
|
+
"./dist/*"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
25
36
|
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
38
|
+
"chalk": "^5.4.1",
|
|
26
39
|
"commander": "^13.1.0",
|
|
40
|
+
"express": "^5.1.0",
|
|
41
|
+
"gradient-string": "^3.0.0",
|
|
27
42
|
"inquirer": "^12.5.0",
|
|
28
|
-
"openai": "^4.
|
|
29
|
-
"
|
|
43
|
+
"openai": "^4.91.1",
|
|
44
|
+
"zod": "^3.24.2",
|
|
45
|
+
"@aigne/core": "^1.6.0"
|
|
30
46
|
},
|
|
31
47
|
"devDependencies": {
|
|
32
|
-
"@types/bun": "^1.2.
|
|
33
|
-
"@types/
|
|
48
|
+
"@types/bun": "^1.2.8",
|
|
49
|
+
"@types/express": "^5.0.1",
|
|
50
|
+
"@types/gradient-string": "^1.1.6",
|
|
51
|
+
"@types/node": "^22.14.0",
|
|
52
|
+
"detect-port": "^2.1.0",
|
|
34
53
|
"npm-run-all": "^4.1.5",
|
|
35
54
|
"rimraf": "^6.0.1",
|
|
36
|
-
"typescript": "^5.8.2"
|
|
55
|
+
"typescript": "^5.8.2",
|
|
56
|
+
"ufo": "^1.6.0",
|
|
57
|
+
"@aigne/test-utils": "^1.0.0"
|
|
37
58
|
},
|
|
38
59
|
"scripts": {
|
|
39
60
|
"lint": "tsc --noEmit",
|
|
40
61
|
"build": "tsc --build tsconfig.build.json",
|
|
41
|
-
"clean": "rimraf dist coverage",
|
|
42
|
-
"test": "
|
|
43
|
-
"test:coverage": "
|
|
62
|
+
"clean": "rimraf dist test/coverage templates/coverage",
|
|
63
|
+
"test": "run-s test:src test:templates",
|
|
64
|
+
"test:coverage": "run-s test:src:coverage test:templates:coverage",
|
|
65
|
+
"test:src": "bun --cwd test test",
|
|
66
|
+
"test:src:coverage": "bun --cwd test test --coverage --coverage-reporter=lcov --coverage-reporter=text",
|
|
67
|
+
"test:templates": "cd templates && node --test",
|
|
68
|
+
"test:templates:coverage": "cd templates && mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test-reporter=spec --test-reporter-destination=stdout"
|
|
44
69
|
}
|
|
45
70
|
}
|