@hizliemre/horse-code 0.2.0 → 0.3.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/dist/{app-2GPGCDX6.js → app-5FXHE7GX.js} +223 -26
- package/dist/{chunk-KKWZBZYK.js → chunk-EAF22QIG.js} +26 -4
- package/dist/chunk-G45RWL7S.js +289 -0
- package/dist/{chunk-7JMWPTJ5.js → chunk-UEWVVN5L.js} +53 -265
- package/dist/{chunk-23CLQ2KO.js → chunk-XYZVZPAY.js} +4 -4
- package/dist/{chunk-5ZV42XGJ.js → chunk-YPZP7LYL.js} +1 -1
- package/dist/cli.js +360 -66
- package/dist/{fix-JOIXQFVP.js → fix-ONLA45HD.js} +3 -2
- package/dist/{save-skills-X7U3KCPU.js → save-skills-ZW5GY6KV.js} +2 -1
- package/dist/{verify-6SC4I77M.js → verify-LC57A6H2.js} +3 -2
- package/package.json +1 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// src/agents/cli-auth.ts
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
3
|
+
|
|
4
|
+
// src/agents/cli-agent.ts
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
var CLI_KINDS = ["claude", "codex", "grok", "zai"];
|
|
7
|
+
function cliBinary(kind) {
|
|
8
|
+
return kind === "zai" ? "claude" : kind;
|
|
9
|
+
}
|
|
10
|
+
var CLI_ERROR_UNSPOKEN = "the CLI reported an error";
|
|
11
|
+
function cliArgs(kind, prompt, extra = []) {
|
|
12
|
+
if (kind === "claude" || kind === "zai") {
|
|
13
|
+
return ["--output-format", "stream-json", "--verbose", ...extra, "-p", "--", prompt];
|
|
14
|
+
}
|
|
15
|
+
if (kind === "codex") {
|
|
16
|
+
return ["exec", "--json", "--skip-git-repo-check", ...extra, "--", prompt];
|
|
17
|
+
}
|
|
18
|
+
return ["--output-format", "streaming-messages-json", ...extra, `--single=${prompt}`];
|
|
19
|
+
}
|
|
20
|
+
function decodeClaudeEvent(line) {
|
|
21
|
+
let e;
|
|
22
|
+
try {
|
|
23
|
+
e = JSON.parse(line);
|
|
24
|
+
} catch {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
const type = e.type;
|
|
28
|
+
if (type === "rate_limit_event") {
|
|
29
|
+
const info = e.rate_limit_info ?? {};
|
|
30
|
+
const status = String(info.status ?? "unknown");
|
|
31
|
+
const raw = info.unifiedWindows ?? {};
|
|
32
|
+
const windows = {};
|
|
33
|
+
for (const [name, w] of Object.entries(raw)) windows[name] = w?.utilization ?? 0;
|
|
34
|
+
const quota = {
|
|
35
|
+
status,
|
|
36
|
+
windows,
|
|
37
|
+
...typeof info.resetsAt === "number" ? { resetsAt: info.resetsAt } : {}
|
|
38
|
+
};
|
|
39
|
+
return status.startsWith("allowed") ? { quota } : {
|
|
40
|
+
quota,
|
|
41
|
+
/**
|
|
42
|
+
* The reset time rides along, as an ISO instant rather than prose.
|
|
43
|
+
*
|
|
44
|
+
* A spent five-hour window reopens; without saying when, the only safe bench is "the rest of the
|
|
45
|
+
* run", which on a ten-hour board writes off a subscription for hours after it recovered. The
|
|
46
|
+
* gateway's wordings said "reset after 4h" and nothing ever parsed them — see `quotaResetAt`.
|
|
47
|
+
*/
|
|
48
|
+
rateLimited: `${status} \u2014 ${describeWindows(windows)}` + (quota.resetsAt ? ` (resets ${new Date(quota.resetsAt * 1e3).toISOString()})` : "")
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (type === "assistant") {
|
|
52
|
+
const msg = e.message;
|
|
53
|
+
const parts = Array.isArray(msg?.content) ? msg.content : [];
|
|
54
|
+
const text = parts.filter((b) => typeof b === "object" && b !== null && b.type === "text").map((b) => b.text).join("");
|
|
55
|
+
const tool = parts.find((b) => typeof b === "object" && b !== null && b.type === "tool_use");
|
|
56
|
+
return {
|
|
57
|
+
...text ? { text } : {},
|
|
58
|
+
...msg?.model ? { served: msg.model } : {},
|
|
59
|
+
...tool ? { tool: { name: tool.name, ...targetOf(tool.input) ? { target: targetOf(tool.input) } : {} } } : {}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (type === "user") {
|
|
63
|
+
const parts = e.message?.content;
|
|
64
|
+
const failed = (Array.isArray(parts) ? parts : []).find(
|
|
65
|
+
(b) => typeof b === "object" && b !== null && b.type === "tool_result" && b.is_error === true
|
|
66
|
+
);
|
|
67
|
+
return failed ? { tool: { name: "tool", ok: false } } : void 0;
|
|
68
|
+
}
|
|
69
|
+
if (type === "result") {
|
|
70
|
+
const u = e.usage ?? {};
|
|
71
|
+
const cost = e.total_cost_usd;
|
|
72
|
+
return {
|
|
73
|
+
usage: {
|
|
74
|
+
freshTokens: u.input_tokens ?? 0,
|
|
75
|
+
cachedTokens: u.cache_read_input_tokens ?? 0,
|
|
76
|
+
cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
|
|
77
|
+
outputTokens: u.output_tokens ?? 0,
|
|
78
|
+
...cost !== void 0 ? { costUsd: cost } : {}
|
|
79
|
+
},
|
|
80
|
+
...e.subtype === "error_during_execution" ? { error: String(e.result ?? CLI_ERROR_UNSPOKEN) } : {}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
function decodeCodexEvent(line) {
|
|
86
|
+
let e;
|
|
87
|
+
try {
|
|
88
|
+
e = JSON.parse(line);
|
|
89
|
+
} catch {
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
const type = String(e.type ?? "");
|
|
93
|
+
if (/rate.?limit/i.test(type)) return { rateLimited: String(e.message ?? "rate limited by the CLI") };
|
|
94
|
+
if (type === "item.completed") {
|
|
95
|
+
const item = e.item;
|
|
96
|
+
if (item?.type === "agent_message" && item.text) return { text: item.text };
|
|
97
|
+
if (item?.type && item.type !== "agent_message") {
|
|
98
|
+
const changes = item.changes;
|
|
99
|
+
const first = Array.isArray(changes) ? changes.find((c) => typeof c?.path === "string")?.path : void 0;
|
|
100
|
+
const more = Array.isArray(changes) && changes.length > 1 ? ` +${changes.length - 1}` : "";
|
|
101
|
+
return {
|
|
102
|
+
tool: {
|
|
103
|
+
name: item.name ?? item.type,
|
|
104
|
+
...first ? { target: `${first}${more}` } : {}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return void 0;
|
|
109
|
+
}
|
|
110
|
+
if (type === "turn.completed") {
|
|
111
|
+
const u = e.usage ?? {};
|
|
112
|
+
return {
|
|
113
|
+
usage: {
|
|
114
|
+
freshTokens: u.input_tokens ?? 0,
|
|
115
|
+
cachedTokens: u.cached_input_tokens ?? 0,
|
|
116
|
+
cacheWriteTokens: u.cache_write_input_tokens ?? 0,
|
|
117
|
+
outputTokens: u.output_tokens ?? 0
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (type === "turn.failed" || type === "error") {
|
|
122
|
+
return { error: String(e.message ?? "codex reported an error") };
|
|
123
|
+
}
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
function describeWindows(windows) {
|
|
127
|
+
const parts = Object.entries(windows).map(([k, v]) => `${k} ${Math.round(v * 100)}%`);
|
|
128
|
+
return parts.length ? parts.join(", ") : "no window reported";
|
|
129
|
+
}
|
|
130
|
+
var SYNTHETIC = "<synthetic>";
|
|
131
|
+
function targetOf(input) {
|
|
132
|
+
for (const k of ["file_path", "path", "filePath", "notebook_path"]) {
|
|
133
|
+
const v = input?.[k];
|
|
134
|
+
if (typeof v === "string" && v) return v;
|
|
135
|
+
}
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
function makeStreamReader(decode, onEvent) {
|
|
139
|
+
let pending = "";
|
|
140
|
+
const drain = (upToNewline) => {
|
|
141
|
+
const lines = pending.split("\n");
|
|
142
|
+
pending = upToNewline ? lines.pop() ?? "" : "";
|
|
143
|
+
for (const line of lines) {
|
|
144
|
+
if (!line.trim()) continue;
|
|
145
|
+
const ev = decode(line);
|
|
146
|
+
if (ev) onEvent(ev);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
push(chunk) {
|
|
151
|
+
pending += chunk;
|
|
152
|
+
drain(true);
|
|
153
|
+
},
|
|
154
|
+
end() {
|
|
155
|
+
if (pending.trim()) drain(false);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function reportedError(decoded, stderr, exitCode) {
|
|
160
|
+
if (decoded && decoded !== CLI_ERROR_UNSPOKEN) return decoded;
|
|
161
|
+
const spoken = exitCode !== 0 ? stderr.trim().slice(0, 500) : "";
|
|
162
|
+
return spoken || decoded || void 0;
|
|
163
|
+
}
|
|
164
|
+
async function runCliAgent(run) {
|
|
165
|
+
const decode = run.kind === "codex" ? decodeCodexEvent : decodeClaudeEvent;
|
|
166
|
+
const args = cliArgs(run.kind, run.prompt, run.args ?? []);
|
|
167
|
+
return new Promise((resolve) => {
|
|
168
|
+
let child;
|
|
169
|
+
try {
|
|
170
|
+
child = spawn(cliBinary(run.kind), args, {
|
|
171
|
+
cwd: run.cwd,
|
|
172
|
+
signal: run.signal,
|
|
173
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
174
|
+
...run.configDir ? { env: { ...process.env, ...profileEnv(run.kind, run.configDir) } } : {}
|
|
175
|
+
});
|
|
176
|
+
} catch (e) {
|
|
177
|
+
resolve({ text: "", error: e instanceof Error ? e.message : String(e), exitCode: -1 });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
let text = "";
|
|
181
|
+
let usage;
|
|
182
|
+
let rateLimited;
|
|
183
|
+
let served;
|
|
184
|
+
let quota;
|
|
185
|
+
let error;
|
|
186
|
+
let stderr = "";
|
|
187
|
+
const reader = makeStreamReader(decode, (ev) => {
|
|
188
|
+
if (ev.text) text += ev.text;
|
|
189
|
+
if (ev.usage) usage = ev.usage;
|
|
190
|
+
if (ev.rateLimited) rateLimited = ev.rateLimited;
|
|
191
|
+
if (ev.served) served = ev.served;
|
|
192
|
+
if (ev.quota) quota = ev.quota;
|
|
193
|
+
if (ev.error) error = ev.error;
|
|
194
|
+
run.onEvent?.(ev);
|
|
195
|
+
});
|
|
196
|
+
child.stdout?.on("data", (d) => reader.push(d.toString()));
|
|
197
|
+
child.stderr?.on("data", (d) => {
|
|
198
|
+
stderr += d.toString();
|
|
199
|
+
});
|
|
200
|
+
child.on("error", (e) => resolve({ text, ...usage ? { usage } : {}, error: e.message, exitCode: -1 }));
|
|
201
|
+
child.on("close", (code) => {
|
|
202
|
+
reader.end();
|
|
203
|
+
const reported = reportedError(error, stderr, code ?? -1);
|
|
204
|
+
resolve({
|
|
205
|
+
text,
|
|
206
|
+
...usage ? { usage } : {},
|
|
207
|
+
...rateLimited ? { rateLimited } : {},
|
|
208
|
+
...quota ? { quota } : {},
|
|
209
|
+
...served ? { served } : {},
|
|
210
|
+
...reported ? { error: reported } : {},
|
|
211
|
+
exitCode: code ?? -1
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/agents/cli-auth.ts
|
|
218
|
+
function profileEnv(kind, configDir) {
|
|
219
|
+
if (!configDir) return {};
|
|
220
|
+
if (kind === "claude" || kind === "zai") return { CLAUDE_CONFIG_DIR: configDir };
|
|
221
|
+
if (kind === "codex") return { CODEX_HOME: configDir };
|
|
222
|
+
return { GROK_HOME: configDir };
|
|
223
|
+
}
|
|
224
|
+
function readAuthStatus(kind, out) {
|
|
225
|
+
if (kind === "grok") {
|
|
226
|
+
const m = /you are logged in with\s+(.+)/i.exec(out);
|
|
227
|
+
if (!m) return { loggedIn: false };
|
|
228
|
+
const plan = m[1].trim().replace(/\.$/, "");
|
|
229
|
+
return { loggedIn: true, ...plan ? { plan } : {} };
|
|
230
|
+
}
|
|
231
|
+
if (kind === "codex") {
|
|
232
|
+
const m = /logged in(?: using (.+))?/i.exec(out);
|
|
233
|
+
if (!m || /not logged in/i.test(out)) return { loggedIn: false };
|
|
234
|
+
const plan = m[1]?.trim();
|
|
235
|
+
return { loggedIn: true, ...plan ? { plan } : {} };
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const j = JSON.parse(out);
|
|
239
|
+
if (!j.loggedIn) return { loggedIn: false };
|
|
240
|
+
return {
|
|
241
|
+
loggedIn: true,
|
|
242
|
+
...j.email ? { email: j.email } : {},
|
|
243
|
+
...j.subscriptionType ? { plan: j.subscriptionType } : {}
|
|
244
|
+
};
|
|
245
|
+
} catch {
|
|
246
|
+
return { loggedIn: false };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function statusArgs(kind) {
|
|
250
|
+
if (kind === "claude" || kind === "zai") return ["auth", "status"];
|
|
251
|
+
if (kind === "codex") return ["login", "status"];
|
|
252
|
+
return ["models"];
|
|
253
|
+
}
|
|
254
|
+
function loginArgs(kind) {
|
|
255
|
+
return kind === "claude" ? ["auth", "login"] : ["login"];
|
|
256
|
+
}
|
|
257
|
+
function checkProfile(kind, configDir) {
|
|
258
|
+
if (kind === "zai" && !configDir) return { loggedIn: false };
|
|
259
|
+
const r = spawnSync(cliBinary(kind), statusArgs(kind), {
|
|
260
|
+
env: { ...process.env, ...profileEnv(kind, configDir) },
|
|
261
|
+
encoding: "utf8",
|
|
262
|
+
// A status check that hangs must not hang the startup summary with it.
|
|
263
|
+
timeout: 2e4
|
|
264
|
+
});
|
|
265
|
+
if (r.error) return { loggedIn: false };
|
|
266
|
+
return readAuthStatus(kind, `${r.stdout ?? ""}${r.stderr ?? ""}`);
|
|
267
|
+
}
|
|
268
|
+
function runLogin(kind, configDir) {
|
|
269
|
+
if (kind === "zai") {
|
|
270
|
+
return { ok: false, error: "z.ai has no sign-in \u2014 a profile is connected by writing its settings file" };
|
|
271
|
+
}
|
|
272
|
+
const r = spawnSync(cliBinary(kind), loginArgs(kind), {
|
|
273
|
+
env: { ...process.env, ...profileEnv(kind, configDir) },
|
|
274
|
+
stdio: "inherit"
|
|
275
|
+
});
|
|
276
|
+
if (r.error) {
|
|
277
|
+
const e = r.error;
|
|
278
|
+
return e.code === "ENOENT" ? { ok: false, error: `\`${kind}\` is not installed, or not on PATH` } : { ok: false, error: e.message };
|
|
279
|
+
}
|
|
280
|
+
return { ok: r.status === 0 };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export {
|
|
284
|
+
checkProfile,
|
|
285
|
+
runLogin,
|
|
286
|
+
CLI_KINDS,
|
|
287
|
+
SYNTHETIC,
|
|
288
|
+
runCliAgent
|
|
289
|
+
};
|