@promptbook/cli 0.113.0-4 → 0.113.0-5

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 (29) hide show
  1. package/esm/index.es.js +782 -6
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +20 -0
  4. package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +5 -0
  5. package/esm/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +29 -0
  6. package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +4 -0
  7. package/esm/src/avatars/renderAvatarVisualAsciiArt.d.ts +75 -0
  8. package/esm/src/utils/ascii-art/$detectTerminalAnsiColorDepth.d.ts +15 -0
  9. package/esm/src/utils/ascii-art/convertImageDataToAsciiArt.d.ts +80 -0
  10. package/esm/src/version.d.ts +1 -1
  11. package/package.json +1 -1
  12. package/src/avatars/renderAvatarVisualAsciiArt.ts +138 -0
  13. package/src/other/templates/getTemplatesPipelineCollection.ts +684 -1029
  14. package/src/utils/ascii-art/$detectTerminalAnsiColorDepth.ts +45 -0
  15. package/src/utils/ascii-art/convertImageDataToAsciiArt.ts +399 -0
  16. package/src/version.ts +2 -2
  17. package/src/versions.txt +1 -0
  18. package/umd/index.umd.js +782 -6
  19. package/umd/index.umd.js.map +1 -1
  20. package/umd/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +20 -0
  21. package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +5 -0
  22. package/umd/scripts/run-codex-prompts/ui/buildCoderRunAgentVisual.d.ts +29 -0
  23. package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +4 -0
  24. package/umd/src/avatars/renderAvatarVisualAsciiArt.d.ts +75 -0
  25. package/umd/src/utils/ascii-art/$detectTerminalAnsiColorDepth.d.ts +15 -0
  26. package/umd/src/utils/ascii-art/convertImageDataToAsciiArt.d.ts +80 -0
  27. package/umd/src/version.d.ts +1 -1
  28. package/esm/scripts/run-codex-prompts/common/resolveAgentSystemMessage.d.ts +0 -6
  29. package/umd/scripts/run-codex-prompts/common/resolveAgentSystemMessage.d.ts +0 -6
package/esm/index.es.js CHANGED
@@ -59,7 +59,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
59
59
  * @generated
60
60
  * @see https://github.com/webgptorg/promptbook
61
61
  */
62
- const PROMPTBOOK_ENGINE_VERSION = '0.113.0-4';
62
+ const PROMPTBOOK_ENGINE_VERSION = '0.113.0-5';
63
63
  /**
64
64
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
65
65
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -4543,7 +4543,7 @@ function buildCoderRunUiFrame(options) {
4543
4543
  const visibleOutputLines = buildVisibleOutputLines(options.agentOutputLines);
4544
4544
  const controls = buildControlPills(pausePresentation.pauseControl, options.pendingEnterLabel).join(' ');
4545
4545
  const frame = [
4546
- ...buildCoderRunOctopusVisual({ totalWidth, animationFrame: octopusAnimationFrame }),
4546
+ ...buildFrameHeaderVisual(options, totalWidth, octopusAnimationFrame),
4547
4547
  '',
4548
4548
  ...renderBox('Session', sessionLines, totalWidth, colors.yellow.bold),
4549
4549
  ...renderBox(options.currentPromptLabel ? 'Current task' : 'Queue', currentTaskLines, totalWidth, colors.magenta.bold),
@@ -4555,6 +4555,18 @@ function buildCoderRunUiFrame(options) {
4555
4555
  frame.push(...renderBox('Controls', [controls], totalWidth, colors.white.bold));
4556
4556
  return frame;
4557
4557
  }
4558
+ /**
4559
+ * Builds the header visual above the dashboard boxes.
4560
+ *
4561
+ * Shows the ASCII-art `--agent` avatar visual when one is available and
4562
+ * falls back to the default brand banner otherwise.
4563
+ */
4564
+ function buildFrameHeaderVisual(options, totalWidth, octopusAnimationFrame) {
4565
+ if (options.agentVisualLines !== undefined && options.agentVisualLines.length > 0) {
4566
+ return options.agentVisualLines.map((agentVisualLine) => centerAnsiText(agentVisualLine, totalWidth));
4567
+ }
4568
+ return buildCoderRunOctopusVisual({ totalWidth, animationFrame: octopusAnimationFrame });
4569
+ }
4558
4570
  /**
4559
4571
  * Builds the structured session lines that combine state, runner, queue, and timing metadata.
4560
4572
  */
@@ -4855,6 +4867,13 @@ class CoderRunUiState extends EventEmitter {
4855
4867
  this.config = config;
4856
4868
  this.emitChange();
4857
4869
  }
4870
+ /**
4871
+ * Replaces the ASCII-art agent visual shown instead of the default brand banner.
4872
+ */
4873
+ setAgentVisualLines(agentVisualLines) {
4874
+ this.agentVisualLines = agentVisualLines === undefined ? undefined : [...agentVisualLines];
4875
+ this.emitChange();
4876
+ }
4858
4877
  /**
4859
4878
  * Feeds new prompt statistics from the main loop so the progress bar updates.
4860
4879
  */
@@ -5190,6 +5209,7 @@ function renderCoderRunUi(startTime, options = {}) {
5190
5209
  pauseState: getPauseState(),
5191
5210
  pauseTargetLabel: getPauseTargetLabel(),
5192
5211
  config: state.config,
5212
+ agentVisualLines: state.agentVisualLines,
5193
5213
  phase: state.phase,
5194
5214
  currentPromptLabel: state.currentPromptLabel,
5195
5215
  currentScriptPaths: state.currentScriptPaths,
@@ -9831,12 +9851,150 @@ function darken(amount) {
9831
9851
  }
9832
9852
 
9833
9853
  /* eslint-disable no-magic-numbers */
9854
+ // Note: [💞] Ignore a discrepancy between file name and entity name
9855
+ /**
9856
+ * Default square size used by avatar renderers.
9857
+ *
9858
+ * @private utility of the avatar rendering system
9859
+ */
9860
+ const DEFAULT_AVATAR_SIZE = 192;
9861
+ /**
9862
+ * Default fallback hash used when no explicit hash is provided.
9863
+ *
9864
+ * @private utility of the avatar rendering system
9865
+ */
9866
+ const DEFAULT_AVATAR_HASH = '0000000000000000000000000000000000000000000000000000000000000000';
9867
+ /**
9868
+ * Default fallback name used when no explicit name is provided.
9869
+ *
9870
+ * @private utility of the avatar rendering system
9871
+ */
9872
+ const DEFAULT_AVATAR_NAME = 'Anonymous Agent';
9834
9873
  /**
9835
9874
  * Corner radius ratio used for the common rounded card frame.
9836
9875
  *
9837
9876
  * @private utility of the avatar rendering system
9838
9877
  */
9839
9878
  const FRAME_RADIUS_RATIO = 0.18;
9879
+ /**
9880
+ * Normalizes arbitrary agent colors into a stable non-empty color list.
9881
+ *
9882
+ * @param colors Raw color list.
9883
+ * @returns Stable list of usable colors.
9884
+ *
9885
+ * @private utility of the avatar rendering system
9886
+ */
9887
+ function normalizeAvatarColors(colors) {
9888
+ const normalizedColors = colors
9889
+ .map((color) => String(color).trim())
9890
+ .filter((color) => color !== '')
9891
+ .map((color) => Color.fromSafe(color).toHex());
9892
+ if (normalizedColors.length > 0) {
9893
+ return normalizedColors;
9894
+ }
9895
+ return [PROMPTBOOK_COLOR.toHex()];
9896
+ }
9897
+ /**
9898
+ * Normalizes the avatar input so visuals can rely on consistent data.
9899
+ *
9900
+ * @param avatarDefinition Raw avatar input.
9901
+ * @returns Normalized avatar definition.
9902
+ *
9903
+ * @private utility of the avatar rendering system
9904
+ */
9905
+ function normalizeAvatarDefinition(avatarDefinition) {
9906
+ return {
9907
+ agentName: (avatarDefinition.agentName || DEFAULT_AVATAR_NAME).trim() || DEFAULT_AVATAR_NAME,
9908
+ agentHash: (avatarDefinition.agentHash || DEFAULT_AVATAR_HASH).trim() || DEFAULT_AVATAR_HASH,
9909
+ colors: normalizeAvatarColors(avatarDefinition.colors),
9910
+ };
9911
+ }
9912
+ /**
9913
+ * Extracts avatar colors from the flexible `META COLOR` agent field.
9914
+ *
9915
+ * @param colorValue Raw `META COLOR` value.
9916
+ * @returns Parsed avatar colors.
9917
+ *
9918
+ * @private utility of the avatar rendering system
9919
+ */
9920
+ function parseAvatarColors(colorValue) {
9921
+ if (!colorValue) {
9922
+ return [];
9923
+ }
9924
+ const colors = [];
9925
+ let currentColor = '';
9926
+ let bracketDepth = 0;
9927
+ for (const character of colorValue) {
9928
+ if (character === '(') {
9929
+ bracketDepth++;
9930
+ currentColor += character;
9931
+ continue;
9932
+ }
9933
+ if (character === ')') {
9934
+ bracketDepth = Math.max(0, bracketDepth - 1);
9935
+ currentColor += character;
9936
+ continue;
9937
+ }
9938
+ if (bracketDepth === 0 && [',', ';', '|', '\n'].includes(character)) {
9939
+ const normalizedColor = currentColor.trim();
9940
+ if (normalizedColor !== '') {
9941
+ colors.push(normalizedColor);
9942
+ }
9943
+ currentColor = '';
9944
+ continue;
9945
+ }
9946
+ currentColor += character;
9947
+ }
9948
+ const lastColor = currentColor.trim();
9949
+ if (lastColor !== '') {
9950
+ colors.push(lastColor);
9951
+ }
9952
+ return colors;
9953
+ }
9954
+ /**
9955
+ * Creates a reusable avatar definition from parsed agent information.
9956
+ *
9957
+ * @param agentBasicInformation Parsed agent information.
9958
+ * @returns Avatar definition ready for canvas rendering.
9959
+ *
9960
+ * @private shared helper for app-level avatar previews
9961
+ */
9962
+ function createAvatarDefinitionFromAgentBasicInformation(agentBasicInformation) {
9963
+ return normalizeAvatarDefinition({
9964
+ agentName: agentBasicInformation.agentName,
9965
+ agentHash: agentBasicInformation.agentHash,
9966
+ colors: parseAvatarColors(agentBasicInformation.meta.color),
9967
+ });
9968
+ }
9969
+ /**
9970
+ * Creates the shared derived palette used by every avatar visual.
9971
+ *
9972
+ * @param avatarDefinition Stable avatar definition.
9973
+ * @param surface Surface style used by the parent UI.
9974
+ * @returns Derived palette.
9975
+ *
9976
+ * @private utility of the avatar rendering system
9977
+ */
9978
+ function createAvatarPalette(avatarDefinition, surface = 'framed') {
9979
+ const normalizedAvatarDefinition = normalizeAvatarDefinition(avatarDefinition);
9980
+ const primaryColor = Color.fromSafe(normalizedAvatarDefinition.colors[0] || PROMPTBOOK_COLOR);
9981
+ const secondaryColor = Color.fromSafe(normalizedAvatarDefinition.colors[1] || primaryColor.then(lighten(0.12)).then(saturate(0.16)));
9982
+ const accentColor = Color.fromSafe(normalizedAvatarDefinition.colors[2] || primaryColor.then(saturate(0.32)).then(lighten(0.22)));
9983
+ const backgroundColor = Color.fromSafe(primaryColor.then(darken(0.34)).then(saturate(-0.1)));
9984
+ const backgroundSecondaryColor = Color.fromSafe(secondaryColor.then(darken(0.42)).then(saturate(-0.16)).then(lighten(0.04)));
9985
+ const highlightColor = Color.fromSafe(accentColor.then(lighten(0.22)).then(saturate(0.08)));
9986
+ const shadowColor = Color.fromSafe(primaryColor.then(darken(0.46)).then(saturate(0.14)));
9987
+ return {
9988
+ background: surface === 'transparent' ? 'transparent' : backgroundColor.toHex(),
9989
+ backgroundSecondary: surface === 'transparent' ? 'transparent' : backgroundSecondaryColor.toHex(),
9990
+ primary: primaryColor.toHex(),
9991
+ secondary: secondaryColor.toHex(),
9992
+ accent: accentColor.toHex(),
9993
+ highlight: highlightColor.toHex(),
9994
+ shadow: shadowColor.toHex(),
9995
+ ink: createInkColor(primaryColor),
9996
+ };
9997
+ }
9840
9998
  /**
9841
9999
  * Draws the common rounded background frame used by most visuals.
9842
10000
  *
@@ -9887,6 +10045,67 @@ function createRoundedRectPath(context, x, y, width, height, radius) {
9887
10045
  context.arcTo(x, y, x + width, y, normalizedRadius);
9888
10046
  context.closePath();
9889
10047
  }
10048
+ /**
10049
+ * Creates a stable pseudo-random number generator from a string seed.
10050
+ *
10051
+ * @param seedSource String seed.
10052
+ * @returns Generator producing values in `[0, 1)`.
10053
+ *
10054
+ * @private utility of the avatar rendering system
10055
+ */
10056
+ function createSeededRandom(seedSource) {
10057
+ let state = hashStringToUint32(seedSource) || 0x9e3779b9;
10058
+ return () => {
10059
+ state = (state + 0x6d2b79f5) >>> 0;
10060
+ let hash = Math.imul(state ^ (state >>> 15), 1 | state);
10061
+ hash ^= hash + Math.imul(hash ^ (hash >>> 7), 61 | hash);
10062
+ return ((hash ^ (hash >>> 14)) >>> 0) / 4294967296;
10063
+ };
10064
+ }
10065
+ /**
10066
+ * Creates a deterministic random factory scoped to the avatar definition.
10067
+ *
10068
+ * @param avatarDefinition Stable avatar definition.
10069
+ * @returns Random factory that can be re-seeded per visual part.
10070
+ *
10071
+ * @private utility of the avatar rendering system
10072
+ */
10073
+ function createAvatarRandomFactory(avatarDefinition) {
10074
+ const normalizedAvatarDefinition = normalizeAvatarDefinition(avatarDefinition);
10075
+ const seedBase = `${normalizedAvatarDefinition.agentName}|${normalizedAvatarDefinition.agentHash}|${normalizedAvatarDefinition.colors.join('|')}`;
10076
+ return (salt) => createSeededRandom(`${seedBase}|${salt}`);
10077
+ }
10078
+ /**
10079
+ * Clears and scales the canvas for crisp avatar rendering on high DPI displays.
10080
+ *
10081
+ * @param canvas Canvas element to prepare.
10082
+ * @param context Canvas 2D context.
10083
+ * @param size Canvas size in CSS pixels.
10084
+ * @param devicePixelRatio Device pixel ratio.
10085
+ *
10086
+ * @private utility of the avatar rendering system
10087
+ */
10088
+ function prepareAvatarCanvas(canvas, context, size, devicePixelRatio) {
10089
+ const normalizedDevicePixelRatio = Math.max(1, Math.round(devicePixelRatio * 100) / 100);
10090
+ const nextCanvasWidth = Math.round(size * normalizedDevicePixelRatio);
10091
+ const nextCanvasHeight = Math.round(size * normalizedDevicePixelRatio);
10092
+ const nextCanvasStyleWidth = `${size}px`;
10093
+ const nextCanvasStyleHeight = `${size}px`;
10094
+ if (canvas.width !== nextCanvasWidth) {
10095
+ canvas.width = nextCanvasWidth;
10096
+ }
10097
+ if (canvas.height !== nextCanvasHeight) {
10098
+ canvas.height = nextCanvasHeight;
10099
+ }
10100
+ if (canvas.style.width !== nextCanvasStyleWidth) {
10101
+ canvas.style.width = nextCanvasStyleWidth;
10102
+ }
10103
+ if (canvas.style.height !== nextCanvasStyleHeight) {
10104
+ canvas.style.height = nextCanvasStyleHeight;
10105
+ }
10106
+ context.setTransform(normalizedDevicePixelRatio, 0, 0, normalizedDevicePixelRatio, 0, 0);
10107
+ context.clearRect(0, 0, size, size);
10108
+ }
9890
10109
  /**
9891
10110
  * Picks one deterministic element from a non-empty collection.
9892
10111
  *
@@ -9899,6 +10118,37 @@ function createRoundedRectPath(context, x, y, width, height, radius) {
9899
10118
  function pickRandomItem(items, random) {
9900
10119
  return items[Math.floor(random() * items.length)];
9901
10120
  }
10121
+ /**
10122
+ * Creates a readable ink color from the given base color.
10123
+ *
10124
+ * @param color Base color.
10125
+ * @returns High-contrast ink color.
10126
+ *
10127
+ * @private utility of the avatar rendering system
10128
+ */
10129
+ function createInkColor(color) {
10130
+ const perceivedBrightness = color.red * 0.299 + color.green * 0.587 + color.blue * 0.114;
10131
+ if (perceivedBrightness > 150) {
10132
+ return '#172033';
10133
+ }
10134
+ return '#f8fbff';
10135
+ }
10136
+ /**
10137
+ * Hashes an arbitrary string into a 32-bit unsigned integer.
10138
+ *
10139
+ * @param value Arbitrary input.
10140
+ * @returns 32-bit unsigned integer hash.
10141
+ *
10142
+ * @private utility of the avatar rendering system
10143
+ */
10144
+ function hashStringToUint32(value) {
10145
+ let hash = 2166136261;
10146
+ for (const character of value) {
10147
+ hash ^= character.charCodeAt(0);
10148
+ hash = Math.imul(hash, 16777619);
10149
+ }
10150
+ return hash >>> 0;
10151
+ }
9902
10152
 
9903
10153
  /* eslint-disable no-magic-numbers */
9904
10154
  /**
@@ -15886,6 +16136,21 @@ function normalizeAvatarVisualLookupKey(value) {
15886
16136
  .toLowerCase()
15887
16137
  .replace(/[^a-z0-9]+/g, '');
15888
16138
  }
16139
+ /**
16140
+ * Returns one avatar visual by its identifier.
16141
+ *
16142
+ * @param visualId Requested visual identifier.
16143
+ * @returns Matching visual definition.
16144
+ *
16145
+ * @private shared registry for the avatar rendering system
16146
+ */
16147
+ function getAvatarVisualById(visualId) {
16148
+ const avatarVisual = AVATAR_VISUALS.find((candidateAvatarVisual) => candidateAvatarVisual.id === visualId);
16149
+ if (!avatarVisual) {
16150
+ throw new Error(`Unknown avatar visual "${visualId}".`);
16151
+ }
16152
+ return avatarVisual;
16153
+ }
15889
16154
  /**
15890
16155
  * Resolves a user-facing avatar visual value to a supported built-in visual id.
15891
16156
  *
@@ -28466,7 +28731,7 @@ function buildClaudeScript(options) {
28466
28731
  const MODEL_ARGUMENT = options.model ? ` --model ${options.model}` : '';
28467
28732
  const THINKING_LEVEL_ARGUMENT = options.thinkingLevel ? ` --effort ${options.thinkingLevel}` : '';
28468
28733
  return spaceTrim((block) => `
28469
- claude --allowedTools "Bash,Read,Edit,Write"${MODEL_ARGUMENT}${THINKING_LEVEL_ARGUMENT} --output-format json --print <<'${CLAUDE_PROMPT_DELIMITER}'
28734
+ claude --allowedTools "Bash,Read,Edit,Write"${MODEL_ARGUMENT}${THINKING_LEVEL_ARGUMENT} --output-format stream-json --verbose --include-partial-messages --print <<'${CLAUDE_PROMPT_DELIMITER}'
28470
28735
 
28471
28736
  ${block(options.prompt)}
28472
28737
 
@@ -71222,7 +71487,7 @@ async function recordPromptDurationSample(key, promptDurationMs) {
71222
71487
  *
71223
71488
  * Returns `undefined` when no agent path is provided.
71224
71489
  */
71225
- async function resolveAgentSystemMessage(agentPath, currentWorkingDirectory) {
71490
+ async function resolveCoderAgent(agentPath, currentWorkingDirectory) {
71226
71491
  if (!agentPath) {
71227
71492
  return undefined;
71228
71493
  }
@@ -71237,7 +71502,10 @@ async function resolveAgentSystemMessage(agentPath, currentWorkingDirectory) {
71237
71502
  }
71238
71503
  throw error;
71239
71504
  });
71240
- return createAgentRunnerSystemMessage(agentSource);
71505
+ return {
71506
+ agentSource: agentSource,
71507
+ systemMessage: await createAgentRunnerSystemMessage(agentSource),
71508
+ };
71241
71509
  }
71242
71510
 
71243
71511
  /**
@@ -71435,6 +71703,498 @@ async function waitForPromptStart(file, section, isFirstPrompt) {
71435
71703
  await waitForEnter(colors.bgWhite(`Press Enter to start the ${label}...`));
71436
71704
  }
71437
71705
 
71706
+ /**
71707
+ * Default alpha channel value below which a half-cell is rendered as terminal background.
71708
+ *
71709
+ * @private within the repository
71710
+ */
71711
+ const DEFAULT_ALPHA_THRESHOLD = 32;
71712
+ /**
71713
+ * Number of channels per pixel in an RGBA buffer.
71714
+ *
71715
+ * @private within the repository
71716
+ */
71717
+ const RGBA_CHANNEL_COUNT = 4;
71718
+ /**
71719
+ * Upper half block character - foreground paints the top pixel, background paints the bottom pixel.
71720
+ *
71721
+ * @private within the repository
71722
+ */
71723
+ const UPPER_HALF_BLOCK = '▀'; // <- ▀
71724
+ /**
71725
+ * Lower half block character - foreground paints the bottom pixel while the top pixel stays transparent.
71726
+ *
71727
+ * @private within the repository
71728
+ */
71729
+ const LOWER_HALF_BLOCK = '▄'; // <- ▄
71730
+ /**
71731
+ * ANSI escape sequence that resets all colors and attributes.
71732
+ *
71733
+ * @private within the repository
71734
+ */
71735
+ const ANSI_RESET = '\u001b[0m';
71736
+ /**
71737
+ * Maximum spread between RGB channels for a color to be treated as (nearly) achromatic gray.
71738
+ *
71739
+ * @private within the repository
71740
+ */
71741
+ const ANSI_256_ACHROMATIC_CHANNEL_SPREAD = 12;
71742
+ /**
71743
+ * Gray level above which an achromatic color maps to the pure white color-cube entry.
71744
+ *
71745
+ * @private within the repository
71746
+ */
71747
+ const ANSI_256_NEAR_WHITE_GRAY_LEVEL = 246;
71748
+ /**
71749
+ * Index of pure white inside the 6×6×6 ANSI color cube.
71750
+ *
71751
+ * @private within the repository
71752
+ */
71753
+ const ANSI_256_WHITE_INDEX = 231;
71754
+ /**
71755
+ * Brightness of the lightest entry of the ANSI 256 grayscale ramp.
71756
+ *
71757
+ * @private within the repository
71758
+ */
71759
+ const ANSI_256_GRAYSCALE_RAMP_MAX_LEVEL = 238;
71760
+ /**
71761
+ * Number of grayscale ramp steps above its first entry (ANSI indexes 232-255).
71762
+ *
71763
+ * @private within the repository
71764
+ */
71765
+ const ANSI_256_GRAYSCALE_RAMP_INDEX_SPAN = 23;
71766
+ /**
71767
+ * Converts raw RGBA image pixels into colored ASCII art for ANSI terminals.
71768
+ *
71769
+ * This is the universal image-to-terminal technique used across the repository:
71770
+ * every output character cell covers a rectangular region of source pixels which is
71771
+ * split into a top and bottom half; each half is area-averaged and rendered with
71772
+ * half-block characters (`▀` / `▄`) so one character shows two "pixels" vertically.
71773
+ * Transparent halves keep the terminal background so non-rectangular images
71774
+ * (for example rounded avatar cards) compose naturally into any terminal UI.
71775
+ *
71776
+ * @param options Source pixels, output grid size, and ANSI color depth.
71777
+ * @returns One ANSI-colored string per output row, each ending with a color reset.
71778
+ *
71779
+ * @private within the repository
71780
+ */
71781
+ function convertImageDataToAsciiArt(options) {
71782
+ const { imageData, columns, rows, colorDepth = 'TRUE_COLOR', alphaThreshold = DEFAULT_ALPHA_THRESHOLD } = options;
71783
+ if (!Number.isInteger(columns) || columns <= 0 || !Number.isInteger(rows) || rows <= 0) {
71784
+ throw new UnexpectedError(spaceTrim$1(`
71785
+ ASCII-art grid size is invalid.
71786
+
71787
+ Both \`columns\` and \`rows\` must be positive integers but \`${columns}\` × \`${rows}\` was requested.
71788
+ `));
71789
+ }
71790
+ if (imageData.width <= 0 ||
71791
+ imageData.height <= 0 ||
71792
+ imageData.data.length < imageData.width * imageData.height * RGBA_CHANNEL_COUNT) {
71793
+ throw new UnexpectedError(spaceTrim$1(`
71794
+ ASCII-art source image data is invalid.
71795
+
71796
+ Expected a RGBA buffer of at least \`${imageData.width} × ${imageData.height} × ${RGBA_CHANNEL_COUNT}\` bytes
71797
+ but got \`${imageData.data.length}\` bytes.
71798
+ `));
71799
+ }
71800
+ const halfCellRowCount = rows * 2;
71801
+ const asciiArtLines = [];
71802
+ for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
71803
+ let line = '';
71804
+ let currentForegroundCode = undefined;
71805
+ let currentBackgroundCode = undefined;
71806
+ for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
71807
+ const topHalfColor = computeHalfCellColor(imageData, columnIndex, rowIndex * 2, columns, halfCellRowCount, alphaThreshold);
71808
+ const bottomHalfColor = computeHalfCellColor(imageData, columnIndex, rowIndex * 2 + 1, columns, halfCellRowCount, alphaThreshold);
71809
+ let character;
71810
+ let nextForegroundCode;
71811
+ let nextBackgroundCode;
71812
+ if (topHalfColor.isOpaque && bottomHalfColor.isOpaque) {
71813
+ character = UPPER_HALF_BLOCK;
71814
+ nextForegroundCode = createForegroundColorCode(topHalfColor, colorDepth);
71815
+ nextBackgroundCode = createBackgroundColorCode(bottomHalfColor, colorDepth);
71816
+ }
71817
+ else if (topHalfColor.isOpaque) {
71818
+ character = UPPER_HALF_BLOCK;
71819
+ nextForegroundCode = createForegroundColorCode(topHalfColor, colorDepth);
71820
+ nextBackgroundCode = undefined;
71821
+ }
71822
+ else if (bottomHalfColor.isOpaque) {
71823
+ character = LOWER_HALF_BLOCK;
71824
+ nextForegroundCode = createForegroundColorCode(bottomHalfColor, colorDepth);
71825
+ nextBackgroundCode = undefined;
71826
+ }
71827
+ else {
71828
+ character = ' ';
71829
+ nextForegroundCode = undefined;
71830
+ nextBackgroundCode = undefined;
71831
+ }
71832
+ if (nextForegroundCode !== currentForegroundCode || nextBackgroundCode !== currentBackgroundCode) {
71833
+ // Note: A reset is required whenever a previously set color must be cleared,
71834
+ // otherwise stale background color would bleed into transparent cells.
71835
+ const isResetNeeded = (currentForegroundCode !== undefined && nextForegroundCode === undefined) ||
71836
+ (currentBackgroundCode !== undefined && nextBackgroundCode === undefined);
71837
+ if (isResetNeeded) {
71838
+ line += ANSI_RESET;
71839
+ currentForegroundCode = undefined;
71840
+ currentBackgroundCode = undefined;
71841
+ }
71842
+ if (nextForegroundCode !== undefined && nextForegroundCode !== currentForegroundCode) {
71843
+ line += nextForegroundCode;
71844
+ }
71845
+ if (nextBackgroundCode !== undefined && nextBackgroundCode !== currentBackgroundCode) {
71846
+ line += nextBackgroundCode;
71847
+ }
71848
+ currentForegroundCode = nextForegroundCode;
71849
+ currentBackgroundCode = nextBackgroundCode;
71850
+ }
71851
+ line += character;
71852
+ }
71853
+ if (currentForegroundCode !== undefined || currentBackgroundCode !== undefined) {
71854
+ line += ANSI_RESET;
71855
+ }
71856
+ asciiArtLines.push(line);
71857
+ }
71858
+ return asciiArtLines;
71859
+ }
71860
+ /**
71861
+ * Computes the area-averaged color of one half-cell of the output grid.
71862
+ *
71863
+ * Color channels are alpha-weighted so semi-transparent edge pixels do not darken towards black.
71864
+ *
71865
+ * @private helper of `convertImageDataToAsciiArt`
71866
+ */
71867
+ function computeHalfCellColor(imageData, columnIndex, halfCellRowIndex, columns, halfCellRowCount, alphaThreshold) {
71868
+ const startX = Math.floor((columnIndex * imageData.width) / columns);
71869
+ const endX = Math.max(startX + 1, Math.floor(((columnIndex + 1) * imageData.width) / columns));
71870
+ const startY = Math.floor((halfCellRowIndex * imageData.height) / halfCellRowCount);
71871
+ const endY = Math.max(startY + 1, Math.floor(((halfCellRowIndex + 1) * imageData.height) / halfCellRowCount));
71872
+ let redSum = 0;
71873
+ let greenSum = 0;
71874
+ let blueSum = 0;
71875
+ let alphaSum = 0;
71876
+ let sampledPixelCount = 0;
71877
+ for (let y = startY; y < endY && y < imageData.height; y++) {
71878
+ for (let x = startX; x < endX && x < imageData.width; x++) {
71879
+ const pixelOffset = (y * imageData.width + x) * RGBA_CHANNEL_COUNT;
71880
+ const alpha = imageData.data[pixelOffset + 3];
71881
+ redSum += imageData.data[pixelOffset] * alpha;
71882
+ greenSum += imageData.data[pixelOffset + 1] * alpha;
71883
+ blueSum += imageData.data[pixelOffset + 2] * alpha;
71884
+ alphaSum += alpha;
71885
+ sampledPixelCount++;
71886
+ }
71887
+ }
71888
+ const averageAlpha = sampledPixelCount === 0 ? 0 : alphaSum / sampledPixelCount;
71889
+ if (averageAlpha < alphaThreshold || alphaSum === 0) {
71890
+ return { red: 0, green: 0, blue: 0, isOpaque: false };
71891
+ }
71892
+ return {
71893
+ red: Math.round(redSum / alphaSum),
71894
+ green: Math.round(greenSum / alphaSum),
71895
+ blue: Math.round(blueSum / alphaSum),
71896
+ isOpaque: true,
71897
+ };
71898
+ }
71899
+ /**
71900
+ * Creates the ANSI escape code that sets the foreground color of following characters.
71901
+ *
71902
+ * @private helper of `convertImageDataToAsciiArt`
71903
+ */
71904
+ function createForegroundColorCode(color, colorDepth) {
71905
+ if (colorDepth === 'TRUE_COLOR') {
71906
+ return `\u001b[38;2;${color.red};${color.green};${color.blue}m`;
71907
+ }
71908
+ return `\u001b[38;5;${mapColorToAnsi256(color)}m`;
71909
+ }
71910
+ /**
71911
+ * Creates the ANSI escape code that sets the background color of following characters.
71912
+ *
71913
+ * @private helper of `convertImageDataToAsciiArt`
71914
+ */
71915
+ function createBackgroundColorCode(color, colorDepth) {
71916
+ if (colorDepth === 'TRUE_COLOR') {
71917
+ return `\u001b[48;2;${color.red};${color.green};${color.blue}m`;
71918
+ }
71919
+ return `\u001b[48;5;${mapColorToAnsi256(color)}m`;
71920
+ }
71921
+ /**
71922
+ * Maps a 24-bit color onto the closest entry of the 256-color ANSI palette.
71923
+ *
71924
+ * Uses the 6×6×6 color cube (entries 16-231) and the grayscale ramp (entries 232-255).
71925
+ *
71926
+ * @private helper of `convertImageDataToAsciiArt`
71927
+ */
71928
+ function mapColorToAnsi256(color) {
71929
+ const { red, green, blue } = color;
71930
+ // Note: Prefer the finer grayscale ramp when the color is (nearly) achromatic
71931
+ const maxChannel = Math.max(red, green, blue);
71932
+ const minChannel = Math.min(red, green, blue);
71933
+ if (maxChannel - minChannel < ANSI_256_ACHROMATIC_CHANNEL_SPREAD) {
71934
+ const gray = Math.round((red + green + blue) / 3);
71935
+ if (gray < 4) {
71936
+ return 16; // <- Note: Pure black lives in the color cube
71937
+ }
71938
+ if (gray > ANSI_256_NEAR_WHITE_GRAY_LEVEL) {
71939
+ return ANSI_256_WHITE_INDEX; // <- Note: Pure white lives in the color cube
71940
+ }
71941
+ return (232 +
71942
+ Math.round(((gray - 8) / ANSI_256_GRAYSCALE_RAMP_MAX_LEVEL) * ANSI_256_GRAYSCALE_RAMP_INDEX_SPAN));
71943
+ }
71944
+ const redIndex = Math.round((red / 255) * 5);
71945
+ const greenIndex = Math.round((green / 255) * 5);
71946
+ const blueIndex = Math.round((blue / 255) * 5);
71947
+ return 16 + 36 * redIndex + 6 * greenIndex + blueIndex;
71948
+ }
71949
+
71950
+ /**
71951
+ * Stable zeroed interaction state used by non-interactive render paths.
71952
+ *
71953
+ * @private utility of the avatar rendering system
71954
+ */
71955
+ const IDLE_AVATAR_INTERACTION_STATE = {
71956
+ gazeX: 0,
71957
+ gazeY: 0,
71958
+ bodyOffsetX: 0,
71959
+ bodyOffsetY: 0,
71960
+ intensity: 0,
71961
+ isPointerActive: false,
71962
+ pointerType: 'idle',
71963
+ };
71964
+ /**
71965
+ * Returns the neutral interaction state used by static/server-side renders.
71966
+ *
71967
+ * @returns Zeroed interaction state.
71968
+ *
71969
+ * @private utility of the avatar rendering system
71970
+ */
71971
+ function createIdleAvatarInteractionState() {
71972
+ return IDLE_AVATAR_INTERACTION_STATE;
71973
+ }
71974
+
71975
+ /**
71976
+ * Resolves the stable avatar render inputs reused across multiple frames.
71977
+ *
71978
+ * @param options Avatar identity and visual selection.
71979
+ * @returns Stable render data ready to be used by `renderAvatarVisual`.
71980
+ *
71981
+ * @private shared helper for canvas avatar rendering
71982
+ */
71983
+ function resolveAvatarRenderDefinition(options) {
71984
+ const avatarDefinition = normalizeAvatarDefinition(options.avatarDefinition);
71985
+ const surface = options.surface || 'framed';
71986
+ return {
71987
+ avatarDefinition,
71988
+ avatarVisual: getAvatarVisualById(options.visualId),
71989
+ surface,
71990
+ palette: createAvatarPalette(avatarDefinition, surface),
71991
+ createRandom: createAvatarRandomFactory(avatarDefinition),
71992
+ };
71993
+ }
71994
+ /**
71995
+ * Renders one deterministic avatar frame into the provided canvas.
71996
+ *
71997
+ * @param options Rendering options.
71998
+ * @param resolvedAvatarRenderDefinition Optional stable render data reused between frames.
71999
+ *
72000
+ * @private shared helper for canvas avatar rendering
72001
+ */
72002
+ function renderAvatarVisual(options, resolvedAvatarRenderDefinition) {
72003
+ const resolvedRenderDefinition = resolvedAvatarRenderDefinition ||
72004
+ resolveAvatarRenderDefinition({
72005
+ avatarDefinition: options.avatarDefinition,
72006
+ visualId: options.visualId,
72007
+ surface: options.surface,
72008
+ });
72009
+ const context = options.canvas.getContext('2d');
72010
+ if (!context) {
72011
+ throw new Error('2D canvas rendering context is unavailable.');
72012
+ }
72013
+ prepareAvatarCanvas(options.canvas, context, options.size, options.devicePixelRatio || 1);
72014
+ resolvedRenderDefinition.avatarVisual.render({
72015
+ canvas: options.canvas,
72016
+ context,
72017
+ size: options.size,
72018
+ devicePixelRatio: options.devicePixelRatio || 1,
72019
+ timeMs: options.timeMs,
72020
+ avatarDefinition: resolvedRenderDefinition.avatarDefinition,
72021
+ palette: resolvedRenderDefinition.palette,
72022
+ createRandom: resolvedRenderDefinition.createRandom,
72023
+ surface: resolvedRenderDefinition.surface,
72024
+ interaction: options.interaction || createIdleAvatarInteractionState(),
72025
+ });
72026
+ }
72027
+
72028
+ /**
72029
+ * Default output width of the ASCII avatar in terminal character cells.
72030
+ *
72031
+ * @private within the repository
72032
+ */
72033
+ const DEFAULT_AVATAR_ASCII_ART_COLUMNS = 32;
72034
+ /**
72035
+ * Stable animation timestamp used when rasterizing animated avatar visuals into a static ASCII frame.
72036
+ *
72037
+ * @private within the repository
72038
+ */
72039
+ const STATIC_AVATAR_ASCII_ART_FRAME_TIME_MS = 840;
72040
+ /**
72041
+ * Renders one built-in avatar visual into ANSI-colored ASCII art for terminal display.
72042
+ *
72043
+ * This is the universal bridge between the canvas avatar visuals shown on the website and
72044
+ * text-based terminal UIs: the visual is rasterized through the exact same `renderAvatarVisual`
72045
+ * pipeline the web uses, then the resulting pixels are converted into colored half-block
72046
+ * characters by `convertImageDataToAsciiArt`.
72047
+ *
72048
+ * @param options Avatar identity, visual selection, output grid size, and the platform canvas factory.
72049
+ * @returns One ANSI-colored string per output row.
72050
+ *
72051
+ * @private within the repository
72052
+ */
72053
+ function renderAvatarVisualAsciiArt(options) {
72054
+ var _a, _b, _c;
72055
+ const columns = (_a = options.columns) !== null && _a !== void 0 ? _a : DEFAULT_AVATAR_ASCII_ART_COLUMNS;
72056
+ const rows = (_b = options.rows) !== null && _b !== void 0 ? _b : Math.round(columns / 2);
72057
+ const canvas = options.createCanvas(DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE);
72058
+ if (!canvas.style) {
72059
+ // Note: `renderAvatarVisual` expects a browser canvas shape; Node.js canvases only need this tiny compatibility shim.
72060
+ canvas.style = {};
72061
+ }
72062
+ renderAvatarVisual({
72063
+ canvas,
72064
+ avatarDefinition: options.avatarDefinition,
72065
+ visualId: options.visualId,
72066
+ size: DEFAULT_AVATAR_SIZE,
72067
+ timeMs: (_c = options.timeMs) !== null && _c !== void 0 ? _c : STATIC_AVATAR_ASCII_ART_FRAME_TIME_MS,
72068
+ devicePixelRatio: 1,
72069
+ });
72070
+ const context = canvas.getContext('2d');
72071
+ if (!context) {
72072
+ throw new EnvironmentMismatchError(spaceTrim$1(`
72073
+ 2D canvas rendering context is unavailable while converting the avatar visual to ASCII art.
72074
+
72075
+ Provide a \`createCanvas\` factory whose canvases support \`getContext('2d')\`,
72076
+ for example \`createCanvas\` of \`@napi-rs/canvas\` in Node.js.
72077
+ `));
72078
+ }
72079
+ const imageData = context.getImageData(0, 0, DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE);
72080
+ return convertImageDataToAsciiArt({
72081
+ imageData,
72082
+ columns,
72083
+ rows,
72084
+ colorDepth: options.colorDepth,
72085
+ });
72086
+ }
72087
+
72088
+ /**
72089
+ * Default built-in avatar visual used when an agent does not define `META IMAGE`.
72090
+ *
72091
+ * @private shared avatar contract
72092
+ */
72093
+ const DEFAULT_AGENT_AVATAR_VISUAL_ID = 'octopus3d4';
72094
+ /**
72095
+ * Resolves the avatar visual preferred by an agent, then falls back to a federated server default
72096
+ * and finally to the caller/server default.
72097
+ *
72098
+ * @param agent Agent metadata and optional remote-profile visual id.
72099
+ * @param defaultAvatarVisualId Optional metadata-resolved server default.
72100
+ * @returns Supported avatar visual id.
72101
+ *
72102
+ * @private shared avatar contract
72103
+ */
72104
+ function resolveAgentAvatarVisualId(agent, defaultAvatarVisualId = DEFAULT_AGENT_AVATAR_VISUAL_ID) {
72105
+ var _a;
72106
+ return (resolveAvatarVisualId((_a = agent.meta) === null || _a === void 0 ? void 0 : _a.avatar) ||
72107
+ agent.avatarVisualId ||
72108
+ agent.defaultAgentAvatarVisualId ||
72109
+ defaultAvatarVisualId);
72110
+ }
72111
+
72112
+ /**
72113
+ * Detects the ANSI color depth supported by the current terminal.
72114
+ *
72115
+ * Prefers 24-bit true color when the environment advertises it (modern terminals such as
72116
+ * Windows Terminal, ConEmu, VS Code, mintty, iTerm2, and WezTerm) and falls back to the
72117
+ * portable 256-color palette otherwise.
72118
+ *
72119
+ * Note: `$` is used to indicate that this function is not a pure function - it reads the process environment
72120
+ *
72121
+ * @returns Color depth usable by `convertImageDataToAsciiArt`
72122
+ *
72123
+ * @private within the repository
72124
+ */
72125
+ function $detectTerminalAnsiColorDepth() {
72126
+ if (typeof process === 'undefined' || !process.env) {
72127
+ return 'ANSI_256';
72128
+ }
72129
+ const colorTerm = (process.env.COLORTERM || '').toLowerCase();
72130
+ if (colorTerm.includes('truecolor') || colorTerm.includes('24bit')) {
72131
+ return 'TRUE_COLOR';
72132
+ }
72133
+ if (process.env.WT_SESSION !== undefined) {
72134
+ return 'TRUE_COLOR'; // <- Note: Windows Terminal always supports true color but does not set COLORTERM
72135
+ }
72136
+ if (process.env.ConEmuANSI === 'ON') {
72137
+ return 'TRUE_COLOR';
72138
+ }
72139
+ const termProgram = process.env.TERM_PROGRAM || '';
72140
+ if (['vscode', 'iTerm.app', 'WezTerm', 'ghostty', 'Hyper'].includes(termProgram)) {
72141
+ return 'TRUE_COLOR';
72142
+ }
72143
+ const term = (process.env.TERM || '').toLowerCase();
72144
+ if (term.includes('truecolor') || term.includes('24bit') || term.includes('direct')) {
72145
+ return 'TRUE_COLOR';
72146
+ }
72147
+ return 'ANSI_256';
72148
+ }
72149
+
72150
+ /**
72151
+ * Output width of the coder-run agent visual in terminal character cells.
72152
+ *
72153
+ * @private internal constant of coder run UI
72154
+ */
72155
+ const CODER_RUN_AGENT_VISUAL_COLUMNS = 24;
72156
+ /**
72157
+ * Output height of the coder-run agent visual in terminal character cells.
72158
+ *
72159
+ * Half of the columns keeps the square avatar visually square in a common terminal font.
72160
+ *
72161
+ * @private internal constant of coder run UI
72162
+ */
72163
+ const CODER_RUN_AGENT_VISUAL_ROWS = 12;
72164
+ /**
72165
+ * Builds the ANSI ASCII-art visual of the `--agent` book shown above the coder-run dashboard.
72166
+ *
72167
+ * The agent's avatar visual is resolved the same way as on the website - the `META AVATAR`
72168
+ * commitment wins, then the `META VISUAL` commitment, then the shared default visual - and is
72169
+ * rendered dynamically through the shared canvas avatar pipeline into terminal ASCII art.
72170
+ *
72171
+ * The visual is decorative, so any failure (for example when the optional `@napi-rs/canvas`
72172
+ * module is not installed) returns `null` and the caller keeps the default brand banner.
72173
+ *
72174
+ * @param agentSource Source of the `--agent` book file.
72175
+ * @returns ANSI-colored ASCII-art lines or `null` when the visual cannot be rendered.
72176
+ */
72177
+ async function buildCoderRunAgentVisual(agentSource) {
72178
+ try {
72179
+ // Note: `@napi-rs/canvas` is an optional native module, so it is imported dynamically and lazily
72180
+ const { createCanvas } = await import('@napi-rs/canvas');
72181
+ const agentBasicInformation = parseAgentSource(agentSource);
72182
+ const avatarDefinition = createAvatarDefinitionFromAgentBasicInformation(agentBasicInformation);
72183
+ const avatarVisualId = resolveAgentAvatarVisualId(agentBasicInformation, resolveAvatarVisualId(agentBasicInformation.meta.visual) || DEFAULT_AGENT_AVATAR_VISUAL_ID);
72184
+ return renderAvatarVisualAsciiArt({
72185
+ avatarDefinition,
72186
+ visualId: avatarVisualId,
72187
+ columns: CODER_RUN_AGENT_VISUAL_COLUMNS,
72188
+ rows: CODER_RUN_AGENT_VISUAL_ROWS,
72189
+ colorDepth: $detectTerminalAnsiColorDepth(),
72190
+ createCanvas: (width, height) => createCanvas(width, height),
72191
+ });
72192
+ }
72193
+ catch (error) {
72194
+ return null;
72195
+ }
72196
+ }
72197
+
71438
72198
  /**
71439
72199
  * Formats commit message lines for console display.
71440
72200
  */
@@ -73351,13 +74111,15 @@ async function runCodexPrompts(providedOptions) {
73351
74111
  startPauseListenerIfNeeded(isRichUiEnabled);
73352
74112
  try {
73353
74113
  const resolvedCoderContext = await resolveCoderContext(options.context, process.cwd());
73354
- const resolvedAgentSystemMessage = await resolveAgentSystemMessage(options.agent, process.cwd());
74114
+ const resolvedCoderAgent = await resolveCoderAgent(options.agent, process.cwd());
74115
+ const resolvedAgentSystemMessage = resolvedCoderAgent === null || resolvedCoderAgent === void 0 ? void 0 : resolvedCoderAgent.systemMessage;
73355
74116
  if (await runDryRunIfRequested(options)) {
73356
74117
  return;
73357
74118
  }
73358
74119
  const { runner, actualRunnerModel, runnerMetadata } = resolvePromptRunner(options);
73359
74120
  console.info(colors.green(`Running prompts with ${runner.name}`));
73360
74121
  initializeRunUi(uiHandle, runner.name, actualRunnerModel, options);
74122
+ await initializeRunUiAgentVisual(uiHandle, resolvedCoderAgent === null || resolvedCoderAgent === void 0 ? void 0 : resolvedCoderAgent.agentSource);
73361
74123
  await seedCachedAveragePromptDuration({
73362
74124
  options,
73363
74125
  actualRunnerModel,
@@ -73595,6 +74357,20 @@ function initializeRunUi(uiHandle, runnerName, actualRunnerModel, options) {
73595
74357
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('loading');
73596
74358
  uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setStatusMessage(`Running prompts with ${runnerName}`);
73597
74359
  }
74360
+ /**
74361
+ * Renders the `--agent` book avatar as ASCII art and shows it instead of the default brand banner.
74362
+ *
74363
+ * Keeps the default banner when no agent is selected, the UI is disabled, or the visual cannot be rendered.
74364
+ */
74365
+ async function initializeRunUiAgentVisual(uiHandle, agentSource) {
74366
+ if (!uiHandle || !agentSource) {
74367
+ return;
74368
+ }
74369
+ const agentVisualLines = await buildCoderRunAgentVisual(agentSource);
74370
+ if (agentVisualLines) {
74371
+ uiHandle.state.setAgentVisualLines(agentVisualLines);
74372
+ }
74373
+ }
73598
74374
  /**
73599
74375
  * Loads prompt files, updates progress displays, and selects the next runnable prompt.
73600
74376
  */