honeydo 0.1.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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -0
  3. package/README.zh-CN.md +88 -0
  4. package/package.json +54 -0
  5. package/packages/cli/dist/index.d.ts +2 -0
  6. package/packages/cli/dist/index.js +171 -0
  7. package/packages/cli/dist/index.js.map +1 -0
  8. package/packages/doubao/dist/cli.d.ts +38 -0
  9. package/packages/doubao/dist/cli.d.ts.map +1 -0
  10. package/packages/doubao/dist/cli.js +206 -0
  11. package/packages/gcli/dist/cli.d.ts +465 -0
  12. package/packages/gcli/dist/cli.js +2017 -0
  13. package/packages/gcli/dist/cli.js.map +1 -0
  14. package/packages/lmedia/dist/index.d.ts +1 -0
  15. package/packages/lmedia/dist/index.js +1594 -0
  16. package/packages/lmedia/python/edit.py +107 -0
  17. package/packages/lmedia/python/esrgan_path.py +16 -0
  18. package/packages/lmedia/python/gen.py +131 -0
  19. package/packages/lmedia/python/serve.py +352 -0
  20. package/packages/lmedia/python/sfx.py +527 -0
  21. package/packages/lmedia/python/teacache.py +255 -0
  22. package/packages/lmedia/python/upscale.py +41 -0
  23. package/packages/minimax/dist/cli.d.ts +51 -0
  24. package/packages/minimax/dist/cli.js +307 -0
  25. package/packages/minimax/dist/cli.js.map +1 -0
  26. package/packages/minimax/dist/client.d.ts +20 -0
  27. package/packages/minimax/dist/client.js +55 -0
  28. package/packages/minimax/dist/client.js.map +1 -0
  29. package/packages/minimax/dist/tts.d.ts +33 -0
  30. package/packages/minimax/dist/tts.js +64 -0
  31. package/packages/minimax/dist/tts.js.map +1 -0
  32. package/packages/minimax/dist/validate.d.ts +29 -0
  33. package/packages/minimax/dist/validate.js +122 -0
  34. package/packages/minimax/dist/validate.js.map +1 -0
  35. package/packages/minimax/dist/voice-clone.d.ts +17 -0
  36. package/packages/minimax/dist/voice-clone.js +47 -0
  37. package/packages/minimax/dist/voice-clone.js.map +1 -0
  38. package/packages/minimax/dist/voices.d.ts +17 -0
  39. package/packages/minimax/dist/voices.js +20 -0
  40. package/packages/minimax/dist/voices.js.map +1 -0
  41. package/packages/qwen/dist/index.d.ts +1 -0
  42. package/packages/qwen/dist/index.js +311 -0
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * doubao — CLI for generating images via the Doubao (Volces Ark) API.
4
+ *
5
+ * Sends a prompt to the Ark image-generation endpoint, walks a model fallback
6
+ * chain (5-0 → lite → 4-5) when a model is not activated for the account,
7
+ * downloads the result, and saves it to a file. Last stdout line is always
8
+ * "Saved to: <absolute path>" so scripts can grep it.
9
+ */
10
+ import { mkdir, writeFile } from "node:fs/promises";
11
+ import { realpathSync } from "node:fs";
12
+ import { dirname, join, resolve } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { parseArgs } from "node:util";
15
+ const DEFAULT_MODEL = "doubao-seedream-5-0-260128";
16
+ const FALLBACK_MODELS = [
17
+ "doubao-seedream-5-0-lite-260128",
18
+ "doubao-seedream-4-5-251128",
19
+ ];
20
+ const MODEL_CANDIDATES = [DEFAULT_MODEL, ...FALLBACK_MODELS];
21
+ export const DEFAULT_SIZE = "2K";
22
+ const PRESET_SIZES = new Set(["2K", "3K"]);
23
+ const PIXEL_SIZE_PATTERN = /^(\d{2,5})x(\d{2,5})$/i;
24
+ const API_URL = "https://ark.cn-beijing.volces.com/api/v3/images/generations";
25
+ // ---------------------------------------------------------------------------
26
+ // Pure helpers (unit-tested)
27
+ // ---------------------------------------------------------------------------
28
+ export function normalizeSize(sizeInput) {
29
+ const trimmedSize = sizeInput.trim();
30
+ const upperSize = trimmedSize.toUpperCase();
31
+ if (PRESET_SIZES.has(upperSize))
32
+ return upperSize;
33
+ const sizeMatch = PIXEL_SIZE_PATTERN.exec(trimmedSize);
34
+ if (sizeMatch)
35
+ return `${sizeMatch[1]}x${sizeMatch[2]}`;
36
+ throw new Error('Invalid size. Supported values: "2K", "3K", or "<width>x<height>" (for example "3072x2048").');
37
+ }
38
+ export function isModelNotOpenError(status, errorText) {
39
+ return status === 404 && errorText.includes("ModelNotOpen");
40
+ }
41
+ export function resolveSizeForModel(size, model) {
42
+ // 3K shorthand is available in 5.0 models; map it to explicit pixels for older fallbacks.
43
+ if (size === "3K" && !model.startsWith("doubao-seedream-5-0")) {
44
+ return "3072x3072";
45
+ }
46
+ return size;
47
+ }
48
+ export function parseCliArgs(argv) {
49
+ try {
50
+ const { values, positionals } = parseArgs({
51
+ args: argv,
52
+ options: {
53
+ size: { type: "string" },
54
+ output: { type: "string" },
55
+ help: { type: "boolean" },
56
+ },
57
+ allowPositionals: true,
58
+ });
59
+ return {
60
+ prompt: positionals[0],
61
+ size: values.size,
62
+ output: values.output,
63
+ help: values.help ?? false,
64
+ };
65
+ }
66
+ catch (err) {
67
+ return { error: err instanceof Error ? err.message : String(err) };
68
+ }
69
+ }
70
+ function sanitizePrompt(prompt) {
71
+ return prompt
72
+ .substring(0, 50)
73
+ .replace(/[^a-z0-9]/gi, "_")
74
+ .toLowerCase();
75
+ }
76
+ export async function generateImage(input) {
77
+ const normalizedSize = input.size ? normalizeSize(input.size) : DEFAULT_SIZE;
78
+ let result = null;
79
+ let modelUsed = DEFAULT_MODEL;
80
+ let sizeUsed = normalizedSize;
81
+ let lastModelNotOpenError = "";
82
+ for (const candidateModel of MODEL_CANDIDATES) {
83
+ const candidateSize = resolveSizeForModel(normalizedSize, candidateModel);
84
+ const requestBody = {
85
+ model: candidateModel,
86
+ prompt: input.prompt,
87
+ sequential_image_generation: "disabled",
88
+ response_format: "url",
89
+ size: candidateSize,
90
+ stream: false,
91
+ watermark: false,
92
+ };
93
+ const response = await fetch(API_URL, {
94
+ method: "POST",
95
+ headers: {
96
+ "Content-Type": "application/json",
97
+ Authorization: `Bearer ${input.apiKey}`,
98
+ },
99
+ body: JSON.stringify(requestBody),
100
+ });
101
+ if (response.ok) {
102
+ result = (await response.json());
103
+ modelUsed = candidateModel;
104
+ sizeUsed = candidateSize;
105
+ break;
106
+ }
107
+ const errorText = await response.text();
108
+ if (isModelNotOpenError(response.status, errorText)) {
109
+ lastModelNotOpenError = `Doubao API error: ${response.status} ${response.statusText} - ${errorText}`;
110
+ continue;
111
+ }
112
+ throw new Error(`Doubao API error: ${response.status} ${response.statusText} - ${errorText}`);
113
+ }
114
+ if (!result) {
115
+ throw new Error(`No available model for this account. Tried: ${MODEL_CANDIDATES.join(", ")}. Last error: ${lastModelNotOpenError}`);
116
+ }
117
+ const imageUrl = result.data?.[0]?.url;
118
+ if (!imageUrl) {
119
+ throw new Error("No image URL in response from Doubao API");
120
+ }
121
+ const imageResponse = await fetch(imageUrl);
122
+ if (!imageResponse.ok) {
123
+ throw new Error(`Failed to download image from ${imageUrl}: ${imageResponse.status} ${imageResponse.statusText}`);
124
+ }
125
+ const imageBuffer = await imageResponse.arrayBuffer();
126
+ const filePath = input.output
127
+ ? resolve(input.output)
128
+ : join(process.cwd(), "generated_images", `${Date.now()}_${sanitizePrompt(input.prompt)}.png`);
129
+ await mkdir(dirname(filePath), { recursive: true });
130
+ await writeFile(filePath, Buffer.from(imageBuffer));
131
+ const returnedSize = typeof result.data?.[0]?.size === "string"
132
+ ? result.data[0].size
133
+ : "unknown";
134
+ return {
135
+ filePath,
136
+ model: typeof result.model === "string" ? result.model : modelUsed,
137
+ requestedSize: normalizedSize,
138
+ appliedSize: sizeUsed,
139
+ returnedSize,
140
+ };
141
+ }
142
+ // ---------------------------------------------------------------------------
143
+ // Main
144
+ // ---------------------------------------------------------------------------
145
+ const HELP = `Usage: doubao "<prompt>" [options]
146
+
147
+ Generate an image via the Doubao (Volces Ark) API and save it to a file.
148
+
149
+ Options:
150
+ "<prompt>" (required) Text description of the image to generate
151
+ --size <s> "2K" (default), "3K", or "<width>x<height>" (e.g. 3072x2048)
152
+ --output <path> Output file path (default: ./generated_images/<ts>_<prompt>.png)
153
+ --help Show this help
154
+
155
+ Exit codes: 0 success | 1 API error / missing DOUBAO_API_KEY | 2 bad args
156
+ On success the last stdout line is "Saved to: <absolute path>".`;
157
+ async function main() {
158
+ const parsed = parseCliArgs(process.argv.slice(2));
159
+ if ("error" in parsed) {
160
+ process.stderr.write(`doubao: ${parsed.error}\n`);
161
+ process.exit(2);
162
+ }
163
+ if (parsed.help) {
164
+ process.stdout.write(`${HELP}\n`);
165
+ process.exit(0);
166
+ }
167
+ if (!parsed.prompt) {
168
+ process.stderr.write("doubao: prompt is required (pass it as the first argument)\n");
169
+ process.exit(2);
170
+ }
171
+ const apiKey = process.env.DOUBAO_API_KEY;
172
+ if (!apiKey) {
173
+ process.stderr.write("doubao: DOUBAO_API_KEY environment variable is not set\n");
174
+ process.exit(1);
175
+ }
176
+ try {
177
+ const r = await generateImage({
178
+ prompt: parsed.prompt,
179
+ size: parsed.size,
180
+ output: parsed.output,
181
+ apiKey,
182
+ });
183
+ process.stdout.write([
184
+ "Image generated successfully!",
185
+ `Model: ${r.model}`,
186
+ `Requested size: ${r.requestedSize}`,
187
+ `Applied size: ${r.appliedSize}`,
188
+ `Returned size: ${r.returnedSize}`,
189
+ `Saved to: ${r.filePath}`,
190
+ ].join("\n") + "\n");
191
+ process.exit(0);
192
+ }
193
+ catch (err) {
194
+ const msg = err instanceof Error ? err.message : String(err);
195
+ process.stderr.write(`doubao: Error generating image: ${msg}\n`);
196
+ process.exit(1);
197
+ }
198
+ }
199
+ const invokedDirectly = process.argv[1] !== undefined &&
200
+ realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
201
+ if (invokedDirectly) {
202
+ main().catch((err) => {
203
+ process.stderr.write(`doubao: fatal ${err instanceof Error ? err.message : String(err)}\n`);
204
+ process.exit(1);
205
+ });
206
+ }
@@ -0,0 +1,465 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * gcli — thin wrapper around the `agy` and `claude` CLI backends.
4
+ *
5
+ * Default backend is claude: bare `gcli ...` is exactly `gcli claude ...`.
6
+ * `gcli agy ...` routes to the agy CLI (explicit subcommand required); the
7
+ * claude backend can switch cc-switch providers inline via
8
+ * `claude -p ... --settings`. Adds value over calling the backends directly:
9
+ * prompt via argv or stdin (`-p -`), 50k-char output truncation, a hard
10
+ * timeout (spawn SIGTERM), explicit exit codes, and empty-output detection.
11
+ *
12
+ * The claude backend resolves `--provider <name>` from the cc-switch SQLite
13
+ * DB (read-only) and never rewrites ~/.claude/settings.json — the provider
14
+ * switch happens entirely through claude's own `--settings` merge. In a TTY
15
+ * without --provider it offers an interactive arrow-key picker over the
16
+ * cc-switch provider list (↑↓/j/k move · Enter confirm · Esc keep default);
17
+ * the last confirmed provider is remembered in ~/.config/gcli/last-provider
18
+ * and reused silently in print mode. Without a TTY the picker never triggers
19
+ * (zero prompts, zero DB reads, zero memory-file IO) so skills/CI never hang.
20
+ *
21
+ * Note: on the default (claude) path --yolo/--sandbox are rejected (exit 2);
22
+ * agy users must opt in via the explicit `gcli agy` subcommand.
23
+ *
24
+ * Timeout is enforced by spawn SIGTERM (deterministic), not via either
25
+ * backend's own timeout flag.
26
+ */
27
+ export declare const DEFAULT_TIMEOUT_MS = 300000;
28
+ export declare const CHARACTER_LIMIT = 50000;
29
+ export declare const API_DEFAULT_MAX_TOKENS = 80000;
30
+ /**
31
+ * Path to the cc-switch SQLite database. cc-switch ships provider configs
32
+ * (including ANTHROPIC_* env) here; gcli reads it read-only to resolve
33
+ * `--provider <name>` for the claude backend.
34
+ */
35
+ export declare const CC_SWITCH_DB_PATH: string;
36
+ /**
37
+ * Memory file for the last picker-confirmed provider (D2). Content is a
38
+ * single UTF-8 line with the provider name. Read on the TTY claude path with
39
+ * no --provider (exact-name match against the current list; mismatch/missing/
40
+ * unreadable → silently ignored); written best-effort after a picker confirm.
41
+ * Never read or written on a non-TTY path (zero side effects for skills/CI).
42
+ */
43
+ export declare const LAST_PROVIDER_PATH: string;
44
+ /**
45
+ * Quota subtitle cache (revise-3, C-Q5): `{[name]: {ts, ok, text?}}`.
46
+ * TTL ok 60s / fail 15s (statusline-sage semantics); reads/writes are
47
+ * best-effort — the quota subtitle is an optimization, never an error.
48
+ */
49
+ export declare const QUOTA_CACHE_PATH: string;
50
+ export type Subcommand = "agy" | "claude" | "api";
51
+ export type SubcommandResult = {
52
+ subcommand: Subcommand | undefined;
53
+ rest: string[];
54
+ } | {
55
+ error: string;
56
+ };
57
+ /** Three-layer provider-name match outcome (C4). */
58
+ export type MatchOutcome = {
59
+ matched: string;
60
+ } | {
61
+ none: true;
62
+ } | {
63
+ ambiguous: string[];
64
+ };
65
+ export type ProviderEnvResult = {
66
+ env: Record<string, string>;
67
+ } | {
68
+ error: string;
69
+ };
70
+ /** One picker keypress → state transition (C-P2). */
71
+ export type PickerKeyAction = {
72
+ type: "move";
73
+ index: number;
74
+ } | {
75
+ type: "confirm";
76
+ } | {
77
+ type: "skip";
78
+ } | {
79
+ type: "noop";
80
+ };
81
+ /** One selectable row of the arrow-key picker: a cc-switch provider. */
82
+ export type PickerEntry = {
83
+ name: string;
84
+ quota?: string;
85
+ };
86
+ /** Result of the arrow-key picker (C-P1): a picked provider, or a skip. */
87
+ export type PickerOutcome = {
88
+ kind: "select";
89
+ entry: PickerEntry;
90
+ } | {
91
+ kind: "skip";
92
+ };
93
+ /** A cc-switch provider row as exposed to the claude backend. */
94
+ export type RawProvider = {
95
+ name: string;
96
+ settingsConfig: unknown;
97
+ };
98
+ export type ProviderLookup = {
99
+ ok: true;
100
+ providers: RawProvider[];
101
+ } | {
102
+ ok: false;
103
+ kind: "db-missing" | "sqlite-missing" | "parse";
104
+ message: string;
105
+ };
106
+ /** Normalized spawn result shared by both backends (print mode). */
107
+ export type SpawnResult = {
108
+ stdout: string;
109
+ stderr: string;
110
+ exitCode: number | null;
111
+ signal?: string | null;
112
+ timedOut?: boolean;
113
+ };
114
+ /** Spawn result for interactive mode: stdio inherited, nothing captured. */
115
+ export type InteractiveSpawnResult = {
116
+ exitCode: number;
117
+ signal?: string | null;
118
+ spawnError?: string;
119
+ };
120
+ /** Injectable dependencies for `run()` — tests pass fakes (C1/C2 routing). */
121
+ export type RunDeps = {
122
+ readCcSwitchProvider: () => Promise<ProviderLookup>;
123
+ runClaude: (args: string[], timeoutMs?: number, cwd?: string) => Promise<SpawnResult>;
124
+ runAgy: (args: string[], timeoutMs?: number, cwd?: string) => Promise<SpawnResult>;
125
+ /**
126
+ * api backend: POST to an anthropic-compatible /v1/messages endpoint.
127
+ * Implementations handle fetch + SSE parsing + idle/absolute timeout and
128
+ * return a normalized RunOutcome (0 success / 1 error·timeout·empty).
129
+ * Injected so acceptance tests never hit a real API.
130
+ */
131
+ runApi: (req: ApiRequest) => Promise<RunOutcome>;
132
+ readStdin: () => Promise<string>;
133
+ runClaudeInteractive: (args: string[], cwd?: string) => Promise<InteractiveSpawnResult>;
134
+ runAgyInteractive: (args: string[], cwd?: string) => Promise<InteractiveSpawnResult>;
135
+ isInteractive: () => boolean;
136
+ /**
137
+ * TTY-only arrow-key provider picker (C-P1/C-P3): present the cc-switch
138
+ * provider entries (the production impl appends its own trailing 不切换
139
+ * row — `entries` holds providers only) and resolve the confirmed entry,
140
+ * or {kind:"skip"} on Esc / the 不切换 row. `initialIndex` is the caller-
141
+ * computed row to highlight (memory hit or 0); implementations clamp it.
142
+ * Only ever invoked on the claude path in a TTY with no --provider —
143
+ * non-interactive callers (skills/CI/pipes) must see zero prompts and
144
+ * zero extra cc-switch DB reads.
145
+ */
146
+ pickProvider: (entries: PickerEntry[], initialIndex: number) => Promise<PickerOutcome>;
147
+ /**
148
+ * Quota subtitles (revise-3, C-Q4): given every menu provider's {name, env},
149
+ * resolve name → formatted quota text (only providers with usable data are
150
+ * in the Map). Only invoked on the TTY menu path right before pickProvider —
151
+ * silent reuse / explicit --provider / non-TTY / degraded paths never call.
152
+ */
153
+ fetchProviderQuotas: (items: {
154
+ name: string;
155
+ env: Record<string, string>;
156
+ }[]) => Promise<Map<string, string>>;
157
+ /**
158
+ * Read the remembered provider name (D2): trimmed first line of
159
+ * LAST_PROVIDER_PATH, or undefined when missing/empty/unreadable. Only
160
+ * invoked on the TTY claude path with no --provider.
161
+ */
162
+ readLastProvider: () => Promise<string | undefined>;
163
+ /**
164
+ * Persist the picker-confirmed provider name (D2). Best-effort: failures
165
+ * are swallowed (memory is an optimization, never an error). Only invoked
166
+ * after an arrow-key picker confirm, before any backend spawn.
167
+ */
168
+ writeLastProvider: (name: string) => Promise<void>;
169
+ };
170
+ export type RunOutcome = {
171
+ exitCode: number;
172
+ stdout: string;
173
+ stderr: string;
174
+ };
175
+ /**
176
+ * Detect a leading `agy`/`claude`/`api` subcommand and strip it. Strict (C1):
177
+ * - argv[0] === "agy" → subcommand "agy", rest = argv.slice(1)
178
+ * - argv[0] === "claude" → subcommand "claude", rest = argv.slice(1)
179
+ * - argv[0] === "api" → subcommand "api", rest = argv.slice(1)
180
+ * - argv empty OR argv[0] starts with "-" → subcommand undefined (default
181
+ * backend: claude)
182
+ * - argv[0] any other non-empty token → error "unknown subcommand: <x>"
183
+ *
184
+ * This keeps `gcli -p claude` (argv[0]="-p") on the default claude path with
185
+ * prompt="claude" — the subcommand must literally lead.
186
+ */
187
+ export declare function parseSubcommand(argv: string[]): SubcommandResult;
188
+ export declare function truncate(text: string): string;
189
+ export interface GcliOptions {
190
+ /** Omit for interactive mode (no -p emitted). */
191
+ prompt?: string;
192
+ model?: string;
193
+ yolo: boolean;
194
+ sandbox: boolean;
195
+ cwd?: string;
196
+ timeoutMs: number;
197
+ /** Args forwarded to the backend verbatim (from `--`). */
198
+ passthrough?: string[];
199
+ }
200
+ /** Translate gcli options into agy argv. Timeout is enforced by spawn kill.
201
+ * prompt is optional — when undefined, no -p is emitted (interactive mode). */
202
+ export declare function buildAgyArgs(opts: GcliOptions): string[];
203
+ /**
204
+ * Three-layer provider name matching (C4a):
205
+ * 1. exact equality
206
+ * 2. case-insensitive equality
207
+ * 3. substring (query within name, case-insensitive)
208
+ * Within the first layer that produces any hit: 1 → {matched}, >1 →
209
+ * {ambiguous}, 0 → fall through. No layer hits → {none}.
210
+ */
211
+ export declare function matchProviderName(query: string, names: string[]): MatchOutcome;
212
+ /**
213
+ * Parse a `settings_config` JSON string and return its `env` block (C4c).
214
+ * Errors (with diagnostics) on malformed JSON, non-object root, or a
215
+ * missing/non-object env.
216
+ */
217
+ export declare function extractProviderEnv(settingsConfigJson: string): ProviderEnvResult;
218
+ /**
219
+ * Copy provider env and explicitly pin `ANTHROPIC_MODEL` (C5).
220
+ *
221
+ * Why: `claude --settings` is a *merge*, not replace — a stale
222
+ * ANTHROPIC_MODEL in the global settings.json would leak through. We always
223
+ * set the key (empty string when nothing is derivable — JSON.stringify omits
224
+ * undefined, which would let a stale global value leak back in).
225
+ *
226
+ * Priority: model > provider ANTHROPIC_MODEL > DEFAULT_SONNET_MODEL
227
+ * > DEFAULT_OPUS_MODEL > first sorted DEFAULT_*_MODEL.
228
+ */
229
+ export declare function buildSettingsEnv(providerEnv: Record<string, string>, model?: string): Record<string, string>;
230
+ /**
231
+ * Shape of a readline keypress event's `key` argument (C-P2, revise-2).
232
+ */
233
+ export type PickerKeyInput = {
234
+ name?: string;
235
+ ctrl?: boolean;
236
+ meta?: boolean;
237
+ };
238
+ /**
239
+ * Map one arrow-key picker keypress to its state transition (C-P2, revise-2).
240
+ *
241
+ * `k` is the keypress event's `key` object:
242
+ * - "up" / "k" → move up one row, wrapping past the top (环形)
243
+ * - "down" / "j" → move down one row, wrapping past the bottom (环形)
244
+ * - "return" / "enter" → confirm the current row (the physical Enter key
245
+ * emits "return" in raw mode; "enter" is the LF byte, which a tty may
246
+ * substitute for CR in input buffered before raw mode was enabled)
247
+ * - "escape" → skip (不切换)
248
+ * - Emacs (revise-2): ctrl+"n" ≡ down, ctrl+"p" ≡ up (same wrap); ctrl+"g"
249
+ * ≡ escape → skip; meta+"<" → first row (absolute), meta+">" → last row
250
+ * (absolute). Horizontal Emacs keys (C-f/C-b/C-a/C-e) and paging (C-v/M-v)
251
+ * are deliberately NOT mapped — meaningless in a vertical menu.
252
+ * - anything else → noop (ctrl-c is handled by the caller: restore raw-mode,
253
+ * exit 130)
254
+ *
255
+ * `index` is the highlighted row, `count` the total rendered rows INCLUDING
256
+ * the trailing 不切换 row. Movement wraps with `(index±1+count)%count`; with
257
+ * count <= 0 moves are a noop (nothing is rendered).
258
+ */
259
+ export declare function applyPickerKey(k: PickerKeyInput, index: number, count: number): PickerKeyAction;
260
+ /** One rate-limit window: usage percentage + ISO8601 reset timestamp. */
261
+ export type QuotaWindow = {
262
+ pct: number;
263
+ resetIso: string;
264
+ };
265
+ /** Parsed quota windows: short = 5h rolling, weekly = long window. */
266
+ export type QuotaWindows = {
267
+ short?: QuotaWindow;
268
+ weekly?: QuotaWindow;
269
+ };
270
+ /**
271
+ * Map a provider env to its quota API request (C-Q1):
272
+ * - base contains kimi.com / moonshot → kimi: `{domain}/coding/v1/usages`,
273
+ * `Authorization: Bearer <token>` (bare token gets 401)
274
+ * - base contains bigmodel / z.ai → glm: `{domain}/api/monitor/usage/quota/limit`,
275
+ * `Authorization: <token>` (NO Bearer prefix)
276
+ * - anything else (deepseek / packy / anthropic official / …) or missing
277
+ * base/token/scheme → null: no quota API, zero requests, no subtitle.
278
+ */
279
+ export declare function buildQuotaRequest(env: {
280
+ ANTHROPIC_BASE_URL?: string;
281
+ ANTHROPIC_AUTH_TOKEN?: string;
282
+ }): {
283
+ kind: "kimi" | "glm";
284
+ url: string;
285
+ authHeader: string;
286
+ } | null;
287
+ /**
288
+ * Parse kimi `/coding/v1/usages` (C-Q2): `limits[]` entries with
289
+ * window.duration == 300 + MINUTE → short (5h) window; top-level `usage` →
290
+ * weekly. Malformed shapes yield missing windows, never throw.
291
+ */
292
+ export declare function parseKimiUsages(body: unknown): QuotaWindows;
293
+ /**
294
+ * Parse GLM `/api/monitor/usage/quota/limit` (C-Q2): data.limits[] entries
295
+ * with type == "TOKENS_LIMIT"; sorted by nextResetTime ascending — first is
296
+ * the short (5h) window, last the weekly one.
297
+ */
298
+ export declare function parseGlmQuota(body: unknown): QuotaWindows;
299
+ /**
300
+ * Format quota windows as the menu subtitle (C-Q3): `5h:P% wk:P% ↻<rel>`
301
+ * (short reset preferred for the ↻ segment); missing windows degrade; a
302
+ * missing/expired reset omits the ↻ segment entirely; no windows → "".
303
+ */
304
+ export declare function formatQuota(q: QuotaWindows, nowMs: number): string;
305
+ export interface ClaudeOptions {
306
+ /** Omit for interactive mode (no -p emitted). */
307
+ prompt?: string;
308
+ /** If provided, the env is wrapped as `--settings '{"env":{...}}'`. */
309
+ settingsEnv?: Record<string, string>;
310
+ cwd?: string;
311
+ /** Args forwarded to the backend verbatim (from `--`). */
312
+ passthrough?: string[];
313
+ }
314
+ /**
315
+ * Build claude argv (C8). `--model` is NOT passed here — for the claude
316
+ * backend it goes into the settings env's ANTHROPIC_MODEL via buildSettingsEnv.
317
+ * `--cwd` becomes `claude --add-dir` to match the agy convention.
318
+ * prompt is optional — when undefined, no -p is emitted (interactive mode).
319
+ */
320
+ export declare function buildClaudeArgs(opts: ClaudeOptions): string[];
321
+ /**
322
+ * Injectable api request (the shape deps.runApi consumes). All fields the
323
+ * production fetch impl needs, with nothing backend-specific leaking into the
324
+ * pure layer.
325
+ */
326
+ export interface ApiRequest {
327
+ url: string;
328
+ token: string;
329
+ model: string;
330
+ maxTokens: number;
331
+ prompt: string;
332
+ stream: boolean;
333
+ timeoutMs: number;
334
+ }
335
+ /**
336
+ * Build the HTTP body for an anthropic-compatible /v1/messages request.
337
+ *
338
+ * thinking is intentionally NOT disabled: k3's extended thinking is the quality
339
+ * source for creative+SVG tasks (dry-run: single-block ~768-char thinking yields
340
+ * high-quality output in ~45s vs claude-agent's 53min). We rely on a sufficient
341
+ * --max-tokens budget (default 80000) to cover both thinking and text, not on
342
+ * disabling thinking. Disabling it would discard the very capability we chose
343
+ * k3 for.
344
+ */
345
+ export declare function buildApiBody(req: Pick<ApiRequest, "model" | "maxTokens" | "prompt" | "stream">): Record<string, unknown>;
346
+ /**
347
+ * Build the full URL for the messages endpoint. The cc-switch base_url is
348
+ * stored with a trailing slash (e.g. `https://api.kimi.com/coding/`); we
349
+ * append `v1/messages` without doubling the slash.
350
+ */
351
+ export declare function buildApiEndpoint(baseUrl: string): string;
352
+ /**
353
+ * SSE line → extracted text delta, or null if the line carries no text payload.
354
+ *
355
+ * Handles anthropic-compatible streaming events:
356
+ * - `data:{...}` JSON with `delta.type === "text_delta"` → the text fragment
357
+ * - `thinking_delta` / `signature_delta` / control events → null (ignored)
358
+ * - non-`data:` lines / malformed JSON → null
359
+ *
360
+ * The `data:` prefix is matched greedily and the remainder is trimmed, so both
361
+ * `data:{...}` (kimi, no space) and `data: {...}` (with space) parse the same.
362
+ */
363
+ export declare function extractTextDelta(line: string): string | null;
364
+ /**
365
+ * Aggregate text from a non-streaming messages response body.
366
+ *
367
+ * Non-stream responses look like `{ content: [{ type: "text", text: "..." }] }`;
368
+ * we concatenate every text block in order.
369
+ */
370
+ export declare function extractNonStreamText(body: unknown): string;
371
+ export interface ParsedArgs {
372
+ prompt?: string;
373
+ model?: string;
374
+ yolo: boolean;
375
+ sandbox: boolean;
376
+ cwd?: string;
377
+ timeoutMs: number;
378
+ version: boolean;
379
+ help: boolean;
380
+ provider?: string;
381
+ /** Force the provider picker menu, even in print mode (claude path only). */
382
+ pick: boolean;
383
+ /** Args after `--`, passed through to the backend verbatim. */
384
+ passthrough: string[];
385
+ }
386
+ export type ParseResult = ParsedArgs | {
387
+ error: string;
388
+ };
389
+ export declare function parseCliArgs(argv: string[]): ParseResult;
390
+ /**
391
+ * Parsed options for the api backend. Unlike agy/claude there is no
392
+ * `passthrough`: the api backend builds an HTTP body directly, so unknown
393
+ * flags have no target to forward to and are rejected (exit 2).
394
+ */
395
+ export interface ParsedApiArgs {
396
+ prompt?: string;
397
+ model?: string;
398
+ provider?: string;
399
+ maxTokens: number;
400
+ timeoutMs: number;
401
+ stream: boolean;
402
+ version: boolean;
403
+ help: boolean;
404
+ /** --cwd was supplied (warned + ignored by the api backend). */
405
+ cwd?: string;
406
+ }
407
+ export type ParseApiResult = ParsedApiArgs | {
408
+ error: string;
409
+ };
410
+ /**
411
+ * Parse argv for the api backend. Strict (contract: api does NOT passthrough):
412
+ * unknown flags and bare positionals are errors (`unknown option/positional`),
413
+ * mapped to exit 2 by the caller. `--no-stream` is accepted via
414
+ * `allowNegative` and flips `stream` to false.
415
+ */
416
+ export declare function parseApiArgs(argv: string[]): ParseApiResult;
417
+ export declare function runAgy(args: string[], timeoutMs: number, cwd?: string): Promise<SpawnResult>;
418
+ /**
419
+ * Spawn the claude CLI with the same IO/timeout contract as `runAgy` (C6/C9).
420
+ * stdin is inherited so a piped prompt reaches claude when gcli passes `-p -`
421
+ * through. SIGTERM enforces the timeout deterministically.
422
+ */
423
+ export declare function runClaude(args: string[], timeoutMs: number, cwd?: string): Promise<SpawnResult>;
424
+ /** Spawn agy in interactive TUI mode (no -p, inherited stdio). */
425
+ export declare function runAgyInteractive(args: string[], cwd?: string): Promise<InteractiveSpawnResult>;
426
+ /** Spawn claude in interactive TUI mode (no -p, inherited stdio). */
427
+ export declare function runClaudeInteractive(args: string[], cwd?: string): Promise<InteractiveSpawnResult>;
428
+ /**
429
+ * Production deps.runApi: POST an anthropic-compatible /v1/messages request
430
+ * and return a normalized RunOutcome.
431
+ *
432
+ * - stream=true: reads the SSE body chunk-by-chunk, decodes UTF-8, splits on
433
+ * newlines, and aggregates `text_delta` payloads into stdout. Two clocks
434
+ * guard against hangs: an idle timer (reset on every text-bearing chunk)
435
+ * and an absolute timer (timeoutMs). Either firing aborts the fetch via
436
+ * AbortController → exit 1 with a timeout message.
437
+ * - stream=false: awaits the full JSON body and extracts `content[].text`,
438
+ * then applies the 50k-char truncation.
439
+ *
440
+ * HTTP errors (non-2xx, network failure, abort) → exit 1 with diagnostics on
441
+ * stderr; stdout stays empty so the caller's empty-output guard still works.
442
+ */
443
+ export declare function runApi(req: ApiRequest): Promise<RunOutcome>;
444
+ /**
445
+ * Read all `app_type='claude'` providers from cc-switch.db (C4b/C4c).
446
+ *
447
+ * Uses `sqlite3 -readonly -json` (never opens the DB for write). Matching
448
+ * stays in the pure `matchProviderName` so it is unit-testable without a DB.
449
+ *
450
+ * Errors are classified for the caller: sqlite-missing (binary not on PATH),
451
+ * db-missing (sqlite3 exited non-zero, e.g. file absent), parse (bad JSON).
452
+ */
453
+ export declare function readCcSwitchProvider(dbPath: string): Promise<ProviderLookup>;
454
+ /**
455
+ * Validate a provider name against the allowlist (C4b). Returns the name, or
456
+ * an error object suitable for an exit-2 stderr line.
457
+ */
458
+ export declare function validateProviderName(name: string): string | {
459
+ error: string;
460
+ };
461
+ /**
462
+ * Route argv to the agy / claude / api backend via injectable deps (C1/C2).
463
+ * Returns a {exitCode, stdout, stderr} outcome; main() owns process.exit.
464
+ */
465
+ export declare function run(argv: string[], deps: RunDeps): Promise<RunOutcome>;