@promptbook/node 0.114.0-24 → 0.114.0-26

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.
Files changed (30) hide show
  1. package/README.md +2 -2
  2. package/esm/index.es.js +243 -54
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/scripts/run-codex-prompts/runners/common/estimateUsageFromTextLength.d.ts +33 -0
  5. package/esm/scripts/run-codex-prompts/runners/common/modelPricing.d.ts +41 -0
  6. package/esm/scripts/run-codex-prompts/runners/gemini/gemini-pricing.d.ts +5 -18
  7. package/esm/scripts/run-codex-prompts/runners/qwen-code/QwenCodeRunner.d.ts +23 -0
  8. package/esm/scripts/run-codex-prompts/runners/qwen-code/buildQwenCodeScript.d.ts +8 -0
  9. package/esm/scripts/run-codex-prompts/runners/qwen-code/parseQwenCodeUsageFromOutput.d.ts +9 -0
  10. package/esm/scripts/run-codex-prompts/runners/qwen-code/qwen-code-pricing.d.ts +30 -0
  11. package/esm/src/book-3.0/cliAgentEnv.d.ts +3 -3
  12. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +8 -2
  13. package/esm/src/commitments/WRITING_RULES/WRITING_RULES.d.ts +14 -7
  14. package/esm/src/commitments/index.d.ts +1 -1
  15. package/esm/src/version.d.ts +1 -1
  16. package/package.json +2 -2
  17. package/umd/index.umd.js +243 -54
  18. package/umd/index.umd.js.map +1 -1
  19. package/umd/scripts/run-codex-prompts/runners/common/estimateUsageFromTextLength.d.ts +33 -0
  20. package/umd/scripts/run-codex-prompts/runners/common/modelPricing.d.ts +41 -0
  21. package/umd/scripts/run-codex-prompts/runners/gemini/gemini-pricing.d.ts +5 -18
  22. package/umd/scripts/run-codex-prompts/runners/qwen-code/QwenCodeRunner.d.ts +23 -0
  23. package/umd/scripts/run-codex-prompts/runners/qwen-code/buildQwenCodeScript.d.ts +8 -0
  24. package/umd/scripts/run-codex-prompts/runners/qwen-code/parseQwenCodeUsageFromOutput.d.ts +9 -0
  25. package/umd/scripts/run-codex-prompts/runners/qwen-code/qwen-code-pricing.d.ts +30 -0
  26. package/umd/src/book-3.0/cliAgentEnv.d.ts +3 -3
  27. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +8 -2
  28. package/umd/src/commitments/WRITING_RULES/WRITING_RULES.d.ts +14 -7
  29. package/umd/src/commitments/index.d.ts +1 -1
  30. package/umd/src/version.d.ts +1 -1
package/README.md CHANGED
@@ -486,7 +486,7 @@ Prompts marked with `[-]` are not ready yet, prompts containing `@@@` are treate
486
486
 
487
487
  #### Features
488
488
 
489
- - **Multi-runner execution:** `openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`
489
+ - **Multi-runner execution:** `openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`, `qwen-code`
490
490
  - **Context injection:** `--agent agents/coding/developer.book --context AGENTS.md` or inline extra instructions
491
491
  - **Reasoning control:** `--thinking-level low|medium|high|xhigh` for supported runners
492
492
  - **Unattended or interactive runs:** default auto mode, or `--no-auto` to wait for user confirmation before each prompt
@@ -563,7 +563,7 @@ ptbk coder verify
563
563
  | Flag | Purpose |
564
564
  | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
565
565
  | `--harness <name>` | Selects the coding harness. |
566
- | `--model <model>` | Chooses the runner model; required for `openai-codex` and `gemini`, optional for `github-copilot`. |
566
+ | `--model <model>` | Chooses the runner model; required for `openai-codex`, `gemini` and `qwen-code`, optional for `github-copilot`. |
567
567
  | `--context <text-or-file>` | Appends extra instructions inline or from a file like `AGENTS.md`. |
568
568
  | `--test <command>` | Runs a verification command after each prompt attempt and feeds failing output back for retries. |
569
569
  | `--test-before <mode>` | Runs tests before coding: `no` (default), `yes-and-fail` (stop with results), or `yes-and-fix` (create one repair prompt first); defaults to `npm test` when enabled without `--test`. |
package/esm/index.es.js CHANGED
@@ -30,7 +30,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
30
30
  * @generated
31
31
  * @see https://github.com/webgptorg/promptbook
32
32
  */
33
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-24';
33
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-26';
34
34
  /**
35
35
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
36
36
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1820,6 +1820,12 @@ const HARNESS_DEFINITIONS = {
1820
1820
  commandName: 'gemini',
1821
1821
  npmPackageName: '@google/gemini-cli',
1822
1822
  },
1823
+ 'qwen-code': {
1824
+ harnessName: 'qwen-code',
1825
+ label: 'Qwen Code',
1826
+ commandName: 'qwen',
1827
+ npmPackageName: '@qwen-code/qwen-code',
1828
+ },
1823
1829
  };
1824
1830
  /**
1825
1831
  * Looks up the definition of one supported CLI coding harness.
@@ -4119,6 +4125,71 @@ function uncertainNumber(value, isUncertain) {
4119
4125
  return { value };
4120
4126
  }
4121
4127
 
4128
+ /**
4129
+ * An estimation of how many characters are in one token.
4130
+ */
4131
+ const CHARS_PER_TOKEN = 4;
4132
+ /**
4133
+ * Estimates the usage of one harness run from the length of its prompt and its output.
4134
+ *
4135
+ * Harnesses which do not report their own token counts are billed per token anyway, so the
4136
+ * character counts are the only signal available for a price estimate.
4137
+ */
4138
+ function estimateUsageFromTextLength(options) {
4139
+ const { harnessLabel, prompt, output, resolvePricing } = options;
4140
+ try {
4141
+ const pricing = resolvePricing();
4142
+ const inputTokens = Math.ceil(prompt.length / CHARS_PER_TOKEN);
4143
+ const outputTokens = Math.ceil(output.length / CHARS_PER_TOKEN);
4144
+ const price = uncertainNumber((inputTokens / 1000000) * pricing.input + (outputTokens / 1000000) * pricing.output);
4145
+ return {
4146
+ ...UNCERTAIN_USAGE,
4147
+ price,
4148
+ input: {
4149
+ ...UNCERTAIN_USAGE.input,
4150
+ tokensCount: uncertainNumber(inputTokens),
4151
+ },
4152
+ output: {
4153
+ ...UNCERTAIN_USAGE.output,
4154
+ tokensCount: uncertainNumber(outputTokens),
4155
+ },
4156
+ };
4157
+ }
4158
+ catch (error) {
4159
+ console.error(colors.bgRed(`Error parsing ${harnessLabel} usage output:`), error);
4160
+ return UNCERTAIN_USAGE;
4161
+ }
4162
+ }
4163
+
4164
+ /**
4165
+ * Resolves the pricing of one model of one harness.
4166
+ *
4167
+ * It first tries exact match, then prefix match, then falls back to the pricing of a stable
4168
+ * default model, because every harness names new models faster than the pricing tables grow.
4169
+ */
4170
+ function resolveModelPricing(options) {
4171
+ var _a;
4172
+ const { pricingTable, modelNameForEstimation, modelName } = options;
4173
+ const fallbackPricing = pricingTable[modelNameForEstimation];
4174
+ if (fallbackPricing === undefined) {
4175
+ throw new UnexpectedError(spaceTrim(`
4176
+ Missing pricing of the model \`${modelNameForEstimation}\` used for price estimation.
4177
+
4178
+ **The model used for estimation must always be listed in its own pricing table.**
4179
+ `));
4180
+ }
4181
+ if (!modelName) {
4182
+ return fallbackPricing;
4183
+ }
4184
+ const exactMatch = pricingTable[modelName];
4185
+ if (exactMatch) {
4186
+ return exactMatch;
4187
+ }
4188
+ const prefixMatch = (_a = Object.entries(pricingTable).find(([knownModelName]) => modelName.startsWith(knownModelName))) === null || _a === void 0 ? void 0 : _a[1];
4189
+ return prefixMatch !== null && prefixMatch !== void 0 ? prefixMatch : fallbackPricing;
4190
+ }
4191
+ // Note: [💞] Ignore a discrepancy between file name and entity name
4192
+
4122
4193
  /**
4123
4194
  * The pricing for Gemini models per 1 million tokens in USD.
4124
4195
  *
@@ -4138,27 +4209,14 @@ const GEMINI_PRICING = {
4138
4209
  const GEMINI_MODEL_FOR_ESTIMATION = 'gemini-1.5-flash';
4139
4210
  /**
4140
4211
  * Resolves Gemini pricing for the requested model.
4141
- *
4142
- * It first tries exact match, then prefix match, then falls back to a stable
4143
- * default pricing model.
4144
4212
  */
4145
4213
  function resolveGeminiPricing(modelName) {
4146
- var _a;
4147
- const fallbackPricing = GEMINI_PRICING[GEMINI_MODEL_FOR_ESTIMATION];
4148
- if (!modelName) {
4149
- return fallbackPricing;
4150
- }
4151
- const exactMatch = GEMINI_PRICING[modelName];
4152
- if (exactMatch) {
4153
- return exactMatch;
4154
- }
4155
- const prefixMatch = (_a = Object.entries(GEMINI_PRICING).find(([knownModel]) => modelName.startsWith(knownModel))) === null || _a === void 0 ? void 0 : _a[1];
4156
- return prefixMatch !== null && prefixMatch !== void 0 ? prefixMatch : fallbackPricing;
4214
+ return resolveModelPricing({
4215
+ pricingTable: GEMINI_PRICING,
4216
+ modelNameForEstimation: GEMINI_MODEL_FOR_ESTIMATION,
4217
+ modelName,
4218
+ });
4157
4219
  }
4158
- /**
4159
- * A an estimation of how many characters are in one token.
4160
- */
4161
- const CHARS_PER_TOKEN = 4;
4162
4220
 
4163
4221
  /**
4164
4222
  * Parses Gemini CLI output and extracts usage information.
@@ -4168,28 +4226,12 @@ const CHARS_PER_TOKEN = 4;
4168
4226
  * @param modelName The Gemini model used for this run.
4169
4227
  */
4170
4228
  function parseGeminiUsageFromOutput(output, prompt, modelName) {
4171
- try {
4172
- const pricing = resolveGeminiPricing(modelName);
4173
- const inputTokens = Math.ceil(prompt.length / CHARS_PER_TOKEN);
4174
- const outputTokens = Math.ceil(output.length / CHARS_PER_TOKEN);
4175
- const price = uncertainNumber((inputTokens / 1000000) * pricing.input + (outputTokens / 1000000) * pricing.output);
4176
- return {
4177
- ...UNCERTAIN_USAGE,
4178
- price,
4179
- input: {
4180
- ...UNCERTAIN_USAGE.input,
4181
- tokensCount: uncertainNumber(inputTokens),
4182
- },
4183
- output: {
4184
- ...UNCERTAIN_USAGE.output,
4185
- tokensCount: uncertainNumber(outputTokens),
4186
- },
4187
- };
4188
- }
4189
- catch (error) {
4190
- console.error(colors.bgRed('Error parsing Gemini usage output:'), error);
4191
- return UNCERTAIN_USAGE;
4192
- }
4229
+ return estimateUsageFromTextLength({
4230
+ harnessLabel: 'Gemini',
4231
+ prompt,
4232
+ output,
4233
+ resolvePricing: () => resolveGeminiPricing(modelName),
4234
+ });
4193
4235
  }
4194
4236
 
4195
4237
  /**
@@ -5636,6 +5678,114 @@ class OpencodeRunner {
5636
5678
  }
5637
5679
  }
5638
5680
 
5681
+ /**
5682
+ * Delimiter used for passing large prompts to the Qwen Code CLI.
5683
+ */
5684
+ const QWEN_CODE_PROMPT_DELIMITER = 'QWEN_CODE_PROMPT';
5685
+ /**
5686
+ * Builds the shell script that runs Qwen Code with the prompt and coding context.
5687
+ *
5688
+ * Note: `-y` runs the harness unattended, because `ptbk coder` approves the whole prompt
5689
+ * up front and there is nobody at the keyboard to confirm the single tool calls.
5690
+ */
5691
+ function buildQwenCodeScript(options) {
5692
+ return spaceTrim((block) => `
5693
+ qwen -y -m ${options.model} -p "$(cat <<'${QWEN_CODE_PROMPT_DELIMITER}'
5694
+
5695
+ ${block(options.prompt)}
5696
+
5697
+ ${QWEN_CODE_PROMPT_DELIMITER}
5698
+ )"
5699
+ `);
5700
+ }
5701
+
5702
+ /**
5703
+ * The pricing for Qwen Code models per 1 million tokens in USD.
5704
+ *
5705
+ * Note: This is an estimation.
5706
+ *
5707
+ * @see https://www.alibabacloud.com/help/en/model-studio/models
5708
+ */
5709
+ const QWEN_CODE_PRICING = {
5710
+ 'qwen3-coder-plus': {
5711
+ input: 1,
5712
+ output: 5,
5713
+ },
5714
+ 'qwen3-coder-flash': {
5715
+ input: 0.3,
5716
+ output: 1.5,
5717
+ },
5718
+ 'qwen3-max': {
5719
+ input: 1.2,
5720
+ output: 6,
5721
+ },
5722
+ };
5723
+ /**
5724
+ * The model to use for price estimation.
5725
+ */
5726
+ const QWEN_CODE_MODEL_FOR_ESTIMATION = 'qwen3-coder-plus';
5727
+ /**
5728
+ * Resolves Qwen Code pricing for the requested model.
5729
+ */
5730
+ function resolveQwenCodePricing(modelName) {
5731
+ return resolveModelPricing({
5732
+ pricingTable: QWEN_CODE_PRICING,
5733
+ modelNameForEstimation: QWEN_CODE_MODEL_FOR_ESTIMATION,
5734
+ modelName,
5735
+ });
5736
+ }
5737
+
5738
+ /**
5739
+ * Parses Qwen Code CLI output and extracts usage information.
5740
+ *
5741
+ * @param output The output from the Qwen Code CLI.
5742
+ * @param prompt The prompt that was sent to the Qwen Code CLI.
5743
+ * @param modelName The Qwen Code model used for this run.
5744
+ */
5745
+ function parseQwenCodeUsageFromOutput(output, prompt, modelName) {
5746
+ return estimateUsageFromTextLength({
5747
+ harnessLabel: 'Qwen Code',
5748
+ prompt,
5749
+ output,
5750
+ resolvePricing: () => resolveQwenCodePricing(modelName),
5751
+ });
5752
+ }
5753
+
5754
+ /**
5755
+ * Default Qwen Code model used by the coding runner.
5756
+ */
5757
+ const DEFAULT_QWEN_CODE_MODEL = 'qwen3.8-max';
5758
+ /**
5759
+ * Runs prompts via the Qwen Code CLI.
5760
+ */
5761
+ class QwenCodeRunner {
5762
+ /**
5763
+ * Creates a new Qwen Code runner.
5764
+ */
5765
+ constructor(options) {
5766
+ this.options = options;
5767
+ this.name = 'qwen-code';
5768
+ }
5769
+ /**
5770
+ * Runs the prompt using Qwen Code and parses usage output.
5771
+ */
5772
+ async runPrompt(options) {
5773
+ const scriptContent = buildQwenCodeScript({
5774
+ prompt: options.prompt,
5775
+ model: this.options.model,
5776
+ });
5777
+ const output = await $runGoScriptWithOutput({
5778
+ scriptPath: options.scriptPath,
5779
+ scriptContent,
5780
+ logPath: options.logPath,
5781
+ shouldPrintLiveOutput: options.shouldPrintLiveOutput,
5782
+ preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
5783
+ });
5784
+ const usage = parseQwenCodeUsageFromOutput(output, options.prompt, this.options.model);
5785
+ return { usage };
5786
+ }
5787
+ }
5788
+
5639
5789
  /**
5640
5790
  * Constant for default codex model.
5641
5791
  */
@@ -5644,6 +5794,11 @@ const DEFAULT_CODEX_MODEL = 'gpt-5.2-codex';
5644
5794
  * Constant for cline model.
5645
5795
  */
5646
5796
  const CLINE_MODEL = 'gemini:gemini-3-flash-preview';
5797
+ /**
5798
+ * Harnesses which refuse to run without an explicit `--model`, because they expose many models
5799
+ * of very different capability and price and never pick a sensible one on their own.
5800
+ */
5801
+ const MODEL_REQUIRING_HARNESS_NAMES = ['openai-codex', 'gemini', 'qwen-code'];
5647
5802
  /**
5648
5803
  * Resolves the configured prompt runner together with status-line metadata.
5649
5804
  *
@@ -5681,6 +5836,9 @@ function resolvePromptRunner(options) {
5681
5836
  if (agentName === 'gemini') {
5682
5837
  return createGeminiRunnerResolution(options);
5683
5838
  }
5839
+ if (agentName === 'qwen-code') {
5840
+ return createQwenCodeRunnerResolution(options);
5841
+ }
5684
5842
  throw new Error(`Unknown harness: ${agentName}`);
5685
5843
  }
5686
5844
  /**
@@ -5722,6 +5880,23 @@ function createGeminiRunnerResolution(options) {
5722
5880
  model: actualRunnerModel,
5723
5881
  }), actualRunnerModel);
5724
5882
  }
5883
+ /**
5884
+ * Builds the Qwen Code CLI runner resolution, including required-model validation.
5885
+ */
5886
+ function createQwenCodeRunnerResolution(options) {
5887
+ const actualRunnerModel = resolveRequiredModel({
5888
+ agentName: 'qwen-code',
5889
+ providedModel: options.model,
5890
+ defaultModel: DEFAULT_QWEN_CODE_MODEL,
5891
+ exampleUsages: [
5892
+ `--harness qwen-code --model ${DEFAULT_QWEN_CODE_MODEL}`,
5893
+ '--harness qwen-code --model default',
5894
+ ],
5895
+ });
5896
+ return createRunnerResolution(options, new QwenCodeRunner({
5897
+ model: actualRunnerModel,
5898
+ }), actualRunnerModel);
5899
+ }
5725
5900
  /**
5726
5901
  * Combines the instantiated runner with prompt status metadata.
5727
5902
  */
@@ -5737,9 +5912,7 @@ function createRunnerResolution(options, runner, actualRunnerModel) {
5737
5912
  */
5738
5913
  function getRunnerMetadata(options, actualRunnerModel) {
5739
5914
  const runnerName = options.agentName ? getHarnessDefinition(options.agentName).label : 'unknown';
5740
- if (options.agentName === 'openai-codex' ||
5741
- options.agentName === 'github-copilot' ||
5742
- options.agentName === 'gemini') {
5915
+ if (options.agentName === 'github-copilot' || isModelRequiringHarnessName(options.agentName)) {
5743
5916
  return { runnerName, modelName: actualRunnerModel };
5744
5917
  }
5745
5918
  if (options.agentName === 'cline') {
@@ -5750,6 +5923,12 @@ function getRunnerMetadata(options, actualRunnerModel) {
5750
5923
  }
5751
5924
  return { runnerName };
5752
5925
  }
5926
+ /**
5927
+ * Checks whether one harness refuses to run without an explicit `--model`.
5928
+ */
5929
+ function isModelRequiringHarnessName(agentName) {
5930
+ return MODEL_REQUIRING_HARNESS_NAMES.includes(agentName);
5931
+ }
5753
5932
  /**
5754
5933
  * Resolves a runner model, allowing `default` but otherwise requiring an explicit value.
5755
5934
  */
@@ -27752,29 +27931,36 @@ class UseUserLocationCommitmentDefinition extends BaseCommitmentDefinition {
27752
27931
  /**
27753
27932
  * `WRITING RULES` commitment definition.
27754
27933
  *
27755
- * It adds constraints that apply strictly to writing style and presentation
27756
- * rather than task-solving behavior.
27934
+ * The `WRITING RULES`/`WRITING RULE` commitment adds constraints that apply strictly to writing style
27935
+ * and presentation rather than task-solving behavior.
27936
+ *
27937
+ * Example usage in agent source:
27938
+ *
27939
+ * ```book
27940
+ * WRITING RULES Keep every response under 120 words
27941
+ * WRITING RULE Always end the message with an emoji
27942
+ * ```
27757
27943
  *
27758
27944
  * @private [🪔] Maybe export the commitments through some package
27759
27945
  */
27760
27946
  class WritingRulesCommitmentDefinition extends BaseCommitmentDefinition {
27761
- constructor() {
27762
- super('WRITING RULES');
27947
+ constructor(type = 'WRITING RULES') {
27948
+ super(type);
27763
27949
  }
27764
27950
  /**
27765
- * Short one-line description of `WRITING RULES`.
27951
+ * Short one-line description of `WRITING RULES`/`WRITING RULE`.
27766
27952
  */
27767
27953
  get description() {
27768
27954
  return 'Add writing-only constraints such as tone, length, formatting, or emoji usage.';
27769
27955
  }
27770
27956
  /**
27771
- * Icon for `WRITING RULES`.
27957
+ * Icon for `WRITING RULES`/`WRITING RULE`.
27772
27958
  */
27773
27959
  get icon() {
27774
27960
  return '📝';
27775
27961
  }
27776
27962
  /**
27777
- * Markdown documentation for `WRITING RULES`.
27963
+ * Markdown documentation for `WRITING RULES`/`WRITING RULE` commitment.
27778
27964
  */
27779
27965
  get documentation() {
27780
27966
  return spaceTrim$1(`
@@ -27784,6 +27970,7 @@ class WritingRulesCommitmentDefinition extends BaseCommitmentDefinition {
27784
27970
 
27785
27971
  ## Key aspects
27786
27972
 
27973
+ - All writing rules are treated equally regardless of singular/plural form.
27787
27974
  - Use it for writing-only constraints such as tone, formatting, length, emoji usage, punctuation, or reading level.
27788
27975
  - Do **not** use it for problem-solving behavior, policy, tool usage, or decision-making logic. Use \`RULE\` for that.
27789
27976
  - Newer writing-rules blocks override conflicting earlier writing-rules blocks.
@@ -27982,7 +28169,8 @@ const COMMITMENT_REGISTRY = [
27982
28169
  new LanguageCommitmentDefinition('LANGUAGE'),
27983
28170
  new LanguageCommitmentDefinition('LANGUAGES'),
27984
28171
  new WritingSampleCommitmentDefinition(),
27985
- new WritingRulesCommitmentDefinition(),
28172
+ new WritingRulesCommitmentDefinition('WRITING RULES'),
28173
+ new WritingRulesCommitmentDefinition('WRITING RULE'),
27986
28174
  new SampleCommitmentDefinition('SAMPLE'),
27987
28175
  new SampleCommitmentDefinition('EXAMPLE'),
27988
28176
  new FormatCommitmentDefinition('FORMAT'),
@@ -30263,6 +30451,7 @@ const CLI_AGENT_HARNESS_NAMES = [
30263
30451
  'claude-code',
30264
30452
  'opencode',
30265
30453
  'gemini',
30454
+ 'qwen-code',
30266
30455
  ];
30267
30456
  /**
30268
30457
  * All supported thinking-level values for CLI coding-agent runners.
@@ -30273,8 +30462,8 @@ const CLI_AGENT_THINKING_LEVEL_VALUES = ['low', 'medium', 'high', 'xhigh', 'max'
30273
30462
  /**
30274
30463
  * Environment variable used as the default runner identifier when `--harness` is omitted or not set in `CliAgent`.
30275
30464
  *
30276
- * Set this to one of the harness names (`openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`)
30277
- * so that `CliAgent` and `ptbk agent exec` can run without an explicit `harness` option.
30465
+ * Set this to one of the harness names (`openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`,
30466
+ * `qwen-code`) so that `CliAgent` and `ptbk agent exec` can run without an explicit `harness` option.
30278
30467
  *
30279
30468
  * @public exported from `@promptbook/node`
30280
30469
  */