@kaydash9999/banker-plugins 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +66 -0
  4. package/README.md +23 -11
  5. package/codex/manifest.json +5 -5
  6. package/codex/transform-matrix.md +5 -5
  7. package/commands/front-qa.md +2 -2
  8. package/commands/setup.md +13 -4
  9. package/hooks/graceful-pause.mjs +222 -0
  10. package/hooks/hooks.json +2 -1
  11. package/hooks/register.mjs +12 -0
  12. package/package.json +4 -1
  13. package/skills/3d-intro-build/SKILL.md +1 -1
  14. package/skills/3d-intro-setup/SKILL.md +1 -1
  15. package/skills/all-in-one/SKILL.md +1 -1
  16. package/skills/append_wiki/SKILL.md +1 -1
  17. package/skills/arch-diagram/SKILL.md +1 -1
  18. package/skills/audit-mock/SKILL.md +1 -1
  19. package/skills/audit-security/SKILL.md +1 -1
  20. package/skills/audit-web-page/SKILL.md +1 -1
  21. package/skills/cleansing-memory/SKILL.md +1 -1
  22. package/skills/compact-copy/SKILL.md +1 -1
  23. package/skills/compact-wiki/SKILL.md +1 -1
  24. package/skills/curation/SKILL.md +1 -1
  25. package/skills/deep-init/SKILL.md +1 -1
  26. package/skills/deep-research/SKILL.md +1 -1
  27. package/skills/docs-setup/SKILL.md +1 -1
  28. package/skills/humanizer/SKILL.md +1 -1
  29. package/skills/interval-report/SKILL.md +1 -1
  30. package/skills/lineage/SKILL.md +152 -16
  31. package/skills/lineage/lineage.py +1210 -108
  32. package/skills/make-notion-guide/SKILL.md +1 -1
  33. package/skills/motion-graphic-make/SKILL.md +1 -1
  34. package/skills/motion-graphic-setup/SKILL.md +1 -1
  35. package/skills/nothing-design/SKILL.md +1 -1
  36. package/skills/obsidizer/SKILL.md +1 -1
  37. package/skills/omc-reference/SKILL.md +1 -1
  38. package/skills/payload-mon/SKILL.md +1 -1
  39. package/skills/pdf-vision-extract/SKILL.md +1 -1
  40. package/skills/play-qa/SKILL.md +1 -1
  41. package/skills/ralph-qa/SKILL.md +304 -112
  42. package/skills/ralph-qa/references/gemini-read-only.toml +9 -0
  43. package/skills/ralph-qa/references/gemini-seat.mjs +207 -0
  44. package/skills/ralph-qa/references/payload-scan.mjs +154 -0
  45. package/skills/ralph-qa/references/verifier-probe.mjs +881 -780
  46. package/skills/ready-compact/SKILL.md +1 -1
  47. package/skills/refresh-git-ignore/SKILL.md +1 -1
  48. package/skills/refresh-readme/SKILL.md +1 -1
  49. package/skills/rfp-author/SKILL.md +1 -1
  50. package/skills/setup-bypass-permissions/SKILL.md +123 -0
  51. package/skills/setup-bypass-permissions/agents/openai.yaml +4 -0
  52. package/skills/setup-bypass-permissions/scripts/bypass-permissions.mjs +381 -0
  53. package/skills/setup-bypass-permissions/scripts/fallback/bypass-permissions.ps1 +247 -0
  54. package/skills/setup-bypass-permissions/scripts/fallback/bypass-permissions.py +241 -0
  55. package/skills/{harness-factory → setup-harness-factory}/SKILL.md +3 -3
  56. package/skills/setup-insane-search/SKILL.md +1 -1
  57. package/skills/setup-java/SKILL.md +1 -1
  58. package/skills/setup-lsp/SKILL.md +1 -1
  59. package/skills/setup-mcp/SKILL.md +1 -1
  60. package/skills/setup-node/SKILL.md +1 -1
  61. package/skills/setup-omc/SKILL.md +1 -1
  62. package/skills/setup-omc-hud/SKILL.md +15 -1
  63. package/skills/setup-omc-hud/scripts/claude-update-last.mjs +247 -0
  64. package/skills/setup-playwright/SKILL.md +1 -1
  65. package/skills/setup-pwsh/SKILL.md +1 -1
  66. package/skills/setup-python/SKILL.md +1 -1
  67. package/skills/setup-sandbox/SKILL.md +1 -1
  68. package/skills/setup-stitch/SKILL.md +1 -1
  69. package/skills/setup-tmux/SKILL.md +1 -1
  70. package/skills/smart-compact/SKILL.md +1 -1
  71. package/skills/summary-wiki/SKILL.md +1 -1
  72. package/skills/tone-compact/SKILL.md +1 -1
  73. package/skills/ultra-init/SKILL.md +1 -1
  74. package/skills/ultra-interview/SKILL.md +1 -1
  75. package/skills/ultra-ui-qa/SKILL.md +1 -1
  76. package/skills/update-banker/SKILL.md +1 -1
  77. package/skills/vertical-pptx/SKILL.md +1 -1
  78. package/skills/vertical-pptx-setup/SKILL.md +1 -1
  79. package/skills/visual-ralph/SKILL.md +1 -1
  80. package/skills/graceful_pause/SKILL.md +0 -95
@@ -0,0 +1,247 @@
1
+ # setup-bypass-permissions step 3 on Windows, for when node cannot run the main script.
2
+ #
3
+ # powershell -NoProfile -File bypass-permissions.ps1 on --yes set permissions.defaultMode = "bypassPermissions"
4
+ # powershell -NoProfile -File bypass-permissions.ps1 off remove that key (Claude Code's default start mode)
5
+ #
6
+ # The file is <config>\settings.json, <config> being $env:CLAUDE_CONFIG_DIR or %USERPROFILE%\.claude.
7
+ # Every other key stays, and so does a BOM. `on` keeps the text from before in
8
+ # settings.json.bypass-permissions.bak; a second `on` finds the mode set and writes nothing. Both files are
9
+ # replaced whole through a temp file, so a stop partway leaves the old text. Paths are taken literally:
10
+ # [ and ] in a folder name are not wildcards. Only the node script's `off` knows what `on` replaced;
11
+ # this `off` removes the key.
12
+ #
13
+ # `on` refuses (exit 1, nothing written) when managed policy (managed-settings.json or its
14
+ # managed-settings.d drop-ins) or the settings file itself sets permissions.disableBypassPermissionsMode,
15
+ # for a file that is not a JSON object, for a policy file or folder this account cannot read, and on a
16
+ # machine with no Claude Code config folder.
17
+ #
18
+ # Exit status: 0 done, or nothing to do. 1 refused. 2 unexpected error. 3 this account may not write the
19
+ # file or its folder. Every result ends with the line `settings file: <path>` on stdout.
20
+ # Windows PowerShell 5.1 or PowerShell 7. ASCII only: Windows PowerShell 5.1 reads a script without a BOM
21
+ # in the ANSI code page.
22
+
23
+ $ErrorActionPreference = 'Stop'
24
+ Set-StrictMode -Off
25
+ $Mode = 'bypassPermissions'
26
+ $Utf8 = New-Object System.Text.UTF8Encoding($false, $true) # no BOM, and bytes that are not UTF-8 throw instead of turning into U+FFFD
27
+
28
+ $script:SettingsPath = ''
29
+
30
+ # Claude Code reads this output through a pipe as UTF-8, but Windows PowerShell 5.1 writes the console code
31
+ # page there (949 on Korean Windows), garbling a non-ASCII path. Redirected streams get UTF-8; a console keeps its own.
32
+ $script:Out = [Console]::Out
33
+ $script:Err = [Console]::Error
34
+ $Utf8Out = New-Object System.Text.UTF8Encoding($false)
35
+ if ([Console]::IsOutputRedirected) { $script:Out = New-Object System.IO.StreamWriter([Console]::OpenStandardOutput(), $Utf8Out); $script:Out.AutoFlush = $true }
36
+ if ([Console]::IsErrorRedirected) { $script:Err = New-Object System.IO.StreamWriter([Console]::OpenStandardError(), $Utf8Out); $script:Err.AutoFlush = $true }
37
+
38
+ # Every result ends with the settings file line, so the report can name the file this acted on.
39
+ function Write-Out([string]$text) {
40
+ $script:Out.WriteLine($text)
41
+ $script:Out.WriteLine("settings file: $script:SettingsPath")
42
+ }
43
+
44
+ function Stop-With([int]$code, [string]$text) {
45
+ $script:Err.WriteLine($text)
46
+ $script:Out.WriteLine("settings file: $script:SettingsPath")
47
+ exit $code
48
+ }
49
+
50
+ function Test-Denied($record) {
51
+ $e = $record.Exception
52
+ while ($null -ne $e) {
53
+ if ($e -is [System.UnauthorizedAccessException]) { return $true }
54
+ $e = $e.InnerException
55
+ }
56
+ return $false
57
+ }
58
+
59
+ trap {
60
+ if (Test-Denied $_) { Stop-With 3 "write blocked: $($_.Exception.Message); settings.json left as it was" }
61
+ Stop-With 2 "unexpected error: $($_.Exception.Message); check settings.json"
62
+ }
63
+
64
+ function Test-Forbids($data) {
65
+ if ($data -isnot [System.Management.Automation.PSCustomObject]) { return $false }
66
+ $perm = $data.PSObject.Properties['permissions']
67
+ if ($null -eq $perm -or $perm.Value -isnot [System.Management.Automation.PSCustomObject]) { return $false }
68
+ $flag = $perm.Value.PSObject.Properties['disableBypassPermissionsMode']
69
+ if ($null -eq $flag) { return $false }
70
+ return (($flag.Value -is [bool]) -and $flag.Value) -or (($flag.Value -is [string]) -and ($flag.Value -ceq 'disable'))
71
+ }
72
+
73
+ # The text of UTF-8 bytes without a leading BOM; bytes that are not UTF-8 throw.
74
+ function Read-Text([byte[]]$bytes) {
75
+ $text = $Utf8.GetString($bytes)
76
+ if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
77
+ return $text
78
+ }
79
+
80
+ function Read-Json([string]$text) {
81
+ if ($text.Trim().Length -eq 0) { return [pscustomobject]@{} }
82
+ return ($text | ConvertFrom-Json)
83
+ }
84
+
85
+ function Get-PolicyHome {
86
+ # The override exists for this skill's tests alone.
87
+ if ($env:BANKER_BYPASS_TEST -eq '1' -and $env:BANKER_BYPASS_POLICY_HOME) { return $env:BANKER_BYPASS_POLICY_HOME }
88
+ if ($IsMacOS) { return '/Library/Application Support/ClaudeCode' }
89
+ if ($IsLinux) { return '/etc/claude-code' }
90
+ return 'C:\Program Files\ClaudeCode'
91
+ }
92
+
93
+ function Stop-Unreadable([string]$path) {
94
+ Stop-With 1 "refused: this machine's managed policy cannot be read ($path); ask the administrator whether it allows the mode; settings.json left as it was"
95
+ }
96
+
97
+ # True when the failure only says the path is not there (as opposed to: there, but this account cannot read it).
98
+ function Test-Missing($record) {
99
+ $e = $record.Exception
100
+ while ($null -ne $e) {
101
+ if ($e -is [System.IO.FileNotFoundException] -or $e -is [System.IO.DirectoryNotFoundException]) { return $true }
102
+ $e = $e.InnerException
103
+ }
104
+ return $false
105
+ }
106
+
107
+ # The policy file that forbids the mode, or ''. A policy file or folder this account cannot read is refused,
108
+ # also when the folder above it cannot be searched (File.Exists and Directory.Exists say false then).
109
+ function Get-PolicyBlock {
110
+ $policyHome = Get-PolicyHome
111
+ $files = @([System.IO.Path]::Combine($policyHome, 'managed-settings.json'))
112
+ $drop = [System.IO.Path]::Combine($policyHome, 'managed-settings.d')
113
+ $listed = @()
114
+ if (-not [System.IO.File]::Exists($drop)) {
115
+ try { $listed = [System.IO.Directory]::GetFiles($drop) } catch { if (-not (Test-Missing $_)) { Stop-Unreadable $drop } }
116
+ }
117
+ $files += @($listed | Where-Object {
118
+ $name = [System.IO.Path]::GetFileName($_)
119
+ $name.EndsWith('.json') -and -not $name.StartsWith('.')
120
+ } | Sort-Object)
121
+ foreach ($file in $files) {
122
+ if ([System.IO.Directory]::Exists($file)) { continue }
123
+ $bytes = $null
124
+ try { $bytes = [System.IO.File]::ReadAllBytes($file) } catch { if (-not (Test-Missing $_)) { Stop-Unreadable $file } }
125
+ if ($null -eq $bytes) { continue }
126
+ try { $policy = Read-Json (Read-Text $bytes) } catch { continue }
127
+ if (Test-Forbids $policy) { return $file }
128
+ }
129
+ return ''
130
+ }
131
+
132
+ # @{ Data; Raw } for a JSON object; Raw is $null when there is no file. Anything else is refused.
133
+ function Read-Settings([string]$path) {
134
+ if (-not [System.IO.File]::Exists($path)) { return @{ Data = [pscustomobject]@{}; Raw = $null } }
135
+ $raw = [System.IO.File]::ReadAllBytes($path)
136
+ try { $text = Read-Text $raw } catch { Stop-With 1 'refused: settings.json is not valid UTF-8; settings.json left as it was' }
137
+ try { $data = Read-Json $text } catch { Stop-With 1 "refused: settings.json is not valid JSON ($($_.Exception.Message)); settings.json left as it was" }
138
+ if ($data -isnot [System.Management.Automation.PSCustomObject]) { Stop-With 1 'refused: settings.json is not a JSON object; settings.json left as it was' }
139
+ $perm = $data.PSObject.Properties['permissions']
140
+ if ($null -ne $perm -and $perm.Value -isnot [System.Management.Automation.PSCustomObject]) {
141
+ Stop-With 1 'refused: the permissions entry of settings.json is not an object; settings.json left as it was'
142
+ }
143
+ return @{ Data = $data; Raw = $raw }
144
+ }
145
+
146
+ function Test-Link([string]$path) {
147
+ $item = Get-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue
148
+ return ($null -ne $item) -and ($item.LinkType -in 'SymbolicLink', 'Junction')
149
+ }
150
+
151
+ # The file a chain of links at path ends at (40 hops at most), or path itself.
152
+ function Resolve-Real([string]$path) {
153
+ for ($hop = 0; $hop -lt 40 -and (Test-Link $path); $hop++) {
154
+ $target = @((Get-Item -LiteralPath $path -Force).Target)[0]
155
+ if (-not [System.IO.Path]::IsPathRooted($target)) {
156
+ $target = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($path), $target)
157
+ }
158
+ $path = [System.IO.Path]::GetFullPath($target)
159
+ }
160
+ return $path
161
+ }
162
+
163
+ # On macOS and Linux (PowerShell 7.3+), give tmp the mode of like, or 0600; Windows keeps the folder's ACL.
164
+ function Set-Mode([string]$tmp, [string]$like, [bool]$private) {
165
+ if (-not ($IsLinux -or $IsMacOS)) { return }
166
+ if ($null -eq [System.IO.File].GetMethod('SetUnixFileMode', [type[]]@([string], [System.IO.UnixFileMode]))) { return }
167
+ $mode = [System.IO.UnixFileMode]'UserRead, UserWrite'
168
+ if (-not $private -and [System.IO.File]::Exists($like)) { $mode = [System.IO.File]::GetUnixFileMode($like) }
169
+ [System.IO.File]::SetUnixFileMode($tmp, $mode)
170
+ }
171
+
172
+ # Writes bytes to path through a temp file in the same folder. A link at path is replaced, not followed.
173
+ # [NullString]::Value: PowerShell turns $null into "" for a .NET string parameter, and Replace refuses "".
174
+ function Write-Whole([string]$path, [byte[]]$bytes, [bool]$private) {
175
+ $full = [System.IO.Path]::GetFullPath($path)
176
+ $tmp = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($full), '.bypass-permissions.' + [guid]::NewGuid().ToString('N') + '.tmp')
177
+ try {
178
+ [System.IO.File]::WriteAllBytes($tmp, $bytes)
179
+ Set-Mode $tmp $full $private
180
+ if (Test-Link $full) { [System.IO.File]::Delete($full) }
181
+ if ([System.IO.File]::Exists($full)) { [System.IO.File]::Replace($tmp, $full, [NullString]::Value) } else { [System.IO.File]::Move($tmp, $full) }
182
+ } catch {
183
+ if ([System.IO.File]::Exists($tmp)) { [System.IO.File]::Delete($tmp) }
184
+ throw
185
+ }
186
+ }
187
+
188
+ function Save-Settings([string]$path, $data, $raw) {
189
+ $bytes = $Utf8.GetBytes(($data | ConvertTo-Json -Depth 64) + "`n")
190
+ if ($null -ne $raw -and $raw.Length -ge 3 -and $raw[0] -eq 0xEF -and $raw[1] -eq 0xBB -and $raw[2] -eq 0xBF) {
191
+ $bytes = [byte[]](@([byte]0xEF, [byte]0xBB, [byte]0xBF) + $bytes)
192
+ }
193
+ Write-Whole (Resolve-Real $path) $bytes $false
194
+ }
195
+
196
+ # Drop the node script's record of what `on` replaced. This fallback keeps no record, so one left by an earlier
197
+ # node `on` is stale: the mode is no longer what it describes, and a later node `off` would restore it.
198
+ function Remove-Record([string]$path) { [System.IO.File]::Delete($path + '.bypass-permissions.json') }
199
+
200
+ function Get-Mode($data) {
201
+ $perm = $data.PSObject.Properties['permissions']
202
+ if ($null -eq $perm -or $null -eq $perm.Value.PSObject.Properties['defaultMode']) { return $null }
203
+ return $perm.Value.defaultMode
204
+ }
205
+
206
+ function Enable-Mode([string]$path) {
207
+ $config = [System.IO.Path]::GetDirectoryName($path)
208
+ if (-not [System.IO.Directory]::Exists($config)) { Stop-With 1 "refused: there is no Claude Code config folder at $config; settings.json left as it was" }
209
+ $settings = Read-Settings $path
210
+ $blocked = Get-PolicyBlock
211
+ if ($blocked) { Stop-With 1 "refused: managed policy forbids bypassPermissions: $blocked; settings.json left as it was" }
212
+ if (Test-Forbids $settings.Data) { Stop-With 1 'refused: settings.json itself sets permissions.disableBypassPermissionsMode; settings.json left as it was' }
213
+ if ((Get-Mode $settings.Data) -ceq $Mode) { Write-Out "defaultMode = $Mode (already, nothing changed)"; exit 0 }
214
+ if ($null -ne $settings.Raw) {
215
+ [System.IO.File]::Open($path, 'Open', 'ReadWrite').Dispose()
216
+ Write-Whole ($path + '.bypass-permissions.bak') $settings.Raw $true
217
+ }
218
+ Remove-Record $path # the mode is not on, so any record is stale
219
+ if ($null -eq $settings.Data.PSObject.Properties['permissions']) {
220
+ $settings.Data | Add-Member -NotePropertyName permissions -NotePropertyValue ([pscustomobject]@{})
221
+ }
222
+ $settings.Data.permissions | Add-Member -NotePropertyName defaultMode -NotePropertyValue $Mode -Force
223
+ Save-Settings $path $settings.Data $settings.Raw
224
+ Write-Out "defaultMode = $Mode"
225
+ exit 0
226
+ }
227
+
228
+ function Disable-Mode([string]$path) {
229
+ $settings = Read-Settings $path
230
+ $current = Get-Mode $settings.Data
231
+ if ($current -cne $Mode) { Write-Out "defaultMode = $current (not bypassPermissions, nothing changed)"; exit 0 }
232
+ [System.IO.File]::Open($path, 'Open', 'ReadWrite').Dispose() # a read-only settings file is a lock: off honours it as on does
233
+ [void]$settings.Data.permissions.PSObject.Properties.Remove('defaultMode')
234
+ Save-Settings $path $settings.Data $settings.Raw
235
+ Remove-Record $path # only after the write: if it failed, the record is still the way back
236
+ Write-Out "defaultMode removed (Claude Code's default start mode)"
237
+ exit 0
238
+ }
239
+
240
+ $userHome = if ($env:USERPROFILE) { $env:USERPROFILE } else { $HOME }
241
+ $configDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { [System.IO.Path]::Combine($userHome, '.claude') }
242
+ $settingsPath = [System.IO.Path]::Combine($configDir, 'settings.json')
243
+ $script:SettingsPath = $settingsPath
244
+ $request = $args -join ' '
245
+ if ($request -ceq 'on --yes') { Enable-Mode $settingsPath }
246
+ if ($request -ceq 'off') { Disable-Mode $settingsPath }
247
+ Stop-With 1 "refused: usage is 'bypass-permissions.ps1 on --yes' or 'bypass-permissions.ps1 off'"
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env python3
2
+ """setup-bypass-permissions step 3 on macOS and Linux, for when node cannot run the main script.
3
+
4
+ python3 bypass-permissions.py on --yes set permissions.defaultMode = "bypassPermissions"
5
+ python3 bypass-permissions.py off remove that key (Claude Code's default start mode)
6
+
7
+ The file is <config>/settings.json, <config> being $CLAUDE_CONFIG_DIR or ~/.claude. Every other
8
+ key stays, and so does a BOM. `on` keeps the text from before in settings.json.bypass-permissions.bak,
9
+ readable by this account alone; a second `on` finds the mode set and writes nothing. Both files are
10
+ replaced whole through a temp file and a rename, so a stop partway leaves the old text, and a link at
11
+ the settings name stays a link. Only the node script's `off` knows what `on` replaced; this `off`
12
+ removes the key.
13
+
14
+ `on` refuses (exit 1, nothing written) where Claude Code would not honour the mode or not start with
15
+ it: managed policy (managed-settings.json or its managed-settings.d drop-ins) or the settings file
16
+ itself sets permissions.disableBypassPermissionsMode, or the account is root outside a sandbox Claude
17
+ Code recognises (IS_SANDBOX=1, or its bubblewrap one). It also refuses a file that is not a JSON object,
18
+ a policy file or folder this account cannot read, and a machine with no Claude Code config folder.
19
+
20
+ Exit status: 0 done, or nothing to do. 1 refused. 2 unexpected error. 3 this account may not write
21
+ the file or its folder. Every result ends with the line `settings file: <path>` on stdout.
22
+ Python 3.6 or later.
23
+ """
24
+ import errno
25
+ import json
26
+ import os
27
+ import sys
28
+ import tempfile
29
+
30
+ MODE = "bypassPermissions"
31
+ BOM = b"\xef\xbb\xbf"
32
+ POLICY_HOMES = {"darwin": "/Library/Application Support/ClaudeCode"}
33
+
34
+
35
+ class Refused(Exception):
36
+ pass
37
+
38
+
39
+ def settings_path():
40
+ config = os.environ.get("CLAUDE_CONFIG_DIR") or os.path.expanduser("~/.claude")
41
+ return os.path.join(config, "settings.json")
42
+
43
+
44
+ def policy_home():
45
+ # The override exists for this skill's tests alone.
46
+ if os.environ.get("BANKER_BYPASS_TEST") == "1" and os.environ.get("BANKER_BYPASS_POLICY_HOME"):
47
+ return os.environ["BANKER_BYPASS_POLICY_HOME"]
48
+ return POLICY_HOMES.get(sys.platform, "/etc/claude-code")
49
+
50
+
51
+ def forbids(data):
52
+ perm = data.get("permissions") if isinstance(data, dict) else None
53
+ value = perm.get("disableBypassPermissionsMode") if isinstance(perm, dict) else None
54
+ return value is True or value == "disable"
55
+
56
+
57
+ # A policy path that is not there, or is not a file of that kind: no policy, not a failure.
58
+ ABSENT = (errno.ENOENT, errno.ENOTDIR, errno.EISDIR)
59
+
60
+
61
+ def unreadable_policy(path):
62
+ return Refused("this machine's managed policy cannot be read (" + path + "); ask the administrator whether it allows the mode")
63
+
64
+
65
+ def policy_files():
66
+ """The managed settings file and its drop-ins, in the order Claude Code reads them."""
67
+ home = policy_home()
68
+ files = [os.path.join(home, "managed-settings.json")]
69
+ drop = os.path.join(home, "managed-settings.d")
70
+ try:
71
+ names = os.listdir(drop)
72
+ except OSError as e:
73
+ if e.errno not in ABSENT:
74
+ raise unreadable_policy(drop)
75
+ names = []
76
+ files += [os.path.join(drop, n) for n in sorted(n for n in names if n.endswith(".json") and not n.startswith("."))]
77
+ return files
78
+
79
+
80
+ def policy_block():
81
+ """The policy file that forbids the mode, or ""; Refused for a policy this account cannot read."""
82
+ for path in policy_files():
83
+ try:
84
+ with open(path, "rb") as handle:
85
+ raw = handle.read()
86
+ except OSError as e:
87
+ if e.errno in ABSENT:
88
+ continue
89
+ raise unreadable_policy(path)
90
+ try:
91
+ if forbids(json.loads(raw.decode("utf-8-sig"))):
92
+ return path
93
+ except ValueError:
94
+ pass # not JSON: nothing this fallback can see forbids it
95
+ return ""
96
+
97
+
98
+ def load(path):
99
+ """(data, raw bytes or None when there is no file) for a JSON object; Refused otherwise."""
100
+ try:
101
+ with open(path, "rb") as handle:
102
+ raw = handle.read()
103
+ except FileNotFoundError:
104
+ return {}, None
105
+ try:
106
+ text = raw.decode("utf-8-sig")
107
+ except UnicodeDecodeError:
108
+ raise Refused("settings.json is not valid UTF-8")
109
+ if not text.strip():
110
+ return {}, raw
111
+ def reject(token):
112
+ raise ValueError("the constant " + token + " is not JSON")
113
+
114
+ try:
115
+ data = json.loads(text, parse_constant=reject)
116
+ except ValueError as e:
117
+ raise Refused("settings.json is not valid JSON (" + str(e) + ")")
118
+ if not isinstance(data, dict):
119
+ raise Refused("settings.json is not a JSON object")
120
+ if not isinstance(data.get("permissions", {}), dict):
121
+ raise Refused("the permissions entry of settings.json is not an object")
122
+ return data, raw
123
+
124
+
125
+ def replace(path, data, mode):
126
+ """Write bytes to path through a temp file in the same folder and a rename."""
127
+ fd, tmp = tempfile.mkstemp(dir=os.path.dirname(path), prefix=".bypass-permissions.")
128
+ try:
129
+ with os.fdopen(fd, "wb") as handle:
130
+ handle.write(data)
131
+ os.chmod(tmp, mode)
132
+ try:
133
+ os.replace(tmp, path)
134
+ except OSError as e:
135
+ if e.errno not in (errno.EBUSY, errno.EXDEV):
136
+ raise
137
+ with open(path, "wb") as target: # a bind-mounted single file refuses the rename: write in place
138
+ target.write(data)
139
+ os.remove(tmp)
140
+ except BaseException:
141
+ if os.path.exists(tmp):
142
+ os.remove(tmp)
143
+ raise
144
+
145
+
146
+ def save(path, data, raw):
147
+ """Replace the real file behind path (a link stays a link), keeping its mode and BOM."""
148
+ real = os.path.realpath(path)
149
+ mode = os.stat(real).st_mode & 0o777 if os.path.exists(real) else 0o600
150
+ bom = BOM if raw is not None and raw.startswith(BOM) else b""
151
+ replace(real, bom + (json.dumps(data, indent=2, ensure_ascii=False) + "\n").encode("utf-8"), mode)
152
+
153
+
154
+ def check_on(path, data):
155
+ # Claude Code lets root use the mode inside a sandbox it recognises: IS_SANDBOX exactly "1", or
156
+ # CLAUDE_CODE_BUBBLEWRAP set to a true value (2.1.289 reads 1, true, yes, on; 0 or false is not one).
157
+ bubblewrap = os.environ.get("CLAUDE_CODE_BUBBLEWRAP", "").strip().lower() in ("1", "true", "yes", "on")
158
+ sandboxed = os.environ.get("IS_SANDBOX") == "1" or bubblewrap
159
+ if hasattr(os, "geteuid") and os.geteuid() == 0 and not sandboxed:
160
+ raise Refused("Claude Code will not start in bypassPermissions as root; IS_SANDBOX=1 marks a real sandbox")
161
+ blocked = policy_block()
162
+ if blocked:
163
+ raise Refused("managed policy forbids bypassPermissions: " + blocked)
164
+ if forbids(data):
165
+ raise Refused("settings.json itself sets permissions.disableBypassPermissionsMode")
166
+
167
+
168
+ def forget_record(path):
169
+ """Drop the node script's record of what `on` replaced. This fallback keeps no record, so one left by an
170
+ earlier node `on` is stale: the mode is no longer what it describes, and a later node `off` would restore it."""
171
+ try:
172
+ os.remove(path + ".bypass-permissions.json")
173
+ except FileNotFoundError:
174
+ pass
175
+
176
+
177
+ def turn_on(path):
178
+ if not os.path.isdir(os.path.dirname(path)):
179
+ raise Refused("there is no Claude Code config folder at " + os.path.dirname(path))
180
+ data, raw = load(path)
181
+ check_on(path, data)
182
+ if data.get("permissions", {}).get("defaultMode") == MODE:
183
+ print("defaultMode = " + MODE + " (already, nothing changed)")
184
+ return 0
185
+ if raw is not None and not os.access(path, os.W_OK):
186
+ raise PermissionError(errno.EACCES, "this account may not write", path)
187
+ if raw is not None:
188
+ replace(path + ".bypass-permissions.bak", raw, 0o600)
189
+ forget_record(path) # the mode is not on, so any record is stale
190
+ data.setdefault("permissions", {})["defaultMode"] = MODE
191
+ save(path, data, raw)
192
+ print("defaultMode = " + MODE)
193
+ return 0
194
+
195
+
196
+ def turn_off(path):
197
+ data, raw = load(path)
198
+ current = data.get("permissions", {}).get("defaultMode")
199
+ if current != MODE:
200
+ print("defaultMode = " + str(current) + " (not bypassPermissions, nothing changed)")
201
+ return 0
202
+ if not os.access(path, os.W_OK): # a read-only settings file is a lock: off honours it as on does
203
+ raise PermissionError(errno.EACCES, "this account may not write", path)
204
+ del data["permissions"]["defaultMode"]
205
+ save(path, data, raw)
206
+ forget_record(path) # only after the write: if it failed, the record is still the way back
207
+ print("defaultMode removed (Claude Code's default start mode)")
208
+ return 0
209
+
210
+
211
+ def outcome(argv, path):
212
+ actions = {("on", "--yes"): turn_on, ("off",): turn_off}
213
+ action = actions.get(tuple(argv[1:]))
214
+ if action is None:
215
+ print("refused: usage is `bypass-permissions.py on --yes` or `bypass-permissions.py off`", file=sys.stderr)
216
+ return 1
217
+ try:
218
+ return action(path)
219
+ except Refused as e:
220
+ print("refused: " + str(e) + "; settings.json left as it was", file=sys.stderr)
221
+ return 1
222
+ except OSError as e:
223
+ if e.errno in (errno.EACCES, errno.EPERM, errno.EROFS):
224
+ print("write blocked: " + str(e) + "; settings.json left as it was", file=sys.stderr)
225
+ return 3
226
+ print("unexpected error: " + str(e) + "; check settings.json", file=sys.stderr)
227
+ return 2
228
+ except Exception as e: # anything else stopped it partway too
229
+ print("unexpected error: " + type(e).__name__ + ": " + str(e) + "; check settings.json", file=sys.stderr)
230
+ return 2
231
+
232
+
233
+ def main(argv):
234
+ path = settings_path()
235
+ code = outcome(argv, path)
236
+ print("settings file: " + path)
237
+ return code
238
+
239
+
240
+ if __name__ == "__main__":
241
+ sys.exit(main(sys.argv))
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: harness-factory
3
- description: "(banker) revfactory/harness(팀 아키텍처 팩토리) 플러그인 설치·구성·사용안내. Claude=harness@harness-marketplace, Codex=meta-harness. 'harness-factory'/'하네스 팩토리'/'에이전트팀 생성' 또는 /banker:setup 시 사용."
2
+ name: setup-harness-factory
3
+ description: "도메인 한 줄로 전문 에이전트 팀을 만들어 주는 `revfactory/harness` 플러그인을 설치하고 구성한 뒤 사용법을 안내. Claude Code 는 `harness@harness-marketplace`, Codex 는 `meta-harness`. 'setup-harness-factory'/'harness-factory'/'하네스 팩토리'/'에이전트팀 생성' 또는 `/banker:setup` 시 사용."
4
4
  ---
5
5
 
6
- # harness-factory: revfactory/harness 팀 아키텍처 팩토리 설치·구성
6
+ # setup-harness-factory: revfactory/harness 팀 아키텍처 팩토리 설치·구성
7
7
 
8
8
  `harness`는 도메인을 한 줄로 받아 `.claude/agents/`+`.claude/skills/`(전문 에이전트팀 + 그 에이전트가 쓸
9
9
  스킬)를 생성하는 **메타 스킬**이다. 이 플러그인의 본체는 설치물이 아니라 **방법론**이다. 따라서 이
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-insane-search
3
- description: "(banker) insane-search(차단·봇방어 사이트 우회 검색·페치) 플러그인 설치(Claude=gptaku_plugins, Codex=gptaku-codex). 'setup-insane-search'/'insane-search 설치' 또는 /banker:setup 시 사용."
3
+ description: "차단이나 봇 방어가 있는 사이트도 우회해 검색하고 가져오는 플러그인을 설치. Claude Code 는 `gptaku-plugins` 의 `insane-search`, Codex 는 `gptaku-codex` 의 `insane-research-codex`. 'setup-insane-search'/'insane-search 설치' 또는 `/banker:setup` 시 사용."
4
4
  ---
5
5
 
6
6
  # setup-insane-search: insane-search 플러그인 설치
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-java
3
- description: "(banker) JDK 21 + JAVA_HOME 설치(alternatives로 java·javac 비대칭 방지, Debian은 Adoptium 전용). jdtls 등 Java LSP 전제. 'setup-java'/'JDK 설치'/'Java 21 설치' 또는 /banker:setup 시 사용."
3
+ description: "JDK 21 을 설치하고 `JAVA_HOME` 을 설정. `alternatives` 로 `java` 와 `javac` 의 버전 비대칭을 막고, Debian 은 Adoptium 만 씀. jdtls 같은 Java LSP 의 전제. 'setup-java'/'JDK 설치'/'Java 21 설치' 또는 `/banker:setup` 시 사용."
4
4
  ---
5
5
 
6
6
  # setup-java: JDK 21 + JAVA_HOME 설치
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-lsp
3
- description: "(banker) 언어별 LSP 서버(vtsls·basedpyright·bash·jdtls·spring)와 lsp-mcp 브리지 설치·연동. 'setup-lsp'/'LSP 설치'/'언어 서버' 또는 /banker:setup 시 사용."
3
+ description: "언어별 LSP 서버(vtsls, basedpyright, bash, jdtls, spring)와 lsp-mcp 브리지를 설치하고 연동. 'setup-lsp'/'LSP 설치'/'언어 서버' 또는 `/banker:setup` 시 사용."
4
4
  ---
5
5
 
6
6
  # setup-lsp: 언어별 LSP 서버 + lsp-mcp 브리지
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-mcp
3
- description: "(banker) context7·sequential-thinking·filesystem·git·fetch(+선택 tavily) 범용 MCP 서버를 Claude Code(claude mcp add)·Codex(codex mcp add+config.toml)에 배선(serena·lsp_bridge 제외). 'setup-mcp'/'MCP 서버 설치'/'context7·sequential-thinking 설치' 또는 /banker:setup 시 사용."
3
+ description: "범용 MCP 서버(context7, sequential-thinking, filesystem, git, fetch, 선택 사항인 tavily)를 Claude Code(`claude mcp add`)와 Codex(`codex mcp add`, `config.toml`)에 등록. serena 와 `lsp_bridge` 는 제외. 'setup-mcp'/'MCP 서버 설치'/'context7 설치'/'sequential-thinking 설치' 또는 `/banker:setup` 시 사용."
4
4
  ---
5
5
 
6
6
  # setup-mcp: 범용 MCP 서버 설치
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-node
3
- description: "(banker) nvm + Node 22(LTS) 런타임 설치(Windows는 winget 또는 nvm-windows). Claude Code npm 설치·Codex/OMX·npx 기반 MCP 서버 전제. 'setup-node'/'Node 설치'/'nvm 설치' 또는 /banker:setup 시 사용."
3
+ description: "nvm 과 Node 22(LTS)를 설치(Windows 는 winget 이나 nvm-windows). Claude Code 의 npm 설치, Codex 와 OMX, npx 로 도는 MCP 서버의 전제. 'setup-node'/'Node 설치'/'nvm 설치' 또는 `/banker:setup` 시 사용."
4
4
  ---
5
5
 
6
6
  # setup-node: nvm + Node 22(LTS) 런타임 설치
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-omc
3
- description: "(banker) all-in-one·ultra-init·front-qa 가 의존하는 oh-my-claudecode(OMC) 설치/갱신(Codex는 OMX). 'setup-omc'/'OMC 설치'/'oh-my-claudecode 설치' 또는 /banker:setup 시 사용."
3
+ description: "`all-in-one`, `ultra-init`, `front-qa` 가 의존하는 oh-my-claudecode(OMC)를 설치하거나 갱신. Codex 에서는 oh-my-codex(OMX)가 대상. 'setup-omc'/'OMC 설치'/'oh-my-claudecode 설치' 또는 `/banker:setup` 시 사용."
4
4
  ---
5
5
 
6
6
  # setup-omc: oh-my-claudecode(OMC) 설치/갱신
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: setup-omc-hud
3
- description: "(banker) omc_hud(Claude Code 상태표시줄)를 OS별 apply 스크립트로 적용(Codex는 OMX hud). 'setup-omc-hud'/'omc_hud 설치' 또는 /banker:setup 시 사용."
3
+ description: "Claude Code 의 상태표시줄 omc_hud 를 OS별 적용 스크립트로 설치하고, Claude Code 갱신 안내를 줄 맨 끝으로 옮김. Codex 는 OMX 의 `hud` 스킬을 씀. 'setup-omc-hud'/'omc_hud 설치'/'Claude 갱신 안내 위치' 또는 `/banker:setup` 시 사용."
4
+ argument-hint: "[off|status]"
4
5
  ---
5
6
 
6
7
  # setup-omc-hud — omc_hud 상태표시줄 설치 (OS별)
@@ -9,6 +10,8 @@ description: "(banker) omc_hud(Claude Code 상태표시줄)를 OS별 apply 스
9
10
 
10
11
  **런타임:** omc_hud 는 Claude Code 상태표시줄(`~/.claude/settings.json` statusLine) 대상이다. **Codex 런타임에선 oh-my-codex(OMX)가 자체 `hud` 스킬/구성을 제공**하므로, Codex에선 이 스킬 대신 OMX 의 `hud` 를 사용한다.
11
12
 
13
+ **인자가 `off` 또는 `status` 일 때:** 0~3단계를 건너뛰고 3단계의 Claude 갱신 안내 스크립트만 그 인자로 실행해 결과를 보고한다(`off` 는 안내를 래퍼가 정한 자리로 되돌림, `status` 는 상태만 확인).
14
+
12
15
  ## 0. 감지
13
16
  ```bash
14
17
  case "$(uname -s)" in Darwin) OS=mac;; Linux) OS=linux;; *) OS=windows;; esac; echo "OS=$OS"
@@ -52,8 +55,19 @@ git 없으면 GitHub "Download ZIP" → 해제 → 동봉 적용 스크립트
52
55
  - **payload-mon 복구**: 0단계에서 `payload-mon=on` 이었다면, 적용 스크립트가 래퍼를 덮어써 payload 표시 블록이 빠진 상태다.
53
56
  payload-mon 스킬(Claude Code `/banker:payload-mon`, Codex `banker-payload-mon`)을 `on` 으로 실행해 다시 켜고, 그 결과를 함께 보고한다.
54
57
  `payload-mon=off` 였다면 아무것도 하지 않는다(사용자가 켠 적 없는 기능을 켜지 않는다).
58
+ - **Claude 갱신 안내 위치**: payload-mon 처리 뒤에 이 스킬의 스크립트를 `on` 으로 실행한다. 재적용을 건너뛴 경우에도 실행하고, Windows 도 같은 명령이다.
59
+ ```bash
60
+ node "<이 스킬 디렉터리 절대경로>/scripts/claude-update-last.mjs" on
61
+ ```
62
+ 새 Claude Code 가 나오면 OMC 가 띄우는 `[Claude#2.1.288] -> 2.1.289 claude update` 를 상태표시줄 1번째 줄 맨 끝(OMC 표시 뒤)으로 옮긴다. 2번째 줄의 `[!] claude 2.1.289 - paste: ! claude update` 안내는 그대로 둔다.
63
+ 출력을 그대로 보고한다. 종료 코드 1(거부)이면 래퍼는 바뀌지 않았으니 이유만 보고하고, 2(예기치 못한 오류로 중간에 멈춤)면 `status` 결과를 함께 보고한다.
64
+ 되돌리기는 `off`, 상태 확인은 `status`.
55
65
 
56
66
  ## 함정
57
67
  - `curl` 다운로드는 **대문자 `-O`**(원격 파일명 유지). 소문자 `-o`는 출력명 지정이라 다름.
58
68
  - raw URL 의 `refs/heads/main` 브랜치명이 바뀌면 404 → 저장소 확인.
59
69
  - `.mjs` 적용은 Node 필요(`node -v`).
70
+ - `claude-update-last` 를 `off` 로 꺼 두었어도 이 스킬을 다시 실행하면 3단계에서 다시 켜진다. 래퍼가 정한 자리를 원하면 끝난 뒤 `off`.
71
+ - 블록은 OMC 의 안내 문구(`[Claude#X] -> Y claude update`)로 안내를 찾는다. OMC 가 문구를 바꾸면 안내는 래퍼가 정한 자리에 남고, 상태표시줄은 깨지지 않는다.
72
+ - 블록은 래퍼가 세그먼트를 합치는 줄에서 `segs`·`colored` 를 읽는다. 래퍼 구조가 바뀌어 그 줄에서 이 이름이 보이지 않으면 `on` 은 성공해도 효과가 없다(상태표시줄은 그대로). 새 Claude Code 가 나왔을 때 안내가 맨 끝에 오는지로 확인한다.
73
+ - 이 블록은 플러그인 밖(`~/.claude/hud/`)에 남는다. 플러그인을 지우기 전에 `off` 를 실행한다.