@aigne/cli 1.37.0 → 1.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/commands/app.js +15 -10
- package/dist/commands/hub.js +3 -1
- package/dist/commands/run.js +1 -3
- package/dist/commands/serve-mcp.js +1 -1
- package/dist/commands/test.js +1 -1
- package/dist/utils/aigne-hub/constants.d.ts +6 -0
- package/dist/utils/aigne-hub/constants.js +12 -0
- package/dist/utils/aigne-hub/credential.d.ts +23 -0
- package/dist/utils/aigne-hub/credential.js +206 -0
- package/dist/utils/aigne-hub/crypto.d.ts +4 -0
- package/dist/utils/aigne-hub/crypto.js +9 -0
- package/dist/utils/aigne-hub/model.d.ts +12 -0
- package/dist/utils/aigne-hub/model.js +75 -0
- package/dist/utils/aigne-hub/type.d.ts +38 -0
- package/dist/utils/aigne-hub/type.js +1 -0
- package/dist/utils/load-aigne.d.ts +3 -8
- package/dist/utils/load-aigne.js +17 -51
- package/dist/utils/run-with-aigne.js +5 -9
- package/package.json +11 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.38.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.38.0...cli-v1.38.1) (2025-08-21)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **cli:** force upgrade app while load app error ([#390](https://github.com/AIGNE-io/aigne-framework/issues/390)) ([fa5e427](https://github.com/AIGNE-io/aigne-framework/commit/fa5e427eb29157c3ebcd9c9bf8c5c6b31efad4ae))
|
|
9
|
+
|
|
10
|
+
## [1.38.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.37.1...cli-v1.38.0) (2025-08-20)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add ImageModel/ImageAgent support ([#383](https://github.com/AIGNE-io/aigne-framework/issues/383)) ([96a2093](https://github.com/AIGNE-io/aigne-framework/commit/96a209368d91d98f47db6de1e404640368a86fa8))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Dependencies
|
|
19
|
+
|
|
20
|
+
* The following workspace dependencies were updated
|
|
21
|
+
* dependencies
|
|
22
|
+
* @aigne/agent-library bumped to 1.21.24
|
|
23
|
+
* @aigne/agentic-memory bumped to 1.0.24
|
|
24
|
+
* @aigne/aigne-hub bumped to 0.6.6
|
|
25
|
+
* @aigne/core bumped to 1.53.0
|
|
26
|
+
* @aigne/default-memory bumped to 1.1.6
|
|
27
|
+
* @aigne/openai bumped to 0.12.0
|
|
28
|
+
|
|
29
|
+
## [1.37.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.37.0...cli-v1.37.1) (2025-08-20)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Dependencies
|
|
33
|
+
|
|
34
|
+
* The following workspace dependencies were updated
|
|
35
|
+
* dependencies
|
|
36
|
+
* @aigne/agent-library bumped to 1.21.23
|
|
37
|
+
* @aigne/agentic-memory bumped to 1.0.23
|
|
38
|
+
* @aigne/aigne-hub bumped to 0.6.5
|
|
39
|
+
* @aigne/core bumped to 1.52.0
|
|
40
|
+
* @aigne/default-memory bumped to 1.1.5
|
|
41
|
+
* @aigne/observability-api bumped to 0.9.1
|
|
42
|
+
* @aigne/openai bumped to 0.11.5
|
|
43
|
+
|
|
3
44
|
## [1.37.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.36.4...cli-v1.37.0) (2025-08-18)
|
|
4
45
|
|
|
5
46
|
|
package/dist/commands/app.js
CHANGED
|
@@ -3,7 +3,6 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import { loadModel } from "@aigne/aigne-hub";
|
|
7
6
|
import { AIGNE } from "@aigne/core";
|
|
8
7
|
import { Listr, PRESET_TIMER } from "@aigne/listr2";
|
|
9
8
|
import { joinURL } from "ufo";
|
|
@@ -95,7 +94,7 @@ const agentCommandModule = ({ dir, agent, }) => {
|
|
|
95
94
|
export async function invokeCLIAgentFromDir(options) {
|
|
96
95
|
const aigne = await loadAIGNE({
|
|
97
96
|
path: options.dir,
|
|
98
|
-
|
|
97
|
+
modelOptions: { model: options.input.model },
|
|
99
98
|
});
|
|
100
99
|
try {
|
|
101
100
|
const agent = aigne.cli.agents[options.agent];
|
|
@@ -110,14 +109,20 @@ export async function invokeCLIAgentFromDir(options) {
|
|
|
110
109
|
export async function loadApplication({ name, dir, forceUpgrade = false, }) {
|
|
111
110
|
name = `@aigne/${name}`;
|
|
112
111
|
dir ??= join(homedir(), ".aigne", "registry.npmjs.org", name);
|
|
113
|
-
|
|
112
|
+
let check = forceUpgrade ? undefined : await isInstallationAvailable(dir);
|
|
114
113
|
if (check?.available) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
const aigne = await AIGNE.load(dir).catch((error) => {
|
|
115
|
+
console.warn(`Failed to load ${name}, trying to reinstall:`, error.message);
|
|
116
|
+
});
|
|
117
|
+
if (aigne) {
|
|
118
|
+
return {
|
|
119
|
+
aigne,
|
|
120
|
+
dir,
|
|
121
|
+
version: check.version,
|
|
122
|
+
isCache: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
check = undefined;
|
|
121
126
|
}
|
|
122
127
|
const result = await new Listr([
|
|
123
128
|
{
|
|
@@ -156,7 +161,7 @@ export async function loadApplication({ name, dir, forceUpgrade = false, }) {
|
|
|
156
161
|
},
|
|
157
162
|
}).run();
|
|
158
163
|
return {
|
|
159
|
-
aigne: await AIGNE.load(dir
|
|
164
|
+
aigne: await AIGNE.load(dir),
|
|
160
165
|
dir,
|
|
161
166
|
version: result.version,
|
|
162
167
|
};
|
package/dist/commands/hub.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
3
|
+
import { AIGNE_HUB_URL } from "@aigne/aigne-hub";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import Table from "cli-table3";
|
|
6
6
|
import inquirer from "inquirer";
|
|
7
7
|
import { parse, stringify } from "yaml";
|
|
8
|
+
import { AIGNE_ENV_FILE, isTest } from "../utils/aigne-hub/constants.js";
|
|
9
|
+
import { connectToAIGNEHub } from "../utils/aigne-hub/credential.js";
|
|
8
10
|
import { getUserInfo } from "../utils/aigne-hub-user.js";
|
|
9
11
|
import { getUrlOrigin } from "../utils/get-url-origin.js";
|
|
10
12
|
const formatNumber = (balance) => {
|
package/dist/commands/run.js
CHANGED
|
@@ -78,12 +78,10 @@ export function createRunCommand({ aigneFilePath, } = {}) {
|
|
|
78
78
|
}
|
|
79
79
|
const aigne = await loadAIGNE({
|
|
80
80
|
path: dir,
|
|
81
|
-
|
|
81
|
+
modelOptions: {
|
|
82
82
|
...options,
|
|
83
83
|
model: options.model || process.env.MODEL,
|
|
84
84
|
aigneHubUrl: options?.aigneHubUrl,
|
|
85
|
-
},
|
|
86
|
-
actionOptions: {
|
|
87
85
|
inquirerPromptFn: (prompt) => {
|
|
88
86
|
if (prompt.type === "input") {
|
|
89
87
|
return task
|
|
@@ -53,7 +53,7 @@ export async function serveMCPServerFromDir(options) {
|
|
|
53
53
|
const port = options.port || DEFAULT_PORT();
|
|
54
54
|
const aigne = await loadAIGNE({
|
|
55
55
|
path: options.dir,
|
|
56
|
-
|
|
56
|
+
modelOptions: { aigneHubUrl: options.aigneHubUrl },
|
|
57
57
|
});
|
|
58
58
|
await serveMCPServer({
|
|
59
59
|
aigne,
|
package/dist/commands/test.js
CHANGED
|
@@ -24,7 +24,7 @@ export function createTestCommand({ aigneFilePath, } = {}) {
|
|
|
24
24
|
const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
25
25
|
const aigne = await loadAIGNE({
|
|
26
26
|
path: absolutePath,
|
|
27
|
-
|
|
27
|
+
modelOptions: { aigneHubUrl: options?.aigneHubUrl },
|
|
28
28
|
});
|
|
29
29
|
assert(aigne.rootDir);
|
|
30
30
|
spawnSync("node", ["--test"], { cwd: aigne.rootDir, stdio: "inherit" });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
|
|
2
|
+
export declare const ACCESS_KEY_SESSION_API = "/api/access-key/session";
|
|
3
|
+
export declare const AIGNE_HUB_PROVIDER = "aignehub";
|
|
4
|
+
export declare const DEFAULT_AIGNE_HUB_PROVIDER_MODEL = "aignehub:openai/gpt-5-mini";
|
|
5
|
+
export declare const isTest: string | boolean;
|
|
6
|
+
export declare const AIGNE_ENV_FILE: string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const ACCESS_KEY_PREFIX = "/api/access-key";
|
|
4
|
+
export const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
|
|
5
|
+
export const ACCESS_KEY_SESSION_API = `${ACCESS_KEY_PREFIX}/session`;
|
|
6
|
+
const DEFAULT_AIGNE_HUB_MODEL = "openai/gpt-5-mini";
|
|
7
|
+
export const AIGNE_HUB_PROVIDER = "aignehub";
|
|
8
|
+
export const DEFAULT_AIGNE_HUB_PROVIDER_MODEL = `${AIGNE_HUB_PROVIDER}:${DEFAULT_AIGNE_HUB_MODEL}`;
|
|
9
|
+
export const isTest = process.env.CI || process.env.NODE_ENV === "test";
|
|
10
|
+
const TEST_AIGNE_ENV_FILE = join(homedir(), ".aigne", "test-aigne-hub-connected.yaml");
|
|
11
|
+
const PROD_AIGNE_ENV_FILE = join(homedir(), ".aigne", "aigne-hub-connected.yaml");
|
|
12
|
+
export const AIGNE_ENV_FILE = isTest ? TEST_AIGNE_ENV_FILE : PROD_AIGNE_ENV_FILE;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CreateConnectOptions, FetchResult, LoadCredentialOptions } from "./type.js";
|
|
2
|
+
export declare const fetchConfigs: ({ connectUrl, sessionId, fetchInterval, fetchTimeout, }: {
|
|
3
|
+
connectUrl: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
fetchInterval: number;
|
|
6
|
+
fetchTimeout: number;
|
|
7
|
+
}) => Promise<any>;
|
|
8
|
+
export declare function createConnect({ connectUrl, openPage, fetchInterval, retry, source, connectAction, wrapSpinner, closeOnSuccess, intervalFetchConfig, appName, appLogo, }: CreateConnectOptions): Promise<FetchResult>;
|
|
9
|
+
export declare function connectToAIGNEHub(url: string): Promise<{
|
|
10
|
+
apiKey: string;
|
|
11
|
+
url: string;
|
|
12
|
+
} | {
|
|
13
|
+
apiKey: undefined;
|
|
14
|
+
url: undefined;
|
|
15
|
+
}>;
|
|
16
|
+
export declare const checkConnectionStatus: (host: string) => Promise<{
|
|
17
|
+
apiKey: any;
|
|
18
|
+
url: any;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function loadAIGNEHubCredential(options?: LoadCredentialOptions): Promise<{
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
url?: string;
|
|
23
|
+
} | undefined>;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { AIGNE_HUB_BLOCKLET_DID, AIGNE_HUB_URL, getAIGNEHubMountPoint } from "@aigne/aigne-hub";
|
|
6
|
+
import { logger } from "@aigne/core/utils/logger.js";
|
|
7
|
+
import inquirer from "inquirer";
|
|
8
|
+
import open from "open";
|
|
9
|
+
import pWaitFor from "p-wait-for";
|
|
10
|
+
import { joinURL, withQuery } from "ufo";
|
|
11
|
+
import { parse, stringify } from "yaml";
|
|
12
|
+
import { ACCESS_KEY_SESSION_API, AIGNE_ENV_FILE, isTest, WELLKNOWN_SERVICE_PATH_PREFIX, } from "./constants.js";
|
|
13
|
+
import { decrypt, encodeEncryptionKey } from "./crypto.js";
|
|
14
|
+
const request = async (config) => {
|
|
15
|
+
const headers = {};
|
|
16
|
+
if (config.requestCount !== undefined) {
|
|
17
|
+
headers["X-Request-Count"] = config.requestCount.toString();
|
|
18
|
+
}
|
|
19
|
+
const response = await fetch(config.url, { method: config.method || "GET", headers });
|
|
20
|
+
if (!response.ok)
|
|
21
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
22
|
+
const data = await response.json();
|
|
23
|
+
return { data };
|
|
24
|
+
};
|
|
25
|
+
export const fetchConfigs = async ({ connectUrl, sessionId, fetchInterval, fetchTimeout, }) => {
|
|
26
|
+
const sessionURL = withQuery(joinURL(connectUrl, ACCESS_KEY_SESSION_API), { sid: sessionId });
|
|
27
|
+
let requestCount = 0;
|
|
28
|
+
const condition = async () => {
|
|
29
|
+
const { data: session } = await request({ url: sessionURL, requestCount });
|
|
30
|
+
requestCount++;
|
|
31
|
+
return Boolean(session.accessKeyId && session.accessKeySecret);
|
|
32
|
+
};
|
|
33
|
+
await pWaitFor(condition, { interval: fetchInterval, timeout: fetchTimeout });
|
|
34
|
+
const { data: session } = await request({ url: sessionURL, requestCount });
|
|
35
|
+
await request({ url: sessionURL, method: "DELETE" });
|
|
36
|
+
return {
|
|
37
|
+
...session,
|
|
38
|
+
accessKeyId: session.accessKeyId,
|
|
39
|
+
accessKeySecret: decrypt(session.accessKeySecret, session.accessKeyId, session.challenge),
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
function baseWrapSpinner(_, waiting) {
|
|
43
|
+
return Promise.resolve(waiting());
|
|
44
|
+
}
|
|
45
|
+
export async function createConnect({ connectUrl, openPage, fetchInterval = 3 * 1000, retry = 1500, source = "Blocklet CLI", connectAction = "connect-cli", wrapSpinner = baseWrapSpinner, closeOnSuccess, intervalFetchConfig, appName = "AIGNE CLI", appLogo = "https://www.aigne.io/favicon.ico?imageFilter=resize&w=32", }) {
|
|
46
|
+
const startSessionURL = joinURL(connectUrl, ACCESS_KEY_SESSION_API);
|
|
47
|
+
const { data: session } = await request({ url: startSessionURL, method: "POST" });
|
|
48
|
+
const token = session.id;
|
|
49
|
+
const pageUrl = withQuery(joinURL(connectUrl, connectAction), {
|
|
50
|
+
__token__: encodeEncryptionKey(token),
|
|
51
|
+
source,
|
|
52
|
+
closeOnSuccess,
|
|
53
|
+
cli: true,
|
|
54
|
+
appName: ` ${appName}`,
|
|
55
|
+
appLogo,
|
|
56
|
+
});
|
|
57
|
+
openPage?.(pageUrl);
|
|
58
|
+
return await wrapSpinner(`Waiting for connection: ${connectUrl}`, async () => {
|
|
59
|
+
const checkAuthorizeStatus = intervalFetchConfig ?? fetchConfigs;
|
|
60
|
+
const authorizeStatus = await checkAuthorizeStatus({
|
|
61
|
+
connectUrl,
|
|
62
|
+
sessionId: token,
|
|
63
|
+
fetchTimeout: retry * fetchInterval,
|
|
64
|
+
fetchInterval: retry,
|
|
65
|
+
});
|
|
66
|
+
return authorizeStatus;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export async function connectToAIGNEHub(url) {
|
|
70
|
+
const { origin, host } = new URL(url);
|
|
71
|
+
const connectUrl = joinURL(origin, WELLKNOWN_SERVICE_PATH_PREFIX);
|
|
72
|
+
const apiUrl = await getAIGNEHubMountPoint(url, AIGNE_HUB_BLOCKLET_DID);
|
|
73
|
+
try {
|
|
74
|
+
const openFn = isTest ? () => { } : open;
|
|
75
|
+
const result = await createConnect({
|
|
76
|
+
connectUrl: connectUrl,
|
|
77
|
+
connectAction: "gen-simple-access-key",
|
|
78
|
+
source: `@aigne/cli connect to AIGNE hub`,
|
|
79
|
+
closeOnSuccess: true,
|
|
80
|
+
openPage: (pageUrl) => openFn(pageUrl),
|
|
81
|
+
});
|
|
82
|
+
const accessKeyOptions = {
|
|
83
|
+
apiKey: result.accessKeySecret,
|
|
84
|
+
url: apiUrl,
|
|
85
|
+
};
|
|
86
|
+
// After redirection, write the AIGNE Hub access token
|
|
87
|
+
const aigneDir = join(homedir(), ".aigne");
|
|
88
|
+
if (!existsSync(aigneDir)) {
|
|
89
|
+
mkdirSync(aigneDir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
92
|
+
await writeFile(AIGNE_ENV_FILE, stringify({
|
|
93
|
+
...envs,
|
|
94
|
+
[host]: {
|
|
95
|
+
AIGNE_HUB_API_KEY: accessKeyOptions.apiKey,
|
|
96
|
+
AIGNE_HUB_API_URL: accessKeyOptions.url,
|
|
97
|
+
},
|
|
98
|
+
default: {
|
|
99
|
+
AIGNE_HUB_API_URL: accessKeyOptions.url,
|
|
100
|
+
},
|
|
101
|
+
}));
|
|
102
|
+
return accessKeyOptions;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
logger.error("Failed to connect to AIGNE Hub", error.message);
|
|
106
|
+
return { apiKey: undefined, url: undefined };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export const checkConnectionStatus = async (host) => {
|
|
110
|
+
// aigne-hub access token
|
|
111
|
+
if (!existsSync(AIGNE_ENV_FILE)) {
|
|
112
|
+
throw new Error("AIGNE_HUB_API_KEY file not found, need to login first");
|
|
113
|
+
}
|
|
114
|
+
const data = await readFile(AIGNE_ENV_FILE, "utf8");
|
|
115
|
+
if (!data.includes("AIGNE_HUB_API_KEY")) {
|
|
116
|
+
throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
|
|
117
|
+
}
|
|
118
|
+
const envs = parse(data);
|
|
119
|
+
if (!envs[host]) {
|
|
120
|
+
throw new Error("AIGNE_HUB_API_KEY host not found, need to login first");
|
|
121
|
+
}
|
|
122
|
+
const env = envs[host];
|
|
123
|
+
if (!env.AIGNE_HUB_API_KEY) {
|
|
124
|
+
throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
apiKey: env.AIGNE_HUB_API_KEY,
|
|
128
|
+
url: env.AIGNE_HUB_API_URL,
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
export async function loadAIGNEHubCredential(options) {
|
|
132
|
+
const isBlocklet = process.env.BLOCKLET_AIGNE_API_URL && process.env.BLOCKLET_AIGNE_API_PROVIDER;
|
|
133
|
+
if (isBlocklet)
|
|
134
|
+
return undefined;
|
|
135
|
+
const aigneDir = join(homedir(), ".aigne");
|
|
136
|
+
if (!existsSync(aigneDir)) {
|
|
137
|
+
mkdirSync(aigneDir, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
140
|
+
const inquirerPrompt = (options?.inquirerPromptFn ?? inquirer.prompt);
|
|
141
|
+
const configUrl = options?.aigneHubUrl || process.env.AIGNE_HUB_API_URL;
|
|
142
|
+
const url = configUrl || envs?.default?.AIGNE_HUB_API_URL || AIGNE_HUB_URL;
|
|
143
|
+
const connectUrl = joinURL(new URL(url).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
|
|
144
|
+
const { host } = new URL(url);
|
|
145
|
+
let credential = {};
|
|
146
|
+
try {
|
|
147
|
+
credential = await checkConnectionStatus(host);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (error instanceof Error && error.message.includes("login first")) {
|
|
151
|
+
let aigneHubUrl = connectUrl;
|
|
152
|
+
if (!configUrl) {
|
|
153
|
+
const { subscribe } = await inquirerPrompt({
|
|
154
|
+
type: "list",
|
|
155
|
+
name: "subscribe",
|
|
156
|
+
message: "No LLM API Keys or AIGNE Hub connections found. How would you like to proceed?",
|
|
157
|
+
choices: [
|
|
158
|
+
{
|
|
159
|
+
name: "Connect to the Arcblock official AIGNE Hub (recommended, free credits for new users)",
|
|
160
|
+
value: "official",
|
|
161
|
+
},
|
|
162
|
+
connectUrl.includes(AIGNE_HUB_URL)
|
|
163
|
+
? {
|
|
164
|
+
name: "Connect to your own AIGNE Hub instance (self-hosted)",
|
|
165
|
+
value: "custom",
|
|
166
|
+
}
|
|
167
|
+
: null,
|
|
168
|
+
{
|
|
169
|
+
name: "Exit and configure my own LLM API Keys",
|
|
170
|
+
value: "manual",
|
|
171
|
+
},
|
|
172
|
+
].filter(Boolean),
|
|
173
|
+
default: "official",
|
|
174
|
+
});
|
|
175
|
+
if (subscribe === "custom") {
|
|
176
|
+
const { customUrl } = await inquirerPrompt({
|
|
177
|
+
type: "input",
|
|
178
|
+
name: "customUrl",
|
|
179
|
+
message: "Enter the URL of your AIGNE Hub:",
|
|
180
|
+
validate(input) {
|
|
181
|
+
try {
|
|
182
|
+
const url = new URL(input);
|
|
183
|
+
return url.protocol.startsWith("http")
|
|
184
|
+
? true
|
|
185
|
+
: "Must be a valid URL with http or https";
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return "Invalid URL";
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
aigneHubUrl = customUrl;
|
|
193
|
+
}
|
|
194
|
+
else if (subscribe === "manual") {
|
|
195
|
+
console.log("You chose to configure your own LLM API Keys. Exiting...");
|
|
196
|
+
process.exit(0);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
credential = await connectToAIGNEHub(aigneHubUrl);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return credential;
|
|
206
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const decrypt: (m: string, s: string, i: string) => string;
|
|
2
|
+
export declare const encrypt: (m: string, s: string, i: string) => string;
|
|
3
|
+
export declare const encodeEncryptionKey: (key: string) => string;
|
|
4
|
+
export declare const decodeEncryptionKey: (str: string) => Uint8Array<ArrayBuffer>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { AesCrypter } from "@ocap/mcrypto/lib/crypter/aes-legacy.js";
|
|
3
|
+
const aes = new AesCrypter();
|
|
4
|
+
export const decrypt = (m, s, i) => aes.decrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
|
|
5
|
+
export const encrypt = (m, s, i) => aes.encrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
|
|
6
|
+
const escapeFn = (str) => str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
7
|
+
const unescapeFn = (str) => (str + "===".slice((str.length + 3) % 4)).replace(/-/g, "+").replace(/_/g, "/");
|
|
8
|
+
export const encodeEncryptionKey = (key) => escapeFn(Buffer.from(key).toString("base64"));
|
|
9
|
+
export const decodeEncryptionKey = (str) => new Uint8Array(Buffer.from(unescapeFn(str), "base64"));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChatModel, ChatModelOptions } from "@aigne/core";
|
|
2
|
+
import type { LoadCredentialOptions } from "./type.js";
|
|
3
|
+
export declare function maskApiKey(apiKey?: string): string | undefined;
|
|
4
|
+
export declare const parseModelOption: (model: string) => {
|
|
5
|
+
provider: string | undefined;
|
|
6
|
+
model: string | undefined;
|
|
7
|
+
};
|
|
8
|
+
export declare const formatModelName: (model: string, inquirerPrompt: NonNullable<LoadCredentialOptions["inquirerPromptFn"]>) => Promise<{
|
|
9
|
+
provider: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function loadChatModel(options?: ChatModelOptions & LoadCredentialOptions): Promise<ChatModel>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { AIGNE_HUB_DEFAULT_MODEL, findModel } from "@aigne/aigne-hub";
|
|
2
|
+
import { flat } from "@aigne/core/utils/type-utils.js";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
import { AIGNE_HUB_PROVIDER } from "./constants.js";
|
|
6
|
+
import { loadAIGNEHubCredential } from "./credential.js";
|
|
7
|
+
export function maskApiKey(apiKey) {
|
|
8
|
+
if (!apiKey || apiKey.length <= 8)
|
|
9
|
+
return apiKey;
|
|
10
|
+
const start = apiKey.slice(0, 4);
|
|
11
|
+
const end = apiKey.slice(-4);
|
|
12
|
+
return `${start}${"*".repeat(8)}${end}`;
|
|
13
|
+
}
|
|
14
|
+
export const parseModelOption = (model) => {
|
|
15
|
+
const { provider, name } = model.match(/(?<provider>[^:]*)(:(?<name>.*))?/)?.groups ?? {};
|
|
16
|
+
return { provider: provider?.replace(/-/g, ""), model: name };
|
|
17
|
+
};
|
|
18
|
+
export const formatModelName = async (model, inquirerPrompt) => {
|
|
19
|
+
let { provider, model: name } = parseModelOption(model);
|
|
20
|
+
provider ||= AIGNE_HUB_PROVIDER;
|
|
21
|
+
const { match, all } = findModel(provider);
|
|
22
|
+
if (!match)
|
|
23
|
+
throw new Error(`Unsupported model: ${provider}/${name}, available providers: ${all.map((m) => m.name).join(", ")}`);
|
|
24
|
+
if (provider.includes(AIGNE_HUB_PROVIDER)) {
|
|
25
|
+
return { provider, model: name || AIGNE_HUB_DEFAULT_MODEL };
|
|
26
|
+
}
|
|
27
|
+
const requireEnvs = flat(match.apiKeyEnvName);
|
|
28
|
+
if (requireEnvs.some((name) => name && process.env[name])) {
|
|
29
|
+
return { provider, model: name };
|
|
30
|
+
}
|
|
31
|
+
const result = await inquirerPrompt({
|
|
32
|
+
type: "list",
|
|
33
|
+
name: "useAigneHub",
|
|
34
|
+
message: `Seems no API Key configured for ${provider}/${name}, select your preferred way to continue:`,
|
|
35
|
+
choices: [
|
|
36
|
+
{
|
|
37
|
+
name: `Connect to AIGNE Hub to use ${name} (Recommended since free credits available)`,
|
|
38
|
+
value: true,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: `Exit and bring my owner API Key by set ${requireEnvs.join(", ")}`,
|
|
42
|
+
value: false,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
default: true,
|
|
46
|
+
});
|
|
47
|
+
if (!result.useAigneHub) {
|
|
48
|
+
console.log(chalk.yellow(`You can use command "export ${requireEnvs[0]}=xxx" to set API Key in your shell. Or you can set environment variables in .env file.`));
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
return { provider: AIGNE_HUB_PROVIDER, model: `${provider}/${name}` };
|
|
52
|
+
};
|
|
53
|
+
export async function loadChatModel(options) {
|
|
54
|
+
const { provider, model } = await formatModelName(options?.model || process.env.MODEL || "", options?.inquirerPromptFn ??
|
|
55
|
+
inquirer.prompt);
|
|
56
|
+
const params = {
|
|
57
|
+
model,
|
|
58
|
+
temperature: options?.temperature,
|
|
59
|
+
topP: options?.topP,
|
|
60
|
+
frequencyPenalty: options?.frequencyPenalty,
|
|
61
|
+
presencePenalty: options?.presencePenalty,
|
|
62
|
+
};
|
|
63
|
+
const { match, all } = findModel(provider);
|
|
64
|
+
if (!match) {
|
|
65
|
+
throw new Error(`Unsupported model provider ${provider}, available providers: ${all.map((m) => m.name).join(", ")}`);
|
|
66
|
+
}
|
|
67
|
+
const credential = provider.toLowerCase().includes(AIGNE_HUB_PROVIDER)
|
|
68
|
+
? await loadAIGNEHubCredential(options)
|
|
69
|
+
: undefined;
|
|
70
|
+
return match.create({
|
|
71
|
+
...credential,
|
|
72
|
+
model: params.model,
|
|
73
|
+
modelOptions: { ...params },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type InquirerPromptFn = (prompt: {
|
|
2
|
+
type: string;
|
|
3
|
+
name: string;
|
|
4
|
+
message: string;
|
|
5
|
+
choices: {
|
|
6
|
+
name: string;
|
|
7
|
+
value: any;
|
|
8
|
+
}[];
|
|
9
|
+
default: any;
|
|
10
|
+
}) => Promise<any>;
|
|
11
|
+
export type LoadCredentialOptions = {
|
|
12
|
+
aigneHubUrl?: string;
|
|
13
|
+
inquirerPromptFn?: InquirerPromptFn;
|
|
14
|
+
};
|
|
15
|
+
export type FetchResult = {
|
|
16
|
+
accessKeyId: string;
|
|
17
|
+
accessKeySecret: string;
|
|
18
|
+
};
|
|
19
|
+
export type BaseWrapSpinner = (_: string, waiting: () => Promise<FetchResult>) => Promise<FetchResult>;
|
|
20
|
+
export interface CreateConnectOptions {
|
|
21
|
+
connectUrl: string;
|
|
22
|
+
openPage?: (url: string) => void;
|
|
23
|
+
fetchInterval?: number;
|
|
24
|
+
retry?: number;
|
|
25
|
+
source?: string;
|
|
26
|
+
connectAction?: string;
|
|
27
|
+
appName?: string;
|
|
28
|
+
appLogo?: string;
|
|
29
|
+
wrapSpinner?: BaseWrapSpinner;
|
|
30
|
+
prettyUrl?: (url: string) => string;
|
|
31
|
+
closeOnSuccess?: boolean;
|
|
32
|
+
intervalFetchConfig?: (options: {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
fetchInterval: number;
|
|
35
|
+
fetchTimeout: number;
|
|
36
|
+
}) => Promise<FetchResult>;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { LoadCredentialOptions, Model } from "@aigne/aigne-hub";
|
|
2
1
|
import { AIGNE, type ChatModelOptions } from "@aigne/core";
|
|
2
|
+
import type { LoadCredentialOptions } from "./aigne-hub/type.js";
|
|
3
3
|
import type { RunAIGNECommandOptions } from "./run-with-aigne.js";
|
|
4
4
|
export interface RunOptions extends RunAIGNECommandOptions {
|
|
5
5
|
path: string;
|
|
@@ -7,12 +7,7 @@ export interface RunOptions extends RunAIGNECommandOptions {
|
|
|
7
7
|
cacheDir?: string;
|
|
8
8
|
aigneHubUrl?: string;
|
|
9
9
|
}
|
|
10
|
-
export declare function loadAIGNE({ path,
|
|
10
|
+
export declare function loadAIGNE({ path, modelOptions, }: {
|
|
11
11
|
path?: string;
|
|
12
|
-
|
|
13
|
-
modelOptions?: ChatModelOptions;
|
|
14
|
-
actionOptions?: {
|
|
15
|
-
inquirerPromptFn?: LoadCredentialOptions["inquirerPromptFn"];
|
|
16
|
-
runTest?: boolean;
|
|
17
|
-
};
|
|
12
|
+
modelOptions?: ChatModelOptions & LoadCredentialOptions;
|
|
18
13
|
}): Promise<AIGNE<import("@aigne/core").UserContext>>;
|
package/dist/utils/load-aigne.js
CHANGED
|
@@ -1,24 +1,17 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { AIGNE_ENV_FILE, checkConnectionStatus, AIGNE_HUB_URL as DEFAULT_AIGNE_HUB_URL, formatModelName, loadModel, maskApiKey, parseModelOption, } from "@aigne/aigne-hub";
|
|
6
1
|
import { AIGNE } from "@aigne/core";
|
|
7
|
-
import {
|
|
2
|
+
import { isNil, omitBy } from "@aigne/core/utils/type-utils.js";
|
|
3
|
+
import { OpenAIImageModel } from "@aigne/openai";
|
|
8
4
|
import boxen from "boxen";
|
|
9
5
|
import chalk from "chalk";
|
|
10
|
-
import inquirer from "inquirer";
|
|
11
|
-
import { parse, stringify } from "yaml";
|
|
12
6
|
import { availableMemories } from "../constants.js";
|
|
7
|
+
import { loadChatModel, maskApiKey } from "./aigne-hub/model.js";
|
|
13
8
|
import { getUrlOrigin } from "./get-url-origin.js";
|
|
14
|
-
const isTest = process.env.CI || process.env.NODE_ENV === "test";
|
|
15
|
-
const mockInquirerPrompt = (() => Promise.resolve({ useAigneHub: true }));
|
|
16
9
|
let printed = false;
|
|
17
10
|
async function printChatModelInfoBox(model) {
|
|
18
11
|
if (printed)
|
|
19
12
|
return;
|
|
20
13
|
printed = true;
|
|
21
|
-
const credential = await model.
|
|
14
|
+
const credential = await model.credential;
|
|
22
15
|
const lines = [`${chalk.cyan("Provider")}: ${chalk.green(model.name.replace("ChatModel", ""))}`];
|
|
23
16
|
if (credential?.model) {
|
|
24
17
|
lines.push(`${chalk.cyan("Model")}: ${chalk.green(credential?.model)}`);
|
|
@@ -32,49 +25,22 @@ async function printChatModelInfoBox(model) {
|
|
|
32
25
|
console.log(boxen(lines.join("\n"), { padding: 1, borderStyle: "classic", borderColor: "cyan" }));
|
|
33
26
|
console.log("");
|
|
34
27
|
}
|
|
35
|
-
async function
|
|
36
|
-
|
|
37
|
-
if (!existsSync(aigneDir)) {
|
|
38
|
-
mkdirSync(aigneDir, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
41
|
-
const inquirerPrompt = (inquirerPromptFn ?? inquirer.prompt);
|
|
42
|
-
// get aigne hub url
|
|
43
|
-
const configUrl = options?.aigneHubUrl || process.env.AIGNE_HUB_API_URL;
|
|
44
|
-
const AIGNE_HUB_URL = configUrl || envs?.default?.AIGNE_HUB_API_URL || DEFAULT_AIGNE_HUB_URL;
|
|
45
|
-
const { host } = new URL(AIGNE_HUB_URL);
|
|
46
|
-
const result = await checkConnectionStatus(host).catch(() => null);
|
|
47
|
-
const alreadyConnected = Boolean(result?.apiKey);
|
|
48
|
-
return { AIGNE_HUB_URL, inquirerPrompt: alreadyConnected ? mockInquirerPrompt : inquirerPrompt };
|
|
49
|
-
}
|
|
50
|
-
export async function loadAIGNE({ path, options, modelOptions, actionOptions, }) {
|
|
51
|
-
const { AIGNE_HUB_URL, inquirerPrompt } = await prepareAIGNEConfig(options, actionOptions?.inquirerPromptFn);
|
|
52
|
-
const { temperature, topP, presencePenalty, frequencyPenalty } = options || {};
|
|
53
|
-
let modelName = options?.model || "";
|
|
28
|
+
export async function loadAIGNE({ path, modelOptions, }) {
|
|
29
|
+
let aigne;
|
|
54
30
|
if (path) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
31
|
+
aigne = await AIGNE.load(path, {
|
|
32
|
+
memories: availableMemories,
|
|
33
|
+
model: (options) => loadChatModel({ ...options, ...omitBy(modelOptions ?? {}, (v) => isNil(v)) }),
|
|
34
|
+
imageModel: () => new OpenAIImageModel(),
|
|
35
|
+
});
|
|
58
36
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const model = await loadModel(parseModelOption(formattedModelName));
|
|
63
|
-
return await AIGNE.load(path, { loadModel, memories: availableMemories, model });
|
|
37
|
+
else {
|
|
38
|
+
const chatModel = await loadChatModel({ ...modelOptions });
|
|
39
|
+
aigne = new AIGNE({ model: chatModel });
|
|
64
40
|
}
|
|
65
41
|
console.log(`${chalk.grey("TIPS:")} run ${chalk.cyan("aigne observe")} to start the observability server.\n`);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
temperature,
|
|
69
|
-
topP,
|
|
70
|
-
presencePenalty,
|
|
71
|
-
frequencyPenalty,
|
|
72
|
-
}, modelOptions, { aigneHubUrl: AIGNE_HUB_URL, inquirerPromptFn: actionOptions?.inquirerPromptFn });
|
|
73
|
-
if (model) {
|
|
74
|
-
await printChatModelInfoBox(model);
|
|
75
|
-
}
|
|
76
|
-
if (path) {
|
|
77
|
-
return await AIGNE.load(path, { loadModel, memories: availableMemories, model });
|
|
42
|
+
if (aigne.model) {
|
|
43
|
+
await printChatModelInfoBox(aigne.model);
|
|
78
44
|
}
|
|
79
|
-
return
|
|
45
|
+
return aigne;
|
|
80
46
|
}
|
|
@@ -2,10 +2,10 @@ import { mkdir, stat, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, isAbsolute, join } from "node:path";
|
|
3
3
|
import { isatty } from "node:tty";
|
|
4
4
|
import { exists } from "@aigne/agent-library/utils/fs.js";
|
|
5
|
-
import { availableModels
|
|
5
|
+
import { availableModels } from "@aigne/aigne-hub";
|
|
6
6
|
import { DEFAULT_OUTPUT_KEY, UserAgent, } from "@aigne/core";
|
|
7
7
|
import { getLevelFromEnv, LogLevel, logger } from "@aigne/core/utils/logger.js";
|
|
8
|
-
import { isEmpty, tryOrThrow } from "@aigne/core/utils/type-utils.js";
|
|
8
|
+
import { isEmpty, isNil, omitBy, pick, tryOrThrow, } from "@aigne/core/utils/type-utils.js";
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
import yargs from "yargs";
|
|
11
11
|
import { hideBin } from "yargs/helpers";
|
|
@@ -106,14 +106,10 @@ export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoop
|
|
|
106
106
|
logger.level = options.logLevel;
|
|
107
107
|
}
|
|
108
108
|
const aigne = await loadAIGNE({
|
|
109
|
-
|
|
110
|
-
...
|
|
111
|
-
|
|
112
|
-
topP: options.topP,
|
|
113
|
-
presencePenalty: options.presencePenalty,
|
|
114
|
-
frequencyPenalty: options.frequencyPenalty,
|
|
109
|
+
modelOptions: {
|
|
110
|
+
...modelOptions,
|
|
111
|
+
...omitBy(pick(options, "model", "temperature", "topP", "presencePenalty", "frequencyPenalty"), (v) => isNil(v)),
|
|
115
112
|
},
|
|
116
|
-
modelOptions,
|
|
117
113
|
});
|
|
118
114
|
try {
|
|
119
115
|
const agent = typeof agentCreator === "function" ? await agentCreator(aigne) : agentCreator;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.1",
|
|
4
4
|
"description": "Your command center for agent development",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"@inquirer/type": "^3.0.8",
|
|
52
52
|
"@listr2/prompt-adapter-inquirer": "^3.0.1",
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
54
|
+
"@ocap/mcrypto": "^1.21.0",
|
|
54
55
|
"@smithy/node-http-handler": "^4.1.0",
|
|
55
56
|
"boxen": "^8.0.1",
|
|
56
57
|
"chalk": "^5.4.1",
|
|
@@ -65,20 +66,22 @@
|
|
|
65
66
|
"log-update": "^6.1.0",
|
|
66
67
|
"marked": "^16.0.0",
|
|
67
68
|
"nunjucks": "^3.2.4",
|
|
69
|
+
"open": "^10.2.0",
|
|
68
70
|
"openai": "^5.8.3",
|
|
71
|
+
"p-wait-for": "^5.0.2",
|
|
69
72
|
"prettier": "^3.6.2",
|
|
70
73
|
"tar": "^7.4.3",
|
|
71
74
|
"wrap-ansi": "^9.0.0",
|
|
72
75
|
"yaml": "^2.8.0",
|
|
73
76
|
"yargs": "^18.0.0",
|
|
74
77
|
"zod": "^3.25.67",
|
|
75
|
-
"@aigne/agent-library": "^1.21.
|
|
76
|
-
"@aigne/aigne-hub": "^0.6.
|
|
77
|
-
"@aigne/
|
|
78
|
-
"@aigne/
|
|
79
|
-
"@aigne/default-memory": "^1.1.
|
|
80
|
-
"@aigne/observability-api": "^0.9.
|
|
81
|
-
"@aigne/openai": "^0.
|
|
78
|
+
"@aigne/agent-library": "^1.21.24",
|
|
79
|
+
"@aigne/aigne-hub": "^0.6.6",
|
|
80
|
+
"@aigne/core": "^1.53.0",
|
|
81
|
+
"@aigne/agentic-memory": "^1.0.24",
|
|
82
|
+
"@aigne/default-memory": "^1.1.6",
|
|
83
|
+
"@aigne/observability-api": "^0.9.1",
|
|
84
|
+
"@aigne/openai": "^0.12.0"
|
|
82
85
|
},
|
|
83
86
|
"devDependencies": {
|
|
84
87
|
"@types/archiver": "^6.0.3",
|