@wirecat/cli-core 0.463.0 → 0.465.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 +92 -0
- package/dist/background/after-upgrade.d.ts +51 -0
- package/dist/background/after-upgrade.js +245 -0
- package/dist/background/budget.d.ts +37 -0
- package/dist/background/budget.js +48 -0
- package/dist/background/index.d.ts +1 -0
- package/dist/background/index.js +1 -0
- package/dist/background/relay.js +5 -1
- package/dist/calendar/busy.d.ts +68 -0
- package/dist/calendar/busy.js +148 -0
- package/dist/calendar/cli/index.d.ts +7 -0
- package/dist/calendar/cli/index.js +98 -15
- package/dist/calendar/index.d.ts +3 -2
- package/dist/calendar/index.js +2 -1
- package/dist/calendar/mcp/index.d.ts +55 -13
- package/dist/calendar/mcp/index.js +19 -4
- package/dist/calendar/recurrence.d.ts +10 -0
- package/dist/calendar/recurrence.js +65 -0
- package/dist/calendar/source.d.ts +19 -0
- package/dist/calendar/sync.d.ts +86 -7
- package/dist/calendar/sync.js +271 -105
- package/dist/calendar/testing/source-contract.js +14 -0
- package/dist/calendar/testing/source.d.ts +6 -0
- package/dist/calendar/testing/source.js +30 -2
- package/dist/calendar/testing/sync-contract.d.ts +2 -1
- package/dist/calendar/testing/sync-contract.js +80 -5
- package/dist/calendar/testing/writer-contract.d.ts +2 -1
- package/dist/calendar/testing/writer-contract.js +46 -2
- package/dist/calendar/testing/writer.js +1 -1
- package/dist/calendar/writer.d.ts +7 -2
- package/dist/calendar/writes.d.ts +12 -2
- package/dist/calendar/writes.js +32 -6
- package/dist/cli/context.d.ts +8 -0
- package/dist/cli/context.js +15 -0
- package/dist/cli/execution.d.ts +7 -0
- package/dist/cli/execution.js +7 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/mcp-server-command.js +16 -10
- package/dist/cli/meeting-preparation-answer.d.ts +1 -0
- package/dist/cli/meeting-preparation-projects.d.ts +1 -0
- package/dist/cli/meeting-preparation-read.d.ts +1 -0
- package/dist/cli/messenger/chat-answers.js +1 -0
- package/dist/cli/messenger/search-answers.js +29 -4
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/program.js +15 -5
- package/dist/cli/upgrade-command.d.ts +7 -2
- package/dist/cli/upgrade-command.js +65 -9
- package/dist/entities/fields.d.ts +2 -0
- package/dist/entities/fields.js +2 -0
- package/dist/entities/hit.d.ts +1 -1
- package/dist/entities/hit.js +3 -3
- package/dist/entities/index.d.ts +10 -800
- package/dist/entities/index.js +37 -31
- package/dist/entities/meeting-preparation.d.ts +1 -0
- package/dist/entities/topic.d.ts +1 -0
- package/dist/entities/topic.js +2 -1
- package/dist/errors/reasons.js +4 -0
- package/dist/mcp/http/state.d.ts +14 -0
- package/dist/mcp/http/state.js +16 -0
- package/dist/mcp/meeting-preparation.d.ts +1 -0
- package/dist/meetings/answers.d.ts +2 -0
- package/dist/meetings/answers.js +3 -0
- package/dist/meetings/mcp/search.d.ts +1 -0
- package/dist/meetings/project-evidence-answer.d.ts +1 -0
- package/dist/ports/app.d.ts +5 -0
- package/dist/services/package-upgrade.d.ts +9 -0
- package/dist/services/package-upgrade.js +8 -1
- package/package.json +3 -3
package/dist/entities/index.js
CHANGED
|
@@ -33,7 +33,7 @@ import { document } from "./document.js";
|
|
|
33
33
|
import { documentLink } from "./document-link.js";
|
|
34
34
|
import { evidence } from "./evidence.js";
|
|
35
35
|
import { fact } from "./fact.js";
|
|
36
|
-
import { reference } from "./fields.js";
|
|
36
|
+
import { reference, untrusted } from "./fields.js";
|
|
37
37
|
import { folder } from "./folder.js";
|
|
38
38
|
import { hit } from "./hit.js";
|
|
39
39
|
import { identity } from "./identity.js";
|
|
@@ -101,21 +101,24 @@ export const ENTITIES = {
|
|
|
101
101
|
message_context: messageContext,
|
|
102
102
|
message_download: messageDownload,
|
|
103
103
|
message_sync: messageSync,
|
|
104
|
-
"message.hit": v.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
104
|
+
"message.hit": v.strictObject({
|
|
105
|
+
...v.pick(message, [
|
|
106
|
+
"ref",
|
|
107
|
+
"chat_title",
|
|
108
|
+
"timestamp",
|
|
109
|
+
"sender_name",
|
|
110
|
+
"excerpt",
|
|
111
|
+
"matched_in",
|
|
112
|
+
"score",
|
|
113
|
+
"match",
|
|
114
|
+
"corrections",
|
|
115
|
+
"found_in",
|
|
116
|
+
"discovery",
|
|
117
|
+
"context",
|
|
118
|
+
"email",
|
|
119
|
+
]).entries,
|
|
120
|
+
untrusted,
|
|
121
|
+
}),
|
|
119
122
|
"message.summary": v.pick(message, [
|
|
120
123
|
"ref",
|
|
121
124
|
"chat_id",
|
|
@@ -182,21 +185,24 @@ export const ENTITIES = {
|
|
|
182
185
|
call_record: callRecord,
|
|
183
186
|
sticker_inventory: stickerInventory,
|
|
184
187
|
note,
|
|
185
|
-
"note.hit": v.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
188
|
+
"note.hit": v.strictObject({
|
|
189
|
+
...v.pick(note, [
|
|
190
|
+
"kind",
|
|
191
|
+
"provider",
|
|
192
|
+
"account",
|
|
193
|
+
"url",
|
|
194
|
+
"ref",
|
|
195
|
+
"source",
|
|
196
|
+
"folder_id",
|
|
197
|
+
"path",
|
|
198
|
+
"title",
|
|
199
|
+
"updated_at",
|
|
200
|
+
"excerpt",
|
|
201
|
+
"found_by",
|
|
202
|
+
"links",
|
|
203
|
+
]).entries,
|
|
204
|
+
untrusted,
|
|
205
|
+
}),
|
|
200
206
|
hit,
|
|
201
207
|
document,
|
|
202
208
|
document_link: documentLink,
|
|
@@ -320,6 +320,7 @@ export declare const meetingPreparation: v.StrictObjectSchema<{
|
|
|
320
320
|
readonly started_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
321
321
|
readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
322
322
|
}, undefined>, undefined>;
|
|
323
|
+
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
323
324
|
}, undefined>, undefined>;
|
|
324
325
|
readonly association: v.LiteralSchema<"unconfirmed-word-evidence", undefined>;
|
|
325
326
|
readonly vocabulary_fingerprint: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
package/dist/entities/topic.d.ts
CHANGED
|
@@ -10,4 +10,5 @@ export declare const topic: v.StrictObjectSchema<{
|
|
|
10
10
|
readonly unread_count: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
11
11
|
readonly last_message_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
12
12
|
readonly created_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
13
|
+
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
13
14
|
}, undefined>;
|
package/dist/entities/topic.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
|
-
import { count, id, text, time } from "./fields.js";
|
|
2
|
+
import { count, id, text, time, untrusted } from "./fields.js";
|
|
3
3
|
/** A topic of a forum group; its messages carry this id as their thread. */
|
|
4
4
|
export const topic = v.strictObject({
|
|
5
5
|
id,
|
|
@@ -11,4 +11,5 @@ export const topic = v.strictObject({
|
|
|
11
11
|
unread_count: v.nullable(count),
|
|
12
12
|
last_message_at: v.nullable(time),
|
|
13
13
|
created_at: v.nullable(time),
|
|
14
|
+
untrusted,
|
|
14
15
|
});
|
package/dist/errors/reasons.js
CHANGED
|
@@ -56,6 +56,10 @@ const OTHERS = [
|
|
|
56
56
|
["sync.partial", "provider_error", "some calendars did not sync; the next sync retries them"],
|
|
57
57
|
validation("event.conflict", "the event changed at the provider since the last sync; sync and try again"),
|
|
58
58
|
validation("event.notify-required", "the event has other attendees; say whether to notify them"),
|
|
59
|
+
validation("event.repeat-invalid", "the repeat rule is outside the RFC 5545 subset the calendars take"),
|
|
60
|
+
validation("event.repeat-kind", "the series' rule holds a date or a time of the kind the event is leaving"),
|
|
61
|
+
["busy.offline", "permission_error", "calendars busy asks the provider; --offline was given"],
|
|
62
|
+
validation("busy.no-calendars", "no calendar syncs and no person was named, so there is nobody to ask"),
|
|
59
63
|
validation("store.refused", "the local store refused the document text"),
|
|
60
64
|
["store.not-writable", "permission_error", "the message store cannot be written from this process"],
|
|
61
65
|
[
|
package/dist/mcp/http/state.d.ts
CHANGED
|
@@ -33,6 +33,20 @@ export declare const mcpHttpMarker: (envPrefix: string) => string;
|
|
|
33
33
|
export declare const httpLogFile: (tokenFile: string) => string;
|
|
34
34
|
/** Beside the token file, so each CLI's own layout decides both: `mcp-http/<profile>.json` → `.lock`. */
|
|
35
35
|
export declare const httpLockFile: (tokenFile: string) => string;
|
|
36
|
+
/**
|
|
37
|
+
* What `mcp server start` ran, so `upgrade` can run it again on the new code with the same port, address and
|
|
38
|
+
* `--permission` limits. Without it a server is left running rather than restarted with fewer limits.
|
|
39
|
+
*/
|
|
40
|
+
export interface HttpLaunch {
|
|
41
|
+
/** What a person types before `mcp`: `tg personal`. */
|
|
42
|
+
invocation: string;
|
|
43
|
+
/** The words after the CLI's entry. */
|
|
44
|
+
argv: string[];
|
|
45
|
+
/** Added to the inherited environment: the marker, and the profile for a CLI that reads it from there. */
|
|
46
|
+
env: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
export declare const httpLaunchFile: (lockFile: string) => string;
|
|
49
|
+
export declare const readHttpLaunch: (path: string) => HttpLaunch | undefined;
|
|
36
50
|
export declare const readHttpLock: (path: string) => HttpLock | undefined;
|
|
37
51
|
/** The lock a serving process keeps current: taken before it listens, given up when it closes. */
|
|
38
52
|
export declare const httpLock: (path: string, first: HttpLock) => {
|
package/dist/mcp/http/state.js
CHANGED
|
@@ -9,6 +9,22 @@ export const mcpHttpMarker = (envPrefix) => `${envPrefix}_MCP_HTTP`;
|
|
|
9
9
|
export const httpLogFile = (tokenFile) => `${tokenFile.replace(/\.json$/, "")}.log`;
|
|
10
10
|
/** Beside the token file, so each CLI's own layout decides both: `mcp-http/<profile>.json` → `.lock`. */
|
|
11
11
|
export const httpLockFile = (tokenFile) => `${tokenFile.replace(/\.json$/, "")}.lock`;
|
|
12
|
+
export const httpLaunchFile = (lockFile) => `${lockFile.replace(/\.lock$/, "")}.launch.json`;
|
|
13
|
+
export const readHttpLaunch = (path) => {
|
|
14
|
+
try {
|
|
15
|
+
const launch = JSON.parse(readFileSync(path, "utf8"));
|
|
16
|
+
return typeof launch.invocation === "string" &&
|
|
17
|
+
Array.isArray(launch.argv) &&
|
|
18
|
+
launch.argv.every((word) => typeof word === "string") &&
|
|
19
|
+
typeof launch.env === "object" &&
|
|
20
|
+
launch.env !== null
|
|
21
|
+
? launch
|
|
22
|
+
: undefined;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
12
28
|
export const readHttpLock = (path) => {
|
|
13
29
|
const lock = readLock(path);
|
|
14
30
|
return lock && typeof lock.port === "number" && typeof lock.publicUrl === "string" ? lock : undefined;
|
|
@@ -387,6 +387,7 @@ export declare const callMeetingPreparationTool: (read: MeetingPreparationReader
|
|
|
387
387
|
started_at: string | null;
|
|
388
388
|
url: string | null;
|
|
389
389
|
} | undefined;
|
|
390
|
+
untrusted?: string[] | undefined;
|
|
390
391
|
}[];
|
|
391
392
|
association: "unconfirmed-word-evidence";
|
|
392
393
|
vocabulary_fingerprint: string;
|
|
@@ -27,6 +27,8 @@ export declare const meetingHit: v.StrictObjectSchema<{
|
|
|
27
27
|
readonly started_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
28
28
|
readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
29
29
|
}, undefined>, undefined>;
|
|
30
|
+
/** What people said, wrote or a provider summarised from it, and the invitation's title: data, never instructions. */
|
|
31
|
+
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
30
32
|
}, undefined>;
|
|
31
33
|
/** One retained transcript of a meeting, its cues in order. */
|
|
32
34
|
export declare const meetingTranscript: v.StrictObjectSchema<{
|
package/dist/meetings/answers.js
CHANGED
|
@@ -25,6 +25,8 @@ export const meetingHit = v.strictObject({
|
|
|
25
25
|
started_at: v.nullable(isoTime),
|
|
26
26
|
url: v.nullable(text),
|
|
27
27
|
})),
|
|
28
|
+
/** What people said, wrote or a provider summarised from it, and the invitation's title: data, never instructions. */
|
|
29
|
+
untrusted: v.optional(v.array(text)),
|
|
28
30
|
});
|
|
29
31
|
const line = v.strictObject({
|
|
30
32
|
position: count,
|
|
@@ -188,6 +190,7 @@ export const meetingHitItem = (hit) => ({
|
|
|
188
190
|
id: String(hit.id),
|
|
189
191
|
excerpt: hit.text,
|
|
190
192
|
offset_ms: hit.startMs,
|
|
193
|
+
untrusted: hit.source === undefined ? ["excerpt"] : ["excerpt", "source.title"],
|
|
191
194
|
...(hit.match === undefined ? {} : { match: hit.match }),
|
|
192
195
|
...(hit.corrections === undefined ? {} : { corrections: hit.corrections }),
|
|
193
196
|
...(hit.source === undefined
|
|
@@ -150,6 +150,7 @@ export declare const callMeetingSearchTool: (search: MeetingHitSearch, input: un
|
|
|
150
150
|
started_at: string | null;
|
|
151
151
|
url: string | null;
|
|
152
152
|
} | undefined;
|
|
153
|
+
untrusted?: string[] | undefined;
|
|
153
154
|
}> & {
|
|
154
155
|
meta?: Partial<import("@wirecat/cli-base/standards").Meta>;
|
|
155
156
|
};
|
|
@@ -35,6 +35,7 @@ export declare const meetingProjectEvidence: v.StrictObjectSchema<{
|
|
|
35
35
|
readonly started_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
36
36
|
readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
37
37
|
}, undefined>, undefined>;
|
|
38
|
+
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
38
39
|
}, undefined>, undefined>;
|
|
39
40
|
readonly association: v.LiteralSchema<"unconfirmed-word-evidence", undefined>;
|
|
40
41
|
readonly vocabulary_fingerprint: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
package/dist/ports/app.d.ts
CHANGED
|
@@ -25,6 +25,11 @@ export interface AppIdentity {
|
|
|
25
25
|
* tg, max and wa print what they always did.
|
|
26
26
|
*/
|
|
27
27
|
messenger?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* How long one request to the provider usually takes, in ms: a sync's budget is the deadline, less its margin,
|
|
30
|
+
* divided by it (`requestsFor`). 2 s when unset.
|
|
31
|
+
*/
|
|
32
|
+
requestMs?: number;
|
|
28
33
|
/** Error reasons and detail keys of its own — max's `max_code`, tg's `migrated_chat_id` — kept in every error it prints. */
|
|
29
34
|
errors?: AppErrors;
|
|
30
35
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { type Installer } from "@wirecat/cli-base/update";
|
|
2
|
+
import type { McpServerRestart, StaleSessions } from "../background/after-upgrade.js";
|
|
2
3
|
export interface ServerRestarts {
|
|
3
4
|
restarted: readonly string[];
|
|
4
5
|
left: readonly string[];
|
|
6
|
+
/** Profiles whose `server restart` ran out of time: their serve may still run the old code. */
|
|
7
|
+
timedOut?: readonly string[];
|
|
8
|
+
mcpServers?: readonly McpServerRestart[];
|
|
9
|
+
staleSessions?: StaleSessions;
|
|
5
10
|
}
|
|
6
11
|
export interface PackageUpgradePorts {
|
|
7
12
|
installer: () => Installer;
|
|
@@ -22,6 +27,9 @@ export interface PackageUpgradeResult {
|
|
|
22
27
|
command: string | null;
|
|
23
28
|
updated: boolean;
|
|
24
29
|
restarted: readonly string[];
|
|
30
|
+
/** Set once an install ran: the HTTP MCP servers it restarted or left, and the stdio sessions still on old code. */
|
|
31
|
+
mcpServers?: readonly McpServerRestart[];
|
|
32
|
+
staleSessions?: StaleSessions;
|
|
25
33
|
installed?: string;
|
|
26
34
|
/** The package manager installed an older version than `latest`. */
|
|
27
35
|
heldBack?: {
|
|
@@ -33,6 +41,7 @@ export interface PackageUpgradeOutcome {
|
|
|
33
41
|
result: PackageUpgradeResult;
|
|
34
42
|
reason: "checked" | "unavailable" | "up_to_date" | "manual" | "updated" | "held_back";
|
|
35
43
|
left: readonly string[];
|
|
44
|
+
timedOut?: readonly string[];
|
|
36
45
|
}
|
|
37
46
|
export declare const upgradePackage: ({ current, command, packageName, check }: {
|
|
38
47
|
current: string;
|
|
@@ -47,9 +47,16 @@ export const upgradePackage = async ({ current, command, packageName, check }, p
|
|
|
47
47
|
const restart = async (result, ports) => {
|
|
48
48
|
const servers = await ports.afterUpdate?.();
|
|
49
49
|
return {
|
|
50
|
-
result: {
|
|
50
|
+
result: {
|
|
51
|
+
...result,
|
|
52
|
+
updated: true,
|
|
53
|
+
restarted: servers?.restarted ?? [],
|
|
54
|
+
...(servers?.mcpServers ? { mcpServers: servers.mcpServers } : {}),
|
|
55
|
+
...(servers?.staleSessions ? { staleSessions: servers.staleSessions } : {}),
|
|
56
|
+
},
|
|
51
57
|
reason: "updated",
|
|
52
58
|
left: servers?.left ?? [],
|
|
59
|
+
...(servers?.timedOut?.length ? { timedOut: servers.timedOut } : {}),
|
|
53
60
|
};
|
|
54
61
|
};
|
|
55
62
|
const dayAfter = (published) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.465.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
"@types/node": "^22.20.5",
|
|
234
234
|
"@types/pngjs": "6.0.5",
|
|
235
235
|
"@vitest/coverage-v8": "^5.0.5",
|
|
236
|
-
"@wirecat/cli-base": "0.
|
|
236
|
+
"@wirecat/cli-base": "0.64.0",
|
|
237
237
|
"@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
|
|
238
238
|
"commander": "15.0.0",
|
|
239
239
|
"cspell": "10.4.0",
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
},
|
|
254
254
|
"peerDependencies": {
|
|
255
255
|
"@napi-rs/canvas": ">=0.1.69 <2",
|
|
256
|
-
"@wirecat/cli-base": ">=0.
|
|
256
|
+
"@wirecat/cli-base": ">=0.64.0 <0.65.0",
|
|
257
257
|
"commander": ">=15"
|
|
258
258
|
},
|
|
259
259
|
"bin": {
|