@xmarts/genius-setup 1.6.0 → 1.7.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.
@@ -251,6 +251,28 @@ def _deadletter(path):
251
251
  pass
252
252
 
253
253
 
254
+ def _local_tz():
255
+ """(iana_name_or_'', utc_offset_minutes) for THIS machine — the authoritative
256
+ signal for dating the work to the consultant's LOCAL day (beats IP geo, which
257
+ VPNs/Cloudflare defeat). The hook runs ON the consultant's machine, so it just
258
+ reads the machine's own timezone."""
259
+ iana = ""
260
+ try:
261
+ p = os.path.realpath("/etc/localtime")
262
+ if "/zoneinfo/" in p:
263
+ iana = p.split("/zoneinfo/", 1)[1]
264
+ elif os.environ.get("TZ"):
265
+ iana = os.environ.get("TZ", "")
266
+ except Exception:
267
+ iana = ""
268
+ off = None
269
+ try:
270
+ off = int(_dt.datetime.now().astimezone().utcoffset().total_seconds() // 60)
271
+ except Exception:
272
+ off = None
273
+ return iana, off
274
+
275
+
254
276
  def main():
255
277
  cfg = _load_cfg()
256
278
  endpoint = cfg.get("capture_endpoint")
@@ -272,13 +294,17 @@ def main():
272
294
  transcript = _redact(_read_transcript(tpath))
273
295
  session_ref = event.get("session_id") or event.get("session_ref") or ""
274
296
 
275
- # Off-platform / sensitive work keeps CONTENT out of the Brain entirely — only
276
- # the TIME (session metadata) is sent. Inferred from an explicit pii_safe flag
277
- # or a topic set with no Genius client (e.g. Ateneai insurance/medical work).
278
- offplatform = bool(cfg.get("pii_safe")) or (
279
- bool(cfg.get("topic")) and not cfg.get("target_client"))
280
-
281
- if transcript.strip() and not offplatform:
297
+ # CONTENT-OUT is decoupled from "off-platform". ONLY an explicit pii_safe flag
298
+ # keeps transcript content out of the Brain (e.g. Ateneai medical/insurance).
299
+ # Off-platform / no-client work STILL contributes learnings we nourish from
300
+ # ALL of a consultant's projects, not just registered clients (owner directive
301
+ # 2026-06-05, Brain #1403). Naming a tema NO LONGER suppresses content.
302
+ content_out = bool(cfg.get("pii_safe"))
303
+ # The session METADATA still carries an off-platform LABEL (no Genius client);
304
+ # the server derives the same. It does NOT gate transcript content.
305
+ is_offplatform = content_out or not cfg.get("target_client")
306
+
307
+ if transcript.strip() and not content_out:
282
308
  payload = {
283
309
  "transcript": transcript,
284
310
  "session_ref": session_ref,
@@ -295,10 +321,11 @@ def main():
295
321
  if session_endpoint and session_ref:
296
322
  metrics = _session_metrics(tpath)
297
323
  if metrics and metrics.get("active_minutes", 0) > 0:
324
+ tz_iana, tz_off = _local_tz()
298
325
  meta = {
299
326
  "session_ref": session_ref,
300
327
  "source": "transcript_hook",
301
- "is_offplatform": offplatform,
328
+ "is_offplatform": is_offplatform,
302
329
  "_endpoint": session_endpoint,
303
330
  }
304
331
  meta.update(metrics)
@@ -309,6 +336,10 @@ def main():
309
336
  meta["workspace"] = ws
310
337
  if cfg.get("topic"):
311
338
  meta["topic"] = cfg["topic"]
339
+ if tz_iana:
340
+ meta["tz_iana"] = tz_iana
341
+ if tz_off is not None:
342
+ meta["utc_offset_minutes"] = tz_off
312
343
  _spool(meta, prefix="meta")
313
344
 
314
345
  # Always attempt to flush the spool (delivers this + any backlog)
@@ -24,6 +24,7 @@ CFG_PATH = os.path.join(HOME, ".genius", "config.json")
24
24
  TIMEOUT = 3.5
25
25
  SELF_UPDATE_TTL = 86400 # at most once/day
26
26
  SELF_UPDATE_STAMP = os.path.join(HOME, ".genius", ".last_selfupdate")
27
+ FORCE_STAMP = os.path.join(HOME, ".genius", ".last_forced_epoch")
27
28
 
28
29
  SESSION_PRIMER = (
29
30
  "[Xmarts Genius — Brain connected]\n"
@@ -73,17 +74,22 @@ def _inject(cfg, prompt):
73
74
  try:
74
75
  resp = urllib.request.urlopen(req, timeout=TIMEOUT)
75
76
  data = json.loads(resp.read())
76
- result = data.get("result", data)
77
- return (result or {}).get("context", "") or ""
77
+ result = data.get("result", data) or {}
78
+ return (result.get("context", "") or ""), result.get("hook_force_epoch")
78
79
  except Exception:
79
- return ""
80
+ return "", None
80
81
 
81
82
 
82
- def _maybe_self_update(cfg):
83
+ def _maybe_self_update(cfg, force_epoch=None):
83
84
  """Keep the consultant on the LATEST Genius package with ZERO action: at most
84
85
  once/day, silently re-run the installer in the background. New hooks, MCP
85
86
  tools, settings and the CLAUDE.md protocol then flow automatically — the
86
87
  consultant never re-onboards. Detached + fully guarded; never blocks a turn.
88
+
89
+ force_epoch (from the server's inject response) is the admin "push now" lever:
90
+ when the server epoch is newer than the one we last acted on, we upgrade
91
+ IMMEDIATELY, bypassing the daily TTL — and record the epoch so we force at most
92
+ once per bump (never a loop).
87
93
  """
88
94
  import time
89
95
  import subprocess
@@ -91,7 +97,18 @@ def _maybe_self_update(cfg):
91
97
  token = cfg.get("token")
92
98
  if not token:
93
99
  return
94
- if os.path.exists(SELF_UPDATE_STAMP) and (
100
+ forced = False
101
+ if force_epoch:
102
+ try:
103
+ fe = int(force_epoch)
104
+ last = 0
105
+ if os.path.exists(FORCE_STAMP):
106
+ with open(FORCE_STAMP) as fh:
107
+ last = int((fh.read() or "0").strip() or 0)
108
+ forced = fe > last
109
+ except Exception:
110
+ forced = False
111
+ if not forced and os.path.exists(SELF_UPDATE_STAMP) and (
95
112
  time.time() - os.path.getmtime(SELF_UPDATE_STAMP)) < SELF_UPDATE_TTL:
96
113
  return
97
114
  # Stamp FIRST so a failing/slow npx never causes a retry storm.
@@ -99,6 +116,9 @@ def _maybe_self_update(cfg):
99
116
  os.makedirs(os.path.dirname(SELF_UPDATE_STAMP), exist_ok=True)
100
117
  with open(SELF_UPDATE_STAMP, "w") as fh:
101
118
  fh.write(str(int(time.time())))
119
+ if force_epoch:
120
+ with open(FORCE_STAMP, "w") as fh:
121
+ fh.write(str(int(force_epoch)))
102
122
  except Exception:
103
123
  return
104
124
  env = dict(os.environ)
@@ -125,14 +145,18 @@ def main():
125
145
  cfg = _cfg()
126
146
  if not cfg.get("token"):
127
147
  return # not configured -> silent no-op
128
- _maybe_self_update(cfg) # background, daily, zero-action upgrades
129
148
  event = _event()
130
149
  name = event.get("hook_event_name") or (
131
150
  "UserPromptSubmit" if event.get("prompt") else "SessionStart")
132
151
  if name == "SessionStart":
152
+ _maybe_self_update(cfg) # TTL-only on priming
133
153
  _emit("SessionStart", SESSION_PRIMER)
134
154
  else:
135
- _emit("UserPromptSubmit", _inject(cfg, event.get("prompt") or ""))
155
+ context, force_epoch = _inject(cfg, event.get("prompt") or "")
156
+ # The inject call already round-tripped the server, so honour any
157
+ # admin-pushed force-update epoch here (bypasses the daily TTL).
158
+ _maybe_self_update(cfg, force_epoch)
159
+ _emit("UserPromptSubmit", context)
136
160
 
137
161
 
138
162
  if __name__ == "__main__":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmarts/genius-setup",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "description": "One-command, self-updating onboarding for Xmarts Genius (Brain MCP + per-turn injection + automatic session/time capture + CLAUDE.md protocol). Secret-free onboarding via single-use setup-token exchange (no Bearer in the GET-able bootstrap). 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"