@rubytech/create-maxy-code 0.1.164 → 0.1.166
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/dist/index.js +17 -9
- package/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +3 -5
- package/payload/platform/plugins/email/PLUGIN.md +2 -2
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts +0 -29
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.js +7 -115
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +7 -44
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +24 -177
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-read.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-read.js +4 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-read.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-search.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-search.js +4 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-search.js.map +1 -1
- package/payload/platform/plugins/email/skills/email-composition/SKILL.md +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +1 -1
- package/payload/platform/plugins/memory/references/schema-estate-agent.md +1 -1
- package/payload/platform/scripts/lib/read-brand-json.sh +1 -1
- package/payload/platform/scripts/lib/resolve-account-dir.sh +1 -1
- package/payload/platform/scripts/seed-neo4j.sh +29 -421
- package/payload/platform/scripts/setup-account.sh +370 -0
- package/payload/platform/services/claude-session-manager/dist/config.js +2 -2
- package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts +9 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +74 -38
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +16 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +16 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +21 -7
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts +7 -0
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.js +10 -0
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts +52 -10
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.js +73 -13
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ============================================================
|
|
3
|
-
# seed-neo4j.sh —
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# seed-neo4j.sh — Neo4j schema + root-node seeding
|
|
4
|
+
# Applies the schema (constraints, indexes, vector indexes) and
|
|
5
|
+
# MERGEs the per-account root :LocalBusiness and owner :AdminUser
|
|
6
|
+
# nodes. Account directory creation, Claude Code settings, hooks
|
|
7
|
+
# wiring, and specialist templates live in `setup-account.sh`.
|
|
8
|
+
# This script does no filesystem setup beyond reading what
|
|
9
|
+
# setup-account.sh wrote.
|
|
7
10
|
# ============================================================
|
|
8
11
|
|
|
9
12
|
set -euo pipefail
|
|
10
13
|
|
|
11
14
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
15
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
13
|
-
TEMPLATES_DIR="$PROJECT_DIR/templates"
|
|
14
|
-
# Accounts live at {installDir}/data/accounts/ — outside the platform/ wipe zone.
|
|
15
|
-
INSTALL_DIR="$(dirname "$PROJECT_DIR")"
|
|
16
|
-
ACCOUNTS_DIR="$INSTALL_DIR/data/accounts"
|
|
17
16
|
NEO4J_DIR="$PROJECT_DIR/neo4j"
|
|
18
17
|
|
|
19
18
|
# NEO4J_URI is hard-required. The previous default
|
|
@@ -41,426 +40,20 @@ fi
|
|
|
41
40
|
CYPHER_SHELL="cypher-shell"
|
|
42
41
|
|
|
43
42
|
# ------------------------------------------------------------------
|
|
44
|
-
# 1.
|
|
43
|
+
# 1. Resolve the account dir (must already exist; setup-account.sh
|
|
44
|
+
# is the creator). The resolver is idempotent — calling it here
|
|
45
|
+
# re-finds the same ACCOUNT_DIR / ACCOUNT_ID setup-account.sh
|
|
46
|
+
# minted.
|
|
45
47
|
# ------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
# Resolve existing account by identity (users.json[0].userId) or mint a new one on fresh install.
|
|
48
|
-
# Orphan siblings under data/accounts/ — non-matching dirs left behind by the userId
|
|
49
|
-
# regeneration — are swept to data/accounts/.trash/<uuid>-<ISO8601-ts>/. Multi-candidate with
|
|
50
|
-
# no identity match aborts loud; .trash/ is operator-emptied, never auto-cleaned.
|
|
51
48
|
# shellcheck source=lib/resolve-account-dir.sh
|
|
52
49
|
. "$SCRIPT_DIR/lib/resolve-account-dir.sh"
|
|
53
50
|
# shellcheck source=lib/read-brand-json.sh
|
|
54
51
|
. "$SCRIPT_DIR/lib/read-brand-json.sh"
|
|
55
|
-
# Resolve brand-aware persistent users.json path before the resolver runs.
|
|
56
|
-
# Mirrors the resolution lower in this file (around the post-Task-904 USERS_FILE
|
|
57
|
-
# block); duplicated here because resolve_and_sweep_account_dir runs first.
|
|
58
52
|
_RESOLVER_CONFIG_DIR_NAME=".maxy"
|
|
59
53
|
read_brand_json_key "$PROJECT_DIR/config/brand.json" "configDir"
|
|
60
54
|
[ -n "$BRAND_JSON_VALUE" ] && _RESOLVER_CONFIG_DIR_NAME="$BRAND_JSON_VALUE"
|
|
61
55
|
USERS_FILE="$HOME/$_RESOLVER_CONFIG_DIR_NAME/users.json" resolve_and_sweep_account_dir
|
|
62
56
|
|
|
63
|
-
mkdir -p "$ACCOUNT_DIR/agents/admin" "$ACCOUNT_DIR/agents/public" "$ACCOUNT_DIR/.claude" "$ACCOUNT_DIR/specialists/.claude-plugin" "$ACCOUNT_DIR/specialists/agents"
|
|
64
|
-
|
|
65
|
-
# Claude Code discovers project-level .claude/ (agents, settings) relative to the
|
|
66
|
-
# nearest .git root. Without a .git in the account directory, Claude Code traverses
|
|
67
|
-
# up to ~/maxy/.git and misses everything in accountDir/.claude/. Initialise a git
|
|
68
|
-
# repo so the account directory IS the project root for Claude Code.
|
|
69
|
-
[ -d "$ACCOUNT_DIR/.git" ] || git init -q "$ACCOUNT_DIR"
|
|
70
|
-
|
|
71
|
-
# Claude Code permissions — project-level settings now work since the account dir
|
|
72
|
-
# has .git. Write permissions to the account's own .claude/settings.json.
|
|
73
|
-
ACCOUNT_SETTINGS="$ACCOUNT_DIR/.claude/settings.json"
|
|
74
|
-
|
|
75
|
-
# Wildcard permissions for all plugin MCP servers (core + optional).
|
|
76
|
-
# Including optional plugins here is safe — permissions without a running
|
|
77
|
-
# MCP server are inert. This ensures tools are allowed when the user
|
|
78
|
-
# enables an optional plugin, without requiring a re-seed.
|
|
79
|
-
MAXY_PERMISSIONS='["Read","Edit","Write","Bash","Glob","Grep","Agent","mcp__memory__*","mcp__contacts__*","mcp__telegram__*","mcp__admin__*","mcp__cloudflare__*","mcp__work__*","mcp__scheduling__*","mcp__email__*","mcp__workflows__*","mcp__web__*","mcp__plugin_playwright_playwright__*"]'
|
|
80
|
-
|
|
81
|
-
# Append wildcard permissions for any optional plugins not already covered
|
|
82
|
-
PLUGINS_DIR="$(dirname "$0")/../plugins"
|
|
83
|
-
if [ -d "$PLUGINS_DIR" ]; then
|
|
84
|
-
for PLUGIN_DIR in "$PLUGINS_DIR"/*/; do
|
|
85
|
-
PLUGIN_NAME="$(basename "$PLUGIN_DIR")"
|
|
86
|
-
# Skip plugins already in the static list
|
|
87
|
-
case "$MAXY_PERMISSIONS" in
|
|
88
|
-
*"mcp__${PLUGIN_NAME}__"*) continue ;;
|
|
89
|
-
esac
|
|
90
|
-
# Only add if plugin has an MCP server
|
|
91
|
-
if [ -d "$PLUGIN_DIR/mcp" ]; then
|
|
92
|
-
MAXY_PERMISSIONS="${MAXY_PERMISSIONS%]},\"mcp__${PLUGIN_NAME}__*\"]"
|
|
93
|
-
fi
|
|
94
|
-
done
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
# Always overwrite — Rubytech-controlled. Project-level settings take
|
|
98
|
-
# precedence over global, so each account is properly isolated.
|
|
99
|
-
#
|
|
100
|
-
# Hook commands use $PLATFORM_ROOT (set by claude-agent.ts at spawn time)
|
|
101
|
-
# to locate scripts relative to the platform installation, not the account dir.
|
|
102
|
-
#
|
|
103
|
-
# Hooks wired:
|
|
104
|
-
# PreToolUse — pre-tool-use.sh: admin write boundaries
|
|
105
|
-
# playwright-file-guard.sh: intercepts browser_navigate with file:// URLs
|
|
106
|
-
# webfetch-preflight.mjs: detects JS-SPA shells before WebFetch's
|
|
107
|
-
# 60s extraction timeout. Fail-open on any error;
|
|
108
|
-
# on positive SPA detection exits 2 with WEBFETCH_CANNOT_READ_JS_SPA.
|
|
109
|
-
# archive-ingest-surface-gate.sh:
|
|
110
|
-
# narrows the database-operator subagent's effective surface
|
|
111
|
-
# during conversation-archive ingestion to exactly one Bash
|
|
112
|
-
# entry (memory/bin/conversation-archive-ingest.sh --source
|
|
113
|
-
# <enum>) plus read-only neighbours. Defensive blocks on
|
|
114
|
-
# withdrawn MCP tool names (mcp__memory__whatsapp-export-
|
|
115
|
-
# {parse,preview,insight-write,insight-pass},
|
|
116
|
-
# mcp__memory__memory-archive-write when
|
|
117
|
-
# archiveType=whatsapp-export) remain so any future
|
|
118
|
-
# reintroduction loud-fails. Preserves the plugin-source
|
|
119
|
-
# edit, JS test-runner, and post-parse-error blocks.
|
|
120
|
-
# Wired at three points: explicit Edit/Write/NotebookEdit/Bash
|
|
121
|
-
# matchers, a no-matcher PreToolUse entry (the
|
|
122
|
-
# post-parse-error gate fires on every tool),
|
|
123
|
-
# and a PostToolUse regex matcher for parse tools.
|
|
124
|
-
# UserPromptSubmit — archive-ingest-surface-gate.sh clears the
|
|
125
|
-
# parse-error flag when the operator's next prompt arrives.
|
|
126
|
-
# askuserquestion-investigate-gate.sh (Task 241): PreToolUse
|
|
127
|
-
# matcher=AskUserQuestion. Blocks the question when no
|
|
128
|
-
# read-only investigation tool has fired since the latest
|
|
129
|
-
# real user turn in the session JSONL. Fail-open on
|
|
130
|
-
# missing transcript or parse error. One [ask-gate]
|
|
131
|
-
# decision line per call.
|
|
132
|
-
#
|
|
133
|
-
# Task 214 — the per-turn Stop-hook registration that fired
|
|
134
|
-
# turn-completed-graph-write.sh once per admin turn is no longer written.
|
|
135
|
-
# The script, envelope walker, loopback /api/admin/claude-sessions 127.0.0.1
|
|
136
|
-
# bypass, [turn-recorder] emitters, recorder-auto-archive subscriber, and
|
|
137
|
-
# initialMessage envelope spec are preserved as dormant infrastructure for
|
|
138
|
-
# any future autonomous post-turn flow. Admin now delegates graph writes
|
|
139
|
-
# to database-operator via the Task tool inside its own session.
|
|
140
|
-
#
|
|
141
|
-
# Task 282 — Stop hook session-end-retrospective.sh wired below. Fires on
|
|
142
|
-
# every assistant end_turn but exits 0 with `trigger-skipped reason=…`
|
|
143
|
-
# on every path EXCEPT when the operator's latest real-user message
|
|
144
|
-
# carries an end-intent token (/end, /archive, end session,
|
|
145
|
-
# archive this session). On that path it blocks (exit 2 + stderr
|
|
146
|
-
# instruction block) until the admin agent calls the
|
|
147
|
-
# session-retrospective-mark-complete MCP sentinel tool, whose tool_use
|
|
148
|
-
# in the JSONL is the release signal. Recursion guard: any session
|
|
149
|
-
# carrying MAXY_SPECIALIST=<name> (specialist subagent) skips the gate.
|
|
150
|
-
HOOKS_PATH="\$PLATFORM_ROOT/plugins/admin/hooks"
|
|
151
|
-
cat > "$ACCOUNT_SETTINGS" << SETTINGS_EOF
|
|
152
|
-
{
|
|
153
|
-
"permissions": {
|
|
154
|
-
"allow": $MAXY_PERMISSIONS
|
|
155
|
-
},
|
|
156
|
-
"hooks": {
|
|
157
|
-
"PreToolUse": [
|
|
158
|
-
{
|
|
159
|
-
"matcher": "Write",
|
|
160
|
-
"hooks": [
|
|
161
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/pre-tool-use.sh admin" },
|
|
162
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
163
|
-
]
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
"matcher": "Edit",
|
|
167
|
-
"hooks": [
|
|
168
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/pre-tool-use.sh admin" },
|
|
169
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
170
|
-
]
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
"matcher": "NotebookEdit",
|
|
174
|
-
"hooks": [
|
|
175
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
176
|
-
]
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
"matcher": "Bash",
|
|
180
|
-
"hooks": [
|
|
181
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/pre-tool-use.sh admin" },
|
|
182
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
183
|
-
]
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
"matcher": "mcp__plugin_memory_memory__memory-write",
|
|
187
|
-
"hooks": [
|
|
188
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/pre-tool-use.sh admin" }
|
|
189
|
-
]
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
"matcher": "mcp__plugin_memory_memory__memory-update",
|
|
193
|
-
"hooks": [
|
|
194
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/pre-tool-use.sh admin" }
|
|
195
|
-
]
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
"hooks": [
|
|
199
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
200
|
-
]
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
"matcher": "mcp__plugin_playwright_playwright__browser_navigate",
|
|
204
|
-
"hooks": [
|
|
205
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/playwright-file-guard.sh" }
|
|
206
|
-
]
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
"matcher": "WebFetch",
|
|
210
|
-
"hooks": [
|
|
211
|
-
{ "type": "command", "command": "node $HOOKS_PATH/webfetch-preflight.mjs" }
|
|
212
|
-
]
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
"matcher": "AskUserQuestion",
|
|
216
|
-
"hooks": [
|
|
217
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/askuserquestion-investigate-gate.sh" }
|
|
218
|
-
]
|
|
219
|
-
}
|
|
220
|
-
],
|
|
221
|
-
"PostToolUse": [
|
|
222
|
-
{
|
|
223
|
-
"matcher": "mcp__.*__.*-(export|import)-parse$",
|
|
224
|
-
"hooks": [
|
|
225
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
226
|
-
]
|
|
227
|
-
}
|
|
228
|
-
],
|
|
229
|
-
"UserPromptSubmit": [
|
|
230
|
-
{
|
|
231
|
-
"hooks": [
|
|
232
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
233
|
-
]
|
|
234
|
-
}
|
|
235
|
-
],
|
|
236
|
-
"Stop": [
|
|
237
|
-
{
|
|
238
|
-
"hooks": [
|
|
239
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/session-end-retrospective.sh" }
|
|
240
|
-
]
|
|
241
|
-
}
|
|
242
|
-
]
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
SETTINGS_EOF
|
|
246
|
-
|
|
247
|
-
# Always overwrite IDENTITY.md — Rubytech-controlled, pure behaviour.
|
|
248
|
-
cp "$TEMPLATES_DIR/agents/admin/IDENTITY.md" "$ACCOUNT_DIR/agents/admin/IDENTITY.md"
|
|
249
|
-
cp "$TEMPLATES_DIR/agents/public/IDENTITY.md" "$ACCOUNT_DIR/agents/public/IDENTITY.md"
|
|
250
|
-
echo " agents/public/IDENTITY.md installed"
|
|
251
|
-
|
|
252
|
-
# Always overwrite specialist plugin — Rubytech-controlled, not user-editable.
|
|
253
|
-
# Specialists are a Claude Code plugin loaded via --plugin-dir. The plugin lives at
|
|
254
|
-
# accountDir/specialists/ with .claude-plugin/plugin.json and agents/*.md.
|
|
255
|
-
# The admin agent dispatches them as specialists:{name} via the Agent tool.
|
|
256
|
-
cp "$TEMPLATES_DIR/specialists/.claude-plugin/plugin.json" "$ACCOUNT_DIR/specialists/.claude-plugin/plugin.json"
|
|
257
|
-
|
|
258
|
-
# Remove orphaned core specialist files before copying fresh templates.
|
|
259
|
-
# Core specialists (no '--' in filename) are Rubytech-controlled and fully replaced
|
|
260
|
-
# on every seed. Premium plugin agents (filename contains '--') are preserved —
|
|
261
|
-
# they are managed by the admin agent when plugins are activated/deactivated.
|
|
262
|
-
for _existing in "$ACCOUNT_DIR/specialists/agents/"*.md; do
|
|
263
|
-
[ -f "$_existing" ] || continue
|
|
264
|
-
case "$(basename "$_existing")" in
|
|
265
|
-
*--*) continue ;; # Premium plugin agent — preserve
|
|
266
|
-
esac
|
|
267
|
-
rm -f "$_existing"
|
|
268
|
-
done
|
|
269
|
-
|
|
270
|
-
for specialist in "$TEMPLATES_DIR/specialists/agents/"*.md; do
|
|
271
|
-
[ -f "$specialist" ] && cp "$specialist" "$ACCOUNT_DIR/specialists/agents/$(basename "$specialist")"
|
|
272
|
-
done
|
|
273
|
-
|
|
274
|
-
# Clean up stale .claude/agents/*.md from previous seed format.
|
|
275
|
-
# The old format placed specialists here as slash-command agents — that never worked
|
|
276
|
-
# for programmatic dispatch. Safe to remove; .claude/ dir stays for settings.json.
|
|
277
|
-
rm -f "$ACCOUNT_DIR/.claude/agents/"*.md 2>/dev/null || true
|
|
278
|
-
|
|
279
|
-
# SOUL.md — user-controlled personalisation. Only create if missing. Never overwrite.
|
|
280
|
-
[ -f "$ACCOUNT_DIR/agents/admin/SOUL.md" ] || cp "$TEMPLATES_DIR/agents/admin/SOUL.md" "$ACCOUNT_DIR/agents/admin/SOUL.md"
|
|
281
|
-
|
|
282
|
-
# Public agent SOUL.md + config.json — create if missing, never overwrite.
|
|
283
|
-
# IDENTITY.md is handled in the Rubytech-controlled block above.
|
|
284
|
-
[ -f "$ACCOUNT_DIR/agents/public/SOUL.md" ] || cp "$TEMPLATES_DIR/agents/public/SOUL.md" "$ACCOUNT_DIR/agents/public/SOUL.md"
|
|
285
|
-
[ -f "$ACCOUNT_DIR/agents/public/config.json" ] || cp "$TEMPLATES_DIR/agents/public/config.json" "$ACCOUNT_DIR/agents/public/config.json"
|
|
286
|
-
|
|
287
|
-
# AGENTS.md — specialist registry. Pre-populated from templates, maintained by admin agent at runtime.
|
|
288
|
-
# Always overwrite on re-seed (Rubytech-controlled initial state, like IDENTITY.md).
|
|
289
|
-
# Dispatch format: specialists:{name} via the Agent tool.
|
|
290
|
-
printf '# Installed Roles\n\nDispatch via the Agent tool with `subagent_type: "specialists:{name}"`.\n\n' > "$ACCOUNT_DIR/agents/admin/AGENTS.md"
|
|
291
|
-
for specialist in "$ACCOUNT_DIR/specialists/agents/"*.md; do
|
|
292
|
-
[ -f "$specialist" ] || continue
|
|
293
|
-
# Extract name and description from YAML frontmatter
|
|
294
|
-
_name=$(sed -n 's/^name: *//p' "$specialist" | head -1)
|
|
295
|
-
_desc=$(sed -n 's/^description: *"*//p' "$specialist" | sed 's/"$//' | head -1)
|
|
296
|
-
[ -z "$_name" ] && continue
|
|
297
|
-
# Skip Phase 1 stubs — not yet functional
|
|
298
|
-
echo "$_desc" | grep -qi "Phase 1" && continue
|
|
299
|
-
printf -- '- **specialists:%s**: %s\n' "$_name" "$_desc" >> "$ACCOUNT_DIR/agents/admin/AGENTS.md"
|
|
300
|
-
done
|
|
301
|
-
|
|
302
|
-
# Create account.json only if it doesn't exist (business config is in the graph).
|
|
303
|
-
# Reads defaults from the template so there is a single source of truth.
|
|
304
|
-
if [ ! -f "$ACCOUNT_DIR/account.json" ]; then
|
|
305
|
-
TEMPLATE="$TEMPLATES_DIR/account.json"
|
|
306
|
-
if [ -f "$TEMPLATE" ]; then
|
|
307
|
-
sed "s/\"accountId\": *\"[^\"]*\"/\"accountId\":\"$ACCOUNT_ID\"/" "$TEMPLATE" \
|
|
308
|
-
| sed "s/\"tier\": *\"[^\"]*\"/\"tier\":\"solo\"/" \
|
|
309
|
-
> "$ACCOUNT_DIR/account.json"
|
|
310
|
-
else
|
|
311
|
-
echo "ERROR: account.json template not found at $TEMPLATE" >&2
|
|
312
|
-
exit 1
|
|
313
|
-
fi
|
|
314
|
-
fi
|
|
315
|
-
|
|
316
|
-
# Backfill defaultAgent in account.json if missing (existing accounts earlier).
|
|
317
|
-
# Uses the first non-admin agent directory that has a config.json.
|
|
318
|
-
if [ -f "$ACCOUNT_DIR/account.json" ] && ! grep -q '"defaultAgent"' "$ACCOUNT_DIR/account.json"; then
|
|
319
|
-
_DEFAULT_SLUG=""
|
|
320
|
-
for _AGENT_DIR in "$ACCOUNT_DIR/agents"/*/; do
|
|
321
|
-
_AGENT_NAME="$(basename "$_AGENT_DIR")"
|
|
322
|
-
[ "$_AGENT_NAME" = "admin" ] && continue
|
|
323
|
-
[ -f "$_AGENT_DIR/config.json" ] && { _DEFAULT_SLUG="$_AGENT_NAME"; break; }
|
|
324
|
-
done
|
|
325
|
-
if [ -n "$_DEFAULT_SLUG" ]; then
|
|
326
|
-
# Insert defaultAgent before the closing brace
|
|
327
|
-
sed -i'' "s/}$/,\"defaultAgent\":\"$_DEFAULT_SLUG\"}/" "$ACCOUNT_DIR/account.json"
|
|
328
|
-
echo " Backfilled defaultAgent=$_DEFAULT_SLUG in account.json"
|
|
329
|
-
fi
|
|
330
|
-
fi
|
|
331
|
-
|
|
332
|
-
# ------------------------------------------------------------------
|
|
333
|
-
# Seed enabledPlugins from brand.json plugins.defaultEnabled
|
|
334
|
-
# ------------------------------------------------------------------
|
|
335
|
-
# Fresh accounts: replaces the template's empty [] with defaultEnabled.
|
|
336
|
-
# Existing accounts: additive merge — adds missing defaultEnabled entries,
|
|
337
|
-
# never removes plugins the user enabled manually.
|
|
338
|
-
# Dependency validation: skips any defaultEnabled plugin whose PLUGIN.md
|
|
339
|
-
# `requires:` dependencies are not satisfied by core or defaultEnabled.
|
|
340
|
-
BRAND_JSON="$PROJECT_DIR/config/brand.json"
|
|
341
|
-
if [ -f "$BRAND_JSON" ] && [ -f "$ACCOUNT_DIR/account.json" ]; then
|
|
342
|
-
python3 -c "
|
|
343
|
-
import json, os, sys
|
|
344
|
-
|
|
345
|
-
brand_path = '$BRAND_JSON'
|
|
346
|
-
account_path = '$ACCOUNT_DIR/account.json'
|
|
347
|
-
plugins_dir = '$PROJECT_DIR/plugins'
|
|
348
|
-
|
|
349
|
-
with open(brand_path) as f:
|
|
350
|
-
brand = json.load(f)
|
|
351
|
-
|
|
352
|
-
plugins_cfg = brand.get('plugins', {})
|
|
353
|
-
default_enabled = plugins_cfg.get('defaultEnabled', [])
|
|
354
|
-
core = set(plugins_cfg.get('core', []))
|
|
355
|
-
|
|
356
|
-
if not default_enabled:
|
|
357
|
-
print(' [seed] no defaultEnabled plugins in brand.json — skipping')
|
|
358
|
-
sys.exit(0)
|
|
359
|
-
|
|
360
|
-
# Dependency validation: check each plugin's PLUGIN.md requires field.
|
|
361
|
-
# A dependency is satisfied if it appears in core or in defaultEnabled.
|
|
362
|
-
satisfied = core | set(default_enabled)
|
|
363
|
-
validated = []
|
|
364
|
-
for plugin in default_enabled:
|
|
365
|
-
plugin_md = os.path.join(plugins_dir, plugin, 'PLUGIN.md')
|
|
366
|
-
requires = []
|
|
367
|
-
if os.path.isfile(plugin_md):
|
|
368
|
-
in_frontmatter = False
|
|
369
|
-
in_requires = False
|
|
370
|
-
with open(plugin_md) as f:
|
|
371
|
-
for line in f:
|
|
372
|
-
stripped = line.rstrip()
|
|
373
|
-
if stripped == '---':
|
|
374
|
-
if in_frontmatter:
|
|
375
|
-
break
|
|
376
|
-
in_frontmatter = True
|
|
377
|
-
continue
|
|
378
|
-
if not in_frontmatter:
|
|
379
|
-
continue
|
|
380
|
-
if stripped.startswith('requires:'):
|
|
381
|
-
# Handle inline list: requires: [a, b]
|
|
382
|
-
inline = stripped.split(':', 1)[1].strip()
|
|
383
|
-
if inline.startswith('['):
|
|
384
|
-
requires = [x.strip().strip('\"').strip(\"'\") for x in inline.strip('[]').split(',') if x.strip()]
|
|
385
|
-
in_requires = False
|
|
386
|
-
else:
|
|
387
|
-
in_requires = True
|
|
388
|
-
continue
|
|
389
|
-
if in_requires:
|
|
390
|
-
if stripped.startswith(' - '):
|
|
391
|
-
requires.append(stripped.lstrip(' -').strip())
|
|
392
|
-
else:
|
|
393
|
-
in_requires = False
|
|
394
|
-
unmet = [r for r in requires if r not in satisfied]
|
|
395
|
-
if unmet:
|
|
396
|
-
print(f' [seed] WARNING: skipping defaultEnabled plugin \"{plugin}\" — unmet requires: {unmet}')
|
|
397
|
-
else:
|
|
398
|
-
validated.append(plugin)
|
|
399
|
-
|
|
400
|
-
with open(account_path) as f:
|
|
401
|
-
account = json.load(f)
|
|
402
|
-
|
|
403
|
-
existing = account.get('enabledPlugins', [])
|
|
404
|
-
existing_set = set(existing)
|
|
405
|
-
added = [p for p in validated if p not in existing_set]
|
|
406
|
-
account['enabledPlugins'] = existing + added
|
|
407
|
-
|
|
408
|
-
with open(account_path, 'w') as f:
|
|
409
|
-
json.dump(account, f, indent=2)
|
|
410
|
-
f.write('\n')
|
|
411
|
-
|
|
412
|
-
if not existing:
|
|
413
|
-
print(f' [seed] seeded enabledPlugins: {validated}')
|
|
414
|
-
elif added:
|
|
415
|
-
print(f' [seed] merged into enabledPlugins: +{added} (existing: {existing})')
|
|
416
|
-
else:
|
|
417
|
-
print(f' [seed] enabledPlugins already contains all defaultEnabled entries')
|
|
418
|
-
"
|
|
419
|
-
fi
|
|
420
|
-
|
|
421
|
-
echo " Account $ACCOUNT_ID at $ACCOUNT_DIR"
|
|
422
|
-
|
|
423
|
-
# ------------------------------------------------------------------
|
|
424
|
-
# 1b. Resolve first admin userId from users.json
|
|
425
|
-
# ------------------------------------------------------------------
|
|
426
|
-
# users.json is created by the set-pin POST during onboarding. The seed
|
|
427
|
-
# script reads it to stamp the AdminUser node; it never creates users.json.
|
|
428
|
-
|
|
429
|
-
CONFIG_DIR="$PROJECT_DIR/config"
|
|
430
|
-
|
|
431
|
-
# Resolve the brand-specific config directory.
|
|
432
|
-
# Mirrors the logic in platform/ui/app/lib/paths.ts.
|
|
433
|
-
_CONFIG_DIR_NAME=".maxy"
|
|
434
|
-
read_brand_json_key "$CONFIG_DIR/brand.json" "configDir"
|
|
435
|
-
[ -n "$BRAND_JSON_VALUE" ] && _CONFIG_DIR_NAME="$BRAND_JSON_VALUE"
|
|
436
|
-
# users.json lives under $HOME/$_CONFIG_DIR_NAME, not the wipe zone.
|
|
437
|
-
USERS_FILE="$HOME/$_CONFIG_DIR_NAME/users.json"
|
|
438
|
-
mkdir -p "$HOME/$_CONFIG_DIR_NAME"
|
|
439
|
-
|
|
440
|
-
if [ -f "$USERS_FILE" ]; then
|
|
441
|
-
USER_ID=$(python3 -c "import json; print(json.load(open('$USERS_FILE'))[0]['userId'])" 2>/dev/null || true)
|
|
442
|
-
[ -n "$USER_ID" ] && echo " Existing users.json found, userId=$USER_ID"
|
|
443
|
-
|
|
444
|
-
# Ensure account.json admins[] contains the owner. Idempotent.
|
|
445
|
-
if [ -n "$USER_ID" ] && [ -f "$ACCOUNT_DIR/account.json" ]; then
|
|
446
|
-
python3 -c "
|
|
447
|
-
import json
|
|
448
|
-
with open('$ACCOUNT_DIR/account.json', 'r') as f:
|
|
449
|
-
config = json.load(f)
|
|
450
|
-
config.setdefault('admins', [])
|
|
451
|
-
if not any(a.get('userId') == '$USER_ID' for a in config['admins']):
|
|
452
|
-
config['admins'].append({'userId': '$USER_ID', 'role': 'owner'})
|
|
453
|
-
with open('$ACCOUNT_DIR/account.json', 'w') as f:
|
|
454
|
-
json.dump(config, f, indent=2)
|
|
455
|
-
f.write('\n')
|
|
456
|
-
print(' Stamped userId=$USER_ID as owner in account.json admins')
|
|
457
|
-
"
|
|
458
|
-
fi
|
|
459
|
-
else
|
|
460
|
-
echo " No users.json found — skipping AdminUser seed (set-pin POST creates it)"
|
|
461
|
-
USER_ID=""
|
|
462
|
-
fi
|
|
463
|
-
|
|
464
57
|
# ------------------------------------------------------------------
|
|
465
58
|
# 2. Apply Neo4j schema (constraints + indexes only)
|
|
466
59
|
# ------------------------------------------------------------------
|
|
@@ -486,7 +79,7 @@ fi
|
|
|
486
79
|
echo "$SCHEMA_CYPHER" | "$CYPHER_SHELL" -u "$NEO4J_USER" -p "$NEO4J_PASSWORD" -a "$NEO4J_URI"
|
|
487
80
|
|
|
488
81
|
# ------------------------------------------------------------------
|
|
489
|
-
#
|
|
82
|
+
# 3. Bootstrap root :LocalBusiness for the account
|
|
490
83
|
# ------------------------------------------------------------------
|
|
491
84
|
# Every node in the graph hierarchy (Project, Task, Person, Organisation,
|
|
492
85
|
# KnowledgeDocument) anchors to a :LocalBusiness via accountId. Without a
|
|
@@ -505,6 +98,7 @@ echo "$SCHEMA_CYPHER" | "$CYPHER_SHELL" -u "$NEO4J_USER" -p "$NEO4J_PASSWORD" -a
|
|
|
505
98
|
# default filled in on re-seed. createdAt is set ON CREATE only, so re-runs
|
|
506
99
|
# preserve the original timestamp.
|
|
507
100
|
|
|
101
|
+
BRAND_JSON="$PROJECT_DIR/config/brand.json"
|
|
508
102
|
read_brand_json_key "$BRAND_JSON" "vertical"
|
|
509
103
|
LOCALBUSINESS_BUSINESS_TYPE=$(derive_business_type "$BRAND_JSON_VALUE")
|
|
510
104
|
LOCALBUSINESS_BT_CYPHER=$(business_type_cypher "$LOCALBUSINESS_BUSINESS_TYPE")
|
|
@@ -547,10 +141,22 @@ fi
|
|
|
547
141
|
echo " [install-invariant] localbusiness-bootstrap accountId=${ACCOUNT_ID:0:8} businessType=$LOCALBUSINESS_BT_LOG result=$LOCALBUSINESS_RESULT"
|
|
548
142
|
|
|
549
143
|
# ------------------------------------------------------------------
|
|
550
|
-
#
|
|
144
|
+
# 4. Create AdminUser node + ADMIN_OF relationship
|
|
551
145
|
# ------------------------------------------------------------------
|
|
146
|
+
# Resolve owner userId from users.json (set-pin POST creates it).
|
|
147
|
+
# Same brand-aware resolution as setup-account.sh.
|
|
148
|
+
CONFIG_DIR="$PROJECT_DIR/config"
|
|
149
|
+
_CONFIG_DIR_NAME=".maxy"
|
|
150
|
+
read_brand_json_key "$CONFIG_DIR/brand.json" "configDir"
|
|
151
|
+
[ -n "$BRAND_JSON_VALUE" ] && _CONFIG_DIR_NAME="$BRAND_JSON_VALUE"
|
|
152
|
+
USERS_FILE="$HOME/$_CONFIG_DIR_NAME/users.json"
|
|
153
|
+
|
|
154
|
+
USER_ID=""
|
|
155
|
+
if [ -f "$USERS_FILE" ]; then
|
|
156
|
+
USER_ID=$(python3 -c "import json; print(json.load(open('$USERS_FILE'))[0]['userId'])" 2>/dev/null || true)
|
|
157
|
+
fi
|
|
552
158
|
|
|
553
|
-
if [ -n "$
|
|
159
|
+
if [ -n "$USER_ID" ]; then
|
|
554
160
|
# Escape single quotes for Cypher string interpolation (e.g. O'Brien → O''Brien).
|
|
555
161
|
USER_NAME=$(python3 -c "import json; n=json.load(open('$USERS_FILE'))[0]['name']; print(n.replace(\"'\",\"''\"))" 2>/dev/null || echo "Owner")
|
|
556
162
|
CREATED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
@@ -576,6 +182,8 @@ ON CREATE SET r.role = 'owner', r.grantedAt = '$CREATED_AT'
|
|
|
576
182
|
RETURN au.userId, b.accountId;
|
|
577
183
|
CYPHER_EOF
|
|
578
184
|
echo " [seed] AdminUser stamped userId=${USER_ID:0:8} accountId=${ACCOUNT_ID:0:8} role=owner"
|
|
185
|
+
else
|
|
186
|
+
echo " No users.json found — skipping AdminUser seed (set-pin POST creates it)"
|
|
579
187
|
fi
|
|
580
188
|
|
|
581
189
|
echo " Done."
|