@atercates/claude-deck 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +4 -18
  2. package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +1 -1
  3. package/app/api/projects/[id]/repositories/[repoId]/route.ts +1 -1
  4. package/app/api/sessions/[id]/fork/route.ts +1 -1
  5. package/app/api/sessions/[id]/pr/route.ts +1 -1
  6. package/app/api/sessions/[id]/preview/route.ts +1 -1
  7. package/app/api/sessions/[id]/route.ts +13 -4
  8. package/app/api/sessions/[id]/send-keys/route.ts +1 -1
  9. package/app/api/sessions/route.ts +2 -2
  10. package/components/ConductorPanel.tsx +1 -1
  11. package/components/DevServers/ServerLogsModal.tsx +24 -21
  12. package/components/DiffViewer/DiffModal.tsx +0 -1
  13. package/components/FileExplorer/index.tsx +1 -1
  14. package/components/GitDrawer/FileEditDialog.tsx +1 -1
  15. package/components/GitPanel/FileChanges.tsx +6 -2
  16. package/components/GitPanel/index.tsx +1 -1
  17. package/components/Pane/index.tsx +16 -15
  18. package/components/Projects/ProjectCard.tsx +1 -1
  19. package/components/QuickSwitcher.tsx +1 -0
  20. package/components/SessionList/SessionList.types.ts +1 -1
  21. package/components/SessionList/index.tsx +8 -8
  22. package/components/Terminal/hooks/useTerminalConnection.ts +3 -2
  23. package/components/Terminal/hooks/websocket-connection.ts +1 -0
  24. package/data/git/queries.ts +0 -1
  25. package/lib/claude/process-manager.ts +1 -1
  26. package/lib/code-search.ts +5 -5
  27. package/lib/db/index.ts +1 -1
  28. package/lib/git-history.ts +1 -1
  29. package/lib/git.ts +0 -1
  30. package/lib/multi-repo-git.ts +0 -1
  31. package/lib/projects.ts +29 -8
  32. package/package.json +4 -4
  33. package/scripts/agent-os +1 -1
  34. package/scripts/install.sh +2 -2
  35. package/server.ts +1 -1
package/README.md CHANGED
@@ -30,13 +30,13 @@ claude-deck start
30
30
  For fresh installs without Node.js:
31
31
 
32
32
  ```bash
33
- curl -fsSL https://raw.githubusercontent.com/atercates/claude-deck/main/scripts/install.sh | bash
33
+ curl -fsSL https://raw.githubusercontent.com/ATERCATES/claude-deck/main/scripts/install.sh | bash
34
34
  claude-deck start
35
35
  ```
36
36
 
37
37
  ### Desktop App
38
38
 
39
- Download native desktop apps from [Releases](https://github.com/atercates/claude-deck/releases):
39
+ Download native desktop apps from [Releases](https://github.com/ATERCATES/claude-deck/releases):
40
40
 
41
41
  - macOS (Apple Silicon): `.dmg`
42
42
  - Linux: `.deb` or `.AppImage`
@@ -48,7 +48,7 @@ Download native desktop apps from [Releases](https://github.com/atercates/claude
48
48
  ### Manual Install
49
49
 
50
50
  ```bash
51
- git clone https://github.com/atercates/claude-deck
51
+ git clone https://github.com/ATERCATES/claude-deck
52
52
  cd claude-deck
53
53
  npm install
54
54
  npm run dev # http://localhost:3011
@@ -61,20 +61,6 @@ npm run dev # http://localhost:3011
61
61
  - [ripgrep](https://github.com/BurntSushi/ripgrep) (for code search - auto-installed by installer script, or run `claude-deck update`)
62
62
  - At least one AI CLI: [Claude Code](https://github.com/anthropics/claude-code), [Codex](https://github.com/openai/codex), [OpenCode](https://github.com/anomalyco/opencode), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Aider](https://aider.chat/), or [Cursor CLI](https://cursor.com/cli)
63
63
 
64
- ## Supported Agents
65
-
66
- | Agent | Resume | Fork | Auto-Approve |
67
- | ----------- | ------ | ---- | -------------------------------- |
68
- | Claude Code | ✅ | ✅ | `--dangerously-skip-permissions` |
69
- | Codex | ❌ | ❌ | `--approval-mode full-auto` |
70
- | OpenCode | ❌ | ❌ | Config file |
71
- | Gemini CLI | ❌ | ❌ | `--yolomode` |
72
- | Aider | ❌ | ❌ | `--yes` |
73
- | Cursor CLI | ❌ | ❌ | N/A |
74
- | Amp | ❌ | ❌ | `--dangerously-allow-all` |
75
- | Pi | ❌ | ❌ | N/A |
76
- | Oh My Pi | ❌ | ❌ | N/A |
77
-
78
64
  ## Features
79
65
 
80
66
  - **Mobile-first** - Full functionality from your phone, not a dumbed-down responsive view
@@ -113,7 +99,7 @@ For configuration and advanced usage, see the [docs](https://www.runagentos.com/
113
99
 
114
100
  ## Related Projects
115
101
 
116
- - **[aTerm](https://github.com/atercates/aTerm)** - A Tauri-based desktop terminal workspace for AI-assisted coding. While ClaudeDeck is a mobile-first web UI, aTerm is a native desktop app with multi-pane layouts optimized for running AI coding agents (Claude Code, Aider, OpenCode) alongside shells, dev servers, and a built-in git panel. Choose ClaudeDeck for mobile access and browser-based workflows, or aTerm for a native desktop terminal experience.
102
+ - **[aTerm](https://github.com/ATERCATES/aTerm)** - A Tauri-based desktop terminal workspace for AI-assisted coding. While ClaudeDeck is a mobile-first web UI, aTerm is a native desktop app with multi-pane layouts optimized for running AI coding agents (Claude Code, Aider, OpenCode) alongside shells, dev servers, and a built-in git panel. Choose ClaudeDeck for mobile access and browser-based workflows, or aTerm for a native desktop terminal experience.
117
103
  - **[LumifyHub](https://lumifyhub.io)** - Team collaboration platform with real-time chat and structured documentation. Useful alongside ClaudeDeck for coordinating multi-agent work across a team — share session context, document architectural decisions from coding sessions, and track progress across parallel agent workflows.
118
104
 
119
105
  ## License
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { updateProjectDevServer, deleteProjectDevServer } from "@/lib/projects";
3
- import { queries, type ProjectDevServer } from "@/lib/db";
3
+ import { queries } from "@/lib/db";
4
4
 
5
5
  interface RouteParams {
6
6
  params: Promise<{ id: string; dsId: string }>;
@@ -3,7 +3,7 @@ import {
3
3
  updateProjectRepository,
4
4
  deleteProjectRepository,
5
5
  } from "@/lib/projects";
6
- import { queries, type ProjectRepository } from "@/lib/db";
6
+ import { queries } from "@/lib/db";
7
7
 
8
8
  interface RouteParams {
9
9
  params: Promise<{ id: string; repoId: string }>;
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { randomUUID } from "crypto";
3
- import { queries, type Session } from "@/lib/db";
3
+ import { queries } from "@/lib/db";
4
4
 
5
5
  interface RouteParams {
6
6
  params: Promise<{ id: string }>;
@@ -1,7 +1,7 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { exec } from "child_process";
3
3
  import { promisify } from "util";
4
- import { queries, type Session } from "@/lib/db";
4
+ import { queries } from "@/lib/db";
5
5
 
6
6
  const execAsync = promisify(exec);
7
7
 
@@ -1,7 +1,7 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { exec } from "child_process";
3
3
  import { promisify } from "util";
4
- import { queries, type Session } from "@/lib/db";
4
+ import { queries } from "@/lib/db";
5
5
 
6
6
  const execAsync = promisify(exec);
7
7
 
@@ -1,7 +1,7 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { exec } from "child_process";
3
3
  import { promisify } from "util";
4
- import { queries, type Session } from "@/lib/db";
4
+ import { queries } from "@/lib/db";
5
5
  import { deleteWorktree, isClaudeDeckWorktree } from "@/lib/worktrees";
6
6
  import { releasePort } from "@/lib/ports";
7
7
  import { killWorker } from "@/lib/orchestration";
@@ -81,7 +81,10 @@ export async function PATCH(request: NextRequest, { params }: RouteParams) {
81
81
  }
82
82
 
83
83
  // If this is a worktree session, also rename the git branch
84
- if (existing.worktree_path && isClaudeDeckWorktree(existing.worktree_path)) {
84
+ if (
85
+ existing.worktree_path &&
86
+ isClaudeDeckWorktree(existing.worktree_path)
87
+ ) {
85
88
  try {
86
89
  const currentBranch = await getCurrentBranch(existing.worktree_path);
87
90
  const newBranchName = generateBranchName(body.name);
@@ -174,7 +177,10 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
174
177
  await queries.deleteSession(id);
175
178
 
176
179
  // Clean up worktree in background (non-blocking)
177
- if (existing.worktree_path && isClaudeDeckWorktree(existing.worktree_path)) {
180
+ if (
181
+ existing.worktree_path &&
182
+ isClaudeDeckWorktree(existing.worktree_path)
183
+ ) {
178
184
  const worktreePath = existing.worktree_path; // Capture for closure
179
185
  runInBackground(async () => {
180
186
  const { exec } = await import("child_process");
@@ -196,7 +202,10 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
196
202
  // Also cleanup worker worktrees in background
197
203
  if (workers.length > 0) {
198
204
  for (const worker of workers) {
199
- if (worker.worktree_path && isClaudeDeckWorktree(worker.worktree_path)) {
205
+ if (
206
+ worker.worktree_path &&
207
+ isClaudeDeckWorktree(worker.worktree_path)
208
+ ) {
200
209
  const worktreePath = worker.worktree_path; // Capture for closure
201
210
  const workerId = worker.id; // Capture ID for task name
202
211
  runInBackground(async () => {
@@ -1,7 +1,7 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { exec } from "child_process";
3
3
  import { promisify } from "util";
4
- import { queries, type Session } from "@/lib/db";
4
+ import { queries } from "@/lib/db";
5
5
  import { appendFileSync } from "fs";
6
6
 
7
7
  const execAsync = promisify(exec);
@@ -1,6 +1,6 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  import { randomUUID } from "crypto";
3
- import { queries, type Session, type Group } from "@/lib/db";
3
+ import { queries, type Session } from "@/lib/db";
4
4
  import { isValidAgentType, type AgentType } from "@/lib/providers";
5
5
  import { createWorktree } from "@/lib/worktrees";
6
6
  import { setupWorktree, type SetupResult } from "@/lib/env-setup";
@@ -88,7 +88,7 @@ export async function POST(request: NextRequest) {
88
88
  let branchName: string | null = null;
89
89
  let actualWorkingDirectory = workingDirectory;
90
90
  let port: number | null = null;
91
- let setupResult: SetupResult | null = null;
91
+ const setupResult: SetupResult | null = null;
92
92
 
93
93
  if (useWorktree && featureName) {
94
94
  try {
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, useEffect, useCallback } from "react";
4
- import { WorkerCard, type WorkerInfo, type WorkerStatus } from "./WorkerCard";
4
+ import { WorkerCard, type WorkerInfo } from "./WorkerCard";
5
5
  import { Button } from "./ui/button";
6
6
  import {
7
7
  RefreshCw,
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useState, useEffect, useRef } from "react";
3
+ import { useState, useEffect, useRef, useCallback } from "react";
4
4
  import { X, RefreshCw, Loader2 } from "lucide-react";
5
5
  import { cn } from "@/lib/utils";
6
6
 
@@ -20,31 +20,34 @@ export function ServerLogsModal({
20
20
  const [refreshing, setRefreshing] = useState(false);
21
21
  const logsRef = useRef<HTMLDivElement>(null);
22
22
 
23
- const fetchLogs = async (isRefresh = false) => {
24
- if (isRefresh) {
25
- setRefreshing(true);
26
- } else {
27
- setLoading(true);
28
- }
23
+ const fetchLogs = useCallback(
24
+ async (isRefresh = false) => {
25
+ if (isRefresh) {
26
+ setRefreshing(true);
27
+ } else {
28
+ setLoading(true);
29
+ }
29
30
 
30
- try {
31
- const res = await fetch(`/api/dev-servers/${serverId}/logs?lines=200`);
32
- if (res.ok) {
33
- const data = await res.json();
34
- setLogs(data.logs || []);
31
+ try {
32
+ const res = await fetch(`/api/dev-servers/${serverId}/logs?lines=200`);
33
+ if (res.ok) {
34
+ const data = await res.json();
35
+ setLogs(data.logs || []);
36
+ }
37
+ } catch (err) {
38
+ console.error("Failed to fetch logs:", err);
39
+ } finally {
40
+ setLoading(false);
41
+ setRefreshing(false);
35
42
  }
36
- } catch (err) {
37
- console.error("Failed to fetch logs:", err);
38
- } finally {
39
- setLoading(false);
40
- setRefreshing(false);
41
- }
42
- };
43
+ },
44
+ [serverId]
45
+ );
43
46
 
44
47
  // Initial fetch
45
48
  useEffect(() => {
46
49
  fetchLogs();
47
- }, [serverId]);
50
+ }, [fetchLogs]);
48
51
 
49
52
  // Auto-scroll to bottom when logs update
50
53
  useEffect(() => {
@@ -59,7 +62,7 @@ export function ServerLogsModal({
59
62
  fetchLogs(true);
60
63
  }, 3000);
61
64
  return () => clearInterval(interval);
62
- }, [serverId]);
65
+ }, [fetchLogs]);
63
66
 
64
67
  return (
65
68
  <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
@@ -4,7 +4,6 @@ import { X, Plus, Minus, ChevronLeft } from "lucide-react";
4
4
  import { Button } from "@/components/ui/button";
5
5
  import { UnifiedDiff } from "./UnifiedDiff";
6
6
  import { parseDiff, getDiffFileName, getDiffSummary } from "@/lib/diff-parser";
7
- import { cn } from "@/lib/utils";
8
7
 
9
8
  interface DiffModalProps {
10
9
  diff: string;
@@ -213,7 +213,7 @@ function DesktopFileExplorer({
213
213
  openFiles,
214
214
  activeFilePath,
215
215
  activeFile,
216
- saving,
216
+ saving: _saving,
217
217
  onFileClick,
218
218
  onSelectTab,
219
219
  onCloseTab,
@@ -108,7 +108,7 @@ export function FileEditDialog({
108
108
  : baseDir;
109
109
  const filePath = `${expandedBaseDir}/${file.path}`;
110
110
  const fileName = file.path.split("/").pop() || file.path;
111
- const repoName = "repoName" in file ? file.repoName : null;
111
+ const _repoName = "repoName" in file ? file.repoName : null;
112
112
  const hasChanges = modifiedContent !== initialModified;
113
113
 
114
114
  useEffect(() => {
@@ -44,7 +44,7 @@ const SWIPE_THRESHOLD = 80;
44
44
  export function FileChanges({
45
45
  files,
46
46
  title,
47
- emptyMessage,
47
+ emptyMessage: _emptyMessage,
48
48
  selectedPath,
49
49
  onFileClick,
50
50
  onStage,
@@ -99,7 +99,11 @@ export function FileChanges({
99
99
  <button
100
100
  onClick={(e) => {
101
101
  e.stopPropagation();
102
- isStaged ? onUnstageAll?.() : onStageAll?.();
102
+ if (isStaged) {
103
+ onUnstageAll?.();
104
+ } else {
105
+ onStageAll?.();
106
+ }
103
107
  }}
104
108
  className="text-muted-foreground hover:text-foreground flex items-center gap-1 text-xs transition-colors"
105
109
  >
@@ -572,7 +572,7 @@ function MobileGitPanel({
572
572
  onUnstageAll,
573
573
  onBack,
574
574
  onCommit,
575
- onShowPRModal,
575
+ onShowPRModal: _onShowPRModal,
576
576
  onClosePRModal,
577
577
  onCreatePR,
578
578
  }: MobileGitPanelProps) {
@@ -4,10 +4,7 @@ import { useRef, useCallback, useEffect, memo, useState, useMemo } from "react";
4
4
  import dynamic from "next/dynamic";
5
5
  import { usePanes } from "@/contexts/PaneContext";
6
6
  import { useViewport } from "@/hooks/useViewport";
7
- import type {
8
- TerminalHandle,
9
- TerminalScrollState,
10
- } from "@/components/Terminal";
7
+ import type { TerminalHandle } from "@/components/Terminal";
11
8
  import type { Session, Project } from "@/lib/db";
12
9
  import { sessionRegistry } from "@/lib/client/session-registry";
13
10
  import { cn } from "@/lib/utils";
@@ -114,16 +111,20 @@ export const Pane = memo(function Pane({
114
111
  ? (terminalRefs.current.get(activeTab.id) ?? null)
115
112
  : null;
116
113
  const isFocused = focusedPaneId === paneId;
117
- const session = activeTab
118
- ? (sessions.find((s) => s.id === activeTab.sessionId) ??
119
- (activeTab.sessionId
120
- ? ({
121
- id: activeTab.sessionId,
122
- name: activeTab.sessionName || activeTab.sessionId.slice(0, 8),
123
- working_directory: activeTab.workingDirectory || "~",
124
- } as Session)
125
- : null))
126
- : null;
114
+ const session = useMemo(
115
+ () =>
116
+ activeTab
117
+ ? (sessions.find((s) => s.id === activeTab.sessionId) ??
118
+ (activeTab.sessionId
119
+ ? ({
120
+ id: activeTab.sessionId,
121
+ name: activeTab.sessionName || activeTab.sessionId.slice(0, 8),
122
+ working_directory: activeTab.workingDirectory || "~",
123
+ } as Session)
124
+ : null))
125
+ : null,
126
+ [activeTab, sessions]
127
+ );
127
128
 
128
129
  // File editor state - lifted here so it persists across view switches
129
130
  const fileEditor = useFileEditor();
@@ -236,7 +237,7 @@ export const Pane = memo(function Pane({
236
237
  setTimeout(() => handle.sendCommand(`tmux attach -t ${tmuxName}`), 100);
237
238
  }
238
239
  },
239
- [paneId, sessions, onRegisterTerminal]
240
+ [paneId, paneData, sessions, onRegisterTerminal]
240
241
  );
241
242
 
242
243
  // Track current tab ID for cleanup
@@ -108,7 +108,7 @@ export function ProjectCard({
108
108
  setIsEditing(false);
109
109
  };
110
110
 
111
- const handleClick = (e: React.MouseEvent) => {
111
+ const handleClick = (_e: React.MouseEvent) => {
112
112
  if (isEditing) return;
113
113
  onClick?.();
114
114
  onToggleExpanded?.(!project.expanded);
@@ -100,6 +100,7 @@ export function QuickSwitcher({
100
100
  (a, b) =>
101
101
  new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime()
102
102
  );
103
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- only re-run when .data changes, not entire query objects
103
104
  }, [s0.data, s1.data, s2.data, s3.data, topProjects]);
104
105
 
105
106
  const filteredSessions = useMemo(() => {
@@ -1,4 +1,4 @@
1
- import type { Session, Group } from "@/lib/db";
1
+ import type { Session } from "@/lib/db";
2
2
 
3
3
  export interface SessionStatus {
4
4
  sessionName: string;
@@ -23,14 +23,14 @@ import type { SessionListProps } from "./SessionList.types";
23
23
  export type { SessionListProps } from "./SessionList.types";
24
24
 
25
25
  export function SessionList({
26
- activeSessionId,
27
- sessionStatuses,
26
+ activeSessionId: _activeSessionId,
27
+ sessionStatuses: _sessionStatuses,
28
28
  onSelect,
29
- onOpenInTab,
30
- onNewSessionInProject,
31
- onOpenTerminal,
32
- onStartDevServer,
33
- onCreateDevServer,
29
+ onOpenInTab: _onOpenInTab,
30
+ onNewSessionInProject: _onNewSessionInProject,
31
+ onOpenTerminal: _onOpenTerminal,
32
+ onStartDevServer: _onStartDevServer,
33
+ onCreateDevServer: _onCreateDevServer,
34
34
  onResumeClaudeSession,
35
35
  onNewSession,
36
36
  }: SessionListProps) {
@@ -66,7 +66,7 @@ export function SessionList({
66
66
  rect: DOMRect;
67
67
  } | null>(null);
68
68
 
69
- const hoverHandlers = {
69
+ const _hoverHandlers = {
70
70
  onHoverStart: useCallback(
71
71
  (_session: Session, _rect: DOMRect) => {
72
72
  if (isMobile) return;
@@ -131,6 +131,7 @@ export function useTerminalConnection({
131
131
  useEffect(() => {
132
132
  if (!terminalRef.current) return;
133
133
 
134
+ const terminalElement = terminalRef.current;
134
135
  let cancelled = false;
135
136
  // Reset intentional close flag (may be true from previous cleanup)
136
137
  intentionalCloseRef.current = false;
@@ -208,9 +209,9 @@ export function useTerminalConnection({
208
209
 
209
210
  // Save scroll state before unmount
210
211
  const term = xtermRef.current;
211
- if (term && callbacksRef.current.onBeforeUnmount && terminalRef.current) {
212
+ if (term && callbacksRef.current.onBeforeUnmount && terminalElement) {
212
213
  const buffer = term.buffer.active;
213
- const viewport = terminalRef.current.querySelector(
214
+ const viewport = terminalElement.querySelector(
214
215
  ".xterm-viewport"
215
216
  ) as HTMLElement;
216
217
  callbacksRef.current.onBeforeUnmount({
@@ -57,6 +57,7 @@ export function createWebSocketConnection(
57
57
 
58
58
  // Force reconnect - kills any existing connection and creates fresh one
59
59
  // Note: savedHandlers is populated after handlers are defined below
60
+ // eslint-disable-next-line prefer-const -- assigned after handler definitions below
60
61
  let savedHandlers: {
61
62
  onopen: typeof ws.onopen;
62
63
  onmessage: typeof ws.onmessage;
@@ -6,7 +6,6 @@ export { gitKeys };
6
6
  import type { CommitSummary, CommitDetail } from "@/lib/git-history";
7
7
  import type { GitStatus } from "@/lib/git-status";
8
8
  import type { MultiRepoGitStatus } from "@/lib/multi-repo-git";
9
- import type { ProjectRepository } from "@/lib/db";
10
9
 
11
10
  export interface PRInfo {
12
11
  number: number;
@@ -1,7 +1,7 @@
1
1
  import { spawn, ChildProcess } from "child_process";
2
2
  import { WebSocket } from "ws";
3
3
  import { StreamParser } from "./stream-parser";
4
- import { queries, type Session } from "../db";
4
+ import { queries } from "../db";
5
5
  import type { ClaudeSessionOptions, ClientEvent } from "./types";
6
6
 
7
7
  interface ManagedSession {
@@ -1,4 +1,4 @@
1
- import { execSync } from "child_process";
1
+ import { execSync, spawnSync } from "child_process";
2
2
 
3
3
  /**
4
4
  * Check if ripgrep is available on the system
@@ -46,13 +46,13 @@ export function searchCode(
46
46
  const {
47
47
  maxResults = 100,
48
48
  contextLines = 2,
49
- filePattern = "*",
50
- caseSensitive = false,
49
+ filePattern: _filePattern = "*",
50
+ caseSensitive: _caseSensitive = false,
51
51
  } = options;
52
52
 
53
53
  try {
54
54
  // Use spawn instead of execSync for better control
55
- const { spawnSync } = require("child_process");
55
+ // spawnSync imported at top level
56
56
 
57
57
  const args = [
58
58
  "--json",
@@ -100,7 +100,7 @@ export function searchCode(
100
100
  } catch (error) {
101
101
  console.error("Error in searchCode:", error);
102
102
  // ENOENT = command not found
103
- if ((error as any).code === "ENOENT") {
103
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
104
104
  throw new Error(
105
105
  "ripgrep (rg) not found. Install with: brew install ripgrep"
106
106
  );
package/lib/db/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import Database from "better-sqlite3";
2
+ import fs from "fs";
2
3
  import path from "path";
3
4
  import os from "os";
4
5
  import { createSchema } from "./schema";
@@ -14,7 +15,6 @@ let _db: Database.Database | null = null;
14
15
 
15
16
  export function getDb(): Database.Database {
16
17
  if (!_db) {
17
- const fs = require("fs");
18
18
  fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
19
19
 
20
20
  _db = new Database(DB_PATH);
@@ -241,7 +241,7 @@ export function getCommitDetail(
241
241
  }
242
242
 
243
243
  // Get total stats
244
- let totalFilesChanged = files.length;
244
+ const totalFilesChanged = files.length;
245
245
  let totalAdditions = 0;
246
246
  let totalDeletions = 0;
247
247
  for (const file of files) {
package/lib/git.ts CHANGED
@@ -5,7 +5,6 @@
5
5
  import { exec } from "child_process";
6
6
  import { promisify } from "util";
7
7
  import * as path from "path";
8
- import * as fs from "fs";
9
8
 
10
9
  const execAsync = promisify(exec);
11
10
 
@@ -7,7 +7,6 @@ import {
7
7
  isGitRepo,
8
8
  expandPath,
9
9
  type GitFile,
10
- type GitStatus,
11
10
  } from "./git-status";
12
11
  import type { ProjectRepository } from "./db";
13
12
 
package/lib/projects.ts CHANGED
@@ -5,7 +5,6 @@
5
5
  * Sessions inherit settings from their parent project.
6
6
  */
7
7
 
8
- import { randomUUID } from "crypto";
9
8
  import fs from "fs";
10
9
  import path from "path";
11
10
  import { exec } from "child_process";
@@ -187,7 +186,9 @@ export async function getAllProjects(): Promise<Project[]> {
187
186
  /**
188
187
  * Get all projects with their dev server configurations
189
188
  */
190
- export async function getAllProjectsWithDevServers(): Promise<ProjectWithRepositories[]> {
189
+ export async function getAllProjectsWithDevServers(): Promise<
190
+ ProjectWithRepositories[]
191
+ > {
191
192
  const projects = await getAllProjects();
192
193
  const result: ProjectWithRepositories[] = [];
193
194
  for (const p of projects) {
@@ -242,7 +243,10 @@ export async function updateProject(
242
243
  /**
243
244
  * Toggle project expanded state
244
245
  */
245
- export async function toggleProjectExpanded(id: string, expanded: boolean): Promise<void> {
246
+ export async function toggleProjectExpanded(
247
+ id: string,
248
+ expanded: boolean
249
+ ): Promise<void> {
246
250
  await queries.updateProjectExpanded(expanded, id);
247
251
  }
248
252
 
@@ -273,7 +277,9 @@ export async function deleteProject(id: string): Promise<boolean> {
273
277
  /**
274
278
  * Get sessions for a project
275
279
  */
276
- export async function getProjectSessions(projectId: string): Promise<Session[]> {
280
+ export async function getProjectSessions(
281
+ projectId: string
282
+ ): Promise<Session[]> {
277
283
  return queries.getSessionsByProject(projectId);
278
284
  }
279
285
 
@@ -478,7 +484,9 @@ export function validateWorkingDirectory(dir: string): boolean {
478
484
  /**
479
485
  * Get repositories for a project
480
486
  */
481
- export async function getProjectRepositories(projectId: string): Promise<ProjectRepository[]> {
487
+ export async function getProjectRepositories(
488
+ projectId: string
489
+ ): Promise<ProjectRepository[]> {
482
490
  const rawRepos = await queries.getProjectRepositories(projectId);
483
491
  return rawRepos.map((r) => ({
484
492
  ...r,
@@ -509,14 +517,23 @@ export async function addProjectRepository(
509
517
  for (const repo of existing) {
510
518
  if (repo.is_primary) {
511
519
  await queries.updateProjectRepository(
512
- repo.name, repo.path, false, repo.sort_order, repo.id
520
+ repo.name,
521
+ repo.path,
522
+ false,
523
+ repo.sort_order,
524
+ repo.id
513
525
  );
514
526
  }
515
527
  }
516
528
  }
517
529
 
518
530
  await queries.createProjectRepository(
519
- id, projectId, opts.name, opts.path, isPrimary, maxOrder + 1
531
+ id,
532
+ projectId,
533
+ opts.name,
534
+ opts.path,
535
+ isPrimary,
536
+ maxOrder + 1
520
537
  );
521
538
 
522
539
  const raw = (await queries.getProjectRepository(id))!;
@@ -549,7 +566,11 @@ export async function updateProjectRepository(
549
566
  for (const repo of allRepos) {
550
567
  if (repo.is_primary && repo.id !== id) {
551
568
  await queries.updateProjectRepository(
552
- repo.name, repo.path, false, repo.sort_order, repo.id
569
+ repo.name,
570
+ repo.path,
571
+ false,
572
+ repo.sort_order,
573
+ repo.id
553
574
  );
554
575
  }
555
576
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atercates/claude-deck",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "claude-deck": "./scripts/claude-deck"
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "repository": {
30
30
  "type": "git",
31
- "url": "git+https://github.com/atercates/claude-deck.git"
31
+ "url": "git+https://github.com/ATERCATES/claude-deck.git"
32
32
  },
33
33
  "keywords": [
34
34
  "claude",
@@ -43,9 +43,9 @@
43
43
  "node": ">=24"
44
44
  },
45
45
  "bugs": {
46
- "url": "https://github.com/atercates/claude-deck/issues"
46
+ "url": "https://github.com/ATERCATES/claude-deck/issues"
47
47
  },
48
- "homepage": "https://github.com/atercates/claude-deck#readme",
48
+ "homepage": "https://github.com/ATERCATES/claude-deck#readme",
49
49
  "dependencies": {
50
50
  "@anthropic-ai/claude-agent-sdk": "^0.2.104",
51
51
  "@codemirror/lang-css": "^6.3.1",
package/scripts/agent-os CHANGED
@@ -9,7 +9,7 @@ set -euo pipefail
9
9
  # Configuration
10
10
  AGENT_OS_HOME="${AGENT_OS_HOME:-$HOME/.agent-os}"
11
11
  PORT="${AGENT_OS_PORT:-3011}"
12
- REPO_URL="https://github.com/atercates/agent-os.git"
12
+ REPO_URL="https://github.com/ATERCATES/agent-os.git"
13
13
 
14
14
  # Derived paths
15
15
  REPO_DIR="$AGENT_OS_HOME/repo"
@@ -3,7 +3,7 @@
3
3
  # ClaudeDeck Installer
4
4
  #
5
5
  # Usage:
6
- # curl -fsSL https://raw.githubusercontent.com/atercates/claude-deck/main/scripts/install.sh | bash
6
+ # curl -fsSL https://raw.githubusercontent.com/ATERCATES/claude-deck/main/scripts/install.sh | bash
7
7
  #
8
8
 
9
9
  set -e
@@ -19,7 +19,7 @@ log_info() { echo -e "${BLUE}==>${NC} $1"; }
19
19
  log_success() { echo -e "${GREEN}==>${NC} $1"; }
20
20
  log_error() { echo -e "${RED}==>${NC} $1"; }
21
21
 
22
- REPO_URL="https://github.com/atercates/claude-deck.git"
22
+ REPO_URL="https://github.com/ATERCATES/claude-deck.git"
23
23
  INSTALL_DIR="$HOME/.claude-deck/repo"
24
24
 
25
25
  echo ""
package/server.ts CHANGED
@@ -49,7 +49,7 @@ app.prepare().then(async () => {
49
49
 
50
50
  // Heartbeat: ping every 30s, kill if no pong in 10s
51
51
  const HEARTBEAT_INTERVAL = 30000;
52
- const HEARTBEAT_TIMEOUT = 10000;
52
+ const _HEARTBEAT_TIMEOUT = 10000;
53
53
 
54
54
  function setupHeartbeat(ws: WebSocket) {
55
55
  let alive = true;