upfynai-code 0.1.0
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/LICENSE +22 -0
- package/bin/cli.js +86 -0
- package/dist/assets/CanvasPanel-B48gAKVY.js +538 -0
- package/dist/assets/CanvasPanel-B48gAKVY.js.map +1 -0
- package/dist/assets/CanvasPanel-BsOG3EVs.css +1 -0
- package/dist/assets/index-CEhTwG68.css +1 -0
- package/dist/assets/index-GqAGWpJI.js +70 -0
- package/dist/assets/index-GqAGWpJI.js.map +1 -0
- package/dist/index.html +18 -0
- package/index.html +17 -0
- package/package.json +67 -0
- package/src/App.tsx +226 -0
- package/src/components/canvas/CanvasPanel.tsx +62 -0
- package/src/components/canvas/layout/graph-builder.ts +136 -0
- package/src/components/canvas/shapes/CompactionNodeShape.tsx +76 -0
- package/src/components/canvas/shapes/SessionNodeShape.tsx +93 -0
- package/src/components/canvas/shapes/StatuslineWidgetShape.tsx +125 -0
- package/src/components/canvas/shapes/TextResponseNodeShape.tsx +86 -0
- package/src/components/canvas/shapes/ToolCallNodeShape.tsx +107 -0
- package/src/components/canvas/shapes/ToolResultNodeShape.tsx +87 -0
- package/src/components/canvas/shapes/shared-styles.ts +35 -0
- package/src/components/chat/ChatPanel.tsx +96 -0
- package/src/components/chat/InputBar.tsx +81 -0
- package/src/components/chat/MessageList.tsx +130 -0
- package/src/components/chat/PermissionDialog.tsx +70 -0
- package/src/components/layout/FolderSelector.tsx +152 -0
- package/src/components/layout/ModelSelector.tsx +65 -0
- package/src/components/layout/SessionManager.tsx +115 -0
- package/src/components/statusline/StatuslineBar.tsx +114 -0
- package/src/main.tsx +10 -0
- package/src/server/claude-session.ts +156 -0
- package/src/server/index.ts +149 -0
- package/src/services/stream-consumer.ts +330 -0
- package/src/statusline-core/bin/statusline.sh +121 -0
- package/src/statusline-core/commands/sls-config.md +42 -0
- package/src/statusline-core/commands/sls-doctor.md +35 -0
- package/src/statusline-core/commands/sls-help.md +48 -0
- package/src/statusline-core/commands/sls-layout.md +38 -0
- package/src/statusline-core/commands/sls-preview.md +34 -0
- package/src/statusline-core/commands/sls-theme.md +40 -0
- package/src/statusline-core/installer.js +228 -0
- package/src/statusline-core/layouts/compact.sh +21 -0
- package/src/statusline-core/layouts/full.sh +62 -0
- package/src/statusline-core/layouts/standard.sh +39 -0
- package/src/statusline-core/lib/core.sh +389 -0
- package/src/statusline-core/lib/helpers.sh +81 -0
- package/src/statusline-core/lib/json-parser.sh +71 -0
- package/src/statusline-core/themes/catppuccin.sh +32 -0
- package/src/statusline-core/themes/default.sh +37 -0
- package/src/statusline-core/themes/gruvbox.sh +32 -0
- package/src/statusline-core/themes/nord.sh +32 -0
- package/src/statusline-core/themes/tokyo-night.sh +32 -0
- package/src/store/canvas-store.ts +50 -0
- package/src/store/chat-store.ts +60 -0
- package/src/store/permission-store.ts +29 -0
- package/src/store/session-store.ts +52 -0
- package/src/store/statusline-store.ts +160 -0
- package/src/styles/global.css +117 -0
- package/src/themes/index.ts +149 -0
- package/src/types/canvas-graph.ts +24 -0
- package/src/types/sdk-messages.ts +156 -0
- package/src/types/statusline-fields.ts +67 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +24 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Theme: Tokyo Night (vibrant neon)
|
|
3
|
+
THEME_NAME="Tokyo Night"
|
|
4
|
+
|
|
5
|
+
CLR_RST='\033[0m'
|
|
6
|
+
CLR_BOLD='\033[1m'
|
|
7
|
+
CLR_DIM='\033[2m'
|
|
8
|
+
|
|
9
|
+
CLR_SKILL='\033[38;2;255;117;127m' # Red/pink
|
|
10
|
+
CLR_MODEL='\033[38;2;187;154;247m' # Purple
|
|
11
|
+
CLR_DIR='\033[38;2;125;207;255m' # Cyan
|
|
12
|
+
CLR_GITHUB='\033[38;2;192;202;245m' # Foreground
|
|
13
|
+
CLR_TOKENS='\033[38;2;224;175;104m' # Yellow/orange
|
|
14
|
+
CLR_COST='\033[38;2;158;206;106m' # Green
|
|
15
|
+
CLR_VIM='\033[38;2;115;218;202m' # Teal
|
|
16
|
+
CLR_AGENT='\033[38;2;122;162;247m' # Blue
|
|
17
|
+
|
|
18
|
+
CLR_CTX_LOW='\033[38;2;192;202;245m' # Foreground
|
|
19
|
+
CLR_CTX_MED='\033[38;2;255;158;100m' # Orange
|
|
20
|
+
CLR_CTX_HIGH='\033[38;2;247;118;142m' # Red
|
|
21
|
+
CLR_CTX_CRIT='\033[38;2;219;75;75m' # Deep red
|
|
22
|
+
|
|
23
|
+
CLR_SEP='\033[38;2;59;66;97m' # Comment color
|
|
24
|
+
CLR_BAR_EMPTY='\033[38;2;41;46;66m' # Dark bg
|
|
25
|
+
CLR_LABEL='\033[38;2;86;95;137m' # Muted
|
|
26
|
+
|
|
27
|
+
CLR_GIT_STAGED='\033[38;2;158;206;106m'
|
|
28
|
+
CLR_GIT_UNSTAGED='\033[38;2;224;175;104m'
|
|
29
|
+
|
|
30
|
+
BAR_FILLED='█'
|
|
31
|
+
BAR_EMPTY='░'
|
|
32
|
+
SEP_CHAR='│'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import type { CanvasNode, CanvasEdge } from '../types/canvas-graph';
|
|
3
|
+
|
|
4
|
+
interface CanvasState {
|
|
5
|
+
nodes: CanvasNode[];
|
|
6
|
+
edges: CanvasEdge[];
|
|
7
|
+
lastNodeId: string | null;
|
|
8
|
+
|
|
9
|
+
addNode: (node: Omit<CanvasNode, 'x' | 'y'>) => void;
|
|
10
|
+
clear: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const NODE_SPACING_Y = 120;
|
|
14
|
+
const NODE_START_X = 100;
|
|
15
|
+
const NODE_START_Y = 100;
|
|
16
|
+
|
|
17
|
+
export const useCanvasStore = create<CanvasState>((set, get) => ({
|
|
18
|
+
nodes: [],
|
|
19
|
+
edges: [],
|
|
20
|
+
lastNodeId: null,
|
|
21
|
+
|
|
22
|
+
addNode: (partial) => {
|
|
23
|
+
const { nodes, edges, lastNodeId } = get();
|
|
24
|
+
const y = NODE_START_Y + nodes.length * NODE_SPACING_Y;
|
|
25
|
+
const x = NODE_START_X;
|
|
26
|
+
|
|
27
|
+
const node: CanvasNode = {
|
|
28
|
+
...partial,
|
|
29
|
+
x,
|
|
30
|
+
y,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const newEdges = [...edges];
|
|
34
|
+
if (lastNodeId) {
|
|
35
|
+
newEdges.push({
|
|
36
|
+
id: `edge-${lastNodeId}-${node.id}`,
|
|
37
|
+
fromId: lastNodeId,
|
|
38
|
+
toId: node.id,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
set({
|
|
43
|
+
nodes: [...nodes, node],
|
|
44
|
+
edges: newEdges,
|
|
45
|
+
lastNodeId: node.id,
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
clear: () => set({ nodes: [], edges: [], lastNodeId: null }),
|
|
50
|
+
}));
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import type { ChatMessage, TokenUsage } from '../types/sdk-messages';
|
|
3
|
+
|
|
4
|
+
interface ChatState {
|
|
5
|
+
messages: ChatMessage[];
|
|
6
|
+
streamingText: string;
|
|
7
|
+
streamingToolName: string;
|
|
8
|
+
input: string;
|
|
9
|
+
isWaiting: boolean;
|
|
10
|
+
|
|
11
|
+
addMessage: (msg: ChatMessage) => void;
|
|
12
|
+
appendStreamDelta: (text: string) => void;
|
|
13
|
+
setStreamingToolName: (name: string) => void;
|
|
14
|
+
finalizeStreaming: (usage?: TokenUsage) => void;
|
|
15
|
+
setInput: (input: string) => void;
|
|
16
|
+
setWaiting: (waiting: boolean) => void;
|
|
17
|
+
clearMessages: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let messageCounter = 0;
|
|
21
|
+
|
|
22
|
+
export const useChatStore = create<ChatState>((set, get) => ({
|
|
23
|
+
messages: [],
|
|
24
|
+
streamingText: '',
|
|
25
|
+
streamingToolName: '',
|
|
26
|
+
input: '',
|
|
27
|
+
isWaiting: false,
|
|
28
|
+
|
|
29
|
+
addMessage: (msg) => set((s) => ({
|
|
30
|
+
messages: [...s.messages, msg],
|
|
31
|
+
})),
|
|
32
|
+
|
|
33
|
+
appendStreamDelta: (text) => set((s) => ({
|
|
34
|
+
streamingText: s.streamingText + text,
|
|
35
|
+
})),
|
|
36
|
+
|
|
37
|
+
setStreamingToolName: (name) => set({ streamingToolName: name }),
|
|
38
|
+
|
|
39
|
+
finalizeStreaming: (usage) => {
|
|
40
|
+
const { streamingText, messages } = get();
|
|
41
|
+
if (streamingText) {
|
|
42
|
+
set({
|
|
43
|
+
messages: [...messages, {
|
|
44
|
+
id: `msg-${++messageCounter}`,
|
|
45
|
+
role: 'assistant',
|
|
46
|
+
content: streamingText,
|
|
47
|
+
timestamp: Date.now(),
|
|
48
|
+
usage,
|
|
49
|
+
}],
|
|
50
|
+
streamingText: '',
|
|
51
|
+
streamingToolName: '',
|
|
52
|
+
isWaiting: false,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
setInput: (input) => set({ input }),
|
|
58
|
+
setWaiting: (waiting) => set({ isWaiting: waiting }),
|
|
59
|
+
clearMessages: () => set({ messages: [], streamingText: '', streamingToolName: '' }),
|
|
60
|
+
}));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
|
|
3
|
+
export interface PendingPermission {
|
|
4
|
+
requestId: string;
|
|
5
|
+
toolName: string;
|
|
6
|
+
toolInput: Record<string, unknown>;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface PermissionState {
|
|
11
|
+
pending: PendingPermission[];
|
|
12
|
+
addRequest: (req: PendingPermission) => void;
|
|
13
|
+
removeRequest: (requestId: string) => void;
|
|
14
|
+
clearAll: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const usePermissionStore = create<PermissionState>((set) => ({
|
|
18
|
+
pending: [],
|
|
19
|
+
|
|
20
|
+
addRequest: (req) => set((s) => ({
|
|
21
|
+
pending: [...s.pending, req],
|
|
22
|
+
})),
|
|
23
|
+
|
|
24
|
+
removeRequest: (requestId) => set((s) => ({
|
|
25
|
+
pending: s.pending.filter((p) => p.requestId !== requestId),
|
|
26
|
+
})),
|
|
27
|
+
|
|
28
|
+
clearAll: () => set({ pending: [] }),
|
|
29
|
+
}));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
|
|
3
|
+
interface SessionState {
|
|
4
|
+
sessionId: string | null;
|
|
5
|
+
model: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
tools: string[];
|
|
8
|
+
permissionMode: string;
|
|
9
|
+
isConnected: boolean;
|
|
10
|
+
isStreaming: boolean;
|
|
11
|
+
|
|
12
|
+
setInit: (data: {
|
|
13
|
+
sessionId: string;
|
|
14
|
+
model: string;
|
|
15
|
+
cwd: string;
|
|
16
|
+
tools: string[];
|
|
17
|
+
permissionMode: string;
|
|
18
|
+
}) => void;
|
|
19
|
+
setCwd: (cwd: string) => void;
|
|
20
|
+
setModel: (model: string) => void;
|
|
21
|
+
setStreaming: (streaming: boolean) => void;
|
|
22
|
+
setConnected: (connected: boolean) => void;
|
|
23
|
+
reset: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const useSessionStore = create<SessionState>((set) => ({
|
|
27
|
+
sessionId: null,
|
|
28
|
+
model: 'unknown',
|
|
29
|
+
cwd: '',
|
|
30
|
+
tools: [],
|
|
31
|
+
permissionMode: 'default',
|
|
32
|
+
isConnected: false,
|
|
33
|
+
isStreaming: false,
|
|
34
|
+
|
|
35
|
+
setInit: (data) => set({
|
|
36
|
+
sessionId: data.sessionId,
|
|
37
|
+
model: data.model,
|
|
38
|
+
cwd: data.cwd,
|
|
39
|
+
tools: data.tools,
|
|
40
|
+
permissionMode: data.permissionMode,
|
|
41
|
+
isConnected: true,
|
|
42
|
+
}),
|
|
43
|
+
|
|
44
|
+
setCwd: (cwd) => set({ cwd }),
|
|
45
|
+
setModel: (model) => set({ model }),
|
|
46
|
+
setStreaming: (streaming) => set({ isStreaming: streaming }),
|
|
47
|
+
setConnected: (connected) => set({ isConnected: connected }),
|
|
48
|
+
reset: () => set({
|
|
49
|
+
sessionId: null, model: 'unknown', cwd: '', tools: [],
|
|
50
|
+
permissionMode: 'default', isConnected: false, isStreaming: false,
|
|
51
|
+
}),
|
|
52
|
+
}));
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import type { StatuslineData } from '../types/statusline-fields';
|
|
3
|
+
import { DEFAULT_STATUSLINE } from '../types/statusline-fields';
|
|
4
|
+
import type { TokenUsage, ModelUsageEntry } from '../types/sdk-messages';
|
|
5
|
+
|
|
6
|
+
// Port of skill-statusline helpers
|
|
7
|
+
export function fmtTok(n: number): string {
|
|
8
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
9
|
+
if (n >= 1_000) return `${Math.round(n / 1_000)}k`;
|
|
10
|
+
return String(n);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function fmtDuration(ms: number): string {
|
|
14
|
+
if (ms < 1000) return `${ms}ms`;
|
|
15
|
+
const sec = Math.floor(ms / 1000);
|
|
16
|
+
if (sec < 60) return `${sec}s`;
|
|
17
|
+
const min = Math.floor(sec / 60);
|
|
18
|
+
const rem = sec % 60;
|
|
19
|
+
if (min < 60) return rem > 0 ? `${min}m${rem}s` : `${min}m`;
|
|
20
|
+
const hr = Math.floor(min / 60);
|
|
21
|
+
const remMin = min % 60;
|
|
22
|
+
return `${hr}h${remMin}m`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function fmtCost(usd: number): string {
|
|
26
|
+
if (usd === 0) return '$0.00';
|
|
27
|
+
if (usd < 0.01) return `$${usd.toFixed(4)}`;
|
|
28
|
+
return `$${usd.toFixed(2)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Parse model ID → clean display name (port of core.sh logic)
|
|
32
|
+
export function parseModelName(modelId: string): string {
|
|
33
|
+
const familyMatch = modelId.match(/^claude-([a-z]+)-/);
|
|
34
|
+
if (!familyMatch) return modelId;
|
|
35
|
+
const family = familyMatch[1].charAt(0).toUpperCase() + familyMatch[1].slice(1);
|
|
36
|
+
const verMatch = modelId.match(/-(\d)-(\d)$/);
|
|
37
|
+
if (verMatch) return `${family} ${verMatch[1]}.${verMatch[2]}`;
|
|
38
|
+
const verDateMatch = modelId.match(/-(\d)-\d{8}$/);
|
|
39
|
+
if (verDateMatch) return `${family} ${verDateMatch[1]}`;
|
|
40
|
+
return family;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Tool name → skill label (port of core.sh case statement)
|
|
44
|
+
const SKILL_MAP: Record<string, string> = {
|
|
45
|
+
Task: 'Agent', Read: 'Read', Write: 'Write', Edit: 'Edit',
|
|
46
|
+
MultiEdit: 'Multi Edit', Glob: 'Search(Files)', Grep: 'Search(Content)',
|
|
47
|
+
Bash: 'Terminal', WebSearch: 'Web Search', WebFetch: 'Web Fetch',
|
|
48
|
+
Skill: 'Skill', AskUserQuestion: 'Asking...', EnterPlanMode: 'Planning',
|
|
49
|
+
ExitPlanMode: 'Plan Ready', TaskCreate: 'Task Create', TaskUpdate: 'Task Update',
|
|
50
|
+
NotebookEdit: 'Notebook',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
interface StatuslineState extends StatuslineData {
|
|
54
|
+
updateFromInit: (model: string, cwd: string, sessionId: string, permissionMode: string) => void;
|
|
55
|
+
updateFromUsage: (usage: TokenUsage) => void;
|
|
56
|
+
updateFromResult: (data: {
|
|
57
|
+
cost: number; duration: number; numTurns: number;
|
|
58
|
+
usage: TokenUsage; modelUsage?: Record<string, ModelUsageEntry>;
|
|
59
|
+
}) => void;
|
|
60
|
+
updateSkill: (toolName: string) => void;
|
|
61
|
+
markCompaction: (preTokens: number) => void;
|
|
62
|
+
updateGit: (data: {
|
|
63
|
+
branch: string; ghUser: string; ghRepo: string;
|
|
64
|
+
staged: boolean; unstaged: boolean;
|
|
65
|
+
}) => void;
|
|
66
|
+
reset: () => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const useStatuslineStore = create<StatuslineState>((set, get) => ({
|
|
70
|
+
...DEFAULT_STATUSLINE,
|
|
71
|
+
|
|
72
|
+
updateFromInit: (modelId, cwd, sessionId, permissionMode) => {
|
|
73
|
+
const model = parseModelName(modelId);
|
|
74
|
+
const parts = cwd.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
75
|
+
const dirShort = parts.length > 3
|
|
76
|
+
? parts.slice(-3).join('/')
|
|
77
|
+
: parts.length > 0 ? parts.slice(-2).join('/') : '~';
|
|
78
|
+
|
|
79
|
+
set({ model, modelId, cwd, dirShort, sessionId, permissionMode });
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
updateFromUsage: (usage) => {
|
|
83
|
+
const s = get();
|
|
84
|
+
const inputTokens = usage.input_tokens || 0;
|
|
85
|
+
const outputTokens = usage.output_tokens || 0;
|
|
86
|
+
const cacheCreate = usage.cache_creation_input_tokens || 0;
|
|
87
|
+
const cacheRead = usage.cache_read_input_tokens || 0;
|
|
88
|
+
|
|
89
|
+
// Context % = (input + cache_creation + cache_read) / context_size
|
|
90
|
+
const contextUsed = inputTokens + cacheCreate + cacheRead;
|
|
91
|
+
const contextPct = s.contextSize > 0
|
|
92
|
+
? Math.min(100, Math.round((contextUsed / s.contextSize) * 100))
|
|
93
|
+
: 0;
|
|
94
|
+
|
|
95
|
+
let compactionWarning = '';
|
|
96
|
+
let isCompacting = false;
|
|
97
|
+
if (contextPct >= 95) {
|
|
98
|
+
compactionWarning = 'COMPACTING';
|
|
99
|
+
isCompacting = true;
|
|
100
|
+
} else if (contextPct >= 85) {
|
|
101
|
+
compactionWarning = `${100 - contextPct}% left`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
set({
|
|
105
|
+
tokensWinIn: inputTokens,
|
|
106
|
+
tokensWinOut: outputTokens,
|
|
107
|
+
tokensCumIn: s.tokensCumIn + inputTokens,
|
|
108
|
+
tokensCumOut: s.tokensCumOut + outputTokens,
|
|
109
|
+
cacheCreate,
|
|
110
|
+
cacheRead,
|
|
111
|
+
contextUsed,
|
|
112
|
+
contextPct,
|
|
113
|
+
contextRemaining: 100 - contextPct,
|
|
114
|
+
compactionWarning,
|
|
115
|
+
isCompacting,
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
updateFromResult: (data) => {
|
|
120
|
+
const contextSize = data.modelUsage
|
|
121
|
+
? Object.values(data.modelUsage)[0]?.contextWindow || 200000
|
|
122
|
+
: 200000;
|
|
123
|
+
|
|
124
|
+
set({
|
|
125
|
+
cost: data.cost,
|
|
126
|
+
costFormatted: fmtCost(data.cost),
|
|
127
|
+
duration: data.duration,
|
|
128
|
+
durationFormatted: fmtDuration(data.duration),
|
|
129
|
+
numTurns: data.numTurns,
|
|
130
|
+
contextSize,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Recompute burn rate
|
|
134
|
+
if (data.duration > 60000 && data.cost > 0) {
|
|
135
|
+
const rate = data.cost / (data.duration / 60000);
|
|
136
|
+
set({ burnRate: `$${rate.toFixed(2)}/m` });
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
updateSkill: (toolName) => {
|
|
141
|
+
set({ skill: SKILL_MAP[toolName] || toolName });
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
markCompaction: (preTokens) => {
|
|
145
|
+
set({ isCompacting: true, compactionWarning: 'COMPACTING' });
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
updateGit: (data) => {
|
|
149
|
+
const github = data.ghRepo
|
|
150
|
+
? `${data.ghUser}/${data.ghRepo}/${data.branch}`
|
|
151
|
+
: data.branch;
|
|
152
|
+
set({
|
|
153
|
+
branch: data.branch,
|
|
154
|
+
gitDirty: { staged: data.staged, unstaged: data.unstaged },
|
|
155
|
+
github,
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
reset: () => set(DEFAULT_STATUSLINE),
|
|
160
|
+
}));
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
|
|
5
|
+
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
|
|
6
|
+
--radius: 8px;
|
|
7
|
+
--radius-sm: 4px;
|
|
8
|
+
--radius-lg: 12px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
* {
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
html, body, #root {
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 100%;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
font-family: var(--font-sans);
|
|
25
|
+
font-size: 14px;
|
|
26
|
+
line-height: 1.5;
|
|
27
|
+
-webkit-font-smoothing: antialiased;
|
|
28
|
+
-moz-osx-font-smoothing: grayscale;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Scrollbar styling */
|
|
32
|
+
::-webkit-scrollbar {
|
|
33
|
+
width: 6px;
|
|
34
|
+
height: 6px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
::-webkit-scrollbar-track {
|
|
38
|
+
background: transparent;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
::-webkit-scrollbar-thumb {
|
|
42
|
+
background: var(--color-border, #2a2a32);
|
|
43
|
+
border-radius: 3px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
::-webkit-scrollbar-thumb:hover {
|
|
47
|
+
background: var(--color-text-muted, #787882);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Selection */
|
|
51
|
+
::selection {
|
|
52
|
+
background: var(--color-accent, #a855f7);
|
|
53
|
+
color: white;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Code blocks */
|
|
57
|
+
code, pre {
|
|
58
|
+
font-family: var(--font-mono);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pre {
|
|
62
|
+
overflow-x: auto;
|
|
63
|
+
border-radius: var(--radius);
|
|
64
|
+
padding: 12px 16px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Markdown content */
|
|
68
|
+
.markdown-content h1,
|
|
69
|
+
.markdown-content h2,
|
|
70
|
+
.markdown-content h3 {
|
|
71
|
+
margin-top: 16px;
|
|
72
|
+
margin-bottom: 8px;
|
|
73
|
+
font-weight: 600;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.markdown-content p {
|
|
77
|
+
margin-bottom: 8px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.markdown-content ul, .markdown-content ol {
|
|
81
|
+
margin-left: 20px;
|
|
82
|
+
margin-bottom: 8px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.markdown-content code:not(pre code) {
|
|
86
|
+
padding: 2px 6px;
|
|
87
|
+
border-radius: var(--radius-sm);
|
|
88
|
+
font-size: 0.9em;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Animations */
|
|
92
|
+
@keyframes fadeIn {
|
|
93
|
+
from { opacity: 0; transform: translateY(4px); }
|
|
94
|
+
to { opacity: 1; transform: translateY(0); }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@keyframes pulse {
|
|
98
|
+
0%, 100% { opacity: 1; }
|
|
99
|
+
50% { opacity: 0.5; }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@keyframes slideUp {
|
|
103
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
104
|
+
to { opacity: 1; transform: translateY(0); }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.animate-fade-in {
|
|
108
|
+
animation: fadeIn 0.2s ease-out;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.animate-pulse {
|
|
112
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.animate-slide-up {
|
|
116
|
+
animation: slideUp 0.3s ease-out;
|
|
117
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
|
|
3
|
+
export interface ThemeColors {
|
|
4
|
+
skill: string;
|
|
5
|
+
model: string;
|
|
6
|
+
dir: string;
|
|
7
|
+
github: string;
|
|
8
|
+
tokens: string;
|
|
9
|
+
cost: string;
|
|
10
|
+
vim: string;
|
|
11
|
+
agent: string;
|
|
12
|
+
ctxLow: string;
|
|
13
|
+
ctxMed: string;
|
|
14
|
+
ctxHigh: string;
|
|
15
|
+
ctxCrit: string;
|
|
16
|
+
separator: string;
|
|
17
|
+
barEmpty: string;
|
|
18
|
+
barFilled: string;
|
|
19
|
+
label: string;
|
|
20
|
+
gitStaged: string;
|
|
21
|
+
gitUnstaged: string;
|
|
22
|
+
// UI colors
|
|
23
|
+
bg: string;
|
|
24
|
+
bgSurface: string;
|
|
25
|
+
bgPanel: string;
|
|
26
|
+
bgHover: string;
|
|
27
|
+
text: string;
|
|
28
|
+
textMuted: string;
|
|
29
|
+
textDim: string;
|
|
30
|
+
border: string;
|
|
31
|
+
accent: string;
|
|
32
|
+
error: string;
|
|
33
|
+
success: string;
|
|
34
|
+
warning: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ThemeDefinition {
|
|
38
|
+
name: string;
|
|
39
|
+
id: string;
|
|
40
|
+
colors: ThemeColors;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Ported from themes/default.sh — RGB values extracted from ANSI escape codes
|
|
44
|
+
export const defaultTheme: ThemeDefinition = {
|
|
45
|
+
name: 'Default',
|
|
46
|
+
id: 'default',
|
|
47
|
+
colors: {
|
|
48
|
+
skill: '#ec4899', model: '#a855f7', dir: '#06b6d4', github: '#e4e4e7',
|
|
49
|
+
tokens: '#f59e0b', cost: '#22c55e', vim: '#2dd4bf', agent: '#60a5fa',
|
|
50
|
+
ctxLow: '#e4e4e7', ctxMed: '#fb923c', ctxHigh: '#ef4444', ctxCrit: '#dc2626',
|
|
51
|
+
separator: '#37373e', barEmpty: '#28282d', barFilled: '#e4e4e7',
|
|
52
|
+
label: '#787882', gitStaged: '#22c55e', gitUnstaged: '#f59e0b',
|
|
53
|
+
bg: '#0a0a0f', bgSurface: '#141419', bgPanel: '#1a1a22', bgHover: '#24242e',
|
|
54
|
+
text: '#e4e4e7', textMuted: '#787882', textDim: '#505058',
|
|
55
|
+
border: '#2a2a32', accent: '#a855f7', error: '#ef4444', success: '#22c55e', warning: '#f59e0b',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Ported from themes/nord.sh
|
|
60
|
+
export const nordTheme: ThemeDefinition = {
|
|
61
|
+
name: 'Nord',
|
|
62
|
+
id: 'nord',
|
|
63
|
+
colors: {
|
|
64
|
+
skill: '#88c0d0', model: '#81a1c1', dir: '#8fbcbb', github: '#d8dee9',
|
|
65
|
+
tokens: '#ebcb8b', cost: '#a3be8c', vim: '#88c0d0', agent: '#81a1c1',
|
|
66
|
+
ctxLow: '#d8dee9', ctxMed: '#d08770', ctxHigh: '#bf616a', ctxCrit: '#bf616a',
|
|
67
|
+
separator: '#434c5e', barEmpty: '#3b4252', barFilled: '#d8dee9',
|
|
68
|
+
label: '#616e88', gitStaged: '#a3be8c', gitUnstaged: '#ebcb8b',
|
|
69
|
+
bg: '#2e3440', bgSurface: '#3b4252', bgPanel: '#434c5e', bgHover: '#4c566a',
|
|
70
|
+
text: '#d8dee9', textMuted: '#616e88', textDim: '#4c566a',
|
|
71
|
+
border: '#434c5e', accent: '#81a1c1', error: '#bf616a', success: '#a3be8c', warning: '#ebcb8b',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Ported from themes/tokyo-night.sh
|
|
76
|
+
export const tokyoNightTheme: ThemeDefinition = {
|
|
77
|
+
name: 'Tokyo Night',
|
|
78
|
+
id: 'tokyo-night',
|
|
79
|
+
colors: {
|
|
80
|
+
skill: '#f7768e', model: '#bb9af7', dir: '#7dcfff', github: '#a9b1d6',
|
|
81
|
+
tokens: '#e0af68', cost: '#9ece6a', vim: '#2ac3de', agent: '#7aa2f7',
|
|
82
|
+
ctxLow: '#a9b1d6', ctxMed: '#ff9e64', ctxHigh: '#f7768e', ctxCrit: '#db4b4b',
|
|
83
|
+
separator: '#3b4261', barEmpty: '#1f2335', barFilled: '#a9b1d6',
|
|
84
|
+
label: '#565f89', gitStaged: '#9ece6a', gitUnstaged: '#e0af68',
|
|
85
|
+
bg: '#1a1b26', bgSurface: '#1f2335', bgPanel: '#24283b', bgHover: '#292e42',
|
|
86
|
+
text: '#a9b1d6', textMuted: '#565f89', textDim: '#3b4261',
|
|
87
|
+
border: '#3b4261', accent: '#bb9af7', error: '#f7768e', success: '#9ece6a', warning: '#e0af68',
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Ported from themes/catppuccin.sh (Mocha)
|
|
92
|
+
export const catppuccinTheme: ThemeDefinition = {
|
|
93
|
+
name: 'Catppuccin',
|
|
94
|
+
id: 'catppuccin',
|
|
95
|
+
colors: {
|
|
96
|
+
skill: '#f38ba8', model: '#cba6f7', dir: '#89dceb', github: '#cdd6f4',
|
|
97
|
+
tokens: '#f9e2af', cost: '#a6e3a1', vim: '#94e2d5', agent: '#89b4fa',
|
|
98
|
+
ctxLow: '#cdd6f4', ctxMed: '#fab387', ctxHigh: '#f38ba8', ctxCrit: '#eba0ac',
|
|
99
|
+
separator: '#45475a', barEmpty: '#313244', barFilled: '#cdd6f4',
|
|
100
|
+
label: '#6c7086', gitStaged: '#a6e3a1', gitUnstaged: '#f9e2af',
|
|
101
|
+
bg: '#1e1e2e', bgSurface: '#24243a', bgPanel: '#313244', bgHover: '#45475a',
|
|
102
|
+
text: '#cdd6f4', textMuted: '#6c7086', textDim: '#45475a',
|
|
103
|
+
border: '#45475a', accent: '#cba6f7', error: '#f38ba8', success: '#a6e3a1', warning: '#f9e2af',
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Ported from themes/gruvbox.sh
|
|
108
|
+
export const gruvboxTheme: ThemeDefinition = {
|
|
109
|
+
name: 'Gruvbox',
|
|
110
|
+
id: 'gruvbox',
|
|
111
|
+
colors: {
|
|
112
|
+
skill: '#d3869b', model: '#b16286', dir: '#83a598', github: '#ebdbb2',
|
|
113
|
+
tokens: '#fabd2f', cost: '#b8bb26', vim: '#83a598', agent: '#83a598',
|
|
114
|
+
ctxLow: '#ebdbb2', ctxMed: '#fe8019', ctxHigh: '#fb4934', ctxCrit: '#cc241d',
|
|
115
|
+
separator: '#504945', barEmpty: '#3c3836', barFilled: '#ebdbb2',
|
|
116
|
+
label: '#7c6f64', gitStaged: '#b8bb26', gitUnstaged: '#fabd2f',
|
|
117
|
+
bg: '#282828', bgSurface: '#3c3836', bgPanel: '#504945', bgHover: '#665c54',
|
|
118
|
+
text: '#ebdbb2', textMuted: '#7c6f64', textDim: '#504945',
|
|
119
|
+
border: '#504945', accent: '#b16286', error: '#fb4934', success: '#b8bb26', warning: '#fabd2f',
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const THEMES: ThemeDefinition[] = [
|
|
124
|
+
defaultTheme, nordTheme, tokyoNightTheme, catppuccinTheme, gruvboxTheme,
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
interface ThemeState {
|
|
128
|
+
activeTheme: ThemeDefinition;
|
|
129
|
+
setTheme: (id: string) => void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const useThemeStore = create<ThemeState>((set) => ({
|
|
133
|
+
activeTheme: defaultTheme,
|
|
134
|
+
setTheme: (id) => {
|
|
135
|
+
const theme = THEMES.find((t) => t.id === id) || defaultTheme;
|
|
136
|
+
set({ activeTheme: theme });
|
|
137
|
+
// Persist choice
|
|
138
|
+
try { localStorage.setItem('upfynai-theme', id); } catch {}
|
|
139
|
+
},
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
// Load persisted theme
|
|
143
|
+
try {
|
|
144
|
+
const saved = localStorage.getItem('upfynai-theme');
|
|
145
|
+
if (saved) {
|
|
146
|
+
const theme = THEMES.find((t) => t.id === saved);
|
|
147
|
+
if (theme) useThemeStore.setState({ activeTheme: theme });
|
|
148
|
+
}
|
|
149
|
+
} catch {}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Canvas node/edge types for tldraw visualization
|
|
2
|
+
|
|
3
|
+
export type CanvasNodeType =
|
|
4
|
+
| 'session'
|
|
5
|
+
| 'toolCall'
|
|
6
|
+
| 'toolResult'
|
|
7
|
+
| 'textResponse'
|
|
8
|
+
| 'compaction';
|
|
9
|
+
|
|
10
|
+
export interface CanvasNode {
|
|
11
|
+
id: string;
|
|
12
|
+
type: CanvasNodeType;
|
|
13
|
+
label: string;
|
|
14
|
+
data: Record<string, unknown>;
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CanvasEdge {
|
|
21
|
+
id: string;
|
|
22
|
+
fromId: string;
|
|
23
|
+
toId: string;
|
|
24
|
+
}
|