@gaoding/cli 1.0.0 → 1.2.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/dist/bin/gd-cli.js +2 -1
- package/dist/bin/insmind-cli.js +60 -0
- package/dist/bin/postinstall.js +31 -4
- package/dist/src/bootstrap/create-cli.js +14 -4
- package/dist/src/bootstrap/create-runtime.js +93 -31
- package/dist/src/bootstrap/validators.js +9 -5
- package/dist/src/cli/agent-commands.js +5 -4
- package/dist/src/cli/auth-commands.js +13 -10
- package/dist/src/cli/credits-command.js +23 -0
- package/dist/src/cli/dam-commands.js +70 -69
- package/dist/src/cli/editor-commands.js +12 -11
- package/dist/src/cli/errors.js +49 -9
- package/dist/src/cli/model-commands.js +6 -5
- package/dist/src/cli/presenter.js +24 -6
- package/dist/src/cli/tool-commands.js +7 -6
- package/dist/src/cli/update-command.js +4 -3
- package/dist/src/features/auth/credential-store.js +1 -1
- package/dist/src/features/auth/sso-service.js +3 -1
- package/dist/src/features/auth/use-cases.js +38 -12
- package/dist/src/features/credits/use-cases.js +52 -0
- package/dist/src/features/dam/storage-upload.js +1 -1
- package/dist/src/features/editor/bridge-process.js +11 -4
- package/dist/src/features/editor/session-state.js +1 -1
- package/dist/src/features/editor/session.js +14 -8
- package/dist/src/features/org/use-cases.js +7 -0
- package/dist/src/features/skill/bundled-skills.js +13 -5
- package/dist/src/features/tool/catalog.js +13 -13
- package/dist/src/features/tool/use-cases.js +3 -3
- package/dist/src/features/update/latest-version.js +2 -2
- package/dist/src/features/update/update-notification.js +12 -3
- package/dist/src/features/update/update-service.js +64 -21
- package/dist/src/platform/installation-owner.js +81 -0
- package/dist/src/platform/signed-http-transport.js +1 -0
- package/dist/src/product/billing.js +45 -0
- package/dist/src/product/config.js +83 -0
- package/dist/src/product/locale.js +38 -0
- package/dist/src/product/text.js +3 -0
- package/dist/src/telemetry/sls-sink.js +1 -2
- package/package.json +5 -5
- package/skills/gd-cli/references/update.md +2 -0
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
export class DeviceAuthorizationExpiredError extends Error {
|
|
2
|
-
constructor() {
|
|
3
|
-
super(
|
|
2
|
+
constructor(commandName = "gd-cli") {
|
|
3
|
+
super(`Authorization expired. Run ${commandName} auth login again.`);
|
|
4
4
|
this.name = "DeviceAuthorizationExpiredError";
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
|
+
export class AuthorizedAccountContextMissingError extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super("The authorized insMind account did not provide its account context.");
|
|
10
|
+
this.name = "AuthorizedAccountContextMissingError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
7
13
|
export function createAuthUseCases(dependencies) {
|
|
14
|
+
const insmind = dependencies.commandName === "insmind-cli";
|
|
8
15
|
return {
|
|
9
16
|
async status(input) {
|
|
10
17
|
const state = await dependencies.telemetry.stage("state", () => dependencies.store.read());
|
|
11
18
|
if (!state)
|
|
12
|
-
return {
|
|
19
|
+
return {
|
|
20
|
+
logged_in: false,
|
|
21
|
+
next_steps: [`${dependencies.commandName ?? "gd-cli"} auth login`]
|
|
22
|
+
};
|
|
13
23
|
dependencies.telemetry.annotate({
|
|
14
24
|
account_id: state.account?.id,
|
|
15
25
|
organization_id: state.organization?.id
|
|
@@ -21,7 +31,7 @@ export function createAuthUseCases(dependencies) {
|
|
|
21
31
|
user_id: state.account?.id ?? null,
|
|
22
32
|
name: state.account?.name ?? null
|
|
23
33
|
},
|
|
24
|
-
organization: state.organization
|
|
34
|
+
organization: !dependencies.requireOrganization && state.organization
|
|
25
35
|
? { id: state.organization.id, name: state.organization.name }
|
|
26
36
|
: null,
|
|
27
37
|
credential: {
|
|
@@ -30,10 +40,12 @@ export function createAuthUseCases(dependencies) {
|
|
|
30
40
|
scope: [...state.credential.scope]
|
|
31
41
|
},
|
|
32
42
|
next_steps: !valid
|
|
33
|
-
? ["gd-cli auth logout
|
|
34
|
-
:
|
|
43
|
+
? [`${dependencies.commandName ?? "gd-cli"} auth logout`, `${dependencies.commandName ?? "gd-cli"} auth login`]
|
|
44
|
+
: dependencies.requireOrganization
|
|
35
45
|
? []
|
|
36
|
-
:
|
|
46
|
+
: state.organization
|
|
47
|
+
? []
|
|
48
|
+
: ["gd-cli org list", "gd-cli org switch"]
|
|
37
49
|
};
|
|
38
50
|
},
|
|
39
51
|
async logout() {
|
|
@@ -42,7 +54,9 @@ export function createAuthUseCases(dependencies) {
|
|
|
42
54
|
async login(input) {
|
|
43
55
|
input.signal.throwIfAborted();
|
|
44
56
|
const existing = await dependencies.telemetry.stage("state", () => dependencies.store.read());
|
|
45
|
-
if (existing
|
|
57
|
+
if (existing
|
|
58
|
+
&& Date.parse(existing.credential.expiresAt) > dependencies.now().getTime()
|
|
59
|
+
&& (!dependencies.requireOrganization || existing.organization !== undefined)) {
|
|
46
60
|
dependencies.telemetry.annotate({
|
|
47
61
|
account_id: existing.account?.id,
|
|
48
62
|
organization_id: existing.organization?.id
|
|
@@ -62,7 +76,9 @@ export function createAuthUseCases(dependencies) {
|
|
|
62
76
|
await input.interaction.openBrowser(authorizationUrl);
|
|
63
77
|
}
|
|
64
78
|
catch {
|
|
65
|
-
input.interaction.warn(
|
|
79
|
+
input.interaction.warn(insmind
|
|
80
|
+
? "Could not open the browser. Open the authorization URL manually."
|
|
81
|
+
: "无法自动打开浏览器,请手动访问授权链接。");
|
|
66
82
|
}
|
|
67
83
|
}
|
|
68
84
|
return authorization;
|
|
@@ -72,7 +88,7 @@ export function createAuthUseCases(dependencies) {
|
|
|
72
88
|
const token = await dependencies.telemetry.stage("wait", async () => {
|
|
73
89
|
while (true) {
|
|
74
90
|
if (dependencies.now().getTime() + interval > deadline) {
|
|
75
|
-
throw new DeviceAuthorizationExpiredError();
|
|
91
|
+
throw new DeviceAuthorizationExpiredError(dependencies.commandName);
|
|
76
92
|
}
|
|
77
93
|
await dependencies.sleep(interval, input.signal);
|
|
78
94
|
input.signal.throwIfAborted();
|
|
@@ -91,7 +107,9 @@ export function createAuthUseCases(dependencies) {
|
|
|
91
107
|
credential: token.credential,
|
|
92
108
|
...(token.account ? { account: token.account } : {})
|
|
93
109
|
};
|
|
94
|
-
|
|
110
|
+
if (!dependencies.requireOrganization) {
|
|
111
|
+
await dependencies.telemetry.stage("state", () => dependencies.store.write(state));
|
|
112
|
+
}
|
|
95
113
|
dependencies.telemetry.annotate({ account_id: state.account?.id });
|
|
96
114
|
let organizationId = token.organizationId;
|
|
97
115
|
try {
|
|
@@ -99,7 +117,9 @@ export function createAuthUseCases(dependencies) {
|
|
|
99
117
|
organizationId = identity.organizationId ?? organizationId;
|
|
100
118
|
if (!state.account && identity.account) {
|
|
101
119
|
state = { ...state, account: identity.account };
|
|
102
|
-
|
|
120
|
+
if (!dependencies.requireOrganization) {
|
|
121
|
+
await dependencies.telemetry.stage("state", () => dependencies.store.write(state));
|
|
122
|
+
}
|
|
103
123
|
dependencies.telemetry.annotate({ account_id: identity.account.id });
|
|
104
124
|
}
|
|
105
125
|
}
|
|
@@ -108,6 +128,8 @@ export function createAuthUseCases(dependencies) {
|
|
|
108
128
|
throw input.signal.reason;
|
|
109
129
|
}
|
|
110
130
|
if (!organizationId) {
|
|
131
|
+
if (dependencies.requireOrganization)
|
|
132
|
+
throw new AuthorizedAccountContextMissingError();
|
|
111
133
|
input.interaction.warn("登录成功,请执行 gd-cli org switch 选择组织。");
|
|
112
134
|
return { status: "logged_in", organizationSelected: false };
|
|
113
135
|
}
|
|
@@ -118,6 +140,8 @@ export function createAuthUseCases(dependencies) {
|
|
|
118
140
|
signal: input.signal
|
|
119
141
|
})));
|
|
120
142
|
if (!organization) {
|
|
143
|
+
if (dependencies.requireOrganization)
|
|
144
|
+
throw new AuthorizedAccountContextMissingError();
|
|
121
145
|
input.interaction.warn("登录成功,但未能选择组织;请执行 gd-cli org switch。");
|
|
122
146
|
return { status: "logged_in", organizationSelected: false };
|
|
123
147
|
}
|
|
@@ -128,6 +152,8 @@ export function createAuthUseCases(dependencies) {
|
|
|
128
152
|
catch {
|
|
129
153
|
if (input.signal.aborted)
|
|
130
154
|
throw input.signal.reason;
|
|
155
|
+
if (dependencies.requireOrganization)
|
|
156
|
+
throw new AuthorizedAccountContextMissingError();
|
|
131
157
|
input.interaction.warn("登录成功,但组织绑定失败;请执行 gd-cli org switch。");
|
|
132
158
|
return { status: "logged_in", organizationSelected: false };
|
|
133
159
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { RemoteProtocolError } from "../../platform/remote-protocol.js";
|
|
2
|
+
export function createCreditsUseCases(options) {
|
|
3
|
+
return {
|
|
4
|
+
async get(input) {
|
|
5
|
+
const body = await options.transport.getJson({
|
|
6
|
+
path: "/trade/rights-overview",
|
|
7
|
+
query: { goods_type_enums: "GAODOU" },
|
|
8
|
+
credential: input.credential,
|
|
9
|
+
organizationId: input.organizationId,
|
|
10
|
+
signal: input.signal
|
|
11
|
+
});
|
|
12
|
+
const source = findCreditsRecord(body);
|
|
13
|
+
const total = number(source.total);
|
|
14
|
+
const used = number(source.used);
|
|
15
|
+
const pending = number(source.wait);
|
|
16
|
+
if (total === undefined || used === undefined || pending === undefined) {
|
|
17
|
+
throw new RemoteProtocolError();
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
credits: {
|
|
21
|
+
unit: options.billingUnit,
|
|
22
|
+
total,
|
|
23
|
+
used,
|
|
24
|
+
pending,
|
|
25
|
+
available: Math.max(0, total - used)
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function findCreditsRecord(value) {
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
const record = value.find((candidate) => (isRecord(candidate) && candidate.goods_type_enum === "GAODOU"));
|
|
34
|
+
if (!record)
|
|
35
|
+
throw new RemoteProtocolError();
|
|
36
|
+
return record;
|
|
37
|
+
}
|
|
38
|
+
if (!isRecord(value))
|
|
39
|
+
throw new RemoteProtocolError();
|
|
40
|
+
const candidates = [value.GAODOU, value.data, isRecord(value.data) ? value.data.GAODOU : undefined];
|
|
41
|
+
const record = candidates.find(isRecord);
|
|
42
|
+
if (!record)
|
|
43
|
+
throw new RemoteProtocolError();
|
|
44
|
+
return record;
|
|
45
|
+
}
|
|
46
|
+
function number(value) {
|
|
47
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
48
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
|
|
49
|
+
}
|
|
50
|
+
function isRecord(value) {
|
|
51
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
52
|
+
}
|
|
@@ -11,7 +11,7 @@ function requiredString(record, key) {
|
|
|
11
11
|
}
|
|
12
12
|
function optionalString(record, key) {
|
|
13
13
|
const value = record[key];
|
|
14
|
-
if (value === undefined)
|
|
14
|
+
if (value === undefined || value === null)
|
|
15
15
|
return undefined;
|
|
16
16
|
if (typeof value !== "string" || value.trim() === "")
|
|
17
17
|
throw new RemoteRequestError();
|
|
@@ -9,7 +9,10 @@ export async function runEditorBridgeProcess(targetOrigin, options = {}) {
|
|
|
9
9
|
const store = createEditorSessionStateStore({
|
|
10
10
|
...(options.homeDirectory === undefined
|
|
11
11
|
? {}
|
|
12
|
-
: { homeDirectory: options.homeDirectory })
|
|
12
|
+
: { homeDirectory: options.homeDirectory }),
|
|
13
|
+
...(options.stateDirectory === undefined
|
|
14
|
+
? {}
|
|
15
|
+
: { stateDirectory: options.stateDirectory })
|
|
13
16
|
});
|
|
14
17
|
const bridge = await (options.startBridge ?? startEditorBridge)({ targetOrigin });
|
|
15
18
|
const shutdown = () => {
|
|
@@ -52,7 +55,9 @@ export function startEditorBridgeProcess(input) {
|
|
|
52
55
|
child = (input.spawn ?? nodeSpawn)(process.execPath, [
|
|
53
56
|
fileURLToPath(new URL("./bridge-process.js", import.meta.url)),
|
|
54
57
|
input.targetOrigin,
|
|
55
|
-
...(input.
|
|
58
|
+
...(input.stateDirectory === undefined
|
|
59
|
+
? (input.homeDirectory === undefined ? [] : [input.homeDirectory])
|
|
60
|
+
: [input.homeDirectory ?? "", input.stateDirectory])
|
|
56
61
|
], {
|
|
57
62
|
detached: true,
|
|
58
63
|
stdio: ["ignore", "ignore", "ignore", "ipc"]
|
|
@@ -208,13 +213,15 @@ function hasExactKeys(value, keys) {
|
|
|
208
213
|
const ownPath = fileURLToPath(import.meta.url);
|
|
209
214
|
if (process.argv[1] && resolve(process.argv[1]) === resolve(ownPath)) {
|
|
210
215
|
const targetOrigin = process.argv[2];
|
|
211
|
-
const homeDirectory = process.argv[3];
|
|
216
|
+
const homeDirectory = process.argv[3] || undefined;
|
|
217
|
+
const stateDirectory = process.argv[4];
|
|
212
218
|
if (!targetOrigin) {
|
|
213
219
|
process.exitCode = 1;
|
|
214
220
|
}
|
|
215
221
|
else {
|
|
216
222
|
void runEditorBridgeProcess(targetOrigin, {
|
|
217
|
-
...(homeDirectory === undefined ? {} : { homeDirectory })
|
|
223
|
+
...(homeDirectory === undefined ? {} : { homeDirectory }),
|
|
224
|
+
...(stateDirectory === undefined ? {} : { stateDirectory })
|
|
218
225
|
}).catch(() => {
|
|
219
226
|
process.exitCode = 1;
|
|
220
227
|
});
|
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { createLocalJsonFile } from "../../platform/local-json-file.js";
|
|
4
4
|
export function createEditorSessionStateStore(options = {}) {
|
|
5
5
|
const file = options.file ?? createLocalJsonFile({
|
|
6
|
-
directory: join(options.homeDirectory ?? homedir(), ".gd"),
|
|
6
|
+
directory: join(options.homeDirectory ?? homedir(), options.stateDirectory ?? ".gd"),
|
|
7
7
|
filename: "editor-session.json"
|
|
8
8
|
});
|
|
9
9
|
const read = async () => {
|
|
@@ -14,8 +14,8 @@ export class EditorTargetError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
export class EditorSessionNotFoundError extends Error {
|
|
16
16
|
code = "EDITOR_SESSION_NOT_FOUND";
|
|
17
|
-
constructor() {
|
|
18
|
-
super(
|
|
17
|
+
constructor(commandName = "gd-cli") {
|
|
18
|
+
super(`当前没有可用的 Editor 会话,请重新执行 ${commandName} editor connect [target]。`);
|
|
19
19
|
this.name = "EditorSessionNotFoundError";
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -27,7 +27,7 @@ export class EditorNotReadyError extends Error {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
export function resolveEditorTarget(value) {
|
|
30
|
-
return resolveTarget(value, DEFAULT_CREATE_URL, DEFAULT_WORK_URL).href;
|
|
30
|
+
return resolveTarget(value, DEFAULT_CREATE_URL, DEFAULT_WORK_URL, false).href;
|
|
31
31
|
}
|
|
32
32
|
export function createEditorSession(dependencies) {
|
|
33
33
|
const startProcess = dependencies.startProcess ?? startEditorBridgeProcess;
|
|
@@ -37,7 +37,7 @@ export function createEditorSession(dependencies) {
|
|
|
37
37
|
return {
|
|
38
38
|
async connect(target, signal) {
|
|
39
39
|
signal.throwIfAborted();
|
|
40
|
-
const targetUrl = resolveTarget(target, dependencies.defaultCreateUrl, dependencies.workUrl);
|
|
40
|
+
const targetUrl = resolveTarget(target, dependencies.defaultCreateUrl, dependencies.workUrl, dependencies.restrictTargets === true);
|
|
41
41
|
await replaceCurrentSession(signal);
|
|
42
42
|
signal.throwIfAborted();
|
|
43
43
|
const state = await startProcess({
|
|
@@ -45,6 +45,9 @@ export function createEditorSession(dependencies) {
|
|
|
45
45
|
...(dependencies.homeDirectory === undefined
|
|
46
46
|
? {}
|
|
47
47
|
: { homeDirectory: dependencies.homeDirectory }),
|
|
48
|
+
...(dependencies.stateDirectory === undefined
|
|
49
|
+
? {}
|
|
50
|
+
: { stateDirectory: dependencies.stateDirectory }),
|
|
48
51
|
signal
|
|
49
52
|
});
|
|
50
53
|
const handoffUrl = buildHandoffUrl(targetUrl, state);
|
|
@@ -82,7 +85,7 @@ export function createEditorSession(dependencies) {
|
|
|
82
85
|
signal.throwIfAborted();
|
|
83
86
|
const state = await dependencies.store.read();
|
|
84
87
|
if (state === null)
|
|
85
|
-
throw new EditorSessionNotFoundError();
|
|
88
|
+
throw new EditorSessionNotFoundError(dependencies.commandName);
|
|
86
89
|
try {
|
|
87
90
|
return await createClient(state).rpc(method, payload, signal);
|
|
88
91
|
}
|
|
@@ -96,7 +99,7 @@ export function createEditorSession(dependencies) {
|
|
|
96
99
|
if (!(cause instanceof EditorBridgeUnavailableError))
|
|
97
100
|
throw cause;
|
|
98
101
|
await dependencies.store.removeIfOwned(state.token);
|
|
99
|
-
throw new EditorSessionNotFoundError();
|
|
102
|
+
throw new EditorSessionNotFoundError(dependencies.commandName);
|
|
100
103
|
}
|
|
101
104
|
async function replaceCurrentSession(signal) {
|
|
102
105
|
const current = await dependencies.store.read();
|
|
@@ -154,7 +157,7 @@ export function createEditorSession(dependencies) {
|
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
}
|
|
157
|
-
function resolveTarget(value, createUrl, workUrl) {
|
|
160
|
+
function resolveTarget(value, createUrl, workUrl, restrictTargets) {
|
|
158
161
|
if (value === undefined)
|
|
159
162
|
return new URL(createUrl.href);
|
|
160
163
|
if (/^\d+$/.test(value)) {
|
|
@@ -176,7 +179,10 @@ function resolveTarget(value, createUrl, workUrl) {
|
|
|
176
179
|
}
|
|
177
180
|
if ((url.protocol !== "http:" && url.protocol !== "https:") ||
|
|
178
181
|
url.username !== "" ||
|
|
179
|
-
url.password !== ""
|
|
182
|
+
url.password !== "" ||
|
|
183
|
+
(restrictTargets && (url.protocol !== "https:" ||
|
|
184
|
+
url.origin !== workUrl.origin ||
|
|
185
|
+
url.pathname !== workUrl.pathname))) {
|
|
180
186
|
throw new EditorTargetError();
|
|
181
187
|
}
|
|
182
188
|
return url;
|
|
@@ -98,6 +98,13 @@ export function createOrgUseCases(dependencies) {
|
|
|
98
98
|
if (!organizationId)
|
|
99
99
|
return null;
|
|
100
100
|
try {
|
|
101
|
+
if (dependencies.singleOrganizationName !== undefined) {
|
|
102
|
+
return await bind(input.credential, {
|
|
103
|
+
id: organizationId,
|
|
104
|
+
publicName: dependencies.singleOrganizationName,
|
|
105
|
+
realName: dependencies.singleOrganizationName
|
|
106
|
+
}, input.signal);
|
|
107
|
+
}
|
|
101
108
|
const records = await dependencies.org.list(input.credential, input.signal);
|
|
102
109
|
const record = records.find((candidate) => candidate.id === organizationId);
|
|
103
110
|
if (!record?.realName)
|
|
@@ -14,16 +14,17 @@ export function loadBundledSkills(startFile) {
|
|
|
14
14
|
catch (error) {
|
|
15
15
|
throw new SkillSyncError(`无法读取 @gaoding/cli package metadata: ${message(error)}`, []);
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
const product = packageProduct(metadata.name);
|
|
18
|
+
if (product) {
|
|
18
19
|
if (typeof metadata.version !== "string" || !metadata.version.trim()) {
|
|
19
|
-
throw new SkillSyncError(
|
|
20
|
+
throw new SkillSyncError(`${product.packageName} package version is invalid.`, []);
|
|
20
21
|
}
|
|
21
|
-
const sourceDirectory = join(directory, "skills",
|
|
22
|
+
const sourceDirectory = join(directory, "skills", product.skillName);
|
|
22
23
|
if (!isFile(join(sourceDirectory, "SKILL.md"))) {
|
|
23
24
|
throw new SkillSyncError(`Agent Skill source 不存在: ${sourceDirectory}`, []);
|
|
24
25
|
}
|
|
25
26
|
return [{
|
|
26
|
-
skill:
|
|
27
|
+
skill: product.skillName,
|
|
27
28
|
version: metadata.version,
|
|
28
29
|
sourceDirectory
|
|
29
30
|
}];
|
|
@@ -31,7 +32,14 @@ export function loadBundledSkills(startFile) {
|
|
|
31
32
|
}
|
|
32
33
|
directory = dirname(directory);
|
|
33
34
|
}
|
|
34
|
-
throw new SkillSyncError("
|
|
35
|
+
throw new SkillSyncError("Unable to locate a supported CLI package root.", []);
|
|
36
|
+
}
|
|
37
|
+
function packageProduct(name) {
|
|
38
|
+
if (name === "@gaoding/cli")
|
|
39
|
+
return { packageName: name, skillName: "gd-cli" };
|
|
40
|
+
if (name === "@insmind/cli")
|
|
41
|
+
return { packageName: name, skillName: "insmind-cli" };
|
|
42
|
+
return null;
|
|
35
43
|
}
|
|
36
44
|
function directoryFrom(path) {
|
|
37
45
|
const absolute = resolve(path);
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
export
|
|
2
|
-
constructor() {
|
|
3
|
-
super("Tool 输入不可用。");
|
|
4
|
-
this.name = "ToolInputError";
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
const tools = [
|
|
1
|
+
export const defaultToolDefinitions = [
|
|
8
2
|
{
|
|
9
3
|
name: "image.generate",
|
|
10
4
|
title: "图片生成与编辑",
|
|
@@ -21,11 +15,17 @@ const tools = [
|
|
|
21
15
|
description: "使用已发布的文本模型,根据文本和可选图片生成文本。"
|
|
22
16
|
}
|
|
23
17
|
];
|
|
24
|
-
|
|
18
|
+
export class ToolInputError extends Error {
|
|
19
|
+
constructor() {
|
|
20
|
+
super("Tool 输入不可用。");
|
|
21
|
+
this.name = "ToolInputError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function toolIndex(tool, tools) {
|
|
25
25
|
return tools.findIndex((candidate) => candidate.name === tool);
|
|
26
26
|
}
|
|
27
|
-
function compareModels(left, right) {
|
|
28
|
-
return toolIndex(left.tool) - toolIndex(right.tool)
|
|
27
|
+
function compareModels(left, right, tools) {
|
|
28
|
+
return toolIndex(left.tool, tools) - toolIndex(right.tool, tools)
|
|
29
29
|
|| left.sceneSort - right.sceneSort
|
|
30
30
|
|| left.modelSort - right.modelSort
|
|
31
31
|
|| (left.model < right.model ? -1 : left.model > right.model ? 1 : 0);
|
|
@@ -40,19 +40,19 @@ function summary(model) {
|
|
|
40
40
|
...(model.estimatedTime === undefined ? {} : { estimatedTime: model.estimatedTime })
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
export function projectToolList(catalog) {
|
|
43
|
+
export function projectToolList(catalog, tools = defaultToolDefinitions) {
|
|
44
44
|
return {
|
|
45
45
|
tools: tools.filter((tool) => catalog.models.some((model) => model.tool === tool.name))
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
-
export function projectModelList(catalog, tool) {
|
|
48
|
+
export function projectModelList(catalog, tool, tools = defaultToolDefinitions) {
|
|
49
49
|
if (tool !== undefined && !catalog.models.some((model) => model.tool === tool)) {
|
|
50
50
|
throw new ToolInputError();
|
|
51
51
|
}
|
|
52
52
|
return {
|
|
53
53
|
models: catalog.models
|
|
54
54
|
.filter((model) => tool === undefined || model.tool === tool)
|
|
55
|
-
.toSorted(compareModels)
|
|
55
|
+
.toSorted((left, right) => compareModels(left, right, tools))
|
|
56
56
|
.map(summary)
|
|
57
57
|
};
|
|
58
58
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseSafeRemoteAssetUrl, UrlSafetyError } from "../../platform/url-safety.js";
|
|
2
|
-
import { findToolModel, projectModelDetail, projectModelList, projectToolList, ToolInputError } from "./catalog.js";
|
|
2
|
+
import { findToolModel, defaultToolDefinitions, projectModelDetail, projectModelList, projectToolList, ToolInputError } from "./catalog.js";
|
|
3
3
|
import { assertModelArguments, buildToolArgumentsSchema } from "./dynamic-schema.js";
|
|
4
4
|
const mediaTypes = {
|
|
5
5
|
avif: "image/avif",
|
|
@@ -56,10 +56,10 @@ export function createToolUseCases(dependencies) {
|
|
|
56
56
|
}
|
|
57
57
|
return {
|
|
58
58
|
async listTools({ access, signal }) {
|
|
59
|
-
return projectToolList(await load(access, signal));
|
|
59
|
+
return projectToolList(await load(access, signal), dependencies.tools ?? defaultToolDefinitions);
|
|
60
60
|
},
|
|
61
61
|
async listModels({ input, access, signal }) {
|
|
62
|
-
return projectModelList(await load(access, signal), input.tool);
|
|
62
|
+
return projectModelList(await load(access, signal), input.tool, dependencies.tools ?? defaultToolDefinitions);
|
|
63
63
|
},
|
|
64
64
|
async getModel({ input, access, signal }) {
|
|
65
65
|
return projectModelDetail(await load(access, signal), input.model);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { valid } from "semver";
|
|
2
|
-
const
|
|
2
|
+
export const GAODING_REGISTRY_DIST_TAGS_URL = "https://registry.npmjs.org/@gaoding%2fcli";
|
|
3
3
|
export async function fetchLatestVersion(options) {
|
|
4
4
|
const headers = new Headers({
|
|
5
5
|
Accept: "application/vnd.npm.install-v1+json"
|
|
6
6
|
});
|
|
7
7
|
if (options.etag)
|
|
8
8
|
headers.set("If-None-Match", options.etag);
|
|
9
|
-
const response = await options.fetch(
|
|
9
|
+
const response = await options.fetch(options.registryUrl ?? GAODING_REGISTRY_DIST_TAGS_URL, {
|
|
10
10
|
headers,
|
|
11
11
|
redirect: "error",
|
|
12
12
|
signal: options.signal
|
|
@@ -13,7 +13,7 @@ export function createUpdateNotificationService(options) {
|
|
|
13
13
|
const fetchPackage = options.fetch ?? globalThis.fetch;
|
|
14
14
|
const now = options.now ?? (() => new Date());
|
|
15
15
|
const env = options.env ?? process.env;
|
|
16
|
-
const stateDirectory = join(options.homeDirectory ?? homedir(), ".gd");
|
|
16
|
+
const stateDirectory = join(options.homeDirectory ?? homedir(), options.stateDirectoryName ?? ".gd");
|
|
17
17
|
const stateFile = options.stateFile ?? createLocalJsonFile({
|
|
18
18
|
directory: stateDirectory,
|
|
19
19
|
filename: "update-check.json"
|
|
@@ -24,6 +24,8 @@ export function createUpdateNotificationService(options) {
|
|
|
24
24
|
});
|
|
25
25
|
return {
|
|
26
26
|
async check(signal) {
|
|
27
|
+
if ((options.installationOwner ?? "user") !== "user")
|
|
28
|
+
return;
|
|
27
29
|
if (env.CI === "true")
|
|
28
30
|
return;
|
|
29
31
|
let release = null;
|
|
@@ -50,6 +52,7 @@ export function createUpdateNotificationService(options) {
|
|
|
50
52
|
latestResult = await fetchLatestVersion({
|
|
51
53
|
fetch: fetchPackage,
|
|
52
54
|
signal: timeout.signal,
|
|
55
|
+
...(options.registryUrl ? { registryUrl: options.registryUrl } : {}),
|
|
53
56
|
...(state.etag ? { etag: state.etag } : {})
|
|
54
57
|
});
|
|
55
58
|
}
|
|
@@ -229,8 +232,14 @@ function withReminder(state, today, version) {
|
|
|
229
232
|
};
|
|
230
233
|
}
|
|
231
234
|
function notify(options, latest) {
|
|
232
|
-
options.
|
|
233
|
-
|
|
235
|
+
if (options.commandName && options.commandName !== "gd-cli") {
|
|
236
|
+
options.notify(`${options.commandName} ${latest} is available; current version is ` +
|
|
237
|
+
`${options.currentVersion}. Run ${options.commandName} update to update.`);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
options.notify(`gd-cli 有新版本 ${latest},当前版本为 ${options.currentVersion}。` +
|
|
241
|
+
"运行 gd-cli update 即可更新。");
|
|
242
|
+
}
|
|
234
243
|
}
|
|
235
244
|
function isNewer(version, currentVersion) {
|
|
236
245
|
return version !== undefined && valid(version) !== null && gt(version, currentVersion);
|