tirtc-devtools-cli 0.0.5 → 0.0.7
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 +4 -2
- package/USAGE.md +152 -365
- package/dist/devtools/cli/src/config.d.ts +4 -13
- package/dist/devtools/cli/src/config.js +7 -19
- package/dist/devtools/cli/src/facade.d.ts +39 -154
- package/dist/devtools/cli/src/facade.js +9 -23
- package/dist/devtools/cli/src/guide.js +7 -9
- package/dist/devtools/cli/src/index.js +146 -248
- package/dist/devtools/cli/src/progress.d.ts +19 -0
- package/dist/devtools/cli/src/progress.js +63 -0
- package/dist/devtools/cli/src/token_command.d.ts +7 -0
- package/dist/devtools/cli/src/token_command.js +139 -0
- package/package.json +1 -1
- package/vendor/app-server/bin/native/macos-arm64/credential_napi.node +0 -0
- package/vendor/app-server/bin/native/macos-arm64/runtime_host_napi.node +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/include/tirtc/trp.h +30 -17
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_audio.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_credential.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_facade.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_foundation_http.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_foundation_logging.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_media.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_transport.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/lib/libmatrix_runtime_video.a +0 -0
- package/vendor/app-server/bin/runtime/macos-arm64/manifest.txt +10 -10
- package/vendor/app-server/dist/host/HostProtocol.d.ts +7 -5
- package/vendor/app-server/dist/host/HostProtocol.js +21 -10
- package/vendor/app-server/dist/host/HostServer.d.ts +2 -2
- package/vendor/app-server/dist/host/HostServer.js +93 -35
- package/vendor/app-server/dist/host/HostState.d.ts +0 -5
- package/vendor/app-server/dist/host/HostState.js +0 -1
- package/vendor/app-server/dist/host/RuntimeAdapter.d.ts +12 -0
- package/vendor/app-server/dist/host/RuntimeAdapter.js +42 -3
- package/vendor/app-server/dist/host/RuntimeSendWorker.d.ts +4 -0
- package/vendor/app-server/dist/host/RuntimeSendWorker.js +17 -0
- package/vendor/app-server/dist/protocol/contract.d.ts +72 -14
- package/vendor/app-server/dist/protocol/contract.js +11 -8
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
const commander_1 = require("commander");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
4
9
|
const protocol_client_1 = require("../../../app-server/protocol-client");
|
|
5
10
|
const facade_1 = require("./facade");
|
|
6
11
|
const config_1 = require("./config");
|
|
7
12
|
const guide_1 = require("./guide");
|
|
8
13
|
const session_manager_1 = require("./session_manager");
|
|
9
14
|
const media_assets_1 = require("./media_assets");
|
|
15
|
+
const token_command_1 = require("./token_command");
|
|
10
16
|
const transport_1 = require("./transport");
|
|
11
|
-
const
|
|
12
|
-
const CLI_VERSION = '0.0.
|
|
17
|
+
const progress_1 = require("./progress");
|
|
18
|
+
const CLI_VERSION = '0.0.6';
|
|
13
19
|
const HOST_VERSION = '1.0.0';
|
|
14
20
|
const PROTOCOL_VERSION = '1.0.0';
|
|
15
|
-
const kTokenIssueAccessIdEnvVar = 'TIRTC_CONN_ACCESS_ID';
|
|
16
|
-
const kTokenIssueSecretKeyEnvVar = 'TIRTC_CONN_SECRET_KEY';
|
|
17
21
|
if (process.argv.includes('--version') || process.argv.includes('-V')) {
|
|
18
22
|
console.log('CLI Version: ' + CLI_VERSION);
|
|
19
23
|
console.log('Host Version: ' + HOST_VERSION);
|
|
@@ -103,57 +107,99 @@ function toResultObject(result) {
|
|
|
103
107
|
}
|
|
104
108
|
return { result };
|
|
105
109
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}, 180_000);
|
|
146
|
-
sendStreams.push({
|
|
147
|
-
streamId: videoStreamId,
|
|
148
|
-
assetsDir,
|
|
149
|
-
media: 'video',
|
|
150
|
-
stream,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
110
|
+
function requireConfigPath(configPath) {
|
|
111
|
+
if (!configPath || configPath.trim().length === 0) {
|
|
112
|
+
throw new Error('server-only service start requires --config <path>');
|
|
113
|
+
}
|
|
114
|
+
return configPath;
|
|
115
|
+
}
|
|
116
|
+
function validateServerConfig(config) {
|
|
117
|
+
const server = config.server;
|
|
118
|
+
const license = server?.license?.trim() ?? '';
|
|
119
|
+
const mp4Path = server?.mp4_path?.trim() ?? '';
|
|
120
|
+
const audioStreamIdValue = server?.audio_stream_id;
|
|
121
|
+
const videoStreamIdValue = server?.video_stream_id;
|
|
122
|
+
if (license.length === 0) {
|
|
123
|
+
throw new Error('server-only config requires [server].license');
|
|
124
|
+
}
|
|
125
|
+
if (mp4Path.length === 0) {
|
|
126
|
+
throw new Error('server-only config requires [server].mp4_path');
|
|
127
|
+
}
|
|
128
|
+
if (typeof audioStreamIdValue !== 'number') {
|
|
129
|
+
throw new Error('server-only config requires [server].audio_stream_id as non-negative integer');
|
|
130
|
+
}
|
|
131
|
+
if (!Number.isInteger(audioStreamIdValue) || audioStreamIdValue < 0) {
|
|
132
|
+
throw new Error('server-only config requires [server].audio_stream_id as non-negative integer');
|
|
133
|
+
}
|
|
134
|
+
if (typeof videoStreamIdValue !== 'number') {
|
|
135
|
+
throw new Error('server-only config requires [server].video_stream_id as non-negative integer');
|
|
136
|
+
}
|
|
137
|
+
if (!Number.isInteger(videoStreamIdValue) || videoStreamIdValue < 0) {
|
|
138
|
+
throw new Error('server-only config requires [server].video_stream_id as non-negative integer');
|
|
139
|
+
}
|
|
140
|
+
const audioStreamId = audioStreamIdValue;
|
|
141
|
+
const videoStreamId = videoStreamIdValue;
|
|
142
|
+
if (audioStreamId === videoStreamId) {
|
|
143
|
+
throw new Error('server-only config requires distinct audio_stream_id and video_stream_id');
|
|
144
|
+
}
|
|
145
|
+
const resolvedMp4Path = path_1.default.resolve(mp4Path);
|
|
146
|
+
let stats;
|
|
147
|
+
try {
|
|
148
|
+
stats = fs_1.default.statSync(resolvedMp4Path);
|
|
153
149
|
}
|
|
150
|
+
catch {
|
|
151
|
+
throw new Error('server-only config mp4_path does not exist: ' + resolvedMp4Path);
|
|
152
|
+
}
|
|
153
|
+
if (!stats.isFile()) {
|
|
154
|
+
throw new Error('server-only config mp4_path must be a readable file: ' + resolvedMp4Path);
|
|
155
|
+
}
|
|
156
|
+
const serviceEntry = server?.service_entry?.trim();
|
|
157
|
+
return {
|
|
158
|
+
serviceEntry: serviceEntry && serviceEntry.length > 0 ? serviceEntry : undefined,
|
|
159
|
+
license,
|
|
160
|
+
mp4Path: resolvedMp4Path,
|
|
161
|
+
audioStreamId,
|
|
162
|
+
videoStreamId,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function createProgressIndicator(options) {
|
|
166
|
+
if (options.json) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
return new progress_1.ProgressIndicator();
|
|
170
|
+
}
|
|
171
|
+
function resolvePrepareOutputRoot(config) {
|
|
172
|
+
const loggingRoot = config.logging?.root_dir?.trim();
|
|
173
|
+
if (loggingRoot && loggingRoot.length > 0) {
|
|
174
|
+
return path_1.default.resolve(loggingRoot, '..', 'prepared-assets');
|
|
175
|
+
}
|
|
176
|
+
return path_1.default.resolve(process.cwd(), '.tmp', 'tirtc-devtools-cli', 'prepared-assets');
|
|
177
|
+
}
|
|
178
|
+
async function applyConfiguredServerBootstrap(client, config, progress) {
|
|
179
|
+
const server = validateServerConfig(config);
|
|
180
|
+
progress?.start('Preparing MP4 assets');
|
|
181
|
+
const prepared = await (0, media_assets_1.prepareMediaAssets)({
|
|
182
|
+
source: server.mp4Path,
|
|
183
|
+
outputRoot: resolvePrepareOutputRoot(config),
|
|
184
|
+
});
|
|
185
|
+
progress?.update('Prepared MP4 assets');
|
|
186
|
+
const bootstrapSendStreams = [
|
|
187
|
+
{ streamId: server.audioStreamId, media: 'audio' },
|
|
188
|
+
{ streamId: server.videoStreamId, media: 'video' },
|
|
189
|
+
].map(({ streamId, media }) => ({
|
|
190
|
+
streamId,
|
|
191
|
+
media,
|
|
192
|
+
source: {
|
|
193
|
+
mode: 'local_assets',
|
|
194
|
+
local_assets: {
|
|
195
|
+
assets_dir: prepared.assets_dir,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
}));
|
|
154
199
|
return {
|
|
155
|
-
|
|
156
|
-
|
|
200
|
+
preparedAssetsDir: prepared.assets_dir,
|
|
201
|
+
mediaSendPolicy: 'AUTO_ON_CONNECTED',
|
|
202
|
+
bootstrapSendStreams,
|
|
157
203
|
};
|
|
158
204
|
}
|
|
159
205
|
async function runCommand(cmdName, params, options) {
|
|
@@ -168,26 +214,34 @@ async function runCommand(cmdName, params, options) {
|
|
|
168
214
|
if (!method) {
|
|
169
215
|
throw new Error('Unknown command mapping for: ' + cmdName);
|
|
170
216
|
}
|
|
171
|
-
|
|
172
|
-
const mode = String(params.mode ?? '');
|
|
173
|
-
if (mode !== 'manual' && mode !== 'auto-if-bound') {
|
|
174
|
-
throw new Error('Invalid mode, expected manual|auto-if-bound');
|
|
175
|
-
}
|
|
176
|
-
return { mode };
|
|
177
|
-
})() : params;
|
|
178
|
-
const result = await client.sendRequest(method, commandParams);
|
|
179
|
-
let resultForPrint = result;
|
|
217
|
+
let resultForPrint;
|
|
180
218
|
if (cmdName === 'service start') {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
219
|
+
const progress = createProgressIndicator(options);
|
|
220
|
+
try {
|
|
221
|
+
const autoApplied = await applyConfiguredServerBootstrap(client, config, progress);
|
|
222
|
+
progress?.update('Starting service session');
|
|
223
|
+
const requestParams = {
|
|
224
|
+
...params,
|
|
225
|
+
bootstrapSendStreams: autoApplied.bootstrapSendStreams,
|
|
226
|
+
};
|
|
227
|
+
const result = await client.sendRequest(method, requestParams, 180_000);
|
|
228
|
+
progress?.succeed('Service session is ready');
|
|
184
229
|
const resultObject = toResultObject(result);
|
|
185
230
|
resultForPrint = {
|
|
186
231
|
...resultObject,
|
|
187
232
|
autoApplied,
|
|
188
233
|
};
|
|
234
|
+
printSuccess(options, resultForPrint, session, cmdName);
|
|
235
|
+
transport.stop();
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
progress?.fail('Service start failed');
|
|
240
|
+
throw error;
|
|
189
241
|
}
|
|
190
242
|
}
|
|
243
|
+
const result = await client.sendRequest(method, params);
|
|
244
|
+
resultForPrint = result;
|
|
191
245
|
printSuccess(options, resultForPrint, session, cmdName);
|
|
192
246
|
transport.stop();
|
|
193
247
|
return 0;
|
|
@@ -290,123 +344,11 @@ async function runMediaAssetsPrepare(source, outputRoot, outputDir, overwrite, o
|
|
|
290
344
|
return exitCode;
|
|
291
345
|
}
|
|
292
346
|
}
|
|
293
|
-
async function runTokenIssue(params, options) {
|
|
294
|
-
try {
|
|
295
|
-
const result = await (0, token_tool_1.issueTokenWithQrcode)(params);
|
|
296
|
-
if (options.json) {
|
|
297
|
-
console.log(JSON.stringify({
|
|
298
|
-
code: 0,
|
|
299
|
-
message: 'OK',
|
|
300
|
-
data: {
|
|
301
|
-
payload: result.payload,
|
|
302
|
-
payloadJson: result.payloadJson,
|
|
303
|
-
token: result.token,
|
|
304
|
-
qrCodePngPath: result.qrCodePngPath,
|
|
305
|
-
},
|
|
306
|
-
}));
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
console.log((0, token_tool_1.formatTokenIssueConsoleOutput)(result));
|
|
310
|
-
}
|
|
311
|
-
return 0;
|
|
312
|
-
}
|
|
313
|
-
catch (error) {
|
|
314
|
-
const normalized = normalizeError(error);
|
|
315
|
-
const reasonCode = normalized.reasonCode;
|
|
316
|
-
const exitCode = facade_1.ErrorReasonCodeMapping[reasonCode] ?? 1;
|
|
317
|
-
if (options.json) {
|
|
318
|
-
console.log(JSON.stringify({
|
|
319
|
-
code: exitCode,
|
|
320
|
-
message: normalized.message,
|
|
321
|
-
data: normalized.data,
|
|
322
|
-
}));
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
console.error('Error (' + reasonCode + '): ' + normalized.message);
|
|
326
|
-
}
|
|
327
|
-
return exitCode;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
async function runTokenIssueFromCli(peerId, commandOptions, options) {
|
|
331
|
-
const parsePositiveInt = (name, raw) => {
|
|
332
|
-
if (raw === undefined) {
|
|
333
|
-
return undefined;
|
|
334
|
-
}
|
|
335
|
-
const parsed = Number.parseInt(raw, 10);
|
|
336
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
337
|
-
throw new Error(name + ' must be a positive integer');
|
|
338
|
-
}
|
|
339
|
-
return parsed;
|
|
340
|
-
};
|
|
341
|
-
const parseQrErrorCorrectionLevel = (raw) => {
|
|
342
|
-
if (raw === undefined) {
|
|
343
|
-
return undefined;
|
|
344
|
-
}
|
|
345
|
-
const normalized = raw.trim().toUpperCase();
|
|
346
|
-
if (normalized !== 'L' && normalized !== 'M' && normalized !== 'Q' && normalized !== 'H') {
|
|
347
|
-
throw new Error('qr-error-correction-level must be one of: L, M, Q, H');
|
|
348
|
-
}
|
|
349
|
-
return normalized;
|
|
350
|
-
};
|
|
351
|
-
try {
|
|
352
|
-
const accessId = resolveTokenIssueCredential(commandOptions.accessId, kTokenIssueAccessIdEnvVar, '--access-id');
|
|
353
|
-
const secretKey = resolveTokenIssueCredential(commandOptions.secretKey, kTokenIssueSecretKeyEnvVar, '--secret-key');
|
|
354
|
-
return await runTokenIssue({
|
|
355
|
-
accessId,
|
|
356
|
-
secretKey,
|
|
357
|
-
localId: commandOptions.localId?.trim() || peerId,
|
|
358
|
-
peerId,
|
|
359
|
-
openapiEntry: commandOptions.openapiEntry,
|
|
360
|
-
serviceEntry: commandOptions.serviceEntry,
|
|
361
|
-
userTtlSeconds: parsePositiveInt('user-ttl-seconds', commandOptions.userTtlSeconds),
|
|
362
|
-
channelTtlSeconds: parsePositiveInt('channel-ttl-seconds', commandOptions.channelTtlSeconds),
|
|
363
|
-
qrErrorCorrectionLevel: parseQrErrorCorrectionLevel(commandOptions.qrErrorCorrectionLevel),
|
|
364
|
-
asciiMaxColumns: parsePositiveInt('ascii-max-columns', commandOptions.asciiMaxColumns),
|
|
365
|
-
}, options);
|
|
366
|
-
}
|
|
367
|
-
catch (error) {
|
|
368
|
-
const normalized = normalizeError(error);
|
|
369
|
-
const reasonCode = normalized.reasonCode;
|
|
370
|
-
const exitCode = facade_1.ErrorReasonCodeMapping[reasonCode] ?? 1;
|
|
371
|
-
if (options.json) {
|
|
372
|
-
console.log(JSON.stringify({
|
|
373
|
-
code: exitCode,
|
|
374
|
-
message: normalized.message,
|
|
375
|
-
data: normalized.data,
|
|
376
|
-
}));
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
console.error('Error (' + reasonCode + '): ' + normalized.message);
|
|
380
|
-
}
|
|
381
|
-
return exitCode;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
347
|
function runAndExit(promise) {
|
|
385
348
|
promise.then((code) => {
|
|
386
349
|
process.exit(code);
|
|
387
350
|
});
|
|
388
351
|
}
|
|
389
|
-
function mustParseJsonPayload(input) {
|
|
390
|
-
try {
|
|
391
|
-
const parsed = JSON.parse(input);
|
|
392
|
-
return JSON.stringify(parsed);
|
|
393
|
-
}
|
|
394
|
-
catch {
|
|
395
|
-
throw new Error('payloadJson must be valid JSON text');
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
function resolveTokenIssueCredential(explicitValue, envVarName, optionName) {
|
|
399
|
-
const normalizedExplicit = explicitValue?.trim();
|
|
400
|
-
if (normalizedExplicit) {
|
|
401
|
-
return normalizedExplicit;
|
|
402
|
-
}
|
|
403
|
-
const normalizedEnv = process.env[envVarName]?.trim();
|
|
404
|
-
if (normalizedEnv) {
|
|
405
|
-
return normalizedEnv;
|
|
406
|
-
}
|
|
407
|
-
throw new Error('missing credential: set environment variable ' + envVarName +
|
|
408
|
-
' or pass ' + optionName + ' explicitly');
|
|
409
|
-
}
|
|
410
352
|
program.name('tirtc-devtools-cli')
|
|
411
353
|
.description('TiRTC DevTools CLI')
|
|
412
354
|
.option('--config <path>', '配置文件路径(TOML)')
|
|
@@ -551,22 +493,18 @@ mediaAssets.command('prepare')
|
|
|
551
493
|
.action((commandOptions) => {
|
|
552
494
|
runAndExit(runMediaAssetsPrepare(commandOptions.source, commandOptions.outputRoot, commandOptions.outputDir, commandOptions.overwrite === true, getCliOptions()));
|
|
553
495
|
});
|
|
554
|
-
// Backward-compatible alias.
|
|
555
496
|
const hostConfig = host.command('config').description('配置辅助(兼容别名,推荐用顶层 config)');
|
|
556
497
|
hostConfig.command('init [outputPath]').description('生成配置模板(等价于 config init)').action((outputPath) => {
|
|
557
498
|
handleConfigInit(outputPath);
|
|
558
499
|
});
|
|
559
500
|
const service = program.command('service').description('服务端能力:启动/停止服务,等待远端接入');
|
|
560
|
-
service.command('start
|
|
561
|
-
.description('
|
|
562
|
-
.action((
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
throw new Error('service start requires license (service_entry can be omitted)');
|
|
568
|
-
}
|
|
569
|
-
runAndExit(runCommand('service start', { serviceEntry: finalServiceEntry, license: finalLicense, timeoutMs: 5000 }, getCliOptions()));
|
|
501
|
+
service.command('start')
|
|
502
|
+
.description('启动 server-only 会话(必须显式提供 --config)')
|
|
503
|
+
.action(() => {
|
|
504
|
+
const options = getCliOptions();
|
|
505
|
+
const config = (0, config_1.loadConfig)(requireConfigPath(options.config));
|
|
506
|
+
const server = validateServerConfig(config);
|
|
507
|
+
runAndExit(runCommand('service start', { serviceEntry: server.serviceEntry, license: server.license, timeoutMs: 5000 }, options));
|
|
570
508
|
});
|
|
571
509
|
service.command('stop').description('停止服务端监听').action(() => {
|
|
572
510
|
runAndExit(runCommand('service stop', {}, getCliOptions()));
|
|
@@ -577,9 +515,10 @@ connection.command('show').description('查看连接状态').action(() => {
|
|
|
577
515
|
});
|
|
578
516
|
connection.command('connect [service_entry] <peer_id> [token]')
|
|
579
517
|
.description('作为客户端连接远端(总是新建 session;不传 token 时自动签发)')
|
|
580
|
-
.action((
|
|
518
|
+
.action((serviceEntry, peerId, token) => {
|
|
581
519
|
const config = (0, config_1.loadConfig)(getCliOptions().config);
|
|
582
|
-
const finalServiceEntry = [
|
|
520
|
+
const finalServiceEntry = [serviceEntry, config.connection?.service_entry, config.server?.service_entry]
|
|
521
|
+
.find((value) => typeof value === 'string' && value.trim().length > 0);
|
|
583
522
|
const configuredToken = config.connection?.token?.trim();
|
|
584
523
|
const manualToken = (token ?? configuredToken)?.trim();
|
|
585
524
|
const autoTokenCfg = config.connection?.auto_token;
|
|
@@ -589,7 +528,7 @@ connection.command('connect [service_entry] <peer_id> [token]')
|
|
|
589
528
|
}
|
|
590
529
|
const params = {
|
|
591
530
|
serviceEntry: finalServiceEntry,
|
|
592
|
-
peerId
|
|
531
|
+
peerId,
|
|
593
532
|
timeoutMs: 5000,
|
|
594
533
|
};
|
|
595
534
|
if (manualToken && manualToken.length > 0) {
|
|
@@ -602,7 +541,7 @@ connection.command('connect [service_entry] <peer_id> [token]')
|
|
|
602
541
|
openapiEntry: autoTokenCfg?.openapi_entry,
|
|
603
542
|
accessId: autoTokenCfg?.access_id,
|
|
604
543
|
secretKey: autoTokenCfg?.secret_key,
|
|
605
|
-
localId: autoTokenCfg?.local_id ??
|
|
544
|
+
localId: autoTokenCfg?.local_id ?? peerId,
|
|
606
545
|
userTtlSeconds: autoTokenCfg?.user_ttl_seconds,
|
|
607
546
|
channelTtlSeconds: autoTokenCfg?.channel_ttl_seconds,
|
|
608
547
|
};
|
|
@@ -612,36 +551,22 @@ connection.command('connect [service_entry] <peer_id> [token]')
|
|
|
612
551
|
connection.command('disconnect').description('断开当前连接').action(() => {
|
|
613
552
|
runAndExit(runCommand('connection disconnect', {}, getCliOptions()));
|
|
614
553
|
});
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
.description('默认从环境变量读取 access/secret,并以 peer_id 作为默认 local_id 来签发 token')
|
|
618
|
-
.option('--access-id <accessId>', '显式 access id;不传时读取 ' + kTokenIssueAccessIdEnvVar)
|
|
619
|
-
.option('--secret-key <secretKey>', '显式 secret key;不传时读取 ' + kTokenIssueSecretKeyEnvVar)
|
|
620
|
-
.option('--local-id <localId>', '显式 local id;不传时默认使用 peer_id')
|
|
621
|
-
.option('--openapi-entry <url>', '可选 openapi entry;留空时走 runtime 默认值')
|
|
622
|
-
.option('--service-entry <entry>', '可选 service entry;用于组合连接 payload 与二维码 PNG')
|
|
623
|
-
.option('--user-ttl-seconds <seconds>', '可选 user token ttl(秒)')
|
|
624
|
-
.option('--channel-ttl-seconds <seconds>', '可选 channel token ttl(秒)')
|
|
625
|
-
.option('--qr-error-correction-level <level>', '二维码纠错级别:L/M/Q/H;默认 M')
|
|
626
|
-
.option('--ascii-max-columns <columns>', 'ASCII 二维码最大宽度;不传时优先读取当前终端宽度或 COLUMNS')
|
|
627
|
-
.action((peer_id, commandOptions) => {
|
|
628
|
-
runAndExit(runTokenIssueFromCli(peer_id, commandOptions, getCliOptions()));
|
|
629
|
-
});
|
|
630
|
-
const stream = program.command('stream').description('流控制:发送/接收/请求策略');
|
|
554
|
+
(0, token_command_1.registerTokenCommands)(program, getCliOptions, runAndExit);
|
|
555
|
+
const stream = program.command('stream').description('流控制:发送/接收/消息');
|
|
631
556
|
stream.command('list').description('查看流快照列表').action(() => {
|
|
632
557
|
runAndExit(runCommand('stream list', {}, getCliOptions()));
|
|
633
558
|
});
|
|
634
559
|
const streamSend = stream.command('send').description('发送流:绑定 prepared local assets 并发起上行');
|
|
635
560
|
streamSend.command('start <streamId> <media> <assets_dir>')
|
|
636
561
|
.description('开始发送流(绑定 source.local_assets 并触发上行)')
|
|
637
|
-
.action((streamId, media,
|
|
562
|
+
.action((streamId, media, assetsDir) => {
|
|
638
563
|
runAndExit(runCommand('stream send start', {
|
|
639
564
|
streamId: Number.parseInt(streamId, 10),
|
|
640
565
|
media,
|
|
641
566
|
source: {
|
|
642
567
|
mode: 'local_assets',
|
|
643
568
|
local_assets: {
|
|
644
|
-
assets_dir,
|
|
569
|
+
assets_dir: assetsDir,
|
|
645
570
|
},
|
|
646
571
|
},
|
|
647
572
|
}, getCliOptions()));
|
|
@@ -663,18 +588,13 @@ streamReceive.command('stop <streamId>')
|
|
|
663
588
|
.action((streamId) => {
|
|
664
589
|
runAndExit(runCommand('stream receive stop', { streamId: Number.parseInt(streamId, 10) }, getCliOptions()));
|
|
665
590
|
});
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
});
|
|
670
|
-
streamRequestPolicy.command('set <mode>')
|
|
671
|
-
.description('设置远端请求策略(manual|auto-if-bound)')
|
|
672
|
-
.action((mode) => {
|
|
673
|
-
runAndExit(runCommand('stream request-policy set', { mode }, getCliOptions()));
|
|
591
|
+
stream.command('message send <streamId> <payload>')
|
|
592
|
+
.description('发送 UTF-8 字符串型 stream message')
|
|
593
|
+
.action((streamId, payload) => {
|
|
594
|
+
runAndExit(runCommand('stream message send', { streamId: Number.parseInt(streamId, 10), payload }, getCliOptions()));
|
|
674
595
|
});
|
|
675
596
|
const output = program.command('output').description('输出挂载:文件等消费者 attach/detach');
|
|
676
|
-
output
|
|
677
|
-
.command('attach <streamId> <consumer> <mediaView> <format> <delivery> [targetPath] [maxFiles]')
|
|
597
|
+
output.command('attach <streamId> <consumer> <mediaView> <format> <delivery> [targetPath] [maxFiles]')
|
|
678
598
|
.description('挂载输出消费者(如 file_sink)')
|
|
679
599
|
.action((streamId, consumer, mediaView, format, delivery, targetPath, maxFiles) => {
|
|
680
600
|
const parsed = facade_1.OutputAttachCliParamsSchema.safeParse({
|
|
@@ -705,7 +625,7 @@ output.command('detach <outputId>').description('卸载输出消费者').action(
|
|
|
705
625
|
});
|
|
706
626
|
const command = program.command('command').description('命令通道:发送命令与事件跟踪');
|
|
707
627
|
command.command('send <commandId> <kind> <payloadEncoding> <payload>')
|
|
708
|
-
.description('
|
|
628
|
+
.description('发送字符串型命令并等待响应')
|
|
709
629
|
.action((commandId, kind, payloadEncoding, payload) => {
|
|
710
630
|
runAndExit(runCommand('command send', {
|
|
711
631
|
commandId: Number.parseInt(commandId, 10),
|
|
@@ -716,28 +636,6 @@ command.command('send <commandId> <kind> <payloadEncoding> <payload>')
|
|
|
716
636
|
timeoutMs: 5000,
|
|
717
637
|
}, getCliOptions()));
|
|
718
638
|
});
|
|
719
|
-
command.command('send-json <commandId> <kind> <payloadJson>')
|
|
720
|
-
.description('发送 JSON 命令并等待响应(JSON-first)')
|
|
721
|
-
.action((commandId, kind, payloadJson) => {
|
|
722
|
-
const parsed = facade_1.CommandSendJsonParamsSchema.safeParse({
|
|
723
|
-
commandId: Number.parseInt(commandId, 10),
|
|
724
|
-
kind,
|
|
725
|
-
payloadJson,
|
|
726
|
-
});
|
|
727
|
-
if (!parsed.success) {
|
|
728
|
-
const msg = parsed.error.issues.map((issue) => issue.message).join('; ');
|
|
729
|
-
throw new Error('invalid command send-json params: ' + msg);
|
|
730
|
-
}
|
|
731
|
-
const payload = mustParseJsonPayload(payloadJson);
|
|
732
|
-
runAndExit(runCommand('command send', {
|
|
733
|
-
commandId: parsed.data.commandId,
|
|
734
|
-
kind: parsed.data.kind,
|
|
735
|
-
payloadEncoding: 'json',
|
|
736
|
-
payload,
|
|
737
|
-
awaitResponse: true,
|
|
738
|
-
timeoutMs: 5000,
|
|
739
|
-
}, getCliOptions()));
|
|
740
|
-
});
|
|
741
639
|
command.command('tail').description('持续监听命令相关事件').action(() => {
|
|
742
640
|
runAndExit(tailEvents('command tail', ['command'], getCliOptions()));
|
|
743
641
|
});
|
|
@@ -745,14 +643,14 @@ const debug = program.command('debug').description('调试工具:连接 bootst
|
|
|
745
643
|
const debugBootstrap = debug.command('bootstrap').description('连接 bootstrap(不在 App Server 侧签发 token)');
|
|
746
644
|
debugBootstrap.command('qrcode <access_id> <secret_key> <peer_id> [service_entry]')
|
|
747
645
|
.description('生成客户端连接 bootstrap 二维码(payload 包含 access_id/secret_key/peer_id)')
|
|
748
|
-
.action((
|
|
646
|
+
.action((accessId, secretKey, peerId, serviceEntry) => {
|
|
749
647
|
const payload = JSON.stringify({
|
|
750
648
|
version: 1,
|
|
751
649
|
type: 'tirtc-connect-bootstrap',
|
|
752
|
-
access_id,
|
|
753
|
-
secret_key,
|
|
754
|
-
peer_id,
|
|
755
|
-
service_entry:
|
|
650
|
+
access_id: accessId,
|
|
651
|
+
secret_key: secretKey,
|
|
652
|
+
peer_id: peerId,
|
|
653
|
+
service_entry: serviceEntry ?? '',
|
|
756
654
|
generated_at: new Date().toISOString(),
|
|
757
655
|
});
|
|
758
656
|
runAndExit(runCommand('debug bootstrap qrcode', { payload, outputStem: 'connect-bootstrap' }, getCliOptions()));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const SPINNER_INTERVAL_MS = 120;
|
|
2
|
+
type ProgressStream = {
|
|
3
|
+
isTTY?: boolean;
|
|
4
|
+
write(chunk: string): boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare class ProgressIndicator {
|
|
7
|
+
private readonly stream;
|
|
8
|
+
private timer;
|
|
9
|
+
private frameIndex;
|
|
10
|
+
private activeMessage;
|
|
11
|
+
constructor(stream?: ProgressStream);
|
|
12
|
+
start(message: string): void;
|
|
13
|
+
update(message: string): void;
|
|
14
|
+
succeed(message: string): void;
|
|
15
|
+
fail(message: string): void;
|
|
16
|
+
private render;
|
|
17
|
+
private finish;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProgressIndicator = exports.SPINNER_INTERVAL_MS = void 0;
|
|
4
|
+
const SPINNER_FRAMES = ['|', '/', '-', '\\'];
|
|
5
|
+
exports.SPINNER_INTERVAL_MS = 120;
|
|
6
|
+
function clearLine(text) {
|
|
7
|
+
return `\r\u001b[2K${text}`;
|
|
8
|
+
}
|
|
9
|
+
class ProgressIndicator {
|
|
10
|
+
stream;
|
|
11
|
+
timer;
|
|
12
|
+
frameIndex = 0;
|
|
13
|
+
activeMessage;
|
|
14
|
+
constructor(stream = process.stderr) {
|
|
15
|
+
this.stream = stream;
|
|
16
|
+
}
|
|
17
|
+
start(message) {
|
|
18
|
+
this.activeMessage = message;
|
|
19
|
+
if (this.stream.isTTY) {
|
|
20
|
+
if (this.timer === undefined) {
|
|
21
|
+
this.render();
|
|
22
|
+
this.timer = setInterval(() => {
|
|
23
|
+
this.render();
|
|
24
|
+
}, exports.SPINNER_INTERVAL_MS);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
this.render();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.stream.write(`${message}...\n`);
|
|
31
|
+
}
|
|
32
|
+
update(message) {
|
|
33
|
+
this.start(message);
|
|
34
|
+
}
|
|
35
|
+
succeed(message) {
|
|
36
|
+
this.finish('OK', message);
|
|
37
|
+
}
|
|
38
|
+
fail(message) {
|
|
39
|
+
this.finish('FAIL', message);
|
|
40
|
+
}
|
|
41
|
+
render() {
|
|
42
|
+
if (this.activeMessage === undefined) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const frame = SPINNER_FRAMES[this.frameIndex % SPINNER_FRAMES.length];
|
|
46
|
+
this.frameIndex += 1;
|
|
47
|
+
this.stream.write(clearLine(`${frame} ${this.activeMessage}...`));
|
|
48
|
+
}
|
|
49
|
+
finish(tag, message) {
|
|
50
|
+
if (this.timer !== undefined) {
|
|
51
|
+
clearInterval(this.timer);
|
|
52
|
+
this.timer = undefined;
|
|
53
|
+
}
|
|
54
|
+
if (this.stream.isTTY) {
|
|
55
|
+
this.stream.write(`${clearLine(`[${tag}] ${message}`)}\n`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
this.stream.write(`[${tag}] ${message}\n`);
|
|
59
|
+
}
|
|
60
|
+
this.activeMessage = undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.ProgressIndicator = ProgressIndicator;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export type CliOptions = {
|
|
3
|
+
config?: string;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
session?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function registerTokenCommands(program: Command, getCliOptions: () => CliOptions, runAndExit: (promise: Promise<number>) => void): void;
|