@rubytech/create-realagent 1.0.832 → 1.0.834
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 +131 -9
- package/package.json +1 -1
- package/payload/platform/lib/admins-write/dist/index.d.ts +87 -0
- package/payload/platform/lib/admins-write/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/admins-write/dist/index.js +248 -0
- package/payload/platform/lib/admins-write/dist/index.js.map +1 -0
- package/payload/platform/lib/admins-write/src/index.ts +311 -0
- package/payload/platform/lib/admins-write/tsconfig.json +8 -0
- package/payload/platform/neo4j/migrations/009-conversation-archive-title.ts +197 -0
- package/payload/platform/neo4j/schema.cypher +1 -1
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/PLUGIN.md +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +37 -44
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/docs/references/internals.md +4 -3
- package/payload/platform/plugins/memory/bin/conversation-archive-ingest.mjs +215 -43
- package/payload/platform/plugins/memory/bin/conversation-archive-ingest.sh +7 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +75 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +16 -10
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +155 -100
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts +13 -5
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js +53 -59
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js +9 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +24 -7
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +47 -11
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +45 -8
- package/payload/platform/scripts/lib/resolve-account-dir.sh +3 -1
- package/payload/platform/scripts/migrate-import.sh +3 -1
- package/payload/platform/scripts/seed-neo4j.sh +13 -3
- package/payload/server/chunk-CRAIGEXY.js +654 -0
- package/payload/server/chunk-GK4WHM3H.js +9961 -0
- package/payload/server/chunk-I2NOLBQA.js +2123 -0
- package/payload/server/chunk-IVTESKFR.js +9961 -0
- package/payload/server/chunk-KD3XP4IK.js +1116 -0
- package/payload/server/chunk-KKGGT5RH.js +654 -0
- package/payload/server/chunk-MRJGG6CS.js +2124 -0
- package/payload/server/chunk-OJZPS4BL.js +367 -0
- package/payload/server/chunk-ZVW5XKPU.js +1116 -0
- package/payload/server/client-pool-FM3YJWV5.js +32 -0
- package/payload/server/client-pool-J5BCVVI2.js +32 -0
- package/payload/server/cloudflare-task-tracker-FSPEJOTH.js +19 -0
- package/payload/server/cloudflare-task-tracker-XCUO4N74.js +19 -0
- package/payload/server/maxy-edge.js +6 -5
- package/payload/server/neo4j-migrations-5AN2U3YO.js +664 -0
- package/payload/server/neo4j-migrations-XP7XDVPX.js +664 -0
- package/payload/server/public/assets/{Checkbox-CTGhpDKq.js → Checkbox-Bq6ORjz2.js} +1 -1
- package/payload/server/public/assets/admin-CstEkw-G.js +352 -0
- package/payload/server/public/assets/data-DwZZ7qbH.js +1 -0
- package/payload/server/public/assets/graph-DceEv42K.js +1 -0
- package/payload/server/public/assets/{jsx-runtime-D4WovFYk.css → jsx-runtime-DidQeNoZ.css} +1 -1
- package/payload/server/public/assets/page-Bpi_jPw6.js +50 -0
- package/payload/server/public/assets/{page-DkBfWy4C.js → page-CFWoVkgV.js} +1 -1
- package/payload/server/public/assets/{public-BdVIVpv8.js → public-BWMwq5Jj.js} +1 -1
- package/payload/server/public/assets/{useAdminFetch-DmHu0oCx.js → useAdminFetch-B93ig7ef.js} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-CSc_hxjV.js → useVoiceRecorder-Cb0nAtOo.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +376 -167
- package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.d.ts +0 -31
- package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.d.ts.map +0 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js +0 -666
- package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/semantic-chunker.d.ts +0 -61
- package/payload/platform/plugins/memory/mcp/dist/lib/semantic-chunker.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/semantic-chunker.js +0 -266
- package/payload/platform/plugins/memory/mcp/dist/lib/semantic-chunker.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.d.ts +0 -27
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.js +0 -477
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-pass.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-write.d.ts +0 -27
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-write.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-write.js +0 -160
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-insight-write.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts +0 -10
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.js +0 -29
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.js.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.d.ts +0 -28
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.d.ts.map +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.js +0 -34
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-preview.js.map +0 -1
- package/payload/server/public/assets/admin-BNwPsMhJ.js +0 -352
- package/payload/server/public/assets/data-Y77FLKjs.js +0 -1
- package/payload/server/public/assets/graph-N_Bw-8oT.js +0 -1
- package/payload/server/public/assets/page-BKLGP-th.js +0 -50
- /package/payload/server/public/assets/{jsx-runtime-DkaAusaX.js → jsx-runtime-DH5S-MwB.js} +0 -0
|
@@ -69,7 +69,10 @@ Capture that path. The progress file lives at `data/accounts/$ACCOUNT_ID/logs/co
|
|
|
69
69
|
Optional flags:
|
|
70
70
|
- `--timezone <iana>` — IANA zone for timestamps (default `Europe/London`).
|
|
71
71
|
- `--date-format <DD/MM/YY|MM/DD/YY|DD/MM/YYYY|MM/DD/YYYY>` — WhatsApp only; override auto-detect for ambiguous locales.
|
|
72
|
-
|
|
72
|
+
|
|
73
|
+
Sessions split deterministically at 8h gap (fixed code constant — Task 902). The pre-902 `--session-gap-hours` flag is removed: chunked chat-mode classify (Task 902 sub-scope C) absorbs oversize sessions internally so the gap value is no longer an operational lever. Passing the legacy flag FAILs at `phase=argv`.
|
|
74
|
+
|
|
75
|
+
`--rebuild` (operator-issued only; never agent-autonomous — see Doctrine below): treats the run as first-ingest, deletes prior `:Section:Conversation` chunks for THIS export's `archiveSha256`, and re-classifies the entire archive. The skipped-cursor-lookup means a `--rebuild` and a fresh-export-with-different-bytes are operationally equivalent — one is destructive on identifiable bytes, the other is additive.
|
|
73
76
|
|
|
74
77
|
### Heartbeat protocol
|
|
75
78
|
|
|
@@ -79,9 +82,35 @@ Every 30 s while the background bash is live:
|
|
|
79
82
|
tail -n 100 <progress-file>
|
|
80
83
|
```
|
|
81
84
|
|
|
82
|
-
|
|
85
|
+
The bin emits one **derived** progress line per session boundary; the agent surfaces those (and only those) as chat tokens.
|
|
86
|
+
|
|
87
|
+
Grep contract — surface ONLY these line shapes to operator chat:
|
|
88
|
+
|
|
89
|
+
| Line shape (bin emits) | Surface as |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `[conversation-archive] progress sessionIndex=K/N pct=P chunks-so-far=X elapsed-ms=M` | `Classifying session K/N (P%) — chunks so far X — elapsed <Ms>` |
|
|
92
|
+
| `[conversation-archive] WARN cleanup-dropped chunks=N archiveSha256=<sha>` | **WARN** `cleanup-dropped chunks=N — prior data deleted (only legitimate under --rebuild; STOP and verify if not)` |
|
|
93
|
+
| `[conversation-archive] FAIL phase=…` | verbatim + yield (do not retry) |
|
|
94
|
+
|
|
95
|
+
Suppress all other `[conversation-archive] …` lines (raw `classify-session`, `session-committed`, `cleanup-skipped`, etc.) — they remain on disk for diagnosis but are not operator-stream noise. The bin's `progress` line is computed deterministically (Task 902 sub-scope F): per-tick percentage arithmetic and running totals come from one place, not from agent prose-rule arithmetic which drifts.
|
|
96
|
+
|
|
97
|
+
If the tail returns no NEW `progress` line for 60 s while the bg pid is still live: surface one `still on session K/N (P%) — last update <Δ>s ago` and stop emitting until the next advance. Forbidden: emitting "polling progress file" or any meta-status the operator did not request. Forbidden: blind-reissue of the bash.
|
|
98
|
+
|
|
99
|
+
### Bash poll `description` field — pin progress forward (Task 905)
|
|
100
|
+
|
|
101
|
+
Each heartbeat `tail` call's `description` carries the most recent progress signal forward, parsed from the prior poll's last surfaced line. Chat timeline rows collapse to the `description`; the body holding the actual progress is hidden until the operator expands every row, so the progress belongs in the title.
|
|
83
102
|
|
|
84
|
-
|
|
103
|
+
Format: `Heartbeat poll — sessionIndex=K/N pct=P chunks-so-far=X`
|
|
104
|
+
|
|
105
|
+
The first poll (no prior progress yet) is `Heartbeat poll — starting`. Every subsequent poll updates the suffix from the last surfaced progress line; if the most recent poll surfaced the `still on session …` stall line, the suffix becomes `Heartbeat poll — sessionIndex=K/N pct=P stalled <Δ>s`.
|
|
106
|
+
|
|
107
|
+
The literal label `Heartbeat poll` never shortens — not to `Poll N`, not to `polling`, not to anything else. Forbidden: any heartbeat `description` that drops the progress suffix once a `progress` line has been surfaced; any label other than `Heartbeat poll`.
|
|
108
|
+
|
|
109
|
+
### Subagent dispatch contract (Task 905)
|
|
110
|
+
|
|
111
|
+
When the heartbeat loop is delegated to a subagent (ad-hoc `Agent` invocation watching a `run_in_background` bash for this skill), the dispatch prompt MUST include the **Heartbeat protocol** subsection above verbatim — the grep contract, the description-format clause, and the no-NEW-progress branch. Subagents do not inherit memory or per-turn feedback; the dispatch prompt is the only carrier of this convention across the subagent boundary.
|
|
112
|
+
|
|
113
|
+
A subagent that emits `Poll 58`, `Poll 69`, or any non-canonical label is a contract violation, not stylistic drift. The main agent's prompt-construction step is the gate: the subagent prompt is wrong if the literal string `Heartbeat poll —` is missing from it.
|
|
85
114
|
|
|
86
115
|
### Resume after silent stall
|
|
87
116
|
|
|
@@ -93,11 +122,12 @@ Forbidden: blind-reissue without reading the progress file first.
|
|
|
93
122
|
- Picks the normaliser for `--source`. WhatsApp: locates `_chat.txt` (zip / dir / direct file), parses deterministically, computes `archiveSha256`. Other sources interpret the path according to their own format.
|
|
94
123
|
- Validates every distinct parsed senderName against the closed set of `{owner, participants...}` candidate names. Any miss LOUD-FAILs `parser-miss reason="senderName=<verbatim> not in confirmed participant set ..."`.
|
|
95
124
|
- Computes `conversationIdentity` from accountId + sorted participant elementIds.
|
|
96
|
-
-
|
|
97
|
-
- Sessionizes the delta lines at the
|
|
98
|
-
-
|
|
125
|
+
- **Without `--rebuild`** (default): looks up any prior `:ConversationArchive` carrying that identity → reads `lastIngestedMessageHash`. If found, slices parsed lines after the cursor (delta-append). Cursor not found → `FAIL delta-cursor-missing`. Cursor at last line → empty-delta noop (exit 0, no writes; existing chunk count surfaced in JSON summary). **With `--rebuild`** (Task 902 sub-scope B): skips the prior-cursor lookup entirely; every line is treated as first-ingest delta; the first session's `memory-ingest` deletes prior chunks for THIS `archiveSha256` and the new cursor overwrites the stale one in the same MERGE.
|
|
126
|
+
- Sessionizes the delta lines at the fixed 8h gap (Task 902 sub-scope D — code constant, no flag).
|
|
127
|
+
- Computes the stable archive `title` once (Task 902 sub-scope A): `<source> · <owner> ↔ <others> · <YYYY-MM-DD>→<YYYY-MM-DD>`.
|
|
128
|
+
- For each session: renders as turn-attributed text (`[ts] Sender: body\n…`) and calls `memory-classify` with `mode='chat'`. Oversize sessions dispatch to the chunked-chat path (Task 902 sub-scope C) — sessionize size is no longer an operator concern. Returns one or more `:Section:Conversation` chunk specs.
|
|
99
129
|
- **Per-session checkpoint** (Task 900 sub-scope E): immediately after each successful classify, calls `memory-ingest` for THAT session's chunks AND advances `lastIngestedMessageHash` / `lastIngestedMessageAt` on the parent `:ConversationArchive` to the last message of that session — atomic (chunk writes + cursor advance happen inside one Cypher transaction). A kill mid-loop leaves the cursor at session N-1's last message; re-issuing with the same argv and `--session-id` resumes from session N onward without re-classifying prior sessions.
|
|
100
|
-
- Server MERGEs the parent on `conversationIdentity
|
|
130
|
+
- Server MERGEs the parent on `conversationIdentity` (writing `title` ON CREATE and COALESCE-on-MATCH so a stable title is never overwritten), MERGEs `:PARTICIPANT_IN` edges, CREATEs new chunks, extends the `:NEXT` chain from its tail. The cleanup-by-`archiveSha256` step runs ONLY when `--rebuild` is set AND only on the FIRST per-session call of a run. Every node and edge stamps `source=<enum>` and `createdByAgent='conversation-archive'`.
|
|
101
131
|
|
|
102
132
|
NO insight pass runs. Phase 2 (operator-driven `:Observation` / `:Task` / `:Preference` derivation against chunks) is its own follow-up task — and applies uniformly to every source once chunks exist.
|
|
103
133
|
|
|
@@ -143,8 +173,15 @@ For an empty-delta re-import (`delta.kind === "empty-delta"`): emit only message
|
|
|
143
173
|
|
|
144
174
|
## Idempotency
|
|
145
175
|
|
|
146
|
-
- **Re-running the same export bytes
|
|
176
|
+
- **Re-running the same export bytes is a no-op by default** (Task 902 sub-scope B). The bin reports the existing chunk count and exits 0; cursor and chunks are unchanged. The pre-902 contract that ran cleanup-by-`archiveSha256` on every same-bytes re-run was the destructive primitive that combined with `--session-gap-hours` to silently destroy 138 chunks (Adam Mackay incident, 2026-05-04).
|
|
147
177
|
- **Re-running with appended messages** (a fresh export from the same chat with new messages at the tail): cursor lookup finds the prior `lastIngestedMessageHash`, slices new messages, sessionizes only those, and appends new chunks at the tail of the existing `:NEXT` chain. Pre-existing chunks are never touched.
|
|
178
|
+
- **Destructive rebuild** requires the explicit `--rebuild` flag — and only the operator may pass it (see Doctrine below). With `--rebuild`, the bin treats the run as first-ingest, the FIRST session's writer cleans prior chunks for the matching `archiveSha256`, the cursor is overwritten by that session's MERGE, and the heartbeat surfaces a WARN line so the destructive action is operator-visible.
|
|
179
|
+
- **Re-ingesting into a previously-trashed archive** revives the parent before MERGE. If the operator drag-trashed a `:ConversationArchive` and then re-runs ingest (delta or `--rebuild`), `memory-ingest`'s `ingestConversationArchive` strips `:Trashed` from the matching `conversationIdentity` so the MERGE binds to a non-trashed parent — otherwise the chunks would land under a `:Trashed` parent the graph UI hides, making the entire archive (and every freshly-written chunk under it) invisible until something else clears the label. Mirrors the KnowledgeDocument trash-revival path (Task 576).
|
|
180
|
+
|
|
181
|
+
## Doctrine
|
|
182
|
+
|
|
183
|
+
- **`--rebuild` is operator-issued only.** The agent must NEVER propose `--rebuild` as a fix for any FAIL phase, classifier error, oversize-session report, or partial-state recovery. The operator must type `--rebuild` themselves. Agent-autonomous `--rebuild` would re-introduce the silent-partial-wipe surface that Task 902 closed; the flag exists exclusively for operators reasoning about identifiable export bytes they want to re-classify.
|
|
184
|
+
- **Use a fresh export** (different `archiveSha256`) for every legitimate re-classify case the agent surfaces. Same-bytes re-classify is the operator's call, not the agent's.
|
|
148
185
|
|
|
149
186
|
## Verification (post-write)
|
|
150
187
|
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
#
|
|
9
9
|
# Contract (inputs via env vars):
|
|
10
10
|
# ACCOUNTS_DIR — {installDir}/data/accounts
|
|
11
|
-
# USERS_FILE —
|
|
11
|
+
# USERS_FILE — $HOME/<brand.configDir>/users.json (Task 904 — was
|
|
12
|
+
# {installDir}/platform/config/users.json pre-Task-904, but
|
|
13
|
+
# that path lived in the installer's wipe zone)
|
|
12
14
|
#
|
|
13
15
|
# Contract (outputs via env vars):
|
|
14
16
|
# ACCOUNT_ID — resolved uuid (existing kept, or freshly minted)
|
|
@@ -376,7 +376,9 @@ if [ -f "$PINS_FILE" ]; then
|
|
|
376
376
|
# users.json from .admin-pin, but only at install time — and install runs
|
|
377
377
|
# before this script writes .admin-pin. Self-contained migration writes
|
|
378
378
|
# users.json directly, mirroring seed-neo4j.sh's migration branch.
|
|
379
|
-
|
|
379
|
+
# Task 904 — write to the persistent location (outside the install wipe
|
|
380
|
+
# zone) so subsequent installs do not overwrite this row with a stale backup.
|
|
381
|
+
USERS_FILE="$PIN_DIR/users.json"
|
|
380
382
|
if [ ! -f "$USERS_FILE" ]; then
|
|
381
383
|
USER_ID="$(cat /proc/sys/kernel/random/uuid 2>/dev/null || python3 -c 'import uuid; print(uuid.uuid4())')"
|
|
382
384
|
PIN_HASH="$(cat "$PIN_DIR/.admin-pin")"
|
|
@@ -50,7 +50,15 @@ CYPHER_SHELL="cypher-shell"
|
|
|
50
50
|
# no identity match aborts loud; .trash/ is operator-emptied, never auto-cleaned.
|
|
51
51
|
# shellcheck source=lib/resolve-account-dir.sh
|
|
52
52
|
. "$SCRIPT_DIR/lib/resolve-account-dir.sh"
|
|
53
|
-
|
|
53
|
+
# Resolve brand-aware persistent users.json path before the resolver runs.
|
|
54
|
+
# Mirrors the resolution lower in this file (around the post-Task-904 USERS_FILE
|
|
55
|
+
# block); duplicated here because resolve_and_sweep_account_dir runs first.
|
|
56
|
+
_RESOLVER_CONFIG_DIR_NAME=".maxy"
|
|
57
|
+
if [ -f "$PROJECT_DIR/config/brand.json" ]; then
|
|
58
|
+
_RESOLVER_BRAND_CFG_DIR=$(python3 -c "import json; print(json.load(open('$PROJECT_DIR/config/brand.json')).get('configDir',''))" 2>/dev/null || true)
|
|
59
|
+
[ -n "$_RESOLVER_BRAND_CFG_DIR" ] && _RESOLVER_CONFIG_DIR_NAME="$_RESOLVER_BRAND_CFG_DIR"
|
|
60
|
+
fi
|
|
61
|
+
USERS_FILE="$HOME/$_RESOLVER_CONFIG_DIR_NAME/users.json" resolve_and_sweep_account_dir
|
|
54
62
|
|
|
55
63
|
mkdir -p "$ACCOUNT_DIR/agents/admin" "$ACCOUNT_DIR/.claude" "$ACCOUNT_DIR/specialists/.claude-plugin" "$ACCOUNT_DIR/specialists/agents"
|
|
56
64
|
|
|
@@ -376,9 +384,8 @@ echo " Account $ACCOUNT_ID at $ACCOUNT_DIR"
|
|
|
376
384
|
# ------------------------------------------------------------------
|
|
377
385
|
|
|
378
386
|
CONFIG_DIR="$PROJECT_DIR/config"
|
|
379
|
-
USERS_FILE="$CONFIG_DIR/users.json"
|
|
380
387
|
|
|
381
|
-
# Resolve the brand-specific config directory
|
|
388
|
+
# Resolve the brand-specific config directory.
|
|
382
389
|
# Mirrors the logic in platform/ui/app/lib/paths.ts.
|
|
383
390
|
_CONFIG_DIR_NAME=".maxy"
|
|
384
391
|
if [ -f "$CONFIG_DIR/brand.json" ]; then
|
|
@@ -386,6 +393,9 @@ if [ -f "$CONFIG_DIR/brand.json" ]; then
|
|
|
386
393
|
[ -n "$_BRAND_CFG_DIR" ] && _CONFIG_DIR_NAME="$_BRAND_CFG_DIR"
|
|
387
394
|
fi
|
|
388
395
|
ADMIN_PIN_FILE="$HOME/$_CONFIG_DIR_NAME/.admin-pin"
|
|
396
|
+
# Task 904 — users.json lives under $HOME/$_CONFIG_DIR_NAME, not the wipe zone.
|
|
397
|
+
USERS_FILE="$HOME/$_CONFIG_DIR_NAME/users.json"
|
|
398
|
+
mkdir -p "$HOME/$_CONFIG_DIR_NAME"
|
|
389
399
|
|
|
390
400
|
# Only create users.json if it doesn't exist AND .admin-pin exists (migration case).
|
|
391
401
|
# Fresh installs: users.json is created by set-pin POST during onboarding.
|