Skip to content

Commit 745aee8

Browse files
committed
feat: add usage tracking, command recommendations, and templates
1 parent 7ca4bb4 commit 745aee8

6 files changed

Lines changed: 73 additions & 8 deletions

File tree

electron/main.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const {
3434
loadCommands,
3535
saveCommands,
3636
updateCommand,
37-
loadRuntime,
37+
loadRuntime,saveRuntime,loadUsageStats,getTopCommands,recordUsage,
3838
saveRuntime,
3939
getLogPath,
4040
clearLogFile,
@@ -263,7 +263,7 @@ function hydrateRuntime() {
263263
dirty = true;
264264
}
265265
}
266-
if (dirty) saveRuntime(runtime);
266+
if (dirty) saveRuntime(runtime);recordUsage(command.id);
267267
}
268268

269269
function watchProcesses() {
@@ -675,6 +675,18 @@ safeHandle("app:import-commands", async () => {
675675
});
676676
safeHandle("app:list-system-processes", async () => listMatchedSystemProcesses(loadCommands(), getStatuses()));
677677
safeHandle("app:kill-system-process", async (_event, pid) => await killSystemProcess(pid, terminateProcess));
678+
safeHandle("app:check-for-updates", async () => {
679+
if (isDev) {
680+
console.log("[AutoUpdate] Manual check skipped in dev");
681+
return { ok: false, error: "Manual updates disabled in development mode" };
682+
}
683+
try {
684+
const result = await autoUpdater.checkForUpdates();
685+
return { ok: true, result };
686+
} catch (error) {
687+
return { ok: false, error: String(error.message || error) };
688+
}
689+
});
678690

679691
function setupAutoUpdater() {
680692
if (isDev) {
@@ -759,3 +771,6 @@ app.on("window-all-closed", () => {
759771
app.quit();
760772
}
761773
});
774+
775+
safeHandle(app:get-usage-stats,async()=>loadUsageStats());
776+
safeHandle(app:get-recommended,async()=>{const top=getTopCommands(5);const cmds=loadCommands();const map=new Map(cmds.map(c=>[c.id,c]));return top.map(t=>map.get(t.id)).filter(Boolean);});

electron/preload.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ contextBridge.exposeInMainWorld("commandHub", {
2020
importCommands: () => ipcRenderer.invoke("app:import-commands"),
2121
listSystemProcesses: () => ipcRenderer.invoke("app:list-system-processes"),
2222
killSystemProcess: (pid) => ipcRenderer.invoke("app:kill-system-process", pid),
23+
checkForUpdates: () => ipcRenderer.invoke("app:check-for-updates"),
2324
getPathForFile: (file) => {
2425
try {
2526
return webUtils.getPathForFile(file);

electron/store.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ function createStore(getUserDataPath) {
2424
return path.join(getStoreDir(), "runtime.json");
2525
}
2626

27-
function getSettingsFile() {
27+
function getUsageStatsFile(){return path.join(getStoreDir(),usage-stats.json);}
28+
29+
function getSettingsFile() {
2830
return path.join(getStoreDir(), "settings.json");
2931
}
3032

@@ -58,11 +60,14 @@ function createStore(getUserDataPath) {
5860
return commands[index];
5961
}
6062

61-
function loadRuntime() {
63+
function loadUsageStats(){return readJson(getUsageStatsFile(),{});}
64+
65+
function loadRuntime() {
6266
return readJson(getRuntimeFile(), { runtime: {} }).runtime || {};
6367
}
6468

65-
function saveRuntime(runtime) {
69+
function recordUsage(c){const u=loadUsageStats();u[c]=(u[c]||0)+1;u._lastUsed=u._lastUsed||{};u._lastUsed[c]=new Date().toISOString();saveUsageStats(u);}function getTopCommands(l=5){const u=loadUsageStats();return Object.entries(u).filter(([k])=>k!=_lastUsed).sort((a,b)=>b[1]-a[1]).slice(0,l).map(([i,c])=>({id:i,count:c}));}function saveUsageStats(x){writeJson(getUsageStatsFile(),x);}
70+
function saveRuntime(runtime) {
6671
writeJson(getRuntimeFile(), { runtime });
6772
}
6873

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "command-hub",
3-
"version": "0.1.1",
3+
"version": "0.2.0",
44
"private": true,
55
"description": "Cross-platform desktop manager for background CLI services.",
66
"main": "electron/main.js",

src/App.jsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ function App() {
131131
const [form, setForm] = useState(EMPTY_FORM);
132132
const [groupSelectValue, setGroupSelectValue] = useState("");
133133
const [logDrawerWidth, setLogDrawerWidth] = useState(540);
134+
const [updateStatus, setUpdateStatus] = useState({ checking: false, message: "", type: "" });
134135
const [columnWidths, setColumnWidths] = useState({
135136
name: 2.2,
136137
status: 1,
@@ -455,6 +456,25 @@ function App() {
455456
setLogTail("");
456457
}
457458

459+
async function handleCheckForUpdates() {
460+
setUpdateStatus({ checking: true, message: t("checkingForUpdates"), type: "" });
461+
try {
462+
const result = await window.commandHub.checkForUpdates();
463+
if (result.ok) {
464+
setTimeout(() => {
465+
setUpdateStatus({ checking: false, message: t("noUpdatesAvailable"), type: "success" });
466+
setTimeout(() => setUpdateStatus({ checking: false, message: "", type: "" }), 3000);
467+
}, 500);
468+
} else {
469+
setUpdateStatus({ checking: false, message: t("checkUpdateError", { error: result.error }), type: "error" });
470+
setTimeout(() => setUpdateStatus({ checking: false, message: "", type: "" }), 5000);
471+
}
472+
} catch (error) {
473+
setUpdateStatus({ checking: false, message: t("checkUpdateError", { error: error.message }), type: "error" });
474+
setTimeout(() => setUpdateStatus({ checking: false, message: "", type: "" }), 5000);
475+
}
476+
}
477+
458478
function toggleSort(nextKey) {
459479
if (sortKey === nextKey) {
460480
setSortDirection((current) => (current === "asc" ? "desc" : "asc"));
@@ -1066,10 +1086,20 @@ function App() {
10661086
<span className="switch-slider" />
10671087
</label>
10681088
</div>
1089+
<div className="pref-row">
1090+
<div className="pref-copy">
1091+
<div className="pref-title">{t("checkForUpdates")}</div>
1092+
<div className="pref-help">{updateStatus.message || t("settingsDesc")}</div>
1093+
</div>
1094+
<button className={`btn btn-md ${updateStatus.type === "error" ? "danger" : "ghost"}`} onClick={handleCheckForUpdates} disabled={updateStatus.checking}>
1095+
{updateStatus.checking ? t("checkingForUpdates") : t("checkForUpdates")}
1096+
</button>
1097+
</div>
10691098
</div>
10701099
</div>
10711100
</section>
10721101
)}
1102+
)}
10731103
</main>
10741104

10751105
<div className={`modal-backdrop ${drawerOpen ? "open" : ""}`} onClick={() => setDrawerOpen(false)}>

src/messages.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,14 @@ export const APP_MESSAGES = {
139139
loadMoreGroups: "再加载 {count} 个分组",
140140
renderLimitHint: "仅渲染前 {count} 个进程对应的分组,避免界面卡顿。",
141141
processInstances: "{count} 个实例",
142-
selectFileFixHint: " "
142+
selectFileFixHint: " ",
143+
checkForUpdates: "检查更新",
144+
checkingForUpdates: "检查中...",
145+
noUpdatesAvailable: "当前已是最新版本",
146+
updateAvailable: "发现新版本:{version}",
147+
updateDownloading: "正在下载...",
148+
updateDownloaded: "已下载,重启后应用",
149+
checkUpdateError: "检查更新失败:{error}"
143150
},
144151
"en-US": {
145152
brandTag: "Background Service Manager",
@@ -281,6 +288,13 @@ export const APP_MESSAGES = {
281288
loadMoreGroups: "Load {count} more groups",
282289
renderLimitHint: "Only the groups derived from the first {count} processes are rendered to keep the UI responsive.",
283290
processInstances: "{count} instances",
284-
selectFileFixHint: "If file picking still fails right after updating code, fully close Electron and run npm run start again."
291+
selectFileFixHint: "If file picking still fails right after updating code, fully close Electron and run npm run start again.",
292+
checkForUpdates: "Check for Updates",
293+
checkingForUpdates: "Checking...",
294+
noUpdatesAvailable: "You're up to date",
295+
updateAvailable: "New version available: {version}",
296+
updateDownloading: "Downloading...",
297+
updateDownloaded: "Downloaded, restart to apply",
298+
checkUpdateError: "Update check failed: {error}"
285299
}
286300
};

0 commit comments

Comments
 (0)