@duckmind/dm-darwin-x64 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.
Files changed (81) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +1 -76
  3. package/package.json +1 -1
  4. package/theme/theme-alps.json +84 -0
  5. package/extensions/dm-chime/README.md +0 -11
  6. package/extensions/dm-chime/docs/protocols.md +0 -107
  7. package/extensions/dm-chime/index.ts +0 -205
  8. package/extensions/dm-chime/package.json +0 -33
  9. package/extensions/dm-phone/README.md +0 -24
  10. package/extensions/dm-phone/index.ts +0 -12
  11. package/extensions/dm-phone/node_modules/.package-lock.json +0 -29
  12. package/extensions/dm-phone/node_modules/ws/LICENSE +0 -20
  13. package/extensions/dm-phone/node_modules/ws/README.md +0 -548
  14. package/extensions/dm-phone/node_modules/ws/browser.js +0 -8
  15. package/extensions/dm-phone/node_modules/ws/index.js +0 -22
  16. package/extensions/dm-phone/node_modules/ws/lib/buffer-util.js +0 -131
  17. package/extensions/dm-phone/node_modules/ws/lib/constants.js +0 -19
  18. package/extensions/dm-phone/node_modules/ws/lib/event-target.js +0 -292
  19. package/extensions/dm-phone/node_modules/ws/lib/extension.js +0 -203
  20. package/extensions/dm-phone/node_modules/ws/lib/limiter.js +0 -55
  21. package/extensions/dm-phone/node_modules/ws/lib/permessage-deflate.js +0 -528
  22. package/extensions/dm-phone/node_modules/ws/lib/receiver.js +0 -760
  23. package/extensions/dm-phone/node_modules/ws/lib/sender.js +0 -607
  24. package/extensions/dm-phone/node_modules/ws/lib/stream.js +0 -161
  25. package/extensions/dm-phone/node_modules/ws/lib/subprotocol.js +0 -62
  26. package/extensions/dm-phone/node_modules/ws/lib/validation.js +0 -152
  27. package/extensions/dm-phone/node_modules/ws/lib/websocket-server.js +0 -562
  28. package/extensions/dm-phone/node_modules/ws/lib/websocket.js +0 -1407
  29. package/extensions/dm-phone/node_modules/ws/package.json +0 -70
  30. package/extensions/dm-phone/node_modules/ws/wrapper.mjs +0 -21
  31. package/extensions/dm-phone/package-lock.json +0 -66
  32. package/extensions/dm-phone/package.json +0 -35
  33. package/extensions/dm-phone/phone-session-pool.ts +0 -8
  34. package/extensions/dm-phone/public/app/attachments.js +0 -233
  35. package/extensions/dm-phone/public/app/autocomplete-controller.js +0 -81
  36. package/extensions/dm-phone/public/app/autocomplete.js +0 -135
  37. package/extensions/dm-phone/public/app/bindings.js +0 -178
  38. package/extensions/dm-phone/public/app/command-catalog.js +0 -76
  39. package/extensions/dm-phone/public/app/commands.js +0 -376
  40. package/extensions/dm-phone/public/app/constants.js +0 -60
  41. package/extensions/dm-phone/public/app/formatters.js +0 -131
  42. package/extensions/dm-phone/public/app/handlers.js +0 -442
  43. package/extensions/dm-phone/public/app/main.js +0 -6
  44. package/extensions/dm-phone/public/app/markdown.js +0 -105
  45. package/extensions/dm-phone/public/app/messages.js +0 -418
  46. package/extensions/dm-phone/public/app/sheet-actions.js +0 -113
  47. package/extensions/dm-phone/public/app/sheet-navigation.js +0 -19
  48. package/extensions/dm-phone/public/app/sheets-view.js +0 -287
  49. package/extensions/dm-phone/public/app/state.js +0 -95
  50. package/extensions/dm-phone/public/app/tool-rendering.js +0 -562
  51. package/extensions/dm-phone/public/app/transport.js +0 -176
  52. package/extensions/dm-phone/public/app/ui.js +0 -417
  53. package/extensions/dm-phone/public/app.js +0 -1
  54. package/extensions/dm-phone/public/icon.svg +0 -15
  55. package/extensions/dm-phone/public/index.html +0 -146
  56. package/extensions/dm-phone/public/manifest.webmanifest +0 -17
  57. package/extensions/dm-phone/public/styles.css +0 -1139
  58. package/extensions/dm-phone/public/sw.js +0 -78
  59. package/extensions/dm-phone/src/extension/duckmind-models.js +0 -264
  60. package/extensions/dm-phone/src/extension/phone-args.ts +0 -121
  61. package/extensions/dm-phone/src/extension/phone-paths.ts +0 -250
  62. package/extensions/dm-phone/src/extension/phone-quota.ts +0 -188
  63. package/extensions/dm-phone/src/extension/phone-runtime.ts +0 -154
  64. package/extensions/dm-phone/src/extension/phone-server-runtime.ts +0 -1217
  65. package/extensions/dm-phone/src/extension/phone-sessions.ts +0 -139
  66. package/extensions/dm-phone/src/extension/phone-static.ts +0 -30
  67. package/extensions/dm-phone/src/extension/phone-tailscale.ts +0 -148
  68. package/extensions/dm-phone/src/extension/phone-theme.ts +0 -85
  69. package/extensions/dm-phone/src/extension/register-phone-child-extension.ts +0 -112
  70. package/extensions/dm-phone/src/extension/register-phone-extension.ts +0 -106
  71. package/extensions/dm-phone/src/extension/types.ts +0 -73
  72. package/extensions/dm-phone/src/session-pool/parent-session-worker.ts +0 -882
  73. package/extensions/dm-phone/src/session-pool/session-pool.ts +0 -470
  74. package/extensions/dm-phone/src/session-pool/session-worker.ts +0 -739
  75. package/extensions/dm-phone/src/session-pool/types.ts +0 -111
  76. package/extensions/dm-phone/src/session-pool/utils.ts +0 -23
  77. package/extensions/dm-phone/test/duckmind-models.test.js +0 -147
  78. package/extensions/dm-thinking-timer/LICENSE +0 -21
  79. package/extensions/dm-thinking-timer/README.md +0 -7
  80. package/extensions/dm-thinking-timer/package.json +0 -20
  81. 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
- }