@slycode/slycode 0.2.21 → 0.2.22

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 (189) hide show
  1. package/dist/bridge/provider-utils.d.ts +10 -0
  2. package/dist/bridge/provider-utils.js +5 -1
  3. package/dist/bridge/provider-utils.js.map +1 -1
  4. package/dist/bridge/session-manager.js +6 -0
  5. package/dist/bridge/session-manager.js.map +1 -1
  6. package/dist/bridge/types.d.ts +4 -0
  7. package/dist/messaging/bridge-client.d.ts +3 -3
  8. package/dist/messaging/bridge-client.js +6 -4
  9. package/dist/messaging/bridge-client.js.map +1 -1
  10. package/dist/messaging/index.js +111 -20
  11. package/dist/messaging/index.js.map +1 -1
  12. package/dist/messaging/state.d.ts +3 -0
  13. package/dist/messaging/state.js +13 -0
  14. package/dist/messaging/state.js.map +1 -1
  15. package/dist/messaging/types.d.ts +3 -0
  16. package/dist/web/.next/BUILD_ID +1 -1
  17. package/dist/web/.next/build-manifest.json +2 -2
  18. package/dist/web/.next/server/app/_global-error.html +2 -2
  19. package/dist/web/.next/server/app/_global-error.rsc +1 -1
  20. package/dist/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  21. package/dist/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  22. package/dist/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  23. package/dist/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  24. package/dist/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  25. package/dist/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  26. package/dist/web/.next/server/app/_not-found.html +1 -1
  27. package/dist/web/.next/server/app/_not-found.rsc +2 -2
  28. package/dist/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  29. package/dist/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  30. package/dist/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  31. package/dist/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  32. package/dist/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  33. package/dist/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  34. package/dist/web/.next/server/app/api/areas/route.js +1 -1
  35. package/dist/web/.next/server/app/api/areas/route.js.nft.json +1 -1
  36. package/dist/web/.next/server/app/api/bridge/[...path]/route.js +1 -1
  37. package/dist/web/.next/server/app/api/bridge/[...path]/route.js.nft.json +1 -1
  38. package/dist/web/.next/server/app/api/cli-assets/assistant/route.js +1 -1
  39. package/dist/web/.next/server/app/api/cli-assets/assistant/route.js.nft.json +1 -1
  40. package/dist/web/.next/server/app/api/cli-assets/fix/route.js +1 -1
  41. package/dist/web/.next/server/app/api/cli-assets/fix/route.js.nft.json +1 -1
  42. package/dist/web/.next/server/app/api/cli-assets/import/route.js +1 -1
  43. package/dist/web/.next/server/app/api/cli-assets/import/route.js.nft.json +1 -1
  44. package/dist/web/.next/server/app/api/cli-assets/route.js +2 -2
  45. package/dist/web/.next/server/app/api/cli-assets/route.js.nft.json +1 -1
  46. package/dist/web/.next/server/app/api/cli-assets/store/preview/route.js +1 -1
  47. package/dist/web/.next/server/app/api/cli-assets/store/preview/route.js.nft.json +1 -1
  48. package/dist/web/.next/server/app/api/cli-assets/store/route.js +2 -2
  49. package/dist/web/.next/server/app/api/cli-assets/store/route.js.nft.json +1 -1
  50. package/dist/web/.next/server/app/api/cli-assets/sync/route.js +1 -1
  51. package/dist/web/.next/server/app/api/cli-assets/sync/route.js.nft.json +1 -1
  52. package/dist/web/.next/server/app/api/cli-assets/updates/route.js +2 -2
  53. package/dist/web/.next/server/app/api/cli-assets/updates/route.js.nft.json +1 -1
  54. package/dist/web/.next/server/app/api/dashboard/route.js +2 -2
  55. package/dist/web/.next/server/app/api/dashboard/route.js.nft.json +1 -1
  56. package/dist/web/.next/server/app/api/events/route.js +1 -1
  57. package/dist/web/.next/server/app/api/events/route.js.nft.json +1 -1
  58. package/dist/web/.next/server/app/api/file/route.js +1 -1
  59. package/dist/web/.next/server/app/api/file/route.js.nft.json +1 -1
  60. package/dist/web/.next/server/app/api/git-status/route.js +1 -1
  61. package/dist/web/.next/server/app/api/git-status/route.js.nft.json +1 -1
  62. package/dist/web/.next/server/app/api/kanban/route.js +1 -1
  63. package/dist/web/.next/server/app/api/kanban/route.js.nft.json +1 -1
  64. package/dist/web/.next/server/app/api/kanban/stream/route.js +1 -1
  65. package/dist/web/.next/server/app/api/kanban/stream/route.js.nft.json +1 -1
  66. package/dist/web/.next/server/app/api/projects/[id]/route.js +2 -2
  67. package/dist/web/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
  68. package/dist/web/.next/server/app/api/projects/analyze/route.js +1 -1
  69. package/dist/web/.next/server/app/api/projects/analyze/route.js.nft.json +1 -1
  70. package/dist/web/.next/server/app/api/projects/reorder/route.js +2 -2
  71. package/dist/web/.next/server/app/api/projects/reorder/route.js.nft.json +1 -1
  72. package/dist/web/.next/server/app/api/projects/route.js +2 -2
  73. package/dist/web/.next/server/app/api/projects/route.js.nft.json +1 -1
  74. package/dist/web/.next/server/app/api/providers/route.js +1 -1
  75. package/dist/web/.next/server/app/api/providers/route.js.nft.json +1 -1
  76. package/dist/web/.next/server/app/api/scheduler/route.js +1 -1
  77. package/dist/web/.next/server/app/api/scheduler/route.js.nft.json +1 -1
  78. package/dist/web/.next/server/app/api/search/route.js +1 -1
  79. package/dist/web/.next/server/app/api/search/route.js.nft.json +1 -1
  80. package/dist/web/.next/server/app/api/settings/route.js +1 -1
  81. package/dist/web/.next/server/app/api/settings/route.js.nft.json +1 -1
  82. package/dist/web/.next/server/app/api/sly-actions/invalidate/route.js +1 -1
  83. package/dist/web/.next/server/app/api/sly-actions/invalidate/route.js.nft.json +1 -1
  84. package/dist/web/.next/server/app/api/sly-actions/route.js +1 -1
  85. package/dist/web/.next/server/app/api/sly-actions/route.js.nft.json +1 -1
  86. package/dist/web/.next/server/app/api/sly-actions/stream/route.js +1 -1
  87. package/dist/web/.next/server/app/api/sly-actions/stream/route.js.nft.json +1 -1
  88. package/dist/web/.next/server/app/api/system-stats/route.js +1 -1
  89. package/dist/web/.next/server/app/api/system-stats/route.js.nft.json +1 -1
  90. package/dist/web/.next/server/app/api/terminal-classes/route.js +1 -1
  91. package/dist/web/.next/server/app/api/terminal-classes/route.js.nft.json +1 -1
  92. package/dist/web/.next/server/app/api/transcribe/route.js +5 -5
  93. package/dist/web/.next/server/app/api/transcribe/route.js.nft.json +1 -1
  94. package/dist/web/.next/server/app/api/version-check/route.js +1 -1
  95. package/dist/web/.next/server/app/api/version-check/route.js.nft.json +1 -1
  96. package/dist/web/.next/server/app/page.js +1 -1
  97. package/dist/web/.next/server/app/page.js.nft.json +1 -1
  98. package/dist/web/.next/server/app/page_client-reference-manifest.js +1 -1
  99. package/dist/web/.next/server/app/project/[id]/page.js +2 -2
  100. package/dist/web/.next/server/app/project/[id]/page.js.nft.json +1 -1
  101. package/dist/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
  102. package/dist/web/.next/server/chunks/{[externals]__c6831f39._.js → [externals]__78e522ea._.js} +2 -2
  103. package/dist/web/.next/server/chunks/{[root-of-the-server]__1ec21ccc._.js → [root-of-the-server]__029203cd._.js} +3 -3
  104. package/dist/web/.next/server/chunks/{[root-of-the-server]__4297cb97._.js → [root-of-the-server]__0d6d4443._.js} +1 -1
  105. package/dist/web/.next/server/chunks/[root-of-the-server]__172ad0b1._.js +18 -0
  106. package/dist/web/.next/server/chunks/[root-of-the-server]__1c5f4ef9._.js +3 -0
  107. package/dist/web/.next/server/chunks/[root-of-the-server]__1cab11f0._.js +3 -0
  108. package/dist/web/.next/server/chunks/{[root-of-the-server]__0f69c28a._.js → [root-of-the-server]__1eb3f172._.js} +2 -2
  109. package/dist/web/.next/server/chunks/[root-of-the-server]__22cba275._.js +3 -0
  110. package/dist/web/.next/server/chunks/[root-of-the-server]__2543e413._.js +3 -0
  111. package/dist/web/.next/server/chunks/[root-of-the-server]__2c42a835._.js +3 -0
  112. package/dist/web/.next/server/chunks/[root-of-the-server]__2ed0ff47._.js +3 -0
  113. package/dist/web/.next/server/chunks/[root-of-the-server]__35454eea._.js +27 -0
  114. package/dist/web/.next/server/chunks/[root-of-the-server]__35768b56._.js +3 -0
  115. package/dist/web/.next/server/chunks/[root-of-the-server]__3880228a._.js +3 -0
  116. package/dist/web/.next/server/chunks/[root-of-the-server]__42322d88._.js +3 -0
  117. package/dist/web/.next/server/chunks/{[root-of-the-server]__d0f4efec._.js → [root-of-the-server]__5152eeff._.js} +3 -3
  118. package/dist/web/.next/server/chunks/[root-of-the-server]__527c7f57._.js +3 -0
  119. package/dist/web/.next/server/chunks/[root-of-the-server]__5c5dac4b._.js +3 -0
  120. package/dist/web/.next/server/chunks/[root-of-the-server]__5cb130f2._.js +3 -0
  121. package/dist/web/.next/server/chunks/[root-of-the-server]__68927e75._.js +3 -0
  122. package/dist/web/.next/server/chunks/[root-of-the-server]__719517c7._.js +3 -0
  123. package/dist/web/.next/server/chunks/[root-of-the-server]__73cf49c2._.js +3 -0
  124. package/dist/web/.next/server/chunks/{[root-of-the-server]__f5dae2ad._.js → [root-of-the-server]__7af4ab09._.js} +1 -1
  125. package/dist/web/.next/server/chunks/{[root-of-the-server]__4244617a._.js → [root-of-the-server]__7e6860e0._.js} +3 -3
  126. package/dist/web/.next/server/chunks/[root-of-the-server]__88bf5e22._.js +3 -0
  127. package/dist/web/.next/server/chunks/{[root-of-the-server]__f97e93fa._.js → [root-of-the-server]__8b4259cb._.js} +3 -3
  128. package/dist/web/.next/server/chunks/[root-of-the-server]__92f81907._.js +3 -0
  129. package/dist/web/.next/server/chunks/[root-of-the-server]__967603e9._.js +3 -0
  130. package/dist/web/.next/server/chunks/[root-of-the-server]__9e4bd28f._.js +3 -0
  131. package/dist/web/.next/server/chunks/[root-of-the-server]__ba1d2e56._.js +3 -0
  132. package/dist/web/.next/server/chunks/[root-of-the-server]__c942d872._.js +3 -0
  133. package/dist/web/.next/server/chunks/[root-of-the-server]__d7893622._.js +3 -0
  134. package/dist/web/.next/server/chunks/{[root-of-the-server]__3b9d3e43._.js → [root-of-the-server]__d843611b._.js} +6 -6
  135. package/dist/web/.next/server/chunks/[root-of-the-server]__f4d2627f._.js +3 -0
  136. package/dist/web/.next/server/chunks/[root-of-the-server]__f597835d._.js +3 -0
  137. package/dist/web/.next/server/chunks/{[root-of-the-server]__cf14e306._.js → [root-of-the-server]__fe8b9abd._.js} +1 -1
  138. package/dist/web/.next/server/chunks/src_677020aa._.js +1 -1
  139. package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__1f5fc489._.js +1 -1
  140. package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__43d93717._.js +3 -0
  141. package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__90f82e6d._.js +3 -0
  142. package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__bcbe4bf2._.js +1 -1
  143. package/dist/web/.next/server/chunks/ssr/src_lib_registry_ts_2fc87c9c._.js +1 -1
  144. package/dist/web/.next/server/pages/404.html +1 -1
  145. package/dist/web/.next/server/pages/500.html +2 -2
  146. package/dist/web/.next/static/chunks/18cfbdd7e977bb01.css +1 -0
  147. package/dist/web/.next/static/chunks/8415039c5941cf5c.js +4 -0
  148. package/dist/web/.next/static/chunks/{3d5195b57fc05540.js → a0f5f9cdee8a22c1.js} +2 -2
  149. package/dist/web/src/app/api/projects/analyze/route.ts +12 -2
  150. package/dist/web/src/app/api/projects/route.ts +1 -11
  151. package/dist/web/src/app/api/providers/route.ts +4 -0
  152. package/dist/web/src/components/ClaudeTerminalPanel.tsx +124 -70
  153. package/dist/web/src/lib/paths.ts +14 -0
  154. package/dist/web/tsconfig.tsbuildinfo +1 -1
  155. package/package.json +1 -1
  156. package/templates/kanban-seed.json +1 -1
  157. package/dist/web/.next/server/chunks/[root-of-the-server]__09aec55a._.js +0 -3
  158. package/dist/web/.next/server/chunks/[root-of-the-server]__12f6cd6f._.js +0 -3
  159. package/dist/web/.next/server/chunks/[root-of-the-server]__15fc9266._.js +0 -18
  160. package/dist/web/.next/server/chunks/[root-of-the-server]__198f01e0._.js +0 -3
  161. package/dist/web/.next/server/chunks/[root-of-the-server]__279e9bf3._.js +0 -3
  162. package/dist/web/.next/server/chunks/[root-of-the-server]__2b639eab._.js +0 -3
  163. package/dist/web/.next/server/chunks/[root-of-the-server]__2d1f0ed9._.js +0 -3
  164. package/dist/web/.next/server/chunks/[root-of-the-server]__3f239285._.js +0 -3
  165. package/dist/web/.next/server/chunks/[root-of-the-server]__47dd878e._.js +0 -3
  166. package/dist/web/.next/server/chunks/[root-of-the-server]__5b8c9374._.js +0 -3
  167. package/dist/web/.next/server/chunks/[root-of-the-server]__5e08b942._.js +0 -3
  168. package/dist/web/.next/server/chunks/[root-of-the-server]__6ffce934._.js +0 -3
  169. package/dist/web/.next/server/chunks/[root-of-the-server]__71bb3374._.js +0 -3
  170. package/dist/web/.next/server/chunks/[root-of-the-server]__7603305e._.js +0 -3
  171. package/dist/web/.next/server/chunks/[root-of-the-server]__7c476ad6._.js +0 -3
  172. package/dist/web/.next/server/chunks/[root-of-the-server]__846ca56f._.js +0 -3
  173. package/dist/web/.next/server/chunks/[root-of-the-server]__98d88050._.js +0 -3
  174. package/dist/web/.next/server/chunks/[root-of-the-server]__b273cc05._.js +0 -3
  175. package/dist/web/.next/server/chunks/[root-of-the-server]__b90bbd70._.js +0 -3
  176. package/dist/web/.next/server/chunks/[root-of-the-server]__d5272169._.js +0 -3
  177. package/dist/web/.next/server/chunks/[root-of-the-server]__d56e68cb._.js +0 -3
  178. package/dist/web/.next/server/chunks/[root-of-the-server]__d6362272._.js +0 -3
  179. package/dist/web/.next/server/chunks/[root-of-the-server]__de1277ee._.js +0 -27
  180. package/dist/web/.next/server/chunks/[root-of-the-server]__e88a19d2._.js +0 -3
  181. package/dist/web/.next/server/chunks/[root-of-the-server]__f3e501b6._.js +0 -3
  182. package/dist/web/.next/server/chunks/[root-of-the-server]__f59af2bc._.js +0 -3
  183. package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__9ac6ea25._.js +0 -3
  184. package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__dfe2728c._.js +0 -3
  185. package/dist/web/.next/static/chunks/59fb302a5bfd2dc0.js +0 -4
  186. package/dist/web/.next/static/chunks/747f5e5f9dcf2621.css +0 -1
  187. /package/dist/web/.next/static/{0sPAbk-Qw-InZ0rdHjHnC → b2V8jC3HBMi4vgm7Kie3H}/_buildManifest.js +0 -0
  188. /package/dist/web/.next/static/{0sPAbk-Qw-InZ0rdHjHnC → b2V8jC3HBMi4vgm7Kie3H}/_clientMiddlewareManifest.json +0 -0
  189. /package/dist/web/.next/static/{0sPAbk-Qw-InZ0rdHjHnC → b2V8jC3HBMi4vgm7Kie3H}/_ssgManifest.js +0 -0
@@ -2,7 +2,7 @@ import { NextResponse } from 'next/server';
2
2
  import { execFile } from 'child_process';
3
3
  import { promisify } from 'util';
4
4
  import path from 'path';
5
- import { getPackageDir } from '@/lib/paths';
5
+ import { getPackageDir, expandTilde } from '@/lib/paths';
6
6
 
7
7
  const execFileAsync = promisify(execFile);
8
8
 
@@ -23,11 +23,21 @@ export async function POST(request: Request) {
23
23
  );
24
24
  }
25
25
 
26
+ // Expand tilde and validate absolute path
27
+ const expanded = expandTilde(targetPath);
28
+ if (!path.isAbsolute(expanded)) {
29
+ return NextResponse.json(
30
+ { error: 'Please enter an absolute path (e.g. ~/Dev/myproject or /home/user/Dev/myproject)' },
31
+ { status: 400 }
32
+ );
33
+ }
34
+ const resolvedPath = path.resolve(expanded);
35
+
26
36
  const scaffoldScript = path.join(getPackageDir(), 'scripts', 'scaffold.js');
27
37
  const args = [
28
38
  scaffoldScript,
29
39
  'analyze',
30
- '--path', targetPath,
40
+ '--path', resolvedPath,
31
41
  '--json',
32
42
  ];
33
43
  if (providers && Array.isArray(providers) && providers.length > 0) {
@@ -3,22 +3,12 @@ import { loadRegistry, saveRegistry } from '@/lib/registry';
3
3
  import { execFile } from 'child_process';
4
4
  import { promisify } from 'util';
5
5
  import path from 'path';
6
- import os from 'os';
7
- import { getSlycodeRoot, getPackageDir } from '@/lib/paths';
6
+ import { getSlycodeRoot, getPackageDir, expandTilde } from '@/lib/paths';
8
7
 
9
8
  const execFileAsync = promisify(execFile);
10
9
 
11
10
  export const dynamic = 'force-dynamic';
12
11
 
13
- function expandTilde(p: string): string {
14
- // Normalize Unicode tildes (U+02DC small tilde, U+FF5E fullwidth tilde) to ASCII
15
- p = p.replace(/^[\u02dc\uff5e]/, '~');
16
- if (p.startsWith('~/') || p === '~') {
17
- return p.replace(/^~/, os.homedir());
18
- }
19
- return p;
20
- }
21
-
22
12
  function toKebabCase(str: string): string {
23
13
  return str
24
14
  .toLowerCase()
@@ -31,6 +31,10 @@ function validateProviderDefault(
31
31
  if (typeof d.skipPermissions !== 'boolean') {
32
32
  return `${label}.skipPermissions must be a boolean`;
33
33
  }
34
+ // Optional model field — pass through to CLI (no validation against available list)
35
+ if ('model' in d && d.model !== undefined && typeof d.model !== 'string') {
36
+ return `${label}.model must be a string if provided`;
37
+ }
34
38
  return null;
35
39
  }
36
40
 
@@ -13,11 +13,16 @@ interface ProviderConfig {
13
13
  command: string;
14
14
  permissions: { flag: string; label: string; default: boolean };
15
15
  resume: { supported: boolean };
16
+ model?: {
17
+ flag: string;
18
+ available: Array<{ id: string; label: string; description?: string }>;
19
+ };
16
20
  }
17
21
 
18
22
  interface ProviderDefault {
19
23
  provider: string;
20
24
  skipPermissions: boolean;
25
+ model?: string;
21
26
  }
22
27
 
23
28
  interface ProvidersData {
@@ -45,6 +50,7 @@ interface SessionInfo {
45
50
  claudeSessionId?: string | null;
46
51
  provider?: string;
47
52
  skipPermissions?: boolean;
53
+ model?: string;
48
54
  }
49
55
 
50
56
  export interface TerminalContext {
@@ -145,6 +151,13 @@ export function ClaudeTerminalPanel({
145
151
  // Spawn error toast — shown when session creation fails (e.g. posix_spawnp failed)
146
152
  const [spawnError, setSpawnError] = useState<string | null>(null);
147
153
 
154
+ // Model selection state
155
+ const [selectedModel, setSelectedModel] = useState(''); // '' = Default (no flag)
156
+ const prevProviderRef = useRef(selectedProvider);
157
+ const userSelectedModelRef = useRef(false);
158
+ const sessionInfoRef = useRef(sessionInfo);
159
+ sessionInfoRef.current = sessionInfo;
160
+
148
161
  // Instruction file check state
149
162
  const [instructionFileCheck, setInstructionFileCheck] = useState<{ needed: boolean; targetFile?: string; copySource?: string } | null>(null);
150
163
  const [createInstructionFile, setCreateInstructionFile] = useState(true);
@@ -177,15 +190,53 @@ export function ClaudeTerminalPanel({
177
190
  setSelectedProvider(def.provider);
178
191
  }
179
192
  setSkipPermissions(def.skipPermissions);
193
+ if (def.model) {
194
+ setSelectedModel(def.model);
195
+ }
180
196
  }
181
197
  }
182
198
  })
183
199
  .catch(() => { /* providers.json not available, use defaults */ });
184
200
  }, [stage]);
185
201
 
202
+ // Pre-fill model when provider changes
203
+ useEffect(() => {
204
+ if (!providersData) return;
205
+ const providerChanged = prevProviderRef.current !== selectedProvider;
206
+ prevProviderRef.current = selectedProvider;
207
+ if (!providerChanged && userSelectedModelRef.current) return;
208
+ userSelectedModelRef.current = false;
209
+ // Priority: last-used from session > stage default > Default
210
+ const si = sessionInfoRef.current;
211
+ if (si?.model && si.provider === selectedProvider) {
212
+ const available = providersData.providers[selectedProvider]?.model?.available;
213
+ if (available?.some(m => m.id === si.model)) {
214
+ setSelectedModel(si.model);
215
+ return;
216
+ }
217
+ }
218
+ const stageDefault = stage ? providersData.defaults.stages[stage] : null;
219
+ const def = stageDefault || providersData.defaults.global;
220
+ if (def?.model && def.provider === selectedProvider) {
221
+ const available = providersData.providers[selectedProvider]?.model?.available;
222
+ if (available?.some(m => m.id === def.model)) {
223
+ setSelectedModel(def.model);
224
+ return;
225
+ }
226
+ }
227
+ setSelectedModel('');
228
+ }, [selectedProvider, providersData, stage]);
229
+
186
230
  // Persist provider default to /api/providers (fire-and-forget)
187
231
  const saveProviderDefault = useCallback((provider: string, skip: boolean) => {
188
- const defaultVal = { provider, skipPermissions: skip };
232
+ // Preserve any existing model field in the stage default (stage model defaults are intentional config)
233
+ const existingDefault = stage
234
+ ? providersData?.defaults.stages[stage]
235
+ : providersData?.defaults.global;
236
+ const defaultVal: Record<string, unknown> = { provider, skipPermissions: skip };
237
+ if (existingDefault?.model && existingDefault.provider === provider) {
238
+ defaultVal.model = existingDefault.model;
239
+ }
189
240
  const defaults = stage
190
241
  ? { stages: { [stage]: defaultVal } }
191
242
  : { global: defaultVal };
@@ -194,7 +245,7 @@ export function ClaudeTerminalPanel({
194
245
  headers: { 'Content-Type': 'application/json' },
195
246
  body: JSON.stringify({ defaults }),
196
247
  }).catch(() => { /* preference save — ignore errors */ });
197
- }, [stage]);
248
+ }, [stage, providersData]);
198
249
 
199
250
  const isRunning = sessionInfo?.status === 'running' || sessionInfo?.status === 'detached';
200
251
  const hasHistory = sessionInfo?.hasHistory;
@@ -288,6 +339,74 @@ export function ClaudeTerminalPanel({
288
339
  </div>
289
340
  ) : null;
290
341
 
342
+ // Helper to render model label from provider config
343
+ const getModelLabel = (providerId: string, modelId?: string) => {
344
+ if (!modelId || !providersData) return null;
345
+ return providersData.providers[providerId]?.model?.available?.find(m => m.id === modelId)?.label || modelId;
346
+ };
347
+
348
+ // Shared provider selector (eliminates duplication between resume and fresh-start screens)
349
+ const renderProviderSelector = (options?: { showModel?: boolean }) => {
350
+ if (!providersData || Object.keys(providersData.providers).length <= 1) return null;
351
+ const currentProvider = providersData.providers[selectedProvider];
352
+ const models = currentProvider?.model?.available;
353
+ const showModel = options?.showModel !== false;
354
+ return (
355
+ <div className="flex flex-col items-center gap-2 mt-3 pt-3 border-t border-void-700/50">
356
+ <div className="flex gap-1">
357
+ {Object.values(providersData.providers).map(p => (
358
+ <button
359
+ key={p.id}
360
+ onClick={() => {
361
+ setSelectedProvider(p.id);
362
+ saveProviderDefault(p.id, skipPermissions);
363
+ onProviderChange?.(p.id);
364
+ }}
365
+ className={`rounded-md px-3 py-1 text-xs font-medium transition-all ${
366
+ selectedProvider === p.id
367
+ ? 'border border-neon-blue-400/60 bg-neon-blue-400/15 text-neon-blue-400 shadow-[0_0_8px_rgba(0,191,255,0.2)]'
368
+ : 'border border-void-600 bg-void-800 text-void-400 hover:border-void-500 hover:text-void-300'
369
+ }`}
370
+ >
371
+ {p.displayName}
372
+ </button>
373
+ ))}
374
+ </div>
375
+ <div className="flex items-center gap-3">
376
+ {showModel && models && models.length > 0 && (
377
+ <div className="flex items-center gap-1.5">
378
+ <span className="text-xs text-void-500">Model</span>
379
+ <select
380
+ value={selectedModel}
381
+ onChange={(e) => { setSelectedModel(e.target.value); userSelectedModelRef.current = true; }}
382
+ className={`max-w-[140px] truncate rounded border px-2 py-1 text-xs font-medium transition-all ${
383
+ selectedModel
384
+ ? 'border-neon-blue-400/40 bg-neon-blue-400/10 text-neon-blue-400'
385
+ : 'border-void-600 bg-void-800 text-void-400'
386
+ }`}
387
+ >
388
+ <option value="">Default</option>
389
+ {models.map(m => (
390
+ <option key={m.id} value={m.id}>{m.label}</option>
391
+ ))}
392
+ </select>
393
+ </div>
394
+ )}
395
+ <label className="flex items-center gap-1.5 text-xs text-void-500 cursor-pointer">
396
+ <input
397
+ type="checkbox"
398
+ checked={skipPermissions}
399
+ onChange={(e) => { setSkipPermissions(e.target.checked); saveProviderDefault(selectedProvider, e.target.checked); }}
400
+ className="rounded border-void-600"
401
+ />
402
+ {providersData.providers[selectedProvider]?.permissions.label || 'Skip permissions'}
403
+ </label>
404
+ </div>
405
+ {instructionFileWarning}
406
+ </div>
407
+ );
408
+ };
409
+
291
410
  // Derive startup and toolbar lists from placement
292
411
  const startupActions = actions.filter(a => a.placement === 'startup' || a.placement === 'both');
293
412
  const toolbarActions = actions.filter(a => a.placement === 'toolbar' || a.placement === 'both');
@@ -317,6 +436,7 @@ export function ClaudeTerminalPanel({
317
436
  cwd,
318
437
  fresh: !hasHistory,
319
438
  prompt,
439
+ model: selectedModel || undefined,
320
440
  createInstructionFile: instructionFileCheck?.needed ? createInstructionFile : undefined,
321
441
  }),
322
442
  });
@@ -619,40 +739,7 @@ export function ClaudeTerminalPanel({
619
739
  Custom...
620
740
  </button>
621
741
  </div>
622
- {/* Provider selector */}
623
- {providersData && Object.keys(providersData.providers).length > 1 && (
624
- <div className="flex flex-col items-center gap-2 mt-3 pt-3 border-t border-void-700/50">
625
- <div className="flex gap-1">
626
- {Object.values(providersData.providers).map(p => (
627
- <button
628
- key={p.id}
629
- onClick={() => {
630
- setSelectedProvider(p.id);
631
- saveProviderDefault(p.id, skipPermissions);
632
- onProviderChange?.(p.id);
633
- }}
634
- className={`rounded-md px-3 py-1 text-xs font-medium transition-all ${
635
- selectedProvider === p.id
636
- ? 'border border-neon-blue-400/60 bg-neon-blue-400/15 text-neon-blue-400 shadow-[0_0_8px_rgba(0,191,255,0.2)]'
637
- : 'border border-void-600 bg-void-800 text-void-400 hover:border-void-500 hover:text-void-300'
638
- }`}
639
- >
640
- {p.displayName}
641
- </button>
642
- ))}
643
- </div>
644
- <label className="flex items-center gap-1.5 text-xs text-void-500 cursor-pointer">
645
- <input
646
- type="checkbox"
647
- checked={skipPermissions}
648
- onChange={(e) => { setSkipPermissions(e.target.checked); saveProviderDefault(selectedProvider, e.target.checked); }}
649
- className="rounded border-void-600"
650
- />
651
- {providersData.providers[selectedProvider]?.permissions.label || 'Skip permissions'}
652
- </label>
653
- {instructionFileWarning}
654
- </div>
655
- )}
742
+ {renderProviderSelector({ showModel: false })}
656
743
  </>
657
744
  ) : (
658
745
  <>
@@ -684,40 +771,7 @@ export function ClaudeTerminalPanel({
684
771
  >
685
772
  Start without prompt
686
773
  </button>
687
- {/* Provider selector */}
688
- {providersData && Object.keys(providersData.providers).length > 1 && (
689
- <div className="flex flex-col items-center gap-2 mt-3 pt-3 border-t border-void-700/50">
690
- <div className="flex gap-1">
691
- {Object.values(providersData.providers).map(p => (
692
- <button
693
- key={p.id}
694
- onClick={() => {
695
- setSelectedProvider(p.id);
696
- saveProviderDefault(p.id, skipPermissions);
697
- onProviderChange?.(p.id);
698
- }}
699
- className={`rounded-md px-3 py-1 text-xs font-medium transition-all ${
700
- selectedProvider === p.id
701
- ? 'border border-neon-blue-400/60 bg-neon-blue-400/15 text-neon-blue-400 shadow-[0_0_8px_rgba(0,191,255,0.2)]'
702
- : 'border border-void-600 bg-void-800 text-void-400 hover:border-void-500 hover:text-void-300'
703
- }`}
704
- >
705
- {p.displayName}
706
- </button>
707
- ))}
708
- </div>
709
- <label className="flex items-center gap-1.5 text-xs text-void-500 cursor-pointer">
710
- <input
711
- type="checkbox"
712
- checked={skipPermissions}
713
- onChange={(e) => { setSkipPermissions(e.target.checked); saveProviderDefault(selectedProvider, e.target.checked); }}
714
- className="rounded border-void-600"
715
- />
716
- {providersData.providers[selectedProvider]?.permissions.label || 'Skip permissions'}
717
- </label>
718
- {instructionFileWarning}
719
- </div>
720
- )}
774
+ {renderProviderSelector()}
721
775
  </>
722
776
  )}
723
777
  </div>
@@ -7,6 +7,20 @@
7
7
 
8
8
  import path from 'path';
9
9
  import fs from 'fs';
10
+ import os from 'os';
11
+
12
+ /**
13
+ * Expand tilde (~) in a path to the user's home directory.
14
+ * Also normalizes Unicode tildes (U+02DC, U+FF5E) that Mac browsers can produce.
15
+ */
16
+ export function expandTilde(p: string): string {
17
+ // Normalize Unicode tildes (U+02DC small tilde, U+FF5E fullwidth tilde) to ASCII
18
+ p = p.replace(/^[\u02dc\uff5e]/, '~');
19
+ if (p.startsWith('~/') || p === '~') {
20
+ return p.replace(/^~/, os.homedir());
21
+ }
22
+ return p;
23
+ }
10
24
 
11
25
  /**
12
26
  * Resolve the SlyCode root directory (workspace).