@atercates/claude-deck 0.2.3 → 0.2.5
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/app/api/sessions/[id]/fork/route.ts +0 -1
- package/app/api/sessions/[id]/route.ts +0 -5
- package/app/api/sessions/[id]/summarize/route.ts +2 -3
- package/app/api/sessions/route.ts +2 -11
- package/app/api/sessions/status/acknowledge/route.ts +8 -0
- package/app/api/sessions/status/route.ts +2 -233
- package/app/page.tsx +6 -13
- package/components/ClaudeProjects/ClaudeProjectCard.tsx +19 -31
- package/components/ClaudeProjects/ClaudeSessionCard.tsx +20 -31
- package/components/NewSessionDialog/AdvancedSettings.tsx +3 -12
- package/components/NewSessionDialog/NewSessionDialog.types.ts +0 -10
- package/components/NewSessionDialog/ProjectSelector.tsx +2 -7
- package/components/NewSessionDialog/hooks/useNewSessionForm.ts +3 -36
- package/components/NewSessionDialog/index.tsx +0 -7
- package/components/Pane/DesktopTabBar.tsx +62 -28
- package/components/Pane/index.tsx +5 -0
- package/components/Projects/index.ts +0 -1
- package/components/QuickSwitcher.tsx +63 -11
- package/components/SessionList/ActiveSessionsSection.tsx +116 -0
- package/components/SessionList/hooks/useSessionListMutations.ts +0 -35
- package/components/SessionList/index.tsx +9 -1
- package/components/SessionStatusBar.tsx +155 -0
- package/components/WaitingBanner.tsx +122 -0
- package/components/views/DesktopView.tsx +27 -8
- package/components/views/MobileView.tsx +6 -1
- package/components/views/types.ts +2 -0
- package/data/sessions/index.ts +0 -1
- package/data/sessions/queries.ts +1 -27
- package/data/statuses/queries.ts +68 -34
- package/hooks/useSessions.ts +0 -12
- package/lib/claude/watcher.ts +28 -5
- package/lib/db/queries.ts +4 -64
- package/lib/db/types.ts +0 -8
- package/lib/hooks/reporter.ts +116 -0
- package/lib/hooks/setup.ts +164 -0
- package/lib/orchestration.ts +16 -23
- package/lib/providers/registry.ts +3 -57
- package/lib/providers.ts +19 -100
- package/lib/status-monitor.ts +303 -0
- package/package.json +1 -1
- package/server.ts +5 -1
- package/app/api/groups/[...path]/route.ts +0 -136
- package/app/api/groups/route.ts +0 -93
- package/components/NewSessionDialog/AgentSelector.tsx +0 -37
- package/components/Projects/ProjectCard.tsx +0 -276
- package/components/TmuxSessions.tsx +0 -132
- package/data/groups/index.ts +0 -1
- package/data/groups/mutations.ts +0 -95
- package/hooks/useGroups.ts +0 -37
- package/hooks/useKeybarVisibility.ts +0 -42
- package/lib/claude/process-manager.ts +0 -278
- package/lib/status-detector.ts +0 -375
|
@@ -121,11 +121,6 @@ export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
|
|
121
121
|
updates.push(`system_prompt = ?`);
|
|
122
122
|
values.push(body.systemPrompt);
|
|
123
123
|
}
|
|
124
|
-
if (body.groupPath !== undefined) {
|
|
125
|
-
updates.push(`group_path = ?`);
|
|
126
|
-
values.push(body.groupPath);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
124
|
if (updates.length > 0) {
|
|
130
125
|
updates.push("updated_at = datetime('now')");
|
|
131
126
|
values.push(id);
|
|
@@ -264,8 +264,8 @@ export async function POST(
|
|
|
264
264
|
// Create new session in DB (using cwd already fetched above)
|
|
265
265
|
getDb()
|
|
266
266
|
.prepare(
|
|
267
|
-
`INSERT INTO sessions (id, name, tmux_name, working_directory, parent_session_id, model, initial_prompt,
|
|
268
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
267
|
+
`INSERT INTO sessions (id, name, tmux_name, working_directory, parent_session_id, model, initial_prompt, agent_type, auto_approve, project_id)
|
|
268
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
269
269
|
)
|
|
270
270
|
.run(
|
|
271
271
|
newId,
|
|
@@ -275,7 +275,6 @@ export async function POST(
|
|
|
275
275
|
null,
|
|
276
276
|
session.model,
|
|
277
277
|
`Continue from previous session. Here's a summary of the work so far:\n\n${summary}`,
|
|
278
|
-
session.group_path,
|
|
279
278
|
agentType,
|
|
280
279
|
session.auto_approve ? 1 : 0,
|
|
281
280
|
session.project_id || "uncategorized"
|
|
@@ -8,19 +8,12 @@ import { findAvailablePort } from "@/lib/ports";
|
|
|
8
8
|
import { runInBackground } from "@/lib/async-operations";
|
|
9
9
|
import { getProject } from "@/lib/projects";
|
|
10
10
|
|
|
11
|
-
// GET /api/sessions - List all sessions
|
|
11
|
+
// GET /api/sessions - List all sessions
|
|
12
12
|
export async function GET() {
|
|
13
13
|
try {
|
|
14
14
|
const sessions = await queries.getAllSessions();
|
|
15
|
-
const groups = await queries.getAllGroups();
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
const formattedGroups = groups.map((g) => ({
|
|
19
|
-
...g,
|
|
20
|
-
expanded: Boolean(g.expanded),
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
return NextResponse.json({ sessions, groups: formattedGroups });
|
|
16
|
+
return NextResponse.json({ sessions });
|
|
24
17
|
} catch (error) {
|
|
25
18
|
console.error("Error fetching sessions:", error);
|
|
26
19
|
return NextResponse.json(
|
|
@@ -56,7 +49,6 @@ export async function POST(request: NextRequest) {
|
|
|
56
49
|
parentSessionId = null,
|
|
57
50
|
model = "sonnet",
|
|
58
51
|
systemPrompt = null,
|
|
59
|
-
groupPath = "sessions",
|
|
60
52
|
claudeSessionId = null,
|
|
61
53
|
agentType: rawAgentType = "claude",
|
|
62
54
|
autoApprove = false,
|
|
@@ -141,7 +133,6 @@ export async function POST(request: NextRequest) {
|
|
|
141
133
|
parentSessionId,
|
|
142
134
|
model,
|
|
143
135
|
systemPrompt,
|
|
144
|
-
groupPath,
|
|
145
136
|
agentType,
|
|
146
137
|
autoApprove,
|
|
147
138
|
projectId
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
|
|
3
|
+
export async function POST() {
|
|
4
|
+
// With JSONL-based detection, acknowledge is a no-op.
|
|
5
|
+
// Status is determined by file content, not by a flag.
|
|
6
|
+
// The endpoint exists for API compatibility.
|
|
7
|
+
return NextResponse.json({ ok: true });
|
|
8
|
+
}
|
|
@@ -1,237 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import {
|
|
3
|
-
import { promisify } from "util";
|
|
4
|
-
import * as fs from "fs";
|
|
5
|
-
import * as path from "path";
|
|
6
|
-
import * as os from "os";
|
|
7
|
-
import { statusDetector, type SessionStatus } from "@/lib/status-detector";
|
|
8
|
-
import type { AgentType } from "@/lib/providers";
|
|
9
|
-
import {
|
|
10
|
-
getManagedSessionPattern,
|
|
11
|
-
getProviderIdFromSessionName,
|
|
12
|
-
getSessionIdFromName,
|
|
13
|
-
} from "@/lib/providers/registry";
|
|
14
|
-
import { getDb } from "@/lib/db";
|
|
15
|
-
|
|
16
|
-
const execAsync = promisify(exec);
|
|
17
|
-
|
|
18
|
-
interface SessionStatusResponse {
|
|
19
|
-
sessionName: string;
|
|
20
|
-
status: SessionStatus;
|
|
21
|
-
lastLine?: string;
|
|
22
|
-
claudeSessionId?: string | null;
|
|
23
|
-
agentType?: AgentType;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function getTmuxSessions(): Promise<string[]> {
|
|
27
|
-
try {
|
|
28
|
-
const { stdout } = await execAsync(
|
|
29
|
-
"tmux list-sessions -F '#{session_name}' 2>/dev/null || true"
|
|
30
|
-
);
|
|
31
|
-
return stdout.trim().split("\n").filter(Boolean);
|
|
32
|
-
} catch {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function getTmuxSessionCwd(sessionName: string): Promise<string | null> {
|
|
38
|
-
try {
|
|
39
|
-
const { stdout } = await execAsync(
|
|
40
|
-
`tmux display-message -t "${sessionName}" -p "#{pane_current_path}" 2>/dev/null || echo ""`
|
|
41
|
-
);
|
|
42
|
-
const cwd = stdout.trim();
|
|
43
|
-
return cwd || null;
|
|
44
|
-
} catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Get Claude session ID from tmux environment variable
|
|
50
|
-
async function getClaudeSessionIdFromEnv(
|
|
51
|
-
sessionName: string
|
|
52
|
-
): Promise<string | null> {
|
|
53
|
-
try {
|
|
54
|
-
const { stdout } = await execAsync(
|
|
55
|
-
`tmux show-environment -t "${sessionName}" CLAUDE_SESSION_ID 2>/dev/null || echo ""`
|
|
56
|
-
);
|
|
57
|
-
const line = stdout.trim();
|
|
58
|
-
if (line.startsWith("CLAUDE_SESSION_ID=")) {
|
|
59
|
-
const sessionId = line.replace("CLAUDE_SESSION_ID=", "");
|
|
60
|
-
if (sessionId && sessionId !== "null") {
|
|
61
|
-
return sessionId;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return null;
|
|
65
|
-
} catch {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Get Claude session ID by looking at session files on disk
|
|
71
|
-
function getClaudeSessionIdFromFiles(projectPath: string): string | null {
|
|
72
|
-
const home = os.homedir();
|
|
73
|
-
const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(home, ".claude");
|
|
74
|
-
const projectDirName = projectPath.replace(/\//g, "-");
|
|
75
|
-
const projectDir = path.join(claudeDir, "projects", projectDirName);
|
|
76
|
-
|
|
77
|
-
if (!fs.existsSync(projectDir)) {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const uuidPattern =
|
|
82
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/;
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
const files = fs.readdirSync(projectDir);
|
|
86
|
-
let mostRecent: string | null = null;
|
|
87
|
-
let mostRecentTime = 0;
|
|
88
|
-
|
|
89
|
-
for (const file of files) {
|
|
90
|
-
if (file.startsWith("agent-")) continue;
|
|
91
|
-
if (!uuidPattern.test(file)) continue;
|
|
92
|
-
|
|
93
|
-
const filePath = path.join(projectDir, file);
|
|
94
|
-
const stat = fs.statSync(filePath);
|
|
95
|
-
|
|
96
|
-
if (stat.mtimeMs > mostRecentTime) {
|
|
97
|
-
mostRecentTime = stat.mtimeMs;
|
|
98
|
-
mostRecent = file.replace(".jsonl", "");
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (mostRecent && Date.now() - mostRecentTime < 5 * 60 * 1000) {
|
|
103
|
-
return mostRecent;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const configFile = path.join(claudeDir, ".claude.json");
|
|
107
|
-
if (fs.existsSync(configFile)) {
|
|
108
|
-
try {
|
|
109
|
-
const config = JSON.parse(fs.readFileSync(configFile, "utf-8"));
|
|
110
|
-
if (config.projects?.[projectPath]?.lastSessionId) {
|
|
111
|
-
return config.projects[projectPath].lastSessionId;
|
|
112
|
-
}
|
|
113
|
-
} catch {
|
|
114
|
-
// Ignore config parse errors
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return null;
|
|
119
|
-
} catch {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function getClaudeSessionId(sessionName: string): Promise<string | null> {
|
|
125
|
-
const envId = await getClaudeSessionIdFromEnv(sessionName);
|
|
126
|
-
if (envId) {
|
|
127
|
-
return envId;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const cwd = await getTmuxSessionCwd(sessionName);
|
|
131
|
-
if (cwd) {
|
|
132
|
-
return getClaudeSessionIdFromFiles(cwd);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function getLastLine(sessionName: string): Promise<string> {
|
|
139
|
-
try {
|
|
140
|
-
const { stdout } = await execAsync(
|
|
141
|
-
`tmux capture-pane -t "${sessionName}" -p -S -5 2>/dev/null || echo ""`
|
|
142
|
-
);
|
|
143
|
-
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
144
|
-
return lines.pop() || "";
|
|
145
|
-
} catch {
|
|
146
|
-
return "";
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// UUID pattern for claude-deck managed sessions (derived from registry)
|
|
151
|
-
const UUID_PATTERN = getManagedSessionPattern();
|
|
152
|
-
|
|
153
|
-
// Track previous statuses to detect changes
|
|
154
|
-
const previousStatuses = new Map<string, SessionStatus>();
|
|
155
|
-
|
|
156
|
-
function getAgentTypeFromSessionName(sessionName: string): AgentType {
|
|
157
|
-
return getProviderIdFromSessionName(sessionName) || "claude";
|
|
158
|
-
}
|
|
2
|
+
import { getStatusSnapshot } from "@/lib/status-monitor";
|
|
159
3
|
|
|
160
4
|
export async function GET() {
|
|
161
|
-
|
|
162
|
-
const sessions = await getTmuxSessions();
|
|
163
|
-
|
|
164
|
-
// Get status for claude-deck managed sessions
|
|
165
|
-
const managedSessions = sessions.filter((s) => UUID_PATTERN.test(s));
|
|
166
|
-
|
|
167
|
-
// Use the new status detector
|
|
168
|
-
const statusMap: Record<string, SessionStatusResponse> = {};
|
|
169
|
-
|
|
170
|
-
const db = getDb();
|
|
171
|
-
const sessionsToUpdate: string[] = [];
|
|
172
|
-
|
|
173
|
-
// Process all sessions in parallel for speed
|
|
174
|
-
const sessionPromises = managedSessions.map(async (sessionName) => {
|
|
175
|
-
const [status, claudeSessionId, lastLine] = await Promise.all([
|
|
176
|
-
statusDetector.getStatus(sessionName),
|
|
177
|
-
getClaudeSessionId(sessionName),
|
|
178
|
-
getLastLine(sessionName),
|
|
179
|
-
]);
|
|
180
|
-
const id = getSessionIdFromName(sessionName);
|
|
181
|
-
const agentType = getAgentTypeFromSessionName(sessionName);
|
|
182
|
-
|
|
183
|
-
return { sessionName, id, status, claudeSessionId, lastLine, agentType };
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
const results = await Promise.all(sessionPromises);
|
|
187
|
-
|
|
188
|
-
for (const {
|
|
189
|
-
sessionName,
|
|
190
|
-
id,
|
|
191
|
-
status,
|
|
192
|
-
claudeSessionId,
|
|
193
|
-
lastLine,
|
|
194
|
-
agentType,
|
|
195
|
-
} of results) {
|
|
196
|
-
// Track status changes - update DB when session becomes active
|
|
197
|
-
const prevStatus = previousStatuses.get(id);
|
|
198
|
-
if (status === "running" || status === "waiting") {
|
|
199
|
-
if (prevStatus !== status) {
|
|
200
|
-
sessionsToUpdate.push(id);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
previousStatuses.set(id, status);
|
|
204
|
-
|
|
205
|
-
statusMap[id] = {
|
|
206
|
-
sessionName,
|
|
207
|
-
status,
|
|
208
|
-
lastLine,
|
|
209
|
-
claudeSessionId,
|
|
210
|
-
agentType,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Batch update sessions and claude_session_id
|
|
215
|
-
for (const id of sessionsToUpdate) {
|
|
216
|
-
db.prepare(
|
|
217
|
-
"UPDATE sessions SET updated_at = datetime('now') WHERE id = ?"
|
|
218
|
-
).run(id);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
for (const { id, claudeSessionId } of results) {
|
|
222
|
-
if (claudeSessionId) {
|
|
223
|
-
db.prepare(
|
|
224
|
-
"UPDATE sessions SET claude_session_id = ? WHERE id = ? AND (claude_session_id IS NULL OR claude_session_id != ?)"
|
|
225
|
-
).run(claudeSessionId, id, claudeSessionId);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Cleanup old trackers
|
|
230
|
-
statusDetector.cleanup();
|
|
231
|
-
|
|
232
|
-
return NextResponse.json({ statuses: statusMap });
|
|
233
|
-
} catch (error) {
|
|
234
|
-
console.error("Error getting session statuses:", error);
|
|
235
|
-
return NextResponse.json({ statuses: {} });
|
|
236
|
-
}
|
|
5
|
+
return NextResponse.json({ statuses: getStatusSnapshot() });
|
|
237
6
|
}
|
package/app/page.tsx
CHANGED
|
@@ -33,7 +33,7 @@ import { useDevServersManager } from "@/hooks/useDevServersManager";
|
|
|
33
33
|
import { useSessionStatuses } from "@/hooks/useSessionStatuses";
|
|
34
34
|
import type { Session } from "@/lib/db";
|
|
35
35
|
import type { TerminalHandle } from "@/components/Terminal";
|
|
36
|
-
import {
|
|
36
|
+
import { CLAUDE_COMMAND, buildClaudeFlags } from "@/lib/providers";
|
|
37
37
|
import { DesktopView } from "@/components/views/DesktopView";
|
|
38
38
|
import { MobileView } from "@/components/views/MobileView";
|
|
39
39
|
import { getPendingPrompt, clearPendingPrompt } from "@/stores/initialPrompt";
|
|
@@ -152,16 +152,9 @@ function HomeContent() {
|
|
|
152
152
|
async (
|
|
153
153
|
session: Session
|
|
154
154
|
): Promise<{ sessionName: string; cwd: string; command: string }> => {
|
|
155
|
-
const
|
|
156
|
-
const sessionName = session.tmux_name || `${provider.id}-${session.id}`;
|
|
155
|
+
const sessionName = session.tmux_name || `claude-${session.id}`;
|
|
157
156
|
const cwd = session.working_directory?.replace("~", "$HOME") || "$HOME";
|
|
158
157
|
|
|
159
|
-
// TODO: Add explicit "Enable Orchestration" toggle that creates .mcp.json
|
|
160
|
-
// for conductor sessions. Removed auto-creation because it pollutes projects
|
|
161
|
-
// with .mcp.json files that aren't in their .gitignore.
|
|
162
|
-
// See: /api/sessions/[id]/mcp-config, lib/mcp-config.ts
|
|
163
|
-
|
|
164
|
-
// Get parent session ID for forking
|
|
165
158
|
let parentSessionId: string | null = null;
|
|
166
159
|
if (!session.claude_session_id && session.parent_session_id) {
|
|
167
160
|
const parentSession = sessions.find(
|
|
@@ -170,22 +163,20 @@ function HomeContent() {
|
|
|
170
163
|
parentSessionId = parentSession?.claude_session_id || null;
|
|
171
164
|
}
|
|
172
165
|
|
|
173
|
-
// Check for pending initial prompt
|
|
174
166
|
const initialPrompt = getPendingPrompt(session.id);
|
|
175
167
|
if (initialPrompt) {
|
|
176
168
|
clearPendingPrompt(session.id);
|
|
177
169
|
}
|
|
178
170
|
|
|
179
|
-
const flags =
|
|
171
|
+
const flags = buildClaudeFlags({
|
|
180
172
|
sessionId: session.claude_session_id,
|
|
181
173
|
parentSessionId,
|
|
182
174
|
autoApprove: session.auto_approve,
|
|
183
175
|
model: session.model,
|
|
184
176
|
initialPrompt: initialPrompt || undefined,
|
|
185
177
|
});
|
|
186
|
-
const flagsStr = flags.join(" ");
|
|
187
178
|
|
|
188
|
-
const agentCmd = `${
|
|
179
|
+
const agentCmd = `${CLAUDE_COMMAND} ${flags.join(" ")}`;
|
|
189
180
|
const command = await getInitScriptCommand(agentCmd);
|
|
190
181
|
|
|
191
182
|
return { sessionName, cwd, command };
|
|
@@ -464,6 +455,7 @@ function HomeContent() {
|
|
|
464
455
|
paneId={paneId}
|
|
465
456
|
sessions={sessions}
|
|
466
457
|
projects={projects}
|
|
458
|
+
sessionStatuses={sessionStatuses}
|
|
467
459
|
onRegisterTerminal={registerTerminalRef}
|
|
468
460
|
onMenuClick={isMobile ? () => setSidebarOpen(true) : undefined}
|
|
469
461
|
onSelectSession={handleSelectSession}
|
|
@@ -473,6 +465,7 @@ function HomeContent() {
|
|
|
473
465
|
[
|
|
474
466
|
sessions,
|
|
475
467
|
projects,
|
|
468
|
+
sessionStatuses,
|
|
476
469
|
registerTerminalRef,
|
|
477
470
|
isMobile,
|
|
478
471
|
handleSelectSession,
|
|
@@ -6,19 +6,12 @@ import {
|
|
|
6
6
|
ChevronRight,
|
|
7
7
|
ChevronDown,
|
|
8
8
|
FolderOpen,
|
|
9
|
-
MoreHorizontal,
|
|
10
9
|
Plus,
|
|
11
10
|
Eye,
|
|
12
11
|
EyeOff,
|
|
13
12
|
Loader2,
|
|
14
13
|
} from "lucide-react";
|
|
15
14
|
import { Button } from "@/components/ui/button";
|
|
16
|
-
import {
|
|
17
|
-
DropdownMenu,
|
|
18
|
-
DropdownMenuContent,
|
|
19
|
-
DropdownMenuItem,
|
|
20
|
-
DropdownMenuTrigger,
|
|
21
|
-
} from "@/components/ui/dropdown-menu";
|
|
22
15
|
import {
|
|
23
16
|
ContextMenu,
|
|
24
17
|
ContextMenuContent,
|
|
@@ -119,30 +112,25 @@ export function ClaudeProjectCard({
|
|
|
119
112
|
<Plus className="h-3.5 w-3.5" />
|
|
120
113
|
</Button>
|
|
121
114
|
)}
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
Hide project
|
|
142
|
-
</DropdownMenuItem>
|
|
143
|
-
)}
|
|
144
|
-
</DropdownMenuContent>
|
|
145
|
-
</DropdownMenu>
|
|
115
|
+
<Button
|
|
116
|
+
variant="ghost"
|
|
117
|
+
size="icon-sm"
|
|
118
|
+
className="h-7 w-7 flex-shrink-0 opacity-100 md:h-6 md:w-6 md:opacity-0 md:group-hover:opacity-100"
|
|
119
|
+
onClick={(e) => {
|
|
120
|
+
e.stopPropagation();
|
|
121
|
+
if (project.hidden) {
|
|
122
|
+
handleUnhideProject();
|
|
123
|
+
} else {
|
|
124
|
+
handleHideProject();
|
|
125
|
+
}
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
{project.hidden ? (
|
|
129
|
+
<Eye className="h-3.5 w-3.5" />
|
|
130
|
+
) : (
|
|
131
|
+
<EyeOff className="h-3.5 w-3.5" />
|
|
132
|
+
)}
|
|
133
|
+
</Button>
|
|
146
134
|
</div>
|
|
147
135
|
);
|
|
148
136
|
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
|
-
import { MessageSquare,
|
|
4
|
+
import { MessageSquare, Eye, EyeOff } from "lucide-react";
|
|
5
5
|
import { Button } from "@/components/ui/button";
|
|
6
|
-
import {
|
|
7
|
-
DropdownMenu,
|
|
8
|
-
DropdownMenuContent,
|
|
9
|
-
DropdownMenuItem,
|
|
10
|
-
DropdownMenuTrigger,
|
|
11
|
-
} from "@/components/ui/dropdown-menu";
|
|
12
6
|
import type { ClaudeSession } from "@/data/claude";
|
|
13
7
|
|
|
14
8
|
interface ClaudeSessionCardProps {
|
|
@@ -71,30 +65,25 @@ export function ClaudeSessionCard({
|
|
|
71
65
|
<span className="text-muted-foreground flex-shrink-0 text-[10px]">
|
|
72
66
|
{getTimeAgo(session.lastActivity)}
|
|
73
67
|
</span>
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Hide session
|
|
94
|
-
</DropdownMenuItem>
|
|
95
|
-
)}
|
|
96
|
-
</DropdownMenuContent>
|
|
97
|
-
</DropdownMenu>
|
|
68
|
+
<Button
|
|
69
|
+
variant="ghost"
|
|
70
|
+
size="icon-sm"
|
|
71
|
+
className="h-6 w-6 flex-shrink-0 opacity-0 group-hover:opacity-100"
|
|
72
|
+
onClick={(e) => {
|
|
73
|
+
e.stopPropagation();
|
|
74
|
+
if (session.hidden) {
|
|
75
|
+
onUnhide();
|
|
76
|
+
} else {
|
|
77
|
+
onHide();
|
|
78
|
+
}
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
{session.hidden ? (
|
|
82
|
+
<Eye className="h-3.5 w-3.5" />
|
|
83
|
+
) : (
|
|
84
|
+
<EyeOff className="h-3.5 w-3.5" />
|
|
85
|
+
)}
|
|
86
|
+
</Button>
|
|
98
87
|
</div>
|
|
99
88
|
);
|
|
100
89
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { ChevronRight } from "lucide-react";
|
|
2
|
-
import
|
|
3
|
-
import { getProviderDefinition } from "@/lib/providers";
|
|
2
|
+
import { CLAUDE_AUTO_APPROVE_FLAG } from "@/lib/providers";
|
|
4
3
|
|
|
5
4
|
interface AdvancedSettingsProps {
|
|
6
5
|
open: boolean;
|
|
7
6
|
onOpenChange: (open: boolean) => void;
|
|
8
|
-
agentType: AgentType;
|
|
9
7
|
useTmux: boolean;
|
|
10
8
|
onUseTmuxChange: (checked: boolean) => void;
|
|
11
9
|
skipPermissions: boolean;
|
|
@@ -15,15 +13,11 @@ interface AdvancedSettingsProps {
|
|
|
15
13
|
export function AdvancedSettings({
|
|
16
14
|
open,
|
|
17
15
|
onOpenChange,
|
|
18
|
-
agentType,
|
|
19
16
|
useTmux,
|
|
20
17
|
onUseTmuxChange,
|
|
21
18
|
skipPermissions,
|
|
22
19
|
onSkipPermissionsChange,
|
|
23
20
|
}: AdvancedSettingsProps) {
|
|
24
|
-
const provider = getProviderDefinition(agentType);
|
|
25
|
-
const supportsAutoApprove = Boolean(provider.autoApproveFlag);
|
|
26
|
-
|
|
27
21
|
return (
|
|
28
22
|
<div className="border-border rounded-lg border">
|
|
29
23
|
<button
|
|
@@ -58,16 +52,13 @@ export function AdvancedSettings({
|
|
|
58
52
|
type="checkbox"
|
|
59
53
|
id="skipPermissions"
|
|
60
54
|
checked={skipPermissions}
|
|
61
|
-
disabled={!supportsAutoApprove}
|
|
62
55
|
onChange={(e) => onSkipPermissionsChange(e.target.checked)}
|
|
63
|
-
className="border-border bg-background accent-primary h-4 w-4 rounded
|
|
56
|
+
className="border-border bg-background accent-primary h-4 w-4 rounded"
|
|
64
57
|
/>
|
|
65
58
|
<label htmlFor="skipPermissions" className="cursor-pointer text-sm">
|
|
66
59
|
Auto-approve tool calls
|
|
67
60
|
<span className="text-muted-foreground ml-1">
|
|
68
|
-
{
|
|
69
|
-
? `(${provider.autoApproveFlag})`
|
|
70
|
-
: "(not supported)"}
|
|
61
|
+
({CLAUDE_AUTO_APPROVE_FLAG})
|
|
71
62
|
</span>
|
|
72
63
|
</label>
|
|
73
64
|
</div>
|
|
@@ -3,7 +3,6 @@ import type { ProjectWithDevServers } from "@/lib/projects";
|
|
|
3
3
|
|
|
4
4
|
// LocalStorage keys
|
|
5
5
|
export const SKIP_PERMISSIONS_KEY = "agentOS:skipPermissions";
|
|
6
|
-
export const AGENT_TYPE_KEY = "agentOS:defaultAgentType";
|
|
7
6
|
export const RECENT_DIRS_KEY = "agentOS:recentDirectories";
|
|
8
7
|
export const USE_TMUX_KEY = "agentOS:useTmux";
|
|
9
8
|
export const MAX_RECENT_DIRS = 5;
|
|
@@ -84,15 +83,6 @@ export interface GitInfo {
|
|
|
84
83
|
currentBranch: string | null;
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
// Agent type options
|
|
88
|
-
export const AGENT_OPTIONS: {
|
|
89
|
-
value: AgentType;
|
|
90
|
-
label: string;
|
|
91
|
-
description: string;
|
|
92
|
-
}[] = [
|
|
93
|
-
{ value: "claude", label: "Claude Code", description: "Anthropic's CLI" },
|
|
94
|
-
];
|
|
95
|
-
|
|
96
86
|
// Props for main dialog
|
|
97
87
|
export interface NewSessionDialogProps {
|
|
98
88
|
open: boolean;
|
|
@@ -9,14 +9,11 @@ import {
|
|
|
9
9
|
} from "@/components/ui/select";
|
|
10
10
|
import { Plus } from "lucide-react";
|
|
11
11
|
import type { ProjectWithDevServers } from "@/lib/projects";
|
|
12
|
-
import type { AgentType } from "@/lib/providers";
|
|
13
|
-
|
|
14
12
|
interface ProjectSelectorProps {
|
|
15
13
|
projects: ProjectWithDevServers[];
|
|
16
14
|
projectId: string | null;
|
|
17
15
|
onProjectChange: (projectId: string | null) => void;
|
|
18
16
|
workingDirectory: string;
|
|
19
|
-
agentType: AgentType;
|
|
20
17
|
showNewProject: boolean;
|
|
21
18
|
onShowNewProjectChange: (show: boolean) => void;
|
|
22
19
|
newProjectName: string;
|
|
@@ -31,7 +28,6 @@ export function ProjectSelector({
|
|
|
31
28
|
projectId,
|
|
32
29
|
onProjectChange,
|
|
33
30
|
workingDirectory,
|
|
34
|
-
agentType,
|
|
35
31
|
showNewProject,
|
|
36
32
|
onShowNewProjectChange,
|
|
37
33
|
newProjectName,
|
|
@@ -129,7 +125,7 @@ export function ProjectSelector({
|
|
|
129
125
|
{showNewProject && (
|
|
130
126
|
<p className="text-muted-foreground text-xs">
|
|
131
127
|
{workingDirectory && workingDirectory !== "~"
|
|
132
|
-
? `New project will use: ${workingDirectory}
|
|
128
|
+
? `New project will use: ${workingDirectory}`
|
|
133
129
|
: "Enter a working directory above to create a project"}
|
|
134
130
|
</p>
|
|
135
131
|
)}
|
|
@@ -137,8 +133,7 @@ export function ProjectSelector({
|
|
|
137
133
|
selectedProject &&
|
|
138
134
|
!selectedProject.is_uncategorized && (
|
|
139
135
|
<p className="text-muted-foreground text-xs">
|
|
140
|
-
Settings inherited: {selectedProject.working_directory}
|
|
141
|
-
{selectedProject.agent_type}
|
|
136
|
+
Settings inherited: {selectedProject.working_directory}
|
|
142
137
|
</p>
|
|
143
138
|
)}
|
|
144
139
|
</div>
|