Skip to content

Commit bab307e

Browse files
committed
feat: Implement dk CLI presence check with installation prompt and integrate sonner for notifications.
1 parent c26dcce commit bab307e

11 files changed

Lines changed: 223 additions & 46 deletions

File tree

electron/main/index.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,13 @@ export function createMainWindow(): BrowserWindow {
6767
mainWindow = null;
6868
});
6969

70-
if (useDevServer) {
71-
mainWindow.webContents.openDevTools({ mode: 'detach' });
72-
}
70+
mainWindow.webContents.setWindowOpenHandler((details) => {
71+
if (details.url.startsWith('http:') || details.url.startsWith('https:')) {
72+
require('electron').shell.openExternal(details.url);
73+
return { action: 'deny' };
74+
}
75+
return { action: 'allow' };
76+
});
7377

7478
return mainWindow;
7579
}
@@ -80,26 +84,26 @@ export function getMainWindow(): BrowserWindow | null {
8084

8185
async function initApp() {
8286
console.log('[oroio] Starting app...');
83-
87+
8488
// Set dock icon on macOS (for dev mode)
8589
if (process.platform === 'darwin' && !app.isPackaged) {
8690
const iconPath = path.join(__dirname, '..', '..', 'assets', 'icon.png');
8791
app.dock.setIcon(iconPath);
8892
}
89-
93+
9094
registerIpcHandlers();
91-
95+
9296
try {
9397
console.log('[oroio] Getting key list...');
9498
const keys = await getKeyList();
9599
console.log('[oroio] Keys loaded:', keys.length);
96-
100+
97101
console.log('[oroio] Initializing tray...');
98102
initTray(keys);
99103
console.log('[oroio] Tray initialized');
100-
104+
101105
initNotifier(keys);
102-
106+
103107
// Auto open main window on start
104108
createMainWindow();
105109
console.log('[oroio] App ready');

electron/main/ipc.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ipcMain, BrowserWindow, shell } from 'electron';
22
import * as fs from 'fs/promises';
33
import * as path from 'path';
44
import * as os from 'os';
5+
import { exec } from 'child_process';
56
import {
67
getKeyList,
78
getCurrentKey,
@@ -358,6 +359,19 @@ Droid instructions here.
358359
ipcMain.handle('util:openPath', async (_event, filePath: string): Promise<void> => {
359360
await shell.openPath(filePath);
360361
});
362+
363+
// dk CLI check
364+
ipcMain.handle('dk:check', async (): Promise<{ installed: boolean; installCmd: string; platform: string }> => {
365+
const platform = os.platform();
366+
const cmd = platform === 'win32' ? 'where dk' : 'which dk';
367+
const installed = await new Promise<boolean>((resolve) => {
368+
exec(cmd, (error) => resolve(!error));
369+
});
370+
const installCmd = platform === 'win32'
371+
? 'irm https://raw.githubusercontent.com/notdp/oroio/main/install.ps1 | iex'
372+
: 'curl -fsSL https://raw.githubusercontent.com/notdp/oroio/main/install.sh | bash';
373+
return { installed, installCmd, platform };
374+
});
361375
}
362376

363377
function notifyAllWindows(channel: string, ...args: any[]): void {

electron/main/keyManager.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,13 @@ function encryptKeys(keys: string[]): Buffer {
6969
}
7070

7171
async function ensureStore(): Promise<void> {
72+
await fs.mkdir(OROIO_DIR, { recursive: true });
73+
// 如果 keys.enc 不存在,创建空的加密文件
7274
try {
73-
await fs.mkdir(OROIO_DIR, { recursive: true });
75+
await fs.access(KEYS_FILE);
7476
} catch {
75-
// ignore
77+
const encrypted = encryptKeys([]);
78+
await fs.writeFile(KEYS_FILE, encrypted);
7679
}
7780
}
7881

electron/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"url": "https://github.com/notdp/oroio.git"
1111
},
1212
"scripts": {
13-
"dev": "concurrently \"npm run dev:main\" \"npm run dev:renderer\"",
13+
"dev": "concurrently \"npm run dev:main\" \"npm run dev:renderer\" \"sleep 2 && USE_DEV_SERVER=true electron .\"",
1414
"dev:main": "tsc -p tsconfig.main.json --watch",
1515
"dev:renderer": "cd ../web && npm run dev",
1616
"build": "npm run build:main && npm run build:renderer",
@@ -74,4 +74,4 @@
7474
]
7575
}
7676
}
77-
}
77+
}

electron/preload/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ export interface McpServer {
2626
env?: Record<string, string>;
2727
}
2828

29+
export interface DkCheckResult {
30+
installed: boolean;
31+
installCmd: string;
32+
platform: string;
33+
}
34+
2935
export interface OroioAPI {
3036
keys: {
3137
list: () => Promise<any[]>;
@@ -39,6 +45,8 @@ export interface OroioAPI {
3945
read: (filename: string) => Promise<ArrayBuffer | null>;
4046
};
4147
on: (channel: string, callback: (...args: any[]) => void) => () => void;
48+
// dk CLI check
49+
checkDk: () => Promise<DkCheckResult>;
4250
// Skills
4351
listSkills: () => Promise<Skill[]>;
4452
createSkill: (name: string) => Promise<void>;
@@ -86,6 +94,8 @@ const api: OroioAPI = {
8694
ipcRenderer.on(channel, listener);
8795
return () => ipcRenderer.removeListener(channel, listener);
8896
},
97+
// dk CLI check
98+
checkDk: () => ipcRenderer.invoke('dk:check'),
8999
// Skills
90100
listSkills: () => ipcRenderer.invoke('skills:list'),
91101
createSkill: (name: string) => ipcRenderer.invoke('skills:create', name),

web/package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"react": "^19.2.0",
2727
"react-dom": "^19.2.0",
2828
"react-grab": "^0.0.54",
29+
"sonner": "^2.0.7",
2930
"tailwind-merge": "^3.4.0",
3031
"tailwindcss": "^4.1.17"
3132
},

web/src/App.tsx

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { useState, useEffect, useCallback } from 'react';
2-
import { Key, Sparkles, Terminal, Bot, Plug, Activity } from 'lucide-react';
3-
import KeyList from '@/components/KeyList';
2+
import { Key, Sparkles, Terminal, Bot, Plug, Github } from 'lucide-react';
3+
import { Toaster } from 'sonner';
4+
import KeyList, { showDkMissingToast } from '@/components/KeyList';
45
import SkillsManager from '@/components/SkillsManager';
56
import CommandsManager from '@/components/CommandsManager';
67
import DroidsManager from '@/components/DroidsManager';
78
import McpManager from '@/components/McpManager';
89
import PinDialog from '@/components/PinDialog';
910
import { cn } from '@/lib/utils';
1011
import { Kbd } from '@/components/ui/kbd';
11-
import { checkAuth, authenticate, isElectron } from '@/utils/api';
12+
import { checkAuth, authenticate, isElectron, checkDk } from '@/utils/api';
1213

1314
type Tab = 'keys' | 'commands' | 'skills' | 'droids' | 'mcp';
1415

@@ -31,28 +32,43 @@ export default function App() {
3132
});
3233
const [fakePid] = useState(() => Math.floor(Math.random() * 9000) + 1000);
3334
const [fakeMemory] = useState(() => Math.floor(Math.random() * 50) + 20);
34-
35+
3536
// Auth state
3637
const [authChecking, setAuthChecking] = useState(true);
3738
const [authenticated, setAuthenticated] = useState(false);
3839
const [authRequired, setAuthRequired] = useState(false);
3940
const [authError, setAuthError] = useState<string | undefined>();
41+
const [dkMissing, setDkMissing] = useState(false);
4042

4143
// Check auth on mount
4244
useEffect(() => {
43-
if (isElectron) {
44-
setAuthenticated(true);
45-
setAuthChecking(false);
46-
return;
47-
}
48-
checkAuth().then(result => {
49-
setAuthRequired(result.required);
50-
setAuthenticated(result.authenticated);
51-
setAuthChecking(false);
52-
}).catch(() => {
53-
setAuthChecking(false);
54-
setAuthenticated(true); // Assume no auth if check fails
55-
});
45+
const initChecks = async () => {
46+
if (isElectron) {
47+
setAuthenticated(true);
48+
setAuthChecking(false);
49+
// Check DK status
50+
try {
51+
const dkResult = await checkDk();
52+
if (dkResult && !dkResult.installed) {
53+
setDkMissing(true);
54+
}
55+
} catch (e) {
56+
console.error("Failed to check DK status", e);
57+
}
58+
return;
59+
}
60+
61+
try {
62+
const result = await checkAuth();
63+
setAuthRequired(result.required);
64+
setAuthenticated(result.authenticated);
65+
} catch {
66+
setAuthenticated(true);
67+
} finally {
68+
setAuthChecking(false);
69+
}
70+
};
71+
initChecks();
5672
}, []);
5773

5874
const handlePinSubmit = async (pin: string): Promise<boolean> => {
@@ -107,6 +123,7 @@ export default function App() {
107123

108124
return (
109125
<div className="min-h-screen bg-background text-foreground font-mono p-4 md:p-8 selection:bg-primary selection:text-primary-foreground relative overflow-hidden transition-colors duration-300">
126+
<Toaster position="bottom-center" theme={theme} richColors />
110127
<div className="scanline" />
111128
<div className="max-w-5xl mx-auto space-y-8 relative z-10">
112129

@@ -122,15 +139,32 @@ export default function App() {
122139

123140
<div className="flex items-center gap-4 text-xs">
124141
<div className="flex items-center gap-2 text-muted-foreground mr-4 border-r border-border pr-4 h-full">
125-
<div className="flex items-center gap-2">
126-
<Activity className="w-3 h-3" />
127-
<span>HOST: LOCALHOST</span>
128-
</div>
129-
<span className="text-muted-foreground/30">|</span>
130-
<div className="flex items-center gap-2">
131-
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
132-
<span>STATUS: CONNECTED</span>
133-
</div>
142+
143+
<button
144+
className={cn(
145+
"flex items-center gap-2",
146+
dkMissing && "cursor-pointer hover:opacity-80 transition-opacity"
147+
)}
148+
onClick={async () => {
149+
if (dkMissing && isElectron) {
150+
const result = await checkDk();
151+
if (result && !result.installed) {
152+
showDkMissingToast(result.installCmd);
153+
}
154+
}
155+
}}
156+
disabled={!dkMissing}
157+
>
158+
<span className={cn(
159+
"w-2 h-2 rounded-full animate-pulse",
160+
dkMissing ? "bg-amber-500" : "bg-emerald-500"
161+
)} />
162+
<span className={cn(
163+
dkMissing && "text-amber-500 font-bold"
164+
)}>
165+
{dkMissing ? "STATUS: DK MISSING" : "STATUS: CONNECTED"}
166+
</span>
167+
</button>
134168
</div>
135169

136170
<div className="flex items-center border border-border select-none bg-background">
@@ -158,6 +192,17 @@ export default function App() {
158192
DARK
159193
</button>
160194
</div>
195+
196+
<a
197+
href="https://github.com/notdp/oroio"
198+
target="_blank"
199+
rel="noopener noreferrer"
200+
className="flex items-center gap-2 px-3 py-1 border border-border bg-background hover:bg-muted text-muted-foreground hover:text-foreground transition-all text-[10px] tracking-wider font-medium"
201+
title="View Source on GitHub"
202+
>
203+
<Github className="w-3 h-3" />
204+
<span>GITHUB</span>
205+
</a>
161206
</div>
162207
</div>
163208
</header>

0 commit comments

Comments
 (0)