@tokenwarden/opencode 0.1.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 (31) hide show
  1. package/README.md +53 -0
  2. package/dist/src/cli/index.js +105 -0
  3. package/dist/src/core/billing.js +252 -0
  4. package/dist/src/core/build-config.js +45 -0
  5. package/dist/src/core/code-summary.js +162 -0
  6. package/dist/src/core/format.js +71 -0
  7. package/dist/src/core/log-reducer.js +97 -0
  8. package/dist/src/core/protection/engine.js +226 -0
  9. package/dist/src/core/protection/license-public-key.generated.js +2 -0
  10. package/dist/src/core/protection/license.js +27 -0
  11. package/dist/src/core/protection/rule-pack.js +38 -0
  12. package/dist/src/core/protection/sidecar-public-key.generated.js +2 -0
  13. package/dist/src/core/protection/sidecar.js +197 -0
  14. package/dist/src/core/protection/signing.js +28 -0
  15. package/dist/src/core/protection/tamper.js +18 -0
  16. package/dist/src/core/savings.js +91 -0
  17. package/dist/src/core/storage.js +123 -0
  18. package/dist/src/core/tokens.js +40 -0
  19. package/dist/src/plugin/index.js +492 -0
  20. package/native/bin/tokenwarden-build-config.json +4 -0
  21. package/native/bin/tokenwarden-engine-darwin-arm64 +0 -0
  22. package/native/bin/tokenwarden-engine-darwin-arm64.manifest.json +17 -0
  23. package/native/bin/tokenwarden-engine-darwin-x64 +0 -0
  24. package/native/bin/tokenwarden-engine-darwin-x64.manifest.json +17 -0
  25. package/native/bin/tokenwarden-engine-linux-arm64 +0 -0
  26. package/native/bin/tokenwarden-engine-linux-arm64.manifest.json +17 -0
  27. package/native/bin/tokenwarden-engine-linux-x64 +0 -0
  28. package/native/bin/tokenwarden-engine-linux-x64.manifest.json +17 -0
  29. package/native/bin/tokenwarden-engine-win32-x64.exe +0 -0
  30. package/native/bin/tokenwarden-engine-win32-x64.exe.manifest.json +17 -0
  31. package/package.json +33 -0
@@ -0,0 +1,492 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import { spawn } from "node:child_process";
3
+ import { formatBillingStatus, tokenWardenSiteURL } from "../core/billing.js";
4
+ import { formatCompactNumber, formatCompactSavingsToast, formatTokenReport } from "../core/format.js";
5
+ import { reducePlainTextForContext } from "../core/log-reducer.js";
6
+ import { createProtectedEngine } from "../core/protection/engine.js";
7
+ import { appendFailureLog, createStore } from "../core/storage.js";
8
+ const DEFAULTS = {
9
+ maxToolOutputTokens: 1200,
10
+ maxContextPartTokens: 1600,
11
+ mode: "balanced",
12
+ showToasts: true,
13
+ showReportToastOnSessionIdle: true,
14
+ };
15
+ export const TokenWardenPlugin = async ({ client, directory }, options) => {
16
+ const config = normalizeOptions(options);
17
+ const engine = await createProtectedEngine({ dataDir: config.dataDir });
18
+ const failureLogStore = createStore(config.dataDir);
19
+ const idleToastSignatures = new Map();
20
+ async function logFailure(input) {
21
+ const error = input.error;
22
+ try {
23
+ await appendFailureLog(failureLogStore, {
24
+ stage: input.stage,
25
+ sessionID: input.sessionID,
26
+ callID: input.callID,
27
+ tool: input.tool,
28
+ command: input.command,
29
+ name: typeof error.name === "string" ? error.name : undefined,
30
+ message: typeof error.message === "string" ? error.message : String(input.error),
31
+ stack: typeof error.stack === "string" ? error.stack : undefined,
32
+ });
33
+ }
34
+ catch {
35
+ // Tool failures must never cascade into opencode tool execution failures.
36
+ }
37
+ }
38
+ async function showSavingsToast(sessionID, savedTokens, percentSaved) {
39
+ if (!config.showToasts || savedTokens <= 0)
40
+ return;
41
+ try {
42
+ await client.tui.showToast({
43
+ body: {
44
+ message: `TokenWarden observed ${savedTokens.toLocaleString()} optimized-context tokens saved (saved ${percentSaved.toFixed(1)}%)`,
45
+ variant: "success",
46
+ },
47
+ });
48
+ }
49
+ catch {
50
+ // The same plugin can run outside the TUI during tests or server-only use.
51
+ }
52
+ }
53
+ async function showReportToast(sessionID, options = {}) {
54
+ if (!config.showToasts || (!options.force && !config.showReportToastOnSessionIdle))
55
+ return;
56
+ const summary = await engine.summarize(sessionID);
57
+ const entitlement = await engine.billing();
58
+ const signature = `${summary.wouldHaveUsedTokens}:${summary.usedTokens}:${summary.savedTokens}`;
59
+ const signatureKey = sessionID ?? "all-sessions";
60
+ if (!options.force && idleToastSignatures.get(signatureKey) === signature)
61
+ return;
62
+ idleToastSignatures.set(signatureKey, signature);
63
+ const toast = formatCompactSavingsToast(summary, entitlement);
64
+ const title = sessionID ? toast.title : `TokenWarden all sessions saved ${summary.percentSaved.toFixed(1)}%`;
65
+ const message = sessionID
66
+ ? toast.message
67
+ : `${summary.sessions.toLocaleString()} sessions, ${formatCompactNumber(summary.wouldHaveUsedTokens)} -> ${formatCompactNumber(summary.usedTokens)}, saved ${formatCompactNumber(summary.savedTokens)}`;
68
+ try {
69
+ await client.tui.showToast({
70
+ body: {
71
+ title,
72
+ message,
73
+ variant: toast.variant,
74
+ },
75
+ });
76
+ }
77
+ catch {
78
+ // Ignore missing TUI.
79
+ }
80
+ }
81
+ async function showConnectToast() {
82
+ let openedURL;
83
+ const activation = await engine.activate({
84
+ onVerificationURL(url) {
85
+ openedURL = url;
86
+ const opened = openBrowser(url);
87
+ client.tui.showToast({
88
+ body: {
89
+ title: "Connect TokenWarden",
90
+ message: opened ? "Browser opened. Waiting for authorization..." : `Open: ${url}`,
91
+ variant: "info",
92
+ duration: 12000,
93
+ },
94
+ }).catch(() => { });
95
+ },
96
+ });
97
+ const entitlement = await engine.billing();
98
+ try {
99
+ await client.tui.showToast({
100
+ body: {
101
+ title: "TokenWarden hosted license activated",
102
+ message: `${formatBillingStatus(entitlement)} | Offline grace until ${activation.offlineGraceUntil}${openedURL ? " | Browser authorization complete." : ""}`,
103
+ variant: "info",
104
+ duration: 12000,
105
+ },
106
+ });
107
+ }
108
+ catch {
109
+ // Ignore missing TUI.
110
+ }
111
+ }
112
+ async function showStatusToast() {
113
+ const entitlement = await engine.billing();
114
+ try {
115
+ await client.tui.showToast({
116
+ body: {
117
+ title: "TokenWarden status",
118
+ message: formatBillingStatus(entitlement),
119
+ variant: "info",
120
+ duration: 12000,
121
+ },
122
+ });
123
+ }
124
+ catch {
125
+ // Ignore missing TUI.
126
+ }
127
+ }
128
+ async function showAccountToast() {
129
+ const entitlement = await engine.billing();
130
+ const url = accountPageURL(entitlement);
131
+ const opened = openBrowser(url);
132
+ try {
133
+ await client.tui.showToast({
134
+ body: {
135
+ title: "TokenWarden account",
136
+ message: opened ? "Account page opened." : `Open: ${url}`,
137
+ variant: "info",
138
+ duration: 12000,
139
+ },
140
+ });
141
+ }
142
+ catch {
143
+ // Ignore missing TUI.
144
+ }
145
+ }
146
+ async function showDisconnectToast() {
147
+ await engine.disconnect();
148
+ const entitlement = await engine.billing();
149
+ try {
150
+ await client.tui.showToast({
151
+ body: {
152
+ title: "TokenWarden disconnected",
153
+ message: `Reverted to free seat. ${formatBillingStatus(entitlement)}`,
154
+ variant: "info",
155
+ duration: 12000,
156
+ },
157
+ });
158
+ }
159
+ catch {
160
+ // Ignore missing TUI.
161
+ }
162
+ }
163
+ function normalizeCommand(command) {
164
+ return command.trim().replace(/^\/+/, "");
165
+ }
166
+ function isReportCommand(command) {
167
+ return normalizeCommand(command) === "tokenwarden-report";
168
+ }
169
+ function isConnectCommand(command) {
170
+ return normalizeCommand(command) === "tokenwarden-connect";
171
+ }
172
+ function isStatusCommand(command) {
173
+ return normalizeCommand(command) === "tokenwarden-status";
174
+ }
175
+ function isAccountCommand(command) {
176
+ return normalizeCommand(command) === "tokenwarden-account";
177
+ }
178
+ function isDisconnectCommand(command) {
179
+ return normalizeCommand(command) === "tokenwarden-disconnect";
180
+ }
181
+ return {
182
+ config: async (cfg) => {
183
+ cfg.command ??= {};
184
+ cfg.command["tokenwarden-report"] ??= {
185
+ description: "Show TokenWarden savings across all locally recorded sessions without an AI call.",
186
+ template: "TokenWarden handles this command locally before the AI runs.",
187
+ };
188
+ cfg.command["tokenwarden-connect"] ??= {
189
+ description: "Connect TokenWarden billing account locally without an AI call.",
190
+ template: "TokenWarden handles this command locally before the AI runs.",
191
+ };
192
+ cfg.command["tokenwarden-status"] ??= {
193
+ description: "Show TokenWarden status in the TUI without opening a browser or spending an AI call.",
194
+ template: "TokenWarden handles this command locally before the AI runs.",
195
+ };
196
+ cfg.command["tokenwarden-account"] ??= {
197
+ description: "Open your TokenWarden account page without an AI call.",
198
+ template: "TokenWarden handles this command locally before the AI runs.",
199
+ };
200
+ cfg.command["tokenwarden-disconnect"] ??= {
201
+ description: "Disconnect the current TokenWarden account and revert to the free seat without an AI call.",
202
+ template: "TokenWarden handles this command locally before the AI runs.",
203
+ };
204
+ },
205
+ event: async ({ event }) => {
206
+ const sessionID = sessionIDFromEvent(event);
207
+ if (event.type === "tui.command.execute") {
208
+ const command = normalizeCommand(String(event.properties.command ?? ""));
209
+ if (isReportCommand(command)) {
210
+ await showReportToast(undefined, { force: true });
211
+ }
212
+ if (isConnectCommand(command))
213
+ await showConnectToast();
214
+ if (isStatusCommand(command))
215
+ await showStatusToast();
216
+ if (isAccountCommand(command))
217
+ await showAccountToast();
218
+ if (isDisconnectCommand(command))
219
+ await showDisconnectToast();
220
+ return;
221
+ }
222
+ if (event.type === "session.idle") {
223
+ await showReportToast(sessionID);
224
+ return;
225
+ }
226
+ if (event.type !== "session.created" || !config.showToasts)
227
+ return;
228
+ try {
229
+ await client.tui.showToast({
230
+ body: {
231
+ message: "TokenWarden active. Run /tokenwarden-report for savings, /tokenwarden-status for TUI status, /tokenwarden-account for your account, or /tokenwarden-connect to connect billing.",
232
+ variant: "info",
233
+ },
234
+ });
235
+ }
236
+ catch {
237
+ // Ignore missing TUI.
238
+ }
239
+ },
240
+ "command.execute.before": async (input) => {
241
+ if (isReportCommand(input.command)) {
242
+ await showReportToast(undefined, { force: true });
243
+ throw new Error("tokenwarden:handled");
244
+ }
245
+ if (isConnectCommand(input.command)) {
246
+ await showConnectToast();
247
+ throw new Error("tokenwarden:handled");
248
+ }
249
+ if (isStatusCommand(input.command)) {
250
+ await showStatusToast();
251
+ throw new Error("tokenwarden:handled");
252
+ }
253
+ if (isAccountCommand(input.command)) {
254
+ await showAccountToast();
255
+ throw new Error("tokenwarden:handled");
256
+ }
257
+ if (isDisconnectCommand(input.command)) {
258
+ await showDisconnectToast();
259
+ throw new Error("tokenwarden:handled");
260
+ }
261
+ },
262
+ "experimental.chat.system.transform": async (_input, output) => {
263
+ if (config.mode === "off")
264
+ return;
265
+ output.system.push([
266
+ "TokenWarden context policy:",
267
+ "Prefer smart_read or smart_pack before reading large source files raw.",
268
+ "When command output is large, preserve exact errors, file paths, line numbers, and failed test names before omitting noisy lines.",
269
+ "Use token_report when the user asks about token savings, would-have-used tokens, used tokens, or percentage saved.",
270
+ "TokenWarden response policy:",
271
+ "Use the shortest answer that can safely complete the user's development task.",
272
+ "Omit background, recap, praise, filler, and details that do not affect coding, debugging, verification, or planning.",
273
+ "Keep exact file paths, symbols, commands, errors, line numbers, failed tests, user decisions, and blockers when they affect future work.",
274
+ "Prefer bullets or short paragraphs. Do not restate tool output unless it changes the plan, explains a failure, or proves verification.",
275
+ "When reporting code work, include only changed behavior, files touched, verification run, blockers, and useful next steps.",
276
+ "If the user asks for detail, teaching, brainstorming, review, or a plan, provide the needed reasoning, but stay concise.",
277
+ "Balanced mode: include brief rationale only when it affects implementation or planning.",
278
+ ].join("\n"));
279
+ },
280
+ "experimental.chat.messages.transform": async (_input, output) => {
281
+ if (config.mode === "off")
282
+ return;
283
+ for (const message of output.messages) {
284
+ const sessionID = String(message.info.sessionID ?? "unknown");
285
+ for (const part of message.parts) {
286
+ const key = findLargeTextKey(part);
287
+ if (!key)
288
+ continue;
289
+ const reduced = reducePlainTextForContext({
290
+ sessionID,
291
+ label: String(part.type ?? "message-part"),
292
+ text: String(part[key]),
293
+ maxTokens: config.maxContextPartTokens,
294
+ });
295
+ if (!reduced)
296
+ continue;
297
+ part[key] = reduced.text;
298
+ await engine.recordSavings(reduced.event);
299
+ }
300
+ }
301
+ },
302
+ "experimental.session.compacting": async (_input, output) => {
303
+ output.context.push([
304
+ "## TokenWarden continuation checklist",
305
+ "Preserve the current objective, relevant files, decisions already made, failed approaches, commands already run, test status, next actions, and anything the next agent must not forget.",
306
+ "Do not over-compress exact file paths, symbols, errors, or line numbers.",
307
+ ].join("\n"));
308
+ },
309
+ "tool.execute.after": async (input, output) => {
310
+ if (config.mode === "off")
311
+ return;
312
+ if (!output.output || typeof output.output !== "string")
313
+ return;
314
+ try {
315
+ const reduced = await engine.reduceToolOutput({
316
+ sessionID: input.sessionID,
317
+ callID: input.callID,
318
+ tool: input.tool,
319
+ args: input.args,
320
+ output: output.output,
321
+ maxTokens: config.maxToolOutputTokens,
322
+ });
323
+ output.output = reduced.output;
324
+ output.metadata = {
325
+ ...(output.metadata ?? {}),
326
+ tokenwarden: {
327
+ wouldHaveUsedTokens: reduced.rawTokens,
328
+ usedTokens: reduced.usedTokens,
329
+ savedTokens: reduced.savedTokens,
330
+ percentSaved: reduced.event.percentSaved,
331
+ rawPath: reduced.rawPath,
332
+ },
333
+ };
334
+ await engine.recordSavings(reduced.event);
335
+ await showSavingsToast(input.sessionID, reduced.savedTokens, reduced.event.percentSaved);
336
+ }
337
+ catch (error) {
338
+ await logFailure({
339
+ stage: "tool.execute.after",
340
+ sessionID: input.sessionID,
341
+ callID: input.callID,
342
+ tool: input.tool,
343
+ error,
344
+ });
345
+ output.metadata = {
346
+ ...(output.metadata ?? {}),
347
+ tokenwarden: {
348
+ error: error.message,
349
+ skipped: true,
350
+ },
351
+ };
352
+ }
353
+ },
354
+ tool: {
355
+ smart_read: tool({
356
+ description: "Read a source file in a token-efficient way. Returns symbols, exports, relevant blocks, or budgeted raw text and records would-have-used vs used token savings.",
357
+ args: {
358
+ path: tool.schema.string().describe("Project-relative file path to read."),
359
+ mode: tool.schema.enum(["symbols", "relevant", "summary", "raw-budgeted"]).optional(),
360
+ query: tool.schema.string().optional().describe("Query for relevant mode, such as refresh token handling."),
361
+ budget: tool.schema.number().int().positive().optional().describe("Maximum estimated tokens to return."),
362
+ },
363
+ async execute(args, context) {
364
+ try {
365
+ const result = await engine.smartRead({
366
+ root: context.directory || directory,
367
+ sessionID: context.sessionID,
368
+ path: args.path,
369
+ mode: args.mode,
370
+ query: args.query,
371
+ budget: args.budget,
372
+ });
373
+ await engine.recordSavings(result.event);
374
+ await showSavingsToast(context.sessionID, result.event.savedTokens, result.event.percentSaved);
375
+ return {
376
+ title: `smart_read ${args.path}`,
377
+ output: result.output,
378
+ metadata: { tokenwarden: result.event },
379
+ };
380
+ }
381
+ catch (error) {
382
+ await logFailure({ stage: "tool.smart_read", sessionID: context.sessionID, tool: "smart_read", error });
383
+ throw error;
384
+ }
385
+ },
386
+ }),
387
+ smart_pack: tool({
388
+ description: "Pack multiple source files into a token budget using code structure instead of raw oversized file reads. Records would-have-used vs used token savings.",
389
+ args: {
390
+ paths: tool.schema.array(tool.schema.string()).min(1).describe("Project-relative file paths or glob patterns."),
391
+ query: tool.schema.string().optional().describe("Optional topic to focus relevant blocks."),
392
+ budget: tool.schema.number().int().positive().optional().describe("Maximum estimated tokens to return."),
393
+ },
394
+ async execute(args, context) {
395
+ try {
396
+ const result = await engine.smartPack({
397
+ root: context.directory || directory,
398
+ sessionID: context.sessionID,
399
+ paths: args.paths,
400
+ query: args.query,
401
+ budget: args.budget,
402
+ });
403
+ await engine.recordSavings(result.event);
404
+ await showSavingsToast(context.sessionID, result.event.savedTokens, result.event.percentSaved);
405
+ return {
406
+ title: "smart_pack",
407
+ output: result.output,
408
+ metadata: { tokenwarden: result.event },
409
+ };
410
+ }
411
+ catch (error) {
412
+ await logFailure({ stage: "tool.smart_pack", sessionID: context.sessionID, tool: "smart_pack", error });
413
+ throw error;
414
+ }
415
+ },
416
+ }),
417
+ token_report: tool({
418
+ description: "Show TokenWarden token savings across all sessions: how many tokens would have been used, how many were used, tokens saved, and percentage saved. Uses red for would-have-used and green for saved values.",
419
+ args: {
420
+ sessionID: tool.schema.string().optional().describe("Optional session id. Defaults to all locally recorded sessions."),
421
+ color: tool.schema.boolean().optional().describe("Use ANSI colors. Defaults to true."),
422
+ },
423
+ async execute(args, context) {
424
+ try {
425
+ const summary = await engine.summarize(args.sessionID);
426
+ return {
427
+ title: "TokenWarden token report",
428
+ output: formatTokenReport(summary, { color: args.color ?? true }),
429
+ metadata: { tokenwarden: summary },
430
+ };
431
+ }
432
+ catch (error) {
433
+ await logFailure({ stage: "tool.token_report", sessionID: context.sessionID, tool: "token_report", error });
434
+ throw error;
435
+ }
436
+ },
437
+ }),
438
+ },
439
+ };
440
+ };
441
+ export default TokenWardenPlugin;
442
+ function normalizeOptions(options) {
443
+ return {
444
+ maxToolOutputTokens: numberOption(options?.maxToolOutputTokens, DEFAULTS.maxToolOutputTokens),
445
+ maxContextPartTokens: numberOption(options?.maxContextPartTokens, DEFAULTS.maxContextPartTokens),
446
+ mode: options?.mode === "off" ? "off" : DEFAULTS.mode,
447
+ dataDir: typeof options?.dataDir === "string" ? options.dataDir : undefined,
448
+ showToasts: typeof options?.showToasts === "boolean" ? options.showToasts : DEFAULTS.showToasts,
449
+ showReportToastOnSessionIdle: typeof options?.showReportToastOnSessionIdle === "boolean"
450
+ ? options.showReportToastOnSessionIdle
451
+ : DEFAULTS.showReportToastOnSessionIdle,
452
+ };
453
+ }
454
+ function numberOption(value, fallback) {
455
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
456
+ }
457
+ function findLargeTextKey(part) {
458
+ for (const key of ["output", "text", "content"]) {
459
+ if (typeof part[key] === "string")
460
+ return key;
461
+ }
462
+ return undefined;
463
+ }
464
+ function sessionIDFromEvent(event) {
465
+ if (!event.properties || typeof event.properties !== "object")
466
+ return undefined;
467
+ const sessionID = event.properties.sessionID;
468
+ return typeof sessionID === "string" ? sessionID : undefined;
469
+ }
470
+ function accountPageURL(entitlement) {
471
+ const url = new URL("/account", tokenWardenSiteURL());
472
+ if (entitlement.monthlyCapTokens === null) {
473
+ url.searchParams.set("optimization", "unlimited");
474
+ return url.toString();
475
+ }
476
+ url.searchParams.set("remainingOptimizableTokens", String(entitlement.remainingFreeTokens ?? 0));
477
+ url.searchParams.set("monthlyOptimizedTokens", String(entitlement.monthlySavedTokens));
478
+ url.searchParams.set("monthlyCapTokens", String(entitlement.monthlyCapTokens));
479
+ return url.toString();
480
+ }
481
+ function openBrowser(url) {
482
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
483
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
484
+ try {
485
+ const child = spawn(command, args, { detached: true, stdio: "ignore" });
486
+ child.unref();
487
+ return true;
488
+ }
489
+ catch {
490
+ return false;
491
+ }
492
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "siteURL": "https://tokenwarden.ai/",
3
+ "generatedAt": "2026-06-11T13:05:49.344Z"
4
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "alg": "Ed25519",
3
+ "payload": {
4
+ "id": "tokenwarden-engine",
5
+ "version": "0.1.0",
6
+ "protocol": 1,
7
+ "binary": "tokenwarden-engine-darwin-arm64",
8
+ "platform": "darwin",
9
+ "arch": "arm64",
10
+ "sha256": "513d90c920758b1f758d2d99c39c459f133d736eb13e188916c1234c94f14076",
11
+ "builtAt": "2026-06-11T13:05:49.344Z",
12
+ "signingKey": "production-env",
13
+ "freeMonthlySavedTokenCap": 1000000,
14
+ "siteURL": "https://tokenwarden.ai/"
15
+ },
16
+ "signature": "ohu6u2V8MxAgOqsD9wTka9kfSgSLCjA5prmvxgK6WsTQoKm471WhcNteclSdGYrQQRVlqeJ0KXa3XRpXBUjmDg"
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "alg": "Ed25519",
3
+ "payload": {
4
+ "id": "tokenwarden-engine",
5
+ "version": "0.1.0",
6
+ "protocol": 1,
7
+ "binary": "tokenwarden-engine-darwin-x64",
8
+ "platform": "darwin",
9
+ "arch": "x64",
10
+ "sha256": "ddba81a47b8bd308839765b04b1c48a837754b4074ff023ca96676019e8e3744",
11
+ "builtAt": "2026-06-11T13:05:49.344Z",
12
+ "signingKey": "production-env",
13
+ "freeMonthlySavedTokenCap": 1000000,
14
+ "siteURL": "https://tokenwarden.ai/"
15
+ },
16
+ "signature": "z3ztSkpcI06pn7z2U1RigF7tQEfsjlrXKSp-8guDC-EE8lU3X2MGNSNef8TiZb16BUnv_i8Sx6ZmX7GJ_k7ACA"
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "alg": "Ed25519",
3
+ "payload": {
4
+ "id": "tokenwarden-engine",
5
+ "version": "0.1.0",
6
+ "protocol": 1,
7
+ "binary": "tokenwarden-engine-linux-arm64",
8
+ "platform": "linux",
9
+ "arch": "arm64",
10
+ "sha256": "06d9ad12796c9870a1a0515af08faa3e27fd1e4485b7c74bfd211ae32d617d7c",
11
+ "builtAt": "2026-06-11T13:05:49.344Z",
12
+ "signingKey": "production-env",
13
+ "freeMonthlySavedTokenCap": 1000000,
14
+ "siteURL": "https://tokenwarden.ai/"
15
+ },
16
+ "signature": "ixfSSTPk6pFKUyQFfcJrNLVybDxwxVojYix4QAYmpGrawkcvtt5JUzRsFCoLHxYPI5ysCIEiGNL9MO_7uKo8Cw"
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "alg": "Ed25519",
3
+ "payload": {
4
+ "id": "tokenwarden-engine",
5
+ "version": "0.1.0",
6
+ "protocol": 1,
7
+ "binary": "tokenwarden-engine-linux-x64",
8
+ "platform": "linux",
9
+ "arch": "x64",
10
+ "sha256": "92767449e681a41c62fc2f9f732ea3b17d8f99b0d66d42abbffcdf3f0bdff226",
11
+ "builtAt": "2026-06-11T13:05:49.344Z",
12
+ "signingKey": "production-env",
13
+ "freeMonthlySavedTokenCap": 1000000,
14
+ "siteURL": "https://tokenwarden.ai/"
15
+ },
16
+ "signature": "NiVRGdhL9C4CFh0ra8HqtCOmjzx6hU9QzIIySx5OWIyBnjft6LNnwCo02moBUYwH9Y9uofQxkiyW1zeb0RuLBA"
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "alg": "Ed25519",
3
+ "payload": {
4
+ "id": "tokenwarden-engine",
5
+ "version": "0.1.0",
6
+ "protocol": 1,
7
+ "binary": "tokenwarden-engine-win32-x64.exe",
8
+ "platform": "win32",
9
+ "arch": "x64",
10
+ "sha256": "135c25f98391ea440bbc806f3b4085eec6beaa111f4ac6fa6758f9a0e99c5074",
11
+ "builtAt": "2026-06-11T13:05:49.344Z",
12
+ "signingKey": "production-env",
13
+ "freeMonthlySavedTokenCap": 1000000,
14
+ "siteURL": "https://tokenwarden.ai/"
15
+ },
16
+ "signature": "xSgOpISMvOIHfC5j-Af6B1We-wwRa5jLEYu6K7hhsCNclcMPUhfv6jk2SZWNs-Uf3t38DYoLDCmrBBUDsjoyDA"
17
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@tokenwarden/opencode",
3
+ "version": "0.1.0",
4
+ "description": "Local-first opencode cost and context control plugin.",
5
+ "type": "module",
6
+ "main": "dist/src/plugin/index.js",
7
+ "bin": {
8
+ "tokenwarden": "dist/src/cli/index.js"
9
+ },
10
+ "files": [
11
+ "dist/src",
12
+ "native/bin",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "npm run clean && npm run build:sidecar && npm run build:ts",
17
+ "build:sidecar": "node scripts/build-sidecar.mjs",
18
+ "build:ts": "tsc -p tsconfig.json",
19
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
20
+ "prepublishOnly": "npm run verify:package",
21
+ "verify:package": "node scripts/verify-package-files.mjs",
22
+ "test": "npm run build && npm run test:built",
23
+ "test:built": "node --test dist/tests/*.test.js",
24
+ "report": "npm run build && node dist/src/cli/index.js report"
25
+ },
26
+ "dependencies": {
27
+ "@opencode-ai/plugin": "latest"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "latest",
31
+ "typescript": "latest"
32
+ }
33
+ }