@rubytech/create-maxy-code 0.1.439 → 0.1.440

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 (60) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +14 -6
  3. package/payload/platform/plugins/docs/references/admin-identity-gate.md +32 -17
  4. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  5. package/payload/platform/plugins/docs/references/internals.md +2 -2
  6. package/payload/platform/plugins/docs/references/outlook-guide.md +2 -2
  7. package/payload/platform/plugins/docs/references/troubleshooting.md +8 -0
  8. package/payload/platform/plugins/graph/.claude-plugin/plugin.json +19 -0
  9. package/payload/platform/plugins/graph/PLUGIN.md +10 -4
  10. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.d.ts +2 -0
  11. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.d.ts.map +1 -0
  12. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.js +55 -0
  13. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.js.map +1 -0
  14. package/payload/platform/plugins/outlook/mcp/dist/index.js +1 -1
  15. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
  16. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +17 -0
  17. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -1
  18. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +84 -21
  19. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -1
  20. package/payload/platform/plugins/outlook/references/graph-surfaces.md +15 -1
  21. package/payload/platform/scripts/__tests__/account-schema-owned-dirs.test.sh +100 -0
  22. package/payload/platform/scripts/lib/account-schema-owned-dirs.py +207 -0
  23. package/payload/platform/scripts/lib/account-schema-owned-dirs.sh +33 -0
  24. package/payload/platform/scripts/lib/provision-account-dir.sh +7 -0
  25. package/payload/platform/scripts/setup-account.sh +10 -0
  26. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  27. package/payload/platform/services/claude-session-manager/dist/http-server.js +6 -4
  28. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  29. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.d.ts +7 -6
  30. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.d.ts.map +1 -1
  31. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.js +56 -7
  32. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.js.map +1 -1
  33. package/payload/server/{chunk-UGPQCVIW.js → chunk-WC4WJR6X.js} +0 -6
  34. package/payload/server/maxy-edge.js +1 -1
  35. package/payload/server/public/assets/{AdminLoginScreens-DeR71H29.js → AdminLoginScreens-CukwQufW.js} +1 -1
  36. package/payload/server/public/assets/AdminShell-BwEBH6tT.js +2 -0
  37. package/payload/server/public/assets/{admin-C15h-NL0.js → admin-CdFRCUUZ.js} +1 -1
  38. package/payload/server/public/assets/{browser-B7xQLIRK.js → browser-BWR59IkB.js} +1 -1
  39. package/payload/server/public/assets/{calendar-A-vGm6pL.js → calendar-kuTcxX1t.js} +1 -1
  40. package/payload/server/public/assets/chat-ejnq2rck.js +1 -0
  41. package/payload/server/public/assets/data-dZbOyLdT.js +1 -0
  42. package/payload/server/public/assets/{graph-D0orIRAh.js → graph-NgQDY0Pb.js} +1 -1
  43. package/payload/server/public/assets/{operator-DZzuotDB.js → operator-oSaB1mya.js} +1 -1
  44. package/payload/server/public/assets/{page-Ck_pLhAk.js → page-DEkI-Gl1.js} +1 -1
  45. package/payload/server/public/assets/{page-3g0A19Rv.js → page-DMeJHVfA.js} +5 -5
  46. package/payload/server/public/assets/{public-WFUQw9AD.js → public-C54tl6V4.js} +1 -1
  47. package/payload/server/public/assets/{useCopyFeedback-CSk9KjhY.js → useCopyFeedback-DHoYHiK9.js} +1 -1
  48. package/payload/server/public/assets/{useVoiceRecorder-DCyJQkFK.js → useVoiceRecorder-b7HzTFs0.js} +1 -1
  49. package/payload/server/public/browser.html +2 -2
  50. package/payload/server/public/calendar.html +2 -2
  51. package/payload/server/public/chat.html +5 -5
  52. package/payload/server/public/data.html +3 -3
  53. package/payload/server/public/graph.html +3 -3
  54. package/payload/server/public/index.html +5 -5
  55. package/payload/server/public/operator.html +6 -6
  56. package/payload/server/public/public.html +5 -5
  57. package/payload/server/server.js +186 -112
  58. package/payload/server/public/assets/AdminShell-DlAVQUda.js +0 -2
  59. package/payload/server/public/assets/chat-DAae6Z51.js +0 -1
  60. package/payload/server/public/assets/data-BNWpKx1d.js +0 -1
@@ -1,30 +1,93 @@
1
1
  import { callGraph } from "../lib/graph-client.js";
2
2
  import { log } from "../lib/log.js";
3
+ /**
4
+ * Map a caller-supplied `folder` value to the Graph `/me/mailFolders/{id}`
5
+ * path segment.
6
+ *
7
+ * Graph accepts only a well-known token (a single word, no spaces:
8
+ * `inbox`, `sentitems`, `drafts`, `deleteditems`, `junkemail`, `archive`,
9
+ * `outbox`) or a real folder id in that segment. A human display name such as
10
+ * "Sent Items" is neither, so Graph rejects it with "Id is malformed". This
11
+ * normalizes the full well-known set plus common aliases — case-insensitively
12
+ * and space-insensitively — to the Graph token. Any value not in the alias
13
+ * table is assumed to be a raw folder id and returned unchanged (`asRawId`),
14
+ * preserving the prior pass-through behaviour.
15
+ */
16
+ export function resolveFolder(folder) {
17
+ const key = folder.toLowerCase().replace(/\s+/g, "");
18
+ // Own-key check only: a bare `WELL_KNOWN_ALIASES[key]` would also read
19
+ // inherited members, so `folder` values like "constructor" or "__proto__"
20
+ // would resolve to an Object.prototype member instead of passing through as
21
+ // raw ids.
22
+ if (Object.hasOwn(WELL_KNOWN_ALIASES, key)) {
23
+ return { token: WELL_KNOWN_ALIASES[key], asRawId: false };
24
+ }
25
+ return { token: folder, asRawId: true };
26
+ }
27
+ // Lookup keys are already lowercased and space-stripped. Inbox maps to the
28
+ // capitalized "Inbox" segment the tool has always used (Graph is
29
+ // case-insensitive on well-known names); the rest map to the lowercase Graph
30
+ // tokens. "" (and whitespace-only) collapses to the key "" → Inbox, folding in
31
+ // the former lone-inbox special case.
32
+ const WELL_KNOWN_ALIASES = {
33
+ "": "Inbox",
34
+ inbox: "Inbox",
35
+ sentitems: "sentitems",
36
+ sent: "sentitems",
37
+ drafts: "drafts",
38
+ deleteditems: "deleteditems",
39
+ trash: "deleteditems",
40
+ junkemail: "junkemail",
41
+ junk: "junkemail",
42
+ spam: "junkemail",
43
+ archive: "archive",
44
+ outbox: "outbox",
45
+ };
3
46
  const FIELDS = "id,subject,from,receivedDateTime,bodyPreview,isRead";
4
47
  export async function runMailList(config, args) {
5
48
  const top = clampTop(args.top, 25);
6
- const folder = args.folder ?? "Inbox";
49
+ const folderIn = args.folder ?? "Inbox";
50
+ const { token: folderResolved, asRawId } = resolveFolder(folderIn);
7
51
  const startMs = Date.now();
8
- const result = await callGraph(config, async (client) => {
9
- const path = folder.toLowerCase() === "inbox" || folder === ""
10
- ? "/me/mailFolders/Inbox/messages"
11
- : `/me/mailFolders/${encodeURIComponent(folder)}/messages`;
12
- return (await client
13
- .api(path)
14
- .top(top)
15
- .orderby("receivedDateTime DESC")
16
- .select(FIELDS)
17
- .get());
18
- }, { tool: "mail-list" });
19
- const items = result.value.map(shapeMessage);
20
- log({
21
- event: "mail-list",
22
- account: config.accountId,
23
- folder,
24
- count: items.length,
25
- elapsedMs: Date.now() - startMs,
26
- });
27
- return items;
52
+ try {
53
+ const result = await callGraph(config, async (client) => {
54
+ const path = `/me/mailFolders/${encodeURIComponent(folderResolved)}/messages`;
55
+ return (await client
56
+ .api(path)
57
+ .top(top)
58
+ .orderby("receivedDateTime DESC")
59
+ .select(FIELDS)
60
+ .get());
61
+ }, { tool: "mail-list" });
62
+ const items = result.value.map(shapeMessage);
63
+ log({
64
+ event: "mail-list",
65
+ op: "resolve",
66
+ account: config.accountId,
67
+ folderIn,
68
+ folderResolved,
69
+ asRawId,
70
+ count: items.length,
71
+ elapsedMs: Date.now() - startMs,
72
+ });
73
+ return items;
74
+ }
75
+ catch (err) {
76
+ // callGraph already logged the graph-error line (status/code, e.g. the 400
77
+ // "Id is malformed"); this line attaches the folder resolution so the
78
+ // failing input and the token it mapped to are visible on the error path.
79
+ log({
80
+ event: "mail-list",
81
+ op: "resolve",
82
+ account: config.accountId,
83
+ folderIn,
84
+ folderResolved,
85
+ asRawId,
86
+ error: err.message,
87
+ elapsedMs: Date.now() - startMs,
88
+ });
89
+ throw err;
90
+ }
28
91
  }
29
92
  function shapeMessage(msg) {
30
93
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"mail-list.js","sourceRoot":"","sources":["../../src/tools/mail-list.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AA6BpC,MAAM,MAAM,GAAG,qDAAqD,CAAC;AAErE,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAyB,EACzB,IAAkB;IAElB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE3B,MAAM,MAAM,GAAG,MAAM,SAAS,CAC5B,MAAM,EACN,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,MAAM,IAAI,GACR,MAAM,CAAC,WAAW,EAAE,KAAK,OAAO,IAAI,MAAM,KAAK,EAAE;YAC/C,CAAC,CAAC,gCAAgC;YAClC,CAAC,CAAC,mBAAmB,kBAAkB,CAAC,MAAM,CAAC,WAAW,CAAC;QAC/D,OAAO,CAAC,MAAM,MAAM;aACjB,GAAG,CAAC,IAAI,CAAC;aACT,GAAG,CAAC,GAAG,CAAC;aACR,OAAO,CAAC,uBAAuB,CAAC;aAChC,MAAM,CAAC,MAAM,CAAC;aACd,GAAG,EAAE,CAA8B,CAAC;IACzC,CAAC,EACD,EAAE,IAAI,EAAE,WAAW,EAAE,CACtB,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC7C,GAAG,CAAC;QACF,KAAK,EAAE,WAAW;QAClB,OAAO,EAAE,MAAM,CAAC,SAAS;QACzB,MAAM;QACN,KAAK,EAAE,KAAK,CAAC,MAAM;QACnB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;KAChC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,GAAe;IACnC,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,IAAI;QAC5B,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,IAAI,IAAI;QAC7C,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;QACtC,WAAW,EAAE,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;QACnD,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,KAAK;KAC5B,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAyB,EAAE,QAAgB;IAC3D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC1E,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACxB,IAAI,KAAK,GAAG,GAAG;QAAE,OAAO,GAAG,CAAC;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"mail-list.js","sourceRoot":"","sources":["../../src/tools/mail-list.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,21 @@ Microsoft Graph endpoints used by each tool, with the response shape and the fie
6
6
 
7
7
  **Endpoint (Inbox):** `GET /me/mailFolders/Inbox/messages?$top={N}&$orderby=receivedDateTime DESC&$select=id,subject,from,receivedDateTime,bodyPreview,isRead`
8
8
 
9
- **Endpoint (other folder):** `GET /me/mailFolders/{folderId-or-displayName}/messages?...`
9
+ **Endpoint (other folder):** `GET /me/mailFolders/{well-known-token-or-folderId}/messages?...`
10
+
11
+ The `folder` arg is normalized before the path is built. Graph's `{id}` segment accepts only a well-known token (single word, no spaces) or a real folder id — a display name like "Sent Items" is rejected with "Id is malformed". The tool maps the full well-known set plus common aliases, case- and space-insensitively, to the Graph token:
12
+
13
+ | Input (any case/spacing) | Graph token |
14
+ |---|---|
15
+ | `Inbox`, `` (empty) | `Inbox` |
16
+ | `Sent Items`, `sent` | `sentitems` |
17
+ | `Drafts` | `drafts` |
18
+ | `Deleted Items`, `trash` | `deleteditems` |
19
+ | `Junk Email`, `junk`, `spam` | `junkemail` |
20
+ | `Archive` | `archive` |
21
+ | `Outbox` | `outbox` |
22
+
23
+ Any value not in this table is treated as a raw folder id and passed through unchanged. A custom folder's display name (e.g. "Projects") is therefore not resolved — resolving custom folders by name would need a `/me/mailFolders` lookup and is out of scope.
10
24
 
11
25
  **Returned shape:**
12
26
  ```typescript
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bash
2
+ # Tests the plugin-declared account-owned-dir merge/reconcile lib: a brand that
3
+ # ships a plugin declaring an owned top-level dir gets it unioned into the
4
+ # account SCHEMA.md allowed-top-level set; a brand without the plugin does not;
5
+ # the merge is idempotent; the reconcile check reports present before/after.
6
+ set -uo pipefail
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PLAT_SCRIPTS="$SCRIPT_DIR/.."
9
+ REAL_PROJECT_DIR="$(cd "$PLAT_SCRIPTS/.." && pwd)"
10
+ REAL_TEMPLATE="$REAL_PROJECT_DIR/templates/account-schema/SCHEMA.md"
11
+ REAL_PREMIUM="$(cd "$REAL_PROJECT_DIR/.." && pwd)/premium-plugins"
12
+ PASS=0; FAIL=0; FAILED=()
13
+ assert_grep() { if printf '%s' "$2" | grep -qF "$1"; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED+=("$3: expected to find [$1]"); fi; }
14
+ assert_nogrep() { if printf '%s' "$2" | grep -qF "$1"; then FAIL=$((FAIL+1)); FAILED+=("$3: expected NOT to find [$1]"); else PASS=$((PASS+1)); fi; }
15
+ assert_eq() { if [ "$1" = "$2" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); FAILED+=("$3: expected [$2] got [$1]"); fi; }
16
+
17
+ . "$PLAT_SCRIPTS/lib/account-schema-owned-dirs.sh"
18
+
19
+ # Build a fake platform tree at $T/platform whose premium sibling is a symlink to
20
+ # the real premium-plugins (so the real sitedesk-job PLUGIN.md is exercised).
21
+ make_tree() { # $1=ships (json array literal)
22
+ local T; T="$(mktemp -d)"
23
+ mkdir -p "$T/platform/plugins" "$T/platform/config" "$T/platform/templates/account-schema"
24
+ ln -s "$REAL_PREMIUM" "$T/premium-plugins"
25
+ cp "$REAL_TEMPLATE" "$T/platform/templates/account-schema/SCHEMA.md"
26
+ printf '{"installDir":"test-brand","shipsPremiumBundles":%s}\n' "$1" > "$T/platform/config/brand.json"
27
+ echo "$T"
28
+ }
29
+ make_account() { # $1=tree
30
+ local A; A="$(mktemp -d)"
31
+ cp "$1/platform/templates/account-schema/SCHEMA.md" "$A/SCHEMA.md"
32
+ printf '{"accountId":"x","role":"house"}\n' > "$A/account.json"
33
+ echo "$A"
34
+ }
35
+
36
+ # --- sitedesk-shipping brand: quoting added ---
37
+ T_SD="$(make_tree '["sitedesk"]')"; A_SD="$(make_account "$T_SD")"
38
+ export PROJECT_DIR="$T_SD/platform"
39
+ OUT="$(merge_owned_dirs_into_schema "$A_SD")"
40
+ assert_grep "op=seed-owned-dirs" "$OUT" "sd-seed-log"
41
+ assert_grep "added=quoting" "$OUT" "sd-added-quoting"
42
+ ALLOWED="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_SD/SCHEMA.md")"
43
+ assert_grep "quoting" "$ALLOWED" "sd-allowlist-has-quoting"
44
+ assert_grep "Plugin-owned top-level workspaces" "$(cat "$A_SD/SCHEMA.md")" "sd-descriptive-section"
45
+ assert_grep "plugin-owned-dirs:start" "$(cat "$A_SD/SCHEMA.md")" "sd-marker"
46
+
47
+ # --- idempotency: second merge is byte-identical ---
48
+ BEFORE="$(shasum "$A_SD/SCHEMA.md" | awk '{print $1}')"
49
+ OUT2="$(merge_owned_dirs_into_schema "$A_SD")"
50
+ AFTER="$(shasum "$A_SD/SCHEMA.md" | awk '{print $1}')"
51
+ assert_eq "$AFTER" "$BEFORE" "idempotent-second-merge"
52
+ assert_grep "added=none" "$OUT2" "sd-second-added-none"
53
+
54
+ # --- non-sitedesk brand: no quoting ---
55
+ T_MX="$(make_tree '[]')"; A_MX="$(make_account "$T_MX")"
56
+ export PROJECT_DIR="$T_MX/platform"
57
+ merge_owned_dirs_into_schema "$A_MX" >/dev/null
58
+ ALLOWED_MX="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_MX/SCHEMA.md")"
59
+ assert_nogrep "quoting" "$ALLOWED_MX" "mx-no-quoting"
60
+ assert_nogrep "Plugin-owned top-level workspaces" "$(cat "$A_MX/SCHEMA.md")" "mx-no-section"
61
+
62
+ # --- reconcile: present=false on bare schema, present=true after merge ---
63
+ A_RC="$(make_account "$T_SD")"
64
+ export PROJECT_DIR="$T_SD/platform"
65
+ RC_BEFORE="$(PROJECT_DIR="$PROJECT_DIR" python3 "$PLAT_SCRIPTS/lib/account-schema-owned-dirs.py" reconcile "$A_RC")"
66
+ assert_grep "ownedDir=quoting present=false" "$RC_BEFORE" "rc-present-false"
67
+ merge_owned_dirs_into_schema "$A_RC" >/dev/null
68
+ RC_AFTER="$(PROJECT_DIR="$PROJECT_DIR" python3 "$PLAT_SCRIPTS/lib/account-schema-owned-dirs.py" reconcile "$A_RC")"
69
+ assert_grep "ownedDir=quoting present=true" "$RC_AFTER" "rc-present-true"
70
+
71
+ # --- sweep: backfills every account with an account.json ---
72
+ ACCTS="$(mktemp -d)"; mkdir -p "$ACCTS/a1"
73
+ cp "$T_SD/platform/templates/account-schema/SCHEMA.md" "$ACCTS/a1/SCHEMA.md"
74
+ printf '{"accountId":"a1","role":"client"}\n' > "$ACCTS/a1/account.json"
75
+ SWEEP="$(reconcile_all_accounts_owned_dirs "$ACCTS" 2>&1)"
76
+ assert_grep "op=reconcile" "$SWEEP" "sweep-reconcile-log"
77
+ ALLOWED_A1="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$ACCTS/a1/SCHEMA.md")"
78
+ assert_grep "quoting" "$ALLOWED_A1" "sweep-backfilled-a1"
79
+
80
+ # --- shape-b: a bundle-root PLUGIN.md declaration is resolved too ---
81
+ # A premium bundle may declare an owned dir in its root PLUGIN.md (depth 1),
82
+ # not only in a sub-plugin manifest (depth 2). Build a fully synthetic tree.
83
+ TB="$(mktemp -d)"
84
+ mkdir -p "$TB/platform/plugins" "$TB/platform/config" "$TB/platform/templates/account-schema"
85
+ mkdir -p "$TB/premium-plugins/acme/plugins/acme-sub"
86
+ cp "$REAL_TEMPLATE" "$TB/platform/templates/account-schema/SCHEMA.md"
87
+ printf '{"installDir":"acme-brand","shipsPremiumBundles":["acme"]}\n' > "$TB/platform/config/brand.json"
88
+ printf -- '---\nname: acme\naccount-owned-dirs: [{"dir": "ledger", "description": "Acme ledger."}]\n---\n' > "$TB/premium-plugins/acme/PLUGIN.md"
89
+ printf -- '---\nname: acme-sub\n---\n' > "$TB/premium-plugins/acme/plugins/acme-sub/PLUGIN.md"
90
+ A_TB="$(mktemp -d)"; cp "$TB/platform/templates/account-schema/SCHEMA.md" "$A_TB/SCHEMA.md"
91
+ export PROJECT_DIR="$TB/platform"
92
+ RES_TB="$(PROJECT_DIR="$PROJECT_DIR" python3 "$PLAT_SCRIPTS/lib/account-schema-owned-dirs.py" resolve)"
93
+ assert_grep "ledger" "$RES_TB" "shapeb-resolve-ledger"
94
+ assert_grep "\"plugin\": \"acme\"" "$RES_TB" "shapeb-owner-is-bundle"
95
+ merge_owned_dirs_into_schema "$A_TB" >/dev/null
96
+ ALLOWED_TB="$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$A_TB/SCHEMA.md")"
97
+ assert_grep "ledger" "$ALLOWED_TB" "shapeb-allowlist-has-ledger"
98
+
99
+ echo "PASS=$PASS FAIL=$FAIL"
100
+ if [ "$FAIL" -ne 0 ]; then printf '%s\n' "${FAILED[@]}"; exit 1; fi
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env python3
2
+ """Resolve plugin-declared account-owned top-level dirs for the brand and merge
3
+ them into an account's SCHEMA.md.
4
+
5
+ A plugin declares owned top-level account dirs in its PLUGIN.md frontmatter:
6
+
7
+ account-owned-dirs: [{"dir": "quoting", "description": "..."}]
8
+
9
+ The owned-dir set for a brand is the union declared by every platform plugin
10
+ ($PROJECT_DIR/plugins/*/PLUGIN.md) and every premium sub-plugin of a bundle the
11
+ brand ships ($PREMIUM_ROOT/<bundle>/plugins/*/PLUGIN.md, gated by brand.json
12
+ shipsPremiumBundles). Reads PROJECT_DIR from the environment.
13
+ """
14
+ import json, os, re, sys
15
+
16
+ MARK_START = "<!-- plugin-owned-dirs:start -->"
17
+ MARK_END = "<!-- plugin-owned-dirs:end -->"
18
+
19
+
20
+ def _project_dir():
21
+ pd = os.environ.get("PROJECT_DIR", "")
22
+ if not pd:
23
+ sys.stderr.write("account-schema-owned-dirs: PROJECT_DIR unset\n")
24
+ sys.exit(1)
25
+ return os.path.normpath(pd)
26
+
27
+
28
+ def _read_brand(project_dir):
29
+ path = os.path.join(project_dir, "config", "brand.json")
30
+ if not os.path.isfile(path):
31
+ return "unknown", []
32
+ try:
33
+ with open(path) as f:
34
+ b = json.load(f)
35
+ return (b.get("installDir") or "unknown"), list(b.get("shipsPremiumBundles") or [])
36
+ except Exception:
37
+ return "unknown", []
38
+
39
+
40
+ def _parse_plugin_md(path):
41
+ """Return (name, [ {dir, description} ]) from a PLUGIN.md, or (None, [])."""
42
+ try:
43
+ with open(path) as f:
44
+ text = f.read()
45
+ except Exception:
46
+ return None, []
47
+ m = re.match(r"^---\n(.*?)\n---", text, re.DOTALL)
48
+ if not m:
49
+ return None, []
50
+ fm = m.group(1)
51
+ name = None
52
+ nm = re.search(r"^name:\s*(.+)$", fm, re.MULTILINE)
53
+ if nm:
54
+ name = nm.group(1).strip().strip('"').strip("'")
55
+ owned = []
56
+ om = re.search(r"^account-owned-dirs:\s*(.+)$", fm, re.MULTILINE)
57
+ if om:
58
+ try:
59
+ raw = json.loads(om.group(1).strip())
60
+ for e in raw:
61
+ d = (e.get("dir") or "").strip()
62
+ if d:
63
+ owned.append({"dir": d, "description": (e.get("description") or "").strip()})
64
+ except Exception:
65
+ pass
66
+ return name, owned
67
+
68
+
69
+ def _plugin_md_paths(project_dir):
70
+ paths = []
71
+ plugins_root = os.path.join(project_dir, "plugins")
72
+ if os.path.isdir(plugins_root):
73
+ for entry in sorted(os.listdir(plugins_root)):
74
+ p = os.path.join(plugins_root, entry, "PLUGIN.md")
75
+ if os.path.isfile(p):
76
+ paths.append(p)
77
+ _brand, ships = _read_brand(project_dir)
78
+ premium_root = os.path.join(os.path.dirname(project_dir), "premium-plugins")
79
+ for bundle in ships:
80
+ bundle_dir = os.path.join(premium_root, bundle)
81
+ # Bundle-root manifest (shape b): premium-plugins/<bundle>/PLUGIN.md.
82
+ root_md = os.path.join(bundle_dir, "PLUGIN.md")
83
+ if os.path.isfile(root_md):
84
+ paths.append(root_md)
85
+ # Sub-plugin manifests (shape a): premium-plugins/<bundle>/plugins/*/PLUGIN.md.
86
+ subs = os.path.join(bundle_dir, "plugins")
87
+ if not os.path.isdir(subs):
88
+ continue
89
+ for entry in sorted(os.listdir(subs)):
90
+ p = os.path.join(subs, entry, "PLUGIN.md")
91
+ if os.path.isfile(p):
92
+ paths.append(p)
93
+ return paths
94
+
95
+
96
+ def resolve(project_dir):
97
+ """Union of owned-dir declarations. First declarer of a dir wins its owner
98
+ label + description."""
99
+ seen = {}
100
+ order = []
101
+ for path in _plugin_md_paths(project_dir):
102
+ name, owned = _parse_plugin_md(path)
103
+ for e in owned:
104
+ if e["dir"] in seen:
105
+ continue
106
+ seen[e["dir"]] = {"dir": e["dir"], "description": e["description"], "plugin": name or "unknown"}
107
+ order.append(e["dir"])
108
+ return [seen[d] for d in order]
109
+
110
+
111
+ def _allowed_block(text):
112
+ """Return (list_of_entries, start_idx, end_idx) for the fenced
113
+ allowed-top-level block. end_idx is the closing-fence line index."""
114
+ lines = text.split("\n")
115
+ start = None
116
+ for i, ln in enumerate(lines):
117
+ if ln.strip() == "```allowed-top-level":
118
+ start = i
119
+ break
120
+ if start is None:
121
+ return None, None, None
122
+ for j in range(start + 1, len(lines)):
123
+ if lines[j].strip() == "```":
124
+ return [l for l in lines[start + 1:j] if l.strip()], start, j
125
+ return None, None, None
126
+
127
+
128
+ def _strip_region(text):
129
+ if MARK_START not in text:
130
+ return text
131
+ pre = text.split(MARK_START, 1)[0]
132
+ post = ""
133
+ if MARK_END in text:
134
+ post = text.split(MARK_END, 1)[1]
135
+ return (pre.rstrip("\n") + "\n" + post.lstrip("\n")).rstrip("\n") + "\n"
136
+
137
+
138
+ def merge(project_dir, account_dir):
139
+ brand, _ = _read_brand(project_dir)
140
+ owned = resolve(project_dir)
141
+ schema_path = os.path.join(account_dir, "SCHEMA.md")
142
+ if not os.path.isfile(schema_path):
143
+ print(f"brand={brand} added=none")
144
+ return
145
+ with open(schema_path) as f:
146
+ text = f.read()
147
+ entries, start, end = _allowed_block(text)
148
+ added = []
149
+ if entries is not None:
150
+ present = set(entries)
151
+ lines = text.split("\n")
152
+ insert = []
153
+ for e in owned:
154
+ if e["dir"] not in present:
155
+ insert.append(e["dir"])
156
+ added.append(e["dir"])
157
+ if insert:
158
+ lines[end:end] = insert
159
+ text = "\n".join(lines)
160
+ # Regenerate the descriptive region wholesale (idempotent).
161
+ text = _strip_region(text)
162
+ if owned:
163
+ region = [MARK_START, "## Plugin-owned top-level workspaces", "",
164
+ "A plugin that owns a top-level workspace declares it. The internal",
165
+ "structure is owned by that plugin's tools and recreated on the next",
166
+ "write. Never reorganise it.", ""]
167
+ for e in owned:
168
+ desc = e["description"] or f"Owned by the {e['plugin']} plugin."
169
+ region.append(f"- `{e['dir']}/` — {desc}")
170
+ region.append(MARK_END)
171
+ text = text.rstrip("\n") + "\n\n" + "\n".join(region) + "\n"
172
+ with open(schema_path, "w") as f:
173
+ f.write(text)
174
+ print(f"brand={brand} added={','.join(added) if added else 'none'}")
175
+
176
+
177
+ def reconcile(project_dir, account_dir):
178
+ brand, _ = _read_brand(project_dir)
179
+ owned = resolve(project_dir)
180
+ schema_path = os.path.join(account_dir, "SCHEMA.md")
181
+ present = set()
182
+ if os.path.isfile(schema_path):
183
+ with open(schema_path) as f:
184
+ entries, _s, _e = _allowed_block(f.read())
185
+ present = set(entries or [])
186
+ for e in owned:
187
+ is_present = "true" if e["dir"] in present else "false"
188
+ print(f"brand={brand} plugin={e['plugin']} ownedDir={e['dir']} present={is_present}")
189
+
190
+
191
+ def main():
192
+ if len(sys.argv) < 2:
193
+ sys.exit("usage: account-schema-owned-dirs.py {resolve|merge|reconcile} [account_dir]")
194
+ cmd = sys.argv[1]
195
+ pd = _project_dir()
196
+ if cmd == "resolve":
197
+ print(json.dumps(resolve(pd)))
198
+ elif cmd == "merge":
199
+ merge(pd, sys.argv[2])
200
+ elif cmd == "reconcile":
201
+ reconcile(pd, sys.argv[2])
202
+ else:
203
+ sys.exit(f"unknown command: {cmd}")
204
+
205
+
206
+ if __name__ == "__main__":
207
+ main()
@@ -0,0 +1,33 @@
1
+ # account-schema-owned-dirs.sh — merge plugin-declared owned top-level dirs into
2
+ # an account's SCHEMA.md, and reconcile every account at install time.
3
+ #
4
+ # Requires PROJECT_DIR exported by the caller. Sourced by provision-account-dir.sh
5
+ # and setup-account.sh.
6
+
7
+ _ACCT_SCHEMA_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ _ACCT_SCHEMA_PY="$_ACCT_SCHEMA_LIB_DIR/account-schema-owned-dirs.py"
9
+
10
+ # merge_owned_dirs_into_schema <account_dir>
11
+ # Unions the brand's declared owned dirs into <account_dir>/SCHEMA.md and logs
12
+ # the seam. Idempotent.
13
+ merge_owned_dirs_into_schema() {
14
+ local _acct="$1"
15
+ local _out
16
+ _out="$(PROJECT_DIR="$PROJECT_DIR" python3 "$_ACCT_SCHEMA_PY" merge "$_acct" 2>/dev/null)"
17
+ echo " [acct-schema] op=seed-owned-dirs ${_out}"
18
+ }
19
+
20
+ # reconcile_all_accounts_owned_dirs <accounts_dir>
21
+ # Standing check + backfill: for every account dir with an account.json, log the
22
+ # pre-merge present state of each declared owned dir, then merge (backfill).
23
+ reconcile_all_accounts_owned_dirs() {
24
+ local _accts="$1"
25
+ [ -d "$_accts" ] || return 0
26
+ local _acct
27
+ for _acct in "$_accts"/*/; do
28
+ [ -f "$_acct/account.json" ] || continue
29
+ PROJECT_DIR="$PROJECT_DIR" python3 "$_ACCT_SCHEMA_PY" reconcile "$_acct" 2>/dev/null \
30
+ | while IFS= read -r _line; do echo " [acct-schema] op=reconcile ${_line}"; done
31
+ merge_owned_dirs_into_schema "$_acct"
32
+ done
33
+ }
@@ -15,6 +15,9 @@
15
15
  # SCRIPT_DIR — the platform/scripts dir (for lib bootstraps)
16
16
  # USERS_FILE — $HOME/<brand.configDir>/users.json (owner stamp; optional)
17
17
 
18
+ # Owned-dir schema merge (plugin-declared top-level account dirs).
19
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/account-schema-owned-dirs.sh"
20
+
18
21
  provision_account_dir() {
19
22
  local ACCOUNT_DIR="$1"
20
23
  local ACCOUNT_ID="$2"
@@ -38,6 +41,10 @@ provision_account_dir() {
38
41
  fi
39
42
  echo " [acct-schema] seeded dirs=$_seeded"
40
43
 
44
+ # Merge brand-shipped plugin-declared owned top-level dirs into the freshly
45
+ # seeded SCHEMA.md so the guard admits them (e.g. quoting/ on SiteDesk).
46
+ merge_owned_dirs_into_schema "$ACCOUNT_DIR"
47
+
41
48
  # Claude Code discovers project-level .claude/ relative to the nearest .git
42
49
  # root. Without a .git in the account dir it traverses up to ~/maxy/.git and
43
50
  # misses accountDir/.claude/. Initialise a repo so the account dir IS the root.
@@ -53,3 +53,13 @@ USERS_FILE="$USERS_FILE" resolve_and_sweep_account_dir
53
53
  # designation (migrating a legacy unlabelled account in the same step).
54
54
  export TEMPLATES_DIR PROJECT_DIR SCRIPT_DIR USERS_FILE
55
55
  provision_account_dir "$ACCOUNT_DIR" "$ACCOUNT_ID" "house"
56
+
57
+ # ------------------------------------------------------------------
58
+ # 3. Reconcile plugin-declared owned top-level dirs across every account
59
+ # ------------------------------------------------------------------
60
+ # Standing check + durable backfill: every account under data/accounts/ gets the
61
+ # brand's plugin-declared owned dirs unioned into its SCHEMA.md. Runs on every
62
+ # install so a re-provision cannot drop a previously-added owned dir.
63
+ # reconcile_all_accounts_owned_dirs is available because provision-account-dir.sh
64
+ # (sourced above) sources the owned-dir lib.
65
+ reconcile_all_accounts_owned_dirs "$ACCOUNTS_DIR"
@@ -1 +1 @@
1
- {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
@@ -2293,10 +2293,12 @@ export function buildHttpApp(deps) {
2293
2293
  : undefined;
2294
2294
  // Task 682 — per-session userId override. Server-resolved by the UI from the
2295
2295
  // authoritative users.json/account.json (never message content) and shape-
2296
- // validated here the same way as personId. Task 1573 — absent for owner
2297
- // self-phone and any unbound sender; USER_ID is then the authenticated
2298
- // adminUserId when present, else empty. The boot owner is no longer a
2299
- // fallback at this layer (see resolveSpawnUserId).
2296
+ // validated here the same way as personId. Task 1573 — absent for a non-owner
2297
+ // unbound sender; USER_ID is then the authenticated adminUserId when present,
2298
+ // else empty. The boot owner is no longer a fallback at this layer (see
2299
+ // resolveSpawnUserId). Task 1575 the UI resolves an owner self-phone to the
2300
+ // owner userId and sends it here as body.userId, so the owner's own-phone
2301
+ // channel session carries the owner userId via this override (not empty).
2300
2302
  const overrideUserId = typeof body.userId === 'string' && body.userId.length > 0 && body.userId.length <= 128
2301
2303
  ? body.userId
2302
2304
  : undefined;