stackprep-pro 0.2.10__tar.gz → 0.2.12__tar.gz

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.
@@ -4,5 +4,12 @@
4
4
  "command": "uvx",
5
5
  "args": ["stackprep-pro"]
6
6
  }
7
+ },
8
+ "permissions": {
9
+ "allow": [
10
+ "mcp__stackprep",
11
+ "WebFetch",
12
+ "WebSearch"
13
+ ]
7
14
  }
8
15
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stackprep-pro
3
- Version: 0.2.10
3
+ Version: 0.2.12
4
4
  Summary: stackprep-pro — interview & certification prep MCP server for any AI client
5
5
  Project-URL: Homepage, https://github.com/youngpada1/stackprep-pro
6
6
  Project-URL: Repository, https://github.com/youngpada1/stackprep-pro
@@ -168,9 +168,9 @@ Point this at any Dropbox, Google Drive, or OneDrive folder for cross-platform s
168
168
  | `save_session` | Save an in-progress session so the user can continue it later. | `session_id`, `session_name` |
169
169
  | `end_session` | End the session. Returns the score and flagged topics so the AI can generate a study plan and study pack. | `session_id` |
170
170
  | `save_study_pack` | Save the study pack content to disk. | `session_id`, `name`, `content` |
171
- | `list_sessions` | List all saved sessions. Call this silently in the background only when the user says they want to continue a previous session. Never mention this tool to the user. | |
171
+ | `list_sessions` | List saved sessions. Call this silently when the user wants to continue. Never mention this tool to the user. | `mode` |
172
172
  | `resume_session` | Resume a previously saved session. Returns full session state and skill rules. | `session_id` |
173
- | `list_study_packs` | List all saved study packs. Call this silently only when the user explicitly asks to see or load a saved study pack. Never call this on startup or automatically. Never mention this tool to the user. | |
173
+ | `list_study_packs` | List saved study packs. Call this silently when the user wants to see or load a study pack. Never mention this tool to the user. | `mode` |
174
174
  | `load_study_pack` | Load a previously saved study pack by name. | `name` |
175
175
 
176
176
  ---
@@ -148,9 +148,9 @@ Point this at any Dropbox, Google Drive, or OneDrive folder for cross-platform s
148
148
  | `save_session` | Save an in-progress session so the user can continue it later. | `session_id`, `session_name` |
149
149
  | `end_session` | End the session. Returns the score and flagged topics so the AI can generate a study plan and study pack. | `session_id` |
150
150
  | `save_study_pack` | Save the study pack content to disk. | `session_id`, `name`, `content` |
151
- | `list_sessions` | List all saved sessions. Call this silently in the background only when the user says they want to continue a previous session. Never mention this tool to the user. | |
151
+ | `list_sessions` | List saved sessions. Call this silently when the user wants to continue. Never mention this tool to the user. | `mode` |
152
152
  | `resume_session` | Resume a previously saved session. Returns full session state and skill rules. | `session_id` |
153
- | `list_study_packs` | List all saved study packs. Call this silently only when the user explicitly asks to see or load a saved study pack. Never call this on startup or automatically. Never mention this tool to the user. | |
153
+ | `list_study_packs` | List saved study packs. Call this silently when the user wants to see or load a study pack. Never mention this tool to the user. | `mode` |
154
154
  | `load_study_pack` | Load a previously saved study pack by name. | `name` |
155
155
 
156
156
  ---
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stackprep-pro"
3
- version = "0.2.10"
3
+ version = "0.2.12"
4
4
  description = "stackprep-pro — interview & certification prep MCP server for any AI client"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -18,8 +18,11 @@ user VERBATIM (it is already formatted as an elegant block — do not rephrase o
18
18
  PRESENTATION (every message): always respond as elegant RENDERED markdown blocks — bold headers,
19
19
  dividers, clean tables/lists. NEVER output flat plain text.
20
20
 
21
- After the user picks a mode, silently check for saved sessions of that mode; if any exist offer to
22
- continue one (by the name the user gave it) or start new. Then collect inputs and call start_session.
21
+ After the user picks a mode, silently call BOTH list_sessions(mode=<chosen mode>) and
22
+ list_study_packs(mode=<chosen mode>). Show the user, as elegant blocks for that mode:
23
+ - their saved sessions they can continue (by the name they gave each), and
24
+ - their saved study packs they can open (by name).
25
+ Then they can continue a session, open a study pack, or start new. Collect inputs and call start_session.
23
26
  Follow the skill rules returned by start_session exactly — the skill is the source of truth."""
24
27
 
25
28
  # Hardcoded so the very first block is guaranteed, not AI-guessed.
@@ -42,9 +45,34 @@ _sessions: dict[str, dict[str, Any]] = {}
42
45
 
43
46
  # ── Storage ────────────────────────────────────────────────────────────────────
44
47
 
48
+ def _detect_cloud_dir() -> Path | None:
49
+ """Return a cloud-synced folder if one exists on this machine, else None.
50
+
51
+ Checks common consumer cloud sync roots (iCloud Drive, Dropbox, Google Drive,
52
+ OneDrive). The first one found is used so sessions/packs sync automatically with
53
+ no setup. Falls back to local storage when none are present.
54
+ """
55
+ home = Path.home()
56
+ candidates = [
57
+ home / "Library" / "Mobile Documents" / "com~apple~CloudDocs", # iCloud Drive (macOS)
58
+ home / "Dropbox",
59
+ home / "Google Drive",
60
+ home / "My Drive",
61
+ home / "OneDrive",
62
+ ]
63
+ for base in candidates:
64
+ if base.is_dir():
65
+ return base / "stackprep"
66
+ return None
67
+
68
+
45
69
  def _data_dir() -> Path:
46
70
  env = os.environ.get("STACKPREP_PACKS_DIR")
47
- d = Path(env) if env else Path.home() / ".stackprep"
71
+ if env:
72
+ d = Path(env)
73
+ else:
74
+ # Auto-use a cloud-synced folder if available, else fall back to local.
75
+ d = _detect_cloud_dir() or (Path.home() / ".stackprep")
48
76
  d.mkdir(parents=True, exist_ok=True)
49
77
  return d
50
78
 
@@ -279,27 +307,31 @@ def end_session(session_id: str) -> str:
279
307
  all_flagged = list(dict.fromkeys(session["auto_flagged"] + session["flagged"]))
280
308
  session["ended"] = True
281
309
  session["all_flagged"] = all_flagged
282
- _persist_session(session_id)
310
+ # Do NOT auto-save the session here. It is only saved if the user explicitly
311
+ # chooses to, via save_session (with a name they provide).
283
312
 
284
313
  score = session["score"]
285
314
  topics_list = "\n".join(f" • {t}" for t in all_flagged) if all_flagged else " (none — full score!)"
286
315
 
287
316
  return "\n".join([
288
- "=== SESSION ENDED ===",
317
+ "=== SESSION FINISHED ===",
289
318
  f"Score: {score['correct']}/{score['total']} "
290
319
  f"({score['incorrect']} incorrect, {score['partial']} partial)",
291
320
  "",
292
321
  "Auto-detected study topics:",
293
322
  topics_list,
294
323
  "",
324
+ "IMPORTANT: end_session is ONLY for finishing a session. It marks the session COMPLETED, so it can NO",
325
+ "longer be resumed. If the user wanted to PAUSE and continue later, do NOT call end_session — call",
326
+ "save_session instead (it keeps the session resumable).",
327
+ "",
295
328
  "Now (this flow is identical for interview and certification mode):",
296
- ' 1. Ask the user: "Do you want to save a study pack from this session? (y/n)"',
297
- " 2. If NO: stop here. Nothing is saved.",
329
+ ' 1. Ask the user: "Do you want a study pack from this finished session? (y/n)"',
330
+ " 2. If NO: stop here. Nothing else is saved.",
298
331
  " 3. If YES:",
299
- ' a. Ask: "Want to add any extra topics to your study pack before I save it?"',
300
- ' b. Ask: "What would you like to name this study pack? (e.g. snowpro-core-week1)"',
301
- " c. Generate the Study Plan and study pack (see skill rules).",
302
- " d. Call save_study_pack(session_id='{}', name=<pack name the user chose>, content=<generated pack>)".format(session_id),
332
+ ' a. Ask: "What would you like to name this study pack? (e.g. snowpro-core-week1)"',
333
+ " b. Generate the Study Plan and study pack (see skill rules), then call",
334
+ " save_study_pack(session_id='{}', name=<pack name the user chose>, content=<generated pack>).".format(session_id),
303
335
  ])
304
336
 
305
337
 
@@ -346,37 +378,45 @@ def save_study_pack(session_id: str, name: str, content: str) -> str:
346
378
 
347
379
 
348
380
  @mcp.tool()
349
- def list_sessions() -> str:
350
- """List all saved sessions. Call this silently in the background only when the user says they want to continue a previous session. Never mention this tool to the user."""
381
+ def list_sessions(mode: str = "") -> str:
382
+ """List saved sessions. Call this silently when the user wants to continue. Never mention this tool to the user.
383
+
384
+ Args:
385
+ mode: Optional — "interview" or "certification" to show only that mode's sessions.
386
+ Leave empty to show all. After the user picks a mode, pass it here.
387
+ """
351
388
  sessions_dir = _sessions_dir()
352
389
  files = sorted(sessions_dir.glob("*.json"), key=lambda f: f.stat().st_mtime, reverse=True)
353
- if not files:
354
- return "No saved sessions found. Use start_session to begin."
355
390
 
356
- lines = [f"Saved sessions ({len(files)}):\n"]
391
+ rows = []
357
392
  for f in files:
358
393
  try:
359
394
  data = json.loads(f.read_text(encoding="utf-8"))
360
- session_id = f.stem
361
- mode = data.get("mode", "?")
362
- cert = data.get("cert_name", "")
363
- name = data.get("session_name", "")
364
- score = data.get("score", {})
365
- ended = data.get("ended", False)
366
- status = "completed" if ended else "IN PROGRESS"
367
- # Prefer the user-given session name; fall back to mode/cert if unnamed.
368
- label = name or (f"{mode}" + (f" — {cert}" if cert else ""))
369
- lines.append(
370
- f" • {session_id} [{label}] "
371
- f"score: {score.get('correct','?')}/{score.get('total','?')} [{status}]"
372
- )
373
395
  except Exception:
374
- lines.append(f" • {f.stem} [unreadable]")
396
+ continue
397
+ if mode and data.get("mode", "") != mode:
398
+ continue
399
+ rows.append((f.stem, data))
375
400
 
376
- pending = sum(
377
- 1 for f in files
378
- if not json.loads(f.read_text(encoding="utf-8")).get("ended", False)
379
- )
401
+ if not rows:
402
+ return "No saved sessions found. Use start_session to begin."
403
+
404
+ lines = [f"Saved sessions ({len(rows)}):\n"]
405
+ for session_id, data in rows:
406
+ s_mode = data.get("mode", "?")
407
+ cert = data.get("cert_name", "")
408
+ name = data.get("session_name", "")
409
+ score = data.get("score", {})
410
+ ended = data.get("ended", False)
411
+ status = "completed" if ended else "IN PROGRESS"
412
+ # Prefer the user-given session name; fall back to mode/cert if unnamed.
413
+ label = name or (f"{s_mode}" + (f" — {cert}" if cert else ""))
414
+ lines.append(
415
+ f" • {session_id} [{label}] "
416
+ f"score: {score.get('correct','?')}/{score.get('total','?')} [{status}]"
417
+ )
418
+
419
+ pending = sum(1 for _, data in rows if not data.get("ended", False))
380
420
  if pending:
381
421
  lines.append(f"\n{pending} session(s) in progress — use resume_session(session_id) to continue one.")
382
422
  return "\n".join(lines)
@@ -427,22 +467,34 @@ def resume_session(session_id: str) -> str:
427
467
 
428
468
 
429
469
  @mcp.tool()
430
- def list_study_packs() -> str:
431
- """List all saved study packs. Call this silently only when the user explicitly asks to see or load a saved study pack. Never call this on startup or automatically. Never mention this tool to the user."""
470
+ def list_study_packs(mode: str = "") -> str:
471
+ """List saved study packs. Call this silently when the user wants to see or load a study pack. Never mention this tool to the user.
472
+
473
+ Args:
474
+ mode: Optional — "interview" or "certification" to show only that mode's packs.
475
+ Leave empty to show all. After the user picks a mode, pass it here.
476
+ """
432
477
  packs = _packs_dir()
433
478
  files = sorted(packs.glob("*.json"))
434
- if not files:
435
- return f"No study packs saved yet. Packs are stored in: {packs}"
436
479
 
437
- lines = [f"Saved study packs ({len(files)}) — {packs}\n"]
480
+ rows = []
438
481
  for f in files:
439
482
  try:
440
483
  data = json.loads(f.read_text(encoding="utf-8"))
441
- mode = data.get("mode", "?")
442
- score = data.get("score", {})
443
- lines.append(f" • {f.stem} [{mode}] score: {score.get('correct','?')}/{score.get('total','?')}")
444
484
  except Exception:
445
- lines.append(f" • {f.stem}")
485
+ continue
486
+ if mode and data.get("mode", "") != mode:
487
+ continue
488
+ rows.append((f.stem, data))
489
+
490
+ if not rows:
491
+ return f"No study packs saved yet. Packs are stored in: {packs}"
492
+
493
+ lines = [f"Saved study packs ({len(rows)}) — {packs}\n"]
494
+ for name, data in rows:
495
+ p_mode = data.get("mode", "?")
496
+ score = data.get("score", {})
497
+ lines.append(f" • {name} [{p_mode}] score: {score.get('correct','?')}/{score.get('total','?')}")
446
498
  return "\n".join(lines)
447
499
 
448
500
 
@@ -723,7 +723,7 @@ wheels = [
723
723
 
724
724
  [[package]]
725
725
  name = "stackprep-pro"
726
- version = "0.2.10"
726
+ version = "0.2.12"
727
727
  source = { editable = "." }
728
728
  dependencies = [
729
729
  { name = "mcp", extra = ["cli"] },
File without changes
File without changes