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.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/README.zh-CN.md +88 -0
- package/package.json +54 -0
- package/packages/cli/dist/index.d.ts +2 -0
- package/packages/cli/dist/index.js +171 -0
- package/packages/cli/dist/index.js.map +1 -0
- package/packages/doubao/dist/cli.d.ts +38 -0
- package/packages/doubao/dist/cli.d.ts.map +1 -0
- package/packages/doubao/dist/cli.js +206 -0
- package/packages/gcli/dist/cli.d.ts +465 -0
- package/packages/gcli/dist/cli.js +2017 -0
- package/packages/gcli/dist/cli.js.map +1 -0
- package/packages/lmedia/dist/index.d.ts +1 -0
- package/packages/lmedia/dist/index.js +1594 -0
- package/packages/lmedia/python/edit.py +107 -0
- package/packages/lmedia/python/esrgan_path.py +16 -0
- package/packages/lmedia/python/gen.py +131 -0
- package/packages/lmedia/python/serve.py +352 -0
- package/packages/lmedia/python/sfx.py +527 -0
- package/packages/lmedia/python/teacache.py +255 -0
- package/packages/lmedia/python/upscale.py +41 -0
- package/packages/minimax/dist/cli.d.ts +51 -0
- package/packages/minimax/dist/cli.js +307 -0
- package/packages/minimax/dist/cli.js.map +1 -0
- package/packages/minimax/dist/client.d.ts +20 -0
- package/packages/minimax/dist/client.js +55 -0
- package/packages/minimax/dist/client.js.map +1 -0
- package/packages/minimax/dist/tts.d.ts +33 -0
- package/packages/minimax/dist/tts.js +64 -0
- package/packages/minimax/dist/tts.js.map +1 -0
- package/packages/minimax/dist/validate.d.ts +29 -0
- package/packages/minimax/dist/validate.js +122 -0
- package/packages/minimax/dist/validate.js.map +1 -0
- package/packages/minimax/dist/voice-clone.d.ts +17 -0
- package/packages/minimax/dist/voice-clone.js +47 -0
- package/packages/minimax/dist/voice-clone.js.map +1 -0
- package/packages/minimax/dist/voices.d.ts +17 -0
- package/packages/minimax/dist/voices.js +20 -0
- package/packages/minimax/dist/voices.js.map +1 -0
- package/packages/qwen/dist/index.d.ts +1 -0
- 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>;
|