@earendil-works/pi-coding-agent 0.78.1 → 0.79.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 (174) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +21 -2
  3. package/dist/cli/args.d.ts +1 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +8 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/project-trust.d.ts +10 -0
  8. package/dist/cli/project-trust.d.ts.map +1 -0
  9. package/dist/cli/project-trust.js +48 -0
  10. package/dist/cli/project-trust.js.map +1 -0
  11. package/dist/cli/startup-ui.d.ts +7 -0
  12. package/dist/cli/startup-ui.d.ts.map +1 -0
  13. package/dist/cli/startup-ui.js +59 -0
  14. package/dist/cli/startup-ui.js.map +1 -0
  15. package/dist/core/agent-session-runtime.d.ts +3 -1
  16. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  17. package/dist/core/agent-session-runtime.js +4 -1
  18. package/dist/core/agent-session-runtime.js.map +1 -1
  19. package/dist/core/agent-session-services.d.ts +2 -1
  20. package/dist/core/agent-session-services.d.ts.map +1 -1
  21. package/dist/core/agent-session-services.js +2 -2
  22. package/dist/core/agent-session-services.js.map +1 -1
  23. package/dist/core/agent-session.d.ts +1 -0
  24. package/dist/core/agent-session.d.ts.map +1 -1
  25. package/dist/core/agent-session.js +6 -0
  26. package/dist/core/agent-session.js.map +1 -1
  27. package/dist/core/compaction/utils.d.ts +1 -1
  28. package/dist/core/compaction/utils.d.ts.map +1 -1
  29. package/dist/core/compaction/utils.js +1 -1
  30. package/dist/core/compaction/utils.js.map +1 -1
  31. package/dist/core/experimental.d.ts +2 -0
  32. package/dist/core/experimental.d.ts.map +1 -0
  33. package/dist/core/experimental.js +4 -0
  34. package/dist/core/experimental.js.map +1 -0
  35. package/dist/core/extensions/index.d.ts +1 -1
  36. package/dist/core/extensions/index.d.ts.map +1 -1
  37. package/dist/core/extensions/index.js.map +1 -1
  38. package/dist/core/extensions/loader.d.ts +1 -1
  39. package/dist/core/extensions/loader.d.ts.map +1 -1
  40. package/dist/core/extensions/loader.js +4 -4
  41. package/dist/core/extensions/loader.js.map +1 -1
  42. package/dist/core/extensions/runner.d.ts +7 -2
  43. package/dist/core/extensions/runner.d.ts.map +1 -1
  44. package/dist/core/extensions/runner.js +34 -0
  45. package/dist/core/extensions/runner.js.map +1 -1
  46. package/dist/core/extensions/types.d.ts +21 -1
  47. package/dist/core/extensions/types.d.ts.map +1 -1
  48. package/dist/core/extensions/types.js.map +1 -1
  49. package/dist/core/index.d.ts +1 -0
  50. package/dist/core/index.d.ts.map +1 -1
  51. package/dist/core/index.js +1 -0
  52. package/dist/core/index.js.map +1 -1
  53. package/dist/core/model-registry.d.ts.map +1 -1
  54. package/dist/core/model-registry.js +1 -0
  55. package/dist/core/model-registry.js.map +1 -1
  56. package/dist/core/package-manager.d.ts +1 -0
  57. package/dist/core/package-manager.d.ts.map +1 -1
  58. package/dist/core/package-manager.js +25 -7
  59. package/dist/core/package-manager.js.map +1 -1
  60. package/dist/core/project-trust.d.ts +15 -0
  61. package/dist/core/project-trust.d.ts.map +1 -0
  62. package/dist/core/project-trust.js +58 -0
  63. package/dist/core/project-trust.js.map +1 -0
  64. package/dist/core/prompt-templates.d.ts +2 -1
  65. package/dist/core/prompt-templates.d.ts.map +1 -1
  66. package/dist/core/prompt-templates.js +24 -26
  67. package/dist/core/prompt-templates.js.map +1 -1
  68. package/dist/core/resource-loader.d.ts +13 -2
  69. package/dist/core/resource-loader.d.ts.map +1 -1
  70. package/dist/core/resource-loader.js +112 -37
  71. package/dist/core/resource-loader.js.map +1 -1
  72. package/dist/core/settings-manager.d.ts +14 -2
  73. package/dist/core/settings-manager.d.ts.map +1 -1
  74. package/dist/core/settings-manager.js +80 -30
  75. package/dist/core/settings-manager.js.map +1 -1
  76. package/dist/core/slash-commands.d.ts.map +1 -1
  77. package/dist/core/slash-commands.js +1 -0
  78. package/dist/core/slash-commands.js.map +1 -1
  79. package/dist/core/tools/bash.d.ts.map +1 -1
  80. package/dist/core/tools/bash.js +1 -1
  81. package/dist/core/tools/bash.js.map +1 -1
  82. package/dist/core/tools/find.d.ts.map +1 -1
  83. package/dist/core/tools/find.js +1 -1
  84. package/dist/core/tools/find.js.map +1 -1
  85. package/dist/core/tools/grep.d.ts.map +1 -1
  86. package/dist/core/tools/grep.js +1 -1
  87. package/dist/core/tools/grep.js.map +1 -1
  88. package/dist/core/tools/ls.d.ts.map +1 -1
  89. package/dist/core/tools/ls.js +1 -1
  90. package/dist/core/tools/ls.js.map +1 -1
  91. package/dist/core/tools/read.d.ts.map +1 -1
  92. package/dist/core/tools/read.js +1 -1
  93. package/dist/core/tools/read.js.map +1 -1
  94. package/dist/core/tools/write.d.ts.map +1 -1
  95. package/dist/core/tools/write.js +1 -1
  96. package/dist/core/tools/write.js.map +1 -1
  97. package/dist/core/trust-manager.d.ts +31 -0
  98. package/dist/core/trust-manager.d.ts.map +1 -0
  99. package/dist/core/trust-manager.js +187 -0
  100. package/dist/core/trust-manager.js.map +1 -0
  101. package/dist/index.d.ts +5 -4
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +2 -1
  104. package/dist/index.js.map +1 -1
  105. package/dist/main.d.ts.map +1 -1
  106. package/dist/main.js +61 -32
  107. package/dist/main.js.map +1 -1
  108. package/dist/migrations.d.ts.map +1 -1
  109. package/dist/migrations.js +39 -34
  110. package/dist/migrations.js.map +1 -1
  111. package/dist/modes/index.d.ts +1 -1
  112. package/dist/modes/index.d.ts.map +1 -1
  113. package/dist/modes/index.js.map +1 -1
  114. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  115. package/dist/modes/interactive/components/bash-execution.js +2 -2
  116. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  117. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  118. package/dist/modes/interactive/components/footer.js +7 -0
  119. package/dist/modes/interactive/components/footer.js.map +1 -1
  120. package/dist/modes/interactive/components/index.d.ts +1 -0
  121. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  122. package/dist/modes/interactive/components/index.js +1 -0
  123. package/dist/modes/interactive/components/index.js.map +1 -1
  124. package/dist/modes/interactive/components/login-dialog.d.ts +1 -0
  125. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/login-dialog.js +7 -1
  127. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  128. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  129. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/settings-selector.js +20 -0
  131. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  132. package/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  133. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
  134. package/dist/modes/interactive/components/trust-selector.js +91 -0
  135. package/dist/modes/interactive/components/trust-selector.js.map +1 -0
  136. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  137. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  138. package/dist/modes/interactive/interactive-mode.js +99 -5
  139. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  140. package/dist/package-manager-cli.d.ts +6 -2
  141. package/dist/package-manager-cli.d.ts.map +1 -1
  142. package/dist/package-manager-cli.js +104 -10
  143. package/dist/package-manager-cli.js.map +1 -1
  144. package/dist/utils/changelog.d.ts +1 -0
  145. package/dist/utils/changelog.d.ts.map +1 -1
  146. package/dist/utils/changelog.js +78 -0
  147. package/dist/utils/changelog.js.map +1 -1
  148. package/docs/docs.json +4 -0
  149. package/docs/extensions.md +31 -2
  150. package/docs/index.md +1 -0
  151. package/docs/models.md +4 -3
  152. package/docs/packages.md +1 -1
  153. package/docs/prompt-templates.md +9 -2
  154. package/docs/sdk.md +5 -0
  155. package/docs/security.md +55 -0
  156. package/docs/settings.md +13 -0
  157. package/docs/skills.md +1 -1
  158. package/docs/terminal-setup.md +36 -2
  159. package/docs/themes.md +1 -1
  160. package/docs/tmux.md +4 -2
  161. package/docs/usage.md +18 -1
  162. package/examples/extensions/README.md +1 -0
  163. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  164. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  165. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  166. package/examples/extensions/gondolin/package-lock.json +2 -2
  167. package/examples/extensions/gondolin/package.json +1 -1
  168. package/examples/extensions/project-trust.ts +64 -0
  169. package/examples/extensions/sandbox/package-lock.json +2 -2
  170. package/examples/extensions/sandbox/package.json +1 -1
  171. package/examples/extensions/with-deps/package-lock.json +2 -2
  172. package/examples/extensions/with-deps/package.json +1 -1
  173. package/npm-shrinkwrap.json +12 -12
  174. package/package.json +4 -8
@@ -24,7 +24,8 @@ import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../cor
24
24
  import { SessionManager } from "../../core/session-manager.js";
25
25
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
26
26
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
27
- import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
27
+ import { hasProjectConfigDir, hasProjectTrustInputs, ProjectTrustStore } from "../../core/trust-manager.js";
28
+ import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
28
29
  import { copyToClipboard } from "../../utils/clipboard.js";
29
30
  import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
30
31
  import { parseGitUrl } from "../../utils/git.js";
@@ -59,6 +60,7 @@ import { SettingsSelectorComponent } from "./components/settings-selector.js";
59
60
  import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
60
61
  import { ToolExecutionComponent } from "./components/tool-execution.js";
61
62
  import { TreeSelectorComponent } from "./components/tree-selector.js";
63
+ import { TrustSelectorComponent } from "./components/trust-selector.js";
62
64
  import { UserMessageComponent } from "./components/user-message.js";
63
65
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
64
66
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
@@ -213,6 +215,7 @@ export class InteractiveMode {
213
215
  // Custom header from extension (undefined = use built-in header)
214
216
  customHeader = undefined;
215
217
  options;
218
+ autoTrustOnReloadCwd;
216
219
  // Convenience accessors
217
220
  get session() {
218
221
  return this.runtimeHost.session;
@@ -229,6 +232,7 @@ export class InteractiveMode {
229
232
  constructor(runtimeHost, options = {}) {
230
233
  this.runtimeHost = runtimeHost;
231
234
  this.options = options;
235
+ this.autoTrustOnReloadCwd = options.autoTrustOnReloadCwd;
232
236
  this.runtimeHost.setBeforeSessionInvalidate(() => {
233
237
  this.resetExtensionUI();
234
238
  });
@@ -368,8 +372,13 @@ export class InteractiveMode {
368
372
  }
369
373
  setupAutocompleteProvider() {
370
374
  let provider = this.createBaseAutocompleteProvider();
375
+ const triggerCharacters = [];
371
376
  for (const wrapProvider of this.autocompleteProviderWrappers) {
372
377
  provider = wrapProvider(provider);
378
+ triggerCharacters.push(...(provider.triggerCharacters ?? []));
379
+ }
380
+ if (triggerCharacters.length > 0) {
381
+ provider.triggerCharacters = [...new Set(triggerCharacters)];
373
382
  }
374
383
  this.autocompleteProvider = provider;
375
384
  this.defaultEditor.setAutocompleteProvider(provider);
@@ -668,7 +677,7 @@ export class InteractiveMode {
668
677
  if (newEntries.length > 0) {
669
678
  this.settingsManager.setLastChangelogVersion(VERSION);
670
679
  this.reportInstallTelemetry(VERSION);
671
- return newEntries.map((e) => e.content).join("\n\n");
680
+ return newEntries.map((e) => normalizeChangelogLinks(e.content, e)).join("\n\n");
672
681
  }
673
682
  return undefined;
674
683
  }
@@ -1276,6 +1285,7 @@ export class InteractiveMode {
1276
1285
  modelRegistry: this.session.modelRegistry,
1277
1286
  model: this.session.model,
1278
1287
  isIdle: () => !this.session.isStreaming,
1288
+ isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
1279
1289
  signal: this.session.agent.signal,
1280
1290
  abort: () => {
1281
1291
  this.restoreQueuedMessagesToEditor({ abort: true });
@@ -1555,6 +1565,20 @@ export class InteractiveMode {
1555
1565
  /**
1556
1566
  * Create the ExtensionUIContext for extensions.
1557
1567
  */
1568
+ createProjectTrustContext(cwd) {
1569
+ const ui = this.createExtensionUIContext();
1570
+ return {
1571
+ cwd,
1572
+ mode: "tui",
1573
+ hasUI: true,
1574
+ ui: {
1575
+ select: ui.select,
1576
+ confirm: ui.confirm,
1577
+ input: ui.input,
1578
+ notify: ui.notify,
1579
+ },
1580
+ };
1581
+ }
1558
1582
  createExtensionUIContext() {
1559
1583
  return {
1560
1584
  select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
@@ -2059,6 +2083,11 @@ export class InteractiveMode {
2059
2083
  this.editor.setText("");
2060
2084
  return;
2061
2085
  }
2086
+ if (text === "/trust") {
2087
+ this.showTrustSelector();
2088
+ this.editor.setText("");
2089
+ return;
2090
+ }
2062
2091
  if (text === "/login") {
2063
2092
  this.showOAuthSelector("login");
2064
2093
  this.editor.setText("");
@@ -2528,6 +2557,7 @@ export class InteractiveMode {
2528
2557
  this.chatContainer.addChild(component);
2529
2558
  // Render user message separately if present
2530
2559
  if (skillBlock.userMessage) {
2560
+ this.chatContainer.addChild(new Spacer(1));
2531
2561
  const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings());
2532
2562
  this.chatContainer.addChild(userComponent);
2533
2563
  }
@@ -2627,6 +2657,7 @@ export class InteractiveMode {
2627
2657
  updateFooter: true,
2628
2658
  populateHistory: true,
2629
2659
  });
2660
+ this.renderProjectTrustWarningIfNeeded();
2630
2661
  // Show compaction info if session was compacted
2631
2662
  const allEntries = this.sessionManager.getEntries();
2632
2663
  const compactionCount = allEntries.filter((e) => e.type === "compaction").length;
@@ -2635,6 +2666,15 @@ export class InteractiveMode {
2635
2666
  this.showStatus(`Session compacted ${times}`);
2636
2667
  }
2637
2668
  }
2669
+ renderProjectTrustWarningIfNeeded() {
2670
+ if (this.settingsManager.isProjectTrusted() || !hasProjectTrustInputs(this.sessionManager.getCwd())) {
2671
+ return;
2672
+ }
2673
+ if (this.chatContainer.children.length > 0) {
2674
+ this.chatContainer.addChild(new Spacer(1));
2675
+ }
2676
+ this.chatContainer.addChild(new Text(theme.fg("warning", "This project is not trusted. Project .pi resources and packages are ignored. Use /trust to save a trust decision, then restart pi."), 1, 0));
2677
+ }
2638
2678
  async getUserInput() {
2639
2679
  const queuedInput = this.pendingUserInputs.shift();
2640
2680
  if (queuedInput !== undefined) {
@@ -3244,6 +3284,7 @@ export class InteractiveMode {
3244
3284
  doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
3245
3285
  treeFilterMode: this.settingsManager.getTreeFilterMode(),
3246
3286
  showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
3287
+ defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
3247
3288
  editorPaddingX: this.settingsManager.getEditorPaddingX(),
3248
3289
  autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
3249
3290
  quietStartup: this.settingsManager.getQuietStartup(),
@@ -3336,6 +3377,9 @@ export class InteractiveMode {
3336
3377
  onQuietStartupChange: (enabled) => {
3337
3378
  this.settingsManager.setQuietStartup(enabled);
3338
3379
  },
3380
+ onDefaultProjectTrustChange: (defaultProjectTrust) => {
3381
+ this.settingsManager.setDefaultProjectTrust(defaultProjectTrust);
3382
+ },
3339
3383
  onDoubleEscapeActionChange: (action) => {
3340
3384
  this.settingsManager.setDoubleEscapeAction(action);
3341
3385
  },
@@ -3450,6 +3494,51 @@ export class InteractiveMode {
3450
3494
  // Ignore auth lookup failures for warning-only checks.
3451
3495
  }
3452
3496
  }
3497
+ maybeSaveImplicitProjectTrustAfterReload() {
3498
+ const cwd = this.sessionManager.getCwd();
3499
+ if (this.autoTrustOnReloadCwd !== cwd) {
3500
+ return false;
3501
+ }
3502
+ if (!this.settingsManager.isProjectTrusted() || !hasProjectConfigDir(cwd)) {
3503
+ return false;
3504
+ }
3505
+ const trustStore = new ProjectTrustStore(this.runtimeHost.services.agentDir);
3506
+ try {
3507
+ if (trustStore.get(cwd) !== null) {
3508
+ this.autoTrustOnReloadCwd = undefined;
3509
+ return false;
3510
+ }
3511
+ trustStore.set(cwd, true);
3512
+ this.autoTrustOnReloadCwd = undefined;
3513
+ return true;
3514
+ }
3515
+ catch (error) {
3516
+ this.showWarning(`Could not save project trust after reload: ${error instanceof Error ? error.message : String(error)}`);
3517
+ return false;
3518
+ }
3519
+ }
3520
+ showTrustSelector() {
3521
+ const cwd = this.sessionManager.getCwd();
3522
+ const trustStore = new ProjectTrustStore(this.runtimeHost.services.agentDir);
3523
+ const savedDecision = trustStore.getEntry(cwd);
3524
+ this.showSelector((done) => {
3525
+ const selector = new TrustSelectorComponent({
3526
+ cwd,
3527
+ savedDecision,
3528
+ projectTrusted: this.settingsManager.isProjectTrusted(),
3529
+ onSelect: (selection) => {
3530
+ trustStore.setMany(selection.updates);
3531
+ done();
3532
+ this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart pi for this to take effect.`);
3533
+ },
3534
+ onCancel: () => {
3535
+ done();
3536
+ this.ui.requestRender();
3537
+ },
3538
+ });
3539
+ return { component: selector, focus: selector };
3540
+ });
3541
+ }
3453
3542
  showModelSelector(initialSearchInput) {
3454
3543
  this.showSelector((done) => {
3455
3544
  const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, async (model) => {
@@ -3728,6 +3817,7 @@ export class InteractiveMode {
3728
3817
  try {
3729
3818
  const result = await this.runtimeHost.switchSession(sessionPath, {
3730
3819
  withSession: options?.withSession,
3820
+ projectTrustContextFactory: (cwd) => this.createProjectTrustContext(cwd),
3731
3821
  });
3732
3822
  if (result.cancelled) {
3733
3823
  return result;
@@ -3746,6 +3836,7 @@ export class InteractiveMode {
3746
3836
  const result = await this.runtimeHost.switchSession(sessionPath, {
3747
3837
  cwdOverride: selectedCwd,
3748
3838
  withSession: options?.withSession,
3839
+ projectTrustContextFactory: (cwd) => this.createProjectTrustContext(cwd),
3749
3840
  });
3750
3841
  if (result.cancelled) {
3751
3842
  return result;
@@ -4144,11 +4235,14 @@ export class InteractiveMode {
4144
4235
  force: false,
4145
4236
  showDiagnosticsWhenQuiet: true,
4146
4237
  });
4238
+ const savedImplicitProjectTrust = this.maybeSaveImplicitProjectTrustAfterReload();
4147
4239
  const modelsJsonError = this.session.modelRegistry.getError();
4148
4240
  if (modelsJsonError) {
4149
4241
  this.showError(`models.json error: ${modelsJsonError}`);
4150
4242
  }
4151
- this.showStatus("Reloaded keybindings, extensions, skills, prompts, themes");
4243
+ this.showStatus(savedImplicitProjectTrust
4244
+ ? "Reloaded keybindings, extensions, skills, prompts, themes; saved project trust"
4245
+ : "Reloaded keybindings, extensions, skills, prompts, themes");
4152
4246
  }
4153
4247
  catch (error) {
4154
4248
  dismissReloadBox(previousEditor);
@@ -4403,7 +4497,7 @@ export class InteractiveMode {
4403
4497
  const changelogMarkdown = allEntries.length > 0
4404
4498
  ? allEntries
4405
4499
  .reverse()
4406
- .map((e) => e.content)
4500
+ .map((e) => normalizeChangelogLinks(e.content, e))
4407
4501
  .join("\n\n")
4408
4502
  : "No changelog entries found.";
4409
4503
  this.chatContainer.addChild(new Spacer(1));
@@ -4470,7 +4564,7 @@ export class InteractiveMode {
4470
4564
  **Navigation**
4471
4565
  | Key | Action |
4472
4566
  |-----|--------|
4473
- | \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | Move cursor / browse history (Up when empty) |
4567
+ | \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | Move cursor / browse history |
4474
4568
  | \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
4475
4569
  | \`${cursorLineStart}\` | Start of line |
4476
4570
  | \`${cursorLineEnd}\` | End of line |