@duckmind/dm-windows-x64 0.60.6 → 0.60.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 (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,115 @@
1
- function N(j){let z=new Map;for(let[D,G]of Object.entries(j.customProviders))for(let C of G.models)z.set(C.id,D);return z}function L(j,z){if(z.kind==="builtin")return[...j.builtinProviders[z.name]?.models??[]];return j.customProviders[z.name]?.models.map((D)=>D.id)??[]}function Q(j,z,D){if(z.kind==="builtin"){let H=D.builtinModelIds.get(z.name)??[],J=new Set(L(j,z));return H.filter((K)=>!J.has(K))}let G=N(j),C=new Set(L(j,z));return D.customPoolIds.filter((H)=>{if(C.has(H))return!1;let J=G.get(H);return J===void 0||J===z.name})}function V(j){return{id:j.id,name:j.name,contextWindow:j.contextWindow,maxTokens:j.maxTokens,reasoning:j.reasoning,cost:j.cost}}function Y(j,z,D){if(z.kind==="builtin"){let C=j.builtinProviders[z.name]??{enabled:!0,models:[]},H=new Set(C.models);H.add(D.id),j.builtinProviders[z.name]={enabled:!0,apiOverride:C.apiOverride??null,models:Array.from(H).sort()};return}for(let[C,H]of Object.entries(j.customProviders)){if(C===z.name)continue;let J=H.models.findIndex((K)=>K.id===D.id);if(J>=0)H.models.splice(J,1)}let G=j.customProviders[z.name]??{api:z.api,models:[]};if(!G.models.some((C)=>C.id===D.id))G.models.push(V(D));j.customProviders[z.name]=G}function Z(j,z,D){if(z.kind==="builtin"){let C=j.builtinProviders[z.name];if(!C)return;C.models=C.models.filter((H)=>H!==D),C.enabled=C.models.length>0;return}let G=j.customProviders[z.name];if(!G)return;G.models=G.models.filter((C)=>C.id!==D)}function _(j,z){if(j===z)return!0;let D=["openai-completions","openai-responses"];if(D.includes(j)&&D.includes(z))return!0;return!1}function W(j,z){let D=new Map;for(let G of j){let C=z.byId.get(G),H=C?.ownedBy||C?.origin||"misc",J=D.get(H)??[];J.push(G),D.set(H,J)}return Array.from(D.entries()).sort((G,C)=>G[0].localeCompare(C[0])).map(([G,C])=>({label:G,ids:C}))}function X(j,z,D){let G=D.trim().toLowerCase();if(!G)return j;return j.filter((C)=>{if(C.toLowerCase().includes(G))return!0;let H=z.byId.get(C)?.name;return H?H.toLowerCase().includes(G):!1})}function $(j,z,D,G=""){let C=X(Q(j,z,D),D,G);return W(C,D).flatMap((H)=>H.ids)}export{Q as poolFor,$ as poolDisplayOrder,W as groupPoolByOwnedBy,X as filterModelIds,Z as detachModel,N as claimedBy,Y as attachModel,L as assignedIdsFor,_ as apiCompatible};
1
+ export function claimedBy(cfg) {
2
+ const m = new Map;
3
+ for (const [slug, p] of Object.entries(cfg.customProviders)) {
4
+ for (const mm of p.models)
5
+ m.set(mm.id, slug);
6
+ }
7
+ return m;
8
+ }
9
+ export function assignedIdsFor(cfg, prov) {
10
+ if (prov.kind === "builtin") {
11
+ return [...cfg.builtinProviders[prov.name]?.models ?? []];
12
+ }
13
+ return cfg.customProviders[prov.name]?.models.map((m) => m.id) ?? [];
14
+ }
15
+ export function poolFor(cfg, prov, catalog) {
16
+ if (prov.kind === "builtin") {
17
+ const ids = catalog.builtinModelIds.get(prov.name) ?? [];
18
+ const assigned = new Set(assignedIdsFor(cfg, prov));
19
+ return ids.filter((id) => !assigned.has(id));
20
+ }
21
+ const claim = claimedBy(cfg);
22
+ const assigned = new Set(assignedIdsFor(cfg, prov));
23
+ return catalog.customPoolIds.filter((id) => {
24
+ if (assigned.has(id))
25
+ return false;
26
+ const owner = claim.get(id);
27
+ return owner === undefined || owner === prov.name;
28
+ });
29
+ }
30
+ function toEntry(m) {
31
+ return {
32
+ id: m.id,
33
+ name: m.name,
34
+ contextWindow: m.contextWindow,
35
+ maxTokens: m.maxTokens,
36
+ reasoning: m.reasoning,
37
+ cost: m.cost
38
+ };
39
+ }
40
+ export function attachModel(cfg, prov, model) {
41
+ if (prov.kind === "builtin") {
42
+ const cur = cfg.builtinProviders[prov.name] ?? {
43
+ enabled: true,
44
+ models: []
45
+ };
46
+ const set = new Set(cur.models);
47
+ set.add(model.id);
48
+ cfg.builtinProviders[prov.name] = {
49
+ enabled: true,
50
+ apiOverride: cur.apiOverride ?? null,
51
+ models: Array.from(set).sort()
52
+ };
53
+ return;
54
+ }
55
+ for (const [slug, p] of Object.entries(cfg.customProviders)) {
56
+ if (slug === prov.name)
57
+ continue;
58
+ const i = p.models.findIndex((mm) => mm.id === model.id);
59
+ if (i >= 0)
60
+ p.models.splice(i, 1);
61
+ }
62
+ const cur = cfg.customProviders[prov.name] ?? { api: prov.api, models: [] };
63
+ if (!cur.models.some((mm) => mm.id === model.id)) {
64
+ cur.models.push(toEntry(model));
65
+ }
66
+ cfg.customProviders[prov.name] = cur;
67
+ }
68
+ export function detachModel(cfg, prov, modelId) {
69
+ if (prov.kind === "builtin") {
70
+ const cur = cfg.builtinProviders[prov.name];
71
+ if (!cur)
72
+ return;
73
+ cur.models = cur.models.filter((id) => id !== modelId);
74
+ cur.enabled = cur.models.length > 0;
75
+ return;
76
+ }
77
+ const cur = cfg.customProviders[prov.name];
78
+ if (!cur)
79
+ return;
80
+ cur.models = cur.models.filter((mm) => mm.id !== modelId);
81
+ }
82
+ export function apiCompatible(provApi, modelApi) {
83
+ if (provApi === modelApi)
84
+ return true;
85
+ const openaiFamily = ["openai-completions", "openai-responses"];
86
+ if (openaiFamily.includes(provApi) && openaiFamily.includes(modelApi))
87
+ return true;
88
+ return false;
89
+ }
90
+ export function groupPoolByOwnedBy(ids, catalog) {
91
+ const groups = new Map;
92
+ for (const id of ids) {
93
+ const m = catalog.byId.get(id);
94
+ const key = m?.ownedBy || m?.origin || "misc";
95
+ const arr = groups.get(key) ?? [];
96
+ arr.push(id);
97
+ groups.set(key, arr);
98
+ }
99
+ return Array.from(groups.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([label, arr]) => ({ label, ids: arr }));
100
+ }
101
+ export function filterModelIds(ids, catalog, query) {
102
+ const q = query.trim().toLowerCase();
103
+ if (!q)
104
+ return ids;
105
+ return ids.filter((id) => {
106
+ if (id.toLowerCase().includes(q))
107
+ return true;
108
+ const name = catalog.byId.get(id)?.name;
109
+ return name ? name.toLowerCase().includes(q) : false;
110
+ });
111
+ }
112
+ export function poolDisplayOrder(cfg, prov, catalog, filter = "") {
113
+ const ids = filterModelIds(poolFor(cfg, prov, catalog), catalog, filter);
114
+ return groupPoolByOwnedBy(ids, catalog).flatMap((g) => g.ids);
115
+ }
@@ -1 +1,35 @@
1
- import{getKeybindings as Z,matchesKey as v}from"@duckmind/dm-tui";import{visibleWidth as $}from"@duckmind/dm-tui";import{visibleWidth as p}from"@duckmind/dm-tui";function K(n,u){let y=p(n);if(y===u)return n;if(y<u)return n+" ".repeat(u-y);return z(n,u)}function z(n,u){if(u<=0)return"\x1B[0m";let y="",g=0,r=0;while(r<n.length){let m=n[r];if(m==="\x1B"&&n[r+1]==="["){let C=n.indexOf("m",r+2);if(C>=0){y+=n.slice(r,C+1),r=C+1;continue}}let x=p(m);if(g+x>u)break;y+=m,g+=x,r++}return`${y}\x1B[0m`}var F="│",I="╭",J="╮",M="╰",Q="╯",c="─";function _(n,u){let y=Math.max(4,u.width);return[T(n,u.title,u.titleColor??"accent",y),...u.lines.map((g)=>X(n,g,y)),Y(n,u.footer,y)]}function T(n,u,y,g){let r=u??"",m=$(r),x=Math.max(0,g-3-m),C=r?n.bold(n.fg(y,r)):"",P=`${I}${c}${C}${c.repeat(x)}${J}`;return n.fg("borderAccent",P)}function X(n,u,y){let g=Math.max(0,y-2),r=n.fg("borderAccent",F);return`${r}${K(u,g)}${r}`}function Y(n,u,y){let g=`${M}${c}`,r=`${Q}`,m=3;if(!u||!u.hint&&!u.badge){let j=Math.max(0,y-3);return n.fg("borderAccent",`${g}${c.repeat(j)}${r}`)}let x=u.hint??"",C=u.badge??"",P=$(x),k=$(C),q=Math.max(0,y-3-P-k),E=n.fg("dim",x),O=C?n.fg("muted",C):"",f=c.repeat(q);return`${n.fg("borderAccent",g)}${E}${n.fg("borderAccent",f)}${O}${n.fg("borderAccent",r)}`}async function V(n,u){return n.ui.custom((y,g,r,m)=>o(g,`Remove custom group “${u}”?`,"All model assignments in this group will be discarded.",m),{overlay:!0,overlayOptions:{width:80,maxHeight:"40%"}})}function o(n,u,y,g){return{render(r){return _(n,{width:r,title:" confirm ",titleColor:"error",lines:["",` ${n.bold(u)}`,` ${n.fg("dim",y)}`,""],footer:{hint:" y / enter = remove · n / esc = cancel "}})},invalidate(){},handleInput(r){if(Z().matches(r,"tui.select.cancel")||v(r,"escape")||v(r,"n")||v(r,"q")){g(!1);return}if(v(r,"y")||v(r,"enter")||v(r,"return")){g(!0);return}}}}export{V as confirmRemoveProvider};
1
+ import {
2
+ getKeybindings,
3
+ matchesKey
4
+ } from "@duckmind/dm-tui";
5
+ import { frame } from "../ui-frame.js";
6
+ export async function confirmRemoveProvider(ctx, slug) {
7
+ return ctx.ui.custom((_tui, theme, _kb, done) => buildConfirmPrompt(theme, `Remove custom group “${slug}”?`, "All model assignments in this group will be discarded.", done), { overlay: true, overlayOptions: { width: 80, maxHeight: "40%" } });
8
+ }
9
+ function buildConfirmPrompt(theme, title, body, done) {
10
+ return {
11
+ render(width) {
12
+ return frame(theme, {
13
+ width,
14
+ title: " confirm ",
15
+ titleColor: "error",
16
+ lines: ["", ` ${theme.bold(title)}`, ` ${theme.fg("dim", body)}`, ""],
17
+ footer: {
18
+ hint: " y / enter = remove · n / esc = cancel "
19
+ }
20
+ });
21
+ },
22
+ invalidate() {},
23
+ handleInput(data) {
24
+ const kb = getKeybindings();
25
+ if (kb.matches(data, "tui.select.cancel") || matchesKey(data, "escape") || matchesKey(data, "n") || matchesKey(data, "q")) {
26
+ done(false);
27
+ return;
28
+ }
29
+ if (matchesKey(data, "y") || matchesKey(data, "enter") || matchesKey(data, "return")) {
30
+ done(true);
31
+ return;
32
+ }
33
+ }
34
+ };
35
+ }
@@ -1 +1,62 @@
1
- import{getKeybindings as _,Input as D,matchesKey as E}from"@duckmind/dm-tui";function Q(n,u){let c=(n??"").trim();return c?`${c}-${u}`:u}import{visibleWidth as J}from"@duckmind/dm-tui";import{visibleWidth as Y}from"@duckmind/dm-tui";function Z(n,u){let c=Y(n);if(c===u)return n;if(c<u)return n+" ".repeat(u-c);return a(n,u)}function a(n,u){if(u<=0)return"\x1B[0m";let c="",q=0,$=0;while($<n.length){let k=n[$];if(k==="\x1B"&&n[$+1]==="["){let y=n.indexOf("m",$+2);if(y>=0){c+=n.slice($,y+1),$=y+1;continue}}let r=Y(k);if(q+r>u)break;c+=k,q+=r,$++}return`${c}\x1B[0m`}var X="│",F="╭",H="╮",L="╰",N="╯",K="─";function l(n,u){let c=Math.max(4,u.width);return[G(n,u.title,u.titleColor??"accent",c),...u.lines.map((q)=>O(n,q,c)),P(n,u.footer,c)]}function G(n,u,c,q){let $=u??"",k=J($),r=Math.max(0,q-3-k),y=$?n.bold(n.fg(c,$)):"",z=`${F}${K}${y}${K.repeat(r)}${H}`;return n.fg("borderAccent",z)}function O(n,u,c){let q=Math.max(0,c-2),$=n.fg("borderAccent",X);return`${$}${Z(u,q)}${$}`}function P(n,u,c){let q=`${L}${K}`,$=`${N}`,k=3;if(!u||!u.hint&&!u.badge){let V=Math.max(0,c-3);return n.fg("borderAccent",`${q}${K.repeat(V)}${$}`)}let r=u.hint??"",y=u.badge??"",z=J(r),g=J(y),j=Math.max(0,c-3-z-g),I=n.fg("dim",r),v=y?n.fg("muted",y):"",M=K.repeat(j);return`${n.fg("borderAccent",q)}${I}${n.fg("borderAccent",M)}${v}${n.fg("borderAccent",$)}`}function C(n){return Math.max(0,n-2)}async function p(n,u){let c=Q(u,"group");return n.ui.custom((q,$,k,r)=>U(q,$,c,r),{overlay:!0,overlayOptions:{width:80,maxHeight:"40%"}})}function U(n,u,c,q){let $=new D;if(c)$.setValue(c);$.focused=!0;let k=null;return $.onSubmit=(r)=>{let y=r.trim();if(!/^[a-z0-9][a-z0-9._-]*$/i.test(y)){k="letters / digits / dot / dash / underscore only",n.requestRender();return}q(y)},$.onEscape=()=>q(null),{render(r){let y=C(r),z=c?`name shown in /model picker, e.g. ${c}`:"letters / digits / dot / dash / underscore",g=$.render(y-4),j=["",` ${u.fg("dim",z)}`,"",...g.map((I)=>` ${u.fg("accent",`❯ ${I}`)}`),"",k?` ${u.fg("error",`! ${k}`)}`:""];return l(u,{width:r,title:" new custom group ",lines:j,footer:{hint:" enter = create · esc = cancel "}})},invalidate(){$.invalidate()},handleInput(r){if(_().matches(r,"tui.select.cancel")||E(r,"escape")){q(null);return}k=null,$.handleInput(r),n.requestRender()}}}export{p as promptNewProviderName};
1
+ import {
2
+ getKeybindings,
3
+ Input,
4
+ matchesKey
5
+ } from "@duckmind/dm-tui";
6
+ import { withProviderPrefix } from "../compat.js";
7
+ import { frame, frameInner } from "../ui-frame.js";
8
+ export async function promptNewProviderName(ctx, prefix) {
9
+ const suggestion = withProviderPrefix(prefix, "group");
10
+ return ctx.ui.custom((tui, theme, _kb, done) => buildNamePrompt(tui, theme, suggestion, done), { overlay: true, overlayOptions: { width: 80, maxHeight: "40%" } });
11
+ }
12
+ function buildNamePrompt(tui, theme, suggestion, done) {
13
+ const input = new Input;
14
+ if (suggestion)
15
+ input.setValue(suggestion);
16
+ input.focused = true;
17
+ let error = null;
18
+ input.onSubmit = (raw) => {
19
+ const v = raw.trim();
20
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(v)) {
21
+ error = "letters / digits / dot / dash / underscore only";
22
+ tui.requestRender();
23
+ return;
24
+ }
25
+ done(v);
26
+ };
27
+ input.onEscape = () => done(null);
28
+ return {
29
+ render(width) {
30
+ const inner = frameInner(width);
31
+ const hint = suggestion ? `name shown in /model picker, e.g. ${suggestion}` : "letters / digits / dot / dash / underscore";
32
+ const inputLines = input.render(inner - 4);
33
+ const lines = [
34
+ "",
35
+ ` ${theme.fg("dim", hint)}`,
36
+ "",
37
+ ...inputLines.map((ln) => ` ${theme.fg("accent", `❯ ${ln}`)}`),
38
+ "",
39
+ error ? ` ${theme.fg("error", `! ${error}`)}` : ""
40
+ ];
41
+ return frame(theme, {
42
+ width,
43
+ title: " new custom group ",
44
+ lines,
45
+ footer: { hint: " enter = create · esc = cancel " }
46
+ });
47
+ },
48
+ invalidate() {
49
+ input.invalidate();
50
+ },
51
+ handleInput(data) {
52
+ const kb = getKeybindings();
53
+ if (kb.matches(data, "tui.select.cancel") || matchesKey(data, "escape")) {
54
+ done(null);
55
+ return;
56
+ }
57
+ error = null;
58
+ input.handleInput(data);
59
+ tui.requestRender();
60
+ }
61
+ };
62
+ }
@@ -1 +1,45 @@
1
- import{getModels as L,getProviders as O}from"@duckmind/dm-ai/compat";function R(B,C){let D=[],G=new Set(C.builtinModelIds.keys()),H=new Set(O()),J=new Set([...G,...H]);for(let j of Array.from(J).sort()){let q=C.builtinModelIds.get(j);if(!q||q.length===0)continue;let z;try{let F=L(j).find((K)=>q.includes(K.id));if(F)z=F.api}catch{}if(!z){let E=q[0];z=C.byId.get(E)?.suggestedApi??"openai-responses"}if(D.push({kind:"builtin",name:j,api:z,subtitle:`built-in · ${z}`}),!B.builtinProviders[j])B.builtinProviders[j]={enabled:!1,models:[]}}for(let j of Object.keys(B.customProviders).sort()){let q=B.customProviders[j];D.push({kind:"custom",name:j,api:q.api,subtitle:`custom · ${q.api}`})}return D}export{R as collectProviders};
1
+ import { getModels, getProviders } from "@duckmind/dm-ai/compat";
2
+ export function collectProviders(cfg, catalog) {
3
+ const out = [];
4
+ const proxyProviderNames = new Set(catalog.builtinModelIds.keys());
5
+ const knownPiAiProviders = new Set(getProviders());
6
+ const builtinNames = new Set([
7
+ ...proxyProviderNames,
8
+ ...knownPiAiProviders
9
+ ]);
10
+ for (const name of Array.from(builtinNames).sort()) {
11
+ const proxyIds = catalog.builtinModelIds.get(name);
12
+ if (!proxyIds || proxyIds.length === 0)
13
+ continue;
14
+ let api;
15
+ try {
16
+ const localModels = getModels(name);
17
+ const hit = localModels.find((m) => proxyIds.includes(m.id));
18
+ if (hit)
19
+ api = hit.api;
20
+ } catch {}
21
+ if (!api) {
22
+ const firstId = proxyIds[0];
23
+ api = catalog.byId.get(firstId)?.suggestedApi ?? "openai-responses";
24
+ }
25
+ out.push({
26
+ kind: "builtin",
27
+ name,
28
+ api,
29
+ subtitle: `built-in · ${api}`
30
+ });
31
+ if (!cfg.builtinProviders[name]) {
32
+ cfg.builtinProviders[name] = { enabled: false, models: [] };
33
+ }
34
+ }
35
+ for (const slug of Object.keys(cfg.customProviders).sort()) {
36
+ const p = cfg.customProviders[slug];
37
+ out.push({
38
+ kind: "custom",
39
+ name: slug,
40
+ api: p.api,
41
+ subtitle: `custom · ${p.api}`
42
+ });
43
+ }
44
+ return out;
45
+ }
@@ -1 +1,34 @@
1
- import{visibleWidth as F}from"@duckmind/dm-tui";function K(j,q){let x=F(j);if(x===q)return j;if(x<q)return j+" ".repeat(q-x);return H(j,q)}function H(j,q){if(q<=0)return"\x1B[0m";let x="",D=0,y=0;while(y<j.length){let z=j[y];if(z==="\x1B"&&j[y+1]==="["){let B=j.indexOf("m",y+2);if(B>=0){x+=j.slice(y,B+1),y=B+1;continue}}let E=F(z);if(D+E>q)break;x+=z,D+=E,y++}return`${x}\x1B[0m`}export{H as truncateAnsi,K as pad};
1
+ import { visibleWidth } from "@duckmind/dm-tui";
2
+ export function pad(s, width) {
3
+ const w = visibleWidth(s);
4
+ if (w === width)
5
+ return s;
6
+ if (w < width)
7
+ return s + " ".repeat(width - w);
8
+ return truncateAnsi(s, width);
9
+ }
10
+ export function truncateAnsi(s, width) {
11
+ if (width <= 0)
12
+ return "\x1B[0m";
13
+ let out = "";
14
+ let visible = 0;
15
+ let i = 0;
16
+ while (i < s.length) {
17
+ const ch = s[i];
18
+ if (ch === "\x1B" && s[i + 1] === "[") {
19
+ const end = s.indexOf("m", i + 2);
20
+ if (end >= 0) {
21
+ out += s.slice(i, end + 1);
22
+ i = end + 1;
23
+ continue;
24
+ }
25
+ }
26
+ const w = visibleWidth(ch);
27
+ if (visible + w > width)
28
+ break;
29
+ out += ch;
30
+ visible += w;
31
+ i++;
32
+ }
33
+ return `${out}\x1B[0m`;
34
+ }
@@ -1 +1,57 @@
1
- import{visibleWidth as I}from"@duckmind/dm-tui";import{visibleWidth as D}from"@duckmind/dm-tui";function Q(j,q){let z=D(j);if(z===q)return j;if(z<q)return j+" ".repeat(q-z);return y(j,q)}function y(j,q){if(q<=0)return"\x1B[0m";let z="",E=0,J=0;while(J<j.length){let H=j[J];if(H==="\x1B"&&j[J+1]==="["){let K=j.indexOf("m",J+2);if(K>=0){z+=j.slice(J,K+1),J=K+1;continue}}let O=D(H);if(E+O>q)break;z+=H,E+=O,J++}return`${z}\x1B[0m`}function B(j,q,z){if(!q)return" ";if(z)return j.fg("accent","▶ ");return j.fg("muted","▸ ")}function G(j,q){let z=I(j);if(z===q)return j;if(z>q)return y(j,q);return Q(j,q)}function A(j,q,z){let{theme:E,width:J,isCursor:H,isFocused:O}=z,K=B(E,H,O),P=j.kind==="builtin"?q.builtinProviders[j.name]?.models.length??0:q.customProviders[j.name]?.models.length??0,U=j.kind==="builtin"?E.fg("accent","B"):E.fg("warning","C"),V=`${E.fg("dim","[")}${U}${E.fg("dim","]")}`,X=P>0?E.fg("success","●"):E.fg("dim","○"),Y=P>0?E.fg("success",String(P).padStart(2)):E.fg("dim"," 0"),Z=H&&O?E.bold(E.fg("accent",j.name)):H?E.bold(j.name):j.name,_=E.fg("dim",j.api),$=`${K}${V} ${Z} ${X}${Y} ${_}`;return G($,J)}function M(j,q,z,E){let J=B(j,q,z),H=q&&z?j.bold(j.fg("accent","+ new custom group…")):j.fg("accent","+ new custom group…");return Q(`${J}${H}`,E)}function S(j,q,z,E,J){let{theme:H,width:O,isCursor:K,isFocused:P}=J,U=B(H,K,P),V=z==="assigned"?H.fg("success","☑"):H.fg("dim","☐"),X=E?` ${H.fg("warning","⚠")}`:"",Y=K&&P?H.fg("accent",j):j,Z=q?.reasoning?` ${H.fg("accent","✨")}`:"",_=q?H.fg("muted",` · ${q.suggestedApi}`):H.fg("error"," · not on proxy"),$=`${U}${V} ${Y}${X}${Z}${_}`;return G($,O)}function W(j,q,z){let E=`${j.fg("borderAccent","──")} ${j.fg("warning",q)} ${j.fg("borderAccent","──")}`;return Q(` ${E}`,z)}function v(j,q,z){return Q(` ${j.fg("dim",q)}`,z)}function f(j,q,z,E){let J=E?j.bold(j.fg("accent","❚ ")):j.fg("dim"," ");return Q(`${J}${q}`,z)}export{W as renderSubheader,A as renderProviderRow,f as renderPanelHeader,M as renderNewProviderRow,S as renderModelRow,v as renderEmpty};
1
+ import { visibleWidth } from "@duckmind/dm-tui";
2
+ import { pad, truncateAnsi } from "./render-text.js";
3
+ function cursorMark(theme, isCursor, isFocused) {
4
+ if (!isCursor)
5
+ return " ";
6
+ if (isFocused)
7
+ return theme.fg("accent", "▶ ");
8
+ return theme.fg("muted", "▸ ");
9
+ }
10
+ function fitLine(line, width) {
11
+ const w = visibleWidth(line);
12
+ if (w === width)
13
+ return line;
14
+ if (w > width)
15
+ return truncateAnsi(line, width);
16
+ return pad(line, width);
17
+ }
18
+ export function renderProviderRow(p, cfg, ctx) {
19
+ const { theme, width, isCursor, isFocused } = ctx;
20
+ const mark = cursorMark(theme, isCursor, isFocused);
21
+ const count = p.kind === "builtin" ? cfg.builtinProviders[p.name]?.models.length ?? 0 : cfg.customProviders[p.name]?.models.length ?? 0;
22
+ const tag = p.kind === "builtin" ? theme.fg("accent", "B") : theme.fg("warning", "C");
23
+ const tagBracket = `${theme.fg("dim", "[")}${tag}${theme.fg("dim", "]")}`;
24
+ const dot = count > 0 ? theme.fg("success", "●") : theme.fg("dim", "○");
25
+ const countStr = count > 0 ? theme.fg("success", String(count).padStart(2)) : theme.fg("dim", " 0");
26
+ const name = isCursor && isFocused ? theme.bold(theme.fg("accent", p.name)) : isCursor ? theme.bold(p.name) : p.name;
27
+ const api = theme.fg("dim", p.api);
28
+ const line = `${mark}${tagBracket} ${name} ${dot}${countStr} ${api}`;
29
+ return fitLine(line, width);
30
+ }
31
+ export function renderNewProviderRow(theme, isCursor, isFocused, width) {
32
+ const mark = cursorMark(theme, isCursor, isFocused);
33
+ const body = isCursor && isFocused ? theme.bold(theme.fg("accent", "+ new custom group…")) : theme.fg("accent", "+ new custom group…");
34
+ return pad(`${mark}${body}`, width);
35
+ }
36
+ export function renderModelRow(id, m, side, compatWarn, ctx) {
37
+ const { theme, width, isCursor, isFocused } = ctx;
38
+ const mark = cursorMark(theme, isCursor, isFocused);
39
+ const box = side === "assigned" ? theme.fg("success", "☑") : theme.fg("dim", "☐");
40
+ const warn = compatWarn ? ` ${theme.fg("warning", "⚠")}` : "";
41
+ const idStr = isCursor && isFocused ? theme.fg("accent", id) : id;
42
+ const reasoning = m?.reasoning ? ` ${theme.fg("accent", "✨")}` : "";
43
+ const apiTag = m ? theme.fg("muted", ` · ${m.suggestedApi}`) : theme.fg("error", " · not on proxy");
44
+ const line = `${mark}${box} ${idStr}${warn}${reasoning}${apiTag}`;
45
+ return fitLine(line, width);
46
+ }
47
+ export function renderSubheader(theme, label, width) {
48
+ const tag = `${theme.fg("borderAccent", "──")} ${theme.fg("warning", label)} ${theme.fg("borderAccent", "──")}`;
49
+ return pad(` ${tag}`, width);
50
+ }
51
+ export function renderEmpty(theme, label, width) {
52
+ return pad(` ${theme.fg("dim", label)}`, width);
53
+ }
54
+ export function renderPanelHeader(theme, text, width, isFocused) {
55
+ const prefix = isFocused ? theme.bold(theme.fg("accent", "❚ ")) : theme.fg("dim", " ");
56
+ return pad(`${prefix}${text}`, width);
57
+ }
@@ -1,2 +1,260 @@
1
- import{existsSync as w}from"node:fs";import{homedir as LJ}from"node:os";import{getKeybindings as BJ,Input as I,matchesKey as RJ}from"@duckmind/dm-tui";import{execSync as c}from"node:child_process";import{randomUUID as F}from"node:crypto";import{chmodSync as T,closeSync as C,constants as h,copyFileSync as l,existsSync as L,fsyncSync as d,linkSync as n,mkdirSync as s,openSync as o,readFileSync as S,renameSync as i,rmSync as D,writeFileSync as a}from"node:fs";import{homedir as H}from"node:os";import{dirname as G,join as B}from"node:path";import{fileURLToPath as r}from"node:url";var V=!1;var E={info(...J){if(!V)console.log("[dm-cliproxy]",...J)},warn(...J){if(!V)console.warn("[dm-cliproxy]",...J)},error(...J){if(!V)console.error("[dm-cliproxy]",...J)},debug(...J){if(!V&&(process.env.DM_CLIPROXY_DEBUG||process.env.PI_CLIPROXYAPI_DEBUG))console.log("[dm-cliproxy]","[debug]",...J)}};var t=B(H(),".dm","agent","dm-cliproxy"),q=B(t,"config.json"),e=[B(H(),".pi","agent","pi-cliproxyapi","config.json"),B(H(),".config","pi-cliproxyapi","config.json")];function k(J){s(J,{recursive:!0,mode:448}),T(J,448)}function N(J){if(L(J))T(J,384)}function JJ(J){let Q;try{Q=JSON.parse(S(J,"utf8"))}catch{return}let X=Q&&typeof Q==="object"?Q.proxy:void 0;for(let Z of[X?.apiKey,X?.usageKey]){if(typeof Z!=="string")continue;let Y=/^!cat\s+((?:~\/|\/)[^\s;&|`]+)$/.exec(Z.trim());if(!Y)continue;let z=Y[1].replace(/^~/,H());N(z)}}function QJ(){return L(B(G(r(import.meta.url)),"..",".git"))}function XJ(J=e,Q=q,X=QJ()){if(L(Q))return;let Y=(typeof J==="string"?[J]:[...J]).find(($)=>L($));if(!Y)return;let z;try{N(Y),JJ(Y),k(G(Q)),z=`${Q}.${process.pid}.${F()}.tmp`,l(Y,z,h.COPYFILE_EXCL),T(z,384),n(z,Q),D(z),z=void 0,E.info("legacy config copied to",Q)}catch($){if(z)try{D(z,{force:!0})}catch(M){E.warn("failed to clean temporary config:",M)}E.warn("failed to migrate legacy config:",$)}}var O={proxy:{endpoint:"",apiKey:""},builtinProviders:{},customProviders:{},discoveryExcludes:["*:*"],overrides:{},refreshIntervalMinutes:0,usageCacheTtlMs:30000};function b(){if(XJ(),!L(q))return E.debug("config not found, using defaults at",q),structuredClone(O);try{k(G(q)),N(q);let J=JSON.parse(S(q,"utf8"));return YJ(J)}catch(J){return E.error("failed to read config:",J,"— using defaults"),structuredClone(O)}}function ZJ(J,Q=q){k(G(Q));let X=`${Q}.${process.pid}.${F()}.tmp`,Z;try{Z=o(X,"wx",384),a(Z,JSON.stringify(J,null,2)+`
2
- `,{encoding:"utf8"}),d(Z),C(Z),Z=void 0,i(X,Q),N(Q)}finally{if(Z!==void 0)C(Z);D(X,{force:!0})}}function x(J){ZJ(J),E.info("config saved to",q)}function YJ(J){if(!J||typeof J!=="object")return structuredClone(O);let Q=J,X=structuredClone(O),Z=Q.proxy??{};if(X.proxy.endpoint=typeof Z.endpoint==="string"?Z.endpoint:X.proxy.endpoint,X.proxy.apiKey=typeof Z.apiKey==="string"?Z.apiKey:"",typeof Z.usageKey==="string")X.proxy.usageKey=Z.usageKey;if(typeof Z.providerPrefix==="string"&&Z.providerPrefix.trim())X.proxy.providerPrefix=Z.providerPrefix.trim();if(Q.builtinProviders&&typeof Q.builtinProviders==="object")X.builtinProviders=Q.builtinProviders;if(Q.customProviders&&typeof Q.customProviders==="object")X.customProviders=Q.customProviders;if(Array.isArray(Q.discoveryExcludes))X.discoveryExcludes=Q.discoveryExcludes.filter((Y)=>typeof Y==="string");if(Q.overrides&&typeof Q.overrides==="object")X.overrides=Q.overrides;if(typeof Q.refreshIntervalMinutes==="number")X.refreshIntervalMinutes=Q.refreshIntervalMinutes;if(typeof Q.usageCacheTtlMs==="number")X.usageCacheTtlMs=Q.usageCacheTtlMs;return X}function v(J){if(!J)return"";let Q=J.trim();if(Q.startsWith("!"))try{return c(Q.slice(1),{encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()}catch(X){let Z=X?.status,Y=typeof Z==="number"?` (exit ${Z})`:"";return E.warn(`failed to resolve "!" config value${Y}`),""}if(Q.startsWith("$"))return process.env[Q.slice(1)]??"";return Q}import{visibleWidth as A}from"@duckmind/dm-tui";import{visibleWidth as P}from"@duckmind/dm-tui";function y(J,Q){let X=P(J);if(X===Q)return J;if(X<Q)return J+" ".repeat(Q-X);return $J(J,Q)}function $J(J,Q){if(Q<=0)return"\x1B[0m";let X="",Z=0,Y=0;while(Y<J.length){let z=J[Y];if(z==="\x1B"&&J[Y+1]==="["){let M=J.indexOf("m",Y+2);if(M>=0){X+=J.slice(Y,M+1),Y=M+1;continue}}let $=P(z);if(Z+$>Q)break;X+=z,Z+=$,Y++}return`${X}\x1B[0m`}var zJ="│",KJ="╭",MJ="╮",jJ="╰",qJ="╯",R="─";function f(J,Q){let X=Math.max(4,Q.width);return[WJ(J,Q.title,Q.titleColor??"accent",X),...Q.lines.map((Z)=>EJ(J,Z,X)),UJ(J,Q.footer,X)]}function WJ(J,Q,X,Z){let Y=Q??"",z=A(Y),$=Math.max(0,Z-3-z),M=Y?J.bold(J.fg(X,Y)):"",K=`${KJ}${R}${M}${R.repeat($)}${MJ}`;return J.fg("borderAccent",K)}function EJ(J,Q,X){let Z=Math.max(0,X-2),Y=J.fg("borderAccent",zJ);return`${Y}${y(Q,Z)}${Y}`}function UJ(J,Q,X){let Z=`${jJ}${R}`,Y=`${qJ}`,z=3;if(!Q||!Q.hint&&!Q.badge){let p=Math.max(0,X-3);return J.fg("borderAccent",`${Z}${R.repeat(p)}${Y}`)}let $=Q.hint??"",M=Q.badge??"",K=A($),j=A(M),W=Math.max(0,X-3-K-j),U=J.fg("dim",$),_=M?J.fg("muted",M):"",u=R.repeat(W);return`${J.fg("borderAccent",Z)}${U}${J.fg("borderAccent",u)}${_}${J.fg("borderAccent",Y)}`}function g(J){return Math.max(0,J-2)}var VJ=[{label:"endpoint",hint:"OpenAI-style base URL of your CliProxyAPI deployment, must include /v1",required:!0,validate:(J)=>{try{if(!new URL(J).pathname.endsWith("/v1"))return"must end with /v1";return null}catch{return"not a valid URL"}}},{label:"apiKey",hint:"CliProxyAPI bearer key. Input is masked; blank keeps an existing key",required:!0,secret:!0},{label:"providerPrefix",hint:"Prefix for your custom provider names. Suggested groups become <prefix>-glm, <prefix>-gemini, etc. Use any short slug (letters/digits/dashes).",required:!0,validate:(J)=>/^[a-z0-9][a-z0-9-]*$/i.test(J)?null:"letters/digits/dashes only"},{label:"usageKey",hint:"Optional X-Plugin-Key for /api/usage. Input is masked; blank keeps it, '-' clears it",required:!1,secret:!0}];async function cJ(J){let Q=b(),X=Boolean(Q.proxy.endpoint),Z=Boolean(v(Q.proxy.apiKey));if(X&&Z)return!1;if(!J.hasUI)return!1;return HJ(J,!0)}async function HJ(J,Q=!1){let X=b(),Z={endpoint:X.proxy.endpoint??"",apiKey:X.proxy.apiKey??"",providerPrefix:X.proxy.providerPrefix??"",usageKey:X.proxy.usageKey??""},Y=!1;for(let $ of VJ){let M=Z[$.label]??"",K=$.secret?"":Q?Z[$.label]??$.initialValue??"":Z[$.label]??"";if(!Q&&K)continue;let j=$.secret&&M?{...$,required:!1}:$,W=await NJ(J,j,K||$.initialValue||"");if(W===void 0){Y=!0;break}let U=GJ($,M,W);if(U===null){if($.required){J.ui.notify(`${$.label} is required — aborted`,"warning"),Y=!0;break}}Z[$.label]=U??""}if(Y)return!1;let z={...X};if(z.proxy={...X.proxy,endpoint:Z.endpoint??"",apiKey:Z.apiKey??"",providerPrefix:Z.providerPrefix??""},Z.usageKey)z.proxy.usageKey=Z.usageKey;else delete z.proxy.usageKey;return x(z),J.ui.notify(`saved to ${q}`,"info"),!0}function OJ(J){if(J.startsWith("!")||J.startsWith("$"))return J;if(J.startsWith("~/")){let Q=J.replace(/^~/,LJ());return w(Q)?`!cat ${J}`:J}if(J.startsWith("/"))return w(J)?`!cat ${J}`:J;return J}function GJ(J,Q,X){let Z=X.trim();if(!Z){if(J.secret&&Q)return Q;return J.required?null:""}if(J.secret&&!J.required&&Z==="-")return"";return J.label==="providerPrefix"||J.label==="endpoint"?Z:OJ(Z)}async function NJ(J,Q,X){return J.ui.custom((Z,Y,z,$)=>_J(Z,Y,Q,X,$),{overlay:!0,overlayOptions:{width:100,maxHeight:"60%"}})}class m{rawInput=new I;displayInput=new I;pasteActive=!1;get focused(){return this.rawInput.focused}set focused(J){this.rawInput.focused=J,this.displayInput.focused=J}set onSubmit(J){this.rawInput.onSubmit=J}set onEscape(J){this.rawInput.onEscape=J}getValue(){return this.rawInput.getValue()}setValue(J){this.rawInput.setValue(J),this.displayInput.setValue("•".repeat(J.length))}handleInput(J){this.rawInput.handleInput(J),this.displayInput.handleInput(this.maskInput(J)),this.displayInput.setValue("•".repeat(this.rawInput.getValue().length))}render(J){return this.displayInput.render(J)}invalidate(){this.rawInput.invalidate(),this.displayInput.invalidate()}maskInput(J){let Q="";for(let X=0;X<J.length;){if(J.startsWith("\x1B[200~",X)){this.pasteActive=!0,Q+="\x1B[200~",X+=6;continue}if(J.startsWith("\x1B[201~",X)){this.pasteActive=!1,Q+="\x1B[201~",X+=6;continue}if(!this.pasteActive&&J.charCodeAt(X)===27)return J;let Z=J.codePointAt(X),Y=String.fromCodePoint(Z);Q+=Z<32||Z===127?Y:"•",X+=Y.length}return Q}}function _J(J,Q,X,Z,Y){let z=X.secret?new m:new I;z.setValue(Z),z.focused=!0;let $=null,M=(K)=>{if(!K&&!X.required){Y("");return}if(!K&&X.required){$="required field",J.requestRender();return}if(X.validate){let j=K.trim();if(j&&!j.startsWith("!")&&!j.startsWith("$")){let W=X.validate(j);if(W){$=W,J.requestRender();return}}}Y(K)};return z.onSubmit=M,z.onEscape=()=>Y(void 0),{render(K){let j=g(K),W=z.render(j-4),U=["",` ${Q.fg("dim",X.hint)}`,"",...W.map((_)=>` ${Q.fg("accent",`❯ ${_}`)}`),"",$?` ${Q.fg("error",`! ${$}`)}`:` ${Q.fg("dim","enter = save · esc = cancel")}`];return f(Q,{width:K,title:` setup: ${X.label} `,lines:U,footer:{hint:" enter = save · esc = cancel "}})},invalidate(){z.invalidate()},handleInput(K){if(BJ().matches(K,"tui.select.cancel")||RJ(K,"escape")){Y(void 0);return}$=null,z.handleInput(K),J.requestRender()}}}export{cJ as runSetupIfNeeded,HJ as runSetup,GJ as resolveWizardValue,_J as buildStepOverlay};
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import {
4
+ getKeybindings,
5
+ Input,
6
+ matchesKey
7
+ } from "@duckmind/dm-tui";
8
+ import {
9
+ CONFIG_PATH,
10
+ loadConfig,
11
+ resolveConfigValue,
12
+ saveConfig
13
+ } from "./config.js";
14
+ import { frame, frameInner } from "./ui-frame.js";
15
+ const STEPS = [
16
+ {
17
+ label: "endpoint",
18
+ hint: "OpenAI-style base URL of your CliProxyAPI deployment, must include /v1",
19
+ required: true,
20
+ validate: (raw) => {
21
+ try {
22
+ const u = new URL(raw);
23
+ if (!u.pathname.endsWith("/v1"))
24
+ return "must end with /v1";
25
+ return null;
26
+ } catch {
27
+ return "not a valid URL";
28
+ }
29
+ }
30
+ },
31
+ {
32
+ label: "apiKey",
33
+ hint: "CliProxyAPI bearer key. Input is masked; blank keeps an existing key",
34
+ required: true,
35
+ secret: true
36
+ },
37
+ {
38
+ label: "providerPrefix",
39
+ hint: "Prefix for your custom provider names. Suggested groups become <prefix>-glm, <prefix>-gemini, etc. Use any short slug (letters/digits/dashes).",
40
+ required: true,
41
+ validate: (raw) => /^[a-z0-9][a-z0-9-]*$/i.test(raw) ? null : "letters/digits/dashes only"
42
+ },
43
+ {
44
+ label: "usageKey",
45
+ hint: "Optional X-Plugin-Key for /api/usage. Input is masked; blank keeps it, '-' clears it",
46
+ required: false,
47
+ secret: true
48
+ }
49
+ ];
50
+ export async function runSetupIfNeeded(ctx) {
51
+ const cfg = loadConfig();
52
+ const hasEndpoint = Boolean(cfg.proxy.endpoint);
53
+ const hasResolvedKey = Boolean(resolveConfigValue(cfg.proxy.apiKey));
54
+ if (hasEndpoint && hasResolvedKey)
55
+ return false;
56
+ if (!ctx.hasUI)
57
+ return false;
58
+ return runSetup(ctx, true);
59
+ }
60
+ export async function runSetup(ctx, forceAll = false) {
61
+ const existing = loadConfig();
62
+ const values = {
63
+ endpoint: existing.proxy.endpoint ?? "",
64
+ apiKey: existing.proxy.apiKey ?? "",
65
+ providerPrefix: existing.proxy.providerPrefix ?? "",
66
+ usageKey: existing.proxy.usageKey ?? ""
67
+ };
68
+ let cancelled = false;
69
+ for (const step of STEPS) {
70
+ const currentValue = values[step.label] ?? "";
71
+ const prefill = step.secret ? "" : forceAll ? values[step.label] ?? step.initialValue ?? "" : values[step.label] ?? "";
72
+ if (!forceAll && prefill) {
73
+ continue;
74
+ }
75
+ const promptStepConfig = step.secret && currentValue ? { ...step, required: false } : step;
76
+ const result = await promptStep(ctx, promptStepConfig, prefill || step.initialValue || "");
77
+ if (result === undefined) {
78
+ cancelled = true;
79
+ break;
80
+ }
81
+ const resolved = resolveWizardValue(step, currentValue, result);
82
+ if (resolved === null) {
83
+ if (step.required) {
84
+ ctx.ui.notify(`${step.label} is required — aborted`, "warning");
85
+ cancelled = true;
86
+ break;
87
+ }
88
+ }
89
+ values[step.label] = resolved ?? "";
90
+ }
91
+ if (cancelled)
92
+ return false;
93
+ const next = { ...existing };
94
+ next.proxy = {
95
+ ...existing.proxy,
96
+ endpoint: values.endpoint ?? "",
97
+ apiKey: values.apiKey ?? "",
98
+ providerPrefix: values.providerPrefix ?? ""
99
+ };
100
+ if (values.usageKey)
101
+ next.proxy.usageKey = values.usageKey;
102
+ else
103
+ delete next.proxy.usageKey;
104
+ saveConfig(next);
105
+ ctx.ui.notify(`saved to ${CONFIG_PATH}`, "info");
106
+ return true;
107
+ }
108
+ function normalizeValue(raw) {
109
+ if (raw.startsWith("!") || raw.startsWith("$"))
110
+ return raw;
111
+ if (raw.startsWith("~/")) {
112
+ const expanded = raw.replace(/^~/, homedir());
113
+ return existsSync(expanded) ? `!cat ${raw}` : raw;
114
+ }
115
+ if (raw.startsWith("/")) {
116
+ return existsSync(raw) ? `!cat ${raw}` : raw;
117
+ }
118
+ return raw;
119
+ }
120
+ export function resolveWizardValue(step, currentValue, raw) {
121
+ const trimmed = raw.trim();
122
+ if (!trimmed) {
123
+ if (step.secret && currentValue)
124
+ return currentValue;
125
+ return step.required ? null : "";
126
+ }
127
+ if (step.secret && !step.required && trimmed === "-")
128
+ return "";
129
+ return step.label === "providerPrefix" || step.label === "endpoint" ? trimmed : normalizeValue(trimmed);
130
+ }
131
+ async function promptStep(ctx, step, prefill) {
132
+ return ctx.ui.custom((tui, theme, _kb, done) => buildStepOverlay(tui, theme, step, prefill, done), { overlay: true, overlayOptions: { width: 100, maxHeight: "60%" } });
133
+ }
134
+
135
+ class MaskedInput {
136
+ rawInput = new Input;
137
+ displayInput = new Input;
138
+ pasteActive = false;
139
+ get focused() {
140
+ return this.rawInput.focused;
141
+ }
142
+ set focused(value) {
143
+ this.rawInput.focused = value;
144
+ this.displayInput.focused = value;
145
+ }
146
+ set onSubmit(handler) {
147
+ this.rawInput.onSubmit = handler;
148
+ }
149
+ set onEscape(handler) {
150
+ this.rawInput.onEscape = handler;
151
+ }
152
+ getValue() {
153
+ return this.rawInput.getValue();
154
+ }
155
+ setValue(value) {
156
+ this.rawInput.setValue(value);
157
+ this.displayInput.setValue("•".repeat(value.length));
158
+ }
159
+ handleInput(data) {
160
+ this.rawInput.handleInput(data);
161
+ this.displayInput.handleInput(this.maskInput(data));
162
+ this.displayInput.setValue("•".repeat(this.rawInput.getValue().length));
163
+ }
164
+ render(width) {
165
+ return this.displayInput.render(width);
166
+ }
167
+ invalidate() {
168
+ this.rawInput.invalidate();
169
+ this.displayInput.invalidate();
170
+ }
171
+ maskInput(data) {
172
+ let output = "";
173
+ for (let i = 0;i < data.length; ) {
174
+ if (data.startsWith("\x1B[200~", i)) {
175
+ this.pasteActive = true;
176
+ output += "\x1B[200~";
177
+ i += 6;
178
+ continue;
179
+ }
180
+ if (data.startsWith("\x1B[201~", i)) {
181
+ this.pasteActive = false;
182
+ output += "\x1B[201~";
183
+ i += 6;
184
+ continue;
185
+ }
186
+ if (!this.pasteActive && data.charCodeAt(i) === 27) {
187
+ return data;
188
+ }
189
+ const codePoint = data.codePointAt(i);
190
+ const char = String.fromCodePoint(codePoint);
191
+ output += codePoint < 32 || codePoint === 127 ? char : "•";
192
+ i += char.length;
193
+ }
194
+ return output;
195
+ }
196
+ }
197
+ export function buildStepOverlay(tui, theme, step, prefill, done) {
198
+ const input = step.secret ? new MaskedInput : new Input;
199
+ input.setValue(prefill);
200
+ input.focused = true;
201
+ let error = null;
202
+ const submit = (raw) => {
203
+ if (!raw && !step.required) {
204
+ done("");
205
+ return;
206
+ }
207
+ if (!raw && step.required) {
208
+ error = "required field";
209
+ tui.requestRender();
210
+ return;
211
+ }
212
+ if (step.validate) {
213
+ const trimmed = raw.trim();
214
+ if (trimmed && !trimmed.startsWith("!") && !trimmed.startsWith("$")) {
215
+ const err = step.validate(trimmed);
216
+ if (err) {
217
+ error = err;
218
+ tui.requestRender();
219
+ return;
220
+ }
221
+ }
222
+ }
223
+ done(raw);
224
+ };
225
+ input.onSubmit = submit;
226
+ input.onEscape = () => done(undefined);
227
+ return {
228
+ render(width) {
229
+ const inner = frameInner(width);
230
+ const inputLines = input.render(inner - 4);
231
+ const lines = [
232
+ "",
233
+ ` ${theme.fg("dim", step.hint)}`,
234
+ "",
235
+ ...inputLines.map((ln) => ` ${theme.fg("accent", `❯ ${ln}`)}`),
236
+ "",
237
+ error ? ` ${theme.fg("error", `! ${error}`)}` : ` ${theme.fg("dim", "enter = save · esc = cancel")}`
238
+ ];
239
+ return frame(theme, {
240
+ width,
241
+ title: ` setup: ${step.label} `,
242
+ lines,
243
+ footer: { hint: " enter = save · esc = cancel " }
244
+ });
245
+ },
246
+ invalidate() {
247
+ input.invalidate();
248
+ },
249
+ handleInput(data) {
250
+ const kb = getKeybindings();
251
+ if (kb.matches(data, "tui.select.cancel") || matchesKey(data, "escape")) {
252
+ done(undefined);
253
+ return;
254
+ }
255
+ error = null;
256
+ input.handleInput(data);
257
+ tui.requestRender();
258
+ }
259
+ };
260
+ }