pantheon-opencode 1.6.0-beta.7 → 1.6.0-beta.9

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 (66) hide show
  1. package/AGENTS.md +58 -15
  2. package/CHANGELOG.md +61 -94
  3. package/README.md +81 -103
  4. package/README.pt-BR.md +19 -37
  5. package/commands/pantheon-audit.md +7 -1
  6. package/commands/pantheon-consolidate.md +1 -1
  7. package/commands/pantheon-focus.md +17 -0
  8. package/package.json +1 -2
  9. package/plugin.json +2 -2
  10. package/pyproject.toml +1 -1
  11. package/scripts/build-agents-md.mjs +2 -3
  12. package/scripts/doctor.mjs +22 -104
  13. package/scripts/hooks/scan-secrets.sh +97 -0
  14. package/scripts/install/config-migration.mjs +9 -42
  15. package/scripts/install/interactive.mjs +1 -1
  16. package/scripts/install/opencode.mjs +174 -77
  17. package/scripts/install/plugin.mjs +43 -9
  18. package/scripts/install/strings.mjs +0 -8
  19. package/scripts/probe-pantheon-cost.mjs +3 -16
  20. package/scripts/sync-artifacts.mjs +1 -0
  21. package/scripts/sync-tui.mjs +24 -3
  22. package/scripts/test-opencode-v2-sandbox.sh +203 -786
  23. package/scripts/validate-sandbox-install.mjs +138 -0
  24. package/skills-lock.json +4 -5
  25. package/src/agents/aphrodite.md +2 -9
  26. package/src/agents/demeter.md +0 -9
  27. package/src/agents/gaia.md +1 -0
  28. package/src/agents/hephaestus.md +0 -9
  29. package/src/agents/hermes.md +2 -9
  30. package/src/agents/mnemosyne.md +18 -53
  31. package/src/agents/prometheus.md +0 -9
  32. package/src/agents/themis.md +0 -1
  33. package/src/agents/zeus.md +180 -19
  34. package/src/instructions/agent-return-format.instructions.md +48 -3
  35. package/src/instructions/memory-protocol.instructions.md +8 -9
  36. package/src/instructions/zeus-anti-stall.instructions.md +54 -6
  37. package/src/instructions/zeus-council-synthesis.instructions.md +71 -0
  38. package/src/instructions/zeus-memory-operations.instructions.md +4 -4
  39. package/src/instructions/zeus-timeout-retry.instructions.md +81 -7
  40. package/src/pantheon/v2-delegation-enforce.ts +2 -1
  41. package/src/plugin-v2.ts +251 -168
  42. package/src/plugins/pantheon-hooks.ts +35 -82
  43. package/src/plugins/tui/dist/tui-v2.d.ts +9 -0
  44. package/src/plugins/tui/dist/tui-v2.d.ts.map +1 -0
  45. package/src/plugins/tui/dist/tui-v2.js +151 -0
  46. package/src/plugins/tui/dist/tui-v2.js.map +1 -0
  47. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  48. package/src/plugins/tui/dist/tui.js +29 -53
  49. package/src/plugins/tui/index-v2.ts +6 -0
  50. package/src/plugins/tui/package-lock.json +5411 -1292
  51. package/src/plugins/tui/package.json +10 -7
  52. package/src/plugins/tui/src/index-v2.tsx +108 -0
  53. package/src/plugins/tui/src/index.tsx +28 -63
  54. package/src/plugins/tui/src/opencode-v2.d.ts +13 -0
  55. package/src/plugins/tui/tsdown.config.ts +2 -2
  56. package/src/plugins/tui/tui-v2.ts +2 -0
  57. package/src/plugins/tui/tui.ts +3 -0
  58. package/src/routing.yml +4 -2
  59. package/src/skills/README.md +7 -12
  60. package/src/skills/agent-coordination/SKILL.md +83 -13
  61. package/src/skills/auto-continue/SKILL.md +13 -14
  62. package/src/skills/context-compression/SKILL.md +26 -879
  63. package/src/skills/session-goal/SKILL.md +14 -5
  64. package/src/pantheon/secret-scanner.ts +0 -489
  65. package/src/plugins/tui/README.md +0 -14
  66. package/src/skills/council-synthesis/SKILL.md +0 -29
@@ -4,13 +4,14 @@
4
4
  * Re-created per Council Synthesis 2026-08-05 (P0): the previous hook plugin
5
5
  * was removed on 24-25/07 because it used the Bun Shell `$` from the plugin
6
6
  * API, whose interface has NO `.timeout()` method — every hook crashed with
7
- * `TypeError: $.quiet().nothrow().timeout is not a function`. The remaining
7
+ * `TypeError: $.quiet().nothrow().timeout is not a function`. The 10 functional
8
8
  * scripts in scripts/hooks/ (gitleaks covers only staged files, NOT tool
9
9
  * calls) were left orphaned. This plugin wires them back into the opencode
10
10
  * runtime via `node:child_process` (version-proof — see hook-runner.ts).
11
11
  *
12
- * Hook mapping:
13
- * - tool.execute.before → in-process secret scan, validate-talos-scope.sh,
12
+ * Hook mapping (Claude Code protocol payload {tool_name, tool_input,
13
+ * agent_id, session_id} delivered on the script stdin):
14
+ * - tool.execute.before → validate-talos-scope.sh, scan-secrets.sh,
14
15
  * validate-tool-safety.sh + on-subagent-delegation-start.sh (only when the
15
16
  * tool is a delegation tool such as `task`, to keep the audit log honest)
16
17
  * - tool.execute.after → format-multi-language.sh, log-session-start.sh +
@@ -22,7 +23,7 @@
22
23
  * ({tool, sessionID, callID}). P0-4 (2026-08-06): a `chat.params` hook
23
24
  * records the active agent per session (sessionAgent map), so payloadFor()
24
25
  * now resolves the real agent_id — validate-talos-scope.sh enforces the
25
- * Talos file-count boundary; the agent-agnostic secret scanner and
26
+ * Talos file-count boundary; the agent-agnostic scan-secrets and
26
27
  * validate-tool-safety protections remain fully active.
27
28
  * - tool.execute.before CAN hard-deny — the old "cannot hard-deny" claim
28
29
  * below this line is OUTDATED for 1.18.x (verified 2026-08-06 on the
@@ -33,15 +34,15 @@
33
34
  * the tool body runs (the docs' .env-protection example relies on this).
34
35
  * The permission system remains the primary deny path, but a thrown
35
36
  * hook error is now a supported hard block (hybrid blocking, Fix 2
36
- * 2026-08-06): high-confidence token matches → the plugin logs via
37
- * reportFailure (non-TUI channels) and THEN throws
37
+ * 2026-08-06): scan-secrets.sh exits 2 on high-confidence token matches
38
+ * → the plugin logs via reportFailure (non-TUI channels) and THEN throws
38
39
  * the DELIBERATE block error — the ONLY throw in this plugin. Exit 1
39
40
  * (low-confidence header/KEY names) stays advisory: toast + logs, no
40
41
  * block.
41
42
  * - Logging policy (P0 fix 2026-08-06): NON-ZERO exit → NEVER console.
42
43
  * `console.error` in a plugin writes to the process stderr, which the
43
44
  * opencode TUI renders directly into the terminal — that polluted the
44
- * chat with noisy nonzero hook output
45
+ * chat with `[pantheon-hooks:scan-secrets.sh] exit 1: [SECRET SCAN]...`
45
46
  * spam on every tool call. Non-zero exits now go to THREE non-TUI channels:
46
47
  * 1. client.tui.showToast() — one short, deduped TUI toast (per
47
48
  * (script, exit code, masked match) per session) so the user gets a
@@ -59,8 +60,8 @@
59
60
  * - Hooks NEVER throw: every body is try/catch'd and only logs. All three
60
61
  * reporting channels (toast, app.log, file append) are individually
61
62
  * try/catch'd so a failure in one can never take down the session.
62
- * EXCEPTION (Fix 2, 2026-08-06): the deliberate high-confidence secret
63
- * block — when the in-process scanner finds a token, the hook awaits
63
+ * EXCEPTION (Fix 2, 2026-08-06): the deliberate scan-secrets high-
64
+ * confidence block — when scan-secrets.sh exits 2, the hook awaits
64
65
  * reportFailure FIRST (app.log + hooks.log) and only then throws. The
65
66
  * throw is deliberately NOT swallowed by the surrounding try/catch
66
67
  * (handled via a blockError variable thrown after it), because that
@@ -176,7 +177,6 @@ import { appendFile, mkdir } from 'node:fs/promises'
176
177
  import { join } from 'node:path'
177
178
  import type { Plugin, PluginInput } from '@opencode-ai/plugin'
178
179
  import { pantheonPluginOnce } from '../pantheon/plugin-once.ts'
179
- import { scanSecretPayload } from '../pantheon/secret-scanner.ts'
180
180
  import { getSharedBoard } from '../pantheon/shared-board.ts'
181
181
  import { type HookPayload, type HookResult, runHook } from './hook-runner.ts'
182
182
 
@@ -320,7 +320,8 @@ const sessionAgent = new Map<string, { agent: string; at: number }>()
320
320
  const RISKY_PATH_RE = /migration|schema\.sql|alembic|models\/|auth|password|token|secret/i
321
321
 
322
322
  /**
323
- * Fix 1 (2026-08-06): high-confidence secret token formats. Used by maskSecret()
323
+ * Fix 1 (2026-08-06): high-confidence secret token formats — mirrors
324
+ * scripts/hooks/scan-secrets.sh HIGH_CONFIDENCE_PATTERNS. Used by maskSecret()
324
325
  * to REDACT secret VALUES from any plugin log line: the talos-guard must
325
326
  * never write a raw credential to hooks.log (a real `sk-bf-...` token leaked
326
327
  * in plain text there on 2026-08-06). Header/KEY names (the Bifrost header alone,
@@ -340,8 +341,8 @@ function maskSecret(text: string): string {
340
341
  }
341
342
 
342
343
  /**
343
- * Fix 2 (2026-08-06): message thrown on a high-confidence token match.
344
- * This is the ONLY deliberate throw in the
344
+ * Fix 2 (2026-08-06): message thrown when scan-secrets.sh exits 2
345
+ * (HIGH_CONFIDENCE token match). This is the ONLY deliberate throw in the
345
346
  * plugin — it is thrown AFTER reportFailure so the log channels always write
346
347
  * first, and it is NOT swallowed by the hook's try/catch: opencode's
347
348
  * Plugin.trigger has no catch around hook invocations, so the rejection
@@ -1092,8 +1093,9 @@ function flushIdleReminders(ctx: HookContext): void {
1092
1093
  * (echo routed to structured log + hooks.log, never the TUI).
1093
1094
  * No console.error / console.log anywhere in this path — the TUI stays clean.
1094
1095
  *
1095
- * Returns the underlying HookResult (or null on unexpected failure) for the
1096
- * non-scanner script hooks.
1096
+ * Returns the underlying HookResult (or null on unexpected failure) so the
1097
+ * tool.execute.before handler can inspect scan-secrets.sh's exit code for the
1098
+ * Fix-2 hybrid block (code 2 = high-confidence token → throw after logging).
1097
1099
  */
1098
1100
  async function safeRun(
1099
1101
  ctx: HookContext,
@@ -1175,26 +1177,15 @@ const plugin: Plugin = async ({ client, directory }) => {
1175
1177
  // catch: opencode propagates the rejection to fail the tool call.
1176
1178
  let blockError: Error | null = null
1177
1179
  try {
1178
- const hooks = ['validate-talos-scope.sh', 'validate-tool-safety.sh']
1180
+ const hooks = ['validate-talos-scope.sh', 'scan-secrets.sh', 'validate-tool-safety.sh']
1179
1181
  // P0-2: the delegation record must exist BEFORE the payload is built
1180
1182
  // so the start hook receives the delegation_id the stop hook joins on.
1181
- const args = output?.args ?? (input as { args?: unknown }).args
1182
- let payload = payloadFor(input, args)
1183
- // Validate and scan before touching delegation args or launching any
1184
- // hook subprocess. The shared scanner catches malformed serialization
1185
- // and returns a fixed fail-closed result.
1186
- const scan = scanSecretPayload({ ...payload, tool_input: args })
1187
- if (scan.status === 'invalid') {
1188
- blockError = new Error(
1189
- '[pantheon-hooks] Bloqueado: o scanner de segredos não validou a entrada da ferramenta (fail-closed)',
1190
- )
1191
- } else if (scan.status === 'block') {
1192
- blockError = new Error(SECRET_BLOCK_MESSAGE)
1193
- }
1194
- if (blockError === null && DELEGATION_TOOL_RE.test(input.tool)) {
1183
+ let payload = payloadFor(input, output?.args ?? {})
1184
+ if (DELEGATION_TOOL_RE.test(input.tool)) {
1195
1185
  hooks.push('on-subagent-delegation-start.sh')
1196
1186
  // tool.execute.before carries args on `output` (typed); `input.args`
1197
1187
  // also exists at runtime — read from whichever is present.
1188
+ const args = output?.args ?? (input as { args?: unknown }).args
1198
1189
  const start: DelegationStart = {
1199
1190
  delegation_id: crypto.randomUUID(),
1200
1191
  agent: delegationAgent(input.tool, args),
@@ -1231,7 +1222,7 @@ const plugin: Plugin = async ({ client, directory }) => {
1231
1222
  const isWriteIntent =
1232
1223
  WRITE_INTENT_TOOLS.has(input.tool) ||
1233
1224
  (input.tool === 'bash' && BASH_WRITE_INTENT_RE.test(bashCommand))
1234
- if (blockError === null && isWriteIntent) {
1225
+ if (isWriteIntent) {
1235
1226
  try {
1236
1227
  const values: string[] = []
1237
1228
  const scan = (v: unknown): void => {
@@ -1282,63 +1273,25 @@ const plugin: Plugin = async ({ client, directory }) => {
1282
1273
  // Advisory only — a failure here must never take down the hook.
1283
1274
  }
1284
1275
  }
1285
- // Scan the same serialized hook payload the removed stdin protocol
1286
- // delivered, but in-process and without logging untrusted input.
1287
- if (scan.status === 'invalid') {
1288
- const message =
1289
- '[pantheon-hooks] Bloqueado: o scanner de segredos não validou a entrada da ferramenta (fail-closed)'
1290
- await reportFailure(
1291
- ctx,
1292
- message,
1293
- { script: 'secret-scan', tool: input.tool, blocked: true, reason: 'invalid-input' },
1294
- 'error',
1295
- )
1296
- blockError = new Error(message)
1297
- } else if (scan.status === 'block') {
1298
- for (const finding of scan.findings) {
1299
- await reportFailure(
1300
- ctx,
1301
- `[SECRET SCAN] High-confidence secret detected: ${finding.masked} (pattern: ${finding.pattern})`,
1302
- { script: 'secret-scan', tool: input.tool, pattern: finding.pattern },
1303
- 'error',
1304
- )
1305
- }
1276
+ // Fix 2 hybrid blocking: safeRun returns each script's HookResult;
1277
+ // scan-secrets.sh exits 2 on HIGH_CONFIDENCE token matches
1278
+ // (sk-bf-*, AKIA..., ghp_..., glpat-..., JWT, bearer...) → BLOCK the
1279
+ // tool call. Exit 1 (low-confidence header/KEY names) stays advisory
1280
+ // — safeRun already logged + toasted it, no block. The toast for
1281
+ // code 1/2 fires inside safeRun BEFORE we reach the throw, and the
1282
+ // reportFailure below is awaited first — log channels always write.
1283
+ const results = await Promise.all(
1284
+ hooks.map(async (script) => ({ script, result: await safeRun(ctx, script, payload) })),
1285
+ )
1286
+ const scan = results.find((r) => r.script === 'scan-secrets.sh')
1287
+ if (scan?.result?.code === 2) {
1306
1288
  await reportFailure(
1307
1289
  ctx,
1308
1290
  SECRET_BLOCK_MESSAGE,
1309
- { script: 'secret-scan', tool: input.tool, blocked: true },
1291
+ { script: 'scan-secrets', tool: input.tool, blocked: true, code: 2 },
1310
1292
  'error',
1311
1293
  )
1312
- void notifyToast(ctx, {
1313
- title: 'Pantheon Hook',
1314
- message: '⚠️ High-confidence secret detected — tool call blocked',
1315
- variant: 'error',
1316
- duration: 4000,
1317
- category: 'errors',
1318
- dedupeKey: `secret-scan|block|${input.sessionID}`,
1319
- })
1320
1294
  blockError = new Error(SECRET_BLOCK_MESSAGE)
1321
- } else if (scan.status === 'advisory') {
1322
- for (const finding of scan.findings) {
1323
- await reportFailure(
1324
- ctx,
1325
- `[SECRET SCAN] Potential secret detected (low confidence): ${finding.masked} (pattern: ${finding.pattern})`,
1326
- { script: 'secret-scan', tool: input.tool, pattern: finding.pattern },
1327
- 'warn',
1328
- )
1329
- }
1330
- void notifyToast(ctx, {
1331
- title: 'Pantheon Hook',
1332
- message: '⚠️ Possible secret detected — see log',
1333
- variant: 'warning',
1334
- duration: 4000,
1335
- category: 'errors',
1336
- dedupeKey: `secret-scan|advisory|${input.sessionID}`,
1337
- })
1338
- }
1339
-
1340
- if (blockError === null) {
1341
- await Promise.all(hooks.map(async (script) => safeRun(ctx, script, payload)))
1342
1295
  }
1343
1296
  } catch {
1344
1297
  // Never let a hook take down the opencode session — except the
@@ -0,0 +1,9 @@
1
+ //#region src/index-v2.d.ts
2
+ /** @jsxImportSource @opentui/solid */
3
+ declare const plugin: {
4
+ id: string;
5
+ setup(context: any): (() => void) | undefined;
6
+ };
7
+ //#endregion
8
+ export { plugin as default };
9
+ //# sourceMappingURL=tui-v2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-v2.d.ts","names":[],"sources":["../src/index-v2.tsx"],"mappings":";;cAuFM;;EAEW,MAAA"}
@@ -0,0 +1,151 @@
1
+ import { createComponent, createElement, createTextNode, effect, insert, insertNode, setProp } from "@opentui/solid";
2
+ import { For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-js";
3
+ import { Plugin } from "@opencode/plugin/tui";
4
+ //#region src/index-v2.tsx
5
+ /** @jsxImportSource @opentui/solid */
6
+ const SINGLETON = Symbol.for("pantheon.tui.v2.loaded");
7
+ function Sidebar(props) {
8
+ const { context } = props;
9
+ const [revision, setRevision] = createSignal(0);
10
+ const location = context.location ?? context.data.location.default();
11
+ const session = createMemo(() => {
12
+ revision();
13
+ return props.sessionID ? context.data.session.get(props.sessionID) : void 0;
14
+ });
15
+ const branch = createMemo(() => {
16
+ revision();
17
+ return context.data.location.vcs.info(location)?.branch?.current ?? null;
18
+ });
19
+ const delegations = createMemo(() => {
20
+ revision();
21
+ const id = props.sessionID;
22
+ if (!id) return [];
23
+ return (context.data.session.family(id) ?? []).filter((childID) => childID !== id).map((childID) => context.data.session.get(childID)).filter((child) => child?.parentID === id).slice(0, 6);
24
+ });
25
+ onMount(() => {
26
+ const stop = [];
27
+ for (const type of [
28
+ "session.created",
29
+ "session.updated",
30
+ "session.status",
31
+ "session.deleted"
32
+ ]) try {
33
+ stop.push(context.data.on(type, () => setRevision((value) => value + 1)));
34
+ } catch {}
35
+ if (props.sessionID) context.data.session.sync(props.sessionID).then(() => setRevision((value) => value + 1));
36
+ context.data.location.vcs.sync(location).then(() => setRevision((value) => value + 1));
37
+ onCleanup(() => {
38
+ for (const unsubscribe of stop) unsubscribe();
39
+ });
40
+ });
41
+ const text = () => context.theme.text?.base;
42
+ const muted = () => context.theme.text?.muted ?? text();
43
+ return (() => {
44
+ var _el$ = createElement("box"), _el$2 = createElement("text"), _el$3 = createElement("text");
45
+ insertNode(_el$, _el$2);
46
+ insertNode(_el$, _el$3);
47
+ setProp(_el$, "flexDirection", "column");
48
+ setProp(_el$, "width", "100%");
49
+ setProp(_el$, "padding", 1);
50
+ setProp(_el$2, "attributes", 1);
51
+ insert(_el$2, `Pantheon v1.6.0-beta.9`);
52
+ insertNode(_el$3, createTextNode(`OpenCode V2 · plugin TUI ativo`));
53
+ insert(_el$, createComponent(Show, {
54
+ get when() {
55
+ return branch();
56
+ },
57
+ children: (value) => (() => {
58
+ var _el$7 = createElement("text");
59
+ insert(_el$7, () => `Branch: ${value()}`);
60
+ effect((_$p) => setProp(_el$7, "fg", muted(), _$p));
61
+ return _el$7;
62
+ })()
63
+ }), null);
64
+ insert(_el$, createComponent(Show, {
65
+ get when() {
66
+ return session();
67
+ },
68
+ get fallback() {
69
+ return (() => {
70
+ var _el$8 = createElement("text");
71
+ insertNode(_el$8, createTextNode(`Nenhuma sessão selecionada`));
72
+ effect((_$p) => setProp(_el$8, "fg", muted(), _$p));
73
+ return _el$8;
74
+ })();
75
+ },
76
+ children: (current) => [(() => {
77
+ var _el$0 = createElement("text");
78
+ insert(_el$0, () => `Sessão: ${current().title || current().id}`);
79
+ effect((_$p) => setProp(_el$0, "fg", text(), _$p));
80
+ return _el$0;
81
+ })(), createComponent(Show, {
82
+ get when() {
83
+ return current().agent;
84
+ },
85
+ get children() {
86
+ var _el$1 = createElement("text");
87
+ insert(_el$1, () => `Agente: ${current().agent}`);
88
+ effect((_$p) => setProp(_el$1, "fg", muted(), _$p));
89
+ return _el$1;
90
+ }
91
+ })]
92
+ }), null);
93
+ insert(_el$, createComponent(Show, {
94
+ get when() {
95
+ return delegations().length > 0;
96
+ },
97
+ get children() {
98
+ return [(() => {
99
+ var _el$5 = createElement("text");
100
+ insertNode(_el$5, createTextNode(`Delegações`));
101
+ setProp(_el$5, "attributes", 1);
102
+ effect((_$p) => setProp(_el$5, "fg", text(), _$p));
103
+ return _el$5;
104
+ })(), createComponent(For, {
105
+ get each() {
106
+ return delegations();
107
+ },
108
+ children: (child) => (() => {
109
+ var _el$10 = createElement("text");
110
+ insert(_el$10, () => `· ${child.title || child.id}`);
111
+ effect((_$p) => setProp(_el$10, "fg", muted(), _$p));
112
+ return _el$10;
113
+ })()
114
+ })];
115
+ }
116
+ }), null);
117
+ effect((_p$) => {
118
+ var _v$ = text(), _v$2 = muted();
119
+ _v$ !== _p$.e && (_p$.e = setProp(_el$2, "fg", _v$, _p$.e));
120
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$3, "fg", _v$2, _p$.t));
121
+ return _p$;
122
+ }, {
123
+ e: void 0,
124
+ t: void 0
125
+ });
126
+ return _el$;
127
+ })();
128
+ }
129
+ const plugin = Plugin.define({
130
+ id: "pantheon.tui",
131
+ setup(context) {
132
+ const globalState = globalThis;
133
+ if (globalState[SINGLETON]) return;
134
+ globalState[SINGLETON] = true;
135
+ const unregister = context.ui.slot({
136
+ append: "sidebar.content",
137
+ render: ({ sessionID }) => createComponent(Sidebar, {
138
+ context,
139
+ sessionID
140
+ })
141
+ });
142
+ return () => {
143
+ if (typeof unregister === "function") unregister();
144
+ delete globalState[SINGLETON];
145
+ };
146
+ }
147
+ });
148
+ //#endregion
149
+ export { plugin as default };
150
+
151
+ //# sourceMappingURL=tui-v2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-v2.js","names":[],"sources":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":"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"}
1
+ {"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":"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"}
@@ -10,9 +10,9 @@ import { For, Show, createMemo, createResource, createSignal, onCleanup, onMount
10
10
  /**
11
11
  * pantheon-tui — Pantheon TUI plugin for opencode.
12
12
  *
13
- * Bundled entry: `tsdown` emits `dist/tui.js`, and the package `exports`
14
- * (`./tui` → ./dist/tui.js, `./server` → ./dist/server.js) is the ONLY load
15
- * path — no raw TSX copy is shipped. Because the bundle inlines relative
13
+ * V1 bundle: `tsdown` emits `dist/tui.js`, re-exported by root `tui.ts`.
14
+ * OpenCode V2 uses the separate `src/index-v2.tsx` entry and native CLI plugin
15
+ * API. No raw TSX implementation is shipped. Because the bundle inlines relative
16
16
  * imports, this entry may be split into modules without breaking the loader.
17
17
  *
18
18
  * Slots:
@@ -167,7 +167,7 @@ async function detectVersion(api) {
167
167
  if (ver) return ver;
168
168
  }
169
169
  } catch {}
170
- return "1.6.0-beta.7";
170
+ return "1.6.0-beta.9";
171
171
  }
172
172
  /**
173
173
  * usage-bar — AI subscription usage gauge for the opencode TUI.
@@ -509,22 +509,12 @@ async function loadConfig(configPath) {
509
509
  return defaultConfig();
510
510
  }
511
511
  }
512
- /** Vendored usage-bar plugin init: reads config, seeds the kv cache, registers
513
- * the `app_bottom` slot, then starts the poll loop. Skips everything (and
512
+ /** Vendored usage-bar plugin init: reads config, seeds the kv cache, starts
513
+ * the poll loop and registers the `app_bottom` slot. Skips everything (and
514
514
  * registers nothing) when no provider is enabled — mirrors the standalone
515
515
  * plugin's early return. */
516
516
  async function setupUsageBar(api) {
517
- let disposed = false;
518
- let clockInterval;
519
- const pollTimeouts = /* @__PURE__ */ new Set();
520
- api.lifecycle.onDispose(() => {
521
- disposed = true;
522
- if (clockInterval !== void 0) clearInterval(clockInterval);
523
- for (const timeout of pollTimeouts) clearTimeout(timeout);
524
- pollTimeouts.clear();
525
- });
526
517
  const config = await loadConfig(api.state.path?.config);
527
- if (disposed) return;
528
518
  if (api.state.path?.state) opencodeAuthFile = join(api.state.path.state, "auth.json");
529
519
  const enabled = providers.filter((p) => config.providers[p.id].enabled);
530
520
  if (enabled.length === 0) return;
@@ -536,6 +526,28 @@ async function setupUsageBar(api) {
536
526
  const [byProvider, setByProvider] = createSignal(seed);
537
527
  const [byStatus, setByStatus] = createSignal({});
538
528
  const [now, setNow] = createSignal(Date.now());
529
+ setInterval(() => setNow(Date.now()), 1e3);
530
+ for (const p of enabled) {
531
+ const cfg = config.providers[p.id];
532
+ const poll = async () => {
533
+ const statusP = config.showStatus && p.statusUrl ? fetchStatus(p.statusUrl) : null;
534
+ const [all, status] = await Promise.all([p.fetchUsage(cfg), statusP]);
535
+ if (all) {
536
+ const windows = all.filter((w) => cfg.show[w.category] && w.resetsAt > Date.now());
537
+ setByProvider((prev) => ({
538
+ ...prev,
539
+ [p.id]: windows
540
+ }));
541
+ api.kv.set(`usage-bar.${p.id}.windows`, windows);
542
+ }
543
+ if (status !== null) setByStatus((prev) => ({
544
+ ...prev,
545
+ [p.id]: status
546
+ }));
547
+ setTimeout(poll, all ? POLL_MS : POLL_MS * 3);
548
+ };
549
+ poll();
550
+ }
539
551
  api.slots.register({
540
552
  order: 60,
541
553
  slots: { app_bottom() {
@@ -684,42 +696,6 @@ async function setupUsageBar(api) {
684
696
  });
685
697
  } }
686
698
  });
687
- clockInterval = setInterval(() => setNow(Date.now()), 1e3);
688
- const schedulePoll = (run, delay) => {
689
- if (disposed) return;
690
- const timeout = setTimeout(() => {
691
- pollTimeouts.delete(timeout);
692
- if (!disposed) run();
693
- }, delay);
694
- pollTimeouts.add(timeout);
695
- };
696
- for (const p of enabled) {
697
- const cfg = config.providers[p.id];
698
- const poll = async () => {
699
- if (disposed) return;
700
- const statusP = config.showStatus && p.statusUrl ? fetchStatus(p.statusUrl) : null;
701
- const [all, status] = await Promise.all([p.fetchUsage(cfg), statusP]);
702
- if (disposed) return;
703
- if (all) {
704
- const windows = all.filter((w) => cfg.show[w.category] && w.resetsAt > Date.now());
705
- setByProvider((prev) => ({
706
- ...prev,
707
- [p.id]: windows
708
- }));
709
- api.kv.set(`usage-bar.${p.id}.windows`, windows);
710
- }
711
- if (status !== null) setByStatus((prev) => ({
712
- ...prev,
713
- [p.id]: status
714
- }));
715
- schedulePoll(runPoll, all ? POLL_MS : POLL_MS * 3);
716
- };
717
- const runPoll = () => {
718
- if (disposed) return;
719
- poll().catch(() => schedulePoll(runPoll, POLL_MS * 3));
720
- };
721
- runPoll();
722
- }
723
699
  }
724
700
  /** True for `\s` characters (space, tab, newline, CR) — plain char checks so
725
701
  * the parser stays regex-free (CodeQL flagged the old `\s*`/`\s+` + `(.+)`
@@ -2232,7 +2208,7 @@ const tui = (api, _options, _meta) => {
2232
2208
  if (pantheonPluginOnce("pantheon:tui")) return;
2233
2209
  const [version, setVersion] = createSignal(null);
2234
2210
  detectVersion(api).then((detected) => setVersion(detected)).catch(() => setVersion(null));
2235
- setupUsageBar(api).catch(() => void 0);
2211
+ setupUsageBar(api);
2236
2212
  const liveStore = createLiveDelegationStore();
2237
2213
  const unsubscribeLive = registerLiveDelegationEvents(api, liveStore);
2238
2214
  api.lifecycle.onDispose(() => {
@@ -0,0 +1,6 @@
1
+ import { Plugin } from '@opencode/plugin'
2
+
3
+ export default Plugin.define({
4
+ id: 'pantheon.tui.server',
5
+ setup() {},
6
+ })