@duckmind/dm-windows-x64 0.60.4 → 0.60.8

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 (197) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +70 -123
  3. package/extensions/dm-9router-ext/src/index.js +410 -5
  4. package/extensions/dm-caveman/extensions/caveman.js +283 -12
  5. package/extensions/dm-cliproxy/index.js +182 -2
  6. package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
  7. package/extensions/dm-cliproxy/src/apply.js +228 -1
  8. package/extensions/dm-cliproxy/src/cache.js +42 -1
  9. package/extensions/dm-cliproxy/src/commands.js +50 -2
  10. package/extensions/dm-cliproxy/src/compat.js +81 -1
  11. package/extensions/dm-cliproxy/src/config.js +192 -2
  12. package/extensions/dm-cliproxy/src/conflicts.js +46 -1
  13. package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
  14. package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
  15. package/extensions/dm-cliproxy/src/log.js +23 -1
  16. package/extensions/dm-cliproxy/src/status-quota.js +77 -1
  17. package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
  18. package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
  19. package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
  20. package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
  21. package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
  22. package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
  23. package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
  24. package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
  25. package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
  26. package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
  27. package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
  28. package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
  29. package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
  30. package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
  31. package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
  32. package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
  33. package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
  34. package/extensions/dm-context/src/context.js +144 -1
  35. package/extensions/dm-context/src/index.js +339 -7
  36. package/extensions/dm-context/src/utils.js +9 -1
  37. package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
  38. package/extensions/dm-cua/index.js +75 -6
  39. package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
  40. package/extensions/dm-cua/src/browser-install.mjs +331 -2
  41. package/extensions/dm-fff/src/index.js +688 -12
  42. package/extensions/dm-fff/src/query.js +60 -1
  43. package/extensions/dm-goal/src/goal.js +894 -23
  44. package/extensions/dm-image2/index.js +103 -9
  45. package/extensions/dm-image2/src/image-lib.mjs +1275 -8
  46. package/extensions/dm-subagents/install.mjs +62 -8
  47. package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
  48. package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
  49. package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
  50. package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
  51. package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
  52. package/extensions/dm-subagents/src/agents/agents.js +1139 -11
  53. package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
  54. package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
  55. package/extensions/dm-subagents/src/agents/identity.js +29 -1
  56. package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
  57. package/extensions/dm-subagents/src/agents/skills.js +614 -6
  58. package/extensions/dm-subagents/src/extension/config.js +35 -2
  59. package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
  60. package/extensions/dm-subagents/src/extension/doctor.js +172 -15
  61. package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
  62. package/extensions/dm-subagents/src/extension/index.js +521 -359
  63. package/extensions/dm-subagents/src/extension/rpc.js +266 -7
  64. package/extensions/dm-subagents/src/extension/schemas.js +275 -1
  65. package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
  66. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
  67. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
  68. package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
  69. package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
  70. package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
  71. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
  72. package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
  73. package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
  74. package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
  75. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
  76. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
  77. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
  78. package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
  79. package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
  80. package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
  81. package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
  82. package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
  83. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
  84. package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
  85. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
  86. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
  87. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
  88. package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
  89. package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
  90. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
  91. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
  92. package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
  93. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
  94. package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
  95. package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
  96. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
  97. package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
  98. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
  99. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
  100. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
  101. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
  102. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
  103. package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
  104. package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
  105. package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
  106. package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
  107. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
  108. package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
  109. package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
  110. package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
  111. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
  112. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
  113. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
  114. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
  115. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
  116. package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
  117. package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
  118. package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
  119. package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
  120. package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
  121. package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
  122. package/extensions/dm-subagents/src/shared/formatters.js +98 -7
  123. package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
  124. package/extensions/dm-subagents/src/shared/model-info.js +62 -1
  125. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
  126. package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
  127. package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
  128. package/extensions/dm-subagents/src/shared/settings.js +198 -11
  129. package/extensions/dm-subagents/src/shared/status-format.js +53 -1
  130. package/extensions/dm-subagents/src/shared/types.js +184 -6
  131. package/extensions/dm-subagents/src/shared/utils.js +462 -2
  132. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
  133. package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
  134. package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
  135. package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
  136. package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
  137. package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
  138. package/extensions/dm-subagents/src/tui/render.js +1542 -4
  139. package/extensions/dm-usage/index.js +1294 -9
  140. package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
  141. package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
  142. package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
  143. package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
  144. package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
  145. package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
  146. package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
  147. package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
  148. package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
  149. package/extensions/greedysearch-dm/bin/search.mjs +620 -540
  150. package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
  151. package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
  152. package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
  153. package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
  154. package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
  155. package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
  156. package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
  157. package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
  158. package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
  159. package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
  160. package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
  161. package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
  162. package/extensions/greedysearch-dm/index.js +123 -23
  163. package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
  164. package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
  165. package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
  166. package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
  167. package/extensions/greedysearch-dm/src/github.mjs +222 -7
  168. package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
  169. package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
  170. package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
  171. package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
  172. package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
  173. package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
  174. package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
  175. package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
  176. package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
  177. package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
  178. package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
  179. package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
  180. package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
  181. package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
  182. package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
  183. package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
  184. package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
  185. package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
  186. package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
  187. package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
  188. package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
  189. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
  190. package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
  191. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
  192. package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
  193. package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
  194. package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
  195. package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
  196. package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
  197. package/package.json +1 -1
@@ -1 +1,379 @@
1
- import{matchesKey as D}from"@duckmind/dm-tui";function $0($){let q=new Map,J=new Map;for(let z of $.builtinProviders){let j=[];for(let Y of z.models)q.set(Y.id,{id:Y.id,name:Y.name,suggestedApi:z.api,subtitle:Y.name&&Y.name!==Y.id?Y.name:void 0,ownedBy:z.name,reasoning:Y.reasoning,contextWindow:Y.contextWindow,maxTokens:Y.maxTokens,cost:Y.cost}),j.push(Y.id);J.set(z.name,j)}let Q=[];for(let z of $.customPool){if(!q.has(z.id))q.set(z.id,{id:z.id,name:z.name,suggestedApi:z.api,subtitle:z.name&&z.name!==z.id?z.name:void 0,origin:z.suggestedProvider,ownedBy:z.ownedBy,reasoning:z.reasoning,contextWindow:z.contextWindow,maxTokens:z.maxTokens,cost:z.cost});Q.push(z.id)}return{byId:q,builtinModelIds:J,customPoolIds:Q}}function i0($){let q=new Map;for(let[J,Q]of Object.entries($.customProviders))for(let z of Q.models)q.set(z.id,J);return q}function b($,q){if(q.kind==="builtin")return[...$.builtinProviders[q.name]?.models??[]];return $.customProviders[q.name]?.models.map((J)=>J.id)??[]}function K0($,q,J){if(q.kind==="builtin"){let j=J.builtinModelIds.get(q.name)??[],Y=new Set(b($,q));return j.filter((V)=>!Y.has(V))}let Q=i0($),z=new Set(b($,q));return J.customPoolIds.filter((j)=>{if(z.has(j))return!1;let Y=Q.get(j);return Y===void 0||Y===q.name})}function r0($){return{id:$.id,name:$.name,contextWindow:$.contextWindow,maxTokens:$.maxTokens,reasoning:$.reasoning,cost:$.cost}}function B0($,q,J){if(q.kind==="builtin"){let z=$.builtinProviders[q.name]??{enabled:!0,models:[]},j=new Set(z.models);j.add(J.id),$.builtinProviders[q.name]={enabled:!0,apiOverride:z.apiOverride??null,models:Array.from(j).sort()};return}for(let[z,j]of Object.entries($.customProviders)){if(z===q.name)continue;let Y=j.models.findIndex((V)=>V.id===J.id);if(Y>=0)j.models.splice(Y,1)}let Q=$.customProviders[q.name]??{api:q.api,models:[]};if(!Q.models.some((z)=>z.id===J.id))Q.models.push(r0(J));$.customProviders[q.name]=Q}function H0($,q,J){if(q.kind==="builtin"){let z=$.builtinProviders[q.name];if(!z)return;z.models=z.models.filter((j)=>j!==J),z.enabled=z.models.length>0;return}let Q=$.customProviders[q.name];if(!Q)return;Q.models=Q.models.filter((z)=>z.id!==J)}function z0($,q){if($===q)return!0;let J=["openai-completions","openai-responses"];if(J.includes($)&&J.includes(q))return!0;return!1}function P0($,q){let J=new Map;for(let Q of $){let z=q.byId.get(Q),j=z?.ownedBy||z?.origin||"misc",Y=J.get(j)??[];Y.push(Q),J.set(j,Y)}return Array.from(J.entries()).sort((Q,z)=>Q[0].localeCompare(z[0])).map(([Q,z])=>({label:Q,ids:z}))}function k0($,q,J){let Q=J.trim().toLowerCase();if(!Q)return $;return $.filter((z)=>{if(z.toLowerCase().includes(Q))return!0;let j=q.byId.get(z)?.name;return j?j.toLowerCase().includes(Q):!1})}import{getModels as t0,getProviders as o0}from"@duckmind/dm-ai/compat";function q0($,q){let J=[],Q=new Set(q.builtinModelIds.keys()),z=new Set(o0()),j=new Set([...Q,...z]);for(let Y of Array.from(j).sort()){let V=q.builtinModelIds.get(Y);if(!V||V.length===0)continue;let X;try{let R=t0(Y).find((E)=>V.includes(E.id));if(R)X=R.api}catch{}if(!X){let G=V[0];X=q.byId.get(G)?.suggestedApi??"openai-responses"}if(J.push({kind:"builtin",name:Y,api:X,subtitle:`built-in · ${X}`}),!$.builtinProviders[Y])$.builtinProviders[Y]={enabled:!1,models:[]}}for(let Y of Object.keys($.customProviders).sort()){let V=$.customProviders[Y];J.push({kind:"custom",name:Y,api:V.api,subtitle:`custom · ${V.api}`})}return J}import{getKeybindings as j$,matchesKey as w}from"@duckmind/dm-tui";import{visibleWidth as Q0}from"@duckmind/dm-tui";import{visibleWidth as F0}from"@duckmind/dm-tui";function A($,q){let J=F0($);if(J===q)return $;if(J<q)return $+" ".repeat(q-J);return J0($,q)}function J0($,q){if(q<=0)return"\x1B[0m";let J="",Q=0,z=0;while(z<$.length){let j=$[z];if(j==="\x1B"&&$[z+1]==="["){let V=$.indexOf("m",z+2);if(V>=0){J+=$.slice(z,V+1),z=V+1;continue}}let Y=F0(j);if(Q+Y>q)break;J+=j,Q+=Y,z++}return`${J}\x1B[0m`}var e0="│",$$="╭",z$="╮",q$="╰",J$="╯",u="─";function p($,q){let J=Math.max(4,q.width);return[Q$($,q.title,q.titleColor??"accent",J),...q.lines.map((Q)=>Y$($,Q,J)),Z$($,q.footer,J)]}function Q$($,q,J,Q){let z=q??"",j=Q0(z),Y=Math.max(0,Q-3-j),V=z?$.bold($.fg(J,z)):"",X=`${$$}${u}${V}${u.repeat(Y)}${z$}`;return $.fg("borderAccent",X)}function Y$($,q,J){let Q=Math.max(0,J-2),z=$.fg("borderAccent",e0);return`${z}${A(q,Q)}${z}`}function Z$($,q,J){let Q=`${q$}${u}`,z=`${J$}`,j=3;if(!q||!q.hint&&!q.badge){let I=Math.max(0,J-3);return $.fg("borderAccent",`${Q}${u.repeat(I)}${z}`)}let Y=q.hint??"",V=q.badge??"",X=Q0(Y),G=Q0(V),R=Math.max(0,J-3-X-G),E=$.fg("dim",Y),k=V?$.fg("muted",V):"",F=u.repeat(R);return`${$.fg("borderAccent",Q)}${E}${$.fg("borderAccent",F)}${k}${$.fg("borderAccent",z)}`}function I0($){return Math.max(0,$-2)}async function W0($,q){return $.ui.custom((J,Q,z,j)=>V$(Q,`Remove custom group “${q}”?`,"All model assignments in this group will be discarded.",j),{overlay:!0,overlayOptions:{width:80,maxHeight:"40%"}})}function V$($,q,J,Q){return{render(z){return p($,{width:z,title:" confirm ",titleColor:"error",lines:["",` ${$.bold(q)}`,` ${$.fg("dim",J)}`,""],footer:{hint:" y / enter = remove · n / esc = cancel "}})},invalidate(){},handleInput(z){if(j$().matches(z,"tui.select.cancel")||w(z,"escape")||w(z,"n")||w(z,"q")){Q(!1);return}if(w(z,"y")||w(z,"enter")||w(z,"return")){Q(!0);return}}}}import{getKeybindings as X$,Input as _$,matchesKey as G$}from"@duckmind/dm-tui";function S0($,q){let J=($??"").trim();return J?`${J}-${q}`:q}async function x0($,q){let J=S0(q,"group");return $.ui.custom((Q,z,j,Y)=>U$(Q,z,J,Y),{overlay:!0,overlayOptions:{width:80,maxHeight:"40%"}})}function U$($,q,J,Q){let z=new _$;if(J)z.setValue(J);z.focused=!0;let j=null;return z.onSubmit=(Y)=>{let V=Y.trim();if(!/^[a-z0-9][a-z0-9._-]*$/i.test(V)){j="letters / digits / dot / dash / underscore only",$.requestRender();return}Q(V)},z.onEscape=()=>Q(null),{render(Y){let V=I0(Y),X=J?`name shown in /model picker, e.g. ${J}`:"letters / digits / dot / dash / underscore",G=z.render(V-4),R=["",` ${q.fg("dim",X)}`,"",...G.map((E)=>` ${q.fg("accent",`❯ ${E}`)}`),"",j?` ${q.fg("error",`! ${j}`)}`:""];return p(q,{width:Y,title:" new custom group ",lines:R,footer:{hint:" enter = create · esc = cancel "}})},invalidate(){z.invalidate()},handleInput(Y){if(X$().matches(Y,"tui.select.cancel")||G$(Y,"escape")){Q(null);return}j=null,z.handleInput(Y),$.requestRender()}}}import{visibleWidth as D$}from"@duckmind/dm-tui";function Y0($,q,J){if(!q)return" ";if(J)return $.fg("accent","▶ ");return $.fg("muted","▸ ")}function L0($,q){let J=D$($);if(J===q)return $;if(J>q)return J0($,q);return A($,q)}function y0($,q,J){let{theme:Q,width:z,isCursor:j,isFocused:Y}=J,V=Y0(Q,j,Y),X=$.kind==="builtin"?q.builtinProviders[$.name]?.models.length??0:q.customProviders[$.name]?.models.length??0,G=$.kind==="builtin"?Q.fg("accent","B"):Q.fg("warning","C"),R=`${Q.fg("dim","[")}${G}${Q.fg("dim","]")}`,E=X>0?Q.fg("success","●"):Q.fg("dim","○"),k=X>0?Q.fg("success",String(X).padStart(2)):Q.fg("dim"," 0"),F=j&&Y?Q.bold(Q.fg("accent",$.name)):j?Q.bold($.name):$.name,I=Q.fg("dim",$.api),T=`${V}${R} ${F} ${E}${k} ${I}`;return L0(T,z)}function b0($,q,J,Q){let z=Y0($,q,J),j=q&&J?$.bold($.fg("accent","+ new custom group…")):$.fg("accent","+ new custom group…");return A(`${z}${j}`,Q)}function Z0($,q,J,Q,z){let{theme:j,width:Y,isCursor:V,isFocused:X}=z,G=Y0(j,V,X),R=J==="assigned"?j.fg("success","☑"):j.fg("dim","☐"),E=Q?` ${j.fg("warning","⚠")}`:"",k=V&&X?j.fg("accent",$):$,F=q?.reasoning?` ${j.fg("accent","✨")}`:"",I=q?j.fg("muted",` · ${q.suggestedApi}`):j.fg("error"," · not on proxy"),T=`${G}${R} ${k}${E}${F}${I}`;return L0(T,Y)}function C0($,q,J){let Q=`${$.fg("borderAccent","──")} ${$.fg("warning",q)} ${$.fg("borderAccent","──")}`;return A(` ${Q}`,J)}function j0($,q,J){return A(` ${$.fg("dim",q)}`,J)}function n0($,q,J,Q){let z=Q?$.bold($.fg("accent","❚ ")):$.fg("dim"," ");return A(`${z}${q}`,J)}import{visibleWidth as l}from"@duckmind/dm-tui";function a($,q,J){let Q=$.slice(q,q+J);while(Q.length<J)Q.push("");return Q}function s($,q,J,Q,z,j){if(z<=Q)return 0;let Y=Math.max(0,z-Q);if(q<j)return 0;let V=Math.max(0,Math.min(J,Y)),X=V+j;if($<X)V=Math.max(0,$-j);let G=V+Q-1;if(q>G)V=Math.min(Y,q-(Q-1));return V}function a$($){let{tui:q,theme:J,ctx:Q,cfg:z}=$,j=$.onChange??(()=>{}),Y=$0($.getDiscovery()),V=q0(z,Y),X="providers",G=0,R=0,E=0,k=0,F=0,I=0,T="",C=!1,v=()=>{if(V.length===0)return null;let Z=Math.max(0,Math.min(G,V.length-1));return V[Z]??null},V0=(Z)=>{let _=k0(K0(z,Z,Y),Y,T);return P0(_,Y)},d=(Z)=>V0(Z).flatMap((_)=>_.ids),f=()=>{V=q0(z,Y);let Z=Math.max(0,V.length);if(G>Z)G=Z;let _=v();if(_){let M=b(z,_).length,U=d(_).length;if(R>=M)R=Math.max(0,M-1);if(E>=U)E=Math.max(0,U-1)}},w0=()=>{Y=$0($.getDiscovery()),f()},X0=(Z)=>{let _=["providers","assigned","pool"],M=_.indexOf(X);X=Z?_[(M-1+_.length)%_.length]:_[(M+1)%_.length]},m=(Z)=>{if(X==="providers"){let U=V.length+1;G=Math.max(0,Math.min(G+Z,U-1));return}let _=v();if(!_)return;if(X==="assigned"){let U=b(z,_).length;if(U===0)return;R=Math.max(0,Math.min(R+Z,U-1));return}let M=d(_).length;if(M===0)return;E=Math.max(0,Math.min(E+Z,M-1))},v0=async()=>{if(X==="providers"){if(G===V.length){let U=await x0(Q,z.proxy.providerPrefix);if(U&&!z.customProviders[U]){if(z.customProviders[U]={api:"openai-completions",models:[]},j(),f(),G=V.findIndex((O)=>O.kind==="custom"&&O.name===U),G<0)G=V.length-1;X="pool"}q.requestRender();return}X="pool";return}let Z=v();if(!Z)return;if(X==="assigned"){let U=b(z,Z)[R];if(!U)return;H0(z,Z,U),j(),f();return}let _=d(Z)[E];if(!_)return;let M=Y.byId.get(_);if(!M)return;B0(z,Z,M),j(),f()},f0=async()=>{if(X!=="providers")return;let Z=v();if(!Z||Z.kind!=="custom")return;if(!await W0(Q,Z.name)){q.requestRender();return}if(delete z.customProviders[Z.name],j(),f(),G>=V.length)G=Math.max(0,V.length-1);q.requestRender()},g0=(Z,_)=>u0(Z,_);function u0(Z,_){let M=Math.max(70,Z),U=Math.min(56,Math.max(34,Math.floor(M*0.4))),O=M-U-1,W=Math.max(8,_),S=Math.max(5,Math.floor((W-1)/2)),G0=W-S,P=v(),g=0,i=0,r=0,U0=0,t=0,D0=0,L=[];L.push(_0(" providers ",U,X==="providers"));for(let N=0;N<V.length;N++){let B=V[N],K=X==="providers"&&N===G;if(K)g=L.length,i=g;L.push(y0(B,z,{theme:J,width:U,isCursor:K,isFocused:X==="providers"}))}{let N=X==="providers"&&G===V.length;if(N)g=L.length,i=g;L.push(b0(J,N,X==="providers",U))}let y=[],p0=P?`assigned to ${J.bold(P.name)} ${J.fg("dim",`· ${P.api}`)}`:J.fg("dim","no provider selected");if(y.push(_0(" assigned ",O,X==="assigned")),y.push(n0(J,p0,O,X==="assigned")),P){let N=b(z,P);if(N.length===0)y.push(j0(J,"(nothing assigned yet)",O));for(let B=0;B<N.length;B++){let K=N[B],H=Y.byId.get(K),c=X==="assigned"&&B===R;if(c)r=y.length,U0=r;let n=H?!z0(P.api,H.suggestedApi):!1;y.push(Z0(K,H,"assigned",n,{theme:J,width:O,isCursor:c,isFocused:X==="assigned"}))}}let x=[];if(x.push(m0(O,X==="pool")),P){let N=V0(P);if(N.reduce((K,H)=>K+H.ids.length,0)===0)x.push(j0(J,T?`(no matches for "${T}")`:"(no models available)",O));else{let K=0;for(let H of N){let c=x.length;x.push(C0(J,H.label,O));for(let n=0;n<H.ids.length;n++){let A0=H.ids[n],e=Y.byId.get(A0),T0=X==="pool"&&K===E;if(T0)t=x.length,D0=n===0?c:t;let d0=e?!z0(P.api,e.suggestedApi):!1;x.push(Z0(A0,e,"pool",d0,{theme:J,width:O,isCursor:T0,isFocused:X==="pool"})),K++}}}}let l0=Math.max(1,G0-1),N0=s(i,g,k,W,L.length,1),O0=s(X==="assigned"?U0:0,X==="assigned"?r:0,F,S,y.length,2),E0=s(X==="pool"?D0:0,X==="pool"?t:0,I,l0,x.length,1);k=N0,F=O0,I=E0;let R0=a(L,N0,W),a0=a(y,O0,S),s0=a(x,E0,G0),M0=J.fg("borderAccent","│"),o=[];for(let N=0;N<W;N++){let B=A(R0[N]??"",U),K=N<S?a0[N]:s0[N-S],H=A(K,O);o.push(`${B}${M0}${H}`)}let h=S;if(h>0&&h<W){let N=A(R0[h]??"",U),B=J.fg("borderAccent","─".repeat(O));o[h]=`${N}${M0}${B}`}return o}function _0(Z,_,M){let U=M?J.bold(J.fg("accent",Z)):J.fg("muted",Z),O=J.fg("borderAccent","─".repeat(Math.max(0,_-l(Z)-1)));return A(`${U}${O}`,_)}function m0(Z,_){let U=_?J.bold(J.fg("accent"," available pool ")):J.fg("muted"," available pool "),O="";if(C)O=`${J.fg("dim"," /")}${J.fg("accent",T)}${J.fg("accent","█")} `;else if(T)O=`${J.fg("dim"," /")}${J.fg("warning",T)} `;let W=l(" available pool ")+l(O),S=J.fg("borderAccent","─".repeat(Math.max(0,Z-W-1)));return A(`${U}${O}${S}`,Z)}function h0(Z){if(C){if(D(Z,"escape"))return T="",C=!1,E=0,!0;if(D(Z,"enter")||D(Z,"return"))return C=!1,!0;if(D(Z,"backspace")||D(Z,"delete"))return T=T.slice(0,-1),E=0,!0;if(Z.length===1&&Z>=" "&&Z!=="")return T+=Z,E=0,!0;return!0}if(D(Z,"tab"))return X0(!1),!0;if(D(Z,"shift+tab"))return X0(!0),!0;if(D(Z,"/"))return X="pool",C=!0,!0;if(D(Z,"enter")||D(Z,"return")||D(Z,"space"))return v0().then(()=>!0);if(X==="providers"&&(D(Z,"d")||D(Z,"delete")||D(Z,"backspace")))return f0().then(()=>!0);if(D(Z,"up")||D(Z,"k"))return m(-1),!0;if(D(Z,"down")||D(Z,"j"))return m(1),!0;if(D(Z,"pageUp")||D(Z,"b"))return m(-8),!0;if(D(Z,"pageDown")||D(Z,"f"))return m(8),!0;if(D(Z,"left")||D(Z,"h"))return X="providers",!0;if(D(Z,"right")||D(Z,"l")){if(X==="providers")X="assigned";else if(X==="assigned")X="pool";return!0}return!1}function c0(){if(C)return" type to filter · ↵ apply · esc clear ";return" tab ↔ panel · ↑↓ nav · ↵ move · / filter · d remove group "}return{id:"models",label:"Models",render:g0,handleInput:h0,footerHint:c0,rebuild:w0}}export{a$ as buildModelsView};
1
+ import { matchesKey } from "@duckmind/dm-tui";
2
+ import { buildCatalog } from "../ui-picker/catalog.js";
3
+ import {
4
+ apiCompatible,
5
+ assignedIdsFor,
6
+ attachModel,
7
+ detachModel,
8
+ filterModelIds,
9
+ groupPoolByOwnedBy,
10
+ poolFor
11
+ } from "../ui-picker/mutate.js";
12
+ import { collectProviders } from "../ui-picker/providers.js";
13
+ import { confirmRemoveProvider } from "../ui-picker/prompt-confirm.js";
14
+ import { promptNewProviderName } from "../ui-picker/prompt-name.js";
15
+ import { pad } from "../ui-picker/render-text.js";
16
+ import {
17
+ renderEmpty,
18
+ renderModelRow,
19
+ renderNewProviderRow,
20
+ renderPanelHeader,
21
+ renderProviderRow,
22
+ renderSubheader
23
+ } from "../ui-picker/rows.js";
24
+ import { clampScroll, takeSlice, visibleWidth } from "./shell.js";
25
+ export function buildModelsView(deps) {
26
+ const { tui, theme, ctx, cfg } = deps;
27
+ const onChange = deps.onChange ?? (() => {});
28
+ let catalog = buildCatalog(deps.getDiscovery());
29
+ let providers = collectProviders(cfg, catalog);
30
+ let focus = "providers";
31
+ let providerCursor = 0;
32
+ let assignedCursor = 0;
33
+ let poolCursor = 0;
34
+ let providerScroll = 0;
35
+ let assignedScroll = 0;
36
+ let poolScroll = 0;
37
+ let filter = "";
38
+ let filterEditing = false;
39
+ const selectedProvider = () => {
40
+ if (providers.length === 0)
41
+ return null;
42
+ const idx = Math.max(0, Math.min(providerCursor, providers.length - 1));
43
+ return providers[idx] ?? null;
44
+ };
45
+ const poolGroups = (prov) => {
46
+ const ids = filterModelIds(poolFor(cfg, prov, catalog), catalog, filter);
47
+ return groupPoolByOwnedBy(ids, catalog);
48
+ };
49
+ const poolOrder = (prov) => poolGroups(prov).flatMap((g) => g.ids);
50
+ const refresh = () => {
51
+ providers = collectProviders(cfg, catalog);
52
+ const maxProv = Math.max(0, providers.length);
53
+ if (providerCursor > maxProv)
54
+ providerCursor = maxProv;
55
+ const prov = selectedProvider();
56
+ if (prov) {
57
+ const aLen = assignedIdsFor(cfg, prov).length;
58
+ const pLen = poolOrder(prov).length;
59
+ if (assignedCursor >= aLen)
60
+ assignedCursor = Math.max(0, aLen - 1);
61
+ if (poolCursor >= pLen)
62
+ poolCursor = Math.max(0, pLen - 1);
63
+ }
64
+ };
65
+ const rebuild = () => {
66
+ catalog = buildCatalog(deps.getDiscovery());
67
+ refresh();
68
+ };
69
+ const onTab = (back) => {
70
+ const order = ["providers", "assigned", "pool"];
71
+ const i = order.indexOf(focus);
72
+ focus = back ? order[(i - 1 + order.length) % order.length] : order[(i + 1) % order.length];
73
+ };
74
+ const moveCursor = (delta) => {
75
+ if (focus === "providers") {
76
+ const total = providers.length + 1;
77
+ providerCursor = Math.max(0, Math.min(providerCursor + delta, total - 1));
78
+ return;
79
+ }
80
+ const prov = selectedProvider();
81
+ if (!prov)
82
+ return;
83
+ if (focus === "assigned") {
84
+ const total = assignedIdsFor(cfg, prov).length;
85
+ if (total === 0)
86
+ return;
87
+ assignedCursor = Math.max(0, Math.min(assignedCursor + delta, total - 1));
88
+ return;
89
+ }
90
+ const total = poolOrder(prov).length;
91
+ if (total === 0)
92
+ return;
93
+ poolCursor = Math.max(0, Math.min(poolCursor + delta, total - 1));
94
+ };
95
+ const onActivate = async () => {
96
+ if (focus === "providers") {
97
+ if (providerCursor === providers.length) {
98
+ const name = await promptNewProviderName(ctx, cfg.proxy.providerPrefix);
99
+ if (name && !cfg.customProviders[name]) {
100
+ cfg.customProviders[name] = { api: "openai-completions", models: [] };
101
+ onChange();
102
+ refresh();
103
+ providerCursor = providers.findIndex((p) => p.kind === "custom" && p.name === name);
104
+ if (providerCursor < 0)
105
+ providerCursor = providers.length - 1;
106
+ focus = "pool";
107
+ }
108
+ tui.requestRender();
109
+ return;
110
+ }
111
+ focus = "pool";
112
+ return;
113
+ }
114
+ const prov = selectedProvider();
115
+ if (!prov)
116
+ return;
117
+ if (focus === "assigned") {
118
+ const id = assignedIdsFor(cfg, prov)[assignedCursor];
119
+ if (!id)
120
+ return;
121
+ detachModel(cfg, prov, id);
122
+ onChange();
123
+ refresh();
124
+ return;
125
+ }
126
+ const id = poolOrder(prov)[poolCursor];
127
+ if (!id)
128
+ return;
129
+ const m = catalog.byId.get(id);
130
+ if (!m)
131
+ return;
132
+ attachModel(cfg, prov, m);
133
+ onChange();
134
+ refresh();
135
+ };
136
+ const onDelete = async () => {
137
+ if (focus !== "providers")
138
+ return;
139
+ const prov = selectedProvider();
140
+ if (!prov || prov.kind !== "custom")
141
+ return;
142
+ const ok = await confirmRemoveProvider(ctx, prov.name);
143
+ if (!ok) {
144
+ tui.requestRender();
145
+ return;
146
+ }
147
+ delete cfg.customProviders[prov.name];
148
+ onChange();
149
+ refresh();
150
+ if (providerCursor >= providers.length)
151
+ providerCursor = Math.max(0, providers.length - 1);
152
+ tui.requestRender();
153
+ };
154
+ const render = (width, height) => renderBody(width, height);
155
+ function renderBody(width, height) {
156
+ const inner = Math.max(70, width);
157
+ const leftW = Math.min(56, Math.max(34, Math.floor(inner * 0.4)));
158
+ const rightW = inner - leftW - 1;
159
+ const bodyH = Math.max(8, height);
160
+ const upperH = Math.max(5, Math.floor((bodyH - 1) / 2));
161
+ const lowerH = bodyH - upperH;
162
+ const prov = selectedProvider();
163
+ let leftCursorLine = 0;
164
+ let leftCursorTop = 0;
165
+ let assignedCursorLine = 0;
166
+ let assignedCursorTop = 0;
167
+ let poolCursorLine = 0;
168
+ let poolCursorTop = 0;
169
+ const leftLines = [];
170
+ leftLines.push(panelHeaderBar(" providers ", leftW, focus === "providers"));
171
+ for (let i = 0;i < providers.length; i++) {
172
+ const p = providers[i];
173
+ const isCursor = focus === "providers" && i === providerCursor;
174
+ if (isCursor) {
175
+ leftCursorLine = leftLines.length;
176
+ leftCursorTop = leftCursorLine;
177
+ }
178
+ leftLines.push(renderProviderRow(p, cfg, {
179
+ theme,
180
+ width: leftW,
181
+ isCursor,
182
+ isFocused: focus === "providers"
183
+ }));
184
+ }
185
+ {
186
+ const isCursor = focus === "providers" && providerCursor === providers.length;
187
+ if (isCursor) {
188
+ leftCursorLine = leftLines.length;
189
+ leftCursorTop = leftCursorLine;
190
+ }
191
+ leftLines.push(renderNewProviderRow(theme, isCursor, focus === "providers", leftW));
192
+ }
193
+ const assignedLines = [];
194
+ const assignedHeader = prov ? `assigned to ${theme.bold(prov.name)} ${theme.fg("dim", `· ${prov.api}`)}` : theme.fg("dim", "no provider selected");
195
+ assignedLines.push(panelHeaderBar(" assigned ", rightW, focus === "assigned"));
196
+ assignedLines.push(renderPanelHeader(theme, assignedHeader, rightW, focus === "assigned"));
197
+ if (prov) {
198
+ const ids = assignedIdsFor(cfg, prov);
199
+ if (ids.length === 0)
200
+ assignedLines.push(renderEmpty(theme, "(nothing assigned yet)", rightW));
201
+ for (let i = 0;i < ids.length; i++) {
202
+ const id = ids[i];
203
+ const m = catalog.byId.get(id);
204
+ const isCursor = focus === "assigned" && i === assignedCursor;
205
+ if (isCursor) {
206
+ assignedCursorLine = assignedLines.length;
207
+ assignedCursorTop = assignedCursorLine;
208
+ }
209
+ const compatWarn = m ? !apiCompatible(prov.api, m.suggestedApi) : false;
210
+ assignedLines.push(renderModelRow(id, m, "assigned", compatWarn, {
211
+ theme,
212
+ width: rightW,
213
+ isCursor,
214
+ isFocused: focus === "assigned"
215
+ }));
216
+ }
217
+ }
218
+ const poolLines = [];
219
+ poolLines.push(poolHeaderBar(rightW, focus === "pool"));
220
+ if (prov) {
221
+ const groups = poolGroups(prov);
222
+ const flatCount = groups.reduce((n, g) => n + g.ids.length, 0);
223
+ if (flatCount === 0) {
224
+ poolLines.push(renderEmpty(theme, filter ? `(no matches for "${filter}")` : "(no models available)", rightW));
225
+ } else {
226
+ let cursorIdx = 0;
227
+ for (const grp of groups) {
228
+ const groupHeaderLine = poolLines.length;
229
+ poolLines.push(renderSubheader(theme, grp.label, rightW));
230
+ for (let gi = 0;gi < grp.ids.length; gi++) {
231
+ const id = grp.ids[gi];
232
+ const m = catalog.byId.get(id);
233
+ const isCursor = focus === "pool" && cursorIdx === poolCursor;
234
+ if (isCursor) {
235
+ poolCursorLine = poolLines.length;
236
+ poolCursorTop = gi === 0 ? groupHeaderLine : poolCursorLine;
237
+ }
238
+ const compatWarn = m ? !apiCompatible(prov.api, m.suggestedApi) : false;
239
+ poolLines.push(renderModelRow(id, m, "pool", compatWarn, {
240
+ theme,
241
+ width: rightW,
242
+ isCursor,
243
+ isFocused: focus === "pool"
244
+ }));
245
+ cursorIdx++;
246
+ }
247
+ }
248
+ }
249
+ }
250
+ const poolUsableH = Math.max(1, lowerH - 1);
251
+ const leftScroll = clampScroll(leftCursorTop, leftCursorLine, providerScroll, bodyH, leftLines.length, 1);
252
+ const aScroll = clampScroll(focus === "assigned" ? assignedCursorTop : 0, focus === "assigned" ? assignedCursorLine : 0, assignedScroll, upperH, assignedLines.length, 2);
253
+ const pScroll = clampScroll(focus === "pool" ? poolCursorTop : 0, focus === "pool" ? poolCursorLine : 0, poolScroll, poolUsableH, poolLines.length, 1);
254
+ providerScroll = leftScroll;
255
+ assignedScroll = aScroll;
256
+ poolScroll = pScroll;
257
+ const leftSlice = takeSlice(leftLines, leftScroll, bodyH);
258
+ const aSlice = takeSlice(assignedLines, aScroll, upperH);
259
+ const pSlice = takeSlice(poolLines, pScroll, lowerH);
260
+ const vsplit = theme.fg("borderAccent", "│");
261
+ const out = [];
262
+ for (let i = 0;i < bodyH; i++) {
263
+ const l = pad(leftSlice[i] ?? "", leftW);
264
+ const rRaw = i < upperH ? aSlice[i] : pSlice[i - upperH];
265
+ const r = pad(rRaw, rightW);
266
+ out.push(`${l}${vsplit}${r}`);
267
+ }
268
+ const divIdx = upperH;
269
+ if (divIdx > 0 && divIdx < bodyH) {
270
+ const left = pad(leftSlice[divIdx] ?? "", leftW);
271
+ const horiz = theme.fg("borderAccent", "─".repeat(rightW));
272
+ out[divIdx] = `${left}${vsplit}${horiz}`;
273
+ }
274
+ return out;
275
+ }
276
+ function panelHeaderBar(label, width, isFocused) {
277
+ const bar = isFocused ? theme.bold(theme.fg("accent", label)) : theme.fg("muted", label);
278
+ const fill = theme.fg("borderAccent", "─".repeat(Math.max(0, width - visibleWidth(label) - 1)));
279
+ return pad(`${bar}${fill}`, width);
280
+ }
281
+ function poolHeaderBar(width, isFocused) {
282
+ const label = " available pool ";
283
+ const bar = isFocused ? theme.bold(theme.fg("accent", label)) : theme.fg("muted", label);
284
+ let filterChip = "";
285
+ if (filterEditing) {
286
+ filterChip = `${theme.fg("dim", " /")}${theme.fg("accent", filter)}${theme.fg("accent", "█")} `;
287
+ } else if (filter) {
288
+ filterChip = `${theme.fg("dim", " /")}${theme.fg("warning", filter)} `;
289
+ }
290
+ const used = visibleWidth(label) + visibleWidth(filterChip);
291
+ const fill = theme.fg("borderAccent", "─".repeat(Math.max(0, width - used - 1)));
292
+ return pad(`${bar}${filterChip}${fill}`, width);
293
+ }
294
+ function handleInput(data) {
295
+ if (filterEditing) {
296
+ if (matchesKey(data, "escape")) {
297
+ filter = "";
298
+ filterEditing = false;
299
+ poolCursor = 0;
300
+ return true;
301
+ }
302
+ if (matchesKey(data, "enter") || matchesKey(data, "return")) {
303
+ filterEditing = false;
304
+ return true;
305
+ }
306
+ if (matchesKey(data, "backspace") || matchesKey(data, "delete")) {
307
+ filter = filter.slice(0, -1);
308
+ poolCursor = 0;
309
+ return true;
310
+ }
311
+ if (data.length === 1 && data >= " " && data !== "") {
312
+ filter += data;
313
+ poolCursor = 0;
314
+ return true;
315
+ }
316
+ return true;
317
+ }
318
+ if (matchesKey(data, "tab")) {
319
+ onTab(false);
320
+ return true;
321
+ }
322
+ if (matchesKey(data, "shift+tab")) {
323
+ onTab(true);
324
+ return true;
325
+ }
326
+ if (matchesKey(data, "/")) {
327
+ focus = "pool";
328
+ filterEditing = true;
329
+ return true;
330
+ }
331
+ if (matchesKey(data, "enter") || matchesKey(data, "return") || matchesKey(data, "space")) {
332
+ return onActivate().then(() => true);
333
+ }
334
+ if (focus === "providers" && (matchesKey(data, "d") || matchesKey(data, "delete") || matchesKey(data, "backspace"))) {
335
+ return onDelete().then(() => true);
336
+ }
337
+ if (matchesKey(data, "up") || matchesKey(data, "k")) {
338
+ moveCursor(-1);
339
+ return true;
340
+ }
341
+ if (matchesKey(data, "down") || matchesKey(data, "j")) {
342
+ moveCursor(1);
343
+ return true;
344
+ }
345
+ if (matchesKey(data, "pageUp") || matchesKey(data, "b")) {
346
+ moveCursor(-8);
347
+ return true;
348
+ }
349
+ if (matchesKey(data, "pageDown") || matchesKey(data, "f")) {
350
+ moveCursor(8);
351
+ return true;
352
+ }
353
+ if (matchesKey(data, "left") || matchesKey(data, "h")) {
354
+ focus = "providers";
355
+ return true;
356
+ }
357
+ if (matchesKey(data, "right") || matchesKey(data, "l")) {
358
+ if (focus === "providers")
359
+ focus = "assigned";
360
+ else if (focus === "assigned")
361
+ focus = "pool";
362
+ return true;
363
+ }
364
+ return false;
365
+ }
366
+ function footerHint() {
367
+ if (filterEditing)
368
+ return " type to filter · ↵ apply · esc clear ";
369
+ return " tab ↔ panel · ↑↓ nav · ↵ move · / filter · d remove group ";
370
+ }
371
+ return {
372
+ id: "models",
373
+ label: "Models",
374
+ render,
375
+ handleInput,
376
+ footerHint,
377
+ rebuild
378
+ };
379
+ }
@@ -1 +1,106 @@
1
- import{matchesKey as q}from"@duckmind/dm-tui";import{execSync as p}from"node:child_process";import{homedir as D}from"node:os";import{dirname as V0,join as G}from"node:path";var L=!1;var R={info(...J){if(!L)console.log("[dm-cliproxy]",...J)},warn(...J){if(!L)console.warn("[dm-cliproxy]",...J)},error(...J){if(!L)console.error("[dm-cliproxy]",...J)},debug(...J){if(!L&&(process.env.DM_CLIPROXY_DEBUG||process.env.PI_CLIPROXYAPI_DEBUG))console.log("[dm-cliproxy]","[debug]",...J)}};var k=G(D(),".dm","agent","dm-cliproxy"),Z0=G(k,"config.json"),X0=[G(D(),".pi","agent","pi-cliproxyapi","config.json"),G(D(),".config","pi-cliproxyapi","config.json")];function T(J){if(!J)return"";let $=J.trim();if($.startsWith("!"))try{return p($.slice(1),{encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()}catch(Q){let Z=Q?.status,Y=typeof Z==="number"?` (exit ${Z})`:"";return R.warn(`failed to resolve "!" config value${Y}`),""}if($.startsWith("$"))return process.env[$.slice(1)]??"";return $}import{dirname as A0,join as y}from"node:path";var R0=y(k,"discovery-cache.json");var I="dm-cliproxy/0.3.5";var f=15000,W=null;function _(){W=null}async function F(J,$,Q={}){if(!Q.force&&W&&Date.now()-W.fetchedAt<J.usageCacheTtlMs)return W.doc;if(!$)throw Error("usage key not configured; set proxy.usageKey in config and rerun");let Z=new URL("/api/usage",new URL(J.proxy.endpoint).origin).toString(),Y=new AbortController,j=setTimeout(()=>Y.abort(Error("timeout")),f),z;try{z=await fetch(Z,{headers:{"X-Plugin-Key":$,Accept:"application/json","User-Agent":I},signal:Y.signal})}finally{clearTimeout(j)}if(!z.ok)throw Error(`/api/usage returned ${z.status}`);let X=await z.json();if(!X||X.schemaVersion!==1||!Array.isArray(X.accounts))throw Error("/api/usage returned unexpected payload shape");return W={fetchedAt:Date.now(),doc:X},R.debug("usage fetched, accounts:",X.accounts.length),X}import{visibleWidth as N}from"@duckmind/dm-tui";function K(J,$){let Q=N(J);if(Q===$)return J;if(Q<$)return J+" ".repeat($-Q);return h(J,$)}function h(J,$){if($<=0)return"\x1B[0m";let Q="",Z=0,Y=0;while(Y<J.length){let j=J[Y];if(j==="\x1B"&&J[Y+1]==="["){let X=J.indexOf("m",Y+2);if(X>=0){Q+=J.slice(Y,X+1),Y=X+1;continue}}let z=N(j);if(Z+z>$)break;Q+=j,Z+=z,Y++}return`${Q}\x1B[0m`}var V={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",accent:"\x1B[38;5;111m",muted:"\x1B[38;5;245m",dim2:"\x1B[38;5;240m",green:"\x1B[38;5;114m",yellow:"\x1B[38;5;179m",red:"\x1B[38;5;167m",greenBg:"\x1B[48;5;22m",yellowBg:"\x1B[48;5;94m",redBg:"\x1B[48;5;52m"};function w(J,$={}){let Q=[],Z=new Map,Y=0;for(let j of J.accounts){if(!$.showDisabled&&j.disabled){Y++;continue}let z=Z.get(j.provider)??[];z.push(j),Z.set(j.provider,z)}if(Q.push(`${V.dim}generated ${J.generatedAt}${V.reset}`),J.unsupportedProviders.length>0)Q.push(`${V.dim}providers without quota lookup:${V.reset} ${J.unsupportedProviders.join(", ")}`);if(Y>0)Q.push(`${V.dim}hidden disabled accounts:${V.reset} ${Y} ${V.dim2}(press d to show)${V.reset}`);for(let[j,z]of Array.from(Z.entries()).sort()){Q.push(""),Q.push(`${V.bold}${V.accent}▌ ${j}${V.reset}`);for(let X of z){if(Q.push(c(X)),!X.supported){Q.push(` ${V.dim2}(provider not quota-aware)${V.reset}`);continue}if(X.error){if($.verbose)for(let A of r(X.error,88))Q.push(` ${V.red}${A}${V.reset}`);continue}let H=X.groups??[];if(H.length===0){Q.push(` ${V.dim2}(no active quota windows)${V.reset}`);continue}for(let A of H)Q.push(d(A))}}return Q.push(""),Q.push(`${V.dim}legend:${V.reset} ${S} ok ${v} error/unavailable ${n} disabled ${V.green}✓N${V.reset}/${V.red}✗N${V.reset} request counters ${V.dim}v = verbose, d = show disabled${V.reset}`),Q}function r(J,$){if(J.length<=$)return[J];let Q=[],Z=J;while(Z.length>$){let Y=Z.lastIndexOf(" ",$);if(Y<$*0.6)Y=$;Q.push(Z.slice(0,Y)),Z=Z.slice(Y).replace(/^\s+/,"")}if(Z)Q.push(Z);return Q}function c(J){let $=l(J),Q=`${V.green}✓${J.success}${V.reset} ${V.red}✗${J.failed}${V.reset}`,Z=u(J.label,36);return` ${$} ${V.bold}${Z}${V.reset} ${Q}`}var S=`${V.green}●${V.reset}`,v=`${V.yellow}⚠${V.reset}`,n=`${V.dim2}⊘${V.reset}`;function l(J){if(J.disabled)return n;if(J.unavailable||J.error||J.status&&J.status!=="active")return v;return S}function d(J){let $=b(J.remainingFraction),Q=Math.round($*100),Z=o($),Y=J.resetTime?` ${V.dim}reset ${i(J.resetTime)}${V.reset}`:"",j=u(J.label,28).padEnd(28," ");return` ${Z} ${m(Q,$)} ${j}${Y}`}function o(J){let $=b(J),Q=Math.round($*18),Z=x($),Y=V.dim2,j="█".repeat(Q),z="░".repeat(18-Q);return`${Y}[${Z}${j}${Y}${z}]${V.reset}`}function m(J,$){return`${x($)}${String(J).padStart(3)}%${V.reset}`}function x(J){if(J>=0.4)return V.green;if(J>=0.15)return V.yellow;return V.red}function i(J){let $=Date.parse(J);if(!Number.isFinite($))return J;let Q=$-Date.now();if(Q<=0)return"now";let Z=Math.round(Q/60000);if(Z<60)return`${Z}m`;let Y=Math.round(Z/60);if(Y<48)return`${Y}h`;return`${Math.round(Y/24)}d`}function b(J){return Math.max(0,Math.min(1,J))}function u(J,$){if(J.length<=$)return J;return`${J.slice(0,$-1)}…`}import{visibleWidth as u0}from"@duckmind/dm-tui";function C(J,$,Q){let Z=J.slice($,$+Q);while(Z.length<Q)Z.push("");return Z}function P(J,$,Q){let Z=Math.max(0,Q-$);return Math.max(0,Math.min(J,Z))}function c0(J){let{tui:$,theme:Q,cfg:Z}=J,Y="idle",j=null,z="",X=0,H=!1,A=!1,B=(M)=>{Y="loading",$.requestRender();let E=T(Z.proxy.usageKey);F(Z,E,{force:M}).then((U)=>{j=U,Y="ready",X=0,$.requestRender()}).catch((U)=>{z=U.message,Y="error",$.requestRender()})},O=()=>{if(Y==="idle"||Y==="loading")return[Q.fg("dim"," loading usage…")];if(Y==="error")return[Q.fg("error",` usage failed: ${z}`)];if(!j)return[Q.fg("dim"," (no data)")];return w(j,{showDisabled:H,verbose:A})};return{id:"usage",label:"Usage",render:(M,E)=>{let U=O();return X=P(X,E,U.length),C(U,X,E).map((g)=>K(` ${g}`,M))},handleInput:(M)=>{if(q(M,"d"))return H=!H,X=0,!0;if(q(M,"v"))return A=!A,X=0,!0;let E=O().length;if(q(M,"up")||q(M,"k"))return X=Math.max(0,X-1),!0;if(q(M,"down")||q(M,"j"))return X=Math.min(Math.max(0,E-1),X+1),!0;if(q(M,"pageUp")||q(M,"b"))return X=Math.max(0,X-10),!0;if(q(M,"pageDown")||q(M,"f"))return X=Math.min(Math.max(0,E-1),X+10),!0;if(q(M,"home")||q(M,"g"))return X=0,!0;if(q(M,"end")||q(M,"shift+g"))return X=Math.max(0,E-1),!0;return!1},footerHint:()=>{let M=H?Q.fg("success","[d disabled]"):"d disabled",E=A?Q.fg("success","[v verbose]"):"v verbose";return` ↑↓ scroll · ${M} · ${E} `},onActivate:()=>{if(Y==="idle")B(!1)},reload:()=>{if(_(),Y!=="idle")B(!0)}}}export{c0 as buildUsageView};
1
+ import { matchesKey } from "@duckmind/dm-tui";
2
+ import { resolveConfigValue } from "../config.js";
3
+ import {
4
+ clearUsageCache,
5
+ fetchUsage
6
+ } from "../fetch-usage.js";
7
+ import { pad } from "../ui-picker/render-text.js";
8
+ import { renderUsage } from "../ui-usage.js";
9
+ import { clampOffset, takeSlice } from "./shell.js";
10
+ export function buildUsageView(deps) {
11
+ const { tui, theme, cfg } = deps;
12
+ let status = "idle";
13
+ let doc = null;
14
+ let errMsg = "";
15
+ let offset = 0;
16
+ let showDisabled = false;
17
+ let verbose = false;
18
+ const load = (force) => {
19
+ status = "loading";
20
+ tui.requestRender();
21
+ const usageKey = resolveConfigValue(cfg.proxy.usageKey);
22
+ fetchUsage(cfg, usageKey, { force }).then((d) => {
23
+ doc = d;
24
+ status = "ready";
25
+ offset = 0;
26
+ tui.requestRender();
27
+ }).catch((e) => {
28
+ errMsg = e.message;
29
+ status = "error";
30
+ tui.requestRender();
31
+ });
32
+ };
33
+ const lines = () => {
34
+ if (status === "idle" || status === "loading")
35
+ return [theme.fg("dim", " loading usage…")];
36
+ if (status === "error")
37
+ return [theme.fg("error", ` usage failed: ${errMsg}`)];
38
+ if (!doc)
39
+ return [theme.fg("dim", " (no data)")];
40
+ return renderUsage(doc, { showDisabled, verbose });
41
+ };
42
+ const render = (width, height) => {
43
+ const body = lines();
44
+ offset = clampOffset(offset, height, body.length);
45
+ return takeSlice(body, offset, height).map((ln) => pad(` ${ln}`, width));
46
+ };
47
+ const handleInput = (data) => {
48
+ if (matchesKey(data, "d")) {
49
+ showDisabled = !showDisabled;
50
+ offset = 0;
51
+ return true;
52
+ }
53
+ if (matchesKey(data, "v")) {
54
+ verbose = !verbose;
55
+ offset = 0;
56
+ return true;
57
+ }
58
+ const total = lines().length;
59
+ if (matchesKey(data, "up") || matchesKey(data, "k")) {
60
+ offset = Math.max(0, offset - 1);
61
+ return true;
62
+ }
63
+ if (matchesKey(data, "down") || matchesKey(data, "j")) {
64
+ offset = Math.min(Math.max(0, total - 1), offset + 1);
65
+ return true;
66
+ }
67
+ if (matchesKey(data, "pageUp") || matchesKey(data, "b")) {
68
+ offset = Math.max(0, offset - 10);
69
+ return true;
70
+ }
71
+ if (matchesKey(data, "pageDown") || matchesKey(data, "f")) {
72
+ offset = Math.min(Math.max(0, total - 1), offset + 10);
73
+ return true;
74
+ }
75
+ if (matchesKey(data, "home") || matchesKey(data, "g")) {
76
+ offset = 0;
77
+ return true;
78
+ }
79
+ if (matchesKey(data, "end") || matchesKey(data, "shift+g")) {
80
+ offset = Math.max(0, total - 1);
81
+ return true;
82
+ }
83
+ return false;
84
+ };
85
+ const footerHint = () => {
86
+ const d = showDisabled ? theme.fg("success", "[d disabled]") : "d disabled";
87
+ const v = verbose ? theme.fg("success", "[v verbose]") : "v verbose";
88
+ return ` ↑↓ scroll · ${d} · ${v} `;
89
+ };
90
+ return {
91
+ id: "usage",
92
+ label: "Usage",
93
+ render,
94
+ handleInput,
95
+ footerHint,
96
+ onActivate: () => {
97
+ if (status === "idle")
98
+ load(false);
99
+ },
100
+ reload: () => {
101
+ clearUsageCache();
102
+ if (status !== "idle")
103
+ load(true);
104
+ }
105
+ };
106
+ }
@@ -1 +1,41 @@
1
- function B(q){let k=new Map,x=new Map;for(let g of q.builtinProviders){let A=[];for(let j of g.models)k.set(j.id,{id:j.id,name:j.name,suggestedApi:g.api,subtitle:j.name&&j.name!==j.id?j.name:void 0,ownedBy:g.name,reasoning:j.reasoning,contextWindow:j.contextWindow,maxTokens:j.maxTokens,cost:j.cost}),A.push(j.id);x.set(g.name,A)}let z=[];for(let g of q.customPool){if(!k.has(g.id))k.set(g.id,{id:g.id,name:g.name,suggestedApi:g.api,subtitle:g.name&&g.name!==g.id?g.name:void 0,origin:g.suggestedProvider,ownedBy:g.ownedBy,reasoning:g.reasoning,contextWindow:g.contextWindow,maxTokens:g.maxTokens,cost:g.cost});z.push(g.id)}return{byId:k,builtinModelIds:x,customPoolIds:z}}export{B as buildCatalog};
1
+ export function buildCatalog(discovery) {
2
+ const byId = new Map;
3
+ const builtinModelIds = new Map;
4
+ for (const p of discovery.builtinProviders) {
5
+ const ids = [];
6
+ for (const m of p.models) {
7
+ byId.set(m.id, {
8
+ id: m.id,
9
+ name: m.name,
10
+ suggestedApi: p.api,
11
+ subtitle: m.name && m.name !== m.id ? m.name : undefined,
12
+ ownedBy: p.name,
13
+ reasoning: m.reasoning,
14
+ contextWindow: m.contextWindow,
15
+ maxTokens: m.maxTokens,
16
+ cost: m.cost
17
+ });
18
+ ids.push(m.id);
19
+ }
20
+ builtinModelIds.set(p.name, ids);
21
+ }
22
+ const customPoolIds = [];
23
+ for (const m of discovery.customPool) {
24
+ if (!byId.has(m.id)) {
25
+ byId.set(m.id, {
26
+ id: m.id,
27
+ name: m.name,
28
+ suggestedApi: m.api,
29
+ subtitle: m.name && m.name !== m.id ? m.name : undefined,
30
+ origin: m.suggestedProvider,
31
+ ownedBy: m.ownedBy,
32
+ reasoning: m.reasoning,
33
+ contextWindow: m.contextWindow,
34
+ maxTokens: m.maxTokens,
35
+ cost: m.cost
36
+ });
37
+ }
38
+ customPoolIds.push(m.id);
39
+ }
40
+ return { byId, builtinModelIds, customPoolIds };
41
+ }