@sayknow-cli/coding-agent 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/bin/skc.js +4 -0
  2. package/dist/types/cli/mcp-cli.d.ts +25 -0
  3. package/dist/types/cli/plugin-cli.d.ts +2 -0
  4. package/dist/types/cli.d.ts +6 -0
  5. package/dist/types/commands/mcp.d.ts +70 -0
  6. package/dist/types/commands/plugin.d.ts +6 -0
  7. package/dist/types/commands/session.d.ts +6 -0
  8. package/dist/types/config/keybindings.d.ts +2 -2
  9. package/dist/types/config/model-profile-activation.d.ts +8 -1
  10. package/dist/types/config/settings-schema.d.ts +1 -59
  11. package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
  12. package/dist/types/extensibility/skc-plugins/compiler.d.ts +19 -0
  13. package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +29 -0
  14. package/dist/types/extensibility/skc-plugins/index.d.ts +9 -0
  15. package/dist/types/extensibility/skc-plugins/injection.d.ts +9 -0
  16. package/dist/types/extensibility/skc-plugins/installer.d.ts +13 -0
  17. package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +26 -0
  18. package/dist/types/extensibility/skc-plugins/observability.d.ts +27 -0
  19. package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +16 -0
  20. package/dist/types/extensibility/skc-plugins/registry.d.ts +32 -0
  21. package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +64 -0
  22. package/dist/types/extensibility/skc-plugins/session-validation.d.ts +42 -0
  23. package/dist/types/extensibility/skc-plugins/types.d.ts +158 -2
  24. package/dist/types/extensibility/skc-plugins/validation.d.ts +8 -1
  25. package/dist/types/i18n/messages/en.d.ts +0 -1
  26. package/dist/types/main.d.ts +2 -0
  27. package/dist/types/modes/components/custom-editor.d.ts +1 -1
  28. package/dist/types/modes/components/model-selector.d.ts +8 -0
  29. package/dist/types/modes/components/settings-defs.d.ts +0 -1
  30. package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
  31. package/dist/types/modes/theme/defaults/index.d.ts +99 -0
  32. package/dist/types/modes/theme/theme.d.ts +1 -1
  33. package/dist/types/notifications/html-format.d.ts +11 -0
  34. package/dist/types/notifications/index.d.ts +149 -1
  35. package/dist/types/notifications/lifecycle-commands.d.ts +60 -0
  36. package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
  37. package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
  38. package/dist/types/notifications/operator-runtime.d.ts +52 -0
  39. package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
  40. package/dist/types/notifications/recent-activity.d.ts +35 -0
  41. package/dist/types/notifications/telegram-daemon.d.ts +114 -16
  42. package/dist/types/notifications/telegram-reference.d.ts +3 -1
  43. package/dist/types/notifications/topic-registry.d.ts +12 -9
  44. package/dist/types/runtime-mcp/types.d.ts +7 -0
  45. package/dist/types/sdk.d.ts +2 -0
  46. package/dist/types/session/agent-session.d.ts +14 -4
  47. package/dist/types/session/blob-store.d.ts +25 -0
  48. package/dist/types/session/session-manager.d.ts +57 -0
  49. package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
  50. package/dist/types/skc-runtime/psmux-detect.d.ts +78 -0
  51. package/dist/types/skc-runtime/team-runtime.d.ts +2 -0
  52. package/dist/types/skc-runtime/tmux-common.d.ts +30 -2
  53. package/dist/types/skc-runtime/tmux-sessions.d.ts +18 -0
  54. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
  55. package/dist/types/system-prompt.d.ts +2 -0
  56. package/dist/types/task/executor.d.ts +9 -1
  57. package/dist/types/tools/composer-bash-policy.d.ts +14 -0
  58. package/dist/types/tools/index.d.ts +3 -1
  59. package/dist/types/utils/changelog.d.ts +1 -0
  60. package/dist/types/web/insane/url-guard.d.ts +6 -3
  61. package/dist/types/web/scrapers/types.d.ts +5 -0
  62. package/dist/types/web/scrapers/utils.d.ts +7 -1
  63. package/package.json +11 -9
  64. package/scripts/g004-tmux-smoke.ts +100 -0
  65. package/scripts/g005-daemon-smoke.ts +180 -0
  66. package/scripts/g011-daemon-path-smoke.ts +153 -0
  67. package/src/cli/fast-help.ts +29 -1
  68. package/src/cli/mcp-cli.ts +272 -0
  69. package/src/cli/plugin-cli.ts +66 -3
  70. package/src/cli.ts +30 -12
  71. package/src/commands/mcp.ts +117 -0
  72. package/src/commands/plugin.ts +4 -0
  73. package/src/commands/session.ts +18 -0
  74. package/src/config/keybindings.ts +2 -2
  75. package/src/config/model-profile-activation.ts +55 -7
  76. package/src/config/settings-schema.ts +8 -93
  77. package/src/deep-interview/plaintext-gate-guard.ts +94 -0
  78. package/src/defaults/skc/extensions/grok-cli-vendor/biome.json +1 -1
  79. package/src/defaults/skc/skills/deep-interview/SKILL.md +7 -6
  80. package/src/defaults/skc/skills/team/SKILL.md +5 -3
  81. package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
  82. package/src/export/html/index.ts +2 -2
  83. package/src/extensibility/extensions/runner.ts +1 -0
  84. package/src/extensibility/skc-plugins/compiler.ts +351 -0
  85. package/src/extensibility/skc-plugins/constrained-hooks.ts +170 -0
  86. package/src/extensibility/skc-plugins/index.ts +9 -0
  87. package/src/extensibility/skc-plugins/injection.ts +109 -0
  88. package/src/extensibility/skc-plugins/installer.ts +434 -0
  89. package/src/extensibility/skc-plugins/loader.ts +3 -1
  90. package/src/extensibility/skc-plugins/mcp-policy.ts +239 -0
  91. package/src/extensibility/skc-plugins/observability.ts +84 -0
  92. package/src/extensibility/skc-plugins/paths.ts +1 -1
  93. package/src/extensibility/skc-plugins/prompt-appendix.ts +109 -0
  94. package/src/extensibility/skc-plugins/registry.ts +180 -0
  95. package/src/extensibility/skc-plugins/runtime-adapters.ts +234 -0
  96. package/src/extensibility/skc-plugins/schema.ts +250 -20
  97. package/src/extensibility/skc-plugins/session-validation.ts +147 -0
  98. package/src/extensibility/skc-plugins/types.ts +199 -3
  99. package/src/extensibility/skc-plugins/validation.ts +80 -0
  100. package/src/extensibility/skills.ts +15 -0
  101. package/src/hooks/skill-state.ts +57 -0
  102. package/src/i18n/messages/de.ts +0 -1
  103. package/src/i18n/messages/en.ts +0 -1
  104. package/src/i18n/messages/es.ts +0 -1
  105. package/src/i18n/messages/fr.ts +0 -1
  106. package/src/i18n/messages/ja.ts +0 -1
  107. package/src/i18n/messages/ko.ts +0 -1
  108. package/src/i18n/messages/zh.ts +0 -1
  109. package/src/internal-urls/docs-index.generated.ts +12 -8
  110. package/src/main.ts +14 -3
  111. package/src/modes/bridge/bridge-mode.ts +11 -0
  112. package/src/modes/components/assistant-message.ts +49 -1
  113. package/src/modes/components/custom-editor.ts +2 -0
  114. package/src/modes/components/footer.ts +2 -3
  115. package/src/modes/components/hook-editor.ts +1 -1
  116. package/src/modes/components/hook-selector.ts +67 -43
  117. package/src/modes/components/model-selector.ts +56 -11
  118. package/src/modes/components/settings-defs.ts +1 -2
  119. package/src/modes/components/settings-selector.ts +2 -7
  120. package/src/modes/components/status-line/git-utils.ts +25 -0
  121. package/src/modes/components/status-line.ts +10 -11
  122. package/src/modes/components/welcome.ts +2 -3
  123. package/src/modes/controllers/extension-ui-controller.ts +0 -27
  124. package/src/modes/controllers/selector-controller.ts +53 -11
  125. package/src/modes/interactive-mode.ts +4 -1
  126. package/src/modes/shared/agent-wire/scopes.ts +1 -1
  127. package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
  128. package/src/modes/theme/defaults/index.ts +2 -0
  129. package/src/modes/theme/theme.ts +0 -4
  130. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  131. package/src/notifications/html-format.ts +38 -0
  132. package/src/notifications/index.ts +242 -12
  133. package/src/notifications/lifecycle-commands.ts +238 -0
  134. package/src/notifications/lifecycle-control-runtime.ts +405 -0
  135. package/src/notifications/lifecycle-orchestrator.ts +358 -0
  136. package/src/notifications/operator-runtime.ts +171 -0
  137. package/src/notifications/rate-limit-pool.ts +19 -0
  138. package/src/notifications/recent-activity.ts +132 -0
  139. package/src/notifications/telegram-daemon.ts +778 -257
  140. package/src/notifications/telegram-reference.ts +25 -7
  141. package/src/notifications/topic-registry.ts +23 -9
  142. package/src/prompts/agents/executor.md +2 -2
  143. package/src/runtime-mcp/transports/stdio.ts +38 -4
  144. package/src/runtime-mcp/types.ts +7 -0
  145. package/src/sdk.ts +157 -10
  146. package/src/session/agent-session.ts +166 -74
  147. package/src/session/blob-store.ts +196 -8
  148. package/src/session/session-manager.ts +762 -12
  149. package/src/skc-runtime/launch-tmux.ts +68 -19
  150. package/src/skc-runtime/psmux-detect.ts +239 -0
  151. package/src/skc-runtime/team-runtime.ts +56 -23
  152. package/src/skc-runtime/tmux-common.ts +88 -4
  153. package/src/skc-runtime/tmux-sessions.ts +111 -9
  154. package/src/skc-runtime/ultragoal-guard.ts +25 -8
  155. package/src/skc-runtime/ultragoal-runtime.ts +75 -15
  156. package/src/slash-commands/builtin-registry.ts +23 -3
  157. package/src/slash-commands/helpers/fast-status-report.ts +13 -3
  158. package/src/slash-commands/helpers/parse.ts +2 -1
  159. package/src/system-prompt.ts +9 -0
  160. package/src/task/executor.ts +31 -7
  161. package/src/task/index.ts +2 -0
  162. package/src/tools/ask.ts +5 -1
  163. package/src/tools/bash.ts +9 -0
  164. package/src/tools/composer-bash-policy.ts +96 -0
  165. package/src/tools/fetch.ts +18 -2
  166. package/src/tools/index.ts +3 -1
  167. package/src/utils/changelog.ts +8 -0
  168. package/src/web/insane/url-guard.ts +18 -14
  169. package/src/web/scrapers/types.ts +143 -45
  170. package/src/web/scrapers/utils.ts +70 -19
@@ -4,8 +4,10 @@ import type { OAuthProvider } from "@sayknow-cli/ai/utils/oauth/types";
4
4
  import type { Component, OverlayHandle } from "@sayknow-cli/tui";
5
5
  import { Input, Loader, Spacer, Text } from "@sayknow-cli/tui";
6
6
  import { getAgentDbPath, getProjectDir } from "@sayknow-cli/utils";
7
- import { activateModelProfile } from "../../config/model-profile-activation";
7
+ import { activateModelProfile, materializeActiveModelProfileAssignment } from "../../config/model-profile-activation";
8
8
  import { recommendModelProfileForProvider } from "../../config/model-profiles";
9
+ import { SKC_MODEL_ASSIGNMENT_TARGETS } from "../../config/model-registry";
10
+ import { formatModelSelectorValue } from "../../config/model-resolver";
9
11
  import { settings } from "../../config/settings";
10
12
  import { DebugSelectorComponent } from "../../debug";
11
13
  import { disableProvider, enableProvider } from "../../discovery";
@@ -667,7 +669,8 @@ export class SelectorController {
667
669
 
668
670
  showModelSelector(options?: { temporaryOnly?: boolean }): void {
669
671
  this.showSelector(done => {
670
- const selector = new ModelSelectorComponent(
672
+ let modelSelector: ModelSelectorComponent;
673
+ modelSelector = new ModelSelectorComponent(
671
674
  this.ctx.ui,
672
675
  this.ctx.session.model,
673
676
  this.ctx.settings,
@@ -717,38 +720,73 @@ export class SelectorController {
717
720
  this.ctx.ui.requestRender();
718
721
  return;
719
722
  }
720
- const { model, role, thinkingLevel, selector } = selection;
723
+ const { model, role, thinkingLevel, selector: selectedSelector } = selection;
721
724
  if (role === null) {
722
725
  // Temporary: update agent state but don't persist to settings
723
726
  await this.ctx.session.setModelTemporary(model, thinkingLevel);
724
727
  this.ctx.statusLine.invalidate();
725
728
  this.ctx.updateEditorBorderColor();
726
- this.ctx.showStatus(`Temporary model: ${selector ?? model.id}`);
729
+ this.ctx.showStatus(`Temporary model: ${selectedSelector ?? model.id}`);
727
730
  done();
728
731
  this.ctx.ui.requestRender();
729
732
  } else if (role === "default") {
730
733
  // Default: update agent state and persist as the active default model.
731
734
  await this.ctx.session.setModel(model, role, {
732
- selector,
735
+ selector: selectedSelector,
733
736
  thinkingLevel,
734
737
  });
738
+ const value = formatModelSelectorValue(
739
+ selectedSelector ?? `${model.provider}/${model.id}`,
740
+ thinkingLevel,
741
+ );
742
+ materializeActiveModelProfileAssignment({
743
+ session: this.ctx.session,
744
+ settings: this.ctx.settings,
745
+ role,
746
+ selector: value,
747
+ });
735
748
  if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
736
749
  this.ctx.session.setThinkingLevel(thinkingLevel);
737
750
  }
751
+ modelSelector.refreshRoleAssignments({
752
+ currentModel: this.ctx.session.model,
753
+ currentThinkingLevel: this.ctx.session.thinkingLevel,
754
+ activeModelProfile:
755
+ this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
756
+ });
738
757
  this.ctx.statusLine.invalidate();
739
758
  this.ctx.updateEditorBorderColor();
740
- this.ctx.showStatus(`Default model: ${selector ?? model.id}`);
759
+ this.ctx.showStatus(`Default model: ${selectedSelector ?? model.id}`);
741
760
  done();
742
761
  this.ctx.ui.requestRender();
743
762
  } else {
744
- // Role-agent assignments configure Task dispatch and must not switch the active chat model.
745
763
  const apiKey = await this.ctx.session.modelRegistry.getApiKey(model, this.ctx.session.sessionId);
746
764
  if (!apiKey) {
747
765
  throw new Error(`No API key for ${model.provider}/${model.id}`);
748
766
  }
749
- const overrides = this.ctx.settings.get("task.agentModelOverrides");
750
- const value = selector ?? `${model.provider}/${model.id}`;
751
- this.ctx.settings.set("task.agentModelOverrides", { ...overrides, [role]: value });
767
+ const value =
768
+ selectedSelector ?? formatModelSelectorValue(`${model.provider}/${model.id}`, thinkingLevel);
769
+ const materializedProfile = materializeActiveModelProfileAssignment({
770
+ session: this.ctx.session,
771
+ settings: this.ctx.settings,
772
+ role,
773
+ selector: value,
774
+ });
775
+ if (!materializedProfile) {
776
+ const target = SKC_MODEL_ASSIGNMENT_TARGETS[role];
777
+ if (target.settingsPath === "modelRoles") {
778
+ this.ctx.settings.setModelRole(role, value);
779
+ } else {
780
+ const overrides = this.ctx.settings.get("task.agentModelOverrides");
781
+ this.ctx.settings.set("task.agentModelOverrides", { ...overrides, [role]: value });
782
+ }
783
+ }
784
+ modelSelector.refreshRoleAssignments({
785
+ currentModel: this.ctx.session.model,
786
+ currentThinkingLevel: this.ctx.session.thinkingLevel,
787
+ activeModelProfile:
788
+ this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
789
+ });
752
790
  this.ctx.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
753
791
  this.ctx.showStatus(`${role} agent model: ${value}`);
754
792
  done();
@@ -765,11 +803,15 @@ export class SelectorController {
765
803
  {
766
804
  ...options,
767
805
  sessionId: this.ctx.session.sessionId,
806
+ currentThinkingLevel: this.ctx.session.thinkingLevel,
807
+ activeModelProfile:
808
+ this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
768
809
  isFastForProvider: provider => this.ctx.session.isFastForProvider(provider),
769
810
  isFastForSubagentProvider: provider => this.ctx.session.isFastForSubagentProvider(provider),
811
+ isCurrentModelFastModeActive: () => this.ctx.session.isFastModeActive(),
770
812
  },
771
813
  );
772
- return { component: selector, focus: selector };
814
+ return { component: modelSelector, focus: modelSelector };
773
815
  });
774
816
  }
775
817
 
@@ -156,7 +156,7 @@ function getShellInputPrefix(isNoContext: boolean): string {
156
156
 
157
157
  function configureDefaultComposerChrome(editor: CustomEditor): void {
158
158
  editor.setBorderVisible(true);
159
- editor.setBorderStyle("sharp");
159
+ editor.setBorderStyle("round");
160
160
  editor.setClosedBorderBox(true);
161
161
  editor.setPromptGutter(undefined);
162
162
  editor.setInputPrefix(getDefaultInputPrefix());
@@ -432,6 +432,8 @@ export class InteractiveMode implements InteractiveModeContext {
432
432
  this.#resizeHandler = () => {
433
433
  this.#syncEditorMaxHeight();
434
434
  this.updateEditorChrome();
435
+ this.editor.invalidate();
436
+ this.ui.requestRender(true, "resize");
435
437
  };
436
438
  process.stdout.on("resize", this.#resizeHandler);
437
439
  try {
@@ -569,6 +571,7 @@ export class InteractiveMode implements InteractiveModeContext {
569
571
  this.ui.addChild(this.hookWidgetContainerAbove);
570
572
  this.ui.addChild(this.editorContainer);
571
573
  this.ui.addChild(this.hookWidgetContainerBelow);
574
+ this.ui.setBottomPinnedComponent(this.statusLine);
572
575
  this.ui.setFocus(this.editor);
573
576
 
574
577
  this.#inputController.setupKeyHandlers();
@@ -73,7 +73,7 @@ const RPC_COMMAND_SCOPE_REGISTRY: Record<RpcCommandType, BridgeCommandScope> = {
73
73
  get_login_providers: "admin",
74
74
  login: "admin",
75
75
  negotiate_unattended: "control",
76
- workflow_gate_response: "prompt",
76
+ workflow_gate_response: "control",
77
77
  };
78
78
 
79
79
  export const RPC_COMMAND_TYPES: readonly RpcCommandType[] = Object.keys(RPC_COMMAND_SCOPE_REGISTRY) as RpcCommandType[];
@@ -0,0 +1,99 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/jaybeyond/Sayknow_CLI/main/packages/coding-agent/theme-schema.json",
3
+ "name": "gruvbox-dark",
4
+ "vars": {
5
+ "bgHard": "#1d2021",
6
+ "surface": "#32302f",
7
+ "surfaceBright": "#504945",
8
+ "borderNeutral": "#504945",
9
+ "borderSubtle": "#3c3836",
10
+ "fg": "#ebdbb2",
11
+ "muted": "#a89984",
12
+ "dim": "#7c6f64",
13
+ "gray": "#928374",
14
+ "red": "#fb4934",
15
+ "green": "#b8bb26",
16
+ "yellow": "#fabd2f",
17
+ "blue": "#83a598",
18
+ "purple": "#d3869b",
19
+ "aqua": "#8ec07c",
20
+ "orange": "#fe8019",
21
+ "diffRemovalRed": "#cc241d"
22
+ },
23
+ "colors": {
24
+ "accent": "orange",
25
+ "border": "borderNeutral",
26
+ "borderAccent": "orange",
27
+ "borderMuted": "borderSubtle",
28
+ "success": "green",
29
+ "error": "red",
30
+ "warning": "yellow",
31
+ "muted": "muted",
32
+ "dim": "dim",
33
+ "text": "fg",
34
+ "thinkingText": "muted",
35
+ "selectedBg": "surfaceBright",
36
+ "userMessageBg": "surface",
37
+ "userMessageText": "fg",
38
+ "customMessageBg": "surface",
39
+ "customMessageText": "fg",
40
+ "customMessageLabel": "orange",
41
+ "toolPendingBg": "surface",
42
+ "toolSuccessBg": "#283626",
43
+ "toolErrorBg": "#3c2323",
44
+ "toolTitle": "fg",
45
+ "toolOutput": "muted",
46
+ "mdHeading": "yellow",
47
+ "mdLink": "blue",
48
+ "mdLinkUrl": "muted",
49
+ "mdCode": "aqua",
50
+ "mdCodeBlock": "fg",
51
+ "mdCodeBlockBorder": "borderNeutral",
52
+ "mdQuote": "muted",
53
+ "mdQuoteBorder": "borderNeutral",
54
+ "mdHr": "dim",
55
+ "mdListBullet": "orange",
56
+ "toolDiffAdded": "green",
57
+ "toolDiffRemoved": "diffRemovalRed",
58
+ "toolDiffContext": "muted",
59
+ "syntaxComment": "gray",
60
+ "syntaxKeyword": "red",
61
+ "syntaxFunction": "green",
62
+ "syntaxVariable": "blue",
63
+ "syntaxString": "green",
64
+ "syntaxNumber": "purple",
65
+ "syntaxType": "yellow",
66
+ "syntaxOperator": "aqua",
67
+ "syntaxPunctuation": "muted",
68
+ "thinkingOff": "dim",
69
+ "thinkingMinimal": "muted",
70
+ "thinkingLow": "aqua",
71
+ "thinkingMedium": "yellow",
72
+ "thinkingHigh": "orange",
73
+ "thinkingXhigh": "red",
74
+ "bashMode": "green",
75
+ "pythonMode": "yellow",
76
+ "statusLineBg": "bgHard",
77
+ "statusLineSep": "dim",
78
+ "statusLineModel": "orange",
79
+ "statusLinePath": "blue",
80
+ "statusLineGitClean": "green",
81
+ "statusLineGitDirty": "yellow",
82
+ "statusLineContext": "aqua",
83
+ "statusLineSpend": "yellow",
84
+ "statusLineStaged": "green",
85
+ "statusLineDirty": "yellow",
86
+ "statusLineUntracked": "diffRemovalRed",
87
+ "statusLineOutput": "fg",
88
+ "statusLineCost": "orange",
89
+ "statusLineSubagents": "purple"
90
+ },
91
+ "export": {
92
+ "pageBg": "#1d2021",
93
+ "cardBg": "#282828",
94
+ "infoBg": "#32302f"
95
+ },
96
+ "symbols": {
97
+ "preset": "unicode"
98
+ }
99
+ }
@@ -1,6 +1,7 @@
1
1
  import blue_octopus from "./blue-octopus.json" with { type: "json" };
2
2
  import claude_code from "./claude-code.json" with { type: "json" };
3
3
  import codex from "./codex.json" with { type: "json" };
4
+ import gruvbox_dark from "./gruvbox-dark.json" with { type: "json" };
4
5
  import opencode from "./opencode.json" with { type: "json" };
5
6
  import red_octopus from "./red-octopus.json" with { type: "json" };
6
7
 
@@ -8,6 +9,7 @@ export const defaultThemes = {
8
9
  "blue-octopus": blue_octopus,
9
10
  "claude-code": claude_code,
10
11
  codex,
12
+ "gruvbox-dark": gruvbox_dark,
11
13
  opencode,
12
14
  "red-octopus": red_octopus,
13
15
  };
@@ -183,7 +183,6 @@ export type SymbolKey =
183
183
  | "tab.appearance"
184
184
  | "tab.model"
185
185
  | "tab.interaction"
186
- | "tab.notifications"
187
186
  | "tab.context"
188
187
  | "tab.editing"
189
188
  | "tab.tools"
@@ -348,7 +347,6 @@ const UNICODE_SYMBOLS: SymbolMap = {
348
347
  "tab.appearance": "🎨",
349
348
  "tab.model": "🤖",
350
349
  "tab.interaction": "⌨",
351
- "tab.notifications": "🔔",
352
350
  "tab.context": "📋",
353
351
  "tab.editing": "💻",
354
352
  "tab.tools": "🔧",
@@ -608,7 +606,6 @@ const NERD_SYMBOLS: SymbolMap = {
608
606
  "tab.appearance": "󰃣",
609
607
  "tab.model": "󰚩",
610
608
  "tab.interaction": "󰌌",
611
- "tab.notifications": "󰂚",
612
609
  "tab.context": "󰘸",
613
610
  "tab.editing": "",
614
611
  "tab.tools": "󰠭",
@@ -771,7 +768,6 @@ const ASCII_SYMBOLS: SymbolMap = {
771
768
  "tab.appearance": "[A]",
772
769
  "tab.model": "[M]",
773
770
  "tab.interaction": "[I]",
774
- "tab.notifications": "[N]",
775
771
  "tab.context": "[X]",
776
772
  "tab.editing": "[E]",
777
773
  "tab.tools": "[T]",
@@ -23,7 +23,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
23
23
  "|-----|--------|",
24
24
  "| `Enter` | Send / queue while busy |",
25
25
  `| \`${appKey(bindings, "app.message.queue")}\` | Queue message for next turn |`,
26
- "| `Shift+Enter` | New line |",
26
+ "| `Shift+Enter` / `Ctrl+J` | New line |",
27
27
  "| `Ctrl+W` / `Option+Backspace` | Delete word backwards |",
28
28
  "| `Ctrl+U` | Delete to start of line |",
29
29
  "| `Ctrl+K` | Delete to end of line |",
@@ -349,16 +349,54 @@ export function buttonLabel(label: string, index: number): string {
349
349
  return `${index + 1}. ${stripped}`;
350
350
  }
351
351
 
352
+ /** Numbered, escaped option list for the Telegram message body. */
353
+ export function numberedOptionList(labels: string[]): string {
354
+ return labels.map((label, i) => `${i + 1}. ${escapeHtml(label.replace(/^\s*\d+[.)]\s+/, ""))}`).join("\n");
355
+ }
356
+
357
+ /** Compact numeric button label; full option text belongs in the message body. */
358
+ export function choiceButtonLabel(index: number): string {
359
+ return String(index + 1);
360
+ }
361
+
352
362
  export interface InlineButton {
353
363
  text: string;
354
364
  callback_data: string;
355
365
  }
356
366
 
367
+ const COMPACT_BUTTONS_PER_ROW = 5;
368
+
357
369
  /** A prefixed button label is "long" when it is wide or contains a newline. */
358
370
  function isLongLabel(label: string): boolean {
359
371
  return label.length > 18 || /[\r\n]/.test(label);
360
372
  }
361
373
 
374
+ /**
375
+ * Lay out option callbacks as compact numeric buttons. Telegram mobile clients
376
+ * ellipsize long inline-keyboard labels and tall keyboards can be obscured by
377
+ * the composer, so the full choice text is rendered in the message body while
378
+ * the keyboard keeps only stable one-based tap targets.
379
+ */
380
+ export function buildCompactChoiceGrid(
381
+ labels: string[],
382
+ callbackForIndex: (index: number) => string,
383
+ ): InlineButton[][] {
384
+ const rows: InlineButton[][] = [];
385
+ let run: InlineButton[] = [];
386
+ const flush = () => {
387
+ if (run.length) {
388
+ rows.push(run);
389
+ run = [];
390
+ }
391
+ };
392
+ labels.forEach((_label, i) => {
393
+ run.push({ text: choiceButtonLabel(i), callback_data: callbackForIndex(i) });
394
+ if (run.length === COMPACT_BUTTONS_PER_ROW) flush();
395
+ });
396
+ flush();
397
+ return rows;
398
+ }
399
+
362
400
  /**
363
401
  * Lay out option labels as a numbered button grid. Long buttons take a
364
402
  * full-width row; runs of short buttons are packed into rows of up to 3. The