@duckmind/dm-darwin-x64 0.32.9 → 0.33.1

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 (95) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +14 -65
  3. package/extensions/dm-9router-ext/README.md +142 -0
  4. package/extensions/dm-9router-ext/package.json +45 -0
  5. package/extensions/dm-9router-ext/src/index.ts +541 -0
  6. package/extensions/dm-9router-ext/tsconfig.json +17 -0
  7. package/extensions/dm-subagents/package-lock.json +3 -3
  8. package/extensions/dm-tasks/node_modules/.package-lock.json +3 -3
  9. package/extensions/dm-tasks/node_modules/typebox/build/type/script/mapping.d.mts +2 -2
  10. package/extensions/dm-tasks/node_modules/typebox/build/type/script/parser.d.mts +2 -2
  11. package/extensions/dm-tasks/node_modules/typebox/build/type/script/parser.mjs +2 -2
  12. package/extensions/dm-tasks/node_modules/typebox/build/type/types/number.d.mts +1 -1
  13. package/extensions/dm-tasks/node_modules/typebox/build/type/types/number.mjs +1 -1
  14. package/extensions/dm-tasks/node_modules/typebox/build/type/types/record.d.mts +1 -1
  15. package/extensions/dm-tasks/node_modules/typebox/package.json +1 -1
  16. package/extensions/dm-tasks/package-lock.json +3 -3
  17. package/package.json +1 -1
  18. package/theme/theme-alps.json +93 -0
  19. package/extensions/dm-chime/README.md +0 -11
  20. package/extensions/dm-chime/docs/protocols.md +0 -107
  21. package/extensions/dm-chime/index.ts +0 -205
  22. package/extensions/dm-chime/package.json +0 -33
  23. package/extensions/dm-phone/README.md +0 -24
  24. package/extensions/dm-phone/index.ts +0 -12
  25. package/extensions/dm-phone/node_modules/.package-lock.json +0 -29
  26. package/extensions/dm-phone/node_modules/ws/LICENSE +0 -20
  27. package/extensions/dm-phone/node_modules/ws/README.md +0 -548
  28. package/extensions/dm-phone/node_modules/ws/browser.js +0 -8
  29. package/extensions/dm-phone/node_modules/ws/index.js +0 -22
  30. package/extensions/dm-phone/node_modules/ws/lib/buffer-util.js +0 -131
  31. package/extensions/dm-phone/node_modules/ws/lib/constants.js +0 -19
  32. package/extensions/dm-phone/node_modules/ws/lib/event-target.js +0 -292
  33. package/extensions/dm-phone/node_modules/ws/lib/extension.js +0 -203
  34. package/extensions/dm-phone/node_modules/ws/lib/limiter.js +0 -55
  35. package/extensions/dm-phone/node_modules/ws/lib/permessage-deflate.js +0 -528
  36. package/extensions/dm-phone/node_modules/ws/lib/receiver.js +0 -760
  37. package/extensions/dm-phone/node_modules/ws/lib/sender.js +0 -607
  38. package/extensions/dm-phone/node_modules/ws/lib/stream.js +0 -161
  39. package/extensions/dm-phone/node_modules/ws/lib/subprotocol.js +0 -62
  40. package/extensions/dm-phone/node_modules/ws/lib/validation.js +0 -152
  41. package/extensions/dm-phone/node_modules/ws/lib/websocket-server.js +0 -562
  42. package/extensions/dm-phone/node_modules/ws/lib/websocket.js +0 -1407
  43. package/extensions/dm-phone/node_modules/ws/package.json +0 -70
  44. package/extensions/dm-phone/node_modules/ws/wrapper.mjs +0 -21
  45. package/extensions/dm-phone/package-lock.json +0 -66
  46. package/extensions/dm-phone/package.json +0 -35
  47. package/extensions/dm-phone/phone-session-pool.ts +0 -8
  48. package/extensions/dm-phone/public/app/attachments.js +0 -233
  49. package/extensions/dm-phone/public/app/autocomplete-controller.js +0 -81
  50. package/extensions/dm-phone/public/app/autocomplete.js +0 -135
  51. package/extensions/dm-phone/public/app/bindings.js +0 -178
  52. package/extensions/dm-phone/public/app/command-catalog.js +0 -76
  53. package/extensions/dm-phone/public/app/commands.js +0 -376
  54. package/extensions/dm-phone/public/app/constants.js +0 -60
  55. package/extensions/dm-phone/public/app/formatters.js +0 -131
  56. package/extensions/dm-phone/public/app/handlers.js +0 -442
  57. package/extensions/dm-phone/public/app/main.js +0 -6
  58. package/extensions/dm-phone/public/app/markdown.js +0 -105
  59. package/extensions/dm-phone/public/app/messages.js +0 -418
  60. package/extensions/dm-phone/public/app/sheet-actions.js +0 -113
  61. package/extensions/dm-phone/public/app/sheet-navigation.js +0 -19
  62. package/extensions/dm-phone/public/app/sheets-view.js +0 -287
  63. package/extensions/dm-phone/public/app/state.js +0 -95
  64. package/extensions/dm-phone/public/app/tool-rendering.js +0 -562
  65. package/extensions/dm-phone/public/app/transport.js +0 -176
  66. package/extensions/dm-phone/public/app/ui.js +0 -417
  67. package/extensions/dm-phone/public/app.js +0 -1
  68. package/extensions/dm-phone/public/icon.svg +0 -15
  69. package/extensions/dm-phone/public/index.html +0 -146
  70. package/extensions/dm-phone/public/manifest.webmanifest +0 -17
  71. package/extensions/dm-phone/public/styles.css +0 -1139
  72. package/extensions/dm-phone/public/sw.js +0 -78
  73. package/extensions/dm-phone/src/extension/duckmind-models.js +0 -264
  74. package/extensions/dm-phone/src/extension/phone-args.ts +0 -121
  75. package/extensions/dm-phone/src/extension/phone-paths.ts +0 -250
  76. package/extensions/dm-phone/src/extension/phone-quota.ts +0 -188
  77. package/extensions/dm-phone/src/extension/phone-runtime.ts +0 -154
  78. package/extensions/dm-phone/src/extension/phone-server-runtime.ts +0 -1217
  79. package/extensions/dm-phone/src/extension/phone-sessions.ts +0 -139
  80. package/extensions/dm-phone/src/extension/phone-static.ts +0 -30
  81. package/extensions/dm-phone/src/extension/phone-tailscale.ts +0 -148
  82. package/extensions/dm-phone/src/extension/phone-theme.ts +0 -85
  83. package/extensions/dm-phone/src/extension/register-phone-child-extension.ts +0 -112
  84. package/extensions/dm-phone/src/extension/register-phone-extension.ts +0 -106
  85. package/extensions/dm-phone/src/extension/types.ts +0 -73
  86. package/extensions/dm-phone/src/session-pool/parent-session-worker.ts +0 -882
  87. package/extensions/dm-phone/src/session-pool/session-pool.ts +0 -470
  88. package/extensions/dm-phone/src/session-pool/session-worker.ts +0 -739
  89. package/extensions/dm-phone/src/session-pool/types.ts +0 -111
  90. package/extensions/dm-phone/src/session-pool/utils.ts +0 -23
  91. package/extensions/dm-phone/test/duckmind-models.test.js +0 -147
  92. package/extensions/dm-thinking-timer/LICENSE +0 -21
  93. package/extensions/dm-thinking-timer/README.md +0 -7
  94. package/extensions/dm-thinking-timer/package.json +0 -20
  95. package/extensions/dm-thinking-timer/thinking-timer.ts +0 -250
@@ -1,70 +0,0 @@
1
- {
2
- "name": "ws",
3
- "version": "8.21.0",
4
- "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
5
- "keywords": [
6
- "HyBi",
7
- "Push",
8
- "RFC-6455",
9
- "WebSocket",
10
- "WebSockets",
11
- "real-time"
12
- ],
13
- "homepage": "https://github.com/websockets/ws",
14
- "bugs": "https://github.com/websockets/ws/issues",
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://github.com/websockets/ws.git"
18
- },
19
- "author": "Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)",
20
- "license": "MIT",
21
- "main": "index.js",
22
- "exports": {
23
- ".": {
24
- "browser": "./browser.js",
25
- "import": "./wrapper.mjs",
26
- "require": "./index.js"
27
- },
28
- "./package.json": "./package.json"
29
- },
30
- "browser": "browser.js",
31
- "engines": {
32
- "node": ">=10.0.0"
33
- },
34
- "files": [
35
- "browser.js",
36
- "index.js",
37
- "lib/*.js",
38
- "wrapper.mjs"
39
- ],
40
- "scripts": {
41
- "test": "nyc --reporter=lcov --reporter=text mocha --throw-deprecation test/*.test.js",
42
- "integration": "mocha --throw-deprecation test/*.integration.js",
43
- "lint": "eslint . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\""
44
- },
45
- "peerDependencies": {
46
- "bufferutil": "^4.0.1",
47
- "utf-8-validate": ">=5.0.2"
48
- },
49
- "peerDependenciesMeta": {
50
- "bufferutil": {
51
- "optional": true
52
- },
53
- "utf-8-validate": {
54
- "optional": true
55
- }
56
- },
57
- "devDependencies": {
58
- "@eslint/js": "^10.0.1",
59
- "benchmark": "^2.1.4",
60
- "bufferutil": "^4.0.1",
61
- "eslint": "^10.0.1",
62
- "eslint-config-prettier": "^10.0.1",
63
- "eslint-plugin-prettier": "^5.0.0",
64
- "globals": "^17.0.0",
65
- "mocha": "^8.4.0",
66
- "nyc": "^15.0.0",
67
- "prettier": "^3.0.0",
68
- "utf-8-validate": "^6.0.0"
69
- }
70
- }
@@ -1,21 +0,0 @@
1
- import createWebSocketStream from './lib/stream.js';
2
- import extension from './lib/extension.js';
3
- import PerMessageDeflate from './lib/permessage-deflate.js';
4
- import Receiver from './lib/receiver.js';
5
- import Sender from './lib/sender.js';
6
- import subprotocol from './lib/subprotocol.js';
7
- import WebSocket from './lib/websocket.js';
8
- import WebSocketServer from './lib/websocket-server.js';
9
-
10
- export {
11
- createWebSocketStream,
12
- extension,
13
- PerMessageDeflate,
14
- Receiver,
15
- Sender,
16
- subprotocol,
17
- WebSocket,
18
- WebSocketServer
19
- };
20
-
21
- export default WebSocket;
@@ -1,66 +0,0 @@
1
- {
2
- "name": "dm-phone",
3
- "version": "0.0.13",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "": {
8
- "name": "dm-phone",
9
- "version": "0.0.13",
10
- "dependencies": {
11
- "ws": "^8.18.3"
12
- },
13
- "devDependencies": {
14
- "@types/ws": "^8.18.1"
15
- }
16
- },
17
- "node_modules/@types/node": {
18
- "version": "25.9.1",
19
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
20
- "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
21
- "dev": true,
22
- "license": "MIT",
23
- "dependencies": {
24
- "undici-types": ">=7.24.0 <7.24.7"
25
- }
26
- },
27
- "node_modules/@types/ws": {
28
- "version": "8.18.1",
29
- "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
30
- "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
31
- "dev": true,
32
- "license": "MIT",
33
- "dependencies": {
34
- "@types/node": "*"
35
- }
36
- },
37
- "node_modules/undici-types": {
38
- "version": "7.24.6",
39
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
40
- "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
41
- "dev": true,
42
- "license": "MIT"
43
- },
44
- "node_modules/ws": {
45
- "version": "8.21.0",
46
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
47
- "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
48
- "license": "MIT",
49
- "engines": {
50
- "node": ">=10.0.0"
51
- },
52
- "peerDependencies": {
53
- "bufferutil": "^4.0.1",
54
- "utf-8-validate": ">=5.0.2"
55
- },
56
- "peerDependenciesMeta": {
57
- "bufferutil": {
58
- "optional": true
59
- },
60
- "utf-8-validate": {
61
- "optional": true
62
- }
63
- }
64
- }
65
- }
66
- }
@@ -1,35 +0,0 @@
1
- {
2
- "name": "dm-phone",
3
- "version": "0.0.13",
4
- "description": "Phone-first remote UI extension for DM with Tailscale-friendly access",
5
- "type": "module",
6
- "keywords": [
7
- "duckmind",
8
- "dm",
9
- "extension",
10
- "phone",
11
- "mobile",
12
- "tailscale"
13
- ],
14
- "publishConfig": {
15
- "access": "public"
16
- },
17
- "files": [
18
- "index.ts",
19
- "phone-session-pool.ts",
20
- "src",
21
- "public",
22
- "README.md"
23
- ],
24
- "pi": {
25
- "extensions": [
26
- "./index.ts"
27
- ]
28
- },
29
- "dependencies": {
30
- "ws": "^8.18.3"
31
- },
32
- "devDependencies": {
33
- "@types/ws": "^8.18.1"
34
- }
35
- }
@@ -1,8 +0,0 @@
1
- export { PhoneSessionPool } from "./src/session-pool/session-pool";
2
- export { PhoneSessionWorker } from "./src/session-pool/session-worker";
3
- export type {
4
- PhoneSessionPoolOptions,
5
- SessionSnapshot,
6
- SessionSummary,
7
- SessionWorkerOptions,
8
- } from "./src/session-pool/types";
@@ -1,233 +0,0 @@
1
- import { insertTextAtCursor } from "./autocomplete.js";
2
- import { escapeHtml, formatBytes } from "./formatters.js";
3
- import { el, state } from "./state.js";
4
- import { autoResizeTextarea, scheduleComposerLayoutSync } from "./ui.js";
5
-
6
- function currentPromptText() {
7
- return String(el.promptInput?.value || "");
8
- }
9
-
10
- function createAttachmentRecord(file) {
11
- const tokenOrder = state.nextAttachmentTokenId || 1;
12
- state.nextAttachmentTokenId = tokenOrder + 1;
13
-
14
- return {
15
- id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
16
- file,
17
- name: file.name,
18
- size: file.size,
19
- type: file.type,
20
- url: URL.createObjectURL(file),
21
- token: `⟦img${tokenOrder}⟧`,
22
- tokenOrder,
23
- };
24
- }
25
-
26
- function orderedAttachments(promptText = currentPromptText()) {
27
- return [...state.attachments].sort((left, right) => {
28
- const leftIndex = promptText.indexOf(left.token);
29
- const rightIndex = promptText.indexOf(right.token);
30
- const leftMissing = leftIndex === -1;
31
- const rightMissing = rightIndex === -1;
32
-
33
- if (leftMissing !== rightMissing) return leftMissing ? 1 : -1;
34
- if (!leftMissing && leftIndex !== rightIndex) return leftIndex - rightIndex;
35
- return (left.tokenOrder || 0) - (right.tokenOrder || 0);
36
- });
37
- }
38
-
39
- function disposeAttachment(attachment) {
40
- if (attachment?.url) {
41
- URL.revokeObjectURL(attachment.url);
42
- }
43
- }
44
-
45
- function disposeAttachments(attachments = []) {
46
- for (const attachment of attachments) {
47
- disposeAttachment(attachment);
48
- }
49
- }
50
-
51
- function buildTokenInsertion(tokens) {
52
- if (!tokens.length) return "";
53
-
54
- const value = currentPromptText();
55
- const start = el.promptInput.selectionStart ?? value.length;
56
- const end = el.promptInput.selectionEnd ?? start;
57
- const beforeChar = start > 0 ? value[start - 1] : "";
58
- const afterChar = end < value.length ? value[end] : "";
59
- const needsLeadingSpace = beforeChar && !/\s/.test(beforeChar);
60
- const needsTrailingSpace = !afterChar || !/\s/.test(afterChar);
61
-
62
- return `${needsLeadingSpace ? " " : ""}${tokens.join(" ")}${needsTrailingSpace ? " " : ""}`;
63
- }
64
-
65
- function stripTokenFromPrompt(token) {
66
- const value = currentPromptText();
67
- if (!token || !value.includes(token)) return false;
68
-
69
- const selectionStart = el.promptInput.selectionStart ?? value.length;
70
- const selectionEnd = el.promptInput.selectionEnd ?? selectionStart;
71
- let nextText = "";
72
- let nextSelectionStart = selectionStart;
73
- let nextSelectionEnd = selectionEnd;
74
- let offset = 0;
75
-
76
- while (offset < value.length) {
77
- const index = value.indexOf(token, offset);
78
- if (index === -1) break;
79
-
80
- nextText += value.slice(offset, index);
81
-
82
- if (index < nextSelectionStart) {
83
- nextSelectionStart -= Math.min(token.length, nextSelectionStart - index);
84
- }
85
- if (index < nextSelectionEnd) {
86
- nextSelectionEnd -= Math.min(token.length, nextSelectionEnd - index);
87
- }
88
-
89
- offset = index + token.length;
90
- }
91
-
92
- nextText += value.slice(offset);
93
- if (nextText === value) return false;
94
-
95
- el.promptInput.value = nextText;
96
- const clampedStart = Math.max(0, Math.min(nextText.length, nextSelectionStart));
97
- const clampedEnd = Math.max(clampedStart, Math.min(nextText.length, nextSelectionEnd));
98
- el.promptInput.focus();
99
- el.promptInput.setSelectionRange(clampedStart, clampedEnd);
100
- autoResizeTextarea();
101
- return true;
102
- }
103
-
104
- export function renderAttachmentStrip() {
105
- const attachments = orderedAttachments();
106
- if (!attachments.length) {
107
- el.attachmentStrip.classList.add("hidden");
108
- el.attachmentStrip.innerHTML = "";
109
- scheduleComposerLayoutSync();
110
- return;
111
- }
112
-
113
- el.attachmentStrip.innerHTML = attachments.map((attachment) => `
114
- <article class="attachment-chip">
115
- <img src="${attachment.url}" alt="${escapeHtml(attachment.name)}" />
116
- <div class="attachment-chip-header">
117
- <div class="attachment-chip-token mono">${escapeHtml(attachment.token)}</div>
118
- <button class="attachment-chip-remove" data-remove-attachment="${attachment.id}" aria-label="Remove image ${escapeHtml(attachment.token)}">✕</button>
119
- </div>
120
- <div class="attachment-chip-name">${escapeHtml(attachment.name)}</div>
121
- <div class="attachment-chip-meta">${escapeHtml(formatBytes(attachment.size))}</div>
122
- </article>
123
- `).join("");
124
- el.attachmentStrip.classList.remove("hidden");
125
- scheduleComposerLayoutSync();
126
- }
127
-
128
- export function addAttachments(files) {
129
- const incoming = Array.from(files || []).filter((file) => file.type.startsWith("image/"));
130
- if (!incoming.length) return;
131
-
132
- const added = incoming.map((file) => createAttachmentRecord(file));
133
- state.attachments.push(...added);
134
-
135
- const insertion = buildTokenInsertion(added.map((attachment) => attachment.token));
136
- if (insertion) {
137
- insertTextAtCursor(insertion);
138
- }
139
-
140
- syncAttachmentsWithPrompt();
141
- renderAttachmentStrip();
142
- }
143
-
144
- export function removeAttachment(id) {
145
- const index = state.attachments.findIndex((attachment) => attachment.id === id);
146
- if (index === -1) return;
147
-
148
- const [removed] = state.attachments.splice(index, 1);
149
- stripTokenFromPrompt(removed.token);
150
- disposeAttachment(removed);
151
- renderAttachmentStrip();
152
- }
153
-
154
- export function clearAttachments(options = {}) {
155
- if (options.removeTokensFromPrompt) {
156
- for (const attachment of state.attachments) {
157
- stripTokenFromPrompt(attachment.token);
158
- }
159
- }
160
-
161
- disposeAttachments(state.attachments);
162
- state.attachments = [];
163
- renderAttachmentStrip();
164
- }
165
-
166
- function fileToBase64(file) {
167
- return new Promise((resolve, reject) => {
168
- const reader = new FileReader();
169
- reader.onload = () => {
170
- const result = String(reader.result || "");
171
- const base64 = result.includes(",") ? result.split(",")[1] : result;
172
- resolve(base64);
173
- };
174
- reader.onerror = () => reject(reader.error || new Error(`Failed to read ${file.name}`));
175
- reader.readAsDataURL(file);
176
- });
177
- }
178
-
179
- export function syncAttachmentsWithPrompt() {
180
- if (!state.attachments.length) return [];
181
-
182
- const promptText = currentPromptText();
183
- const kept = [];
184
- const removed = [];
185
-
186
- for (const attachment of state.attachments) {
187
- if (promptText.includes(attachment.token)) {
188
- kept.push(attachment);
189
- } else {
190
- removed.push(attachment);
191
- }
192
- }
193
-
194
- if (removed.length) {
195
- disposeAttachments(removed);
196
- state.attachments = kept;
197
- }
198
-
199
- renderAttachmentStrip();
200
- return removed;
201
- }
202
-
203
- function attachmentOccurrences(promptText = currentPromptText()) {
204
- const matches = [];
205
-
206
- for (const attachment of state.attachments) {
207
- let offset = 0;
208
- while (offset <= promptText.length) {
209
- const index = promptText.indexOf(attachment.token, offset);
210
- if (index === -1) break;
211
- matches.push({ index, attachment });
212
- offset = index + attachment.token.length;
213
- }
214
- }
215
-
216
- matches.sort((left, right) => left.index - right.index || (left.attachment.tokenOrder || 0) - (right.attachment.tokenOrder || 0));
217
- return matches.map((match) => match.attachment);
218
- }
219
-
220
- export async function buildPromptPayload(promptText = currentPromptText()) {
221
- const rawPrompt = String(promptText || "");
222
- const attachments = attachmentOccurrences(rawPrompt);
223
-
224
- const images = await Promise.all(
225
- attachments.map(async (attachment) => ({
226
- type: "image",
227
- data: await fileToBase64(attachment.file),
228
- mimeType: attachment.type || "image/png",
229
- })),
230
- );
231
-
232
- return { message: rawPrompt, images };
233
- }
@@ -1,81 +0,0 @@
1
- import {
2
- clearAutocompleteSuggestions,
3
- detectCdAutocompleteContext,
4
- detectMentionAutocompleteContext,
5
- detectSlashCommandAutocompleteContext,
6
- renderAutocompleteItems,
7
- } from "./autocomplete.js";
8
- import { visibleCommandCatalog } from "./command-catalog.js";
9
- import { el, state } from "./state.js";
10
- import { sendLocalCommand } from "./transport.js";
11
-
12
- function activeAutocompleteContext() {
13
- const value = el.promptInput.value || "";
14
- const cursor = el.promptInput.selectionStart ?? value.length;
15
-
16
- const mention = detectMentionAutocompleteContext(value, cursor);
17
- if (mention) return mention;
18
-
19
- const cd = detectCdAutocompleteContext(value, cursor);
20
- if (cd) return cd;
21
-
22
- const slash = detectSlashCommandAutocompleteContext(value, cursor);
23
- if (slash) return slash;
24
-
25
- return null;
26
- }
27
-
28
- function requestPathSuggestions(context) {
29
- if (state.socket?.readyState !== WebSocket.OPEN) {
30
- renderAutocompleteItems([]);
31
- return;
32
- }
33
-
34
- const requestId = ++state.autocompleteRemoteRequestId;
35
- sendLocalCommand({
36
- type: "path-suggestions",
37
- mode: context.mode,
38
- query: context.query,
39
- requestId,
40
- });
41
- }
42
-
43
- function queuePathSuggestions(context) {
44
- if (state.autocompleteRemoteTimer) {
45
- clearTimeout(state.autocompleteRemoteTimer);
46
- }
47
-
48
- state.autocompleteRemoteTimer = setTimeout(() => {
49
- state.autocompleteRemoteTimer = null;
50
- requestPathSuggestions(context);
51
- }, 90);
52
- }
53
-
54
- export function renderCommandSuggestions() {
55
- const context = activeAutocompleteContext();
56
- state.autocompleteContext = context;
57
-
58
- if (!context) {
59
- clearAutocompleteSuggestions();
60
- return;
61
- }
62
-
63
- if (context.type === "slash-command") {
64
- const matches = visibleCommandCatalog()
65
- .filter((command) => command.name.toLowerCase().startsWith(context.query.toLowerCase()))
66
- .slice(0, 10)
67
- .map((command) => ({
68
- kind: command.source === "local" ? (command.insertOnly ? "local-command-insert" : "local-command-run") : "remote-command-insert",
69
- label: `/${command.name}`,
70
- badge: command.source || "command",
71
- description: command.description || "",
72
- name: command.name,
73
- }));
74
-
75
- renderAutocompleteItems(matches);
76
- return;
77
- }
78
-
79
- renderAutocompleteItems([]);
80
- queuePathSuggestions(context);
81
- }
@@ -1,135 +0,0 @@
1
- import { AUTOCOMPLETE_DELIMITERS } from "./constants.js";
2
- import { el, state } from "./state.js";
3
- import { escapeAttribute, escapeHtml } from "./formatters.js";
4
- import { autoResizeTextarea, scheduleComposerLayoutSync } from "./ui.js";
5
-
6
- export function clearAutocompleteSuggestions() {
7
- if (state.autocompleteRemoteTimer) {
8
- clearTimeout(state.autocompleteRemoteTimer);
9
- state.autocompleteRemoteTimer = null;
10
- }
11
- state.autocompleteContext = null;
12
- state.autocompleteItems = [];
13
- el.commandStrip.classList.add("hidden");
14
- el.commandStrip.innerHTML = "";
15
- scheduleComposerLayoutSync();
16
- }
17
-
18
- export function renderAutocompleteItems(items = []) {
19
- state.autocompleteItems = items;
20
-
21
- if (!items.length) {
22
- el.commandStrip.classList.add("hidden");
23
- el.commandStrip.innerHTML = "";
24
- scheduleComposerLayoutSync();
25
- return;
26
- }
27
-
28
- el.commandStrip.innerHTML = items.map((item, index) => `
29
- <button class="command-chip secondary" data-autocomplete-index="${index}" title="${escapeAttribute(item.title || item.description || item.label || "")}">
30
- <span>${escapeHtml(item.label || "")}</span>
31
- <span class="source">${escapeHtml(item.badge || "")}</span>
32
- </button>
33
- `).join("");
34
- el.commandStrip.classList.remove("hidden");
35
- scheduleComposerLayoutSync();
36
- }
37
-
38
- function delimiterBeforeIndex(text, index) {
39
- return index <= 0 || AUTOCOMPLETE_DELIMITERS.has(text[index - 1]);
40
- }
41
-
42
- function findTokenBounds(text, start, end) {
43
- let tokenStart = start;
44
- let tokenEnd = end;
45
-
46
- while (tokenStart > 0 && !AUTOCOMPLETE_DELIMITERS.has(text[tokenStart - 1])) {
47
- tokenStart -= 1;
48
- }
49
- while (tokenEnd < text.length && !AUTOCOMPLETE_DELIMITERS.has(text[tokenEnd])) {
50
- tokenEnd += 1;
51
- }
52
-
53
- return { start: tokenStart, end: tokenEnd };
54
- }
55
-
56
- export function detectMentionAutocompleteContext(text, cursor) {
57
- const scanLimit = Math.min(cursor, text.length);
58
- let tokenStart = scanLimit;
59
- while (tokenStart > 0 && !AUTOCOMPLETE_DELIMITERS.has(text[tokenStart - 1])) {
60
- tokenStart -= 1;
61
- }
62
-
63
- if (text[tokenStart] !== "@") return null;
64
- if (!delimiterBeforeIndex(text, tokenStart)) return null;
65
-
66
- const bounds = findTokenBounds(text, tokenStart, cursor);
67
- return {
68
- type: "path",
69
- mode: "mention",
70
- query: text.slice(tokenStart + 1, cursor),
71
- replaceStart: bounds.start,
72
- replaceEnd: bounds.end,
73
- };
74
- }
75
-
76
- export function detectCdAutocompleteContext(text, cursor) {
77
- const leadingWhitespace = text.match(/^\s*/)?.[0] || "";
78
- const trimmed = text.slice(leadingWhitespace.length);
79
- if (!trimmed.startsWith("/cd")) return null;
80
-
81
- const afterCommand = trimmed.slice(3);
82
- if (afterCommand && !/^\s/.test(afterCommand)) return null;
83
-
84
- const commandStart = leadingWhitespace.length;
85
- const argsStart = commandStart + 3 + (afterCommand.match(/^\s*/) || [""])[0].length;
86
- if (cursor < argsStart) return null;
87
-
88
- return {
89
- type: "path",
90
- mode: "cd",
91
- query: text.slice(argsStart, cursor),
92
- replaceStart: argsStart,
93
- replaceEnd: text.length,
94
- };
95
- }
96
-
97
- export function detectSlashCommandAutocompleteContext(text, cursor) {
98
- const leadingWhitespace = text.match(/^\s*/)?.[0] || "";
99
- const trimmedBeforeCursor = text.slice(leadingWhitespace.length, cursor);
100
- if (!trimmedBeforeCursor.startsWith("/")) return null;
101
- if (/\s/.test(trimmedBeforeCursor.slice(1))) return null;
102
-
103
- return {
104
- type: "slash-command",
105
- query: trimmedBeforeCursor.slice(1),
106
- };
107
- }
108
-
109
- export function replacePromptRange(start, end, nextText) {
110
- const value = el.promptInput.value;
111
- el.promptInput.value = `${value.slice(0, start)}${nextText}${value.slice(end)}`;
112
- const nextCursor = start + nextText.length;
113
- el.promptInput.focus();
114
- el.promptInput.setSelectionRange(nextCursor, nextCursor);
115
- autoResizeTextarea();
116
- }
117
-
118
- export function insertTextAtCursor(text) {
119
- const start = el.promptInput.selectionStart ?? el.promptInput.value.length;
120
- const end = el.promptInput.selectionEnd ?? start;
121
- replacePromptRange(start, end, text);
122
- }
123
-
124
- export function insertCdCommand() {
125
- const value = el.promptInput.value;
126
- if (!value.trim()) {
127
- el.promptInput.value = "/cd ";
128
- el.promptInput.focus();
129
- el.promptInput.setSelectionRange(4, 4);
130
- autoResizeTextarea();
131
- return;
132
- }
133
-
134
- insertTextAtCursor("/cd ");
135
- }