@pushrec/skills 0.2.0 → 0.3.4

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.
@@ -0,0 +1,412 @@
1
+ # pushREC Skills Prerequisites
2
+ # version: 1.1.0
3
+ #
4
+ # Skills NOT listed here require NO API keys or credentials (Level 0).
5
+ # 80+ skills work immediately after install with zero configuration.
6
+ #
7
+ # Level definitions:
8
+ # L1: Google OAuth2 - one-time browser consent flow (~15 min total)
9
+ # L2: API keys - individual service signups (~2-5 min each)
10
+ # L3: Service accounts - KYC verification or usage-based billing (~10 min each)
11
+ # L4: Applications - requires desktop app (Obsidian) installed
12
+
13
+ # ============================================================================
14
+ # LEVEL 1: Google OAuth2
15
+ # One-time setup: Create a Google Cloud project, enable APIs, create OAuth
16
+ # credentials. All Google skills share the same GCP project.
17
+ # ============================================================================
18
+
19
+ gmail:
20
+ level: 1
21
+ requires:
22
+ - env: GOOGLE_OAUTH_CREDENTIALS
23
+ type: oauth2
24
+ service: Google Cloud Console
25
+ setup_url: https://console.cloud.google.com/apis/credentials
26
+ setup_time: "15 minutes (one-time for all Google services)"
27
+ credentials_path: "~/.claude/.credentials/gmail-oauth.keys.json"
28
+ notes: "OAuth2 browser consent flow. Tokens stored at ~/.gmail-mcp/credentials.json"
29
+
30
+ google-sheets:
31
+ level: 1
32
+ requires:
33
+ - env: GOOGLE_OAUTH_CREDENTIALS
34
+ type: oauth2
35
+ service: Google Cloud Console
36
+ setup_url: https://console.cloud.google.com/apis/credentials
37
+ setup_time: "15 minutes (shared with other Google services)"
38
+ credentials_path: "~/.claude/.credentials/sheets-client-secret.json"
39
+ notes: "OAuth2 preferred. Service Account fallback supported."
40
+
41
+ google-docs:
42
+ level: 1
43
+ requires:
44
+ - env: GOOGLE_OAUTH_CREDENTIALS
45
+ type: oauth2
46
+ service: Google Cloud Console
47
+ setup_url: https://console.cloud.google.com/apis/credentials
48
+ setup_time: "15 minutes (shared with other Google services)"
49
+ credentials_path: "~/.claude/.credentials/docs-client-secret.json"
50
+ notes: "OAuth2 preferred. Service Account fallback supported."
51
+
52
+ google-calendar:
53
+ level: 1
54
+ requires:
55
+ - env: GOOGLE_OAUTH_CREDENTIALS
56
+ type: oauth2
57
+ service: Google Cloud Console
58
+ setup_url: https://console.cloud.google.com/apis/credentials
59
+ setup_time: "15 minutes (shared with other Google services)"
60
+ credentials_path: "~/.claude/.credentials/calendar-oauth.keys.json"
61
+ notes: "OAuth2 browser consent flow."
62
+
63
+ google-drive:
64
+ level: 1
65
+ requires:
66
+ - env: GOOGLE_OAUTH_CREDENTIALS
67
+ type: oauth2
68
+ service: Google Cloud Console
69
+ setup_url: https://console.cloud.google.com/apis/credentials
70
+ setup_time: "15 minutes (shared with other Google services)"
71
+ credentials_path: "~/.claude/.credentials/drive-oauth.keys.json"
72
+ notes: "OAuth2 browser consent flow."
73
+
74
+ google-contacts:
75
+ level: 1
76
+ requires:
77
+ - env: GOOGLE_OAUTH_CREDENTIALS
78
+ type: oauth2
79
+ service: Google Cloud Console
80
+ setup_url: https://console.cloud.google.com/apis/credentials
81
+ setup_time: "15 minutes (shared with other Google services)"
82
+ credentials_path: "~/.claude/.credentials/contacts-oauth.keys.json"
83
+ notes: "Read-only. Shares GCP project with Gmail."
84
+
85
+ google-slides:
86
+ level: 1
87
+ requires:
88
+ - env: GOOGLE_OAUTH_CREDENTIALS
89
+ type: oauth2
90
+ service: Google Cloud Console
91
+ setup_url: https://console.cloud.google.com/apis/credentials
92
+ setup_time: "15 minutes (shared with other Google services)"
93
+ credentials_path: "~/.claude/.credentials/slides-token.json"
94
+ notes: "OAuth2 or Service Account (GOOGLE_SERVICE_ACCOUNT_JSON)."
95
+
96
+ finance-manager:
97
+ level: 1
98
+ requires:
99
+ - env: GOOGLE_OAUTH_CREDENTIALS
100
+ type: oauth2
101
+ service: Google Cloud Console
102
+ setup_url: https://console.cloud.google.com/apis/credentials
103
+ setup_time: "15 minutes (shared with other Google services)"
104
+ notes: "Composes gmail + google-sheets + google-docs + ocr-processor. Set up Google OAuth first."
105
+
106
+ # ============================================================================
107
+ # LEVEL 2: API Keys
108
+ # Individual service signups. Each key takes 2-5 minutes to obtain.
109
+ # ============================================================================
110
+
111
+ fal-ai:
112
+ level: 2
113
+ requires:
114
+ - env: FAL_KEY
115
+ type: api_key
116
+ service: FAL.ai
117
+ setup_url: https://fal.ai/dashboard/keys
118
+ setup_time: "2 minutes"
119
+ notes: "Set in ~/.claude/.env as FAL_KEY=your-key"
120
+
121
+ ultimate-infographics:
122
+ level: 2
123
+ requires:
124
+ - env: FAL_KEY
125
+ type: api_key
126
+ service: FAL.ai
127
+ setup_url: https://fal.ai/dashboard/keys
128
+ setup_time: "2 minutes (shared with fal-ai)"
129
+ notes: "Uses fal-ai for Seedream 4.5 image generation."
130
+
131
+ elevenlabs:
132
+ level: 2
133
+ requires:
134
+ - env: ELEVENLABS_API_KEY
135
+ type: api_key
136
+ service: ElevenLabs
137
+ setup_url: https://elevenlabs.io/app/settings/api-keys
138
+ setup_time: "2 minutes"
139
+ notes: "Set in ~/.claude/.env as ELEVENLABS_API_KEY=sk_xxx"
140
+
141
+ gemini-vision:
142
+ level: 2
143
+ requires:
144
+ - env: GOOGLE_API_KEY
145
+ type: api_key
146
+ service: Google AI Studio
147
+ setup_url: https://aistudio.google.com/apikey
148
+ setup_time: "2 minutes"
149
+ notes: "Shared with ocr-processor. Set in ~/.claude/.env as GOOGLE_API_KEY=AIza..."
150
+
151
+ ocr-processor:
152
+ level: 2
153
+ requires:
154
+ - env: GOOGLE_API_KEY
155
+ type: api_key
156
+ service: Google AI Studio
157
+ setup_url: https://aistudio.google.com/apikey
158
+ setup_time: "2 minutes (shared with gemini-vision)"
159
+ notes: "Primary provider: Gemini."
160
+ - env: MISTRAL_API_KEY
161
+ type: api_key
162
+ service: Mistral AI
163
+ setup_url: https://console.mistral.ai/api-keys
164
+ setup_time: "2 minutes"
165
+ notes: "Alternative provider for OCR. Set in ~/.claude/.env as MISTRAL_API_KEY=xxx"
166
+
167
+ airtable:
168
+ level: 2
169
+ requires:
170
+ - env: AIRTABLE_API_KEY
171
+ type: api_key
172
+ service: Airtable
173
+ setup_url: https://airtable.com/create/tokens
174
+ setup_time: "3 minutes"
175
+ notes: "Personal Access Token (pat prefix). Set as AIRTABLE_API_KEY=patXXX"
176
+
177
+ crm:
178
+ level: 2
179
+ requires:
180
+ - env: AIRTABLE_API_KEY
181
+ type: api_key
182
+ service: Airtable
183
+ setup_url: https://airtable.com/create/tokens
184
+ setup_time: "3 minutes (shared with airtable)"
185
+ notes: "CRM orchestration layer built on Airtable. Same API key."
186
+
187
+ deploy-vercel:
188
+ level: 2
189
+ requires:
190
+ - env: VERCEL_TOKEN
191
+ type: api_key
192
+ service: Vercel
193
+ setup_url: https://vercel.com/account/tokens
194
+ setup_time: "2 minutes"
195
+ notes: "Personal access token. Set in ~/.claude/.env as VERCEL_TOKEN=xxx"
196
+
197
+ retell-ai:
198
+ level: 2
199
+ requires:
200
+ - env: RETELL_API_KEY
201
+ type: api_key
202
+ service: Retell AI
203
+ setup_url: https://www.retellai.com/dashboard
204
+ setup_time: "3 minutes"
205
+ notes: "Set in ~/.claude/.env as RETELL_API_KEY=key_xxx"
206
+
207
+ exa:
208
+ level: 2
209
+ requires:
210
+ - env: EXA_API_KEY
211
+ type: api_key
212
+ service: Exa.ai
213
+ setup_url: https://dashboard.exa.ai/api-keys
214
+ setup_time: "2 minutes"
215
+ notes: "AI-native search API. Set in ~/.claude/.env as EXA_API_KEY=xxx"
216
+
217
+ gumroad:
218
+ level: 2
219
+ requires:
220
+ - env: GUMROAD_ACCESS_TOKEN
221
+ type: api_key
222
+ service: Gumroad
223
+ setup_url: https://app.gumroad.com/settings/advanced#application-form
224
+ setup_time: "3 minutes"
225
+ notes: "OAuth access token. Also supports ~/.gumroad/credentials file."
226
+
227
+ convex:
228
+ level: 2
229
+ requires:
230
+ - env: CONVEX_DEPLOY_KEY
231
+ type: api_key
232
+ service: Convex
233
+ setup_url: https://dashboard.convex.dev
234
+ setup_time: "3 minutes"
235
+ notes: "Deploy key for Convex projects. Set in ~/.claude/.env as CONVEX_DEPLOY_KEY=xxx"
236
+
237
+ youtube-api:
238
+ level: 2
239
+ requires:
240
+ - env: YOUTUBE_API_KEY
241
+ type: api_key
242
+ service: Google Cloud Console
243
+ setup_url: https://console.cloud.google.com/apis/credentials
244
+ setup_time: "3 minutes (shared with GCP project)"
245
+ notes: "YouTube Data API v3. Optional - many operations work without key via yt-dlp."
246
+
247
+ skool-api:
248
+ level: 2
249
+ requires:
250
+ - env: SKOOL_COOKIES
251
+ type: cookie_auth
252
+ service: Skool.com
253
+ setup_url: https://www.skool.com
254
+ setup_time: "5 minutes (browser login)"
255
+ notes: "Cookie-based auth persisted at ~/.skool/cookies.json. No API key - uses browser login."
256
+
257
+ # ============================================================================
258
+ # LEVEL 2 (continued): General-purpose API keys
259
+ # Used by multiple skills or for advanced integrations.
260
+ # ============================================================================
261
+
262
+ deep-research:
263
+ level: 2
264
+ requires:
265
+ - env: OPENAI_API_KEY
266
+ type: api_key
267
+ service: OpenAI
268
+ setup_url: https://platform.openai.com/api-keys
269
+ setup_time: "2 minutes"
270
+ notes: "Used by deep-research and other skills that support OpenAI models."
271
+ - env: ANTHROPIC_API_KEY
272
+ type: api_key
273
+ service: Anthropic
274
+ setup_url: https://console.anthropic.com/settings/keys
275
+ setup_time: "2 minutes"
276
+ notes: "Used for API-routed sub-agents and headless operations."
277
+
278
+ browser-automation:
279
+ level: 2
280
+ requires:
281
+ - env: RAPIDAPI_KEY
282
+ type: api_key
283
+ service: RapidAPI
284
+ setup_url: https://rapidapi.com/developer/dashboard
285
+ setup_time: "3 minutes"
286
+ notes: "Used by browser-automation and data enrichment integrations."
287
+
288
+ # ============================================================================
289
+ # LEVEL 3: Service Accounts
290
+ # Require KYC verification, usage-based billing, or special configuration.
291
+ # ============================================================================
292
+
293
+ twilio:
294
+ level: 3
295
+ requires:
296
+ - env: TWILIO_ACCOUNT_SID
297
+ type: service_account
298
+ service: Twilio
299
+ setup_url: https://console.twilio.com
300
+ setup_time: "10 minutes"
301
+ - env: TWILIO_AUTH_TOKEN
302
+ type: service_account
303
+ service: Twilio
304
+ setup_url: https://console.twilio.com
305
+ setup_time: "included above"
306
+ - env: TWILIO_PHONE_NUMBER
307
+ type: service_account
308
+ service: Twilio
309
+ setup_url: https://console.twilio.com
310
+ setup_time: "included above"
311
+ notes: "Three env vars required. KYC may be needed for full access."
312
+
313
+ bright-data:
314
+ level: 3
315
+ requires:
316
+ - env: API_TOKEN
317
+ type: service_account
318
+ service: Bright Data
319
+ setup_url: https://brightdata.com/cp/setting
320
+ setup_time: "10 minutes"
321
+ notes: "Usage-based billing. Set in ~/.claude/.env as API_TOKEN=xxx"
322
+
323
+ google-maps:
324
+ level: 3
325
+ requires:
326
+ - env: GOOGLE_MAPS_API_KEY
327
+ type: api_key
328
+ service: Google Cloud Console
329
+ setup_url: https://console.cloud.google.com/apis/credentials
330
+ setup_time: "5 minutes"
331
+ notes: "Requires billing enabled on GCP project. Set as GOOGLE_MAPS_API_KEY=AIza..."
332
+
333
+ weather:
334
+ level: 3
335
+ requires:
336
+ - env: OPENWEATHERMAP_API_KEY
337
+ type: api_key
338
+ service: OpenWeatherMap
339
+ setup_url: https://home.openweathermap.org/api_keys
340
+ setup_time: "3 minutes"
341
+ notes: "Free tier available. One Call 3.0 requires paid subscription."
342
+
343
+ flight-tracker:
344
+ level: 3
345
+ requires:
346
+ - env: AVIATIONSTACK_API_KEY
347
+ type: api_key
348
+ service: AviationStack
349
+ setup_url: https://aviationstack.com/signup/free
350
+ setup_time: "3 minutes"
351
+ notes: "Free tier available with limited requests."
352
+
353
+ claude-gateway:
354
+ level: 3
355
+ requires:
356
+ - env: DISCORD_BOT_TOKEN
357
+ type: api_key
358
+ service: Discord
359
+ setup_url: https://discord.com/developers/applications
360
+ setup_time: "10 minutes"
361
+ - env: DISCORD_CHANNEL_ID
362
+ type: config
363
+ service: Discord
364
+ setup_time: "included above"
365
+ - env: DISCORD_GUILD_ID
366
+ type: config
367
+ service: Discord
368
+ setup_time: "included above"
369
+ notes: "Three env vars required. Create Discord bot application and invite to server."
370
+
371
+ voice-agent-v2:
372
+ level: 3
373
+ requires:
374
+ - env: LIVEKIT_API_KEY
375
+ type: api_key
376
+ service: LiveKit
377
+ setup_url: https://cloud.livekit.io
378
+ setup_time: "5 minutes"
379
+ - env: LIVEKIT_API_SECRET
380
+ type: api_key
381
+ service: LiveKit
382
+ setup_time: "included above"
383
+ - env: LIVEKIT_URL
384
+ type: config
385
+ service: LiveKit
386
+ setup_time: "included above"
387
+ notes: "Three env vars required. LiveKit Cloud or self-hosted."
388
+
389
+ # ============================================================================
390
+ # LEVEL 4: Applications
391
+ # Requires desktop software installed on the user's machine.
392
+ # ============================================================================
393
+
394
+ vault-manager:
395
+ level: 4
396
+ requires:
397
+ - env: OBSIDIAN_INSTALLED
398
+ type: application
399
+ service: Obsidian
400
+ setup_url: https://obsidian.md
401
+ setup_time: "30 minutes (install + vault setup)"
402
+ notes: "Free app. Requires an Obsidian vault configured with PARA structure."
403
+
404
+ life-os:
405
+ level: 4
406
+ requires:
407
+ - env: OBSIDIAN_INSTALLED
408
+ type: application
409
+ service: Obsidian
410
+ setup_url: https://obsidian.md
411
+ setup_time: "30 minutes (install + vault setup)"
412
+ notes: "Requires vault-manager configured first. Life Operating System orchestrator."
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env python3
2
+ """pushREC Skills diagnostic: checks system readiness for Claude Code skills.
3
+
4
+ Standalone Python 3.8+, zero external dependencies.
5
+ Output: JSON to stdout (parseable by Claude Code).
6
+ Exit codes: 0 = all good, 1 = missing critical prereqs, 2 = warnings only.
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import platform
12
+ import subprocess
13
+ import sys
14
+ from datetime import datetime, timezone
15
+ from pathlib import Path
16
+
17
+ VERSION = "1.1.0"
18
+ SKILLS_DIR = Path.home() / ".claude" / "skills"
19
+ CONFIG_PATH = Path.home() / ".pushrec" / "config.json"
20
+ ENV_PATH = Path.home() / ".claude" / ".env"
21
+ PREREQUISITES_PATH = Path(__file__).resolve().parent.parent / "references" / "prerequisites.yaml"
22
+
23
+ # Fallback list used when prerequisites.yaml cannot be read
24
+ _FALLBACK_KEY_PREFIXES = [
25
+ "ELEVENLABS_API_KEY",
26
+ "OPENAI_API_KEY",
27
+ "ANTHROPIC_API_KEY",
28
+ "FAL_KEY",
29
+ "GOOGLE_API_KEY",
30
+ "GOOGLE_MAPS_API_KEY",
31
+ "YOUTUBE_API_KEY",
32
+ "RAPIDAPI_KEY",
33
+ "TWILIO_ACCOUNT_SID",
34
+ "TWILIO_AUTH_TOKEN",
35
+ "RETELL_API_KEY",
36
+ "API_TOKEN",
37
+ "AIRTABLE_API_KEY",
38
+ "VERCEL_TOKEN",
39
+ "OPENWEATHERMAP_API_KEY",
40
+ "AVIATIONSTACK_API_KEY",
41
+ "DISCORD_BOT_TOKEN",
42
+ "MISTRAL_API_KEY",
43
+ ]
44
+
45
+
46
+ def load_key_prefixes():
47
+ """Load API key env var names from prerequisites.yaml, falling back to hardcoded list."""
48
+ try:
49
+ with open(PREREQUISITES_PATH, "r") as f:
50
+ content = f.read()
51
+ # Simple YAML parsing: extract all `env: VALUE` lines (no PyYAML dependency)
52
+ prefixes = set()
53
+ for line in content.splitlines():
54
+ stripped = line.strip()
55
+ if stripped.startswith("- env:") or stripped.startswith("env:"):
56
+ value = stripped.split(":", 1)[1].strip()
57
+ if value:
58
+ prefixes.add(value)
59
+ if prefixes:
60
+ return sorted(prefixes)
61
+ except (OSError, ValueError):
62
+ pass
63
+ return _FALLBACK_KEY_PREFIXES
64
+
65
+
66
+ KNOWN_KEY_PREFIXES = load_key_prefixes()
67
+
68
+
69
+ def run_command(args):
70
+ """Run a shell command and return (success, stdout_stripped)."""
71
+ try:
72
+ result = subprocess.run(
73
+ args,
74
+ capture_output=True,
75
+ text=True,
76
+ timeout=10,
77
+ )
78
+ return result.returncode == 0, result.stdout.strip()
79
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
80
+ return False, ""
81
+
82
+
83
+ def parse_version(version_string):
84
+ """Extract numeric version tuple from a version string like 'v22.1.0' or '2.1.45'."""
85
+ cleaned = version_string.lstrip("v").strip()
86
+ parts = []
87
+ for segment in cleaned.split("."):
88
+ digits = ""
89
+ for char in segment:
90
+ if char.isdigit():
91
+ digits += char
92
+ else:
93
+ break
94
+ if digits:
95
+ parts.append(int(digits))
96
+ return tuple(parts) if parts else (0,)
97
+
98
+
99
+ def version_gte(current, minimum):
100
+ """Check if current version >= minimum version."""
101
+ current_tuple = parse_version(current)
102
+ minimum_tuple = parse_version(minimum)
103
+ return current_tuple >= minimum_tuple
104
+
105
+
106
+ def check_claude_code():
107
+ """Check Claude Code installation and version."""
108
+ success, output = run_command(["claude", "--version"])
109
+ if not success:
110
+ return {"status": "fail", "version": None, "minimum": "2.1.0"}
111
+ version = output.strip()
112
+ if version_gte(version, "2.1.0"):
113
+ return {"status": "pass", "version": version, "minimum": "2.1.0"}
114
+ return {"status": "fail", "version": version, "minimum": "2.1.0"}
115
+
116
+
117
+ def check_node():
118
+ """Check Node.js installation and version."""
119
+ success, output = run_command(["node", "--version"])
120
+ if not success:
121
+ return {"status": "fail", "version": None, "minimum": "20.0.0"}
122
+ version = output.strip()
123
+ if version_gte(version, "20.0.0"):
124
+ return {"status": "pass", "version": version, "minimum": "20.0.0"}
125
+ return {"status": "fail", "version": version, "minimum": "20.0.0"}
126
+
127
+
128
+ def check_npm():
129
+ """Check npm installation."""
130
+ success, output = run_command(["npm", "--version"])
131
+ if not success:
132
+ return {"status": "fail", "version": None}
133
+ return {"status": "pass", "version": output.strip()}
134
+
135
+
136
+ def check_skills_installed():
137
+ """Count installed skills by looking for SKILL.md files."""
138
+ if not SKILLS_DIR.is_dir():
139
+ return {"status": "fail", "count": 0}
140
+ count = 0
141
+ for child in SKILLS_DIR.iterdir():
142
+ if child.is_dir() and (child / "SKILL.md").is_file():
143
+ count += 1
144
+ if count == 0:
145
+ return {"status": "fail", "count": 0}
146
+ return {"status": "pass", "count": count}
147
+
148
+
149
+ def check_license():
150
+ """Check pushREC license status via config.json."""
151
+ if not CONFIG_PATH.is_file():
152
+ return {"status": "fail", "installed_skills": 0}
153
+ try:
154
+ with open(CONFIG_PATH, "r") as f:
155
+ config = json.load(f)
156
+ installed = config.get("installedSkills", {})
157
+ count = len(installed) if isinstance(installed, dict) else 0
158
+ if count > 0:
159
+ return {"status": "pass", "installed_skills": count}
160
+ return {"status": "fail", "installed_skills": 0}
161
+ except (json.JSONDecodeError, OSError):
162
+ return {"status": "fail", "installed_skills": 0}
163
+
164
+
165
+ def check_api_keys():
166
+ """Detect configured API keys from ~/.claude/.env."""
167
+ detected = []
168
+ if not ENV_PATH.is_file():
169
+ return {"status": "info", "detected": 0, "keys": []}
170
+ try:
171
+ with open(ENV_PATH, "r") as f:
172
+ for line in f:
173
+ line = line.strip()
174
+ if not line or line.startswith("#"):
175
+ continue
176
+ for prefix in KNOWN_KEY_PREFIXES:
177
+ if line.startswith(prefix + "="):
178
+ value = line.split("=", 1)[1].strip().strip('"').strip("'")
179
+ if value:
180
+ detected.append(prefix)
181
+ break
182
+ except OSError:
183
+ pass
184
+ return {"status": "info", "detected": len(detected), "keys": detected}
185
+
186
+
187
+ def check_obsidian():
188
+ """Check if Obsidian is installed (platform-specific)."""
189
+ system = platform.system()
190
+ if system == "Darwin":
191
+ installed = Path("/Applications/Obsidian.app").exists()
192
+ elif system == "Linux":
193
+ success, _ = run_command(["which", "obsidian"])
194
+ installed = success
195
+ elif system == "Windows":
196
+ success, _ = run_command(["where", "obsidian"])
197
+ installed = success
198
+ else:
199
+ installed = False
200
+ return {"status": "info", "installed": installed}
201
+
202
+
203
+ def main():
204
+ # Exit silently in headless mode — hooks must never block headless sessions
205
+ # (e.g. CI pipelines, sub-agents) with interactive diagnostics or stdout noise.
206
+ if os.environ.get("CLAUDE_HEADLESS"):
207
+ sys.exit(0)
208
+
209
+ checks = {
210
+ "claude_code": check_claude_code(),
211
+ "node": check_node(),
212
+ "npm": check_npm(),
213
+ "skills_installed": check_skills_installed(),
214
+ "license": check_license(),
215
+ "api_keys": check_api_keys(),
216
+ "obsidian": check_obsidian(),
217
+ }
218
+
219
+ critical_failures = []
220
+ warnings = []
221
+
222
+ if checks["claude_code"]["status"] == "fail":
223
+ critical_failures.append("Claude Code")
224
+ if checks["node"]["status"] == "fail":
225
+ critical_failures.append("Node.js v20+")
226
+ if checks["npm"]["status"] == "fail":
227
+ critical_failures.append("npm")
228
+ if checks["skills_installed"]["status"] == "fail":
229
+ warnings.append("No skills installed")
230
+ if checks["license"]["status"] == "fail":
231
+ warnings.append("No active license")
232
+
233
+ if critical_failures:
234
+ exit_code = 1
235
+ summary = f"Missing critical prerequisites: {', '.join(critical_failures)}."
236
+ elif warnings:
237
+ exit_code = 2
238
+ summary = f"Warnings: {', '.join(warnings)}."
239
+ else:
240
+ skills_count = checks["skills_installed"].get("count", 0)
241
+ api_count = checks["api_keys"].get("detected", 0)
242
+ summary = f"All systems ready. {skills_count} skills installed, {api_count} API keys detected."
243
+ exit_code = 0
244
+
245
+ report = {
246
+ "version": VERSION,
247
+ "timestamp": datetime.now(timezone.utc).isoformat(),
248
+ "platform": platform.system().lower(),
249
+ "npm_version": checks["npm"].get("version"),
250
+ "skills_count": checks["skills_installed"].get("count", 0),
251
+ "checks": checks,
252
+ "exit_code": exit_code,
253
+ "summary": summary,
254
+ }
255
+
256
+ print(json.dumps(report, indent=2))
257
+ sys.exit(exit_code)
258
+
259
+
260
+ if __name__ == "__main__":
261
+ main()