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

Commit

Merge branch 'main' into worktree-agent-a654d8ba46392bedd

syntaqxcommitted Parents890badea5f8660Browse files
55 files+1509−4160/55 viewed
+1−1
77 "name": "g1t",
88 "source": "./plugins/claude-code",
99 "description": "Work on g1t issues from Claude Code: records your sessions on g1t, loads an issue's work context, claims and hands off work, and connects g1t's MCP server.",
10− "version": "0.2.4",
10+ "version": "0.2.5",
1111 "category": "development"
1212 }
1313 ]
+1−1
4141 integrations: [
4242 starlight({
4343 title: 'g1t docs',
44− description: 'Guides and reference for g1t, where a team and its coding agents talk, build and ship.',
44+ description: 'Guides and reference for g1t, the code platform for your team and its coding agents.',
4545 components: {
4646 Footer: './src/components/Footer.astro',
4747 Head: './src/components/Head.astro',
+4−0
1+User-agent: *
2+Allow: /
3+
4+Sitemap: https://docs.g1t.sh/sitemap-index.xml
+1−1
11 {
22 "name": "g1t docs",
33 "short_name": "g1t",
4− "description": "How to use g1t: chat, agents, docs and code in one workspace.",
4+ "description": "How to use g1t, the code platform for your team and its coding agents.",
55 "start_url": "/",
66 "scope": "/",
77 "display": "minimal-ui",
+30−0
4545 if (current && tabs && current.offsetLeft + current.offsetWidth > tabs.clientWidth) {
4646 tabs.scrollLeft = current.offsetLeft - (tabs.clientWidth - current.offsetWidth) / 2;
4747 }
48+ // The row fades out at an edge with more tabs past it, so a cut-off
49+ // tab reads as "scroll for more" rather than as a clipped word.
50+ const bar = document.querySelector<HTMLElement>('.g1t-tabs');
51+ if (bar) {
52+ const edges = () => {
53+ const end = bar.scrollWidth - bar.clientWidth;
54+ bar.toggleAttribute('data-more-start', bar.scrollLeft > 1);
55+ bar.toggleAttribute('data-more-end', bar.scrollLeft < end - 1);
56+ };
57+ edges();
58+ bar.addEventListener('scroll', edges, { passive: true });
59+ addEventListener('resize', edges);
60+ }
4861 </script>
4962
5063 <style>
144157 .g1t-tabs::-webkit-scrollbar {
145158 display: none;
146159 }
160+ .g1t-tabs[data-more-start],
161+ .g1t-tabs[data-more-end] {
162+ --fade: 2.5rem;
163+ mask-image: linear-gradient(
164+ to right,
165+ transparent,
166+ #000 var(--fade-start, 0px),
167+ #000 calc(100% - var(--fade-end, 0px)),
168+ transparent
169+ );
170+ }
171+ .g1t-tabs[data-more-start] {
172+ --fade-start: var(--fade);
173+ }
174+ .g1t-tabs[data-more-end] {
175+ --fade-end: var(--fade);
176+ }
147177 .g1t-tabs a {
148178 display: flex;
149179 flex-shrink: 0;
+9−1
99
1010 const { entry, id } = Astro.locals.starlightRoute;
1111 const { title, description } = entry.data;
12−const section = NAV.get(Astro.locals.starlightRoute)?.section;
12+const nav = NAV.get(Astro.locals.starlightRoute)?.section;
13+// No eyebrow that only repeats the title, as on Chat's page in Chat.
14+const section = nav && nav.toLowerCase() !== title.toLowerCase() ? nav : undefined;
1315 const markdown = `/${id === '' || id === 'index' ? 'index' : id}.md`;
1416 const absolute = new URL(markdown, Astro.site).href;
1517 const ask = encodeURIComponent(`Read ${absolute} so I can ask questions about it.`);
250252 font-size: 2rem;
251253 overflow-wrap: anywhere;
252254 }
255+ /* The lead a step above the body, not a headline of its own. */
256+ .g1t-description {
257+ margin-top: 0.75rem;
258+ font-size: 1.0625rem;
259+ line-height: 1.5;
260+ }
253261 .g1t-copy {
254262 height: 2.5rem;
255263 margin-top: 0;
+39−27
165165
166166 - Version update pull requests are opened for npm, Cargo, Go and pip only.
167167 Entries for other ecosystems are checked and listed, and open nothing.
168−
169168 - A multi-ecosystem group opens one pull request per ecosystem, not one
170169 for the group.
171170 - Registries that sign in with OIDC are not used.
172171
173−- **Instead.** Keep a separate entry per ecosystem and directory, and
174− check the Security page, which lists what each entry reads but does not
175− act on. See [Dependency updates](/guides/dependency-updates/#options).
176−- **Status.** Planned.
172+**Instead.** Keep a separate entry per ecosystem and directory, and check
173+the Security page, which lists what each entry reads but does not act on.
174+See [Dependency updates](/guides/dependency-updates/#options).
175+**Status.** Planned.
177176
178177 ### Very large changes are shown in parts
179178
270269 stop it in a later one; write a schedule's `cron` in UTC.
271270 - **Status.** Not scheduled.
272271
273−### Self-hosted Windows and macOS jobs run on the host
272+### Self-hosted macOS jobs run on the host
274273
275−On a self-hosted runner, a job native to Windows or macOS runs directly on
276−the machine, unsandboxed, in host mode, which is deprecated. Only Linux
277−jobs run in a sandbox of their own, a container inside the runner's own
278−container under Sysbox.
274+On a self-hosted runner, a job native to macOS runs directly on the Mac,
275+unsandboxed, in host mode, which is deprecated. Linux jobs run in a
276+sandbox of their own on every computer, and Windows jobs in a Windows
277+Sandbox per job on Windows 11 Pro, Enterprise and Education. Windows 11
278+Home has no Windows Sandbox, so it runs Linux jobs only.
279279
280−- **Why.** Sandboxes for them are not built yet: Windows containers with
281− Hyper-V isolation, and ephemeral macOS virtual machines (two per Mac at
282− most, by Apple's licence).
280+- **Why.** Sandboxed macOS jobs are designed, not built: an ephemeral
281+ macOS virtual machine per job, at most two per Mac by Apple's licence.
283282 - **Instead.** Run Linux jobs in
284− [the runner's container](/guides/self-hosted-runners/#run-it-as-a-container),
285− and keep Windows and macOS runners for repositories whose code you
286− trust. Host mode refuses public repositories' work.
283+ [the sandbox on your computer](/guides/self-hosted-runners/#sandbox-on-your-computer),
284+ and keep host-mode Mac runners for repositories whose code you trust.
285+ Host mode refuses public repositories' work.
287286 - **Status.** Planned. See
288− [Windows and macOS](/guides/self-hosted-runners/#windows-and-macos).
287+ [how each computer runs it](/guides/self-hosted-runners/#how-each-computer-runs-it).
288+
289+### The runner's sandbox is checked end to end on Windows only
289290
291+The sandbox for Linux jobs on macOS (a VM under Apple's Virtualization
292+framework) and on Linux without Sysbox (a VM under QEMU with KVM) is
293+built to the same design as on Windows, but has not yet been run on a
294+Mac or on such a Linux machine.
295+
296+- **Instead.** Run `g1t-runner sandbox status` first: it says what the
297+ computer lacks. On Linux, the runner's container under Sysbox needs no
298+ VM. See [what it cannot stop](/guides/self-hosted-runners/#what-it-cannot-stop).
299+- **Status.** Planned.
300+
290301 ### A self-hosted job's Docker can reach its runner's container
291302
292303 On a runner in its container under Sysbox, a job that uses Docker gets an
444455
445456 ### Some costs are not fully defined yet
446457
447−Cloudflare's [Artifacts pricing documentation](https://developers.cloudflare.com/artifacts/platform/pricing/)
448−lists October 14, 2026 as the billing start, while its launch announcement
449−lists October 15. The pricing documentation identifies repository operations
450−such as create, push, pull and clone, and storage measured from daily peaks.
451−g1t's measurements still need reconciliation against the upstream bill.
458+Cloudflare Artifacts, where every repository is stored, started billing in
459+mid-October 2026. Its [pricing](https://developers.cloudflare.com/artifacts/platform/pricing/)
460+counts repository operations such as create, push, pull and clone, and
461+storage measured from daily peaks. g1t's own counts are still being
462+reconciled against that bill.
452463
453464 - **What g1t does.** It counts every clone, fetch and push through its git
454465 endpoints. Each day it checks what Cloudflare billed it for containers and
480491 cover personal access tokens only, not applications you signed in to with
481492 OAuth. **Status.** Planned.
482493
483−### No uptime commitment during the beta
494+### No uptime commitment during the alpha
484495
485496 g1t does not promise a particular uptime or offer a service level agreement
486497 unless you have a written agreement with us. Several of the Cloudflare
488499 [status.g1t.sh](https://status.g1t.sh/) shows how each part of g1t is doing,
489500 and every incident. Sandboxes are not checked there yet. See
490501 [Status and incidents](/guides/status/). **Status.** Not scheduled during
491−the beta.
502+the alpha.
492503
493504 ### Self-hosting is early
494505
500511 runner. Use the hosted service to try that integration loop. Self-hosting
501512 is not ready for the open internet. **Status.** Planned, in phases.
502513
503−### Not built yet
514+### No wikis
504515
505−
506−- **Wikis.** Not scheduled. Keep docs in the repository.
516+A repository has no wiki. **Instead.** Keep docs in the repository, or
517+write them together in [Artifacts](/guides/artifacts/). **Status.** Not
518+scheduled.
+12−6
77 import Diagram from '../../../components/Diagram.astro';
88
99 Everything that runs code you did not write runs in a sandbox: workflow
10−jobs, merge queue builds, deployments, dependency updates, backups. Three
11−pieces share the work.
10+jobs, merge checks, deployments, dependency updates, backups. Three pieces
11+share the work.
1212
1313 | Piece | Is | Does |
1414 | --- | --- | --- |
2626 <Diagram name="actions-job" />
2727
2828 1. **Trigger.** `actions` reads its event queue, `g1t-events-actions`, ten
29− at a time (step 1), and matches each event to the workflows read from
30− the default branch's `.g1t/workflows` (or `.github/workflows`). The
31− files are GitHub Actions syntax, unchanged; [GitHub
29+ at a time (step 1), and matches each event to the workflows in
30+ `.g1t/workflows` at the commit the event is about (the default branch
31+ for schedules, issues and manual runs; `services/actions/src/sync.rs`).
32+ g1t never reads `.github/workflows`. The files are GitHub Actions
33+ syntax, unchanged; [GitHub
3234 Actions](/guides/actions/) lists what is supported.
3335 2. **Plan.** It expands matrices, orders jobs by `needs`, and reports the
3436 run as pending on the commit (steps 2 and 3).
148150 | Mode | Started by | Does |
149151 | --- | --- | --- |
150152 | `actions` | `actions` | A workflow job |
151−| `queue` | `runner`, when the queue changes | Builds a merge queue candidate |
152153 | `mergecheck` | `runner`, when a pull request changes | Checks whether a pull request merges cleanly |
153154 | `deploy` | `deployments` | Builds an app ([Deployments](/architecture/deployments/)) |
154155 | `bump` | `security` | Makes a dependency update |
155156 | `backup` | `runner`'s sweep, every 5 minutes | Cuts a repository backup ([Git](/architecture/git/#backups)) |
156157
158+The merge queue needs no sandbox of its own: `work` builds each queued
159+state in the repository itself ([Work](/architecture/work/#the-merge-queue)),
160+and only the workflows that run on `merge_group` run in sandboxes, as
161+workflow jobs.
162+
157163 Compute is admitted through billing: a sandbox starts only after
158164 `billing` holds its estimated cost (`reserve`), and the hold is settled
159165 with the real cost when it ends (`settle`).
+2−2
3939 | Field | Holds |
4040 | --- | --- |
4141 | `kind` | `User`, `Workspace`, `Agent` or `System`: what kind of principal this is |
42−| `workspaces` | Each membership: the role, the workspace's base permission, organisation roles such as billing or security manager, and privileges |
42+| `workspaces` | Each membership: the role, the workspace's base permission, the roles that add to a member, such as billing or security manager, and privileges |
4343 | `grants` | Roles on repositories, directly or through teams |
4444 | `acting` | For a job or run token: the narrower scope it acts within |
4545
6363
6464 | Level | Roles |
6565 | --- | --- |
66−| Workspace | Owner and member, plus organisation roles (billing manager, security manager) and member privileges |
66+| Workspace | Owner and member, plus the roles that add to a member (billing manager, security manager) and member privileges |
6767 | Repository | Read, Triage, Write, Maintain and Admin, given to a person, a team, or everyone in the workspace as its base permission |
6868 | Outside collaborators | A repository role for someone who is not a member |
6969
+1−1
3838 | `services/notify` | TypeScript | core | Notifications, live pages, presence, browser and phone push | A Durable Object per person, per workspace and per repository | `identity` |
3939 | `services/telemetry` | TypeScript | core | Logs, traces, metrics and errors for projects, at `otel.g1t.sh` | A Durable Object per project | `billing` |
4040 | `services/billing` | Rust | core | Usage, holds and settlements, limits, Stripe | D1 `g1t-billing` | `identity`, `packages`, `repos` |
41−| `services/integrations` | Rust | core | Model providers, alert sources, issue trackers, the GitHub App, Ask g1t | D1 `g1t-integrations` | `identity`, `projects`, `repos`, `work` |
41+| `services/integrations` | Rust | core | The workspace's AI providers and Ask g1t, alert sources, issue trackers, the GitHub App | D1 `g1t-integrations` | `identity`, `projects`, `repos`, `work` |
4242 | `services/webhooks` | Rust | core | Webhook subscriptions and signed deliveries with retries | D1 `g1t-webhooks` | `identity`, `repos` |
4343 | `services/og` | TypeScript | core | Social cards and page screenshots, at `og.g1t.sh` | The screenshots bucket | `billing`, `identity`, `projects`, `repos`, `work` |
4444
+7−5
9595 and whether the person asking may bypass it. A merge happens only when
9696 none fails or waits, for people and coding agents alike.
9797
98−When everything passes, `work` asks `repos` to land the pull request's
99−head on the base, and publishes `pull.merged`.
98+When everything passes, `work` asks `repos` to land the pull request on
99+the base by the merge method asked for: a merge commit, squash, rebase or
100+fast-forward (`services/repos/src/merge_methods.rs`). Then it publishes
101+`pull.merged`.
100102
101103 ## The merge queue
102104
108110 <Diagram name="merge-queue" />
109111
110112 1. **Build.** Up to `max_entries_to_build` entries (4 by default) are
111− tested at once. For each, a sandbox merges the default branch with that
112− entry and every entry ahead of it, and pushes the result to
113− `g1t-queue/<entry>` in the repository.
113+ tested at once. Each entry is landed by its own merge method on the
114+ state of the entry ahead of it (the default branch, for the first), and
115+ the result is pushed to `g1t-queue/<entry>` in the repository.
114116 2. **Test.** Workflows that run on `merge_group` run on each of those
115117 commits, and the required checks have to pass there.
116118 3. **Land.** Entries land in order. Once everything ahead of it has landed,
+121−118
11 ---
22 title: How g1t works
3−description: How Code works on g1t, and how issues, pull requests, checks, review, merging and sessions fit together.
3+description: How Code works on g1t, and how issues, pull requests, checks, review, merging and sessions fit together, for people and coding agents alike.
44 ---
55
6−g1t is where a team's code, conversations and shipping live. A workspace
7−holds your repositories, with issues, pull requests, required checks and
8−the merge queue; [Actions](/guides/actions/) that run your workflows;
9−[Deployments](/guides/deployments/) that put every pull request live;
10−and [Chat](/guides/chat/) and [Artifacts](/guides/artifacts/) for your
11−team. This page is about **Code**, where changes are made and landed.
6+A workspace holds your repositories, with issues, pull requests, required
7+checks and the merge queue; [Actions](/guides/actions/) that run your
8+workflows; [Deployments](/guides/deployments/) that put every pull request
9+live; and [Chat](/guides/chat/) for your team. This page is about
10+**Code**, where changes are made and landed.
11+
12+Underneath is ordinary git: clone, branch, commit, push and pull work as
13+they do anywhere. On top of it are the two things you already know,
14+**issues** and **pull requests**, built so that many people and coding
15+agents can work at once without getting in each other's way.
1216
1317 The coding agents you already use, such as Claude Code, Codex, Cursor or
1418 OpenCode, work on g1t from wherever they run. They connect over
15−[MCP](/guides/bring-your-own-agent/), the [REST API](/reference/api/) and
16−[webhooks](/guides/webhooks/), act as the person who connected them, and
17−go through the same pull requests, checks, reviews and rules as everyone
18−else.
19−
20−Underneath it is ordinary git: repositories, commits, branches, clone, push
21−and pull all work as they do anywhere. On top of that it has the two things
22−you already know, **issues** and **pull requests**, built
23−so that many people and agents can work at once without getting in each
24−other's way.
19+[MCP](/guides/bring-your-own-agent/), the [g1t CLI](/guides/cli/), the
20+[REST API](/reference/api/) and [webhooks](/guides/webhooks/), act as the
21+person who connected them, and go through the same pull requests, checks,
22+reviews and rules as everyone else.
2523
2624 | | What it is |
2725 | --- | --- |
2826 | **Issue** | What should change: a bug, a feature, a question. |
29−| **Pull request** | A proposed change, in its own fork or on a branch. Usually made for an issue. |
30−| **Session** | The record of how a pull request was made, written by the coding agent that made it: prompts, reasoning, tool calls. |
27+| **Pull request** | A proposed change, on a branch or in a working copy of its own. Usually made for an issue. |
28+| **Session** | One run of a coding agent: its prompts, reasoning and tool calls, tied to the issue and pull request it worked on. |
3129
32−What g1t adds: one issue can have several pull requests, each from a
33−different person or agent, and g1t keeps track of which one was merged.
30+One issue can have several pull requests, each from a different person or
31+agent, and g1t keeps track of which one was merged.
3432
3533 ## Issues
3634
37−An issue says what should change in a repository. People open them, coding
38−agents open them, and so can anything with an access token, such as an error
39−tracker reporting a crash.
35+An issue says what should change in a repository. People open them,
36+coding agents open them, and so can anything with an access token, such
37+as an error tracker reporting a crash.
4038
4139 An issue has:
4240
4341 - a **title** and a **description** in Markdown. Whoever works on the
44− issue, person or agent, works from this text. It can say what done means
45− in plain words, under a `## Definition of done` heading if you like:
42+ issue, person or agent, works from this text. Say what done means in
43+ plain words, under a `## Definition of done` heading if you like: it is
4644 context for whoever works on it and its reviewers, not something a merge
4745 waits on;
4846 - **labels**, which say what kind of issue it is;
49−- **comments**;
47+- **comments** and **assignees**;
5048 - a **state**: open or closed. A closed issue records why: `completed` or
5149 `not_planned`.
5250
5351 Issues and pull requests share one sequence of numbers per repository, so
5452 `#12` names exactly one of them.
5553
56−### Labels
57−
58−Every repository starts with `bug`, `feature`, `docs`, `chore` and
59−`question`. There is nothing to set up for others: putting a new name on an
60−issue creates the label. Labels are lowercase, and an issue can carry up to
61−ten.
62−
63−Filter a repository's issues by label on the site, or with `?label=` in the
64−API.
54+A new repository starts with a set of labels such as `bug`,
55+`enhancement` and `question`; anyone with the Triage role can make more as
56+they label. An issue or pull request carries up to 20. See
57+[labels](/guides/labels/).
6558
6659 ## Pull requests
6760
6861 A pull request is a proposed change. There are two ways to make one.
6962
70−**In a fork.** This is how coding agents usually work: the `pull_request`
71−tool's `create` action opens one for an issue. Opening the pull request creates a
72−copy of the repository that belongs to that pull request alone, ready in
73−about the time a branch would be. Its author clones the fork, commits and pushes to it. Nothing they do
74−can touch `main` or another pull request. The fork lives at
75−`g1t.sh/pulls/<pull request id>.git` and is exactly as visible as the
76−repository it came from. The pull request starts as a draft.
63+**From a branch.** Push a branch to the repository, then open a pull
64+request from it on the **Pull requests** tab or with `g1t pr create`. It
65+needs write access to the repository, and is ready for review as soon as
66+it is opened.
7767
78−**From a branch.** This is the way you already know. Push a branch to the
79−repository, then open a pull request from it on the **Pull requests** tab.
80−It needs write access to the repository, and it is ready for review as soon
81−as it is opened.
68+**In a working copy.** This is how coding agents usually work: the
69+`pull_request` tool's `create` action, or `g1t pr create --issue 12` with
70+no branch of your own, opens a draft pull request for an issue and makes
71+it a copy of the repository that belongs to that pull request alone,
72+ready in about the time a branch would be. The author clones it, commits
73+and pushes to it, and nothing they do can touch `main` or another pull
74+request. It lives at `g1t.sh/pulls/<pull request id>.git` and is exactly
75+as visible as the repository it came from.
8276
83−[Forks and working copies](/concepts/forks/) explains when each is the better
84−choice.
77+[Forks and working copies](/concepts/forks/) explains when each is the
78+better choice.
8579
8680 | Status | Meaning |
8781 | --- | --- |
88−| `draft` | Still being worked on. A pull request with a fork starts here. |
82+| `draft` | Still being worked on. A pull request in a working copy starts here. |
8983 | `open` | Ready for review, with a description of what changed and why. |
90−| `merged` | Landed on `main`. |
84+| `merged` | Landed on its base branch. |
9185 | `closed` | Closed without merging. |
9286
93−A pull request is normally opened **for an issue**. It can also stand alone,
94−with its own title, for a change nobody filed an issue about.
87+A pull request is normally opened **for an issue**. It can also stand
88+alone, with its own title, for a change nobody filed an issue about.
9589
9690 ## Assignees and reviewers
9791
98−An issue is assigned to people. A pull request has assignees too, and
99−reviewers: the people whose review was asked for. Each shows beside the
100−conversation, with where every reviewer stands.
92+Issues and pull requests have assignees, and pull requests have
93+reviewers: the people and teams whose review was asked for. Each shows
94+beside the conversation, with where every reviewer stands.
95+
96+Assigning an issue to `g1t`, or mentioning `@g1t` in a comment, starts a
97+session on the workspace's own Anthropic account, as the person who asked;
98+see [Assign g1t](/guides/sessions/#assign-g1t-or-ask-it-in-the-thread).
10199
102−Whatever happens is told in the conversation, in order, between the
103−comments: who assigned whom, whose review was asked for, and when it was
104−marked ready, merged or closed.
100+The conversation tells what happened, in order, between the comments: who
101+assigned whom, whose review was asked for, which sessions started, and
102+when it was marked ready, merged or closed.
105103
106104 ## Several pull requests for one issue
107105
108106 An issue can have more than one pull request: a second attempt after the
109−first fell short, or two agents trying different approaches. Each is in its own
110−fork, with its own session and its own diff. The issue's page lists them
111−all with their status.
107+first fell short, or two agents trying different approaches. Each has its
108+own branch or working copy, its own session and its own diff. The issue's
109+page lists them all with their status.
112110
113111 When you merge one:
114112
121119 So the answer to "which one did we take?" is on the issue, on the merged
122120 pull request, and on each one that was passed over.
123121
124−Sometimes several pull requests each do part of an issue. When merging, say
125−that the issue should stay open. The pull request merges, and the issue and
122+When several pull requests each do part of an issue, say that the issue
123+should stay open as you merge. The pull request merges, and the issue and
126124 the other pull requests are left as they are.
127125
128126 ## Checks
129127
130128 A pull request's checks are what the repository's
131−[workflows](/guides/actions/) report on its head commit. Every workflow
132−that runs on `pull_request` runs on every pull request, whoever opened it,
133−a person or an agent, and reports a check named after the workflow, such as
129+[workflows](/guides/actions/) and integrations report on its head commit.
130+Every workflow that runs on `pull_request` runs on every pull request,
131+whoever opened it, and reports a check named after the workflow, such as
134132 `CI`.
135133
136−The default branch decides which checks a merge needs: its
134+The base branch decides which checks a merge needs: its
137135 [required status checks](/guides/pull-requests/#required-status-checks).
138136 A pull request merges only once each of them has passed on its head. One
139137 that failed, is still running or has not reported yet holds the merge,
140138 unless the repository allows bypassing them and someone who can merge
141139 chooses to. Checks that are not required are shown, and never hold a merge.
142140
143−The same rules hold for people and agents. A pull request a coding agent
144−opens is checked by the same workflows as yours, and an agent cannot mark
145−its own work as passing: only the workflow runs report.
141+The same rules hold for people and agents. A pull request an agent opens
142+is checked by the same workflows as yours, and an agent cannot mark its
143+own work as passing: only the checks report.
146144
147145 ## Review
148146
149147 Anyone who can see a pull request can comment on it, on the whole of it or
150−on a single line of its change. Line comments are shown in the **Files changed**
151−tab under the line they are about.
148+on a single line of its change, in **Files changed**.
152149
153150 A reviewer can also give a verdict: **approve**, or **request changes**.
154−The pull request shows where each reviewer stands. You cannot give a verdict
155−on a pull request you opened, and that holds for a coding agent working as
156−you too.
151+You cannot give a verdict on a pull request you opened, and neither can a
152+coding agent working as you.
157153
158154 ## Overlap
159155
160−When many changes are in flight, some touch the same files. g1t keeps track
161−of which files each pull request changes, from every push, and shows on a
156+When many changes are in flight, some touch the same files. g1t tracks
157+which files each pull request changes, from every push, and shows on a
162158 pull request which others in progress change the same ones.
163159
164160 Two pull requests for the *same* issue are expected to overlap: they are
165−alternatives, and one will be merged. Two for *different* issues are heading
166−for a conflict, and g1t says so while the work is still going on rather than
167−when the second one tries to merge. Coding agents get the same list from
168−the `pull_request` tool's `get` action, as `overlaps`, so they can keep
169−their edits small and local where they have to touch the same files.
161+alternatives, and one will be merged. Two for *different* issues are
162+heading for a conflict, and g1t says so while the work is still going on,
163+not when the second one tries to merge. Agents get the same list from the
164+`pull_request` tool's `get` action, as `overlaps`, so they can keep their
165+edits small where they must touch the same files.
170166
171167 ## Merging
172168
173−Someone with the [Write role](/guides/access-and-roles/) or higher on the
174−repository merges a pull request once it is
175−marked ready and its [required checks](/guides/pull-requests/#required-status-checks)
176−have passed. Merging moves `main` to the pull
177−request's head commit, or, in a repository that merges through
178−[the merge queue](/guides/merge-queue/), adds it to the queue.
169+Someone with the [Write role](/guides/access-and-roles/) or higher merges
170+a pull request once it is ready and its
171+[required checks](/guides/pull-requests/#required-status-checks) have
172+passed. It lands by one of the repository's
173+[merge methods](/guides/pull-requests/#merge-methods): a merge commit, a
174+squash, a rebase or a fast-forward. In a repository that uses
175+[the merge queue](/guides/merge-queue/), merging adds it to the queue
176+instead. **Auto-merge** lands it by itself once everything it needs is met.
179177
180−`main` only moves forward to a commit that contains everything already on
181−it. If something else landed first, the pull request is **behind**, and its
182−page says so. Merging it then brings it up to date first and lands it once
183−that is done, unless the repository requires pull requests to be up to date
184−before they merge; then merging is refused until it has caught up.
178+When something else landed first, the pull request is **behind**, and its
179+page says so. Merging brings it up to date and lands it, unless the
180+repository requires pull requests to be up to date first; then merging
181+waits until it has caught up.
185182
186−Updating the branch brings it up to date. When the pull request's and `main`'s
187−changes do not clash (edits to the same file merge line by line), g1t merges
188−or rebases `main` in itself and pushes the result in a few seconds, and the
189−workflows run again on it
190−([how updating works](/guides/pull-requests/#keeping-it-up-to-date)). When they
191−changed the same lines, do it by hand: pull `main` into the fork or
192−the branch, resolve, and push. `main` never loses a commit this way, however many
193−pull requests are in flight.
183+**Update branch** brings it up to date. When the two sides' changes do
184+not clash (edits to the same file merge line by line), g1t merges or
185+rebases the base in itself in a few seconds, and the workflows run again
186+([keeping it up to date](/guides/pull-requests/#keeping-it-up-to-date)).
187+When they changed the same lines, pull the base into the branch or
188+working copy, resolve, and push. The base never loses a commit this way,
189+however many pull requests are in flight.
194190
195191 ## The merge queue
196192
197−Merging one pull request at a time keeps every merge clean as text, but two
198−changes can merge without a conflict and still break each other. A
193+Merging one pull request at a time keeps every merge clean as text, but
194+two changes can merge without a conflict and still break each other. A
199195 repository whose rules include **Require the merge queue** (**Settings →
200−Rules**) tests each pull request
201−together with the ones ahead of it, and `main` only moves to a state whose
202−required checks passed.
203−See [merge queue](/guides/merge-queue/).
196+Rules**) tests each pull request together with the ones ahead of it, and
197+the base only moves to a state whose required checks passed. When one
198+fails in combination, g1t says which pull requests and which check, so its
199+author, person or agent, can fix it. See [merge queue](/guides/merge-queue/).
204200
205201 ## Sessions and why-blame
206202
207−A session is the record of how a pull request was made: the prompt the
208−agent was given, its reasoning, the tools it called and what they returned.
209−The coding agent records it with the `pull_request` tool's `record_session`
210−action as it works.
211−Each entry is tied to the commit that was the head when it was recorded, so
212−**Blame** on any file can show not only the commit that last changed a
203+A session is one run of a coding agent: the prompts it was given, its
204+reasoning, the tools it called, and what it touched. g1t's
205+[Claude Code plugin](/guides/claude-code-plugin/) records one for every
206+Claude Code session in a g1t repository; other agents record theirs with
207+the `session` tool or `g1t session`. A session is tied to the issue it
208+claimed and the pull request it opened, and people read it on the pull
209+request's **Session** tab and on the repository's **Work** page, which
210+shows who is working on what right now. See
211+[sessions, claims and dependencies](/guides/sessions/).
212+
213+Each entry is tied to the commit that was the head when it was recorded,
214+so **Blame** on any file can show not only the commit that last changed a
213215 line, but the pull request and issue it came from and the agent's own
214216 account of the change. See [sessions and why-blame](/guides/why-blame/).
215217
216218 ## Events
217219
218220 Every state change in g1t is published as an event: a push, an issue being
219−opened, a pull request being merged, a session growing. Events are delivered
220−to the services that react to them and are kept as a timeline per
221−repository, which you can read through the [API](/reference/api/).
221+opened, a pull request being merged, a session growing. Events reach the
222+services that react to them, your [webhooks](/guides/webhooks/), and a
223+timeline per repository that you can read through the
224+[API](/reference/api/) or follow with `g1t work events --follow`.
222225 [Events](/architecture/events/) in the Architecture section shows how.
223226
224227 ## What is not built yet
225228
226−g1t is under active development. Every current limit, and why, is on
229+Every current limit, and why, is on
227230 [What g1t can't do yet](/about/limitations/).
+49−18
55
66 import AgentSetup from '../../../components/AgentSetup.astro';
77
8−g1t is where your coding agents do their work. Claude Code, Codex, Cursor,
9−OpenCode and any other harness connect to g1t the same way a person's tools
10−do, and act with the access you give them:
8+Claude Code, Codex, Cursor, OpenCode and any other harness connect to g1t
9+the way a person's tools do, and act as you, with the access you give
10+them:
1111
1212 | To | Use |
1313 | --- | --- |
1414 | Read and change anything on g1t from inside the agent | The MCP server at `https://mcp.g1t.sh`: [MCP tools](/reference/mcp/) |
15−| Push code | git over HTTPS, with an [access token](/guides/authentication/#access-tokens) |
15+| Work from the agent's shell | The [g1t CLI](/guides/cli/): `g1t pr create`, `g1t pr checks --watch`, `g1t run logs --failed` |
16+| Push code | git over HTTPS, signed in with `g1t auth setup-git` or an [access token](/guides/authentication/#access-tokens) |
1617 | Start the agent when something happens | [Webhooks](/guides/webhooks/), or a [workflow](/guides/actions/) on `issues` or `issue_comment` |
1718 | Show people how a change was made | The pull request's [session](/guides/why-blame/#sessions) |
1819 | See who is working on what, and not collide | [Sessions, claims and dependencies](/guides/sessions/) |
1920 | Script anything else | The [REST API](/reference/api/), which has the same operations as the tools |
2021
2122 The agent runs where you run it: on your machine, in your CI, or on a
22−[runner](/guides/self-hosted-runners/) of your own. g1t keeps the code,
23−the issues and pull requests, the checks and the record of what happened.
23+[runner](/guides/self-hosted-runners/) of your own, on your own model
24+account. g1t runs no agents of its own; it keeps the code, the issues and
25+pull requests, the checks and the record of what happened. To start one
26+from g1t instead, see [hand an issue to g1t](#hand-an-issue-to-g1t).
2427
2528 ## Connect your coding agent
2629
4043 [Settings → Connected applications](https://g1t.sh/settings/applications),
4144 where you can change what it may do, or sign it out.
4245
43−The agent also needs to push with git, which asks for a username and a
44−password: use your g1t username and an
45−[access token](/guides/authentication/#access-tokens) with `code:write`.
46+The agent also needs to push with git. Where the [g1t CLI](/guides/cli/)
47+is signed in, `g1t auth setup-git` makes git use the same sign-in.
48+Otherwise git asks for a username and a password: use your g1t username
49+and an [access token](/guides/authentication/#access-tokens) with
50+`code:write`.
4651
4752 Ask it to list the open issues on a repository, or to work on one, and it
4853 will use g1t's tools. It sees only the tools and actions its access allows.
6166
6267 | Choose | For an agent |
6368 | --- | --- |
64−| Scopes | The **Agent** preset: reading across g1t, and writing code, issues, pull requests and notifications. Add an admin scope only for a task that needs it. |
69+| Scopes | The **Agent** [preset](/guides/authentication/#presets), which reads across g1t and writes what an agent's work needs, and never includes an admin scope. Add one only for a task that needs it. |
6570 | Expires | The shortest that fits the work, such as 30 days. |
6671
6772 A token reaches every workspace and repository you can. To keep an agent
8287 and what it is blocked by. Then `issue` with `claim`, which also says
8388 who else is on it.
8489 2. `pull_request` with `create` and the issue's number. This opens a draft
85− pull request and returns the git remote of its fork.
86−3. Clone the fork, make changes, commit and push.
90+ pull request and returns the git remote of its [working copy](/concepts/forks/#working-copies-for-pull-requests).
91+3. Clone the working copy, make changes, commit and push.
8792 4. `pull_request` with `record_session` as it goes, so people can see its
8893 reasoning.
8994 5. `pull_request` with `get`, to check `overlaps`, `behind` and the checks.
124129 says why; push a fix and the workflows run again. Run the same tests and
125130 linters the workflows run before you push, and you will rarely need to.
126131
127−If merging reports that `main` has moved, pull `main` from the repository
128−into the fork and push. The pull request can then be merged, or joins the
129−[merge queue](/guides/merge-queue/) when the repository uses one.
132+When `main` has moved, `pull_request` with `update_branch` brings the pull
133+request up to date in a few seconds. When the changes clash, pull `main`
134+into the working copy, resolve and push. The pull request can then be
135+merged, or joins the [merge queue](/guides/merge-queue/) when the
136+repository uses one.
130137
131138 When the merge queue takes an agent's pull request out because it fails
132139 combined with the pull requests ahead of it, g1t
186193 ```
187194
188195 Its hooks record your prompts, the tools Claude uses and what it called
189−them with, and its pushes, through the [g1t CLI](/guides/cli/), and title
190−each session with what its first prompt asked for. They never send what a
196+them with, and its pushes, through the [g1t CLI](/guides/cli/), which
197+must be installed and signed in (`g1t auth login`), and title each
198+session with what its first prompt asked for. They never send what a
191199 tool returned. The plugin also gives Claude the issue's work context when
192200 a session starts (or, with no issue, when to claim or open one), g1t's MCP
193201 server, and commands to claim and hand off issues. See [Claude Code plugin](/guides/claude-code-plugin/) for
207215 issue will become a conflict for whichever merges second, so narrow the
208216 change, or say so in the pull request.
209217 - `behind`: whether `main` has moved since the pull request was made. If
210− it has, pull `main` into the fork and push before asking for a merge.
218+ it has, `update_branch` before asking for a merge.
211219
212220 Required checks, [rules](/guides/rules/) and the
213221 [merge queue](/guides/merge-queue/) hold for an agent's pull request
214222 exactly as for a person's.
215223
224+## Hand an issue to g1t
225+
226+g1t runs no model of its own, but it can start a session for you on the
227+workspace's own Anthropic account. Once an owner adds an Anthropic key
228+under [**AI**](/guides/ai/):
229+
230+- **Assign to g1t** in an issue's sidebar (or `g1t issue edit 12
231+ --add-assignee g1t`), or mention `@g1t` in a comment on an issue or pull
232+ request, with what you want done.
233+- **Start a session** on an issue's page, `g1t work dispatch 12`, or the
234+ `session` tool's `dispatch` action, to choose the mode (`plan`,
235+ `implement` or `review`).
236+
237+g1t reacts 👀, then starts the session as you, with a token for that
238+repository only that is revoked when the session ends. It installs the
239+g1t CLI, clones the repository and works the way this page describes; its
240+card shows in the issue's conversation, and Anthropic bills the account
241+for it. Without an Anthropic key, `g1t work dispatch` prints a Claude Code
242+command to run yourself instead. See
243+[Assign g1t](/guides/sessions/#assign-g1t-or-ask-it-in-the-thread) and
244+[Start a session on your Anthropic account](/guides/sessions/#start-a-session-on-your-anthropic-account).
245+
216246 ## Start an agent when something happens
217247
218248 To run your agent without anyone starting it by hand, react to g1t's
309339 | `access` | Who has which role on a repository, and deploy keys |
310340 | `team` | Teams and their members and repositories |
311341 | `workspace` | Workspaces, members, integrations, projects, rulesets and token rules |
342+| `ai` | The workspace's AI providers, and what Ask answers with |
312343 | `billing` | Usage, budget and invoices |
313344 | `notifications` | Your inbox |
314345 | `account` | Who the token acts as, and your invitations |
+6−3
6060
6161 The plugin works in any working copy whose git remote is on g1t.sh, or on
6262 the host in `G1T_HOST` for a self-hosted g1t. Everywhere else its hooks do
63−nothing.
63+nothing. A session that starts in a folder with no g1t remote is recorded
64+once it has one: from the next prompt, or the `git remote`, `git push` or
65+`g1t repo create` command that added it, titled by its first prompt.
6466
6567 ### Update
6668
198200 the parent's session as `--parent`, its kind (`Explore`, your own
199201 subagents' names) and the task Claude gave it: the description and
200202 prompt of the Agent call, kept from the moment Claude made it
201− (`PreToolUse`). The subagent is told it has a session of its own, to
203+ (`PreToolUse`) and matched to the subagent by the call's id or its
204+ prompt, so subagents started together each get their own. The subagent is told it has a session of its own, to
202205 look at its siblings with `g1t session list --issue N --subagents`
203206 before changing anything, and to claim only what it takes.
204207 - **While it works**, what it does is recorded in its own session, not its
269272 out of date, and said the same way.
270273
271274 A CLI older than 0.3.3 records sessions but cannot acknowledge
272−[coordination updates](#working-on-g1t). The hooks then show
275+[coordination updates](/guides/sessions/#pending-coordination-updates). The hooks then show
273276 pending updates only when they change, not before every prompt, and ask
274277 Claude to tell you once to update the CLI.
275278
+29−5
178178 Commands about a repository work on the one the current directory is a
179179 clone of, found from its git remotes: `upstream` if it is on g1t, then
180180 `origin`, then the first one that is. In a pull request's own [working
181−copy](/guides/pull-requests/), they work on the repository the pull request
181+copy](/concepts/forks/#working-copies-for-pull-requests), they work on the repository the pull request
182182 is for.
183183
184184 Anywhere else, or to choose another, pass `--repo` (`-R`), or set
191191
192192 ## Everyday work
193193
194+### Repositories
195+
196+```sh
197+g1t repo create my-project --source . --remote g1t # make it, add it as a remote, push
198+g1t repo create acme/web --public --clone
199+g1t repo list acme
200+g1t repo view acme/web --web
201+g1t repo clone acme/web
202+g1t repo fork acme/web --clone
203+g1t repo sync # bring your fork's default branch up to date
204+```
205+
206+A repository is private unless `--public` says otherwise, and is made in
207+your own workspace unless its name says `WORKSPACE/NAME`. `--source`
208+adds it as a remote of a local repository (`origin` unless `--remote`
209+names another) and pushes its current branch. `g1t repo fork` forks into
210+your own workspace, or `--workspace`; see
211+[forking a repository](/guides/forking-repositories/).
212+
194213 ### Issues
195214
196215 ```sh
221240 the branch's commits; `--issue` says which issue it is for; `--draft` opens
222241 it as a draft.
223242
224−To start a pull request for an issue without a branch of your own, as g1t's
225−agents do, run it with `--issue` from the default branch. g1t opens a draft
226−with a [working copy](/guides/pull-requests/) of its own and prints the
243+To start a pull request for an issue without a branch of your own, as
244+coding agents usually do, run it with `--issue` from the default branch. g1t opens a draft
245+with a [working copy](/concepts/forks/#working-copies-for-pull-requests) of its own and prints the
227246 copy's git remote. Push your commits there, then mark it ready with a
228247 summary of what changed and why:
229248
368387
369388 | Command | What it does |
370389 | --- | --- |
371−| `g1t session start` | Starts a session and prints its id. `--harness` (`claude_code`, `codex`, `cursor`, `copilot` or `other`; `claude-code` works too; `claude_managed_agents` is only for the sessions g1t starts itself, with `g1t work dispatch`), `--harness-session-id`, `--issue`, `--pull`, `--branch` (the current one by default) and `--title`. Starting again with the same `--harness-session-id` returns the same session. For a subagent, `--parent` (the session that started it), `--agent-type`, `--task`, `--prompt` and `--model`; a subagent's session is not kept as the working copy's (nor is one with `--no-remember`). |
390+| `g1t session start` | Starts a session and prints its id. `--harness` (`claude_code`, `codex`, `cursor`, `copilot` or `other`; `claude-code` works too), `--harness-session-id`, `--issue`, `--pull`, `--branch` (the current one by default) and `--title`. Starting again with the same `--harness-session-id` returns the same session. For a subagent, `--parent` (the session that started it), `--agent-type`, `--task`, `--prompt` and `--model`; a subagent's session is not kept as the working copy's (nor is one with `--no-remember`). |
372391 | `g1t session heartbeat` | Says the session is still working, and renews its claims. A session with no heartbeat for 15 minutes reads as idle, and after 2 hours as ended. |
373392 | `g1t session log TEXT` | Records a note. `--kind` takes `note`, `prompt`, `message`, `tool_call` (or `tool-call`) or `skill` (a skill's name), `--tool` names the tool, and `--key` makes recording the same entry twice do nothing. `-` reads the text from stdin; put `--` before text that starts with a dash. |
374393 | `g1t session attach` | Points the session at an `--issue`, a `--pull`, a `--branch` or its latest `--head-commit`, or gives it a `--title`: what it is for, which replaces the title it had. |
433452 `--managed` fails rather than hand off. `--mode` is `plan`, `implement` or
434453 `review`. See [Start a session on your Anthropic account](/guides/sessions/#start-a-session-on-your-anthropic-account).
435454
455+`g1t issue edit 12 --add-assignee g1t` does the same as **Assign to g1t**
456+on the site: g1t starts a session on the issue on the workspace's
457+Anthropic account, as you. See
458+[Assign g1t](/guides/sessions/#assign-g1t-or-ask-it-in-the-thread).
459+
436460 ### Dependencies
437461
438462 ```sh
+26−0
314314 | Workflow run | A [workflow](/guides/actions/) run that is queued, waiting, running, or waiting for approval, in the ten projects pushed to most in the span (their newest 20 runs each) |
315315 | Build | A project's newest build, queued or building |
316316
317+## While the page is open
318+
319+Home keeps up with the workspace without a reload. What lands, starts
320+running, finishes or comes to need you while you are on the page joins its
321+list, sliding in at its place and lit for a moment; counts and the
322+sentence under the heading update with it. Runs that finish leave Running
323+now.
324+
325+| Home updates when | From |
326+| --- | --- |
327+| Commits are pushed to a project's default branch, or a tag is pushed | The workspace's projects (the first 25) |
328+| A pull request is opened, reviewed, merged or closed, or an issue or comment changes | The same projects |
329+| A workflow run or a job in it starts or finishes, a build or deployment moves, or a release or package is published | The same projects |
330+| A [session](/guides/sessions/) starts or ends | The same projects |
331+| Something comes to need you, or you deal with it elsewhere | Your notifications |
332+
333+A busy workspace sends changes all the time, so Home refreshes a second
334+after the last one and at most every five seconds. It works like the
335+[pages that update live](/guides/notifications/#pages-that-update-live):
336+never while you are typing in Ask or have a dialog open, and a tab out of
337+view catches up when you come back to it. Pushes to other branches and a
338+commit's checks don't refresh Home, because it doesn't show them. Spend
339+updates with the rest but changes only as usage is metered.
340+
341+Refreshing in place keeps the same span: it is the same visit.
342+
317343 ## Without Code access
318344
319345 A member whose membership does not include Code sees Home without anything
+1−0
207207 | An **issue** | Someone comments on it or changes it, or a pull request linked to it moves. |
208208 | **Actions** and a **run** | A run starts, one of its jobs starts or finishes, or the run finishes. A running job's log streams on its own. |
209209 | **Security** | An alert is opened, fixed, dismissed or reopened. |
210+| Your workspace's **Home** | Something lands, runs, deploys or comes to need you in its projects (the first 25). See [Home](/guides/home/#while-the-page-is-open). |
210211
211212 A page refreshes a second after the last change it shows, so a burst of
212213 them is one refresh. It never refreshes while you are typing in it or have
+2−2
599599 git push origin my-branch
600600 ```
601601
602− For a pull request in its own fork, clone the fork and pull `main` into
602+ For a pull request in its own [working copy](/concepts/forks/#working-copies-for-pull-requests), clone the working copy and pull `main` into
603603 it instead:
604604
605605 ```sh
633633 is behind; otherwise it shows it when the repository suggests updating
634634 branches (on by default), and merging brings the branch up to date by
635635 itself. Whoever can push to the pull request (whoever opened it, for one
636−in its own fork; anyone with the Write [role](/guides/access-and-roles/)
636+in its own working copy; anyone with the Write [role](/guides/access-and-roles/)
637637 or higher, for a branch of the repository; whoever can push to the fork,
638638 for a branch of a fork) can update it, two ways:
639639
+3−3
6969 ## Private vulnerability reporting
7070
7171 A repository takes private reports when **Private vulnerability reporting**
72−is on: a switch on the Security overview's Reporting rows and in
73−[Security settings](/guides/security/#settings), for Admins and security
74−managers. Off by default.
72+is on: a switch on the [Security overview](/guides/security/#the-overview)'s
73+Reporting rows and on the Security section's **Settings** page, for Admins
74+and security managers. Off by default.
7575
7676 With it on, **Report a vulnerability** is on the repository's Security tab
7777 for anyone who can see it. Someone signed in reports under their name; on a
+2−3
11 ---
22 title: Self-hosted runners
3−description: Run your workflow jobs, and if you choose g1t's checks and merge queue, on your own machines. Their time costs nothing.
3+description: Run your workflow jobs, and if you choose g1t's own checks, on your own machines. Their time costs nothing.
44 ---
55
66 import { TabItem, Tabs } from '@astrojs/starlight/components';
721721 ## g1t's work on your runners
722722
723723 **Runners → Where work runs** can send g1t's own work to your runners
724−too: the checks g1t runs on pull requests, merge checks and the merge
725−queue's test runs. Switch on **Run g1t's work on self-hosted runners** and
724+too: the checks g1t runs on pull requests and merge checks. Switch on **Run g1t's work on self-hosted runners** and
726725 give the labels a runner needs to take it (`self-hosted` is always one).
727726
728727 - The work runs exactly as in g1t's sandbox: the same harness, with a
+62−47
11 ---
22 title: What is g1t?
3−description: g1t is the code platform your team and your own coding agents work on. Repositories, pull requests, Actions and Deployments, with Chat, Artifacts and a front page for the day, and an MCP server, API and webhooks for the agents you bring. In alpha, with every feature marked Live, Preview or Coming.
3+description: g1t is the code platform for your team and the coding agents you bring. Repositories, pull requests, checks, the merge queue, Actions and Deployments; Chat for your team; and an MCP server, CLI, API and webhooks for your agents. In alpha, with every feature marked Live, Preview or Coming.
44 tableOfContents: false
55 prev: false
66 ---
1111 import LinkCard from '../../components/LinkCard.astro';
1212 import Status from '../../components/Status.astro';
1313
14−g1t is the code platform your team and your own coding agents work on.
1514 **Code** holds your repositories, issues and pull requests, with required
16−checks, the merge queue and Actions; **Deployments** puts every pull request
17−live on a preview and ships production on merge. Your team talks in
18−**Chat**, writes docs together in **Artifacts**, and **Home** tells you
19−where you're needed and what happened while you were away.
15+checks, the merge queue and Actions. **Deployments** puts every pull
16+request live on a preview and ships production on merge.
17+
18+**Chat** is where your team talks: channels, direct messages and threads,
19+free on every plan. **Home** shows where you're needed and what happened
20+while you were away, and **Artifacts** holds the docs you write together.
2021
21−Bring the coding agents you already use, such as Claude Code, Codex,
22−Cursor or OpenCode. They connect over [MCP](/guides/bring-your-own-agent/)
23−and work as you: they pick up issues, open pull requests, run your checks,
24−and record their sessions and the decisions behind each change.
22+**Your coding agents** work here as you. Claude Code, Codex, Cursor,
23+OpenCode or any MCP client connects once, then picks up issues, opens pull
24+requests, waits on your checks and records how it made each change. g1t
25+runs no agents of its own: the work runs in your harness, on your account.
26+With an Anthropic key in the workspace, assigning an issue to `g1t` starts
27+a session on that account for you.
2528
26−g1t is MIT-licensed. You can use it on g1t.sh, or run it yourself.
29+g1t is MIT-licensed, and its source is at
30+[g1t.sh/flagon-io/g1t](https://g1t.sh/flagon-io/g1t). Use it on g1t.sh,
31+or run it yourself.
2732
2833 <Aside type="note" title="g1t is in alpha">
29−Accounts are by invite while the alpha runs. Every feature on these pages
30−is marked with where it stands:
34+Anyone can sign up. Every feature on these pages is marked with where it
35+stands:
3136
3237 - <Status is="live" /> works on g1t.sh for every workspace.
3338 - <Status is="preview" /> works on g1t.sh, but is limited to some workspaces
4247 <LinkCard
4348 title="Quickstart"
4449 icon="rocket"
45− description="Create a workspace, push a repository and connect your coding agent in a few minutes."
50+ description="Sign up, push a repository, and have your coding agent open a pull request you merge, in a few minutes."
4651 href="/quickstart/"
4752 />
4853 <LinkCard
5358 />
5459 <LinkCard
5560 title="Build and ship an app with an agent"
56− icon="rocket"
61+ icon="ship"
5762 description="Your agent creates the repository, pushes the app and merges its pull requests. You turn deployments on once, and every merge ships."
5863 href="/guides/build-and-ship/"
5964 />
6570 />
6671 </CardGrid>
6772
68−## The workspace
69−
70−Everything happens in a workspace: your company, or one team in it. The
71−rail down the left holds Home, Chat, Notifications, Code and Artifacts, the apps you pin, and People, Workspace and your account at its
72−foot. On a phone it is a bar along the bottom.
73−
74−| Feature | What it does | Status |
75−| --- | --- | --- |
76−| [Home](/guides/home/) | The front page: where you're needed now, what happened since you were last here, what is running, and where to start. | <Status is="preview" /> |
77−| [Chat](/guides/chat/) | Channels, direct messages and threads, live. No seats and no history limit. | <Status is="live" /> |
78−| [Notifications](/guides/notifications/) | Everything waiting on you: mentions, reviews, approvals and alerts, with browser push. | <Status is="live" /> |
79−| [People and teams](/guides/people-and-teams/) | Everyone in the workspace in one search, with profiles and teams. Members, roles, nested teams and invites. | <Status is="live" /> |
80−| [Artifacts](/guides/artifacts/) | Documents written together, live, by your team and the coding agents you connect. Private until you share them. | <Status is="live" /> |
81−| More kinds of artifact | Slides, designs and dashboards that keep the query behind every number. | <Status is="coming" /> |
82−| Scratchpads | Tools and reports anyone can build on a copy of the data, used in production only after review. | <Status is="coming" /> |
83−| [The desktop app](/guides/desktop/) | g1t in a window of its own for Windows, macOS and Linux, with notifications from your system. | <Status is="preview" /> |
84−| [The phone app](/guides/mobile/) | Chat with your team, live, with notifications on the phone; review pull requests. Android first, for g1t.sh or your own g1t; not in the stores yet. | <Status is="preview" /> |
85−| [A runner in the desktop app](/guides/desktop/#the-runner) | Your workspace's self-hosted jobs on your computer while g1t is open, registered and kept up to date by the app, sandboxed by default: Linux jobs in a sandbox of the runner's own on Windows, macOS and Linux, and Windows jobs in Windows Sandbox on Windows 11 Pro and up. | <Status is="preview" /> |
86−
8773 ## Code and Deployments
8874
8975 | Feature | What it does | Status |
9076 | --- | --- | --- |
91−| [Repositories](/guides/managing-repositories/) and [git](/guides/git/) | Public and private repositories over HTTPS, with creating one by pushing to it. | <Status is="live" /> |
92−| [Issues and pull requests](/guides/pull-requests/) | Issues, pull requests, line comments and reviews. | <Status is="live" /> |
77+| [Repositories](/guides/managing-repositories/) and [git](/guides/git/) | Public and private repositories over HTTPS. Pushing to a repository that does not exist creates it, private. | <Status is="live" /> |
78+| [Issues and pull requests](/guides/pull-requests/) | Issues, pull requests, line comments, reviews, merge methods and auto-merge. | <Status is="live" /> |
9379 | [Checks](/guides/checks/), [rules](/guides/rules/) and the [merge queue](/guides/merge-queue/) | Required checks, protected branches, code owners, and changes tested together before they land. | <Status is="live" /> |
9480 | [Workflows](/guides/actions/) | Workflows from `.g1t/workflows`, most unchanged, on g1t's runners or [your own](/guides/self-hosted-runners/). | <Status is="live" /> |
95−| [Connect a coding agent](/guides/bring-your-own-agent/) | Any coding agent or MCP client works on g1t as you and records its session onto its pull requests. | <Status is="live" /> |
81+| [Forks](/guides/forking-repositories/) | Fork a repository into your workspace, keep it in sync, and propose changes back. | <Status is="live" /> |
9682 | [Packages](/guides/packages/) | npm, Cargo, Composer, Maven, NuGet, RubyGems, Go modules and container images. | <Status is="live" /> |
9783 | [Security](/guides/security/) | Secret push protection, history scanning and pull requests that upgrade vulnerable dependencies. | <Status is="live" /> |
9884 | [Deployments](/guides/deployments/) | A live preview of every pull request on g1t.page, production on merge, and custom domains. | <Status is="live" /> |
99−| Preview data | Each preview gets its own copy of the database and bucket, deleted when the pull request closes. | <Status is="coming" /> |
10085 | [Docker deployments](/guides/deployments/#docker-deployments) | Any app with a Dockerfile, or a Node.js, Python, Go, Ruby or PHP server, runs as a container that sleeps when no one visits. | <Status is="live" /> |
10186 | [Observability](/guides/observability/) | Requests, logs, traces, metrics and grouped errors, by deployment: from apps on g1t.page with nothing to set up, and from anywhere else over OpenTelemetry. | <Status is="live" /> |
87+| [GitHub](/guides/github/) | Import repositories with every branch and tag, and their issues. | <Status is="live" /> |
88+| Preview data | Each preview gets its own copy of the database and bucket, deleted when the pull request closes. | <Status is="coming" /> |
10289 | Rollback and always on | One click back to any deploy, and container apps kept warm. | <Status is="coming" /> |
103−| [GitHub](/guides/github/) | Import repositories with every branch and tag, and their issues. | <Status is="live" /> |
10490 | Git over SSH | `git@g1t.sh` remotes. Use HTTPS until then. | <Status is="coming" /> |
10591
92+## Coding agents
93+
94+| Feature | What it does | Status |
95+| --- | --- | --- |
96+| [Connect a coding agent](/guides/bring-your-own-agent/) | Claude Code, Codex, Cursor, OpenCode or any MCP client signs in once and works on g1t as you, with the access you give it. | <Status is="live" /> |
97+| [The Claude Code plugin](/guides/claude-code-plugin/) | Records every Claude Code session in a g1t repository, gives Claude the issue's context, and adds g1t's MCP server, skills and commands. | <Status is="live" /> |
98+| [The g1t CLI](/guides/cli/) | Issues, pull requests, checks, runs, workflows, secrets and logs from a terminal, a script or an agent's shell. | <Status is="live" /> |
99+| [Sessions and claims](/guides/sessions/) | Who is working on what, right now: each agent's session, the issue it claimed, and the files it shares with others. | <Status is="live" /> |
100+| [Assign g1t](/guides/sessions/#assign-g1t-or-ask-it-in-the-thread) | Assign an issue to `g1t`, or mention `@g1t` in a thread, to start a session on the workspace's own Anthropic account. | <Status is="live" /> |
101+| [Why-blame](/guides/why-blame/) | Any line traced to its commit, pull request, issue, and the agent's own account of why it changed. | <Status is="live" /> |
102+
103+## The workspace
104+
105+Everything happens in a workspace: your company, or one team in it. The
106+rail down the left holds Home, Chat, Notifications, Code and Artifacts,
107+the apps you pin, and People, Workspace and your account at its foot. On
108+a phone it is a bar along the bottom.
109+
110+| Feature | What it does | Status |
111+| --- | --- | --- |
112+| [Chat](/guides/chat/) | Channels, direct messages and threads, live, and public channels anyone signed in to g1t can join. No seats and no history limit. | <Status is="live" /> |
113+| [Home](/guides/home/) | The front page: where you're needed now, what happened since you were last here, what is running, and where to start. | <Status is="live" /> |
114+| [Ask g1t](/guides/ask/) | Questions about your workspace, answered on the workspace's own AI provider, from Home, the Ask panel or a direct message to `@g1t`. | <Status is="live" /> |
115+| [Notifications](/guides/notifications/) | Everything waiting on you: mentions, reviews, approvals and alerts, with browser push. | <Status is="live" /> |
116+| [People and teams](/guides/people-and-teams/) | Everyone in the workspace in one search, with profiles and teams. Members, roles, nested teams and invites. | <Status is="live" /> |
117+| [Artifacts](/guides/artifacts/) | Documents written together, live, by your team and the coding agents you connect. Private until you share them. | <Status is="live" /> |
118+| [The desktop app](/guides/desktop/) | g1t in a window of its own for Windows, macOS and Linux, with notifications from your system. Unsigned during the alpha. | <Status is="preview" /> |
119+| [A runner in the desktop app](/guides/desktop/#the-runner) | Your workspace's self-hosted jobs on your computer while g1t is open, sandboxed by default. Checked end to end on Windows; built the same way, not yet run, on macOS and Linux. | <Status is="preview" /> |
120+| [The phone app](/guides/mobile/) | Chat with your team, live, with notifications on the phone; review pull requests. Android first, not in the stores yet. | <Status is="preview" /> |
121+| More kinds of artifact | Slides, designs and dashboards that keep the query behind every number. | <Status is="coming" /> |
122+
106123 ## Extend and connect
107124
108125 | Feature | What it does | Status |
109126 | --- | --- | --- |
110−| [Integrations](/guides/integrations/) | The GitHub App, Sentry, Datadog, Jira, Linear and alerts. | <Status is="live" /> |
111127 | [API](/reference/api/), [MCP](/reference/mcp/) and [webhooks](/guides/webhooks/) | Everything as a REST endpoint and an MCP tool, and a webhook for every event. | <Status is="live" /> |
128+| [Integrations](/guides/integrations/) | The GitHub App, Sentry, Datadog, Jira, Linear and alerts. | <Status is="live" /> |
112129 | More integrations | Drive, Gmail, Calendar, Slack and Microsoft 365. | <Status is="coming" /> |
113−| Mail | Email on your own domain, with shared inboxes. | <Status is="coming" /> |
114130
115−## Your workspace
131+## Your account and workspace
116132
117133 | Feature | What it does | Status |
118134 | --- | --- | --- |
119−| [Accounts and sign-in](/guides/authentication/) | Invites, two-factor authentication, access tokens and OAuth apps. | <Status is="live" /> |
120−| Single sign-on | SAML or OIDC and SCIM, set up by each workspace for itself. | <Status is="coming" /> |
121−| [Usage and billing](/guides/usage-and-billing/) | No seats. Usage at what it costs, a spend limit, and itemised invoices. | <Status is="live" /> |
122−| Storage | Databases and buckets your teams can create, at a level each team is given, with copies of production data for anything not yet reviewed. | <Status is="coming" /> |
135+| [Accounts and sign-in](/guides/authentication/) | Open sign-up with an email address or GitHub, two-factor authentication, access tokens and OAuth apps. | <Status is="live" /> |
136+| [Usage and billing](/guides/usage-and-billing/) | No seats. The forge and Chat are free; compute at what it costs, a spend limit, and itemised invoices. | <Status is="live" /> |
123137 | [Audit log](/guides/audit-log/) | Every change to the workspace, with export. | <Status is="live" /> |
124−| [Run g1t yourself](/guides/self-hosting/) | The core forge in Docker. Deployments are off in this version. | <Status is="preview" /> |
138+| [Run g1t yourself](/guides/self-hosting/) | The core forge in Docker. Hosted runners, deployments and billing are off in this version. | <Status is="preview" /> |
125139 | [Architecture](/architecture/overview/) | How g1t is built, service by service, for developers building on it, contributing to it or running it. | <Status is="live" /> |
140+| Single sign-on | SAML or OIDC and SCIM, set up by each workspace for itself. | <Status is="coming" /> |
126141
127142 ## Bring a repository
128143
+75−43
11 ---
22 title: Quickstart
3−description: Create a workspace, push a repository, open an issue and have your own coding agent work on it. Then talk to your team in Chat.
3+description: Sign up, push a repository, open an issue, and have your own coding agent open a pull request that you merge. Then bring your team into Chat.
44 ---
55
66 import { Steps, Tabs, TabItem } from '@astrojs/starlight/components';
77 import Aside from '../../components/Aside.astro';
88 import AgentSetup from '../../components/AgentSetup.astro';
99
10−A g1t workspace holds your repositories, your team's conversations and
11−everything you ship. This page gets you from sign-up to a pull request your
12−own coding agent opened and you merged, in a few minutes.
10+This page takes you from sign-up to a pull request your coding agent
11+opened and you merged. It takes a few minutes.
1312
1413 <Aside type="tip" title="Let an assistant do it">
1514 Give your AI assistant [g1t.sh/llms.txt](https://g1t.sh/llms.txt) and ask it
1716 and approve it. It never sees your password.
1817 </Aside>
1918
20−## Put your coding agent on your code
19+<Steps>
2120
22−<Steps>
21+1. **Sign up**
2322
24−1. **Create an account and a workspace**
23+ [Sign up](https://g1t.sh/register) with an email address and a
24+ password, or with GitHub. Anyone can; no invite is needed. Confirm your
25+ email with the code g1t sends (or the link in the same email).
2526
26− [Sign up](https://g1t.sh/register) (or open the invite link someone sent
27− you; see [invites](/guides/authentication/#invites)) and confirm your
28− email with the code g1t sends it (or the link in the same email). Then create a
29− **workspace**: it holds your repositories and your channels. See
27+ Your account comes with a **workspace** of its own, named after your
28+ username. It holds your repositories and your channels. See
3029 [workspaces](/guides/workspaces/).
31−
32−2. **Create an access token**
3330
34− Git and the API authenticate with a token. Create one in
35− [Settings → Access tokens](https://g1t.sh/settings/tokens) and copy it: it is shown once.
31+2. **Install the CLI and sign in**
3632
3733 <Tabs syncKey="os">
3834 <TabItem label="macOS and Linux">
3935 ```sh
40− export G1T_TOKEN=g1t_…
36+ curl -fsSL https://g1t.sh/install | sh
4137 ```
4238 </TabItem>
4339 <TabItem label="Windows">
4440 ```powershell
45− $env:G1T_TOKEN = "g1t_…"
41+ irm https://g1t.sh/install.ps1 | iex
4642 ```
4743 </TabItem>
4844 </Tabs>
4945
46+ ```sh
47+ g1t auth login --git
48+ ```
49+
50+ `g1t` shows a code and opens your browser: check the code and approve.
51+ `--git` also sets git up to use the same sign-in for `g1t.sh`, so
52+ `git push` asks for nothing. `g1t auth status` shows who you are signed
53+ in as. See [the g1t CLI](/guides/cli/).
54+
55+ <Aside title="Without the CLI">
56+ Git and the API also take an [access token](/guides/authentication/#access-tokens):
57+ create one in [Settings → Access tokens](https://g1t.sh/settings/tokens),
58+ and give it to git as the password, with your username.
59+ </Aside>
60+
5061 3. **Push a repository**
5162
52− Pushing to a repository that does not exist creates it, as a **private**
53− repository; make it public later if you want to. When git asks, give it your username and, as the password,
54− your token.
63+ From a git repository with at least one commit:
64+
65+ ```sh
66+ g1t repo create my-project --source . --remote g1t
67+ ```
68+
69+ This makes `<workspace>/my-project`, private, adds it as the remote
70+ `g1t` and pushes. Plain git works too: pushing to a repository that does
71+ not exist creates it, private.
5572
5673 ```sh
5774 git remote add g1t https://g1t.sh/<workspace>/my-project.git
5875 git push -u g1t main
5976 ```
6077
61− For a private repository, create it first with **New** in the header,
62− which also makes one empty, or imported from GitHub or any git host by
63− its address.
78+ **New** in the site's header makes one empty, or imports one from
79+ GitHub or any git host by its address.
6480
6581 <Aside title="Coming from GitHub?">
6682 Rename `.github` to `.g1t` before you push, and your GitHub Actions
67− workflows run on g1t, most without changes. See
68− [GitHub Actions](/guides/actions/), and
83+ workflows run on g1t, most without changes. Workflows are compute: on a
84+ free workspace, an owner checks a card once under **Settings → Billing
85+ and plans** to start a $5 trial, which is never charged for the check.
86+ See [GitHub Actions](/guides/actions/),
87+ [No card, no compute](/guides/usage-and-billing/#no-card-no-compute) and
6988 [what is not supported yet](/about/limitations/#actions-and-runners).
7089 </Aside>
7190
7291 4. **Open an issue**
7392
74− On the repository's **Issues** page, choose **New issue**. Say what is
75− wrong or wanted in the title and description; your agent works from that.
76− Say what done means, if you know it. What a pull request must pass to
77− merge is your workflows' checks: see
78− [required status checks](/guides/pull-requests/#required-status-checks).
93+ On the repository's **Issues** page, choose **New issue**, or:
94+
95+ ```sh
96+ g1t issue create -t "Greeting should name the caller" -b "Take a name from the first argument."
97+ ```
98+
99+ Say what is wrong or wanted, and what done means if you know it. Your
100+ agent works from this text.
79101
80102 5. **Connect your coding agent**
81103
82− Connect Claude Code, Codex, Cursor, OpenCode or any MCP client to g1t:
83−
84104 <AgentSetup />
85105
86106 Approve in your browser when it asks. Then ask:
88108 > Work on issue 1 of `<workspace>/my-project` on g1t. Open a pull request
89109 > for it and record your session as you go.
90110
91− It opens a pull request, pushes its change, and your workflows run on it
92− as on anyone's. See [Connect a coding agent](/guides/bring-your-own-agent/).
111+ It opens a pull request, pushes its change, and your
112+ workflows run on it as on anyone's. See
113+ [Connect a coding agent](/guides/bring-your-own-agent/).
93114
94−6. **Merge**
115+ <Aside title="Or assign it to g1t">
116+ With an Anthropic key under the workspace's [**AI**](/guides/ai/)
117+ settings, **Assign to g1t** in the issue's sidebar starts a session on
118+ that account, as you, and its card shows in the issue's conversation.
119+ See [Assign g1t](/guides/sessions/#assign-g1t-or-ask-it-in-the-thread).
120+ </Aside>
95121
96− Open the pull request, read **Changes** and **Session**, and choose
97− **Merge**, or turn on auto-merge in the repository's settings and let g1t
98− land what passes. The issue closes, recording which pull request resolved
99− it.
122+6. **Review and merge**
100123
124+ Open the pull request. **Files changed** shows what it did, and
125+ **Session** shows how: its prompts, reasoning and tool calls. Choose
126+ **Merge**, or turn on **auto-merge** to have it land once its required
127+ checks pass. The issue closes, recording which pull request resolved it.
128+
101129 </Steps>
102130
103−## Talk to your team
131+## Bring your team
104132
105−Choose **Chat** in the rail, or go to `g1t.sh/<workspace>/-/chat`. Every
106−workspace starts with `#general`. Chat is free on every plan. See
133+Choose **Chat** in the rail. Every workspace starts with `#general`, and
134+Chat is free on every plan. Adding people to a workspace comes with
135+[the g1t plan](/guides/usage-and-billing/#the-g1t-plan); on a free one,
136+[invite a friend](/guides/authentication/#invite-a-friend) to g1t instead,
137+or open a [public channel](/guides/chat/#public-channels) that anyone signed in to g1t can join. See
107138 [Chat](/guides/chat/).
108139
109140 ## Next
110141
142+- [Build and ship an app with an agent](/guides/build-and-ship/): from an empty folder to a deployed app.
111143 - [How g1t works](/concepts/overview/): issues, pull requests, checks, review and merging.
112144 - [Connect a coding agent](/guides/bring-your-own-agent/): access, repository instructions and recording sessions.
113−- [GitHub Actions](/guides/actions/): your workflows, unchanged.
145+- [GitHub Actions](/guides/actions/): your workflows, most unchanged.
114146 - [MCP tools](/reference/mcp/) and the [API](/reference/api/): everything above, from code.
+16−2
5454 return slug.startsWith('reference/') ? TABS[TABS.length - 1] : tabForGroup(group.label ?? '');
5555 }
5656
57+/**
58+ * A tab's groups in the tab's own order (`groups` in tabs.ts), so a tab
59+ * opens on, and its sidebar starts with, the group it is named for: Code
60+ * on Landing changes, not on Security, which comes first in the sidebar.
61+ * The API reference tab names no groups and keeps the sidebar's order.
62+ */
63+function inTabOrder(groups: Entry[], tab: Tab): Entry[] {
64+ const rank = (group: Entry) => {
65+ const index = tab.groups.indexOf(group.label ?? '');
66+ return index === -1 ? tab.groups.length : index;
67+ };
68+ return groups.filter((group) => tabOf(group) === tab).sort((a, b) => rank(a) - rank(b));
69+}
70+
5771 /** Whether the current page is in these entries, at any depth. */
5872 function holdsCurrent(entries: Entry[]): boolean {
5973 return entries.some((entry) => entry.isCurrent || holdsCurrent(entry.entries ?? []));
7488 const tab = holder ? tabOf(holder) : TABS[0];
7589 // A tab opens on the first page of its first group; the first tab, on the home page.
7690 const tabs = TABS.map((each) => {
77− const group = each === TABS[0] ? undefined : groups.find((entry) => tabOf(entry) === each);
91+ const group = each === TABS[0] ? undefined : inTabOrder(groups, each)[0];
7892 const slug = group ? firstSlug(group.entries ?? []) : undefined;
7993 return { label: each.label, href: slug === undefined ? '/' : `/${slug}/`, current: each === tab };
8094 });
8195 NAV.set(route, { tabs, section: section ?? (route.id === '' ? undefined : tab.label) });
82− route.sidebar = route.sidebar.filter((entry) => entry.type === 'group' && tabOf(entry as Entry) === tab);
96+ route.sidebar = inTabOrder(groups, tab) as typeof route.sidebar;
8397
8498 const { title, description } = route.entry.data;
8599 const card = new URL('/docs', OG);
+24−0
280280 display: block;
281281 overflow-x: auto;
282282 }
283+ /* A shadow at an edge with more columns past it, so a cut-off column
284+ reads as "scroll for more". The covers scroll with the table and hide
285+ the shadows at its ends. */
286+ .sl-markdown-content table:not(:has(.status)) {
287+ --cover: var(--g1t-bg);
288+ /* On the dark page a shadow is a faint light, as a dark one would not show. */
289+ --shade: rgb(255 255 255 / 0.12);
290+ background:
291+ linear-gradient(to right, var(--cover) 30%, transparent) left / 2.5rem 100% no-repeat local,
292+ linear-gradient(to left, var(--cover) 30%, transparent) right / 2.5rem 100% no-repeat local,
293+ radial-gradient(farthest-side at 0 50%, var(--shade), transparent) left / 0.875rem 100% no-repeat scroll,
294+ radial-gradient(farthest-side at 100% 50%, var(--shade), transparent) right / 0.875rem 100% no-repeat scroll;
295+ }
296+ :root[data-theme='light'] .sl-markdown-content table:not(:has(.status)) {
297+ --shade: rgb(24 24 27 / 0.16);
298+ }
283299 }
284300 /*
285301 * There, a name, route or value in a table is never broken, not even at a
345361
346362 /* --- On this page --------------------------------------------------------- */
347363
364+/* On a phone the list drops over the page: give it an edge and a shadow, so
365+ where it stops is clear rather than its last row running into the text. */
366+mobile-starlight-toc .dropdown {
367+ border-color: var(--g1t-line);
368+ border-bottom-color: var(--g1t-line-strong);
369+ box-shadow: var(--g1t-docs-menu-shadow);
370+}
371+
348372 .right-sidebar-panel {
349373 padding-top: 2.75rem;
350374 }
+1−1
6666 // 45 minutes, then every 6 hours). Empty sends none.
6767 "STATUS_ALERT_EMAIL": "hey@flagon.io",
6868 "STATUS_FROM": "g1t status <noreply@g1t.sh>",
69− "OG_IMAGE": "https://og.g1t.sh/image?path=%2Fstatus&v=2"
69+ "OG_IMAGE": "https://og.g1t.sh/image?path=%2Fstatus&v=3"
7070 },
7171 // Asking for a subscription sends an email: limited per client address
7272 // and per email address (src/index.ts). Ids and limits: RATE_LIMITS in
+10−1
6060 --color-danger: light-dark(var(--g1t-light-danger), var(--g1t-danger));
6161
6262 --animate-fade-up: fade-up 0.6s ease-out both;
63+ /* A row that arrived on a live page (Home): in from above, lit for a moment. */
64+ --animate-arrive: g1t-arrive 0.42s cubic-bezier(0.16, 1, 0.3, 1), g1t-fresh 2s ease-out;
6365 --animate-dash: dash 1.2s linear infinite;
6466
6567 /* Menus, popovers and dialogs (components/ui): in quickly, out quicker. */
138140 }
139141 }
140142
141−/* A row that just arrived on a live page (Observability's logs). */
143+/* A row that just arrived on a live page (Observability's logs, Home). */
142144 @keyframes g1t-fresh {
143145 from {
144146 background-color: color-mix(in srgb, var(--color-accent) 18%, transparent);
145147 }
146148 }
147149
150+@keyframes g1t-arrive {
151+ from {
152+ opacity: 0;
153+ transform: translateY(-8px);
154+ }
155+}
156+
148157 @keyframes fade-in {
149158 from {
150159 opacity: 0;
+45−6
1−import { Ban, Check, ChevronRight, CircleAlert, Copy, ExternalLink, LoaderCircle, Play, SquareTerminal } from "lucide-react";
1+import { Ban, Check, ChevronRight, CircleAlert, Copy, ExternalLink, LoaderCircle, Play, Rocket, SquareTerminal } from "lucide-react";
22 import { useEffect, useState } from "react";
33
44 import type { AskSource } from "@g1t/contracts";
55
6−import { type Proposal, type Step, foldAnswer, proposalTitle } from "../lib/ask";
6+import { type Proposal, type Step, foldAnswer, proposalSteps, proposalTitle } from "../lib/ask";
77 import { Badge } from "./ui/badge";
88 import { Button } from "./ui/button";
99 import { Card } from "./ui/card";
116116 );
117117 }
118118
119−type Started = { session_url: string | null; provider_session_url: string | null; handoff: boolean; command: string | null; prompt: string };
119+type Started = {
120+ session_url: string | null;
121+ provider_session_url: string | null;
122+ handoff: boolean;
123+ command: string | null;
124+ prompt: string;
125+ /** A new repository's proposal: what the card made first. */
126+ repo_url?: string;
127+ production_url?: string | null;
128+ deploy_error?: string | null;
129+};
120130
121131 /**
122132 * Work g1t proposed to hand to a session in the person's own harness. Ask
130140 const [started, setStarted] = useState<Started | null>(null);
131141 const [copied, setCopied] = useState(false);
132142 const [error, setError] = useState<string | null>(null);
143+ const [made, setMade] = useState<Pick<Started, "repo_url" | "production_url" | "deploy_error"> | null>(null);
133144
134145 useEffect(() => {
135146 let live = true;
152163 body: JSON.stringify({ ...proposal, handoff }),
153164 });
154165 const body = (await response.json().catch(() => null)) as (Started & { error?: string }) | null;
166+ // The repository may be made even when the session is not: keep where it is.
167+ if (body?.repo_url) setMade({ repo_url: body.repo_url, production_url: body.production_url ?? null, deploy_error: body.deploy_error ?? null });
155168 if (!response.ok || !body) throw new Error(body?.error ?? "The session could not be started.");
156169 setStarted(body);
157170 if (handoff && body.command) {
176189 <span className="truncate text-sm font-medium text-fg">{proposalTitle(proposal)}</span>
177190 <Badge className="px-1.5 py-0 font-normal">Session</Badge>
178191 </div>
179− <p className="mt-0.5 text-xs text-muted">
180− {proposal.issue ? "Runs in your own harness, with your skills, tied to the issue." : "Opens the issue, then runs in your own harness, tied to it."}
181− </p>
192+ <p className="mt-0.5 text-xs text-muted">{proposalSteps(proposal)}</p>
182193 {proposal.instruction && <p className="mt-1 line-clamp-2 text-xs text-faint">{proposal.instruction}</p>}
183194 </div>
184195 </div>
196+ {made && (
197+ <div className="flex flex-col gap-1 text-xs">
198+ <span className="flex flex-wrap items-center gap-1.5">
199+ <Check size={13} aria-hidden="true" className="text-success" />
200+ <span className="text-muted">Created</span>
201+ <Button asChild variant="link" size="inline" className="text-xs">
202+ <a href={made.repo_url}>{proposal.repo}</a>
203+ </Button>
204+ </span>
205+ {made.production_url && (
206+ <span className="flex flex-wrap items-center gap-1.5">
207+ <Rocket size={13} aria-hidden="true" className="text-accent" />
208+ <span className="text-muted">Live once the first version is pushed at</span>
209+ <Button asChild variant="link" size="inline" className="text-xs">
210+ <a href={made.production_url} target="_blank" rel="noreferrer">
211+ {made.production_url.replace(/^https?:\/\//, "")} <ExternalLink size={11} aria-hidden="true" />
212+ </a>
213+ </Button>
214+ </span>
215+ )}
216+ {made.deploy_error && (
217+ <span className="flex items-start gap-1.5 text-warn">
218+ <CircleAlert size={12} aria-hidden="true" className="mt-0.5 shrink-0" />
219+ Not deployed: {made.deploy_error}
220+ </span>
221+ )}
222+ </div>
223+ )}
185224 {running ? (
186225 <div className="flex flex-wrap items-center gap-2 text-xs">
187226 <Check size={13} aria-hidden="true" className="text-success" />
+6−3
7878 {
7979 title: "Product",
8080 links: [
81− ["Explore", "/explore"],
82− ["Pricing", "/pricing"],
83− ["Download", "/download"],
81+ // Code first, as on the landing page: then Chat, then the agents.
82+ ["Pull requests", "https://docs.g1t.sh/guides/pull-requests/"],
83+ ["Deployments", "https://docs.g1t.sh/guides/deployments/"],
8484 ["Chat", "https://docs.g1t.sh/guides/chat/"],
8585 ["Bring your own agent", "https://docs.g1t.sh/guides/bring-your-own-agent/"],
8686 ["Integrations", "https://docs.g1t.sh/guides/integrations/"],
87+ ["Explore", "/explore"],
88+ ["Pricing", "/pricing"],
89+ ["Download", "/download"],
8790 ],
8891 },
8992 {
+37−11
22 * Home's parts (routes/workspace/home.tsx): Where you're needed, Start
33 * here, what happened in the span (what people did, what landed, builds,
44 * spend) and Running now. Each takes what
5− * lib/home.ts worked out and only draws it.
5+ * lib/home.ts worked out and only draws it; a row that arrived while the
6+ * page was open comes in (`animate-arrive`, lib/use-home-live.ts).
67 */
78 import {
89 AlertTriangle,
5354 whenShort,
5455 } from "../lib/home";
5556 import { askG1t } from "../lib/ask";
57+import { useArrived } from "../lib/use-home-live";
5658 import { cn } from "../lib/cn";
5759 import { money } from "../lib/money";
5860 import { CommitText } from "./commit-message";
165167 /** Rows shown before the rest fold away. */
166168 const ROWS = 8;
167169
168−function NeedItem({ row, now }: { row: AttentionRow; now: number }) {
170+/** A row that arrived while the page was open: in from above, lit for a moment. */
171+const ARRIVED = "animate-arrive motion-reduce:animate-none";
172+
173+function NeedItem({ row, now, arrived }: { row: AttentionRow; now: number; arrived?: boolean }) {
169174 return (
170− <li className="flex items-start gap-3 border-t border-line px-5 py-3 first:border-t-0">
175+ <li className={cn("flex items-start gap-3 border-t border-line px-5 py-3 first:border-t-0", arrived && ARRIVED)}>
171176 <span className="mt-0.5 shrink-0">{ICON[row.kind]}</span>
172177 <div className="min-w-0 grow">
173178 <Link to={row.action.to} className="line-clamp-2 text-sm font-semibold text-fg hover:underline sm:block sm:truncate">
202207 /** Everything waiting on you, most pressing and longest waiting first; past the first few, the rest fold away. */
203208 export function NeedsList({ attention, now }: { attention: Attention; now: number }) {
204209 const { rows, missing } = attention;
210+ const arrived = useArrived(rows.map((row) => row.key));
205211 return (
206212 <>
207213 {rows.length === 0 ? (
209215 ) : (
210216 <ul>
211217 {rows.slice(0, ROWS).map((row) => (
212− <NeedItem key={row.key} row={row} now={now} />
218+ <NeedItem key={row.key} row={row} now={now} arrived={arrived.has(row.key)} />
213219 ))}
214220 </ul>
215221 )}
221227 </summary>
222228 <ul className="border-t border-line">
223229 {rows.slice(ROWS).map((row) => (
224− <NeedItem key={row.key} row={row} now={now} />
230+ <NeedItem key={row.key} row={row} now={now} arrived={arrived.has(row.key)} />
225231 ))}
226232 </ul>
227233 </details>
565571 );
566572 }
567573
568−function LandedItem({ change, now, tz, timeOnly }: { change: LandedChange; now: number; tz: string | null; timeOnly?: boolean }) {
574+function LandedItem({ change, now, tz, timeOnly, arrived }: { change: LandedChange; now: number; tz: string | null; timeOnly?: boolean; arrived?: boolean }) {
569575 return (
570− <li className={cn(rowClass, "items-start")}>
576+ <li className={cn(rowClass, "items-start", arrived && ARRIVED)}>
571577 <span className="mt-0.5 shrink-0">{LANDED_ICON[change.kind]}</span>
572578 <div className="min-w-0 grow">
573579 <div className="flex min-w-0 items-center gap-1.5">
608614 now,
609615 tz,
610616 byDay = false,
617+ scope,
611618 }: {
612619 landed: LandedChange[] | null;
613620 missing: string[];
615622 now: number;
616623 tz: string | null;
617624 byDay?: boolean;
625+ /** The span: switching it lists other things, which are not arrivals. */
626+ scope?: string;
618627 }) {
628+ const arrived = useArrived(landed?.map((change) => change.key) ?? [], scope);
619629 if (!code) return null;
620− const render = (change: LandedChange) => <LandedItem key={change.key} change={change} now={now} tz={tz} timeOnly={byDay} />;
630+ const render = (change: LandedChange) => <LandedItem key={change.key} change={change} now={now} tz={tz} timeOnly={byDay} arrived={arrived.has(change.key)} />;
621631 return (
622632 <Card title="Landed" hint={landed && landed.length > 0 ? landedHint(landed) : undefined}>
623633 {!landed ? (
713723 );
714724 }
715725
716−export function DeploysCard({ deploys, complete, code, now, tz }: { deploys: DeployRow[] | null; complete: boolean; code: boolean; now: number; tz: string | null }) {
726+export function DeploysCard({
727+ deploys,
728+ complete,
729+ code,
730+ now,
731+ tz,
732+ scope,
733+}: {
734+ deploys: DeployRow[] | null;
735+ complete: boolean;
736+ code: boolean;
737+ now: number;
738+ tz: string | null;
739+ scope?: string;
740+}) {
741+ const arrived = useArrived(deploys?.map((row) => row.key) ?? [], scope);
717742 // A workspace that doesn't deploy has nothing to say here.
718743 if (!code || (deploys && deploys.length === 0)) return null;
719744 const failed = deploys?.filter((row) => row.outcome === "failed").length ?? 0;
725750 <Folded
726751 items={deploys}
727752 render={(row) => (
728− <li key={row.key} className={rowClass}>
753+ <li key={row.key} className={cn(rowClass, arrived.has(row.key) && ARRIVED)}>
729754 <Rocket size={15} className={cn("shrink-0", row.outcome === "failed" ? "text-danger" : "text-success")} />
730755 <div className="min-w-0 grow">
731756 <Link to={row.to} className="block truncate text-sm text-fg hover:underline">
823848 };
824849
825850 export function RunningList({ rows, missing, now }: { rows: RunningRow[]; missing: string[]; now: number }) {
851+ const arrived = useArrived(rows.map((row) => row.key));
826852 return (
827853 <>
828854 {rows.length === 0 ? (
831857 <Folded
832858 items={rows}
833859 render={(row) => (
834− <li key={row.key} className={rowClass}>
860+ <li key={row.key} className={cn(rowClass, arrived.has(row.key) && ARRIVED)}>
835861 <span className="shrink-0">{RUNNING_ICON[row.kind]}</span>
836862 <div className="min-w-0 grow">
837863 <Link to={row.to} className="block truncate text-sm text-fg hover:underline">
+12−0
7070 /** A session g1t proposed in an answer: the person starts it, or copies its command. */
7171 export type Proposal = Extract<AskEvent, { type: "proposal" }>;
7272
73+/** What starting a proposal does, in a line, under its title. */
74+export function proposalSteps(proposal: Proposal): string {
75+ if (proposal.new_repository) {
76+ const made = `Creates ${proposal.private ? "the private" : "the public"} repository`;
77+ return proposal.deploy
78+ ? `${made}, turns on its deployments, then runs in your own harness and pushes the first version, which goes live on g1t.page.`
79+ : `${made}, then runs in your own harness and pushes the first version.`;
80+ }
81+ return proposal.issue ? "Runs in your own harness, with your skills, tied to the issue." : "Opens the issue, then runs in your own harness, tied to it.";
82+}
83+
7384 /** What a proposal asks the session to do, in a line: "Implement #12 in acme/web". */
7485 export function proposalTitle(proposal: Proposal): string {
86+ if (proposal.new_repository) return `Build “${proposal.title}” in ${proposal.repo}`;
7587 const verb = { plan: "Plan", implement: "Implement", review: "Review" }[proposal.mode] ?? "Work on";
7688 return proposal.issue ? `${verb} #${proposal.issue} in ${proposal.repo}` : `${verb} “${proposal.title}” in ${proposal.repo}`;
7789 }
+55−0
1+import assert from "node:assert/strict";
2+import { describe, it } from "node:test";
3+
4+import type { RepoChange } from "@g1t/contracts";
5+
6+import { arrivedOf, homeShows, type HomeRepo } from "./home-live.ts";
7+
8+const repos: HomeRepo[] = [{ id: "r1", path: "acme/web", defaultBranch: "main" }];
9+const change = (kind: string, extra: Partial<RepoChange> = {}): RepoChange => ({ repo_id: "r1", kind, at: "2026-10-15T00:00:00Z", ...extra });
10+
11+describe("homeShows", () => {
12+ it("takes a default branch's pushes and tags, not another branch's", () => {
13+ assert.equal(homeShows(change("git.push", { ref: "refs/heads/main" }), repos), true);
14+ assert.equal(homeShows(change("git.push", { ref: "refs/tags/v1.0.0" }), repos), true);
15+ assert.equal(homeShows(change("git.push", { ref: "refs/heads/feature" }), repos), false);
16+ });
17+
18+ it("takes any branch's push where the default branch is not known", () => {
19+ assert.equal(homeShows({ ...change("git.push", { ref: "refs/heads/feature" }), repo_id: "other" }, repos), true);
20+ });
21+
22+ it("takes what Home lists and leaves a commit's checks", () => {
23+ for (const kind of ["pull.merged", "review.submitted", "issue.opened", "comment.created", "workflow.completed", "run.updated", "deployment_status.created", "release.published", "session.started", "session.ended"]) {
24+ assert.equal(homeShows(change(kind), repos), true, kind);
25+ }
26+ for (const kind of ["status.created", "check_run.completed", "check_suite.completed", "checks.completed", "session.progressed", "git.deleted"]) {
27+ assert.equal(homeShows(change(kind), repos), false, kind);
28+ }
29+ });
30+});
31+
32+describe("arrivedOf", () => {
33+ it("finds nothing new on the first draw", () => {
34+ const { arrived, seen } = arrivedOf(null, "last", ["a", "b"]);
35+ assert.deepEqual([...arrived], []);
36+ assert.deepEqual([...seen.keys], ["a", "b"]);
37+ });
38+
39+ it("finds the rows that were not shown before", () => {
40+ const first = arrivedOf(null, "last", ["a", "b"]).seen;
41+ const { arrived } = arrivedOf(first, "last", ["c", "a", "b"]);
42+ assert.deepEqual([...arrived], ["c"]);
43+ });
44+
45+ it("does not count a row that went and came back as new", () => {
46+ const first = arrivedOf(null, "last", ["a", "b"]).seen;
47+ const second = arrivedOf(first, "last", ["b"]).seen;
48+ assert.deepEqual([...arrivedOf(second, "last", ["a", "b"]).arrived], []);
49+ });
50+
51+ it("starts over when the span switches", () => {
52+ const first = arrivedOf(null, "last", ["a"]).seen;
53+ assert.deepEqual([...arrivedOf(first, "7d", ["a", "b", "c"]).arrived], []);
54+ });
55+});
+54−0
1+/**
2+ * Home that updates live: which of its repositories' changes it shows,
3+ * and which rows are new since it last drew. Pure; the hook that uses
4+ * them is lib/use-home-live.ts.
5+ *
6+ * Home watches the workspace's projects (the first HOME_WATCH_MAX, as the
7+ * digest reads them) over the live socket, as a repository page
8+ * watches its own (lib/repo-live.ts), and refreshes when something it
9+ * lists moves: a push to a default branch, a pull request or review, an
10+ * issue or comment, a run starting or finishing, a deployment or release,
11+ * a session starting or ending. Not a branch's pushes or a commit's
12+ * checks: a busy workspace sends those all the time, and nothing on Home
13+ * shows them. Its loader reads many services, so it refreshes at most
14+ * every HOME_GAP_MS.
15+ */
16+import type { RepoChange } from "@g1t/contracts";
17+
18+import { branchOf } from "./repo-live.ts";
19+
20+/** The most projects Home watches (a person watches at most 50, other tabs' pages too). */
21+export const HOME_WATCH_MAX = 25;
22+/** However many changes arrive, Home refreshes at most this often. */
23+export const HOME_GAP_MS = 5000;
24+
25+/** A project Home watches: its id, `owner/name` and default branch. */
26+export type HomeRepo = { id: string; path: string; defaultBranch: string | null };
27+
28+const FAMILIES = ["pull", "review", "issue", "comment", "workflow", "run", "deployment", "deployment_status", "release", "package"];
29+const KINDS = ["session.started", "session.ended", "queue.changed"];
30+
31+/** Whether a change moves something Home shows. */
32+export function homeShows(change: RepoChange, repos: readonly HomeRepo[]): boolean {
33+ if (change.kind === "git.push") {
34+ const repo = repos.find((one) => one.id === change.repo_id);
35+ const branch = branchOf(change.ref);
36+ // A default branch's push lands; a tag's is a release.
37+ return branch == null || !repo?.defaultBranch || branch === repo.defaultBranch;
38+ }
39+ return FAMILIES.includes(change.kind.split(".")[0] ?? "") || KINDS.includes(change.kind);
40+}
41+
42+/** What a list on the page remembers between draws: the keys it has shown, under which scope. */
43+export type Seen = { scope: string; keys: ReadonlySet<string> };
44+
45+/**
46+ * The rows of `keys` that were not on the list when it last drew, and
47+ * what to remember now. On a first draw, or once the scope changed (the
48+ * span switched), none are new: only what arrives while it is open is.
49+ */
50+export function arrivedOf(seen: Seen | null, scope: string, keys: readonly string[]): { arrived: ReadonlySet<string>; seen: Seen } {
51+ if (!seen || seen.scope !== scope) return { arrived: new Set(), seen: { scope, keys: new Set(keys) } };
52+ const arrived = new Set(keys.filter((key) => !seen.keys.has(key)));
53+ return { arrived, seen: { scope, keys: new Set([...seen.keys, ...keys]) } };
54+}
+3−1
229229 busy: boolean;
230230 /** A navigation or a submission is under way: the router reloads the page itself. */
231231 navigating: boolean;
232+ /** The page refreshes at most this often; LIVE_MIN_GAP_MS unless a page's loader is heavier (Home's). */
233+ gap?: number;
232234 };
233235
234236 /**
251253 const quiet = input.now - (state.lastChangeAt ?? state.pendingSince);
252254 const waited = input.now - state.pendingSince;
253255 const since = state.lastRunAt == null ? Infinity : input.now - state.lastRunAt;
254− const ms = Math.max(quiet < LIVE_DEBOUNCE_MS && waited < LIVE_MAX_WAIT_MS ? Math.min(LIVE_DEBOUNCE_MS - quiet, LIVE_MAX_WAIT_MS - waited) : 0, LIVE_MIN_GAP_MS - since);
256+ const ms = Math.max(quiet < LIVE_DEBOUNCE_MS && waited < LIVE_MAX_WAIT_MS ? Math.min(LIVE_DEBOUNCE_MS - quiet, LIVE_MAX_WAIT_MS - waited) : 0, (input.gap ?? LIVE_MIN_GAP_MS) - since);
255257 return ms > 0 ? { action: "wait", ms } : { action: "run" };
256258 }
257259
+45−0
1+/**
2+ * `useHomeLive`: Home refreshing itself as the workspace moves
3+ * (lib/home-live.ts says what it shows), and `useArrived`: which rows of
4+ * a list on it are new, so they come in rather than appear.
5+ *
6+ * Besides its projects' changes, Home refreshes when the inbox moves or a
7+ * notification for this workspace arrives: Where you're needed reads them.
8+ */
9+import { useEffect, useRef } from "react";
10+
11+import { HOME_GAP_MS, type HomeRepo, type Seen, arrivedOf, homeShows } from "./home-live";
12+import { useNotifyState } from "./notify-client";
13+import { useLive } from "./use-repo-live";
14+
15+export function useHomeLive(slug: string, repos: readonly HomeRepo[]): boolean {
16+ const { live, nudge } = useLive({ repos, signedIn: true, match: (change) => homeShows(change, repos), gap: HOME_GAP_MS });
17+
18+ const { inbox, recent } = useNotifyState();
19+ const newest = recent.find((n) => n.workspace.toLowerCase() === slug)?.id ?? null;
20+ const before = useRef<{ inbox: number | null; newest: string | null } | null>(null);
21+ useEffect(() => {
22+ const was = before.current;
23+ before.current = { inbox, newest };
24+ // The first the feed says is where the page started, not a change.
25+ if (was && ((was.inbox != null && inbox !== was.inbox) || newest !== was.newest)) nudge();
26+ }, [inbox, newest, nudge]);
27+
28+ return live;
29+}
30+
31+/**
32+ * The keys of a list's rows that were not on it when it last drew: none
33+ * on the first draw, nor right after `scope` (the span) changes. The same
34+ * keys again give the same answer, so a row keeps its entrance for as
35+ * long as it plays.
36+ */
37+export function useArrived(keys: readonly string[], scope = ""): ReadonlySet<string> {
38+ const last = useRef<{ seen: Seen | null; id: string; arrived: ReadonlySet<string> }>({ seen: null, id: "", arrived: new Set() });
39+ const id = `${scope}\n${keys.join("\n")}`;
40+ if (last.current.seen == null || id !== last.current.id) {
41+ const next = arrivedOf(last.current.seen, scope, keys);
42+ last.current = { seen: next.seen, id, arrived: next.arrived };
43+ }
44+ return last.current.arrived;
45+}
+60−19
1313 * socket is down, the page refreshes when the tab comes back into view
1414 * (at most every 30 s) and once a minute while it is in view.
1515 *
16+ * `useLive` is the same for a page over several repositories (Home,
17+ * the workspace's): one `watch_repos` asks for them all.
18+ *
1619 * Browser-only work happens in effects; on the server the hook does
1720 * nothing.
1821 */
6063 show: () => void;
6164 };
6265
66+/** A repository a live page watches: its id, as changes name it, and its `owner/name`, as it is asked for. */
67+export type WatchedRepo = { id: string; path: string };
68+
69+export type LiveOptions = RepoLiveOptions & {
70+ /** The repositories whose changes the page hears. */
71+ repos: readonly WatchedRepo[];
72+ /** Whether the viewer is signed in: only then is there a socket to hear on. */
73+ signedIn: boolean;
74+ /** However many changes arrive, the page refreshes at most this often (LIVE_MIN_GAP_MS). */
75+ gap?: number;
76+};
77+
78+export type Live = RepoLive & {
79+ /** Something outside the repositories changed (the inbox, on Home): refresh as a change would. */
80+ nudge: () => void;
81+};
82+
6383 // ── Watching ─────────────────────────────────────────────────────────────
6484
6585 /** When each repository was last asked for, by `owner/name`, so pages and reconnects ask once. */
6787 /** Asking again sooner than this is the same ask. */
6888 const ASK_AGAIN_MS = 5000;
6989
70−async function watchRepo(path: string, force: boolean): Promise<boolean> {
90+/** Asks to hear `paths`, one request for them all; true when any is watched. */
91+async function watchRepos(paths: readonly string[], force: boolean): Promise<boolean> {
7192 const now = Date.now();
72− const last = asked.get(path);
73− if (!force && last != null && now - last < ASK_AGAIN_MS) return true;
74− asked.set(path, now);
93+ const due = paths.filter((path) => {
94+ const last = asked.get(path);
95+ return force || last == null || now - last >= ASK_AGAIN_MS;
96+ });
97+ if (due.length === 0) return true;
98+ for (const path of due) asked.set(path, now);
7599 try {
76100 const answer = await fetch("/-/notify", {
77101 method: "POST",
78102 headers: { "content-type": "application/json", accept: "application/json" },
79103 credentials: "same-origin",
80− body: JSON.stringify({ intent: "watch_repo", repo: path }),
104+ body: JSON.stringify(due.length === 1 ? { intent: "watch_repo", repo: due[0] } : { intent: "watch_repos", repos: due }),
81105 });
82106 if (!answer.ok) throw new Error(`status ${answer.status}`);
83107 const body = (await answer.json().catch(() => null)) as { ok?: unknown } | null;
84108 if (body?.ok !== true) throw new Error("refused");
85109 return true;
86110 } catch {
87− asked.delete(path);
111+ for (const path of due) asked.delete(path);
88112 return false;
89113 }
90114 }
111135 export function useRepoLive(options: RepoLiveOptions): RepoLive {
112136 const layout = useRouteLoaderData("routes/repo/layout") as LayoutData | undefined;
113137 const repo = layout?.repo ?? null;
114− const signedIn = layout?.signedIn === true;
115− const enabled = options.enabled !== false && !!repo;
116− const path = repo ? `${repo.namespace}/${repo.name}` : null;
117− const repoId = repo?.id ?? null;
138+ const repos = repo ? [{ id: repo.id, path: `${repo.namespace}/${repo.name}` }] : [];
139+ const { live, held, show } = useLive({ ...options, enabled: options.enabled !== false && !!repo, repos, signedIn: layout?.signedIn === true });
140+ return { live, held, show };
141+}
142+
143+/**
144+ * A page that refreshes itself as any of `repos` changes (Home watches
145+ * the workspace's): `useRepoLive` for a page with no repository layout.
146+ */
147+export function useLive(options: LiveOptions): Live {
148+ const { repos, signedIn } = options;
149+ // With no repositories there is nothing to hear: the page refreshes as it would without the socket.
150+ const enabled = options.enabled !== false;
151+ // One string for the effects: the same repositories are the same watch.
152+ const paths = repos.map((repo) => repo.path).join(" ");
153+ const ids = repos.map((repo) => repo.id).join(" ");
118154
119155 const connected = useFeedConnected();
120156 const [watching, setWatching] = useState(false);
145181 const check = useCallback(() => {
146182 if (timer.current) clearTimeout(timer.current);
147183 timer.current = null;
148− const decision = gate(state.current, { now: Date.now(), visible: visibleNow(), busy: busyNow(), navigating: latest.current.navigating });
184+ const decision = gate(state.current, { now: Date.now(), visible: visibleNow(), busy: busyNow(), navigating: latest.current.navigating, gap: latest.current.options.gap });
149185 if (decision.action === "run") run();
150186 else if (decision.action === "wait") timer.current = setTimeout(check, decision.ms);
151187 // "hold": out of view; coming back into view (`back` below) asks again.
162198 [check],
163199 );
164200
165− // Watching the repository: on opening the page, whenever the socket
201+ // Watching the repositories: on opening the page, whenever the socket
166202 // (re)connects, and before the watch runs out.
167203 useEffect(() => {
168− if (!enabled || !signedIn || !connected || !path) {
204+ if (!enabled || !signedIn || !connected || !paths) {
169205 setWatching(false);
170206 return;
171207 }
172208 let cancelled = false;
173209 const ask = (force: boolean) =>
174− void watchRepo(path, force).then((ok) => {
210+ void watchRepos(paths.split(" "), force).then((ok) => {
175211 if (!cancelled) setWatching(ok);
176212 });
177213 ask(true);
180216 cancelled = true;
181217 clearInterval(renew);
182218 };
183− }, [enabled, signedIn, connected, path]);
219+ }, [enabled, signedIn, connected, paths]);
184220
185221 // Changes as they arrive.
186222 useEffect(() => {
187− if (!enabled || !repoId) return;
223+ if (!enabled || !ids) return;
224+ const watched = new Set(ids.split(" "));
188225 return onRepoChanges((changes) => {
189226 const { match, hold } = latest.current.options;
190− const mine = changes.filter((change) => change.repo_id === repoId);
227+ const mine = changes.filter((change) => watched.has(change.repo_id));
191228 const holding = hold ? mine.filter((change) => hold(change)) : [];
192229 const matched = mine.filter((change) => !holding.includes(change) && match(change));
193230 if (holding.length) setHeld((before) => [...before, ...holding]);
194231 // While something is held, the rest waits with it: shown together.
195232 if (matched.length && !holding.length && heldCount.current === 0) queue();
196233 });
197− }, [enabled, repoId, queue]);
234+ }, [enabled, ids, queue]);
198235
199236 // Another branch or page: what was held is no longer about it.
200237 useEffect(() => {
245282 run();
246283 }, [run]);
247284
248− return { live, held, show };
285+ const nudge = useCallback(() => {
286+ if (enabled && heldCount.current === 0) queue();
287+ }, [enabled, queue]);
288+
289+ return { live, held, show, nudge };
249290 }
+2−1
1818 ...page(args, {
1919 title: "g1t · Where your team's code lands",
2020 description:
21− "Repositories, pull requests, required checks, a merge queue and deployments, with chat for your team. Connect the coding agents you already use through MCP and the API. Chat is free on every plan, never per seat.",
21+ // At most 200 characters, or a preview cuts it short (lib/meta.ts).
22+ "Repositories, pull requests, required checks, a merge queue and deployments, for your team and the coding agents you bring over MCP and the API. Chat is free on every plan. Never per seat.",
2223 }),
2324 organization(),
2425 application(20),
+28−9
Loading the diff…
+10−1
Loading the diff…
+66−6
Loading the diff…
+33−3
Loading the diff…
+93−22
Loading the diff…
+1−1
Loading the diff…
+1−1
Loading the diff…
+11−0
Loading the diff…
+9−0
Loading the diff…
+1−1
Loading the diff…
+1−1
Loading the diff…
+209−30
Loading the diff…
+122−2
Loading the diff…
+5−1
Loading the diff…
+15−0
Loading the diff…
+8−1
Loading the diff…
+41−3
Loading the diff…
+1−1
Loading the diff…
+1−1
Loading the diff…

Comments

Sign in to comment.