@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.
- package/dm.exe +0 -0
- package/extensions/.dm-extensions.json +70 -123
- package/extensions/dm-9router-ext/src/index.js +410 -5
- package/extensions/dm-caveman/extensions/caveman.js +283 -12
- package/extensions/dm-cliproxy/index.js +182 -2
- package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
- package/extensions/dm-cliproxy/src/apply.js +228 -1
- package/extensions/dm-cliproxy/src/cache.js +42 -1
- package/extensions/dm-cliproxy/src/commands.js +50 -2
- package/extensions/dm-cliproxy/src/compat.js +81 -1
- package/extensions/dm-cliproxy/src/config.js +192 -2
- package/extensions/dm-cliproxy/src/conflicts.js +46 -1
- package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
- package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
- package/extensions/dm-cliproxy/src/log.js +23 -1
- package/extensions/dm-cliproxy/src/status-quota.js +77 -1
- package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
- package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
- package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
- package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
- package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
- package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
- package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
- package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
- package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
- package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
- package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
- package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
- package/extensions/dm-context/src/context.js +144 -1
- package/extensions/dm-context/src/index.js +339 -7
- package/extensions/dm-context/src/utils.js +9 -1
- package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
- package/extensions/dm-cua/index.js +75 -6
- package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
- package/extensions/dm-cua/src/browser-install.mjs +331 -2
- package/extensions/dm-fff/src/index.js +688 -12
- package/extensions/dm-fff/src/query.js +60 -1
- package/extensions/dm-goal/src/goal.js +894 -23
- package/extensions/dm-image2/index.js +103 -9
- package/extensions/dm-image2/src/image-lib.mjs +1275 -8
- package/extensions/dm-subagents/install.mjs +62 -8
- package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
- package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
- package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
- package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
- package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
- package/extensions/dm-subagents/src/agents/agents.js +1139 -11
- package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
- package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
- package/extensions/dm-subagents/src/agents/identity.js +29 -1
- package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
- package/extensions/dm-subagents/src/agents/skills.js +614 -6
- package/extensions/dm-subagents/src/extension/config.js +35 -2
- package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
- package/extensions/dm-subagents/src/extension/doctor.js +172 -15
- package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
- package/extensions/dm-subagents/src/extension/index.js +521 -359
- package/extensions/dm-subagents/src/extension/rpc.js +266 -7
- package/extensions/dm-subagents/src/extension/schemas.js +275 -1
- package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
- package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
- package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
- package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
- package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
- package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
- package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
- package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
- package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
- package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
- package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
- package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
- package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
- package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
- package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
- package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
- package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
- package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
- package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
- package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
- package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
- package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
- package/extensions/dm-subagents/src/shared/formatters.js +98 -7
- package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
- package/extensions/dm-subagents/src/shared/model-info.js +62 -1
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
- package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
- package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
- package/extensions/dm-subagents/src/shared/settings.js +198 -11
- package/extensions/dm-subagents/src/shared/status-format.js +53 -1
- package/extensions/dm-subagents/src/shared/types.js +184 -6
- package/extensions/dm-subagents/src/shared/utils.js +462 -2
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
- package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
- package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
- package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
- package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
- package/extensions/dm-subagents/src/tui/render.js +1542 -4
- package/extensions/dm-usage/index.js +1294 -9
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
- package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
- package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
- package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
- package/extensions/greedysearch-dm/bin/search.mjs +620 -540
- package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
- package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
- package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
- package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
- package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
- package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
- package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
- package/extensions/greedysearch-dm/index.js +123 -23
- package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
- package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
- package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
- package/extensions/greedysearch-dm/src/github.mjs +222 -7
- package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
- package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
- package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
- package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
- package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
- package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
- package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
- package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
- package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
- package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
- package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
- package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
- package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
- package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
- package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
- package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
- package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
- package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
- package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
- package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
- package/package.json +1 -1
|
@@ -1 +1,115 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2
|
-
|
|
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
|
+
}
|