git0 0.3.66 → 0.3.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -1
- package/demo/git0-button.js +237 -0
- package/demo/index.html +71 -0
- package/dist/cli.js +1015 -4
- package/dist/cli.js.map +11 -5
- package/docs/content/docs/meta.json +1 -0
- package/docs/content/docs/service.mdx +51 -0
- package/package.json +1 -1
- package/src/args.ts +10 -0
- package/src/cli.ts +9 -0
- package/src/service/assets.ts +260 -0
- package/src/service/client.ts +61 -0
- package/src/service/index.ts +77 -0
- package/src/service/installer.ts +246 -0
- package/src/service/server.ts +540 -0
- package/src/service/system-service.ts +190 -0
- package/test/args.test.ts +11 -0
- package/test/service-assets.test.ts +181 -0
- package/test/service-installer.test.ts +201 -0
- package/test/service-server.test.ts +262 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<!-- template-git-repo:badges:start -->
|
|
6
6
|
<p align="center">
|
|
7
7
|
<a href="https://git0.js.org"><img height="20px" src="https://img.shields.io/badge/App-blueviolet?style=for-the-badge&logo=googlechrome&logoColor=white" alt="Website" /></a>
|
|
8
|
-
<a href="https://
|
|
8
|
+
<a href="https://1computer.online/docs/packages/git0-repo-downloader"><img src="https://img.shields.io/badge/Docs-blue?logo=ReadTheDocs&logoColor=white" alt="Documentation" /></a>
|
|
9
9
|
<br />
|
|
10
10
|
<a href="https://github.com/OpenSourceAGI/dev-tools-starter-agent/stargazers"><img src="https://img.shields.io/github/stars/OpenSourceAGI/dev-tools-starter-agent" alt="GitHub Stars" /></a>
|
|
11
11
|
<a href="https://www.npmjs.com/package/git0"><img src="https://img.shields.io/npm/dm/git0.svg" alt="NPM Monthly Downloads" /></a>
|
|
@@ -61,6 +61,7 @@ bun install -g git0
|
|
|
61
61
|
- **Partial paths** - paste a `/tree/` or `/blob/` link and download just that folder or file
|
|
62
62
|
- **Optional `.git` history** - `--history` attaches the full history *after* the source is already on disk
|
|
63
63
|
- **Faster than git** - skips `.git` history and uncompresses while downloading
|
|
64
|
+
- **One-click install service** - `--service=install` lets website download buttons install the right release for your system and show progress in the button
|
|
64
65
|
|
|
65
66
|
## 🎯 Usage
|
|
66
67
|
|
|
@@ -218,6 +219,53 @@ git0 automatically detects and opens projects in your preferred IDE:
|
|
|
218
219
|
- **Neovim**
|
|
219
220
|
- **Webstorm**
|
|
220
221
|
|
|
222
|
+
## ⚡ Background Install Service
|
|
223
|
+
|
|
224
|
+
git0 can run as a small background service that turns any website's download button into a one-click installer. The button hands the service a repository; the service picks the release asset built for your OS and CPU, downloads it, installs it, and opens the app, while the button fills up as a progress bar.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
git0 --service=install # start at login and register git0:// links
|
|
228
|
+
git0 --service=status # is it running, and what is it installing
|
|
229
|
+
git0 --service=uninstall # remove it
|
|
230
|
+
git0 --service # or run it in the foreground, on 127.0.0.1:47100
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
It installs as a **per-user** service, because launching a desktop app has to happen inside your session: a `systemd --user` unit on Linux, a LaunchAgent on macOS, and a logon task on Windows. No admin rights are needed to install it.
|
|
234
|
+
|
|
235
|
+
### Add the button to a site
|
|
236
|
+
|
|
237
|
+
```html
|
|
238
|
+
<a href="https://github.com/owner/repo/releases/latest" data-git0-repo="owner/repo">Download</a>
|
|
239
|
+
<script src="https://unpkg.com/git0/demo/git0-button.js"></script>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
When the service is running, clicking the link does not download a file. The button morphs into a progress bar showing the download, then the install, then the launch. When the service is not running, the link behaves as a normal link. `demo/index.html` has a live and a simulated example.
|
|
243
|
+
|
|
244
|
+
Links also work without the script: `git0://install/owner/repo` is registered with the OS on Linux and Windows, and `http://127.0.0.1:47100/install?repo=owner/repo` works everywhere.
|
|
245
|
+
|
|
246
|
+
### What it installs
|
|
247
|
+
|
|
248
|
+
| OS | Preferred asset, in order | Result |
|
|
249
|
+
| --- | --- | --- |
|
|
250
|
+
| Linux | `.AppImage`, `.deb`, `.rpm`, `.tar.gz`, bare binary | AppImage and binaries go to `~/.git0/apps`; `.deb`/`.rpm` install through the desktop's password prompt (`pkexec`) |
|
|
251
|
+
| macOS | `.dmg`, `.pkg`, `.zip`/`.tar.gz` | The `.app` is copied to `~/Applications`; a `.pkg` opens Installer |
|
|
252
|
+
| Windows | setup `.exe`, `.msi`, `.zip` | The installer runs with its own UI; a portable `.zip` is extracted to `~/.git0/apps` |
|
|
253
|
+
|
|
254
|
+
Asset names are matched on whole words, so `darwin` is never mistaken for `win`. An exact CPU match beats an unlabelled build, and an unlabelled build beats Rosetta or 32-bit fallbacks. Checksums, signatures and source archives are never picked. A command-line tool is installed but not launched, because it would start with no terminal attached.
|
|
255
|
+
|
|
256
|
+
### Security
|
|
257
|
+
|
|
258
|
+
Any website can ask, but only you can say yes. The first install of a repository opens a git0 page from `127.0.0.1` asking you to allow it, and the website cannot answer that prompt for you: the page carries a one-time token, is served without CORS headers so other sites can't read it, and refuses to be framed. Tick "Always allow" to skip the prompt for that repository next time (stored in `~/.git0/service.json`). The service listens on loopback only, rejects requests whose `Host` isn't loopback (DNS rebinding), and caps pending prompts at five.
|
|
259
|
+
|
|
260
|
+
### HTTP API
|
|
261
|
+
|
|
262
|
+
| Route | Returns |
|
|
263
|
+
| --- | --- |
|
|
264
|
+
| `GET /health` | `{ ok, name: "git0", version, os, arch }` |
|
|
265
|
+
| `POST /install` `{ "repo": "owner/repo" }` | the job, plus `statusUrl` and `approveUrl` |
|
|
266
|
+
| `GET /status/:id` | `status` (`awaiting-approval`, `resolving`, `downloading`, `installing`, `launching`, `done`, `error`, `denied`), `progress` 0–1, `receivedBytes`, `totalBytes`, `asset`, `tag`, `installedPath`, `launched`, `error` |
|
|
267
|
+
| `GET /jobs` | every job |
|
|
268
|
+
|
|
221
269
|
## 🔧 Configuration
|
|
222
270
|
|
|
223
271
|
### What Happens After Download
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* git0 install button.
|
|
3
|
+
*
|
|
4
|
+
* Turns any link or button carrying `data-git0-repo="owner/repo"` into a
|
|
5
|
+
* one-click installer. When the git0 background service is running on the
|
|
6
|
+
* visitor's machine, a click does not download a file: the button asks the
|
|
7
|
+
* service to install the right release for that machine, then morphs into a
|
|
8
|
+
* progress bar that fills as the service downloads, installs and launches it.
|
|
9
|
+
* When the service is not running, the link behaves exactly as before — it
|
|
10
|
+
* still points at your normal download page.
|
|
11
|
+
*
|
|
12
|
+
* <a href="https://github.com/owner/repo/releases/latest"
|
|
13
|
+
* data-git0-repo="owner/repo" class="git0-button">Download</a>
|
|
14
|
+
* <script src="git0-button.js"></script>
|
|
15
|
+
*
|
|
16
|
+
* Options, as attributes on the element:
|
|
17
|
+
* data-git0-port="47100" the service port, if changed with --port
|
|
18
|
+
* data-git0-simulate run the animation without the service (demos)
|
|
19
|
+
*
|
|
20
|
+
* The first install of a repository opens a small git0 page from 127.0.0.1
|
|
21
|
+
* asking the visitor to allow it. That prompt is the service's, not the
|
|
22
|
+
* website's, and a website cannot answer it.
|
|
23
|
+
*/
|
|
24
|
+
(function () {
|
|
25
|
+
const DEFAULT_PORT = 47100;
|
|
26
|
+
const TERMINAL = ['done', 'error', 'denied'];
|
|
27
|
+
|
|
28
|
+
const css = `
|
|
29
|
+
.git0-button{--g0-accent:#6d28d9;--g0-track:rgba(109,40,217,.18);--g0-fg:#fff;
|
|
30
|
+
position:relative;display:inline-flex;align-items:center;justify-content:center;
|
|
31
|
+
min-width:220px;height:48px;padding:0 24px;border:0;border-radius:24px;
|
|
32
|
+
background:var(--g0-accent);color:var(--g0-fg);font:600 15px/1 system-ui,sans-serif;
|
|
33
|
+
text-decoration:none;cursor:pointer;overflow:hidden;isolation:isolate;
|
|
34
|
+
transition:min-width .45s cubic-bezier(.2,.8,.2,1),border-radius .45s,background-color .3s,box-shadow .3s}
|
|
35
|
+
.git0-button:hover{box-shadow:0 6px 20px rgba(109,40,217,.35)}
|
|
36
|
+
.git0-button:focus-visible{outline:3px solid var(--g0-track);outline-offset:3px}
|
|
37
|
+
.git0-button .g0-label{position:relative;z-index:2;white-space:nowrap;transition:opacity .2s}
|
|
38
|
+
.git0-button .g0-fill{position:absolute;inset:0;width:0;z-index:1;background:var(--g0-accent);
|
|
39
|
+
transition:width .35s ease}
|
|
40
|
+
.git0-button.g0-busy{min-width:320px;border-radius:12px;background:var(--g0-track);color:inherit;cursor:default;box-shadow:none}
|
|
41
|
+
.git0-button .g0-label-on{position:absolute;inset:0;z-index:3;display:none;align-items:center;justify-content:center;
|
|
42
|
+
color:#fff;white-space:nowrap;clip-path:inset(0 calc(100% - var(--g0-p,0%)) 0 0);transition:clip-path .35s ease}
|
|
43
|
+
.git0-button.g0-busy .g0-label{color:#3b0764}
|
|
44
|
+
.git0-button.g0-busy:not(.g0-indeterminate) .g0-label-on{display:flex}
|
|
45
|
+
.git0-button.g0-indeterminate .g0-fill{width:35%!important;animation:g0-slide 1.1s ease-in-out infinite}
|
|
46
|
+
.git0-button.g0-done{background:#15803d;min-width:260px;border-radius:24px}
|
|
47
|
+
.git0-button.g0-done .g0-fill{opacity:0}
|
|
48
|
+
.git0-button.g0-done .g0-label{color:#fff}
|
|
49
|
+
.git0-button.g0-error{background:#b91c1c;border-radius:24px}
|
|
50
|
+
.git0-button.g0-error .g0-fill{opacity:0}
|
|
51
|
+
.git0-button.g0-error .g0-label{color:#fff}
|
|
52
|
+
@keyframes g0-slide{0%{transform:translateX(-100%)}100%{transform:translateX(300%)}}
|
|
53
|
+
@media (prefers-reduced-motion:reduce){.git0-button,.git0-button .g0-fill{transition:none}
|
|
54
|
+
.git0-button.g0-indeterminate .g0-fill{animation:none;width:100%!important;opacity:.4}}`;
|
|
55
|
+
|
|
56
|
+
function injectCss() {
|
|
57
|
+
if (document.getElementById('git0-button-css')) return;
|
|
58
|
+
const style = document.createElement('style');
|
|
59
|
+
style.id = 'git0-button-css';
|
|
60
|
+
style.textContent = css;
|
|
61
|
+
document.head.appendChild(style);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const mb = n => (n / 1048576).toFixed(1);
|
|
65
|
+
|
|
66
|
+
function labelFor(job) {
|
|
67
|
+
switch (job.status) {
|
|
68
|
+
case 'awaiting-approval': return 'Allow the install in the git0 tab…';
|
|
69
|
+
case 'resolving': return 'Finding your build…';
|
|
70
|
+
case 'downloading': return job.totalBytes
|
|
71
|
+
? `Downloading ${Math.round((job.receivedBytes / job.totalBytes) * 100)}% · ${mb(job.receivedBytes)} / ${mb(job.totalBytes)} MB`
|
|
72
|
+
: 'Downloading…';
|
|
73
|
+
case 'installing': return 'Installing…';
|
|
74
|
+
case 'launching': return 'Launching…';
|
|
75
|
+
case 'done': return job.launched ? 'Installed · opened ✓' : 'Installed ✓';
|
|
76
|
+
case 'denied': return 'Install cancelled';
|
|
77
|
+
default: return `Failed — ${job.error || 'try again'}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Paints a job onto the button. */
|
|
82
|
+
function render(el, job) {
|
|
83
|
+
const fill = el.querySelector('.g0-fill');
|
|
84
|
+
const label = el.querySelector('.g0-label');
|
|
85
|
+
const busy = !TERMINAL.includes(job.status);
|
|
86
|
+
const indeterminate = job.status === 'awaiting-approval' || job.status === 'resolving' ||
|
|
87
|
+
(job.status === 'downloading' && !job.totalBytes);
|
|
88
|
+
|
|
89
|
+
el.classList.toggle('g0-busy', busy);
|
|
90
|
+
el.classList.toggle('g0-indeterminate', busy && indeterminate);
|
|
91
|
+
el.classList.toggle('g0-done', job.status === 'done');
|
|
92
|
+
el.classList.toggle('g0-error', job.status === 'error' || job.status === 'denied');
|
|
93
|
+
el.setAttribute('aria-busy', String(busy));
|
|
94
|
+
const pct = `${Math.round(job.progress * 100)}%`;
|
|
95
|
+
if (!indeterminate) fill.style.width = pct;
|
|
96
|
+
// A white copy of the label, clipped to the filled part, keeps the text
|
|
97
|
+
// readable on both sides of the bar's edge.
|
|
98
|
+
el.style.setProperty('--g0-p', pct);
|
|
99
|
+
label.textContent = labelFor(job);
|
|
100
|
+
el.querySelector('.g0-label-on').textContent = label.textContent;
|
|
101
|
+
fill.setAttribute('aria-valuenow', String(Math.round(job.progress * 100)));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function prepare(el) {
|
|
105
|
+
if (el.querySelector('.g0-label')) return;
|
|
106
|
+
const text = el.textContent.trim();
|
|
107
|
+
el.textContent = '';
|
|
108
|
+
const fill = document.createElement('span');
|
|
109
|
+
fill.className = 'g0-fill';
|
|
110
|
+
fill.setAttribute('role', 'progressbar');
|
|
111
|
+
fill.setAttribute('aria-valuemin', '0');
|
|
112
|
+
fill.setAttribute('aria-valuemax', '100');
|
|
113
|
+
const label = document.createElement('span');
|
|
114
|
+
label.className = 'g0-label';
|
|
115
|
+
label.setAttribute('aria-live', 'polite');
|
|
116
|
+
label.textContent = text;
|
|
117
|
+
const labelOn = document.createElement('span');
|
|
118
|
+
labelOn.className = 'g0-label-on';
|
|
119
|
+
labelOn.setAttribute('aria-hidden', 'true');
|
|
120
|
+
el.append(fill, label, labelOn);
|
|
121
|
+
el.dataset.git0Label = text;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function reset(el) {
|
|
125
|
+
el.className = el.className.replace(/\bg0-\S+/g, '').trim();
|
|
126
|
+
el.querySelector('.g0-fill').style.width = '0';
|
|
127
|
+
el.style.removeProperty('--g0-p');
|
|
128
|
+
el.querySelector('.g0-label').textContent = el.dataset.git0Label;
|
|
129
|
+
el.removeAttribute('aria-busy');
|
|
130
|
+
delete el.dataset.git0Running;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function serviceUp(base) {
|
|
134
|
+
try {
|
|
135
|
+
const ctl = new AbortController();
|
|
136
|
+
const t = setTimeout(() => ctl.abort(), 700);
|
|
137
|
+
const res = await fetch(`${base}/health`, { signal: ctl.signal });
|
|
138
|
+
clearTimeout(t);
|
|
139
|
+
return res.ok && (await res.json()).name === 'git0';
|
|
140
|
+
} catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Plays the whole sequence with fake numbers, for demo pages. */
|
|
146
|
+
async function simulate(el) {
|
|
147
|
+
const total = 48 * 1048576;
|
|
148
|
+
const job = { status: 'resolving', progress: 0, receivedBytes: 0, totalBytes: 0, launched: false };
|
|
149
|
+
render(el, job);
|
|
150
|
+
await new Promise(r => setTimeout(r, 900));
|
|
151
|
+
job.status = 'downloading';
|
|
152
|
+
job.totalBytes = total;
|
|
153
|
+
while (job.receivedBytes < total) {
|
|
154
|
+
job.receivedBytes = Math.min(total, job.receivedBytes + total * (0.015 + Math.random() * 0.035));
|
|
155
|
+
job.progress = (job.receivedBytes / total) * 0.9;
|
|
156
|
+
render(el, job);
|
|
157
|
+
await new Promise(r => setTimeout(r, 90));
|
|
158
|
+
}
|
|
159
|
+
for (const [status, progress, wait] of [['installing', 0.94, 900], ['launching', 0.98, 600]]) {
|
|
160
|
+
Object.assign(job, { status, progress });
|
|
161
|
+
render(el, job);
|
|
162
|
+
await new Promise(r => setTimeout(r, wait));
|
|
163
|
+
}
|
|
164
|
+
Object.assign(job, { status: 'done', progress: 1, launched: true });
|
|
165
|
+
render(el, job);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function onClick(event) {
|
|
169
|
+
const el = event.currentTarget;
|
|
170
|
+
if (el.dataset.git0Running) { event.preventDefault(); return; }
|
|
171
|
+
|
|
172
|
+
if (el.hasAttribute('data-git0-simulate')) {
|
|
173
|
+
event.preventDefault();
|
|
174
|
+
el.dataset.git0Running = '1';
|
|
175
|
+
await simulate(el);
|
|
176
|
+
setTimeout(() => reset(el), 4000);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const base = `http://127.0.0.1:${el.dataset.git0Port || DEFAULT_PORT}`;
|
|
181
|
+
// Decide synchronously that we own the click, so the browser does not
|
|
182
|
+
// start the plain download while we check for the service. If it turns
|
|
183
|
+
// out not to be running, follow the link ourselves.
|
|
184
|
+
event.preventDefault();
|
|
185
|
+
el.dataset.git0Running = '1';
|
|
186
|
+
// Opened now, inside the click, so pop-up blockers allow it; pointed at
|
|
187
|
+
// the approval page once we know there is one, closed if there is not.
|
|
188
|
+
let approval = null;
|
|
189
|
+
|
|
190
|
+
if (!(await serviceUp(base))) {
|
|
191
|
+
reset(el);
|
|
192
|
+
const href = el.getAttribute('href');
|
|
193
|
+
if (href) window.location.href = href;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
approval = window.open('', 'git0-approval', 'popup,width=480,height=420');
|
|
199
|
+
const res = await fetch(`${base}/install`, {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
headers: { 'Content-Type': 'application/json' },
|
|
202
|
+
body: JSON.stringify({ repo: el.dataset.git0Repo }),
|
|
203
|
+
});
|
|
204
|
+
let job = await res.json();
|
|
205
|
+
if (!res.ok) throw new Error(job.error || `HTTP ${res.status}`);
|
|
206
|
+
|
|
207
|
+
if (job.approveUrl && approval) approval.location.href = job.approveUrl;
|
|
208
|
+
else if (approval) approval.close();
|
|
209
|
+
|
|
210
|
+
while (!TERMINAL.includes(job.status)) {
|
|
211
|
+
render(el, job);
|
|
212
|
+
await new Promise(r => setTimeout(r, 400));
|
|
213
|
+
job = await (await fetch(`${base}/status/${job.id}`)).json();
|
|
214
|
+
}
|
|
215
|
+
render(el, job);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (approval) approval.close();
|
|
218
|
+
render(el, { status: 'error', progress: 0, error: err.message });
|
|
219
|
+
}
|
|
220
|
+
setTimeout(() => reset(el), 6000);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function init(root) {
|
|
224
|
+
injectCss();
|
|
225
|
+
for (const el of (root || document).querySelectorAll('[data-git0-repo]')) {
|
|
226
|
+
if (el.dataset.git0Bound) continue;
|
|
227
|
+
el.dataset.git0Bound = '1';
|
|
228
|
+
el.classList.add('git0-button');
|
|
229
|
+
prepare(el);
|
|
230
|
+
el.addEventListener('click', onClick);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
window.git0Button = { init, render };
|
|
235
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => init());
|
|
236
|
+
else init();
|
|
237
|
+
})();
|
package/demo/index.html
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>git0 install button</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { --bg:#fafafa; --fg:#18181b; --muted:#52525b; --card:#fff; --line:#e4e4e7; }
|
|
9
|
+
@media (prefers-color-scheme: dark) {
|
|
10
|
+
:root:not([data-theme="light"]) { --bg:#0b0b0f; --fg:#f4f4f5; --muted:#a1a1aa; --card:#18181b; --line:#27272a; }
|
|
11
|
+
}
|
|
12
|
+
:root[data-theme="dark"] { --bg:#0b0b0f; --fg:#f4f4f5; --muted:#a1a1aa; --card:#18181b; --line:#27272a; }
|
|
13
|
+
body { margin:0; background:var(--bg); color:var(--fg); font:16px/1.6 system-ui,sans-serif; }
|
|
14
|
+
main { max-width:720px; margin:0 auto; padding:48px 16px; }
|
|
15
|
+
h1 { font-size:28px; margin:0 0 8px; }
|
|
16
|
+
p { color:var(--muted); margin:0 0 16px; }
|
|
17
|
+
.card { background:var(--card); border:1px solid var(--line); border-radius:16px; padding:28px; margin:24px 0; }
|
|
18
|
+
.card h2 { font-size:17px; margin:0 0 6px; }
|
|
19
|
+
.stage { display:flex; flex-wrap:wrap; gap:16px; align-items:center; margin-top:18px; }
|
|
20
|
+
code, pre { font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; }
|
|
21
|
+
pre { background:var(--bg); border:1px solid var(--line); border-radius:10px; padding:14px; overflow-x:auto; }
|
|
22
|
+
.status { font-size:13px; }
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<main>
|
|
27
|
+
<h1>One-click install from any website</h1>
|
|
28
|
+
<p>
|
|
29
|
+
With the git0 background service running, a download button installs the right
|
|
30
|
+
release for the visitor's OS and CPU, shows the progress inside the button,
|
|
31
|
+
and opens the app when it is done.
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<section class="card">
|
|
35
|
+
<h2>Simulated</h2>
|
|
36
|
+
<p>Plays the full sequence with fake numbers, no service needed.</p>
|
|
37
|
+
<div class="stage">
|
|
38
|
+
<a href="#" data-git0-repo="junegunn/fzf" data-git0-simulate>Download for your system</a>
|
|
39
|
+
</div>
|
|
40
|
+
</section>
|
|
41
|
+
|
|
42
|
+
<section class="card">
|
|
43
|
+
<h2>Live, through your local service</h2>
|
|
44
|
+
<p class="status" id="svc">Checking for the git0 service on 127.0.0.1:47100…</p>
|
|
45
|
+
<div class="stage">
|
|
46
|
+
<a href="https://github.com/obsidianmd/obsidian-releases/releases/latest"
|
|
47
|
+
data-git0-repo="obsidianmd/obsidian-releases">Install Obsidian</a>
|
|
48
|
+
<a href="https://github.com/junegunn/fzf/releases/latest"
|
|
49
|
+
data-git0-repo="junegunn/fzf">Install fzf</a>
|
|
50
|
+
</div>
|
|
51
|
+
<p style="margin-top:16px">Without the service these links fall back to the normal releases page. Start it with:</p>
|
|
52
|
+
<pre>npx git0 --service=install # start at login, register git0:// links
|
|
53
|
+
npx git0 --service # or just run it in this terminal</pre>
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
<section class="card">
|
|
57
|
+
<h2>Add it to your site</h2>
|
|
58
|
+
<pre><a href="https://github.com/owner/repo/releases/latest"
|
|
59
|
+
data-git0-repo="owner/repo">Download</a>
|
|
60
|
+
<script src="https://unpkg.com/git0/demo/git0-button.js"></script></pre>
|
|
61
|
+
</section>
|
|
62
|
+
</main>
|
|
63
|
+
<script src="git0-button.js"></script>
|
|
64
|
+
<script>
|
|
65
|
+
fetch('http://127.0.0.1:47100/health')
|
|
66
|
+
.then(r => r.json())
|
|
67
|
+
.then(h => { document.getElementById('svc').textContent = `git0 service ${h.version ?? ''} is running (${h.os} ${h.arch}).`; })
|
|
68
|
+
.catch(() => { document.getElementById('svc').textContent = 'The git0 service is not running, so these buttons fall back to the releases page.'; });
|
|
69
|
+
</script>
|
|
70
|
+
</body>
|
|
71
|
+
</html>
|