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
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ Declarative and reproducible environment across machines:
- Homebrew integration via `nix-homebrew`
- Cursor and Claude Code configurations from shared definitions

Agent rules, skills, and MCP servers are defined once in `users/shared/programs/agents`. Nix renders them into the per-client format each tool expects, so Cursor and Claude Code stay in sync.

## Layout

| Path | Contents |
Expand Down
3 changes: 1 addition & 2 deletions users/gkhaykin/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
imports = [
../shared/programs
./onepassword.nix
./programs/finicky.nix
./programs/vscode
./programs

../shared/completions.nix
../shared/fonts.nix
Expand Down
1 change: 1 addition & 0 deletions users/gkhaykin/onepassword.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ let

onePasswordEnvironmentVariables = {
ANTHROPIC_API_KEY = "op://Employee/Anthropic/credential";
GRAFANA_SERVICE_ACCOUNT_TOKEN = "op://Employee/Grafana MCP Service Account/token";
NETBOX_TOKEN = "op://Employee/NetBox/credential";
TOGETHER_PROD_API_KEY = "op://Employee/Together Prod/public_credential";
TOGETHER_PROD_INTERNAL_API_KEY = "op://Employee/Together Prod/internal_credential";
Expand Down
32 changes: 32 additions & 0 deletions users/gkhaykin/programs/agents/mcp.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{ lib, pkgs }:
let
linear.url = "https://mcp.linear.app/mcp";
notion.url = "https://mcp.notion.com/mcp";

mkGrafana = tokenReference: {
type = "stdio";
command = lib.getExe pkgs.mcp-grafana;
env = {
GRAFANA_URL = "https://togetherai.grafana.net";
GRAFANA_SERVICE_ACCOUNT_TOKEN = tokenReference;
};
};

cursor = {
inherit linear notion;
grafana = mkGrafana "\${env:GRAFANA_SERVICE_ACCOUNT_TOKEN}";
};

claude = {
linear = linear // {
type = "http";
};
notion = notion // {
type = "http";
};
grafana = mkGrafana "\${GRAFANA_SERVICE_ACCOUNT_TOKEN}";
};
in
{
inherit cursor claude;
}
37 changes: 37 additions & 0 deletions users/gkhaykin/programs/claude.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{ lib, pkgs, ... }:
let
mcpServers = (import ./agents/mcp.nix { inherit lib pkgs; }).claude;

skillSources = (import ../../shared/programs/agents/skills.nix { inherit lib; }) ./agents/skills;
skillFiles = lib.mapAttrs' (
skillName: source:
lib.nameValuePair ".claude/skills/${skillName}" {
inherit source;
recursive = true;
}
) skillSources;

removeMcpServer = serverName: ''
if printf '%s\n' "$registered_mcp_servers" | grep -q ${lib.escapeShellArg "^${serverName}:"}; then
$DRY_RUN_CMD claude mcp remove ${lib.escapeShellArg serverName} --scope user >/dev/null
fi
'';

addMcpServer = serverName: serverConfiguration: ''
$DRY_RUN_CMD claude mcp add-json --scope user ${lib.escapeShellArg serverName} ${lib.escapeShellArg (builtins.toJSON serverConfiguration)} >/dev/null
'';
in
{
home.file = skillFiles;

# Claude stores MCP servers alongside mutable state in ~/.claude.json, so use its CLI to preserve unrelated settings.
home.activation.claudeMcpServers = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
if command -v claude >/dev/null 2>&1; then
# Listing performs health checks, so cache the result before reconciling each server.
registered_mcp_servers="$(claude mcp list 2>/dev/null)"
${lib.concatMapStringsSep "\n" removeMcpServer (builtins.attrNames mcpServers)}
${lib.concatStringsSep "\n" (lib.mapAttrsToList addMcpServer mcpServers)}
unset registered_mcp_servers
fi
'';
}
19 changes: 19 additions & 0 deletions users/gkhaykin/programs/cursor.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{ lib, pkgs, ... }:
let
mcpServers = (import ./agents/mcp.nix { inherit lib pkgs; }).cursor;

skillSources = (import ../../shared/programs/agents/skills.nix { inherit lib; }) ./agents/skills;
skillFiles = lib.mapAttrs' (
skillName: source:
lib.nameValuePair ".cursor/skills/${skillName}" {
inherit source;
recursive = true;
}
) skillSources;
in
{
home.file = skillFiles // {
# Cursor keeps global MCP configuration in a dedicated file, so Home Manager can own it directly.
".cursor/mcp.json".text = builtins.toJSON { inherit mcpServers; };
};
}
9 changes: 9 additions & 0 deletions users/gkhaykin/programs/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{ ... }:
{
imports = [
./claude.nix
./cursor.nix
./finicky.nix
./vscode
];
}
16 changes: 9 additions & 7 deletions users/gkhaykin/programs/finicky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ export default {
defaultBrowser: "Google Chrome",
handlers: [
{
match: finicky.matchHostnames([
"together.ai",
/\.together\.ai$/,
"together-ai.okta.com",
/\.awsapps\.com$/,
/^oidc\..+\.amazonaws\.com$/,
]),
match: [
"together.ai*",
"*.together.ai*",
"together-ai.okta.com*",
"*.awsapps.com*",
"oidc.*.amazonaws.com*",
"github.com/togethercomputer*",
"github.com/orgs/togethercomputer*",
],
browser: {
name: "Google Chrome",
profile: "Together",
Expand Down
15 changes: 0 additions & 15 deletions users/shared/programs/agents/default.nix

This file was deleted.

103 changes: 0 additions & 103 deletions users/shared/programs/agents/mcp-servers.nix

This file was deleted.

22 changes: 4 additions & 18 deletions users/shared/programs/agents/rules.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{ lib, pkgs, ... }:
{ lib, pkgs }:
let
# NOTE:
# Absence of `filePatterns` in a rule means it always loads when the agent is active.
rules = {
code-principles = {
Expand Down Expand Up @@ -133,23 +134,8 @@ let
mkCursorRule =
name: rule: pkgs.writeText "${name}.mdc" (mkCursorMarkdownFrontmatter rule + readRuleContent name);

/**
Creates `home.file` entries for every rule using a tool-specific generator.

# Type

```
mkHomeRuleFiles :: String -> String -> (String -> AttrSet -> Path) -> AttrSet
```
*/
mkHomeRuleFiles =
directory: extension: mkRule:
lib.mapAttrs' (
name: rule: lib.nameValuePair "${directory}/${name}.${extension}" { source = mkRule name rule; }
) rules;
in
{
home.file =
mkHomeRuleFiles ".claude/rules" "md" mkClaudeRule
// mkHomeRuleFiles ".cursor/rules" "mdc" mkCursorRule;
claude = lib.mapAttrs mkClaudeRule rules;
cursor = lib.mapAttrs mkCursorRule rules;
}
18 changes: 18 additions & 0 deletions users/shared/programs/agents/skills.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{ lib }:
/**
Returns each immediate skill directory keyed by its name.
Non-directory entries are ignored.

# Type

```
Path -> AttrSet
```
*/
skillsDirectory:
let
skillNames = lib.attrNames (
lib.filterAttrs (_: entryType: entryType == "directory") (builtins.readDir skillsDirectory)
);
in
lib.genAttrs skillNames (skillName: skillsDirectory + "/${skillName}")
37 changes: 14 additions & 23 deletions users/shared/programs/claude.nix
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
# Claude Code global configuration
{ lib, ... }:
{ lib, pkgs, ... }:
let
mcpServers = (import ./agents/mcp-servers.nix { inherit lib; }).claude;
ruleSources = (import ./agents/rules.nix { inherit lib pkgs; }).claude;
ruleFiles = lib.mapAttrs' (
ruleName: source: lib.nameValuePair ".claude/rules/${ruleName}.md" { inherit source; }
) ruleSources;

/**
Generates an activation-script fragment that replaces a managed MCP server
with its declared configuration.

# Type

```
reconcileMcpServer :: String -> AttrSet -> String
```
*/
reconcileMcpServer = serverName: serverConfiguration: ''
if claude mcp list 2>/dev/null | grep -q ${lib.escapeShellArg "^${serverName}:"}; then
$DRY_RUN_CMD claude mcp remove ${lib.escapeShellArg serverName} --scope user >/dev/null || true
fi
$DRY_RUN_CMD claude mcp add-json --scope user ${lib.escapeShellArg serverName} ${lib.escapeShellArg (builtins.toJSON serverConfiguration)} >/dev/null || true
'';
skillSources = (import ./agents/skills.nix { inherit lib; }) ./agents/skills;
skillFiles = lib.mapAttrs' (
skillName: source:
lib.nameValuePair ".claude/skills/${skillName}" {
inherit source;
recursive = true;
}
) skillSources;
in
{
home.activation.claudeMcpServers = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
if command -v claude >/dev/null 2>&1; then
${lib.concatStringsSep "\n" (lib.mapAttrsToList reconcileMcpServer mcpServers)}
fi
'';
home.file = ruleFiles // skillFiles;
}
19 changes: 19 additions & 0 deletions users/shared/programs/cursor/agents.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{ lib, pkgs, ... }:
let
ruleSources = (import ../agents/rules.nix { inherit lib pkgs; }).cursor;
ruleFiles = lib.mapAttrs' (
ruleName: source: lib.nameValuePair ".cursor/rules/${ruleName}.mdc" { inherit source; }
) ruleSources;

skillSources = (import ../agents/skills.nix { inherit lib; }) ../agents/skills;
skillFiles = lib.mapAttrs' (
skillName: source:
lib.nameValuePair ".cursor/skills/${skillName}" {
inherit source;
recursive = true;
}
) skillSources;
in
{
home.file = ruleFiles // skillFiles;
}
Loading
Loading