@rubytech/create-realagent 1.0.771 → 1.0.772

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent",
3
- "version": "1.0.771",
3
+ "version": "1.0.772",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent": "./dist/index.js"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: admin
3
- description: "Platform administration plugin. Provides system-status, brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, agent-list, agent-config-read, logs-read, plugin-read, render-component, session-reset, session-resume, file-attach, wifi, review-cadence tools (review-rules-list, review-rules-suppress, review-rules-unsuppress, review-rules-add, review-rules-remove, review-alerts-recent), adherence-read (attention-weighted adherence ledger), and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform."
3
+ description: "Platform administration plugin. Provides system-status, brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, render-component, session-reset, session-resume, file-attach, wifi, review-cadence tools (review-rules-list, review-rules-suppress, review-rules-unsuppress, review-rules-add, review-rules-remove, review-alerts-recent), adherence-read (attention-weighted adherence ledger), and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform."
4
4
  tools:
5
5
  - system-status
6
6
  - brand-settings
@@ -9,6 +9,7 @@ tools:
9
9
  - admin-add
10
10
  - admin-remove
11
11
  - admin-list
12
+ - admin-update-pin
12
13
  - agent-list
13
14
  - agent-config-read
14
15
  - logs-read
@@ -545,9 +545,9 @@ server.tool("account-update", "Update a user-configurable setting in account.jso
545
545
  // ===================================================================
546
546
  // Admin user management tools
547
547
  // ===================================================================
548
- server.tool("admin-add", "Add a new admin user to this account. Creates a device-level user entry (users.json) and adds them to this account's admins list (account.json). Generates a unique 4-digit PIN unless one is specified. Returns the userId and PIN to share with the new admin.", {
549
- name: z.string().describe("Display name for the new admin"),
550
- pin: z.string().optional().describe("Optional 4-digit PIN. If omitted, a unique PIN is generated."),
548
+ server.tool("admin-add", "Add a new admin user to this account. Creates a device-level user entry (users.json) and adds them to this account's admins list (account.json). PIN must be at least 4 digits. If no PIN is provided, a unique 4-digit PIN is generated. Returns the userId and PIN to share with the new admin.", {
549
+ name: z.string().describe("Display name for the new admin (stored on the AdminUser node in Neo4j)."),
550
+ pin: z.string().optional().describe("Optional PIN (minimum 4 digits). If omitted, a unique 4-digit PIN is generated."),
551
551
  }, async ({ name, pin: rawPin }) => {
552
552
  const TAG = "[admin]";
553
553
  if (!name.trim()) {
@@ -577,7 +577,7 @@ server.tool("admin-add", "Add a new admin user to this account. Creates a device
577
577
  }
578
578
  // Resolve the calling user's identity from the session environment
579
579
  const callerUserId = process.env.USER_ID;
580
- // PIN: use provided or generate
580
+ // PIN: use provided or generate. Constraint: minimum 4 digits, no upper bound.
581
581
  let plaintextPin;
582
582
  if (rawPin) {
583
583
  if (rawPin.length < 4) {
@@ -599,8 +599,9 @@ server.tool("admin-add", "Add a new admin user to this account. Creates a device
599
599
  }
600
600
  const pinHash = hashPin(plaintextPin);
601
601
  const userId = crypto.randomUUID();
602
- // 1. Write to users.json (device-level)
603
- users.push({ userId, name: name.trim(), pin: pinHash });
602
+ // 1. Write to users.json (device-level). Auth fields only — `name` lives
603
+ // on the AdminUser node in Neo4j (Task 829).
604
+ users.push({ userId, pin: pinHash });
604
605
  try {
605
606
  writeUsersJson(users);
606
607
  }
@@ -676,13 +677,22 @@ server.tool("admin-remove", "Remove an admin from this account. Removes them fro
676
677
  if (admins.length <= 1) {
677
678
  return { content: [{ type: "text", text: `${TAG} Cannot remove the last admin. At least one admin must remain on the account.` }], isError: true };
678
679
  }
679
- // Resolve the admin's name for the confirmation message
680
+ // Resolve the admin's name from Neo4j (canonical) for the confirmation
681
+ // message. Best-effort — fall back to userId if the graph is unreachable.
680
682
  let removedName = userId;
681
683
  try {
682
- const users = readUsersJson();
683
- const user = users.find(u => u.userId === userId);
684
- if (user)
685
- removedName = user.name;
684
+ const session = getSession();
685
+ try {
686
+ const result = await session.run(`MATCH (au:AdminUser {userId: $userId}) RETURN au.name AS name LIMIT 1`, { userId });
687
+ if (result.records.length > 0) {
688
+ const name = result.records[0].get("name");
689
+ if (name && name.trim())
690
+ removedName = name.trim();
691
+ }
692
+ }
693
+ finally {
694
+ await session.close();
695
+ }
686
696
  }
687
697
  catch { /* name lookup is best-effort */ }
688
698
  // 1. Remove from account.json
@@ -733,13 +743,27 @@ server.tool("admin-list", "List all admins for this account with their names and
733
743
  if (admins.length === 0) {
734
744
  return { content: [{ type: "text", text: `${TAG} No admins configured for this account.` }] };
735
745
  }
736
- // Enrich with names from users.json
737
- let users = [];
746
+ // Enrich with names from Neo4j AdminUser (canonical, Task 829). Best
747
+ // effort render "(unknown)" for any userId without a graph entry.
748
+ const userMap = new Map();
738
749
  try {
739
- users = readUsersJson();
750
+ const session = getSession();
751
+ try {
752
+ const result = await session.run(`UNWIND $userIds AS uid
753
+ MATCH (au:AdminUser {userId: uid})
754
+ RETURN au.userId AS userId, au.name AS name`, { userIds: admins.map(a => a.userId) });
755
+ for (const record of result.records) {
756
+ const uid = record.get("userId");
757
+ const name = record.get("name");
758
+ if (name && name.trim())
759
+ userMap.set(uid, name.trim());
760
+ }
761
+ }
762
+ finally {
763
+ await session.close();
764
+ }
740
765
  }
741
- catch { /* name lookup is best-effort — show userIds if users.json is unavailable */ }
742
- const userMap = new Map(users.map(u => [u.userId, u.name]));
766
+ catch { /* name lookup is best-effort — userIds shown when graph is unreachable */ }
743
767
  const lines = admins.map(a => {
744
768
  const name = userMap.get(a.userId) ?? "(unknown)";
745
769
  return `- **${name}** — role: ${a.role}, userId: ${a.userId}`;
@@ -748,6 +772,54 @@ server.tool("admin-list", "List all admins for this account with their names and
748
772
  content: [{ type: "text", text: `Admins for this account:\n\n${lines.join("\n")}` }],
749
773
  };
750
774
  });
775
+ server.tool("admin-update-pin", "Update an existing admin user's PIN. Defaults to the calling admin if no userId is given. PIN must be at least 4 digits and unique across all users on the device. PINs are device-level: updating another admin's PIN does not require shared account membership — any admin on the device can rotate any other admin's PIN, matching the existing trust model used by admin-remove.", {
776
+ userId: z.string().optional().describe("The userId of the admin whose PIN to update. Defaults to the caller (the admin invoking this tool)."),
777
+ newPin: z.string().describe("The new PIN. Minimum 4 digits, no upper bound."),
778
+ }, async ({ userId: targetUserId, newPin }) => {
779
+ const TAG = "[admin-update-pin]";
780
+ const callerUserId = process.env.USER_ID;
781
+ const userId = targetUserId ?? callerUserId;
782
+ const userIdLabel = userId ? userId.slice(0, 8) : "unknown";
783
+ if (!userId) {
784
+ console.error(`${TAG} userId=${userIdLabel} result=user-not-found reason=no-caller-context`);
785
+ return { content: [{ type: "text", text: `${TAG} No userId supplied and no caller context — cannot update.` }], isError: true };
786
+ }
787
+ if (newPin.length < 4) {
788
+ console.error(`${TAG} userId=${userIdLabel} result=too-short`);
789
+ return { content: [{ type: "text", text: `${TAG} PIN must be at least 4 digits.` }], isError: true };
790
+ }
791
+ let users;
792
+ try {
793
+ users = readUsersJson();
794
+ }
795
+ catch (err) {
796
+ console.error(`${TAG} userId=${userIdLabel} result=user-not-found reason=users-json-read-failed`);
797
+ return { content: [{ type: "text", text: `${TAG} ${err instanceof Error ? err.message : String(err)}` }], isError: true };
798
+ }
799
+ const targetIndex = users.findIndex(u => u.userId === userId);
800
+ if (targetIndex === -1) {
801
+ console.error(`${TAG} userId=${userIdLabel} result=user-not-found`);
802
+ return { content: [{ type: "text", text: `${TAG} User ${userId} not found in users.json.` }], isError: true };
803
+ }
804
+ const newHash = hashPin(newPin);
805
+ const collidesWithOther = users.some((u, i) => i !== targetIndex && u.pin === newHash);
806
+ if (collidesWithOther) {
807
+ console.error(`${TAG} userId=${userIdLabel} result=collision`);
808
+ return { content: [{ type: "text", text: `${TAG} That PIN is already in use by another user. Choose a different PIN.` }], isError: true };
809
+ }
810
+ users[targetIndex].pin = newHash;
811
+ try {
812
+ writeUsersJson(users);
813
+ }
814
+ catch (err) {
815
+ return { content: [{ type: "text", text: `${TAG} Failed to write users.json: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
816
+ }
817
+ console.error(`${TAG} userId=${userIdLabel} result=ok`);
818
+ const self = userId === callerUserId;
819
+ return {
820
+ content: [{ type: "text", text: `PIN updated${self ? "" : ` for userId ${userId}`}. The new PIN takes effect on the next login.` }],
821
+ };
822
+ });
751
823
  server.tool("agent-image", "Upload, update, or remove an agent's image. action=set: copy the file at filePath to the agent's assets directory and update config.json with image URL and shape. action=remove: delete the image file and clear the image fields from config.json. imageShape: 'circle' for avatars/icons, 'rounded' for logos.", {
752
824
  agentSlug: z.string().describe("Agent slug (e.g. 'coaching', 'sales')"),
753
825
  action: z.enum(["set", "remove"]),