Token导航 LogoToken导航TokenDH.com
前端设计敏感数据github未标认证来源可访问许可证需确认审计提醒

gitbackup-github-desktopgitbackup GitHub desktop 前端

Agent Skill

用于围绕 GitHub 仓库、Issue、Pull Request、分支、提交和代码协作流程提供辅助能力。它适合让 Agent 查询项目状态、整理变更、辅助创建或检查协作事项,并把仓库中的信息转成可执行的下一步。使用时需要区分只读查询和写入操作;涉及创建 PR、修改 Issue、推送分支或访问私有仓库时,应确认 token 权限、目标仓库范围和用户授权。

总安装

776

周安装

32

GitHub Stars

39

下载量

253
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:gitbackup-github-desktop(gitbackup GitHub desktop 前端)
来源仓库:https://github.com/aradotso/trending-skills
仓库路径:skills/gitbackup-github-desktop
安装命令:
npx skills add https://github.com/aradotso/trending-skills --skill gitbackup-github-desktop
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/aradotso/trending-skills --skill gitbackup-github-desktop

简介

用于围绕 GitHub 仓库、Issue、Pull Request、分支、提交和代码协作流程提供辅助能力。

  • 适合让 Agent 查询项目状态、整理变更、辅助创建或检查协作事项。
  • 通过 npx skills add 命令从指定仓库安装使用。
  • 涉及写入操作时需确认 token 权限和用户授权,区分只读与写操作。
  • 可结合原始 README 进一步了解具体功能和使用方法。

SKILL.md

GitBackup — GitHub Repository Backup Desktop App

Skill by ara.so — Daily 2026 Skills collection.

GitBackup is an Electron + React desktop application that clones all your GitHub repositories locally and optionally uploads compressed .tar.gz archives to AWS S3 or Cloudflare R2. It supports incremental updates, scheduled backups, concurrent processing, and encrypted local settings storage.

Installation

Download Pre-built Binary

Download from Releases:

PlatformFile
macOSGitBackup-x.x.x.dmg
WindowsGitBackup-Setup-x.x.x.exe
LinuxGitBackup-x.x.x.AppImage

Prerequisite: Git must be installed and available in PATH.

Build from Source

git clone https://github.com/hiteshchoudhary/gitbackup.git
cd gitbackup
npm install

# Development with hot reload
npm run dev

# Package for current platform
npm run package

# Platform-specific builds
npm run package:mac
npm run package:win
npm run package:linux

GitHub Token Setup

  1. Go to github.com/settings/tokens
  2. Tokens (classic)Generate new token (classic)
  3. Select scopes: repo (full access) + read:org (for org repos)
  4. Copy the token and paste into the app's Setup page

Fine-grained tokens also work — grant Repository access → All repositories.

Project Structure

gitbackup/
├── electron/                       # Main process (Node.js)
│   ├── main.ts                     # App window, tray, lifecycle
│   ├── preload.ts                  # Secure IPC bridge
│   ├── tray.ts                     # System tray icon
│   ├── ipc/                        # IPC handler modules
│   ├── services/
│   │   ├── github.service.ts       # GitHub API via Octokit
│   │   ├── git.service.ts          # Clone & fetch repos (simple-git)
│   │   ├── compress.service.ts     # tar.gz archiving
│   │   ├── cloud.service.ts        # S3/R2 uploads (AWS SDK v3)
│   │   ├── backup-orchestrator.ts  # Core backup pipeline
│   │   └── scheduler.service.ts    # Cron scheduling (node-cron)
│   └── store/store.ts              # Encrypted settings (electron-store)
└── src/                            # Renderer process (React 19)
    ├── pages/                      # Setup, Repos, Backup, Settings
    ├── components/                 # UI components
    └── hooks/                      # IPC & state hooks

Tech Stack

LayerTechnology
FrameworkElectron 35
FrontendReact 19 + Tailwind CSS
LanguageTypeScript 5
BundlerVite 8
GitHub API@octokit/rest
Git operationssimple-git
Cloud storageAWS SDK v3 (S3-compatible)
Settingselectron-store (encrypted)
Schedulingnode-cron
Packagingelectron-builder

Core Services — Code Examples

GitHub Service (Octokit)

// electron/services/github.service.ts
import { Octokit } from "@octokit/rest";

export class GitHubService {
  private octokit: Octokit;

  constructor(token: string) {
    this.octokit = new Octokit({ auth: token });
  }

  async getAuthenticatedUser() {
    const { data } = await this.octokit.users.getAuthenticated();
    return data;
  }

  // Fetch all repos with pagination — handles 200-300+ repos
  async fetchAllRepositories(filters: RepoFilters): Promise<Repository[]> {
    const repos: Repository[] = [];

    if (filters.owned) {
      for await (const response of this.octokit.paginate.iterator(
        this.octokit.repos.listForAuthenticatedUser,
        { affiliation: "owner", per_page: 100 }
      )) {
        repos.push(...response.data);
      }
    }

    if (filters.organizations) {
      const orgs = await this.octokit.orgs.listForAuthenticatedUser();
      for (const org of orgs.data) {
        for await (const response of this.octokit.paginate.iterator(
          this.octokit.repos.listForOrg,
          { org: org.login, per_page: 100 }
        )) {
          repos.push(...response.data);
        }
      }
    }

    if (filters.starred) {
      for await (const response of this.octokit.paginate.iterator(
        this.octokit.activity.listReposStarredByAuthenticatedUser,
        { per_page: 100 }
      )) {
        repos.push(...response.data as any);
      }
    }

    return repos;
  }
}

Git Service (Clone & Fetch)

// electron/services/git.service.ts
import simpleGit, { SimpleGit } from "simple-git";
import path from "path";
import fs from "fs";

export class GitService {
  // Clone with all branches or fetch updates if already exists
  async backupRepository(
    repoUrl: string,
    backupPath: string,
    token: string
  ): Promise<void> {
    // Embed token in URL (cleaned after operation)
    const authenticatedUrl = repoUrl.replace(
      "https://",
      `https://x-access-token:${token}@`
    );

    const repoExists = fs.existsSync(path.join(backupPath, ".git"));

    if (repoExists) {
      // Incremental update — only fetch changes
      const git: SimpleGit = simpleGit(backupPath);
      await git.fetch(["--all", "--prune"]);
    } else {
      // First run — full clone with all branches
      fs.mkdirSync(backupPath, { recursive: true });
      const git: SimpleGit = simpleGit();
      await git.clone(authenticatedUrl, backupPath, ["--mirror"]);
    }

    // Remove token from remote URL after operation
    const git: SimpleGit = simpleGit(backupPath);
    await git.remote(["set-url", "origin", repoUrl]);
  }
}

Cloud Service (S3 / Cloudflare R2)

// electron/services/cloud.service.ts
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import fs from "fs";

export interface CloudConfig {
  provider: "s3" | "r2";
  bucket: string;
  region?: string;         // AWS S3
  endpoint?: string;       // Cloudflare R2 endpoint
  accessKeyId: string;     // from env: process.env.AWS_ACCESS_KEY_ID
  secretAccessKey: string; // from env: process.env.AWS_SECRET_ACCESS_KEY
}

export class CloudService {
  private client: S3Client;
  private bucket: string;

  constructor(config: CloudConfig) {
    this.bucket = config.bucket;
    this.client = new S3Client({
      region: config.region ?? "auto",
      endpoint: config.endpoint,       // Set for Cloudflare R2
      credentials: {
        accessKeyId: config.accessKeyId,
        secretAccessKey: config.secretAccessKey,
      },
    });
  }

  async uploadArchive(archivePath: string, key: string): Promise<void> {
    const fileStream = fs.createReadStream(archivePath);
    await this.client.send(
      new PutObjectCommand({
        Bucket: this.bucket,
        Key: key,              // e.g. "owner/repo-name.tar.gz"
        Body: fileStream,
        ContentType: "application/gzip",
      })
    );
  }
}

Compress Service

// electron/services/compress.service.ts
import tar from "tar";
import path from "path";

export class CompressService {
  async createArchive(
    sourceDir: string,
    outputPath: string
  ): Promise<string> {
    const archiveName = `${path.basename(sourceDir)}.tar.gz`;
    const archivePath = path.join(outputPath, archiveName);

    await tar.create(
      { gzip: true, file: archivePath, cwd: path.dirname(sourceDir) },
      [path.basename(sourceDir)]
    );

    return archivePath;
  }
}

Backup Orchestrator (Pipeline)

// electron/services/backup-orchestrator.ts
import pLimit from "p-limit";

export interface BackupOptions {
  repos: Repository[];
  backupPath: string;
  token: string;
  concurrency: number;       // 1-10
  cloudConfig?: CloudConfig;
  onProgress: (repoName: string, status: RepoStatus) => void;
}

export class BackupOrchestrator {
  async run(options: BackupOptions): Promise<void> {
    const limit = pLimit(options.concurrency);
    const gitService = new GitService();
    const compressService = new CompressService();
    const cloudService = options.cloudConfig
      ? new CloudService(options.cloudConfig)
      : null;

    const tasks = options.repos.map((repo) =>
      limit(async () => {
        const repoPath = path.join(
          options.backupPath,
          repo.owner.login,
          repo.name
        );

        try {
          options.onProgress(repo.full_name, { status: "cloning" });
          await gitService.backupRepository(
            repo.clone_url,
            repoPath,
            options.token
          );

          if (cloudService) {
            options.onProgress(repo.full_name, { status: "compressing" });
            const archivePath = await compressService.createArchive(
              repoPath,
              options.backupPath
            );

            options.onProgress(repo.full_name, { status: "uploading" });
            await cloudService.uploadArchive(
              archivePath,
              `${repo.owner.login}/${repo.name}.tar.gz`
            );
          }

          options.onProgress(repo.full_name, { status: "done" });
        } catch (error) {
          options.onProgress(repo.full_name, {
            status: "error",
            error: (error as Error).message,
          });
        }
      })
    );

    await Promise.all(tasks);
  }
}

Scheduler Service

// electron/services/scheduler.service.ts
import cron from "node-cron";

export type ScheduleFrequency = "daily" | "weekly" | "monthly";

export class SchedulerService {
  private task: cron.ScheduledTask | null = null;

  schedule(
    frequency: ScheduleFrequency,
    time: string,             // "HH:MM" format
    callback: () => void
  ): void {
    this.stop();

    const [hour, minute] = time.split(":").map(Number);

    const cronExpressions: Record<ScheduleFrequency, string> = {
      daily:   `${minute} ${hour} * * *`,
      weekly:  `${minute} ${hour} * * 0`,
      monthly: `${minute} ${hour} 1 * *`,
    };

    this.task = cron.schedule(cronExpressions[frequency], callback);
  }

  stop(): void {
    this.task?.stop();
    this.task = null;
  }
}

Encrypted Settings Store

// electron/store/store.ts
import Store from "electron-store";

interface AppSettings {
  githubToken: string;
  backupPath: string;
  concurrency: number;
  schedule?: {
    enabled: boolean;
    frequency: "daily" | "weekly" | "monthly";
    time: string;
  };
  cloud?: {
    provider: "s3" | "r2";
    bucket: string;
    region?: string;
    endpoint?: string;
    accessKeyId: string;
    secretAccessKey: string;
  };
}

export const store = new Store<AppSettings>({
  encryptionKey: "your-app-encryption-key", // electron-store encrypts at rest
  defaults: {
    githubToken: "",
    backupPath: "",
    concurrency: 3,
  },
});

// Usage
store.set("githubToken", token);
store.get("backupPath");
store.set("cloud", {
  provider: "r2",
  bucket: process.env.R2_BUCKET_NAME!,
  endpoint: process.env.R2_ENDPOINT!,
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
});

IPC Bridge (Preload → Renderer)

// electron/preload.ts
import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("electronAPI", {
  // GitHub
  validateToken: (token: string) =>
    ipcRenderer.invoke("github:validate-token", token),
  fetchRepos: (filters: RepoFilters) =>
    ipcRenderer.invoke("github:fetch-repos", filters),

  // Backup
  startBackup: (options: BackupOptions) =>
    ipcRenderer.invoke("backup:start", options),
  onProgress: (callback: (data: ProgressEvent) => void) =>
    ipcRenderer.on("backup:progress", (_event, data) => callback(data)),

  // Settings
  getSettings: () => ipcRenderer.invoke("settings:get"),
  saveSettings: (settings: Partial<AppSettings>) =>
    ipcRenderer.invoke("settings:save", settings),

  // Folder picker
  selectFolder: () => ipcRenderer.invoke("dialog:select-folder"),
});
// src/hooks/useBackup.ts — React renderer side
import { useState } from "react";

export function useBackup() {
  const [progress, setProgress] = useState<Record<string, RepoStatus>>({});

  const startBackup = async (repos: Repository[]) => {
    // Listen for per-repo progress events
    window.electronAPI.onProgress((data) => {
      setProgress((prev) => ({
        ...prev,
        [data.repoName]: data.status,
      }));
    });

    await window.electronAPI.startBackup({
      repos,
      concurrency: 5,
    });
  };

  return { startBackup, progress };
}

Configuration Reference

SettingDescriptionDefault
githubTokenPAT with repo + read:org scopes
backupPathLocal directory for cloned repos
concurrencyParallel repo operations (1–10)3
schedule.frequencydaily / weekly / monthly
schedule.timeTime in HH:MM format
cloud.providers3 or r2
cloud.bucketS3/R2 bucket name
cloud.regionAWS region (S3 only)us-east-1
cloud.endpointCustom endpoint (R2: https://<id>.r2.cloudflarestorage.com)

Cloudflare R2 vs AWS S3 Configuration

// AWS S3
const s3Config: CloudConfig = {
  provider: "s3",
  bucket: process.env.S3_BUCKET!,
  region: process.env.AWS_REGION ?? "us-east-1",
  accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
};

// Cloudflare R2 — uses S3-compatible API
const r2Config: CloudConfig = {
  provider: "r2",
  bucket: process.env.R2_BUCKET!,
  endpoint: `https://${process.env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
};

Repository Filters

interface RepoFilters {
  owned: boolean;          // Repos you own
  organizations: boolean;  // Repos in your orgs
  starred: boolean;        // Repos you've starred
  forked: boolean;         // Forked repos
  collaborator: boolean;   // Repos you collaborate on
}

// Example: back up only owned + org repos
const filters: RepoFilters = {
  owned: true,
  organizations: true,
  starred: false,
  forked: false,
  collaborator: false,
};

Repo Storage Layout

~/GitBackup/
├── hiteshchoudhary/
│   ├── gitbackup/        # Mirror clone (all branches)
│   ├── project-a/
│   └── project-b/
├── some-org/
│   └── org-repo/
└── archives/             # .tar.gz files (if cloud upload enabled)
    ├── hiteshchoudhary/
    │   ├── gitbackup.tar.gz
    │   └── project-a.tar.gz
    └── some-org/
        └── org-repo.tar.gz

Common Patterns

Adding a New IPC Handler

// electron/ipc/backup.handler.ts
import { ipcMain } from "electron";
import { BackupOrchestrator } from "../services/backup-orchestrator";

export function registerBackupHandlers(mainWindow: BrowserWindow) {
  ipcMain.handle("backup:start", async (_event, options: BackupOptions) => {
    const orchestrator = new BackupOrchestrator();

    await orchestrator.run({
      ...options,
      onProgress: (repoName, status) => {
        // Send progress back to renderer
        mainWindow.webContents.send("backup:progress", { repoName, status });
      },
    });
  });
}

Adding a New Settings Page (React)

// src/pages/Settings.tsx
import { useEffect, useState } from "react";

export default function Settings() {
  const [settings, setSettings] = useState<AppSettings | null>(null);

  useEffect(() => {
    window.electronAPI.getSettings().then(setSettings);
  }, []);

  const handleSave = async () => {
    await window.electronAPI.saveSettings(settings!);
  };

  return (
    <div>
      <label>Concurrency (1-10)</label>
      <input
        type="number"
        min={1}
        max={10}
        value={settings?.concurrency ?? 3}
        onChange={(e) =>
          setSettings((s) => ({ ...s!, concurrency: Number(e.target.value) }))
        }
      />
      <button onClick={handleSave}>Save</button>
    </div>
  );
}

Troubleshooting

IssueFix
git: command not foundInstall Git and ensure it's in system PATH
Token validation failsConfirm repo + read:org scopes are granted
macOS "app can't be opened"Right-click → Open to bypass Gatekeeper
Windows Defender warningClick "More info" → "Run anyway"
Rate limit errors (large accounts)Reduce concurrency to 1–2; app handles pagination automatically
R2 upload 403 errorsVerify CF_ACCOUNT_ID in endpoint URL and correct R2 API token permissions
Incremental fetch not workingEnsure backupPath still contains .git directory
Scheduled backup not triggeringKeep app running in system tray (don't fully quit)

Development Tips

# Run dev server (Vite + Electron with hot reload)
npm run dev

# Type-check without building
npx tsc --noEmit

# Build renderer only
npx vite build

# Inspect electron-store saved data location
# macOS: ~/Library/Application Support/GitBackup/config.json
# Windows: %APPDATA%\GitBackup\config.json
# Linux: ~/.config/GitBackup/config.json

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

36.34%
按下载量换算92

Claude

29.41%
按下载量换算74

Cursor

18.97%
按下载量换算48

Gemini CLI

8.31%
按下载量换算21

安全审计

Gen Agent Trust Hub

可疑

Socket

可疑

Snyk

通过

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills