helper-tools

Production extracts · MIT · no secrets

How to build a git Nostr client without starting from a blank panic.

This is the public face of gittr-helper-tools: the same snippets gittr uses, plus the screenshots so you can see why they exist. Copy a folder. Adapt relays. Keep the attribution. The boring README is still in the repo — this page is the fun door.

Step 0 · brain

Git files and Nostr notes are not the same pile.

Clients that mix these up show empty PRs, 0 commits, or a clone URL that is actually a relay. Two layers. Two jobs. Same hostname can wear both hats.

🟣 Git — the files

Commits, trees, blobs. You clone with git clone https://…. On Nostr git these HTTPS remotes are often GRASP hosts: git server and relay. If one advertised clone is down, try the next. They are advertised mirrors, not a magic always-in-sync cluster.

⚡ Nostr — the conversation

Announcements, PRs, issues, stars, pages, apps. These are events on wss:// relays. Looking at git will never find a pull request. Looking at a random social relay may miss the git announce. Query both worlds on purpose.

gittr sidebar Git Server and Clone URL list with three HTTPS remotes
Live gittr sidebar: Git Server is the preferred clone from the announcement (not “where the original push happened”). Clone URL (event) is the author’s advertised HTTPS remotes.
The path

Cookbook — ship a client in this order

1

Announce the repo

Kind 30617, empty content, tags only. Need a d (repo id) and at least one full clone URL with /npub…/repo.git. Optional 30618 for HEAD/state.

2

Fetch files from clone URLs

Parse every value on the clone tag (not only tag[1]). Race remotes. Classify GRASP vs GitHub vs someone’s NAS. Skip htree:// if you only speak HTTPS git.

3

Split GRASP hosts from ordinary relays

Same name can be git + wss. relay.gittr.space is the Nostr relay; git bytes for gittr live on git.gittr.space. Do not advertise the relay hostname as a clone.

4

Issues, PRs, comments

Kinds 1621 / 1618 / 1111 on relays. Subscribe wider than “our one default relay” or you will show empty threads that other clients can see.

5

Let humans sign in

Browser extension is NIP-07. Phone / Amber is NIP-46. Lightning wallet control is NIP-47 (NWC). Three protocols. Three secrets. Never put an nsec in the page.

6

Optional sparkle

Stars, watch lists, zaps, issue bounties, a static Page, an app listing. Each has a snippet. Steal only what your product needs.

See it · steal it

Screenshot, then the snippet that makes that screen honest

gittr-helper-tools repository page on gittr.space
A NIP-34 repo on gittr: Code tab, clone URLs in the sidebar, README from git. The announcement event is the index card. The git remotes are the warehouse.
Announce a repo snippets/nip34-repository-events
const event = {
  kind: 30617,
  content: "", // NIP-34: empty on purpose
  tags: [
    ["d", "my-repo"],
    ["name", "My Repository"],
    ["description", "A cool repo"],
    [
      "clone",
      "https://git.gittr.space/npub1…/my-repo.git",
      "https://relay.ngit.dev/npub1…/my-repo.git",
    ],
    ["relays", "wss://relay.gittr.space", "wss://relay.ngit.dev"],
  ],
};

// Parsers: read EVERY value after index 0 on clone/relays.
// Older events used one tag row per URL — accept both.
GRASP vs normal relays snippets/grasp-detection
export const GRASP_SERVERS_FOR_PUSHING = [
  "git.gittr.space",   // git bytes for gittr
  "relay.ngit.dev",
  "gitnostr.com",
  "ngit.danconwaydev.com",
  "git.shakespeare.diy",
];

// NOT a clone host:
// "relay.gittr.space"  ← Pyramid wss relay only

const isGrasp = isGraspServer("wss://relay.ngit.dev");
// true: git HTTPS + Nostr on related hostnames
gittr sidebar Relays split into Grasp Servers and Relays
Sidebar split is a label, not two query engines. Grasp chips are hosts that look like git+Nostr. Relays are wss addresses from the announcement plus what this browser already connected. Live PR/issue lookup uses a wider merged set.
Diagram of where gittr fetches repository files from
Where do the files come from? Browser drafts, GitHub/GitLab, self-hosted git, the gittr bridge, other GRASP, or Iris hashtree. Helper-tools is the snippet, not a host.
Parse a clone URL snippets/file-fetching
export type GitSourceType =
  | "nostr-git"        // GRASP https://host/npub/repo.git
  | "github" | "codeberg" | "gitlab"
  | "self-hosted-git"  // Gitea / NAS / Freebox
  | "hashtree"         // htree://  — not HTTPS git
  | "unknown";

// Host-only clones (https://git.example.com with no path)
// break other clients. Normalize before you publish.
// See snippets/clone-url-quality
Make a Page (this one) snippets/nip5a-gittr-pages
{
  "kind": 35128,
  "content": "",
  "tags": [
    ["d", "helper-tools"],          // 1–13 chars
    ["path", "/index.html", "<sha256>"],
    ["path", "/site.css", "<sha256>"],
    ["server", "https://blossom.gittr.space"],
    ["title", "Steal our homework"],
    ["relay", "wss://relay.gittr.space"]
  ]
}

// 1. Put index.html in the repo root
// 2. Upload bytes to Pages Blossom
// 3. Publish the manifest
// Apps/APKs do NOT go to blossom.gittr.space
gittr Published sites directory
gittr.space/pages lists sites that actually have a homepage. Yours shows up after Push Manifest on the repo sidebar.
gittr Apps on Nostr directory with NIP-82 listings
Apps are a different sport: NIP-82 kinds 32267 / 30063 / 3063. Default download URL is the forge Release. Optional pin to public Blossom — never Pages Blossom.
Announce an app snippets/nip82-software-announce
// 32267  application  (d = app id, name, repository)
// 3063   file asset    (x = sha256, url, version, m = MIME)
// 30063  release       (d = appId@version)

// Default url = GitHub/Codeberg/GitLab Release asset
// Optional pin hosts (first success wins):
//   blossom.primal.net
//   blossom.ditto.pub
//   haven.danconwaydev.com
// blossom.gittr.space is Pages-only. Keep APKs out.
Bounties on issues snippets/nip34-issue-bounties
{
  "kind": 9806,
  "content": "{\"amount\":50000,\"status\":\"paid\",\"lnurl\":\"…\"}",
  "tags": [
    ["e", "<issue-event-id>", "", "issue"],
    ["repo", "<entity>", "<repo-name>"],
    ["status", "paid"]
  ]
}

// Discovery on relays. Settlement on the host.
// PRs are kind 1618. Issues are 1621.
// Comments / threads often 1111.
gittr platform map: client, bridge, relay, pages, blossom, other NIP-34 clients
The neighborhood: gittr client, gitnostr bridge, Pyramid relay, Pages gateway, Blossom, plus gitworkshop / ngit / gitplaza. Helper-tools ships the recipes, not another forge.
Clone URL event list including nostr:// helper form
nostr://npub/repo is a helper URL for git-remote-nostr, not a fourth copy of the git. Plain git clone https://… is what most humans want.
Star and watch snippets/nip25-stars-nip51-following
// Star  = kind 7  reaction on the 30617 event id  ("+")
// Watch = kind 10018 replaceable list
//         a tags: "30617:<owner-hex>:<d>"

// Star needs the announcement event id from a WIDE
// relay set. Watching only needs the a-tag coordinate.

// Auth: NIP-07 in the browser, NIP-46 for Amber.
// Pay:  NIP-47 nostr+walletconnect://  (not the signer)
Drawer

Every snippet, without the lecture

Each folder has a README plus TypeScript you can paste. No @/ imports. Stub key storage yourself. Filter by mood:

file-fetching

Parse clone URLs: GRASP, forges, NAS, htree://.

url-normalization

SSH / git:// / nostr:// → HTTPS.

grasp-detection

Known GRASP domains vs ordinary relays. Push allowlist.

grasp-list

Kind 10317 preferred GRASP hosts (g tags).

clone-url-quality

Host-only / localhost / LAN clones that break other clients.

repair-host-only-clones

“Please republish” predicates. No sneaky push coupling.

filter-display-clone-urls

Sidebar: hide extra GRASP mirrors when a primary host exists.

filter-grasp-mirror-pollution

Strip mirror npub/hex roots from file trees.

nip34-repository-events

Build/parse 30617 / 30618. Multi-value tags.

nip34-push-paywall

Optional push_cost_sats on the announce.

nip34-issue-bounties

Kind 9806 offer/take + LNURL-withdraw.

nip25-stars-nip51-following

Star (kind 7) and Watch (kind 10018).

nip46-remote-signer

Amber / bunker pairing. No nsec in the page.

nip47-nwc

Wallet Connect pay_invoice / balance. Not NIP-46.

nip5a-gittr-pages

Named sites. You are looking at the result.

nip82-software-announce

Zapstore-shaped app events from a forge Release.

markdown-media-handling

README images, embeds, relative links that don’t 404.

nip-c0-code-snippets

Kind 1337 snippet events + renderer.

wot-trust-badges

Viewer-relative Web of Trust hop labels.

todos-discussions-kanban

How gittr does ToDo / Kanban today + future NIP notes.

github-oauth-writeback

Plan only: forge write-back. Not a NIP.

Scars

Don't ship this (we already did, then we un-did it)

Host-only clone tags

https://git.example.com with no /owner/repo.git makes gitworkshop and friends try to clone the hostname. Hide those announces or repair them before publish.

SSH inside clone[] for a web client

Browsers cannot git-over-SSH. Keep git@ out of kind 30617 clone. Show SSH in the sidebar if you must.

Only reading tag[1]

gittr (and ngit-style clients) put several HTTPS URLs on one clone row. If you only take the first extra cell, you throw away the mirrors.

relay.gittr.space as a git remote

That hostname is the Nostr relay. On-disk repos for gittr live under git.gittr.space. Mixing them makes the sidebar lie and git fetch 404.

Pages Blossom vs Apps Blossom

blossom.gittr.space is static sites. APKs / AppImages go to the forge or public Blossom (Primal / Ditto / Haven). Mixing them is how you accidentally host installers on the Pages disk.

Do this instead

Copy one snippet folder. Point relays at your defaults plus a discovery set (ngit, shakespeare, gitnostr, a general relay). Sign with NIP-07 or NIP-46. Keep nsecs out of git. Then add sparkle.

Kinds

Sticker sheet

Tap a pill in the hero, or just steal the numbers.

30617 repo announce 30618 repo state 1618 pull request 1621 issue 1111 comment 7 star 10018 watch list 10317 GRASP list 35128 named page 32267 app 3063 file asset 30063 release 9806 bounty 24133 NIP-46 RPC 23194 NWC 24242 Blossom auth 10002 NIP-65 relays