Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
248 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r91// The settings window's work: show what the app has, and keep each change
2// the moment it is made. Everything it asks is a command in
Merge branch 'main' into rm-agents3// src/bridge.rs. The settings usually open over the site, which draws
4// them (apps/web components/desktop-settings.tsx); this window is for when
5// the window shows no page of the site's. The runner has a window of its
6// own (runner.html); here it is a line that opens it. A button that asks
7// the app for something is busy until it is done (busy.js).
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r98(function () {
The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is.9 var SWITCHES = ["startHidden", "closeToTray", "notifications", "flash", "badge", "systemTitleBar", "autoUpdate"];
10 // The settings the app keeps (src/settings.rs Settings); a save sends them all.
11 var KEPT = SWITCHES.concat(["zoom", "runner", "runnerBackend", "runnerWorkspace", "runnerCapabilities"]);
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r912 var current = null;
13
14 function byId(id) {
15 return document.getElementById(id);
16 }
17
18 function invoke(command, args) {
19 var core = window.__TAURI__ && window.__TAURI__.core;
20 if (core && typeof core.invoke === "function") return core.invoke(command, args || {});
21 return Promise.reject(new Error("This page belongs inside the g1t desktop app."));
22 }
23
24 function fail(error) {
25 var box = byId("error");
26 box.textContent = typeof error === "string" ? error : (error && error.message) || "That could not be changed.";
27 box.hidden = false;
28 }
29
30 function hostOf(server) {
31 try {
32 return new URL(server).host;
33 } catch (error) {
34 return server;
35 }
36 }
37
38 // What changes between Windows, macOS and Linux.
39 function platform(name) {
40 byId("flash-label").textContent =
41 name === "macos" ? "Bounce the dock icon" : name === "windows" ? "Flash the taskbar icon" : "Ask for attention";
42 // macOS keeps its menu bar at the top of the screen, whichever title bar the window has.
43 if (name === "macos") byId("system-title-bar-note").textContent = "In place of g1t's own, which sits under the window's buttons.";
44 if (name === "macos") byId("zoom-note").textContent = "The View menu changes it too: ⌘+, ⌘− and ⌘0.";
45 else byId("zoom-note").textContent = "The View menu changes it too: Ctrl+=, Ctrl+− and Ctrl+0.";
46 }
47
48 function render(view) {
49 current = view;
50 byId("error").hidden = true;
51 byId("openAtLogin").checked = !!view.openAtLogin;
52 SWITCHES.forEach(function (key) {
53 byId(key).checked = !!view[key];
54 });
55 // Starting in the tray only means something when g1t starts at sign-in.
56 byId("startHidden").disabled = !view.openAtLogin;
57 byId("startHidden").closest(".setting").classList.toggle("off", !view.openAtLogin);
58 // The title bar changes when g1t restarts.
59 byId("restart-row").hidden = !view.titleBarChanged;
60 var zoom = byId("zoom");
61 zoom.value = String(view.zoom);
62 if (zoom.value === "") zoom.value = "1";
63 byId("version").textContent = "g1t " + view.version;
64 if (!checking) updateStatus(view);
65 byId("server").textContent = hostOf(view.server);
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.66 renderRunner(view.runnerView);
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r967 }
68
The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is.69 // The runner, in a line: what it is doing, and the window that has the rest.
70 function renderRunner(runner) {
71 if (!runner) return;
72 byId("runner-dot").className = "dot " + runner.chip.tone;
73 byId("runner-title").textContent = runner.chip.label;
74 byId("runner-summary").textContent = runner.diagnosis.title + (runner.version ? " · g1t-runner " + runner.version : "");
75 }
76
77 // Every setting, as the app has it now (the Runner window changes the
78 // runner's), with this window's own as they are on the page.
79 function settingsOf(fresh) {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r980 var settings = {};
The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is.81 KEPT.forEach(function (key) {
82 if (key in fresh) settings[key] = fresh[key];
83 });
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r984 SWITCHES.forEach(function (key) {
85 settings[key] = byId(key).checked;
86 });
87 settings.zoom = Number(byId("zoom").value) || 1;
88 return settings;
89 }
90
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.91
The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is.92 function save() {
93 invoke("get_settings")
94 .then(function (fresh) {
95 return invoke("save_settings", { settings: settingsOf(fresh) });
96 })
97 .then(render, function (error) {
98 fail(error);
99 if (current) render(current);
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.100 });
101 }
102
The desktop app is 0.3.4, and the runner has a window of its own. Beside the title bar's Settings button a chip says what the runner is doing, live, with a dot in its colour (Runner running, Running a job, Running 2 jobs, Runner starting, Runner updating, Runner stopped, Runner offline, Runner needs you), as Docker Desktop shows its engine; in a narrow window only the dot shows. It opens the Runner window, as the tray's runner row now does: at its top what the whole runner is doing, with what it is running, its version and workspace, and its own Stop or Start; then each kind of job's card as before; then its setup (running jobs here at all, how jobs run, the workspace, the computer with Check for runner updates and View on g1t); and its log, open and following along as it is written. Settings keeps the runner as one line, what it is doing with Open runner, and each window saves only its own settings over what the app has now, so neither undoes the other's. The chip's words are diagnosis.rs's, the Runner window is made off the main thread as Settings now is, and it has a capability of its own with only the commands it uses; the title bar may now ask the runner's status. Two things fixed on the way: a settings row hidden until it applies, Restart to change the title bar, showed all the time, since its row's display beat the hidden attribute; and in a narrow window the title bar's buttons ran over the search, which now shrinks instead. The desktop guide describes the chip, the Runner window and where each runner setting now is.103 byId("open-runner").addEventListener("click", function () {
Merge branch 'main' into rm-agents104 window.g1tBusy.run(byId("open-runner"), "open-runner", "", false, function () {
105 return invoke("runner_control", { action: "open_runner" });
106 }, fail);
The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report.107 });
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.108
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9109 SWITCHES.forEach(function (key) {
110 byId(key).addEventListener("change", save);
111 });
112 byId("zoom").addEventListener("change", save);
113 byId("openAtLogin").addEventListener("change", function (event) {
114 invoke("set_open_at_login", { on: event.target.checked }).then(render, function (error) {
115 fail(error);
116 if (current) render(current);
117 });
118 });
119
120 // Updates: where this copy stands, said in the window, so a check from
121 // the menu or the tray is answered here and cannot be missed.
122 var checking = false;
123
124 function when(ms) {
125 var date = new Date(ms);
126 var today = new Date().toDateString() === date.toDateString();
127 var time = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
128 return today ? "today at " + time : date.toLocaleDateString([], { month: "short", day: "numeric" }) + " at " + time;
129 }
130
131 function ready(version) {
132 byId("update-status").textContent = "Version " + version + " is downloaded and installs when g1t restarts.";
133 byId("restart").hidden = false;
134 }
135
136 function updateStatus(view) {
137 if (view.updateReady) return ready(view.updateReady);
138 byId("restart").hidden = true;
139 byId("update-status").textContent = view.checkedAt
140 ? "Up to date. Last checked " + when(view.checkedAt) + "."
141 : view.autoUpdate
142 ? "Not checked yet since g1t started; it checks on its own shortly."
143 : "Checking on its own is off.";
144 }
145
146 function check() {
147 if (checking) return;
148 checking = true;
149 var button = byId("check");
150 var status = byId("update-status");
Merge branch 'main' into rm-agents151 window.g1tBusy.start(button);
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9152 status.textContent = "Checking, and downloading any new version…";
153 invoke("check_for_updates")
154 .then(function (answer) {
155 if (answer.indexOf("ready:") === 0) ready(answer.slice(6));
156 else status.textContent = "Up to date. Last checked " + when(Date.now()) + ".";
157 })
158 .catch(function (error) {
159 var text = typeof error === "string" ? error : "";
160 status.textContent = /sending request|connect|dns|timed out/i.test(text) || !text
161 ? "Could not reach g1t.sh to check. Check your connection and try again."
162 : "Could not check: " + text;
163 })
164 .then(function () {
165 checking = false;
Merge branch 'main' into rm-agents166 window.g1tBusy.stop(button);
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9167 });
168 }
169
170 byId("check").addEventListener("click", check);
171 byId("restart").addEventListener("click", function () {
Merge branch 'main' into rm-agents172 window.g1tBusy.start(byId("restart"));
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9173 byId("update-status").textContent = "Installing…";
174 invoke("restart_to_update").catch(function (error) {
Merge branch 'main' into rm-agents175 window.g1tBusy.stop(byId("restart"));
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9176 fail(error);
177 });
178 });
179
180 // The menu's or tray's "Check for updates…" with this window open, and a
181 // download finishing in the background (src/settings.rs).
182 var events = window.__TAURI__ && window.__TAURI__.event;
183 if (events && typeof events.listen === "function") {
184 events.listen("g1t-check-updates", check).catch(function () {});
185 events
Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it.186 .listen("g1t-runner-status", function (event) {
187 if (current) current.runnerView = event.payload;
188 renderRunner(event.payload);
189 })
190 .catch(function () {});
191 events
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9192 .listen("g1t-update-ready", function (event) {
193 if (!checking && typeof event.payload === "string") ready(event.payload);
194 })
195 .catch(function () {});
196 }
197
198 byId("restart-app").addEventListener("click", function () {
Merge branch 'main' into rm-agents199 window.g1tBusy.start(byId("restart-app"));
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9200 invoke("restart").catch(function (error) {
Merge branch 'main' into rm-agents201 window.g1tBusy.stop(byId("restart-app"));
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9202 fail(error);
203 });
204 });
205
206 byId("change-server").addEventListener("click", function () {
Merge branch 'main' into rm-agents207 window.g1tBusy.run(byId("change-server"), "change-server", "", false, function () {
208 return invoke("change_server");
209 }, fail);
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9210 });
211
212 // Links out, in the system's browser (src/bridge.js does the same on the site).
213 document.addEventListener("click", function (event) {
214 var anchor = event.target && event.target.closest ? event.target.closest("a[href]") : null;
215 if (!anchor) return;
216 event.preventDefault();
217 invoke("open_url", { url: anchor.href }).catch(function () {});
218 });
219
220 // g1t's own title bar, when the window has no system frame.
221 function frame(view) {
222 if (!view.customFrame) return;
223 if (view.platform === "macos") document.documentElement.classList.add("mac");
224 var bar = byId("titlebar");
225 bar.hidden = false;
226 document.body.classList.add("framed");
227 bar.addEventListener("mousedown", function (event) {
228 if (event.button !== 0 || event.target.closest("button")) return;
229 invoke("window_control", { action: "drag" }).catch(function () {});
230 });
231 byId("close-window").addEventListener("click", function () {
232 invoke("window_control", { action: "close" }).catch(function () {});
233 });
234 }
235
236 invoke("get_settings")
237 .then(function (view) {
238 frame(view);
239 platform(view.platform);
240 render(view);
241 // Opened by "Check for updates…" in a menu.
242 if (new URLSearchParams(location.search).has("check")) {
243 byId("updates-row").scrollIntoView({ block: "center" });
244 check();
245 }
246 })
247 .catch(fail);
248})();

This file's history is long; its oldest lines are credited to the oldest commit read.