@autonoelle/wsg-active 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -0
- package/dist/activeRestClient.d.ts +30 -0
- package/dist/activeRestClient.d.ts.map +1 -0
- package/dist/activeRestClient.js +130 -0
- package/dist/activeRestClient.js.map +1 -0
- package/dist/args.d.ts +33 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +83 -0
- package/dist/args.js.map +1 -0
- package/dist/commands/account.d.ts +23 -0
- package/dist/commands/account.d.ts.map +1 -0
- package/dist/commands/account.js +322 -0
- package/dist/commands/account.js.map +1 -0
- package/dist/commands/auth.d.ts +8 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +30 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/battleMap.d.ts +4 -0
- package/dist/commands/battleMap.d.ts.map +1 -0
- package/dist/commands/battleMap.js +146 -0
- package/dist/commands/battleMap.js.map +1 -0
- package/dist/commands/config.d.ts +11 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +125 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/discovery.d.ts +2 -0
- package/dist/commands/discovery.d.ts.map +1 -0
- package/dist/commands/discovery.js +109 -0
- package/dist/commands/discovery.js.map +1 -0
- package/dist/commands/functions.d.ts +26 -0
- package/dist/commands/functions.d.ts.map +1 -0
- package/dist/commands/functions.js +237 -0
- package/dist/commands/functions.js.map +1 -0
- package/dist/commands/mission.d.ts +7 -0
- package/dist/commands/mission.d.ts.map +1 -0
- package/dist/commands/mission.js +217 -0
- package/dist/commands/mission.js.map +1 -0
- package/dist/commands/session.d.ts +7 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +17 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/shipPipe.d.ts +11 -0
- package/dist/commands/shipPipe.d.ts.map +1 -0
- package/dist/commands/shipPipe.js +127 -0
- package/dist/commands/shipPipe.js.map +1 -0
- package/dist/commands/snapshot.d.ts +33 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +308 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/store.d.ts +32 -0
- package/dist/commands/store.d.ts.map +1 -0
- package/dist/commands/store.js +212 -0
- package/dist/commands/store.js.map +1 -0
- package/dist/configTypes.d.ts +131 -0
- package/dist/configTypes.d.ts.map +1 -0
- package/dist/configTypes.js +152 -0
- package/dist/configTypes.js.map +1 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +42 -0
- package/dist/errors.js.map +1 -0
- package/dist/gameDataClient.d.ts +15 -0
- package/dist/gameDataClient.d.ts.map +1 -0
- package/dist/gameDataClient.js +63 -0
- package/dist/gameDataClient.js.map +1 -0
- package/dist/graphqlClient.d.ts +32 -0
- package/dist/graphqlClient.d.ts.map +1 -0
- package/dist/graphqlClient.js +106 -0
- package/dist/graphqlClient.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +712 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +24 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +39 -0
- package/dist/output.js.map +1 -0
- package/dist/security.d.ts +3 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +41 -0
- package/dist/security.js.map +1 -0
- package/dist/tests/clients.test.d.ts +2 -0
- package/dist/tests/clients.test.d.ts.map +1 -0
- package/dist/tests/clients.test.js +31 -0
- package/dist/tests/clients.test.js.map +1 -0
- package/dist/tests/security.test.d.ts +2 -0
- package/dist/tests/security.test.d.ts.map +1 -0
- package/dist/tests/security.test.js +26 -0
- package/dist/tests/security.test.js.map +1 -0
- package/dist/tests/shipPipe.test.d.ts +2 -0
- package/dist/tests/shipPipe.test.d.ts.map +1 -0
- package/dist/tests/shipPipe.test.js +31 -0
- package/dist/tests/shipPipe.test.js.map +1 -0
- package/dist/tests/validation.test.d.ts +2 -0
- package/dist/tests/validation.test.d.ts.map +1 -0
- package/dist/tests/validation.test.js +58 -0
- package/dist/tests/validation.test.js.map +1 -0
- package/dist/tests/writeWorkflow.test.d.ts +2 -0
- package/dist/tests/writeWorkflow.test.d.ts.map +1 -0
- package/dist/tests/writeWorkflow.test.js +22 -0
- package/dist/tests/writeWorkflow.test.js.map +1 -0
- package/dist/validation.d.ts +18 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +328 -0
- package/dist/validation.js.map +1 -0
- package/dist/writeWorkflow.d.ts +16 -0
- package/dist/writeWorkflow.d.ts.map +1 -0
- package/dist/writeWorkflow.js +135 -0
- package/dist/writeWorkflow.js.map +1 -0
- package/package.json +31 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command, CommanderError } from 'commander';
|
|
3
|
+
import { resolveGlobalOptions, toActiveBaseOpts, toActiveOpts, toGameDataOpts, toGqlOpts, } from './args.js';
|
|
4
|
+
import { errorFromUnknown, exitWith, success } from './output.js';
|
|
5
|
+
import { CliError } from './errors.js';
|
|
6
|
+
import { createWritePlan, readJsonObject } from './writeWorkflow.js';
|
|
7
|
+
import { storeTypes } from './configTypes.js';
|
|
8
|
+
import * as authCmd from './commands/auth.js';
|
|
9
|
+
import * as accountCmd from './commands/account.js';
|
|
10
|
+
import * as battleMapCmd from './commands/battleMap.js';
|
|
11
|
+
import * as configCmd from './commands/config.js';
|
|
12
|
+
import * as discoveryCmd from './commands/discovery.js';
|
|
13
|
+
import * as missionCmd from './commands/mission.js';
|
|
14
|
+
import * as sessionCmd from './commands/session.js';
|
|
15
|
+
import * as shipPipeCmd from './commands/shipPipe.js';
|
|
16
|
+
import * as snapshotCmd from './commands/snapshot.js';
|
|
17
|
+
import * as storeCmd from './commands/store.js';
|
|
18
|
+
import * as functionsCmd from './commands/functions.js';
|
|
19
|
+
function parseOptionalNumber(value, name) {
|
|
20
|
+
if (value === undefined) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const parsed = Number.parseInt(value, 10);
|
|
24
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed.toString() !== value.trim()) {
|
|
25
|
+
throw new Error(`Invalid ${name}: ${value}`);
|
|
26
|
+
}
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
function parseCsv(value) {
|
|
30
|
+
if (!value) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const items = value.split(',').map((item) => item.trim()).filter(Boolean);
|
|
34
|
+
return items.length > 0 ? items : undefined;
|
|
35
|
+
}
|
|
36
|
+
const program = new Command()
|
|
37
|
+
.name('autonoelle-wsg-active')
|
|
38
|
+
.description('AutoNoelle WSG Active CLI')
|
|
39
|
+
.option('--query-url <url>', 'GraphQL endpoint (UserConfig)', process.env.AUTO_NOELLE_QUERY_URL)
|
|
40
|
+
.option('--active-url <url>', 'Active API base URL', process.env.AUTO_NOELLE_ACTIVE_URL)
|
|
41
|
+
.option('--data-url <url>', 'GameData GraphQL endpoint', process.env.AUTO_NOELLE_DATA_URL)
|
|
42
|
+
.option('--k <key>', 'User key')
|
|
43
|
+
.option('--scope <scope>', 'Region scope (CN|JP)', process.env.AUTO_NOELLE_SCOPE ?? 'CN')
|
|
44
|
+
.option('--timeout <ms>', 'HTTP timeout in milliseconds', process.env.AUTO_NOELLE_TIMEOUT_MS)
|
|
45
|
+
.showHelpAfterError(true)
|
|
46
|
+
.exitOverride();
|
|
47
|
+
const auth = program.command('auth');
|
|
48
|
+
auth
|
|
49
|
+
.command('login')
|
|
50
|
+
.description('Login and verify the AutoNoelle user key (credential is redacted in output)')
|
|
51
|
+
.option('--username <name>', 'Login username')
|
|
52
|
+
.option('--password <password>', 'Login password')
|
|
53
|
+
.option('--servername <name>', 'Server name')
|
|
54
|
+
.action(async (opts) => {
|
|
55
|
+
try {
|
|
56
|
+
const g = resolveGlobalOptions(program.opts());
|
|
57
|
+
const username = opts.username ?? process.env.AUTO_NOELLE_USERNAME;
|
|
58
|
+
const password = opts.password ?? process.env.AUTO_NOELLE_PASSWORD;
|
|
59
|
+
const servername = opts.servername ?? process.env.AUTO_NOELLE_SERVERNAME;
|
|
60
|
+
if (!username)
|
|
61
|
+
throw new Error('Missing username. Use --username or AUTO_NOELLE_USERNAME.');
|
|
62
|
+
if (!password)
|
|
63
|
+
throw new Error('Missing password. Use --password or AUTO_NOELLE_PASSWORD.');
|
|
64
|
+
if (!servername)
|
|
65
|
+
throw new Error('Missing servername. Use --servername or AUTO_NOELLE_SERVERNAME.');
|
|
66
|
+
const data = await authCmd.login(toActiveBaseOpts(g), { username, password, servername });
|
|
67
|
+
exitWith(success(data));
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
exitWith(errorFromUnknown(e));
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const config = program.command('config');
|
|
74
|
+
config
|
|
75
|
+
.command('types')
|
|
76
|
+
.description('List supported config and store types')
|
|
77
|
+
.action(async () => {
|
|
78
|
+
try {
|
|
79
|
+
exitWith(success(await configCmd.listTypes()));
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
exitWith(errorFromUnknown(e));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
config
|
|
86
|
+
.command('schema')
|
|
87
|
+
.description('Describe configuration fields, semantics, credential rules, and ID types')
|
|
88
|
+
.option('--type <name>', 'MissionBattleConfig, ShipPipe, TeamPipe, BattleMapConfig, credentials, or ids')
|
|
89
|
+
.action((opts) => {
|
|
90
|
+
try {
|
|
91
|
+
exitWith(success(discoveryCmd.schema(opts.type)));
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
exitWith(errorFromUnknown(e));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
config
|
|
98
|
+
.command('get')
|
|
99
|
+
.description('Get a config object')
|
|
100
|
+
.requiredOption('--type <name>', 'Config type name')
|
|
101
|
+
.action(async (opts) => {
|
|
102
|
+
try {
|
|
103
|
+
const g = resolveGlobalOptions(program.opts());
|
|
104
|
+
const data = await configCmd.getConfig(await toGqlOpts(g), opts.type);
|
|
105
|
+
exitWith(success(data));
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
exitWith(errorFromUnknown(e));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
config
|
|
112
|
+
.command('set')
|
|
113
|
+
.description('Safely merge and set a config object from a JSON file')
|
|
114
|
+
.requiredOption('--type <name>', 'Config type name')
|
|
115
|
+
.requiredOption('--file <path>', 'JSON file path')
|
|
116
|
+
.option('--dry-run', 'Read, merge, and validate without writing')
|
|
117
|
+
.option('--replace', 'Replace instead of preserving fields omitted from the input')
|
|
118
|
+
.option('--expect-current-hash <sha256>', 'Reject the write if the server value changed')
|
|
119
|
+
.action(async (opts) => {
|
|
120
|
+
try {
|
|
121
|
+
const g = resolveGlobalOptions(program.opts());
|
|
122
|
+
const data = await configCmd.setConfig(await toGqlOpts(g), opts.type, opts.file, {
|
|
123
|
+
dryRun: Boolean(opts.dryRun),
|
|
124
|
+
replace: Boolean(opts.replace),
|
|
125
|
+
expectedCurrentHash: opts.expectCurrentHash,
|
|
126
|
+
});
|
|
127
|
+
exitWith(success(data));
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
exitWith(errorFromUnknown(e));
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
const store = program.command('store');
|
|
134
|
+
for (const [typeName, def] of Object.entries(storeTypes)) {
|
|
135
|
+
const key = typeName;
|
|
136
|
+
const api = storeCmd.storeApi[key];
|
|
137
|
+
const sub = store.command(typeName)
|
|
138
|
+
.description(def.description);
|
|
139
|
+
sub
|
|
140
|
+
.command('list')
|
|
141
|
+
.description('列出所有条目')
|
|
142
|
+
.action(async () => {
|
|
143
|
+
try {
|
|
144
|
+
const g = resolveGlobalOptions(program.opts());
|
|
145
|
+
const data = await api.list(await toGqlOpts(g));
|
|
146
|
+
exitWith(success(data));
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
exitWith(errorFromUnknown(e));
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
sub
|
|
153
|
+
.command('get')
|
|
154
|
+
.description('获取一个条目')
|
|
155
|
+
.requiredOption('--id <guid>', '条目 UUID')
|
|
156
|
+
.action(async (opts) => {
|
|
157
|
+
try {
|
|
158
|
+
const g = resolveGlobalOptions(program.opts());
|
|
159
|
+
const data = await api.get(await toGqlOpts(g), opts.id);
|
|
160
|
+
exitWith(success(data));
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
exitWith(errorFromUnknown(e));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
sub
|
|
167
|
+
.command('set')
|
|
168
|
+
.description('安全创建或更新条目(从 JSON 文件)')
|
|
169
|
+
.requiredOption('--file <path>', 'JSON 文件路径')
|
|
170
|
+
.option('--dry-run', '读取、合并并校验,不写入')
|
|
171
|
+
.option('--replace', '替换,而不是保留输入中省略的字段')
|
|
172
|
+
.option('--expect-current-hash <sha256>', '服务端值变化时拒绝写入')
|
|
173
|
+
.action(async (opts) => {
|
|
174
|
+
try {
|
|
175
|
+
const g = resolveGlobalOptions(program.opts());
|
|
176
|
+
const data = await api.set(await toGqlOpts(g), opts.file, {
|
|
177
|
+
dryRun: Boolean(opts.dryRun),
|
|
178
|
+
replace: Boolean(opts.replace),
|
|
179
|
+
expectedCurrentHash: opts.expectCurrentHash,
|
|
180
|
+
});
|
|
181
|
+
exitWith(success(data));
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
exitWith(errorFromUnknown(e));
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
sub
|
|
188
|
+
.command('remove')
|
|
189
|
+
.description('安全删除一个条目(支持预检和并发保护)')
|
|
190
|
+
.requiredOption('--id <guid>', '条目 UUID')
|
|
191
|
+
.option('--dry-run', '读取并校验删除计划,不写入')
|
|
192
|
+
.option('--expect-current-hash <sha256>', '服务端值变化时拒绝删除')
|
|
193
|
+
.option('--force', '即使仍被其他配置引用也删除(高风险)')
|
|
194
|
+
.action(async (opts) => {
|
|
195
|
+
try {
|
|
196
|
+
const g = resolveGlobalOptions(program.opts());
|
|
197
|
+
const gqlOpts = await toGqlOpts(g);
|
|
198
|
+
const references = await storeCmd.findReferences(gqlOpts, typeName, opts.id);
|
|
199
|
+
if (references.length > 0 && !opts.force) {
|
|
200
|
+
throw new CliError('STORE_ITEM_REFERENCED', `${typeName} cannot be removed while other configurations reference it.`, {
|
|
201
|
+
category: 'conflict',
|
|
202
|
+
details: { id: opts.id, references, dryRun: Boolean(opts.dryRun) },
|
|
203
|
+
suggestion: 'Update and verify every owner first. Use --force only with explicit approval and a recovery plan.',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const data = await api.remove(gqlOpts, opts.id, {
|
|
207
|
+
dryRun: Boolean(opts.dryRun),
|
|
208
|
+
expectedCurrentHash: opts.expectCurrentHash,
|
|
209
|
+
});
|
|
210
|
+
exitWith(success({ ...data, references, forced: Boolean(opts.force) }));
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
exitWith(errorFromUnknown(e));
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
if (typeName === 'ShipPipe') {
|
|
217
|
+
sub
|
|
218
|
+
.command('preview')
|
|
219
|
+
.description('Preview which owned ship instances a ShipPipe matches')
|
|
220
|
+
.option('--id <guid>', 'Read an existing ShipPipe by UUID')
|
|
221
|
+
.option('--file <path>', 'Preview a complete ShipPipe JSON file')
|
|
222
|
+
.option('--first <number>', 'Maximum returned matches, default 20', '20')
|
|
223
|
+
.option('--all', 'Return all matching ships')
|
|
224
|
+
.option('--fields <csv>', 'Return selected owned-ship fields')
|
|
225
|
+
.action(async (opts) => {
|
|
226
|
+
try {
|
|
227
|
+
if (Boolean(opts.id) === Boolean(opts.file)) {
|
|
228
|
+
throw new CliError('PIPE_SOURCE_REQUIRED', 'Specify exactly one of --id or --file.', {
|
|
229
|
+
category: 'usage',
|
|
230
|
+
details: { expected: ['--id <guid>', '--file <path>'] },
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const g = resolveGlobalOptions(program.opts());
|
|
234
|
+
const pipe = opts.id
|
|
235
|
+
? await api.get(await toGqlOpts(g), opts.id)
|
|
236
|
+
: shipPipeCmd.readShipPipeFile(opts.file);
|
|
237
|
+
if (pipe === null || pipe === undefined) {
|
|
238
|
+
throw new CliError('SHIP_PIPE_NOT_FOUND', `ShipPipe not found: ${opts.id}`, {
|
|
239
|
+
category: 'not_found',
|
|
240
|
+
field: 'id',
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
const data = await shipPipeCmd.preview(await toActiveOpts(g), toGameDataOpts(g), pipe, {
|
|
244
|
+
first: parseOptionalNumber(opts.first, 'first'),
|
|
245
|
+
all: Boolean(opts.all),
|
|
246
|
+
fields: parseCsv(opts.fields),
|
|
247
|
+
});
|
|
248
|
+
exitWith(success(data));
|
|
249
|
+
}
|
|
250
|
+
catch (e) {
|
|
251
|
+
exitWith(errorFromUnknown(e));
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (typeName === 'BattleMapConfig') {
|
|
256
|
+
sub
|
|
257
|
+
.command('validate')
|
|
258
|
+
.description('Validate a BattleMapConfig against GameData map nodes and backend behavior')
|
|
259
|
+
.option('--id <guid>', 'Read an existing BattleMapConfig by UUID')
|
|
260
|
+
.option('--file <path>', 'Validate a complete BattleMapConfig JSON file')
|
|
261
|
+
.action(async (opts) => {
|
|
262
|
+
try {
|
|
263
|
+
if (Boolean(opts.id) === Boolean(opts.file)) {
|
|
264
|
+
throw new CliError('MAP_CONFIG_SOURCE_REQUIRED', 'Specify exactly one of --id or --file.', {
|
|
265
|
+
category: 'usage',
|
|
266
|
+
details: { expected: ['--id <guid>', '--file <path>'] },
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
const g = resolveGlobalOptions(program.opts());
|
|
270
|
+
const configValue = opts.id
|
|
271
|
+
? await api.get(await toGqlOpts(g), opts.id)
|
|
272
|
+
: battleMapCmd.readBattleMapFile(opts.file);
|
|
273
|
+
if (configValue === null || configValue === undefined) {
|
|
274
|
+
throw new CliError('BATTLE_MAP_CONFIG_NOT_FOUND', `BattleMapConfig not found: ${opts.id}`, {
|
|
275
|
+
category: 'not_found',
|
|
276
|
+
field: 'id',
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
exitWith(success(await battleMapCmd.validateBattleMapConfig(toGameDataOpts(g), configValue)));
|
|
280
|
+
}
|
|
281
|
+
catch (e) {
|
|
282
|
+
exitWith(errorFromUnknown(e));
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const session = program.command('session');
|
|
288
|
+
session
|
|
289
|
+
.command('info')
|
|
290
|
+
.description('Get session info')
|
|
291
|
+
.action(async () => {
|
|
292
|
+
try {
|
|
293
|
+
const g = resolveGlobalOptions(program.opts());
|
|
294
|
+
const data = await sessionCmd.info(await toActiveOpts(g));
|
|
295
|
+
exitWith(success(data));
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
exitWith(errorFromUnknown(e));
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
session
|
|
302
|
+
.command('reconnect')
|
|
303
|
+
.description('Reconnect session')
|
|
304
|
+
.action(async () => {
|
|
305
|
+
try {
|
|
306
|
+
const g = resolveGlobalOptions(program.opts());
|
|
307
|
+
const data = await sessionCmd.reconnect(await toActiveOpts(g));
|
|
308
|
+
exitWith(success(data));
|
|
309
|
+
}
|
|
310
|
+
catch (e) {
|
|
311
|
+
exitWith(errorFromUnknown(e));
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
session
|
|
315
|
+
.command('disconnect')
|
|
316
|
+
.description('Disconnect session')
|
|
317
|
+
.action(async () => {
|
|
318
|
+
try {
|
|
319
|
+
const g = resolveGlobalOptions(program.opts());
|
|
320
|
+
const data = await sessionCmd.disconnect(await toActiveOpts(g));
|
|
321
|
+
exitWith(success(data));
|
|
322
|
+
}
|
|
323
|
+
catch (e) {
|
|
324
|
+
exitWith(errorFromUnknown(e));
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
session
|
|
328
|
+
.command('unlock')
|
|
329
|
+
.description('Unlock login')
|
|
330
|
+
.action(async () => {
|
|
331
|
+
try {
|
|
332
|
+
const g = resolveGlobalOptions(program.opts());
|
|
333
|
+
const data = await sessionCmd.unlock(await toActiveOpts(g));
|
|
334
|
+
exitWith(success(data));
|
|
335
|
+
}
|
|
336
|
+
catch (e) {
|
|
337
|
+
exitWith(errorFromUnknown(e));
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
session
|
|
341
|
+
.command('autologin')
|
|
342
|
+
.description('Set auto-login state')
|
|
343
|
+
.requiredOption('--state <bool>', 'true or false')
|
|
344
|
+
.action(async (opts) => {
|
|
345
|
+
try {
|
|
346
|
+
const g = resolveGlobalOptions(program.opts());
|
|
347
|
+
const state = opts.state === 'true' || opts.state === true;
|
|
348
|
+
const data = await sessionCmd.setAutoLogin(await toActiveOpts(g), state);
|
|
349
|
+
exitWith(success(data));
|
|
350
|
+
}
|
|
351
|
+
catch (e) {
|
|
352
|
+
exitWith(errorFromUnknown(e));
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
const mission = program.command('mission');
|
|
356
|
+
mission
|
|
357
|
+
.command('list')
|
|
358
|
+
.description('List mission states')
|
|
359
|
+
.action(async () => {
|
|
360
|
+
try {
|
|
361
|
+
const g = resolveGlobalOptions(program.opts());
|
|
362
|
+
const data = await missionCmd.list(await toActiveOpts(g));
|
|
363
|
+
exitWith(success(data));
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
exitWith(errorFromUnknown(e));
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
mission
|
|
370
|
+
.command('validate')
|
|
371
|
+
.description('Run pre-start dependency validation for a mission')
|
|
372
|
+
.requiredOption('--type <name>', 'Mission config type name')
|
|
373
|
+
.option('--file <path>', 'Validate a proposed partial/full config without writing')
|
|
374
|
+
.action(async (opts) => {
|
|
375
|
+
try {
|
|
376
|
+
if (opts.type !== 'MissionBattleConfig') {
|
|
377
|
+
throw new CliError('MISSION_VALIDATOR_UNAVAILABLE', `No dedicated preflight validator is available for ${opts.type}.`, {
|
|
378
|
+
category: 'validation',
|
|
379
|
+
field: 'type',
|
|
380
|
+
details: { supported: ['MissionBattleConfig'] },
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
const g = resolveGlobalOptions(program.opts());
|
|
384
|
+
const gqlOpts = await toGqlOpts(g);
|
|
385
|
+
let proposed;
|
|
386
|
+
let proposal = null;
|
|
387
|
+
if (opts.file) {
|
|
388
|
+
const current = await configCmd.getConfig(gqlOpts, 'MissionBattleConfig');
|
|
389
|
+
const plan = createWritePlan(current, readJsonObject(opts.file));
|
|
390
|
+
proposed = plan.proposed;
|
|
391
|
+
proposal = {
|
|
392
|
+
mode: 'merge',
|
|
393
|
+
changed: plan.changed,
|
|
394
|
+
currentHash: plan.currentHash,
|
|
395
|
+
proposedHash: plan.proposedHash,
|
|
396
|
+
changedPaths: plan.changedPaths,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const data = await missionCmd.validateBattleStart(await toActiveOpts(g), gqlOpts, toGameDataOpts(g), proposed);
|
|
400
|
+
data.proposal = proposal;
|
|
401
|
+
if (data.valid !== true) {
|
|
402
|
+
throw new CliError('MISSION_PREFLIGHT_FAILED', 'MissionBattleConfig is not safe to start.', {
|
|
403
|
+
category: 'validation',
|
|
404
|
+
details: data,
|
|
405
|
+
suggestion: 'Resolve every preflight error, safely write and verify dependencies, then validate again.',
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
exitWith(success(data));
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
exitWith(errorFromUnknown(e));
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
mission
|
|
415
|
+
.command('enable')
|
|
416
|
+
.description('Validate, enable, and reread a mission state')
|
|
417
|
+
.requiredOption('--type <name>', 'Mission config type name')
|
|
418
|
+
.option('--dry-run', 'Run preflight without changing mission state')
|
|
419
|
+
.action(async (opts) => {
|
|
420
|
+
try {
|
|
421
|
+
const g = resolveGlobalOptions(program.opts());
|
|
422
|
+
const activeOpts = await toActiveOpts(g);
|
|
423
|
+
let preflight = null;
|
|
424
|
+
if (opts.type === 'MissionBattleConfig') {
|
|
425
|
+
preflight = await missionCmd.validateBattleStart(activeOpts, await toGqlOpts(g), toGameDataOpts(g));
|
|
426
|
+
if (preflight.valid !== true) {
|
|
427
|
+
throw new CliError('MISSION_PREFLIGHT_FAILED', 'MissionBattleConfig is not safe to start.', {
|
|
428
|
+
category: 'validation',
|
|
429
|
+
details: preflight,
|
|
430
|
+
suggestion: 'Resolve every preflight error before enabling the mission.',
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else if (opts.dryRun) {
|
|
435
|
+
throw new CliError('MISSION_VALIDATOR_UNAVAILABLE', `Dry-run validation is not implemented for ${opts.type}.`, {
|
|
436
|
+
category: 'validation',
|
|
437
|
+
details: { supported: ['MissionBattleConfig'] },
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
if (opts.dryRun) {
|
|
441
|
+
exitWith(success({ operation: 'validate-enable', type: opts.type, changed: false, preflight }));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const state = await missionCmd.setState(activeOpts, opts.type, true);
|
|
445
|
+
exitWith(success({ operation: 'enable', preflight, state }));
|
|
446
|
+
}
|
|
447
|
+
catch (e) {
|
|
448
|
+
exitWith(errorFromUnknown(e));
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
mission
|
|
452
|
+
.command('disable')
|
|
453
|
+
.description('Disable a mission')
|
|
454
|
+
.requiredOption('--type <name>', 'Mission config type name')
|
|
455
|
+
.action(async (opts) => {
|
|
456
|
+
try {
|
|
457
|
+
const g = resolveGlobalOptions(program.opts());
|
|
458
|
+
const data = await missionCmd.setState(await toActiveOpts(g), opts.type, false);
|
|
459
|
+
exitWith(success(data));
|
|
460
|
+
}
|
|
461
|
+
catch (e) {
|
|
462
|
+
exitWith(errorFromUnknown(e));
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
const account = program.command('account')
|
|
466
|
+
.description('Read account resources and owned objects with AI-friendly summaries');
|
|
467
|
+
account
|
|
468
|
+
.command('summary')
|
|
469
|
+
.description('Return resource, capacity, and account-object counts')
|
|
470
|
+
.action(async () => {
|
|
471
|
+
try {
|
|
472
|
+
const g = resolveGlobalOptions(program.opts());
|
|
473
|
+
exitWith(success(await accountCmd.summary(await toActiveOpts(g))));
|
|
474
|
+
}
|
|
475
|
+
catch (e) {
|
|
476
|
+
exitWith(errorFromUnknown(e));
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
account
|
|
480
|
+
.command('ships')
|
|
481
|
+
.description('Query owned ship instances enriched with GameData definitions')
|
|
482
|
+
.option('--id <number>', 'Exact owned ship instance ID')
|
|
483
|
+
.option('--cid <number>', 'Exact catalog/configuration CID')
|
|
484
|
+
.option('--title <name>', 'Exact catalog title or custom name')
|
|
485
|
+
.option('--title-contains <text>', 'Catalog title or custom name contains text')
|
|
486
|
+
.option('--ship-type <csv>', 'ShipType values, comma-separated (OR within this option)')
|
|
487
|
+
.option('--country <csv>', 'ShipCountry values, comma-separated (OR within this option)')
|
|
488
|
+
.option('--level-min <number>', 'Minimum level')
|
|
489
|
+
.option('--level-max <number>', 'Maximum level')
|
|
490
|
+
.option('--locked', 'Only locked ships')
|
|
491
|
+
.option('--unlocked', 'Only unlocked ships')
|
|
492
|
+
.option('--married', 'Only married ships')
|
|
493
|
+
.option('--unmarried', 'Only unmarried ships')
|
|
494
|
+
.option('--evolved', 'Only retrofit/evolved definitions')
|
|
495
|
+
.option('--base', 'Only base/non-evolved definitions')
|
|
496
|
+
.option('--available', 'Only ships available to the backend team selector')
|
|
497
|
+
.option('--occupied', 'Only ships unavailable to the backend team selector')
|
|
498
|
+
.option('--offset <number>', 'Zero-based result offset', '0')
|
|
499
|
+
.option('--first <number>', 'Maximum returned items, default 20', '20')
|
|
500
|
+
.option('--all', 'Return all matching items after offset')
|
|
501
|
+
.option('--fields <csv>', 'Return selected fields only')
|
|
502
|
+
.action(async (opts) => {
|
|
503
|
+
try {
|
|
504
|
+
if (opts.locked && opts.unlocked)
|
|
505
|
+
throw new CliError('CONFLICTING_FILTERS', '--locked and --unlocked are mutually exclusive.', { category: 'usage' });
|
|
506
|
+
if (opts.married && opts.unmarried)
|
|
507
|
+
throw new CliError('CONFLICTING_FILTERS', '--married and --unmarried are mutually exclusive.', { category: 'usage' });
|
|
508
|
+
if (opts.evolved && opts.base)
|
|
509
|
+
throw new CliError('CONFLICTING_FILTERS', '--evolved and --base are mutually exclusive.', { category: 'usage' });
|
|
510
|
+
if (opts.available && opts.occupied)
|
|
511
|
+
throw new CliError('CONFLICTING_FILTERS', '--available and --occupied are mutually exclusive.', { category: 'usage' });
|
|
512
|
+
const g = resolveGlobalOptions(program.opts());
|
|
513
|
+
const data = await accountCmd.ownedShips(await toActiveOpts(g), toGameDataOpts(g), {
|
|
514
|
+
id: parseOptionalNumber(opts.id, 'id'),
|
|
515
|
+
cid: parseOptionalNumber(opts.cid, 'cid'),
|
|
516
|
+
title: opts.title,
|
|
517
|
+
titleContains: opts.titleContains,
|
|
518
|
+
shipTypes: parseCsv(opts.shipType)?.map((item) => item.toUpperCase()),
|
|
519
|
+
countries: parseCsv(opts.country)?.map((item) => item.toUpperCase()),
|
|
520
|
+
levelMin: parseOptionalNumber(opts.levelMin, 'level-min'),
|
|
521
|
+
levelMax: parseOptionalNumber(opts.levelMax, 'level-max'),
|
|
522
|
+
locked: opts.locked ? true : opts.unlocked ? false : undefined,
|
|
523
|
+
married: opts.married ? true : opts.unmarried ? false : undefined,
|
|
524
|
+
evolved: opts.evolved ? true : opts.base ? false : undefined,
|
|
525
|
+
available: opts.available ? true : opts.occupied ? false : undefined,
|
|
526
|
+
offset: parseOptionalNumber(opts.offset, 'offset'),
|
|
527
|
+
first: parseOptionalNumber(opts.first, 'first'),
|
|
528
|
+
all: Boolean(opts.all),
|
|
529
|
+
fields: parseCsv(opts.fields),
|
|
530
|
+
});
|
|
531
|
+
exitWith(success(data));
|
|
532
|
+
}
|
|
533
|
+
catch (e) {
|
|
534
|
+
exitWith(errorFromUnknown(e));
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
for (const [commandName, partName, description] of [
|
|
538
|
+
['resources', 'resources', 'Return player resource fields'],
|
|
539
|
+
['equips', 'equips', 'Return owned equipment instances'],
|
|
540
|
+
['teams', 'teams', 'Return account fleets and teams'],
|
|
541
|
+
['items', 'items', 'Return account item inventory'],
|
|
542
|
+
['tasks', 'tasks', 'Return player task state'],
|
|
543
|
+
['maps', 'maps', 'Return player map progress'],
|
|
544
|
+
['expeditions', 'expeditions', 'Return expedition state'],
|
|
545
|
+
['dock', 'dock', 'Return repair/build dock state'],
|
|
546
|
+
['battle', 'battle', 'Return current battle state'],
|
|
547
|
+
]) {
|
|
548
|
+
account
|
|
549
|
+
.command(commandName)
|
|
550
|
+
.description(description)
|
|
551
|
+
.option('--id <id>', 'Return one item by instance ID')
|
|
552
|
+
.option('--first <number>', 'Maximum returned items, default 20')
|
|
553
|
+
.option('--all', 'Return all items')
|
|
554
|
+
.option('--fields <csv>', 'Return selected fields only')
|
|
555
|
+
.action(async (opts) => {
|
|
556
|
+
try {
|
|
557
|
+
const g = resolveGlobalOptions(program.opts());
|
|
558
|
+
const data = await snapshotCmd.getPart(await toActiveOpts(g), partName, {
|
|
559
|
+
id: opts.id,
|
|
560
|
+
first: parseOptionalNumber(opts.first, 'first'),
|
|
561
|
+
all: Boolean(opts.all),
|
|
562
|
+
fields: parseCsv(opts.fields),
|
|
563
|
+
});
|
|
564
|
+
exitWith(success(data));
|
|
565
|
+
}
|
|
566
|
+
catch (e) {
|
|
567
|
+
exitWith(errorFromUnknown(e));
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
const snapshot = program.command('snapshot');
|
|
572
|
+
// ── active-info parent (no action — just groups sub-commands) ──
|
|
573
|
+
const ai = snapshot.command('active-info');
|
|
574
|
+
ai
|
|
575
|
+
.command('full')
|
|
576
|
+
.description('Return the full ActiveUserInfoData as-is')
|
|
577
|
+
.action(async () => {
|
|
578
|
+
try {
|
|
579
|
+
const g = resolveGlobalOptions(program.opts());
|
|
580
|
+
const data = await snapshotCmd.getFullActiveInfo(await toActiveOpts(g));
|
|
581
|
+
exitWith(success(data));
|
|
582
|
+
}
|
|
583
|
+
catch (e) {
|
|
584
|
+
exitWith(errorFromUnknown(e));
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
ai
|
|
588
|
+
.command('parts')
|
|
589
|
+
.description('List known ActiveUserInfoData parts')
|
|
590
|
+
.action(() => {
|
|
591
|
+
exitWith(success(snapshotCmd.listParts()));
|
|
592
|
+
});
|
|
593
|
+
ai
|
|
594
|
+
.command('summary')
|
|
595
|
+
.description('Return part names and counts without fetching full data')
|
|
596
|
+
.action(async () => {
|
|
597
|
+
try {
|
|
598
|
+
const g = resolveGlobalOptions(program.opts());
|
|
599
|
+
const data = await snapshotCmd.getSummary(await toActiveOpts(g));
|
|
600
|
+
exitWith(success(data));
|
|
601
|
+
}
|
|
602
|
+
catch (e) {
|
|
603
|
+
exitWith(errorFromUnknown(e));
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
// ── generic part access ──
|
|
607
|
+
ai
|
|
608
|
+
.command('get')
|
|
609
|
+
.description('Return one part (ships, equips, teams, resources, …)')
|
|
610
|
+
.argument('<part>', 'Part name or alias')
|
|
611
|
+
.option('--id <id>', 'Return one item by id/uid')
|
|
612
|
+
.option('--first <number>', 'Limit items, default 20')
|
|
613
|
+
.option('--all', 'Return all items from the part')
|
|
614
|
+
.option('--fields <csv>', 'Keep selected fields, comma-separated')
|
|
615
|
+
.action(async (part, opts) => {
|
|
616
|
+
try {
|
|
617
|
+
const g = resolveGlobalOptions(program.opts());
|
|
618
|
+
const data = await snapshotCmd.getPart(await toActiveOpts(g), part, {
|
|
619
|
+
id: opts.id,
|
|
620
|
+
first: parseOptionalNumber(opts.first, 'first'),
|
|
621
|
+
all: Boolean(opts.all),
|
|
622
|
+
fields: parseCsv(opts.fields),
|
|
623
|
+
});
|
|
624
|
+
exitWith(success(data));
|
|
625
|
+
}
|
|
626
|
+
catch (e) {
|
|
627
|
+
exitWith(errorFromUnknown(e));
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
// ── ships sub-command with extra filters ──
|
|
631
|
+
ai
|
|
632
|
+
.command('ships')
|
|
633
|
+
.description('Return owned ships, with optional filters')
|
|
634
|
+
.option('--cid <number>', 'Filter by ship config ID (Cid)')
|
|
635
|
+
.option('--level-min <number>', 'Minimum level')
|
|
636
|
+
.option('--level-max <number>', 'Maximum level')
|
|
637
|
+
.option('--locked', 'Only locked ships')
|
|
638
|
+
.option('--unlocked', 'Only unlocked ships')
|
|
639
|
+
.option('--married', 'Only married ships')
|
|
640
|
+
.option('--unmarried', 'Only unmarried ships')
|
|
641
|
+
.option('--id <id>', 'Return one ship by instance uid')
|
|
642
|
+
.option('--first <number>', 'Limit items, default 20')
|
|
643
|
+
.option('--all', 'Return all matching ships')
|
|
644
|
+
.option('--fields <csv>', 'Keep selected fields, comma-separated')
|
|
645
|
+
.action(async (opts) => {
|
|
646
|
+
try {
|
|
647
|
+
if (opts.locked && opts.unlocked) {
|
|
648
|
+
throw new Error('--locked and --unlocked are mutually exclusive.');
|
|
649
|
+
}
|
|
650
|
+
if (opts.married && opts.unmarried) {
|
|
651
|
+
throw new Error('--married and --unmarried are mutually exclusive.');
|
|
652
|
+
}
|
|
653
|
+
const g = resolveGlobalOptions(program.opts());
|
|
654
|
+
const data = await snapshotCmd.getShips(await toActiveOpts(g), {
|
|
655
|
+
cid: parseOptionalNumber(opts.cid, 'cid'),
|
|
656
|
+
levelMin: parseOptionalNumber(opts.levelMin, 'level-min'),
|
|
657
|
+
levelMax: parseOptionalNumber(opts.levelMax, 'level-max'),
|
|
658
|
+
locked: opts.locked === 'true' || opts.locked === true ? true : undefined,
|
|
659
|
+
unlocked: opts.unlocked === 'true' || opts.unlocked === true ? true : undefined,
|
|
660
|
+
married: opts.married === 'true' || opts.married === true ? true : undefined,
|
|
661
|
+
unmarried: opts.unmarried === 'true' || opts.unmarried === true ? true : undefined,
|
|
662
|
+
id: opts.id,
|
|
663
|
+
first: parseOptionalNumber(opts.first, 'first'),
|
|
664
|
+
all: Boolean(opts.all),
|
|
665
|
+
fields: parseCsv(opts.fields),
|
|
666
|
+
});
|
|
667
|
+
exitWith(success(data));
|
|
668
|
+
}
|
|
669
|
+
catch (e) {
|
|
670
|
+
exitWith(errorFromUnknown(e));
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
const functions = program.command('functions')
|
|
674
|
+
.description('Common utility functions');
|
|
675
|
+
functions
|
|
676
|
+
.command('check-archive')
|
|
677
|
+
.description('Check dock/archive consistency — requires --k')
|
|
678
|
+
.action(async () => {
|
|
679
|
+
try {
|
|
680
|
+
const g = resolveGlobalOptions(program.opts());
|
|
681
|
+
const activeOpts = await toActiveOpts(g);
|
|
682
|
+
const gameDataOpts = toGameDataOpts(g);
|
|
683
|
+
const data = await functionsCmd.checkArchive(activeOpts, gameDataOpts);
|
|
684
|
+
exitWith(success(data));
|
|
685
|
+
}
|
|
686
|
+
catch (e) {
|
|
687
|
+
exitWith(errorFromUnknown(e));
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
if (process.argv.length <= 2) {
|
|
691
|
+
program.outputHelp();
|
|
692
|
+
process.exit(0);
|
|
693
|
+
}
|
|
694
|
+
try {
|
|
695
|
+
await program.parseAsync(process.argv);
|
|
696
|
+
}
|
|
697
|
+
catch (value) {
|
|
698
|
+
if (value instanceof CommanderError && (value.code === 'commander.helpDisplayed' || value.code === 'commander.version')) {
|
|
699
|
+
process.exitCode = 0;
|
|
700
|
+
}
|
|
701
|
+
else if (value instanceof CommanderError) {
|
|
702
|
+
exitWith(errorFromUnknown(new CliError('ARGUMENT_ERROR', value.message, {
|
|
703
|
+
category: 'usage',
|
|
704
|
+
suggestion: 'Run the same command with --help to inspect accepted arguments.',
|
|
705
|
+
cause: value,
|
|
706
|
+
})));
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
exitWith(errorFromUnknown(value));
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
//# sourceMappingURL=index.js.map
|