@goondan/openharness-cli 0.0.1-alpha4 → 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/dist/bin.js +0 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +260 -2
- package/package.json +23 -40
- package/README.md +0 -11
package/dist/bin.js
CHANGED
|
File without changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,260 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
|
|
6
|
+
// src/commands/run.ts
|
|
7
|
+
import * as path2 from "path";
|
|
8
|
+
import { createHarness } from "@goondan/openharness";
|
|
9
|
+
|
|
10
|
+
// src/env-loader.ts
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
import dotenv from "dotenv";
|
|
13
|
+
function loadEnv(workdir) {
|
|
14
|
+
const envPath = path.join(workdir, ".env");
|
|
15
|
+
dotenv.config({ path: envPath, override: false });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/config-loader.ts
|
|
19
|
+
import { pathToFileURL } from "url";
|
|
20
|
+
async function loadConfig(configPath) {
|
|
21
|
+
const fileUrl = pathToFileURL(configPath).href;
|
|
22
|
+
const mod = await import(fileUrl);
|
|
23
|
+
const config = mod.default ?? mod;
|
|
24
|
+
if (config === null || typeof config !== "object" || !("agents" in config) || typeof config.agents !== "object") {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Config file "${configPath}" must export a default HarnessConfig with an "agents" property.`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return config;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/commands/run.ts
|
|
33
|
+
async function runCommand(text, options) {
|
|
34
|
+
const workdir = options.workdir ?? process.cwd();
|
|
35
|
+
const configPath = options.config ? path2.resolve(workdir, options.config) : path2.resolve(workdir, "harness.config.ts");
|
|
36
|
+
loadEnv(workdir);
|
|
37
|
+
let config;
|
|
38
|
+
try {
|
|
39
|
+
config = await loadConfig(configPath);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(`Error loading config: ${err instanceof Error ? err.message : String(err)}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const agentNames = Object.keys(config.agents);
|
|
45
|
+
let agentName;
|
|
46
|
+
if (options.agent) {
|
|
47
|
+
if (!config.agents[options.agent]) {
|
|
48
|
+
console.error(`Unknown agent: "${options.agent}". Available agents: ${agentNames.join(", ")}`);
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
agentName = options.agent;
|
|
52
|
+
} else if (agentNames.length === 1) {
|
|
53
|
+
agentName = agentNames[0];
|
|
54
|
+
} else {
|
|
55
|
+
console.error(
|
|
56
|
+
`Multiple agents defined. Specify one with --agent. Available agents: ${agentNames.join(", ")}`
|
|
57
|
+
);
|
|
58
|
+
process.exit(2);
|
|
59
|
+
}
|
|
60
|
+
let harness;
|
|
61
|
+
try {
|
|
62
|
+
harness = await createHarness(config);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error(`Error creating harness: ${err instanceof Error ? err.message : String(err)}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const result = await harness.processTurn(agentName, text, {
|
|
69
|
+
conversationId: options.conversation
|
|
70
|
+
});
|
|
71
|
+
if (result.text) {
|
|
72
|
+
process.stdout.write(result.text);
|
|
73
|
+
process.stdout.write("\n");
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(`Runtime error: ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
} finally {
|
|
79
|
+
await harness.close();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/commands/repl.ts
|
|
84
|
+
import * as path3 from "path";
|
|
85
|
+
import * as readline from "readline";
|
|
86
|
+
import { randomUUID } from "crypto";
|
|
87
|
+
import { createHarness as createHarness2 } from "@goondan/openharness";
|
|
88
|
+
async function replCommand(options) {
|
|
89
|
+
const workdir = options.workdir ?? process.cwd();
|
|
90
|
+
const configPath = options.config ? path3.resolve(workdir, options.config) : path3.resolve(workdir, "harness.config.ts");
|
|
91
|
+
loadEnv(workdir);
|
|
92
|
+
let config;
|
|
93
|
+
try {
|
|
94
|
+
config = await loadConfig(configPath);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(`Error loading config: ${err instanceof Error ? err.message : String(err)}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
const agentNames = Object.keys(config.agents);
|
|
100
|
+
let agentName;
|
|
101
|
+
if (options.agent) {
|
|
102
|
+
if (!config.agents[options.agent]) {
|
|
103
|
+
console.error(`Unknown agent: "${options.agent}". Available agents: ${agentNames.join(", ")}`);
|
|
104
|
+
process.exit(2);
|
|
105
|
+
}
|
|
106
|
+
agentName = options.agent;
|
|
107
|
+
} else if (agentNames.length === 1) {
|
|
108
|
+
agentName = agentNames[0];
|
|
109
|
+
} else {
|
|
110
|
+
console.error(
|
|
111
|
+
`Multiple agents defined. Specify one with --agent. Available agents: ${agentNames.join(", ")}`
|
|
112
|
+
);
|
|
113
|
+
process.exit(2);
|
|
114
|
+
}
|
|
115
|
+
let harness;
|
|
116
|
+
try {
|
|
117
|
+
harness = await createHarness2(config);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error(`Error creating harness: ${err instanceof Error ? err.message : String(err)}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
const conversationId = options.conversation ?? randomUUID();
|
|
123
|
+
const rl = readline.createInterface({
|
|
124
|
+
input: process.stdin,
|
|
125
|
+
output: process.stdout
|
|
126
|
+
});
|
|
127
|
+
const prompt = () => {
|
|
128
|
+
rl.question("> ", async (line) => {
|
|
129
|
+
const trimmed = line.trim();
|
|
130
|
+
if (trimmed === "exit" || trimmed === "quit") {
|
|
131
|
+
rl.close();
|
|
132
|
+
await harness.close();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!trimmed) {
|
|
136
|
+
prompt();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const result = await harness.processTurn(agentName, trimmed, { conversationId });
|
|
141
|
+
if (result.text) {
|
|
142
|
+
console.log(result.text);
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
146
|
+
}
|
|
147
|
+
prompt();
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
rl.on("SIGINT", () => {
|
|
151
|
+
console.log("\nExiting...");
|
|
152
|
+
rl.close();
|
|
153
|
+
harness.close().then(() => process.exit(0)).catch(() => process.exit(1));
|
|
154
|
+
});
|
|
155
|
+
rl.on("close", () => {
|
|
156
|
+
harness.close().catch(() => {
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
console.log(`OpenHarness REPL - Agent: ${agentName} (conversation: ${conversationId})`);
|
|
160
|
+
console.log('Type "exit" or press Ctrl+C to quit.\n');
|
|
161
|
+
prompt();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/index.ts
|
|
165
|
+
var CLI_VERSION = JSON.parse(
|
|
166
|
+
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
167
|
+
);
|
|
168
|
+
function parseArgs(argv) {
|
|
169
|
+
const args = argv.slice(2);
|
|
170
|
+
let i = 0;
|
|
171
|
+
const result = { command: "repl" };
|
|
172
|
+
if (args[i] && !args[i].startsWith("-")) {
|
|
173
|
+
const cmd = args[i];
|
|
174
|
+
if (cmd === "run") {
|
|
175
|
+
result.command = "run";
|
|
176
|
+
i++;
|
|
177
|
+
if (args[i] && !args[i].startsWith("-")) {
|
|
178
|
+
result.text = args[i];
|
|
179
|
+
i++;
|
|
180
|
+
}
|
|
181
|
+
} else if (cmd === "repl") {
|
|
182
|
+
result.command = "repl";
|
|
183
|
+
i++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
while (i < args.length) {
|
|
187
|
+
const arg = args[i];
|
|
188
|
+
if (arg === "--workdir" || arg === "-w") {
|
|
189
|
+
result.workdir = args[++i];
|
|
190
|
+
} else if (arg.startsWith("--workdir=")) {
|
|
191
|
+
result.workdir = arg.slice("--workdir=".length);
|
|
192
|
+
} else if (arg === "--config" || arg === "-c") {
|
|
193
|
+
result.config = args[++i];
|
|
194
|
+
} else if (arg.startsWith("--config=")) {
|
|
195
|
+
result.config = arg.slice("--config=".length);
|
|
196
|
+
} else if (arg === "--agent" || arg === "-a") {
|
|
197
|
+
result.agent = args[++i];
|
|
198
|
+
} else if (arg.startsWith("--agent=")) {
|
|
199
|
+
result.agent = arg.slice("--agent=".length);
|
|
200
|
+
} else if (arg === "--conversation") {
|
|
201
|
+
result.conversation = args[++i];
|
|
202
|
+
} else if (arg.startsWith("--conversation=")) {
|
|
203
|
+
result.conversation = arg.slice("--conversation=".length);
|
|
204
|
+
} else if (arg === "--max-steps") {
|
|
205
|
+
result.maxSteps = parseInt(args[++i] ?? "25", 10);
|
|
206
|
+
} else if (arg.startsWith("--max-steps=")) {
|
|
207
|
+
result.maxSteps = parseInt(arg.slice("--max-steps=".length), 10);
|
|
208
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
209
|
+
printHelp();
|
|
210
|
+
process.exit(0);
|
|
211
|
+
} else if (arg === "--version" || arg === "-v") {
|
|
212
|
+
console.log(CLI_VERSION.version);
|
|
213
|
+
process.exit(0);
|
|
214
|
+
}
|
|
215
|
+
i++;
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
function printHelp() {
|
|
220
|
+
console.log(`
|
|
221
|
+
OpenHarness CLI (oh)
|
|
222
|
+
|
|
223
|
+
Usage:
|
|
224
|
+
oh Start REPL (default)
|
|
225
|
+
oh repl Start REPL
|
|
226
|
+
oh run "<text>" Run a single turn
|
|
227
|
+
|
|
228
|
+
Options:
|
|
229
|
+
--workdir, -w <dir> Working directory (default: cwd)
|
|
230
|
+
--config, -c <file> Config file (default: harness.config.ts)
|
|
231
|
+
--agent, -a <name> Agent name (required if multiple agents)
|
|
232
|
+
--conversation <id> Conversation ID
|
|
233
|
+
--max-steps <n> Maximum steps per turn
|
|
234
|
+
--help, -h Show this help
|
|
235
|
+
--version, -v Show version
|
|
236
|
+
`);
|
|
237
|
+
}
|
|
238
|
+
async function main() {
|
|
239
|
+
const parsed = parseArgs(process.argv);
|
|
240
|
+
const opts = {
|
|
241
|
+
workdir: parsed.workdir,
|
|
242
|
+
config: parsed.config,
|
|
243
|
+
agent: parsed.agent,
|
|
244
|
+
conversation: parsed.conversation,
|
|
245
|
+
maxSteps: parsed.maxSteps
|
|
246
|
+
};
|
|
247
|
+
if (parsed.command === "run") {
|
|
248
|
+
if (!parsed.text) {
|
|
249
|
+
console.error('Usage: oh run "<text>"');
|
|
250
|
+
process.exit(2);
|
|
251
|
+
}
|
|
252
|
+
await runCommand(parsed.text, opts);
|
|
253
|
+
} else {
|
|
254
|
+
await replCommand(opts);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
main().catch((err) => {
|
|
258
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
259
|
+
process.exit(1);
|
|
260
|
+
});
|
package/package.json
CHANGED
|
@@ -1,51 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goondan/openharness-cli",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "CLI for running and debugging OpenHarness agents locally",
|
|
3
|
+
"version": "0.1.0",
|
|
5
4
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
"dist"
|
|
10
|
-
],
|
|
11
|
-
"homepage": "https://github.com/goondan/openharness#readme",
|
|
12
|
-
"bugs": {
|
|
13
|
-
"url": "https://github.com/goondan/openharness/issues"
|
|
14
|
-
},
|
|
15
|
-
"repository": {
|
|
16
|
-
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/goondan/openharness.git",
|
|
18
|
-
"directory": "packages/cli"
|
|
19
|
-
},
|
|
20
|
-
"publishConfig": {
|
|
21
|
-
"access": "public"
|
|
22
|
-
},
|
|
23
|
-
"keywords": [
|
|
24
|
-
"agent",
|
|
25
|
-
"cli",
|
|
26
|
-
"harness",
|
|
27
|
-
"llm"
|
|
28
|
-
],
|
|
29
5
|
"bin": {
|
|
30
|
-
"oh": "dist/
|
|
6
|
+
"oh": "./dist/index.js"
|
|
31
7
|
},
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
32
9
|
"exports": {
|
|
33
10
|
".": {
|
|
34
|
-
"
|
|
35
|
-
"
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
36
13
|
}
|
|
37
14
|
},
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
"prepack": "pnpm build",
|
|
42
|
-
"link:global": "node ./scripts/link-global-bin.mjs",
|
|
43
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
44
|
-
"test": "node -e \"process.exit(0)\"",
|
|
45
|
-
"start": "node dist/bin.js",
|
|
46
|
-
"clean": "rm -rf dist"
|
|
47
|
-
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
48
18
|
"dependencies": {
|
|
49
|
-
"
|
|
19
|
+
"dotenv": "^16.4.0",
|
|
20
|
+
"@goondan/openharness": "0.1.0",
|
|
21
|
+
"@goondan/openharness-types": "0.1.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.7.0",
|
|
25
|
+
"tsup": "^8.4.0",
|
|
26
|
+
"vitest": "^3.0.0",
|
|
27
|
+
"@types/node": "^22.0.0"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"test": "vitest run"
|
|
50
33
|
}
|
|
51
|
-
}
|
|
34
|
+
}
|