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' of https://g1t.sh/flagon-io/g1t

syntaqxcommitted Parents9f0a909e77b820Browse files
53 files+261−637 files not counted0/53 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.2",
10+ "version": "0.2.3",
1111 "category": "development"
1212 }
1313 ]
+3−0
7272 key: cli-release-${{ runner.os }}-${{ steps.keys.outputs.zigbuild }}-${{ steps.keys.outputs.cargo }}
7373 restore-keys: cli-release-${{ runner.os }}-${{ steps.keys.outputs.zigbuild }}-
7474 - name: Build
75+ env:
76+ # Built in, so `g1t upgrade` checks what it downloads.
77+ G1T_RUNNER_RELEASE_KEY: ${{ vars.RUNNER_RELEASE_PUBLIC_KEY }}
7578 run: node scripts/cli-release.mjs build
7679 - uses: actions/upload-artifact@v4
7780 with:
+2−1
13201320
13211321 [[package]]
13221322 name = "g1t"
1323−version = "0.3.3"
1323+version = "0.3.4"
13241324 dependencies = [
13251325 "anstyle-query",
13261326 "base64 0.22.1",
13271327 "clap",
13281328 "clap_complete",
1329+ "ed25519-dalek",
13291330 "flate2",
13301331 "hex",
13311332 "jaq-core",
+2−0
506506 actor: actor(),
507507 repo,
508508 issue,
509+ pull: None,
510+ asked: None,
509511 title: text(input, "title"),
510512 body: text(input, "body"),
511513 instruction: text(input, "instruction"),
+2−0
359359 Op::AddComment,
360360 Op::EditComment,
361361 Op::DeleteComment,
362+ Op::React,
362363 Op::ListIssueLabels,
363364 Op::AddIssueLabels,
364365 Op::SetIssueLabels,
734735 Op::AddComment => "Add a comment",
735736 Op::EditComment => "Edit a comment",
736737 Op::DeleteComment => "Delete a comment",
738+ Op::React => "React with an emoji",
737739 Op::ReviewPullRequest => "Review a pull request",
738740 Op::ListPullRequests => "List pull requests",
739741 Op::GetPullRequest => "Get a pull request",
+35−1
190190 AddComment,
191191 EditComment,
192192 DeleteComment,
193+ React,
193194 ReviewPullRequest,
194195 ListPullRequests,
195196 GetPullRequest,
811812 }
812813
813814 impl Op {
814− pub const ALL: [Op; 405] = [
815+ pub const ALL: [Op; 406] = [
815816 Op::Whoami,
816817 Op::GetWorkspace,
817818 Op::CreateWorkspace,
885886 Op::AddComment,
886887 Op::EditComment,
887888 Op::DeleteComment,
889+ Op::React,
888890 Op::ReviewPullRequest,
889891 Op::ListPullRequests,
890892 Op::GetPullRequest,
12991301 Op::AddComment => "add_comment",
13001302 Op::EditComment => "edit_comment",
13011303 Op::DeleteComment => "delete_comment",
1304+ Op::React => "react",
13021305 Op::ReviewPullRequest => "review_pull_request",
13031306 Op::ListPullRequests => "list_pull_requests",
13041307 Op::GetPullRequest => "get_pull_request",
16631666 Op::DeleteComment => {
16641667 "Delete a comment on an issue or a pull request, named by comment_id. Its author may delete it, and so may anyone with the Maintain role or higher. A review that approved or requested changes cannot be deleted, only edited, and notes of what happened cannot be deleted. This cannot be undone. Publishes comment.deleted with the comment as it was."
16651668 }
1669+ Op::React => {
1670+ "React to an issue or a pull request with an emoji: to its description, or with comment_id to one of its comments. content is one of +1, -1, laugh, hooray, confused, heart, rocket and eyes; remove true takes your reaction back. Anyone who can read the repository may. Answers with the reactions it has now, each with its count and the usernames of who reacted (`g1t` for g1t, which reacts eyes to what it was asked to take on). get_issue and get_pull_request give each comment's reactions, and the description's as reactions."
1671+ }
16661672 Op::ReviewPullRequest => {
16671673 "Give a verdict on a pull request: approve it, or request changes and say what. Read get_pull_request_changes first. You cannot review a pull request you opened, or one g1t made for you (you are its requested_by)."
16681674 }
27162722 json!({ "repo": repo_schema(), "comment_id": comment_id_schema() }),
27172723 &["repo", "comment_id"],
27182724 ),
2725+ Op::React => object(
2726+ numbered(json!({
2727+ "content": {
2728+ "type": "string",
2729+ "enum": ["+1", "-1", "laugh", "hooray", "confused", "heart", "rocket", "eyes"],
2730+ },
2731+ "comment_id": {
2732+ "type": "string",
2733+ "description": "A comment on it, to react to that comment rather than to its description.",
2734+ },
2735+ "remove": { "type": "boolean", "description": "Take your reaction back." },
2736+ })),
2737+ &["repo", "number", "content"],
2738+ ),
27192739 Op::ReviewPullRequest => object(
27202740 numbered(json!({
27212741 "verdict": { "type": "string", "enum": ["approve", "request_changes"] },
52385258 Op::ClosePullRequest => pass(work, "close_pull", &pull_action()).await,
52395259 Op::ReopenPullRequest => pass(work, "reopen_pull", &pull_action()).await,
52405260 Op::ConvertPullRequestToDraft => pass(work, "convert_pull_to_draft", &pull_action()).await,
5261+ Op::React => {
5262+ let Some(content) = input["content"].as_str().and_then(ReactionContent::parse) else {
5263+ return failed(FailureCode::Invalid, "content must be one of +1, -1, laugh, hooray, confused, heart, rocket and eyes.");
5264+ };
5265+ let asked = ReactArgs {
5266+ actor: actor(),
5267+ repo,
5268+ number,
5269+ comment_id: optional_text(input, "comment_id"),
5270+ content,
5271+ remove: input["remove"].as_bool().unwrap_or(false),
5272+ };
5273+ pass(work, "react", &asked).await
5274+ }
52415275 Op::EditComment | Op::DeleteComment => {
52425276 let asked = CommentActionArgs {
52435277 actor: actor(),
+12−0
16041604 },
16051605 "notes": "Answers `204` with no body. Its author, or someone with the Maintain role or higher. A review that approved or requested changes answers `409`: edit it instead."
16061606 },
1607+ "react": {
1608+ "params": {
1609+ "number": 12,
1610+ "comment_id": "cmt_01m43sv4c8e2g6j0m4q8t2x6a1",
1611+ "content": "heart"
1612+ },
1613+ "response": [
1614+ { "content": "heart", "count": 1, "users": ["ana"] },
1615+ { "content": "eyes", "count": 1, "users": ["g1t"] }
1616+ ],
1617+ "notes": "Reacting twice the same way is one reaction. Without comment_id it reacts to the issue's or pull request's description."
1618+ },
16071619 "list_labels": {
16081620 "response": [
16091621 {
+2−0
10461046 ),
10471047 route("PATCH", "/repos/:owner/:name/issues/comments/:comment_id", Op::EditComment, &[]),
10481048 route("DELETE", "/repos/:owner/:name/issues/comments/:comment_id", Op::DeleteComment, &[]),
1049+ route("POST", "/repos/:owner/:name/issues/:number/reactions", Op::React, &[]),
10491050 route(
10501051 "GET",
10511052 "/repos/:owner/:name/pulls",
15791580 assert_eq!(input, json!({ "comment_id": "cmt_1", "repo": "acme/web" }));
15801581 // Still an issue's comments, labels and subscription.
15811582 assert_eq!(op("POST", "/repos/acme/web/issues/7/comments"), Op::AddComment);
1583+ assert_eq!(op("POST", "/repos/acme/web/issues/7/reactions"), Op::React);
15821584 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/labels"), Op::RemoveIssueLabels);
15831585 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/subscription"), Op::DeleteThreadSubscription);
15841586 }
+1−0
165165 a("comment", Op::AddComment, "Comment on an issue or pull request; path and line for one line of a change"),
166166 a("edit_comment", Op::EditComment, "Change a comment's text: your own, or any with the Maintain role"),
167167 a("delete_comment", Op::DeleteComment, "Delete a comment: your own, or any with the Maintain role"),
168+ a("react", Op::React, "React with an emoji to it, or with comment_id to one of its comments; remove takes it back"),
168169 a("import", Op::ImportIssue, "Open an issue from a Jira, Linear or Sentry item"),
169170 a("work", Op::Coordination(CoordinationOp::GetWork), "Everything to pick it up: done means, claims, sessions, pull requests, blockers, decisions, recent events"),
170171 a("claim", Op::Coordination(CoordinationOp::ClaimIssue), "Say you are working on it; lists who else is, and what it is blocked by"),
+7−1
250250 Claude Code says so once. The plugin never installs anything itself.
251251 Install the CLI or run `g1t auth login`, and the next session is recorded.
252252
253−To update the CLI, run its installer again:
253+To update the CLI, run `g1t upgrade` (0.3.4 and later). For an older CLI,
254+run its installer again:
254255
255256 ```sh
256257 curl -fsSL https://g1t.sh/install | sh
260261 which you have. A CLI that refuses a command the hooks send is taken as
261262 out of date, and said the same way.
262263
264+A CLI older than 0.3.3 records sessions but cannot acknowledge
265+[coordination updates](#working-on-g1t). The hooks then show
266+pending updates only when they change, not before every prompt, and ask
267+Claude to tell you once to update the CLI.
268+
263269 The hooks run on Node. Without Node 18 or later they record nothing:
264270 with an older Node, Claude Code says so once; with no `node` on your
265271 `PATH` at all, Claude Code shows each hook's failure (`node: command not
+26−1
3838 release's `SHA256SUMS`, and puts `g1t` in `~/.local/bin` (Windows:
3939 `%LOCALAPPDATA%\g1t\bin`, which it adds to your `PATH`). Set
4040 `G1T_INSTALL_DIR` to put it elsewhere, or `G1T_VERSION` to install a
41−version other than the latest. Run it again to update.
41+version other than the latest.
42+
43+### Upgrade
44+
45+From 0.3.4 on, `g1t upgrade` updates g1t to the newest release:
46+
47+```sh
48+g1t upgrade # download and install the newest release
49+g1t upgrade --check # only say whether there is one
50+```
51+
52+It reads the newest release's manifest, `latest.json`, and checks its
53+signature (`latest.json.sig`) against g1t's release key, which is built
54+into every release. It then downloads the build for your system, checks
55+its SHA-256 against the manifest, and puts it where the running `g1t` is.
56+If anything does not check out, nothing changes. Releases come from
57+`https://g1t.sh/downloads/cli`, or from `G1T_DOWNLOAD_URL` if you set it.
58+
59+In a terminal, g1t looks for a newer release about once a day, in the
60+background, and says so after a command when there is one. It never looks
61+in CI or when its output is not a terminal. Set `G1T_NO_UPDATE_NOTIFIER=1`
62+to turn it off.
63+
64+A g1t built from source has no release key and cannot upgrade itself.
65+Neither can versions before 0.3.4. Update those by running the install
66+script again.
4267
4368 ### Without the script
4469
+25−0
182182
183183 ## Sessions
184184
185+Each session that works on a pull request, or on its issue, shows in the
186+conversation as a card where it started, the way a comment or a review
187+does: who it is (or g1t, working for someone), where it stands, its
188+branch, and its report once it ends, linked to its page. Mention `@g1t`
189+in a comment to have g1t carry on with the pull request; see
190+[assign g1t, or ask it in the thread](/guides/sessions/#assign-g1t-or-ask-it-in-the-thread).
191+
185192 A coding agent that works on a pull request can record how it made the
186193 change, entry by entry, with the `pull_request` tool's `record_session`
187194 action. Read it on the pull request's **Session** tab, or with
293300 `changes.body.from`; deleting publishes `comment.deleted`, with the comment
294301 as it was in `comment`.
295302
303+## Reactions
304+
305+React to an issue's or pull request's description, or to any comment on
306+it, with one of eight emoji: 👍 👎 😄 🎉 😕 ❤️ 🚀 👀. Select the smiley
307+under it, or a reaction already there to add or take back yours; hovering
308+one says who reacted. Anyone who can read the repository may react. g1t
309+reacts 👀 to what it was asked to take on.
310+
311+Through the API, `POST /repos/{owner}/{name}/issues/{number}/reactions`
312+with `content` (`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`,
313+`rocket` or `eyes`), and `comment_id` to react to a comment rather than
314+the description, reacts and answers with the reactions it has now;
315+`remove: true` takes yours back. On the MCP server it is the `issue`
316+tool's `react` action. Each comment's `reactions`, and the description's,
317+are in `GET /repos/{owner}/{name}/issues/{number}` and
318+`GET /repos/{owner}/{name}/pulls/{number}`: each with its `content`,
319+`count` and the `users` who reacted.
320+
296321 ## Commit comments
297322
298323 You can comment on any commit: on the whole commit, under its changes on
+36−0
313313 result. Whoever started it can stop it from its page. It runs on your
314314 workspace's key and Anthropic bills the account for it; g1t bills nothing.
315315
316+## Assign g1t, or ask it in the thread
317+
318+You can also hand work to g1t from the issue or pull request itself, the
319+way you would ask a teammate:
320+
321+- **Assign the issue to `g1t`**: **Assign to g1t** in the issue's
322+ sidebar, `g1t` among the assignees in
323+ [`update_issue`](/reference/api/issues/update-issue/), or
324+ `g1t issue edit 12 --add-assignee g1t`.
325+- **Mention `@g1t` in a comment** on an issue or a pull request:
326+ "@g1t the button still overlaps on a phone, fix that". On a pull
327+ request, the session carries on with that pull request on its branch.
328+ On an issue, it takes the issue on.
329+
330+g1t reacts 👀 to what asked it, the comment or (for an assignment) the
331+issue, then starts a session on the workspace's Anthropic account, as
332+the person who asked, with what they wrote as its instruction. Each
333+session shows in the issue's or pull request's conversation as a card,
334+where it started, the way a comment or a review does: who it works for,
335+where it stands, its branch, and its report when it ends, linked to its
336+page. Not happy with what a session did? Say so with `@g1t` in the same
337+thread. The next session is told to fix the work on the same branch and
338+pull request, not start over, and `g1t work context` shows it every
339+session and pull request so far. If the session you started on that
340+thread is still going, your comment goes to it instead of starting
341+another.
342+
343+Only the workspace's members can ask g1t; anyone else's `@g1t` is an
344+ordinary mention. A mention in quoted text or code (`` `@g1t` ``) asks
345+nothing. g1t answers one thread at most six times an hour. When it cannot
346+start a session, for instance because the workspace has no Anthropic AI
347+provider, it says why in the thread. On an update pull request g1t opened,
348+`@g1t rebase` and the other
349+[comment commands](/guides/dependency-updates/#comment-commands) still do what
350+they say.
351+
316352 ## Claims
317353
318354 A claim says you are working on an issue. It is advisory: anyone may claim
+18−0
14661466 The token is the first of: --token-stdin, G1T_TOKEN, the account `g1t auth login`
14671467 signed in for the registry's host, or what `docker login g1t.sh` stored.
14681468 ```
1469+
1470+## g1t upgrade
1471+
1472+Update g1t to the newest release, checked against g1t's release key.
1473+
1474+```text
1475+g1t upgrade [OPTIONS]
1476+```
1477+
1478+| Argument or flag | |
1479+| --- | --- |
1480+| `--check` | Only say whether a newer release is out. |
1481+
1482+```text
1483+Downloads the newest release for this machine from g1t.sh, checks its signature and SHA-256, and puts it
1484+where this g1t is. In a terminal, g1t also says once a day when a newer release is out;
1485+G1T_NO_UPDATE_NOTIFIER=1 turns that off.
1486+```
+0−0

Binary or large file; its contents are not shown.

+89−1
7777 import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
7878 import { RadioGroup, RadioOption } from "./ui/radio-group";
7979 import { Textarea } from "./ui/textarea";
80−import { PersonLink } from "./work";
80+import { Markdown } from "./markdown";
81+import { PersonLink, TimelineItem } from "./work";
82+import { repoAt } from "../lib/markdown-plugins";
8183 import { IssueIcon, PullIcon } from "./work-icons";
8284
8385 /** Text with each `#N` linked to that issue or pull request (the issue's address leads to a pull request too). */
223225 );
224226 }
225227
228+/**
229+ * A session in an issue's or pull request's conversation, where it
230+ * started, as a card like a comment or a review: g1t working for someone
231+ * (a session g1t started on the workspace's Claude, when it was assigned
232+ * the issue or asked in the thread), or someone working in their own
233+ * harness. What it is about, where it stands now, its branch, and what it
234+ * reported once it ended, linked to its page.
235+ */
236+export function TimelineSession({ session, base }: { session: WorkSession; base: string }) {
237+ const g1t = session.harness === "claude_managed_agents";
238+ const ended = session.state === "ended";
239+ return (
240+ <TimelineItem
241+ id={session.id}
242+ author={g1t ? "g1t" : session.actor.username}
243+ bot={g1t}
244+ at={session.startedAt}
245+ action={
246+ <>
247+ {g1t ? (
248+ <span>
249+ {ended ? "worked on this for" : "is working on this for"}{" "}
250+ <PersonLink name={session.actor.username} className="font-medium text-fg hover:underline">
251+ @{session.actor.username}
252+ </PersonLink>
253+ </span>
254+ ) : (
255+ <span>{ended ? "worked on this in a session" : "is working on this in a session"}</span>
256+ )}
257+ <HarnessBadge harness={session.harness} />
258+ <SessionStateBadge state={session.state} />
259+ </>
260+ }
261+ aside={
262+ <Link
263+ to={`${base}/work/sessions/${session.id}`}
264+ prefetch="intent"
265+ className="inline-flex items-center gap-1 text-xs text-faint hover:text-fg"
266+ >
267+ <Activity size={12} />
268+ View session
269+ </Link>
270+ }
271+ >
272+ <Link to={`${base}/work/sessions/${session.id}`} prefetch="intent" className="font-medium hover:text-accent">
273+ {sessionTitle(session)}
274+ </Link>
275+ <p className="mt-1 flex flex-wrap items-center gap-x-1.5 gap-y-1 text-xs text-muted">
276+ {ended ? (
277+ <span>
278+ Ended <TimeAgo at={session.endedAt ?? session.lastSeenAt} />
279+ </span>
280+ ) : (
281+ <span>
282+ Last heard from <TimeAgo at={session.lastSeenAt} />
283+ </span>
284+ )}
285+ {session.branch && (
286+ <span className="inline-flex min-w-0 items-center gap-1 font-mono">
287+ · <GitBranch size={11} />
288+ <span className="truncate">{session.branch}</span>
289+ </span>
290+ )}
291+ {session.pull != null && (
292+ <span>
293+ · on{" "}
294+ <Link to={`${base}/pull/${session.pull}`} className="text-fg hover:text-accent">
295+ #{session.pull}
296+ </Link>
297+ </span>
298+ )}
299+ {(session.subagents ?? 0) > 0 && (
300+ <span>
301+ · {session.subagents} {session.subagents === 1 ? "subagent" : "subagents"}
302+ </span>
303+ )}
304+ </p>
305+ {ended && session.result && (
306+ <div className="mt-3 border-t border-line pt-3 text-sm">
307+ <Markdown source={session.result} repo={repoAt(base)} />
308+ </div>
309+ )}
310+ </TimelineItem>
311+ );
312+}
313+
226314 /** A subagent in a line: its kind, its task, where it stands, linked to its page. */
227315 export function SubagentLine({ session, base }: { session: WorkSession; base: string }) {
228316 return (
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…

Comments

Sign in to comment.