feat(cli): cover OpenAPI with a remote npm CLI and internal panel tools (#3074)

* feat(cli): add unified Commander CLI for QingLong 2.x

* fix(cli): publish via npm and address security review feedback

* ci(cli): package npm artifacts and remove evaluation collateral

* test(cli): use a fixed shell fixture for log retention

* refactor(cli): separate remote npm client from panel tools

* feat(cli): cover active panel OpenAPI resources

* docs(cli): unify authentication and skill guidance

* refactor(cli): isolate internal commands and generate Commander help

* refactor(cli): organize remote and internal modules by responsibility

* ci(cli): publish verified npm archives from master

* fix(cli): publish under the whyour npm scope

* ci: use npm trusted publishing for both packages

* docs: introduce the published CLI on the project homepage

* fix(cli): preserve server log truncation and correct login hints

* fix(cli): accept dashboard record request bodies

* fix(cli): preserve stdin for local task execution

* fix(cli): resolve task executables after changing directory

* fix(cli): preserve shell function tasks and sanitize test failures

* fix(cli): preserve shell hook state and resolve workdir after hooks

* fix(cli): preserve cleanup across shared shell task timeouts

* fix(cli): isolate shell control descriptors and reap timed-out descendants
This commit is contained in:
whyour
2026-09-25 23:24:41 +08:00
committed by GitHub
parent f051135fc4
commit 801a71d740
185 changed files with 22412 additions and 6 deletions
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env node
import { translate } from '../shared/i18n/index';
import { standaloneHelp } from '../internal/help/standalone';
import {
withCommandCancellation,
interruptedCode,
} from '../internal/runtime/cancellation';
import { CliError, fail } from '../shared/errors';
export interface CompatibilityRoute {
surface: 'local' | 'subscription';
args: string[];
}
export function compatibilityRoute(input: string[]): CompatibilityRoute {
const args = input[0] === '-l' ? input.slice(1) : [...input];
const [action, ...values] = args;
if (action === 'repo' || action === 'raw')
return { surface: 'subscription', args };
if (action === 'update') {
if (
values.length > 1 ||
(values.length && !['true', 'false'].includes(values[0]!))
)
fail(translate(process.env, '用法:ql-compat update [true|false]'), 2);
return {
surface: 'local',
args: ['update', ...(values[0] === 'false' ? ['--download-only'] : [])],
};
}
if (action === 'reload') {
if (
values.length > 1 ||
(values.length && !['services', 'system', 'data'].includes(values[0]!))
)
fail(
translate(process.env, '用法:ql-compat reload [services|system|data]'),
2,
);
return {
surface: 'local',
args: ['reload', '--target', values[0] || 'services'],
};
}
if (
action &&
[
'rmlog',
'extra',
'bot',
'check',
'resetlet',
'resettfa',
'resetpwd',
'resetname',
].includes(action)
) {
// A legacy positional value beginning with '-' is data, never a CLI option.
return { surface: 'local', args: [action, '--', ...values] };
}
fail(translate(process.env, '未知旧命令,请运行 ql-compat --help。'), 2);
}
export async function compatibilityMain(
args = process.argv.slice(2),
): Promise<number> {
return withCommandCancellation(async (signal) => {
try {
if (
!args.length ||
(args.length === 1 && ['--help', '-h'].includes(args[0]!))
) {
process.stdout.write(standaloneHelp('compat'));
return 0;
}
const route = compatibilityRoute(args);
if (route.surface === 'subscription') {
const { subscriptionWorker } = await import(
'../internal/subscription/worker'
);
return subscriptionWorker(route.args);
}
const { parse } = await import('../shared/cli/arguments');
const { localContext } = await import('../internal/runtime/context');
const { maintenance } = await import(
'../internal/maintenance/maintenance'
);
const { loggedOperation } = await import(
'../internal/runtime/commandLog'
);
const { registry } = await import('../internal/commands/registry');
const command = parse(route.args, registry);
const context = await localContext(command, { signal });
const { result, logPath } = await loggedOperation(
context,
route.args[0]!,
() => maintenance(command, context),
signal,
);
process.stdout.write(
JSON.stringify({ code: 200, data: result, logPath }) + '\n',
);
return 0;
} catch (error) {
const code =
interruptedCode(signal) ??
(error instanceof CliError ? error.exitCode : 1);
process.stderr.write(
JSON.stringify({
code,
message: signal.aborted
? translate(process.env, '命令已中断。')
: error instanceof CliError
? error.message
: translate(process.env, '旧命令适配器执行失败。'),
}) + '\n',
);
return code;
}
});
}
if (require.main === module)
void compatibilityMain().then((code) => {
process.exitCode = code;
});