@rockysoft/opencode-hybrid-workflow 0.0.0-stage → 0.3.2-beta.1

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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +252 -3
  3. package/THIRD_PARTY_NOTICES.md +18 -0
  4. package/assets/agents/backend-engineer.md +167 -0
  5. package/assets/agents/code-reviewer.md +129 -0
  6. package/assets/agents/database-engineer.md +162 -0
  7. package/assets/agents/devops-engineer.md +115 -0
  8. package/assets/agents/frontend-engineer.md +38 -0
  9. package/assets/agents/fullstack-architect.md +43 -0
  10. package/assets/agents/security-auditor.md +132 -0
  11. package/assets/agents/technical-writer.md +78 -0
  12. package/assets/agents/test-engineer.md +154 -0
  13. package/assets/rules.md +93 -0
  14. package/assets/skills/frontend-design/INSTALLATION.json +9 -0
  15. package/assets/skills/frontend-design/LICENSE.txt +177 -0
  16. package/assets/skills/frontend-design/SKILL.md +78 -0
  17. package/assets/skills/ui-ux-pro-max/INSTALLATION.json +10 -0
  18. package/assets/skills/ui-ux-pro-max/LICENSE +21 -0
  19. package/assets/skills/ui-ux-pro-max/SKILL.md +221 -0
  20. package/assets/skills/ui-ux-pro-max/data/app-interface.csv +33 -0
  21. package/assets/skills/ui-ux-pro-max/data/catalog-summary.json +78 -0
  22. package/assets/skills/ui-ux-pro-max/data/charts.csv +26 -0
  23. package/assets/skills/ui-ux-pro-max/data/colors.csv +193 -0
  24. package/assets/skills/ui-ux-pro-max/data/data-provenance.json +1348 -0
  25. package/assets/skills/ui-ux-pro-max/data/google-font-licenses.json +20185 -0
  26. package/assets/skills/ui-ux-pro-max/data/google-fonts.csv +1935 -0
  27. package/assets/skills/ui-ux-pro-max/data/icons.csv +106 -0
  28. package/assets/skills/ui-ux-pro-max/data/landing.csv +35 -0
  29. package/assets/skills/ui-ux-pro-max/data/motion.csv +18 -0
  30. package/assets/skills/ui-ux-pro-max/data/phosphor-icons-upstream.json +32582 -0
  31. package/assets/skills/ui-ux-pro-max/data/products.csv +193 -0
  32. package/assets/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  33. package/assets/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
  34. package/assets/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  35. package/assets/skills/ui-ux-pro-max/data/stacks/avalonia.csv +57 -0
  36. package/assets/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  37. package/assets/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +60 -0
  38. package/assets/skills/ui-ux-pro-max/data/stacks/javafx.csv +76 -0
  39. package/assets/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  40. package/assets/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
  41. package/assets/skills/ui-ux-pro-max/data/stacks/nextjs.csv +62 -0
  42. package/assets/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +77 -0
  43. package/assets/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +68 -0
  44. package/assets/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  45. package/assets/skills/ui-ux-pro-max/data/stacks/react.csv +67 -0
  46. package/assets/skills/ui-ux-pro-max/data/stacks/shadcn.csv +69 -0
  47. package/assets/skills/ui-ux-pro-max/data/stacks/svelte.csv +56 -0
  48. package/assets/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  49. package/assets/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
  50. package/assets/skills/ui-ux-pro-max/data/stacks/uno.csv +60 -0
  51. package/assets/skills/ui-ux-pro-max/data/stacks/uwp.csv +56 -0
  52. package/assets/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  53. package/assets/skills/ui-ux-pro-max/data/stacks/winui.csv +60 -0
  54. package/assets/skills/ui-ux-pro-max/data/stacks/wpf.csv +57 -0
  55. package/assets/skills/ui-ux-pro-max/data/styles.csv +89 -0
  56. package/assets/skills/ui-ux-pro-max/data/typography.csv +75 -0
  57. package/assets/skills/ui-ux-pro-max/data/ui-reasoning.csv +193 -0
  58. package/assets/skills/ui-ux-pro-max/data/ux-guidelines.csv +120 -0
  59. package/assets/skills/ui-ux-pro-max/references/pro-rules.md +117 -0
  60. package/assets/skills/ui-ux-pro-max/references/quick-reference.md +256 -0
  61. package/assets/skills/ui-ux-pro-max/scripts/core.py +993 -0
  62. package/assets/skills/ui-ux-pro-max/scripts/design_system.py +1668 -0
  63. package/assets/skills/ui-ux-pro-max/scripts/reasoning_contract.py +123 -0
  64. package/assets/skills/ui-ux-pro-max/scripts/search.py +177 -0
  65. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/google-api.json +36 -0
  66. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/google-catalog.json +33 -0
  67. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/google-existing.csv +3 -0
  68. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/google-metadata.json +17 -0
  69. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/google-overrides.json +7 -0
  70. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/icons-curated.csv +4 -0
  71. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/phosphor-core.json +23 -0
  72. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/phosphor-package.json +13 -0
  73. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/phosphor-react-exports.json +4 -0
  74. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/catalogs/phosphor-react-package.json +5 -0
  75. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/relevance-baseline.json +3366 -0
  76. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/relevance-cases.json +111 -0
  77. package/assets/skills/ui-ux-pro-max/scripts/tests/fixtures/relevance-thresholds.json +100 -0
  78. package/assets/skills/ui-ux-pro-max/scripts/tests/test_catalog_refresh.py +354 -0
  79. package/assets/skills/ui-ux-pro-max/scripts/tests/test_catalog_summary_line_endings.py +78 -0
  80. package/assets/skills/ui-ux-pro-max/scripts/tests/test_core.py +343 -0
  81. package/assets/skills/ui-ux-pro-max/scripts/tests/test_core_data_quality.py +198 -0
  82. package/assets/skills/ui-ux-pro-max/scripts/tests/test_data_contracts.py +421 -0
  83. package/assets/skills/ui-ux-pro-max/scripts/tests/test_design_system_mode.py +233 -0
  84. package/assets/skills/ui-ux-pro-max/scripts/tests/test_design_system_stack.py +61 -0
  85. package/assets/skills/ui-ux-pro-max/scripts/tests/test_native_desktop_stack_freshness.py +179 -0
  86. package/assets/skills/ui-ux-pro-max/scripts/tests/test_relevance_evaluator.py +182 -0
  87. package/assets/skills/ui-ux-pro-max/scripts/tests/test_skill_script_paths.py +82 -0
  88. package/assets/skills/ui-ux-pro-max/scripts/tests/test_style_taxonomy.py +175 -0
  89. package/assets/skills/ui-ux-pro-max/scripts/tests/test_text_layout_resilience.py +120 -0
  90. package/assets/skills/ui-ux-pro-max/scripts/tests/test_web_stack_freshness.py +177 -0
  91. package/assets/skills/ui-ux-pro-max/scripts/validate_data.py +1095 -0
  92. package/assets/skills/web-design-guidelines/INSTALLATION.json +1 -0
  93. package/assets/skills/web-design-guidelines/SKILL.md +18 -0
  94. package/cli.mjs +91 -0
  95. package/core/controller.mjs +367 -0
  96. package/core/dashboard-state.js +48 -0
  97. package/core/dashboard.css +2 -0
  98. package/core/dashboard.html +12 -0
  99. package/core/dashboard.js +72 -0
  100. package/core/decision.mjs +22 -0
  101. package/core/design.mjs +139 -0
  102. package/core/evidence.mjs +82 -0
  103. package/core/frontend-policy.mjs +34 -0
  104. package/core/images.mjs +35 -0
  105. package/core/monitor-data.mjs +58 -0
  106. package/core/monitor.mjs +24 -0
  107. package/core/operation-lock.mjs +82 -0
  108. package/core/plugin.mjs +91 -0
  109. package/core/posix-lock.mjs +31 -0
  110. package/core/posix-lock.py +23 -0
  111. package/core/pricing.mjs +53 -0
  112. package/core/process-identity.mjs +19 -0
  113. package/core/research-tools.mjs +37 -0
  114. package/core/review-context.mjs +43 -0
  115. package/core/snapshot.mjs +95 -0
  116. package/core/telemetry.mjs +107 -0
  117. package/core/workers.mjs +192 -0
  118. package/core/workflow-policy.mjs +10 -0
  119. package/index.js +19 -0
  120. package/lib/commands.mjs +48 -0
  121. package/lib/config.mjs +63 -0
  122. package/lib/install.mjs +111 -0
  123. package/lib/native-browser.mjs +79 -0
  124. package/lib/opencode-reviewer.mjs +129 -0
  125. package/lib/platform.mjs +14 -0
  126. package/lib/reviewer-browser-proxy.mjs +61 -0
  127. package/lib/reviewer-browser.mjs +35 -0
  128. package/lib/reviewers.mjs +11 -0
  129. package/lib/select-reviewers.mjs +58 -0
  130. package/lib/settings-rpc.mjs +9 -0
  131. package/lib/settings-service.mjs +129 -0
  132. package/package.json +63 -6
  133. package/sidebar/reader.mjs +80 -0
  134. package/sidebar/settings.mjs +99 -0
  135. package/tui.js +303 -0
@@ -0,0 +1,99 @@
1
+ import {SettingsRPC} from '../lib/settings-rpc.mjs';
2
+ const unwrap=value=>value?.data??value;
3
+ const label=lane=>lane?`${lane.adapter==='opencode'?lane.providerID:lane.adapter} / ${lane.model} / ${lane.variant??lane.effort??'default'}`:'Disabled';
4
+ export function createSettingsUI(context,onSaved=()=>{}){
5
+ const rpc=context.client.rpc(SettingsRPC);let open=false,disposed=false;
6
+ const select=(title,options,current)=>context.ui.dialog.select({title,current,options});
7
+ const alert=(title,message)=>context.ui.dialog.alert({title,message});
8
+ async function invoke(method,input={}){const response=await rpc[method](input),value=unwrap(response);const error=response?.error??value?.error;if(error)throw Error(typeof error==='string'?error:error.message??'OpenCode could not complete this settings operation.');return value;}
9
+ function dispatch(name){
10
+ const commands=context.keymap.commands();
11
+ const command=commands.find(c=>c.slash?.name===name||c.slash?.aliases?.includes(name));
12
+ if(!command)throw Error(`OpenCode's ${name} menu is unavailable in this version.`);
13
+ context.keymap.dispatch(command.id);
14
+ }
15
+ async function reviewer(who,state,current){
16
+ const connection=await select(`Reviewer ${who==='astra'?1:2}: connection`,[{title:'OpenCode connected provider',value:'opencode',description:'Use an account already connected to OpenCode'},{title:'External CLI',value:'cli',description:'Codex CLI or Claude Code CLI login'}],!state.configured||current?.adapter==='opencode'?'opencode':'cli');
17
+ if(!connection)return;
18
+ if(connection==='opencode'){
19
+ const providers=[...new Set(state.catalog.map(m=>m.providerID))];
20
+ if(!providers.length){await alert('Connect a provider','No connected tool-capable models are available. Use Connect provider in Hybrid settings, then try again.');return;}
21
+ const provider=await select('Provider',providers.map(id=>({title:id,value:id})),current?.providerID);if(!provider)return;
22
+ const model=await select('Reviewer model',state.catalog.filter(m=>m.providerID===provider).map(m=>({title:m.name??m.id,value:m.id,description:m.id+(m.vision?' · images supported':' · text only')})),current?.model);if(!model)return;
23
+ const chosen=state.catalog.find(m=>m.providerID===provider&&m.id===model);
24
+ const variant=await select('Reasoning effort',[{title:'Provider default',value:'__default'},...chosen.variants.map(v=>({title:v,value:v}))],current?.variant??'__default');if(!variant)return;
25
+ return {adapter:'opencode',providerID:provider,model,...(variant==='__default'?{}:{variant})};
26
+ }
27
+ const adapter=await select('External CLI',[{title:'Codex CLI',value:'codex-cli'},{title:'Claude Code CLI',value:'claude-cli'}],current?.adapter);if(!adapter)return;
28
+ const model=await context.ui.dialog.prompt({title:'CLI model',placeholder:current?.adapter===adapter?current.model:adapter==='codex-cli'?'gpt-6-astra':'opus',value:current?.adapter===adapter?current.model:adapter==='codex-cli'?'gpt-6-astra':'opus'});if(!model?.trim())return;
29
+ const effort=await select('CLI reasoning effort',['low','medium','high','xhigh','max'].map(v=>({title:v,value:v})),current?.effort??'high');if(!effort)return;
30
+ return {adapter,model:model.trim(),effort};
31
+ }
32
+ async function wizard(state){
33
+ const count=await select('Hybrid setup: reviewers',[{title:'Two reviewers',value:2,description:'Independent planning and review'},{title:'One reviewer',value:1,description:'A single premium planner/reviewer'}],state.reviewers.opus?2:1);if(!count)return;
34
+ const first=await reviewer('astra',state,state.reviewers.astra);if(!first)return;
35
+ const second=count===2?await reviewer('opus',state,state.reviewers.opus):null;if(count===2&&!second)return;
36
+ const lead=count===1?'astra':await select('Planning and design lead',[{title:first.model,value:'astra',description:'Reviewer 1'},{title:second.model,value:'opus',description:'Reviewer 2'}],state.designAuthor);if(!lead)return;
37
+ const confirmed=await context.ui.dialog.confirm({title:'Save Hybrid setup',message:`Reviewer 1: ${label(first)}\nReviewer 2: ${label(second)}\nPlanning/design lead: ${lead==='astra'?first.model:second.model}\n\nSave for all projects. Active tasks retain their existing reviewers.`,label:{confirm:'Save',cancel:'Cancel'}});
38
+ if(!confirmed)return;
39
+ const result=await invoke('save',{revision:state.revision,reviewers:{astra:first,opus:second},designAuthor:lead});
40
+ onSaved();await alert('Hybrid setup saved',result.message+(result.preservedCustomFiles?.length?'\nExisting customized agents and skills were preserved.':''));
41
+ }
42
+ async function browser(state){
43
+ const action=await select('Reviewer browser',[
44
+ {title:state.browserEnabled?'Disable browser access':'Enable existing browser',value:state.browserEnabled?'disable':'enable'},
45
+ {title:'Install browser support',value:'install',description:'Download managed Playwright MCP and Firefox; enable reviewer access'},
46
+ ]);if(!action)return;
47
+ if(action==='install'&&!await context.ui.dialog.confirm({title:'Install browser support',message:'Download Playwright MCP and Firefox on the OpenCode server machine? Existing browser settings and user profiles will be preserved.',label:{confirm:'Install',cancel:'Cancel'}}))return;
48
+ context.ui.toast.show({message:action==='install'?'Installing browser support…':'Updating browser settings…',variant:'info'});
49
+ const result=await invoke('browser',{action});onSaved();await alert('Reviewer browser',result.message);
50
+ }
51
+ async function dependencies(){
52
+ const action=await select('Tools and dependencies',[
53
+ {title:'Install pnpm for verification',value:'pnpm',description:'Managed installation; does not replace your global pnpm'},
54
+ {title:'Enable Context7 documentation',value:'context7',description:'Configure official remote documentation MCP'},
55
+ {title:'System prerequisites',value:'system',description:'Node, Git, Python and optional CLI login guidance'},
56
+ ]);if(!action)return;
57
+ if(action==='system'){await alert('System prerequisites','OpenCode and Node 22+ are required. Git is needed for projects; Python 3 supports design research. External CLI choices also require that CLI and its official login. Diagnostics identifies what is missing. System installers and account sign-in remain machine/account setup; the plugin never copies credentials.');return;}
58
+ if(!await context.ui.dialog.confirm({title:'Set up dependency',message:action==='pnpm'?'Install managed pnpm on the OpenCode server?':'Add the official Context7 documentation MCP? Existing settings are preserved.',label:{confirm:'Set up',cancel:'Cancel'}}))return;
59
+ context.ui.toast.show({message:'Setting up dependency…',variant:'info'});const result=await invoke('dependencies',{action});await alert('Dependency setup',result.message);
60
+ }
61
+ async function show(firstRun=false){
62
+ if(open||disposed)return;open=true;
63
+ try{
64
+ let state=await invoke('get');
65
+ if(firstRun&&state.configured)return;
66
+ if(!state.configured){await wizard(state);state=await invoke('get');}
67
+ while(!disposed){
68
+ const action=await select(`Hybrid Workflow ${state.version}`, [
69
+ {title:state.configured?'Reviewers and design lead':'Complete setup',value:'reviewers',description:label(state.reviewers.astra)+' | '+label(state.reviewers.opus)},
70
+ {title:'Connect provider',value:'connect',description:'Open OpenCode’s account connection menu'},
71
+ {title:'Implementation model',value:'models',description:'Select the model that builds your code in OpenCode'},
72
+ {title:'Reviewer browser',value:'browser',description:state.browserEnabled?'Enabled · isolated sessions':'Disabled'},
73
+ {title:'Tools and dependencies',value:'dependencies',description:'Browser installation is in Reviewer browser'},
74
+ {title:'Run diagnostics',value:'diagnostics',description:'Check setup and logins without model calls'},
75
+ {title:'About this setup',value:'about',description:'Global configuration and active-task behavior'},
76
+ ]);
77
+ if(!action)return;
78
+ try{
79
+ if(action==='reviewers')await wizard(state);
80
+ else if(action==='connect'||action==='models'){dispatch(action);return;}
81
+ else if(action==='browser')await browser(state);
82
+ else if(action==='dependencies')await dependencies();
83
+ else if(action==='diagnostics'){
84
+ context.ui.toast.show({message:'Checking Hybrid setup…',variant:'info'});
85
+ const report=await invoke('diagnostics');
86
+ while(!disposed){
87
+ const item=await select('Hybrid diagnostics',report.checks.map((c,i)=>({title:`${c.ok?'OK':'Needs attention'} · ${c.name}`,value:String(i),description:c.detail.split('\n')[0]})));
88
+ if(item===undefined||item===null)break;
89
+ const check=report.checks[Number(item)];await alert(check.name,check.detail);
90
+ }
91
+ }else if(action==='about')await alert('Hybrid Workflow',`Global settings: ${state.configFile}\nServer platform: ${state.host}\nNew tasks use saved defaults. Active tasks preserve assigned reviewers, findings and history.\nAt least one pass and no unresolved medium/high/critical findings are required.\nSkills and agent rules are installed when setup is saved. No separate dashboard is needed.`);
92
+ }catch(error){await alert('Hybrid settings',error.message);}
93
+ state=await invoke('get');
94
+ }
95
+ }catch(error){context.ui.toast.show({message:'Hybrid settings: '+error.message,variant:'error'});}
96
+ finally{open=false;}
97
+ }
98
+ return {show,dispose(){disposed=true;},rpc};
99
+ }
package/tui.js ADDED
@@ -0,0 +1,303 @@
1
+ // Generated by npm run build; edit tui.tsx.
2
+ import { memo as _$memo } from "@opentui/solid";
3
+ import { createComponent as _$createComponent } from "@opentui/solid";
4
+ import { insert as _$insert } from "@opentui/solid";
5
+ import { effect as _$effect } from "@opentui/solid";
6
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
7
+ import { insertNode as _$insertNode } from "@opentui/solid";
8
+ import { setProp as _$setProp } from "@opentui/solid";
9
+ import { createElement as _$createElement } from "@opentui/solid";
10
+ import { Plugin } from '@opencode/plugin/tui';
11
+ import { For, Show, createEffect, createMemo, createSignal, onCleanup } from 'solid-js';
12
+ import { readSettings } from './lib/config.mjs';
13
+ import { createReader, cards, number, money } from './sidebar/reader.mjs';
14
+ import { captureTotals } from './core/dashboard-state.js';
15
+ import { createSettingsUI } from './sidebar/settings.mjs';
16
+ export default Plugin.define({
17
+ id: 'personal.hybrid-usage',
18
+ setup(context) {
19
+ const preferences = createSettingsUI(context);
20
+ context.keymap.layer(() => ({
21
+ mode: 'global',
22
+ commands: [{
23
+ id: 'hybrid.settings',
24
+ title: 'Hybrid Workflow: settings',
25
+ group: 'Hybrid Workflow',
26
+ palette: true,
27
+ slash: {
28
+ name: 'hybrid'
29
+ },
30
+ run: () => preferences.show()
31
+ }]
32
+ }));
33
+ const home = context.ui.slot({
34
+ append: 'home.footer',
35
+ render: () => (() => {
36
+ var _el$ = _$createElement("text");
37
+ _$insertNode(_el$, _$createTextNode(`[Hybrid Workflow settings]`));
38
+ _$setProp(_el$, "onMouseDown", () => preferences.show());
39
+ _$effect(_$p => _$setProp(_el$, "fg", context.theme.text.muted, _$p));
40
+ return _el$;
41
+ })()
42
+ });
43
+ const firstRun = setTimeout(() => {
44
+ void preferences.show(true);
45
+ }, 750);
46
+ const config = readSettings(context.options);
47
+ const settings = {
48
+ ...config,
49
+ efforts: {
50
+ astra: config.codex?.variant ?? config.codex?.effort ?? 'default',
51
+ opus: config.claude?.variant ?? config.claude?.effort ?? 'default'
52
+ },
53
+ models: {
54
+ astra: config.codex?.model,
55
+ opus: config.claude?.model
56
+ }
57
+ };
58
+ const read = createReader(settings);
59
+ const [saved, update] = context.storage.store('totals', {
60
+ initial: {
61
+ resets: {}
62
+ }
63
+ });
64
+ function Usage(props) {
65
+ const [snapshot, setSnapshot] = createSignal(null);
66
+ const [error, setError] = createSignal('');
67
+ const location = createMemo(() => context.data.session.get(props.sessionID)?.location);
68
+ createEffect(() => {
69
+ const directory = location()?.directory;
70
+ let disposed = false,
71
+ busy = false;
72
+ setSnapshot(null);
73
+ setError('');
74
+ const poll = async () => {
75
+ if (busy || !directory) return;
76
+ busy = true;
77
+ try {
78
+ const next = await read(directory);
79
+ if (!disposed) {
80
+ setSnapshot(next);
81
+ setError('');
82
+ }
83
+ } catch {
84
+ if (!disposed) {
85
+ setSnapshot(null);
86
+ setError('Telemetry unavailable');
87
+ }
88
+ } finally {
89
+ busy = false;
90
+ }
91
+ };
92
+ void poll();
93
+ const timer = setInterval(poll, 3000);
94
+ onCleanup(() => {
95
+ disposed = true;
96
+ clearInterval(timer);
97
+ });
98
+ });
99
+ const totals = createMemo(() => snapshot() ? cards(snapshot(), saved.resets[snapshot().key]) : []);
100
+ async function clear() {
101
+ const value = snapshot();
102
+ if (!value) return;
103
+ try {
104
+ await update(draft => {
105
+ draft.resets[value.key] = captureTotals(value.calls);
106
+ });
107
+ } catch {
108
+ context.ui.toast.show({
109
+ message: 'Could not save usage reset',
110
+ variant: 'error'
111
+ });
112
+ }
113
+ }
114
+ async function restore() {
115
+ const value = snapshot();
116
+ if (!value) return;
117
+ try {
118
+ await update(draft => {
119
+ delete draft.resets[value.key];
120
+ });
121
+ } catch {
122
+ context.ui.toast.show({
123
+ message: 'Could not restore usage totals',
124
+ variant: 'error'
125
+ });
126
+ }
127
+ }
128
+ context.keymap.layer(() => ({
129
+ mode: 'global',
130
+ commands: [{
131
+ id: 'hybrid-usage.clear',
132
+ title: 'Hybrid usage: clear totals for this project',
133
+ palette: true,
134
+ slash: {
135
+ name: 'hybrid-clear'
136
+ },
137
+ run: clear
138
+ }, {
139
+ id: 'hybrid-usage.restore',
140
+ title: 'Hybrid usage: restore totals for this project',
141
+ palette: true,
142
+ slash: {
143
+ name: 'hybrid-restore'
144
+ },
145
+ run: restore
146
+ }]
147
+ }));
148
+ return (() => {
149
+ var _el$3 = _$createElement("box"),
150
+ _el$4 = _$createElement("text");
151
+ _$insertNode(_el$3, _el$4);
152
+ _$setProp(_el$3, "flexDirection", "column");
153
+ _$setProp(_el$3, "gap", 1);
154
+ _$insertNode(_el$4, _$createTextNode(`[Hybrid Workflow settings]`));
155
+ _$setProp(_el$4, "onMouseDown", () => preferences.show());
156
+ _$insert(_el$3, _$createComponent(Show, {
157
+ get when() {
158
+ return error();
159
+ },
160
+ get children() {
161
+ var _el$6 = _$createElement("text");
162
+ _$insert(_el$6, error);
163
+ _$effect(_$p => _$setProp(_el$6, "fg", context.theme.text.muted, _$p));
164
+ return _el$6;
165
+ }
166
+ }), null);
167
+ _$insert(_el$3, _$createComponent(For, {
168
+ get each() {
169
+ return totals();
170
+ },
171
+ children: card => (() => {
172
+ var _el$11 = _$createElement("box"),
173
+ _el$12 = _$createElement("text"),
174
+ _el$13 = _$createElement("b"),
175
+ _el$14 = _$createTextNode(` · `),
176
+ _el$15 = _$createElement("text"),
177
+ _el$16 = _$createTextNode(` tokens · `),
178
+ _el$17 = _$createElement("text"),
179
+ _el$18 = _$createElement("text"),
180
+ _el$19 = _$createTextNode(`Input: `),
181
+ _el$20 = _$createElement("text"),
182
+ _el$21 = _$createTextNode(`Output: `),
183
+ _el$22 = _$createElement("text"),
184
+ _el$23 = _$createTextNode(`Cache: `);
185
+ _$insertNode(_el$11, _el$12);
186
+ _$insertNode(_el$11, _el$15);
187
+ _$insertNode(_el$11, _el$17);
188
+ _$insertNode(_el$11, _el$18);
189
+ _$insertNode(_el$11, _el$20);
190
+ _$insertNode(_el$11, _el$22);
191
+ _$setProp(_el$11, "flexDirection", "column");
192
+ _$insertNode(_el$12, _el$13);
193
+ _$insertNode(_el$13, _el$14);
194
+ _$insert(_el$13, () => card.model ?? settings.models[card.who], _el$14);
195
+ _$insert(_el$13, () => card.effort, null);
196
+ _$insertNode(_el$15, _el$16);
197
+ _$insert(_el$15, () => number(card.total), _el$16);
198
+ _$insert(_el$15, () => card.status, null);
199
+ _$insert(_el$17, () => money(card.cost));
200
+ _$insertNode(_el$18, _el$19);
201
+ _$insert(_el$18, () => number(card.input), null);
202
+ _$insertNode(_el$20, _el$21);
203
+ _$insert(_el$20, () => number(card.output), null);
204
+ _$insertNode(_el$22, _el$23);
205
+ _$insert(_el$22, () => number(card.cache), null);
206
+ _$insert(_el$11, _$createComponent(Show, {
207
+ get when() {
208
+ return card.partial;
209
+ },
210
+ get children() {
211
+ var _el$24 = _$createElement("text"),
212
+ _el$25 = _$createTextNode(`Partial · `),
213
+ _el$26 = _$createTextNode(`/`),
214
+ _el$27 = _$createTextNode(` measured`);
215
+ _$insertNode(_el$24, _el$25);
216
+ _$insertNode(_el$24, _el$26);
217
+ _$insertNode(_el$24, _el$27);
218
+ _$insert(_el$24, () => card.measuredCalls, _el$26);
219
+ _$insert(_el$24, () => card.calls, _el$27);
220
+ _$effect(_$p => _$setProp(_el$24, "fg", context.theme.text.muted, _$p));
221
+ return _el$24;
222
+ }
223
+ }), null);
224
+ _$effect(_p$ => {
225
+ var _v$ = context.theme.text.base,
226
+ _v$2 = context.theme.text.base,
227
+ _v$3 = context.theme.text.base,
228
+ _v$4 = context.theme.text.muted,
229
+ _v$5 = context.theme.text.muted,
230
+ _v$6 = context.theme.text.muted;
231
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$12, "fg", _v$, _p$.e));
232
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$15, "fg", _v$2, _p$.t));
233
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$17, "fg", _v$3, _p$.a));
234
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$18, "fg", _v$4, _p$.o));
235
+ _v$5 !== _p$.i && (_p$.i = _$setProp(_el$20, "fg", _v$5, _p$.i));
236
+ _v$6 !== _p$.n && (_p$.n = _$setProp(_el$22, "fg", _v$6, _p$.n));
237
+ return _p$;
238
+ }, {
239
+ e: undefined,
240
+ t: undefined,
241
+ a: undefined,
242
+ o: undefined,
243
+ i: undefined,
244
+ n: undefined
245
+ });
246
+ return _el$11;
247
+ })()
248
+ }), null);
249
+ _$insert(_el$3, _$createComponent(Show, {
250
+ get when() {
251
+ return snapshot();
252
+ },
253
+ get children() {
254
+ return [(() => {
255
+ var _el$7 = _$createElement("text");
256
+ _$insert(_el$7, () => snapshot().task ? 'Project bridge usage · USD' : 'No bridge calls in this project');
257
+ _$effect(_$p => _$setProp(_el$7, "fg", context.theme.text.muted, _$p));
258
+ return _el$7;
259
+ })(), (() => {
260
+ var _el$8 = _$createElement("box"),
261
+ _el$9 = _$createElement("text");
262
+ _$insertNode(_el$8, _el$9);
263
+ _$setProp(_el$8, "flexDirection", "row");
264
+ _$setProp(_el$8, "gap", 2);
265
+ _$insertNode(_el$9, _$createTextNode(`[Clear totals]`));
266
+ _$setProp(_el$9, "onMouseDown", clear);
267
+ _$insert(_el$8, _$createComponent(Show, {
268
+ get when() {
269
+ return saved.resets[snapshot()?.key];
270
+ },
271
+ get children() {
272
+ var _el$1 = _$createElement("text");
273
+ _$insertNode(_el$1, _$createTextNode(`[Restore]`));
274
+ _$setProp(_el$1, "onMouseDown", restore);
275
+ _$effect(_$p => _$setProp(_el$1, "fg", context.theme.text.muted, _$p));
276
+ return _el$1;
277
+ }
278
+ }), null);
279
+ _$effect(_$p => _$setProp(_el$9, "fg", context.theme.text.muted, _$p));
280
+ return _el$8;
281
+ })()];
282
+ }
283
+ }), null);
284
+ _$effect(_$p => _$setProp(_el$4, "fg", context.theme.text.muted, _$p));
285
+ return _el$3;
286
+ })();
287
+ }
288
+ const sidebar = context.ui.slot({
289
+ append: 'sidebar.content',
290
+ render: props => _$createComponent(Usage, {
291
+ get sessionID() {
292
+ return props.sessionID;
293
+ }
294
+ })
295
+ });
296
+ return () => {
297
+ clearTimeout(firstRun);
298
+ preferences.dispose();
299
+ home?.();
300
+ sidebar?.();
301
+ };
302
+ }
303
+ });