@0xkahi/pi-qol 0.0.5 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xkahi/pi-qol",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "quality of life pi extensions",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -54,6 +54,7 @@
54
54
  "typescript": "^5"
55
55
  },
56
56
  "dependencies": {
57
+ "@0xkahi/cli-dye": "^1.1.0",
57
58
  "zod": "^4.4.3"
58
59
  }
59
60
  }
@@ -45,5 +45,5 @@ The extension is feature-gated by the `custom_footer` config flag and can dynami
45
45
  - **`@earendil-works/pi-tui`**: Implements the `Component` contract and uses `truncateToWidth`, `visibleWidth`, and `TUI.requestRender`.
46
46
  - **`../../config-loader`**: Reads the `custom_footer` feature flag and full footer config (colors, icons, display toggles) via `ConfigLoader`.
47
47
  - **`../../schemas/config.schema`**: `CustomFooterConfig` is derived from the `Config` schema's `custom_footer` shape.
48
- - **`../../utils/crayon.util`**: Applies custom ANSI colors to directory text, model name, and subscription usage labels.
48
+ - **`@0xkahi/cli-dye`**: Applies configured truecolor ANSI styling to directory text, model name, and subscription usage labels.
49
49
  - **`../../libs/subscription-usage/**`**: Delegates OAuth usage fetching and reset formatting to `SubscriptionUsageApi` and provider-specific strategies.
@@ -1,7 +1,7 @@
1
1
  import { basename } from 'node:path';
2
+ import { dye } from '@0xkahi/cli-dye';
2
3
  import type { Component } from '@earendil-works/pi-tui';
3
4
  import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
4
- import { crayon } from '../../utils/crayon.util';
5
5
  import { pickSubscriptionUsageWindow, resolveSupportedProvider, SubscriptionUsageManager } from './subscription-usage-manager';
6
6
  import { buildStatsLeft, buildSubscriptionUsageSegment, calculateUsageTotals } from './token-stats';
7
7
  import type { CustomFooterComponentDeps, CustomFooterConfig } from './types';
@@ -74,7 +74,7 @@ export class CustomFooterComponent implements Component {
74
74
 
75
75
  let line: string;
76
76
  if (config.colors.directory) {
77
- line = crayon.colorize(directoryText, { fg: config.colors.directory });
77
+ line = dye.colorize(directoryText, { fg: dye.hex(config.colors.directory) });
78
78
  if (branch) line += this.deps.theme.fg('dim', ` (${branch})`);
79
79
  if (sessionName) line += this.deps.theme.fg('dim', ` • ${sessionName}`);
80
80
  } else {
@@ -140,7 +140,7 @@ export class CustomFooterComponent implements Component {
140
140
  const model = this.deps.ctx.model;
141
141
  const modelName = model?.id ?? 'no-model';
142
142
  const modelNameSegment = config.colors.modelName
143
- ? crayon.colorize(modelName, { fg: config.colors.modelName })
143
+ ? dye.colorize(modelName, { fg: dye.hex(config.colors.modelName) })
144
144
  : this.deps.theme.fg('dim', modelName);
145
145
 
146
146
  if (!model?.reasoning) return modelNameSegment;
@@ -1,5 +1,5 @@
1
+ import { dye } from '@0xkahi/cli-dye';
1
2
  import type { ContextUsage, SessionEntry } from '@earendil-works/pi-coding-agent';
2
- import { crayon } from '../../utils/crayon.util';
3
3
  import { clampPercent, renderProgressBar } from './progress-bar';
4
4
  import type { CustomFooterColors, CustomFooterDisplay, CustomFooterIcons, FooterTheme, SupportedProvider, UsageTotals } from './types';
5
5
 
@@ -71,14 +71,15 @@ export function buildSubscriptionUsageSegment({
71
71
  const pct = clampPercent(usage.usedPercent);
72
72
  const roundedPercent = Math.round(pct).toString();
73
73
  const bar = renderProgressBar(pct);
74
+ const foreground = dye.hex(providerColor);
74
75
 
75
76
  const resetParts = usage.resetDescription ? [icons.refresh, usage.resetDescription].filter(Boolean).join(' ') : '';
76
77
 
77
78
  return [
78
- crayon.colorize(usage.responseLabel, { fg: providerColor }),
79
- crayon.colorize(usage.windowLabel, { fg: providerColor }),
80
- `${crayon.colorize(bar.filled, { fg: providerColor })}${theme.fg('dim', bar.empty)}`,
81
- crayon.colorize(`${roundedPercent}%`, { fg: providerColor }),
79
+ dye.colorize(usage.responseLabel, { fg: foreground }),
80
+ dye.colorize(usage.windowLabel, { fg: foreground }),
81
+ `${dye.colorize(bar.filled, { fg: foreground })}${theme.fg('dim', bar.empty)}`,
82
+ dye.colorize(`${roundedPercent}%`, { fg: foreground }),
82
83
  resetParts ? theme.fg('dim', resetParts) : undefined,
83
84
  ]
84
85
  .filter((part): part is string => Boolean(part))
@@ -4,7 +4,6 @@
4
4
 
5
5
  This folder contains low-level, reusable helpers used across the `pi-qol` extension. They are pure utility modules with no business logic or UI concerns.
6
6
 
7
- - **`crayon.util.ts`** — ANSI terminal styling: colorize text with hex foreground/background colors, apply reverse video, and strip ANSI escape sequences.
8
7
  - **`model-resolver.util.ts`** — Resolve a concrete AI model and its authentication credentials from a configured `ModelConfig` or the active session model.
9
8
  - **`raw-data-parser.util.ts`** — Type-safe coercion helpers for `unknown` runtime values into `Record<string, unknown>`, trimmed `string`, or finite `number`.
10
9
  - **`path.util.ts`** — Locate extension-specific files on disk: generic file existence checks, global/project extension `config.json`, and the shared `auth.json` file.
@@ -13,30 +12,24 @@ This folder contains low-level, reusable helpers used across the `pi-qol` extens
13
12
 
14
13
  - **Static utility classes** (`RawDataParser`, `PathUtil`) group related stateless functions under a clear namespace.
15
14
  - **Service class with injected context** (`ModelResolver`) receives an `ExtensionContext` dependency rather than importing global state, keeping it testable and decoupled.
16
- - **Object-as-namespace export** (`crayon`) exposes a small, discoverable API surface while hiding internal helpers (`hexToRgb`, `fgAnsi`, `bgAnsi`, `ANSI_ESCAPE_REGEX`).
17
15
  - **Result/Option types** (`ResolveModelResult`, `FileSearchResult`) avoid exceptions and force callers to handle success and failure paths explicitly.
18
16
  - **Function overloads** (`PathUtil.findExtensionConfig`) provide type-safe, domain-driven entry points for global vs. project lookups.
19
- - **Defensive validation** (`RawDataParser`, `crayon.colorize`) silently returns `undefined` or the original input when data is invalid instead of throwing.
17
+ - **Defensive validation** (`RawDataParser`) silently returns `undefined` when data is invalid instead of throwing.
20
18
 
21
19
  ## Data & Control Flow
22
20
 
23
- 1. **Terminal styling**
24
- - `crayon.colorize(text, { fg?, bg? })` validates hex strings against `COLOR_HEX_REGEX`.
25
- - Valid colors are converted to RGB and wrapped in ANSI escape codes; the text is returned unchanged if no valid colors are provided.
26
- - `crayon.stripAnsi(text)` removes all ANSI escape sequences using a compiled regex.
27
-
28
- 2. **Model resolution**
21
+ 1. **Model resolution**
29
22
  - `ModelResolver.resolveModel(configModel?)` builds a candidate list in priority order: configured model first, then the active session model (deduplicated).
30
23
  - For each candidate it calls `ctx.modelRegistry.getApiKeyAndHeaders(model)`.
31
24
  - The first candidate with successful auth is returned as a `ResolvedModel`, including the API key, headers, reasoning settings, and OAuth flag.
32
25
  - If all candidates fail, a consolidated error string is returned.
33
26
 
34
- 3. **Raw data parsing**
27
+ 2. **Raw data parsing**
35
28
  - `RawDataParser.asRecord` checks for non-null, non-array objects.
36
29
  - `stringValue` returns only non-empty trimmed strings.
37
30
  - `numberValue` coerces strings/numbers and returns only finite values.
38
31
 
39
- 4. **Path resolution**
32
+ 3. **Path resolution**
40
33
  - `PathUtil.findFile` checks `existsSync` and returns both existence and the resolved path.
41
34
  - `findExtensionConfig({ type: 'global' })` resolves to `<agentDir>/extensions/<EXTENSION_ID>/config.json`.
42
35
  - `findExtensionConfig({ type: 'project', cwd })` resolves to `<cwd>/.pi/extensions/<EXTENSION_ID>/config.json`.
@@ -50,7 +43,6 @@ This folder contains low-level, reusable helpers used across the `pi-qol` extens
50
43
  - `@earendil-works/pi-ai`
51
44
  - `Model<Api>` is the resolved entity returned by `ModelResolver`.
52
45
  - `../constants`
53
- - `COLOR_HEX_REGEX` drives hex validation in `crayon`.
54
46
  - `EXTENSION_ID` is used by `PathUtil` to build config directory paths.
55
47
  - `../schemas/shared-config.schema`
56
48
  - `ModelConfig` and its `reasoning` field are consumed by `ModelResolver`.
@@ -1,58 +0,0 @@
1
- import { COLOR_HEX_REGEX } from '../constants';
2
-
3
- type ColorizeOptions = {
4
- bg?: string;
5
- fg?: string;
6
- };
7
-
8
- function hexToRgb(hex: string): { r: number; g: number; b: number } {
9
- return {
10
- r: Number.parseInt(hex.slice(1, 3), 16),
11
- g: Number.parseInt(hex.slice(3, 5), 16),
12
- b: Number.parseInt(hex.slice(5, 7), 16),
13
- };
14
- }
15
-
16
- function fgAnsi(hex: string): string {
17
- const { r, g, b } = hexToRgb(hex);
18
- return `\x1b[38;2;${r};${g};${b}m`;
19
- }
20
-
21
- function bgAnsi(hex: string): string {
22
- const { r, g, b } = hexToRgb(hex);
23
- return `\x1b[48;2;${r};${g};${b}m`;
24
- }
25
-
26
- function colorize(text: string, { bg, fg }: ColorizeOptions = {}): string {
27
- const hasFg = fg !== undefined && COLOR_HEX_REGEX.test(fg);
28
- const hasBg = bg !== undefined && COLOR_HEX_REGEX.test(bg);
29
-
30
- if (!hasFg && !hasBg) {
31
- return text;
32
- }
33
-
34
- const prefix = `${hasBg ? bgAnsi(bg) : ''}${hasFg ? fgAnsi(fg) : ''}`;
35
- const suffix = `${hasFg ? '\x1b[39m' : ''}${hasBg ? '\x1b[49m' : ''}`;
36
-
37
- return `${prefix}${text}${suffix}`;
38
- }
39
-
40
- const ANSI_ESCAPE_REGEX = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, 'g');
41
-
42
- function stripAnsi(text: string): string {
43
- return text.replace(ANSI_ESCAPE_REGEX, '');
44
- }
45
-
46
- export const crayon = {
47
- colorize(text: string, opts: ColorizeOptions = {}): string {
48
- return colorize(text, opts);
49
- },
50
-
51
- reverseVideo(text: string): string {
52
- return `\x1b[7m${text}\x1b[27m`;
53
- },
54
-
55
- stripAnsi(text: string): string {
56
- return stripAnsi(text);
57
- },
58
- };