@xmarts/genius-setup 1.12.0 → 1.13.1

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": "@xmarts/genius-setup",
3
- "version": "1.12.0",
3
+ "version": "1.13.1",
4
4
  "description": "One-command, self-updating onboarding for Xmarts Genius (Brain MCP + per-turn injection + automatic session/time capture + presence heartbeat for honest time-tracking 'amarre' + plan/committee→Brain enforcement + CLAUDE.md protocol + MCP fan-out: provisions the consultant's granted MCP servers into Claude Code & Claude Desktop). Secret-free onboarding via single-use setup-token exchange. Installs once; self-updates daily so new tools/skills/features arrive with zero action.",
5
5
  "bin": {
6
6
  "genius-setup": "bin/genius-setup.js"
@@ -10,7 +10,11 @@ description: >
10
10
  deploy a webapp to a client, can't see/manage a client's modules, or whenever the work
11
11
  touches xma_genius, xma_connect, beesmart_genius, the Genius Brain, GEA Ecuador, or
12
12
  deploying/controlling anything on a client instance from the central — even if the user
13
- doesn't name the tool. If you ever think "I don't have access to that client", read this
13
+ doesn't name the tool. ALSO use it the moment a consultant needs to LOG INTO a client's
14
+ Odoo UI / needs a login / user / password / access URL for a client instance (e.g.
15
+ "necesito acceso a la UI de X", "no puedo entrar a la instancia de X", "olvidé mi
16
+ contraseña de X", "how do I log into X") — provision it and hand back the credentials
17
+ on the spot. If you ever think "I don't have access to that client", read this
14
18
  skill first: you almost certainly do.
15
19
  ---
16
20
 
@@ -78,6 +82,19 @@ argument shape or hit an edge case.
78
82
  33 tools are routable to clients in total. If a tool you need isn't listed, check
79
83
  [references/tool-catalog.md](references/tool-catalog.md) before concluding it's impossible.
80
84
 
85
+ ## Consultant needs to log into a client's UI → hand them credentials NOW
86
+
87
+ If a consultant says any of *"necesito/quiero acceso a la UI de \<cliente\>"*, *"no puedo entrar
88
+ a la instancia de \<cliente\>"*, *"how do I log into \<client\>"*, or *"olvidé mi contraseña de
89
+ \<cliente\>"* — **provision it and give back URL + user + password on the spot. No waiting, no
90
+ admin hop.** Every consultant has `odoo.write odoo.execute` on their **assigned** clients, so this
91
+ runs in their own session via `target_client`. It is one idempotent `execute_orm` (creates on first
92
+ ask, resets the temp password on re-ask). The **exact one-shot snippet + how to hand back the
93
+ credential without leaking it to the Brain** is in
94
+ [references/provision-consultant-ui-access.md](references/provision-consultant-ui-access.md) — read
95
+ it and act. The only thing that can block you is the client not being in their assignments (then an
96
+ admin assigns it); everything else is self-serve.
97
+
81
98
  ## Always orient before you act
82
99
 
83
100
  The Brain (`query_knowledge`) is the server-side source of truth and it remembers things this
@@ -12,6 +12,74 @@ login. It has only `base.group_user` (headless), sharing a password kills the au
12
12
  and a plaintext password in a Claude session can be indexed by the Brain. Give each
13
13
  consultant their **own** audited user.
14
14
 
15
+ ## ⚡ Frictionless trigger — do this the moment a consultant asks
16
+
17
+ **Trigger phrases** (any consultant, any session): *"necesito/quiero acceso a la UI de \<cliente\>"*,
18
+ *"how do I log into \<client\>'s Odoo"*, *"no puedo entrar a la instancia de \<cliente\>"*,
19
+ *"olvidé mi contraseña de \<cliente\>"*. → **Provision immediately and hand back URL + user +
20
+ password. Do NOT make them wait or ask an admin.** Every consultant has `odoo.write odoo.execute`
21
+ on their **assigned** clients, so this runs in their own session through `target_client`.
22
+
23
+ **One shot (idempotent — creates on first ask, resets the temp password on re-ask):** run via
24
+ `execute_orm` with `target_client=<client name or UUID>`. Resolve the requesting person's real
25
+ name + xmarts.com email first (from `get_my_profile`, or the consultant record on the master).
26
+
27
+ ```python
28
+ U = env['res.users'].sudo()
29
+ NAME, LOGIN = '<Full Name>', '<person@xmarts.com>' # the consultant who needs access
30
+ env.cr.execute("SELECT replace(gen_random_uuid()::text, '-', '')")
31
+ raw = env.cr.fetchone()[0]
32
+ pw = 'Xma-%s-%s-Ux!' % (raw[:6], raw[6:12]) # temp, no spaces, meets complexity
33
+ user = U.search([('login', '=', LOGIN)], limit=1)
34
+ if user:
35
+ user.write({'password': pw, 'active': True}) # re-ask / forgot password -> fresh temp pw
36
+ status = 'reset'
37
+ else:
38
+ # Clone the RICHEST non-admin consultant peer (best working profile, never over-grant
39
+ # Settings-admin). Filter service accounts / admin IN PYTHON — the gateway's anti-SQL
40
+ # guard mangles `like`/`%` domains (a `not like 'xma_%'` term silently returns nothing).
41
+ # Plain loop — execute_orm forbids lambda closures. Fall back to the Sales baseline if
42
+ # the client has no usable peer yet.
43
+ peers = U.search([('share', '=', False), ('active', '=', True), ('id', '!=', 1)])
44
+ sysg = env.ref('base.group_system', raise_if_not_found=False)
45
+ peer = U.browse(); best = -1
46
+ for p in peers:
47
+ lg = p.login or ''
48
+ if lg == 'admin' or lg.startswith('xma_'):
49
+ continue # skip the admin + service accounts
50
+ if sysg and sysg.id in p.group_ids.ids:
51
+ continue # never clone an admin's groups
52
+ n = len(p.group_ids)
53
+ if n > best:
54
+ best = n; peer = p
55
+ gids = peer.group_ids.ids if peer else []
56
+ if not gids:
57
+ base = env.ref('base.group_user', raise_if_not_found=False)
58
+ sales = env.ref('sales_team.group_sale_salesman', raise_if_not_found=False)
59
+ gids = [g.id for g in (base, sales) if g]
60
+ comp = (peer.company_id.id if peer else False) or env.company.id
61
+ user = U.with_context(no_reset_password=True, mail_create_nosubscribe=True).create({
62
+ 'name': NAME, 'login': LOGIN, 'email': LOGIN, 'password': pw,
63
+ 'company_id': comp, 'company_ids': [Command.set([comp])],
64
+ 'group_ids': [Command.set(gids)]})
65
+ status = 'created'
66
+ result = {'status': status, 'uid': user.id, 'login': user.login, 'password': pw,
67
+ 'url': env['ir.config_parameter'].sudo().get_param('web.base.url') + '/web/login'}
68
+ ```
69
+
70
+ Then **hand it back immediately**, formatting the secret so the capture hook redacts it from the
71
+ Brain but the consultant still sees it live:
72
+
73
+ ```
74
+ url: <result.url>
75
+ login: <result.login>
76
+ password: <result.password> ← temp, change it on first login (Preferences → Account Security)
77
+ ```
78
+
79
+ Only assigned clients are reachable (`target_client` refuses unassigned). If the client is NOT in
80
+ their assignments, tell them to ask an admin to assign it — that is the only gate. Nothing else
81
+ about this should require a human hop.
82
+
15
83
  ## The pattern (scalable, sustainable, auditable)
16
84
 
17
85
  **Per-consultant internal user in the client instance, provisioned on demand through the