mirror of
https://github.com/abusoww/tuxmate.git
synced 2026-04-17 19:53:11 +02:00
feat: add AI tools category, universal npm/custom installs, and update UI with app notes ref #40
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
* [Flatpak](#flatpak)
|
* [Flatpak](#flatpak)
|
||||||
* [Snap](#snap)
|
* [Snap](#snap)
|
||||||
* [Homebrew](#homebrew)
|
* [Homebrew](#homebrew)
|
||||||
|
* [Universal Targets](#universal-targets-npm--script)
|
||||||
* [Icon System](#5-icon-system)
|
* [Icon System](#5-icon-system)
|
||||||
* [Valid Categories](#6-valid-categories)
|
* [Valid Categories](#6-valid-categories)
|
||||||
4. [Adding Distributions](#adding-distributions)
|
4. [Adding Distributions](#adding-distributions)
|
||||||
@@ -140,12 +141,18 @@ All applications are defined in category-specific JSON files within [`src/lib/ap
|
|||||||
"arch": "exact-package-name", // pacman OR AUR package name
|
"arch": "exact-package-name", // pacman OR AUR package name
|
||||||
"flatpak": "com.vendor.AppId", // FULL Flatpak App ID (reverse DNS)
|
"flatpak": "com.vendor.AppId", // FULL Flatpak App ID (reverse DNS)
|
||||||
"snap": "snap-name", // Add --classic if needed
|
"snap": "snap-name", // Add --classic if needed
|
||||||
"homebrew": "formula-name" // Formula (CLI) or '--cask name' (GUI)
|
"homebrew": "formula-name", // Formula (CLI) or '--cask name' (GUI)
|
||||||
|
"npm": "@scope/package-name", // Global npm install (universal fallback)
|
||||||
|
"script": "curl -fsSL ... | bash" // Custom install script (universal fallback)
|
||||||
},
|
},
|
||||||
|
"note": "Context for universal targets", // Shown on hover for universal-fallback apps
|
||||||
"unavailableReason": "Markdown install instructions"
|
"unavailableReason": "Markdown install instructions"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **Priority system**: Native distro targets (e.g., `arch`, `ubuntu`) always take precedence over universal targets (`npm`, `script`). If an app has both `arch: "ollama"` and `script: "curl ..."`, the script is only used when the user selects a distro where no native package exists.
|
||||||
|
|
||||||
### 3. Unavailable Reason Guidelines
|
### 3. Unavailable Reason Guidelines
|
||||||
|
|
||||||
This field renders Markdown when a target is missing. It serves as the manual fallback instruction.
|
This field renders Markdown when a target is missing. It serves as the manual fallback instruction.
|
||||||
@@ -217,6 +224,64 @@ Homebrew (macOS/Linux) has two package types. Check [formulae.brew.sh](https://f
|
|||||||
* Run `brew search <name>` locally to confirm type.
|
* Run `brew search <name>` locally to confirm type.
|
||||||
* We skip `--cask` targets on Linux installs automatically.
|
* We skip `--cask` targets on Linux installs automatically.
|
||||||
|
|
||||||
|
#### Universal Targets (npm & script)
|
||||||
|
|
||||||
|
Universal targets provide cross-distro installation via package managers or custom scripts. They serve as **fallbacks** — only used when no native distro target exists for the selected distribution.
|
||||||
|
|
||||||
|
* **`npm`**: Install via `npm install -g`. Requires Node.js runtime on the system.
|
||||||
|
* **`script`**: Raw shell command (typically a `curl | bash` installer). Runs directly.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **Native targets always take priority.** If an app defines `arch: "ollama"` alongside `script: "curl ..."`, the script target is completely ignored when Arch is selected. The fallback only activates for distros without a native package.
|
||||||
|
|
||||||
|
**When to use each:**
|
||||||
|
|
||||||
|
| Target | Use Case | Example |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `npm` | CLI tools distributed via npmjs.com | `"npm": "@google/gemini-cli"` |
|
||||||
|
| `script` | Apps with official install scripts | `"script": "curl -fsSL https://ollama.com/install.sh \| sh"` |
|
||||||
|
|
||||||
|
**Real examples from the codebase:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Ollama: native on arch/fedora/nix/homebrew, falls back to script on ubuntu/debian
|
||||||
|
{
|
||||||
|
"id": "ollama",
|
||||||
|
"targets": {
|
||||||
|
"fedora": "ollama",
|
||||||
|
"arch": "ollama",
|
||||||
|
"nix": "ollama",
|
||||||
|
"homebrew": "ollama",
|
||||||
|
"script": "curl -fsSL https://ollama.com/install.sh | sh"
|
||||||
|
},
|
||||||
|
"note": "Falls back to official installer (ollama.com/install.sh) on distros without a native package."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gemini CLI: npm-only (plus homebrew)
|
||||||
|
{
|
||||||
|
"id": "gemini-cli",
|
||||||
|
"targets": {
|
||||||
|
"npm": "@google/gemini-cli",
|
||||||
|
"homebrew": "gemini-cli"
|
||||||
|
},
|
||||||
|
"note": "Requires Node.js runtime. Installed globally via npm where native packages are unavailable."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**The `note` field:**
|
||||||
|
* Used to explain the universal target behavior to users (shown on hover).
|
||||||
|
* **Required** when using `npm` or `script` targets.
|
||||||
|
* Keep concise and professional. Examples:
|
||||||
|
* ✅ `"Falls back to official installer (ollama.com/install.sh) on distros without a native package."`
|
||||||
|
* ✅ `"Requires Node.js runtime. Installed globally via npm where native packages are unavailable."`
|
||||||
|
* ❌ `"Installed globally via npm."` *(too terse, doesn't explain when or why)*
|
||||||
|
|
||||||
|
**Script safety rules:**
|
||||||
|
1. Only use **official** installer scripts from the app's own domain.
|
||||||
|
2. Always use `curl -fsSL` flags (fail silently on errors, follow redirects, show errors).
|
||||||
|
3. Never combine multiple pipes or add `sudo` — the install script handles privileges itself.
|
||||||
|
4. Verify the script URL is stable and maintained by the upstream project.
|
||||||
|
|
||||||
### 5. Icon System
|
### 5. Icon System
|
||||||
|
|
||||||
Every app needs an icon! Our JSON format makes it super simple to add icons using [Iconify](https://iconify.design/).
|
Every app needs an icon! Our JSON format makes it super simple to add icons using [Iconify](https://iconify.design/).
|
||||||
@@ -259,7 +324,7 @@ Just change the `"type"` to `"url"`:
|
|||||||
Use **exactly** one of these:
|
Use **exactly** one of these:
|
||||||
* Web Browsers • Communication • Media • Creative • Gaming • Office
|
* Web Browsers • Communication • Media • Creative • Gaming • Office
|
||||||
* Dev: Languages • Dev: Editors • Dev: Tools
|
* Dev: Languages • Dev: Editors • Dev: Tools
|
||||||
* Terminal • CLI Tools • VPN & Network • Security • File Sharing • System
|
* Terminal • CLI Tools • AI Tools • VPN & Network • Security • File Sharing • System
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -316,6 +381,8 @@ Create a new file `src/lib/scripts/<distroId>.ts`. This file must export a funct
|
|||||||
- [ ] Snap `--classic` flag verification.
|
- [ ] Snap `--classic` flag verification.
|
||||||
- [ ] Nix unfree packages added to JSON.
|
- [ ] Nix unfree packages added to JSON.
|
||||||
- [ ] Homebrew Casks prefixed correctly.
|
- [ ] Homebrew Casks prefixed correctly.
|
||||||
|
- [ ] Universal targets: `npm`/`script` only used as fallbacks, `note` field provided.
|
||||||
|
- [ ] Script URLs verified as official, stable endpoints.
|
||||||
- [ ] `npm run lint` & `npm run test` passed.
|
- [ ] `npm run lint` & `npm run test` passed.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -345,6 +412,9 @@ Brief description of changes.
|
|||||||
## Testing
|
## Testing
|
||||||
- [ ] `npm run dev` working
|
- [ ] `npm run dev` working
|
||||||
- [ ] `npm run build` passed
|
- [ ] `npm run build` passed
|
||||||
|
- [ ] `npm run test` passed
|
||||||
|
- [ ] `npm run lint` passed
|
||||||
|
|
||||||
|
|
||||||
## Screenshots (if applicable)
|
## Screenshots (if applicable)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const COLOR_MAP: Record<string, string> = {
|
|||||||
'green': '#22c55e',
|
'green': '#22c55e',
|
||||||
'teal': '#14b8a6',
|
'teal': '#14b8a6',
|
||||||
'gray': '#6b7280',
|
'gray': '#6b7280',
|
||||||
|
'fuchsia': '#d946ef',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AppItemProps {
|
interface AppItemProps {
|
||||||
@@ -54,10 +55,22 @@ export const AppItem = memo(function AppItem({
|
|||||||
}: AppItemProps) {
|
}: AppItemProps) {
|
||||||
const getUnavailableText = () => {
|
const getUnavailableText = () => {
|
||||||
const distroName = distros.find(d => d.id === selectedDistro)?.name || '';
|
const distroName = distros.find(d => d.id === selectedDistro)?.name || '';
|
||||||
return app.unavailableReason || `Not available in ${distroName} repos`;
|
let msg = '';
|
||||||
|
if (!isAvailable) {
|
||||||
|
msg = app.unavailableReason || `Not available in ${distroName} repos`;
|
||||||
|
}
|
||||||
|
if (app.note) {
|
||||||
|
msg = msg ? `${msg} — ${app.note}` : app.note;
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAur = selectedDistro === 'arch' && app.targets?.arch && isAurPackage(app.targets.arch);
|
const isAur = selectedDistro === 'arch' && app.targets?.arch && isAurPackage(app.targets.arch);
|
||||||
|
const universalTarget = !app.targets?.[selectedDistro] ? (
|
||||||
|
app.targets?.npm ? 'npm' :
|
||||||
|
app.targets?.script ? 'script' : null
|
||||||
|
) : null;
|
||||||
|
|
||||||
const hexColor = COLOR_MAP[color] || COLOR_MAP['gray'];
|
const hexColor = COLOR_MAP[color] || COLOR_MAP['gray'];
|
||||||
const checkboxColor = isAur ? '#1793d1' : hexColor;
|
const checkboxColor = isAur ? '#1793d1' : hexColor;
|
||||||
|
|
||||||
@@ -111,7 +124,8 @@ export const AppItem = memo(function AppItem({
|
|||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onTooltipEnter(app.description, e);
|
const tooltipText = app.note ? `${app.description} — ${app.note}` : app.description;
|
||||||
|
onTooltipEnter(tooltipText, e);
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -142,15 +156,31 @@ export const AppItem = memo(function AppItem({
|
|||||||
<path d="M23 12l-2.44-2.79.34-3.69-3.61-.82-1.89-3.2L12 2.96 8.6 1.5 6.71 4.69 3.1 5.5l.34 3.7L1 12l2.44 2.79-.34 3.7 3.61.82 1.89 3.2 3.4-1.47 3.4 1.46 1.89-3.19 3.61-.82-.34-3.69L23 12m-12.91 4.72l-3.8-3.81 1.48-1.48 2.32 2.33 5.85-5.87 1.48 1.48-7.33 7.35z" />
|
<path d="M23 12l-2.44-2.79.34-3.69-3.61-.82-1.89-3.2L12 2.96 8.6 1.5 6.71 4.69 3.1 5.5l.34 3.7L1 12l2.44 2.79-.34 3.7 3.61.82 1.89 3.2 3.4-1.47 3.4 1.46 1.89-3.19 3.61-.82-.34-3.69L23 12m-12.91 4.72l-3.8-3.81 1.48-1.48 2.32 2.33 5.85-5.87 1.48 1.48-7.33 7.35z" />
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
|
{universalTarget && (
|
||||||
|
<span
|
||||||
|
className="ml-1.5 px-1.5 py-[1px] text-[10px] font-bold uppercase rounded-sm border opacity-80"
|
||||||
|
style={{ color: hexColor, borderColor: hexColor }}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onTooltipEnter(`Installed via ${universalTarget}. Requires correct runtime.`, e);
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onTooltipLeave();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{universalTarget}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isAvailable && (
|
{(!isAvailable || universalTarget) && (
|
||||||
<div
|
<div
|
||||||
className="relative group flex-shrink-0 cursor-help"
|
className="relative group flex-shrink-0 cursor-help"
|
||||||
onMouseEnter={(e) => { e.stopPropagation(); onTooltipEnter(getUnavailableText(), e); }}
|
onMouseEnter={(e) => { e.stopPropagation(); onTooltipEnter(getUnavailableText(), e); }}
|
||||||
onMouseLeave={(e) => { e.stopPropagation(); onTooltipLeave(); }}
|
onMouseLeave={(e) => { e.stopPropagation(); onTooltipLeave(); }}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-[18px] h-[18px] text-[var(--text-muted)] transition-[color,transform] duration-300 hover:rotate-[360deg] hover:scale-110"
|
className={`w-[18px] h-[18px] transition-[color,transform] duration-300 hover:rotate-[360deg] hover:scale-110 ${universalTarget ? 'text-amber-600/40 hover:text-amber-500/60' : 'text-[var(--text-muted)]'}`}
|
||||||
style={{ color: isFocused ? hexColor : undefined }}
|
style={{ color: isFocused ? hexColor : undefined }}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@@ -161,5 +191,6 @@ export const AppItem = memo(function AppItem({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import {
|
import {
|
||||||
ChevronRight, Globe, MessageCircle, Code2, FileCode, Wrench,
|
ChevronRight, Globe, MessageCircle, Code2, FileCode, Wrench,
|
||||||
Terminal, Command, Play, Palette, Gamepad2, Briefcase,
|
Terminal, Command, Play, Palette, Gamepad2, Briefcase,
|
||||||
Network, Lock, Share2, Cpu, type LucideIcon
|
Network, Lock, Share2, Cpu, Sparkles, type LucideIcon
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const CATEGORY_ICONS: Record<string, LucideIcon> = {
|
const CATEGORY_ICONS: Record<string, LucideIcon> = {
|
||||||
@@ -22,6 +22,7 @@ const CATEGORY_ICONS: Record<string, LucideIcon> = {
|
|||||||
'Security': Lock,
|
'Security': Lock,
|
||||||
'File Sharing': Share2,
|
'File Sharing': Share2,
|
||||||
'System': Cpu,
|
'System': Cpu,
|
||||||
|
'AI Tools': Sparkles,
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLOR_MAP: Record<string, string> = {
|
const COLOR_MAP: Record<string, string> = {
|
||||||
@@ -40,6 +41,7 @@ const COLOR_MAP: Record<string, string> = {
|
|||||||
'green': '#22c55e',
|
'green': '#22c55e',
|
||||||
'teal': '#14b8a6',
|
'teal': '#14b8a6',
|
||||||
'gray': '#6b7280',
|
'gray': '#6b7280',
|
||||||
|
'fuchsia': '#d946ef',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Category header.
|
// Category header.
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ const categoryColors: Record<Category, string> = {
|
|||||||
'Dev: Languages': 'rose',
|
'Dev: Languages': 'rose',
|
||||||
'Dev: Tools': 'slate',
|
'Dev: Tools': 'slate',
|
||||||
'Terminal': 'zinc',
|
'Terminal': 'zinc',
|
||||||
'CLI Tools': 'gray'
|
'CLI Tools': 'gray',
|
||||||
|
'AI Tools': 'fuchsia'
|
||||||
};
|
};
|
||||||
|
|
||||||
function CategorySectionComponent({
|
function CategorySectionComponent({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||||
import { distros, apps, type DistroId } from '@/lib/data';
|
import { distros, apps, type DistroId, isAppAvailable as globalIsAppAvailable } from '@/lib/data';
|
||||||
import { isAurPackage } from '@/lib/aur';
|
import { isAurPackage } from '@/lib/aur';
|
||||||
import { isUnfreePackage } from '@/lib/nixUnfree';
|
import { isUnfreePackage } from '@/lib/nixUnfree';
|
||||||
|
|
||||||
@@ -59,8 +59,7 @@ export function useLinuxInit(): UseLinuxInitReturn {
|
|||||||
const validApps = appIds.filter(id => {
|
const validApps = appIds.filter(id => {
|
||||||
const app = apps.find(a => a.id === id);
|
const app = apps.find(a => a.id === id);
|
||||||
if (!app) return false;
|
if (!app) return false;
|
||||||
const pkg = app.targets[savedDistro || 'ubuntu'];
|
return globalIsAppAvailable(app, savedDistro || 'ubuntu');
|
||||||
return pkg !== undefined && pkg !== null;
|
|
||||||
});
|
});
|
||||||
setSelectedApps(new Set(validApps));
|
setSelectedApps(new Set(validApps));
|
||||||
}
|
}
|
||||||
@@ -131,8 +130,7 @@ export function useLinuxInit(): UseLinuxInitReturn {
|
|||||||
const isAppAvailable = useCallback((appId: string): boolean => {
|
const isAppAvailable = useCallback((appId: string): boolean => {
|
||||||
const app = apps.find(a => a.id === appId);
|
const app = apps.find(a => a.id === appId);
|
||||||
if (!app) return false;
|
if (!app) return false;
|
||||||
const packageName = app.targets[selectedDistro];
|
return globalIsAppAvailable(app, selectedDistro);
|
||||||
return packageName !== undefined && packageName !== null;
|
|
||||||
}, [selectedDistro]);
|
}, [selectedDistro]);
|
||||||
|
|
||||||
const getPackageName = useCallback((appId: string): string | null => {
|
const getPackageName = useCallback((appId: string): string | null => {
|
||||||
@@ -147,11 +145,8 @@ export function useLinuxInit(): UseLinuxInitReturn {
|
|||||||
const newSelected = new Set<string>();
|
const newSelected = new Set<string>();
|
||||||
prevSelected.forEach(appId => {
|
prevSelected.forEach(appId => {
|
||||||
const app = apps.find(a => a.id === appId);
|
const app = apps.find(a => a.id === appId);
|
||||||
if (app) {
|
if (app && globalIsAppAvailable(app, distroId)) {
|
||||||
const packageName = app.targets[distroId];
|
newSelected.add(appId);
|
||||||
if (packageName !== undefined && packageName !== null) {
|
|
||||||
newSelected.add(appId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return newSelected;
|
return newSelected;
|
||||||
@@ -161,8 +156,7 @@ export function useLinuxInit(): UseLinuxInitReturn {
|
|||||||
const toggleApp = useCallback((appId: string) => {
|
const toggleApp = useCallback((appId: string) => {
|
||||||
const app = apps.find(a => a.id === appId);
|
const app = apps.find(a => a.id === appId);
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
const pkg = app.targets[selectedDistro];
|
if (!globalIsAppAvailable(app, selectedDistro)) return;
|
||||||
if (pkg === undefined || pkg === null) return;
|
|
||||||
|
|
||||||
setSelectedApps(prev => {
|
setSelectedApps(prev => {
|
||||||
const newSet = new Set(prev);
|
const newSet = new Set(prev);
|
||||||
@@ -177,10 +171,7 @@ export function useLinuxInit(): UseLinuxInitReturn {
|
|||||||
|
|
||||||
const selectAll = useCallback(() => {
|
const selectAll = useCallback(() => {
|
||||||
const allAvailable = apps
|
const allAvailable = apps
|
||||||
.filter(app => {
|
.filter(app => globalIsAppAvailable(app, selectedDistro))
|
||||||
const pkg = app.targets[selectedDistro];
|
|
||||||
return pkg !== undefined && pkg !== null;
|
|
||||||
})
|
|
||||||
.map(app => app.id);
|
.map(app => app.id);
|
||||||
setSelectedApps(new Set(allAvailable));
|
setSelectedApps(new Set(allAvailable));
|
||||||
}, [selectedDistro]);
|
}, [selectedDistro]);
|
||||||
@@ -190,10 +181,7 @@ export function useLinuxInit(): UseLinuxInitReturn {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const availableCount = useMemo(() => {
|
const availableCount = useMemo(() => {
|
||||||
return apps.filter(app => {
|
return apps.filter(app => globalIsAppAvailable(app, selectedDistro)).length;
|
||||||
const pkg = app.targets[selectedDistro];
|
|
||||||
return pkg !== undefined && pkg !== null;
|
|
||||||
}).length;
|
|
||||||
}, [selectedDistro]);
|
}, [selectedDistro]);
|
||||||
|
|
||||||
const generatedCommand = useMemo(() => {
|
const generatedCommand = useMemo(() => {
|
||||||
@@ -205,57 +193,84 @@ export function useLinuxInit(): UseLinuxInitReturn {
|
|||||||
if (!distro) return '';
|
if (!distro) return '';
|
||||||
|
|
||||||
const packageNames: string[] = [];
|
const packageNames: string[] = [];
|
||||||
|
const npmPkgs: string[] = [];
|
||||||
|
const scriptPkgs: string[] = [];
|
||||||
|
|
||||||
selectedApps.forEach(appId => {
|
selectedApps.forEach(appId => {
|
||||||
const app = apps.find(a => a.id === appId);
|
const app = apps.find(a => a.id === appId);
|
||||||
if (app) {
|
if (app) {
|
||||||
const pkg = app.targets[selectedDistro];
|
const pkg = app.targets[selectedDistro];
|
||||||
if (pkg) packageNames.push(pkg);
|
if (pkg) {
|
||||||
|
packageNames.push(pkg);
|
||||||
|
} else {
|
||||||
|
if (app.targets.npm) npmPkgs.push(app.targets.npm);
|
||||||
|
if (app.targets.script) scriptPkgs.push(app.targets.script);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (packageNames.length === 0) return '# No packages selected';
|
const extras: string[] = [];
|
||||||
|
if (npmPkgs.length > 0) extras.push(`npm install -g ${npmPkgs.join(' ')}`);
|
||||||
|
|
||||||
|
const extrasStr = extras.join(' && ');
|
||||||
|
|
||||||
|
const appendExtras = (cmd: string) => {
|
||||||
|
if (!cmd || cmd.startsWith('#')) return extrasStr || cmd;
|
||||||
|
return extrasStr ? `${cmd} && ${extrasStr}` : cmd;
|
||||||
|
};
|
||||||
|
|
||||||
|
const appendScripts = (cmd: string) => {
|
||||||
|
if (scriptPkgs.length === 0) return cmd;
|
||||||
|
const scriptsStr = scriptPkgs.join(' && ');
|
||||||
|
return cmd ? `${cmd} && ${scriptsStr}` : scriptsStr;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (packageNames.length === 0 && extras.length === 0 && scriptPkgs.length === 0) {
|
||||||
|
return '# No packages selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseCmd = '';
|
||||||
|
|
||||||
|
if (packageNames.length > 0) {
|
||||||
|
if (selectedDistro === 'nix') {
|
||||||
|
const sortedPkgs = packageNames.filter(p => p.trim()).sort();
|
||||||
|
const pkgList = sortedPkgs.map(p => ` ${p}`).join('\\n');
|
||||||
|
baseCmd = `environment.systemPackages = with pkgs; [\\n${pkgList}\\n];`;
|
||||||
|
} else if (selectedDistro === 'snap') {
|
||||||
|
if (packageNames.length === 1) {
|
||||||
|
baseCmd = `${distro.installPrefix} ${packageNames[0]}`;
|
||||||
|
} else {
|
||||||
|
baseCmd = packageNames.map(p => `sudo snap install ${p}`).join(' && ');
|
||||||
|
}
|
||||||
|
} else if (selectedDistro === 'arch' && aurPackageInfo.hasAur) {
|
||||||
|
if (!hasYayInstalled) {
|
||||||
|
const helperName = selectedHelper; // yay or paru
|
||||||
|
const installHelperCmd = `sudo pacman -S --needed git base-devel && git clone https://aur.archlinux.org/${helperName}.git /tmp/${helperName} && cd /tmp/${helperName} && makepkg -si --noconfirm && cd - && rm -rf /tmp/${helperName}`;
|
||||||
|
const installCmd = `${helperName} -S --needed --noconfirm ${packageNames.join(' ')}`;
|
||||||
|
baseCmd = `${installHelperCmd} && ${installCmd}`;
|
||||||
|
} else {
|
||||||
|
baseCmd = `${selectedHelper} -S --needed --noconfirm ${packageNames.join(' ')}`;
|
||||||
|
}
|
||||||
|
} else if (selectedDistro === 'homebrew') {
|
||||||
|
const formulae = packageNames.filter(p => !p.startsWith('--cask '));
|
||||||
|
const casks = packageNames.filter(p => p.startsWith('--cask ')).map(p => p.replace('--cask ', ''));
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (formulae.length > 0) parts.push(`brew install ${formulae.join(' ')}`);
|
||||||
|
if (casks.length > 0) parts.push(`brew install --cask ${casks.join(' ')}`);
|
||||||
|
baseCmd = parts.join(' && ');
|
||||||
|
} else {
|
||||||
|
baseCmd = `${distro.installPrefix} ${packageNames.join(' ')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedDistro === 'nix') {
|
if (selectedDistro === 'nix') {
|
||||||
const sortedPkgs = packageNames.filter(p => p.trim()).sort();
|
let combined = baseCmd;
|
||||||
const pkgList = sortedPkgs.map(p => ` ${p}`).join('\n');
|
if (extrasStr) combined += '\\n# NPM limits:\\n# ' + extrasStr;
|
||||||
return `environment.systemPackages = with pkgs; [\n${pkgList}\n];`;
|
if (scriptPkgs.length > 0) combined += '\\n# Custom scripts:\\n# ' + scriptPkgs.join('\\n# ');
|
||||||
|
return combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedDistro === 'snap') {
|
return appendScripts(appendExtras(baseCmd));
|
||||||
if (packageNames.length === 1) {
|
|
||||||
return `${distro.installPrefix} ${packageNames[0]}`;
|
|
||||||
}
|
|
||||||
return packageNames.map(p => `sudo snap install ${p}`).join(' && ');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedDistro === 'arch' && aurPackageInfo.hasAur) {
|
|
||||||
if (!hasYayInstalled) {
|
|
||||||
const helperName = selectedHelper; // yay or paru
|
|
||||||
|
|
||||||
const installHelperCmd = `sudo pacman -S --needed git base-devel && git clone https://aur.archlinux.org/${helperName}.git /tmp/${helperName} && cd /tmp/${helperName} && makepkg -si --noconfirm && cd - && rm -rf /tmp/${helperName}`;
|
|
||||||
|
|
||||||
const installCmd = `${helperName} -S --needed --noconfirm ${packageNames.join(' ')}`;
|
|
||||||
|
|
||||||
return `${installHelperCmd} && ${installCmd}`;
|
|
||||||
} else {
|
|
||||||
return `${selectedHelper} -S --needed --noconfirm ${packageNames.join(' ')}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedDistro === 'homebrew') {
|
|
||||||
const formulae = packageNames.filter(p => !p.startsWith('--cask '));
|
|
||||||
const casks = packageNames.filter(p => p.startsWith('--cask ')).map(p => p.replace('--cask ', ''));
|
|
||||||
const parts: string[] = [];
|
|
||||||
if (formulae.length > 0) {
|
|
||||||
parts.push(`brew install ${formulae.join(' ')}`);
|
|
||||||
}
|
|
||||||
if (casks.length > 0) {
|
|
||||||
parts.push(`brew install --cask ${casks.join(' ')}`);
|
|
||||||
}
|
|
||||||
return parts.join(' && ') || '# No packages selected';
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${distro.installPrefix} ${packageNames.join(' ')}`;
|
|
||||||
}, [selectedDistro, selectedApps, aurPackageInfo.hasAur, hasYayInstalled, selectedHelper]);
|
}, [selectedDistro, selectedApps, aurPackageInfo.hasAur, hasYayInstalled, selectedHelper]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
128
src/lib/apps/ai-tools.json
Normal file
128
src/lib/apps/ai-tools.json
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "opencode",
|
||||||
|
"name": "OpenCode",
|
||||||
|
"description": "AI-powered coding platform and environment",
|
||||||
|
"category": "AI Tools",
|
||||||
|
"targets": {
|
||||||
|
"homebrew": "opencode",
|
||||||
|
"arch": "opencode",
|
||||||
|
"nix": "opencode",
|
||||||
|
"script": "curl -fsSL https://opencode.ai/install | bash"
|
||||||
|
},
|
||||||
|
"note": "Falls back to official installer (opencode.ai/install) on distros without a native package.",
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "mdi",
|
||||||
|
"name": "code-braces",
|
||||||
|
"color": "#3B82F6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "codex",
|
||||||
|
"name": "OpenAI Codex",
|
||||||
|
"description": "AI model that parses natural language and generates code",
|
||||||
|
"category": "AI Tools",
|
||||||
|
"targets": {
|
||||||
|
"npm": "@openai/codex",
|
||||||
|
"homebrew": "codex",
|
||||||
|
"nix": "codex"
|
||||||
|
},
|
||||||
|
"note": "Requires Node.js runtime. Installed globally via npm where native packages are unavailable.",
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "openai",
|
||||||
|
"color": "#412991"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gemini-cli",
|
||||||
|
"name": "Gemini CLI",
|
||||||
|
"description": "Command-line interface for Google's Gemini API",
|
||||||
|
"category": "AI Tools",
|
||||||
|
"targets": {
|
||||||
|
"npm": "@google/gemini-cli",
|
||||||
|
"homebrew": "gemini-cli",
|
||||||
|
"nix": "gemini-cli"
|
||||||
|
},
|
||||||
|
"note": "Requires Node.js runtime. Installed globally via npm where native packages are unavailable.",
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "googlegemini",
|
||||||
|
"color": "#8E75B2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "claude-code",
|
||||||
|
"name": "Claude Code",
|
||||||
|
"description": "Anthropic's official CLI tool for Claude",
|
||||||
|
"category": "AI Tools",
|
||||||
|
"targets": {
|
||||||
|
"homebrew": "--cask claude-code",
|
||||||
|
"nix": "claude-code",
|
||||||
|
"script": "curl -fsSL https://claude.ai/install.sh | bash"
|
||||||
|
},
|
||||||
|
"note": "Uses official installer script (claude.ai/install.sh) on Linux distros without a native package.",
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "anthropic",
|
||||||
|
"color": "#D97757"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ollama",
|
||||||
|
"name": "Ollama",
|
||||||
|
"description": "Get up and running with large language models locally",
|
||||||
|
"category": "AI Tools",
|
||||||
|
"targets": {
|
||||||
|
"fedora": "ollama",
|
||||||
|
"opensuse": "ollama",
|
||||||
|
"arch": "ollama",
|
||||||
|
"nix": "ollama",
|
||||||
|
"homebrew": "ollama",
|
||||||
|
"script": "curl -fsSL https://ollama.com/install.sh | sh"
|
||||||
|
},
|
||||||
|
"note": "Falls back to official installer (ollama.com/install.sh) on distros without a native package.",
|
||||||
|
"icon": {
|
||||||
|
"type": "url",
|
||||||
|
"url": "https://ollama.com/public/icon-64x64.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "llama-cpp",
|
||||||
|
"name": "llama.cpp",
|
||||||
|
"description": "Inference of LLaMA model in pure C/C++",
|
||||||
|
"category": "AI Tools",
|
||||||
|
"targets": {
|
||||||
|
"nix": "llama-cpp",
|
||||||
|
"homebrew": "llama.cpp"
|
||||||
|
},
|
||||||
|
"unavailableReason": "Available via [brew or nix](https://github.com/ggml-org/llama.cpp/blob/master/docs/install.md), [Docker](https://github.com/ggml-org/llama.cpp/blob/master/docs/docker.md), pre-built [releases](https://github.com/ggml-org/llama.cpp/releases), or [build from source](https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md).",
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "mdi",
|
||||||
|
"name": "head-cog",
|
||||||
|
"color": "#64748B"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "jan",
|
||||||
|
"name": "Jan",
|
||||||
|
"description": "Open-source ChatGPT-alternative that runs locally offline",
|
||||||
|
"category": "AI Tools",
|
||||||
|
"targets": {
|
||||||
|
"arch": "jan-bin",
|
||||||
|
"nix": "jan",
|
||||||
|
"homebrew": "--cask jan",
|
||||||
|
"flatpak": "ai.jan.Jan"
|
||||||
|
},
|
||||||
|
"unavailableReason": "Not in most official repos. Use [flatpak](https://flathub.org/en/apps/ai.jan.Jan) or Download AppImage or .deb package from [jan.ai/download](https://jan.ai/download).",
|
||||||
|
"icon": {
|
||||||
|
"type": "url",
|
||||||
|
"url": "https://www.jan.ai/_next/static/media/logo-jan.db83c5f0.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,4 +1,22 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"id": "cursor",
|
||||||
|
"name": "Cursor",
|
||||||
|
"description": "AI-powered code editor based on VS Code",
|
||||||
|
"category": "Dev: Editors",
|
||||||
|
"targets": {
|
||||||
|
"arch": "cursor-bin",
|
||||||
|
"nix": "code-cursor",
|
||||||
|
"homebrew": "--cask cursor"
|
||||||
|
},
|
||||||
|
"unavailableReason": "Only available via [AUR](https://aur.archlinux.org/packages/cursor-bin) or Nix. Download from [cursor.com/download](https://cursor.com/download).",
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "cursor",
|
||||||
|
"color": "#232020ff"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "vscode",
|
"id": "vscode",
|
||||||
"name": "VS Code",
|
"name": "VS Code",
|
||||||
@@ -38,97 +56,6 @@
|
|||||||
"color": "#2F80ED"
|
"color": "#2F80ED"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "vim",
|
|
||||||
"name": "Vim",
|
|
||||||
"description": "The classic modal text editor that started it all",
|
|
||||||
"category": "Dev: Editors",
|
|
||||||
"targets": {
|
|
||||||
"ubuntu": "vim",
|
|
||||||
"debian": "vim",
|
|
||||||
"arch": "vim",
|
|
||||||
"fedora": "vim-enhanced",
|
|
||||||
"opensuse": "vim",
|
|
||||||
"nix": "vim",
|
|
||||||
"flatpak": "org.vim.Vim",
|
|
||||||
"snap": "vim-editor",
|
|
||||||
"homebrew": "vim"
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"type": "iconify",
|
|
||||||
"set": "simple-icons",
|
|
||||||
"name": "vim",
|
|
||||||
"color": "#019733"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "neovim",
|
|
||||||
"name": "Neovim",
|
|
||||||
"description": "Modernized Vim with better extensibility",
|
|
||||||
"category": "Dev: Editors",
|
|
||||||
"targets": {
|
|
||||||
"ubuntu": "neovim",
|
|
||||||
"debian": "neovim",
|
|
||||||
"arch": "neovim",
|
|
||||||
"fedora": "neovim",
|
|
||||||
"opensuse": "neovim",
|
|
||||||
"nix": "neovim",
|
|
||||||
"flatpak": "com.neovim.Neovim",
|
|
||||||
"snap": "nvim --classic",
|
|
||||||
"homebrew": "neovim"
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"type": "iconify",
|
|
||||||
"set": "simple-icons",
|
|
||||||
"name": "neovim",
|
|
||||||
"color": "#57A143"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "helix",
|
|
||||||
"name": "Helix",
|
|
||||||
"description": "Modal editor with LSP and tree-sitter built-in",
|
|
||||||
"category": "Dev: Editors",
|
|
||||||
"targets": {
|
|
||||||
"arch": "helix",
|
|
||||||
"fedora": "helix",
|
|
||||||
"opensuse": "helix",
|
|
||||||
"nix": "helix",
|
|
||||||
"flatpak": "com.helix-editor.Helix",
|
|
||||||
"snap": "helix --classic",
|
|
||||||
"homebrew": "helix"
|
|
||||||
},
|
|
||||||
"unavailableReason": "Not in official Debian/Ubuntu repos. For Ubuntu, add the PPA: `sudo add-apt-repository ppa:maveonair/helix-editor && sudo apt update && sudo apt install helix`. For Debian, download .deb from [GitHub releases](https://github.com/helix-editor/helix/releases).",
|
|
||||||
"icon": {
|
|
||||||
"type": "iconify",
|
|
||||||
"set": "mdi",
|
|
||||||
"name": "dna",
|
|
||||||
"color": "#4E2F7F"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "micro",
|
|
||||||
"name": "Micro",
|
|
||||||
"description": "Easy-to-use terminal text editor like nano",
|
|
||||||
"category": "Dev: Editors",
|
|
||||||
"targets": {
|
|
||||||
"arch": "micro",
|
|
||||||
"ubuntu": "micro",
|
|
||||||
"debian": "micro",
|
|
||||||
"fedora": "micro",
|
|
||||||
"opensuse": "micro-editor",
|
|
||||||
"nix": "micro-editor",
|
|
||||||
"flatpak": "io.github.zyedidia.micro",
|
|
||||||
"snap": "micro --classic",
|
|
||||||
"homebrew": "micro"
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"type": "iconify",
|
|
||||||
"set": "simple-icons",
|
|
||||||
"name": "microeditor",
|
|
||||||
"color": "#2E3192"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "zed",
|
"id": "zed",
|
||||||
"name": "Zed",
|
"name": "Zed",
|
||||||
@@ -148,134 +75,6 @@
|
|||||||
"color": "#084CCF"
|
"color": "#084CCF"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "sublime",
|
|
||||||
"name": "Sublime Text",
|
|
||||||
"description": "Lightning-fast proprietary text editor",
|
|
||||||
"category": "Dev: Editors",
|
|
||||||
"targets": {
|
|
||||||
"arch": "sublime-text-4",
|
|
||||||
"nix": "sublime",
|
|
||||||
"flatpak": "com.sublimetext.three",
|
|
||||||
"snap": "sublime-text --classic",
|
|
||||||
"homebrew": "--cask sublime-text"
|
|
||||||
},
|
|
||||||
"unavailableReason": "Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.sublimetext.three)/[Snap](https://snapcraft.io/sublime-text) or follow instructions at [sublimetext.com/docs/linux_repositories](https://www.sublimetext.com/docs/linux_repositories.html).",
|
|
||||||
"icon": {
|
|
||||||
"type": "iconify",
|
|
||||||
"set": "simple-icons",
|
|
||||||
"name": "sublimetext",
|
|
||||||
"color": "#FF9800"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "arduino",
|
|
||||||
"name": "Arduino IDE",
|
|
||||||
"description": "IDE for Arduino microcontroller development",
|
|
||||||
"category": "Dev: Editors",
|
|
||||||
"targets": {
|
|
||||||
"ubuntu": "arduino",
|
|
||||||
"debian": "arduino",
|
|
||||||
"arch": "arduino",
|
|
||||||
"fedora": "arduino",
|
|
||||||
"nix": "arduino-ide",
|
|
||||||
"flatpak": "cc.arduino.IDE2",
|
|
||||||
"snap": "arduino",
|
|
||||||
"homebrew": "--cask arduino-ide"
|
|
||||||
},
|
|
||||||
"unavailableReason": "Arduino IDE is not in official openSUSE repos. Use [Flatpak](https://flathub.org/en/apps/cc.arduino.IDE2) or [Snap](https://snapcraft.io/arduino) instead.",
|
|
||||||
"icon": {
|
|
||||||
"type": "iconify",
|
|
||||||
"set": "simple-icons",
|
|
||||||
"name": "arduino",
|
|
||||||
"color": "#00878F"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cursor",
|
|
||||||
"name": "Cursor",
|
|
||||||
"description": "AI-powered code editor based on VS Code",
|
|
||||||
"category": "Dev: Editors",
|
|
||||||
"targets": {
|
|
||||||
"arch": "cursor-bin",
|
|
||||||
"nix": "code-cursor",
|
|
||||||
"homebrew": "--cask cursor"
|
|
||||||
},
|
|
||||||
"unavailableReason": "Only available via [AUR](https://aur.archlinux.org/packages/cursor-bin) or Nix. Download from [cursor.com/download](https://cursor.com/download).",
|
|
||||||
"icon": {
|
|
||||||
"type": "iconify",
|
|
||||||
"set": "simple-icons",
|
|
||||||
"name": "cursor",
|
|
||||||
"color": "#232020ff"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "kate",
|
|
||||||
"name": "Kate",
|
|
||||||
"description": "Feature-rich text editor by KDE with syntax highlighting",
|
|
||||||
"category": "Dev: Editors",
|
|
||||||
"targets": {
|
|
||||||
"ubuntu": "kate",
|
|
||||||
"debian": "kate",
|
|
||||||
"arch": "kate",
|
|
||||||
"fedora": "kate",
|
|
||||||
"opensuse": "kate",
|
|
||||||
"nix": "kdePackages.kate",
|
|
||||||
"flatpak": "org.kde.kate",
|
|
||||||
"snap": "kate --classic",
|
|
||||||
"homebrew": "--cask kate"
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"type": "iconify",
|
|
||||||
"set": "simple-icons",
|
|
||||||
"name": "kde",
|
|
||||||
"color": "#1D99F3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "emacs",
|
|
||||||
"name": "Emacs",
|
|
||||||
"description": "Extensible, customizable, free/libre text editor",
|
|
||||||
"category": "Dev: Editors",
|
|
||||||
"targets": {
|
|
||||||
"ubuntu": "emacs",
|
|
||||||
"debian": "emacs",
|
|
||||||
"arch": "emacs",
|
|
||||||
"fedora": "emacs",
|
|
||||||
"opensuse": "emacs",
|
|
||||||
"nix": "emacs",
|
|
||||||
"flatpak": "org.gnu.emacs",
|
|
||||||
"snap": "emacs --classic",
|
|
||||||
"homebrew": "--cask emacs-app"
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"type": "iconify",
|
|
||||||
"set": "simple-icons",
|
|
||||||
"name": "gnuemacs",
|
|
||||||
"color": "#7F5AB6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "geany",
|
|
||||||
"name": "Geany",
|
|
||||||
"description": "Fast and lightweight IDE",
|
|
||||||
"category": "Dev: Editors",
|
|
||||||
"targets": {
|
|
||||||
"ubuntu": "geany",
|
|
||||||
"debian": "geany",
|
|
||||||
"arch": "geany",
|
|
||||||
"fedora": "geany",
|
|
||||||
"opensuse": "geany",
|
|
||||||
"nix": "geany",
|
|
||||||
"flatpak": "org.geany.Geany",
|
|
||||||
"homebrew": "--cask geany"
|
|
||||||
},
|
|
||||||
"unavailableReason": "Snap is unmaintained, download from [Geany](https://www.geany.org/download/releases/) or use [Flatpak](https://flathub.org/en/apps/org.geany.Geany).",
|
|
||||||
"icon": {
|
|
||||||
"type": "url",
|
|
||||||
"url": "https://www.geany.org/static/img/geany.svg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "intellij-idea",
|
"id": "intellij-idea",
|
||||||
"name": "Intellij IDEA",
|
"name": "Intellij IDEA",
|
||||||
@@ -332,5 +131,206 @@
|
|||||||
"set": "logos",
|
"set": "logos",
|
||||||
"name": "clion"
|
"name": "clion"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arduino",
|
||||||
|
"name": "Arduino IDE",
|
||||||
|
"description": "IDE for Arduino microcontroller development",
|
||||||
|
"category": "Dev: Editors",
|
||||||
|
"targets": {
|
||||||
|
"ubuntu": "arduino",
|
||||||
|
"debian": "arduino",
|
||||||
|
"arch": "arduino",
|
||||||
|
"fedora": "arduino",
|
||||||
|
"nix": "arduino-ide",
|
||||||
|
"flatpak": "cc.arduino.IDE2",
|
||||||
|
"snap": "arduino",
|
||||||
|
"homebrew": "--cask arduino-ide"
|
||||||
|
},
|
||||||
|
"unavailableReason": "Arduino IDE is not in official openSUSE repos. Use [Flatpak](https://flathub.org/en/apps/cc.arduino.IDE2) or [Snap](https://snapcraft.io/arduino) instead.",
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "arduino",
|
||||||
|
"color": "#00878F"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sublime",
|
||||||
|
"name": "Sublime Text",
|
||||||
|
"description": "Lightning-fast proprietary text editor",
|
||||||
|
"category": "Dev: Editors",
|
||||||
|
"targets": {
|
||||||
|
"arch": "sublime-text-4",
|
||||||
|
"nix": "sublime",
|
||||||
|
"flatpak": "com.sublimetext.three",
|
||||||
|
"snap": "sublime-text --classic",
|
||||||
|
"homebrew": "--cask sublime-text"
|
||||||
|
},
|
||||||
|
"unavailableReason": "Not in official repos. Use [Flatpak](https://flathub.org/en/apps/com.sublimetext.three)/[Snap](https://snapcraft.io/sublime-text) or follow instructions at [sublimetext.com/docs/linux_repositories](https://www.sublimetext.com/docs/linux_repositories.html).",
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "sublimetext",
|
||||||
|
"color": "#FF9800"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kate",
|
||||||
|
"name": "Kate",
|
||||||
|
"description": "Feature-rich text editor by KDE with syntax highlighting",
|
||||||
|
"category": "Dev: Editors",
|
||||||
|
"targets": {
|
||||||
|
"ubuntu": "kate",
|
||||||
|
"debian": "kate",
|
||||||
|
"arch": "kate",
|
||||||
|
"fedora": "kate",
|
||||||
|
"opensuse": "kate",
|
||||||
|
"nix": "kdePackages.kate",
|
||||||
|
"flatpak": "org.kde.kate",
|
||||||
|
"snap": "kate --classic",
|
||||||
|
"homebrew": "--cask kate"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "kde",
|
||||||
|
"color": "#1D99F3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "geany",
|
||||||
|
"name": "Geany",
|
||||||
|
"description": "Fast and lightweight IDE",
|
||||||
|
"category": "Dev: Editors",
|
||||||
|
"targets": {
|
||||||
|
"ubuntu": "geany",
|
||||||
|
"debian": "geany",
|
||||||
|
"arch": "geany",
|
||||||
|
"fedora": "geany",
|
||||||
|
"opensuse": "geany",
|
||||||
|
"nix": "geany",
|
||||||
|
"flatpak": "org.geany.Geany",
|
||||||
|
"homebrew": "--cask geany"
|
||||||
|
},
|
||||||
|
"unavailableReason": "Snap is unmaintained, download from [Geany](https://www.geany.org/download/releases/) or use [Flatpak](https://flathub.org/en/apps/org.geany.Geany).",
|
||||||
|
"icon": {
|
||||||
|
"type": "url",
|
||||||
|
"url": "https://www.geany.org/static/img/geany.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "neovim",
|
||||||
|
"name": "Neovim",
|
||||||
|
"description": "Modernized Vim with better extensibility",
|
||||||
|
"category": "Dev: Editors",
|
||||||
|
"targets": {
|
||||||
|
"ubuntu": "neovim",
|
||||||
|
"debian": "neovim",
|
||||||
|
"arch": "neovim",
|
||||||
|
"fedora": "neovim",
|
||||||
|
"opensuse": "neovim",
|
||||||
|
"nix": "neovim",
|
||||||
|
"flatpak": "com.neovim.Neovim",
|
||||||
|
"snap": "nvim --classic",
|
||||||
|
"homebrew": "neovim"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "neovim",
|
||||||
|
"color": "#57A143"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vim",
|
||||||
|
"name": "Vim",
|
||||||
|
"description": "The classic modal text editor that started it all",
|
||||||
|
"category": "Dev: Editors",
|
||||||
|
"targets": {
|
||||||
|
"ubuntu": "vim",
|
||||||
|
"debian": "vim",
|
||||||
|
"arch": "vim",
|
||||||
|
"fedora": "vim-enhanced",
|
||||||
|
"opensuse": "vim",
|
||||||
|
"nix": "vim",
|
||||||
|
"flatpak": "org.vim.Vim",
|
||||||
|
"snap": "vim-editor",
|
||||||
|
"homebrew": "vim"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "vim",
|
||||||
|
"color": "#019733"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "helix",
|
||||||
|
"name": "Helix",
|
||||||
|
"description": "Modal editor with LSP and tree-sitter built-in",
|
||||||
|
"category": "Dev: Editors",
|
||||||
|
"targets": {
|
||||||
|
"arch": "helix",
|
||||||
|
"fedora": "helix",
|
||||||
|
"opensuse": "helix",
|
||||||
|
"nix": "helix",
|
||||||
|
"flatpak": "com.helix-editor.Helix",
|
||||||
|
"snap": "helix --classic",
|
||||||
|
"homebrew": "helix"
|
||||||
|
},
|
||||||
|
"unavailableReason": "Not in official Debian/Ubuntu repos. For Ubuntu, add the PPA: `sudo add-apt-repository ppa:maveonair/helix-editor && sudo apt update && sudo apt install helix`. For Debian, download .deb from [GitHub releases](https://github.com/helix-editor/helix/releases).",
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "mdi",
|
||||||
|
"name": "dna",
|
||||||
|
"color": "#4E2F7F"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "micro",
|
||||||
|
"name": "Micro",
|
||||||
|
"description": "Easy-to-use terminal text editor like nano",
|
||||||
|
"category": "Dev: Editors",
|
||||||
|
"targets": {
|
||||||
|
"arch": "micro",
|
||||||
|
"ubuntu": "micro",
|
||||||
|
"debian": "micro",
|
||||||
|
"fedora": "micro",
|
||||||
|
"opensuse": "micro-editor",
|
||||||
|
"nix": "micro-editor",
|
||||||
|
"flatpak": "io.github.zyedidia.micro",
|
||||||
|
"snap": "micro --classic",
|
||||||
|
"homebrew": "micro"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "microeditor",
|
||||||
|
"color": "#2E3192"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "emacs",
|
||||||
|
"name": "Emacs",
|
||||||
|
"description": "Extensible, customizable, free/libre text editor",
|
||||||
|
"category": "Dev: Editors",
|
||||||
|
"targets": {
|
||||||
|
"ubuntu": "emacs",
|
||||||
|
"debian": "emacs",
|
||||||
|
"arch": "emacs",
|
||||||
|
"fedora": "emacs",
|
||||||
|
"opensuse": "emacs",
|
||||||
|
"nix": "emacs",
|
||||||
|
"flatpak": "org.gnu.emacs",
|
||||||
|
"snap": "emacs --classic",
|
||||||
|
"homebrew": "--cask emacs-app"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "iconify",
|
||||||
|
"set": "simple-icons",
|
||||||
|
"name": "gnuemacs",
|
||||||
|
"color": "#7F5AB6"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -13,9 +13,11 @@ import vpnNetwork from './apps/vpn-network.json';
|
|||||||
import security from './apps/security.json';
|
import security from './apps/security.json';
|
||||||
import fileSharing from './apps/file-sharing.json';
|
import fileSharing from './apps/file-sharing.json';
|
||||||
import system from './apps/system.json';
|
import system from './apps/system.json';
|
||||||
|
import aiTools from './apps/ai-tools.json';
|
||||||
|
|
||||||
|
|
||||||
export type DistroId = 'ubuntu' | 'debian' | 'arch' | 'fedora' | 'opensuse' | 'nix' | 'flatpak' | 'snap' | 'homebrew';
|
export type DistroId = 'ubuntu' | 'debian' | 'arch' | 'fedora' | 'opensuse' | 'nix' | 'flatpak' | 'snap' | 'homebrew';
|
||||||
|
export type UniversalTargetId = 'npm' | 'script';
|
||||||
|
|
||||||
export type Category =
|
export type Category =
|
||||||
| 'Web Browsers'
|
| 'Web Browsers'
|
||||||
@@ -32,7 +34,8 @@ export type Category =
|
|||||||
| 'VPN & Network'
|
| 'VPN & Network'
|
||||||
| 'Security'
|
| 'Security'
|
||||||
| 'File Sharing'
|
| 'File Sharing'
|
||||||
| 'System';
|
| 'System'
|
||||||
|
| 'AI Tools';
|
||||||
|
|
||||||
export type IconDef =
|
export type IconDef =
|
||||||
| { type: 'iconify'; set: string; name: string; color?: string }
|
| { type: 'iconify'; set: string; name: string; color?: string }
|
||||||
@@ -52,8 +55,9 @@ export interface AppData {
|
|||||||
description: string;
|
description: string;
|
||||||
category: Category;
|
category: Category;
|
||||||
icon: IconDef;
|
icon: IconDef;
|
||||||
targets: Partial<Record<DistroId, string>>;
|
targets: Partial<Record<DistroId | UniversalTargetId, string>>;
|
||||||
unavailableReason?: string;
|
unavailableReason?: string;
|
||||||
|
note?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getIconUrl = (icon: IconDef): string => {
|
export const getIconUrl = (icon: IconDef): string => {
|
||||||
@@ -94,7 +98,8 @@ export const apps: AppData[] = [
|
|||||||
...(vpnNetwork as AppData[]),
|
...(vpnNetwork as AppData[]),
|
||||||
...(security as AppData[]),
|
...(security as AppData[]),
|
||||||
...(fileSharing as AppData[]),
|
...(fileSharing as AppData[]),
|
||||||
...(system as AppData[])
|
...(system as AppData[]),
|
||||||
|
...(aiTools as AppData[])
|
||||||
];
|
];
|
||||||
|
|
||||||
export const categories: Category[] = [
|
export const categories: Category[] = [
|
||||||
@@ -113,6 +118,7 @@ export const categories: Category[] = [
|
|||||||
'Dev: Tools',
|
'Dev: Tools',
|
||||||
'Terminal',
|
'Terminal',
|
||||||
'CLI Tools',
|
'CLI Tools',
|
||||||
|
'AI Tools',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getAppsByCategory = (category: Category): AppData[] => {
|
export const getAppsByCategory = (category: Category): AppData[] => {
|
||||||
@@ -120,5 +126,7 @@ export const getAppsByCategory = (category: Category): AppData[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isAppAvailable = (app: AppData, distro: DistroId): boolean => {
|
export const isAppAvailable = (app: AppData, distro: DistroId): boolean => {
|
||||||
return distro in app.targets;
|
return (distro in app.targets) ||
|
||||||
|
('npm' in app.targets) ||
|
||||||
|
('script' in app.targets);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { distros, type DistroId } from './data';
|
import { distros, type DistroId } from './data';
|
||||||
import {
|
import {
|
||||||
getSelectedPackages,
|
getSelectedPackages,
|
||||||
|
getUniversalPackages,
|
||||||
|
generateUniversalScript,
|
||||||
|
generateAsciiHeader,
|
||||||
|
generateSharedUtils,
|
||||||
generateUbuntuScript,
|
generateUbuntuScript,
|
||||||
generateDebianScript,
|
generateDebianScript,
|
||||||
generateArchScript,
|
generateArchScript,
|
||||||
@@ -22,43 +26,84 @@ export function generateInstallScript(options: GenerateOptions): string {
|
|||||||
const { distroId, selectedAppIds, helper = 'yay' } = options;
|
const { distroId, selectedAppIds, helper = 'yay' } = options;
|
||||||
const distro = distros.find(d => d.id === distroId);
|
const distro = distros.find(d => d.id === distroId);
|
||||||
|
|
||||||
if (!distro) return '#!/bin/bash\necho "Error: Unknown distribution"\nexit 1';
|
if (!distro) return '#!/bin/bash\\necho "Error: Unknown distribution"\\nexit 1';
|
||||||
|
|
||||||
|
const uScript = generateUniversalScript(selectedAppIds, distroId);
|
||||||
const packages = getSelectedPackages(selectedAppIds, distroId);
|
const packages = getSelectedPackages(selectedAppIds, distroId);
|
||||||
if (packages.length === 0) return '#!/bin/bash\necho "No packages selected"\nexit 0';
|
|
||||||
|
if (packages.length === 0 && !uScript) return '#!/bin/bash\\necho "No packages selected"\\nexit 0';
|
||||||
|
|
||||||
|
const injectUniversal = (script: string) => script.replace('\\nprint_summary', '\\n' + uScript + '\\nprint_summary');
|
||||||
|
|
||||||
|
let scriptContent = '';
|
||||||
|
|
||||||
switch (distroId) {
|
switch (distroId) {
|
||||||
case 'ubuntu': return generateUbuntuScript(packages);
|
case 'ubuntu': scriptContent = injectUniversal(generateUbuntuScript(packages)); break;
|
||||||
case 'debian': return generateDebianScript(packages);
|
case 'debian': scriptContent = injectUniversal(generateDebianScript(packages)); break;
|
||||||
case 'arch': return generateArchScript(packages, helper);
|
case 'arch': scriptContent = injectUniversal(generateArchScript(packages, helper)); break;
|
||||||
case 'fedora': return generateFedoraScript(packages);
|
case 'fedora': scriptContent = injectUniversal(generateFedoraScript(packages)); break;
|
||||||
case 'opensuse': return generateOpenSUSEScript(packages);
|
case 'opensuse': scriptContent = injectUniversal(generateOpenSUSEScript(packages)); break;
|
||||||
case 'nix': return generateNixConfig(packages);
|
case 'flatpak': scriptContent = injectUniversal(generateFlatpakScript(packages)); break;
|
||||||
case 'flatpak': return generateFlatpakScript(packages);
|
case 'snap': scriptContent = injectUniversal(generateSnapScript(packages)); break;
|
||||||
case 'snap': return generateSnapScript(packages);
|
case 'homebrew': scriptContent = injectUniversal(generateHomebrewScript(packages)); break;
|
||||||
case 'homebrew': return generateHomebrewScript(packages);
|
case 'nix':
|
||||||
default: return '#!/bin/bash\necho "Unsupported distribution"\nexit 1';
|
if (packages.length === 0) return '# Nix\\n\\n# Generic Installers (Please run separately outside NixOS configuration):\\n' + uScript;
|
||||||
|
return generateNixConfig(packages) + '\\n\\n# ----------------------------------------\\n# NOTE: Universal packages (npm) cannot be strictly placed in environment.systemPackages.\\n# You may need to run these commands in a standard terminal:\\n# \\n/* \\n' + uScript + '\\n*/\\n';
|
||||||
|
default: return '#!/bin/bash\\necho "Unsupported distribution"\\nexit 1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (packages.length === 0) {
|
||||||
|
const universalCount = getUniversalPackages(selectedAppIds, 'npm', distroId).length
|
||||||
|
+ getUniversalPackages(selectedAppIds, 'script', distroId).length;
|
||||||
|
return generateAsciiHeader(distro.name, universalCount)
|
||||||
|
+ generateSharedUtils(distro.name.toLowerCase(), universalCount)
|
||||||
|
+ uScript
|
||||||
|
+ '\nprint_summary\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateCommandline(options: GenerateOptions): string {
|
export function generateCommandline(options: GenerateOptions): string {
|
||||||
const { selectedAppIds, distroId } = options;
|
const { selectedAppIds, distroId } = options;
|
||||||
|
|
||||||
|
const npmPkgs = getUniversalPackages(selectedAppIds, 'npm', distroId);
|
||||||
|
const scriptPkgs = getUniversalPackages(selectedAppIds, 'script', distroId);
|
||||||
|
|
||||||
|
const extras: string[] = [];
|
||||||
|
if (npmPkgs.length > 0) extras.push(`npm install -g ${npmPkgs.map(p => p.pkg).join(' ')}`);
|
||||||
|
|
||||||
|
const extrasStr = extras.length > 0 ? (extras.join(' && ')) : '';
|
||||||
|
const appendExtras = (cmd: string) => {
|
||||||
|
if (!cmd || cmd.startsWith('#')) return extrasStr ? extrasStr : cmd;
|
||||||
|
return extrasStr ? `${cmd} && ${extrasStr}` : cmd;
|
||||||
|
};
|
||||||
|
const appendScripts = (cmd: string) => {
|
||||||
|
if (scriptPkgs.length === 0) return cmd;
|
||||||
|
const scriptsStr = scriptPkgs.map(p => p.pkg).join(' && ');
|
||||||
|
if (!cmd || cmd.startsWith('#')) return scriptsStr;
|
||||||
|
return `${cmd} && ${scriptsStr}`;
|
||||||
|
};
|
||||||
|
|
||||||
const packages = getSelectedPackages(selectedAppIds, distroId);
|
const packages = getSelectedPackages(selectedAppIds, distroId);
|
||||||
if (packages.length === 0) return '# No packages selected';
|
if (packages.length === 0 && extras.length === 0 && scriptPkgs.length === 0) return '# No packages selected';
|
||||||
|
|
||||||
const pkgList = packages.map(p => p.pkg).join(' ');
|
const pkgList = packages.map(p => p.pkg).join(' ');
|
||||||
|
|
||||||
switch (distroId) {
|
switch (distroId) {
|
||||||
case 'ubuntu':
|
case 'ubuntu':
|
||||||
case 'debian': return `sudo apt install -y ${pkgList}`;
|
case 'debian': return appendScripts(appendExtras(pkgList ? `sudo apt install -y ${pkgList}` : ''));
|
||||||
case 'arch': return `yay -S --needed --noconfirm ${pkgList}`;
|
case 'arch': return appendScripts(appendExtras(pkgList ? `yay -S --needed --noconfirm ${pkgList}` : ''));
|
||||||
case 'fedora': return `sudo dnf install -y ${pkgList}`;
|
case 'fedora': return appendScripts(appendExtras(pkgList ? `sudo dnf install -y ${pkgList}` : ''));
|
||||||
case 'opensuse': return `sudo zypper install -y ${pkgList}`;
|
case 'opensuse': return appendScripts(appendExtras(pkgList ? `sudo zypper install -y ${pkgList}` : ''));
|
||||||
case 'nix': return generateNixConfig(packages);
|
case 'nix': return generateNixConfig(packages); // Nix handles its own thing without extras as simple cmds
|
||||||
case 'flatpak': return `flatpak install flathub -y ${pkgList}`;
|
case 'flatpak': return appendScripts(appendExtras(pkgList ? `flatpak install flathub -y ${pkgList}` : ''));
|
||||||
case 'snap':
|
case 'snap': {
|
||||||
if (packages.length === 1) return `sudo snap install ${pkgList}`;
|
let cmd = '';
|
||||||
return packages.map(p => `sudo snap install ${p.pkg}`).join(' && ');
|
if (packages.length === 1) cmd = `sudo snap install ${pkgList}`;
|
||||||
|
else if (packages.length > 1) cmd = packages.map(p => `sudo snap install ${p.pkg}`).join(' && ');
|
||||||
|
return appendScripts(appendExtras(cmd));
|
||||||
|
}
|
||||||
case 'homebrew': {
|
case 'homebrew': {
|
||||||
const formulae = packages.filter(p => !p.pkg.startsWith('--cask '));
|
const formulae = packages.filter(p => !p.pkg.startsWith('--cask '));
|
||||||
const casks = packages.filter(p => p.pkg.startsWith('--cask '));
|
const casks = packages.filter(p => p.pkg.startsWith('--cask '));
|
||||||
@@ -69,8 +114,8 @@ export function generateCommandline(options: GenerateOptions): string {
|
|||||||
if (casks.length > 0) {
|
if (casks.length > 0) {
|
||||||
parts.push(`brew install --cask ${casks.map(p => p.pkg.replace('--cask ', '')).join(' ')}`);
|
parts.push(`brew install --cask ${casks.map(p => p.pkg.replace('--cask ', '')).join(' ')}`);
|
||||||
}
|
}
|
||||||
return parts.join(' && ') || '# No packages selected';
|
return appendScripts(appendExtras(parts.join(' && ')));
|
||||||
}
|
}
|
||||||
default: return `# Install: ${pkgList}`;
|
default: return appendScripts(appendExtras(pkgList ? `# Install: ${pkgList}` : ''));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"jetbrains.idea",
|
"jetbrains.idea",
|
||||||
"jetbrains.pycharm",
|
"jetbrains.pycharm",
|
||||||
"jetbrains.clion",
|
"jetbrains.clion",
|
||||||
"microsoft-edge"
|
"microsoft-edge",
|
||||||
|
"claude-code"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
export { escapeShellString, getSelectedPackages, type PackageInfo } from './shared';
|
export { escapeShellString, getSelectedPackages, getUniversalPackages, generateUniversalScript, generateAsciiHeader, generateSharedUtils, type PackageInfo } from './shared';
|
||||||
export { generateUbuntuScript } from './ubuntu';
|
export { generateUbuntuScript } from './ubuntu';
|
||||||
export { generateDebianScript } from './debian';
|
export { generateDebianScript } from './debian';
|
||||||
export { generateArchScript } from './arch';
|
export { generateArchScript } from './arch';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { apps, type DistroId, type AppData } from '../data';
|
import { apps, type DistroId, type AppData, type UniversalTargetId } from '../data';
|
||||||
|
|
||||||
export interface PackageInfo {
|
export interface PackageInfo {
|
||||||
app: AppData;
|
app: AppData;
|
||||||
@@ -21,6 +21,33 @@ export function getSelectedPackages(selectedAppIds: Set<string>, distroId: Distr
|
|||||||
.map(app => ({ app, pkg: app.targets[distroId]! }));
|
.map(app => ({ app, pkg: app.targets[distroId]! }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUniversalPackages(selectedAppIds: Set<string>, target: UniversalTargetId, distroId?: DistroId): PackageInfo[] {
|
||||||
|
return Array.from(selectedAppIds)
|
||||||
|
.map(id => apps.find(a => a.id === id))
|
||||||
|
.filter((app): app is AppData => !!app && !!app.targets[target] && (!distroId || !app.targets[distroId]))
|
||||||
|
.map(app => ({ app, pkg: app.targets[target]! }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateUniversalScript(selectedAppIds: Set<string>, distroId?: DistroId): string {
|
||||||
|
let script = '';
|
||||||
|
const npmPkgs = getUniversalPackages(selectedAppIds, 'npm', distroId);
|
||||||
|
const scriptPkgs = getUniversalPackages(selectedAppIds, 'script', distroId);
|
||||||
|
|
||||||
|
if (npmPkgs.length === 0 && scriptPkgs.length === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (npmPkgs.length > 0) {
|
||||||
|
script += `if command -v npm >/dev/null 2>&1; then\\n info "Installing Node.js (npm) packages..."\\n${npmPkgs.map(p => ` with_retry npm install -g ${escapeShellString(p.pkg)}`).join('\\n')}\\nelse\\n warn "npm is not installed. Skipping: ${npmPkgs.map(p => escapeShellString(p.app.name)).join(', ')}"\\nfi\\n\\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptPkgs.length > 0) {
|
||||||
|
script += `info "Running custom install scripts..."\\n${scriptPkgs.map(p => `info "Running script for ${escapeShellString(p.app.name)}..."\\n${p.pkg}`).join('\\n')}\\n\\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
export function generateAsciiHeader(distroName: string, pkgCount: number): string {
|
export function generateAsciiHeader(distroName: string, pkgCount: number): string {
|
||||||
const date = new Date().toISOString().split('T')[0];
|
const date = new Date().toISOString().split('T')[0];
|
||||||
return `#!/bin/bash
|
return `#!/bin/bash
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
"fetchedAt": "2026-03-25T08:54:15.513Z"
|
"fetchedAt": "2026-03-26T11:12:34.224Z"
|
||||||
},
|
},
|
||||||
"count": 702,
|
"count": 702,
|
||||||
"apps": [
|
"apps": [
|
||||||
@@ -174,6 +174,7 @@
|
|||||||
"com.tomjwatson.Emote",
|
"com.tomjwatson.Emote",
|
||||||
"com.toolstack.Folio",
|
"com.toolstack.Folio",
|
||||||
"com.ulaa.Ulaa",
|
"com.ulaa.Ulaa",
|
||||||
|
"com.unicornsonlsd.finamp",
|
||||||
"com.usebottles.bottles",
|
"com.usebottles.bottles",
|
||||||
"com.usebruno.Bruno",
|
"com.usebruno.Bruno",
|
||||||
"com.valvesoftware.SteamLink",
|
"com.valvesoftware.SteamLink",
|
||||||
@@ -227,7 +228,6 @@
|
|||||||
"eu.jumplink.Learn6502",
|
"eu.jumplink.Learn6502",
|
||||||
"eu.nokun.MirrorHall",
|
"eu.nokun.MirrorHall",
|
||||||
"eu.vcmi.VCMI",
|
"eu.vcmi.VCMI",
|
||||||
"fr.arnaudmichel.launcherstudio",
|
|
||||||
"fr.handbrake.ghb",
|
"fr.handbrake.ghb",
|
||||||
"fr.quentium.acters",
|
"fr.quentium.acters",
|
||||||
"garden.jamie.Morphosis",
|
"garden.jamie.Morphosis",
|
||||||
@@ -255,6 +255,7 @@
|
|||||||
"io.frama.tractor.carburetor",
|
"io.frama.tractor.carburetor",
|
||||||
"io.freetubeapp.FreeTube",
|
"io.freetubeapp.FreeTube",
|
||||||
"io.gdevelop.ide",
|
"io.gdevelop.ide",
|
||||||
|
"io.github.AshBuk.FingerGo",
|
||||||
"io.github.BrisklyDev.Brisk",
|
"io.github.BrisklyDev.Brisk",
|
||||||
"io.github.CyberTimon.RapidRAW",
|
"io.github.CyberTimon.RapidRAW",
|
||||||
"io.github.DenysMb.Kontainer",
|
"io.github.DenysMb.Kontainer",
|
||||||
@@ -326,7 +327,6 @@
|
|||||||
"io.github.linx_systems.ClamUI",
|
"io.github.linx_systems.ClamUI",
|
||||||
"io.github.lluciocc.Vish",
|
"io.github.lluciocc.Vish",
|
||||||
"io.github.lullabyX.sone",
|
"io.github.lullabyX.sone",
|
||||||
"io.github.maddecoder.Classic-RBDOOM-3-BFG",
|
|
||||||
"io.github.marco_calautti.DeltaPatcher",
|
"io.github.marco_calautti.DeltaPatcher",
|
||||||
"io.github.martchus.syncthingtray",
|
"io.github.martchus.syncthingtray",
|
||||||
"io.github.martinrotter.rssguard",
|
"io.github.martinrotter.rssguard",
|
||||||
@@ -357,7 +357,6 @@
|
|||||||
"io.github.realmazharhussain.GdmSettings",
|
"io.github.realmazharhussain.GdmSettings",
|
||||||
"io.github.revisto.drum-machine",
|
"io.github.revisto.drum-machine",
|
||||||
"io.github.rfrench3.scopebuddy-gui",
|
"io.github.rfrench3.scopebuddy-gui",
|
||||||
"io.github.schwarzen.colormydesktop",
|
|
||||||
"io.github.seadve.Kooha",
|
"io.github.seadve.Kooha",
|
||||||
"io.github.seadve.Mousai",
|
"io.github.seadve.Mousai",
|
||||||
"io.github.sepehr_rs.Sudoku",
|
"io.github.sepehr_rs.Sudoku",
|
||||||
@@ -444,6 +443,7 @@
|
|||||||
"net.sapples.LiveCaptions",
|
"net.sapples.LiveCaptions",
|
||||||
"net.shadps4.shadPS4",
|
"net.shadps4.shadPS4",
|
||||||
"net.sourceforge.VMPK",
|
"net.sourceforge.VMPK",
|
||||||
|
"net.sourceforge.m64py.M64Py",
|
||||||
"net.supertuxkart.SuperTuxKart",
|
"net.supertuxkart.SuperTuxKart",
|
||||||
"net.trowell.typesetter",
|
"net.trowell.typesetter",
|
||||||
"net.waterfox.waterfox",
|
"net.waterfox.waterfox",
|
||||||
|
|||||||
Reference in New Issue
Block a user