@rubytech/create-maxy-code 0.1.569 → 0.1.570
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.
- package/package.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +3 -3
- package/payload/platform/plugins/admin/hooks/__tests__/pdf-text-layer-inject.test.sh +177 -0
- package/payload/platform/plugins/admin/hooks/pdf-text-layer-inject.sh +137 -0
- package/payload/platform/plugins/admin/skills/a4-print-documents/SKILL.md +12 -1
- package/payload/platform/plugins/admin/skills/a4-print-documents/pdf-inspect.mjs +113 -0
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
- package/payload/platform/plugins/docs/references/admin-session.md +15 -0
- package/payload/platform/scripts/__tests__/mailbox-inject-registered.test.sh +15 -39
- package/payload/platform/scripts/__tests__/public-prompt-gate-registered.test.sh +22 -37
- package/payload/platform/scripts/__tests__/public-surface-registered.test.sh +8 -31
- package/payload/platform/scripts/setup-account.sh +7 -62
- package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.d.ts +129 -0
- package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.js +344 -0
- package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.js +20 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/templates/account-settings.json +6 -0
- package/payload/server/server.js +750 -656
- package/payload/platform/scripts/lib/__tests__/account-settings-askgate.test.sh +0 -144
- package/payload/platform/scripts/lib/__tests__/account-settings-mailbox-inject.test.sh +0 -166
- package/payload/platform/scripts/lib/__tests__/account-settings-pdf-conformance.test.sh +0 -151
- package/payload/platform/scripts/lib/__tests__/account-settings-public-surface.test.sh +0 -109
- package/payload/platform/scripts/lib/account-settings-askgate.sh +0 -98
- package/payload/platform/scripts/lib/account-settings-mailbox-inject.sh +0 -91
- package/payload/platform/scripts/lib/account-settings-pdf-conformance.sh +0 -98
- package/payload/platform/scripts/lib/account-settings-public-surface.sh +0 -109
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# account-settings-askgate.sh — retrofit the AskUserQuestion carrier gate onto
|
|
2
|
-
# existing accounts' .claude/settings.json. Task 1683.
|
|
3
|
-
#
|
|
4
|
-
# Task 1552 added askuserquestion-channel-carrier-gate.sh as a second command on
|
|
5
|
-
# the AskUserQuestion PreToolUse matcher in provision-account-dir.sh. That writer
|
|
6
|
-
# only runs at account-provision time; the upgrade path re-provisions the house
|
|
7
|
-
# account but NOT existing client sub-accounts, so any account provisioned before
|
|
8
|
-
# 1552 keeps a stale matcher with no carrier gate and re-wedges on a channel
|
|
9
|
-
# AskUserQuestion (live SiteDesk incident: session d39a22b0, account 2078cb54,
|
|
10
|
-
# 2026-07-15). This lib is the standing backfill: setup-account.sh reconciles
|
|
11
|
-
# every account on every install so the gate reaches pre-1552 accounts.
|
|
12
|
-
#
|
|
13
|
-
# Requires jq. Idempotent. Never echoes file contents. Fail-open (nudge, not
|
|
14
|
-
# brick): any unusable file logs a status and is left untouched.
|
|
15
|
-
#
|
|
16
|
-
# Sourced by setup-account.sh; also runnable standalone for a manual repair.
|
|
17
|
-
|
|
18
|
-
# The exact command strings provision-account-dir.sh writes. $PLATFORM_ROOT is a
|
|
19
|
-
# literal in the settings file, resolved by Claude Code at hook-run time.
|
|
20
|
-
_ASKGATE_CARRIER='bash $PLATFORM_ROOT/plugins/admin/hooks/askuserquestion-channel-carrier-gate.sh'
|
|
21
|
-
_ASKGATE_INVESTIGATE='bash $PLATFORM_ROOT/plugins/admin/hooks/askuserquestion-investigate-gate.sh'
|
|
22
|
-
|
|
23
|
-
# ensure_askgate_carrier <settings_file>
|
|
24
|
-
# Guarantees the AskUserQuestion PreToolUse matcher carries the carrier-gate
|
|
25
|
-
# command: appends it if the matcher exists without it, or adds the whole matcher
|
|
26
|
-
# (both gates, investigate first) if the matcher is absent. Logs exactly one
|
|
27
|
-
# `[backfill-1683] file=<f> status=<absent|no-hooks|already-set|updated|rewrite-failed>`
|
|
28
|
-
# to stdout. Always returns 0.
|
|
29
|
-
ensure_askgate_carrier() {
|
|
30
|
-
local f="$1"
|
|
31
|
-
if [ ! -f "$f" ]; then
|
|
32
|
-
echo "[backfill-1683] file=$f status=absent"
|
|
33
|
-
return 0
|
|
34
|
-
fi
|
|
35
|
-
# Patchable only when the file is valid JSON with a PreToolUse array.
|
|
36
|
-
if ! jq -e '.hooks.PreToolUse | type == "array"' "$f" >/dev/null 2>&1; then
|
|
37
|
-
if jq -e . "$f" >/dev/null 2>&1; then
|
|
38
|
-
echo "[backfill-1683] file=$f status=no-hooks"
|
|
39
|
-
else
|
|
40
|
-
echo "[backfill-1683] file=$f status=rewrite-failed"
|
|
41
|
-
fi
|
|
42
|
-
return 0
|
|
43
|
-
fi
|
|
44
|
-
local tmp="$f.1683.tmp"
|
|
45
|
-
if ! jq \
|
|
46
|
-
--arg carrier "$_ASKGATE_CARRIER" \
|
|
47
|
-
--arg invest "$_ASKGATE_INVESTIGATE" '
|
|
48
|
-
.hooks.PreToolUse |= (
|
|
49
|
-
if any(.[]; .matcher == "AskUserQuestion")
|
|
50
|
-
then map(
|
|
51
|
-
if .matcher == "AskUserQuestion"
|
|
52
|
-
then .hooks = (
|
|
53
|
-
if any(.hooks[]; .command == $carrier)
|
|
54
|
-
then .hooks
|
|
55
|
-
else .hooks + [{type: "command", command: $carrier}]
|
|
56
|
-
end)
|
|
57
|
-
else . end)
|
|
58
|
-
else . + [{matcher: "AskUserQuestion", hooks: [{type: "command", command: $invest}, {type: "command", command: $carrier}]}]
|
|
59
|
-
end)
|
|
60
|
-
' "$f" > "$tmp" 2>/dev/null; then
|
|
61
|
-
rm -f "$tmp"
|
|
62
|
-
echo "[backfill-1683] file=$f status=rewrite-failed"
|
|
63
|
-
return 0
|
|
64
|
-
fi
|
|
65
|
-
# Semantic no-op? Compare canonical forms; leave the file byte-identical if so.
|
|
66
|
-
# `|| true` keeps a jq failure here (e.g. $f truncated by a concurrent writer
|
|
67
|
-
# between the type-check above and this read) from tripping a caller's `set -e`
|
|
68
|
-
# and aborting the whole install — this lib is fail-open by contract.
|
|
69
|
-
local before after
|
|
70
|
-
before="$(jq -S . "$f" 2>/dev/null || true)"
|
|
71
|
-
after="$(jq -S . "$tmp" 2>/dev/null || true)"
|
|
72
|
-
if [ "$before" = "$after" ]; then
|
|
73
|
-
rm -f "$tmp"
|
|
74
|
-
echo "[backfill-1683] file=$f status=already-set"
|
|
75
|
-
return 0
|
|
76
|
-
fi
|
|
77
|
-
if mv "$tmp" "$f"; then
|
|
78
|
-
echo "[backfill-1683] file=$f status=updated"
|
|
79
|
-
else
|
|
80
|
-
rm -f "$tmp"
|
|
81
|
-
echo "[backfill-1683] file=$f status=rewrite-failed"
|
|
82
|
-
fi
|
|
83
|
-
return 0
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
# reconcile_all_accounts_askgate <accounts_dir>
|
|
87
|
-
# Standing check + backfill: every account dir with an account.json gets its
|
|
88
|
-
# AskUserQuestion matcher's carrier gate ensured. Mirrors
|
|
89
|
-
# reconcile_all_accounts_owned_dirs.
|
|
90
|
-
reconcile_all_accounts_askgate() {
|
|
91
|
-
local accts="$1"
|
|
92
|
-
[ -d "$accts" ] || return 0
|
|
93
|
-
local acct
|
|
94
|
-
for acct in "$accts"/*/; do
|
|
95
|
-
[ -f "$acct/account.json" ] || continue
|
|
96
|
-
ensure_askgate_carrier "$acct/.claude/settings.json"
|
|
97
|
-
done
|
|
98
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# account-settings-mailbox-inject.sh — retrofit the per-turn <mailboxes>
|
|
2
|
-
# injection hook onto existing accounts' .claude/settings.json. Task 2157.
|
|
3
|
-
#
|
|
4
|
-
# provision-account-dir.sh writes the UserPromptSubmit array only at provision
|
|
5
|
-
# time, and setup-account.sh re-provisions the HOUSE account only: a client
|
|
6
|
-
# sub-account's settings.json is written once, at creation, by the admin-core
|
|
7
|
-
# account-lifecycle tool, and no install ever rewrites it. Without this lib the
|
|
8
|
-
# hook reaches house accounts only, and no device gate would notice, because the
|
|
9
|
-
# verification account on a single-account install IS that install's house
|
|
10
|
-
# account. Same shape as account-settings-askgate.sh (Task 1683) and
|
|
11
|
-
# account-settings-pdf-conformance.sh (Task 1929).
|
|
12
|
-
#
|
|
13
|
-
# Requires jq. Idempotent. Never echoes file contents. Fail-open (nudge, not
|
|
14
|
-
# brick): any unusable file logs a status and is left untouched.
|
|
15
|
-
#
|
|
16
|
-
# Sourced by setup-account.sh; also runnable standalone for a manual repair.
|
|
17
|
-
|
|
18
|
-
# The exact command string provision-account-dir.sh writes. $PLATFORM_ROOT is a
|
|
19
|
-
# literal in the settings file, resolved by Claude Code at hook-run time.
|
|
20
|
-
_MBXINJECT_CMD='bash $PLATFORM_ROOT/plugins/admin/hooks/mailbox-inject.sh'
|
|
21
|
-
|
|
22
|
-
# ensure_mailbox_inject_hook <settings_file>
|
|
23
|
-
# Guarantees the UserPromptSubmit array carries the mailbox-inject command:
|
|
24
|
-
# appends it to the first entry when the array exists without it, or adds a whole
|
|
25
|
-
# entry when the array is empty. Logs exactly one
|
|
26
|
-
# `[backfill-2157] file=<f> status=<absent|no-hooks|already-set|updated|rewrite-failed>`
|
|
27
|
-
# to stdout. Always returns 0.
|
|
28
|
-
ensure_mailbox_inject_hook() {
|
|
29
|
-
local f="$1"
|
|
30
|
-
if [ ! -f "$f" ]; then
|
|
31
|
-
echo "[backfill-2157] file=$f status=absent"
|
|
32
|
-
return 0
|
|
33
|
-
fi
|
|
34
|
-
# Patchable only when the file is valid JSON with a UserPromptSubmit array.
|
|
35
|
-
if ! jq -e '.hooks.UserPromptSubmit | type == "array"' "$f" >/dev/null 2>&1; then
|
|
36
|
-
if jq -e . "$f" >/dev/null 2>&1; then
|
|
37
|
-
echo "[backfill-2157] file=$f status=no-hooks"
|
|
38
|
-
else
|
|
39
|
-
echo "[backfill-2157] file=$f status=rewrite-failed"
|
|
40
|
-
fi
|
|
41
|
-
return 0
|
|
42
|
-
fi
|
|
43
|
-
local tmp="$f.2157.tmp"
|
|
44
|
-
if ! jq --arg cmd "$_MBXINJECT_CMD" '
|
|
45
|
-
.hooks.UserPromptSubmit |= (
|
|
46
|
-
if length == 0
|
|
47
|
-
then [{hooks: [{type: "command", command: $cmd}]}]
|
|
48
|
-
else
|
|
49
|
-
if any(.[]; (.hooks // []) | any(.[]; .command == $cmd))
|
|
50
|
-
then .
|
|
51
|
-
else (.[0].hooks = ((.[0].hooks // []) + [{type: "command", command: $cmd}]))
|
|
52
|
-
end
|
|
53
|
-
end)
|
|
54
|
-
' "$f" > "$tmp" 2>/dev/null; then
|
|
55
|
-
rm -f "$tmp"
|
|
56
|
-
echo "[backfill-2157] file=$f status=rewrite-failed"
|
|
57
|
-
return 0
|
|
58
|
-
fi
|
|
59
|
-
# Semantic no-op? Compare canonical forms; leave the file byte-identical if so.
|
|
60
|
-
# `|| true` keeps a jq failure here (e.g. $f truncated by a concurrent writer
|
|
61
|
-
# between the type-check above and this read) from tripping a caller's `set -e`
|
|
62
|
-
# and aborting the whole install — this lib is fail-open by contract.
|
|
63
|
-
local before after
|
|
64
|
-
before="$(jq -S . "$f" 2>/dev/null || true)"
|
|
65
|
-
after="$(jq -S . "$tmp" 2>/dev/null || true)"
|
|
66
|
-
if [ "$before" = "$after" ]; then
|
|
67
|
-
rm -f "$tmp"
|
|
68
|
-
echo "[backfill-2157] file=$f status=already-set"
|
|
69
|
-
return 0
|
|
70
|
-
fi
|
|
71
|
-
if mv "$tmp" "$f"; then
|
|
72
|
-
echo "[backfill-2157] file=$f status=updated"
|
|
73
|
-
else
|
|
74
|
-
rm -f "$tmp"
|
|
75
|
-
echo "[backfill-2157] file=$f status=rewrite-failed"
|
|
76
|
-
fi
|
|
77
|
-
return 0
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
# reconcile_all_accounts_mailbox_inject <accounts_dir>
|
|
81
|
-
# Standing check + backfill: every account dir with an account.json gets the
|
|
82
|
-
# mailbox-inject hook ensured. Mirrors reconcile_all_accounts_askgate.
|
|
83
|
-
reconcile_all_accounts_mailbox_inject() {
|
|
84
|
-
local accts="$1"
|
|
85
|
-
[ -d "$accts" ] || return 0
|
|
86
|
-
local acct
|
|
87
|
-
for acct in "$accts"/*/; do
|
|
88
|
-
[ -f "$acct/account.json" ] || continue
|
|
89
|
-
ensure_mailbox_inject_hook "$acct/.claude/settings.json"
|
|
90
|
-
done
|
|
91
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# account-settings-pdf-conformance.sh — retrofit the quote-render PDF-conformance
|
|
2
|
-
# PostToolUse matcher onto existing accounts' .claude/settings.json. Task 1929.
|
|
3
|
-
#
|
|
4
|
-
# Task 1922 added a PostToolUse matcher on the browser-pdf-save tool running
|
|
5
|
-
# quote-render-pdf-conformance.sh to provision-account-dir.sh. That writer only
|
|
6
|
-
# runs at account-provision time; the upgrade path re-provisions the house
|
|
7
|
-
# account but NOT existing client sub-accounts, so any account provisioned before
|
|
8
|
-
# 1922 keeps a settings.json without the matcher and never runs the PDF gate
|
|
9
|
-
# (live glsmith is such an account). This lib is the standing backfill:
|
|
10
|
-
# setup-account.sh reconciles every account on every install so the gate reaches
|
|
11
|
-
# pre-1922 accounts. Mirrors account-settings-askgate.sh (Task 1683).
|
|
12
|
-
#
|
|
13
|
-
# Requires jq. Idempotent. Never echoes file contents. Fail-open (nudge, not
|
|
14
|
-
# brick): any unusable file logs a status and is left untouched.
|
|
15
|
-
#
|
|
16
|
-
# Sourced by setup-account.sh; also runnable standalone for a manual repair.
|
|
17
|
-
|
|
18
|
-
# The exact command string provision-account-dir.sh writes. $PLATFORM_ROOT is a
|
|
19
|
-
# literal in the settings file, resolved by Claude Code at hook-run time.
|
|
20
|
-
_PDF_CONFORMANCE_MATCHER='mcp__plugin_browser_browser__browser-pdf-save'
|
|
21
|
-
_PDF_CONFORMANCE_CMD='bash $PLATFORM_ROOT/plugins/admin/hooks/quote-render-pdf-conformance.sh'
|
|
22
|
-
|
|
23
|
-
# ensure_pdf_conformance_matcher <settings_file>
|
|
24
|
-
# Guarantees the PostToolUse browser-pdf-save matcher carries the conformance
|
|
25
|
-
# command: appends it if the matcher exists without it, or adds the whole matcher
|
|
26
|
-
# (single conformance command) if the matcher is absent. Logs exactly one
|
|
27
|
-
# `[backfill-1929] file=<f> status=<absent|no-hooks|already-set|updated|rewrite-failed>`
|
|
28
|
-
# to stdout. Always returns 0.
|
|
29
|
-
ensure_pdf_conformance_matcher() {
|
|
30
|
-
local f="$1"
|
|
31
|
-
if [ ! -f "$f" ]; then
|
|
32
|
-
echo "[backfill-1929] file=$f status=absent"
|
|
33
|
-
return 0
|
|
34
|
-
fi
|
|
35
|
-
# Patchable only when the file is valid JSON with a PostToolUse array.
|
|
36
|
-
if ! jq -e '.hooks.PostToolUse | type == "array"' "$f" >/dev/null 2>&1; then
|
|
37
|
-
if jq -e . "$f" >/dev/null 2>&1; then
|
|
38
|
-
echo "[backfill-1929] file=$f status=no-hooks"
|
|
39
|
-
else
|
|
40
|
-
echo "[backfill-1929] file=$f status=rewrite-failed"
|
|
41
|
-
fi
|
|
42
|
-
return 0
|
|
43
|
-
fi
|
|
44
|
-
local tmp="$f.1929.tmp"
|
|
45
|
-
if ! jq \
|
|
46
|
-
--arg matcher "$_PDF_CONFORMANCE_MATCHER" \
|
|
47
|
-
--arg cmd "$_PDF_CONFORMANCE_CMD" '
|
|
48
|
-
.hooks.PostToolUse |= (
|
|
49
|
-
if any(.[]; .matcher == $matcher)
|
|
50
|
-
then map(
|
|
51
|
-
if .matcher == $matcher
|
|
52
|
-
then .hooks = (
|
|
53
|
-
if any(.hooks[]; .command == $cmd)
|
|
54
|
-
then .hooks
|
|
55
|
-
else .hooks + [{type: "command", command: $cmd}]
|
|
56
|
-
end)
|
|
57
|
-
else . end)
|
|
58
|
-
else . + [{matcher: $matcher, hooks: [{type: "command", command: $cmd}]}]
|
|
59
|
-
end)
|
|
60
|
-
' "$f" > "$tmp" 2>/dev/null; then
|
|
61
|
-
rm -f "$tmp"
|
|
62
|
-
echo "[backfill-1929] file=$f status=rewrite-failed"
|
|
63
|
-
return 0
|
|
64
|
-
fi
|
|
65
|
-
# Semantic no-op? Compare canonical forms; leave the file byte-identical if so.
|
|
66
|
-
# `|| true` keeps a jq failure here (e.g. $f truncated by a concurrent writer
|
|
67
|
-
# between the type-check above and this read) from tripping a caller's `set -e`
|
|
68
|
-
# and aborting the whole install. This lib is fail-open by contract.
|
|
69
|
-
local before after
|
|
70
|
-
before="$(jq -S . "$f" 2>/dev/null || true)"
|
|
71
|
-
after="$(jq -S . "$tmp" 2>/dev/null || true)"
|
|
72
|
-
if [ "$before" = "$after" ]; then
|
|
73
|
-
rm -f "$tmp"
|
|
74
|
-
echo "[backfill-1929] file=$f status=already-set"
|
|
75
|
-
return 0
|
|
76
|
-
fi
|
|
77
|
-
if mv "$tmp" "$f"; then
|
|
78
|
-
echo "[backfill-1929] file=$f status=updated"
|
|
79
|
-
else
|
|
80
|
-
rm -f "$tmp"
|
|
81
|
-
echo "[backfill-1929] file=$f status=rewrite-failed"
|
|
82
|
-
fi
|
|
83
|
-
return 0
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
# reconcile_all_accounts_pdf_conformance <accounts_dir>
|
|
87
|
-
# Standing check + backfill: every account dir with an account.json gets its
|
|
88
|
-
# PostToolUse browser-pdf-save matcher's conformance command ensured. Mirrors
|
|
89
|
-
# reconcile_all_accounts_askgate.
|
|
90
|
-
reconcile_all_accounts_pdf_conformance() {
|
|
91
|
-
local accts="$1"
|
|
92
|
-
[ -d "$accts" ] || return 0
|
|
93
|
-
local acct
|
|
94
|
-
for acct in "$accts"/*/; do
|
|
95
|
-
[ -f "$acct/account.json" ] || continue
|
|
96
|
-
ensure_pdf_conformance_matcher "$acct/.claude/settings.json"
|
|
97
|
-
done
|
|
98
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# account-settings-public-surface.sh — retrofit the realised-public-tool-surface
|
|
2
|
-
# hook onto existing accounts' .claude/settings.json. Task 2339.
|
|
3
|
-
#
|
|
4
|
-
# provision-account-dir.sh writes the hooks block only at provision time, and
|
|
5
|
-
# setup-account.sh re-provisions the HOUSE account only: a client sub-account's
|
|
6
|
-
# settings.json is written once, at creation, by the admin-core account-lifecycle
|
|
7
|
-
# tool, and no install ever rewrites it. Without this lib the hook reaches house
|
|
8
|
-
# accounts only, and the account the 2026-08-01 breach was observed on would keep
|
|
9
|
-
# a settings file with no such command — so the instrumentation could not be
|
|
10
|
-
# verified where the defect was seen. Same shape as
|
|
11
|
-
# account-settings-mailbox-inject.sh (Task 2157).
|
|
12
|
-
#
|
|
13
|
-
# Requires jq. Idempotent. Never echoes file contents. Fail-open (nudge, not
|
|
14
|
-
# brick): any unusable file logs a status and is left untouched.
|
|
15
|
-
#
|
|
16
|
-
# Sourced by setup-account.sh; also runnable standalone for a manual repair.
|
|
17
|
-
|
|
18
|
-
# The exact command string provision-account-dir.sh writes. $PLATFORM_ROOT is a
|
|
19
|
-
# literal in the settings file, resolved by Claude Code at hook-run time.
|
|
20
|
-
_PUBSURFACE_CMD='bash $PLATFORM_ROOT/plugins/admin/hooks/public-tool-surface.sh'
|
|
21
|
-
|
|
22
|
-
# ensure_public_surface_hook <settings_file>
|
|
23
|
-
# Guarantees the matcher-less PreToolUse entry, the UserPromptSubmit entry and
|
|
24
|
-
# the Stop entry all carry the command. The PreToolUse target is the
|
|
25
|
-
# matcher-less entry specifically: a matcher-scoped entry would narrow the hook
|
|
26
|
-
# to one tool, and the whole point is every tool. UserPromptSubmit is the
|
|
27
|
-
# turn-start reset without which a killed turn's tally inflates the next turn's
|
|
28
|
-
# census. Logs exactly one
|
|
29
|
-
# `[backfill-2339] file=<f> status=<absent|no-hooks|already-set|updated|rewrite-failed>`
|
|
30
|
-
# to stdout. Always returns 0.
|
|
31
|
-
ensure_public_surface_hook() {
|
|
32
|
-
local f="$1"
|
|
33
|
-
if [ ! -f "$f" ]; then
|
|
34
|
-
echo "[backfill-2339] file=$f status=absent"
|
|
35
|
-
return 0
|
|
36
|
-
fi
|
|
37
|
-
# Patchable only when the file is valid JSON with both arrays.
|
|
38
|
-
if ! jq -e '(.hooks.PreToolUse | type == "array") and (.hooks.UserPromptSubmit | type == "array") and (.hooks.Stop | type == "array")' "$f" >/dev/null 2>&1; then
|
|
39
|
-
if jq -e . "$f" >/dev/null 2>&1; then
|
|
40
|
-
echo "[backfill-2339] file=$f status=no-hooks"
|
|
41
|
-
else
|
|
42
|
-
echo "[backfill-2339] file=$f status=rewrite-failed"
|
|
43
|
-
fi
|
|
44
|
-
return 0
|
|
45
|
-
fi
|
|
46
|
-
local tmp="$f.2339.tmp"
|
|
47
|
-
if ! jq --arg cmd "$_PUBSURFACE_CMD" '
|
|
48
|
-
.hooks.PreToolUse |= (
|
|
49
|
-
if any(.[]; (.hooks // []) | any(.[]; .command == $cmd))
|
|
50
|
-
then .
|
|
51
|
-
elif any(.[]; has("matcher") | not)
|
|
52
|
-
then map(if (has("matcher") | not)
|
|
53
|
-
then .hooks = ((.hooks // []) + [{type: "command", command: $cmd}])
|
|
54
|
-
else . end)
|
|
55
|
-
else . + [{hooks: [{type: "command", command: $cmd}]}]
|
|
56
|
-
end)
|
|
57
|
-
| .hooks.UserPromptSubmit |= (
|
|
58
|
-
if any(.[]; (.hooks // []) | any(.[]; .command == $cmd))
|
|
59
|
-
then .
|
|
60
|
-
elif length == 0
|
|
61
|
-
then [{hooks: [{type: "command", command: $cmd}]}]
|
|
62
|
-
else (.[0].hooks = ((.[0].hooks // []) + [{type: "command", command: $cmd}]))
|
|
63
|
-
end)
|
|
64
|
-
| .hooks.Stop |= (
|
|
65
|
-
if any(.[]; (.hooks // []) | any(.[]; .command == $cmd))
|
|
66
|
-
then .
|
|
67
|
-
elif length == 0
|
|
68
|
-
then [{hooks: [{type: "command", command: $cmd}]}]
|
|
69
|
-
else (.[0].hooks = ((.[0].hooks // []) + [{type: "command", command: $cmd}]))
|
|
70
|
-
end)
|
|
71
|
-
' "$f" > "$tmp" 2>/dev/null; then
|
|
72
|
-
rm -f "$tmp"
|
|
73
|
-
echo "[backfill-2339] file=$f status=rewrite-failed"
|
|
74
|
-
return 0
|
|
75
|
-
fi
|
|
76
|
-
# Semantic no-op? Compare canonical forms; leave the file byte-identical if so.
|
|
77
|
-
# `|| true` keeps a jq failure here (e.g. $f truncated by a concurrent writer
|
|
78
|
-
# between the type-check above and this read) from tripping a caller's `set -e`
|
|
79
|
-
# and aborting the whole install — this lib is fail-open by contract.
|
|
80
|
-
local before after
|
|
81
|
-
before="$(jq -S . "$f" 2>/dev/null || true)"
|
|
82
|
-
after="$(jq -S . "$tmp" 2>/dev/null || true)"
|
|
83
|
-
if [ "$before" = "$after" ]; then
|
|
84
|
-
rm -f "$tmp"
|
|
85
|
-
echo "[backfill-2339] file=$f status=already-set"
|
|
86
|
-
return 0
|
|
87
|
-
fi
|
|
88
|
-
if mv "$tmp" "$f"; then
|
|
89
|
-
echo "[backfill-2339] file=$f status=updated"
|
|
90
|
-
else
|
|
91
|
-
rm -f "$tmp"
|
|
92
|
-
echo "[backfill-2339] file=$f status=rewrite-failed"
|
|
93
|
-
fi
|
|
94
|
-
return 0
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
# reconcile_all_accounts_public_surface <accounts_dir>
|
|
98
|
-
# Standing check + backfill: every account dir with an account.json gets the
|
|
99
|
-
# realised-surface hook ensured on both events. Mirrors
|
|
100
|
-
# reconcile_all_accounts_mailbox_inject.
|
|
101
|
-
reconcile_all_accounts_public_surface() {
|
|
102
|
-
local accts="$1"
|
|
103
|
-
[ -d "$accts" ] || return 0
|
|
104
|
-
local acct
|
|
105
|
-
for acct in "$accts"/*/; do
|
|
106
|
-
[ -f "$acct/account.json" ] || continue
|
|
107
|
-
ensure_public_surface_hook "$acct/.claude/settings.json"
|
|
108
|
-
done
|
|
109
|
-
}
|