Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions UPSTREAM.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"files": {
"CONTRIBUTING.md": "0613b71aa497f8ca7d7296bf34ade87bfc7237a664d2e812d9b77b3b6befb0ad",
"README.md": "e54193fee53061889b3d5e82df7c76e72e440f265377d260b4a793475c216a7a",
"adapters/adapters.go": "80110df7289ea69f830b48667326ef297770c596e4ddbf1bc66201639f8288ad",
"adapters/adapters.go": "1b09f188ed9d3f8a1b159055556bd2a68153ad6ee9d59810b23c18a96a4a83bd",
"assets/pitot-boundary.png": "8a0ddb7d81831d94e14813f50ea4ca8670d77417f339ed2f91f0c653bf52f41d",
"assets/pitot-boundary.svg": "0c3871d70c84748573f231842091deb38a6def2862403ca34e8cc4493b9c9ebf",
"assets/pitot-hero.png": "a73532252b1e66c06273abbf5a4fe6261e98de3133b09e8d550edacfeeab92f8",
Expand All @@ -13,15 +13,15 @@
"bridge/bridge.go": "79ac2e025e16782f3c283b43cbea5b9ba4f837446583864df8f57d6346cae816",
"bridge/bridge_test.go": "23a19b7580d4b1e826224ec8322208ccca44ddd9a97b150efe15cafecc53e47f",
"cmd/generate-schema/main.go": "6e9d0030290d99e36967433f96e38385a122974f899ad9421aac1ef7e50d8fcb",
"cmd/pitot/main.go": "a8ef8a789abe13f41d8a1014de3fb4ad46522b558f0ace70786b904e81892b05",
"cmd/pitot/main_test.go": "ab2894327e7a5e72e9f3d83cae637e3ccb2e1ae05f14458be4fe9016149aeef7",
"cmd/pitot/main.go": "266cfdd1b758f2842a3cc232346e4ada4360077b1aa18c3a635697f56cab78a8",
"cmd/pitot/main_test.go": "7281ac0029d5e92c63c9feb1d2095aaef924aacf4e8a3db9af52872174d609ac",
"conformance/conformance.go": "43b692114f45c8b52958e34b35aee1cee339d8321c90f92ab4f5b963e79935bb",
"conformance/conformance_test.go": "83ab0bcc15371265a954d177e4e97d81ad3ea734bbf736a29a54628ef64b52cd",
"conformance/fixtures/negative.jsonl": "383dd001910699886bb1074d9225c91d8a6201e9fb3267d1a1f6e1c2753b0ba6",
"conformance/fixtures/positive.jsonl": "d3af0f2529dac9b33fa4900f5938e36fd0b17383088dd4f0de7e6eca269441d1",
"doc.go": "4dcd7a831a0a8ee6c3f6eeb8408c2e6898994509b11209564c0ed6b9a5218fce",
"doc.go": "9b51db0301aa428db731edcf8aa6b5a4fd71ae84f6a0d6cb2fc27656b04b1428",
"e2e/e2e_coverage_test.go": "8fac62d6d1c4ced359f8bb070379a88e77ded9912d867c642cda2d4703df23a7",
"e2e/e2e_hook_test.go": "a1c4601964eed674693306450249cf11a50978e5d2e00df152e33dc2c45ee1a0",
"e2e/e2e_hook_test.go": "cb9f27f4471c81c3320ea11be039a19514f5399b5ec5a80ae3b375dad8f8e8b5",
"examples/doc.go": "58f3f9eb7d272d7b6eecdb05f43e1613d5e3ef92d15d97c5440bd4b6990c26f9",
"examples/local-approval/main.go": "51386af324cd7d3bb07fe3ace53503884b02714b96b83073342fde81ce3b83a5",
"examples/token-meter/main.go": "4b1b9c1a43c3cf48b09dba6f607776caced9d2b5b562373496b31ed184582dd1",
Expand All @@ -42,7 +42,7 @@
"sensor/sensor.go": "939e146eaa51906972f98cf4615747eb75811b0c54d467a938825750336abeae",
"sensor/sensor_test.go": "ea9a58d45ad56d29214a40fb2cfa935e769f85334afcb1856a20fb938d486245",
"windtunnel/doc.go": "44e0bcde632da73e1f8b98beade3a34ca8e0d0ea79cdfb91d131de290b164fc4",
"windtunnel/windtunnel_test.go": "1c8b6c5d56ff5c4e66403baeae0d98108255d153c1275572b8c68e333e091493"
"windtunnel/windtunnel_test.go": "7e70ddf99411278175d2d2eb8ed73733367c77b3d04d55b4bf00738518673200"
},
"schema_version": 1
}
21 changes: 21 additions & 0 deletions adapters/adapters.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
Cursor Host = "cursor"
Claude Host = "claude"
Codex Host = "codex"
Gemini Host = "gemini"
)

// AdapterVersion is the semantic version stamped onto normalized events so
Expand Down Expand Up @@ -122,6 +123,26 @@ var (
Controllable: []string{"PreToolUse"},
},
},
Gemini: {
MainEventName: "BeforeTool",
Parser: ParserConfig{
CanonicalEvent: []byte(`{"hook_event_name":"BeforeTool","tool_name":"run_shell_command","tool_input":{"command":"git status --short"}}`),
CommandFor: func(raw RawHookEvent) (string, bool) {
if raw.ToolInput == nil {
return "", false
}
value, present := raw.ToolInput["command"].(string)
return value, present && value != ""
},
ActionKinds: map[string]string{
"BeforeTool": "shell",
},
},
Partition: ControlPartition{
// In Gemini, the BeforeTool hook is synchronous (blocking).
Controllable: []string{"BeforeTool"},
},
},
}
)

Expand Down
4 changes: 2 additions & 2 deletions cmd/pitot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ func run(args []string, stdout, stderr io.Writer) error {
// from stdin, normalizes it, and exits with 0 (allow) or 2 (block/deny).
func runHook(args []string, stdout, stderr io.Writer) error {
if len(args) == 0 {
return fmt.Errorf("pitot: hook requires a host identifier (cursor, claude, codex)")
return fmt.Errorf("pitot: hook requires a host identifier (cursor, claude, codex, gemini)")
}
host := adapters.Host(args[0])
if host != adapters.Cursor && host != adapters.Claude && host != adapters.Codex {
if host != adapters.Cursor && host != adapters.Claude && host != adapters.Codex && host != adapters.Gemini {
return fmt.Errorf("pitot: unsupported hook host %q", host)
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/pitot/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestDoctorReportsBoundary(t *testing.T) {
t.Fatalf("doctor: %v", err)
}
out := stdout.String()
for _, want := range []string{"local boundary", "cursor", "claude", "codex", "decoder=PASS", "unauthenticated local socket: none"} {
for _, want := range []string{"local boundary", "cursor", "claude", "codex", "gemini", "decoder=PASS", "unauthenticated local socket: none"} {
if !strings.Contains(out, want) {
t.Errorf("doctor output missing %q\n%s", want, out)
}
Expand Down
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//
// schema/ public event and response types + versioned constants
// protocol/ newline-delimited JSON framing and state-machine helpers
// adapters/ Claude Code, Cursor, and Codex host boundaries
// adapters/ Claude Code, Cursor, Codex, and Gemini CLI host boundaries
// sensor/ normalization and observation pipeline (decoder)
// bridge/ controller routing and single-response transport
// projection/ full, sha256, and omit content policies
Expand Down
1 change: 1 addition & 0 deletions e2e/e2e_hook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestE2ESensorsConformityAcrossAllAdapters(t *testing.T) {
adapters.Claude: `{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"git status --short"}}`,
adapters.Cursor: `{"hook_event_name":"beforeShellExecution","command":"git status --short"}`,
adapters.Codex: `{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"git status --short"}}`,
adapters.Gemini: `{"hook_event_name":"BeforeTool","tool_name":"run_shell_command","tool_input":{"command":"git status --short"}}`,
}

hosts := adapters.Supported()
Expand Down
1 change: 1 addition & 0 deletions windtunnel/windtunnel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var boatstackCanonicalEvents = map[adapters.Host]string{
adapters.Cursor: `{"hook_event_name":"beforeShellExecution","command":"git status --short"}`,
adapters.Claude: `{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"git status --short"}}`,
adapters.Codex: `{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"git status --short"}}`,
adapters.Gemini: `{"hook_event_name":"BeforeTool","tool_name":"run_shell_command","tool_input":{"command":"git status --short"}}`,
}

func TestSensorConsumesBoatstackCanonicalEvents(t *testing.T) {
Expand Down
Loading