@rubytech/create-maxy-code 0.1.329 → 0.1.331

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 (130) hide show
  1. package/dist/index.js +19 -2
  2. package/package.json +1 -1
  3. package/payload/platform/config/brand.json +1 -0
  4. package/payload/platform/lib/account-enumeration/dist/index.d.ts +33 -0
  5. package/payload/platform/lib/account-enumeration/dist/index.d.ts.map +1 -1
  6. package/payload/platform/lib/account-enumeration/dist/index.js +76 -0
  7. package/payload/platform/lib/account-enumeration/dist/index.js.map +1 -1
  8. package/payload/platform/lib/account-enumeration/src/index.ts +78 -0
  9. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  10. package/payload/platform/plugins/admin/PLUGIN.md +13 -1
  11. package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-authoring-skill-gate.test.js +1 -2
  12. package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-authoring-skill-gate.test.js.map +1 -1
  13. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.d.ts +1 -1
  14. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.d.ts.map +1 -1
  15. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.js +18 -13
  16. package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.js.map +1 -1
  17. package/payload/platform/plugins/admin/mcp/dist/index.js +102 -0
  18. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  19. package/payload/platform/plugins/admin/mcp/dist/tools/__tests__/account-lifecycle.test.d.ts +2 -0
  20. package/payload/platform/plugins/admin/mcp/dist/tools/__tests__/account-lifecycle.test.d.ts.map +1 -0
  21. package/payload/platform/plugins/admin/mcp/dist/tools/__tests__/account-lifecycle.test.js +57 -0
  22. package/payload/platform/plugins/admin/mcp/dist/tools/__tests__/account-lifecycle.test.js.map +1 -0
  23. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts +34 -0
  24. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts.map +1 -0
  25. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js +78 -0
  26. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js.map +1 -0
  27. package/payload/platform/plugins/admin/skills/plainly/SKILL.md +7 -0
  28. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -2
  29. package/payload/platform/plugins/contacts/mcp/dist/index.js +1 -1
  30. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  31. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.d.ts +2 -0
  32. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.d.ts.map +1 -0
  33. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.js +71 -0
  34. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.js.map +1 -0
  35. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +1 -0
  36. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -1
  37. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +10 -3
  38. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -1
  39. package/payload/platform/plugins/contacts/mcp/package.json +4 -2
  40. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  41. package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
  42. package/payload/platform/plugins/memory/PLUGIN.md +1 -1
  43. package/payload/platform/plugins/memory/mcp/dist/index.js +3 -1
  44. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-lookup-by-name.test.js +39 -0
  46. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-lookup-by-name.test.js.map +1 -1
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-lookup-by-name.d.ts.map +1 -1
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-lookup-by-name.js +13 -1
  49. package/payload/platform/plugins/memory/mcp/dist/tools/memory-lookup-by-name.js.map +1 -1
  50. package/payload/platform/plugins/slides/skills/deck-system/SKILL.md +1 -1
  51. package/payload/platform/scripts/__tests__/provision-role-stamp.test.sh +40 -0
  52. package/payload/platform/scripts/__tests__/sweep-durability.test.sh +103 -0
  53. package/payload/platform/scripts/lib/provision-account-dir.sh +319 -0
  54. package/payload/platform/scripts/lib/resolve-account-dir.sh +79 -132
  55. package/payload/platform/scripts/setup-account.sh +28 -361
  56. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  57. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +4 -0
  58. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  59. package/payload/platform/services/claude-session-manager/dist/config.d.ts +18 -0
  60. package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
  61. package/payload/platform/services/claude-session-manager/dist/config.js +63 -7
  62. package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
  63. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  64. package/payload/platform/services/claude-session-manager/dist/http-server.js +44 -2
  65. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  66. package/payload/platform/services/claude-session-manager/dist/index.js +50 -2
  67. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  68. package/payload/platform/services/claude-session-manager/dist/rc-child-log.d.ts +13 -0
  69. package/payload/platform/services/claude-session-manager/dist/rc-child-log.d.ts.map +1 -0
  70. package/payload/platform/services/claude-session-manager/dist/rc-child-log.js +52 -0
  71. package/payload/platform/services/claude-session-manager/dist/rc-child-log.js.map +1 -0
  72. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  73. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +7 -0
  74. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  75. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts +5 -0
  76. package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts.map +1 -1
  77. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js +8 -0
  78. package/payload/platform/services/claude-session-manager/dist/session-sidecar.js.map +1 -1
  79. package/payload/platform/services/claude-session-manager/dist/stuck-turn.d.ts +70 -0
  80. package/payload/platform/services/claude-session-manager/dist/stuck-turn.d.ts.map +1 -0
  81. package/payload/platform/services/claude-session-manager/dist/stuck-turn.js +213 -0
  82. package/payload/platform/services/claude-session-manager/dist/stuck-turn.js.map +1 -0
  83. package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
  84. package/payload/platform/services/webchat-channel/dist/instructions.js +25 -1
  85. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
  86. package/payload/platform/services/webchat-channel/dist/notification.d.ts +17 -0
  87. package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
  88. package/payload/platform/services/webchat-channel/dist/notification.js +14 -0
  89. package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
  90. package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
  91. package/payload/platform/services/webchat-channel/dist/server.js +66 -2
  92. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  93. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts +28 -0
  94. package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts.map +1 -0
  95. package/payload/platform/services/webchat-channel/dist/turn-follow.js +188 -0
  96. package/payload/platform/services/webchat-channel/dist/turn-follow.js.map +1 -0
  97. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  98. package/payload/platform/services/whatsapp-channel/dist/notification.js +24 -1
  99. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  100. package/payload/platform/templates/account.json +2 -0
  101. package/payload/premium-plugins/management-consulting/skills/pitch-deck/SKILL.md +44 -37
  102. package/payload/server/public/assets/AdminLoginScreens-DexSvtjD.js +1 -0
  103. package/payload/server/public/assets/AdminShell-Bf9tndNg.js +1 -0
  104. package/payload/server/public/assets/{Checkbox-DWGpkJD5.js → Checkbox-D58GsKoQ.js} +1 -1
  105. package/payload/server/public/assets/{OperatorConversations-BWu6GMQM.css → OperatorConversations-CDdp2nVn.css} +1 -1
  106. package/payload/server/public/assets/OperatorConversations-RmqANYz8.js +9 -0
  107. package/payload/server/public/assets/{admin-CnIlm4Yi.js → admin-CH5Vkkt4.js} +1 -1
  108. package/payload/server/public/assets/{browser-ePr5LxPd.js → browser-BwDtzSlr.js} +1 -1
  109. package/payload/server/public/assets/chat-3GY0LmYH.js +1 -0
  110. package/payload/server/public/assets/data-CttrzhfL.js +1 -0
  111. package/payload/server/public/assets/{graph-B5uxt5YB.js → graph-CZSdF2jv.js} +3 -3
  112. package/payload/server/public/assets/{graph-labels-fx-bv3Y-.js → graph-labels-Bi0fu8Ns.js} +1 -1
  113. package/payload/server/public/assets/operator-BEivxmRy.js +1 -0
  114. package/payload/server/public/assets/page-DUvT07SB.js +30 -0
  115. package/payload/server/public/assets/{public-DXAdRKJe.js → public-B3eIlVbd.js} +1 -1
  116. package/payload/server/public/browser.html +4 -4
  117. package/payload/server/public/chat.html +5 -5
  118. package/payload/server/public/data.html +4 -4
  119. package/payload/server/public/graph.html +6 -6
  120. package/payload/server/public/index.html +6 -6
  121. package/payload/server/public/operator.html +7 -7
  122. package/payload/server/public/public.html +5 -5
  123. package/payload/server/server.js +314 -222
  124. package/payload/server/public/assets/AdminLoginScreens-DstNf4M-.js +0 -1
  125. package/payload/server/public/assets/AdminShell-B5EcXCYR.js +0 -1
  126. package/payload/server/public/assets/OperatorConversations-CSQnX7Dx.js +0 -9
  127. package/payload/server/public/assets/chat-XjXSornk.js +0 -1
  128. package/payload/server/public/assets/data-DuObS80U.js +0 -1
  129. package/payload/server/public/assets/operator-CIGXegWd.js +0 -1
  130. package/payload/server/public/assets/page-BVOAwj-e.js +0 -30
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env bash
2
+ # Tests for resolve-account-dir.sh under the multi-account managed-service model
3
+ # (Task 983). Registry-membership keep-all: any dir with a valid account.json is
4
+ # a registered account and is NEVER swept; the destructive mv is removed (sweep
5
+ # is log-only this sprint). Boot resolves ACCOUNT_DIR to the single role:"house".
6
+
7
+ set -uo pipefail
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ RESOLVER="$SCRIPT_DIR/../lib/resolve-account-dir.sh"
11
+ PASS=0
12
+ FAIL=0
13
+ FAILED_TESTS=()
14
+
15
+ assert_eq() {
16
+ if [ "$1" = "$2" ]; then PASS=$((PASS+1));
17
+ else FAIL=$((FAIL+1)); FAILED_TESTS+=("$3: expected [$2] got [$1]"); fi
18
+ }
19
+ assert_dir_exists() {
20
+ if [ -d "$1" ]; then PASS=$((PASS+1));
21
+ else FAIL=$((FAIL+1)); FAILED_TESTS+=("$2: dir missing [$1]"); fi
22
+ }
23
+ assert_dir_absent() {
24
+ if [ ! -d "$1" ]; then PASS=$((PASS+1));
25
+ else FAIL=$((FAIL+1)); FAILED_TESTS+=("$2: dir unexpectedly present [$1]"); fi
26
+ }
27
+
28
+ HOUSE="11111111-1111-1111-1111-111111111111"
29
+ CLIENT_A="22222222-2222-2222-2222-222222222222"
30
+ CLIENT_B="33333333-3333-3333-3333-333333333333"
31
+ ORPHAN="44444444-4444-4444-4444-444444444444"
32
+
33
+ seed_account() {
34
+ local root="$1" id="$2" role="$3"
35
+ mkdir -p "$root/$id"
36
+ printf '{"accountId":"%s","role":"%s"}\n' "$id" "$role" > "$root/$id/account.json"
37
+ }
38
+
39
+ # --- Test 1: house + 2 clients + 1 orphan; all registered survive, house resolved, nothing trashed ---
40
+ run_durability() {
41
+ local tmp; tmp=$(mktemp -d)
42
+ local accounts="$tmp/data/accounts"
43
+ mkdir -p "$accounts"
44
+ seed_account "$accounts" "$CLIENT_A" "client"
45
+ seed_account "$accounts" "$CLIENT_B" "client"
46
+ seed_account "$accounts" "$HOUSE" "house"
47
+ mkdir -p "$accounts/$ORPHAN" # orphan: no account.json
48
+
49
+ local resolved; resolved=$(
50
+ . "$RESOLVER"
51
+ ACCOUNTS_DIR="$accounts" USERS_FILE="/nonexistent/users.json" resolve_and_sweep_account_dir >/dev/null 2>&1
52
+ echo "$ACCOUNT_ID"
53
+ )
54
+ assert_eq "$resolved" "$HOUSE" "house-resolved"
55
+ assert_dir_exists "$accounts/$HOUSE" "house-survives"
56
+ assert_dir_exists "$accounts/$CLIENT_A" "clientA-survives"
57
+ assert_dir_exists "$accounts/$CLIENT_B" "clientB-survives"
58
+ assert_dir_exists "$accounts/$ORPHAN" "orphan-not-trashed"
59
+ assert_dir_absent "$accounts/.trash" "no-trash-created"
60
+ rm -rf "$tmp"
61
+ }
62
+
63
+ # --- Test 2: sole unlabelled account (legacy) resolves as the account ---
64
+ run_sole_legacy() {
65
+ local tmp; tmp=$(mktemp -d)
66
+ local accounts="$tmp/data/accounts"
67
+ mkdir -p "$accounts/$HOUSE"
68
+ printf '{"accountId":"%s"}\n' "$HOUSE" > "$accounts/$HOUSE/account.json" # no role
69
+ local resolved rc
70
+ resolved=$(
71
+ . "$RESOLVER"
72
+ ACCOUNTS_DIR="$accounts" USERS_FILE="/nonexistent/users.json" resolve_and_sweep_account_dir >/dev/null 2>&1
73
+ echo "$ACCOUNT_ID"
74
+ )
75
+ assert_eq "$resolved" "$HOUSE" "sole-legacy-resolved"
76
+ rm -rf "$tmp"
77
+ }
78
+
79
+ # --- Test 3: zero-house with multiple candidates aborts (rc != 0) ---
80
+ run_drift_aborts() {
81
+ local tmp; tmp=$(mktemp -d)
82
+ local accounts="$tmp/data/accounts"
83
+ mkdir -p "$accounts"
84
+ seed_account "$accounts" "$CLIENT_A" "client"
85
+ seed_account "$accounts" "$CLIENT_B" "client"
86
+ local rc=0
87
+ (
88
+ . "$RESOLVER"
89
+ ACCOUNTS_DIR="$accounts" USERS_FILE="/nonexistent/users.json" resolve_and_sweep_account_dir >/dev/null 2>&1
90
+ ) || rc=$?
91
+ if [ "$rc" -ne 0 ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED_TESTS+=("drift-aborts: expected non-zero exit"); fi
92
+ rm -rf "$tmp"
93
+ }
94
+
95
+ run_durability
96
+ run_sole_legacy
97
+ run_drift_aborts
98
+
99
+ echo "PASS=$PASS FAIL=$FAIL"
100
+ if [ "$FAIL" -gt 0 ]; then
101
+ printf '%s\n' "${FAILED_TESTS[@]}"
102
+ exit 1
103
+ fi
@@ -0,0 +1,319 @@
1
+ # provision-account-dir.sh — per-account scaffolding, callable per account.
2
+ # Sourced by setup-account.sh (for the resolved house account) and by the
3
+ # admin-core `account_create` lifecycle tool (for a new client account). Defines
4
+ # `provision_account_dir <account_dir> <account_id> <role>`.
5
+ #
6
+ # The function is pure scaffolding: it creates the account dir layout, writes the
7
+ # Claude Code project settings (hooks only), installs agent identities and the
8
+ # specialist plugin, seeds account.json (stamping accountId + role), seeds
9
+ # enabledPlugins from brand defaults, and stamps the install owner into admins[].
10
+ # It does NO account resolution and NO Neo4j work.
11
+ #
12
+ # Required globals (set by the caller before invoking):
13
+ # TEMPLATES_DIR — <project>/templates
14
+ # PROJECT_DIR — the platform/ dir
15
+ # SCRIPT_DIR — the platform/scripts dir (for lib bootstraps)
16
+ # USERS_FILE — $HOME/<brand.configDir>/users.json (owner stamp; optional)
17
+
18
+ provision_account_dir() {
19
+ local ACCOUNT_DIR="$1"
20
+ local ACCOUNT_ID="$2"
21
+ local ROLE="$3"
22
+
23
+ mkdir -p "$ACCOUNT_DIR/agents/admin" "$ACCOUNT_DIR/.claude" "$ACCOUNT_DIR/specialists/.claude-plugin" "$ACCOUNT_DIR/specialists/agents"
24
+
25
+ # Claude Code discovers project-level .claude/ relative to the nearest .git
26
+ # root. Without a .git in the account dir it traverses up to ~/maxy/.git and
27
+ # misses accountDir/.claude/. Initialise a repo so the account dir IS the root.
28
+ [ -d "$ACCOUNT_DIR/.git" ] || git init -q "$ACCOUNT_DIR"
29
+
30
+ # --- project-level .claude/settings.json (hooks; no allowlist) -------------
31
+ local ACCOUNT_SETTINGS="$ACCOUNT_DIR/.claude/settings.json"
32
+ local HOOKS_PATH="\$PLATFORM_ROOT/plugins/admin/hooks"
33
+ cat > "$ACCOUNT_SETTINGS" << SETTINGS_EOF
34
+ {
35
+ "permissions": {
36
+ "defaultMode": "bypassPermissions",
37
+ "allow": [],
38
+ "deny": []
39
+ },
40
+ "hooks": {
41
+ "PreToolUse": [
42
+ {
43
+ "matcher": "Write",
44
+ "hooks": [
45
+ { "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
46
+ ]
47
+ },
48
+ {
49
+ "matcher": "Edit",
50
+ "hooks": [
51
+ { "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
52
+ ]
53
+ },
54
+ {
55
+ "matcher": "NotebookEdit",
56
+ "hooks": [
57
+ { "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
58
+ ]
59
+ },
60
+ {
61
+ "matcher": "Bash",
62
+ "hooks": [
63
+ { "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
64
+ ]
65
+ },
66
+ {
67
+ "hooks": [
68
+ { "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
69
+ ]
70
+ },
71
+ {
72
+ "matcher": "WebFetch",
73
+ "hooks": [
74
+ { "type": "command", "command": "node $HOOKS_PATH/webfetch-preflight.mjs" }
75
+ ]
76
+ },
77
+ {
78
+ "matcher": "AskUserQuestion",
79
+ "hooks": [
80
+ { "type": "command", "command": "bash $HOOKS_PATH/askuserquestion-investigate-gate.sh" }
81
+ ]
82
+ }
83
+ ],
84
+ "PostToolUse": [
85
+ {
86
+ "matcher": "Agent",
87
+ "hooks": [
88
+ { "type": "command", "command": "bash $HOOKS_PATH/post-tool-use-agent.sh" }
89
+ ]
90
+ },
91
+ {
92
+ "matcher": "mcp__.*__.*-(export|import)-parse$",
93
+ "hooks": [
94
+ { "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
95
+ ]
96
+ },
97
+ {
98
+ "matcher": "mcp__.*",
99
+ "hooks": [
100
+ { "type": "command", "command": "bash $HOOKS_PATH/mcp-tool-missing.sh" }
101
+ ]
102
+ },
103
+ {
104
+ "matcher": "Write",
105
+ "hooks": [
106
+ { "type": "command", "command": "bash $HOOKS_PATH/admin-authoring-observer.sh" }
107
+ ]
108
+ },
109
+ {
110
+ "matcher": "Edit",
111
+ "hooks": [
112
+ { "type": "command", "command": "bash $HOOKS_PATH/admin-authoring-observer.sh" }
113
+ ]
114
+ }
115
+ ],
116
+ "UserPromptSubmit": [
117
+ {
118
+ "hooks": [
119
+ { "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" },
120
+ { "type": "command", "command": "bash $HOOKS_PATH/prompt-optimiser-directive.sh" }
121
+ ]
122
+ }
123
+ ],
124
+ "Stop": [
125
+ {
126
+ "hooks": [
127
+ { "type": "command", "command": "bash $HOOKS_PATH/prompt-optimiser-compliance.sh" }
128
+ ]
129
+ }
130
+ ]
131
+ }
132
+ }
133
+ SETTINGS_EOF
134
+
135
+ # --- agent identities + specialist plugin templates ------------------------
136
+ cp "$TEMPLATES_DIR/agents/admin/IDENTITY.md" "$ACCOUNT_DIR/agents/admin/IDENTITY.md"
137
+
138
+ # On re-provision, re-sync the Rubytech-controlled IDENTITY.md for every
139
+ # already-existing non-admin agent dir (security-load-bearing). Never create
140
+ # dirs; never write SOUL/KNOWLEDGE/config.
141
+ local _AGENT_DIR _AGENT_NAME
142
+ for _AGENT_DIR in "$ACCOUNT_DIR/agents"/*/; do
143
+ [ -d "$_AGENT_DIR" ] || continue
144
+ _AGENT_NAME="$(basename "$_AGENT_DIR")"
145
+ [ "$_AGENT_NAME" = "admin" ] && continue
146
+ [ -f "$_AGENT_DIR/IDENTITY.md" ] || continue
147
+ cp "$TEMPLATES_DIR/agents/public/IDENTITY.md" "$_AGENT_DIR/IDENTITY.md"
148
+ echo " agents/$_AGENT_NAME/IDENTITY.md re-synced"
149
+ done
150
+
151
+ cp "$TEMPLATES_DIR/specialists/.claude-plugin/plugin.json" "$ACCOUNT_DIR/specialists/.claude-plugin/plugin.json"
152
+
153
+ # Replace core specialist files (no '--'); preserve premium plugin agents.
154
+ local _existing
155
+ for _existing in "$ACCOUNT_DIR/specialists/agents/"*.md; do
156
+ [ -f "$_existing" ] || continue
157
+ case "$(basename "$_existing")" in
158
+ *--*) continue ;;
159
+ esac
160
+ rm -f "$_existing"
161
+ done
162
+
163
+ local specialist
164
+ for specialist in "$TEMPLATES_DIR/specialists/agents/"*.md; do
165
+ [ -f "$specialist" ] && cp "$specialist" "$ACCOUNT_DIR/specialists/agents/$(basename "$specialist")"
166
+ done
167
+
168
+ rm -f "$ACCOUNT_DIR/.claude/agents/"*.md 2>/dev/null || true
169
+
170
+ # SOUL.md — user-controlled. Only create if missing. Never overwrite.
171
+ if [ -f "$ACCOUNT_DIR/agents/admin/SOUL.md" ]; then
172
+ echo " agents/admin/SOUL.md preserved ($(wc -c < "$ACCOUNT_DIR/agents/admin/SOUL.md" | tr -d ' ') bytes)"
173
+ else
174
+ cp "$TEMPLATES_DIR/agents/admin/SOUL.md" "$ACCOUNT_DIR/agents/admin/SOUL.md"
175
+ echo " agents/admin/SOUL.md created ($(wc -c < "$ACCOUNT_DIR/agents/admin/SOUL.md" | tr -d ' ') bytes)"
176
+ fi
177
+
178
+ bash "$SCRIPT_DIR/lib/agents-md-bootstrap.sh" "$ACCOUNT_DIR"
179
+ bash "$SCRIPT_DIR/lib/admin-skills-bootstrap.sh" "$ACCOUNT_DIR" "$PROJECT_DIR/plugins"
180
+
181
+ # --- account.json: create from template, stamp accountId + role ------------
182
+ local TEMPLATE="$TEMPLATES_DIR/account.json"
183
+ if [ ! -f "$ACCOUNT_DIR/account.json" ]; then
184
+ if [ -f "$TEMPLATE" ]; then
185
+ sed "s/\"accountId\": *\"[^\"]*\"/\"accountId\":\"$ACCOUNT_ID\"/" "$TEMPLATE" \
186
+ | sed "s/\"tier\": *\"[^\"]*\"/\"tier\":\"solo\"/" \
187
+ > "$ACCOUNT_DIR/account.json"
188
+ else
189
+ echo "ERROR: account.json template not found at $TEMPLATE" >&2
190
+ return 1
191
+ fi
192
+ fi
193
+
194
+ # Stamp role on the account.json (idempotent migration + designation). A
195
+ # pre-existing legacy account.json with no role gets ROLE; an account already
196
+ # carrying a role keeps it unless ROLE differs (operator-driven via the
197
+ # lifecycle tools, never here).
198
+ ACCOUNT_ID="$ACCOUNT_ID" ROLE="$ROLE" python3 - "$ACCOUNT_DIR/account.json" <<'PY'
199
+ import json, os, sys
200
+ path = sys.argv[1]
201
+ role = os.environ["ROLE"]
202
+ acct_id = os.environ["ACCOUNT_ID"]
203
+ with open(path) as f:
204
+ cfg = json.load(f)
205
+ changed = False
206
+ if not cfg.get("accountId"):
207
+ cfg["accountId"] = acct_id; changed = True
208
+ if role and cfg.get("role") != role:
209
+ cfg["role"] = role; changed = True
210
+ if "managedService" not in cfg:
211
+ cfg["managedService"] = False; changed = True
212
+ if changed:
213
+ with open(path, "w") as f:
214
+ json.dump(cfg, f, indent=2); f.write("\n")
215
+ print(f" account.json role={cfg.get('role')} stamped")
216
+ PY
217
+
218
+ # --- enabledPlugins from brand.json plugins.defaultEnabled -----------------
219
+ local BRAND_JSON="$PROJECT_DIR/config/brand.json"
220
+ if [ -f "$BRAND_JSON" ] && [ -f "$ACCOUNT_DIR/account.json" ]; then
221
+ PROJECT_DIR="$PROJECT_DIR" BRAND_JSON="$BRAND_JSON" ACCOUNT_JSON="$ACCOUNT_DIR/account.json" python3 - <<'PY'
222
+ import json, os, sys
223
+
224
+ brand_path = os.environ['BRAND_JSON']
225
+ account_path = os.environ['ACCOUNT_JSON']
226
+ plugins_dir = os.path.join(os.environ['PROJECT_DIR'], 'plugins')
227
+
228
+ with open(brand_path) as f:
229
+ brand = json.load(f)
230
+
231
+ plugins_cfg = brand.get('plugins', {})
232
+ default_enabled = plugins_cfg.get('defaultEnabled', [])
233
+ core = set(plugins_cfg.get('core', []))
234
+
235
+ if not default_enabled:
236
+ print(' [setup] no defaultEnabled plugins in brand.json — skipping')
237
+ sys.exit(0)
238
+
239
+ satisfied = core | set(default_enabled)
240
+ validated = []
241
+ for plugin in default_enabled:
242
+ plugin_md = os.path.join(plugins_dir, plugin, 'PLUGIN.md')
243
+ requires = []
244
+ if os.path.isfile(plugin_md):
245
+ in_frontmatter = False
246
+ in_requires = False
247
+ with open(plugin_md) as f:
248
+ for line in f:
249
+ stripped = line.rstrip()
250
+ if stripped == '---':
251
+ if in_frontmatter:
252
+ break
253
+ in_frontmatter = True
254
+ continue
255
+ if not in_frontmatter:
256
+ continue
257
+ if stripped.startswith('requires:'):
258
+ inline = stripped.split(':', 1)[1].strip()
259
+ if inline.startswith('['):
260
+ requires = [x.strip().strip('"').strip("'") for x in inline.strip('[]').split(',') if x.strip()]
261
+ in_requires = False
262
+ else:
263
+ in_requires = True
264
+ continue
265
+ if in_requires:
266
+ if stripped.startswith(' - '):
267
+ requires.append(stripped.lstrip(' -').strip())
268
+ else:
269
+ in_requires = False
270
+ unmet = [r for r in requires if r not in satisfied]
271
+ if unmet:
272
+ print(f' [setup] WARNING: skipping defaultEnabled plugin "{plugin}" — unmet requires: {unmet}')
273
+ else:
274
+ validated.append(plugin)
275
+
276
+ with open(account_path) as f:
277
+ account = json.load(f)
278
+
279
+ existing = account.get('enabledPlugins', [])
280
+ existing_set = set(existing)
281
+ added = [p for p in validated if p not in existing_set]
282
+ account['enabledPlugins'] = existing + added
283
+
284
+ with open(account_path, 'w') as f:
285
+ json.dump(account, f, indent=2)
286
+ f.write('\n')
287
+
288
+ if not existing:
289
+ print(f' [setup] seeded enabledPlugins: {validated}')
290
+ elif added:
291
+ print(f' [setup] merged into enabledPlugins: +{added} (existing: {existing})')
292
+ else:
293
+ print(f' [setup] enabledPlugins already contains all defaultEnabled entries')
294
+ PY
295
+ fi
296
+
297
+ # --- stamp install owner into admins[] (idempotent) ------------------------
298
+ if [ -n "${USERS_FILE:-}" ] && [ -f "$USERS_FILE" ]; then
299
+ local USER_ID
300
+ USER_ID=$(python3 -c "import json; print(json.load(open('$USERS_FILE'))[0]['userId'])" 2>/dev/null || true)
301
+ if [ -n "$USER_ID" ] && [ -f "$ACCOUNT_DIR/account.json" ]; then
302
+ USER_ID="$USER_ID" python3 - "$ACCOUNT_DIR/account.json" <<'PY'
303
+ import json, os, sys
304
+ path = sys.argv[1]
305
+ uid = os.environ["USER_ID"]
306
+ with open(path) as f:
307
+ cfg = json.load(f)
308
+ cfg.setdefault("admins", [])
309
+ if not any(a.get("userId") == uid for a in cfg["admins"]):
310
+ cfg["admins"].append({"userId": uid, "role": "owner"})
311
+ with open(path, "w") as f:
312
+ json.dump(cfg, f, indent=2); f.write("\n")
313
+ print(f" Stamped userId={uid} as owner in account.json admins")
314
+ PY
315
+ fi
316
+ fi
317
+
318
+ echo " Account $ACCOUNT_ID at $ACCOUNT_DIR (role=$ROLE)"
319
+ }