opencode-agenthub 0.1.0 → 0.1.2

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.
@@ -37,19 +37,30 @@ import {
37
37
  import { readPackageVersion } from "./package-version.js";
38
38
  import {
39
39
  mergeAgentHubSettingsDefaults,
40
- hrPrimaryAgentName,
41
- hrSubagentNames,
40
+ hrAgentNames,
41
+ loadNativeOpenCodePreferences,
42
+ listAvailableOpencodeModels,
43
+ probeOpencodeModelAvailability,
44
+ readHrKnownModelIds,
42
45
  recommendedHrBootstrapModel,
43
- recommendedHrBootstrapVariant,
44
46
  readAgentHubSettings,
47
+ resolveHrBootstrapAgentModels,
48
+ validateHrAgentModelConfiguration,
45
49
  writeAgentHubSettings
46
50
  } from "./settings.js";
51
+ import {
52
+ validateModelAgainstCatalog,
53
+ validateModelIdentifier
54
+ } from "./model-utils.js";
47
55
  import {
48
56
  displayHomeConfigPath,
57
+ interactivePromptResetSequence,
49
58
  resolvePythonCommand,
50
59
  shouldChmod,
51
60
  shouldOfferEnvrc,
61
+ shouldUseReadlineTerminal,
52
62
  spawnOptions,
63
+ stripTerminalControlInput,
53
64
  windowsStartupNotice
54
65
  } from "./platform.js";
55
66
  const cliCommand = "agenthub";
@@ -144,8 +155,8 @@ FLAGS (hr)
144
155
  hr <profile> Test an HR-home profile in the current workspace
145
156
  hr last Reuse the last HR profile tested in this workspace
146
157
  hr set <profile> Unsupported (use explicit '${cliCommand} hr <profile>' each time)
147
- first bootstrap Interactive terminals can choose recommended / free / custom
148
- defaults to openai/gpt-5.4-mini when left blank
158
+ first bootstrap HR first asks about your situation, inspects resources,
159
+ reports a recommendation, then lets you accept or override it
149
160
 
150
161
  FLAGS (new / compose profile)
151
162
  --from <profile> Seed bundles/plugins from an existing profile
@@ -709,6 +720,7 @@ const printRuntimeBanner = (label, root) => {
709
720
  process.stdout.write(`[agenthub] Home: ${root}
710
721
  `);
711
722
  };
723
+ let suppressNextHrRuntimeBanner = false;
712
724
  const listPromotablePackageIds = async (hrRoot = defaultHrHome()) => {
713
725
  try {
714
726
  const entries = await readdir(path.join(hrRoot, "staging"), { withFileTypes: true });
@@ -838,70 +850,285 @@ const ensureHomeReadyOrBootstrap = async (targetRoot = defaultAgentHubHome()) =>
838
850
  process.stdout.write(`\u2713 First run \u2014 initialised coding system at ${targetRoot}
839
851
  `);
840
852
  };
841
- const promptHrBootstrapModelSelection = async (hrRoot) => {
842
- const rl = createPromptInterface();
843
- try {
844
- process.stdout.write("\nFirst-time HR Office setup\n");
845
- process.stdout.write("Choose an HR model preset:\n");
853
+ const formatCountLabel = (count, singular, plural = `${singular}s`) => {
854
+ if (count === null) return `unknown ${plural}`;
855
+ return `${count} ${count === 1 ? singular : plural}`;
856
+ };
857
+ const inspectHrBootstrapResources = async (hrRoot) => {
858
+ const [configuredGithubSources, configuredModelCatalogSources, knownModels, availableModels, freeModels, native] = await Promise.all([
859
+ countConfiguredHrGithubSources(hrRoot),
860
+ countConfiguredHrModelCatalogSources(hrRoot),
861
+ readHrKnownModelIds(hrRoot),
862
+ listAvailableOpencodeModels(),
863
+ listOpencodeFreeModels(),
864
+ loadNativeOpenCodePreferences()
865
+ ]);
866
+ const recommendedAvailability = await probeOpencodeModelAvailability(
867
+ recommendedHrBootstrapModel,
868
+ { listModels: async () => availableModels }
869
+ );
870
+ return {
871
+ configuredGithubSources,
872
+ configuredModelCatalogSources,
873
+ knownModels,
874
+ availableModels,
875
+ freeModels,
876
+ nativeModel: native?.model,
877
+ recommendedAvailability
878
+ };
879
+ };
880
+ const recommendHrBootstrapSelection = (resources) => {
881
+ if (resources.recommendedAvailability.available) {
882
+ return {
883
+ strategy: "recommended",
884
+ summary: `I recommend starting with the recommended HR model (${recommendedHrBootstrapModel}).`,
885
+ reason: "It is available in this opencode environment and matches the built-in HR default."
886
+ };
887
+ }
888
+ if (resources.freeModels.length > 0) {
889
+ return {
890
+ strategy: "free",
891
+ summary: "I recommend starting with the best available free HR model.",
892
+ reason: `${resources.recommendedAvailability.message} A free fallback is available right now.`
893
+ };
894
+ }
895
+ const nativeModelSyntax = resources.nativeModel ? validateModelIdentifier(resources.nativeModel) : void 0;
896
+ if (resources.nativeModel && nativeModelSyntax?.ok) {
897
+ return {
898
+ strategy: "native",
899
+ summary: `I recommend reusing your native default model (${resources.nativeModel}).`,
900
+ reason: "No verified free fallback is visible, but your native opencode default looks usable."
901
+ };
902
+ }
903
+ return {
904
+ strategy: "custom",
905
+ summary: "I recommend entering a custom HR model now.",
906
+ reason: "The recommended preset is not currently verified and no safer automatic fallback was found."
907
+ };
908
+ };
909
+ const printHrBootstrapAssessment = (resources, recommendation) => {
910
+ void resources;
911
+ process.stdout.write(`
912
+ Recommended setup:
913
+ ${recommendation.summary}
914
+
915
+ `);
916
+ };
917
+ const buildHrModelSelection = async (rl, hrRoot, strategy) => {
918
+ if (strategy === "recommended") {
846
919
  process.stdout.write(
847
- ` recommended Use ${recommendedHrBootstrapModel} (${recommendedHrBootstrapVariant})
920
+ `[agenthub] Recommended HR preset requires OpenAI model access in your opencode environment.
848
921
  `
849
922
  );
850
- process.stdout.write(
851
- " free Pick from current opencode free models (quality may drop)\n"
923
+ return {
924
+ consoleModel: recommendedHrBootstrapModel,
925
+ subagentStrategy: "recommended",
926
+ sharedSubagentModel: recommendedHrBootstrapModel
927
+ };
928
+ }
929
+ if (strategy === "native") {
930
+ const native = await loadNativeOpenCodePreferences();
931
+ if (!native?.model) {
932
+ process.stdout.write("[agenthub] No native default model is configured. Choose another fallback.\n");
933
+ return buildHrModelSelection(rl, hrRoot, "free");
934
+ }
935
+ return {
936
+ consoleModel: native.model,
937
+ subagentStrategy: "native",
938
+ sharedSubagentModel: native.model
939
+ };
940
+ }
941
+ if (strategy === "free") {
942
+ const freeModels = await listOpencodeFreeModels();
943
+ const fallbackFreeModel = freeModels.includes("opencode/minimax-m2.5-free") ? "opencode/minimax-m2.5-free" : freeModels[0] || "opencode/minimax-m2.5-free";
944
+ const choices = freeModels.length > 0 ? freeModels : [fallbackFreeModel];
945
+ process.stdout.write("Current opencode free models:\n");
946
+ const selected = choices.length === 1 ? (process.stdout.write(` 1. ${choices[0]}
947
+ `), choices[0]) : await promptIndexedChoice(
948
+ rl,
949
+ "Choose a free model for HR",
950
+ choices,
951
+ fallbackFreeModel
852
952
  );
853
- process.stdout.write(" custom Enter any model id yourself\n\n");
854
- const subagentStrategy = await promptChoice(
953
+ return {
954
+ consoleModel: selected,
955
+ subagentStrategy: "free",
956
+ sharedSubagentModel: selected
957
+ };
958
+ }
959
+ const custom = await promptRequired(rl, "Custom HR model", recommendedHrBootstrapModel);
960
+ return {
961
+ consoleModel: custom,
962
+ subagentStrategy: "custom",
963
+ sharedSubagentModel: custom
964
+ };
965
+ };
966
+ const checkHrBootstrapSelection = async (hrRoot, selection) => {
967
+ const model = selection.sharedSubagentModel || selection.consoleModel;
968
+ if (!model) {
969
+ return {
970
+ ok: false,
971
+ selection,
972
+ stage: "syntax",
973
+ message: "Model id cannot be blank."
974
+ };
975
+ }
976
+ const syntax = validateModelIdentifier(model);
977
+ if (!syntax.ok) {
978
+ return { ok: false, selection, stage: "syntax", message: syntax.message };
979
+ }
980
+ const knownModels = await readHrKnownModelIds(hrRoot);
981
+ const catalog = validateModelAgainstCatalog(model, knownModels);
982
+ if (!catalog.ok) {
983
+ return { ok: false, selection, stage: "catalog", message: catalog.message };
984
+ }
985
+ const availability = await probeOpencodeModelAvailability(model, {
986
+ listModels: listAvailableOpencodeModels
987
+ });
988
+ if (!availability.available) {
989
+ return {
990
+ ok: false,
991
+ selection,
992
+ stage: availability.reason === "probe_failed" ? "probe_failed" : "availability",
993
+ message: availability.message
994
+ };
995
+ }
996
+ return { ok: true, selection };
997
+ };
998
+ const promptValidatedHrModelSelection = async (rl, hrRoot, strategy) => {
999
+ let selection = await buildHrModelSelection(rl, hrRoot, strategy);
1000
+ while (true) {
1001
+ const check = await checkHrBootstrapSelection(hrRoot, selection);
1002
+ if (check.ok) return check.selection;
1003
+ process.stdout.write(`${check.message}
1004
+ `);
1005
+ if (check.stage === "syntax" && selection.subagentStrategy === "custom") {
1006
+ selection = await buildHrModelSelection(rl, hrRoot, "custom");
1007
+ continue;
1008
+ }
1009
+ const action = await promptChoice(
855
1010
  rl,
856
- "HR model preset",
857
- ["recommended", "free", "custom"],
858
- "recommended"
1011
+ check.stage === "probe_failed" ? "Model verification failed \u2014 continue or choose a fallback" : "Choose a fallback",
1012
+ ["continue", "free", "native", "custom", "retry recommended"],
1013
+ check.stage === "probe_failed" ? "continue" : "free"
859
1014
  );
860
- let sharedSubagentModel;
861
- let consoleModel;
862
- if (subagentStrategy === "recommended") {
863
- consoleModel = recommendedHrBootstrapModel;
864
- sharedSubagentModel = recommendedHrBootstrapModel;
865
- }
866
- if (subagentStrategy === "free") {
867
- const freeModels = await listOpencodeFreeModels();
868
- const fallbackFreeModel = freeModels.includes("opencode/minimax-m2.5-free") ? "opencode/minimax-m2.5-free" : freeModels[0] || "opencode/minimax-m2.5-free";
869
- process.stdout.write("Current opencode free models:\n");
870
- sharedSubagentModel = await promptIndexedChoice(
871
- rl,
872
- "Choose a free model for HR",
873
- freeModels.length > 0 ? freeModels : [fallbackFreeModel],
874
- fallbackFreeModel
875
- );
876
- consoleModel = sharedSubagentModel;
877
- } else if (subagentStrategy === "custom") {
878
- sharedSubagentModel = await promptRequired(
1015
+ if (action === "continue") return selection;
1016
+ selection = await buildHrModelSelection(
1017
+ rl,
1018
+ hrRoot,
1019
+ action === "retry recommended" ? "recommended" : action
1020
+ );
1021
+ }
1022
+ };
1023
+ const promptHrBootstrapModelSelection = async (hrRoot) => {
1024
+ const rl = createPromptInterface();
1025
+ try {
1026
+ process.stdout.write("\nFirst-time HR Office setup\n");
1027
+ const resources = await inspectHrBootstrapResources(hrRoot);
1028
+ const recommendation = recommendHrBootstrapSelection(resources);
1029
+ printHrBootstrapAssessment(resources, recommendation);
1030
+ while (true) {
1031
+ const action = await promptChoice(
879
1032
  rl,
880
- "Custom HR model",
881
- recommendedHrBootstrapModel
1033
+ "Apply this recommendation now",
1034
+ ["accept", "recommended", "free", "native", "custom"],
1035
+ "accept"
882
1036
  );
883
- consoleModel = sharedSubagentModel;
1037
+ const strategy = action === "accept" ? recommendation.strategy : action;
1038
+ const validated = await promptValidatedHrModelSelection(rl, hrRoot, strategy);
1039
+ const finalModel = validated.sharedSubagentModel || validated.consoleModel;
1040
+ if (!finalModel) continue;
1041
+ const finalSyntax = validateModelIdentifier(finalModel);
1042
+ if (finalSyntax.ok) {
1043
+ return validated;
1044
+ }
884
1045
  }
885
- process.stdout.write(`
886
- [agenthub] HR settings will be written to ${path.join(hrRoot, "settings.json")}
1046
+ } finally {
1047
+ rl.close();
1048
+ }
1049
+ };
1050
+ const shouldUseInteractivePrompts = () => process.env.OPENCODE_AGENTHUB_FORCE_INTERACTIVE_PROMPTS === "1" || Boolean(process.stdin.isTTY && process.stdout.isTTY);
1051
+ const applyHrModelSelection = async (targetRoot, selection) => {
1052
+ await installHrOfficeHomeWithOptions({
1053
+ hrRoot: targetRoot,
1054
+ hrModelSelection: selection
1055
+ });
1056
+ };
1057
+ const repairHrModelConfigurationIfNeeded = async (targetRoot) => {
1058
+ const settings = await readAgentHubSettings(targetRoot);
1059
+ if (!shouldUseInteractivePrompts()) {
1060
+ for (const agentName of hrAgentNames) {
1061
+ const model = settings?.agents?.[agentName]?.model;
1062
+ if (typeof model !== "string" || model.trim().length === 0) continue;
1063
+ const syntax = validateModelIdentifier(model);
1064
+ if (!syntax.ok) {
1065
+ fail(`HR model configuration needs attention. Agent '${agentName}' model '${model}' is invalid: ${syntax.message}`);
1066
+ }
1067
+ }
1068
+ return;
1069
+ }
1070
+ const status = await validateHrAgentModelConfiguration(targetRoot, settings);
1071
+ if (status.valid) return;
1072
+ const rl = createPromptInterface();
1073
+ try {
1074
+ process.stdout.write("[agenthub] HR model configuration needs attention.\n");
1075
+ if (status.message) process.stdout.write(`${status.message}
887
1076
  `);
888
- return {
889
- consoleModel,
890
- subagentStrategy,
891
- sharedSubagentModel
1077
+ const repair = await promptBoolean(
1078
+ rl,
1079
+ "Reconfigure HR models now?",
1080
+ true
1081
+ );
1082
+ if (!repair) {
1083
+ fail("Aborted before repairing invalid HR model configuration.");
1084
+ }
1085
+ const fallback = await promptChoice(
1086
+ rl,
1087
+ "Choose a fallback",
1088
+ ["free", "native", "custom", "retry recommended"],
1089
+ "free"
1090
+ );
1091
+ const validated = await promptValidatedHrModelSelection(
1092
+ rl,
1093
+ targetRoot,
1094
+ fallback === "retry recommended" ? "recommended" : fallback
1095
+ );
1096
+ const resolved = await resolveHrBootstrapAgentModels({
1097
+ targetRoot,
1098
+ selection: validated
1099
+ });
1100
+ const merged = mergeAgentHubSettingsDefaults(settings || {});
1101
+ merged.agents = merged.agents || {};
1102
+ for (const agentName of hrAgentNames) {
1103
+ const resolvedSelection = resolved.agentModels[agentName];
1104
+ merged.agents[agentName] = {
1105
+ ...merged.agents[agentName] || {},
1106
+ model: resolvedSelection.model,
1107
+ ...resolvedSelection.variant ? { variant: resolvedSelection.variant } : {}
1108
+ };
1109
+ if (!resolvedSelection.variant) delete merged.agents[agentName].variant;
1110
+ }
1111
+ merged.meta = {
1112
+ ...merged.meta,
1113
+ onboarding: {
1114
+ ...merged.meta?.onboarding,
1115
+ modelStrategy: resolved.strategy,
1116
+ mode: merged.meta?.onboarding?.mode || "hr-office",
1117
+ importedNativeBasics: merged.meta?.onboarding?.importedNativeBasics ?? true,
1118
+ importedNativeAgents: merged.meta?.onboarding?.importedNativeAgents ?? true,
1119
+ createdAt: merged.meta?.onboarding?.createdAt || (/* @__PURE__ */ new Date()).toISOString()
1120
+ }
892
1121
  };
1122
+ await writeAgentHubSettings(targetRoot, merged);
1123
+ process.stdout.write("[agenthub] Updated HR model configuration.\n");
893
1124
  } finally {
894
1125
  rl.close();
895
1126
  }
896
1127
  };
897
1128
  const printHrModelOverrideHint = (targetRoot) => {
898
- process.stdout.write(
899
- [
900
- `[agenthub] HR model settings: ${path.join(targetRoot, "settings.json")}`,
901
- `[agenthub] Change later with: ${cliCommand} doctor --target-root ${targetRoot} --agent ${hrPrimaryAgentName} --model <model>`,
902
- `[agenthub] HR subagents: ${hrSubagentNames.join(", ")} (use the same doctor command with --agent <name>)`
903
- ].join("\n") + "\n"
904
- );
1129
+ void targetRoot;
1130
+ process.stdout.write(`Tip: change HR models later with '${cliCommand} doctor'.
1131
+ `);
905
1132
  };
906
1133
  const countConfiguredHrGithubSources = async (targetRoot) => {
907
1134
  try {
@@ -952,8 +1179,12 @@ const syncHrSourceInventoryOnFirstRun = async (targetRoot) => {
952
1179
  );
953
1180
  }
954
1181
  const sourceLabel = sourceParts.length > 0 ? sourceParts.join(" + ") : "configured HR sources";
955
- process.stdout.write(`[agenthub] First run \u2014 syncing HR inventory from ${sourceLabel}...
956
- `);
1182
+ process.stdout.write(
1183
+ `
1184
+ Step 3/3 \xB7 Sync inventory
1185
+ Sync the HR sourcer inventory from ${sourceLabel} \u2014 this may take a moment, please wait...
1186
+ `
1187
+ );
957
1188
  try {
958
1189
  const pythonCommand = resolvePythonCommand();
959
1190
  const scriptPath = path.join(targetRoot, "bin", "sync_sources.py");
@@ -980,12 +1211,10 @@ const syncHrSourceInventoryOnFirstRun = async (targetRoot) => {
980
1211
  });
981
1212
  const summary = stdout.trim();
982
1213
  if (code === 0) {
983
- if (summary) process.stdout.write(`${summary}
1214
+ void summary;
1215
+ const repoSummary = configuredSourceCount && configuredSourceCount > 0 ? `${configuredSourceCount} repo${configuredSourceCount === 1 ? "" : "s"}` : "configured sources";
1216
+ process.stdout.write(`\u2713 HR sourcer inventory sync complete (${repoSummary}).
984
1217
  `);
985
- process.stdout.write(
986
- `[agenthub] HR source status: ${path.join(targetRoot, "source-status.json")}
987
- `
988
- );
989
1218
  return;
990
1219
  }
991
1220
  process.stderr.write(
@@ -1005,18 +1234,33 @@ const syncHrSourceInventoryOnFirstRun = async (targetRoot) => {
1005
1234
  };
1006
1235
  const ensureHrOfficeReadyOrBootstrap = async (targetRoot = defaultHrHome(), options = {}) => {
1007
1236
  if (await hrHomeInitialized(targetRoot)) return;
1008
- const shouldPrompt = process.stdin.isTTY && process.stdout.isTTY;
1237
+ const shouldPrompt = shouldUseInteractivePrompts();
1238
+ process.stdout.write("\nHR Office \u2014 first-time setup\n\n");
1239
+ process.stdout.write(
1240
+ "Heads up: a full HR assemble can take about 20\u201330 minutes because AI may need time to choose and evaluate the souls and skills your agents need.\n\n"
1241
+ );
1242
+ process.stdout.write("This will:\n");
1243
+ process.stdout.write("1. Choose an AI model for HR agents\n");
1244
+ process.stdout.write("2. Create the HR Office workspace\n");
1245
+ if (options.syncSourcesOnFirstRun ?? true) {
1246
+ process.stdout.write("3. Sync the HR sourcer inventory (this may take a little longer)\n\n");
1247
+ } else {
1248
+ process.stdout.write("3. Skip inventory sync for now because you are assembling only\n\n");
1249
+ }
1009
1250
  const hrModelSelection = shouldPrompt ? await promptHrBootstrapModelSelection(targetRoot) : void 0;
1010
- await installHrOfficeHomeWithOptions({
1011
- hrRoot: targetRoot,
1012
- hrModelSelection
1013
- });
1014
- process.stdout.write(`\u2713 First run \u2014 initialised HR Office at ${targetRoot}
1251
+ await applyHrModelSelection(targetRoot, hrModelSelection || {});
1252
+ process.stdout.write(`
1253
+ Step 2/3 \xB7 Create workspace
1254
+ \u2713 First run \u2014 initialised HR Office at ${targetRoot}
1015
1255
  `);
1016
1256
  printHrModelOverrideHint(targetRoot);
1017
1257
  if (options.syncSourcesOnFirstRun ?? true) {
1018
1258
  await syncHrSourceInventoryOnFirstRun(targetRoot);
1019
1259
  }
1260
+ process.stdout.write(`
1261
+ \u2713 HR Office is ready.
1262
+ `);
1263
+ suppressNextHrRuntimeBanner = true;
1020
1264
  };
1021
1265
  const isHrRuntimeSelection = (selection) => selection?.kind === "profile" && selection.profile === "hr";
1022
1266
  const normalizeCsv = (value) => value.split(",").map((item) => item.trim()).filter(Boolean);
@@ -1186,10 +1430,44 @@ const maybeConfigureEnvrc = async (workspace, configRoot) => {
1186
1430
  rl.close();
1187
1431
  }
1188
1432
  };
1189
- const createPromptInterface = () => readline.createInterface({ input: process.stdin, output: process.stdout });
1433
+ const createPromptInterface = () => {
1434
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
1435
+ const terminal = interactive && shouldUseReadlineTerminal();
1436
+ if (terminal) {
1437
+ const resetSequence = interactivePromptResetSequence();
1438
+ if (resetSequence) process.stdout.write(resetSequence);
1439
+ }
1440
+ return readline.createInterface({
1441
+ input: process.stdin,
1442
+ output: process.stdout,
1443
+ terminal
1444
+ });
1445
+ };
1446
+ const scriptedPromptAnswers = (() => {
1447
+ const raw = process.env.OPENCODE_AGENTHUB_SCRIPTED_ANSWERS;
1448
+ if (!raw) return void 0;
1449
+ try {
1450
+ const parsed2 = JSON.parse(raw);
1451
+ return Array.isArray(parsed2) ? parsed2.map((value) => typeof value === "string" ? value : String(value)) : void 0;
1452
+ } catch {
1453
+ return raw.split("\n");
1454
+ }
1455
+ })();
1456
+ let scriptedPromptIndex = 0;
1457
+ const askPrompt = async (rl, question) => {
1458
+ if (scriptedPromptAnswers && scriptedPromptIndex < scriptedPromptAnswers.length) {
1459
+ const answer = scriptedPromptAnswers[scriptedPromptIndex++] || "";
1460
+ const sanitized = stripTerminalControlInput(answer);
1461
+ process.stdout.write(`${question}${sanitized}
1462
+ `);
1463
+ return sanitized;
1464
+ }
1465
+ return stripTerminalControlInput(await rl.question(question));
1466
+ };
1190
1467
  const promptRequired = async (rl, question, defaultValue) => {
1191
1468
  while (true) {
1192
- const answer = await rl.question(
1469
+ const answer = await askPrompt(
1470
+ rl,
1193
1471
  defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `
1194
1472
  );
1195
1473
  const value = normalizeOptional(answer) || defaultValue;
@@ -1198,14 +1476,16 @@ const promptRequired = async (rl, question, defaultValue) => {
1198
1476
  }
1199
1477
  };
1200
1478
  const promptOptional = async (rl, question, defaultValue) => {
1201
- const answer = await rl.question(
1479
+ const answer = await askPrompt(
1480
+ rl,
1202
1481
  defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `
1203
1482
  );
1204
1483
  return normalizeOptional(answer) || defaultValue;
1205
1484
  };
1206
1485
  const promptCsv = async (rl, question, defaultValues = []) => {
1207
1486
  const defaultValue = defaultValues.join(", ");
1208
- const answer = await rl.question(
1487
+ const answer = await askPrompt(
1488
+ rl,
1209
1489
  defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `
1210
1490
  );
1211
1491
  return normalizeCsv(answer || defaultValue);
@@ -1213,7 +1493,7 @@ const promptCsv = async (rl, question, defaultValues = []) => {
1213
1493
  const promptBoolean = async (rl, question, defaultValue) => {
1214
1494
  const suffix = defaultValue ? "[Y/n]" : "[y/N]";
1215
1495
  while (true) {
1216
- const answer = (await rl.question(`${question} ${suffix}: `)).trim().toLowerCase();
1496
+ const answer = (await askPrompt(rl, `${question} ${suffix}: `)).trim().toLowerCase();
1217
1497
  if (!answer) return defaultValue;
1218
1498
  if (answer === "y" || answer === "yes") return true;
1219
1499
  if (answer === "n" || answer === "no") return false;
@@ -1223,7 +1503,7 @@ const promptBoolean = async (rl, question, defaultValue) => {
1223
1503
  const promptChoice = async (rl, question, choices, defaultValue) => {
1224
1504
  const label = `${question} [${choices.join("/")}] (${defaultValue})`;
1225
1505
  while (true) {
1226
- const answer = (await rl.question(`${label}: `)).trim().toLowerCase();
1506
+ const answer = (await askPrompt(rl, `${label}: `)).trim().toLowerCase();
1227
1507
  if (!answer) return defaultValue;
1228
1508
  const match = choices.find((choice) => choice === answer);
1229
1509
  if (match) return match;
@@ -1238,7 +1518,10 @@ const promptIndexedChoice = async (rl, question, choices, defaultValue) => {
1238
1518
  });
1239
1519
  const defaultIndex = Math.max(choices.indexOf(defaultValue), 0) + 1;
1240
1520
  while (true) {
1241
- const answer = (await rl.question(`${question} [1-${choices.length}] (${defaultIndex}): `)).trim().toLowerCase();
1521
+ const answer = (await askPrompt(
1522
+ rl,
1523
+ `${question} [1-${choices.length}] (${defaultIndex}): `
1524
+ )).trim().toLowerCase();
1242
1525
  if (!answer) return defaultValue;
1243
1526
  const numeric = Number(answer);
1244
1527
  if (Number.isInteger(numeric) && numeric >= 1 && numeric <= choices.length) {
@@ -1522,7 +1805,8 @@ const createSkillDefinition = async (root, name, reservedOk = false) => {
1522
1805
  };
1523
1806
  const readProfileDefinition = async (root, name) => readJsonIfExists(path.join(root, "profiles", `${name}.json`));
1524
1807
  const promptRecord = async (rl, question) => {
1525
- const answer = await rl.question(
1808
+ const answer = await askPrompt(
1809
+ rl,
1526
1810
  `${question} (comma-separated key=value, blank to skip): `
1527
1811
  );
1528
1812
  const entries = normalizeCsv(answer);
@@ -2239,6 +2523,7 @@ if (parsed.command === "hr") {
2239
2523
  await ensureHrOfficeReadyOrBootstrap(resolveSelectedHomeRoot(parsed), {
2240
2524
  syncSourcesOnFirstRun: !parsed.assembleOnly
2241
2525
  });
2526
+ await repairHrModelConfigurationIfNeeded(resolveSelectedHomeRoot(parsed) || defaultHrHome());
2242
2527
  if (parsed.hrIntent?.kind === "office") {
2243
2528
  parsed.workspace = resolveSelectedHomeRoot(parsed) || defaultHrHome();
2244
2529
  } else if (parsed.hrIntent?.kind === "compose") {
@@ -2263,10 +2548,13 @@ if (parsed.command === "run" || parsed.command === "start" || parsed.command ===
2263
2548
  }
2264
2549
  const finalConfigRoot = resolveConfigRoot(parsed);
2265
2550
  const result = await composeSelection(parsed, finalConfigRoot);
2266
- printRuntimeBanner(
2267
- parsed.command === "hr" ? "HR Office" : "My Team",
2268
- resolveSelectedHomeRoot(parsed) || defaultAgentHubHome()
2269
- );
2551
+ if (!(parsed.command === "hr" && suppressNextHrRuntimeBanner)) {
2552
+ printRuntimeBanner(
2553
+ parsed.command === "hr" ? "HR Office" : "My Team",
2554
+ resolveSelectedHomeRoot(parsed) || defaultAgentHubHome()
2555
+ );
2556
+ }
2557
+ suppressNextHrRuntimeBanner = false;
2270
2558
  if (shouldChmod()) {
2271
2559
  await chmod(path.join(result.configRoot, "run.sh"), 493);
2272
2560
  }
@@ -5,6 +5,22 @@ 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 degradedLeadingCsiFragmentPattern = /^(?:(?:\d{1,3};){1,10}\d{1,3}m)+/;
13
+ const stripTerminalControlInput = (value) => value.replace(oscSequencePattern, "").replace(csiSequencePattern, "").replace(singleEscapePattern, "").replace(controlCharacterPattern, "").replace(degradedLeadingCsiFragmentPattern, "");
14
+ const interactivePromptResetSequence = (win = detectWindows()) => isWindows(win) ? [
15
+ "\x1B[?1000l",
16
+ "\x1B[?1001l",
17
+ "\x1B[?1002l",
18
+ "\x1B[?1003l",
19
+ "\x1B[?1005l",
20
+ "\x1B[?1006l",
21
+ "\x1B[?1015l"
22
+ ].join("") : "";
23
+ const shouldUseReadlineTerminal = (win = detectWindows()) => !isWindows(win);
8
24
  const generateRunScript = () => `#!/usr/bin/env bash
9
25
  set -euo pipefail
10
26
 
@@ -37,12 +53,15 @@ export {
37
53
  displayHomeConfigPath,
38
54
  generateRunCmd,
39
55
  generateRunScript,
56
+ interactivePromptResetSequence,
40
57
  isWindows,
41
58
  resolveHomeConfigRoot,
42
59
  resolvePythonCommand,
43
60
  shouldChmod,
44
61
  shouldOfferEnvrc,
62
+ shouldUseReadlineTerminal,
45
63
  spawnOptions,
64
+ stripTerminalControlInput,
46
65
  symlinkType,
47
66
  windowsStartupNotice
48
67
  };