@osovv/vv-opencode 1.2.11 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +27 -2
  3. package/dist/cli.js +4 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/analytics.d.ts +58 -0
  6. package/dist/commands/analytics.js +220 -0
  7. package/dist/commands/analytics.js.map +1 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +6 -5
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/analytics/metrics.d.ts +33 -0
  12. package/dist/lib/analytics/metrics.js +162 -0
  13. package/dist/lib/analytics/metrics.js.map +1 -0
  14. package/dist/lib/analytics/store.d.ts +20 -0
  15. package/dist/lib/analytics/store.js +81 -0
  16. package/dist/lib/analytics/store.js.map +1 -0
  17. package/dist/lib/analytics/types.d.ts +67 -0
  18. package/dist/lib/analytics/types.js +26 -0
  19. package/dist/lib/analytics/types.js.map +1 -0
  20. package/dist/lib/managed-skills.d.ts +1 -1
  21. package/dist/lib/managed-skills.js +3 -2
  22. package/dist/lib/managed-skills.js.map +1 -1
  23. package/dist/lib/opencode.d.ts +2 -0
  24. package/dist/lib/opencode.js +4 -2
  25. package/dist/lib/opencode.js.map +1 -1
  26. package/dist/lib/plugin-toggle-config.d.ts +1 -1
  27. package/dist/lib/plugin-toggle-config.js +2 -1
  28. package/dist/lib/plugin-toggle-config.js.map +1 -1
  29. package/dist/plugins/analytics/index.d.ts +18 -0
  30. package/dist/plugins/analytics/index.js +151 -0
  31. package/dist/plugins/analytics/index.js.map +1 -0
  32. package/dist/tui/analytics/indicator.d.ts +40 -0
  33. package/dist/tui/analytics/indicator.js +117 -0
  34. package/dist/tui/analytics/indicator.js.map +1 -0
  35. package/dist/tui/branding/footer.d.ts +14 -0
  36. package/dist/tui/branding/footer.js +34 -0
  37. package/dist/tui/branding/footer.js.map +1 -0
  38. package/dist/tui.js +25 -7
  39. package/dist/tui.js.map +1 -1
  40. package/package.json +6 -2
  41. package/schemas/vvoc/v3.json +1 -1
  42. package/templates/skills/vvoc-usage-analytics/SKILL.md +58 -0
  43. package/templates/skills/vvoc-usage-analytics/references/opencode-db-queries.md +113 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indicator.js","sourceRoot":"","sources":["../../../src/tui/analytics/indicator.tsx"],"names":[],"mappings":";AA0BA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAoBxE,MAAM,oBAAoB,GAA0B;IAClD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACrB,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAAC;YACvC,KAAK,EAAE,WAAW;YAClB,YAAY,EAAE,IAAI;YAClB,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS;SAC9B,CAAC,CAAC;QACH,OAAO,mBAAmB,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACvD,CAAC;IACD,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,eAAM,KAAK,EAAE,EAAE,KAAK,EAAE,YAAG,KAAK,CAAC,IAAI,GAAQ;CAC3E,CAAC;AAEF,oCAAoC;AACpC,2FAA2F;AAC3F,MAAM,UAAU,0BAA0B;IAIxC,IAAI,KAAK,GAAoB;QAC3B,KAAK,EAAE,CAAC;QACR,aAAa,EAAE,CAAC;QAChB,SAAS,EAAE,CAAC;QACZ,UAAU,EAAE,CAAC;QACb,KAAK,EAAE,CAAC;KACT,CAAC;IACF,OAAO;QACL,SAAS,CAAC,IAAoB;YAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa;gBAAE,OAAO;YACxC,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAGhC,CAAC;YACF,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAChD,KAAK,GAAG;gBACN,KAAK,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC;gBACtB,aAAa,EAAE,KAAK,CAAC,aAAa,GAAG,CAAC,SAAS,GAAG,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACzE,SAAS,EAAE,KAAK,CAAC,SAAS,GAAG,SAAS;gBACtC,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,UAAU;gBACzC,KAAK,EAAE,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;aAC3C,CAAC;QACJ,CAAC;QACD,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK;KACjB,CAAC;AACJ,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,IAAI,KAAK,CAAC,aAAa,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3E,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAClF,MAAM,IAAI,GAAG,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;IACpE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;AAC7D,CAAC;AAED,6EAA6E;AAC7E,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACtF,CAAC;AACD,kCAAkC;AAElC,iCAAiC;AACjC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,GAAiB,EACjB,OAAuB,EACvB,eAAsC,oBAAoB;IAE1D,IAAI,OAAO,EAAE,OAAO,KAAK,KAAK;QAAE,OAAO;IACvC,IAAI,CAAC,CAAC,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAAE,OAAO;IAE/C,MAAM,WAAW,GAAG,0BAA0B,EAAE,CAAC;IACjD,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,sBAAsB,EAAE,CAAC,KAAK,EAAE,EAAE;QACjE,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YAClD,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAErC,IAAI,CAAC;QACH,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;YACjB,KAAK,EAAE;gBACL,oBAAoB,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;oBACpC,IAAI,KAAK,CAAC,UAAU,KAAK,gBAAgB,CAAC,GAAG,CAAC;wBAAE,OAAO,SAAS,CAAC;oBACjE,MAAM,KAAK,GAAG,cAAc,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC;oBAChD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;wBAAE,OAAO,SAAS,CAAC;oBAC7C,OAAO,YAAY,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBACrE,CAAC;aACF;SACF,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,4CAA4C;IAC9C,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,SAAS,gBAAgB,CAAC,GAAiB;IACzC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;IAChC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;QAClD,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC;QAC1C,OAAO,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,0DAA0D;AAC1D,SAAS,SAAS,CAAC,GAAiB,EAAE,IAA4B;IAChE,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;IAChC,MAAM,IAAI,GACR,IAAI,KAAK,OAAO;QACd,CAAC,CAAC,KAAK,CAAC,OAAO;QACf,CAAC,CAAC,IAAI,KAAK,QAAQ;YACjB,CAAC,CAAC,KAAK,CAAC,OAAO;YACf,CAAC,CAAC,IAAI,KAAK,KAAK;gBACd,CAAC,CAAC,KAAK,CAAC,KAAK;gBACb,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,QAAQ,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { JSX } from "@opentui/solid";
2
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
3
+ export type BrandingDependencies = {
4
+ /** Renders the label node; defaults to the muted span. */
5
+ renderLabel: (text: string, color: string) => JSX.Element;
6
+ };
7
+ /** The rendered label, e.g. "vvoc v1.2.11". */
8
+ export declare function brandingFooterLabel(): string;
9
+ /**
10
+ * Registers the branding footer in the "sidebar_footer" host slot.
11
+ * Always on (independent of analytics config); returns silently when the slot
12
+ * API is unavailable or registration fails.
13
+ */
14
+ export declare function registerBrandingFooter(api: TuiPluginApi, dependencies?: BrandingDependencies): void;
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx } from "@opentui/solid/jsx-runtime";
2
+ import { getPackageVersionSync } from "../../lib/package.js";
3
+ const DEFAULT_DEPENDENCIES = {
4
+ renderLabel: (text, color) => _jsx("span", { style: { color }, children: text }),
5
+ };
6
+ /** The rendered label, e.g. "vvoc v1.2.11". */
7
+ export function brandingFooterLabel() {
8
+ return `vvoc v${getPackageVersionSync()}`;
9
+ }
10
+ // START_BLOCK_REGISTER_BRANDING_FOOTER
11
+ /**
12
+ * Registers the branding footer in the "sidebar_footer" host slot.
13
+ * Always on (independent of analytics config); returns silently when the slot
14
+ * API is unavailable or registration fails.
15
+ */
16
+ export function registerBrandingFooter(api, dependencies = DEFAULT_DEPENDENCIES) {
17
+ if (typeof api.slots?.register !== "function")
18
+ return;
19
+ try {
20
+ api.slots.register({
21
+ slots: {
22
+ sidebar_footer: () => {
23
+ const muted = api.theme.current.textMuted;
24
+ const color = `rgba(${muted.r}, ${muted.g}, ${muted.b}, ${muted.a})`;
25
+ return dependencies.renderLabel(brandingFooterLabel(), color);
26
+ },
27
+ },
28
+ });
29
+ }
30
+ catch {
31
+ // Fail-soft: no footer for this session.
32
+ }
33
+ }
34
+ //# sourceMappingURL=footer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../src/tui/branding/footer.tsx"],"names":[],"mappings":";AAuBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAO7D,MAAM,oBAAoB,GAAyB;IACjD,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,eAAM,KAAK,EAAE,EAAE,KAAK,EAAE,YAAG,IAAI,GAAQ;CACpE,CAAC;AAEF,+CAA+C;AAC/C,MAAM,UAAU,mBAAmB;IACjC,OAAO,SAAS,qBAAqB,EAAE,EAAE,CAAC;AAC5C,CAAC;AAED,uCAAuC;AACvC;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,GAAiB,EACjB,eAAqC,oBAAoB;IAEzD,IAAI,OAAO,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,UAAU;QAAE,OAAO;IACtD,IAAI,CAAC;QACH,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;YACjB,KAAK,EAAE;gBACL,cAAc,EAAE,GAAG,EAAE;oBACnB,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;oBAC1C,MAAM,KAAK,GAAG,QAAQ,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,GAAG,CAAC;oBACrE,OAAO,YAAY,CAAC,WAAW,CAAC,mBAAmB,EAAE,EAAE,KAAK,CAAC,CAAC;gBAChE,CAAC;aACF;SACF,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;AACH,CAAC"}
package/dist/tui.js CHANGED
@@ -1,27 +1,45 @@
1
1
  // FILE: src/tui.tsx
2
- // VERSION: 1.0.0
2
+ // VERSION: 1.1.0
3
3
  // START_MODULE_CONTRACT
4
- // PURPOSE: Publish the default @osovv/vv-opencode/tui module containing the managed context inspector.
4
+ // PURPOSE: Publish the default @osovv/vv-opencode/tui module containing the managed context inspector, analytics indicator, and branding footer.
5
5
  // SCOPE: Stable TUI package entrypoint and module identity only.
6
- // DEPENDS: [@opencode-ai/plugin/tui, src/tui/context/plugin.ts]
7
- // LINKS: [M-PLUGIN-CONTEXT-TUI, V-M-PLUGIN-CONTEXT-TUI]
6
+ // DEPENDS: [@opencode-ai/plugin/tui, @opencode-ai/plugin, src/tui/context/plugin.ts, src/tui/analytics/indicator.tsx, src/tui/branding/footer.tsx]
7
+ // LINKS: [M-PLUGIN-CONTEXT-TUI, M-TUI-ANALYTICS-INDICATOR, M-TUI-BRANDING-FOOTER, V-M-PLUGIN-CONTEXT-TUI]
8
8
  // ROLE: BARREL
9
9
  // MAP_MODE: SUMMARY
10
10
  // END_MODULE_CONTRACT
11
11
  //
12
12
  // START_MODULE_MAP
13
- // default - OpenCode TUI plugin module registering the /context inspector.
13
+ // default - OpenCode TUI plugin module registering /context, the analytics indicator, and the branding footer.
14
14
  // ContextTuiPlugin - Named TUI plugin factory for direct consumers and tests.
15
15
  // END_MODULE_MAP
16
16
  //
17
17
  // START_CHANGE_SUMMARY
18
- // LAST_CHANGE: [C-CONTEXT-TUI-PLUGIN - Added the public TUI package subpath.]
18
+ // LAST_CHANGE: [2026-08-19-cache-hit-rate-analytics - Registered the live cache indicator and vvoc version footer alongside /context.]
19
19
  // END_CHANGE_SUMMARY
20
20
  import { ContextTuiPlugin } from "./tui/context/plugin.js";
21
+ import { registerAnalyticsIndicator } from "./tui/analytics/indicator.js";
22
+ import { registerBrandingFooter } from "./tui/branding/footer.js";
21
23
  export { ContextTuiPlugin };
24
+ // START_BLOCK_TUI_MODULE
22
25
  const plugin = {
23
26
  id: "vvoc-context",
24
- tui: ContextTuiPlugin,
27
+ tui: async (api, options, meta) => {
28
+ await ContextTuiPlugin(api, options, meta);
29
+ try {
30
+ await registerAnalyticsIndicator(api, options);
31
+ }
32
+ catch {
33
+ // Fail-soft: indicator unavailable for this session.
34
+ }
35
+ try {
36
+ registerBrandingFooter(api);
37
+ }
38
+ catch {
39
+ // Fail-soft: footer unavailable for this session.
40
+ }
41
+ },
25
42
  };
43
+ // END_BLOCK_TUI_MODULE
26
44
  export default plugin;
27
45
  //# sourceMappingURL=tui.js.map
package/dist/tui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.tsx"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,iBAAiB;AACjB,wBAAwB;AACxB,yGAAyG;AACzG,mEAAmE;AACnE,kEAAkE;AAClE,0DAA0D;AAC1D,iBAAiB;AACjB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,6EAA6E;AAC7E,gFAAgF;AAChF,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,gFAAgF;AAChF,qBAAqB;AAGrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,MAAM,MAAM,GAAqC;IAC/C,EAAE,EAAE,cAAc;IAClB,GAAG,EAAE,gBAAgB;CACtB,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.tsx"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,iBAAiB;AACjB,wBAAwB;AACxB,mJAAmJ;AACnJ,mEAAmE;AACnE,qJAAqJ;AACrJ,4GAA4G;AAC5G,iBAAiB;AACjB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,iHAAiH;AACjH,gFAAgF;AAChF,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,yIAAyI;AACzI,qBAAqB;AAGrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,yBAAyB;AACzB,MAAM,MAAM,GAAqC;IAC/C,EAAE,EAAE,cAAc;IAClB,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAChC,MAAM,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,0BAA0B,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,qDAAqD;QACvD,CAAC;QACD,IAAI,CAAC;YACH,sBAAsB,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,kDAAkD;QACpD,CAAC;IACH,CAAC;CACF,CAAC;AACF,uBAAuB;AAEvB,eAAe,MAAM,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osovv/vv-opencode",
3
- "version": "1.2.11",
3
+ "version": "1.3.0",
4
4
  "description": "A curated, opinionated set of OpenCode plugins for spec-first, review-driven, safer agentic development.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -77,6 +77,10 @@
77
77
  "./plugins/tool-history-compaction": {
78
78
  "types": "./dist/plugins/tool-history-compaction/index.d.ts",
79
79
  "import": "./dist/plugins/tool-history-compaction/index.js"
80
+ },
81
+ "./plugins/analytics": {
82
+ "types": "./dist/plugins/analytics/index.d.ts",
83
+ "import": "./dist/plugins/analytics/index.js"
80
84
  }
81
85
  },
82
86
  "scripts": {
@@ -89,7 +93,7 @@
89
93
  "grace:check": "bun scripts/check-grace-markup.ts",
90
94
  "test": "bun test",
91
95
  "check": "bun run typecheck && bun run lint && bun run fmt:check && bun run grace:check && bun test",
92
- "pack:check": "bun run build && bun -e \"const root = await import('./dist/index.js'); if (!('GuardianPlugin' in root)) throw new Error('dist root export missing GuardianPlugin'); if (!('HashlineEditPlugin' in root)) throw new Error('dist root export missing HashlineEditPlugin'); if (!('ModelRolesPlugin' in root)) throw new Error('dist root export missing ModelRolesPlugin'); if (!('SystemContextInjectionPlugin' in root)) throw new Error('dist root export missing SystemContextInjectionPlugin'); if (!('WorkflowPlugin' in root)) throw new Error('dist root export missing WorkflowPlugin'); if (!('SecretsRedactionPlugin' in root)) throw new Error('dist root export missing SecretsRedactionPlugin'); if (!('WebToolsPlugin' in root)) throw new Error('dist root export missing WebToolsPlugin'); const tui = await import('./dist/tui.js'); if (!tui.default || typeof tui.default.tui !== 'function') throw new Error('dist TUI export missing default tui module'); await import('./dist/plugins/guardian/index.js'); await import('./dist/plugins/hashline-edit/index.js'); await import('./dist/plugins/model-roles/index.js'); await import('./dist/plugins/system-context-injection/index.js'); await import('./dist/plugins/workflow/index.js'); await import('./dist/plugins/secrets-redaction/index.js'); await import('./dist/plugins/web-tools/index.js')\" && npm pack --dry-run",
96
+ "pack:check": "bun run build && bun -e \"const root = await import('./dist/index.js'); if (!('GuardianPlugin' in root)) throw new Error('dist root export missing GuardianPlugin'); if (!('HashlineEditPlugin' in root)) throw new Error('dist root export missing HashlineEditPlugin'); if (!('ModelRolesPlugin' in root)) throw new Error('dist root export missing ModelRolesPlugin'); if (!('SystemContextInjectionPlugin' in root)) throw new Error('dist root export missing SystemContextInjectionPlugin'); if (!('WorkflowPlugin' in root)) throw new Error('dist root export missing WorkflowPlugin'); if (!('SecretsRedactionPlugin' in root)) throw new Error('dist root export missing SecretsRedactionPlugin'); if (!('WebToolsPlugin' in root)) throw new Error('dist root export missing WebToolsPlugin'); if (!('AnalyticsPlugin' in root)) throw new Error('dist root export missing AnalyticsPlugin'); const tui = await import('./dist/tui.js'); if (!tui.default || typeof tui.default.tui !== 'function') throw new Error('dist TUI export missing default tui module'); await import('./dist/plugins/guardian/index.js'); await import('./dist/plugins/hashline-edit/index.js'); await import('./dist/plugins/model-roles/index.js'); await import('./dist/plugins/system-context-injection/index.js'); await import('./dist/plugins/workflow/index.js'); await import('./dist/plugins/secrets-redaction/index.js'); await import('./dist/plugins/web-tools/index.js'); await import('./dist/plugins/analytics/index.js')\" && npm pack --dry-run",
93
97
  "tui:local": "bun scripts/tui-local.ts",
94
98
  "release:check": "bun scripts/release-check.ts",
95
99
  "release:bump": "bun scripts/release-bump.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://cdn.jsdelivr.net/npm/@osovv/vv-opencode@1.2.11/schemas/vvoc/v3.json",
3
+ "$id": "https://cdn.jsdelivr.net/npm/@osovv/vv-opencode@1.3.0/schemas/vvoc/v3.json",
4
4
  "title": "vvoc config",
5
5
  "description": "Canonical vvoc configuration document.",
6
6
  "type": "object",
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: vvoc-usage-analytics
3
+ description: Use when the user asks about token usage, cache hit rate, cache efficiency, model or provider consumption, session costs, or whether a vvoc release or OpenCode upgrade changed caching behavior. Runs read-only analysis over vvoc analytics (CLI and JSONL) and historical opencode.db data, then reports findings without modifying anything.
4
+ ---
5
+
6
+ <skill>
7
+ <identity>
8
+ You are the vvoc-usage-analytics skill. You turn usage questions into evidence-based answers. You know the three data sources, their coverage boundaries, and the exact metric definitions, and you never guess a number you did not read from a source. You are an analyst, not a fixer: you report findings and leave changes to the user.
9
+ </identity>
10
+
11
+ <scope>
12
+ <rule>OpenCode's database is strictly read-only: always connect with a `?mode=ro` URI (see references/opencode-db-queries.md). Never run UPDATE/INSERT/DELETE/DDL against it, even diagnostically.</rule>
13
+ <rule>Never modify, delete, truncate, or "repair" analytics JSONL files, and never suggest doing so as a fix.</rule>
14
+ <rule>Read-only also means no config changes: do not toggle plugins, rewrite vvoc.json, or restart anything unless the user explicitly asks for that as a separate task.</rule>
15
+ <rule>Report findings only. Recommendations are fine; silent "fixes" to telemetry or config are out of scope.</rule>
16
+ <rule>If a source is unavailable (no db file, empty JSONL, sqlite3 missing), say so explicitly and continue with the sources that exist instead of implying there was no usage.</rule>
17
+ </scope>
18
+
19
+ <data_sources>
20
+ <source name="vvoc CLI" precedence="1">
21
+ Prefer `vvoc analytics cache-hit-rate` for anything it can answer. It already dedupes, filters, groups, and formats:
22
+ - Trend: `vvoc analytics cache-hit-rate --group-by day|week|month --since 30d`
23
+ - Compare vvoc releases: `--group-by vvoc --since 30d`
24
+ - Compare OpenCode versions: `--group-by opencode --since 30d`
25
+ - Per session / model / provider / project: `--group-by session|model|provider|project`
26
+ - Filters and output: `--since`/`--until` accept `Nd`/`Nw`/`Nm` or `YYYY-MM-DD`; `--project` is a case-insensitive substring; `--order steps|hit-rate|date`; `--limit N`; `--json` for machine-readable rows.
27
+ Note: CLI coverage starts when the analytics plugin first ran (JSONL start date), and only steps after an OpenCode restart are recorded.
28
+ </source>
29
+ <source name="analytics JSONL" precedence="2">
30
+ Use the raw monthly files `$XDG_DATA_HOME/vvoc/analytics/usage-YYYY-MM.jsonl` when the question needs something the CLI does not compute (unusual groupings, per-agent or per-message drill-downs, exact record inspection). Lines are JSON objects: `kind` is `usage` (one per step-finish part, dedupe by `partID` keeping the last) or `session` (dedupe by `sessionID`). jq, python, or bun one-liners are all fine — reading only.
31
+ </source>
32
+ <source name="opencode.db history" precedence="3">
33
+ Use OpenCode's own SQLite database for history that predates the plugin and for per-session cost aggregates: `~/.local/share/opencode/opencode.db` (override via $OPENCODE_DATA_HOME when the user has one). It holds months of step-finish parts with tokens, session cost/token totals, and `session.version` = the OpenCode version. Always read it with the validated snippets in references/opencode-db-queries.md (`?mode=ro` URI). This is the only source that can answer "how did caching look before vvoc analytics existed" and "what did sessions cost".
34
+ </source>
35
+ </data_sources>
36
+
37
+ <workflow>
38
+ <step>Clarify the question into: metric(s), grouping, and time range. If the user's ask is vague ("is my cache ok?"), default to hit rate grouped by day for the last 14 days plus coverage, then offer deeper cuts.</step>
39
+ <step>Pick the narrowest source that can answer it: CLI first; JSONL only for cuts the CLI lacks; opencode.db only for pre-plugin history or cost-per-session questions. State which source you are using and why.</step>
40
+ <step>Run the query read-only. For db queries, use the reference snippets; adapt only filters (WHERE/LIMIT), not the hit-rate formula.</step>
41
+ <step>Sanity-check coverage before answering: how many steps does the range contain, what share is cache-eligible (coverage), and where does each source's data begin. A hit rate over 12 steps means more than one over 12,000.</step>
42
+ <step>Present findings as a compact table plus 2-4 sentences of interpretation. Every answer names its data source and time range. When mixing sources, state the JSONL start boundary explicitly so the user knows which numbers come from where.</step>
43
+ <step>Offer the natural follow-up cut (by model, by session, before/after a date) instead of running all of them unasked.</step>
44
+ </workflow>
45
+
46
+ <metric_definitions>
47
+ <metric name="cache hit rate">Token-weighted: `cacheRead / (cacheRead + cacheWrite + input)` summed over cache-eligible steps. A step is cache-eligible when `cacheRead + cacheWrite > 0`. Identical to the CLI and the SQL reference — do not invent per-step averaging.</metric>
48
+ <metric name="coverage">`eligibleSteps / steps`. Low coverage means the provider rarely reports cache tokens; say that, do not hide it behind a hit rate.</metric>
49
+ <metric name="n/a">Groups with zero eligible steps have no hit rate. Report `n/a` — never `0%` — and show coverage so "provider does not report cache" stays distinguishable from "cache misses".</metric>
50
+ </metric_definitions>
51
+
52
+ <output_discipline>
53
+ <rule>Tables over prose for any comparison; columns in the CLI's spirit (GROUP, STEPS, COVERAGE, HIT-RATE, CACHE-READ, CACHE-WRITE, FRESH-IN or their subset).</rule>
54
+ <rule>Every number carries its source (CLI / JSONL / opencode.db) and time range.</rule>
55
+ <rule>State validation context for db queries: snippets were validated against OpenCode 1.18.x; if columns error out, report the schema mismatch instead of improvising SQL guesses.</rule>
56
+ <rule>Interpretation stays factual: rising/falling hit rate, coverage share, outliers. Avoid unfounded causal claims; a vvoc version bump and a project switch can co-occur.</rule>
57
+ </output_discipline>
58
+ </skill>
@@ -0,0 +1,113 @@
1
+ # opencode.db read-only queries
2
+
3
+ Validated against OpenCode **1.18.x** on a real 5.7 GB database (2026-08). If a column
4
+ errors out after an OpenCode upgrade, report the schema mismatch — do not improvise
5
+ guesses about the new schema.
6
+
7
+ ## Connection — always read-only
8
+
9
+ OpenCode keeps this database open (WAL). Concurrent readers are fine; writers are not.
10
+
11
+ ```bash
12
+ sqlite3 "file:$HOME/.local/share/opencode/opencode.db?mode=ro" "SELECT ..."
13
+ ```
14
+
15
+ No `sqlite3` CLI? Use bun:sqlite read-only:
16
+
17
+ ```ts
18
+ import { Database } from "bun:sqlite";
19
+ const db = new Database(`${process.env.HOME}/.local/share/opencode/opencode.db`, { readonly: true });
20
+ ```
21
+
22
+ Respect `$XDG_DATA_HOME`/`$OPENCODE_DATA_HOME` if the user has a custom data dir
23
+ (check the effective OpenCode config before assuming the default path).
24
+
25
+ ## Schema orientation
26
+
27
+ - `session` — one row per session. `version` is the **OpenCode version** that created
28
+ it. Cost/token aggregate columns: `cost`, `tokens_input`, `tokens_output`,
29
+ `tokens_reasoning`, `tokens_cache_read`, `tokens_cache_write`. `time_created` is
30
+ epoch **milliseconds**.
31
+ - `message` — one row per message; `data` JSON carries `role`, `providerID`,
32
+ `modelID`, `agent`, `cost`, `tokens` for assistant messages.
33
+ - `part` — one row per message part; `data` JSON with `type: "step-finish"` carries
34
+ final per-step tokens: `tokens.input`, `tokens.output`, `tokens.reasoning`,
35
+ `tokens.cache.read`, `tokens.cache.write`.
36
+
37
+ ## Hit-rate formula (must match the vvoc CLI)
38
+
39
+ `cacheRead / (cacheRead + cacheWrite + input)` **summed over cache-eligible steps**
40
+ ( eligible := cache.read + cache.write > 0 ), token-weighted. `NULLIF` guards the
41
+ division; NULL hit rate means "no eligible steps" → report `n/a`, not 0%.
42
+
43
+ ## (a) Cache hit rate by OpenCode version
44
+
45
+ ```sql
46
+ SELECT s.version AS opencode,
47
+ COUNT(*) AS steps,
48
+ SUM(CASE WHEN json_extract(p.data,'$.tokens.cache.read')
49
+ + json_extract(p.data,'$.tokens.cache.write') > 0
50
+ THEN 1 ELSE 0 END) AS eligible,
51
+ ROUND(100.0 * SUM(json_extract(p.data,'$.tokens.cache.read')) /
52
+ NULLIF(SUM(json_extract(p.data,'$.tokens.cache.read')
53
+ + json_extract(p.data,'$.tokens.cache.write')
54
+ + json_extract(p.data,'$.tokens.input')), 0), 1) AS hit_pct
55
+ FROM part p JOIN session s ON s.id = p.session_id
56
+ WHERE json_extract(p.data,'$.type') = 'step-finish'
57
+ GROUP BY s.version
58
+ ORDER BY MIN(s.time_created) DESC;
59
+ ```
60
+
61
+ ## (b) Cache hit rate by month
62
+
63
+ ```sql
64
+ SELECT strftime('%Y-%m', p.time_created/1000, 'unixepoch') AS month,
65
+ COUNT(*) AS steps,
66
+ ROUND(100.0 * SUM(json_extract(p.data,'$.tokens.cache.read')) /
67
+ NULLIF(SUM(json_extract(p.data,'$.tokens.cache.read')
68
+ + json_extract(p.data,'$.tokens.cache.write')
69
+ + json_extract(p.data,'$.tokens.input')), 0), 1) AS hit_pct
70
+ FROM part p
71
+ WHERE json_extract(p.data,'$.type') = 'step-finish'
72
+ GROUP BY month
73
+ ORDER BY month DESC;
74
+ ```
75
+
76
+ ## (c) Recent sessions: cost and token totals
77
+
78
+ Straight from the session table — no JSON extraction needed:
79
+
80
+ ```sql
81
+ SELECT substr(s.id, 1, 8) AS session,
82
+ s.title,
83
+ s.version AS opencode,
84
+ ROUND(s.cost, 3) AS cost,
85
+ s.tokens_cache_read AS cache_read,
86
+ s.tokens_cache_write AS cache_write,
87
+ s.tokens_input AS fresh_in,
88
+ datetime(s.time_created/1000, 'unixepoch') AS started
89
+ FROM session s
90
+ ORDER BY s.time_created DESC
91
+ LIMIT 20;
92
+ ```
93
+
94
+ Caveat: `cost` is what OpenCode recorded (often `0` for subscription/zai providers);
95
+ say "recorded cost" in findings, do not treat 0 as free.
96
+
97
+ ## (d) Step-finish counts by provider/model
98
+
99
+ Model attribution lives on the message, so join through it:
100
+
101
+ ```sql
102
+ SELECT json_extract(m.data,'$.providerID') AS provider,
103
+ json_extract(m.data,'$.modelID') AS model,
104
+ COUNT(*) AS steps
105
+ FROM part p JOIN message m ON m.id = p.message_id
106
+ WHERE json_extract(p.data,'$.type') = 'step-finish'
107
+ GROUP BY provider, model
108
+ ORDER BY steps DESC;
109
+ ```
110
+
111
+ To get hit rate per model instead of counts, wrap snippet (a)'s `SUM/NULLIF`
112
+ expression in the same query but group by `json_extract(m.data,'$.providerID') ||
113
+ '/' || json_extract(m.data,'$.modelID')`.