opencode-dux 1.0.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 (302) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +452 -0
  3. package/dist/agents/descriptions.d.ts +6 -0
  4. package/dist/agents/designer.d.ts +2 -0
  5. package/dist/agents/explorer.d.ts +2 -0
  6. package/dist/agents/fixer.d.ts +2 -0
  7. package/dist/agents/index.d.ts +22 -0
  8. package/dist/agents/interpreter.d.ts +2 -0
  9. package/dist/agents/librarian.d.ts +2 -0
  10. package/dist/agents/oracle.d.ts +2 -0
  11. package/dist/agents/orchestrator.d.ts +27 -0
  12. package/dist/agents/overrides.d.ts +18 -0
  13. package/dist/agents/prompt-blocks.d.ts +97 -0
  14. package/dist/agents/steward.d.ts +3 -0
  15. package/dist/cli/config-io.d.ts +24 -0
  16. package/dist/cli/config-manager.d.ts +4 -0
  17. package/dist/cli/index.d.ts +2 -0
  18. package/dist/cli/index.js +1006 -0
  19. package/dist/cli/install.d.ts +2 -0
  20. package/dist/cli/mcps.d.ts +13 -0
  21. package/dist/cli/model-key-normalization.d.ts +1 -0
  22. package/dist/cli/paths.d.ts +35 -0
  23. package/dist/cli/providers.d.ts +137 -0
  24. package/dist/cli/skills.d.ts +22 -0
  25. package/dist/cli/system.d.ts +5 -0
  26. package/dist/cli/types.d.ts +38 -0
  27. package/dist/config/constants.d.ts +12 -0
  28. package/dist/config/index.d.ts +4 -0
  29. package/dist/config/loader.d.ts +40 -0
  30. package/dist/config/runtime-preset.d.ts +12 -0
  31. package/dist/config/schema.d.ts +281 -0
  32. package/dist/config/utils.d.ts +10 -0
  33. package/dist/discovery/local/types.d.ts +79 -0
  34. package/dist/discovery/local.d.ts +73 -0
  35. package/dist/discovery/mcp-servers.d.ts +88 -0
  36. package/dist/discovery/skills.d.ts +94 -0
  37. package/dist/hooks/apply-patch/codec.d.ts +7 -0
  38. package/dist/hooks/apply-patch/errors.d.ts +25 -0
  39. package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
  40. package/dist/hooks/apply-patch/index.d.ts +15 -0
  41. package/dist/hooks/apply-patch/matching.d.ts +26 -0
  42. package/dist/hooks/apply-patch/operations.d.ts +3 -0
  43. package/dist/hooks/apply-patch/patch.d.ts +2 -0
  44. package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
  45. package/dist/hooks/apply-patch/resolution.d.ts +19 -0
  46. package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
  47. package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
  48. package/dist/hooks/apply-patch/types.d.ts +80 -0
  49. package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
  50. package/dist/hooks/auto-update-checker/checker.d.ts +32 -0
  51. package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
  52. package/dist/hooks/auto-update-checker/index.d.ts +18 -0
  53. package/dist/hooks/auto-update-checker/types.d.ts +22 -0
  54. package/dist/hooks/chat-headers.d.ts +16 -0
  55. package/dist/hooks/context-pressure-reminder/index.d.ts +33 -0
  56. package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
  57. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
  58. package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
  59. package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
  60. package/dist/hooks/filter-available-skills/index.d.ts +32 -0
  61. package/dist/hooks/foreground-fallback/index.d.ts +72 -0
  62. package/dist/hooks/image-hook.d.ts +5 -0
  63. package/dist/hooks/index.d.ts +14 -0
  64. package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
  65. package/dist/hooks/json-error-recovery/index.d.ts +1 -0
  66. package/dist/hooks/phase-reminder/index.d.ts +26 -0
  67. package/dist/hooks/post-file-tool-nudge/index.d.ts +19 -0
  68. package/dist/hooks/task-session-manager/index.d.ts +52 -0
  69. package/dist/hooks/todo-continuation/index.d.ts +53 -0
  70. package/dist/hooks/todo-continuation/todo-hygiene.d.ts +35 -0
  71. package/dist/index.d.ts +5 -0
  72. package/dist/index.js +31782 -0
  73. package/dist/mcp/context7.d.ts +6 -0
  74. package/dist/mcp/grep-app.d.ts +6 -0
  75. package/dist/mcp/index.d.ts +13 -0
  76. package/dist/mcp/types.d.ts +12 -0
  77. package/dist/mcp/websearch.d.ts +9 -0
  78. package/dist/skills/registry.d.ts +29 -0
  79. package/dist/subscriptions/accounts-store.d.ts +57 -0
  80. package/dist/subscriptions/index.d.ts +13 -0
  81. package/dist/subscriptions/neuralwatt-scraper.d.ts +14 -0
  82. package/dist/subscriptions/opencode-go-scraper.d.ts +27 -0
  83. package/dist/subscriptions/types.d.ts +115 -0
  84. package/dist/subscriptions/usage-service.d.ts +74 -0
  85. package/dist/tools/ast-grep/cli.d.ts +15 -0
  86. package/dist/tools/ast-grep/constants.d.ts +25 -0
  87. package/dist/tools/ast-grep/downloader.d.ts +5 -0
  88. package/dist/tools/ast-grep/index.d.ts +10 -0
  89. package/dist/tools/ast-grep/tools.d.ts +3 -0
  90. package/dist/tools/ast-grep/types.d.ts +30 -0
  91. package/dist/tools/ast-grep/utils.d.ts +4 -0
  92. package/dist/tools/delegate.d.ts +14 -0
  93. package/dist/tools/index.d.ts +5 -0
  94. package/dist/tools/preset-manager.d.ts +27 -0
  95. package/dist/tools/smartfetch/binary.d.ts +3 -0
  96. package/dist/tools/smartfetch/cache.d.ts +6 -0
  97. package/dist/tools/smartfetch/constants.d.ts +12 -0
  98. package/dist/tools/smartfetch/index.d.ts +3 -0
  99. package/dist/tools/smartfetch/network.d.ts +38 -0
  100. package/dist/tools/smartfetch/secondary-model.d.ts +28 -0
  101. package/dist/tools/smartfetch/tool.d.ts +3 -0
  102. package/dist/tools/smartfetch/types.d.ts +122 -0
  103. package/dist/tools/smartfetch/utils.d.ts +18 -0
  104. package/dist/tui-state.d.ts +168 -0
  105. package/dist/tui.d.ts +37 -0
  106. package/dist/tui.js +1896 -0
  107. package/dist/utils/agent-variant.d.ts +63 -0
  108. package/dist/utils/compat.d.ts +30 -0
  109. package/dist/utils/env.d.ts +1 -0
  110. package/dist/utils/index.d.ts +9 -0
  111. package/dist/utils/internal-initiator.d.ts +6 -0
  112. package/dist/utils/logger.d.ts +8 -0
  113. package/dist/utils/polling.d.ts +21 -0
  114. package/dist/utils/session-manager.d.ts +55 -0
  115. package/dist/utils/session.d.ts +90 -0
  116. package/dist/utils/subagent-depth.d.ts +35 -0
  117. package/dist/utils/system-collapse.d.ts +6 -0
  118. package/dist/utils/task.d.ts +4 -0
  119. package/dist/utils/zip-extractor.d.ts +1 -0
  120. package/index.ts +1 -0
  121. package/opencode-dux.schema.json +634 -0
  122. package/package.json +103 -0
  123. package/src/agents/descriptions.ts +55 -0
  124. package/src/agents/designer.test.ts +86 -0
  125. package/src/agents/designer.ts +154 -0
  126. package/src/agents/display-name.test.ts +186 -0
  127. package/src/agents/explorer.test.ts +79 -0
  128. package/src/agents/explorer.ts +144 -0
  129. package/src/agents/fixer.test.ts +79 -0
  130. package/src/agents/fixer.ts +145 -0
  131. package/src/agents/index.test.ts +472 -0
  132. package/src/agents/index.ts +248 -0
  133. package/src/agents/interpreter.ts +136 -0
  134. package/src/agents/librarian.test.ts +80 -0
  135. package/src/agents/librarian.ts +145 -0
  136. package/src/agents/oracle.test.ts +89 -0
  137. package/src/agents/oracle.ts +184 -0
  138. package/src/agents/orchestrator.test.ts +116 -0
  139. package/src/agents/orchestrator.ts +574 -0
  140. package/src/agents/overrides.ts +95 -0
  141. package/src/agents/prompt-blocks.test.ts +114 -0
  142. package/src/agents/prompt-blocks.ts +640 -0
  143. package/src/agents/steward.ts +146 -0
  144. package/src/cli/config-io.test.ts +536 -0
  145. package/src/cli/config-io.ts +473 -0
  146. package/src/cli/config-manager.test.ts +141 -0
  147. package/src/cli/config-manager.ts +4 -0
  148. package/src/cli/index.ts +88 -0
  149. package/src/cli/install.ts +282 -0
  150. package/src/cli/mcps.test.ts +62 -0
  151. package/src/cli/mcps.ts +39 -0
  152. package/src/cli/model-key-normalization.test.ts +21 -0
  153. package/src/cli/model-key-normalization.ts +60 -0
  154. package/src/cli/paths.test.ts +167 -0
  155. package/src/cli/paths.ts +144 -0
  156. package/src/cli/providers.test.ts +118 -0
  157. package/src/cli/providers.ts +141 -0
  158. package/src/cli/skills.test.ts +111 -0
  159. package/src/cli/skills.ts +103 -0
  160. package/src/cli/system.test.ts +91 -0
  161. package/src/cli/system.ts +180 -0
  162. package/src/cli/types.ts +43 -0
  163. package/src/config/constants.ts +58 -0
  164. package/src/config/index.ts +4 -0
  165. package/src/config/loader.test.ts +1194 -0
  166. package/src/config/loader.ts +269 -0
  167. package/src/config/model-resolution.test.ts +176 -0
  168. package/src/config/runtime-preset.test.ts +61 -0
  169. package/src/config/runtime-preset.ts +37 -0
  170. package/src/config/schema.ts +248 -0
  171. package/src/config/utils.test.ts +41 -0
  172. package/src/config/utils.ts +23 -0
  173. package/src/discovery/local/types.ts +85 -0
  174. package/src/discovery/local.ts +322 -0
  175. package/src/discovery/mcp-servers.ts +804 -0
  176. package/src/discovery/skills.ts +959 -0
  177. package/src/hooks/apply-patch/codec.test.ts +184 -0
  178. package/src/hooks/apply-patch/codec.ts +352 -0
  179. package/src/hooks/apply-patch/errors.ts +117 -0
  180. package/src/hooks/apply-patch/execution-context.ts +432 -0
  181. package/src/hooks/apply-patch/hook.test.ts +768 -0
  182. package/src/hooks/apply-patch/index.ts +126 -0
  183. package/src/hooks/apply-patch/matching.test.ts +215 -0
  184. package/src/hooks/apply-patch/matching.ts +586 -0
  185. package/src/hooks/apply-patch/operations.test.ts +1535 -0
  186. package/src/hooks/apply-patch/operations.ts +3 -0
  187. package/src/hooks/apply-patch/patch.ts +9 -0
  188. package/src/hooks/apply-patch/prepared-changes.ts +400 -0
  189. package/src/hooks/apply-patch/resolution.test.ts +420 -0
  190. package/src/hooks/apply-patch/resolution.ts +437 -0
  191. package/src/hooks/apply-patch/rewrite.ts +496 -0
  192. package/src/hooks/apply-patch/test-helpers.ts +52 -0
  193. package/src/hooks/apply-patch/types.ts +111 -0
  194. package/src/hooks/auto-update-checker/cache.test.ts +179 -0
  195. package/src/hooks/auto-update-checker/cache.ts +188 -0
  196. package/src/hooks/auto-update-checker/checker.test.ts +159 -0
  197. package/src/hooks/auto-update-checker/checker.ts +308 -0
  198. package/src/hooks/auto-update-checker/constants.ts +33 -0
  199. package/src/hooks/auto-update-checker/index.test.ts +282 -0
  200. package/src/hooks/auto-update-checker/index.ts +225 -0
  201. package/src/hooks/auto-update-checker/types.ts +26 -0
  202. package/src/hooks/chat-headers.test.ts +236 -0
  203. package/src/hooks/chat-headers.ts +97 -0
  204. package/src/hooks/context-pressure-reminder/index.test.ts +179 -0
  205. package/src/hooks/context-pressure-reminder/index.ts +137 -0
  206. package/src/hooks/delegate-task-retry/guidance.ts +41 -0
  207. package/src/hooks/delegate-task-retry/hook.ts +23 -0
  208. package/src/hooks/delegate-task-retry/index.test.ts +38 -0
  209. package/src/hooks/delegate-task-retry/index.ts +7 -0
  210. package/src/hooks/delegate-task-retry/patterns.ts +79 -0
  211. package/src/hooks/filter-available-skills/index.test.ts +297 -0
  212. package/src/hooks/filter-available-skills/index.ts +160 -0
  213. package/src/hooks/foreground-fallback/index.test.ts +624 -0
  214. package/src/hooks/foreground-fallback/index.ts +374 -0
  215. package/src/hooks/image-hook.ts +6 -0
  216. package/src/hooks/index.ts +17 -0
  217. package/src/hooks/json-error-recovery/hook.ts +73 -0
  218. package/src/hooks/json-error-recovery/index.test.ts +111 -0
  219. package/src/hooks/json-error-recovery/index.ts +6 -0
  220. package/src/hooks/phase-reminder/index.test.ts +74 -0
  221. package/src/hooks/phase-reminder/index.ts +85 -0
  222. package/src/hooks/post-file-tool-nudge/index.test.ts +94 -0
  223. package/src/hooks/post-file-tool-nudge/index.ts +63 -0
  224. package/src/hooks/task-session-manager/index.test.ts +833 -0
  225. package/src/hooks/task-session-manager/index.ts +434 -0
  226. package/src/hooks/todo-continuation/index.test.ts +3026 -0
  227. package/src/hooks/todo-continuation/index.ts +878 -0
  228. package/src/hooks/todo-continuation/todo-hygiene.test.ts +204 -0
  229. package/src/hooks/todo-continuation/todo-hygiene.ts +207 -0
  230. package/src/index.ts +1672 -0
  231. package/src/mcp/context7.ts +14 -0
  232. package/src/mcp/grep-app.ts +11 -0
  233. package/src/mcp/index.test.ts +96 -0
  234. package/src/mcp/index.ts +66 -0
  235. package/src/mcp/types.ts +16 -0
  236. package/src/mcp/websearch.ts +47 -0
  237. package/src/skills/codemap/README.md +60 -0
  238. package/src/skills/codemap/SKILL.md +174 -0
  239. package/src/skills/codemap/scripts/codemap.mjs +483 -0
  240. package/src/skills/codemap/scripts/codemap.test.ts +129 -0
  241. package/src/skills/registry.ts +218 -0
  242. package/src/skills/simplify/README.md +19 -0
  243. package/src/skills/simplify/SKILL.md +138 -0
  244. package/src/subscriptions/accounts-store.test.ts +236 -0
  245. package/src/subscriptions/accounts-store.ts +184 -0
  246. package/src/subscriptions/index.ts +30 -0
  247. package/src/subscriptions/neuralwatt-scraper.ts +108 -0
  248. package/src/subscriptions/opencode-go-scraper.ts +301 -0
  249. package/src/subscriptions/types.ts +145 -0
  250. package/src/subscriptions/usage-service.test.ts +202 -0
  251. package/src/subscriptions/usage-service.ts +651 -0
  252. package/src/tools/ast-grep/cli.ts +257 -0
  253. package/src/tools/ast-grep/constants.ts +214 -0
  254. package/src/tools/ast-grep/downloader.ts +131 -0
  255. package/src/tools/ast-grep/index.ts +24 -0
  256. package/src/tools/ast-grep/tools.ts +117 -0
  257. package/src/tools/ast-grep/types.ts +51 -0
  258. package/src/tools/ast-grep/utils.ts +126 -0
  259. package/src/tools/delegate-handoff.test.ts +18 -0
  260. package/src/tools/delegate.ts +508 -0
  261. package/src/tools/index.ts +8 -0
  262. package/src/tools/preset-manager.test.ts +795 -0
  263. package/src/tools/preset-manager.ts +332 -0
  264. package/src/tools/smartfetch/binary.ts +58 -0
  265. package/src/tools/smartfetch/cache.test.ts +34 -0
  266. package/src/tools/smartfetch/cache.ts +112 -0
  267. package/src/tools/smartfetch/constants.ts +29 -0
  268. package/src/tools/smartfetch/index.ts +8 -0
  269. package/src/tools/smartfetch/network.test.ts +178 -0
  270. package/src/tools/smartfetch/network.ts +614 -0
  271. package/src/tools/smartfetch/secondary-model.test.ts +85 -0
  272. package/src/tools/smartfetch/secondary-model.ts +276 -0
  273. package/src/tools/smartfetch/tool.test.ts +60 -0
  274. package/src/tools/smartfetch/tool.ts +832 -0
  275. package/src/tools/smartfetch/types.ts +135 -0
  276. package/src/tools/smartfetch/utils.test.ts +24 -0
  277. package/src/tools/smartfetch/utils.ts +456 -0
  278. package/src/tui-state.test.ts +867 -0
  279. package/src/tui-state.ts +1255 -0
  280. package/src/tui.test.ts +336 -0
  281. package/src/tui.ts +1539 -0
  282. package/src/utils/agent-variant.test.ts +244 -0
  283. package/src/utils/agent-variant.ts +187 -0
  284. package/src/utils/compat.ts +91 -0
  285. package/src/utils/env.ts +12 -0
  286. package/src/utils/index.ts +9 -0
  287. package/src/utils/internal-initiator.ts +28 -0
  288. package/src/utils/logger.test.ts +220 -0
  289. package/src/utils/logger.ts +136 -0
  290. package/src/utils/polling.test.ts +191 -0
  291. package/src/utils/polling.ts +67 -0
  292. package/src/utils/session-manager.test.ts +173 -0
  293. package/src/utils/session-manager.ts +356 -0
  294. package/src/utils/session.test.ts +110 -0
  295. package/src/utils/session.ts +389 -0
  296. package/src/utils/subagent-depth.test.ts +170 -0
  297. package/src/utils/subagent-depth.ts +75 -0
  298. package/src/utils/system-collapse.test.ts +86 -0
  299. package/src/utils/system-collapse.ts +24 -0
  300. package/src/utils/task.test.ts +24 -0
  301. package/src/utils/task.ts +20 -0
  302. package/src/utils/zip-extractor.ts +102 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Type definitions for multi-provider subscription usage tracking.
3
+ *
4
+ * Supports OpenCode Go (dashboard scraping) and Neuralwatt (REST API)
5
+ * as discriminated unions on the `provider` field.
6
+ */
7
+
8
+ /** Provider discriminator. */
9
+ export type SubscriptionProvider = 'opencode-go' | 'neuralwatt';
10
+
11
+ // ── Account definitions (discriminated union) ──
12
+
13
+ export interface OpenCodeGoAccount {
14
+ provider: 'opencode-go';
15
+ name: string;
16
+ workspaceId: string;
17
+ authCookie: string;
18
+ apiKey?: string;
19
+ }
20
+
21
+ export interface NeuralwattAccount {
22
+ provider: 'neuralwatt';
23
+ name: string;
24
+ apiKey: string;
25
+ }
26
+
27
+ export type StoredAccount = OpenCodeGoAccount | NeuralwattAccount;
28
+
29
+ // ── Usage window (OpenCode Go) ──
30
+
31
+ /** Per-time-window usage data scraped from the OpenCode Go dashboard. */
32
+ export interface UsageWindow {
33
+ /** Usage percentage [0..100] */
34
+ usagePercent: number;
35
+ /** Seconds until usage resets */
36
+ resetInSec: number;
37
+ /** Remaining percentage [0..100] */
38
+ percentRemaining: number;
39
+ /** ISO reset timestamp */
40
+ resetTimeIso: string;
41
+ }
42
+
43
+ // ── OpenCode Go usage entry ──
44
+
45
+ /** Snapshot entry per OpenCode Go account - stored in tui-state.json. */
46
+ export interface OpenCodeGoUsageEntry {
47
+ provider: 'opencode-go';
48
+ /** Display name for this account (from config). */
49
+ accountName: string;
50
+ /** OpenCode Go workspace ID. */
51
+ workspaceId: string;
52
+ /** Rolling (~5h) usage window, when present. */
53
+ rolling?: UsageWindow;
54
+ /** Weekly usage window, when present. */
55
+ weekly?: UsageWindow;
56
+ /** Monthly usage window, when present. */
57
+ monthly?: UsageWindow;
58
+ /** Timestamp when data was fetched. */
59
+ fetchedAt: number;
60
+ /** Error message if the scrape failed for this account. */
61
+ error?: string;
62
+ }
63
+
64
+ // ── Neuralwatt types ──
65
+
66
+ export interface NeuralwattBalance {
67
+ credits_remaining_usd: number;
68
+ total_credits_usd: number;
69
+ credits_used_usd: number;
70
+ accounting_method: string;
71
+ }
72
+
73
+ export interface NeuralwattUsagePeriod {
74
+ cost_usd: number;
75
+ requests: number;
76
+ tokens: number;
77
+ energy_kwh: number;
78
+ }
79
+
80
+ export interface NeuralwattUsage {
81
+ lifetime: NeuralwattUsagePeriod;
82
+ current_month: NeuralwattUsagePeriod;
83
+ }
84
+
85
+ export interface NeuralwattSubscription {
86
+ plan: string;
87
+ status: 'active' | 'canceling' | 'past_due' | 'paused' | 'trialing';
88
+ billing_interval: string | null;
89
+ current_period_start: string | null;
90
+ current_period_end: string | null;
91
+ auto_renew: boolean | null;
92
+ kwh_included: number | null;
93
+ kwh_used: number | null;
94
+ kwh_remaining: number | null;
95
+ in_overage: boolean | null;
96
+ }
97
+
98
+ /** Snapshot entry per Neuralwatt account - stored in tui-state.json. */
99
+ export interface NeuralwattUsageEntry {
100
+ provider: 'neuralwatt';
101
+ /** Display name for this account. */
102
+ accountName: string;
103
+ /** ISO timestamp from the API response. */
104
+ snapshot_at: string;
105
+ /** Credit balance. */
106
+ balance: NeuralwattBalance;
107
+ /** Usage data (lifetime + current month). */
108
+ usage: NeuralwattUsage;
109
+ /** Subscription details, null if no active subscription. */
110
+ subscription: NeuralwattSubscription | null;
111
+ /** Timestamp when data was fetched. */
112
+ fetchedAt: number;
113
+ /** Error message if the fetch failed. */
114
+ error?: string;
115
+ }
116
+
117
+ // ── Unified usage entry ──
118
+
119
+ export type SubscriptionUsageEntry =
120
+ | OpenCodeGoUsageEntry
121
+ | NeuralwattUsageEntry;
122
+
123
+ // ── Detailed usage (OpenCode Go /usage page) ──
124
+
125
+ /** Detailed usage data from the /usage page. */
126
+ export interface UsageDetail {
127
+ /** Total number of API calls. */
128
+ totalCalls: number;
129
+ /** Total estimated cost in USD. */
130
+ totalCost: number;
131
+ /** Per-model breakdown. */
132
+ perModel: Array<{
133
+ model: string;
134
+ calls: number;
135
+ cost: number;
136
+ }>;
137
+ }
138
+
139
+ // ── Config ──
140
+
141
+ /** Config for the subscription tracking feature. */
142
+ export interface SubscriptionsConfig {
143
+ /** Minimum interval between auto-refreshes in ms (default: 60000). */
144
+ refreshIntervalMs: number;
145
+ }
@@ -0,0 +1,202 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import { readTuiSnapshot } from '../tui-state';
6
+ import { UsageService } from './usage-service';
7
+
8
+ let previousXdgDataHome: string | undefined;
9
+ let tempDir: string;
10
+
11
+ function getStorageDir(): string {
12
+ return path.join(tempDir, 'opencode', 'storage', 'opencode-dux');
13
+ }
14
+
15
+ function getSubscriptionsPath(): string {
16
+ return path.join(getStorageDir(), 'subscriptions.json');
17
+ }
18
+
19
+ function getAuthPath(): string {
20
+ return path.join(tempDir, 'opencode', 'auth.json');
21
+ }
22
+
23
+ function writeSubscriptions(raw: unknown): void {
24
+ fs.mkdirSync(getStorageDir(), { recursive: true });
25
+ fs.writeFileSync(getSubscriptionsPath(), `${JSON.stringify(raw, null, 2)}\n`);
26
+ }
27
+
28
+ function createUsageService(options?: {
29
+ authSet?: (input: unknown) => Promise<void>;
30
+ }): UsageService {
31
+ const client = {
32
+ auth: {
33
+ set: options?.authSet ?? (async () => undefined),
34
+ },
35
+ tui: {
36
+ showToast: async () => undefined,
37
+ },
38
+ } as unknown as import('@opencode-ai/plugin').PluginInput['client'];
39
+ return new UsageService(client, 60_000, 600_000);
40
+ }
41
+
42
+ beforeEach(() => {
43
+ previousXdgDataHome = process.env.XDG_DATA_HOME;
44
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'omos-usage-service-'));
45
+ process.env.XDG_DATA_HOME = tempDir;
46
+ });
47
+
48
+ afterEach(() => {
49
+ if (previousXdgDataHome === undefined) {
50
+ delete process.env.XDG_DATA_HOME;
51
+ } else {
52
+ process.env.XDG_DATA_HOME = previousXdgDataHome;
53
+ }
54
+ fs.rmSync(tempDir, { recursive: true, force: true });
55
+ });
56
+
57
+ describe('usage-service', () => {
58
+ test('creates explicit sidebar error for faulty OpenCode Go credentials', async () => {
59
+ writeSubscriptions({
60
+ version: 2,
61
+ accounts: [
62
+ {
63
+ provider: 'opencode-go',
64
+ name: 'broken-go',
65
+ workspaceId: 'wrk_123',
66
+ authCookie: '',
67
+ apiKey: '',
68
+ },
69
+ ],
70
+ });
71
+
72
+ const service = createUsageService();
73
+ const entries = await service.refresh(true);
74
+ service.dispose();
75
+
76
+ expect(entries).toHaveLength(1);
77
+ expect(entries[0]?.accountName).toBe('broken-go');
78
+ expect(entries[0]?.error).toContain('Missing OpenCode Go cookie');
79
+
80
+ const snapshot = readTuiSnapshot();
81
+ const usage = Object.values(snapshot.subscriptionUsage);
82
+ expect(usage).toHaveLength(1);
83
+ expect(usage[0]?.accountName).toBe('broken-go');
84
+ expect(usage[0]?.error).toContain('Missing OpenCode Go cookie');
85
+ });
86
+
87
+ test('syncActiveAccounts resolves active names per provider from auth.json', () => {
88
+ writeSubscriptions({
89
+ version: 2,
90
+ accounts: [
91
+ {
92
+ provider: 'opencode-go',
93
+ name: 'go-main',
94
+ workspaceId: 'wrk_123',
95
+ authCookie: 'cookie',
96
+ apiKey: 'go-key',
97
+ },
98
+ {
99
+ provider: 'neuralwatt',
100
+ name: 'nw-main',
101
+ apiKey: 'nw-key',
102
+ },
103
+ ],
104
+ });
105
+
106
+ fs.mkdirSync(path.dirname(getAuthPath()), { recursive: true });
107
+ fs.writeFileSync(
108
+ getAuthPath(),
109
+ `${JSON.stringify(
110
+ {
111
+ 'opencode-go': { type: 'api', key: 'go-key' },
112
+ neuralwatt: { type: 'api', key: 'nw-key' },
113
+ },
114
+ null,
115
+ 2,
116
+ )}\n`,
117
+ );
118
+
119
+ const service = createUsageService();
120
+ const active = service.syncActiveAccounts();
121
+ service.dispose();
122
+
123
+ expect(active['opencode-go']).toBe('go-main');
124
+ expect(active.neuralwatt).toBe('nw-main');
125
+ const snapshot = readTuiSnapshot();
126
+ expect(snapshot.activeSubscriptionByProvider['opencode-go']).toBe(
127
+ 'go-main',
128
+ );
129
+ expect(snapshot.activeSubscriptionByProvider.neuralwatt).toBe('nw-main');
130
+ });
131
+
132
+ test('failed account-file read does not poison next non-forced refresh', async () => {
133
+ fs.mkdirSync(getStorageDir(), { recursive: true });
134
+ fs.writeFileSync(getSubscriptionsPath(), '{ malformed json');
135
+
136
+ const service = createUsageService();
137
+ const first = await service.refresh(false);
138
+ expect(first).toEqual([]);
139
+
140
+ writeSubscriptions({
141
+ version: 2,
142
+ accounts: [
143
+ {
144
+ provider: 'opencode-go',
145
+ name: 'needs-key',
146
+ workspaceId: 'wrk_123',
147
+ authCookie: 'cookie',
148
+ },
149
+ ],
150
+ });
151
+
152
+ const second = await service.refresh(false);
153
+ service.dispose();
154
+
155
+ expect(second).toHaveLength(1);
156
+ expect(second[0]?.accountName).toBe('needs-key');
157
+ expect(second[0]?.error).toContain('Missing OpenCode Go API key');
158
+ });
159
+
160
+ test('switch requires provider and selects matching account', async () => {
161
+ writeSubscriptions({
162
+ version: 2,
163
+ accounts: [
164
+ {
165
+ provider: 'opencode-go',
166
+ name: 'Main',
167
+ workspaceId: 'wrk_123',
168
+ authCookie: 'cookie',
169
+ apiKey: 'go-main-key',
170
+ },
171
+ {
172
+ provider: 'neuralwatt',
173
+ name: 'Main',
174
+ apiKey: 'nw-main-key',
175
+ },
176
+ ],
177
+ });
178
+
179
+ const authSetCalls: unknown[] = [];
180
+ const service = createUsageService({
181
+ authSet: async (input) => {
182
+ authSetCalls.push(input);
183
+ },
184
+ });
185
+ const output = { parts: [] as Array<{ type: string; text?: string }> };
186
+ await service.handleCommandExecuteBefore(
187
+ {
188
+ command: 'subscriptions',
189
+ sessionID: 'ses_test',
190
+ arguments: 'switch neuralwatt Main',
191
+ },
192
+ output,
193
+ );
194
+ service.dispose();
195
+
196
+ expect(authSetCalls).toHaveLength(1);
197
+ expect(authSetCalls[0]).toEqual({
198
+ path: { id: 'neuralwatt' },
199
+ body: { type: 'api', key: 'nw-main-key' },
200
+ });
201
+ });
202
+ });