@slycode/slycode 0.2.20 → 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.
- package/dist/bridge/provider-utils.d.ts +10 -0
- package/dist/bridge/provider-utils.js +5 -1
- package/dist/bridge/provider-utils.js.map +1 -1
- package/dist/bridge/pty-handler.js +58 -12
- package/dist/bridge/pty-handler.js.map +1 -1
- package/dist/bridge/session-manager.js +6 -0
- package/dist/bridge/session-manager.js.map +1 -1
- package/dist/bridge/types.d.ts +4 -0
- package/dist/messaging/bridge-client.d.ts +3 -3
- package/dist/messaging/bridge-client.js +6 -4
- package/dist/messaging/bridge-client.js.map +1 -1
- package/dist/messaging/index.js +117 -26
- package/dist/messaging/index.js.map +1 -1
- package/dist/messaging/state.d.ts +3 -0
- package/dist/messaging/state.js +13 -0
- package/dist/messaging/state.js.map +1 -1
- package/dist/messaging/stt.d.ts +1 -1
- package/dist/messaging/stt.js +41 -9
- package/dist/messaging/stt.js.map +1 -1
- package/dist/messaging/types.d.ts +3 -0
- package/dist/web/.next/BUILD_ID +1 -1
- package/dist/web/.next/build-manifest.json +2 -2
- package/dist/web/.next/server/app/_global-error.html +2 -2
- package/dist/web/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/_not-found.html +1 -1
- package/dist/web/.next/server/app/_not-found.rsc +2 -2
- package/dist/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/dist/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/dist/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dist/web/.next/server/app/api/areas/route.js +1 -1
- package/dist/web/.next/server/app/api/areas/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/bridge/[...path]/route.js +1 -1
- package/dist/web/.next/server/app/api/bridge/[...path]/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/assistant/route.js +1 -1
- package/dist/web/.next/server/app/api/cli-assets/assistant/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/fix/route.js +1 -1
- package/dist/web/.next/server/app/api/cli-assets/fix/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/import/route.js +1 -1
- package/dist/web/.next/server/app/api/cli-assets/import/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/route.js +2 -2
- package/dist/web/.next/server/app/api/cli-assets/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/store/preview/route.js +1 -1
- package/dist/web/.next/server/app/api/cli-assets/store/preview/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/store/route.js +2 -2
- package/dist/web/.next/server/app/api/cli-assets/store/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/sync/route.js +1 -1
- package/dist/web/.next/server/app/api/cli-assets/sync/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/updates/route.js +2 -2
- package/dist/web/.next/server/app/api/cli-assets/updates/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/dashboard/route.js +2 -2
- package/dist/web/.next/server/app/api/dashboard/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/events/route.js +1 -1
- package/dist/web/.next/server/app/api/events/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/file/route.js +1 -1
- package/dist/web/.next/server/app/api/file/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/git-status/route.js +1 -1
- package/dist/web/.next/server/app/api/git-status/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/kanban/route.js +1 -1
- package/dist/web/.next/server/app/api/kanban/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/kanban/stream/route.js +1 -1
- package/dist/web/.next/server/app/api/kanban/stream/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/[id]/route.js +2 -2
- package/dist/web/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/analyze/route.js +1 -1
- package/dist/web/.next/server/app/api/projects/analyze/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/reorder/route.js +2 -2
- package/dist/web/.next/server/app/api/projects/reorder/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/route.js +2 -2
- package/dist/web/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/providers/route.js +1 -1
- package/dist/web/.next/server/app/api/providers/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/scheduler/route.js +1 -1
- package/dist/web/.next/server/app/api/scheduler/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/search/route.js +1 -1
- package/dist/web/.next/server/app/api/search/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/settings/route.js +1 -1
- package/dist/web/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/invalidate/route.js +1 -1
- package/dist/web/.next/server/app/api/sly-actions/invalidate/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/route.js +1 -1
- package/dist/web/.next/server/app/api/sly-actions/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/stream/route.js +1 -1
- package/dist/web/.next/server/app/api/sly-actions/stream/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/system-stats/route.js +1 -1
- package/dist/web/.next/server/app/api/system-stats/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/terminal-classes/route.js +1 -1
- package/dist/web/.next/server/app/api/terminal-classes/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/transcribe/route.js +5 -5
- package/dist/web/.next/server/app/api/transcribe/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/version-check/route.js +1 -1
- package/dist/web/.next/server/app/api/version-check/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/page.js +1 -1
- package/dist/web/.next/server/app/page.js.nft.json +1 -1
- package/dist/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/project/[id]/page.js +2 -2
- package/dist/web/.next/server/app/project/[id]/page.js.nft.json +1 -1
- package/dist/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/chunks/{[externals]__c6831f39._.js → [externals]__78e522ea._.js} +2 -2
- package/dist/web/.next/server/chunks/{[root-of-the-server]__1ec21ccc._.js → [root-of-the-server]__029203cd._.js} +3 -3
- package/dist/web/.next/server/chunks/{[root-of-the-server]__4297cb97._.js → [root-of-the-server]__0d6d4443._.js} +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__172ad0b1._.js +18 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__1c5f4ef9._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__1cab11f0._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__0f69c28a._.js → [root-of-the-server]__1eb3f172._.js} +2 -2
- package/dist/web/.next/server/chunks/[root-of-the-server]__22cba275._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__2543e413._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__2c42a835._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__2ed0ff47._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__35454eea._.js +27 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__35768b56._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__3880228a._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__42322d88._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__d0f4efec._.js → [root-of-the-server]__5152eeff._.js} +3 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__527c7f57._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__5c5dac4b._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__5cb130f2._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__68927e75._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__719517c7._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__73cf49c2._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__f5dae2ad._.js → [root-of-the-server]__7af4ab09._.js} +1 -1
- package/dist/web/.next/server/chunks/{[root-of-the-server]__4244617a._.js → [root-of-the-server]__7e6860e0._.js} +3 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__88bf5e22._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__f97e93fa._.js → [root-of-the-server]__8b4259cb._.js} +3 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__92f81907._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__967603e9._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__9e4bd28f._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__ba1d2e56._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__c942d872._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__d7893622._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__3b9d3e43._.js → [root-of-the-server]__d843611b._.js} +6 -6
- package/dist/web/.next/server/chunks/[root-of-the-server]__f4d2627f._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__f597835d._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__cf14e306._.js → [root-of-the-server]__fe8b9abd._.js} +1 -1
- package/dist/web/.next/server/chunks/src_677020aa._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__1f5fc489._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__43d93717._.js +3 -0
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__90f82e6d._.js +3 -0
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__bcbe4bf2._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/src_components_c4135402._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/src_lib_registry_ts_2fc87c9c._.js +1 -1
- package/dist/web/.next/server/pages/404.html +1 -1
- package/dist/web/.next/server/pages/500.html +2 -2
- package/dist/web/.next/static/chunks/18cfbdd7e977bb01.css +1 -0
- package/dist/web/.next/static/chunks/{8cb404d087e9f3c7.js → 4049cceee6a49323.js} +1 -1
- package/dist/web/.next/static/chunks/8415039c5941cf5c.js +4 -0
- package/dist/web/.next/static/chunks/{3d5195b57fc05540.js → a0f5f9cdee8a22c1.js} +2 -2
- package/dist/web/src/app/api/projects/analyze/route.ts +12 -2
- package/dist/web/src/app/api/projects/route.ts +10 -11
- package/dist/web/src/app/api/providers/route.ts +4 -0
- package/dist/web/src/components/CardModal.tsx +31 -24
- package/dist/web/src/components/ClaudeTerminalPanel.tsx +124 -70
- package/dist/web/src/lib/paths.ts +14 -0
- package/dist/web/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/templates/kanban-seed.json +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__09aec55a._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__12f6cd6f._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__15fc9266._.js +0 -18
- package/dist/web/.next/server/chunks/[root-of-the-server]__198f01e0._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__279e9bf3._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__2b639eab._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__2d1f0ed9._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__3f239285._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__47dd878e._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__5b8c9374._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__5e08b942._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__6ffce934._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__71bb3374._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__7603305e._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__7c476ad6._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__846ca56f._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__98d88050._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__b273cc05._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__b90bbd70._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__d5272169._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__d56e68cb._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__d6362272._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__de1277ee._.js +0 -27
- package/dist/web/.next/server/chunks/[root-of-the-server]__e88a19d2._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__f3e501b6._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__f59af2bc._.js +0 -3
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__9ac6ea25._.js +0 -3
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__dfe2728c._.js +0 -3
- package/dist/web/.next/static/chunks/59fb302a5bfd2dc0.js +0 -4
- package/dist/web/.next/static/chunks/747f5e5f9dcf2621.css +0 -1
- /package/dist/web/.next/static/{tQdF18XbrwPnmXEMVlcfU → b2V8jC3HBMi4vgm7Kie3H}/_buildManifest.js +0 -0
- /package/dist/web/.next/static/{tQdF18XbrwPnmXEMVlcfU → b2V8jC3HBMi4vgm7Kie3H}/_clientMiddlewareManifest.json +0 -0
- /package/dist/web/.next/static/{tQdF18XbrwPnmXEMVlcfU → 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',
|
|
40
|
+
'--path', resolvedPath,
|
|
31
41
|
'--json',
|
|
32
42
|
];
|
|
33
43
|
if (providers && Array.isArray(providers) && providers.length > 0) {
|
|
@@ -3,20 +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
|
|
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
|
-
if (p.startsWith('~/') || p === '~') {
|
|
15
|
-
return p.replace(/^~/, os.homedir());
|
|
16
|
-
}
|
|
17
|
-
return p;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
12
|
function toKebabCase(str: string): string {
|
|
21
13
|
return str
|
|
22
14
|
.toLowerCase()
|
|
@@ -55,8 +47,15 @@ export async function POST(request: Request) {
|
|
|
55
47
|
);
|
|
56
48
|
}
|
|
57
49
|
|
|
58
|
-
// Resolve tilde and
|
|
59
|
-
const
|
|
50
|
+
// Resolve tilde and validate absolute path
|
|
51
|
+
const expanded = expandTilde(projectPath);
|
|
52
|
+
if (!path.isAbsolute(expanded)) {
|
|
53
|
+
return NextResponse.json(
|
|
54
|
+
{ error: 'Please enter an absolute path (e.g. ~/Dev/myproject or /home/user/Dev/myproject)' },
|
|
55
|
+
{ status: 400 }
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const resolvedPath = path.resolve(expanded);
|
|
60
59
|
|
|
61
60
|
const registry = await loadRegistry();
|
|
62
61
|
const projectId = toKebabCase(name);
|
|
@@ -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
|
|
|
@@ -649,33 +649,40 @@ export function CardModal({ card, stage, projectId, projectPath, onClose, onUpda
|
|
|
649
649
|
};
|
|
650
650
|
const currentDocPath = getDocPath(activeTab);
|
|
651
651
|
|
|
652
|
-
// Track loaded docs by path
|
|
652
|
+
// Track loaded docs by path
|
|
653
653
|
const [loadedDocs, setLoadedDocs] = useState<Record<string, string>>({});
|
|
654
654
|
const [docErrors, setDocErrors] = useState<Record<string, string>>({});
|
|
655
655
|
|
|
656
|
-
//
|
|
656
|
+
// Re-fetch document every time a doc tab is selected (always show latest from disk)
|
|
657
657
|
useEffect(() => {
|
|
658
658
|
const isDocTab = activeTab === 'design' || activeTab === 'feature' || activeTab === 'test';
|
|
659
|
-
if (isDocTab
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
|
|
659
|
+
if (!isDocTab || !currentDocPath) return;
|
|
660
|
+
|
|
661
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- gating fetch with loading flag
|
|
662
|
+
setDocLoading(true);
|
|
663
|
+
const fetchPath = currentDocPath;
|
|
664
|
+
fetch(`/api/file?path=${encodeURIComponent(fetchPath)}&projectId=${encodeURIComponent(projectId)}`)
|
|
665
|
+
.then((res) => res.json())
|
|
666
|
+
.then((data) => {
|
|
667
|
+
if (data.error) {
|
|
668
|
+
setDocErrors((prev) => ({ ...prev, [fetchPath]: data.error }));
|
|
669
|
+
} else {
|
|
670
|
+
setDocErrors((prev) => {
|
|
671
|
+
const next = { ...prev };
|
|
672
|
+
delete next[fetchPath];
|
|
673
|
+
return next;
|
|
674
|
+
});
|
|
675
|
+
setLoadedDocs((prev) => ({ ...prev, [fetchPath]: data.content }));
|
|
676
|
+
}
|
|
677
|
+
})
|
|
678
|
+
.catch((err) => {
|
|
679
|
+
setDocErrors((prev) => ({ ...prev, [fetchPath]: err.message }));
|
|
680
|
+
})
|
|
681
|
+
.finally(() => {
|
|
682
|
+
setDocLoading(false);
|
|
683
|
+
});
|
|
684
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally re-fetch on every tab switch
|
|
685
|
+
}, [activeTab, currentDocPath, projectId]);
|
|
679
686
|
|
|
680
687
|
const handleTitleSave = () => {
|
|
681
688
|
if (editedTitle.trim() && editedTitle !== card.title) {
|
|
@@ -1623,12 +1630,12 @@ export function CardModal({ card, stage, projectId, projectPath, onClose, onUpda
|
|
|
1623
1630
|
)}
|
|
1624
1631
|
</button>
|
|
1625
1632
|
)}
|
|
1626
|
-
{docLoading && !loadedDocs[currentDocPath!] && (
|
|
1633
|
+
{docLoading && !loadedDocs[currentDocPath!] && !docErrors[currentDocPath!] && (
|
|
1627
1634
|
<div className="flex items-center justify-center py-8">
|
|
1628
1635
|
<div className="text-void-500">Loading document...</div>
|
|
1629
1636
|
</div>
|
|
1630
1637
|
)}
|
|
1631
|
-
{currentDocPath && docErrors[currentDocPath] && (
|
|
1638
|
+
{currentDocPath && docErrors[currentDocPath] && !loadedDocs[currentDocPath] && (
|
|
1632
1639
|
<div className="rounded-lg bg-red-50 p-4 text-red-700 dark:bg-red-900/20 dark:text-red-300">
|
|
1633
1640
|
Error loading document: {docErrors[currentDocPath]}
|
|
1634
1641
|
</div>
|
|
@@ -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
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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).
|