@optique/core 1.0.0-dev.1682 → 1.0.0-dev.1686

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/dist/facade.cjs CHANGED
@@ -1180,6 +1180,53 @@ function disposeContextsSync(contexts) {
1180
1180
  if (errors.length > 1) throw new AggregateError(errors, "Failed to dispose one or more source contexts.");
1181
1181
  }
1182
1182
  /**
1183
+ * Body of {@link runWith}, extracted so that the caller can handle
1184
+ * disposal outside a `finally` block (avoiding `no-unsafe-finally` lint).
1185
+ */
1186
+ async function runWithBody(parser, programName, contexts, args, options) {
1187
+ require_validate.validateContextIds(contexts);
1188
+ if (needsEarlyExit(args, options)) {
1189
+ if (parser.$mode === "async") return runParser(parser, programName, args, options);
1190
+ return Promise.resolve(runParser(parser, programName, args, options));
1191
+ }
1192
+ const ctxOptions = options.contextOptions;
1193
+ const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts, ctxOptions);
1194
+ if (!needsTwoPhase) {
1195
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1196
+ if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1197
+ return Promise.resolve(runParser(augmentedParser, programName, args, options));
1198
+ }
1199
+ const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1200
+ let firstPassResult;
1201
+ let firstPassDeferred;
1202
+ let firstPassDeferredKeys;
1203
+ let firstPassFailed = false;
1204
+ try {
1205
+ if (parser.$mode === "async") firstPassResult = await require_parser.parseAsync(augmentedParser1, args);
1206
+ else firstPassResult = require_parser.parseSync(augmentedParser1, args);
1207
+ if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
1208
+ const result = firstPassResult;
1209
+ if (result.success) {
1210
+ firstPassResult = result.value;
1211
+ firstPassDeferred = result.deferred;
1212
+ firstPassDeferredKeys = result.deferredKeys;
1213
+ } else firstPassFailed = true;
1214
+ }
1215
+ } catch {
1216
+ firstPassFailed = true;
1217
+ }
1218
+ if (firstPassFailed) {
1219
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1220
+ if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1221
+ return Promise.resolve(runParser(augmentedParser, programName, args, options));
1222
+ }
1223
+ const { annotationsList: phase2AnnotationsList } = await collectAnnotations(contexts, firstPassResult, ctxOptions, firstPassDeferred, firstPassDeferredKeys);
1224
+ const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1225
+ const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1226
+ if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
1227
+ return Promise.resolve(runParser(augmentedParser2, programName, args, options));
1228
+ }
1229
+ /**
1183
1230
  * Runs a parser with multiple source contexts.
1184
1231
  *
1185
1232
  * This function automatically handles static and dynamic contexts with proper
@@ -1207,6 +1254,9 @@ function disposeContextsSync(contexts) {
1207
1254
  * @returns Promise that resolves to the parsed result.
1208
1255
  * @throws {TypeError} If two or more contexts share the same
1209
1256
  * {@link SourceContext.id}.
1257
+ * @throws {SuppressedError} If parsing fails and a context's disposal also
1258
+ * throws. The original parse error is available via `.suppressed` and the
1259
+ * disposal error via `.error`.
1210
1260
  * @since 0.10.0
1211
1261
  *
1212
1262
  * @example
@@ -1229,49 +1279,6 @@ function disposeContextsSync(contexts) {
1229
1279
  * );
1230
1280
  * ```
1231
1281
  */
1232
- async function runWithBody(parser, programName, contexts, args, options) {
1233
- require_validate.validateContextIds(contexts);
1234
- if (needsEarlyExit(args, options)) {
1235
- if (parser.$mode === "async") return runParser(parser, programName, args, options);
1236
- return Promise.resolve(runParser(parser, programName, args, options));
1237
- }
1238
- const ctxOptions = options?.contextOptions;
1239
- const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts, ctxOptions);
1240
- if (!needsTwoPhase) {
1241
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1242
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1243
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
1244
- }
1245
- const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1246
- let firstPassResult;
1247
- let firstPassDeferred;
1248
- let firstPassDeferredKeys;
1249
- let firstPassFailed = false;
1250
- try {
1251
- if (parser.$mode === "async") firstPassResult = await require_parser.parseAsync(augmentedParser1, args);
1252
- else firstPassResult = require_parser.parseSync(augmentedParser1, args);
1253
- if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
1254
- const result = firstPassResult;
1255
- if (result.success) {
1256
- firstPassResult = result.value;
1257
- firstPassDeferred = result.deferred;
1258
- firstPassDeferredKeys = result.deferredKeys;
1259
- } else firstPassFailed = true;
1260
- }
1261
- } catch {
1262
- firstPassFailed = true;
1263
- }
1264
- if (firstPassFailed) {
1265
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1266
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1267
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
1268
- }
1269
- const { annotationsList: phase2AnnotationsList } = await collectAnnotations(contexts, firstPassResult, ctxOptions, firstPassDeferred, firstPassDeferredKeys);
1270
- const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1271
- const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1272
- if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
1273
- return Promise.resolve(runParser(augmentedParser2, programName, args, options));
1274
- }
1275
1282
  async function runWith(parser, programName, contexts, options) {
1276
1283
  const args = options?.args ?? [];
1277
1284
  if (contexts.length === 0) {
@@ -1303,7 +1310,7 @@ async function runWith(parser, programName, contexts, options) {
1303
1310
  function runWithSyncBody(parser, programName, contexts, args, options) {
1304
1311
  require_validate.validateContextIds(contexts);
1305
1312
  if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
1306
- const ctxOptions = options?.contextOptions;
1313
+ const ctxOptions = options.contextOptions;
1307
1314
  const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts, ctxOptions);
1308
1315
  if (!needsTwoPhase) {
1309
1316
  const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
@@ -1348,6 +1355,9 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
1348
1355
  * {@link SourceContext.id}.
1349
1356
  * @throws {Error} If any context returns a Promise or if a context's
1350
1357
  * `[Symbol.asyncDispose]` returns a Promise.
1358
+ * @throws {SuppressedError} If parsing fails and a context's disposal also
1359
+ * throws. The original parse error is available via `.suppressed` and the
1360
+ * disposal error via `.error`.
1351
1361
  * @since 0.10.0
1352
1362
  */
1353
1363
  function runWithSync(parser, programName, contexts, options) {
package/dist/facade.d.cts CHANGED
@@ -402,6 +402,59 @@ type ContextOptionsParam<TContexts extends readonly SourceContext<unknown>[], TV
402
402
  } : {
403
403
  readonly contextOptions: ExtractRequiredOptions<TContexts, TValue>;
404
404
  };
405
+ /**
406
+ * Runs a parser with multiple source contexts.
407
+ *
408
+ * This function automatically handles static and dynamic contexts with proper
409
+ * priority. Earlier contexts in the array override later ones.
410
+ *
411
+ * The function uses a smart two-phase approach:
412
+ *
413
+ * 1. *Phase 1*: Collect annotations from all contexts (static contexts return
414
+ * their data, dynamic contexts may return empty).
415
+ * 2. *First parse*: Parse with Phase 1 annotations.
416
+ * 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with the first
417
+ * parse result.
418
+ * 4. *Second parse*: Parse again with merged annotations from both phases.
419
+ *
420
+ * If all contexts are static (no dynamic contexts), the second parse is skipped
421
+ * for optimization.
422
+ *
423
+ * @template TParser The parser type.
424
+ * @template THelp Return type when help is shown.
425
+ * @template TError Return type when an error occurs.
426
+ * @param parser The parser to execute.
427
+ * @param programName Name of the program for help/error output.
428
+ * @param contexts Source contexts to use (priority: earlier overrides later).
429
+ * @param options Run options including args, help, version, etc.
430
+ * @returns Promise that resolves to the parsed result.
431
+ * @throws {TypeError} If two or more contexts share the same
432
+ * {@link SourceContext.id}.
433
+ * @throws {SuppressedError} If parsing fails and a context's disposal also
434
+ * throws. The original parse error is available via `.suppressed` and the
435
+ * disposal error via `.error`.
436
+ * @since 0.10.0
437
+ *
438
+ * @example
439
+ * ```typescript
440
+ * import { runWith } from "@optique/core/facade";
441
+ * import type { SourceContext } from "@optique/core/context";
442
+ *
443
+ * const envContext: SourceContext = {
444
+ * id: Symbol.for("@myapp/env"),
445
+ * getAnnotations() {
446
+ * return { [Symbol.for("@myapp/env")]: process.env };
447
+ * }
448
+ * };
449
+ *
450
+ * const result = await runWith(
451
+ * parser,
452
+ * "myapp",
453
+ * [envContext],
454
+ * { args: process.argv.slice(2) }
455
+ * );
456
+ * ```
457
+ */
405
458
  declare function runWith<TParser extends Parser<Mode, unknown, unknown>, TContexts extends readonly SourceContext<unknown>[], THelp = void, TError = never>(parser: TParser, programName: string, contexts: TContexts, options: RunWithOptions<THelp, TError> & ContextOptionsParam<TContexts, InferValue<TParser>>): Promise<InferValue<TParser>>;
406
459
  /**
407
460
  * Runs a synchronous parser with multiple source contexts.
@@ -423,6 +476,9 @@ declare function runWith<TParser extends Parser<Mode, unknown, unknown>, TContex
423
476
  * {@link SourceContext.id}.
424
477
  * @throws {Error} If any context returns a Promise or if a context's
425
478
  * `[Symbol.asyncDispose]` returns a Promise.
479
+ * @throws {SuppressedError} If parsing fails and a context's disposal also
480
+ * throws. The original parse error is available via `.suppressed` and the
481
+ * disposal error via `.error`.
426
482
  * @since 0.10.0
427
483
  */
428
484
  declare function runWithSync<TParser extends Parser<"sync", unknown, unknown>, TContexts extends readonly SourceContext<unknown>[], THelp = void, TError = never>(parser: TParser, programName: string, contexts: TContexts, options: RunWithOptions<THelp, TError> & ContextOptionsParam<TContexts, InferValue<TParser>>): InferValue<TParser>;
package/dist/facade.d.ts CHANGED
@@ -402,6 +402,59 @@ type ContextOptionsParam<TContexts extends readonly SourceContext<unknown>[], TV
402
402
  } : {
403
403
  readonly contextOptions: ExtractRequiredOptions<TContexts, TValue>;
404
404
  };
405
+ /**
406
+ * Runs a parser with multiple source contexts.
407
+ *
408
+ * This function automatically handles static and dynamic contexts with proper
409
+ * priority. Earlier contexts in the array override later ones.
410
+ *
411
+ * The function uses a smart two-phase approach:
412
+ *
413
+ * 1. *Phase 1*: Collect annotations from all contexts (static contexts return
414
+ * their data, dynamic contexts may return empty).
415
+ * 2. *First parse*: Parse with Phase 1 annotations.
416
+ * 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with the first
417
+ * parse result.
418
+ * 4. *Second parse*: Parse again with merged annotations from both phases.
419
+ *
420
+ * If all contexts are static (no dynamic contexts), the second parse is skipped
421
+ * for optimization.
422
+ *
423
+ * @template TParser The parser type.
424
+ * @template THelp Return type when help is shown.
425
+ * @template TError Return type when an error occurs.
426
+ * @param parser The parser to execute.
427
+ * @param programName Name of the program for help/error output.
428
+ * @param contexts Source contexts to use (priority: earlier overrides later).
429
+ * @param options Run options including args, help, version, etc.
430
+ * @returns Promise that resolves to the parsed result.
431
+ * @throws {TypeError} If two or more contexts share the same
432
+ * {@link SourceContext.id}.
433
+ * @throws {SuppressedError} If parsing fails and a context's disposal also
434
+ * throws. The original parse error is available via `.suppressed` and the
435
+ * disposal error via `.error`.
436
+ * @since 0.10.0
437
+ *
438
+ * @example
439
+ * ```typescript
440
+ * import { runWith } from "@optique/core/facade";
441
+ * import type { SourceContext } from "@optique/core/context";
442
+ *
443
+ * const envContext: SourceContext = {
444
+ * id: Symbol.for("@myapp/env"),
445
+ * getAnnotations() {
446
+ * return { [Symbol.for("@myapp/env")]: process.env };
447
+ * }
448
+ * };
449
+ *
450
+ * const result = await runWith(
451
+ * parser,
452
+ * "myapp",
453
+ * [envContext],
454
+ * { args: process.argv.slice(2) }
455
+ * );
456
+ * ```
457
+ */
405
458
  declare function runWith<TParser extends Parser<Mode, unknown, unknown>, TContexts extends readonly SourceContext<unknown>[], THelp = void, TError = never>(parser: TParser, programName: string, contexts: TContexts, options: RunWithOptions<THelp, TError> & ContextOptionsParam<TContexts, InferValue<TParser>>): Promise<InferValue<TParser>>;
406
459
  /**
407
460
  * Runs a synchronous parser with multiple source contexts.
@@ -423,6 +476,9 @@ declare function runWith<TParser extends Parser<Mode, unknown, unknown>, TContex
423
476
  * {@link SourceContext.id}.
424
477
  * @throws {Error} If any context returns a Promise or if a context's
425
478
  * `[Symbol.asyncDispose]` returns a Promise.
479
+ * @throws {SuppressedError} If parsing fails and a context's disposal also
480
+ * throws. The original parse error is available via `.suppressed` and the
481
+ * disposal error via `.error`.
426
482
  * @since 0.10.0
427
483
  */
428
484
  declare function runWithSync<TParser extends Parser<"sync", unknown, unknown>, TContexts extends readonly SourceContext<unknown>[], THelp = void, TError = never>(parser: TParser, programName: string, contexts: TContexts, options: RunWithOptions<THelp, TError> & ContextOptionsParam<TContexts, InferValue<TParser>>): InferValue<TParser>;
package/dist/facade.js CHANGED
@@ -1180,6 +1180,53 @@ function disposeContextsSync(contexts) {
1180
1180
  if (errors.length > 1) throw new AggregateError(errors, "Failed to dispose one or more source contexts.");
1181
1181
  }
1182
1182
  /**
1183
+ * Body of {@link runWith}, extracted so that the caller can handle
1184
+ * disposal outside a `finally` block (avoiding `no-unsafe-finally` lint).
1185
+ */
1186
+ async function runWithBody(parser, programName, contexts, args, options) {
1187
+ validateContextIds(contexts);
1188
+ if (needsEarlyExit(args, options)) {
1189
+ if (parser.$mode === "async") return runParser(parser, programName, args, options);
1190
+ return Promise.resolve(runParser(parser, programName, args, options));
1191
+ }
1192
+ const ctxOptions = options.contextOptions;
1193
+ const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts, ctxOptions);
1194
+ if (!needsTwoPhase) {
1195
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1196
+ if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1197
+ return Promise.resolve(runParser(augmentedParser, programName, args, options));
1198
+ }
1199
+ const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1200
+ let firstPassResult;
1201
+ let firstPassDeferred;
1202
+ let firstPassDeferredKeys;
1203
+ let firstPassFailed = false;
1204
+ try {
1205
+ if (parser.$mode === "async") firstPassResult = await parseAsync(augmentedParser1, args);
1206
+ else firstPassResult = parseSync(augmentedParser1, args);
1207
+ if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
1208
+ const result = firstPassResult;
1209
+ if (result.success) {
1210
+ firstPassResult = result.value;
1211
+ firstPassDeferred = result.deferred;
1212
+ firstPassDeferredKeys = result.deferredKeys;
1213
+ } else firstPassFailed = true;
1214
+ }
1215
+ } catch {
1216
+ firstPassFailed = true;
1217
+ }
1218
+ if (firstPassFailed) {
1219
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1220
+ if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1221
+ return Promise.resolve(runParser(augmentedParser, programName, args, options));
1222
+ }
1223
+ const { annotationsList: phase2AnnotationsList } = await collectAnnotations(contexts, firstPassResult, ctxOptions, firstPassDeferred, firstPassDeferredKeys);
1224
+ const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1225
+ const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1226
+ if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
1227
+ return Promise.resolve(runParser(augmentedParser2, programName, args, options));
1228
+ }
1229
+ /**
1183
1230
  * Runs a parser with multiple source contexts.
1184
1231
  *
1185
1232
  * This function automatically handles static and dynamic contexts with proper
@@ -1207,6 +1254,9 @@ function disposeContextsSync(contexts) {
1207
1254
  * @returns Promise that resolves to the parsed result.
1208
1255
  * @throws {TypeError} If two or more contexts share the same
1209
1256
  * {@link SourceContext.id}.
1257
+ * @throws {SuppressedError} If parsing fails and a context's disposal also
1258
+ * throws. The original parse error is available via `.suppressed` and the
1259
+ * disposal error via `.error`.
1210
1260
  * @since 0.10.0
1211
1261
  *
1212
1262
  * @example
@@ -1229,49 +1279,6 @@ function disposeContextsSync(contexts) {
1229
1279
  * );
1230
1280
  * ```
1231
1281
  */
1232
- async function runWithBody(parser, programName, contexts, args, options) {
1233
- validateContextIds(contexts);
1234
- if (needsEarlyExit(args, options)) {
1235
- if (parser.$mode === "async") return runParser(parser, programName, args, options);
1236
- return Promise.resolve(runParser(parser, programName, args, options));
1237
- }
1238
- const ctxOptions = options?.contextOptions;
1239
- const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts, ctxOptions);
1240
- if (!needsTwoPhase) {
1241
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1242
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1243
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
1244
- }
1245
- const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1246
- let firstPassResult;
1247
- let firstPassDeferred;
1248
- let firstPassDeferredKeys;
1249
- let firstPassFailed = false;
1250
- try {
1251
- if (parser.$mode === "async") firstPassResult = await parseAsync(augmentedParser1, args);
1252
- else firstPassResult = parseSync(augmentedParser1, args);
1253
- if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
1254
- const result = firstPassResult;
1255
- if (result.success) {
1256
- firstPassResult = result.value;
1257
- firstPassDeferred = result.deferred;
1258
- firstPassDeferredKeys = result.deferredKeys;
1259
- } else firstPassFailed = true;
1260
- }
1261
- } catch {
1262
- firstPassFailed = true;
1263
- }
1264
- if (firstPassFailed) {
1265
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1266
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1267
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
1268
- }
1269
- const { annotationsList: phase2AnnotationsList } = await collectAnnotations(contexts, firstPassResult, ctxOptions, firstPassDeferred, firstPassDeferredKeys);
1270
- const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1271
- const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1272
- if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
1273
- return Promise.resolve(runParser(augmentedParser2, programName, args, options));
1274
- }
1275
1282
  async function runWith(parser, programName, contexts, options) {
1276
1283
  const args = options?.args ?? [];
1277
1284
  if (contexts.length === 0) {
@@ -1303,7 +1310,7 @@ async function runWith(parser, programName, contexts, options) {
1303
1310
  function runWithSyncBody(parser, programName, contexts, args, options) {
1304
1311
  validateContextIds(contexts);
1305
1312
  if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
1306
- const ctxOptions = options?.contextOptions;
1313
+ const ctxOptions = options.contextOptions;
1307
1314
  const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts, ctxOptions);
1308
1315
  if (!needsTwoPhase) {
1309
1316
  const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
@@ -1348,6 +1355,9 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
1348
1355
  * {@link SourceContext.id}.
1349
1356
  * @throws {Error} If any context returns a Promise or if a context's
1350
1357
  * `[Symbol.asyncDispose]` returns a Promise.
1358
+ * @throws {SuppressedError} If parsing fails and a context's disposal also
1359
+ * throws. The original parse error is available via `.suppressed` and the
1360
+ * disposal error via `.error`.
1351
1361
  * @since 0.10.0
1352
1362
  */
1353
1363
  function runWithSync(parser, programName, contexts, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1682+79707747",
3
+ "version": "1.0.0-dev.1686+4d8b8f84",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",