@duckmind/dm-darwin-arm64 0.33.0 → 0.33.2
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 +0 -0
- package/extensions/.dm-extensions.json +1 -76
- package/package.json +1 -1
- package/theme/theme-alps.json +84 -0
- package/extensions/dm-chime/README.md +0 -11
- package/extensions/dm-chime/docs/protocols.md +0 -107
- package/extensions/dm-chime/index.ts +0 -205
- package/extensions/dm-chime/package.json +0 -33
- package/extensions/dm-phone/README.md +0 -24
- package/extensions/dm-phone/index.ts +0 -12
- package/extensions/dm-phone/node_modules/.package-lock.json +0 -29
- package/extensions/dm-phone/node_modules/ws/LICENSE +0 -20
- package/extensions/dm-phone/node_modules/ws/README.md +0 -548
- package/extensions/dm-phone/node_modules/ws/browser.js +0 -8
- package/extensions/dm-phone/node_modules/ws/index.js +0 -22
- package/extensions/dm-phone/node_modules/ws/lib/buffer-util.js +0 -131
- package/extensions/dm-phone/node_modules/ws/lib/constants.js +0 -19
- package/extensions/dm-phone/node_modules/ws/lib/event-target.js +0 -292
- package/extensions/dm-phone/node_modules/ws/lib/extension.js +0 -203
- package/extensions/dm-phone/node_modules/ws/lib/limiter.js +0 -55
- package/extensions/dm-phone/node_modules/ws/lib/permessage-deflate.js +0 -528
- package/extensions/dm-phone/node_modules/ws/lib/receiver.js +0 -760
- package/extensions/dm-phone/node_modules/ws/lib/sender.js +0 -607
- package/extensions/dm-phone/node_modules/ws/lib/stream.js +0 -161
- package/extensions/dm-phone/node_modules/ws/lib/subprotocol.js +0 -62
- package/extensions/dm-phone/node_modules/ws/lib/validation.js +0 -152
- package/extensions/dm-phone/node_modules/ws/lib/websocket-server.js +0 -562
- package/extensions/dm-phone/node_modules/ws/lib/websocket.js +0 -1407
- package/extensions/dm-phone/node_modules/ws/package.json +0 -70
- package/extensions/dm-phone/node_modules/ws/wrapper.mjs +0 -21
- package/extensions/dm-phone/package-lock.json +0 -66
- package/extensions/dm-phone/package.json +0 -35
- package/extensions/dm-phone/phone-session-pool.ts +0 -8
- package/extensions/dm-phone/public/app/attachments.js +0 -233
- package/extensions/dm-phone/public/app/autocomplete-controller.js +0 -81
- package/extensions/dm-phone/public/app/autocomplete.js +0 -135
- package/extensions/dm-phone/public/app/bindings.js +0 -178
- package/extensions/dm-phone/public/app/command-catalog.js +0 -76
- package/extensions/dm-phone/public/app/commands.js +0 -376
- package/extensions/dm-phone/public/app/constants.js +0 -60
- package/extensions/dm-phone/public/app/formatters.js +0 -131
- package/extensions/dm-phone/public/app/handlers.js +0 -442
- package/extensions/dm-phone/public/app/main.js +0 -6
- package/extensions/dm-phone/public/app/markdown.js +0 -105
- package/extensions/dm-phone/public/app/messages.js +0 -418
- package/extensions/dm-phone/public/app/sheet-actions.js +0 -113
- package/extensions/dm-phone/public/app/sheet-navigation.js +0 -19
- package/extensions/dm-phone/public/app/sheets-view.js +0 -287
- package/extensions/dm-phone/public/app/state.js +0 -95
- package/extensions/dm-phone/public/app/tool-rendering.js +0 -562
- package/extensions/dm-phone/public/app/transport.js +0 -176
- package/extensions/dm-phone/public/app/ui.js +0 -417
- package/extensions/dm-phone/public/app.js +0 -1
- package/extensions/dm-phone/public/icon.svg +0 -15
- package/extensions/dm-phone/public/index.html +0 -146
- package/extensions/dm-phone/public/manifest.webmanifest +0 -17
- package/extensions/dm-phone/public/styles.css +0 -1139
- package/extensions/dm-phone/public/sw.js +0 -78
- package/extensions/dm-phone/src/extension/duckmind-models.js +0 -264
- package/extensions/dm-phone/src/extension/phone-args.ts +0 -121
- package/extensions/dm-phone/src/extension/phone-paths.ts +0 -250
- package/extensions/dm-phone/src/extension/phone-quota.ts +0 -188
- package/extensions/dm-phone/src/extension/phone-runtime.ts +0 -154
- package/extensions/dm-phone/src/extension/phone-server-runtime.ts +0 -1217
- package/extensions/dm-phone/src/extension/phone-sessions.ts +0 -139
- package/extensions/dm-phone/src/extension/phone-static.ts +0 -30
- package/extensions/dm-phone/src/extension/phone-tailscale.ts +0 -148
- package/extensions/dm-phone/src/extension/phone-theme.ts +0 -85
- package/extensions/dm-phone/src/extension/register-phone-child-extension.ts +0 -112
- package/extensions/dm-phone/src/extension/register-phone-extension.ts +0 -106
- package/extensions/dm-phone/src/extension/types.ts +0 -73
- package/extensions/dm-phone/src/session-pool/parent-session-worker.ts +0 -882
- package/extensions/dm-phone/src/session-pool/session-pool.ts +0 -470
- package/extensions/dm-phone/src/session-pool/session-worker.ts +0 -739
- package/extensions/dm-phone/src/session-pool/types.ts +0 -111
- package/extensions/dm-phone/src/session-pool/utils.ts +0 -23
- package/extensions/dm-phone/test/duckmind-models.test.js +0 -147
- package/extensions/dm-thinking-timer/LICENSE +0 -21
- package/extensions/dm-thinking-timer/README.md +0 -7
- package/extensions/dm-thinking-timer/package.json +0 -20
- package/extensions/dm-thinking-timer/thinking-timer.ts +0 -250
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
const CACHE = "dm-phone-v17";
|
|
2
|
-
const ASSETS = [
|
|
3
|
-
"/",
|
|
4
|
-
"/styles.css",
|
|
5
|
-
"/app.js",
|
|
6
|
-
"/app/attachments.js",
|
|
7
|
-
"/app/autocomplete-controller.js",
|
|
8
|
-
"/app/autocomplete.js",
|
|
9
|
-
"/app/bindings.js",
|
|
10
|
-
"/app/command-catalog.js",
|
|
11
|
-
"/app/commands.js",
|
|
12
|
-
"/app/constants.js",
|
|
13
|
-
"/app/formatters.js",
|
|
14
|
-
"/app/handlers.js",
|
|
15
|
-
"/app/main.js",
|
|
16
|
-
"/app/markdown.js",
|
|
17
|
-
"/app/messages.js",
|
|
18
|
-
"/app/sheet-actions.js",
|
|
19
|
-
"/app/sheet-navigation.js",
|
|
20
|
-
"/app/sheets-view.js",
|
|
21
|
-
"/app/state.js",
|
|
22
|
-
"/app/tool-rendering.js",
|
|
23
|
-
"/app/transport.js",
|
|
24
|
-
"/app/ui.js",
|
|
25
|
-
"/manifest.webmanifest",
|
|
26
|
-
"/icon.svg",
|
|
27
|
-
];
|
|
28
|
-
const APP_SHELL = new Set(ASSETS);
|
|
29
|
-
|
|
30
|
-
self.addEventListener("install", (event) => {
|
|
31
|
-
event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(ASSETS)));
|
|
32
|
-
self.skipWaiting();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
self.addEventListener("activate", (event) => {
|
|
36
|
-
event.waitUntil(
|
|
37
|
-
caches.keys().then((keys) => Promise.all(keys.filter((key) => key !== CACHE).map((key) => caches.delete(key)))),
|
|
38
|
-
);
|
|
39
|
-
self.clients.claim();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
async function updateCache(request) {
|
|
43
|
-
const response = await fetch(request);
|
|
44
|
-
if (response.ok) {
|
|
45
|
-
const copy = response.clone();
|
|
46
|
-
caches.open(CACHE).then((cache) => cache.put(request, copy));
|
|
47
|
-
}
|
|
48
|
-
return response;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
self.addEventListener("fetch", (event) => {
|
|
52
|
-
const { request } = event;
|
|
53
|
-
if (request.method !== "GET") return;
|
|
54
|
-
|
|
55
|
-
const url = new URL(request.url);
|
|
56
|
-
if (url.origin !== self.location.origin) return;
|
|
57
|
-
if (url.pathname.startsWith("/api/") || url.pathname === "/ws") return;
|
|
58
|
-
|
|
59
|
-
const useNetworkFirst = request.mode === "navigate" || APP_SHELL.has(url.pathname);
|
|
60
|
-
|
|
61
|
-
if (useNetworkFirst) {
|
|
62
|
-
event.respondWith(
|
|
63
|
-
updateCache(request).catch(async () => {
|
|
64
|
-
const cached = await caches.match(request);
|
|
65
|
-
if (cached) return cached;
|
|
66
|
-
return caches.match("/");
|
|
67
|
-
}),
|
|
68
|
-
);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
event.respondWith(
|
|
73
|
-
caches.match(request).then((cached) => {
|
|
74
|
-
if (cached) return cached;
|
|
75
|
-
return updateCache(request);
|
|
76
|
-
}),
|
|
77
|
-
);
|
|
78
|
-
});
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
|
|
4
|
-
const DUCKMIND_PROVIDER = "duckmind";
|
|
5
|
-
const OPENROUTER_PROVIDER = "openrouter";
|
|
6
|
-
const OPENAI_CODEX_PROVIDER = "openai-codex";
|
|
7
|
-
|
|
8
|
-
const DUCKMIND_PRESETS = [
|
|
9
|
-
{
|
|
10
|
-
alias: "free",
|
|
11
|
-
envKey: "DM_FREE_MODEL",
|
|
12
|
-
fallbackModelId: "@preset/free",
|
|
13
|
-
displayName: "Free",
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
alias: "auto",
|
|
17
|
-
envKey: "DM_AUTO_MODEL",
|
|
18
|
-
fallbackModelId: "@preset/auto",
|
|
19
|
-
displayName: "Auto",
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
alias: "lite",
|
|
23
|
-
envKey: "DM_LITE_MODEL",
|
|
24
|
-
fallbackModelId: "@preset/lite",
|
|
25
|
-
displayName: "Lite",
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
alias: "smart",
|
|
29
|
-
envKey: "DM_SMART_MODEL",
|
|
30
|
-
fallbackModelId: "@preset/smart",
|
|
31
|
-
displayName: "Smart",
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
alias: "deep",
|
|
35
|
-
envKey: "DM_DEEP_MODEL",
|
|
36
|
-
fallbackModelId: "@preset/deep",
|
|
37
|
-
displayName: "Deep",
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
alias: "image2",
|
|
41
|
-
envKey: "DM_IMAGE2_MODEL",
|
|
42
|
-
fallbackModelId: "@preset/image2",
|
|
43
|
-
displayName: "Image2",
|
|
44
|
-
},
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
const DUCKMIND_ULTRA = {
|
|
48
|
-
alias: "ultra",
|
|
49
|
-
actualModelId: "gpt-5.5",
|
|
50
|
-
displayName: "Ultra",
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
function normalize(value) {
|
|
54
|
-
return String(value ?? "").trim().toLowerCase();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function resolvePresetTarget(envKey, fallbackModelId) {
|
|
58
|
-
const override = process.env[envKey]?.trim();
|
|
59
|
-
return override || fallbackModelId;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function getAgentDir() {
|
|
63
|
-
return process.env.DM_CODING_AGENT_DIR?.trim()
|
|
64
|
-
|| process.env.PI_CODING_AGENT_DIR?.trim()
|
|
65
|
-
|| join(process.env.HOME || process.env.USERPROFILE || process.cwd(), ".dm", "agent");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function hasUltradexAccounts() {
|
|
69
|
-
const accountsPath = join(getAgentDir(), "codex-accounts.json");
|
|
70
|
-
if (!existsSync(accountsPath)) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const parsed = JSON.parse(readFileSync(accountsPath, "utf-8"));
|
|
76
|
-
return Array.isArray(parsed?.accounts) && parsed.accounts.length > 0;
|
|
77
|
-
} catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function pickProviderModel(models, provider, actualModelId, fallbackModelId, displayName) {
|
|
83
|
-
const providerModels = Array.isArray(models)
|
|
84
|
-
? models.filter((model) => normalize(model?.provider) === normalize(provider))
|
|
85
|
-
: [];
|
|
86
|
-
if (!providerModels.length) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const exact = providerModels.find((model) => normalize(model?.id) === normalize(actualModelId));
|
|
91
|
-
if (exact) {
|
|
92
|
-
return exact;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const fallback = providerModels.find((model) => normalize(model?.id) === normalize(fallbackModelId)) || providerModels[0];
|
|
96
|
-
return {
|
|
97
|
-
...fallback,
|
|
98
|
-
id: actualModelId,
|
|
99
|
-
name: displayName,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function finiteContextWindow(model) {
|
|
104
|
-
const contextWindow = Number(model?.contextWindow);
|
|
105
|
-
return Number.isFinite(contextWindow) && contextWindow > 0 ? contextWindow : undefined;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function getResolvedPresets(presets = DUCKMIND_PRESETS) {
|
|
109
|
-
return presets.map((preset) => ({
|
|
110
|
-
...preset,
|
|
111
|
-
actualModelId: resolvePresetTarget(preset.envKey, preset.fallbackModelId),
|
|
112
|
-
}));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function toVisibleOption(model, preset) {
|
|
116
|
-
return {
|
|
117
|
-
provider: DUCKMIND_PROVIDER,
|
|
118
|
-
id: preset.alias,
|
|
119
|
-
name: preset.displayName,
|
|
120
|
-
actualProvider: OPENROUTER_PROVIDER,
|
|
121
|
-
actualModelId: preset.actualModelId,
|
|
122
|
-
...(finiteContextWindow(model) ? { contextWindow: finiteContextWindow(model) } : {}),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function getPhoneVisibleModels(models) {
|
|
127
|
-
const visible = [];
|
|
128
|
-
|
|
129
|
-
for (const preset of getResolvedPresets()) {
|
|
130
|
-
const model = pickProviderModel(
|
|
131
|
-
models,
|
|
132
|
-
OPENROUTER_PROVIDER,
|
|
133
|
-
preset.actualModelId,
|
|
134
|
-
preset.fallbackModelId,
|
|
135
|
-
preset.displayName,
|
|
136
|
-
);
|
|
137
|
-
if (!model) {
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
visible.push(toVisibleOption(model, preset));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (hasUltradexAccounts()) {
|
|
145
|
-
const ultraModel = pickProviderModel(
|
|
146
|
-
models,
|
|
147
|
-
OPENAI_CODEX_PROVIDER,
|
|
148
|
-
DUCKMIND_ULTRA.actualModelId,
|
|
149
|
-
DUCKMIND_ULTRA.actualModelId,
|
|
150
|
-
DUCKMIND_ULTRA.displayName,
|
|
151
|
-
);
|
|
152
|
-
if (ultraModel) {
|
|
153
|
-
visible.push({
|
|
154
|
-
provider: DUCKMIND_PROVIDER,
|
|
155
|
-
id: DUCKMIND_ULTRA.alias,
|
|
156
|
-
name: DUCKMIND_ULTRA.displayName,
|
|
157
|
-
actualProvider: OPENAI_CODEX_PROVIDER,
|
|
158
|
-
actualModelId: DUCKMIND_ULTRA.actualModelId,
|
|
159
|
-
...(finiteContextWindow(ultraModel) ? { contextWindow: finiteContextWindow(ultraModel) } : {}),
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const providerModels = Array.isArray(models)
|
|
165
|
-
? models
|
|
166
|
-
.filter((model) => normalize(model?.provider) !== DUCKMIND_PROVIDER)
|
|
167
|
-
.sort((a, b) => {
|
|
168
|
-
const providerCmp = String(a?.provider ?? "").localeCompare(String(b?.provider ?? ""));
|
|
169
|
-
if (providerCmp !== 0) return providerCmp;
|
|
170
|
-
return String(a?.id ?? "").localeCompare(String(b?.id ?? ""));
|
|
171
|
-
})
|
|
172
|
-
: [];
|
|
173
|
-
for (const model of providerModels) {
|
|
174
|
-
const displayProvider = normalize(model?.provider) === OPENROUTER_PROVIDER ? DUCKMIND_PROVIDER : model.provider;
|
|
175
|
-
visible.push({
|
|
176
|
-
provider: displayProvider,
|
|
177
|
-
id: model.id,
|
|
178
|
-
name: model.name || model.id,
|
|
179
|
-
actualProvider: model.provider,
|
|
180
|
-
actualModelId: model.id,
|
|
181
|
-
...(finiteContextWindow(model) ? { contextWindow: finiteContextWindow(model) } : {}),
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return visible;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function normalizePhoneSessionModel(model) {
|
|
189
|
-
if (!model || typeof model !== "object") {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const provider = normalize(model.provider);
|
|
194
|
-
const modelId = normalize(model.id);
|
|
195
|
-
for (const preset of getResolvedPresets(DUCKMIND_PRESETS)) {
|
|
196
|
-
const matchesAlias = provider === normalize(DUCKMIND_PROVIDER) && modelId === normalize(preset.alias);
|
|
197
|
-
const matchesActual = provider === normalize(OPENROUTER_PROVIDER) && modelId === normalize(preset.actualModelId);
|
|
198
|
-
if (!matchesAlias && !matchesActual) {
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
return {
|
|
202
|
-
...model,
|
|
203
|
-
...toVisibleOption(model, preset),
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const matchesUltraAlias = provider === normalize(DUCKMIND_PROVIDER) && modelId === normalize(DUCKMIND_ULTRA.alias);
|
|
208
|
-
const matchesUltraActual = provider === normalize(OPENAI_CODEX_PROVIDER) && modelId === normalize(DUCKMIND_ULTRA.actualModelId);
|
|
209
|
-
if (hasUltradexAccounts() && (matchesUltraAlias || matchesUltraActual)) {
|
|
210
|
-
return {
|
|
211
|
-
...model,
|
|
212
|
-
provider: DUCKMIND_PROVIDER,
|
|
213
|
-
id: DUCKMIND_ULTRA.alias,
|
|
214
|
-
name: DUCKMIND_ULTRA.displayName,
|
|
215
|
-
actualProvider: OPENAI_CODEX_PROVIDER,
|
|
216
|
-
actualModelId: DUCKMIND_ULTRA.actualModelId,
|
|
217
|
-
...(finiteContextWindow(model) ? { contextWindow: finiteContextWindow(model) } : {}),
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
...model,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export function resolvePhoneVisibleModel(models, provider, modelId) {
|
|
227
|
-
const requestedProvider = normalize(provider);
|
|
228
|
-
const requestedId = normalize(modelId);
|
|
229
|
-
if (!requestedId) {
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
for (const option of getPhoneVisibleModels(models)) {
|
|
234
|
-
const optionProvider = normalize(option.provider);
|
|
235
|
-
const optionId = normalize(option.id);
|
|
236
|
-
const actualProvider = normalize(option.actualProvider);
|
|
237
|
-
const actualModelId = normalize(option.actualModelId);
|
|
238
|
-
|
|
239
|
-
const matchesAlias = requestedId === optionId && (!requestedProvider || requestedProvider === optionProvider);
|
|
240
|
-
const matchesActual = requestedProvider === actualProvider && requestedId === actualModelId;
|
|
241
|
-
const matchesProviderlessActual = !requestedProvider && requestedId === actualModelId;
|
|
242
|
-
const matchesCompat = requestedProvider === optionProvider && requestedId === actualModelId;
|
|
243
|
-
|
|
244
|
-
if (!matchesAlias && !matchesActual && !matchesProviderlessActual && !matchesCompat) {
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const preset = DUCKMIND_PRESETS.find((entry) => entry.alias === option.id);
|
|
249
|
-
const model = pickProviderModel(
|
|
250
|
-
models,
|
|
251
|
-
option.actualProvider,
|
|
252
|
-
option.actualModelId,
|
|
253
|
-
preset?.fallbackModelId || option.actualModelId,
|
|
254
|
-
option.name || option.id,
|
|
255
|
-
);
|
|
256
|
-
if (!model) {
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return { option, model };
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
|
-
import type { ParsedPhoneArgs, PhoneConfig } from "./types";
|
|
3
|
-
|
|
4
|
-
export function parsePhoneStartArgs(args: string | undefined, current: PhoneConfig): ParsedPhoneArgs {
|
|
5
|
-
const next = { ...current };
|
|
6
|
-
let tokenSpecified = false;
|
|
7
|
-
let idleSpecified = false;
|
|
8
|
-
|
|
9
|
-
if (!args?.trim()) {
|
|
10
|
-
return { config: next, tokenSpecified, idleSpecified };
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const tokens = args.trim().split(/\s+/);
|
|
14
|
-
let index = 0;
|
|
15
|
-
|
|
16
|
-
while (index < tokens.length) {
|
|
17
|
-
const token = tokens[index];
|
|
18
|
-
|
|
19
|
-
if (token === "--port" && tokens[index + 1]) {
|
|
20
|
-
const port = Number(tokens[index + 1]);
|
|
21
|
-
if (Number.isFinite(port) && port > 0) next.port = port;
|
|
22
|
-
index += 2;
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (token.startsWith("--port=")) {
|
|
27
|
-
const port = Number(token.slice(7));
|
|
28
|
-
if (Number.isFinite(port) && port > 0) next.port = port;
|
|
29
|
-
index += 1;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (token === "--host" && tokens[index + 1]) {
|
|
34
|
-
next.host = tokens[index + 1];
|
|
35
|
-
index += 2;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (token.startsWith("--host=")) {
|
|
40
|
-
next.host = token.slice(7);
|
|
41
|
-
index += 1;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (token === "--token" && tokens[index + 1] !== undefined) {
|
|
46
|
-
tokenSpecified = true;
|
|
47
|
-
next.token = tokens[index + 1] === "-" ? "" : tokens[index + 1];
|
|
48
|
-
index += 2;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (token.startsWith("--token=")) {
|
|
53
|
-
tokenSpecified = true;
|
|
54
|
-
const value = token.slice(8);
|
|
55
|
-
next.token = value === "-" ? "" : value;
|
|
56
|
-
index += 1;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (token === "--cwd" && tokens[index + 1]) {
|
|
61
|
-
next.cwd = resolve(tokens[index + 1]);
|
|
62
|
-
index += 2;
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (token.startsWith("--cwd=")) {
|
|
67
|
-
next.cwd = resolve(token.slice(6));
|
|
68
|
-
index += 1;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (token === "--idle-mins" && tokens[index + 1] !== undefined) {
|
|
73
|
-
idleSpecified = true;
|
|
74
|
-
const minutes = Number(tokens[index + 1]);
|
|
75
|
-
if (Number.isFinite(minutes) && minutes >= 0) next.idleTimeoutMs = Math.round(minutes * 60_000);
|
|
76
|
-
index += 2;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (token.startsWith("--idle-mins=")) {
|
|
81
|
-
idleSpecified = true;
|
|
82
|
-
const minutes = Number(token.slice(12));
|
|
83
|
-
if (Number.isFinite(minutes) && minutes >= 0) next.idleTimeoutMs = Math.round(minutes * 60_000);
|
|
84
|
-
index += 1;
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (token === "--idle-secs" && tokens[index + 1] !== undefined) {
|
|
89
|
-
idleSpecified = true;
|
|
90
|
-
const seconds = Number(tokens[index + 1]);
|
|
91
|
-
if (Number.isFinite(seconds) && seconds >= 0) next.idleTimeoutMs = Math.round(seconds * 1_000);
|
|
92
|
-
index += 2;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (token.startsWith("--idle-secs=")) {
|
|
97
|
-
idleSpecified = true;
|
|
98
|
-
const seconds = Number(token.slice(12));
|
|
99
|
-
if (Number.isFinite(seconds) && seconds >= 0) next.idleTimeoutMs = Math.round(seconds * 1_000);
|
|
100
|
-
index += 1;
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (/^\d+$/.test(token)) {
|
|
105
|
-
next.port = Number(token);
|
|
106
|
-
index += 1;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!token.startsWith("--") && next.token === current.token) {
|
|
111
|
-
tokenSpecified = true;
|
|
112
|
-
next.token = token === "-" ? "" : token;
|
|
113
|
-
index += 1;
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
index += 1;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return { config: next, tokenSpecified, idleSpecified };
|
|
121
|
-
}
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
-
import type { PhonePathSuggestion, PhonePathSuggestionMode } from "./types";
|
|
6
|
-
|
|
7
|
-
const agentDirFromEnv = process.env.DM_CODING_AGENT_DIR?.trim() || process.env.PI_CODING_AGENT_DIR?.trim();
|
|
8
|
-
const agentDir = agentDirFromEnv
|
|
9
|
-
? agentDirFromEnv
|
|
10
|
-
: join(process.env.HOME || process.env.USERPROFILE || process.cwd(), ".dm", "agent");
|
|
11
|
-
|
|
12
|
-
const PATH_SUGGESTION_LIMIT = 20;
|
|
13
|
-
const PATH_SUGGESTION_MAX_RESULTS = 100;
|
|
14
|
-
|
|
15
|
-
function resolveFdBinaryPath(): string | null {
|
|
16
|
-
const bundledFd = join(agentDir, "bin", "fd");
|
|
17
|
-
if (existsSync(bundledFd)) return bundledFd;
|
|
18
|
-
|
|
19
|
-
for (const candidate of ["fd", "fdfind"]) {
|
|
20
|
-
const result = spawnSync(candidate, ["--version"], { stdio: "ignore" });
|
|
21
|
-
if (!result.error && result.status === 0) return candidate;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const fdBinaryPath = resolveFdBinaryPath();
|
|
28
|
-
|
|
29
|
-
function stripWrappingQuotes(value: string): string {
|
|
30
|
-
const trimmed = value.trim();
|
|
31
|
-
if (
|
|
32
|
-
(trimmed.startsWith('"') && trimmed.endsWith('"'))
|
|
33
|
-
|| (trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
34
|
-
) {
|
|
35
|
-
return trimmed.slice(1, -1);
|
|
36
|
-
}
|
|
37
|
-
return trimmed;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function expandHomePath(value: string): string {
|
|
41
|
-
if (value === "~") return homedir();
|
|
42
|
-
if (value.startsWith("~/")) return join(homedir(), value.slice(2));
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function resolvePhoneCdTargetPath(rawArgs: string | undefined, currentCwd: string, previousCwd?: string | null): string {
|
|
47
|
-
const input = stripWrappingQuotes(rawArgs ?? "").trim();
|
|
48
|
-
|
|
49
|
-
if (!input) return homedir();
|
|
50
|
-
if (input === "-") {
|
|
51
|
-
if (!previousCwd) {
|
|
52
|
-
throw new Error("No previous directory available yet.");
|
|
53
|
-
}
|
|
54
|
-
return previousCwd;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const expanded = expandHomePath(input);
|
|
58
|
-
return resolve(currentCwd, expanded);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function createCdPathSuggestions(prefix: string, currentCwd: string, previousCwd?: string | null): PhonePathSuggestion[] {
|
|
62
|
-
const raw = prefix ?? "";
|
|
63
|
-
const trimmed = raw.trimStart();
|
|
64
|
-
const suggestions: PhonePathSuggestion[] = [];
|
|
65
|
-
|
|
66
|
-
if ("-".startsWith(trimmed)) {
|
|
67
|
-
suggestions.push({
|
|
68
|
-
value: "-",
|
|
69
|
-
label: "-",
|
|
70
|
-
description: previousCwd || "Previous directory",
|
|
71
|
-
isDirectory: true,
|
|
72
|
-
kind: "previous",
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const expanded = expandHomePath(trimmed);
|
|
77
|
-
const endsWithSeparator = /[\\/]$/.test(expanded);
|
|
78
|
-
const resolvedInput = expanded ? resolve(currentCwd, expanded) : currentCwd;
|
|
79
|
-
|
|
80
|
-
const baseDir = expanded
|
|
81
|
-
? endsWithSeparator
|
|
82
|
-
? resolvedInput
|
|
83
|
-
: dirname(resolvedInput)
|
|
84
|
-
: currentCwd;
|
|
85
|
-
|
|
86
|
-
const partial = expanded && !endsWithSeparator ? basename(expanded) : "";
|
|
87
|
-
const valuePrefix = trimmed
|
|
88
|
-
? endsWithSeparator
|
|
89
|
-
? trimmed
|
|
90
|
-
: trimmed.slice(0, Math.max(0, trimmed.length - partial.length))
|
|
91
|
-
: "";
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
if (!existsSync(baseDir) || !statSync(baseDir).isDirectory()) {
|
|
95
|
-
return suggestions.slice(0, PATH_SUGGESTION_LIMIT);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const directories = readdirSync(baseDir, { withFileTypes: true })
|
|
99
|
-
.filter((entry) => {
|
|
100
|
-
if (entry.isDirectory()) return true;
|
|
101
|
-
if (!entry.isSymbolicLink()) return false;
|
|
102
|
-
try {
|
|
103
|
-
return statSync(join(baseDir, entry.name)).isDirectory();
|
|
104
|
-
} catch {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
.filter((entry) => !partial || entry.name.startsWith(partial))
|
|
109
|
-
.sort((left, right) => left.name.localeCompare(right.name));
|
|
110
|
-
|
|
111
|
-
for (const entry of directories.slice(0, PATH_SUGGESTION_LIMIT)) {
|
|
112
|
-
suggestions.push({
|
|
113
|
-
value: `${valuePrefix}${entry.name}/`,
|
|
114
|
-
label: `${entry.name}/`,
|
|
115
|
-
description: join(baseDir, entry.name),
|
|
116
|
-
isDirectory: true,
|
|
117
|
-
kind: "path",
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
} catch {
|
|
121
|
-
return suggestions.slice(0, PATH_SUGGESTION_LIMIT);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return suggestions.slice(0, PATH_SUGGESTION_LIMIT);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function resolveScopedMentionQuery(rawQuery: string, currentCwd: string): { baseDir: string; query: string; displayBase: string } | null {
|
|
128
|
-
const slashIndex = rawQuery.lastIndexOf("/");
|
|
129
|
-
if (slashIndex === -1) return null;
|
|
130
|
-
|
|
131
|
-
const displayBase = rawQuery.slice(0, slashIndex + 1);
|
|
132
|
-
const query = rawQuery.slice(slashIndex + 1);
|
|
133
|
-
const baseDir = displayBase.startsWith("~/")
|
|
134
|
-
? expandHomePath(displayBase)
|
|
135
|
-
: displayBase.startsWith("/")
|
|
136
|
-
? displayBase
|
|
137
|
-
: join(currentCwd, displayBase);
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
if (!statSync(baseDir).isDirectory()) return null;
|
|
141
|
-
} catch {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return { baseDir, query, displayBase };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function scopedPathForDisplay(displayBase: string, relativePath: string): string {
|
|
149
|
-
if (displayBase === "/") return `/${relativePath}`;
|
|
150
|
-
return `${displayBase}${relativePath}`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function scorePhonePathEntry(filePath: string, query: string, isDirectory: boolean): number {
|
|
154
|
-
if (!query) return isDirectory ? 2 : 1;
|
|
155
|
-
|
|
156
|
-
const fileName = basename(filePath).toLowerCase();
|
|
157
|
-
const normalizedQuery = query.toLowerCase();
|
|
158
|
-
let score = 0;
|
|
159
|
-
|
|
160
|
-
if (fileName === normalizedQuery) score = 100;
|
|
161
|
-
else if (fileName.startsWith(normalizedQuery)) score = 80;
|
|
162
|
-
else if (fileName.includes(normalizedQuery)) score = 50;
|
|
163
|
-
else if (filePath.toLowerCase().includes(normalizedQuery)) score = 30;
|
|
164
|
-
|
|
165
|
-
if (isDirectory && score > 0) score += 10;
|
|
166
|
-
return score;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function walkDirectoryWithFd(baseDir: string, query: string, maxResults = PATH_SUGGESTION_MAX_RESULTS) {
|
|
170
|
-
if (!fdBinaryPath) return [] as Array<{ path: string; isDirectory: boolean }>;
|
|
171
|
-
|
|
172
|
-
const args = [
|
|
173
|
-
"--base-directory",
|
|
174
|
-
baseDir,
|
|
175
|
-
"--max-results",
|
|
176
|
-
String(maxResults),
|
|
177
|
-
"--type",
|
|
178
|
-
"f",
|
|
179
|
-
"--type",
|
|
180
|
-
"d",
|
|
181
|
-
"--full-path",
|
|
182
|
-
"--hidden",
|
|
183
|
-
"--exclude",
|
|
184
|
-
".git",
|
|
185
|
-
"--exclude",
|
|
186
|
-
".git/*",
|
|
187
|
-
"--exclude",
|
|
188
|
-
".git/**",
|
|
189
|
-
];
|
|
190
|
-
|
|
191
|
-
if (query) args.push(query);
|
|
192
|
-
|
|
193
|
-
const result = spawnSync(fdBinaryPath, args, {
|
|
194
|
-
encoding: "utf8",
|
|
195
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
196
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
if (result.status !== 0 || !result.stdout) return [];
|
|
200
|
-
|
|
201
|
-
return result.stdout
|
|
202
|
-
.trim()
|
|
203
|
-
.split("\n")
|
|
204
|
-
.filter(Boolean)
|
|
205
|
-
.map((line) => ({
|
|
206
|
-
path: line.endsWith("/") ? line.slice(0, -1) : line,
|
|
207
|
-
isDirectory: line.endsWith("/"),
|
|
208
|
-
}))
|
|
209
|
-
.filter((entry) => entry.path !== ".git" && !entry.path.startsWith(".git/") && !entry.path.includes("/.git/"));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function createMentionPathSuggestions(query: string, currentCwd: string): PhonePathSuggestion[] {
|
|
213
|
-
const scopedQuery = resolveScopedMentionQuery(query, currentCwd);
|
|
214
|
-
const fdBaseDir = scopedQuery?.baseDir ?? currentCwd;
|
|
215
|
-
const fdQuery = scopedQuery?.query ?? query;
|
|
216
|
-
const entries = walkDirectoryWithFd(fdBaseDir, fdQuery, PATH_SUGGESTION_MAX_RESULTS);
|
|
217
|
-
|
|
218
|
-
return entries
|
|
219
|
-
.map((entry) => ({
|
|
220
|
-
...entry,
|
|
221
|
-
score: scorePhonePathEntry(entry.path, fdQuery, entry.isDirectory),
|
|
222
|
-
}))
|
|
223
|
-
.filter((entry) => entry.score > 0)
|
|
224
|
-
.sort((left, right) => right.score - left.score)
|
|
225
|
-
.slice(0, PATH_SUGGESTION_LIMIT)
|
|
226
|
-
.map((entry) => {
|
|
227
|
-
const displayPath = scopedQuery
|
|
228
|
-
? scopedPathForDisplay(scopedQuery.displayBase, entry.path)
|
|
229
|
-
: entry.path;
|
|
230
|
-
const completionPath = entry.isDirectory ? `${displayPath}/` : displayPath;
|
|
231
|
-
return {
|
|
232
|
-
value: completionPath,
|
|
233
|
-
label: `${basename(entry.path)}${entry.isDirectory ? "/" : ""}`,
|
|
234
|
-
description: displayPath,
|
|
235
|
-
isDirectory: entry.isDirectory,
|
|
236
|
-
kind: "path" as const,
|
|
237
|
-
};
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export function listPhonePathSuggestions(
|
|
242
|
-
mode: PhonePathSuggestionMode,
|
|
243
|
-
query: string,
|
|
244
|
-
currentCwd: string,
|
|
245
|
-
previousCwd?: string | null,
|
|
246
|
-
): PhonePathSuggestion[] {
|
|
247
|
-
return mode === "cd"
|
|
248
|
-
? createCdPathSuggestions(query, currentCwd, previousCwd)
|
|
249
|
-
: createMentionPathSuggestions(query, currentCwd);
|
|
250
|
-
}
|