@pushrec/skills 0.1.0 → 0.3.2
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 +32 -8
- package/bundled-skills/README.md +9 -0
- package/bundled-skills/pushrec-skills/SKILL.md +360 -0
- package/bundled-skills/pushrec-skills/references/prerequisites.yaml +375 -0
- package/bundled-skills/pushrec-skills/scripts/diagnostic.py +232 -0
- package/dist/index.js +531 -4
- package/package.json +2 -1
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# pushREC Skills Prerequisites
|
|
2
|
+
# version: 1.0.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: "~/.gmail-mcp/gcp-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: "~/.calendar-mcp/gcp-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: "~/.drive-mcp/gcp-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: "~/.contacts-mcp/gcp-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. Also supports MISTRAL_API_KEY as alternative."
|
|
160
|
+
|
|
161
|
+
airtable:
|
|
162
|
+
level: 2
|
|
163
|
+
requires:
|
|
164
|
+
- env: AIRTABLE_API_KEY
|
|
165
|
+
type: api_key
|
|
166
|
+
service: Airtable
|
|
167
|
+
setup_url: https://airtable.com/create/tokens
|
|
168
|
+
setup_time: "3 minutes"
|
|
169
|
+
notes: "Personal Access Token (pat prefix). Set as AIRTABLE_API_KEY=patXXX"
|
|
170
|
+
|
|
171
|
+
crm:
|
|
172
|
+
level: 2
|
|
173
|
+
requires:
|
|
174
|
+
- env: AIRTABLE_API_KEY
|
|
175
|
+
type: api_key
|
|
176
|
+
service: Airtable
|
|
177
|
+
setup_url: https://airtable.com/create/tokens
|
|
178
|
+
setup_time: "3 minutes (shared with airtable)"
|
|
179
|
+
notes: "CRM orchestration layer built on Airtable. Same API key."
|
|
180
|
+
|
|
181
|
+
deploy-vercel:
|
|
182
|
+
level: 2
|
|
183
|
+
requires:
|
|
184
|
+
- env: VERCEL_TOKEN
|
|
185
|
+
type: api_key
|
|
186
|
+
service: Vercel
|
|
187
|
+
setup_url: https://vercel.com/account/tokens
|
|
188
|
+
setup_time: "2 minutes"
|
|
189
|
+
notes: "Personal access token. Set in ~/.claude/.env as VERCEL_TOKEN=xxx"
|
|
190
|
+
|
|
191
|
+
retell-ai:
|
|
192
|
+
level: 2
|
|
193
|
+
requires:
|
|
194
|
+
- env: RETELL_API_KEY
|
|
195
|
+
type: api_key
|
|
196
|
+
service: Retell AI
|
|
197
|
+
setup_url: https://www.retellai.com/dashboard
|
|
198
|
+
setup_time: "3 minutes"
|
|
199
|
+
notes: "Set in ~/.claude/.env as RETELL_API_KEY=key_xxx"
|
|
200
|
+
|
|
201
|
+
exa:
|
|
202
|
+
level: 2
|
|
203
|
+
requires:
|
|
204
|
+
- env: EXA_API_KEY
|
|
205
|
+
type: api_key
|
|
206
|
+
service: Exa.ai
|
|
207
|
+
setup_url: https://dashboard.exa.ai/api-keys
|
|
208
|
+
setup_time: "2 minutes"
|
|
209
|
+
notes: "AI-native search API. Set in ~/.claude/.env as EXA_API_KEY=xxx"
|
|
210
|
+
|
|
211
|
+
gumroad:
|
|
212
|
+
level: 2
|
|
213
|
+
requires:
|
|
214
|
+
- env: GUMROAD_ACCESS_TOKEN
|
|
215
|
+
type: api_key
|
|
216
|
+
service: Gumroad
|
|
217
|
+
setup_url: https://app.gumroad.com/settings/advanced#application-form
|
|
218
|
+
setup_time: "3 minutes"
|
|
219
|
+
notes: "OAuth access token. Also supports ~/.gumroad/credentials file."
|
|
220
|
+
|
|
221
|
+
convex:
|
|
222
|
+
level: 2
|
|
223
|
+
requires:
|
|
224
|
+
- env: CONVEX_DEPLOY_KEY
|
|
225
|
+
type: api_key
|
|
226
|
+
service: Convex
|
|
227
|
+
setup_url: https://dashboard.convex.dev
|
|
228
|
+
setup_time: "3 minutes"
|
|
229
|
+
notes: "Deploy key for Convex projects. Set in ~/.claude/.env as CONVEX_DEPLOY_KEY=xxx"
|
|
230
|
+
|
|
231
|
+
youtube-api:
|
|
232
|
+
level: 2
|
|
233
|
+
requires:
|
|
234
|
+
- env: YOUTUBE_API_KEY
|
|
235
|
+
type: api_key
|
|
236
|
+
service: Google Cloud Console
|
|
237
|
+
setup_url: https://console.cloud.google.com/apis/credentials
|
|
238
|
+
setup_time: "3 minutes (shared with GCP project)"
|
|
239
|
+
notes: "YouTube Data API v3. Optional - many operations work without key via yt-dlp."
|
|
240
|
+
|
|
241
|
+
skool-api:
|
|
242
|
+
level: 2
|
|
243
|
+
requires:
|
|
244
|
+
- env: SKOOL_COOKIES
|
|
245
|
+
type: cookie_auth
|
|
246
|
+
service: Skool.com
|
|
247
|
+
setup_url: https://www.skool.com
|
|
248
|
+
setup_time: "5 minutes (browser login)"
|
|
249
|
+
notes: "Cookie-based auth persisted at ~/.skool/cookies.json. No API key - uses browser login."
|
|
250
|
+
|
|
251
|
+
# ============================================================================
|
|
252
|
+
# LEVEL 3: Service Accounts
|
|
253
|
+
# Require KYC verification, usage-based billing, or special configuration.
|
|
254
|
+
# ============================================================================
|
|
255
|
+
|
|
256
|
+
twilio:
|
|
257
|
+
level: 3
|
|
258
|
+
requires:
|
|
259
|
+
- env: TWILIO_ACCOUNT_SID
|
|
260
|
+
type: service_account
|
|
261
|
+
service: Twilio
|
|
262
|
+
setup_url: https://console.twilio.com
|
|
263
|
+
setup_time: "10 minutes"
|
|
264
|
+
- env: TWILIO_AUTH_TOKEN
|
|
265
|
+
type: service_account
|
|
266
|
+
service: Twilio
|
|
267
|
+
setup_url: https://console.twilio.com
|
|
268
|
+
setup_time: "included above"
|
|
269
|
+
- env: TWILIO_PHONE_NUMBER
|
|
270
|
+
type: service_account
|
|
271
|
+
service: Twilio
|
|
272
|
+
setup_url: https://console.twilio.com
|
|
273
|
+
setup_time: "included above"
|
|
274
|
+
notes: "Three env vars required. KYC may be needed for full access."
|
|
275
|
+
|
|
276
|
+
bright-data:
|
|
277
|
+
level: 3
|
|
278
|
+
requires:
|
|
279
|
+
- env: API_TOKEN
|
|
280
|
+
type: service_account
|
|
281
|
+
service: Bright Data
|
|
282
|
+
setup_url: https://brightdata.com/cp/setting
|
|
283
|
+
setup_time: "10 minutes"
|
|
284
|
+
notes: "Usage-based billing. Set in ~/.claude/.env as API_TOKEN=xxx"
|
|
285
|
+
|
|
286
|
+
google-maps:
|
|
287
|
+
level: 3
|
|
288
|
+
requires:
|
|
289
|
+
- env: GOOGLE_MAPS_API_KEY
|
|
290
|
+
type: api_key
|
|
291
|
+
service: Google Cloud Console
|
|
292
|
+
setup_url: https://console.cloud.google.com/apis/credentials
|
|
293
|
+
setup_time: "5 minutes"
|
|
294
|
+
notes: "Requires billing enabled on GCP project. Set as GOOGLE_MAPS_API_KEY=AIza..."
|
|
295
|
+
|
|
296
|
+
weather:
|
|
297
|
+
level: 3
|
|
298
|
+
requires:
|
|
299
|
+
- env: OPENWEATHERMAP_API_KEY
|
|
300
|
+
type: api_key
|
|
301
|
+
service: OpenWeatherMap
|
|
302
|
+
setup_url: https://home.openweathermap.org/api_keys
|
|
303
|
+
setup_time: "3 minutes"
|
|
304
|
+
notes: "Free tier available. One Call 3.0 requires paid subscription."
|
|
305
|
+
|
|
306
|
+
flight-tracker:
|
|
307
|
+
level: 3
|
|
308
|
+
requires:
|
|
309
|
+
- env: AVIATIONSTACK_API_KEY
|
|
310
|
+
type: api_key
|
|
311
|
+
service: AviationStack
|
|
312
|
+
setup_url: https://aviationstack.com/signup/free
|
|
313
|
+
setup_time: "3 minutes"
|
|
314
|
+
notes: "Free tier available with limited requests."
|
|
315
|
+
|
|
316
|
+
claude-gateway:
|
|
317
|
+
level: 3
|
|
318
|
+
requires:
|
|
319
|
+
- env: DISCORD_BOT_TOKEN
|
|
320
|
+
type: api_key
|
|
321
|
+
service: Discord
|
|
322
|
+
setup_url: https://discord.com/developers/applications
|
|
323
|
+
setup_time: "10 minutes"
|
|
324
|
+
- env: DISCORD_CHANNEL_ID
|
|
325
|
+
type: config
|
|
326
|
+
service: Discord
|
|
327
|
+
setup_time: "included above"
|
|
328
|
+
- env: DISCORD_GUILD_ID
|
|
329
|
+
type: config
|
|
330
|
+
service: Discord
|
|
331
|
+
setup_time: "included above"
|
|
332
|
+
notes: "Three env vars required. Create Discord bot application and invite to server."
|
|
333
|
+
|
|
334
|
+
voice-agent-v2:
|
|
335
|
+
level: 3
|
|
336
|
+
requires:
|
|
337
|
+
- env: LIVEKIT_API_KEY
|
|
338
|
+
type: api_key
|
|
339
|
+
service: LiveKit
|
|
340
|
+
setup_url: https://cloud.livekit.io
|
|
341
|
+
setup_time: "5 minutes"
|
|
342
|
+
- env: LIVEKIT_API_SECRET
|
|
343
|
+
type: api_key
|
|
344
|
+
service: LiveKit
|
|
345
|
+
setup_time: "included above"
|
|
346
|
+
- env: LIVEKIT_URL
|
|
347
|
+
type: config
|
|
348
|
+
service: LiveKit
|
|
349
|
+
setup_time: "included above"
|
|
350
|
+
notes: "Three env vars required. LiveKit Cloud or self-hosted."
|
|
351
|
+
|
|
352
|
+
# ============================================================================
|
|
353
|
+
# LEVEL 4: Applications
|
|
354
|
+
# Requires desktop software installed on the user's machine.
|
|
355
|
+
# ============================================================================
|
|
356
|
+
|
|
357
|
+
vault-manager:
|
|
358
|
+
level: 4
|
|
359
|
+
requires:
|
|
360
|
+
- env: OBSIDIAN_INSTALLED
|
|
361
|
+
type: application
|
|
362
|
+
service: Obsidian
|
|
363
|
+
setup_url: https://obsidian.md
|
|
364
|
+
setup_time: "30 minutes (install + vault setup)"
|
|
365
|
+
notes: "Free app. Requires an Obsidian vault configured with PARA structure."
|
|
366
|
+
|
|
367
|
+
life-os:
|
|
368
|
+
level: 4
|
|
369
|
+
requires:
|
|
370
|
+
- env: OBSIDIAN_INSTALLED
|
|
371
|
+
type: application
|
|
372
|
+
service: Obsidian
|
|
373
|
+
setup_url: https://obsidian.md
|
|
374
|
+
setup_time: "30 minutes (install + vault setup)"
|
|
375
|
+
notes: "Requires vault-manager configured first. Life Operating System orchestrator."
|
|
@@ -0,0 +1,232 @@
|
|
|
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.0.0"
|
|
18
|
+
SKILLS_DIR = Path.home() / ".claude" / "skills"
|
|
19
|
+
CONFIG_PATH = Path.home() / ".pushrec" / "config.json"
|
|
20
|
+
ENV_PATH = Path.home() / ".claude" / ".env"
|
|
21
|
+
|
|
22
|
+
KNOWN_KEY_PREFIXES = [
|
|
23
|
+
"ELEVENLABS_API_KEY",
|
|
24
|
+
"OPENAI_API_KEY",
|
|
25
|
+
"ANTHROPIC_API_KEY",
|
|
26
|
+
"FAL_KEY",
|
|
27
|
+
"GOOGLE_API_KEY",
|
|
28
|
+
"GOOGLE_MAPS_API_KEY",
|
|
29
|
+
"YOUTUBE_API_KEY",
|
|
30
|
+
"RAPIDAPI_KEY",
|
|
31
|
+
"TWILIO_ACCOUNT_SID",
|
|
32
|
+
"TWILIO_AUTH_TOKEN",
|
|
33
|
+
"RETELL_API_KEY",
|
|
34
|
+
"API_TOKEN",
|
|
35
|
+
"AIRTABLE_API_KEY",
|
|
36
|
+
"VERCEL_TOKEN",
|
|
37
|
+
"OPENWEATHERMAP_API_KEY",
|
|
38
|
+
"AVIATIONSTACK_API_KEY",
|
|
39
|
+
"DISCORD_BOT_TOKEN",
|
|
40
|
+
"MISTRAL_API_KEY",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_command(args):
|
|
45
|
+
"""Run a shell command and return (success, stdout_stripped)."""
|
|
46
|
+
try:
|
|
47
|
+
result = subprocess.run(
|
|
48
|
+
args,
|
|
49
|
+
capture_output=True,
|
|
50
|
+
text=True,
|
|
51
|
+
timeout=10,
|
|
52
|
+
)
|
|
53
|
+
return result.returncode == 0, result.stdout.strip()
|
|
54
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
55
|
+
return False, ""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def parse_version(version_string):
|
|
59
|
+
"""Extract numeric version tuple from a version string like 'v22.1.0' or '2.1.45'."""
|
|
60
|
+
cleaned = version_string.lstrip("v").strip()
|
|
61
|
+
parts = []
|
|
62
|
+
for segment in cleaned.split("."):
|
|
63
|
+
digits = ""
|
|
64
|
+
for char in segment:
|
|
65
|
+
if char.isdigit():
|
|
66
|
+
digits += char
|
|
67
|
+
else:
|
|
68
|
+
break
|
|
69
|
+
if digits:
|
|
70
|
+
parts.append(int(digits))
|
|
71
|
+
return tuple(parts) if parts else (0,)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def version_gte(current, minimum):
|
|
75
|
+
"""Check if current version >= minimum version."""
|
|
76
|
+
current_tuple = parse_version(current)
|
|
77
|
+
minimum_tuple = parse_version(minimum)
|
|
78
|
+
return current_tuple >= minimum_tuple
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def check_claude_code():
|
|
82
|
+
"""Check Claude Code installation and version."""
|
|
83
|
+
success, output = run_command(["claude", "--version"])
|
|
84
|
+
if not success:
|
|
85
|
+
return {"status": "fail", "version": None, "minimum": "2.1.0"}
|
|
86
|
+
version = output.strip()
|
|
87
|
+
if version_gte(version, "2.1.0"):
|
|
88
|
+
return {"status": "pass", "version": version, "minimum": "2.1.0"}
|
|
89
|
+
return {"status": "fail", "version": version, "minimum": "2.1.0"}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def check_node():
|
|
93
|
+
"""Check Node.js installation and version."""
|
|
94
|
+
success, output = run_command(["node", "--version"])
|
|
95
|
+
if not success:
|
|
96
|
+
return {"status": "fail", "version": None, "minimum": "20.0.0"}
|
|
97
|
+
version = output.strip()
|
|
98
|
+
if version_gte(version, "20.0.0"):
|
|
99
|
+
return {"status": "pass", "version": version, "minimum": "20.0.0"}
|
|
100
|
+
return {"status": "fail", "version": version, "minimum": "20.0.0"}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def check_npm():
|
|
104
|
+
"""Check npm installation."""
|
|
105
|
+
success, output = run_command(["npm", "--version"])
|
|
106
|
+
if not success:
|
|
107
|
+
return {"status": "fail", "version": None}
|
|
108
|
+
return {"status": "pass", "version": output.strip()}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def check_skills_installed():
|
|
112
|
+
"""Count installed skills by looking for SKILL.md files."""
|
|
113
|
+
if not SKILLS_DIR.is_dir():
|
|
114
|
+
return {"status": "fail", "count": 0}
|
|
115
|
+
count = 0
|
|
116
|
+
for child in SKILLS_DIR.iterdir():
|
|
117
|
+
if child.is_dir() and (child / "SKILL.md").is_file():
|
|
118
|
+
count += 1
|
|
119
|
+
if count == 0:
|
|
120
|
+
return {"status": "fail", "count": 0}
|
|
121
|
+
return {"status": "pass", "count": count}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def check_license():
|
|
125
|
+
"""Check pushREC license status via config.json."""
|
|
126
|
+
if not CONFIG_PATH.is_file():
|
|
127
|
+
return {"status": "fail", "installed_skills": 0}
|
|
128
|
+
try:
|
|
129
|
+
with open(CONFIG_PATH, "r") as f:
|
|
130
|
+
config = json.load(f)
|
|
131
|
+
installed = config.get("installedSkills", {})
|
|
132
|
+
count = len(installed) if isinstance(installed, dict) else 0
|
|
133
|
+
if count > 0:
|
|
134
|
+
return {"status": "pass", "installed_skills": count}
|
|
135
|
+
return {"status": "fail", "installed_skills": 0}
|
|
136
|
+
except (json.JSONDecodeError, OSError):
|
|
137
|
+
return {"status": "fail", "installed_skills": 0}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def check_api_keys():
|
|
141
|
+
"""Detect configured API keys from ~/.claude/.env."""
|
|
142
|
+
detected = []
|
|
143
|
+
if not ENV_PATH.is_file():
|
|
144
|
+
return {"status": "info", "detected": 0, "keys": []}
|
|
145
|
+
try:
|
|
146
|
+
with open(ENV_PATH, "r") as f:
|
|
147
|
+
for line in f:
|
|
148
|
+
line = line.strip()
|
|
149
|
+
if not line or line.startswith("#"):
|
|
150
|
+
continue
|
|
151
|
+
for prefix in KNOWN_KEY_PREFIXES:
|
|
152
|
+
if line.startswith(prefix + "="):
|
|
153
|
+
value = line.split("=", 1)[1].strip().strip('"').strip("'")
|
|
154
|
+
if value:
|
|
155
|
+
detected.append(prefix)
|
|
156
|
+
break
|
|
157
|
+
except OSError:
|
|
158
|
+
pass
|
|
159
|
+
return {"status": "info", "detected": len(detected), "keys": detected}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def check_obsidian():
|
|
163
|
+
"""Check if Obsidian is installed (platform-specific)."""
|
|
164
|
+
system = platform.system()
|
|
165
|
+
if system == "Darwin":
|
|
166
|
+
installed = Path("/Applications/Obsidian.app").exists()
|
|
167
|
+
elif system == "Linux":
|
|
168
|
+
success, _ = run_command(["which", "obsidian"])
|
|
169
|
+
installed = success
|
|
170
|
+
elif system == "Windows":
|
|
171
|
+
success, _ = run_command(["where", "obsidian"])
|
|
172
|
+
installed = success
|
|
173
|
+
else:
|
|
174
|
+
installed = False
|
|
175
|
+
return {"status": "info", "installed": installed}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def main():
|
|
179
|
+
if os.environ.get("CLAUDE_HEADLESS"):
|
|
180
|
+
sys.exit(0)
|
|
181
|
+
|
|
182
|
+
checks = {
|
|
183
|
+
"claude_code": check_claude_code(),
|
|
184
|
+
"node": check_node(),
|
|
185
|
+
"npm": check_npm(),
|
|
186
|
+
"skills_installed": check_skills_installed(),
|
|
187
|
+
"license": check_license(),
|
|
188
|
+
"api_keys": check_api_keys(),
|
|
189
|
+
"obsidian": check_obsidian(),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
critical_failures = []
|
|
193
|
+
warnings = []
|
|
194
|
+
|
|
195
|
+
if checks["claude_code"]["status"] == "fail":
|
|
196
|
+
critical_failures.append("Claude Code")
|
|
197
|
+
if checks["node"]["status"] == "fail":
|
|
198
|
+
critical_failures.append("Node.js v20+")
|
|
199
|
+
if checks["npm"]["status"] == "fail":
|
|
200
|
+
critical_failures.append("npm")
|
|
201
|
+
if checks["skills_installed"]["status"] == "fail":
|
|
202
|
+
warnings.append("No skills installed")
|
|
203
|
+
if checks["license"]["status"] == "fail":
|
|
204
|
+
warnings.append("No active license")
|
|
205
|
+
|
|
206
|
+
if critical_failures:
|
|
207
|
+
exit_code = 1
|
|
208
|
+
summary = f"Missing critical prerequisites: {', '.join(critical_failures)}."
|
|
209
|
+
elif warnings:
|
|
210
|
+
exit_code = 2
|
|
211
|
+
summary = f"Warnings: {', '.join(warnings)}."
|
|
212
|
+
else:
|
|
213
|
+
skills_count = checks["skills_installed"].get("count", 0)
|
|
214
|
+
api_count = checks["api_keys"].get("detected", 0)
|
|
215
|
+
summary = f"All systems ready. {skills_count} skills installed, {api_count} API keys detected."
|
|
216
|
+
exit_code = 0
|
|
217
|
+
|
|
218
|
+
report = {
|
|
219
|
+
"version": VERSION,
|
|
220
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
221
|
+
"platform": platform.system().lower(),
|
|
222
|
+
"checks": checks,
|
|
223
|
+
"exit_code": exit_code,
|
|
224
|
+
"summary": summary,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
print(json.dumps(report, indent=2))
|
|
228
|
+
sys.exit(exit_code)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
if __name__ == "__main__":
|
|
232
|
+
main()
|