Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

129 changes: 65 additions & 64 deletions scripts/build-desktop.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ if (!fs.existsSync(electronDir)) {

// 3. 创建主进程文件
const mainJs = `
const { app, BrowserWindow, Menu, shell } = require('electron');
const { app, BrowserWindow, Menu, shell, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');
const isDev = process.env.NODE_ENV === 'development';

let mainWindow;
Expand All @@ -35,11 +36,12 @@ function createWindow() {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
webSecurity: true
webSecurity: true,
preload: path.join(__dirname, 'preload.js'),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n scripts/build-desktop.js | head -160

Repository: AmintaCCCP/GithubStarsManager

Length of output: 5317


Gate these IPC channels by trusted renderer origin and use async file operations.

The new ipcMain.handle() calls for snapshot read/write operations (lines 77-109) do not validate the sender via event.senderFrame, leaving them accessible to any code executing in the renderer context. Although contextIsolation and the preload bridge enforce basic isolation, an XSS vulnerability or injected content could reach these handlers. Per Electron security recommendations, validate that IPC calls originate from the expected frame origin. Additionally, lines 85 and 99 use fs.writeFileSync() and fs.readFileSync(), which block the main process; use async variants instead (or wrap in setImmediate).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/build-desktop.js` at line 40, The snapshot IPC handlers registered
with ipcMain.handle (the snapshot read/write handlers introduced around lines
77-109) must validate the caller origin and use non-blocking file I/O: in each
ipcMain.handle callback, check event.senderFrame?.url (or derive origin via new
URL(event.senderFrame.url).origin) and compare it to an explicit trusted origin
(e.g., the app's known origin stored in a constant or computed from the main
window's webContents.getURL()) and immediately throw/return an error if it
doesn't match; then convert the handlers to async functions that use
fs.promises.writeFile and fs.promises.readFile (or otherwise run synchronous ops
off the main loop) and await results, and ensure errors are caught and rethrown
as IPC errors so the renderer receives a clear failure.

},
icon: path.join(__dirname, '../dist/icon.svg'),
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
show: false
show: false,
});

// 加载应用
Expand All @@ -52,60 +54,6 @@ function createWindow() {

mainWindow.once('ready-to-show', () => {
mainWindow.show();

// 设置应用菜单
if (process.platform === 'darwin') {
const template = [
{
label: 'GitHub Stars Manager',
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
},
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'selectall' }
]
},
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
{
label: 'Window',
submenu: [
{ role: 'minimize' },
{ role: 'close' }
]
}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}
});

// 处理外部链接
Expand All @@ -119,6 +67,47 @@ function createWindow() {
});
}

// 获取快照文件路径(固定位置)
function getSnapshotPath() {
const appDataPath = app.getPath('userData');
return path.join(appDataPath, 'github-stars.snapshot.json');
}

// 写快照文件
ipcMain.handle('write-snapshot', async (event, data) => {
try {
const snapshotPath = getSnapshotPath();
const dir = path.dirname(snapshotPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const content = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
fs.writeFileSync(snapshotPath, content, 'utf8');
return { ok: true, path: snapshotPath };
} catch (err) {
return { ok: false, error: err.message };
}
});

// 读快照文件
ipcMain.handle('read-snapshot', async () => {
try {
const snapshotPath = getSnapshotPath();
if (!fs.existsSync(snapshotPath)) {
return { ok: false, error: 'Snapshot file not found', path: snapshotPath };
}
const content = fs.readFileSync(snapshotPath, 'utf8');
return { ok: true, data: JSON.parse(content), path: snapshotPath };
} catch (err) {
return { ok: false, error: err.message };
}
});

// 获取快照路径
ipcMain.handle('get-snapshot-path', async () => {
return getSnapshotPath();
});

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
Expand All @@ -133,7 +122,6 @@ app.on('activate', () => {
}
});

// 安全设置
app.on('web-contents-created', (event, contents) => {
contents.on('new-window', (event, navigationUrl) => {
event.preventDefault();
Expand All @@ -144,22 +132,35 @@ app.on('web-contents-created', (event, contents) => {

fs.writeFileSync(path.join(electronDir, 'main.js'), mainJs);

// 4. 创建Electron package.json
// 4. 创建 preload.js(安全桥接)
const preloadJs = `
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
writeSnapshot: (data) => ipcRenderer.invoke('write-snapshot', data),
readSnapshot: () => ipcRenderer.invoke('read-snapshot'),
getSnapshotPath: () => ipcRenderer.invoke('get-snapshot-path'),
});
`;

fs.writeFileSync(path.join(electronDir, 'preload.js'), preloadJs);

// 5. 创建Electron package.json
const electronPackageJson = {
name: 'github-stars-manager-desktop',
version: '1.0.0',
description: 'GitHub Stars Manager Desktop App',
main: 'main.js',
author: 'GitHub Stars Manager',
license: 'MIT'
license: 'MIT',
};

fs.writeFileSync(
path.join(electronDir, 'package.json'),
path.join(electronDir, 'package.json'),
JSON.stringify(electronPackageJson, null, 2)
);

// 5. 安装Electron依赖
// 6. 安装Electron依赖
console.log('📥 安装Electron依赖...');
try {
execSync('npm install --save-dev electron electron-builder', { stdio: 'inherit' });
Expand All @@ -168,7 +169,7 @@ try {
process.exit(1);
}

// 6. 构建应用
// 7. 构建应用
console.log('🔨 构建桌面应用...');
try {
execSync('npx electron-builder', { stdio: 'inherit' });
Expand All @@ -177,4 +178,4 @@ try {
} catch (error) {
console.error('构建失败:', error.message);
process.exit(1);
}
}
82 changes: 82 additions & 0 deletions skills/githubstars-snapshot/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
name: githubstars-snapshot
description: >
Query, categorize, and tag your GitHub starred repositories using a local
snapshot maintained by the GithubStarsManager desktop app. Use when: searching
your GitHub stars for specific topics, answering questions about your own
starred repos, organizing or labeling stars, finding repos by name/description/topic/language.
Trigger phrases: "哪些仓库…", "有没有…", "我的stars里", "搜一下我的仓库", any question
about the user's own GitHub starred repositories.
---

# GitHub Stars Snapshot

Query and manage your GitHub starred repositories via a local snapshot file maintained by the desktop app.

## Snapshot file location

The GithubStarsManager desktop app writes a snapshot to a fixed path. Pass this path to the tool with `--snapshot`:

**macOS:** `~/Library/Application Support/github-stars-manager-desktop/github-stars.snapshot.json`
**Linux:** `~/.config/github-stars-manager-desktop/github-stars.snapshot.json`
**Windows:** `%APPDATA%/github-stars-manager-desktop/github-stars.snapshot.json`

## Tool

**Script:** `./scripts/githubstars-snapshot-tool.mjs`

## Commands

```bash
# Search repositories (returns ranked results by relevance)
node ./scripts/githubstars-snapshot-tool.mjs search "mcp server" --snapshot ~/Library/Application\ Support/github-stars-manager-desktop/github-stars.snapshot.json

# Set a repository's category
node ./scripts/githubstars-snapshot-tool.mjs category set --repo owner/name --category ai-tools --snapshot <path>

# Add tags to a repository (comma-separated, duplicates are deduplicated)
node ./scripts/githubstars-snapshot-tool.mjs tags add --repo owner/name --tags mcp,agent --snapshot <path>

# Preview changes without writing (dry-run)
node ./scripts/githubstars-snapshot-tool.mjs tags add --repo owner/name --tags mcp,agent --snapshot <path> --dry-run
node ./scripts/githubstars-snapshot-tool.mjs category set --repo owner/name --category ai-tools --snapshot <path> --dry-run
```

## Workflow

1. **Find the snapshot file** — the desktop app maintains it at the paths above. If multiple platforms are in use, the snapshot lives in the user home of whichever machine ran the desktop app most recently.
2. **Search first** — always start with `search` to locate the target repository by name, topic, description, or language.
3. **Act if needed** — use `category set` or `tags add` to organize. Prefer `--dry-run` first to preview.
4. **Progressive disclosure** — if a search result's metadata is insufficient, the next step is reading the repository README, not the source code. Only escalate to code inspection when the question genuinely requires it.

## Snapshot schema

```json
{
"version": 1,
"exportedAt": "2026-03-29T00:00:00.000Z",
"repositories": [
{
"id": 1,
"name": "repo-name",
"full_name": "owner/repo-name",
"description": "What this repo does",
"html_url": "https://github.com/owner/repo-name",
"stargazers_count": 1234,
"language": "TypeScript",
"topics": ["mcp", "ai"],
"ai_summary": "AI-generated description",
"ai_tags": ["server", "agent"],
"custom_tags": ["infra"],
"custom_category": "ai-tools"
}
],
"categories": []
}
```

## Notes

- If the snapshot file does not exist yet, the desktop app has not been run or no repositories have been synced. Ask the user to open the app and sync their stars first.
- The `--snapshot` flag is required unless running from the repo root with a default path configured.
- The tool writes changes directly to the snapshot file. Use `--dry-run` to preview without modifying.
Loading
Loading