@aigne/cli 1.56.1-beta → 1.57.0-beta.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 +34 -0
- package/dist/commands/hub.js +63 -57
- package/dist/utils/aigne-hub/credential.js +12 -36
- package/dist/utils/aigne-hub/model.js +8 -7
- package/dist/utils/aigne-hub/store/file.d.ts +15 -0
- package/dist/utils/aigne-hub/store/file.js +69 -0
- package/dist/utils/aigne-hub/store/index.d.ts +4 -0
- package/dist/utils/aigne-hub/store/index.js +40 -0
- package/dist/utils/aigne-hub/store/keytar.d.ts +15 -0
- package/dist/utils/aigne-hub/store/keytar.js +67 -0
- package/dist/utils/aigne-hub/store/migrate.d.ts +2 -0
- package/dist/utils/aigne-hub/store/migrate.js +57 -0
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.57.0-beta.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.57.0-beta...cli-v1.57.0-beta.1) (2025-11-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **cli:** use sequential migration to handle keyring and callback file save ([#776](https://github.com/AIGNE-io/aigne-framework/issues/776)) ([da0db46](https://github.com/AIGNE-io/aigne-framework/commit/da0db46597b76cc0f41d604fd51bcd64931f0315))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @aigne/secrets bumped to 0.1.1-beta.1
|
|
16
|
+
|
|
17
|
+
## [1.57.0-beta](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.56.1-beta...cli-v1.57.0-beta) (2025-11-26)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **secure:** secure credential storage with keyring support ([#771](https://github.com/AIGNE-io/aigne-framework/issues/771)) ([023c202](https://github.com/AIGNE-io/aigne-framework/commit/023c202f75eddb37d003b1fad447b491e8e1a8c2))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* **secrets:** support system keyring for secure credential storage ([#773](https://github.com/AIGNE-io/aigne-framework/issues/773)) ([859ac2d](https://github.com/AIGNE-io/aigne-framework/commit/859ac2d9eb6019d7a68726076d65841cd96bc9a4))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Dependencies
|
|
31
|
+
|
|
32
|
+
* The following workspace dependencies were updated
|
|
33
|
+
* dependencies
|
|
34
|
+
* @aigne/secrets bumped to 0.1.1-beta
|
|
35
|
+
* @aigne/aigne-hub bumped to 0.10.11-beta.1
|
|
36
|
+
|
|
3
37
|
## [1.56.1-beta](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.56.0...cli-v1.56.1-beta) (2025-11-24)
|
|
4
38
|
|
|
5
39
|
|
package/dist/commands/hub.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
3
1
|
import { AIGNE_HUB_URL } from "@aigne/aigne-hub";
|
|
4
2
|
import chalk from "chalk";
|
|
5
3
|
import Table from "cli-table3";
|
|
6
4
|
import inquirer from "inquirer";
|
|
7
|
-
import {
|
|
8
|
-
import { AIGNE_ENV_FILE, isTest } from "../utils/aigne-hub/constants.js";
|
|
5
|
+
import { isTest } from "../utils/aigne-hub/constants.js";
|
|
9
6
|
import { connectToAIGNEHub } from "../utils/aigne-hub/credential.js";
|
|
7
|
+
import getSecretStore from "../utils/aigne-hub/store/index.js";
|
|
10
8
|
import { getUserInfo } from "../utils/aigne-hub-user.js";
|
|
11
9
|
import { getUrlOrigin } from "../utils/get-url-origin.js";
|
|
12
10
|
export const formatNumber = (balance) => {
|
|
@@ -53,21 +51,16 @@ function printHubStatus(data) {
|
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
async function getHubs() {
|
|
56
|
-
if (!existsSync(AIGNE_ENV_FILE)) {
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
54
|
try {
|
|
60
|
-
const
|
|
61
|
-
const
|
|
55
|
+
const secretStore = await getSecretStore();
|
|
56
|
+
const hosts = await secretStore.listHosts();
|
|
62
57
|
const statusList = [];
|
|
63
|
-
for (const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
70
|
-
}
|
|
58
|
+
for (const host of hosts) {
|
|
59
|
+
statusList.push({
|
|
60
|
+
host: new URL(host.AIGNE_HUB_API_URL).host,
|
|
61
|
+
apiUrl: host.AIGNE_HUB_API_URL,
|
|
62
|
+
apiKey: host.AIGNE_HUB_API_KEY || "",
|
|
63
|
+
});
|
|
71
64
|
}
|
|
72
65
|
return statusList;
|
|
73
66
|
}
|
|
@@ -76,8 +69,14 @@ async function getHubs() {
|
|
|
76
69
|
}
|
|
77
70
|
}
|
|
78
71
|
const getDefaultHub = async () => {
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
try {
|
|
73
|
+
const secretStore = await getSecretStore();
|
|
74
|
+
const defaultHost = await secretStore.getDefault();
|
|
75
|
+
return defaultHost?.AIGNE_HUB_API_URL || AIGNE_HUB_URL;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return AIGNE_HUB_URL;
|
|
79
|
+
}
|
|
81
80
|
};
|
|
82
81
|
async function formatHubsList(statusList) {
|
|
83
82
|
if (statusList?.length === 0) {
|
|
@@ -211,7 +210,12 @@ async function showInfo() {
|
|
|
211
210
|
value: h.apiUrl,
|
|
212
211
|
})),
|
|
213
212
|
});
|
|
214
|
-
|
|
213
|
+
try {
|
|
214
|
+
await printHubDetails(hubApiKey);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
console.error(chalk.red("✗ Failed to print hub details:"), error.message);
|
|
218
|
+
}
|
|
215
219
|
}
|
|
216
220
|
function validateUrl(input) {
|
|
217
221
|
try {
|
|
@@ -223,27 +227,18 @@ function validateUrl(input) {
|
|
|
223
227
|
}
|
|
224
228
|
}
|
|
225
229
|
async function saveAndConnect(url) {
|
|
226
|
-
const
|
|
227
|
-
const
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
console.log(chalk.green(`✓ Hub ${getUrlOrigin(currentUrl)} connected successfully.`));
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
230
|
+
const secretStore = await getSecretStore();
|
|
231
|
+
const currentKey = await secretStore.getKey(url);
|
|
232
|
+
if (currentKey?.AIGNE_HUB_API_URL) {
|
|
233
|
+
await setDefaultHub(currentKey.AIGNE_HUB_API_URL);
|
|
234
|
+
console.log(chalk.green(`✓ Hub ${getUrlOrigin(currentKey.AIGNE_HUB_API_URL)} connected successfully.`));
|
|
235
|
+
return;
|
|
235
236
|
}
|
|
236
237
|
try {
|
|
237
238
|
if (isTest) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
AIGNE_HUB_API_URL: "https://hub.aigne.io/ai-kit",
|
|
242
|
-
},
|
|
243
|
-
default: {
|
|
244
|
-
AIGNE_HUB_API_URL: "https://hub.aigne.io/ai-kit",
|
|
245
|
-
},
|
|
246
|
-
}));
|
|
239
|
+
await secretStore.setKey("https://hub.aigne.io/ai-kit", "test-key");
|
|
240
|
+
await secretStore.setDefault("https://hub.aigne.io/ai-kit");
|
|
241
|
+
console.log(chalk.green(`✓ Hub https://hub.aigne.io/ai-kit connected successfully.`));
|
|
247
242
|
return;
|
|
248
243
|
}
|
|
249
244
|
await connectToAIGNEHub(url);
|
|
@@ -254,35 +249,46 @@ async function saveAndConnect(url) {
|
|
|
254
249
|
}
|
|
255
250
|
}
|
|
256
251
|
async function setDefaultHub(url) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
252
|
+
try {
|
|
253
|
+
const secretStore = await getSecretStore();
|
|
254
|
+
const key = await secretStore.getKey(url);
|
|
255
|
+
if (!key) {
|
|
256
|
+
console.error(chalk.red("✗ Hub not found"));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
await secretStore.setDefault(key.AIGNE_HUB_API_URL);
|
|
260
|
+
console.log(chalk.green(`✓ Switched active hub to ${getUrlOrigin(url)}`));
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
console.error(chalk.red("✗ Failed to set default hub"));
|
|
262
264
|
}
|
|
263
|
-
await writeFile(AIGNE_ENV_FILE, stringify({ ...envs, default: { AIGNE_HUB_API_URL: envs[host]?.AIGNE_HUB_API_URL } }));
|
|
264
|
-
console.log(chalk.green(`✓ Switched active hub to ${getUrlOrigin(url)}`));
|
|
265
265
|
}
|
|
266
266
|
async function deleteHub(url) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
267
|
+
try {
|
|
268
|
+
const secretStore = await getSecretStore();
|
|
269
|
+
const key = await secretStore.getKey(url);
|
|
270
|
+
if (key) {
|
|
271
|
+
await secretStore.deleteKey(url);
|
|
272
|
+
}
|
|
273
|
+
await secretStore.deleteDefault();
|
|
274
|
+
console.log(chalk.green(`✓ Hub ${getUrlOrigin(url)} removed`));
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
console.error(chalk.red("✗ Failed to delete hub"));
|
|
272
278
|
}
|
|
273
|
-
await writeFile(AIGNE_ENV_FILE, stringify(envs));
|
|
274
|
-
console.log(chalk.green(`✓ Hub ${getUrlOrigin(url)} removed`));
|
|
275
279
|
}
|
|
276
280
|
async function printHubDetails(url) {
|
|
277
|
-
const
|
|
278
|
-
const
|
|
281
|
+
const secretStore = await getSecretStore();
|
|
282
|
+
const key = await secretStore.getKey(url);
|
|
283
|
+
const defaultHub = await getDefaultHub();
|
|
284
|
+
const isDefault = getUrlOrigin(url) === getUrlOrigin(defaultHub);
|
|
279
285
|
const userInfo = await getUserInfo({
|
|
280
|
-
baseUrl:
|
|
281
|
-
apiKey:
|
|
286
|
+
baseUrl: key?.AIGNE_HUB_API_URL || "",
|
|
287
|
+
apiKey: key?.AIGNE_HUB_API_KEY || "",
|
|
282
288
|
}).catch(() => null);
|
|
283
289
|
printHubStatus({
|
|
284
290
|
hub: getUrlOrigin(url),
|
|
285
|
-
status:
|
|
291
|
+
status: isDefault ? "Connected" : "Not connected",
|
|
286
292
|
user: {
|
|
287
293
|
name: userInfo?.user.fullName || "",
|
|
288
294
|
did: userInfo?.user.did || "",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
3
2
|
import { homedir } from "node:os";
|
|
4
3
|
import { join } from "node:path";
|
|
5
4
|
import { AIGNE_HUB_BLOCKLET_DID, AIGNE_HUB_URL, getAIGNEHubMountPoint } from "@aigne/aigne-hub";
|
|
@@ -9,9 +8,9 @@ import inquirer from "inquirer";
|
|
|
9
8
|
import open from "open";
|
|
10
9
|
import pWaitFor from "p-wait-for";
|
|
11
10
|
import { joinURL, withQuery } from "ufo";
|
|
12
|
-
import {
|
|
13
|
-
import { ACCESS_KEY_SESSION_API, AIGNE_ENV_FILE, isTest, WELLKNOWN_SERVICE_PATH_PREFIX, } from "./constants.js";
|
|
11
|
+
import { ACCESS_KEY_SESSION_API, isTest, WELLKNOWN_SERVICE_PATH_PREFIX } from "./constants.js";
|
|
14
12
|
import { decrypt, encodeEncryptionKey } from "./crypto.js";
|
|
13
|
+
import getSecretStore from "./store/index.js";
|
|
15
14
|
const request = async (config) => {
|
|
16
15
|
const headers = {};
|
|
17
16
|
if (config.requestCount !== undefined) {
|
|
@@ -66,7 +65,7 @@ export async function createConnect({ connectUrl, openPage, fetchInterval = 3 *
|
|
|
66
65
|
});
|
|
67
66
|
}
|
|
68
67
|
export async function connectToAIGNEHub(url) {
|
|
69
|
-
const { origin
|
|
68
|
+
const { origin } = new URL(url);
|
|
70
69
|
const connectUrl = joinURL(origin, WELLKNOWN_SERVICE_PATH_PREFIX);
|
|
71
70
|
const apiUrl = await getAIGNEHubMountPoint(url, AIGNE_HUB_BLOCKLET_DID);
|
|
72
71
|
try {
|
|
@@ -82,22 +81,9 @@ export async function connectToAIGNEHub(url) {
|
|
|
82
81
|
apiKey: result.accessKeySecret,
|
|
83
82
|
url: apiUrl,
|
|
84
83
|
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
mkdirSync(aigneDir, { recursive: true });
|
|
89
|
-
}
|
|
90
|
-
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
91
|
-
await writeFile(AIGNE_ENV_FILE, stringify({
|
|
92
|
-
...envs,
|
|
93
|
-
[host]: {
|
|
94
|
-
AIGNE_HUB_API_KEY: accessKeyOptions.apiKey,
|
|
95
|
-
AIGNE_HUB_API_URL: accessKeyOptions.url,
|
|
96
|
-
},
|
|
97
|
-
default: {
|
|
98
|
-
AIGNE_HUB_API_URL: accessKeyOptions.url,
|
|
99
|
-
},
|
|
100
|
-
}));
|
|
84
|
+
const secretStore = await getSecretStore();
|
|
85
|
+
await secretStore.setKey(accessKeyOptions.url, accessKeyOptions.apiKey);
|
|
86
|
+
await secretStore.setDefault(accessKeyOptions.url);
|
|
101
87
|
return accessKeyOptions;
|
|
102
88
|
}
|
|
103
89
|
catch (error) {
|
|
@@ -106,20 +92,9 @@ export async function connectToAIGNEHub(url) {
|
|
|
106
92
|
}
|
|
107
93
|
}
|
|
108
94
|
export const checkConnectionStatus = async (host) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
const data = await readFile(AIGNE_ENV_FILE, "utf8");
|
|
114
|
-
if (!data.includes("AIGNE_HUB_API_KEY")) {
|
|
115
|
-
throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
|
|
116
|
-
}
|
|
117
|
-
const envs = parse(data);
|
|
118
|
-
if (!envs[host]) {
|
|
119
|
-
throw new Error("AIGNE_HUB_API_KEY host not found, need to login first");
|
|
120
|
-
}
|
|
121
|
-
const env = envs[host];
|
|
122
|
-
if (!env.AIGNE_HUB_API_KEY) {
|
|
95
|
+
const secretStore = await getSecretStore();
|
|
96
|
+
const env = await secretStore.getKey(host);
|
|
97
|
+
if (!env?.AIGNE_HUB_API_KEY) {
|
|
123
98
|
throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
|
|
124
99
|
}
|
|
125
100
|
return {
|
|
@@ -135,10 +110,11 @@ export async function loadAIGNEHubCredential(options) {
|
|
|
135
110
|
if (!existsSync(aigneDir)) {
|
|
136
111
|
mkdirSync(aigneDir, { recursive: true });
|
|
137
112
|
}
|
|
138
|
-
const
|
|
113
|
+
const secretStore = await getSecretStore();
|
|
114
|
+
const defaultHost = await secretStore.getDefault();
|
|
139
115
|
const inquirerPrompt = (options?.inquirerPromptFn ?? inquirer.prompt);
|
|
140
116
|
const configUrl = options?.aigneHubUrl || process.env.AIGNE_HUB_API_URL;
|
|
141
|
-
const url = configUrl ||
|
|
117
|
+
const url = configUrl || defaultHost?.AIGNE_HUB_API_URL || AIGNE_HUB_URL;
|
|
142
118
|
const connectUrl = joinURL(new URL(url).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
|
|
143
119
|
const { host } = new URL(url);
|
|
144
120
|
let credential = {};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
2
1
|
import { AIGNE_HUB_DEFAULT_MODEL, AIGNE_HUB_URL, findImageModel, findModel, getSupportedProviders, parseModel, resolveProviderModelId, } from "@aigne/aigne-hub";
|
|
3
2
|
import { flat, omit } from "@aigne/core/utils/type-utils.js";
|
|
4
3
|
import chalk from "chalk";
|
|
5
4
|
import inquirer from "inquirer";
|
|
6
|
-
import {
|
|
7
|
-
import { AIGNE_ENV_FILE, AIGNE_HUB_PROVIDER } from "./constants.js";
|
|
5
|
+
import { AIGNE_HUB_PROVIDER } from "./constants.js";
|
|
8
6
|
import { loadAIGNEHubCredential } from "./credential.js";
|
|
7
|
+
import getSecretStore from "./store/index.js";
|
|
9
8
|
export function maskApiKey(apiKey) {
|
|
10
9
|
if (!apiKey || apiKey.length <= 8)
|
|
11
10
|
return apiKey;
|
|
@@ -50,8 +49,9 @@ export const formatModelName = async (model, inquirerPrompt) => {
|
|
|
50
49
|
if (requireEnvs.some((name) => name && process.env[name])) {
|
|
51
50
|
return { provider, model: name };
|
|
52
51
|
}
|
|
53
|
-
const
|
|
54
|
-
|
|
52
|
+
const secretStore = await getSecretStore();
|
|
53
|
+
const defaultHost = await secretStore.getDefault();
|
|
54
|
+
if (process.env.AIGNE_HUB_API_KEY || defaultHost?.AIGNE_HUB_API_URL) {
|
|
55
55
|
return { provider: AIGNE_HUB_PROVIDER, model: `${provider}/${name}` };
|
|
56
56
|
}
|
|
57
57
|
const result = await inquirerPrompt({
|
|
@@ -74,12 +74,13 @@ export const formatModelName = async (model, inquirerPrompt) => {
|
|
|
74
74
|
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.`));
|
|
75
75
|
process.exit(0);
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
const envs = await secretStore.listHostsMap();
|
|
78
|
+
if (envs && Object.keys(envs).length > 0 && !defaultHost?.AIGNE_HUB_API_URL) {
|
|
78
79
|
const host = new URL(AIGNE_HUB_URL).host;
|
|
79
80
|
const defaultEnv = envs[host]?.AIGNE_HUB_API_URL
|
|
80
81
|
? envs[host]
|
|
81
82
|
: Object.values(envs)[0] || { AIGNE_HUB_API_URL: "" };
|
|
82
|
-
await
|
|
83
|
+
await secretStore.setDefault(defaultEnv?.AIGNE_HUB_API_URL);
|
|
83
84
|
}
|
|
84
85
|
return { provider: AIGNE_HUB_PROVIDER, model: `${provider}/${name}` };
|
|
85
86
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { GetDefaultOptions, ItemInfo, StoreOptions } from "@aigne/secrets";
|
|
2
|
+
import { FileStore as BaseFileStore } from "@aigne/secrets";
|
|
3
|
+
declare class FileStore extends BaseFileStore {
|
|
4
|
+
private outputConfig;
|
|
5
|
+
constructor(options: Required<Pick<StoreOptions, "filepath">>);
|
|
6
|
+
setKey(url: string, apiKey: string): Promise<void>;
|
|
7
|
+
getKey(url: string): Promise<ItemInfo | null>;
|
|
8
|
+
deleteKey(url: string): Promise<boolean>;
|
|
9
|
+
listHosts(): Promise<ItemInfo[]>;
|
|
10
|
+
listHostsMap(): Promise<Record<string, ItemInfo>>;
|
|
11
|
+
setDefault(url: string): Promise<void>;
|
|
12
|
+
getDefault(options?: GetDefaultOptions): Promise<ItemInfo | null>;
|
|
13
|
+
deleteDefault(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export default FileStore;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { FileStore as BaseFileStore } from "@aigne/secrets";
|
|
2
|
+
class FileStore extends BaseFileStore {
|
|
3
|
+
outputConfig;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super(options);
|
|
6
|
+
this.outputConfig = { url: "AIGNE_HUB_API_URL", key: "AIGNE_HUB_API_KEY" };
|
|
7
|
+
}
|
|
8
|
+
async setKey(url, apiKey) {
|
|
9
|
+
return this.setItem(this.normalizeHostFrom(url), {
|
|
10
|
+
[this.outputConfig.url]: url,
|
|
11
|
+
[this.outputConfig.key]: apiKey,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async getKey(url) {
|
|
15
|
+
const host = this.normalizeHostFrom(url);
|
|
16
|
+
return this.getItem(host);
|
|
17
|
+
}
|
|
18
|
+
async deleteKey(url) {
|
|
19
|
+
const host = this.normalizeHostFrom(url);
|
|
20
|
+
return this.deleteItem(host);
|
|
21
|
+
}
|
|
22
|
+
async listHosts() {
|
|
23
|
+
return this.listEntries();
|
|
24
|
+
}
|
|
25
|
+
async listHostsMap() {
|
|
26
|
+
return this.listMap();
|
|
27
|
+
}
|
|
28
|
+
async setDefault(url) {
|
|
29
|
+
return this.setDefaultItem({ [this.outputConfig.url]: url });
|
|
30
|
+
}
|
|
31
|
+
async getDefault(options = {}) {
|
|
32
|
+
const { fallbackToFirst = false, presetIfFallback = false } = options;
|
|
33
|
+
if (!(await this.available()))
|
|
34
|
+
return null;
|
|
35
|
+
try {
|
|
36
|
+
const value = await this.getDefaultItem();
|
|
37
|
+
const apiUrl = value?.[this.outputConfig.url];
|
|
38
|
+
if (apiUrl) {
|
|
39
|
+
const defaultInfo = await this.getKey(apiUrl);
|
|
40
|
+
if (defaultInfo)
|
|
41
|
+
return defaultInfo;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// ignore
|
|
46
|
+
}
|
|
47
|
+
if (!fallbackToFirst)
|
|
48
|
+
return null;
|
|
49
|
+
const hosts = await this.listHosts();
|
|
50
|
+
if (hosts.length === 0)
|
|
51
|
+
return null;
|
|
52
|
+
const firstHost = hosts[0];
|
|
53
|
+
if (!firstHost)
|
|
54
|
+
return null;
|
|
55
|
+
if (presetIfFallback && firstHost[this.outputConfig.url]) {
|
|
56
|
+
try {
|
|
57
|
+
await this.setDefault(firstHost[this.outputConfig.url]);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// ignore
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return firstHost;
|
|
64
|
+
}
|
|
65
|
+
async deleteDefault() {
|
|
66
|
+
return this.deleteDefaultItem();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export default FileStore;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { logger } from "@aigne/core/utils/logger.js";
|
|
2
|
+
import { AIGNE_ENV_FILE } from "../constants.js";
|
|
3
|
+
import FileStore from "./file.js";
|
|
4
|
+
import KeyringStore from "./keytar.js";
|
|
5
|
+
import { migrateFileToKeyring } from "./migrate.js";
|
|
6
|
+
async function createSecretStore(options) {
|
|
7
|
+
if (!options.serviceName) {
|
|
8
|
+
throw new Error("Secret store key is required");
|
|
9
|
+
}
|
|
10
|
+
const keyring = new KeyringStore(options);
|
|
11
|
+
if (await keyring.available()) {
|
|
12
|
+
if (options.filepath) {
|
|
13
|
+
try {
|
|
14
|
+
await migrateFileToKeyring(options);
|
|
15
|
+
logger.debug("Successfully migrated credentials from file to keyring");
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
logger.warn("Failed to migrate credentials from file to keyring:", error.message);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return keyring;
|
|
22
|
+
}
|
|
23
|
+
const filepath = options.filepath;
|
|
24
|
+
if (!filepath) {
|
|
25
|
+
throw new Error("Filepath is required");
|
|
26
|
+
}
|
|
27
|
+
return new FileStore({ filepath });
|
|
28
|
+
}
|
|
29
|
+
let cachedSecretStore;
|
|
30
|
+
const getSecretStore = async () => {
|
|
31
|
+
if (!cachedSecretStore) {
|
|
32
|
+
cachedSecretStore = await createSecretStore({
|
|
33
|
+
filepath: AIGNE_ENV_FILE,
|
|
34
|
+
serviceName: "aigne-hub-keyring",
|
|
35
|
+
forceKeytarUnavailable: true,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return cachedSecretStore;
|
|
39
|
+
};
|
|
40
|
+
export default getSecretStore;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { GetDefaultOptions, ItemInfo, StoreOptions } from "@aigne/secrets";
|
|
2
|
+
import { KeyringStore as BaseKeyringStore } from "@aigne/secrets";
|
|
3
|
+
declare class KeyringStore extends BaseKeyringStore {
|
|
4
|
+
private outputConfig;
|
|
5
|
+
constructor(options: StoreOptions);
|
|
6
|
+
setKey(url: string, apiKey: string): Promise<any>;
|
|
7
|
+
getKey(url: string): Promise<ItemInfo | null>;
|
|
8
|
+
deleteKey(url: string): Promise<boolean>;
|
|
9
|
+
listHosts(): Promise<ItemInfo[]>;
|
|
10
|
+
listHostsMap(): Promise<Record<string, ItemInfo>>;
|
|
11
|
+
setDefault(url: string): Promise<void>;
|
|
12
|
+
getDefault(options?: GetDefaultOptions): Promise<ItemInfo | null>;
|
|
13
|
+
deleteDefault(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export default KeyringStore;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { KeyringStore as BaseKeyringStore } from "@aigne/secrets";
|
|
2
|
+
class KeyringStore extends BaseKeyringStore {
|
|
3
|
+
outputConfig;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super(options);
|
|
6
|
+
this.outputConfig = { url: "AIGNE_HUB_API_URL", key: "AIGNE_HUB_API_KEY" };
|
|
7
|
+
}
|
|
8
|
+
async setKey(url, apiKey) {
|
|
9
|
+
return this.setItem(this.normalizeHostFrom(url), {
|
|
10
|
+
[this.outputConfig.url]: url,
|
|
11
|
+
[this.outputConfig.key]: apiKey,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async getKey(url) {
|
|
15
|
+
const v = await this.getItem(this.normalizeHostFrom(url));
|
|
16
|
+
return v;
|
|
17
|
+
}
|
|
18
|
+
async deleteKey(url) {
|
|
19
|
+
const ok = await this.deleteItem(this.normalizeHostFrom(url));
|
|
20
|
+
return !!ok;
|
|
21
|
+
}
|
|
22
|
+
async listHosts() {
|
|
23
|
+
return this.listEntries();
|
|
24
|
+
}
|
|
25
|
+
async listHostsMap() {
|
|
26
|
+
return this.listMap();
|
|
27
|
+
}
|
|
28
|
+
async setDefault(url) {
|
|
29
|
+
return this.setDefaultItem({ [this.outputConfig.url]: url });
|
|
30
|
+
}
|
|
31
|
+
async getDefault(options = {}) {
|
|
32
|
+
const { fallbackToFirst = false, presetIfFallback = false } = options;
|
|
33
|
+
try {
|
|
34
|
+
const value = await this.getDefaultItem();
|
|
35
|
+
const apiUrl = value?.[this.outputConfig.url];
|
|
36
|
+
if (apiUrl) {
|
|
37
|
+
const defaultInfo = await this.getKey(apiUrl);
|
|
38
|
+
if (defaultInfo)
|
|
39
|
+
return defaultInfo;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// ignore
|
|
44
|
+
}
|
|
45
|
+
if (!fallbackToFirst)
|
|
46
|
+
return null;
|
|
47
|
+
const hosts = await this.listHosts();
|
|
48
|
+
if (hosts.length === 0)
|
|
49
|
+
return null;
|
|
50
|
+
const firstHost = hosts[0];
|
|
51
|
+
if (!firstHost)
|
|
52
|
+
return null;
|
|
53
|
+
if (presetIfFallback && firstHost[this.outputConfig.url]) {
|
|
54
|
+
try {
|
|
55
|
+
await this.setDefault(firstHost[this.outputConfig.url]);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// ignore
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return firstHost;
|
|
62
|
+
}
|
|
63
|
+
async deleteDefault() {
|
|
64
|
+
return this.deleteDefaultItem();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export default KeyringStore;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { logger } from "@aigne/core/utils/logger.js";
|
|
3
|
+
import FileStore from "./file.js";
|
|
4
|
+
import KeyringStore from "./keytar.js";
|
|
5
|
+
export async function migrateFileToKeyring(options) {
|
|
6
|
+
const { filepath } = options;
|
|
7
|
+
const outputConfig = {
|
|
8
|
+
url: "AIGNE_HUB_API_URL",
|
|
9
|
+
key: "AIGNE_HUB_API_KEY",
|
|
10
|
+
};
|
|
11
|
+
if (!filepath) {
|
|
12
|
+
throw new Error("Filepath is required for migration");
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
await fs.access(filepath);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
const keyring = new KeyringStore(options);
|
|
21
|
+
if (!(await keyring.available())) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const fileStore = new FileStore({ filepath });
|
|
25
|
+
if (!(await fileStore.available())) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const backupPath = `${filepath}.backup`;
|
|
29
|
+
try {
|
|
30
|
+
// Create backup before migration
|
|
31
|
+
await fs.copyFile(filepath, backupPath);
|
|
32
|
+
const hosts = await fileStore.listHosts();
|
|
33
|
+
for (const host of hosts) {
|
|
34
|
+
if (host[outputConfig.url] && host[outputConfig.key]) {
|
|
35
|
+
await keyring.setKey(host[outputConfig.url], host[outputConfig.key]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const defaultKey = await fileStore.getDefault();
|
|
39
|
+
if (defaultKey) {
|
|
40
|
+
await keyring.setDefault(defaultKey[outputConfig.url]);
|
|
41
|
+
}
|
|
42
|
+
await fs.rm(filepath);
|
|
43
|
+
await fs.rm(backupPath);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
try {
|
|
48
|
+
await fs.copyFile(backupPath, filepath);
|
|
49
|
+
await fs.rm(backupPath);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// If restore fails, at least backup exists
|
|
53
|
+
}
|
|
54
|
+
logger.error(`Migration failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.57.0-beta.1",
|
|
4
4
|
"description": "Your command center for agent development",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -90,13 +90,14 @@
|
|
|
90
90
|
"zod": "^3.25.67",
|
|
91
91
|
"zod-to-json-schema": "^3.24.6",
|
|
92
92
|
"@aigne/afs": "^1.2.1",
|
|
93
|
-
"@aigne/afs-history": "^1.1.0",
|
|
94
93
|
"@aigne/afs-local-fs": "^1.2.1-beta",
|
|
95
94
|
"@aigne/agentic-memory": "^1.1.1-beta",
|
|
96
95
|
"@aigne/agent-library": "^1.22.1-beta",
|
|
97
|
-
"@aigne/
|
|
98
|
-
"@aigne/
|
|
96
|
+
"@aigne/afs-history": "^1.1.0",
|
|
97
|
+
"@aigne/secrets": "^0.1.1-beta.1",
|
|
99
98
|
"@aigne/core": "^1.69.1-beta",
|
|
99
|
+
"@aigne/aigne-hub": "^0.10.11-beta.1",
|
|
100
|
+
"@aigne/default-memory": "^1.3.1-beta",
|
|
100
101
|
"@aigne/openai": "^0.16.11-beta",
|
|
101
102
|
"@aigne/observability-api": "^0.11.10-beta"
|
|
102
103
|
},
|