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
1 change: 1 addition & 0 deletions dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ upvote
URQL
Usercentrics
uuuu
venti
vnext
vsix
VXNlcjox
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using ChilliCream.Nitro.CommandLine.Services.Hook;
using ChilliCream.Nitro.CommandLine.Services.Workspace;

namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hook.Claude;

/// <summary>
/// Adapts Venti's <c>AgentNaming</c> hook: registers a new main session and answers with its
/// actor name as the agent name. Payload JSON on stdin; <c>{}</c> on stdout for any other agent.
/// </summary>
internal sealed class AgentNamingHookCommand : Command
{
public AgentNamingHookCommand(ClaudeHooksTemplate template) : base("agent-naming")
{
Description = $"Adapt {AgentHarnessDisplay.Name(template.Harness)}'s AgentNaming hook: "
+ "name a new main session after its actor.";

this.SetHookAction(
template.Harness,
ClaudeHooksTemplate.AgentNamingEvent,
(handler, payload, ct) => handler.HandleAgentNamingAsync(payload, skipSessionFileLookup: false, ct));
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
using ChilliCream.Nitro.CommandLine.Services.Hook;
using ChilliCream.Nitro.CommandLine.Services.Workspace;

namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hook.Claude;

/// <summary>
/// Groups the turn-boundary hook adapters of one harness speaking Claude Code's hook contract.
/// </summary>
internal sealed class ClaudeHookCommand : Command
{
public ClaudeHookCommand() : base("claude")
public ClaudeHookCommand(ClaudeHooksTemplate template) : base(template.CommandName)
{
Description = "Adapt Claude Code turn-boundary hook events.";
Description = $"Adapt {AgentHarnessDisplay.Name(template.Harness)} turn-boundary hook events.";

if (template.Events.Contains(ClaudeHooksTemplate.AgentNamingEvent))
{
Subcommands.Add(new AgentNamingHookCommand(template));
}

Subcommands.Add(new SessionStartHookCommand());
Subcommands.Add(new UserPromptSubmitHookCommand());
Subcommands.Add(new StopHookCommand());
Subcommands.Add(new NotificationHookCommand());
Subcommands.Add(new SessionEndHookCommand());
Subcommands.Add(new SessionStartHookCommand(template));
Subcommands.Add(new UserPromptSubmitHookCommand(template));
Subcommands.Add(new StopHookCommand(template));
Subcommands.Add(new NotificationHookCommand(template));
Subcommands.Add(new SessionEndHookCommand(template));
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
using ChilliCream.Nitro.CommandLine.Services.Hook;
using ChilliCream.Nitro.CommandLine.Services.Workspace;

namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hook.Claude;

/// <summary>
/// Adapts Claude Code's <c>Notification</c> hook: touches the session's last-seen
/// time on an idle-prompt notification. Payload JSON on stdin, <c>{}</c> on stdout,
/// always.
/// Adapts the <c>Notification</c> hook of a harness speaking Claude Code's hook
/// contract: touches the session's last-seen time on an idle-prompt notification.
/// Payload JSON on stdin, <c>{}</c> on stdout, always.
/// </summary>
internal sealed class NotificationHookCommand : Command
{
public NotificationHookCommand() : base("notification")
public NotificationHookCommand(ClaudeHooksTemplate template) : base("notification")
{
Description = "Adapt Claude Code's Notification hook: touch last-seen on an idle-prompt notification.";
Description = $"Adapt {AgentHarnessDisplay.Name(template.Harness)}'s Notification hook: "
+ "touch last-seen on an idle-prompt notification.";

this.SetHookAction(
template.Harness,
"Notification",
(handler, payload, ct) => handler.HandleNotificationAsync(payload, skipSessionFileLookup: false, ct));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
using ChilliCream.Nitro.CommandLine.Services.Hook;
using ChilliCream.Nitro.CommandLine.Services.Workspace;

namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hook.Claude;

/// <summary>
/// Adapts Claude Code's <c>SessionEnd</c> hook: marks this session's agent
/// row as ended. Payload JSON on stdin, <c>{}</c> on stdout, always.
/// Adapts the <c>SessionEnd</c> hook of a harness speaking Claude Code's hook
/// contract: marks this session's agent row as ended. Payload JSON on stdin,
/// <c>{}</c> on stdout, always.
/// </summary>
internal sealed class SessionEndHookCommand : Command
{
public SessionEndHookCommand() : base("session-end")
public SessionEndHookCommand(ClaudeHooksTemplate template) : base("session-end")
{
Description = "Adapt Claude Code's SessionEnd hook: mark this session's agent row as ended.";
Description = $"Adapt {AgentHarnessDisplay.Name(template.Harness)}'s SessionEnd hook: "
+ "mark this session's agent row as ended.";

this.SetHookAction(
template.Harness,
"SessionEnd",
(handler, payload, ct) => handler.HandleSessionEndAsync(payload, skipSessionFileLookup: false, ct));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
using ChilliCream.Nitro.CommandLine.Services.Hook;
using ChilliCream.Nitro.CommandLine.Services.Workspace;

namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hook.Claude;

/// <summary>
/// Adapts Claude Code's <c>SessionStart</c> hook: upserts this session's
/// presence row. Payload JSON on stdin, <c>{}</c> on stdout, always.
/// Adapts the <c>SessionStart</c> hook of a harness speaking Claude Code's hook
/// contract: upserts this session's presence row. Payload JSON on stdin,
/// <c>{}</c> on stdout, always.
/// </summary>
internal sealed class SessionStartHookCommand : Command
{
public SessionStartHookCommand() : base("session-start")
public SessionStartHookCommand(ClaudeHooksTemplate template) : base("session-start")
{
Description = "Adapt Claude Code's SessionStart hook: upsert this session's presence row.";
Description = $"Adapt {AgentHarnessDisplay.Name(template.Harness)}'s SessionStart hook: "
+ "upsert this session's presence row.";

this.SetHookAction(
template.Harness,
"SessionStart",
(handler, payload, ct) => handler.HandleSessionStartAsync(payload, skipSessionFileLookup: false, ct));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
using ChilliCream.Nitro.CommandLine.Services.Hook;
using ChilliCream.Nitro.CommandLine.Services.Workspace;

namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hook.Claude;

/// <summary>
/// Adapts Claude Code's <c>Stop</c> hook: blocks the turn from ending while
/// unread mail not yet delivered on the gate channel exists, honoring
/// reentrancy and the per-turn block budget. Payload JSON on stdin,
/// <c>{decision, reason}</c> or <c>{}</c> on stdout, always.
/// Adapts the <c>Stop</c> hook of a harness speaking Claude Code's hook contract:
/// blocks the turn from ending while unread mail not yet delivered on the gate
/// channel exists, honoring reentrancy and the per-turn block budget. Payload JSON
/// on stdin, <c>{decision, reason}</c> or <c>{}</c> on stdout, always.
/// </summary>
internal sealed class StopHookCommand : Command
{
public StopHookCommand() : base("stop")
public StopHookCommand(ClaudeHooksTemplate template) : base("stop")
{
Description = "Adapt Claude Code's Stop hook: block the turn while unread mail is undelivered.";
Description = $"Adapt {AgentHarnessDisplay.Name(template.Harness)}'s Stop hook: "
+ "block the turn while unread mail is undelivered.";

this.SetHookAction(
template.Harness,
"Stop",
(handler, payload, ct) => handler.HandleStopAsync(payload, skipSessionFileLookup: false, ct));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
using ChilliCream.Nitro.CommandLine.Services.Hook;
using ChilliCream.Nitro.CommandLine.Services.Workspace;

namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hook.Claude;

/// <summary>
/// Adapts Claude Code's <c>UserPromptSubmit</c> hook: resets the Stop
/// gate's per-turn block budget and injects the unread-mail digest.
/// Payload JSON on stdin, harness-shaped JSON on stdout, always.
/// Adapts the <c>UserPromptSubmit</c> hook of a harness speaking Claude Code's
/// hook contract: resets the Stop gate's per-turn block budget and injects the
/// unread-mail digest. Payload JSON on stdin, harness-shaped JSON on stdout, always.
/// </summary>
internal sealed class UserPromptSubmitHookCommand : Command
{
public UserPromptSubmitHookCommand() : base("user-prompt-submit")
public UserPromptSubmitHookCommand(ClaudeHooksTemplate template) : base("user-prompt-submit")
{
Description = "Adapt Claude Code's UserPromptSubmit hook: reset the block budget and "
+ "inject the unread-mail digest.";
Description = $"Adapt {AgentHarnessDisplay.Name(template.Harness)}'s UserPromptSubmit hook: "
+ "reset the block budget and inject the unread-mail digest.";

this.SetHookAction(
template.Harness,
"UserPromptSubmit",
(handler, payload, ct) => handler.HandleUserPromptSubmitAsync(payload, skipSessionFileLookup: false, ct));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@
namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hook;

/// <summary>
/// Binds a Claude hook command to stdin payload handling through
/// <see cref="ClaudeHookExecutor"/>.
/// Binds a Claude-shaped hook command to stdin payload handling through
/// <see cref="ClaudeHookExecutor"/> and the hook handler registered for its harness.
/// </summary>
internal static class ClaudeHookCommandExtensions
{
public static Command SetHookAction(
this Command command,
string harness,
string hookEventName,
Func<IClaudeHookHandler, ClaudeHookPayload, CancellationToken, Task<ClaudeHookOutcome>> handle)
{
command.SetAction(async (parseResult, cancellationToken) =>
{
var services = CommandExecutionContext.Services;
var handler = services.GetRequiredService<IClaudeHookHandler>();
var handler = services.GetRequiredKeyedService<IClaudeHookHandler>(harness);
var environmentVariables = services.GetRequiredService<IEnvironmentVariableProvider>();
return await ClaudeHookExecutor.RunAsync(
environmentVariables,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using ChilliCream.Nitro.CommandLine.Commands.Agent.Hook.Claude;
using ChilliCream.Nitro.CommandLine.Commands.Agent.Hook.Codex;
using ChilliCream.Nitro.CommandLine.Commands.Agent.Hook.Opencode;
using ChilliCream.Nitro.CommandLine.Services.Hook;

namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hook;

Expand All @@ -16,8 +17,9 @@ public HookCommand() : base("hook")
Description = "Translate harness turn-boundary hook payloads into digest and gate behavior.";
Hidden = true;

Subcommands.Add(new ClaudeHookCommand());
Subcommands.Add(new ClaudeHookCommand(ClaudeHooksTemplate.Claude));
Subcommands.Add(new CodexHookCommand());
Subcommands.Add(new OpencodeHookCommand());
Subcommands.Add(new ClaudeHookCommand(ClaudeHooksTemplate.Venti));
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
using ChilliCream.Nitro.CommandLine.Services.Hook;
using ChilliCream.Nitro.CommandLine.Services.Workspace;

namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hooks.Claude;

/// <summary>
/// Installs, inspects, and removes this CLI's Claude Code turn-boundary hook
/// entries in <c>settings.json</c>.
/// Installs, inspects, and removes this CLI's turn-boundary hook entries in the
/// <c>settings.json</c> of one harness speaking Claude Code's hook format.
/// </summary>
internal sealed class ClaudeHooksCommand : Command
{
public ClaudeHooksCommand() : base("claude")
public ClaudeHooksCommand(ClaudeHooksTemplate template, Option<string> scopeOption) : base(template.CommandName)
{
Description = "Install, inspect, and remove Nitro's Claude Code hook entries.";
Description =
$"Install, inspect, and remove Nitro's {AgentHarnessDisplay.Name(template.Harness)} hook entries.";

Subcommands.Add(new InstallClaudeHooksCommand());
Subcommands.Add(new StatusClaudeHooksCommand());
Subcommands.Add(new UninstallClaudeHooksCommand());
Subcommands.Add(new InstallClaudeHooksCommand(template, scopeOption));
Subcommands.Add(new StatusClaudeHooksCommand(template, scopeOption));
Subcommands.Add(new UninstallClaudeHooksCommand(template, scopeOption));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using ChilliCream.Nitro.CommandLine.Helpers;

namespace ChilliCream.Nitro.CommandLine.Commands.Agent.Hooks.Claude;

/// <summary>
/// Tells the user which of Nitro's hook entries their harness's own switches keep from running
/// and how to switch them on. Nitro never changes those switches itself.
/// </summary>
internal static class HookSwitchGuidance
{
public static void Write(
INitroConsole console,
string harnessName,
string settingsPath,
IReadOnlyList<string> disabledEvents,
string? allHooksDisabledIn)
{
if (disabledEvents.Count > 0)
{
console.MarkupLine(
($"{harnessName} skips Nitro's {string.Join(", ", disabledEvents)} hooks because their entries in "
+ $"'{settingsPath}' set \"enabled\": false. Remove that flag or set it to true to let them run.")
.AsWarning());
}

if (allHooksDisabledIn is not null)
{
console.MarkupLine(
($"{harnessName} runs no hooks, Nitro's included, while '{allHooksDisabledIn}' sets "
+ "\"disableAllHooks\": true. Set it to false or remove it to let them run.")
.AsWarning());
}
}
}
Loading
Loading