@krtclcdy/tersio 1.0.2 → 1.0.4

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.
@@ -63,6 +63,16 @@ export default function comboToggleExtension(pi) {
63
63
  c.ui.setStatus("rtk", undefined);
64
64
  c.ui.setStatus("ponytail", undefined);
65
65
  }
66
+ // ponytail: same persistence as /combo — siblings (incl. upstream ponytail)
67
+ // restore from these entries, so the fallback must write them too or the
68
+ // preset evaporates on resume and ponytail never activates.
69
+ function persistPreset(level) {
70
+ const modes = COMBO_LEVELS[level];
71
+ pi.appendEntry?.("caveman-mode", { mode: modes.caveman });
72
+ pi.appendEntry?.("rtk-mode", { enabled: modes.rtk === "on" });
73
+ pi.appendEntry?.("ponytail-mode", { mode: modes.ponytail });
74
+ pi.appendEntry?.("combo-level", { level });
75
+ }
66
76
  function useState(state, ctx) {
67
77
  currentState = state;
68
78
  syncStatus(ctx);
@@ -99,12 +109,7 @@ export default function comboToggleExtension(pi) {
99
109
  ctx?.ui?.notify?.(`Unknown combo level: ${arg}. Use: off | medium | balanced | max`, "warning");
100
110
  return;
101
111
  }
102
- const modes = COMBO_LEVELS[level];
103
- // Persist per-extension state so they restore on session_start
104
- pi.appendEntry?.("caveman-mode", { mode: modes.caveman });
105
- pi.appendEntry?.("rtk-mode", { enabled: modes.rtk === "on" });
106
- pi.appendEntry?.("ponytail-mode", { mode: modes.ponytail });
107
- pi.appendEntry?.("combo-level", { level });
112
+ persistPreset(level);
108
113
  useState(setSharedComboLevel(level), ctx);
109
114
  ctx?.ui?.notify?.(`Combo ${level} applied: ${levelSummary(currentState)}`, "info");
110
115
  if (ctx?.reload)
@@ -122,6 +127,7 @@ export default function comboToggleExtension(pi) {
122
127
  if (getSharedComboState().level === "off" && !(ctx?.sessionManager?.getBranch?.() || ctx?.sessionManager?.getEntries?.() || []).length) {
123
128
  const fallback = readComboDefault();
124
129
  if (fallback !== "off") {
130
+ persistPreset(fallback);
125
131
  useState(setSharedComboLevel(fallback), ctx);
126
132
  ctx?.ui?.notify?.(`Combo default applied: ${fallback}`, "info");
127
133
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@krtclcdy/tersio",
3
- "version": "1.0.2",
4
- "description": "Amanai reward detector and three toggleable OMP extensions for terse replies, compact shell output, and minimal code decisions — with a manual updater and dry-run support.",
3
+ "version": "1.0.4",
4
+ "description": "Amanai reward detector and three toggleable OMP extensions for terse replies, compact shell output, and minimal code decisions \u2014 with a manual updater and dry-run support.",
5
5
  "keywords": [
6
6
  "omp",
7
7
  "tersio",
@@ -94,6 +94,18 @@
94
94
  "type": "boolean",
95
95
  "description": "RTK mode applied on session start when no session state exists",
96
96
  "default": false
97
+ },
98
+ "ponytailDefault": {
99
+ "type": "enum",
100
+ "description": "Ponytail mode applied on session start when no session state exists (follows combo preset unless overridden)",
101
+ "values": [
102
+ "off",
103
+ "lite",
104
+ "full",
105
+ "ultra",
106
+ "review"
107
+ ],
108
+ "default": "off"
97
109
  }
98
110
  }
99
111
  },
package/tersio.js CHANGED
@@ -19,6 +19,14 @@ const { version: PACKAGE_VERSION } = createRequire(import.meta.url)("./package.j
19
19
  // Session-start mode defaults for fresh installs.
20
20
  const COMBO_DEFAULTS = { off: true, medium: true, balanced: true, max: true };
21
21
  const CAVEMAN_DEFAULTS = { off: true, lite: true, full: true, ultra: true, wenyan: true };
22
+ // ponytail: Combo preset implies all three modes. Mirrors COMBO_LEVELS in
23
+ // extensions/shared/session-state.ts (rtk as boolean here). Single source.
24
+ const COMBO_PRESET_MODES = {
25
+ off: { caveman: "off", rtk: false, ponytail: "off" },
26
+ medium: { caveman: "lite", rtk: true, ponytail: "lite" },
27
+ balanced: { caveman: "full", rtk: true, ponytail: "full" },
28
+ max: { caveman: "ultra", rtk: true, ponytail: "ultra" },
29
+ };
22
30
  function parseEnum(value, valid, flag) {
23
31
  if (value === undefined)
24
32
  return undefined;
@@ -84,9 +92,11 @@ Commands:
84
92
 
85
93
  Options:
86
94
  --scope user|project|both
87
- --combo-default off|medium|balanced|max
88
- --caveman-default off|lite|full|ultra|wenyan
89
- --rtk-default on|off
95
+ --remove-ponytail (uninstall: also remove the Ponytail plugin)
96
+ --remove-rtk (uninstall: also remove the RTK binary)
97
+ --combo-default off|medium|balanced|max (implies caveman, rtk, ponytail)
98
+ --caveman-default off|lite|full|ultra|wenyan (override; default follows combo)
99
+ --rtk-default on|off (override; default follows combo)
90
100
  --yes, -y
91
101
  --dry-run
92
102
  --verbose
@@ -165,8 +175,8 @@ async function ensureExtensionInConfig(configPath, extensionPath, label, options
165
175
  console.log(` [dry-run] would add ${label} to config.yml: ${normalizedPath}`);
166
176
  return true;
167
177
  }
168
- // Handle "extensions: []" (empty YAML array)
169
- const emptyArrayIdx = lines.findIndex((l) => /^\s*extensions\s*:\s*\[\s*\]\s*$/i.test(l));
178
+ // ponytail: omp ships "extensions: null"; appending list items under a null scalar breaks YAML parsing. Normalize null/~/[]/empty to a mapping key first.
179
+ const emptyArrayIdx = lines.findIndex((l) => /^\s*extensions\s*:\s*(?:\[\s*\]|null|~)?\s*$/i.test(l));
170
180
  if (emptyArrayIdx !== -1) {
171
181
  lines[emptyArrayIdx] = "extensions:";
172
182
  lines.splice(emptyArrayIdx + 1, 0, line, "");
@@ -210,6 +220,8 @@ async function ensureExtensionAfterConfigEntry(configPath, extensionPath, afterP
210
220
  lines.push("extensions:", line, "");
211
221
  }
212
222
  else {
223
+ if (/^\s*extensions\s*:\s*(?:\[\s*\]|null|~)?\s*$/i.test(lines[extensionsIndex]))
224
+ lines[extensionsIndex] = "extensions:";
213
225
  lines.splice(extensionsIndex + 1, 0, line);
214
226
  }
215
227
  }
@@ -790,6 +802,8 @@ async function runUninstall(options = {}) {
790
802
  const extDir = path.join(HOME, ".omp", "agent", "extensions");
791
803
  const configPath = path.join(HOME, ".omp", "agent", "config.yml");
792
804
  const rtkBin = path.join(HOME, ".bun", "bin", IS_WINDOWS ? "rtk.exe" : "rtk");
805
+ const pluginsDir = path.join(HOME, ".omp", "plugins");
806
+ const ponytailPkgDir = path.join(pluginsDir, "node_modules", "@dietrichgebert", "ponytail");
793
807
  const targets = [
794
808
  path.join(extDir, "caveman-session"),
795
809
  path.join(extDir, "rtk-session"),
@@ -797,6 +811,8 @@ async function runUninstall(options = {}) {
797
811
  path.join(extDir, "combo-toggle"),
798
812
  path.join(extDir, "shared"),
799
813
  path.join(extDir, "amanai-reward"),
814
+ // Only consumed by ai-addons-updater (removed above); otherwise orphaned.
815
+ path.join(extDir, "lib"),
800
816
  // Legacy always-on combo helper; imports shared/session-state.js, so it
801
817
  // breaks with a module-not-found warning once the shared dir is removed.
802
818
  path.join(extDir, "aaa-combo-boot"),
@@ -805,6 +821,9 @@ async function runUninstall(options = {}) {
805
821
  for (const t of targets) {
806
822
  console.log(` ${t}`);
807
823
  }
824
+ if (shouldRemovePonytail) {
825
+ console.log(` ${ponytailPkgDir} (ponytail plugin package)`);
826
+ }
808
827
  if (shouldRemoveRtk) {
809
828
  console.log(` ${rtkBin}`);
810
829
  }
@@ -851,8 +870,62 @@ async function runUninstall(options = {}) {
851
870
  }
852
871
  }
853
872
  }
873
+ // Remove the Ponytail plugin package the installer added (dep, files,
874
+ // lock entry); the config.yml entry was filtered above.
875
+ if (shouldRemovePonytail) {
876
+ const pluginsPkgPath = path.join(pluginsDir, "package.json");
877
+ const ponytailRaw = await readTextIfExists(pluginsPkgPath);
878
+ if (ponytailRaw) {
879
+ try {
880
+ const pkg = JSON.parse(ponytailRaw);
881
+ if (pkg.dependencies && "@dietrichgebert/ponytail" in pkg.dependencies) {
882
+ if (shouldDryRun)
883
+ console.log(` [dry-run] would remove @dietrichgebert/ponytail from ${pluginsPkgPath}`);
884
+ else {
885
+ delete pkg.dependencies["@dietrichgebert/ponytail"];
886
+ await fs.writeFile(pluginsPkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
887
+ console.log(" [write] Removed @dietrichgebert/ponytail from plugins/package.json");
888
+ }
889
+ }
890
+ }
891
+ catch {
892
+ debug("Could not update plugins/package.json for ponytail");
893
+ }
894
+ }
895
+ try {
896
+ if (shouldDryRun)
897
+ console.log(` [dry-run] would remove ${ponytailPkgDir}`);
898
+ else {
899
+ await fs.rm(ponytailPkgDir, { recursive: true, force: true });
900
+ console.log(` [rm] ${ponytailPkgDir}`);
901
+ await fs.rm(path.dirname(ponytailPkgDir));
902
+ console.log(` [rm] ${path.dirname(ponytailPkgDir)} (empty scope)`);
903
+ }
904
+ }
905
+ catch {
906
+ debug("Could not remove ponytail package dir (scope may hold other packages)");
907
+ }
908
+ const lockPath = path.join(pluginsDir, "omp-plugins.lock.json");
909
+ const lockRaw = await readTextIfExists(lockPath);
910
+ if (lockRaw) {
911
+ try {
912
+ const lock = JSON.parse(lockRaw);
913
+ if (lock.plugins && "@dietrichgebert/ponytail" in lock.plugins) {
914
+ if (shouldDryRun)
915
+ console.log(` [dry-run] would remove @dietrichgebert/ponytail from ${lockPath}`);
916
+ else {
917
+ delete lock.plugins["@dietrichgebert/ponytail"];
918
+ await fs.writeFile(lockPath, JSON.stringify(lock, null, 2) + "\n", "utf8");
919
+ console.log(` [write] Removed @dietrichgebert/ponytail from ${lockPath}`);
920
+ }
921
+ }
922
+ }
923
+ catch {
924
+ debug("Could not update omp-plugins.lock.json for ponytail");
925
+ }
926
+ }
927
+ }
854
928
  // Remove our plugin registration from ~/.omp/plugins
855
- const pluginsDir = path.join(HOME, ".omp", "plugins");
856
929
  const pluginsPkgPath = path.join(pluginsDir, "package.json");
857
930
  const pluginsPkgRaw = await readTextIfExists(pluginsPkgPath);
858
931
  if (pluginsPkgRaw) {
@@ -958,6 +1031,7 @@ function defaultProfile() {
958
1031
  comboDefault: "off",
959
1032
  cavemanDefault: "off",
960
1033
  rtkDefault: false,
1034
+ ponytailDefault: "off",
961
1035
  };
962
1036
  }
963
1037
  function tty() {
@@ -965,29 +1039,51 @@ function tty() {
965
1039
  }
966
1040
  async function resolveProfile() {
967
1041
  const profile = defaultProfile();
968
- // Interactive prompt: only for a real user at a terminal, only when no
969
- // default flags were given, and never for --apply-update runs.
1042
+ // Single interactive prompt: the Combo preset implies all three modes.
1043
+ // Only for a real user at a terminal, only when no default flags were
1044
+ // given, and never for --apply-update runs.
970
1045
  if (tty() && !profileFlagsGiven && !applyUpdate && (install || reinstall)) {
971
1046
  const comboAnswer = (await ask(" Default Combo preset on session start? [off/medium/balanced/max] (off): ")).trim().toLowerCase();
972
1047
  if (comboAnswer && comboAnswer in COMBO_DEFAULTS)
973
1048
  profile.comboDefault = comboAnswer;
974
- const cavemanAnswer = (await ask(" Default Caveman mode on session start? [off/lite/full/ultra/wenyan] (off): ")).trim().toLowerCase();
975
- if (cavemanAnswer && cavemanAnswer in CAVEMAN_DEFAULTS)
976
- profile.cavemanDefault = cavemanAnswer;
977
- const rtkAnswer = (await ask(" Default RTK state on session start? [on/off] (off): ")).trim().toLowerCase();
978
- if (rtkAnswer === "on" || rtkAnswer === "off")
979
- profile.rtkDefault = rtkAnswer === "on";
980
- }
981
- // Explicit flags always win over the prompt.
1049
+ }
1050
+ // Explicit flags always win. Caveman/RTK flags override the Combo preset.
982
1051
  if (comboDefaultFlag !== undefined)
983
1052
  profile.comboDefault = comboDefaultFlag;
1053
+ const preset = COMBO_PRESET_MODES[profile.comboDefault] ?? COMBO_PRESET_MODES.off;
1054
+ profile.cavemanDefault = preset.caveman;
1055
+ profile.rtkDefault = preset.rtk;
1056
+ profile.ponytailDefault = preset.ponytail;
984
1057
  if (cavemanDefaultFlag !== undefined)
985
1058
  profile.cavemanDefault = cavemanDefaultFlag;
986
1059
  if (rtkDefaultFlag !== undefined)
987
1060
  profile.rtkDefault = rtkDefaultFlag === "on";
988
- console.log(` Profile: combo default=${profile.comboDefault} · caveman default=${profile.cavemanDefault} · rtk default=${profile.rtkDefault ? "on" : "off"}`);
1061
+ console.log(` Profile: combo default=${profile.comboDefault} (caveman=${profile.cavemanDefault} · rtk=${profile.rtkDefault ? "on" : "off"} · ponytail=${profile.ponytailDefault})`);
989
1062
  return profile;
990
1063
  }
1064
+ // ponytail: post-install guard. List entries under `extensions: null` break
1065
+ // OMP launch, so fail loudly here instead of at the next OMP start.
1066
+ async function validateConfigExtensions(agentDir, options) {
1067
+ if (options.dryRun)
1068
+ return;
1069
+ const raw = await readTextIfExists(path.join(agentDir, "config.yml"));
1070
+ if (!raw)
1071
+ return;
1072
+ const lines = raw.split("\n");
1073
+ const keyIdx = lines.findIndex((l) => /^\s*extensions\s*:/i.test(l));
1074
+ if (keyIdx === -1)
1075
+ return;
1076
+ const scalarNull = /^\s*extensions\s*:\s*(null|~|\[\s*\])\s*$/i.test(lines[keyIdx]);
1077
+ const hasEntries = lines.slice(keyIdx + 1).some((l) => /^\s*-\s+\S/.test(l));
1078
+ if (scalarNull && hasEntries) {
1079
+ console.log(" [fail] config.yml lists extensions under `extensions: null` — OMP will fail to launch.");
1080
+ console.log(" [hint] Replace the `extensions: null` line with `extensions:`, then reinstall.");
1081
+ process.exitCode = 1;
1082
+ }
1083
+ else {
1084
+ console.log(" [ok] config.yml extensions key valid");
1085
+ }
1086
+ }
991
1087
  // Persist the profile as omp plugin settings so `omp plugin config get`
992
1088
  // reflects the choice and the extensions pick it up on session start.
993
1089
  async function writePluginSettings(profile, options) {
@@ -1009,6 +1105,7 @@ async function writePluginSettings(profile, options) {
1009
1105
  settings.comboDefault = profile.comboDefault;
1010
1106
  settings.cavemanDefault = profile.cavemanDefault;
1011
1107
  settings.rtkDefault = profile.rtkDefault;
1108
+ settings.ponytailDefault = profile.ponytailDefault;
1012
1109
  config.settings[PACKAGE_NAME] = settings;
1013
1110
  if (options.dryRun) {
1014
1111
  console.log(` [dry-run] would write plugin settings (${PACKAGE_NAME}) to ${lockPath}`);
@@ -1120,6 +1217,7 @@ async function main() {
1120
1217
  await stepAmanaiReward(userExtDir, options, selfPlugin);
1121
1218
  if (selfPlugin)
1122
1219
  await writePluginSettings(profile, options);
1220
+ await validateConfigExtensions(userDir, options);
1123
1221
  }
1124
1222
  if (scope === "2" || scope === "3") {
1125
1223
  console.log("\n--- Project-level install ---");