tirtc-devtools-cli 0.0.3 → 0.0.4

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 CHANGED
@@ -1,6 +1,6 @@
1
- # TiRTC DevTool CLI
1
+ # TiRTC DevTools CLI
2
2
 
3
- `devtools/cli/` 承接 `TiRTC DevTool CLI`。它是当前唯一用户界面的产品门面 (command client),为用户提供了一套操作 `Matrix Host` 控制面的终端工具。
3
+ `devtools/cli/` 承接 `TiRTC DevTools CLI`。它是当前唯一用户界面的产品门面 (command client),为用户提供了一套操作 `Matrix Host` 控制面的终端工具。
4
4
 
5
5
  ## 使用说明 (C-Lite)
6
6
 
package/USAGE.md CHANGED
@@ -1,4 +1,4 @@
1
- # TiRTC DevTool CLI(用户手册)
1
+ # TiRTC DevTools CLI(用户手册)
2
2
 
3
3
  `tirtc-devtools-cli` 是基于 `app-server/host` 的命令行门面,用来控制会话、服务、连接、流、命令通道与调试工具。
4
4
 
@@ -126,19 +126,23 @@ node devtools/cli/bin/tirtc-devtools-cli.js --help
126
126
  - `--service-entry <entry>`:附加到组合 payload,便于扫码后直接连到目标服务。
127
127
  - `--user-ttl-seconds <seconds>`:覆盖 user token ttl。
128
128
  - `--channel-ttl-seconds <seconds>`:覆盖 channel token ttl。
129
+ - `--qr-error-correction-level <level>`:设置二维码纠错级别,可选 `L/M/Q/H`,默认 `M`。
130
+ - `--ascii-max-columns <columns>`:显式限制 ASCII 二维码最大宽度;不传时优先读取当前终端宽度或 `COLUMNS`。
129
131
 
130
132
  默认输出四部分:
131
133
 
132
134
  - `Issued Token Summary`:面向人类阅读的摘要,快速确认 local/peer/entry/ttl。
133
135
  - `Token`:单独罗列 token 字符串,便于复制粘贴。
134
136
  - `Payload JSON`:组合 JSON,对方或 Agent 可直接复制使用。
137
+ - `QR Code ASCII`:非 `--json` 模式下尽量输出 ASCII 二维码;如果终端宽度不足,会提示改看 PNG。
135
138
  - `QR Code PNG`:基于组合 JSON 生成的本地 PNG 文件绝对路径。适合人工打开图片后扫码。
136
139
 
137
140
  说明:
138
141
 
139
142
  - 组合 JSON 的 `type` 固定为 `tirtc-connect-token`。
140
143
  - PNG 二维码承载的是组合 JSON,不再暴露 `secret_key`。
141
- - `--json` 模式下会输出 `payload`、`payloadJson`、`token` 和 `qrCodePngPath`。
144
+ - `--json` 模式下只输出 `payload`、`payloadJson`、`token` 和 `qrCodePngPath`,不输出 ASCII 二维码。
145
+ - 如果觉得 ASCII 二维码太密,可把纠错级别从默认 `M` 调低到 `L`;如果希望更耐遮挡,可调高到 `Q` 或 `H`,但二维码会更密。
142
146
 
143
147
  ### Stream
144
148
 
@@ -9,7 +9,7 @@ const session_manager_1 = require("./session_manager");
9
9
  const media_assets_1 = require("./media_assets");
10
10
  const transport_1 = require("./transport");
11
11
  const token_tool_1 = require("./token_tool");
12
- const CLI_VERSION = '0.0.3';
12
+ const CLI_VERSION = '0.0.4';
13
13
  const HOST_VERSION = '1.0.0';
14
14
  const PROTOCOL_VERSION = '1.0.0';
15
15
  if (process.argv.includes('--version') || process.argv.includes('-V')) {
@@ -551,6 +551,8 @@ token.command('issue <access_id> <secret_key> <local_id> <peer_id>')
551
551
  .option('--service-entry <entry>', '可选 service entry;用于组合连接 payload 与二维码 PNG')
552
552
  .option('--user-ttl-seconds <seconds>', '可选 user token ttl(秒)')
553
553
  .option('--channel-ttl-seconds <seconds>', '可选 channel token ttl(秒)')
554
+ .option('--qr-error-correction-level <level>', '二维码纠错级别:L/M/Q/H;默认 M')
555
+ .option('--ascii-max-columns <columns>', 'ASCII 二维码最大宽度;不传时优先读取当前终端宽度或 COLUMNS')
554
556
  .action((access_id, secret_key, local_id, peer_id, commandOptions) => {
555
557
  const parsePositiveInt = (name, raw) => {
556
558
  if (raw === undefined) {
@@ -562,6 +564,16 @@ token.command('issue <access_id> <secret_key> <local_id> <peer_id>')
562
564
  }
563
565
  return parsed;
564
566
  };
567
+ const parseQrErrorCorrectionLevel = (raw) => {
568
+ if (raw === undefined) {
569
+ return undefined;
570
+ }
571
+ const normalized = raw.trim().toUpperCase();
572
+ if (normalized !== 'L' && normalized !== 'M' && normalized !== 'Q' && normalized !== 'H') {
573
+ throw new Error('qr-error-correction-level must be one of: L, M, Q, H');
574
+ }
575
+ return normalized;
576
+ };
565
577
  runAndExit(runTokenIssue({
566
578
  accessId: access_id,
567
579
  secretKey: secret_key,
@@ -571,6 +583,8 @@ token.command('issue <access_id> <secret_key> <local_id> <peer_id>')
571
583
  serviceEntry: commandOptions.serviceEntry,
572
584
  userTtlSeconds: parsePositiveInt('user-ttl-seconds', commandOptions.userTtlSeconds),
573
585
  channelTtlSeconds: parsePositiveInt('channel-ttl-seconds', commandOptions.channelTtlSeconds),
586
+ qrErrorCorrectionLevel: parseQrErrorCorrectionLevel(commandOptions.qrErrorCorrectionLevel),
587
+ asciiMaxColumns: parsePositiveInt('ascii-max-columns', commandOptions.asciiMaxColumns),
574
588
  }, getCliOptions()));
575
589
  });
576
590
  const stream = program.command('stream').description('流控制:发送/接收/请求策略');
@@ -7,6 +7,8 @@ export type TokenIssueInput = {
7
7
  serviceEntry?: string;
8
8
  userTtlSeconds?: number;
9
9
  channelTtlSeconds?: number;
10
+ qrErrorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';
11
+ asciiMaxColumns?: number;
10
12
  };
11
13
  export type IssuedTokenPayload = {
12
14
  version: 1;
@@ -26,10 +28,11 @@ export type TokenIssueOutput = {
26
28
  token: string;
27
29
  qrCodePngPath: string;
28
30
  qrCodeAscii: string;
31
+ qrCodeAsciiIncluded: boolean;
29
32
  };
30
33
  export declare function issueToken(input: TokenIssueInput): Promise<string>;
31
34
  export declare function buildIssuedTokenPayload(input: TokenIssueInput, token: string): IssuedTokenPayload;
32
- export declare function writePngQrcode(payloadJson: string, outputPath: string): Promise<string>;
33
- export declare function buildAsciiQrcode(payloadJson: string): Promise<string>;
35
+ export declare function writePngQrcode(payloadJson: string, outputPath: string, errorCorrectionLevel: 'L' | 'M' | 'Q' | 'H'): Promise<string>;
36
+ export declare function buildAsciiQrcode(payloadJson: string, terminalColumns?: number, errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H'): Promise<string>;
34
37
  export declare function issueTokenWithQrcode(input: TokenIssueInput): Promise<TokenIssueOutput>;
35
38
  export declare function formatTokenIssueConsoleOutput(output: TokenIssueOutput): string;
@@ -17,8 +17,6 @@ const embedded_paths_1 = require("./embedded_paths");
17
17
  const kDefaultOpenapiEntry = 'http://api-test-tirtc.tange365.com';
18
18
  const kDefaultUserTtlSeconds = 86400;
19
19
  const kDefaultChannelTtlSeconds = 300;
20
- const kAsciiQrBlack = '██';
21
- const kAsciiQrWhite = ' ';
22
20
  const kAsciiQrQuietZoneModules = 2;
23
21
  function pathExists(filePath) {
24
22
  return fs_1.default.existsSync(filePath);
@@ -176,6 +174,58 @@ function issueTokenViaSubprocess(addonPath, input) {
176
174
  function readQrModule(modules, row, column) {
177
175
  return Boolean(modules.data[row * modules.size + column]);
178
176
  }
177
+ function resolveTerminalColumns() {
178
+ if (typeof process.stdout.columns === 'number' && Number.isInteger(process.stdout.columns) && process.stdout.columns > 0) {
179
+ return process.stdout.columns;
180
+ }
181
+ const raw = process.env.COLUMNS;
182
+ if (!raw) {
183
+ return undefined;
184
+ }
185
+ const parsed = Number.parseInt(raw, 10);
186
+ if (!Number.isInteger(parsed) || parsed <= 0) {
187
+ return undefined;
188
+ }
189
+ return parsed;
190
+ }
191
+ function resolveAsciiMaxColumns(explicit) {
192
+ if (explicit !== undefined) {
193
+ return explicit;
194
+ }
195
+ return resolveTerminalColumns();
196
+ }
197
+ function readQrModuleOrWhite(modules, row, column) {
198
+ if (row < 0 || column < 0 || row >= modules.size || column >= modules.size) {
199
+ return false;
200
+ }
201
+ return readQrModule(modules, row, column);
202
+ }
203
+ function renderHalfBlockQr(modules) {
204
+ const lines = [];
205
+ const start = -kAsciiQrQuietZoneModules;
206
+ const end = modules.size + kAsciiQrQuietZoneModules;
207
+ for (let row = start; row < end; row += 2) {
208
+ let line = '';
209
+ for (let column = start; column < end; column += 1) {
210
+ const top = readQrModuleOrWhite(modules, row, column);
211
+ const bottom = readQrModuleOrWhite(modules, row + 1, column);
212
+ if (top && bottom) {
213
+ line += '█';
214
+ }
215
+ else if (top) {
216
+ line += '▀';
217
+ }
218
+ else if (bottom) {
219
+ line += '▄';
220
+ }
221
+ else {
222
+ line += ' ';
223
+ }
224
+ }
225
+ lines.push(line);
226
+ }
227
+ return lines.join('\n');
228
+ }
179
229
  async function issueToken(input) {
180
230
  const runtimePlatform = resolveRuntimePlatform();
181
231
  const runtimeBundleRoot = resolveRuntimeBundleRoot(runtimePlatform);
@@ -204,50 +254,45 @@ function buildIssuedTokenPayload(input, token) {
204
254
  generated_at: new Date().toISOString(),
205
255
  };
206
256
  }
207
- async function writePngQrcode(payloadJson, outputPath) {
257
+ async function writePngQrcode(payloadJson, outputPath, errorCorrectionLevel) {
208
258
  const resolvedPath = path_1.default.resolve(outputPath);
209
259
  ensureDirectory(path_1.default.dirname(resolvedPath));
210
260
  await qrcode_1.default.toFile(resolvedPath, payloadJson, {
211
- errorCorrectionLevel: 'M',
261
+ errorCorrectionLevel,
212
262
  margin: 2,
213
263
  type: 'png',
214
264
  width: 960,
215
265
  });
216
266
  return resolvedPath;
217
267
  }
218
- async function buildAsciiQrcode(payloadJson) {
219
- const qr = qrcode_1.default.create(payloadJson, { errorCorrectionLevel: 'M' });
220
- const quietZone = kAsciiQrWhite.repeat(kAsciiQrQuietZoneModules);
221
- const fullQuietLine = quietZone + kAsciiQrWhite.repeat(qr.modules.size) + quietZone;
222
- const lines = [];
223
- for (let index = 0; index < kAsciiQrQuietZoneModules; index += 1) {
224
- lines.push(fullQuietLine);
268
+ async function buildAsciiQrcode(payloadJson, terminalColumns, errorCorrectionLevel = 'M') {
269
+ const qr = qrcode_1.default.create(payloadJson, { errorCorrectionLevel });
270
+ const requiredColumns = qr.modules.size + (kAsciiQrQuietZoneModules * 2);
271
+ const availableColumns = resolveAsciiMaxColumns(terminalColumns);
272
+ if (availableColumns !== undefined && availableColumns < requiredColumns) {
273
+ return [
274
+ '(omitted: terminal width too narrow for ASCII QR)',
275
+ `required_columns: ${requiredColumns}`,
276
+ `available_columns: ${availableColumns}`,
277
+ 'open the QR Code PNG path below instead.',
278
+ ].join('\n');
225
279
  }
226
- for (let row = 0; row < qr.modules.size; row += 1) {
227
- let line = quietZone;
228
- for (let column = 0; column < qr.modules.size; column += 1) {
229
- line += readQrModule(qr.modules, row, column) ? kAsciiQrBlack : kAsciiQrWhite;
230
- }
231
- line += quietZone;
232
- lines.push(line);
233
- }
234
- for (let index = 0; index < kAsciiQrQuietZoneModules; index += 1) {
235
- lines.push(fullQuietLine);
236
- }
237
- return lines.join('\n');
280
+ return renderHalfBlockQr(qr.modules);
238
281
  }
239
282
  async function issueTokenWithQrcode(input) {
240
283
  const token = await issueToken(input);
241
284
  const payload = buildIssuedTokenPayload(input, token);
242
285
  const payloadJson = JSON.stringify(payload);
243
- const qrCodePngPath = await writePngQrcode(payloadJson, buildQrCodePngPath(payload));
244
- const qrCodeAscii = await buildAsciiQrcode(payloadJson);
286
+ const qrErrorCorrectionLevel = input.qrErrorCorrectionLevel ?? 'M';
287
+ const qrCodePngPath = await writePngQrcode(payloadJson, buildQrCodePngPath(payload), qrErrorCorrectionLevel);
288
+ const qrCodeAscii = await buildAsciiQrcode(payloadJson, input.asciiMaxColumns, qrErrorCorrectionLevel);
245
289
  return {
246
290
  payload,
247
291
  payloadJson,
248
292
  token,
249
293
  qrCodePngPath,
250
294
  qrCodeAscii,
295
+ qrCodeAsciiIncluded: !qrCodeAscii.startsWith('(omitted:'),
251
296
  };
252
297
  }
253
298
  function formatTokenIssueConsoleOutput(output) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tirtc-devtools-cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "bin": {