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
102 changes: 102 additions & 0 deletions config/factory/activate-settings.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
# Merge managed Droid settings into Factory's app-owned settings file.
# Usage: activate-settings.sh <managed_settings_json> <jq_bin>
set -euo pipefail

MANAGED_SETTINGS="$1"
JQ_BIN="$2"
FACTORY_DIR="${HOME}/.factory"
SETTINGS="${FACTORY_DIR}/settings.json"
TEMP_SETTINGS=""

cleanup() {
if [ -n "$TEMP_SETTINGS" ]; then
rm -f "$TEMP_SETTINGS"
fi
}
trap cleanup EXIT

mkdir -p "$FACTORY_DIR"
TEMP_SETTINGS="$(mktemp "$FACTORY_DIR/settings.json.XXXXXX")"

if [ -f "$SETTINGS" ]; then
# The jq program intentionally contains literal $HOME for Factory to expand.
# shellcheck disable=SC2016
"$JQ_BIN" --slurpfile managed "$MANAGED_SETTINGS" '
def is_legacy_droid_command:
tostring | contains("droid-hook.sh");

def is_home_command($command; $relative):
($command == ("$HOME/" + $relative))
or (((env.HOME // "") != "")
and ($command == ((env.HOME // "") + "/" + $relative)));

def is_managed_hook_command($matcher):
tostring as $command
| (($matcher == "Execute")
and (is_home_command($command; "dotfiles/config/shared/hooks/security.sh")
or is_home_command($command; "dotfiles/config/shared/hooks/block-git-push.sh")
or is_home_command($command; "dotfiles/config/shared/hooks/block-gh-settings.sh")))
or (($matcher == "^(Edit|Write|Create|ApplyPatch)$")
and (is_home_command($command; "dotfiles/config/shared/hooks/secret-guard.sh")
or is_home_command($command; ".cargo/bin/git-ai checkpoint droid --hook-input stdin")));

def clean_hook_group:
if ((.hooks? | type) == "array") then
.matcher as $matcher
| .hooks |= map(
select(
(((.command? // "") | is_legacy_droid_command) | not)
and (((.command? // "") | is_managed_hook_command($matcher)) | not)
)
)
| select((.hooks | length) > 0)
else
.
end;

($managed[0]) as $managed_settings
| .sessionDefaultSettings =
((.sessionDefaultSettings // {}) * $managed_settings.sessionDefaultSettings)
| .customModels =
((.customModels // [])
| if type == "array" then
map(select(.id != $managed_settings.customModels[0].id))
+ $managed_settings.customModels
else
$managed_settings.customModels
end)
| .hooks = (.hooks // {})
| .hooks |= with_entries(
if ((.value | type) == "array") then
.value |= map(clean_hook_group)
else
.
end
)
| if ((.hooks.importedClaudeHooks? | type) == "array") then
.hooks.importedClaudeHooks |= map(
select(if type == "string"
then ((is_legacy_droid_command or contains("git-ai checkpoint droid")) | not)
else true
end)
)
else
.
end
| reduce ($managed_settings.hooks | to_entries[]) as $entry (.;
.hooks[$entry.key] =
(((.hooks[$entry.key] // [])
| if type == "array" then map(clean_hook_group) else [] end)
+ $entry.value)
)
' "$SETTINGS" >"$TEMP_SETTINGS"
else
# shellcheck disable=SC2016
"$JQ_BIN" --slurpfile managed "$MANAGED_SETTINGS" -n '
($managed[0])
' >"$TEMP_SETTINGS"
fi

mv -f "$TEMP_SETTINGS" "$SETTINGS"
chmod 600 "$SETTINGS"
20 changes: 19 additions & 1 deletion config/factory/default.nix
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
_: {
{
config,
lib,
pkgs,
...
}:
let
factorySettingsActivation = ./activate-settings.sh;
in
{
home.file.".factory/config.json" = {
source = ./config.json;
force = true;
};

# Factory owns settings.json and rewrites it as the app evolves. Merge only
# the managed Droid model and git-ai hooks so unrelated Factory preferences
# survive.
home.activation.factorySettings = config.lib.dag.entryAfter [ "writeBoundary" ] ''
$DRY_RUN_CMD ${pkgs.bash}/bin/bash "${factorySettingsActivation}" \
"${./settings.json}" \
"${pkgs.jq}/bin/jq"
'';
}
64 changes: 64 additions & 0 deletions config/factory/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"sessionDefaultSettings": {
"model": "custom:gemma3:4b-0",
"reasoningEffort": "none"
},
"customModels": [
{
"model": "gemma3:4b",
"id": "custom:gemma3:4b-0",
"index": 0,
"baseUrl": "http://127.0.0.1:11434/v1",
"apiKey": "ollama",
"displayName": "Gemma3:4b",
"maxOutputTokens": 64000,
"noImageSupport": true,
"provider": "generic-chat-completion-api"
}
],
"hooks": {
"PreToolUse": [
{
"matcher": "Execute",
"hooks": [
{
"command": "$HOME/dotfiles/config/shared/hooks/security.sh",
"type": "command"
},
{
"command": "$HOME/dotfiles/config/shared/hooks/block-git-push.sh",
"type": "command"
},
{
"command": "$HOME/dotfiles/config/shared/hooks/block-gh-settings.sh",
"type": "command"
}
]
},
{
"matcher": "^(Edit|Write|Create|ApplyPatch)$",
"hooks": [
{
"command": "$HOME/dotfiles/config/shared/hooks/secret-guard.sh",
"type": "command"
},
{
"command": "$HOME/.cargo/bin/git-ai checkpoint droid --hook-input stdin",
"type": "command"
}
]
}
],
"PostToolUse": [
{
"matcher": "^(Edit|Write|Create|ApplyPatch)$",
"hooks": [
{
"command": "$HOME/.cargo/bin/git-ai checkpoint droid --hook-input stdin",
"type": "command"
}
]
}
]
}
}
64 changes: 64 additions & 0 deletions config/factory/settings.tpl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"sessionDefaultSettings": {
"model": "custom:__GEMMA_LOCAL_NONDOT__-0",
"reasoningEffort": "none"
},
"customModels": [
{
"model": "__GEMMA_LOCAL__",
"id": "custom:__GEMMA_LOCAL_NONDOT__-0",
"index": 0,
"baseUrl": "http://127.0.0.1:11434/v1",
"apiKey": "ollama",
"displayName": "__GEMMA_LOCAL_PRETTY__",
"maxOutputTokens": 64000,
"noImageSupport": true,
"provider": "generic-chat-completion-api"
}
],
"hooks": {
"PreToolUse": [
{
"matcher": "Execute",
"hooks": [
{
"command": "$HOME/dotfiles/config/shared/hooks/security.sh",
"type": "command"
},
{
"command": "$HOME/dotfiles/config/shared/hooks/block-git-push.sh",
"type": "command"
},
{
"command": "$HOME/dotfiles/config/shared/hooks/block-gh-settings.sh",
"type": "command"
}
]
},
{
"matcher": "^(Edit|Write|Create|ApplyPatch)$",
"hooks": [
{
"command": "$HOME/dotfiles/config/shared/hooks/secret-guard.sh",
"type": "command"
},
{
"command": "$HOME/.cargo/bin/git-ai checkpoint droid --hook-input stdin",
"type": "command"
}
]
}
],
"PostToolUse": [
{
"matcher": "^(Edit|Write|Create|ApplyPatch)$",
"hooks": [
{
"command": "$HOME/.cargo/bin/git-ai checkpoint droid --hook-input stdin",
"type": "command"
}
]
}
]
}
}
3 changes: 3 additions & 0 deletions config/shared/hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

Both hooks exit `0` when a command may proceed and exit `2` with a `BLOCKED by ...` diagnostic when it must stop.

Factory Droid uses the Execute matcher for shell commands and the standard edit
matcher for file writes with these shared guardrails.

## Protected operations

The push hook blocks explicit and implicit updates or deletions of `main`, `master`, and the cached remote default branch. It resolves upstream and push configuration, bulk pushes, force variants, and Git aliases without executing alias bodies. Direct pushes remain allowed for `shunkakinoki/wiki` and `shunkakinoki/gthq`.
Expand Down
2 changes: 1 addition & 1 deletion config/shared/hooks/block-gh-settings.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Shared agent guardrail for GitHub repository control-plane mutations.
# Shared Codex/Claude/Cursor/Factory Droid agent guardrail for GitHub repository control-plane mutations.
# This is an early warning only; restricted credentials and server-side
# rulesets are the authoritative enforcement boundary.

Expand Down
2 changes: 1 addition & 1 deletion config/shared/hooks/block-git-push.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Shared agent guardrail for pushes to protected default branches.
# Shared Codex/Claude/Cursor/Factory Droid agent guardrail for pushes to protected default branches.
# This is an early warning only; remote rulesets and restricted credentials are
# the authoritative enforcement boundary.

Expand Down
2 changes: 1 addition & 1 deletion config/shared/hooks/secret-guard.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Shared PreToolUse hook for Claude Code and Codex Write/Edit operations.
# Shared PreToolUse hook for Claude Code, Codex, and Factory Droid Write/Edit operations.
# Blocks writes containing secrets detected by gitleaks.
# See: https://zenn.dev/takna/articles/secret-leak-prevention-4-layer
set -euo pipefail
Expand Down
4 changes: 2 additions & 2 deletions config/shared/hooks/security.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash

# Shared Codex/Copilot/Cursor/Grok Security Hook
# Shared Codex/Copilot/Cursor/Factory Droid/Grok Security Hook
# Blocks dangerous Bash commands by checking against deny patterns.
# Returns exit code 2 to block, exit code 0 to allow.
#
Expand All @@ -26,7 +26,7 @@ input=$(cat)
# Cursor sends only .command without a tool_name → empty passes through.
tool_name=$(echo "$input" | jq -r '.tool.name // .tool_name // .toolName // empty' 2>/dev/null)
case "$tool_name" in
"" | Bash | bash | shell) ;;
"" | Bash | bash | Execute | execute | shell) ;;
*) exit 0 ;;
esac

Expand Down
1 change: 1 addition & 0 deletions models.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"glm": "glm-4.7",
"minimax": "minimax-m3",
"gemma": "gemma-4-31b-it",
"gemma-local": "gemma3:4b",
"kimi": "kimi-k3",
"qwen": "qwen3.6-plus",
"qwen-local": "qwen3.5-0.8b-optiq"
Expand Down
1 change: 1 addition & 0 deletions scripts/llm-update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ declare -A TEMPLATES=(
["config/codex/config.tpl.toml"]=config/codex/config.toml
["config/cliproxyapi/config.tpl.yaml"]=config/cliproxyapi/config.template.yaml
["config/handy/settings_store.tpl.json"]=config/handy/settings_store.template.json
["config/factory/settings.tpl.json"]=config/factory/settings.json
["config/hermes/config.tpl.yaml"]=config/hermes/config.template.yaml
["config/omp/config.tpl.yml"]=config/omp/config.yml
["config/pi/models.tpl.json"]=config/pi/models.json
Expand Down
Loading