neural-loom 0.2.13 → 0.2.14

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 (139) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/cache/.previewinfo +1 -1
  5. package/.next/cache/.rscinfo +1 -1
  6. package/.next/cache/.tsbuildinfo +1 -1
  7. package/.next/diagnostics/route-bundle-stats.json +2 -2
  8. package/.next/fallback-build-manifest.json +3 -3
  9. package/.next/prerender-manifest.json +3 -3
  10. package/.next/routes-manifest.json +12 -0
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  26. package/.next/server/app/api/context/aider/route.js +1 -1
  27. package/.next/server/app/api/context/aider/route.js.nft.json +1 -1
  28. package/.next/server/app/api/context/claude/route.js +1 -1
  29. package/.next/server/app/api/context/claude/route.js.nft.json +1 -1
  30. package/.next/server/app/api/recents/route/app-paths-manifest.json +3 -0
  31. package/.next/server/app/api/recents/route/build-manifest.json +9 -0
  32. package/.next/server/app/api/recents/route/server-reference-manifest.json +4 -0
  33. package/.next/server/app/api/recents/route.js +6 -0
  34. package/.next/server/app/api/recents/route.js.map +5 -0
  35. package/.next/server/app/api/recents/route.js.nft.json +1 -0
  36. package/.next/server/app/api/recents/route_client-reference-manifest.js +3 -0
  37. package/.next/server/app/api/sessions/inject/route.js +1 -1
  38. package/.next/server/app/api/sessions/inject/route.js.nft.json +1 -1
  39. package/.next/server/app/api/sessions/input/route.js +1 -1
  40. package/.next/server/app/api/sessions/input/route.js.nft.json +1 -1
  41. package/.next/server/app/api/sessions/launch/route.js +1 -1
  42. package/.next/server/app/api/sessions/launch/route.js.nft.json +1 -1
  43. package/.next/server/app/api/sessions/route.js +1 -1
  44. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  45. package/.next/server/app/api/sessions/scope/route/app-paths-manifest.json +3 -0
  46. package/.next/server/app/api/sessions/scope/route/build-manifest.json +9 -0
  47. package/.next/server/app/api/sessions/scope/route/server-reference-manifest.json +4 -0
  48. package/.next/server/app/api/sessions/scope/route.js +7 -0
  49. package/.next/server/app/api/sessions/scope/route.js.map +5 -0
  50. package/.next/server/app/api/sessions/scope/route.js.nft.json +1 -0
  51. package/.next/server/app/api/sessions/scope/route_client-reference-manifest.js +3 -0
  52. package/.next/server/app/api/sessions/stats/route.js +1 -1
  53. package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -1
  54. package/.next/server/app/api/sessions/stop/route.js +1 -1
  55. package/.next/server/app/api/sessions/stop/route.js.nft.json +1 -1
  56. package/.next/server/app/index.html +1 -1
  57. package/.next/server/app/index.rsc +2 -2
  58. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  59. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  60. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  61. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  62. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  63. package/.next/server/app/page/react-loadable-manifest.json +4 -3
  64. package/.next/server/app/page_client-reference-manifest.js +1 -1
  65. package/.next/server/app-paths-manifest.json +2 -0
  66. package/.next/server/chunks/[root-of-the-server]__0_2_i_q._.js +15 -0
  67. package/.next/server/chunks/{[root-of-the-server]__0rd5ojc._.js.map → [root-of-the-server]__0_2_i_q._.js.map} +1 -1
  68. package/.next/server/chunks/[root-of-the-server]__0__9rdk._.js +15 -0
  69. package/.next/server/chunks/{[root-of-the-server]__0m7xwu2._.js.map → [root-of-the-server]__0__9rdk._.js.map} +1 -1
  70. package/.next/server/chunks/[root-of-the-server]__0bi3d5z._.js +15 -0
  71. package/.next/server/chunks/{[root-of-the-server]__0ezygp7._.js.map → [root-of-the-server]__0bi3d5z._.js.map} +1 -1
  72. package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js +3 -3
  73. package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js.map +1 -1
  74. package/.next/server/chunks/[root-of-the-server]__0iqaxhx._.js +15 -0
  75. package/.next/server/chunks/[root-of-the-server]__0iqaxhx._.js.map +1 -0
  76. package/.next/server/chunks/{[root-of-the-server]__12-x1lx._.js → [root-of-the-server]__0m91xif._.js} +3 -3
  77. package/.next/server/chunks/{[root-of-the-server]__12-x1lx._.js.map → [root-of-the-server]__0m91xif._.js.map} +1 -1
  78. package/.next/server/chunks/[root-of-the-server]__0p~u94k._.js +3 -0
  79. package/.next/server/chunks/[root-of-the-server]__0p~u94k._.js.map +1 -0
  80. package/.next/server/chunks/[root-of-the-server]__0qyrt6_._.js +3 -0
  81. package/.next/server/chunks/[root-of-the-server]__0qyrt6_._.js.map +1 -0
  82. package/.next/server/chunks/[root-of-the-server]__0qz~w0~._.js +15 -0
  83. package/.next/server/chunks/[root-of-the-server]__0qz~w0~._.js.map +1 -0
  84. package/.next/server/chunks/[root-of-the-server]__0ty8hwz._.js +15 -0
  85. package/.next/server/chunks/{[root-of-the-server]__0tl2n51._.js.map → [root-of-the-server]__0ty8hwz._.js.map} +1 -1
  86. package/.next/server/chunks/[root-of-the-server]__0ylcu31._.js +3 -0
  87. package/.next/server/chunks/[root-of-the-server]__0ylcu31._.js.map +1 -0
  88. package/.next/server/chunks/_next-internal_server_app_api_recents_route_actions_0wmi3sl.js +3 -0
  89. package/.next/server/chunks/_next-internal_server_app_api_recents_route_actions_0wmi3sl.js.map +1 -0
  90. package/.next/server/chunks/_next-internal_server_app_api_sessions_scope_route_actions_0g112ee.js +3 -0
  91. package/.next/server/chunks/_next-internal_server_app_api_sessions_scope_route_actions_0g112ee.js.map +1 -0
  92. package/.next/server/chunks/ssr/_0t7oqy6._.js +1 -1
  93. package/.next/server/chunks/ssr/_0t7oqy6._.js.map +1 -1
  94. package/.next/server/middleware-build-manifest.js +3 -3
  95. package/.next/server/pages/404.html +1 -1
  96. package/.next/server/pages/500.html +1 -1
  97. package/.next/server/server-reference-manifest.js +1 -1
  98. package/.next/server/server-reference-manifest.json +1 -1
  99. package/.next/static/chunks/0b89ti7ob.09t.js +1 -0
  100. package/.next/static/chunks/0eidfx-s54367.js +1 -0
  101. package/.next/static/chunks/0ew-ei_k3d-qg.js +1 -0
  102. package/.next/static/chunks/0g45rn_9mbzt6.js +1 -0
  103. package/.next/static/chunks/0jxar8nsw_py3.js +1 -0
  104. package/.next/trace +1 -1
  105. package/.next/trace-build +1 -1
  106. package/.next/types/routes.d.ts +3 -1
  107. package/.next/types/validator.ts +18 -0
  108. package/package.json +1 -1
  109. package/src/app/api/context/aider/route.ts +37 -22
  110. package/src/app/api/context/claude/route.ts +28 -16
  111. package/src/app/api/recents/route.ts +34 -0
  112. package/src/app/api/sessions/launch/route.ts +12 -0
  113. package/src/app/api/sessions/scope/route.ts +139 -0
  114. package/src/app/components/AiderWizard.tsx +70 -4
  115. package/src/app/components/ClaudeWizard.tsx +70 -4
  116. package/src/app/components/IdeLayout.tsx +86 -12
  117. package/src/app/page.tsx +1 -0
  118. package/src/lib/agents/AgentRunner.ts +2 -0
  119. package/src/lib/agents/SessionManager.ts +60 -2
  120. package/src/lib/agents/SessionPersister.ts +4 -2
  121. package/src/lib/recents.ts +57 -0
  122. package/src/lib/workspaceParser.ts +40 -0
  123. package/.next/server/chunks/[root-of-the-server]__0e5cwnx._.js +0 -3
  124. package/.next/server/chunks/[root-of-the-server]__0e5cwnx._.js.map +0 -1
  125. package/.next/server/chunks/[root-of-the-server]__0efi5cr._.js +0 -3
  126. package/.next/server/chunks/[root-of-the-server]__0efi5cr._.js.map +0 -1
  127. package/.next/server/chunks/[root-of-the-server]__0ezygp7._.js +0 -15
  128. package/.next/server/chunks/[root-of-the-server]__0m7xwu2._.js +0 -15
  129. package/.next/server/chunks/[root-of-the-server]__0rd5ojc._.js +0 -15
  130. package/.next/server/chunks/[root-of-the-server]__0tl2n51._.js +0 -15
  131. package/.next/server/chunks/[root-of-the-server]__0y-63z8._.js +0 -15
  132. package/.next/server/chunks/[root-of-the-server]__0y-63z8._.js.map +0 -1
  133. package/.next/static/chunks/095uz52in_39t.js +0 -1
  134. package/.next/static/chunks/0ayrw9yot-bjb.js +0 -1
  135. package/.next/static/chunks/0wdcf.26dj03~.js +0 -1
  136. package/.next/static/chunks/178yphwi3n~oc.js +0 -1
  137. /package/.next/static/{w0G47Es9qpUItx3l4uJ_v → g7S_nNTPWKtmCY0Va4YcB}/_buildManifest.js +0 -0
  138. /package/.next/static/{w0G47Es9qpUItx3l4uJ_v → g7S_nNTPWKtmCY0Va4YcB}/_clientMiddlewareManifest.js +0 -0
  139. /package/.next/static/{w0G47Es9qpUItx3l4uJ_v → g7S_nNTPWKtmCY0Va4YcB}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neural-loom",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "bin": {
5
5
  "neural-loom": "./bin/cli.js"
6
6
  },
@@ -17,36 +17,51 @@ function maskKey(key: string): string {
17
17
  return `${key.slice(0, 6)}...${key.slice(-4)}`;
18
18
  }
19
19
 
20
+ import { getWorkspaceFileFolders } from "@/lib/workspaceParser";
21
+
20
22
  export async function GET() {
21
23
  try {
24
+ let openaiApiKey = "";
25
+ let anthropicApiKey = "";
26
+ let geminiApiKey = "";
27
+ let model = "claude-3-5-sonnet";
28
+ let extraFlags = "";
29
+ let workspaceRoot = "";
30
+ let scopedDirs: string[] = [];
31
+
22
32
  if (fs.existsSync(configPath)) {
23
- const rawData = fs.readFileSync(configPath, "utf8");
24
- const config = JSON.parse(rawData);
25
-
26
- return NextResponse.json({
27
- success: true,
28
- config: {
29
- openaiApiKey: config.openaiApiKey ? maskKey(config.openaiApiKey) : "",
30
- anthropicApiKey: config.anthropicApiKey ? maskKey(config.anthropicApiKey) : "",
31
- geminiApiKey: config.geminiApiKey ? maskKey(config.geminiApiKey) : "",
32
- model: config.model || "claude-3-5-sonnet",
33
- extraFlags: config.extraFlags || "",
34
- workspaceRoot: config.workspaceRoot || "",
35
- scopedDirs: config.scopedDirs || [],
36
- },
37
- });
33
+ try {
34
+ const rawData = fs.readFileSync(configPath, "utf8");
35
+ const config = JSON.parse(rawData);
36
+ openaiApiKey = config.openaiApiKey ? maskKey(config.openaiApiKey) : "";
37
+ anthropicApiKey = config.anthropicApiKey ? maskKey(config.anthropicApiKey) : "";
38
+ geminiApiKey = config.geminiApiKey ? maskKey(config.geminiApiKey) : "";
39
+ model = config.model || "claude-3-5-sonnet";
40
+ extraFlags = config.extraFlags || "";
41
+ workspaceRoot = config.workspaceRoot || "";
42
+ scopedDirs = config.scopedDirs || [];
43
+ } catch (e) {
44
+ console.error("Failed to parse aider.json:", e);
45
+ }
38
46
  }
39
47
 
48
+ // Resolve workspace folders from the code-workspace file if it exists
49
+ const rootToScan = workspaceRoot || process.env.ORIGINAL_CWD || process.cwd();
50
+ const wsFolders = getWorkspaceFileFolders(rootToScan);
51
+
52
+ // Merge scopedDirs, filtering out duplicates
53
+ const mergedScopes = Array.from(new Set([...scopedDirs, ...wsFolders]));
54
+
40
55
  return NextResponse.json({
41
56
  success: true,
42
57
  config: {
43
- openaiApiKey: "",
44
- anthropicApiKey: "",
45
- geminiApiKey: "",
46
- model: "claude-3-5-sonnet",
47
- extraFlags: "",
48
- workspaceRoot: "",
49
- scopedDirs: [],
58
+ openaiApiKey,
59
+ anthropicApiKey,
60
+ geminiApiKey,
61
+ model,
62
+ extraFlags,
63
+ workspaceRoot,
64
+ scopedDirs: mergedScopes,
50
65
  },
51
66
  });
52
67
  } catch (error) {
@@ -11,30 +11,42 @@ function ensureDir() {
11
11
  }
12
12
  }
13
13
 
14
+ import { getWorkspaceFileFolders } from "@/lib/workspaceParser";
15
+
14
16
  export async function GET() {
15
17
  try {
18
+ let name = "Claude Local";
19
+ let workspaceRoot = "";
20
+ let scopedDirs: string[] = [];
21
+ let extraFlags = "";
22
+
16
23
  if (fs.existsSync(configPath)) {
17
- const rawData = fs.readFileSync(configPath, "utf8");
18
- const config = JSON.parse(rawData);
19
-
20
- return NextResponse.json({
21
- success: true,
22
- config: {
23
- name: config.name || "Claude Local",
24
- workspaceRoot: config.workspaceRoot || "",
25
- scopedDirs: config.scopedDirs || [],
26
- extraFlags: config.extraFlags || "",
27
- },
28
- });
24
+ try {
25
+ const rawData = fs.readFileSync(configPath, "utf8");
26
+ const config = JSON.parse(rawData);
27
+ name = config.name || "Claude Local";
28
+ workspaceRoot = config.workspaceRoot || "";
29
+ scopedDirs = config.scopedDirs || [];
30
+ extraFlags = config.extraFlags || "";
31
+ } catch (e) {
32
+ console.error("Failed to parse claude.json:", e);
33
+ }
29
34
  }
30
35
 
36
+ // Resolve workspace folders from the code-workspace file if it exists
37
+ const rootToScan = workspaceRoot || process.env.ORIGINAL_CWD || process.cwd();
38
+ const wsFolders = getWorkspaceFileFolders(rootToScan);
39
+
40
+ // Merge scopedDirs, filtering out duplicates
41
+ const mergedScopes = Array.from(new Set([...scopedDirs, ...wsFolders]));
42
+
31
43
  return NextResponse.json({
32
44
  success: true,
33
45
  config: {
34
- name: "Claude Local",
35
- workspaceRoot: "",
36
- scopedDirs: [],
37
- extraFlags: "",
46
+ name,
47
+ workspaceRoot,
48
+ scopedDirs: mergedScopes,
49
+ extraFlags,
38
50
  },
39
51
  });
40
52
  } catch (error) {
@@ -0,0 +1,34 @@
1
+ import { NextResponse } from "next/server";
2
+ import { loadRecents, addRecent } from "@/lib/recents";
3
+
4
+ export async function GET() {
5
+ try {
6
+ const recents = loadRecents();
7
+ return NextResponse.json({ success: true, recents });
8
+ } catch (error) {
9
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
10
+ return NextResponse.json({ success: false, error: errorMessage }, { status: 500 });
11
+ }
12
+ }
13
+
14
+ export async function POST(request: Request) {
15
+ try {
16
+ const body = await request.json();
17
+ const { type, path } = body;
18
+
19
+ if (!type || !path) {
20
+ return NextResponse.json({ success: false, error: "Missing type or path" }, { status: 400 });
21
+ }
22
+
23
+ if (type !== "workspace" && type !== "scope") {
24
+ return NextResponse.json({ success: false, error: "Invalid type. Must be 'workspace' or 'scope'" }, { status: 400 });
25
+ }
26
+
27
+ addRecent(type, path);
28
+ const recents = loadRecents();
29
+ return NextResponse.json({ success: true, recents });
30
+ } catch (error) {
31
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
32
+ return NextResponse.json({ success: false, error: errorMessage }, { status: 500 });
33
+ }
34
+ }
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { SessionManager } from "@/lib/agents/SessionManager";
3
+ import { addRecent } from "@/lib/recents";
3
4
 
4
5
  export async function POST(request: Request) {
5
6
  try {
@@ -14,6 +15,17 @@ export async function POST(request: Request) {
14
15
  const manager = SessionManager.getInstance();
15
16
  const session = await manager.createSession(type, name, workspaceRoot, scopedDirs);
16
17
 
18
+ // Save workspace root to recents list
19
+ if (workspaceRoot) {
20
+ addRecent("workspace", workspaceRoot);
21
+ }
22
+ // Save scoped directories to recents list
23
+ if (scopedDirs && Array.isArray(scopedDirs)) {
24
+ for (const dir of scopedDirs) {
25
+ addRecent("scope", dir);
26
+ }
27
+ }
28
+
17
29
  return NextResponse.json({
18
30
  success: true,
19
31
  session: session.getStatus()
@@ -0,0 +1,139 @@
1
+ import { NextResponse } from "next/server";
2
+ import { SessionManager } from "@/lib/agents/SessionManager";
3
+ import { SessionPersister } from "@/lib/agents/SessionPersister";
4
+ import { ContextManager } from "@/lib/agents/ContextManager";
5
+ import { addRecent } from "@/lib/recents";
6
+ import fs from "fs";
7
+ import path from "path";
8
+
9
+ export async function POST(request: Request) {
10
+ try {
11
+ const body = await request.json();
12
+ const { sessionId, path: newPath, action = "add" } = body;
13
+
14
+ if (!sessionId || !newPath) {
15
+ return NextResponse.json({ error: "Missing sessionId or path" }, { status: 400 });
16
+ }
17
+
18
+ const manager = SessionManager.getInstance();
19
+ const session = manager.getSession(sessionId);
20
+
21
+ if (!session) {
22
+ return NextResponse.json({ error: "Session not found" }, { status: 404 });
23
+ }
24
+
25
+ // Normalize path
26
+ const resolvedPath = path.resolve(newPath).replace(/\\/g, "/");
27
+
28
+ if (action === "add") {
29
+ if (!fs.existsSync(resolvedPath)) {
30
+ return NextResponse.json({ error: "Path does not exist on the server" }, { status: 400 });
31
+ }
32
+
33
+ // 1. Add to active session runner
34
+ if (!session.scopedDirs) {
35
+ session.scopedDirs = [];
36
+ }
37
+ if (!session.scopedDirs.includes(resolvedPath)) {
38
+ session.scopedDirs.push(resolvedPath);
39
+ SessionPersister.saveSession(session, true);
40
+ }
41
+
42
+ // 2. Carry over to global configurations for next Launch
43
+ const configDir = path.join(process.env.ORIGINAL_CWD || process.cwd(), ".ai", "neural-loom");
44
+
45
+ if (session.type === "claude" || session.type === "claude-docker") {
46
+ const claudeConfigPath = path.join(configDir, "claude.json");
47
+ if (fs.existsSync(claudeConfigPath)) {
48
+ try {
49
+ const config = JSON.parse(fs.readFileSync(claudeConfigPath, "utf8"));
50
+ if (!config.scopedDirs) config.scopedDirs = [];
51
+ if (!config.scopedDirs.includes(resolvedPath)) {
52
+ config.scopedDirs.push(resolvedPath);
53
+ fs.writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), "utf8");
54
+ }
55
+ } catch (err) {
56
+ console.error("Failed to update claude.json with new scoped folder:", err);
57
+ }
58
+ }
59
+ } else if (session.type === "aider" || session.type === "aider-docker") {
60
+ const aiderConfigPath = path.join(configDir, "aider.json");
61
+ if (fs.existsSync(aiderConfigPath)) {
62
+ try {
63
+ const config = JSON.parse(fs.readFileSync(aiderConfigPath, "utf8"));
64
+ if (!config.scopedDirs) config.scopedDirs = [];
65
+ if (!config.scopedDirs.includes(resolvedPath)) {
66
+ config.scopedDirs.push(resolvedPath);
67
+ fs.writeFileSync(aiderConfigPath, JSON.stringify(config, null, 2), "utf8");
68
+ }
69
+ } catch (err) {
70
+ console.error("Failed to update aider.json with new scoped folder:", err);
71
+ }
72
+ }
73
+ }
74
+
75
+ // Add to recents
76
+ addRecent("scope", resolvedPath);
77
+
78
+ // Re-compile context & hot-reload in runner
79
+ const root = session.workspaceRoot || process.env.ORIGINAL_CWD || process.cwd();
80
+ ContextManager.compileSessionContext(root, session.scopedDirs);
81
+ if (session.status === "running") {
82
+ await manager.injectCommand(sessionId, "/ccc");
83
+ }
84
+ } else if (action === "remove") {
85
+ // 1. Remove from active session runner
86
+ if (session.scopedDirs) {
87
+ session.scopedDirs = session.scopedDirs.filter(p => p !== resolvedPath);
88
+ SessionPersister.saveSession(session, true);
89
+ }
90
+
91
+ // 2. Remove from global configuration so it doesn't carry over
92
+ const configDir = path.join(process.env.ORIGINAL_CWD || process.cwd(), ".ai", "neural-loom");
93
+
94
+ if (session.type === "claude" || session.type === "claude-docker") {
95
+ const claudeConfigPath = path.join(configDir, "claude.json");
96
+ if (fs.existsSync(claudeConfigPath)) {
97
+ try {
98
+ const config = JSON.parse(fs.readFileSync(claudeConfigPath, "utf8"));
99
+ if (config.scopedDirs) {
100
+ config.scopedDirs = config.scopedDirs.filter((p: string) => p !== resolvedPath);
101
+ fs.writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), "utf8");
102
+ }
103
+ } catch (err) {
104
+ console.error("Failed to remove scoped folder from claude.json:", err);
105
+ }
106
+ }
107
+ } else if (session.type === "aider" || session.type === "aider-docker") {
108
+ const aiderConfigPath = path.join(configDir, "aider.json");
109
+ if (fs.existsSync(aiderConfigPath)) {
110
+ try {
111
+ const config = JSON.parse(fs.readFileSync(aiderConfigPath, "utf8"));
112
+ if (config.scopedDirs) {
113
+ config.scopedDirs = config.scopedDirs.filter((p: string) => p !== resolvedPath);
114
+ fs.writeFileSync(aiderConfigPath, JSON.stringify(config, null, 2), "utf8");
115
+ }
116
+ } catch (err) {
117
+ console.error("Failed to remove scoped folder from aider.json:", err);
118
+ }
119
+ }
120
+ }
121
+
122
+ // Re-compile context & hot-reload in runner
123
+ const root = session.workspaceRoot || process.env.ORIGINAL_CWD || process.cwd();
124
+ ContextManager.compileSessionContext(root, session.scopedDirs);
125
+ if (session.status === "running") {
126
+ await manager.injectCommand(sessionId, "/ccc");
127
+ }
128
+ }
129
+
130
+ return NextResponse.json({
131
+ success: true,
132
+ scopedDirs: session.scopedDirs || []
133
+ });
134
+
135
+ } catch (error) {
136
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
137
+ return NextResponse.json({ error: errorMessage }, { status: 500 });
138
+ }
139
+ }
@@ -36,10 +36,11 @@ export default function AiderWizard({ onClose, onConfirmLaunch, isDocker }: Aide
36
36
  const [errorMessage, setErrorMessage] = useState("");
37
37
  const [isBrowsingWorkspace, setIsBrowsingWorkspace] = useState(false);
38
38
  const [isBrowsingScope, setIsBrowsingScope] = useState(false);
39
+ const [recents, setRecents] = useState<{ workspaces: string[]; scopes: string[] }>({ workspaces: [], scopes: [] });
39
40
 
40
- // Load config on mount
41
+ // Load config and recents on mount
41
42
  useEffect(() => {
42
- const loadConfig = async () => {
43
+ const loadConfigAndRecents = async () => {
43
44
  try {
44
45
  const res = await fetch("/api/context/aider");
45
46
  if (res.ok) {
@@ -56,14 +57,22 @@ export default function AiderWizard({ onClose, onConfirmLaunch, isDocker }: Aide
56
57
  });
57
58
  }
58
59
  }
60
+
61
+ const recentsRes = await fetch("/api/recents");
62
+ if (recentsRes.ok) {
63
+ const rData = await recentsRes.json();
64
+ if (rData.success && rData.recents) {
65
+ setRecents(rData.recents);
66
+ }
67
+ }
59
68
  } catch (err) {
60
- console.error("Failed to load Aider configuration:", err);
69
+ console.error("Failed to load Aider configuration / recents:", err);
61
70
  } finally {
62
71
  setIsLoading(false);
63
72
  }
64
73
  };
65
74
 
66
- loadConfig();
75
+ loadConfigAndRecents();
67
76
  }, []);
68
77
 
69
78
  const handleSaveAndLaunch = async () => {
@@ -172,6 +181,31 @@ export default function AiderWizard({ onClose, onConfirmLaunch, isDocker }: Aide
172
181
  Browse...
173
182
  </button>
174
183
  </div>
184
+ {recents.workspaces.length > 0 && (
185
+ <div style={{ marginTop: "0.35rem", display: "flex", gap: "0.35rem", flexWrap: "wrap", alignItems: "center" }}>
186
+ <span style={{ fontSize: "0.65rem", color: "var(--muted)" }}>Recent:</span>
187
+ {recents.workspaces.map((wsPath) => (
188
+ <button
189
+ key={wsPath}
190
+ type="button"
191
+ onClick={() => setConfig({ ...config, workspaceRoot: wsPath })}
192
+ style={{
193
+ background: "rgba(255,255,255,0.03)",
194
+ border: "1px solid var(--border)",
195
+ borderRadius: "3px",
196
+ color: "var(--muted)",
197
+ fontSize: "0.65rem",
198
+ padding: "1px 6px",
199
+ cursor: "pointer",
200
+ fontFamily: "var(--font-mono)"
201
+ }}
202
+ title={wsPath}
203
+ >
204
+ {wsPath.split(/[/\\]/).pop() || wsPath}
205
+ </button>
206
+ ))}
207
+ </div>
208
+ )}
175
209
  <span style={{ fontSize: "0.7rem", color: "var(--muted)", marginTop: "0.25rem" }}>
176
210
  Leave blank to default to the current orchestrator directory.
177
211
  </span>
@@ -238,6 +272,38 @@ export default function AiderWizard({ onClose, onConfirmLaunch, isDocker }: Aide
238
272
  Add
239
273
  </button>
240
274
  </div>
275
+ {recents.scopes.length > 0 && (
276
+ <div style={{ marginTop: "0.35rem", display: "flex", gap: "0.35rem", flexWrap: "wrap", alignItems: "center" }}>
277
+ <span style={{ fontSize: "0.65rem", color: "var(--muted)" }}>Recent:</span>
278
+ {recents.scopes
279
+ .filter(scopePath => !config.scopedDirs.includes(scopePath))
280
+ .map((scopePath) => (
281
+ <button
282
+ key={scopePath}
283
+ type="button"
284
+ onClick={() => {
285
+ if (!config.scopedDirs.includes(scopePath)) {
286
+ setConfig({ ...config, scopedDirs: [...config.scopedDirs, scopePath] });
287
+ }
288
+ }}
289
+ style={{
290
+ background: "rgba(168, 85, 247, 0.05)",
291
+ border: "1px solid rgba(168, 85, 247, 0.2)",
292
+ borderRadius: "3px",
293
+ color: "#c084fc",
294
+ fontSize: "0.65rem",
295
+ padding: "1px 6px",
296
+ cursor: "pointer",
297
+ fontFamily: "var(--font-mono)"
298
+ }}
299
+ title={scopePath}
300
+ >
301
+ +{scopePath.split(/[/\\]/).pop() || scopePath}
302
+ </button>
303
+ ))
304
+ }
305
+ </div>
306
+ )}
241
307
  </div>
242
308
 
243
309
  <div style={formGroupStyle}>
@@ -30,10 +30,11 @@ export default function ClaudeWizard({ onClose, onConfirmLaunch, isDocker }: Cla
30
30
  const [errorMessage, setErrorMessage] = useState("");
31
31
  const [isBrowsingWorkspace, setIsBrowsingWorkspace] = useState(false);
32
32
  const [isBrowsingScope, setIsBrowsingScope] = useState(false);
33
+ const [recents, setRecents] = useState<{ workspaces: string[]; scopes: string[] }>({ workspaces: [], scopes: [] });
33
34
 
34
- // Load config on mount
35
+ // Load config and recents on mount
35
36
  useEffect(() => {
36
- const loadConfig = async () => {
37
+ const loadConfigAndRecents = async () => {
37
38
  try {
38
39
  const res = await fetch("/api/context/claude");
39
40
  if (res.ok) {
@@ -47,14 +48,22 @@ export default function ClaudeWizard({ onClose, onConfirmLaunch, isDocker }: Cla
47
48
  });
48
49
  }
49
50
  }
51
+
52
+ const recentsRes = await fetch("/api/recents");
53
+ if (recentsRes.ok) {
54
+ const rData = await recentsRes.json();
55
+ if (rData.success && rData.recents) {
56
+ setRecents(rData.recents);
57
+ }
58
+ }
50
59
  } catch (err) {
51
- console.error("Failed to load Claude configuration:", err);
60
+ console.error("Failed to load Claude configuration / recents:", err);
52
61
  } finally {
53
62
  setIsLoading(false);
54
63
  }
55
64
  };
56
65
 
57
- loadConfig();
66
+ loadConfigAndRecents();
58
67
  }, [isDocker]);
59
68
 
60
69
  const handleSaveAndLaunch = async () => {
@@ -141,6 +150,31 @@ export default function ClaudeWizard({ onClose, onConfirmLaunch, isDocker }: Cla
141
150
  Browse...
142
151
  </button>
143
152
  </div>
153
+ {recents.workspaces.length > 0 && (
154
+ <div style={{ marginTop: "0.35rem", display: "flex", gap: "0.35rem", flexWrap: "wrap", alignItems: "center" }}>
155
+ <span style={{ fontSize: "0.65rem", color: "var(--muted)" }}>Recent:</span>
156
+ {recents.workspaces.map((wsPath) => (
157
+ <button
158
+ key={wsPath}
159
+ type="button"
160
+ onClick={() => setConfig({ ...config, workspaceRoot: wsPath })}
161
+ style={{
162
+ background: "rgba(255,255,255,0.03)",
163
+ border: "1px solid var(--border)",
164
+ borderRadius: "3px",
165
+ color: "var(--muted)",
166
+ fontSize: "0.65rem",
167
+ padding: "1px 6px",
168
+ cursor: "pointer",
169
+ fontFamily: "var(--font-mono)"
170
+ }}
171
+ title={wsPath}
172
+ >
173
+ {wsPath.split(/[/\\]/).pop() || wsPath}
174
+ </button>
175
+ ))}
176
+ </div>
177
+ )}
144
178
  <span style={{ fontSize: "0.7rem", color: "var(--muted)", marginTop: "0.25rem" }}>
145
179
  The active folder Claude Code will run in. Leave blank to run in the current orchestrator directory.
146
180
  </span>
@@ -221,6 +255,38 @@ export default function ClaudeWizard({ onClose, onConfirmLaunch, isDocker }: Cla
221
255
  Add
222
256
  </button>
223
257
  </div>
258
+ {recents.scopes.length > 0 && (
259
+ <div style={{ marginTop: "0.35rem", display: "flex", gap: "0.35rem", flexWrap: "wrap", alignItems: "center" }}>
260
+ <span style={{ fontSize: "0.65rem", color: "var(--muted)" }}>Recent:</span>
261
+ {recents.scopes
262
+ .filter(scopePath => !config.scopedDirs.includes(scopePath))
263
+ .map((scopePath) => (
264
+ <button
265
+ key={scopePath}
266
+ type="button"
267
+ onClick={() => {
268
+ if (!config.scopedDirs.includes(scopePath)) {
269
+ setConfig({ ...config, scopedDirs: [...config.scopedDirs, scopePath] });
270
+ }
271
+ }}
272
+ style={{
273
+ background: "rgba(168, 85, 247, 0.05)",
274
+ border: "1px solid rgba(168, 85, 247, 0.2)",
275
+ borderRadius: "3px",
276
+ color: "#c084fc",
277
+ fontSize: "0.65rem",
278
+ padding: "1px 6px",
279
+ cursor: "pointer",
280
+ fontFamily: "var(--font-mono)"
281
+ }}
282
+ title={scopePath}
283
+ >
284
+ +{scopePath.split(/[/\\]/).pop() || scopePath}
285
+ </button>
286
+ ))
287
+ }
288
+ </div>
289
+ )}
224
290
  </div>
225
291
 
226
292
  {errorMessage && (