gsd-pi 2.66.1-dev.0df32ec → 2.66.1-dev.3cea7ac

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 (230) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +79 -11
  2. package/dist/resources/extensions/claude-code-cli/partial-builder.js +4 -3
  3. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -3
  4. package/dist/resources/extensions/gsd/auto/loop.js +13 -1
  5. package/dist/resources/extensions/gsd/auto/phases.js +10 -4
  6. package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
  7. package/dist/resources/extensions/gsd/auto/session.js +1 -1
  8. package/dist/resources/extensions/gsd/auto-dashboard.js +65 -15
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +30 -28
  10. package/dist/resources/extensions/gsd/auto-prompts.js +6 -6
  11. package/dist/resources/extensions/gsd/auto-recovery.js +11 -12
  12. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +18 -6
  13. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +59 -5
  14. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +8 -5
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +186 -14
  16. package/dist/resources/extensions/gsd/codebase-generator.js +4 -0
  17. package/dist/resources/extensions/gsd/commands/handlers/core.js +3 -3
  18. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -4
  19. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -1
  20. package/dist/resources/extensions/gsd/detection.js +6 -0
  21. package/dist/resources/extensions/gsd/files.js +19 -2
  22. package/dist/resources/extensions/gsd/guided-flow.js +12 -8
  23. package/dist/resources/extensions/gsd/index.js +1 -1
  24. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +2 -0
  25. package/dist/resources/extensions/gsd/parsers-legacy.js +3 -1
  26. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
  28. package/dist/resources/extensions/gsd/prompts/discuss.md +3 -3
  29. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
  30. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  31. package/dist/resources/extensions/gsd/prompts/rethink.md +6 -2
  32. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  34. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
  35. package/dist/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
  36. package/dist/resources/extensions/gsd/safety/file-change-validator.js +2 -1
  37. package/dist/resources/extensions/gsd/state.js +2 -1
  38. package/dist/resources/extensions/gsd/visualizer-overlay.js +27 -26
  39. package/dist/resources/extensions/gsd/workflow-reconcile.js +46 -7
  40. package/dist/resources/extensions/remote-questions/manager.js +8 -0
  41. package/dist/resources/extensions/shared/interview-ui.js +10 -0
  42. package/dist/web/standalone/.next/BUILD_ID +1 -1
  43. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  44. package/dist/web/standalone/.next/build-manifest.json +2 -2
  45. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  46. package/dist/web/standalone/.next/required-server-files.json +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  48. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.html +1 -1
  64. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  71. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  72. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  73. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  74. package/dist/web/standalone/server.js +1 -1
  75. package/package.json +1 -1
  76. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  77. package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -3
  78. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  79. package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
  80. package/packages/pi-ai/dist/utils/json-parse.js +11 -1
  81. package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
  82. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  83. package/packages/pi-ai/dist/utils/repair-tool-json.js +60 -1
  84. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  85. package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts +2 -0
  86. package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts.map +1 -0
  87. package/packages/pi-ai/dist/utils/tests/json-parse.test.js +14 -0
  88. package/packages/pi-ai/dist/utils/tests/json-parse.test.js.map +1 -0
  89. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +10 -0
  90. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  91. package/packages/pi-ai/src/providers/anthropic-shared.ts +4 -3
  92. package/packages/pi-ai/src/utils/json-parse.ts +11 -1
  93. package/packages/pi-ai/src/utils/repair-tool-json.ts +69 -1
  94. package/packages/pi-ai/src/utils/tests/json-parse.test.ts +17 -0
  95. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +13 -0
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts +2 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts.map +1 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +17 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +2 -1
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +1 -0
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +2 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
  114. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  115. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +18 -0
  116. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +2 -1
  117. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +11 -2
  118. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +2 -1
  119. package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -1
  120. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -2
  121. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +13 -0
  122. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  123. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts +2 -0
  124. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts.map +1 -0
  125. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +35 -0
  126. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -0
  127. package/packages/pi-tui/dist/__tests__/tui.test.d.ts +2 -0
  128. package/packages/pi-tui/dist/__tests__/tui.test.d.ts.map +1 -0
  129. package/packages/pi-tui/dist/__tests__/tui.test.js +43 -0
  130. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -0
  131. package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
  132. package/packages/pi-tui/dist/autocomplete.js +9 -7
  133. package/packages/pi-tui/dist/autocomplete.js.map +1 -1
  134. package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts +2 -0
  135. package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts.map +1 -0
  136. package/packages/pi-tui/dist/components/__tests__/editor.test.js +54 -0
  137. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -0
  138. package/packages/pi-tui/dist/components/editor.d.ts +3 -1
  139. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  140. package/packages/pi-tui/dist/components/editor.js +14 -3
  141. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  142. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  143. package/packages/pi-tui/dist/stdin-buffer.js +6 -0
  144. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  145. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  146. package/packages/pi-tui/dist/tui.js +8 -0
  147. package/packages/pi-tui/dist/tui.js.map +1 -1
  148. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +15 -0
  149. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +43 -0
  150. package/packages/pi-tui/src/__tests__/tui.test.ts +50 -0
  151. package/packages/pi-tui/src/autocomplete.ts +9 -7
  152. package/packages/pi-tui/src/components/__tests__/editor.test.ts +64 -0
  153. package/packages/pi-tui/src/components/editor.ts +14 -3
  154. package/packages/pi-tui/src/stdin-buffer.ts +7 -0
  155. package/packages/pi-tui/src/tui.ts +9 -0
  156. package/src/resources/extensions/ask-user-questions.ts +103 -11
  157. package/src/resources/extensions/claude-code-cli/partial-builder.ts +4 -3
  158. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -3
  159. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +17 -0
  160. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +18 -0
  161. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -1
  162. package/src/resources/extensions/gsd/auto/loop.ts +14 -1
  163. package/src/resources/extensions/gsd/auto/phases.ts +10 -5
  164. package/src/resources/extensions/gsd/auto/run-unit.ts +14 -2
  165. package/src/resources/extensions/gsd/auto/session.ts +1 -1
  166. package/src/resources/extensions/gsd/auto-dashboard.ts +76 -16
  167. package/src/resources/extensions/gsd/auto-dispatch.ts +36 -35
  168. package/src/resources/extensions/gsd/auto-prompts.ts +5 -6
  169. package/src/resources/extensions/gsd/auto-recovery.ts +15 -15
  170. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +27 -6
  171. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +67 -6
  172. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +11 -8
  173. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +209 -16
  174. package/src/resources/extensions/gsd/codebase-generator.ts +4 -0
  175. package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -6
  176. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +11 -4
  177. package/src/resources/extensions/gsd/custom-workflow-engine.ts +3 -1
  178. package/src/resources/extensions/gsd/detection.ts +6 -0
  179. package/src/resources/extensions/gsd/files.ts +21 -2
  180. package/src/resources/extensions/gsd/guided-flow.ts +15 -8
  181. package/src/resources/extensions/gsd/index.ts +6 -0
  182. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +2 -0
  183. package/src/resources/extensions/gsd/parsers-legacy.ts +3 -1
  184. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  185. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
  186. package/src/resources/extensions/gsd/prompts/discuss.md +3 -3
  187. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
  188. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  189. package/src/resources/extensions/gsd/prompts/rethink.md +6 -2
  190. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  191. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  192. package/src/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
  193. package/src/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
  194. package/src/resources/extensions/gsd/safety/file-change-validator.ts +4 -1
  195. package/src/resources/extensions/gsd/state.ts +2 -1
  196. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +52 -1
  197. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +50 -2
  198. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +48 -0
  199. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  200. package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +44 -0
  201. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +7 -1
  202. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +31 -0
  203. package/src/resources/extensions/gsd/tests/detection.test.ts +37 -0
  204. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +50 -0
  205. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +35 -0
  206. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +34 -0
  207. package/src/resources/extensions/gsd/tests/health-widget.test.ts +45 -0
  208. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +53 -13
  209. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -2
  210. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -1
  211. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +3 -4
  212. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +21 -0
  213. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +71 -2
  214. package/src/resources/extensions/gsd/tests/parsers.test.ts +25 -0
  215. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -1
  216. package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +9 -0
  217. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +19 -0
  218. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +73 -0
  219. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +98 -0
  220. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +2 -2
  221. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +26 -0
  222. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +59 -0
  223. package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +91 -0
  224. package/src/resources/extensions/gsd/tests/write-gate.test.ts +210 -35
  225. package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -27
  226. package/src/resources/extensions/gsd/workflow-reconcile.ts +59 -8
  227. package/src/resources/extensions/remote-questions/manager.ts +9 -0
  228. package/src/resources/extensions/shared/interview-ui.ts +13 -0
  229. /package/dist/web/standalone/.next/static/{Zw5aZFHFtOwjJSOsINh1m → HxFcJ8GrYNPsg9ARz7GPz}/_buildManifest.js +0 -0
  230. /package/dist/web/standalone/.next/static/{Zw5aZFHFtOwjJSOsINh1m → HxFcJ8GrYNPsg9ARz7GPz}/_ssgManifest.js +0 -0
@@ -0,0 +1,18 @@
1
+ // GSD-2 — Provider display name mapping tests
2
+ import { test, describe } from "node:test";
3
+ import assert from "node:assert/strict";
4
+ import { providerDisplayName } from "../model-selector.js";
5
+
6
+ describe("providerDisplayName", () => {
7
+ test("renames 'anthropic' to 'anthropic-api'", () => {
8
+ assert.equal(providerDisplayName("anthropic"), "anthropic-api");
9
+ });
10
+
11
+ test("passes through unmapped providers unchanged", () => {
12
+ assert.equal(providerDisplayName("claude-code"), "claude-code");
13
+ assert.equal(providerDisplayName("openai"), "openai");
14
+ assert.equal(providerDisplayName("bedrock"), "bedrock");
15
+ assert.equal(providerDisplayName("github-copilot"), "github-copilot");
16
+ assert.equal(providerDisplayName("openrouter"), "openrouter");
17
+ });
18
+ });
@@ -2,6 +2,7 @@ import { type Component, truncateToWidth, visibleWidth } from "@gsd/pi-tui";
2
2
  import type { AgentSession } from "../../../core/agent-session.js";
3
3
  import type { ReadonlyFooterDataProvider } from "../../../core/footer-data-provider.js";
4
4
  import { theme } from "../theme/theme.js";
5
+ import { providerDisplayName } from "./model-selector.js";
5
6
 
6
7
  /**
7
8
  * Sanitize text for display in a single-line status.
@@ -189,7 +190,7 @@ export class FooterComponent implements Component {
189
190
  // Prepend the provider in parentheses if there are multiple providers and there's enough room
190
191
  let rightSide = rightSideWithoutProvider;
191
192
  if (this.footerData.getAvailableProviderCount() > 1 && displayModel) {
192
- rightSide = `(${displayModel.provider}) ${rightSideWithoutProvider}`;
193
+ rightSide = `(${providerDisplayName(displayModel.provider)}) ${rightSideWithoutProvider}`;
193
194
  if (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {
194
195
  // Too wide, fall back
195
196
  rightSide = rightSideWithoutProvider;
@@ -15,6 +15,15 @@ import { theme } from "../theme/theme.js";
15
15
  import { DynamicBorder } from "./dynamic-border.js";
16
16
  import { keyHint } from "./keybinding-hints.js";
17
17
 
18
+ /** Display names for providers in the model selector UI. */
19
+ const PROVIDER_DISPLAY_NAMES: Record<string, string> = {
20
+ anthropic: "anthropic-api",
21
+ };
22
+
23
+ export function providerDisplayName(provider: string): string {
24
+ return PROVIDER_DISPLAY_NAMES[provider] ?? provider;
25
+ }
26
+
18
27
  function formatTokenCount(count: number): string {
19
28
  if (count >= 1_000_000) {
20
29
  const millions = count / 1_000_000;
@@ -391,7 +400,7 @@ export class ModelSelectorComponent extends Container implements Focusable {
391
400
 
392
401
  const ctx = formatTokenCount(item.model.contextWindow);
393
402
  const ctxBadge = theme.fg("muted", `${ctx}`);
394
- const providerBadge = theme.fg("muted", `[${item.provider}]`);
403
+ const providerBadge = theme.fg("muted", `[${providerDisplayName(item.provider)}]`);
395
404
  const checkmark = isCurrent ? theme.fg("success", " ✓") : "";
396
405
 
397
406
  let line: string;
@@ -447,7 +456,7 @@ export class ModelSelectorComponent extends Container implements Focusable {
447
456
 
448
457
  if (row.kind === "header") {
449
458
  // Provider group header — always unselectable
450
- const providerLabel = theme.fg("borderAccent", row.provider);
459
+ const providerLabel = theme.fg("borderAccent", providerDisplayName(row.provider));
451
460
  const count = theme.fg("muted", ` (${row.count})`);
452
461
  // Add blank line before header if not the very first visible row
453
462
  if (i > startIndex) {
@@ -13,6 +13,7 @@ import {
13
13
  } from "@gsd/pi-tui";
14
14
  import type { AuthStorage } from "../../../core/auth-storage.js";
15
15
  import { getDiscoverableProviders } from "../../../core/model-discovery.js";
16
+ import { providerDisplayName } from "./model-selector.js";
16
17
  import type { ModelRegistry } from "../../../core/model-registry.js";
17
18
  import { ModelsJsonWriter } from "../../../core/models-json-writer.js";
18
19
  import { theme } from "../theme/theme.js";
@@ -149,7 +150,7 @@ export class ProviderManagerComponent extends Container implements Focusable {
149
150
  const countBadge = theme.fg("muted", `(${p.modelCount} models)`);
150
151
 
151
152
  const prefix = isSelected ? theme.fg("accent", "> ") : " ";
152
- const nameText = isSelected ? theme.fg("accent", p.name) : p.name;
153
+ const nameText = isSelected ? theme.fg("accent", providerDisplayName(p.name)) : providerDisplayName(p.name);
153
154
 
154
155
  const parts = [prefix, nameText, " ", authBadge];
155
156
  if (discoveryBadge) parts.push(" ", discoveryBadge);
@@ -1,4 +1,5 @@
1
1
  import type { Model } from "@gsd/pi-ai";
2
+ import { providerDisplayName } from "./model-selector.js";
2
3
  import {
3
4
  Container,
4
5
  type Focusable,
@@ -204,7 +205,7 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
204
205
  const isSelected = i === this.selectedIndex;
205
206
  const prefix = isSelected ? theme.fg("accent", "→ ") : " ";
206
207
  const modelText = isSelected ? theme.fg("accent", item.model.id) : item.model.id;
207
- const providerBadge = theme.fg("muted", ` [${item.model.provider}]`);
208
+ const providerBadge = theme.fg("muted", ` [${providerDisplayName(item.model.provider)}]`);
208
209
  const status = allEnabled ? "" : item.enabled ? theme.fg("success", " ✓") : theme.fg("dim", " ✗");
209
210
  this.listContainer.addChild(new Text(`${prefix}${modelText}${providerBadge}${status}`, 0, 0));
210
211
  }
@@ -79,7 +79,7 @@ import { ExtensionSelectorComponent } from "./components/extension-selector.js";
79
79
  import { FooterComponent } from "./components/footer.js";
80
80
  import { appKey, appKeyHint, editorKey, formatKeyForDisplay, keyHint, rawKeyHint } from "./components/keybinding-hints.js";
81
81
  import { LoginDialogComponent } from "./components/login-dialog.js";
82
- import { ModelSelectorComponent } from "./components/model-selector.js";
82
+ import { ModelSelectorComponent, providerDisplayName } from "./components/model-selector.js";
83
83
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
84
84
  import { ProviderManagerComponent } from "./components/provider-manager.js";
85
85
  import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
@@ -338,7 +338,7 @@ export class InteractiveMode {
338
338
  return filtered.map((item) => ({
339
339
  value: item.label,
340
340
  label: item.id,
341
- description: item.provider,
341
+ description: providerDisplayName(item.provider),
342
342
  }));
343
343
  };
344
344
  }
@@ -44,6 +44,13 @@ describe("CombinedAutocompleteProvider — slash commands", () => {
44
44
  const result = provider.getSuggestions(["hello /se"], 0, 9);
45
45
  assert.equal(result, null);
46
46
  });
47
+ it("triggers slash commands after leading whitespace", () => {
48
+ const provider = makeProvider(sampleCommands);
49
+ const result = provider.getSuggestions([" /se"], 0, 5);
50
+ assert.ok(result);
51
+ assert.equal(result.prefix, "/se");
52
+ assert.ok(result.items.some((item) => item.value === "settings"));
53
+ });
47
54
  });
48
55
  describe("CombinedAutocompleteProvider — argument completions", () => {
49
56
  it("returns argument completions for commands that support them", () => {
@@ -127,6 +134,12 @@ describe("CombinedAutocompleteProvider — applyCompletion", () => {
127
134
  assert.equal(result.lines[0], "/settings ");
128
135
  assert.equal(result.cursorCol, 10); // after "/settings "
129
136
  });
137
+ it("preserves leading whitespace when applying slash command completion", () => {
138
+ const provider = makeProvider(sampleCommands);
139
+ const result = provider.applyCompletion([" /se"], 0, 5, { value: "settings", label: "settings" }, "/se");
140
+ assert.equal(result.lines[0], " /settings ");
141
+ assert.equal(result.cursorCol, 12);
142
+ });
130
143
  it("applies file path completion for @ prefix", () => {
131
144
  const provider = makeProvider();
132
145
  const result = provider.applyCompletion(["@src/"], 0, 5, { value: "@src/index.ts", label: "index.ts" }, "@src/");
@@ -1 +1 @@
1
- {"version":3,"file":"autocomplete.test.js","sourceRoot":"","sources":["../../src/__tests__/autocomplete.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { CombinedAutocompleteProvider } from \"../autocomplete.js\";\nimport type { SlashCommand } from \"../autocomplete.js\";\n\nfunction makeProvider(commands: SlashCommand[] = [], basePath: string = \"/tmp\") {\n\treturn new CombinedAutocompleteProvider(commands, basePath);\n}\n\nconst sampleCommands: SlashCommand[] = [\n\t{ name: \"settings\", description: \"Open settings menu\" },\n\t{ name: \"model\", description: \"Select model\" },\n\t{ name: \"session\", description: \"Show session info\" },\n\t{ name: \"export\", description: \"Export session\" },\n\t{ name: \"thinking\", description: \"Set thinking level\" },\n];\n\ndescribe(\"CombinedAutocompleteProvider — slash commands\", () => {\n\tit(\"returns all commands for bare /\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/\"], 0, 1);\n\t\tassert.ok(result, \"should return suggestions\");\n\t\tassert.equal(result!.items.length, sampleCommands.length);\n\t\tassert.equal(result!.prefix, \"/\");\n\t});\n\n\tit(\"filters commands by typed prefix\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/se\"], 0, 3);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 2); // settings, session\n\t\tassert.ok(result!.items.some((i) => i.value === \"settings\"));\n\t\tassert.ok(result!.items.some((i) => i.value === \"session\"));\n\t});\n\n\tit(\"returns null when no commands match\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/zzz\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"includes description in suggestions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/mod\"], 0, 4);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items[0]?.description, \"Select model\");\n\t});\n\n\tit(\"does not trigger slash commands mid-line\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\t// \"/\" not at position 0 in the line — should not match slash commands\n\t\tconst result = provider.getSuggestions([\"hello /se\"], 0, 9);\n\t\tassert.equal(result, null);\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — argument completions\", () => {\n\tit(\"returns argument completions for commands that support them\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"thinking\",\n\t\t\t\tdescription: \"Set thinking level\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst levels = [\"off\", \"low\", \"medium\", \"high\"];\n\t\t\t\t\tconst filtered = levels\n\t\t\t\t\t\t.filter((l) => l.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((l) => ({ value: l, label: l }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/thinking m\"], 0, 11);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 1);\n\t\tassert.equal(result!.items[0]?.value, \"medium\");\n\t});\n\n\tit(\"returns null for commands without argument completions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/settings foo\"], 0, 13);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"returns all arg completions for empty prefix after space\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"test\",\n\t\t\t\tdescription: \"Test command\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst subs = [\"start\", \"stop\", \"status\"];\n\t\t\t\t\tconst filtered = subs\n\t\t\t\t\t\t.filter((s) => s.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((s) => ({ value: s, label: s }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/test \"], 0, 6);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 3);\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — @ file prefix extraction\", () => {\n\tit(\"detects @ at start of line\", () => {\n\t\tconst provider = makeProvider();\n\t\t// @ triggers fuzzy file search — we can't test the actual file results\n\t\t// but we can test that getSuggestions returns null (no files in /tmp matching)\n\t\t// rather than crashing\n\t\tconst result = provider.getSuggestions([\"@nonexistent_xyz\"], 0, 16);\n\t\t// May return null or empty — the key thing is it doesn't crash\n\t\tassert.ok(result === null || result.items.length >= 0);\n\t});\n\n\tit(\"detects @ after space\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.getSuggestions([\"check @nonexistent_xyz\"], 0, 22);\n\t\tassert.ok(result === null || result.items.length >= 0);\n\t});\n\n\tit(\"returns null for bare @ with no query to avoid full tree walk (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\t// A bare \"@\" produces an empty rawPrefix after stripping the \"@\".\n\t\t// This must return null to avoid a synchronous full filesystem walk\n\t\t// via the native fuzzyFind addon, which freezes the TUI on large repos.\n\t\tconst result = provider.getSuggestions([\"@\"], 0, 1);\n\t\tassert.equal(result, null, \"bare @ should not trigger fuzzy file search\");\n\t});\n\n\tit(\"returns null for @ after space with no query (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\tconst result = provider.getSuggestions([\"look at @\"], 0, 9);\n\t\tassert.equal(result, null, \"@ after space with no query should not trigger fuzzy file search\");\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — applyCompletion\", () => {\n\tit(\"applies slash command completion with trailing space\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\"/se\"], 0, 3, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \"/settings \");\n\t\tassert.equal(result.cursorCol, 10); // after \"/settings \"\n\t});\n\n\tit(\"applies file path completion for @ prefix\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@src/\"],\n\t\t\t0,\n\t\t\t5,\n\t\t\t{ value: \"@src/index.ts\", label: \"index.ts\" },\n\t\t\t\"@src/\",\n\t\t);\n\t\tassert.equal(result.lines[0], \"@src/index.ts \");\n\t});\n\n\tit(\"applies directory completion without trailing space\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@sr\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"@src/\", label: \"src/\" },\n\t\t\t\"@sr\",\n\t\t);\n\t\t// Directories should not get trailing space so user can continue typing\n\t\tassert.ok(!result.lines[0]!.endsWith(\" \"));\n\t});\n\n\tit(\"preserves text after cursor\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"/se and more text\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"settings\", label: \"settings\" },\n\t\t\t\"/se\",\n\t\t);\n\t\tassert.ok(result.lines[0]!.includes(\"and more text\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — force file suggestions\", () => {\n\tit(\"does not trigger for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getForceFileSuggestions([\"/set\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns false for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"/set\"], 0, 4), false);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns true for regular text\", () => {\n\t\tconst provider = makeProvider();\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"some text\"], 0, 9), true);\n\t});\n});\n"]}
1
+ {"version":3,"file":"autocomplete.test.js","sourceRoot":"","sources":["../../src/__tests__/autocomplete.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { CombinedAutocompleteProvider } from \"../autocomplete.js\";\nimport type { SlashCommand } from \"../autocomplete.js\";\n\nfunction makeProvider(commands: SlashCommand[] = [], basePath: string = \"/tmp\") {\n\treturn new CombinedAutocompleteProvider(commands, basePath);\n}\n\nconst sampleCommands: SlashCommand[] = [\n\t{ name: \"settings\", description: \"Open settings menu\" },\n\t{ name: \"model\", description: \"Select model\" },\n\t{ name: \"session\", description: \"Show session info\" },\n\t{ name: \"export\", description: \"Export session\" },\n\t{ name: \"thinking\", description: \"Set thinking level\" },\n];\n\ndescribe(\"CombinedAutocompleteProvider — slash commands\", () => {\n\tit(\"returns all commands for bare /\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/\"], 0, 1);\n\t\tassert.ok(result, \"should return suggestions\");\n\t\tassert.equal(result!.items.length, sampleCommands.length);\n\t\tassert.equal(result!.prefix, \"/\");\n\t});\n\n\tit(\"filters commands by typed prefix\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/se\"], 0, 3);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 2); // settings, session\n\t\tassert.ok(result!.items.some((i) => i.value === \"settings\"));\n\t\tassert.ok(result!.items.some((i) => i.value === \"session\"));\n\t});\n\n\tit(\"returns null when no commands match\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/zzz\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"includes description in suggestions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/mod\"], 0, 4);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items[0]?.description, \"Select model\");\n\t});\n\n\tit(\"does not trigger slash commands mid-line\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\t// \"/\" not at position 0 in the line — should not match slash commands\n\t\tconst result = provider.getSuggestions([\"hello /se\"], 0, 9);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"triggers slash commands after leading whitespace\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\" /se\"], 0, 5);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.prefix, \"/se\");\n\t\tassert.ok(result!.items.some((item) => item.value === \"settings\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — argument completions\", () => {\n\tit(\"returns argument completions for commands that support them\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"thinking\",\n\t\t\t\tdescription: \"Set thinking level\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst levels = [\"off\", \"low\", \"medium\", \"high\"];\n\t\t\t\t\tconst filtered = levels\n\t\t\t\t\t\t.filter((l) => l.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((l) => ({ value: l, label: l }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/thinking m\"], 0, 11);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 1);\n\t\tassert.equal(result!.items[0]?.value, \"medium\");\n\t});\n\n\tit(\"returns null for commands without argument completions\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getSuggestions([\"/settings foo\"], 0, 13);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"returns all arg completions for empty prefix after space\", () => {\n\t\tconst commands: SlashCommand[] = [\n\t\t\t{\n\t\t\t\tname: \"test\",\n\t\t\t\tdescription: \"Test command\",\n\t\t\t\tgetArgumentCompletions: (prefix) => {\n\t\t\t\t\tconst subs = [\"start\", \"stop\", \"status\"];\n\t\t\t\t\tconst filtered = subs\n\t\t\t\t\t\t.filter((s) => s.startsWith(prefix.trim()))\n\t\t\t\t\t\t.map((s) => ({ value: s, label: s }));\n\t\t\t\t\treturn filtered.length > 0 ? filtered : null;\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t\tconst provider = makeProvider(commands);\n\t\tconst result = provider.getSuggestions([\"/test \"], 0, 6);\n\t\tassert.ok(result);\n\t\tassert.equal(result!.items.length, 3);\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — @ file prefix extraction\", () => {\n\tit(\"detects @ at start of line\", () => {\n\t\tconst provider = makeProvider();\n\t\t// @ triggers fuzzy file search — we can't test the actual file results\n\t\t// but we can test that getSuggestions returns null (no files in /tmp matching)\n\t\t// rather than crashing\n\t\tconst result = provider.getSuggestions([\"@nonexistent_xyz\"], 0, 16);\n\t\t// May return null or empty — the key thing is it doesn't crash\n\t\tassert.ok(result === null || result.items.length >= 0);\n\t});\n\n\tit(\"detects @ after space\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.getSuggestions([\"check @nonexistent_xyz\"], 0, 22);\n\t\tassert.ok(result === null || result.items.length >= 0);\n\t});\n\n\tit(\"returns null for bare @ with no query to avoid full tree walk (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\t// A bare \"@\" produces an empty rawPrefix after stripping the \"@\".\n\t\t// This must return null to avoid a synchronous full filesystem walk\n\t\t// via the native fuzzyFind addon, which freezes the TUI on large repos.\n\t\tconst result = provider.getSuggestions([\"@\"], 0, 1);\n\t\tassert.equal(result, null, \"bare @ should not trigger fuzzy file search\");\n\t});\n\n\tit(\"returns null for @ after space with no query (#1824)\", () => {\n\t\tconst provider = makeProvider([], process.cwd());\n\t\tconst result = provider.getSuggestions([\"look at @\"], 0, 9);\n\t\tassert.equal(result, null, \"@ after space with no query should not trigger fuzzy file search\");\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — applyCompletion\", () => {\n\tit(\"applies slash command completion with trailing space\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\"/se\"], 0, 3, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \"/settings \");\n\t\tassert.equal(result.cursorCol, 10); // after \"/settings \"\n\t});\n\n\tit(\"preserves leading whitespace when applying slash command completion\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion([\" /se\"], 0, 5, { value: \"settings\", label: \"settings\" }, \"/se\");\n\t\tassert.equal(result.lines[0], \" /settings \");\n\t\tassert.equal(result.cursorCol, 12);\n\t});\n\n\tit(\"applies file path completion for @ prefix\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@src/\"],\n\t\t\t0,\n\t\t\t5,\n\t\t\t{ value: \"@src/index.ts\", label: \"index.ts\" },\n\t\t\t\"@src/\",\n\t\t);\n\t\tassert.equal(result.lines[0], \"@src/index.ts \");\n\t});\n\n\tit(\"applies directory completion without trailing space\", () => {\n\t\tconst provider = makeProvider();\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"@sr\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"@src/\", label: \"src/\" },\n\t\t\t\"@sr\",\n\t\t);\n\t\t// Directories should not get trailing space so user can continue typing\n\t\tassert.ok(!result.lines[0]!.endsWith(\" \"));\n\t});\n\n\tit(\"preserves text after cursor\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.applyCompletion(\n\t\t\t[\"/se and more text\"],\n\t\t\t0,\n\t\t\t3,\n\t\t\t{ value: \"settings\", label: \"settings\" },\n\t\t\t\"/se\",\n\t\t);\n\t\tassert.ok(result.lines[0]!.includes(\"and more text\"));\n\t});\n});\n\ndescribe(\"CombinedAutocompleteProvider — force file suggestions\", () => {\n\tit(\"does not trigger for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tconst result = provider.getForceFileSuggestions([\"/set\"], 0, 4);\n\t\tassert.equal(result, null);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns false for slash commands\", () => {\n\t\tconst provider = makeProvider(sampleCommands);\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"/set\"], 0, 4), false);\n\t});\n\n\tit(\"shouldTriggerFileCompletion returns true for regular text\", () => {\n\t\tconst provider = makeProvider();\n\t\tassert.equal(provider.shouldTriggerFileCompletion([\"some text\"], 0, 9), true);\n\t});\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stdin-buffer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdin-buffer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/stdin-buffer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { setTimeout as delay } from "node:timers/promises";
4
+ import { StdinBuffer } from "../stdin-buffer.js";
5
+ describe("StdinBuffer", () => {
6
+ it("flushes a lone Escape keypress", async () => {
7
+ const buffer = new StdinBuffer({ timeout: 5 });
8
+ const received = [];
9
+ buffer.on("data", (sequence) => received.push(sequence));
10
+ buffer.process("\x1b");
11
+ await delay(20);
12
+ assert.deepEqual(received, ["\x1b"]);
13
+ assert.equal(buffer.getBuffer(), "");
14
+ });
15
+ it("keeps split CSI focus and mouse sequences buffered until completion", async () => {
16
+ const buffer = new StdinBuffer({ timeout: 5 });
17
+ const received = [];
18
+ buffer.on("data", (sequence) => received.push(sequence));
19
+ buffer.process("\x1b[");
20
+ await delay(20);
21
+ assert.deepEqual(received, []);
22
+ assert.equal(buffer.getBuffer(), "\x1b[");
23
+ buffer.process("I");
24
+ assert.deepEqual(received, ["\x1b[I"]);
25
+ assert.equal(buffer.getBuffer(), "");
26
+ buffer.process("\x1b[<35;20;");
27
+ await delay(20);
28
+ assert.deepEqual(received, ["\x1b[I"]);
29
+ assert.equal(buffer.getBuffer(), "\x1b[<35;20;");
30
+ buffer.process("5m");
31
+ assert.deepEqual(received, ["\x1b[I", "\x1b[<35;20;5m"]);
32
+ assert.equal(buffer.getBuffer(), "");
33
+ });
34
+ });
35
+ //# sourceMappingURL=stdin-buffer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdin-buffer.test.js","sourceRoot":"","sources":["../../src/__tests__/stdin-buffer.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAEzD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACvB,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;QAEhB,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;QACpF,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAEzD,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACxB,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;QAChB,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;QAE1C,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpB,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QAErC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC/B,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;QAChB,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,cAAc,CAAC,CAAC;QAEjD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACrB,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC;QACzD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it } from \"node:test\";\nimport { setTimeout as delay } from \"node:timers/promises\";\n\nimport { StdinBuffer } from \"../stdin-buffer.js\";\n\ndescribe(\"StdinBuffer\", () => {\n\tit(\"flushes a lone Escape keypress\", async () => {\n\t\tconst buffer = new StdinBuffer({ timeout: 5 });\n\t\tconst received: string[] = [];\n\t\tbuffer.on(\"data\", (sequence) => received.push(sequence));\n\n\t\tbuffer.process(\"\\x1b\");\n\t\tawait delay(20);\n\n\t\tassert.deepEqual(received, [\"\\x1b\"]);\n\t\tassert.equal(buffer.getBuffer(), \"\");\n\t});\n\n\tit(\"keeps split CSI focus and mouse sequences buffered until completion\", async () => {\n\t\tconst buffer = new StdinBuffer({ timeout: 5 });\n\t\tconst received: string[] = [];\n\t\tbuffer.on(\"data\", (sequence) => received.push(sequence));\n\n\t\tbuffer.process(\"\\x1b[\");\n\t\tawait delay(20);\n\t\tassert.deepEqual(received, []);\n\t\tassert.equal(buffer.getBuffer(), \"\\x1b[\");\n\n\t\tbuffer.process(\"I\");\n\t\tassert.deepEqual(received, [\"\\x1b[I\"]);\n\t\tassert.equal(buffer.getBuffer(), \"\");\n\n\t\tbuffer.process(\"\\x1b[<35;20;\");\n\t\tawait delay(20);\n\t\tassert.deepEqual(received, [\"\\x1b[I\"]);\n\t\tassert.equal(buffer.getBuffer(), \"\\x1b[<35;20;\");\n\n\t\tbuffer.process(\"5m\");\n\t\tassert.deepEqual(received, [\"\\x1b[I\", \"\\x1b[<35;20;5m\"]);\n\t\tassert.equal(buffer.getBuffer(), \"\");\n\t});\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tui.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tui.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { TUI } from "../tui.js";
4
+ function makeTerminal() {
5
+ return {
6
+ isTTY: true,
7
+ columns: 80,
8
+ rows: 24,
9
+ kittyProtocolActive: false,
10
+ start() { },
11
+ stop() { },
12
+ drainInput: async () => { },
13
+ write() { },
14
+ moveBy() { },
15
+ hideCursor() { },
16
+ showCursor() { },
17
+ clearLine() { },
18
+ clearFromCursor() { },
19
+ clearScreen() { },
20
+ setTitle() { },
21
+ };
22
+ }
23
+ describe("TUI", () => {
24
+ it("does not swallow a bare Escape keypress while waiting for the cell-size response", () => {
25
+ const tui = new TUI(makeTerminal());
26
+ const received = [];
27
+ tui.setFocus({
28
+ render: () => [],
29
+ handleInput: (data) => {
30
+ received.push(data);
31
+ },
32
+ invalidate() { },
33
+ });
34
+ const anyTui = tui;
35
+ anyTui.cellSizeQueryPending = true;
36
+ anyTui.inputBuffer = "";
37
+ anyTui.handleInput("\x1b");
38
+ assert.deepEqual(received, ["\x1b"]);
39
+ assert.equal(anyTui.cellSizeQueryPending, false);
40
+ assert.equal(anyTui.inputBuffer, "");
41
+ });
42
+ });
43
+ //# sourceMappingURL=tui.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.test.js","sourceRoot":"","sources":["../../src/__tests__/tui.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAGhC,SAAS,YAAY;IACpB,OAAO;QACN,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,EAAE;QACX,IAAI,EAAE,EAAE;QACR,mBAAmB,EAAE,KAAK;QAC1B,KAAK,KAAI,CAAC;QACV,IAAI,KAAI,CAAC;QACT,UAAU,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QAC1B,KAAK,KAAI,CAAC;QACV,MAAM,KAAI,CAAC;QACX,UAAU,KAAI,CAAC;QACf,UAAU,KAAI,CAAC;QACf,SAAS,KAAI,CAAC;QACd,eAAe,KAAI,CAAC;QACpB,WAAW,KAAI,CAAC;QAChB,QAAQ,KAAI,CAAC;KACb,CAAC;AACH,CAAC;AAED,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE;IACpB,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC3F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,GAAG,CAAC,QAAQ,CAAC;YACZ,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE;YAChB,WAAW,EAAE,CAAC,IAAY,EAAE,EAAE;gBAC7B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;YACD,UAAU,KAAI,CAAC;SACf,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,GAAU,CAAC;QAC1B,MAAM,CAAC,oBAAoB,GAAG,IAAI,CAAC;QACnC,MAAM,CAAC,WAAW,GAAG,EAAE,CAAC;QAExB,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAE3B,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it } from \"node:test\";\n\nimport { TUI } from \"../tui.js\";\nimport type { Terminal } from \"../terminal.js\";\n\nfunction makeTerminal(): Terminal {\n\treturn {\n\t\tisTTY: true,\n\t\tcolumns: 80,\n\t\trows: 24,\n\t\tkittyProtocolActive: false,\n\t\tstart() {},\n\t\tstop() {},\n\t\tdrainInput: async () => {},\n\t\twrite() {},\n\t\tmoveBy() {},\n\t\thideCursor() {},\n\t\tshowCursor() {},\n\t\tclearLine() {},\n\t\tclearFromCursor() {},\n\t\tclearScreen() {},\n\t\tsetTitle() {},\n\t};\n}\n\ndescribe(\"TUI\", () => {\n\tit(\"does not swallow a bare Escape keypress while waiting for the cell-size response\", () => {\n\t\tconst tui = new TUI(makeTerminal());\n\t\tconst received: string[] = [];\n\n\t\ttui.setFocus({\n\t\t\trender: () => [],\n\t\t\thandleInput: (data: string) => {\n\t\t\t\treceived.push(data);\n\t\t\t},\n\t\t\tinvalidate() {},\n\t\t});\n\n\t\tconst anyTui = tui as any;\n\t\tanyTui.cellSizeQueryPending = true;\n\t\tanyTui.inputBuffer = \"\";\n\n\t\tanyTui.handleInput(\"\\x1b\");\n\n\t\tassert.deepEqual(received, [\"\\x1b\"]);\n\t\tassert.equal(anyTui.cellSizeQueryPending, false);\n\t\tassert.equal(anyTui.inputBuffer, \"\");\n\t});\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../src/autocomplete.ts"],"names":[],"mappings":"AAuFA,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,sBAAsB,CAAC,CAAC,cAAc,EAAE,MAAM,GAAG,gBAAgB,EAAE,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,oBAAoB;IAGpC,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QACF,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC;KACf,GAAG,IAAI,CAAC;IAIT,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QACF,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;CACF;AAGD,qBAAa,4BAA6B,YAAW,oBAAoB;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,gBAAgB,CAAU;IAClC,OAAO,CAAC,WAAW,CAAc;gBAGhC,QAAQ,GAAE,CAAC,YAAY,GAAG,gBAAgB,CAAC,EAAO,EAClD,QAAQ,GAAE,MAAsB,EAChC,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE;IAQjE,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAIzC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAIpC,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IA+FvD,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAkF7D,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,uBAAuB;IA6B/B,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,kBAAkB;IAwI1B,OAAO,CAAC,uBAAuB;IA4D/B,uBAAuB,CACtB,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAyBvD,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;CAW5F"}
1
+ {"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../src/autocomplete.ts"],"names":[],"mappings":"AAuFA,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,sBAAsB,CAAC,CAAC,cAAc,EAAE,MAAM,GAAG,gBAAgB,EAAE,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,oBAAoB;IAGpC,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QACF,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC;KACf,GAAG,IAAI,CAAC;IAIT,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QACF,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;CACF;AAGD,qBAAa,4BAA6B,YAAW,oBAAoB;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,gBAAgB,CAAU;IAClC,OAAO,CAAC,WAAW,CAAc;gBAGhC,QAAQ,GAAE,CAAC,YAAY,GAAG,gBAAgB,CAAC,EAAO,EAClD,QAAQ,GAAE,MAAsB,EAChC,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE;IAQjE,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAIzC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAIpC,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAgGvD,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAmF7D,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,uBAAuB;IA6B/B,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,kBAAkB;IAwI1B,OAAO,CAAC,uBAAuB;IA4D/B,uBAAuB,CACtB,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAyBvD,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;CAW5F"}
@@ -84,6 +84,7 @@ export class CombinedAutocompleteProvider {
84
84
  getSuggestions(lines, cursorLine, cursorCol) {
85
85
  const currentLine = lines[cursorLine] || "";
86
86
  const textBeforeCursor = currentLine.slice(0, cursorCol);
87
+ const trimmedBeforeCursor = textBeforeCursor.trimStart();
87
88
  // Check for @ file reference (fuzzy search) - must be after a delimiter or at start
88
89
  const atPrefix = this.extractAtPrefix(textBeforeCursor);
89
90
  if (atPrefix) {
@@ -97,11 +98,11 @@ export class CombinedAutocompleteProvider {
97
98
  };
98
99
  }
99
100
  // Check for slash commands
100
- if (textBeforeCursor.startsWith("/")) {
101
- const spaceIndex = textBeforeCursor.indexOf(" ");
101
+ if (trimmedBeforeCursor.startsWith("/")) {
102
+ const spaceIndex = trimmedBeforeCursor.indexOf(" ");
102
103
  if (spaceIndex === -1) {
103
104
  // No space yet - complete command names with fuzzy matching
104
- const prefix = textBeforeCursor.slice(1); // Remove the "/"
105
+ const prefix = trimmedBeforeCursor.slice(1); // Remove the "/"
105
106
  const commandItems = this.commands.map((cmd) => ({
106
107
  name: "name" in cmd ? cmd.name : cmd.value,
107
108
  label: "name" in cmd ? cmd.name : cmd.label,
@@ -116,13 +117,13 @@ export class CombinedAutocompleteProvider {
116
117
  return null;
117
118
  return {
118
119
  items: filtered,
119
- prefix: textBeforeCursor,
120
+ prefix: `/${prefix}`,
120
121
  };
121
122
  }
122
123
  else {
123
124
  // Space found - complete command arguments
124
- const commandName = textBeforeCursor.slice(1, spaceIndex); // Command without "/"
125
- const argumentText = textBeforeCursor.slice(spaceIndex + 1); // Text after space
125
+ const commandName = trimmedBeforeCursor.slice(1, spaceIndex); // Command without "/"
126
+ const argumentText = trimmedBeforeCursor.slice(spaceIndex + 1); // Text after space
126
127
  const command = this.commands.find((cmd) => {
127
128
  const name = "name" in cmd ? cmd.name : cmd.value;
128
129
  return name === commandName;
@@ -174,7 +175,8 @@ export class CombinedAutocompleteProvider {
174
175
  const adjustedAfterCursor = isQuotedPrefix && hasTrailingQuoteInItem && hasLeadingQuoteAfterCursor ? afterCursor.slice(1) : afterCursor;
175
176
  // Check if we're completing a slash command (prefix starts with "/" but NOT a file path)
176
177
  // Slash commands are at the start of the line and don't contain path separators after the first /
177
- const isSlashCommand = prefix.startsWith("/") && beforePrefix.trim() === "" && !prefix.slice(1).includes("/");
178
+ const trimmedPrefix = prefix.trimStart();
179
+ const isSlashCommand = trimmedPrefix.startsWith("/") && beforePrefix.trim() === "" && !trimmedPrefix.slice(1).includes("/");
178
180
  if (isSlashCommand) {
179
181
  // This is a command name completion
180
182
  const newLine = `${beforePrefix}/${item.value} ${adjustedAfterCursor}`;