feat: add skill manager interface

This commit is contained in:
2026-05-13 16:50:09 +08:00
parent 12a6a83840
commit a0f2860a57
10 changed files with 1408 additions and 98 deletions

View File

@@ -1,59 +1,335 @@
#app { .app-shell {
height: 100vh; min-height: 100vh;
text-align: center; display: grid;
grid-template-columns: 240px minmax(0, 1fr);
background: #f5f7fa;
color: #17202a;
} }
#logo { .sidebar {
display: block; background: #ffffff;
width: 50%; border-right: 1px solid #d7dde5;
height: 50%; padding: 18px 14px;
margin: auto;
padding: 10% 0 0;
background-position: center;
background-repeat: no-repeat;
background-size: 100% 100%;
background-origin: content-box;
} }
.result { .brand {
height: 20px; display: flex;
line-height: 20px; align-items: center;
margin: 1.5rem auto; gap: 12px;
padding: 4px 6px 18px;
border-bottom: 1px solid #e6eaf0;
} }
.input-box .btn { .brand-mark {
width: 60px; width: 38px;
height: 30px; height: 38px;
line-height: 30px; display: grid;
border-radius: 3px; place-items: center;
border: none; border-radius: 8px;
margin: 0 0 0 20px; background: #1d4ed8;
padding: 0 8px; color: #ffffff;
cursor: pointer; font-weight: 700;
} }
.input-box .btn:hover { .brand h1 {
background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%); margin: 0;
color: #333333; font-size: 17px;
line-height: 22px;
} }
.input-box .input { .brand span,
border: none; .topbar span {
border-radius: 3px; color: #657386;
outline: none; font-size: 12px;
height: 30px;
line-height: 30px;
padding: 0 10px;
background-color: rgba(240, 240, 240, 1);
-webkit-font-smoothing: antialiased;
} }
.input-box .input:hover { .tabs {
border: none; display: grid;
background-color: rgba(255, 255, 255, 1); gap: 6px;
margin-top: 18px;
} }
.input-box .input:focus { .tabs button,
border: none; button {
background-color: rgba(255, 255, 255, 1); min-height: 34px;
} display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
border: 1px solid #cfd7e3;
border-radius: 6px;
background: #ffffff;
color: #17202a;
font: inherit;
cursor: pointer;
white-space: nowrap;
}
.tabs button {
justify-content: flex-start;
padding: 0 10px;
}
button:hover:not(:disabled) {
border-color: #1d4ed8;
color: #1d4ed8;
}
button:disabled {
cursor: not-allowed;
opacity: 0.56;
}
button.active,
.tabs button.active,
.segmented button.active {
background: #eaf1ff;
border-color: #1d4ed8;
color: #1d4ed8;
}
button.secondary {
background: #f9fafb;
}
button.ghost {
background: transparent;
}
button.danger {
border-color: #f2c8c8;
color: #b42318;
}
button.danger:hover:not(:disabled) {
border-color: #b42318;
color: #8a1f16;
}
.icon-only {
width: 34px;
padding: 0;
}
.workspace {
min-width: 0;
padding: 18px;
}
.topbar {
min-height: 58px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 12px;
}
.topbar h2 {
margin: 0;
font-size: 22px;
line-height: 28px;
}
.notice {
margin-bottom: 12px;
padding: 10px 12px;
border: 1px solid #b9dfc5;
border-radius: 6px;
background: #eefbf2;
color: #166534;
}
.notice.error {
border-color: #f1b7b7;
background: #fff1f1;
color: #9f1c1c;
}
.panel {
background: #ffffff;
border: 1px solid #d7dde5;
border-radius: 8px;
padding: 14px;
}
.form-grid {
display: grid;
grid-template-columns: repeat(3, minmax(180px, 1fr));
gap: 14px;
}
label,
.field {
display: grid;
gap: 6px;
align-content: start;
}
label span,
.field > span {
color: #4b5b6f;
font-size: 12px;
font-weight: 600;
}
input {
width: 100%;
box-sizing: border-box;
min-height: 34px;
border: 1px solid #cfd7e3;
border-radius: 6px;
background: #ffffff;
color: #17202a;
font: inherit;
padding: 0 10px;
outline: none;
}
input:focus {
border-color: #1d4ed8;
box-shadow: 0 0 0 3px rgba(29, 78, 216, 0.12);
}
.checkbox {
grid-template-columns: 18px 1fr;
align-items: center;
gap: 8px;
}
.checkbox input {
width: 16px;
min-height: 16px;
}
.segmented {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.toolbar {
display: flex;
align-items: center;
gap: 8px;
margin-top: 14px;
}
.toolbar.split {
justify-content: space-between;
margin-top: 0;
margin-bottom: 12px;
}
.search {
max-width: 360px;
}
.table-wrap {
overflow: auto;
border: 1px solid #e2e7ee;
border-radius: 8px;
}
table {
width: 100%;
min-width: 840px;
border-collapse: collapse;
table-layout: fixed;
}
th,
td {
height: 44px;
border-bottom: 1px solid #e8edf3;
padding: 0 10px;
text-align: left;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
}
th {
background: #f8fafc;
color: #4b5b6f;
font-size: 12px;
font-weight: 700;
}
tr:last-child td {
border-bottom: none;
}
.name-cell {
font-weight: 700;
}
.path-cell {
max-width: 280px;
color: #4b5b6f;
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
}
.row-actions {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.row-actions button {
min-height: 30px;
padding: 0 8px;
font-size: 12px;
}
.badge {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 0 8px;
border-radius: 999px;
background: #eef2f7;
color: #4b5b6f;
font-size: 12px;
line-height: 22px;
}
.badge.downloaded,
.badge.installed,
.badge.local {
background: #e8f7ee;
color: #166534;
}
.badge.check_failed,
.badge.error {
background: #fff1f1;
color: #9f1c1c;
}
.empty {
height: 72px;
text-align: center;
color: #657386;
}
@media (max-width: 860px) {
.app-shell {
grid-template-columns: 1fr;
}
.sidebar {
border-right: none;
border-bottom: 1px solid #d7dde5;
}
.tabs {
grid-template-columns: repeat(3, 1fr);
}
.form-grid {
grid-template-columns: 1fr;
}
}

View File

@@ -1,28 +1,479 @@
import {useState} from 'react'; import {useEffect, useMemo, useState} from 'react';
import logo from './assets/images/logo-universal.png'; import {
Code2,
Download,
FolderOpen,
Link2,
PlugZap,
RefreshCw,
Save,
Settings,
Trash2,
Unlink,
} from 'lucide-react';
import './App.css'; import './App.css';
import {Greet} from "../wailsjs/go/main/App"; import {api} from './api';
import {Config, RemoteSkill, SkillState, defaultConfig, saveConfigRequest} from './types';
import {domain} from '../wailsjs/go/models';
type Tab = 'config' | 'remote' | 'local';
function App() { function App() {
const [resultText, setResultText] = useState("Please enter your name below 👇"); const [activeTab, setActiveTab] = useState<Tab>('config');
const [name, setName] = useState(''); const [config, setConfig] = useState<Config>(defaultConfig());
const updateName = (e: any) => setName(e.target.value); const [password, setPassword] = useState('');
const updateResultText = (result: string) => setResultText(result); const [token, setToken] = useState('');
const [remoteSkills, setRemoteSkills] = useState<RemoteSkill[]>([]);
const [localSkills, setLocalSkills] = useState<SkillState[]>([]);
const [search, setSearch] = useState('');
const [message, setMessage] = useState('');
const [error, setError] = useState('');
const [busy, setBusy] = useState('');
function greet() { useEffect(() => {
Greet(name).then(updateResultText); void loadInitialData();
}, []);
const filteredRemote = useMemo(() => {
const needle = search.trim().toLowerCase();
if (!needle) {
return remoteSkills;
} }
return remoteSkills.filter((skill) => {
return [skill.name, skill.fullName, skill.description]
.filter(Boolean)
.some((value) => value.toLowerCase().includes(needle));
});
}, [remoteSkills, search]);
return ( async function loadInitialData() {
<div id="App"> await run('load', async () => {
<img src={logo} id="logo" alt="logo"/> const loaded = await api.loadConfig();
<div id="result" className="result">{resultText}</div> setConfig(domain.Config.createFrom(loaded));
<div id="input" className="input-box"> await refreshLocal();
<input id="name" className="input" onChange={updateName} autoComplete="off" name="input" type="text"/> });
<button className="btn" onClick={greet}>Greet</button> }
</div>
async function refreshRemote() {
await run('remote:refresh', async () => {
setRemoteSkills(await api.listRemoteSkills());
setMessage('Remote list refreshed');
});
}
async function refreshLocal() {
setLocalSkills(await api.listLocalSkills());
}
async function saveConfig() {
await run('config:save', async () => {
await api.saveConfig(saveConfigRequest(config, password, token));
setPassword('');
setToken('');
setMessage('Config saved');
});
}
async function testConnection() {
await run('config:test', async () => {
const result = await api.testConnection(saveConfigRequest(config, password, token));
setMessage(result.ok ? `Connected as ${result.username}` : result.message);
});
}
async function downloadOrUpdate(skill: RemoteSkill) {
await run(`remote:${skill.name}`, async () => {
if (skill.isDownloaded) {
await api.updateSkill(config.gitea.org, skill.name);
setMessage(`${skill.name} updated`);
} else {
await api.downloadSkill(skill);
setMessage(`${skill.name} downloaded`);
}
await refreshLocal();
await refreshRemote();
});
}
async function updateLocal(skill: SkillState) {
await run(`local:update:${skill.repo}`, async () => {
await api.updateSkill(skill.org, skill.repo);
await refreshLocal();
setMessage(`${skill.repo} updated`);
});
}
async function install(skill: SkillState, targetID: 'codex' | 'claude') {
await run(`install:${targetID}:${skill.repo}`, async () => {
await api.installSkill(skill.org, skill.repo, targetID);
await refreshLocal();
setMessage(`${skill.repo} installed to ${targetLabel(targetID)}`);
});
}
async function uninstall(skill: SkillState, targetID: 'codex' | 'claude') {
await run(`uninstall:${targetID}:${skill.repo}`, async () => {
await api.uninstallSkill(skill.org, skill.repo, targetID);
await refreshLocal();
setMessage(`${skill.repo} uninstalled from ${targetLabel(targetID)}`);
});
}
async function deleteSkill(skill: SkillState) {
if (!window.confirm(`Delete local skill ${skill.repo}?`)) {
return;
}
await run(`delete:${skill.repo}`, async () => {
await api.deleteSkill(skill.org, skill.repo);
await refreshLocal();
setMessage(`${skill.repo} deleted`);
});
}
async function openFolder(skill: SkillState) {
await run(`folder:${skill.repo}`, async () => {
await api.openFolder(skill.org, skill.repo);
});
}
async function openRemoteFolder(skill: RemoteSkill) {
await run(`remote:folder:${skill.name}`, async () => {
await api.openFolder(config.gitea.org, skill.name);
});
}
async function openCode(skill: SkillState) {
await run(`code:${skill.repo}`, async () => {
await api.openInVSCode(skill.org, skill.repo);
});
}
async function run(key: string, action: () => Promise<void>) {
setBusy(key);
setError('');
setMessage('');
try {
await action();
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setBusy('');
}
}
function updateConfig(mutator: (current: Config) => unknown) {
const next = domain.Config.createFrom(config);
mutator(next);
setConfig(next);
}
return (
<main className="app-shell">
<aside className="sidebar">
<div className="brand">
<div className="brand-mark">S</div>
<div>
<h1>Skill Manager</h1>
<span>Gitea / Codex / Claude</span>
</div>
</div> </div>
) <nav className="tabs" aria-label="Primary">
<button className={activeTab === 'config' ? 'active' : ''} onClick={() => setActiveTab('config')}>
<Settings size={17} /> Config
</button>
<button className={activeTab === 'remote' ? 'active' : ''} onClick={() => setActiveTab('remote')}>
<Download size={17} /> Remote
</button>
<button className={activeTab === 'local' ? 'active' : ''} onClick={() => setActiveTab('local')}>
<FolderOpen size={17} /> Local
</button>
</nav>
</aside>
<section className="workspace">
<header className="topbar">
<div>
<h2>{pageTitle(activeTab)}</h2>
<span>{statusSummary(remoteSkills, localSkills)}</span>
</div>
<button className="ghost" onClick={() => run('auto:update', async () => { await api.runAutoUpdate(); await refreshLocal(); })} disabled={busy !== ''}>
<RefreshCw size={16} /> Check Updates
</button>
</header>
{(message || error) && (
<div className={error ? 'notice error' : 'notice'}>
{error || message}
</div>
)}
{activeTab === 'config' && (
<section className="panel">
<div className="form-grid">
<label>
<span>Gitea Base URL</span>
<input
value={config.gitea.baseURL || ''}
placeholder="https://gitea.example.com"
onChange={(event) => updateConfig((next) => { next.gitea.baseURL = event.target.value; })}
/>
</label>
<label>
<span>Organization</span>
<input
value={config.gitea.org || ''}
onChange={(event) => updateConfig((next) => { next.gitea.org = event.target.value; })}
/>
</label>
<div className="field">
<span>Auth Type</span>
<div className="segmented">
<button
className={config.gitea.authType !== 'token' ? 'active' : ''}
onClick={() => updateConfig((next) => { next.gitea.authType = 'password'; })}
>
Password
</button>
<button
className={config.gitea.authType === 'token' ? 'active' : ''}
onClick={() => updateConfig((next) => { next.gitea.authType = 'token'; })}
>
Token
</button>
</div>
</div>
<label>
<span>Username</span>
<input
value={config.gitea.username || ''}
onChange={(event) => updateConfig((next) => { next.gitea.username = event.target.value; })}
/>
</label>
{config.gitea.authType === 'token' ? (
<label>
<span>Token</span>
<input type="password" value={token} onChange={(event) => setToken(event.target.value)} />
</label>
) : (
<label>
<span>Password</span>
<input type="password" value={password} onChange={(event) => setPassword(event.target.value)} />
</label>
)}
<label>
<span>Credential Key</span>
<input
value={config.gitea.credentialKey || ''}
onChange={(event) => updateConfig((next) => { next.gitea.credentialKey = event.target.value; })}
/>
</label>
<label className="checkbox">
<input
type="checkbox"
checked={Boolean(config.update.autoUpdate)}
onChange={(event) => updateConfig((next) => { next.update.autoUpdate = event.target.checked; })}
/>
<span>Auto update</span>
</label>
<label className="checkbox">
<input
type="checkbox"
checked={Boolean(config.update.checkOnStartup)}
onChange={(event) => updateConfig((next) => { next.update.checkOnStartup = event.target.checked; })}
/>
<span>Check on startup</span>
</label>
<label>
<span>Interval Minutes</span>
<input
type="number"
min="1"
value={config.update.intervalMinutes || 60}
onChange={(event) => updateConfig((next) => { next.update.intervalMinutes = Number(event.target.value); })}
/>
</label>
</div>
<div className="toolbar">
<button onClick={saveConfig} disabled={busy !== ''}>
<Save size={16} /> Save
</button>
<button className="secondary" onClick={testConnection} disabled={busy !== ''}>
<PlugZap size={16} /> Test
</button>
</div>
</section>
)}
{activeTab === 'remote' && (
<section className="panel">
<div className="toolbar split">
<input
className="search"
value={search}
placeholder="Search repositories"
onChange={(event) => setSearch(event.target.value)}
/>
<button onClick={refreshRemote} disabled={busy !== ''}>
<RefreshCw size={16} /> Refresh
</button>
</div>
<div className="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Branch</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{filteredRemote.map((skill) => (
<tr key={skill.fullName || skill.name}>
<td className="name-cell">{skill.name}</td>
<td>{skill.description || '-'}</td>
<td>{skill.defaultBranch || 'main'}</td>
<td>{badge(skill.status || (skill.isDownloaded ? 'downloaded' : 'remote'))}</td>
<td>
<div className="row-actions">
<button onClick={() => downloadOrUpdate(skill)} disabled={busy !== ''} title={skill.isDownloaded ? 'Update' : 'Download'}>
{skill.isDownloaded ? <RefreshCw size={15} /> : <Download size={15} />}
{skill.isDownloaded ? 'Update' : 'Download'}
</button>
{skill.isDownloaded && (
<button className="icon-only" onClick={() => openRemoteFolder(skill)} title="Open folder">
<FolderOpen size={15} />
</button>
)}
</div>
</td>
</tr>
))}
{filteredRemote.length === 0 && (
<tr>
<td colSpan={5} className="empty">No remote skills</td>
</tr>
)}
</tbody>
</table>
</div>
</section>
)}
{activeTab === 'local' && (
<section className="panel">
<div className="toolbar">
<button onClick={refreshLocal} disabled={busy !== ''}>
<RefreshCw size={16} /> Refresh
</button>
</div>
<div className="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>Path</th>
<th>Commit</th>
<th>Status</th>
<th>Codex</th>
<th>Claude</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{localSkills.map((skill) => (
<tr key={`${skill.org}/${skill.repo}`}>
<td className="name-cell">{skill.repo}</td>
<td className="path-cell" title={skill.localPath}>{skill.localPath}</td>
<td>{shortCommit(skill.currentCommit)}</td>
<td>{skill.lastError ? <span className="badge error">{skill.lastError}</span> : <span className="badge local">ready</span>}</td>
<td>{targetBadge(skill, 'codex')}</td>
<td>{targetBadge(skill, 'claude')}</td>
<td>
<div className="row-actions">
<button onClick={() => updateLocal(skill)} disabled={busy !== ''} title="Update">
<RefreshCw size={15} /> Update
</button>
{targetInstalled(skill, 'codex') ? (
<button className="secondary" onClick={() => uninstall(skill, 'codex')} disabled={busy !== ''} title="Uninstall Codex">
<Unlink size={15} /> Codex
</button>
) : (
<button className="secondary" onClick={() => install(skill, 'codex')} disabled={busy !== ''} title="Install Codex">
<Link2 size={15} /> Codex
</button>
)}
{targetInstalled(skill, 'claude') ? (
<button className="secondary" onClick={() => uninstall(skill, 'claude')} disabled={busy !== ''} title="Uninstall Claude">
<Unlink size={15} /> Claude
</button>
) : (
<button className="secondary" onClick={() => install(skill, 'claude')} disabled={busy !== ''} title="Install Claude">
<Link2 size={15} /> Claude
</button>
)}
<button className="icon-only" onClick={() => openCode(skill)} disabled={busy !== ''} title="Open VS Code">
<Code2 size={15} />
</button>
<button className="icon-only" onClick={() => openFolder(skill)} disabled={busy !== ''} title="Open folder">
<FolderOpen size={15} />
</button>
<button className="danger icon-only" onClick={() => deleteSkill(skill)} disabled={busy !== ''} title="Delete local">
<Trash2 size={15} />
</button>
</div>
</td>
</tr>
))}
{localSkills.length === 0 && (
<tr>
<td colSpan={7} className="empty">No local skills</td>
</tr>
)}
</tbody>
</table>
</div>
</section>
)}
</section>
</main>
);
} }
export default App function pageTitle(tab: Tab) {
if (tab === 'remote') {
return 'Remote Market';
}
if (tab === 'local') {
return 'Local Skills';
}
return 'Configuration';
}
function statusSummary(remote: RemoteSkill[], local: SkillState[]) {
return `${remote.length} remote / ${local.length} local`;
}
function badge(status: string) {
const label = status.replace(/_/g, ' ') || 'remote';
return <span className={`badge ${status}`}>{label}</span>;
}
function targetBadge(skill: SkillState, targetID: 'codex' | 'claude') {
return targetInstalled(skill, targetID) ? <span className="badge installed">installed</span> : <span className="badge">not installed</span>;
}
function targetInstalled(skill: SkillState, targetID: 'codex' | 'claude') {
return Boolean(skill.installedTargets?.[targetID]);
}
function targetLabel(targetID: 'codex' | 'claude') {
return targetID === 'codex' ? 'Codex' : 'Claude';
}
function shortCommit(commit: string) {
return commit ? commit.slice(0, 8) : '-';
}
export default App;

32
frontend/src/api.ts Normal file
View File

@@ -0,0 +1,32 @@
import {
DeleteSkill,
DownloadSkill,
InstallSkill,
ListLocalSkills,
ListRemoteSkills,
LoadConfig,
OpenFolder,
OpenInVSCode,
RunAutoUpdate,
SaveConfig,
TestConnection,
UninstallSkill,
UpdateSkill,
} from '../wailsjs/go/main/App';
import type {RemoteSkill, SaveConfigRequest} from './types';
export const api = {
loadConfig: () => LoadConfig(),
saveConfig: (request: SaveConfigRequest) => SaveConfig(request),
testConnection: (request: SaveConfigRequest) => TestConnection(request),
listRemoteSkills: () => ListRemoteSkills(),
listLocalSkills: () => ListLocalSkills(),
downloadSkill: (skill: RemoteSkill) => DownloadSkill(skill),
updateSkill: (org: string, repo: string) => UpdateSkill(org, repo),
installSkill: (org: string, repo: string, targetID: string) => InstallSkill(org, repo, targetID),
uninstallSkill: (org: string, repo: string, targetID: string) => UninstallSkill(org, repo, targetID),
deleteSkill: (org: string, repo: string) => DeleteSkill(org, repo),
openInVSCode: (org: string, repo: string) => OpenInVSCode(org, repo),
openFolder: (org: string, repo: string) => OpenFolder(org, repo),
runAutoUpdate: () => RunAutoUpdate(),
};

View File

@@ -1,26 +1,23 @@
html { html,
background-color: rgba(27, 38, 54, 1); body,
text-align: center; #root {
color: white; min-height: 100vh;
} }
body { body {
margin: 0; margin: 0;
color: white; font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", background: #f5f7fa;
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", color: #17202a;
sans-serif;
} }
@font-face { @font-face {
font-family: "Nunito"; font-family: "Nunito";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: local(""), src: local(""), url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2");
url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2");
} }
#app { * {
height: 100vh; letter-spacing: 0;
text-align: center;
} }

28
frontend/src/types.ts Normal file
View File

@@ -0,0 +1,28 @@
import {domain} from '../wailsjs/go/models';
export type Config = domain.Config;
export type RemoteSkill = domain.RemoteSkill;
export type SkillState = domain.SkillState;
export type SaveConfigRequest = domain.SaveConfigRequest;
export type TestConnectionResult = domain.TestConnectionResult;
export function defaultConfig(): Config {
return domain.Config.createFrom({
gitea: {
baseURL: '',
org: '',
authType: 'password',
username: '',
credentialKey: 'sgg-ai-skill-manager:gitea',
},
update: {
autoUpdate: true,
checkOnStartup: true,
intervalMinutes: 60,
},
});
}
export function saveConfigRequest(config: Config, password: string, token: string): SaveConfigRequest {
return domain.SaveConfigRequest.createFrom({config, password, token});
}

View File

@@ -1,4 +1,29 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
import {domain} from '../models';
export function Greet(arg1: string): Promise<string>; export function DeleteSkill(arg1:string,arg2:string):Promise<void>;
export function DownloadSkill(arg1:domain.RemoteSkill):Promise<domain.SkillState>;
export function InstallSkill(arg1:string,arg2:string,arg3:string):Promise<domain.SkillState>;
export function ListLocalSkills():Promise<Array<domain.SkillState>>;
export function ListRemoteSkills():Promise<Array<domain.RemoteSkill>>;
export function LoadConfig():Promise<domain.Config>;
export function OpenFolder(arg1:string,arg2:string):Promise<void>;
export function OpenInVSCode(arg1:string,arg2:string):Promise<void>;
export function RunAutoUpdate():Promise<void>;
export function SaveConfig(arg1:domain.SaveConfigRequest):Promise<void>;
export function TestConnection(arg1:domain.SaveConfigRequest):Promise<domain.TestConnectionResult>;
export function UninstallSkill(arg1:string,arg2:string,arg3:string):Promise<domain.SkillState>;
export function UpdateSkill(arg1:string,arg2:string):Promise<domain.SkillState>;

View File

@@ -2,6 +2,54 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
export function Greet(arg1) { export function DeleteSkill(arg1, arg2) {
return window['go']['main']['App']['Greet'](arg1); return window['go']['main']['App']['DeleteSkill'](arg1, arg2);
}
export function DownloadSkill(arg1) {
return window['go']['main']['App']['DownloadSkill'](arg1);
}
export function InstallSkill(arg1, arg2, arg3) {
return window['go']['main']['App']['InstallSkill'](arg1, arg2, arg3);
}
export function ListLocalSkills() {
return window['go']['main']['App']['ListLocalSkills']();
}
export function ListRemoteSkills() {
return window['go']['main']['App']['ListRemoteSkills']();
}
export function LoadConfig() {
return window['go']['main']['App']['LoadConfig']();
}
export function OpenFolder(arg1, arg2) {
return window['go']['main']['App']['OpenFolder'](arg1, arg2);
}
export function OpenInVSCode(arg1, arg2) {
return window['go']['main']['App']['OpenInVSCode'](arg1, arg2);
}
export function RunAutoUpdate() {
return window['go']['main']['App']['RunAutoUpdate']();
}
export function SaveConfig(arg1) {
return window['go']['main']['App']['SaveConfig'](arg1);
}
export function TestConnection(arg1) {
return window['go']['main']['App']['TestConnection'](arg1);
}
export function UninstallSkill(arg1, arg2, arg3) {
return window['go']['main']['App']['UninstallSkill'](arg1, arg2, arg3);
}
export function UpdateSkill(arg1, arg2) {
return window['go']['main']['App']['UpdateSkill'](arg1, arg2);
} }

View File

@@ -0,0 +1,218 @@
export namespace domain {
export class UpdateConfig {
autoUpdate: boolean;
checkOnStartup: boolean;
intervalMinutes: number;
static createFrom(source: any = {}) {
return new UpdateConfig(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.autoUpdate = source["autoUpdate"];
this.checkOnStartup = source["checkOnStartup"];
this.intervalMinutes = source["intervalMinutes"];
}
}
export class GiteaConfig {
baseURL: string;
org: string;
authType: string;
username: string;
credentialKey: string;
static createFrom(source: any = {}) {
return new GiteaConfig(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.baseURL = source["baseURL"];
this.org = source["org"];
this.authType = source["authType"];
this.username = source["username"];
this.credentialKey = source["credentialKey"];
}
}
export class Config {
gitea: GiteaConfig;
update: UpdateConfig;
static createFrom(source: any = {}) {
return new Config(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.gitea = this.convertValues(source["gitea"], GiteaConfig);
this.update = this.convertValues(source["update"], UpdateConfig);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class InstalledTarget {
path: string;
linkType: string;
targetPath: string;
static createFrom(source: any = {}) {
return new InstalledTarget(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.path = source["path"];
this.linkType = source["linkType"];
this.targetPath = source["targetPath"];
}
}
export class RemoteSkill {
name: string;
fullName: string;
description: string;
cloneURL: string;
sshURL: string;
defaultBranch: string;
updatedAt: string;
isDownloaded: boolean;
status: string;
error: string;
static createFrom(source: any = {}) {
return new RemoteSkill(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.fullName = source["fullName"];
this.description = source["description"];
this.cloneURL = source["cloneURL"];
this.sshURL = source["sshURL"];
this.defaultBranch = source["defaultBranch"];
this.updatedAt = source["updatedAt"];
this.isDownloaded = source["isDownloaded"];
this.status = source["status"];
this.error = source["error"];
}
}
export class SaveConfigRequest {
config: Config;
password: string;
token: string;
static createFrom(source: any = {}) {
return new SaveConfigRequest(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.config = this.convertValues(source["config"], Config);
this.password = source["password"];
this.token = source["token"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class SkillState {
org: string;
repo: string;
localPath: string;
remoteURL: string;
defaultBranch: string;
currentCommit: string;
lastCheckedAt: string;
lastError: string;
installedTargets: Record<string, InstalledTarget>;
static createFrom(source: any = {}) {
return new SkillState(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.org = source["org"];
this.repo = source["repo"];
this.localPath = source["localPath"];
this.remoteURL = source["remoteURL"];
this.defaultBranch = source["defaultBranch"];
this.currentCommit = source["currentCommit"];
this.lastCheckedAt = source["lastCheckedAt"];
this.lastError = source["lastError"];
this.installedTargets = this.convertValues(source["installedTargets"], InstalledTarget, true);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class TestConnectionResult {
ok: boolean;
message: string;
username: string;
org: string;
static createFrom(source: any = {}) {
return new TestConnectionResult(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.ok = source["ok"];
this.message = source["message"];
this.username = source["username"];
this.org = source["org"];
}
}
}

View File

@@ -21,8 +21,8 @@ export interface Size {
export interface Screen { export interface Screen {
isCurrent: boolean; isCurrent: boolean;
isPrimary: boolean; isPrimary: boolean;
width: number width : number
height: number height : number
} }
// Environment information such as platform, buildtype, ... // Environment information such as platform, buildtype, ...
@@ -38,22 +38,22 @@ export interface EnvironmentInfo {
export function EventsEmit(eventName: string, ...data: any): void; export function EventsEmit(eventName: string, ...data: any): void;
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. // [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
export function EventsOn(eventName: string, callback: (...data: any) => void): void; export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) // [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
// sets up a listener for the given event name, but will only trigger a given number times. // sets up a listener for the given event name, but will only trigger a given number times.
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): void; export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) // [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
// sets up a listener for the given event name, but will only trigger once. // sets up a listener for the given event name, but will only trigger once.
export function EventsOnce(eventName: string, callback: (...data: any) => void): void; export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsff) // [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
// unregisters the listener for the given event name. // unregisters the listener for the given event name.
export function EventsOff(eventName: string): void; export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) // [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
// unregisters all event listeners. // unregisters all listeners.
export function EventsOffAll(): void; export function EventsOffAll(): void;
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
@@ -128,6 +128,10 @@ export function WindowFullscreen(): void;
// Restores the previous window dimensions and position prior to full screen. // Restores the previous window dimensions and position prior to full screen.
export function WindowUnfullscreen(): void; export function WindowUnfullscreen(): void;
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
export function WindowIsFullscreen(): Promise<boolean>;
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window. // Sets the width and height of the window.
export function WindowSetSize(width: number, height: number): void; export function WindowSetSize(width: number, height: number): void;
@@ -174,6 +178,10 @@ export function WindowToggleMaximise(): void;
// Restores the window to the dimensions and position prior to maximising. // Restores the window to the dimensions and position prior to maximising.
export function WindowUnmaximise(): void; export function WindowUnmaximise(): void;
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
// Returns the state of the window, i.e. whether the window is maximised or not.
export function WindowIsMaximised(): Promise<boolean>;
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) // [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
// Minimises the window. // Minimises the window.
export function WindowMinimise(): void; export function WindowMinimise(): void;
@@ -182,6 +190,14 @@ export function WindowMinimise(): void;
// Restores the window to the dimensions and position prior to minimising. // Restores the window to the dimensions and position prior to minimising.
export function WindowUnminimise(): void; export function WindowUnminimise(): void;
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
// Returns the state of the window, i.e. whether the window is minimised or not.
export function WindowIsMinimised(): Promise<boolean>;
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
// Returns the state of the window, i.e. whether the window is normal or not.
export function WindowIsNormal(): Promise<boolean>;
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) // [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. // Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
@@ -209,3 +225,106 @@ export function Hide(): void;
// [Show](https://wails.io/docs/reference/runtime/intro#show) // [Show](https://wails.io/docs/reference/runtime/intro#show)
// Shows the application. // Shows the application.
export function Show(): void; export function Show(): void;
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
// Returns the current text stored on clipboard
export function ClipboardGetText(): Promise<string>;
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
// Sets a text on the clipboard
export function ClipboardSetText(text: string): Promise<boolean>;
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
// OnFileDropOff removes the drag and drop listeners and handlers.
export function OnFileDropOff() :void
// Check if the file path resolver is available
export function CanResolveFilePaths(): boolean;
// Resolves file paths for an array of files
export function ResolveFilePaths(files: File[]): void
// Notification types
export interface NotificationOptions {
id: string;
title: string;
subtitle?: string; // macOS and Linux only
body?: string;
categoryId?: string;
data?: { [key: string]: any };
}
export interface NotificationAction {
id?: string;
title?: string;
destructive?: boolean; // macOS-specific
}
export interface NotificationCategory {
id?: string;
actions?: NotificationAction[];
hasReplyField?: boolean;
replyPlaceholder?: string;
replyButtonTitle?: string;
}
// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
// Initializes the notification service for the application.
// This must be called before sending any notifications.
export function InitializeNotifications(): Promise<void>;
// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
// Cleans up notification resources and releases any held connections.
export function CleanupNotifications(): Promise<void>;
// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
// Checks if notifications are available on the current platform.
export function IsNotificationAvailable(): Promise<boolean>;
// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
// Requests notification authorization from the user (macOS only).
export function RequestNotificationAuthorization(): Promise<boolean>;
// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
// Checks the current notification authorization status (macOS only).
export function CheckNotificationAuthorization(): Promise<boolean>;
// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
// Sends a basic notification with the given options.
export function SendNotification(options: NotificationOptions): Promise<void>;
// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
// Sends a notification with action buttons. Requires a registered category.
export function SendNotificationWithActions(options: NotificationOptions): Promise<void>;
// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
// Registers a notification category that can be used with SendNotificationWithActions.
export function RegisterNotificationCategory(category: NotificationCategory): Promise<void>;
// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
// Removes a previously registered notification category.
export function RemoveNotificationCategory(categoryId: string): Promise<void>;
// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
// Removes all pending notifications from the notification center.
export function RemoveAllPendingNotifications(): Promise<void>;
// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
// Removes a specific pending notification by its identifier.
export function RemovePendingNotification(identifier: string): Promise<void>;
// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
// Removes all delivered notifications from the notification center.
export function RemoveAllDeliveredNotifications(): Promise<void>;
// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
// Removes a specific delivered notification by its identifier.
export function RemoveDeliveredNotification(identifier: string): Promise<void>;
// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
// Removes a notification by its identifier (cross-platform convenience function).
export function RemoveNotification(identifier: string): Promise<void>;

View File

@@ -37,23 +37,23 @@ export function LogFatal(message) {
} }
export function EventsOnMultiple(eventName, callback, maxCallbacks) { export function EventsOnMultiple(eventName, callback, maxCallbacks) {
window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
} }
export function EventsOn(eventName, callback) { export function EventsOn(eventName, callback) {
EventsOnMultiple(eventName, callback, -1); return EventsOnMultiple(eventName, callback, -1);
} }
export function EventsOff(eventName) { export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName); return window.runtime.EventsOff(eventName, ...additionalEventNames);
} }
export function EventsOffAll() { export function EventsOffAll() {
return window.runtime.EventsOffAll(); return window.runtime.EventsOffAll();
} }
export function EventsOnce(eventName, callback) { export function EventsOnce(eventName, callback) {
EventsOnMultiple(eventName, callback, 1); return EventsOnMultiple(eventName, callback, 1);
} }
export function EventsEmit(eventName) { export function EventsEmit(eventName) {
@@ -101,6 +101,10 @@ export function WindowUnfullscreen() {
window.runtime.WindowUnfullscreen(); window.runtime.WindowUnfullscreen();
} }
export function WindowIsFullscreen() {
return window.runtime.WindowIsFullscreen();
}
export function WindowGetSize() { export function WindowGetSize() {
return window.runtime.WindowGetSize(); return window.runtime.WindowGetSize();
} }
@@ -145,6 +149,10 @@ export function WindowUnmaximise() {
window.runtime.WindowUnmaximise(); window.runtime.WindowUnmaximise();
} }
export function WindowIsMaximised() {
return window.runtime.WindowIsMaximised();
}
export function WindowMinimise() { export function WindowMinimise() {
window.runtime.WindowMinimise(); window.runtime.WindowMinimise();
} }
@@ -161,6 +169,14 @@ export function ScreenGetAll() {
return window.runtime.ScreenGetAll(); return window.runtime.ScreenGetAll();
} }
export function WindowIsMinimised() {
return window.runtime.WindowIsMinimised();
}
export function WindowIsNormal() {
return window.runtime.WindowIsNormal();
}
export function BrowserOpenURL(url) { export function BrowserOpenURL(url) {
window.runtime.BrowserOpenURL(url); window.runtime.BrowserOpenURL(url);
} }
@@ -180,3 +196,103 @@ export function Hide() {
export function Show() { export function Show() {
window.runtime.Show(); window.runtime.Show();
} }
export function ClipboardGetText() {
return window.runtime.ClipboardGetText();
}
export function ClipboardSetText(text) {
return window.runtime.ClipboardSetText(text);
}
/**
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
*
* @export
* @callback OnFileDropCallback
* @param {number} x - x coordinate of the drop
* @param {number} y - y coordinate of the drop
* @param {string[]} paths - A list of file paths.
*/
/**
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
*
* @export
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
*/
export function OnFileDrop(callback, useDropTarget) {
return window.runtime.OnFileDrop(callback, useDropTarget);
}
/**
* OnFileDropOff removes the drag and drop listeners and handlers.
*/
export function OnFileDropOff() {
return window.runtime.OnFileDropOff();
}
export function CanResolveFilePaths() {
return window.runtime.CanResolveFilePaths();
}
export function ResolveFilePaths(files) {
return window.runtime.ResolveFilePaths(files);
}
export function InitializeNotifications() {
return window.runtime.InitializeNotifications();
}
export function CleanupNotifications() {
return window.runtime.CleanupNotifications();
}
export function IsNotificationAvailable() {
return window.runtime.IsNotificationAvailable();
}
export function RequestNotificationAuthorization() {
return window.runtime.RequestNotificationAuthorization();
}
export function CheckNotificationAuthorization() {
return window.runtime.CheckNotificationAuthorization();
}
export function SendNotification(options) {
return window.runtime.SendNotification(options);
}
export function SendNotificationWithActions(options) {
return window.runtime.SendNotificationWithActions(options);
}
export function RegisterNotificationCategory(category) {
return window.runtime.RegisterNotificationCategory(category);
}
export function RemoveNotificationCategory(categoryId) {
return window.runtime.RemoveNotificationCategory(categoryId);
}
export function RemoveAllPendingNotifications() {
return window.runtime.RemoveAllPendingNotifications();
}
export function RemovePendingNotification(identifier) {
return window.runtime.RemovePendingNotification(identifier);
}
export function RemoveAllDeliveredNotifications() {
return window.runtime.RemoveAllDeliveredNotifications();
}
export function RemoveDeliveredNotification(identifier) {
return window.runtime.RemoveDeliveredNotification(identifier);
}
export function RemoveNotification(identifier) {
return window.runtime.RemoveNotification(identifier);
}