opencode-agenthub 0.1.0 → 0.1.1
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/README.md +5 -2
- package/dist/composer/library/bundles/auto.json +1 -1
- package/dist/composer/opencode-profile.js +29 -8
- package/dist/composer/platform.js +16 -0
- package/dist/skills/agenthub-doctor/fix.js +1 -1
- package/dist/skills/agenthub-doctor/interactive.js +2 -2
- package/dist/skills/hr-support/bin/validate_staged_package.py +28 -10
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# opencode-agenthub
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-agenthub)
|
|
4
|
+
[](LICENSE)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
> Requires Node >= 18.0.0. Supports macOS and Linux directly. Windows users should use WSL 2 for the best experience; native Windows support remains best-effort alpha.
|
|
7
|
+
|
|
8
|
+
`opencode-agenthub` is a control plane and CLI for organizing, composing, and activating OpenCode agents, skills, profiles, bundles, and workspace runtime setup.
|
|
6
9
|
|
|
7
10
|
The npm package name is `opencode-agenthub`. The CLI command is `agenthub`. `opencode-agenthub` also works as a compatibility alias.
|
|
8
11
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"name": "auto",
|
|
10
10
|
"mode": "primary",
|
|
11
11
|
"hidden": false,
|
|
12
|
-
"model": "
|
|
12
|
+
"model": "",
|
|
13
13
|
"description": "Default coding agent with OMO-style intent detection and native build/edit/test workflow",
|
|
14
14
|
"permission": {
|
|
15
15
|
"*": "allow"
|
|
@@ -46,10 +46,12 @@ import {
|
|
|
46
46
|
} from "./settings.js";
|
|
47
47
|
import {
|
|
48
48
|
displayHomeConfigPath,
|
|
49
|
+
interactivePromptResetSequence,
|
|
49
50
|
resolvePythonCommand,
|
|
50
51
|
shouldChmod,
|
|
51
52
|
shouldOfferEnvrc,
|
|
52
53
|
spawnOptions,
|
|
54
|
+
stripTerminalControlInput,
|
|
53
55
|
windowsStartupNotice
|
|
54
56
|
} from "./platform.js";
|
|
55
57
|
const cliCommand = "agenthub";
|
|
@@ -1186,10 +1188,23 @@ const maybeConfigureEnvrc = async (workspace, configRoot) => {
|
|
|
1186
1188
|
rl.close();
|
|
1187
1189
|
}
|
|
1188
1190
|
};
|
|
1189
|
-
const createPromptInterface = () =>
|
|
1191
|
+
const createPromptInterface = () => {
|
|
1192
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1193
|
+
if (interactive) {
|
|
1194
|
+
const resetSequence = interactivePromptResetSequence();
|
|
1195
|
+
if (resetSequence) process.stdout.write(resetSequence);
|
|
1196
|
+
}
|
|
1197
|
+
return readline.createInterface({
|
|
1198
|
+
input: process.stdin,
|
|
1199
|
+
output: process.stdout,
|
|
1200
|
+
terminal: interactive
|
|
1201
|
+
});
|
|
1202
|
+
};
|
|
1203
|
+
const askPrompt = async (rl, question) => stripTerminalControlInput(await rl.question(question));
|
|
1190
1204
|
const promptRequired = async (rl, question, defaultValue) => {
|
|
1191
1205
|
while (true) {
|
|
1192
|
-
const answer = await
|
|
1206
|
+
const answer = await askPrompt(
|
|
1207
|
+
rl,
|
|
1193
1208
|
defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `
|
|
1194
1209
|
);
|
|
1195
1210
|
const value = normalizeOptional(answer) || defaultValue;
|
|
@@ -1198,14 +1213,16 @@ const promptRequired = async (rl, question, defaultValue) => {
|
|
|
1198
1213
|
}
|
|
1199
1214
|
};
|
|
1200
1215
|
const promptOptional = async (rl, question, defaultValue) => {
|
|
1201
|
-
const answer = await
|
|
1216
|
+
const answer = await askPrompt(
|
|
1217
|
+
rl,
|
|
1202
1218
|
defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `
|
|
1203
1219
|
);
|
|
1204
1220
|
return normalizeOptional(answer) || defaultValue;
|
|
1205
1221
|
};
|
|
1206
1222
|
const promptCsv = async (rl, question, defaultValues = []) => {
|
|
1207
1223
|
const defaultValue = defaultValues.join(", ");
|
|
1208
|
-
const answer = await
|
|
1224
|
+
const answer = await askPrompt(
|
|
1225
|
+
rl,
|
|
1209
1226
|
defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `
|
|
1210
1227
|
);
|
|
1211
1228
|
return normalizeCsv(answer || defaultValue);
|
|
@@ -1213,7 +1230,7 @@ const promptCsv = async (rl, question, defaultValues = []) => {
|
|
|
1213
1230
|
const promptBoolean = async (rl, question, defaultValue) => {
|
|
1214
1231
|
const suffix = defaultValue ? "[Y/n]" : "[y/N]";
|
|
1215
1232
|
while (true) {
|
|
1216
|
-
const answer = (await rl
|
|
1233
|
+
const answer = (await askPrompt(rl, `${question} ${suffix}: `)).trim().toLowerCase();
|
|
1217
1234
|
if (!answer) return defaultValue;
|
|
1218
1235
|
if (answer === "y" || answer === "yes") return true;
|
|
1219
1236
|
if (answer === "n" || answer === "no") return false;
|
|
@@ -1223,7 +1240,7 @@ const promptBoolean = async (rl, question, defaultValue) => {
|
|
|
1223
1240
|
const promptChoice = async (rl, question, choices, defaultValue) => {
|
|
1224
1241
|
const label = `${question} [${choices.join("/")}] (${defaultValue})`;
|
|
1225
1242
|
while (true) {
|
|
1226
|
-
const answer = (await rl
|
|
1243
|
+
const answer = (await askPrompt(rl, `${label}: `)).trim().toLowerCase();
|
|
1227
1244
|
if (!answer) return defaultValue;
|
|
1228
1245
|
const match = choices.find((choice) => choice === answer);
|
|
1229
1246
|
if (match) return match;
|
|
@@ -1238,7 +1255,10 @@ const promptIndexedChoice = async (rl, question, choices, defaultValue) => {
|
|
|
1238
1255
|
});
|
|
1239
1256
|
const defaultIndex = Math.max(choices.indexOf(defaultValue), 0) + 1;
|
|
1240
1257
|
while (true) {
|
|
1241
|
-
const answer = (await
|
|
1258
|
+
const answer = (await askPrompt(
|
|
1259
|
+
rl,
|
|
1260
|
+
`${question} [1-${choices.length}] (${defaultIndex}): `
|
|
1261
|
+
)).trim().toLowerCase();
|
|
1242
1262
|
if (!answer) return defaultValue;
|
|
1243
1263
|
const numeric = Number(answer);
|
|
1244
1264
|
if (Number.isInteger(numeric) && numeric >= 1 && numeric <= choices.length) {
|
|
@@ -1522,7 +1542,8 @@ const createSkillDefinition = async (root, name, reservedOk = false) => {
|
|
|
1522
1542
|
};
|
|
1523
1543
|
const readProfileDefinition = async (root, name) => readJsonIfExists(path.join(root, "profiles", `${name}.json`));
|
|
1524
1544
|
const promptRecord = async (rl, question) => {
|
|
1525
|
-
const answer = await
|
|
1545
|
+
const answer = await askPrompt(
|
|
1546
|
+
rl,
|
|
1526
1547
|
`${question} (comma-separated key=value, blank to skip): `
|
|
1527
1548
|
);
|
|
1528
1549
|
const entries = normalizeCsv(answer);
|
|
@@ -5,6 +5,20 @@ const shouldChmod = (win) => !isWindows(win);
|
|
|
5
5
|
const shouldOfferEnvrc = (win) => !isWindows(win);
|
|
6
6
|
const resolvePythonCommand = (win) => isWindows(win) ? "python" : "python3";
|
|
7
7
|
const spawnOptions = (win) => isWindows(win) ? { shell: true } : {};
|
|
8
|
+
const csiSequencePattern = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
9
|
+
const oscSequencePattern = /\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g;
|
|
10
|
+
const singleEscapePattern = /\u001b[@-_]/g;
|
|
11
|
+
const controlCharacterPattern = /[\u0000-\u001f\u007f]/g;
|
|
12
|
+
const stripTerminalControlInput = (value) => value.replace(oscSequencePattern, "").replace(csiSequencePattern, "").replace(singleEscapePattern, "").replace(controlCharacterPattern, "");
|
|
13
|
+
const interactivePromptResetSequence = (win = detectWindows()) => isWindows(win) ? [
|
|
14
|
+
"\x1B[?1000l",
|
|
15
|
+
"\x1B[?1001l",
|
|
16
|
+
"\x1B[?1002l",
|
|
17
|
+
"\x1B[?1003l",
|
|
18
|
+
"\x1B[?1005l",
|
|
19
|
+
"\x1B[?1006l",
|
|
20
|
+
"\x1B[?1015l"
|
|
21
|
+
].join("") : "";
|
|
8
22
|
const generateRunScript = () => `#!/usr/bin/env bash
|
|
9
23
|
set -euo pipefail
|
|
10
24
|
|
|
@@ -37,12 +51,14 @@ export {
|
|
|
37
51
|
displayHomeConfigPath,
|
|
38
52
|
generateRunCmd,
|
|
39
53
|
generateRunScript,
|
|
54
|
+
interactivePromptResetSequence,
|
|
40
55
|
isWindows,
|
|
41
56
|
resolveHomeConfigRoot,
|
|
42
57
|
resolvePythonCommand,
|
|
43
58
|
shouldChmod,
|
|
44
59
|
shouldOfferEnvrc,
|
|
45
60
|
spawnOptions,
|
|
61
|
+
stripTerminalControlInput,
|
|
46
62
|
symlinkType,
|
|
47
63
|
windowsStartupNotice
|
|
48
64
|
};
|
|
@@ -132,7 +132,7 @@ async function createBundleForSoul(targetRoot, soulName, options = {}) {
|
|
|
132
132
|
agent: {
|
|
133
133
|
name: options.agentName ?? soulName,
|
|
134
134
|
mode: options.mode ?? "primary",
|
|
135
|
-
model: options.model
|
|
135
|
+
model: options.model ?? "",
|
|
136
136
|
description: "Auto-generated bundle for imported soul"
|
|
137
137
|
}
|
|
138
138
|
};
|
|
@@ -149,9 +149,9 @@ async function createBundleFlow(rl, targetRoot) {
|
|
|
149
149
|
const modeInput = await rl.question("Mode [primary/subagent, default: primary]: ");
|
|
150
150
|
const mode = modeInput.trim() === "subagent" ? "subagent" : "primary";
|
|
151
151
|
const modelInput = await rl.question(
|
|
152
|
-
"Model [default:
|
|
152
|
+
"Model [default: none]: "
|
|
153
153
|
);
|
|
154
|
-
const model = modelInput.trim()
|
|
154
|
+
const model = modelInput.trim();
|
|
155
155
|
const result = await createBundleForSoul(targetRoot, soulName.trim(), {
|
|
156
156
|
agentName: finalAgentName,
|
|
157
157
|
mode,
|
|
@@ -9,12 +9,22 @@ import tempfile
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def
|
|
12
|
+
def wrap_executable(command: str) -> list[str]:
|
|
13
|
+
path = Path(command)
|
|
14
|
+
suffix = path.suffix.lower()
|
|
15
|
+
if suffix == ".js":
|
|
16
|
+
return ["node", command]
|
|
17
|
+
if os.name == "nt" and suffix in {".cmd", ".bat"}:
|
|
18
|
+
return ["cmd", "/c", command]
|
|
19
|
+
return [command]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def resolve_agenthub_command() -> list[str]:
|
|
13
23
|
if os.getenv("OPENCODE_AGENTHUB_BIN"):
|
|
14
|
-
return os.environ["OPENCODE_AGENTHUB_BIN"]
|
|
24
|
+
return wrap_executable(os.environ["OPENCODE_AGENTHUB_BIN"])
|
|
15
25
|
found = shutil.which("opencode-agenthub")
|
|
16
26
|
if found:
|
|
17
|
-
return found
|
|
27
|
+
return wrap_executable(found)
|
|
18
28
|
# Portable repo-local fallback: when running from src/skills/hr-support/bin/
|
|
19
29
|
# inside the source tree, try <repo-root>/bin/opencode-agenthub
|
|
20
30
|
this_file = Path(__file__).resolve()
|
|
@@ -28,13 +38,19 @@ def resolve_agenthub_bin() -> str:
|
|
|
28
38
|
and parts[-4] == "skills"
|
|
29
39
|
and parts[-5] == "src"
|
|
30
40
|
):
|
|
41
|
+
repo_bin_cmd = this_file.parents[4] / "bin" / "opencode-agenthub.cmd"
|
|
42
|
+
if os.name == "nt" and repo_bin_cmd.exists():
|
|
43
|
+
return ["cmd", "/c", str(repo_bin_cmd)]
|
|
31
44
|
repo_bin = this_file.parents[4] / "bin" / "opencode-agenthub"
|
|
32
|
-
if repo_bin.exists():
|
|
33
|
-
return str(repo_bin)
|
|
45
|
+
if os.name != "nt" and repo_bin.exists():
|
|
46
|
+
return [str(repo_bin)]
|
|
47
|
+
repo_dist = this_file.parents[4] / "dist" / "composer" / "opencode-profile.js"
|
|
48
|
+
if repo_dist.exists():
|
|
49
|
+
return ["node", str(repo_dist)]
|
|
34
50
|
raise SystemExit(
|
|
35
51
|
"Could not locate opencode-agenthub.\n"
|
|
36
52
|
" Set OPENCODE_AGENTHUB_BIN to the full path, or add opencode-agenthub to PATH.\n"
|
|
37
|
-
" When running from source, ensure bin/opencode-agenthub exists in the repo root."
|
|
53
|
+
" When running from source, ensure bin/opencode-agenthub (or .cmd on Windows) exists, or build dist/composer/opencode-profile.js in the repo root."
|
|
38
54
|
)
|
|
39
55
|
|
|
40
56
|
|
|
@@ -223,7 +239,6 @@ def main() -> int:
|
|
|
223
239
|
"Usage: validate_staged_package.py <stage-package-root|agenthub-home-root>"
|
|
224
240
|
)
|
|
225
241
|
|
|
226
|
-
agenthub_bin = resolve_agenthub_bin()
|
|
227
242
|
import_root = resolve_import_root(sys.argv[1])
|
|
228
243
|
workspace_root = Path.cwd()
|
|
229
244
|
validate_bundle_metadata(import_root)
|
|
@@ -235,6 +250,9 @@ def main() -> int:
|
|
|
235
250
|
if not profiles:
|
|
236
251
|
raise SystemExit("No profiles found in staged import root.")
|
|
237
252
|
|
|
253
|
+
# Integration smoke phase below requires a runnable agenthub binary.
|
|
254
|
+
agenthub_cmd = resolve_agenthub_command()
|
|
255
|
+
|
|
238
256
|
with tempfile.TemporaryDirectory(prefix="agenthub-stage-validate-") as temp_dir:
|
|
239
257
|
temp_root = Path(temp_dir)
|
|
240
258
|
temp_home = temp_root / "home"
|
|
@@ -243,7 +261,7 @@ def main() -> int:
|
|
|
243
261
|
|
|
244
262
|
run(
|
|
245
263
|
[
|
|
246
|
-
|
|
264
|
+
*agenthub_cmd,
|
|
247
265
|
"setup",
|
|
248
266
|
"minimal",
|
|
249
267
|
"--target-root",
|
|
@@ -252,7 +270,7 @@ def main() -> int:
|
|
|
252
270
|
)
|
|
253
271
|
run(
|
|
254
272
|
[
|
|
255
|
-
|
|
273
|
+
*agenthub_cmd,
|
|
256
274
|
"hub-import",
|
|
257
275
|
"--source",
|
|
258
276
|
str(import_root),
|
|
@@ -268,7 +286,7 @@ def main() -> int:
|
|
|
268
286
|
for profile in profiles:
|
|
269
287
|
run(
|
|
270
288
|
[
|
|
271
|
-
|
|
289
|
+
*agenthub_cmd,
|
|
272
290
|
"run",
|
|
273
291
|
profile,
|
|
274
292
|
"--workspace",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-agenthub",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A control plane for organizing, composing, and activating OpenCode agents, skills, profiles, and bundles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/plugins/opencode-agenthub.js",
|
|
7
7
|
"exports": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "node scripts/build.mjs",
|
|
17
17
|
"test:smoke": "bun test test/smoke-*.test.ts",
|
|
18
|
-
"
|
|
18
|
+
"prepack": "npm run build"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"LICENSE",
|