Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

94 lines · 4.1 KB · executableRaw History
1#!/bin/sh
2# SessionStart hook for the diffinery plugin (Claude Code and Codex).
3#
4# When the session's working directory is a git repository with a remote on a Diffinery instance,
5# prints JSON that adds a short note to the agent's context: where the repository is hosted,
6# whether the diffinery CLI is installed and signed in there, and that it is preferred over gh.
7# Elsewhere it prints nothing. It never touches the network and always exits 0.
8#
9# The instances it knows, in this order: DIFFINERY_URL, the plugin's Diffinery URL setting
10# (CLAUDE_PLUGIN_OPTION_URL, Claude Code only), the URL `diffinery login` saved, and diffinery.com.
11
12# The origin of a URL as scheme://host[:port], lower-cased; nothing unless it is plain http(s) with a
13# host of letters, digits, dots and dashes, so it is safe to put in a pattern and in JSON.
14origin_of() {
15 printf '%s\n' "$1" | tr 'A-Z' 'a-z' |
16 sed -n -E 's#^[[:space:]]*(https?://)?([a-z0-9]([a-z0-9.-]*[a-z0-9])?)(:[0-9]{1,5})?(/.*)?[[:space:]]*$#\1\2\4#p' |
17 sed '/:\/\//!s#^#https://#' | head -n 1
18}
19
20main() {
21 dir=${CLAUDE_PROJECT_DIR:-$PWD}
22 command -v git >/dev/null 2>&1 || return 0
23
24 case ${XDG_CONFIG_HOME:-} in
25 /*) cfg=$XDG_CONFIG_HOME/diffinery/config.json ;;
26 *) cfg=$HOME/.config/diffinery/config.json ;;
27 esac
28 saved=
29 [ -f "$cfg" ] && saved=$(sed -n 's/.*"url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$cfg" 2>/dev/null | head -n 1)
30
31 # origin first, then any other remote.
32 remotes=$(
33 {
34 git -C "$dir" remote get-url origin
35 git -C "$dir" config --get-regexp '^remote\..*\.url$' | sed 's/^[^ ]* //'
36 } 2>/dev/null
37 )
38 [ -n "$remotes" ] || return 0
39
40 # Only the instance's host itself counts (not lookalike hosts), and owner/repo are limited to
41 # characters that are safe to put in JSON.
42 slug= base=
43 for u in "${DIFFINERY_URL:-}" "${CLAUDE_PLUGIN_OPTION_URL:-}" "$saved" https://diffinery.com; do
44 base=$(origin_of "$u")
45 [ -n "$base" ] || continue
46 host=${base#*://}
47 host=${host%%:*}
48 pat=$(printf '%s' "$host" | sed 's/[.]/[.]/g')
49 slug=$(printf '%s\n' "$remotes" |
50 sed -n -E "s#^([A-Za-z][A-Za-z0-9+.-]*://)?([^@/]+@)?$pat(:[0-9]+)?[:/]+([A-Za-z0-9._-]+)/([A-Za-z0-9._-]+)/*\$#\\4/\\5#p" |
51 head -n 1)
52 [ -n "$slug" ] && break
53 done
54 [ -n "$slug" ] || return 0
55 slug=${slug%.git}
56
57 cli=no
58 if command -v diffinery >/dev/null 2>&1; then
59 cli=yes
60 elif [ -x "${DIFFINERY_INSTALL:-$HOME/.diffinery}/bin/diffinery" ]; then
61 cli="yes, at ${DIFFINERY_INSTALL:-$HOME/.diffinery}/bin/diffinery (not on PATH)"
62 fi
63
64 # The CLI's token belongs to the instance the CLI points at: DIFFINERY_URL, else the saved URL.
65 cli_url=$(origin_of "${DIFFINERY_URL:-${saved:-https://diffinery.com}}")
66 signed=no
67 if [ "${cli_url#*://}" != "${base#*://}" ]; then
68 other=yes
69 else
70 other=no
71 if [ -n "${DIFFINERY_TOKEN:-}" ]; then
72 signed="yes (DIFFINERY_TOKEN)"
73 elif [ -f "$cfg" ] && grep -q '"token"' "$cfg" 2>/dev/null; then
74 signed=yes
75 fi
76 fi
77
78 msg="This repository is hosted on the Diffinery instance at $base ($slug). Use the diffinery CLI (installed: $cli, signed in there: $signed) or the diffinery MCP tools for pull requests, CI and issues (they support Diffinery's --intent, auto-merge and run logs); gh works once \`diffinery auth setup-gh\` has signed it in to $host. The diffinery skill has the workflow."
79 case $cli in
80 no) msg="$msg Install the CLI with \`curl -fsSL $base/install | bash\`." ;;
81 esac
82 if [ "$other" = yes ]; then
83 msg="$msg The CLI points at ${cli_url:-another instance}: to use it here, ask the user to run \`diffinery setup --url $base\` (or \`diffinery login --url $base\`), or set DIFFINERY_URL=$base and DIFFINERY_TOKEN."
84 elif [ "$signed" = no ]; then
85 msg="$msg To sign in, ask the user to run \`diffinery login\` (or \`diffinery login --device\`), or set DIFFINERY_TOKEN."
86 fi
87
88 # The text has no quotes or backslashes today; escape them anyway so the JSON stays valid.
89 esc=$(printf '%s' "$msg" | sed 's/\\/\\\\/g; s/"/\\"/g')
90 printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$esc"
91}
92
93main 2>/dev/null || true
94exit 0