@krtclcdy/tersio 2.25.5 → 2.25.7

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.
Files changed (231) hide show
  1. package/README.md +42 -38
  2. package/dashboard/dist/index.html +15 -15
  3. package/dist/bench/code-out/pfull-0/debounce.js +13 -0
  4. package/dist/bench/code-out/pfull-0/deepClone.js +18 -0
  5. package/dist/bench/code-out/pfull-0/groupBy.js +15 -0
  6. package/dist/bench/code-out/pfull-1/debounce.js +13 -0
  7. package/dist/bench/code-out/pfull-1/deepClone.js +17 -0
  8. package/dist/bench/code-out/pfull-1/groupBy.js +14 -0
  9. package/dist/bench/code-out/pfull-2/debounce.js +13 -0
  10. package/dist/bench/code-out/pfull-2/deepClone.js +18 -0
  11. package/dist/bench/code-out/pfull-2/groupBy.js +15 -0
  12. package/dist/bench/code-out/pfull-3/debounce.js +12 -0
  13. package/dist/bench/code-out/pfull-3/deepClone.js +17 -0
  14. package/dist/bench/code-out/pfull-3/groupBy.js +7 -0
  15. package/dist/bench/code-out/pfull-4/debounce.js +10 -0
  16. package/dist/bench/code-out/pfull-4/deepClone.js +17 -0
  17. package/dist/bench/code-out/pfull-4/groupBy.js +14 -0
  18. package/dist/bench/code-out/plite-0/debounce.js +13 -0
  19. package/dist/bench/code-out/plite-0/deepClone.js +18 -0
  20. package/dist/bench/code-out/plite-0/groupBy.js +15 -0
  21. package/dist/bench/code-out/plite-1/debounce.js +13 -0
  22. package/dist/bench/code-out/plite-1/deepClone.js +17 -0
  23. package/dist/bench/code-out/plite-1/groupBy.js +11 -0
  24. package/dist/bench/code-out/plite-2/debounce.js +12 -0
  25. package/dist/bench/code-out/plite-2/deepClone.js +17 -0
  26. package/dist/bench/code-out/plite-2/groupBy.js +14 -0
  27. package/dist/bench/code-out/plite-3/debounce.js +12 -0
  28. package/dist/bench/code-out/plite-3/deepClone.js +17 -0
  29. package/dist/bench/code-out/plite-3/groupBy.js +14 -0
  30. package/dist/bench/code-out/plite-4/debounce.js +12 -0
  31. package/dist/bench/code-out/plite-4/deepClone.js +17 -0
  32. package/dist/bench/code-out/plite-4/groupBy.js +14 -0
  33. package/dist/bench/code-out/pultra-0/debounce.js +12 -0
  34. package/dist/bench/code-out/pultra-0/deepClone.js +20 -0
  35. package/dist/bench/code-out/pultra-0/groupBy.js +14 -0
  36. package/dist/bench/code-out/pultra-1/debounce.js +10 -0
  37. package/dist/bench/code-out/pultra-1/deepClone.js +17 -0
  38. package/dist/bench/code-out/pultra-1/groupBy.js +7 -0
  39. package/dist/bench/code-out/pultra-2/debounce.js +13 -0
  40. package/dist/bench/code-out/pultra-2/deepClone.js +18 -0
  41. package/dist/bench/code-out/pultra-2/groupBy.js +15 -0
  42. package/dist/bench/code-out/pultra-3/debounce.js +13 -0
  43. package/dist/bench/code-out/pultra-3/deepClone.js +18 -0
  44. package/dist/bench/code-out/pultra-3/groupBy.js +15 -0
  45. package/dist/bench/code-out/pultra-4/debounce.js +13 -0
  46. package/dist/bench/code-out/pultra-4/deepClone.js +18 -0
  47. package/dist/bench/code-out/pultra-4/groupBy.js +15 -0
  48. package/dist/bench/code-out/run-0/debounce.js +12 -0
  49. package/dist/bench/code-out/run-0/deepClone.js +17 -0
  50. package/dist/bench/code-out/run-0/groupBy.js +14 -0
  51. package/dist/bench/code-out/run-1/debounce.js +13 -0
  52. package/dist/bench/code-out/run-1/deepClone.js +15 -0
  53. package/dist/bench/code-out/run-1/groupBy.js +13 -0
  54. package/dist/bench/code-out/run-2/debounce.js +12 -0
  55. package/dist/bench/code-out/run-2/deepClone.js +18 -0
  56. package/dist/bench/code-out/run-2/groupBy.js +13 -0
  57. package/dist/bench/code-out/run-3/debounce.js +12 -0
  58. package/dist/bench/code-out/run-3/deepClone.js +18 -0
  59. package/dist/bench/code-out/run-3/groupBy.js +14 -0
  60. package/dist/bench/code-out/run-4/debounce.js +12 -0
  61. package/dist/bench/code-out/run-4/deepClone.js +16 -0
  62. package/dist/bench/code-out/run-4/groupBy.js +13 -0
  63. package/dist/cli/backups.js +7 -14
  64. package/dist/cli/banner.js +3 -3
  65. package/dist/cli/common.js +29 -13
  66. package/dist/cli/currency.js +1 -1
  67. package/dist/cli/dashboard.js +66 -35
  68. package/dist/cli/doctor-fix.js +204 -19
  69. package/dist/cli/doctor.js +263 -40
  70. package/dist/cli/hosts.js +25 -6
  71. package/dist/cli/install.js +150 -49
  72. package/dist/cli/interactive.js +3 -3
  73. package/dist/cli/manifest.js +109 -9
  74. package/dist/cli/profile.js +13 -8
  75. package/dist/cli/rtk-wiring.js +1 -1
  76. package/dist/cli/settings.js +20 -5
  77. package/dist/cli/uninstall.js +4 -4
  78. package/dist/cli/update.js +6 -7
  79. package/dist/cli/usage.js +6 -9
  80. package/dist/extensions/caveman/index.js +10 -4
  81. package/dist/extensions/caveman/rule-lite.md +77 -0
  82. package/dist/extensions/caveman/rule-megacave-lite.md +59 -0
  83. package/dist/extensions/caveman/rule-megacave-ultra.md +59 -0
  84. package/dist/extensions/caveman/rule-megacave.md +5 -3
  85. package/dist/extensions/caveman/rule-ultra.md +4 -4
  86. package/dist/extensions/caveman/rule.md +55 -45
  87. package/dist/extensions/combo/index.js +85 -25
  88. package/dist/extensions/commands/index.js +1 -1
  89. package/dist/extensions/pstack/index.js +121 -0
  90. package/dist/extensions/pstack/rule.md +141 -0
  91. package/dist/extensions/rtk/filter.js +0 -1
  92. package/dist/extensions/rtk/session.js +20 -12
  93. package/dist/extensions/server.js +90 -6
  94. package/dist/extensions/shared/agent-stats.js +214 -278
  95. package/dist/extensions/shared/brand-marks.js +33 -34
  96. package/dist/extensions/shared/carbon.js +1 -1
  97. package/dist/extensions/shared/plugin-settings.js +11 -6
  98. package/dist/extensions/shared/pricing.js +8 -16
  99. package/dist/extensions/shared/rtk-eligible.js +84 -0
  100. package/dist/extensions/shared/rtk-gain.js +5 -10
  101. package/dist/extensions/shared/session-state.js +15 -10
  102. package/dist/extensions/shared/sqlite.js +162 -0
  103. package/dist/extensions/shared/status.js +8 -9
  104. package/dist/extensions/shared/types.js +1 -1
  105. package/dist/extensions/shared/usage-ledger.js +155 -61
  106. package/dist/extensions/shared/usage-store.js +111 -52
  107. package/dist/extensions/shared/utils.js +5 -5
  108. package/dist/extensions/timing/index.js +31 -0
  109. package/dist/extensions/updater/index.js +3 -6
  110. package/dist/tersio.js +8 -3
  111. package/extensions/caveman/index.ts +10 -4
  112. package/extensions/caveman/rule-lite.md +77 -0
  113. package/extensions/caveman/rule-megacave-lite.md +59 -0
  114. package/extensions/caveman/rule-megacave-ultra.md +59 -0
  115. package/extensions/caveman/rule-megacave.md +5 -3
  116. package/extensions/caveman/rule-ultra.md +4 -4
  117. package/extensions/caveman/rule.md +55 -45
  118. package/extensions/combo/index.ts +86 -23
  119. package/extensions/commands/index.ts +1 -1
  120. package/extensions/pstack/index.ts +130 -0
  121. package/extensions/pstack/playbooks/authoring-a-skill.md +12 -0
  122. package/extensions/pstack/playbooks/autonomous-run.md +13 -0
  123. package/extensions/pstack/playbooks/autopilot-full.md +13 -0
  124. package/extensions/pstack/playbooks/autopilot-stack.md +16 -0
  125. package/extensions/pstack/playbooks/babysit.md +23 -0
  126. package/extensions/pstack/playbooks/bug-fix.md +15 -0
  127. package/extensions/pstack/playbooks/eval.md +25 -0
  128. package/extensions/pstack/playbooks/feature.md +21 -0
  129. package/extensions/pstack/playbooks/hillclimb.md +21 -0
  130. package/extensions/pstack/playbooks/investigation.md +14 -0
  131. package/extensions/pstack/playbooks/multi-phase-plan.md +155 -0
  132. package/extensions/pstack/playbooks/opening-a-pr.md +34 -0
  133. package/extensions/pstack/playbooks/orchestrate.md +111 -0
  134. package/extensions/pstack/playbooks/pause-safely.md +10 -0
  135. package/extensions/pstack/playbooks/perf-issue.md +25 -0
  136. package/extensions/pstack/playbooks/prototype.md +14 -0
  137. package/extensions/pstack/playbooks/refactoring.md +16 -0
  138. package/extensions/pstack/playbooks/runtime-forensics.md +11 -0
  139. package/extensions/pstack/playbooks/session-pickup.md +11 -0
  140. package/extensions/pstack/playbooks/shipping.md +17 -0
  141. package/extensions/pstack/playbooks/trace-forensics.md +14 -0
  142. package/extensions/pstack/playbooks/visual-parity.md +11 -0
  143. package/extensions/pstack/playbooks/worktree-cleanup.md +12 -0
  144. package/extensions/pstack/principles/attack-the-premise.md +22 -0
  145. package/extensions/pstack/principles/boundary-discipline.md +33 -0
  146. package/extensions/pstack/principles/build-the-lever.md +22 -0
  147. package/extensions/pstack/principles/encode-lessons-in-structure.md +10 -0
  148. package/extensions/pstack/principles/exhaust-the-design-space.md +8 -0
  149. package/extensions/pstack/principles/experience-first.md +18 -0
  150. package/extensions/pstack/principles/explain-the-number.md +16 -0
  151. package/extensions/pstack/principles/fix-root-causes.md +23 -0
  152. package/extensions/pstack/principles/foundational-thinking.md +20 -0
  153. package/extensions/pstack/principles/guard-the-context-window.md +11 -0
  154. package/extensions/pstack/principles/laziness-protocol.md +9 -0
  155. package/extensions/pstack/principles/make-operations-idempotent.md +23 -0
  156. package/extensions/pstack/principles/migrate-callers-then-delete-legacy-apis.md +21 -0
  157. package/extensions/pstack/principles/minimize-reader-load.md +17 -0
  158. package/extensions/pstack/principles/model-the-domain.md +23 -0
  159. package/extensions/pstack/principles/never-block-on-the-human.md +14 -0
  160. package/extensions/pstack/principles/outcome-oriented-execution.md +20 -0
  161. package/extensions/pstack/principles/prove-it-works.md +21 -0
  162. package/extensions/pstack/principles/redesign-from-first-principles.md +15 -0
  163. package/extensions/pstack/principles/separate-before-serializing-shared-state.md +15 -0
  164. package/extensions/pstack/principles/sequence-verifiable-units.md +16 -0
  165. package/extensions/pstack/principles/subtract-before-you-add.md +17 -0
  166. package/extensions/pstack/principles/test-behavior-not-implementation.md +20 -0
  167. package/extensions/pstack/principles/type-system-discipline.md +17 -0
  168. package/extensions/pstack/references/bugbot-triage.md +142 -0
  169. package/extensions/pstack/rule.md +141 -0
  170. package/extensions/pstack/skills/architect/SKILL.md +78 -0
  171. package/extensions/pstack/skills/architect/references/design-red-flags.md +57 -0
  172. package/extensions/pstack/skills/architect/references/rationale-template.md +35 -0
  173. package/extensions/pstack/skills/architect/references/runner-prompt.md +20 -0
  174. package/extensions/pstack/skills/arena/SKILL.md +47 -0
  175. package/extensions/pstack/skills/benchmark-checklist/SKILL.md +38 -0
  176. package/extensions/pstack/skills/blast-radius/SKILL.md +49 -0
  177. package/extensions/pstack/skills/bro/SKILL.md +6 -0
  178. package/extensions/pstack/skills/correct/SKILL.md +31 -0
  179. package/extensions/pstack/skills/figure-it-out/SKILL.md +51 -0
  180. package/extensions/pstack/skills/how/SKILL.md +35 -0
  181. package/extensions/pstack/skills/how/references/explainer-prompt.md +55 -0
  182. package/extensions/pstack/skills/how/references/explorer-prompt.md +35 -0
  183. package/extensions/pstack/skills/interrogate/SKILL.md +73 -0
  184. package/extensions/pstack/skills/interrogate/references/code-quality-review.md +47 -0
  185. package/extensions/pstack/skills/interrogate/references/lead-judgment.md +58 -0
  186. package/extensions/pstack/skills/interrogate/references/reviewer-prompt.md +62 -0
  187. package/extensions/pstack/skills/interrogate/references/rubric.md +77 -0
  188. package/extensions/pstack/skills/no-comments/SKILL.md +23 -0
  189. package/extensions/pstack/skills/reflect/SKILL.md +66 -0
  190. package/extensions/pstack/skills/reflect/references/divergent-reviewer.md +38 -0
  191. package/extensions/pstack/skills/reflect/references/judgment-reviewer.md +37 -0
  192. package/extensions/pstack/skills/reflect/references/synthesizer.md +56 -0
  193. package/extensions/pstack/skills/reflect/references/tooling-reviewer.md +48 -0
  194. package/extensions/pstack/skills/show-me-your-work/SKILL.md +81 -0
  195. package/extensions/pstack/skills/swarm/SKILL.md +45 -0
  196. package/extensions/pstack/skills/tdd/SKILL.md +41 -0
  197. package/extensions/pstack/skills/technical-writing/SKILL.md +105 -0
  198. package/extensions/pstack/skills/unslop/SKILL.md +61 -0
  199. package/extensions/pstack/skills/why/SKILL.md +97 -0
  200. package/extensions/pstack/skills/why/references/epistemics.md +71 -0
  201. package/extensions/pstack/skills/why/references/investigator-prompt.md +84 -0
  202. package/extensions/pstack/skills/why/references/source-playbook.md +17 -0
  203. package/extensions/pstack/skills/why/references/sources/code-archaeology.md +51 -0
  204. package/extensions/pstack/skills/why/references/sources/databricks.md +70 -0
  205. package/extensions/pstack/skills/why/references/sources/datadog.md +97 -0
  206. package/extensions/pstack/skills/why/references/sources/incident-postmortem.md +5 -0
  207. package/extensions/pstack/skills/why/references/sources/linear.md +48 -0
  208. package/extensions/pstack/skills/why/references/sources/notion.md +55 -0
  209. package/extensions/pstack/skills/why/references/sources/sentry.md +66 -0
  210. package/extensions/pstack/skills/why/references/sources/slack.md +17 -0
  211. package/extensions/pstack/skills/why/references/synthesizer-prompt.md +129 -0
  212. package/extensions/rtk/filter.ts +0 -1
  213. package/extensions/rtk/session.ts +19 -11
  214. package/extensions/server.ts +72 -7
  215. package/extensions/shared/agent-stats.ts +226 -278
  216. package/extensions/shared/brand-marks.ts +34 -34
  217. package/extensions/shared/carbon.ts +1 -1
  218. package/extensions/shared/plugin-settings.ts +12 -6
  219. package/extensions/shared/pricing.ts +10 -26
  220. package/extensions/shared/rtk-eligible.ts +83 -0
  221. package/extensions/shared/rtk-gain.ts +5 -10
  222. package/extensions/shared/session-state.ts +18 -13
  223. package/extensions/shared/sqlite.ts +162 -0
  224. package/extensions/shared/status.ts +9 -9
  225. package/extensions/shared/types.ts +2 -1
  226. package/extensions/shared/usage-ledger.ts +157 -66
  227. package/extensions/shared/usage-store.ts +120 -52
  228. package/extensions/shared/utils.ts +5 -5
  229. package/extensions/timing/index.ts +44 -0
  230. package/extensions/updater/index.ts +3 -8
  231. package/package.json +21 -9
@@ -0,0 +1,13 @@
1
+ // Debounce: fires fn once, ms after the last call, using that last call's arguments. A call during the wait restarts it.
2
+ export function debounce(fn, ms) {
3
+ let timer;
4
+ return (...args) => {
5
+ if (timer !== undefined) {
6
+ clearTimeout(timer);
7
+ }
8
+ timer = setTimeout(() => {
9
+ timer = undefined;
10
+ fn(...args);
11
+ }, ms);
12
+ };
13
+ }
@@ -0,0 +1,18 @@
1
+ // deepClone: recursive clone of plain objects, arrays, Date, and primitives. Shares no mutable structure with the input; no structuredClone or JSON round-trip.
2
+ export function deepClone(value) {
3
+ if (value === null || typeof value !== 'object') {
4
+ return value;
5
+ }
6
+ if (value instanceof Date) {
7
+ return new Date(value.getTime());
8
+ }
9
+ if (Array.isArray(value)) {
10
+ return value.map((item) => deepClone(item));
11
+ }
12
+ const source = value;
13
+ const clone = {};
14
+ for (const key of Object.keys(source)) {
15
+ clone[key] = deepClone(source[key]);
16
+ }
17
+ return clone;
18
+ }
@@ -0,0 +1,15 @@
1
+ // groupBy: buckets items by keyFn. Keys land in first-seen order and each bucket keeps the input order.
2
+ export function groupBy(items, keyFn) {
3
+ const groups = {};
4
+ for (const item of items) {
5
+ const key = keyFn(item);
6
+ const bucket = groups[key];
7
+ if (bucket === undefined) {
8
+ groups[key] = [item];
9
+ }
10
+ else {
11
+ bucket.push(item);
12
+ }
13
+ }
14
+ return groups;
15
+ }
@@ -0,0 +1,12 @@
1
+ export function debounce(fn, ms) {
2
+ let timer;
3
+ return (...args) => {
4
+ if (timer !== undefined) {
5
+ clearTimeout(timer);
6
+ }
7
+ timer = setTimeout(() => {
8
+ timer = undefined;
9
+ fn(...args);
10
+ }, ms);
11
+ };
12
+ }
@@ -0,0 +1,17 @@
1
+ export function deepClone(value) {
2
+ if (value === null || typeof value !== 'object') {
3
+ return value;
4
+ }
5
+ if (value instanceof Date) {
6
+ return new Date(value.getTime());
7
+ }
8
+ if (Array.isArray(value)) {
9
+ return value.map((item) => deepClone(item));
10
+ }
11
+ const source = value;
12
+ const clone = {};
13
+ for (const key of Object.keys(source)) {
14
+ clone[key] = deepClone(source[key]);
15
+ }
16
+ return clone;
17
+ }
@@ -0,0 +1,14 @@
1
+ export function groupBy(items, keyFn) {
2
+ const groups = {};
3
+ for (const item of items) {
4
+ const key = keyFn(item);
5
+ const existing = groups[key];
6
+ if (existing === undefined) {
7
+ groups[key] = [item];
8
+ }
9
+ else {
10
+ existing.push(item);
11
+ }
12
+ }
13
+ return groups;
14
+ }
@@ -0,0 +1,13 @@
1
+ // Debounce: the returned wrapper fires fn once, after ms milliseconds of quiet, using the last call's arguments.
2
+ export function debounce(fn, ms) {
3
+ let timer;
4
+ // Each call rebinds args, so the closure the timeout reads is always the latest one.
5
+ return (...args) => {
6
+ if (timer !== undefined)
7
+ clearTimeout(timer);
8
+ timer = setTimeout(() => {
9
+ timer = undefined;
10
+ fn(...args);
11
+ }, ms);
12
+ };
13
+ }
@@ -0,0 +1,15 @@
1
+ // deepClone: recursive clone for plain objects, arrays, Date, and primitives. No structuredClone or JSON round-trip.
2
+ export function deepClone(value) {
3
+ if (value === null || typeof value !== 'object')
4
+ return value;
5
+ if (value instanceof Date)
6
+ return new Date(value.getTime());
7
+ if (Array.isArray(value))
8
+ return value.map((item) => deepClone(item));
9
+ const source = value;
10
+ const clone = {};
11
+ for (const key of Object.keys(source)) {
12
+ clone[key] = deepClone(source[key]);
13
+ }
14
+ return clone;
15
+ }
@@ -0,0 +1,13 @@
1
+ // groupBy: buckets items by keyFn. Keys land in first-seen order and each bucket keeps the input order.
2
+ export function groupBy(items, keyFn) {
3
+ const groups = {};
4
+ for (const item of items) {
5
+ const key = keyFn(item);
6
+ const bucket = groups[key];
7
+ if (bucket === undefined)
8
+ groups[key] = [item];
9
+ else
10
+ bucket.push(item);
11
+ }
12
+ return groups;
13
+ }
@@ -0,0 +1,12 @@
1
+ // Trailing-edge debounce: `fn` runs only once `ms` passes with no further call, and always with the last call's arguments.
2
+ export function debounce(fn, ms) {
3
+ let timer;
4
+ return (...args) => {
5
+ if (timer !== undefined)
6
+ clearTimeout(timer);
7
+ timer = setTimeout(() => {
8
+ timer = undefined;
9
+ fn(...args);
10
+ }, ms);
11
+ };
12
+ }
@@ -0,0 +1,18 @@
1
+ // Recursive structural copy: plain objects, arrays, Dates, and primitives. No shared mutable structure with the input.
2
+ export function deepClone(value) {
3
+ if (value === null || typeof value !== 'object')
4
+ return value;
5
+ // SAFETY: a Date owns no enumerable props, so it must be rebuilt from its epoch ms or it clones to `{}`.
6
+ if (value instanceof Date)
7
+ return new Date(value.getTime());
8
+ // SAFETY: Array.isArray above proves an array, so mapping deepClone over the elements preserves its shape.
9
+ if (Array.isArray(value)) {
10
+ const items = value;
11
+ return items.map((item) => deepClone(item));
12
+ }
13
+ // SAFETY: Object.entries copies every own enumerable key, so the plain-object form of T is reproduced.
14
+ const clone = {};
15
+ for (const [key, item] of Object.entries(value))
16
+ clone[key] = deepClone(item);
17
+ return clone;
18
+ }
@@ -0,0 +1,13 @@
1
+ // Buckets items by `keyFn`, preserving input order inside each group.
2
+ export function groupBy(items, keyFn) {
3
+ const groups = {};
4
+ for (const item of items) {
5
+ const key = keyFn(item);
6
+ const bucket = groups[key];
7
+ if (bucket === undefined)
8
+ groups[key] = [item];
9
+ else
10
+ bucket.push(item);
11
+ }
12
+ return groups;
13
+ }
@@ -0,0 +1,12 @@
1
+ export function debounce(fn, ms) {
2
+ let timer;
3
+ return (...args) => {
4
+ if (timer !== undefined) {
5
+ clearTimeout(timer);
6
+ }
7
+ timer = setTimeout(() => {
8
+ timer = undefined;
9
+ fn(...args);
10
+ }, ms);
11
+ };
12
+ }
@@ -0,0 +1,18 @@
1
+ export function deepClone(value) {
2
+ if (value === null || typeof value !== 'object') {
3
+ return value;
4
+ }
5
+ if (value instanceof Date) {
6
+ return new Date(value.getTime());
7
+ }
8
+ if (Array.isArray(value)) {
9
+ const source = value;
10
+ return source.map((item) => deepClone(item));
11
+ }
12
+ const source = value;
13
+ const clone = {};
14
+ for (const [key, item] of Object.entries(source)) {
15
+ clone[key] = deepClone(item);
16
+ }
17
+ return clone;
18
+ }
@@ -0,0 +1,14 @@
1
+ export function groupBy(items, keyFn) {
2
+ const groups = {};
3
+ for (const item of items) {
4
+ const key = keyFn(item);
5
+ const existing = groups[key];
6
+ if (existing === undefined) {
7
+ groups[key] = [item];
8
+ }
9
+ else {
10
+ existing.push(item);
11
+ }
12
+ }
13
+ return groups;
14
+ }
@@ -0,0 +1,12 @@
1
+ export function debounce(fn, ms) {
2
+ let timer;
3
+ return (...args) => {
4
+ if (timer !== undefined) {
5
+ clearTimeout(timer);
6
+ }
7
+ timer = setTimeout(() => {
8
+ timer = undefined;
9
+ fn(...args);
10
+ }, ms);
11
+ };
12
+ }
@@ -0,0 +1,16 @@
1
+ export function deepClone(value) {
2
+ if (value === null || typeof value !== 'object') {
3
+ return value;
4
+ }
5
+ if (value instanceof Date) {
6
+ return new Date(value.getTime());
7
+ }
8
+ if (Array.isArray(value)) {
9
+ return value.map((item) => deepClone(item));
10
+ }
11
+ const clone = {};
12
+ for (const [key, item] of Object.entries(value)) {
13
+ clone[key] = deepClone(item);
14
+ }
15
+ return clone;
16
+ }
@@ -0,0 +1,13 @@
1
+ export function groupBy(items, keyFn) {
2
+ const groups = {};
3
+ for (const item of items) {
4
+ const key = keyFn(item);
5
+ if (Object.hasOwn(groups, key)) {
6
+ groups[key].push(item);
7
+ }
8
+ else {
9
+ groups[key] = [item];
10
+ }
11
+ }
12
+ return groups;
13
+ }
@@ -1,25 +1,19 @@
1
- // Data snapshots: what tersio keeps, where the copies live, and how one is put back.
2
- //
3
- // A snapshot is a directory with a manifest, not a single file, so one copy holds the whole set.
4
- // Older installs wrote a lone `usage-<stamp>.db`; those still list and restore, over the ledger alone.
1
+ // Data snapshots: a manifest directory, not one file; older lone `usage-<stamp>.db` installs still restore.
5
2
  import { promises as fs } from 'node:fs';
6
3
  import { readdirSync, readFileSync, statSync } from 'node:fs';
7
- import { execFileSync } from 'node:child_process';
8
4
  import path from 'node:path';
5
+ import { sqliteExec } from '../extensions/shared/sqlite.js';
9
6
  import { agentDbPath, statsDbPath } from '../extensions/shared/agent-stats.js';
10
7
  import { rtkDbPath } from '../extensions/shared/rtk-gain.js';
11
8
  import { pricesCachePath } from '../extensions/shared/pricing.js';
12
9
  import { usageDbPath } from '../extensions/shared/usage-store.js';
13
10
  import { resetMarkerPath } from '../extensions/shared/usage-ledger.js';
14
11
  import { tersioHome } from '../extensions/shared/utils.js';
15
- /** Snapshots kept on disk; the oldest is pruned first. Six keeps the list past one screen, so
16
- * the pane scrolls instead of showing the whole history at once. */
12
+ /** Snapshots kept on disk, oldest pruned first; six fills past one screen so the pane scrolls. */
17
13
  export const BACKUP_KEEP = 6;
18
- /** Where a snapshot's files live. */
19
14
  export function backupDir() {
20
15
  return path.join(tersioHome(), 'backups');
21
16
  }
22
- /** A snapshot directory's manifest, read back out with its files. */
23
17
  function manifestPath(dir) {
24
18
  return path.join(dir, 'manifest.json');
25
19
  }
@@ -90,7 +84,6 @@ export function listDataBackups() {
90
84
  }
91
85
  return [...dirs, ...legacy].toSorted((a, b) => b.mtime - a.mtime);
92
86
  }
93
- /** The files a snapshot directory holds, read from its manifest. */
94
87
  function readManifest(dir) {
95
88
  try {
96
89
  const raw = JSON.parse(readFileSync(manifestPath(dir), 'utf8'));
@@ -120,13 +113,13 @@ function totalOf(dir) {
120
113
  /** Put the sqlite ledger's write-ahead log back in the main file, so a copy is one whole database. */
121
114
  function checkpointLedger() {
122
115
  try {
123
- execFileSync('sqlite3', [usageDbPath(), 'PRAGMA wal_checkpoint(TRUNCATE);'], { stdio: 'ignore', timeout: 10_000 });
116
+ sqliteExec(usageDbPath(), 'PRAGMA wal_checkpoint(TRUNCATE);');
124
117
  }
125
118
  catch {
126
- /* no sqlite3, or no ledger: the plain file copy is the best available */
119
+ /* no SQLite on this host, or no ledger: the plain file copy is the best available */
127
120
  }
128
121
  }
129
- /** Take a snapshot now: one copy of every file that exists, under a stamp directory, then keep only the newest {@link BACKUP_KEEP}. */
122
+ /** Take a snapshot: one copy of every existing file under a stamp dir, keeping the newest {@link BACKUP_KEEP}. */
130
123
  export async function createDataBackup() {
131
124
  // The stamp keeps milliseconds: two snapshots in the same second must not land in one directory.
132
125
  const stamp = new Date().toISOString().replace(/[-:TZ.]/g, '');
@@ -159,7 +152,7 @@ function resolveBackup(name) {
159
152
  const found = listDataBackups().find((entry) => entry.name === name);
160
153
  return found ?? null;
161
154
  }
162
- /** Put a snapshot back: every file it holds is copied over its live source. A file it lacks is left alone, so a restore never deletes what it does not carry. */
155
+ /** Restore: files the snapshot holds overwrite their live source; files it lacks stay put, so nothing is lost. */
163
156
  export async function restoreDataBackup(name) {
164
157
  const found = resolveBackup(name);
165
158
  if (!found)
@@ -1,4 +1,4 @@
1
- // cli/banner.ts — mini pixel-scissor welcome. TTY-only and skipped on --dry-run, so piped output stays byte-identical.
1
+ // cli/banner.ts — mini pixel-scissor welcome. TTY-only, skipped on --dry-run; piped output stays byte-identical.
2
2
  import { intro as clackIntro, note as clackNote } from '@clack/prompts';
3
3
  import { PACKAGE_VERSION, dryRun } from './common.js';
4
4
  function tty() {
@@ -81,9 +81,9 @@ function printWelcome() {
81
81
  'tersio update refresh CLI + add-ons',
82
82
  'tersio doctor verify the install',
83
83
  ];
84
- // Below 26 rows the note scrolls off the top and slices the art.
84
+ // Below 26 rows the tips scroll off the top and slice the art, so they take one line there.
85
85
  if (process.stdout.rows < 26) {
86
- clackNote(tips.map((t) => ` ${t}`).join('\n'));
86
+ clackNote(` ${tips.join(' · ')}`);
87
87
  return;
88
88
  }
89
89
  const art = bannerLines();
@@ -19,14 +19,15 @@ const PACKAGE_BIN = 'tersio';
19
19
  const { version: PACKAGE_VERSION } = createRequire(import.meta.url)(path.join(findPackageRoot(path.dirname(fileURLToPath(import.meta.url))), 'package.json'));
20
20
  const CAVEMAN_DEFAULTS = new Set(['off', 'lite', 'full', 'ultra', 'megacave-lite', 'megacave-full', 'megacave-ultra']);
21
21
  const PONYTAIL_DEFAULTS = new Set(['off', 'lite', 'full', 'ultra']);
22
+ const PSTACK_DEFAULTS = new Set(['off', 'on']);
22
23
  const RTK_DEFAULTS = new Set(['on', 'off']);
23
24
  const DIAG_SCHEDULES = new Set(['manual', 'daily', 'weekly', 'monthly']);
24
- // Mirrors COMBO_LEVELS in extensions/shared/session-state.ts (rtk as a boolean here), so the combo preset implies all three modes.
25
+ // Mirrors COMBO_LEVELS in extensions/shared/session-state.ts, so the combo preset implies all four modes.
25
26
  const COMBO_PRESET_MODES = {
26
- off: { caveman: 'off', rtk: false, ponytail: 'off' },
27
- medium: { caveman: 'lite', rtk: true, ponytail: 'lite' },
28
- balanced: { caveman: 'full', rtk: true, ponytail: 'full' },
29
- max: { caveman: 'ultra', rtk: true, ponytail: 'ultra' },
27
+ off: { caveman: 'off', rtk: false, ponytail: 'off', pstack: 'off' },
28
+ medium: { caveman: 'lite', rtk: true, ponytail: 'lite', pstack: 'on' },
29
+ balanced: { caveman: 'full', rtk: true, ponytail: 'full', pstack: 'on' },
30
+ max: { caveman: 'ultra', rtk: true, ponytail: 'ultra', pstack: 'on' },
30
31
  };
31
32
  const COMBO_DEFAULTS = new Set(Object.keys(COMBO_PRESET_MODES));
32
33
  function parseEnum(value, valid, flag) {
@@ -46,12 +47,11 @@ function flagValue(name) {
46
47
  const prefix = `${name}=`;
47
48
  return args.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
48
49
  }
49
- // --- CLI flags ---
50
- const COMMANDS = { install: true, update: true, doctor: true, uninstall: true, usage: true, dashboard: true, reset: true, settings: true, version: true, help: true };
50
+ const COMMANDS = { install: true, reinstall: true, update: true, doctor: true, uninstall: true, usage: true, dashboard: true, reset: true, settings: true, version: true, help: true };
51
51
  const args = process.argv.slice(2);
52
52
  const commandArg = args.find((arg) => !arg.startsWith('-'));
53
53
  const command = commandArg?.toLowerCase() || null;
54
- // `tersio settings diagnosis` jumps to one prompt instead of walking the chain. Index-based, so flag values never count as positionals.
54
+ // `tersio settings diagnosis` jumps to one prompt. Index-based, so flag values never count as positionals.
55
55
  const settingArg = (() => {
56
56
  const at = args.indexOf(commandArg ?? '');
57
57
  const next = at >= 0 ? args[at + 1] : undefined;
@@ -61,6 +61,7 @@ const settingArg = (() => {
61
61
  })();
62
62
  const unknownCommand = command !== null && !COMMANDS[command];
63
63
  const install = command === 'install';
64
+ const reinstall = command === 'reinstall';
64
65
  const update = command === 'update';
65
66
  const showVersion = command === 'version' || args.includes('--version') || args.includes('-v');
66
67
  const showHelp = command === 'help' || args.includes('--help') || args.includes('-h');
@@ -96,9 +97,10 @@ if (legacyScope !== null && legacyScope !== 'user') {
96
97
  const comboDefaultFlag = parseEnum(flagValue('--combo-default'), COMBO_DEFAULTS, '--combo-default');
97
98
  const cavemanDefaultFlag = parseEnum(flagValue('--caveman-default'), CAVEMAN_DEFAULTS, '--caveman-default');
98
99
  const ponytailDefaultFlag = parseEnum(flagValue('--ponytail-default'), PONYTAIL_DEFAULTS, '--ponytail-default');
100
+ const pstackDefaultFlag = parseEnum(flagValue('--pstack-default'), PSTACK_DEFAULTS, '--pstack-default');
99
101
  const rtkDefaultFlag = parseEnum(flagValue('--rtk-default'), RTK_DEFAULTS, '--rtk-default');
100
102
  const diagScheduleFlag = parseEnum(flagValue('--diag-schedule'), DIAG_SCHEDULES, '--diag-schedule');
101
- const profileFlagsGiven = [comboDefaultFlag, cavemanDefaultFlag, rtkDefaultFlag, ponytailDefaultFlag]
103
+ const profileFlagsGiven = [comboDefaultFlag, cavemanDefaultFlag, rtkDefaultFlag, ponytailDefaultFlag, pstackDefaultFlag]
102
104
  .some((flag) => flag !== undefined);
103
105
  // Repeatable `--subagent-marker`; overrides the built-in host prompt marker.
104
106
  const subagentMarkerFlags = args
@@ -129,7 +131,7 @@ async function writeIfChanged(dest, content, options = {}) {
129
131
  return true;
130
132
  }
131
133
  await fs.mkdir(path.dirname(dest), { recursive: true });
132
- if (existing !== null) {
134
+ if (existing !== null && options.backup === true) {
133
135
  await fs.copyFile(dest, `${dest}.bak`);
134
136
  debug(`${path.basename(dest)} → ${path.basename(dest)}.bak`);
135
137
  }
@@ -138,7 +140,7 @@ async function writeIfChanged(dest, content, options = {}) {
138
140
  console.log(` [write] ${dest}`);
139
141
  return true;
140
142
  }
141
- // omp ships 'extensions: null', and list items under a null scalar break YAML: normalize null/~/[]/empty to a mapping key first.
143
+ // omp ships 'extensions: null'; normalize it to a mapping key first, or YAML rejects the list items.
142
144
  const EXTENSIONS_KEY_RE = /^\s*extensions\s*:/i;
143
145
  const EXTENSIONS_NULL_RE = /^\s*extensions\s*:\s*(?:\[\s*\]|null|~)?\s*$/i;
144
146
  function normalizeExtensionsKey(lines) {
@@ -160,7 +162,7 @@ async function ensureExtensionInConfig(configPath, extensionPath, label, options
160
162
  const line = ` - ${normalizedPath}`;
161
163
  const raw = await readTextIfExists(configPath);
162
164
  let lines = (raw || '').split('\n');
163
- // Drop legacy compiled twins (same dir/name, .js): OMP must never load both copies. Matched on the trailing path so relative entries are caught too.
165
+ // Drop legacy compiled twins (.js beside the name): OMP must never load both. Matched on the trailing path.
164
166
  if (normalizedPath.endsWith('.ts')) {
165
167
  const legacyTail = `${normalizedPath.slice(0, -3)}.js`.split('/').slice(-2).join('/');
166
168
  lines = lines.filter((l) => !l.trim().replace(/^\.\//, '').endsWith(legacyTail));
@@ -239,6 +241,12 @@ function readPonytailConfig() {
239
241
  : path.join(HOME, '.config', 'ponytail');
240
242
  return { dir, path: path.join(dir, 'config.json') };
241
243
  }
244
+ /** This package's ponytail range, or null; repair pins it, since npm installs the published range. */
245
+ async function bundledPonytailSpec() {
246
+ const root = findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
247
+ const text = await readTextIfExists(path.join(root, 'package.json'));
248
+ return parseJsonObject(text)?.dependencies?.['@dietrichgebert/ponytail'] ?? null;
249
+ }
242
250
  function parseJsonObject(text) {
243
251
  if (!text)
244
252
  return null;
@@ -280,6 +288,14 @@ async function ensurePonytailConfigValue(key, value, options = {}) {
280
288
  return true;
281
289
  }, `would set Ponytail ${key}=${value}`, `Set Ponytail ${key}=${value}`, options);
282
290
  }
291
+ // npm prefers a nested copy over the pin when the plugin's range excludes it; only `overrides` reaches it.
292
+ function ponytailOverride(pkg) {
293
+ const overrides = pkg.overrides;
294
+ if (typeof overrides !== 'object' || overrides === null)
295
+ return null;
296
+ const value = overrides['@dietrichgebert/ponytail'];
297
+ return typeof value === 'string' ? value : null;
298
+ }
283
299
  // Read plugins/package.json tolerantly; a corrupt or missing file starts fresh with installer-managed defaults.
284
300
  async function readPluginsPackage(pkgPath) {
285
301
  const pkg = parseJsonObject(await readTextIfExists(pkgPath)) ?? {};
@@ -300,4 +316,4 @@ function relTime(ageMs) {
300
316
  const weeks = Math.floor(days / 7);
301
317
  return `${weeks} week${weeks === 1 ? '' : 's'} ago`;
302
318
  }
303
- export { IS_WINDOWS, RTK_BINARY_NAME, HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, BUN_BIN_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_BIN, PACKAGE_VERSION, CAVEMAN_DEFAULTS, PONYTAIL_DEFAULTS, RTK_DEFAULTS, DIAG_SCHEDULES, COMBO_PRESET_MODES, COMBO_DEFAULTS, parseEnum, flagValue, COMMANDS, commandArg, command, settingArg, unknownCommand, install, update, showVersion, showHelp, applyUpdate, args, dryRun, yes, verbose, doctor, fix, uninstall, usage, dashboard, reset, settings, allowUnverified, dashboardPort, dashboardOpen, dashboardExport, currency, currencyGiven, removePonytail, keepPonytail, removeRtk, comboDefaultFlag, cavemanDefaultFlag, ponytailDefaultFlag, rtkDefaultFlag, diagScheduleFlag, profileFlagsGiven, subagentMarkerFlags, subagentMarkersGiven, debug, execFileP, execP, writeIfChanged, normalizeExtensionsKey, writeConfigLines, ensureExtensionInConfig, removeExtensionFromConfig, readPonytailConfig, parseJsonObject, parsePonytailConfig, patchPonytailConfig, ensurePonytailConfigValue, readPluginsPackage, relTime, };
319
+ export { IS_WINDOWS, RTK_BINARY_NAME, HOME, OMP_AGENT_DIR, OMP_PLUGINS_DIR, BUN_BIN_DIR, OMP_BIN, PACKAGE_NAME, PACKAGE_BIN, PACKAGE_VERSION, CAVEMAN_DEFAULTS, PONYTAIL_DEFAULTS, PSTACK_DEFAULTS, RTK_DEFAULTS, DIAG_SCHEDULES, COMBO_PRESET_MODES, COMBO_DEFAULTS, parseEnum, flagValue, COMMANDS, commandArg, command, settingArg, unknownCommand, install, reinstall, update, showVersion, showHelp, applyUpdate, args, dryRun, yes, verbose, doctor, fix, uninstall, usage, dashboard, reset, settings, allowUnverified, dashboardPort, dashboardOpen, dashboardExport, currency, currencyGiven, removePonytail, keepPonytail, removeRtk, comboDefaultFlag, cavemanDefaultFlag, ponytailDefaultFlag, pstackDefaultFlag, rtkDefaultFlag, diagScheduleFlag, profileFlagsGiven, subagentMarkerFlags, subagentMarkersGiven, debug, execFileP, execP, writeIfChanged, normalizeExtensionsKey, writeConfigLines, ensureExtensionInConfig, removeExtensionFromConfig, readPonytailConfig, parseJsonObject, parsePonytailConfig, patchPonytailConfig, ensurePonytailConfigValue, readPluginsPackage, relTime, bundledPonytailSpec, ponytailOverride, };
@@ -1,4 +1,4 @@
1
- // cli/currency.ts — USD-base currency helpers on offline snapshot rates. Same 10 currencies as the dashboard, which prefers live frankfurter figures.
1
+ // cli/currency.ts — USD-base helpers on offline snapshot rates. Same 10 currencies as the dashboard's live figures.
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { tersioSettingsFile } from '../extensions/shared/plugin-settings.js';
4
4
  const CURRENCY_CODES = [