syntaqxAn agent can put an image in a pull request, an issue or a comment without committing it: on flagon-io/session-lab #2 a cloud session asked to show a screenshot of its preview in the pull request's description found no way to upload one (not in g1t --help, g1t api --list or the docs), so it committed the PNG, linked it at that commit and removed it in a second commit. The site has no comment-image upload either; images only ever came from repository files. Now upload_attachment (POST /repos/{owner}/{repo}/attachments, issues:write, the issue tool's attach action over MCP) takes name, data (the file base64-encoded, as upload_sarif takes its file) and an optional content_type, and answers in snake_case with id, name, url, markdown (, ready to paste), content_type and size. The work service decides, as add_comment does: a confirmed account that can read the repository, which is not archived. The type is read from the bytes (PNG, JPEG, GIF or WebP; a content_type that says otherwise is refused), at most 10 MB; too large is the new FailureCode too_large (413), another type the new unsupported_type (415), in Rust and TS contracts and the OpenAPI error codes. The bytes go in the usercontent namespace (AVATARS, now bound to work) under attachments/<id>, where the id is 256 random bits so the address is its permission, as Docs files' is; a row in the new attachments table (migration 0048) names the repository, and repo.purged deletes the bytes, then the rows. The usercontent origin serves /attachments/<id> as it serves avatars (stored type, nosniff, sandboxed). The API forms url from USERCONTENT_URL as the site does (g1tusercontent.com for g1t.sh, SITE_URL/-/usercontent elsewhere; the self-host config now passes it to the API). The CLI has g1t attach FILE... (prints each image's Markdown; --json the fields, a list for several) and --attach FILE (repeatable) on g1t issue create, issue comment, pr create, pr comment and pr ready, which uploads and appends the Markdown after the text. A session's run credential gets upload_attachment through issues:write, bound to its own repository (tested), and the cloud session's system prompt says to upload screenshots with g1t attach and never commit images just to link them. Docs: the CLI guide (Images in descriptions and comments), pull requests (Images), sessions, the MCP reference, self-hosting's USERCONTENT_URL row, reference.json and the regenerated openapi.json, CLI ops table and CLI reference. Deploy order: apply work migration 0048 and deploy work (new AVATARS binding), then the site (usercontent serves /attachments), then api, then integrations (prompt) and the docs; g1t attach and --attach ship with the next CLI release, which cloud sessions need before the prompt's advice works. Checked: cargo test -p g1t-contracts (319), -p g1t-work (209), -p g1t-api (211; openapi regenerated with G1T_WRITE_OPENAPI), -p g1t (112 + 51; ops_table and cli_reference regenerated with G1T_WRITE_OPS and G1T_WRITE_DOCS), -p g1t-integrations (76); cargo check -p g1t-api, -p g1t-work and -p g1t-integrations --target wasm32-unknown-unknown; node --test apps/web/app/lib/token-scopes.test.ts.5011621History
README.md
g1t/detect
Says what a g1t.page build of the checkout would find, without building or deploying anything: the same rules g1t/deploy builds with.
steps:
- uses: actions/checkout@v5
- id: detect
uses: g1t/detect@v1
- if: steps.detect.outputs.kind == 'none'
run: echo "::error::Nothing here would be served."
- if: steps.detect.outputs.framework == 'hugo'
run: echo "A Hugo site, served from ${{ steps.detect.outputs.output-dir }}"
- if: steps.detect.outputs.install != ''
run: ${{ steps.detect.outputs.install }}yaml
Inputs
| Input | Default | |
|---|---|---|
root-dir | The checkout | Where the project is in the checkout. |
build-command | None | A build command of your own, as g1t/deploy takes it. |
output-dir | None | An output folder of your own, as g1t/deploy takes it. |
build-as | auto | What it is built as: auto, static, workers or docker (Container), as g1t/deploy takes it. |
dockerfile | None | A Dockerfile's path of your own, as g1t/deploy takes it. |
Outputs
| Output | |
|---|---|
kind | workers (a wrangler.jsonc, wrangler.json or wrangler.toml, a framework's Cloudflare adapter, or a Node.js server run as a Worker), static (a site a build writes), html (the files as they are), container (a server that is not run as a Worker, a framework set up to need a server, or a Dockerfile with nothing else to serve) or none (nothing to serve). |
framework | The framework it is built with, from the table below; node for a Node.js server run as a Worker; for a container without one, dockerfile, or the server g1t writes a Dockerfile for (node, django, flask, fastapi, python, go, rails, rack, laravel, php). Empty when there is none. |
dockerfile | A container's own Dockerfile, relative to the project. Empty when g1t writes one, and for anything that is not a container. |
port | The port a container listens on: its Dockerfile's EXPOSE, else 8080. |
entry | For a Node.js server run as a Worker, the file that starts it, relative to the project. Empty for anything else. |
install | How dependencies are installed, from the lockfile: npm ci, pnpm install --frozen-lockfile, and so on, and bundle install for Jekyll. Empty when there is nothing to install. |
build | What builds it: your command, the package's build script (npm run build), the framework's own command, or wrangler deploy --dry-run for Workers. Empty when nothing does. |
output-dir | The folder served, relative to the project (. for the project itself): yours, a Workers project's assets directory, the folder the framework writes, or the first of dist, build, out, public, _site, .output/public that exists. Empty when a build has yet to write it, as for a Cloudflare adapter's Worker. |
When kind is none, the step's log says what to change or add, such as
a Dockerfile. Docker deployments
has the order in which a project's kind is decided.
Frameworks
framework | Found by | Serves |
|---|---|---|
vite, vite-react, vite-vue, vite-svelte, vite-solid, vite-preact | vite, and the framework's Vite plugin | dist, or build.outDir |
astro | astro | dist, or outDir; a Worker with @astrojs/cloudflare |
nextjs | next | out with output: 'export'; a Worker with @opennextjs/cloudflare and a wrangler.jsonc |
sveltekit | @sveltejs/kit | build with adapter-static; a Worker with adapter-cloudflare |
nuxt | nuxt | .output/public, from nuxi generate |
react-router, remix | @react-router/dev, @remix-run/dev | build/client with ssr: false |
gatsby | gatsby | public |
docusaurus | @docusaurus/core | build |
eleventy | @11ty/eleventy | _site |
angular | @angular/core and angular.json | dist/<project>/browser |
create-react-app | react-scripts | build |
hugo | hugo.toml, or Hugo's config.toml | public |
jekyll | _config.yml and a Gemfile | _site |
mdbook | book.toml | book |
What deploys has
each one's build command. Your build-command and output-dir win over
what is detected.
It runs g1t-runner detect, found or downloaded as g1t/deploy finds it.