@xmarts/genius-setup 1.2.0

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/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Xmarts Genius — Frictionless Setup
2
+
3
+ Three ways to get a consultant onto the Genius Brain with **automatic learning
4
+ capture**. Pick one; they all converge on the same `~/.genius/config.json` +
5
+ Claude Code hooks, and all are idempotent.
6
+
7
+ The capture guarantee is **defense-in-depth** and does NOT depend on any single
8
+ piece:
9
+
10
+ - **L0/L2 — server-side (always on):** every MCP tool call is recorded, and the
11
+ Brain miner (`gemini-2.5-flash` LLM-as-judge, every 2h) extracts the learnings
12
+ the agent forgot — even if the consultant never installs anything beyond the
13
+ MCP connection.
14
+ - **L3 — deterministic hook (richer):** the `SessionEnd` / `PreCompact` / `Stop`
15
+ hook POSTs the transcript to `/xma/genius/v1/capture/transcript`. Hooks
16
+ guarantee *command execution* where directives don't.
17
+ - **L4 — durable spool:** the hook writes to `~/.genius/outbox/` first, then
18
+ delivers; a central outage never loses a capture. The server side mirrors this
19
+ with `xma.capture.inbox` (persist-then-process + bounded retry).
20
+
21
+ Everything collapses to **one** learning row via the content-hash idempotency
22
+ sink, so all layers can fire at-least-once with exactly-once effect.
23
+
24
+ ## Option A — npx (any OS)
25
+
26
+ ```bash
27
+ npx -y @xmarts/genius-setup --token <YOUR_BEARER> --base-url https://beesmart.digital
28
+ # optional: --client "Acme Corp" (default scope for this machine)
29
+ ```
30
+
31
+ Restart Claude Code. Done.
32
+
33
+ ## Option B — Claude Code plugin
34
+
35
+ Install the `xmarts-genius` plugin from the Genius marketplace. It bundles the
36
+ hooks + slash commands (`/genius-status`, `/genius-capture`) and auto-updates.
37
+ You still set the MCP Bearer once (the plugin reads `~/.genius/config.json` for
38
+ the capture endpoint + token — run Option A once, or `/genius-status` guides you).
39
+
40
+ ## Option C — VS Code panel
41
+
42
+ Install the **Xmarts Genius** extension. Click **Genius: Set up**, paste your
43
+ Bearer token once (stored in SecretStorage), and it runs the same bootstrap. The
44
+ panel shows Brain connection, capture endpoint, default client, and outbox
45
+ backlog. It is **UI only** — never the capture mechanism.
46
+
47
+ ## Where the token comes from
48
+
49
+ Each consultant's Bearer is their MCP API key, generated from their
50
+ `xma.consultant` ficha in Genius ("Generate Claude Code Setup"). No new secret.
51
+
52
+ ## Files this writes
53
+
54
+ | Path | Purpose |
55
+ |---|---|
56
+ | `~/.genius/config.json` | endpoint + token (+ optional default client) |
57
+ | `~/.genius/hooks/genius_capture.py` | the deterministic capture hook |
58
+ | `~/.genius/outbox/` | durable local spool (auto-flushed) |
59
+ | `~/.claude.json` → `mcpServers.beesmart_genius` | the Brain MCP |
60
+ | `~/.claude/settings.json` → `hooks` | SessionEnd / PreCompact / Stop |
61
+
62
+ All edits are merged (never clobbered) with a timestamped backup.
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @xmarts/genius-setup — one-command frictionless onboarding for Xmarts Genius.
4
+ *
5
+ * npx @xmarts/genius-setup --token <bearer> [--base-url https://beesmart.digital] [--client "Acme"]
6
+ *
7
+ * Idempotent + cross-platform (incl. Windows). It:
8
+ * 1. writes ~/.genius/config.json (endpoints + token) and installs the per-turn
9
+ * inject hook (hooks/genius_inject.py) + the session-capture hook
10
+ * (hooks/genius_capture.py);
11
+ * 2. registers the beesmart_genius MCP server in ~/.claude.json;
12
+ * 3. wires per-turn injection (UserPromptSubmit) + SessionStart priming +
13
+ * SessionEnd/PreCompact/Stop capture into ~/.claude/settings.json — merged,
14
+ * never duplicated, with a timestamped backup first.
15
+ * After this, the consultant has Brain + capture + feedback flowing with zero
16
+ * further prompts. The token is the consultant's MCP Bearer (no new secret).
17
+ */
18
+ 'use strict';
19
+ const fs = require('fs');
20
+ const os = require('os');
21
+ const path = require('path');
22
+
23
+ function arg(name, def) {
24
+ const i = process.argv.indexOf('--' + name);
25
+ return i > -1 && process.argv[i + 1] ? process.argv[i + 1] : def;
26
+ }
27
+ function log(msg) { process.stdout.write(msg + '\n'); }
28
+
29
+ // (#pkg-4) Prefer GENIUS_TOKEN env over --token argv: argv leaks into the
30
+ // process list and shell history. Warn if a token is passed on the CLI.
31
+ let TOKEN = process.env.GENIUS_TOKEN || '';
32
+ if (!TOKEN) {
33
+ TOKEN = arg('token', '');
34
+ if (TOKEN) log('WARN: passing --token on the CLI leaks it to the process list / shell history. Prefer GENIUS_TOKEN=... npx @xmarts/genius-setup');
35
+ }
36
+ const BASE = (arg('base-url', 'https://beesmart.digital')).replace(/\/+$/, '');
37
+ const CLIENT = arg('client', '');
38
+ if (!TOKEN) {
39
+ log('ERROR: a consultant Bearer token is required. Set GENIUS_TOKEN=<token> (preferred) or pass --token <token>.');
40
+ process.exit(1);
41
+ }
42
+
43
+ const HOME = os.homedir();
44
+ const GENIUS_DIR = path.join(HOME, '.genius');
45
+ const HOOKS_DIR = path.join(GENIUS_DIR, 'hooks');
46
+ const CLAUDE_DIR = path.join(HOME, '.claude');
47
+
48
+ function ensureDir(d) { fs.mkdirSync(d, { recursive: true }); }
49
+ function readJson(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return {}; } }
50
+ function backup(p) {
51
+ if (fs.existsSync(p)) {
52
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
53
+ fs.copyFileSync(p, p + '.genius-bak-' + stamp);
54
+ }
55
+ }
56
+ function writeJson(p, obj, secret) {
57
+ // (#pkg-3) secret files (token-bearing) are written 0600, never world-readable.
58
+ ensureDir(path.dirname(p));
59
+ fs.writeFileSync(p, JSON.stringify(obj, null, 2), secret ? { mode: 0o600 } : undefined);
60
+ if (secret) { try { fs.chmodSync(p, 0o600); } catch (_) {} }
61
+ }
62
+
63
+ // 1) ~/.genius/config.json + hook
64
+ ensureDir(HOOKS_DIR);
65
+ const cfg = {
66
+ base_url: BASE,
67
+ token: TOKEN,
68
+ capture_endpoint: BASE + '/xma/genius/v1/capture/transcript',
69
+ inject_endpoint: BASE + '/xma/genius/v1/inject',
70
+ };
71
+ if (CLIENT) cfg.target_client = CLIENT;
72
+ writeJson(path.join(GENIUS_DIR, 'config.json'), cfg, true); // secret: 0600
73
+
74
+ // Copy the hooks shipped alongside this script (capture + per-turn inject).
75
+ function copyHook(fname) {
76
+ const src = path.join(__dirname, '..', 'hooks', fname);
77
+ const dst = path.join(HOOKS_DIR, fname);
78
+ try {
79
+ fs.copyFileSync(src, dst);
80
+ try { fs.chmodSync(dst, 0o755); } catch (_) {}
81
+ } catch (e) {
82
+ log('WARN: could not copy hook from ' + src + ' (' + e.message + ')');
83
+ }
84
+ return dst;
85
+ }
86
+ const hookDst = copyHook('genius_capture.py');
87
+ const injectDst = copyHook('genius_inject.py');
88
+
89
+ // 2) ~/.claude.json mcpServers (merge, no clobber)
90
+ const claudeJsonPath = path.join(HOME, '.claude.json');
91
+ const claudeJson = readJson(claudeJsonPath);
92
+ backup(claudeJsonPath);
93
+ claudeJson.mcpServers = claudeJson.mcpServers || {};
94
+ claudeJson.mcpServers['beesmart_genius'] = {
95
+ type: 'http',
96
+ url: BASE + '/mcp/consultant',
97
+ headers: { Authorization: 'Bearer ' + TOKEN },
98
+ };
99
+ writeJson(claudeJsonPath, claudeJson, true); // contains the Bearer -> 0600
100
+
101
+ // 3) ~/.claude/settings.json hooks (merge by command, no dup)
102
+ const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
103
+ const settings = readJson(settingsPath);
104
+ backup(settingsPath);
105
+ settings.hooks = settings.hooks || {};
106
+ function addHook(eventName, scriptPath) {
107
+ settings.hooks[eventName] = settings.hooks[eventName] || [];
108
+ const marker = path.basename(scriptPath);
109
+ const already = JSON.stringify(settings.hooks[eventName]).includes(marker);
110
+ if (!already) {
111
+ const cmd = `python3 ${JSON.stringify(scriptPath)} || true`;
112
+ settings.hooks[eventName].push({ hooks: [{ type: 'command', command: cmd }] });
113
+ }
114
+ }
115
+ // Capture (transcript -> Brain) on session boundaries.
116
+ ['SessionEnd', 'PreCompact', 'Stop'].forEach((e) => addHook(e, hookDst));
117
+ // Per-turn knowledge injection + session priming (consultant_access guarantee).
118
+ ['UserPromptSubmit', 'SessionStart'].forEach((e) => addHook(e, injectDst));
119
+ writeJson(settingsPath, settings);
120
+
121
+ log('');
122
+ log(' Xmarts Genius is set up.');
123
+ log(' - MCP server : beesmart_genius -> ' + BASE + '/mcp/consultant');
124
+ log(' - Capture hook : ' + hookDst + ' (SessionEnd / PreCompact / Stop)');
125
+ log(' - Inject hook : ' + injectDst + ' (UserPromptSubmit / SessionStart)');
126
+ log(' - Config : ' + path.join(GENIUS_DIR, 'config.json'));
127
+ if (CLIENT) log(' - Default client : ' + CLIENT);
128
+ log('');
129
+ log(' Restart Claude Code. Per-turn Brain injection + automatic capture are now active — no further prompts.');
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env python3
2
+ """Genius deterministic session-capture hook (P3).
3
+
4
+ Wired to Claude Code SessionEnd (+ PreCompact + Stop, guarded). The hook is the
5
+ COMMAND-EXECUTION guarantee: directives the model may ignore, a hook always runs.
6
+ It reads the session transcript and POSTs it to the Genius capture endpoint, so
7
+ the server-side miner extracts the learnings the agent forgot to share — with
8
+ zero consultant action.
9
+
10
+ Hard rules:
11
+ - ALWAYS exit 0 (never block or fail a session, even on network/parse errors).
12
+ - Persist-then-network: every payload is first written to a local outbox spool;
13
+ on a successful POST it is removed, otherwise it stays and is retried on the
14
+ next hook firing. A central outage therefore never loses a capture.
15
+ - Reads its config (endpoint + bearer token) from ~/.genius/config.json, written
16
+ once by the installer. No secrets are embedded here.
17
+
18
+ Input: Claude Code passes a JSON event on stdin including `transcript_path` and
19
+ `session_id`. We tolerate missing fields.
20
+ """
21
+ import json
22
+ import os
23
+ import re
24
+ import sys
25
+ import time
26
+ import urllib.request
27
+
28
+ HOME = os.path.expanduser("~")
29
+ CFG_PATH = os.path.join(HOME, ".genius", "config.json")
30
+ OUTBOX_DIR = os.path.join(HOME, ".genius", "outbox")
31
+ DEADLETTER_DIR = os.path.join(OUTBOX_DIR, "failed")
32
+ MAX_OUTBOX_FILES = 200
33
+ MAX_TRANSCRIPT_BYTES = 200_000
34
+
35
+
36
+ def _load_cfg():
37
+ try:
38
+ with open(CFG_PATH) as fh:
39
+ return json.load(fh)
40
+ except Exception:
41
+ return {}
42
+
43
+
44
+ def _read_stdin_event():
45
+ try:
46
+ raw = sys.stdin.read()
47
+ return json.loads(raw) if raw.strip() else {}
48
+ except Exception:
49
+ return {}
50
+
51
+
52
+ def _read_transcript(path):
53
+ if not path or not os.path.isfile(path):
54
+ return ""
55
+ try:
56
+ with open(path, "rb") as fh:
57
+ try:
58
+ fh.seek(-MAX_TRANSCRIPT_BYTES, os.SEEK_END)
59
+ except OSError:
60
+ fh.seek(0)
61
+ return fh.read().decode("utf-8", "replace")
62
+ except Exception:
63
+ return ""
64
+
65
+
66
+ _CRED_PATTERNS = [
67
+ re.compile(r'-----BEGIN[A-Z ]*PRIVATE KEY-----.*?-----END[A-Z ]*PRIVATE KEY-----', re.DOTALL),
68
+ re.compile(r'(?i)\bBearer\s+[A-Za-z0-9._\-]{20,}'),
69
+ re.compile(r'(?i)postgres(?:ql)?://[^:\s/]+:[^@\s]+@'),
70
+ re.compile(r'(?i)(?:^|[\s_=:\'"])(password|passwd|pwd|secret|api[_-]?key|'
71
+ r'access[_-]?token|auth[_-]?token|fernet|secret[_-]?key|'
72
+ r'db[_-]?password|admin[_-]?passwd|client[_-]?secret)'
73
+ r'\s*[:=]\s*[\'"]?[^\s\'"]{6,}'),
74
+ re.compile(r'\b[A-Za-z0-9_\-]{43}=(?![A-Za-z0-9_\-=])'),
75
+ re.compile(r'\bAKIA[0-9A-Z]{16}\b'),
76
+ re.compile(r'\bAIza[0-9A-Za-z_\-]{35}\b'),
77
+ re.compile(r'\beyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}'),
78
+ ]
79
+
80
+
81
+ def _redact(text):
82
+ """Client-side defense-in-depth: mask obvious secrets BEFORE they leave the
83
+ machine. The server re-redacts authoritatively at inbox enqueue (APS-1); this
84
+ just means a credential never even transits the network."""
85
+ if not text:
86
+ return text
87
+ for pat in _CRED_PATTERNS:
88
+ text = pat.sub("[REDACTED]", text)
89
+ return text
90
+
91
+
92
+ def _spool_files():
93
+ if not os.path.isdir(OUTBOX_DIR):
94
+ return []
95
+ return sorted(f for f in os.listdir(OUTBOX_DIR) if f.endswith(".json"))
96
+
97
+
98
+ def _spool(payload):
99
+ """Persist a payload. (#pkg-2) One file per session_ref — repeated Stop
100
+ firings of the SAME session OVERWRITE one file instead of creating N — and a
101
+ true on-disk RING BUFFER cap, dropping the oldest when at MAX_OUTBOX_FILES,
102
+ so the directory can never grow without bound."""
103
+ os.makedirs(OUTBOX_DIR, exist_ok=True)
104
+ sref = (payload.get("session_ref") or "s")[:40].replace("/", "_").replace(".", "_")
105
+ # session dedup: a stable name per session -> overwrite, not append
106
+ name = "sess_%s.json" % sref if sref != "s" else "%d_anon.json" % int(time.time() * 1000)
107
+ path = os.path.join(OUTBOX_DIR, name)
108
+ if not os.path.exists(path):
109
+ existing = _spool_files()
110
+ if len(existing) >= MAX_OUTBOX_FILES:
111
+ for old in existing[:len(existing) - MAX_OUTBOX_FILES + 1]:
112
+ try:
113
+ os.remove(os.path.join(OUTBOX_DIR, old))
114
+ except Exception:
115
+ pass
116
+ try:
117
+ with open(path, "w") as fh:
118
+ json.dump(payload, fh)
119
+ except Exception:
120
+ return None
121
+ return path
122
+
123
+
124
+ def _post(endpoint, token, payload, timeout=15):
125
+ body = json.dumps({"jsonrpc": "2.0", "method": "call",
126
+ "params": payload, "id": 1}).encode()
127
+ req = urllib.request.Request(endpoint, data=body)
128
+ req.add_header("Content-Type", "application/json")
129
+ req.add_header("Authorization", "Bearer %s" % token)
130
+ # Cloudflare in front of beesmart.digital 403s the default Python-urllib UA.
131
+ req.add_header("User-Agent", "XmartsGenius-Capture/1.0")
132
+ resp = urllib.request.urlopen(req, timeout=timeout)
133
+ data = json.loads(resp.read())
134
+ result = data.get("result", data)
135
+ if isinstance(result, dict) and result.get("status") == "error":
136
+ # server-side rejection of THIS payload -> permanent
137
+ raise PermanentError(result.get("message", "capture error"))
138
+ return result
139
+
140
+
141
+ class PermanentError(Exception):
142
+ pass
143
+
144
+
145
+ def _flush_outbox(endpoint, token):
146
+ """Deliver spooled payloads oldest-first. (#pkg-1) A permanent rejection
147
+ (4xx other than 408/429, or a JSON-RPC error envelope) is dead-lettered to
148
+ outbox/failed/ and we CONTINUE, so one poison payload can never wedge the
149
+ whole queue; a transient error (offline / 5xx / timeout) stops the pass and
150
+ is retried next firing."""
151
+ import urllib.error
152
+ for name in _spool_files()[:MAX_OUTBOX_FILES]:
153
+ path = os.path.join(OUTBOX_DIR, name)
154
+ try:
155
+ with open(path) as fh:
156
+ payload = json.load(fh)
157
+ _post(endpoint, token, payload)
158
+ os.remove(path) # delivered
159
+ except PermanentError:
160
+ _deadletter(path) # bad payload -> set aside, keep going
161
+ continue
162
+ except urllib.error.HTTPError as he:
163
+ if he.code in (408, 429) or he.code >= 500:
164
+ break # transient server state -> retry later
165
+ _deadletter(path) # 4xx (e.g. revoked token, 413) -> permanent
166
+ continue
167
+ except Exception:
168
+ break # offline / timeout -> retry next firing
169
+
170
+
171
+ def _deadletter(path):
172
+ try:
173
+ os.makedirs(DEADLETTER_DIR, exist_ok=True)
174
+ os.replace(path, os.path.join(DEADLETTER_DIR, os.path.basename(path)))
175
+ except Exception:
176
+ try:
177
+ os.remove(path)
178
+ except Exception:
179
+ pass
180
+
181
+
182
+ def main():
183
+ cfg = _load_cfg()
184
+ endpoint = cfg.get("capture_endpoint")
185
+ token = cfg.get("token")
186
+ if not endpoint or not token:
187
+ return # not configured -> silent no-op
188
+
189
+ event = _read_stdin_event()
190
+ transcript = _redact(_read_transcript(event.get("transcript_path")))
191
+ session_ref = event.get("session_id") or event.get("session_ref") or ""
192
+
193
+ if transcript.strip():
194
+ payload = {
195
+ "transcript": transcript,
196
+ "session_ref": session_ref,
197
+ "source": "session_end_hook",
198
+ }
199
+ if cfg.get("target_client"):
200
+ payload["target_client"] = cfg["target_client"]
201
+ # persist first (durability), then try to deliver
202
+ _spool(payload)
203
+
204
+ # Always attempt to flush the spool (delivers this + any backlog)
205
+ try:
206
+ _flush_outbox(endpoint, token)
207
+ except Exception:
208
+ pass
209
+
210
+
211
+ if __name__ == "__main__":
212
+ try:
213
+ main()
214
+ except Exception:
215
+ pass
216
+ sys.exit(0) # NEVER block a session
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env python3
2
+ """Genius per-turn knowledge injection hook (P0 — consultant_access).
3
+
4
+ Wired to Claude Code UserPromptSubmit (every turn) and SessionStart (priming).
5
+ On UserPromptSubmit it asks the consultant-scoped Genius Brain for the top-k
6
+ learnings relevant to the current prompt and emits them as
7
+ `hookSpecificOutput.additionalContext`, so the consultant ALWAYS has the right
8
+ prior knowledge WITHOUT asking. Editor-agnostic (terminal / VSCode / headless).
9
+
10
+ Hard rules:
11
+ - ALWAYS exit 0 (never block a turn).
12
+ - Time-box the network call (<=2.5s) so injection adds no perceptible latency.
13
+ - Relevance-floored: nothing relevant -> inject nothing (zero noise).
14
+ - Reads endpoint + bearer from ~/.genius/config.json (written by the installer);
15
+ no secret is embedded here.
16
+ """
17
+ import json
18
+ import os
19
+ import sys
20
+ import urllib.request
21
+
22
+ HOME = os.path.expanduser("~")
23
+ CFG_PATH = os.path.join(HOME, ".genius", "config.json")
24
+ TIMEOUT = 3.5
25
+
26
+ SESSION_PRIMER = (
27
+ "[Xmarts Genius — Brain connected]\n"
28
+ "The Genius Brain (beesmart_genius MCP) is active for this session. Relevant "
29
+ "prior learnings are injected automatically on each prompt — you do not need "
30
+ "to ask for them. New learnings you produce are captured automatically at "
31
+ "session end. You may still call query_knowledge for a deeper search and "
32
+ "get_my_profile for your profile."
33
+ )
34
+
35
+
36
+ def _cfg():
37
+ try:
38
+ with open(CFG_PATH) as fh:
39
+ return json.load(fh)
40
+ except Exception:
41
+ return {}
42
+
43
+
44
+ def _event():
45
+ try:
46
+ raw = sys.stdin.read()
47
+ return json.loads(raw) if raw.strip() else {}
48
+ except Exception:
49
+ return {}
50
+
51
+
52
+ def _emit(event_name, context):
53
+ if context:
54
+ print(json.dumps({"hookSpecificOutput": {
55
+ "hookEventName": event_name, "additionalContext": context}}))
56
+ sys.exit(0)
57
+
58
+
59
+ def _inject(cfg, prompt):
60
+ base = (cfg.get("base_url") or "https://beesmart.digital").rstrip("/")
61
+ token = cfg.get("token")
62
+ if not token or not prompt.strip():
63
+ return ""
64
+ body = json.dumps({"jsonrpc": "2.0", "method": "call", "id": 1,
65
+ "params": {"prompt": prompt[:4000], "limit": 4}}).encode()
66
+ req = urllib.request.Request(base + "/xma/genius/v1/inject", data=body)
67
+ req.add_header("Content-Type", "application/json")
68
+ req.add_header("Authorization", "Bearer %s" % token)
69
+ # Cloudflare in front of beesmart.digital 403s the default Python-urllib UA.
70
+ req.add_header("User-Agent", "XmartsGenius-Inject/1.0")
71
+ try:
72
+ resp = urllib.request.urlopen(req, timeout=TIMEOUT)
73
+ data = json.loads(resp.read())
74
+ result = data.get("result", data)
75
+ return (result or {}).get("context", "") or ""
76
+ except Exception:
77
+ return ""
78
+
79
+
80
+ def main():
81
+ cfg = _cfg()
82
+ if not cfg.get("token"):
83
+ return # not configured -> silent no-op
84
+ event = _event()
85
+ name = event.get("hook_event_name") or (
86
+ "UserPromptSubmit" if event.get("prompt") else "SessionStart")
87
+ if name == "SessionStart":
88
+ _emit("SessionStart", SESSION_PRIMER)
89
+ else:
90
+ _emit("UserPromptSubmit", _inject(cfg, event.get("prompt") or ""))
91
+
92
+
93
+ if __name__ == "__main__":
94
+ try:
95
+ main()
96
+ except Exception:
97
+ pass
98
+ sys.exit(0) # NEVER block a turn
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@xmarts/genius-setup",
3
+ "version": "1.2.0",
4
+ "description": "One-command frictionless onboarding for Xmarts Genius (Brain MCP + per-turn injection + automatic session capture).",
5
+ "bin": {
6
+ "genius-setup": "bin/genius-setup.js"
7
+ },
8
+ "files": [
9
+ "bin/genius-setup.js",
10
+ "hooks/genius_inject.py",
11
+ "hooks/genius_capture.py",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=16"
16
+ },
17
+ "license": "UNLICENSED",
18
+ "author": "Xmarts",
19
+ "private": false
20
+ }