honeydo 0.1.3 → 0.2.0

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": "honeydo",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "One CLI for every AI capability — chat, vision, image, video, audio. Built for AI agents.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -99,10 +99,26 @@ export type PickerKeyAction = {
99
99
  } | {
100
100
  type: "noop";
101
101
  };
102
- /** One selectable row of the arrow-key picker: a cc-switch provider. */
102
+ /**
103
+ * One selectable row of the arrow-key picker: a cc-switch provider.
104
+ * `sessions` = attributable live claude sessions for this provider (0 included);
105
+ * absent when the scan is unavailable (degraded — no session column, like an
106
+ * absent quota).
107
+ *
108
+ * `unattributed` is frame-level, not per-entry: the bare-claude process count
109
+ * attached (same value) to every entry by the claude menu path so ANY
110
+ * deps.pickProvider implementation still renders the footer hint — including
111
+ * two-arg wrappers that forward only `(entries, initialIndex)` to the
112
+ * production picker and would otherwise drop the positional third argument.
113
+ * Consumers must resolve it via `pickProviderInteractive` (positional arg
114
+ * wins, entries-borne value is the fallback), never read it per entry.
115
+ */
103
116
  export type PickerEntry = {
104
117
  name: string;
105
118
  quota?: string;
119
+ tag?: string;
120
+ sessions?: number;
121
+ unattributed?: number;
106
122
  };
107
123
  /** Result of the arrow-key picker (C-P1): a picked provider, or a skip. */
108
124
  export type PickerOutcome = {
@@ -216,15 +232,23 @@ export type RunDeps = {
216
232
  isInteractive: () => boolean;
217
233
  /**
218
234
  * TTY-only arrow-key provider picker (C-P1/C-P3): present the cc-switch
219
- * provider entries (the production impl appends its own trailing 不切换
235
+ * provider entries (the production impl renders a dim Esc 不切换 footer;
220
236
  * row — `entries` holds providers only) and resolve the confirmed entry,
221
- * or {kind:"skip"} on Esc / the 不切换 row. `initialIndex` is the caller-
237
+ * or {kind:"skip"} on Esc/C-g (= 退出,不启动后端). `initialIndex` is the caller-
222
238
  * computed row to highlight (memory hit or 0); implementations clamp it.
223
239
  * Only ever invoked on the claude path in a TTY with no --provider —
224
240
  * non-interactive callers (skills/CI/pipes) must see zero prompts and
225
241
  * zero extra cc-switch DB reads.
226
242
  */
227
- pickProvider: (entries: PickerEntry[], initialIndex: number) => Promise<PickerOutcome>;
243
+ pickProvider: (entries: PickerEntry[], initialIndex: number,
244
+ /**
245
+ * Count of live bare-claude processes (no --settings) shown as a dim
246
+ * footer hint on the menu. 0 / omitted → plain Esc footer. Only forwarded
247
+ * by the claude TTY menu path. Redundantly carried on the entries
248
+ * themselves (`PickerEntry.unattributed`), so implementations that drop
249
+ * this positional argument still render the hint.
250
+ */
251
+ unattributedCount?: number) => Promise<PickerOutcome>;
228
252
  /**
229
253
  * Quota subtitles (revise-3, C-Q4): given every menu provider's {name, env},
230
254
  * resolve name → formatted quota text (only providers with usable data are
@@ -235,6 +259,17 @@ export type RunDeps = {
235
259
  name: string;
236
260
  env: Record<string, string>;
237
261
  }[]) => Promise<Map<string, string>>;
262
+ /**
263
+ * One joined-argv line per live claude process (`ps -ww -o command=`), for
264
+ * TTY-picker session attribution (revise: picker 会话列). Default impl is a
265
+ * read-only `pgrep -x claude` + `ps -ww` pair. `pgrep` exit 1 (no match)
266
+ * resolves to []; pgrep exit >1 / ps failure / spawn error REJECTS — the
267
+ * caller degrades to no session column. Optional: when absent the scan
268
+ * capability is missing and the picker renders without session counts.
269
+ * Only ever invoked on the claude TTY menu path — never non-TTY, never
270
+ * `--provider`, never silent reuse (zero spawn discipline preserved).
271
+ */
272
+ listClaudeProcessArgs?: () => Promise<string[]>;
238
273
  /**
239
274
  * Read the remembered provider name (D2): trimmed first line of
240
275
  * LAST_PROVIDER_PATH, or undefined when missing/empty/unreadable. Only
@@ -347,7 +382,7 @@ export type PickerKeyInput = {
347
382
  * - "return" / "enter" → confirm the current row (the physical Enter key
348
383
  * emits "return" in raw mode; "enter" is the LF byte, which a tty may
349
384
  * substitute for CR in input buffered before raw mode was enabled)
350
- * - "escape" → skip (不切换)
385
+ * - "escape" → skip (退出,不启动后端)
351
386
  * - Emacs (revise-2): ctrl+"n" ≡ down, ctrl+"p" ≡ up (same wrap); ctrl+"g"
352
387
  * ≡ escape → skip; meta+"<" → first row (absolute), meta+">" → last row
353
388
  * (absolute). Horizontal Emacs keys (C-f/C-b/C-a/C-e) and paging (C-v/M-v)
@@ -356,7 +391,7 @@ export type PickerKeyInput = {
356
391
  * exit 130)
357
392
  *
358
393
  * `index` is the highlighted row, `count` the total rendered rows INCLUDING
359
- * the trailing 不切换 row. Movement wraps with `(index±1+count)%count`; with
394
+ * the footer row. Movement wraps with `(index±1+count)%count`; with
360
395
  * count <= 0 moves are a noop (nothing is rendered).
361
396
  */
362
397
  export declare function applyPickerKey(k: PickerKeyInput, index: number, count: number): PickerKeyAction;
@@ -395,16 +430,35 @@ export declare function buildQuotaRequest(env: {
395
430
  export declare function parseKimiUsages(body: unknown): QuotaWindows;
396
431
  /**
397
432
  * Parse GLM `/api/monitor/usage/quota/limit` (C-Q2): data.limits[] entries
398
- * with type == "TOKENS_LIMIT"; sorted by nextResetTime ascending — first is
433
+ * with type "TOKENS_LIMIT" (coding-plan 订阅) or "CREDIT_LIMIT" (credit 资源
434
+ * 包) — billing type is per-account, fields are identical, same rendering
435
+ * (parity with statusline-sage); sorted by nextResetTime ascending — first is
399
436
  * the short (5h) window, last the weekly one.
400
437
  */
401
438
  export declare function parseGlmQuota(body: unknown): QuotaWindows;
402
439
  /**
403
- * Format quota windows as the menu subtitle (C-Q3): `5h:P% wk:P% ↻<rel>`
404
- * (short reset preferred for the ↻ segment); missing windows degrade; a
405
- * missing/expired reset omits the ↻ segment entirely; no windows → "".
440
+ * Format quota windows as the menu subtitle (C-Q3, 2026-09 双窗重置):
441
+ * `5h:P% ↻<rel> wk:P% ↻<rel>` — each window carries its own reset time.
442
+ * Missing windows degrade; a missing/expired reset omits that window's ↻;
443
+ * no windows → "". Byte-exact contract with downstream consumers — the
444
+ * plain-text output is locked by unit tests; colors live in `colorQuota`,
445
+ * never here.
406
446
  */
407
447
  export declare function formatQuota(q: QuotaWindows, nowMs: number): string;
448
+ export declare const QUOTA_HIGH = 85;
449
+ export declare const QUOTA_MID = 60;
450
+ /**
451
+ * Limit 用量 → 段颜色:≥85 朱红、≥60 琥珀、其余(含非有限数)回退苔绿
452
+ * (阈值语义照搬 statusline-sage `_level_color`)。
453
+ */
454
+ export declare function levelColor(pct: number): string;
455
+ /**
456
+ * `formatQuota` 的染色版(picker 用):`5h:P% ↻rel`/`wk:P% ↻rel` 每个窗口段
457
+ * 按各自 pct 独立选色,各自的重置时间恒 dim,不参与染色。结构与降级规则和
458
+ * `formatQuota` 一致(无窗口 → "")。行内使用 `\x1b[39m`/`\x1b[22m` 收尾而
459
+ * 非全复位,以免抹掉选中行背景。
460
+ */
461
+ export declare function colorQuota(q: QuotaWindows, nowMs: number): string;
408
462
  export interface ClaudeOptions {
409
463
  /** Omit for interactive mode (no -p emitted). */
410
464
  prompt?: string;
@@ -667,6 +721,20 @@ export declare function runAgy(args: string[], timeoutMs: number, cwd?: string):
667
721
  * through. SIGTERM enforces the timeout deterministically.
668
722
  */
669
723
  export declare function runClaude(args: string[], timeoutMs: number, cwd?: string): Promise<SpawnResult>;
724
+ /**
725
+ * Spawn a backend with stdio fully inherited (interactive TUI mode).
726
+ *
727
+ * Unlike runAgy/runClaude: no timeout, no stdout/stderr capture —
728
+ * the child owns the terminal. The child's exit code is passed
729
+ * through unchanged (SIGINT→130, SIGTERM→143 per shell convention).
730
+ *
731
+ * An inherited CLAUDE_CODE_CHILD_SESSION=1 is stripped: claude reads it as
732
+ * "spawned sub-worker" and silently turns transcript saving off, so the
733
+ * session never shows up in `claude --resume`. An attended TUI is a
734
+ * top-level session, never a sub-worker. agy never reads the var, so the
735
+ * strip is a no-op there; print mode keeps the env verbatim.
736
+ */
737
+ export declare function spawnInteractive(bin: string, args: string[], cwd?: string): Promise<InteractiveSpawnResult>;
670
738
  /** Spawn agy in interactive TUI mode (no -p, inherited stdio). */
671
739
  export declare function runAgyInteractive(args: string[], cwd?: string): Promise<InteractiveSpawnResult>;
672
740
  /** Spawn claude in interactive TUI mode (no -p, inherited stdio). */
@@ -713,6 +781,68 @@ export declare function readCcSwitchProvider(dbPath: string): Promise<ProviderLo
713
781
  export declare function validateProviderName(name: string): string | {
714
782
  error: string;
715
783
  };
784
+ /**
785
+ * The full picker frame as plain lines: 标题 2 行 + dim 分隔线 + 条目 N 行 +
786
+ * dim 末行提示 (non-selectable — skip 只经 Esc/C-g). With `unattributedCount >
787
+ * 0` the footer reads `另有 N 个裸 claude 会话未归属 · Esc 不切换` (bare claude
788
+ * processes carry no --settings and cannot be attributed to a provider);
789
+ * otherwise it is plain `Esc 不切换`. Pure: no I/O; with `noColor` (NO_COLOR
790
+ * downgrade) zero ANSI escapes — 仅纯文本排版,❯ 缩进 + 列对齐保留。Redraw
791
+ * cursor-up height = `entries.length + 4` regardless (footer merges into the
792
+ * one hint row).
793
+ */
794
+ export declare function renderPickerRows(entries: PickerEntry[], selectedIndex: number, noColor: boolean, unattributedCount?: number): string[];
795
+ /**
796
+ * Production deps.pickProvider: arrow-key menu rendered entirely on stderr
797
+ * (stdout stays pipe-clean). ↑↓/j/k move with wrap, Enter confirms, Esc
798
+ * skips, ctrl-c restores the terminal then exits 130; any other key is a
799
+ * noop (C-P1). Frame = 标题 2 行 + 分隔线 + 条目 N 行 + dim `Esc 不切换`
800
+ * 末行提示——旧的可选中「不切换」行已退化为提示(skip 走 Esc/C-g),移动
801
+ * 只覆盖条目行。
802
+ *
803
+ * Zero deps: `readline.emitKeypressEvents` + raw-mode stdin + hand-written
804
+ * ANSI (16-color structure + truecolor Sage Limit 染色; `NO_COLOR` 非空 →
805
+ * 全无色纯文本). Rows are redrawn in place (cursor-up + `\r` + clear-to-EOL
806
+ * per line) so navigation leaves no ghosting (C-P3).
807
+ *
808
+ * Raw-mode lifecycle: `process.stdin.isRaw` is saved before
809
+ * `setRawMode(true)` and restored on EVERY exit path (confirm / Esc / ctrl-c
810
+ * / stream end / error) before the promise settles, and the keypress (and
811
+ * its lazily-attached internal `data`) listeners are removed — so the
812
+ * spawned claude TUI takes stdin over cleanly. The picker always completes
813
+ * before any backend spawn.
814
+ *
815
+ * `unattributedCount` (optional) drives the bare-claude footer hint; 0 or
816
+ * omitted renders the plain `Esc 不切换` footer. When the positional arg is
817
+ * absent, the count is recovered from the entries themselves
818
+ * (`PickerEntry.unattributed`, attached by the claude menu path) so two-arg
819
+ * pickProvider wrappers still render the hint.
820
+ */
821
+ export declare function pickProviderInteractive(entries: PickerEntry[], initialIndex: number, unattributedCount?: number): Promise<PickerOutcome>;
822
+ /**
823
+ * One provider's attribution facts, mapped from `extractProviderEnv()` output
824
+ * in the caller. Tokens live only in memory for comparison — they are never
825
+ * printed, logged, persisted, or embedded in any error message.
826
+ */
827
+ export type SessionAttributionProvider = {
828
+ name: string;
829
+ baseUrl?: string;
830
+ authToken?: string;
831
+ apiKey?: string;
832
+ };
833
+ /** Per-provider live session counts + the bare-claude (unattributable) count. */
834
+ export type SessionAttribution = {
835
+ counts: Record<string, number>;
836
+ unattributed: number;
837
+ };
838
+ /**
839
+ * Attribute joined ps argv lines to providers (pure, total — never throws).
840
+ * Lines with a parseable `--settings` env matching a provider increment that
841
+ * provider's count; providers with identical URL+token each count the same
842
+ * line (accepted edge). Bare claude lines, unparseable JSON, and settings
843
+ * matching no provider all count as `unattributed`.
844
+ */
845
+ export declare function parseSessionAttribution(lines: string[], providers: SessionAttributionProvider[]): SessionAttribution;
716
846
  /**
717
847
  * Route argv to the agy / claude / api backend via injectable deps (C1/C2).
718
848
  * Returns a {exitCode, stdout, stderr} outcome; main() owns process.exit.