qcoder 0.5.0a3__tar.gz → 0.5.0a4__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.
Files changed (69) hide show
  1. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/CHANGELOG.md +11 -0
  2. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/PKG-INFO +6 -2
  3. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/README.md +5 -1
  4. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/pyproject.toml +1 -1
  5. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/__init__.py +1 -1
  6. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/cli.py +327 -12
  7. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/pro_preview/client.py +56 -5
  8. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder.egg-info/PKG-INFO +6 -2
  9. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/LICENSE +0 -0
  10. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/MANIFEST.in +0 -0
  11. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/NOTICE +0 -0
  12. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/setup.cfg +0 -0
  13. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/__main__.py +0 -0
  14. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/core/__init__.py +0 -0
  15. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/core/context.py +0 -0
  16. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/core/qasm2/__init__.py +0 -0
  17. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/core/qasm2/adjoint_eligibility.py +0 -0
  18. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/core/qasm2/mirror_build.py +0 -0
  19. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/core/run_config.py +0 -0
  20. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/core/schema.py +0 -0
  21. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/context/__init__.py +0 -0
  22. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/context/bundle.py +0 -0
  23. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/context/markdown.py +0 -0
  24. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/adapters/__init__.py +0 -0
  25. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/adapters/cirq_intake.py +0 -0
  26. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/adapters/pennylane_intake.py +0 -0
  27. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/adapters/qiskit_intake.py +0 -0
  28. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/extractor.py +0 -0
  29. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/features/compute_v0.py +0 -0
  30. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/features/glossary_v0.py +0 -0
  31. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/features/schema_v0.py +0 -0
  32. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/ir.py +0 -0
  33. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/labeling.py +0 -0
  34. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/parsers/__init__.py +0 -0
  35. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/qasm2_regex_parser.py +0 -0
  36. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/reps/cut_profile.py +0 -0
  37. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/reps/depth.py +0 -0
  38. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/reps/entangling_layers.py +0 -0
  39. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/reps/gate_set_stats.py +0 -0
  40. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/reps/interaction_graph.py +0 -0
  41. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/reps/interaction_graph_metrics.py +0 -0
  42. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/feature_extraction/reps/spans.py +0 -0
  43. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/guidance/__init__.py +0 -0
  44. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/guidance/model_pack.py +0 -0
  45. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/guidance/resource.py +0 -0
  46. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/guidance/structural_scores.py +0 -0
  47. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/profiles/__init__.py +0 -0
  48. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/profiles/feature_profiles_v0.py +0 -0
  49. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/review/__init__.py +0 -0
  50. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/review/bundle.py +0 -0
  51. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/review/counts_v0.py +0 -0
  52. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/review/markdown.py +0 -0
  53. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/engines/review/qiskit_counts.py +0 -0
  54. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/model_packs/__init__.py +0 -0
  55. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/model_packs/resource_guidance_local_v0.json +0 -0
  56. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/pipelines/analyze.py +0 -0
  57. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/pipelines/batch.py +0 -0
  58. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/pipelines/context.py +0 -0
  59. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/pipelines/review.py +0 -0
  60. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/pro_preview/__init__.py +0 -0
  61. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/pro_preview/config.py +0 -0
  62. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/pro_preview/errors.py +0 -0
  63. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/pro_preview/manifest.py +0 -0
  64. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder/tools/batch.py +0 -0
  65. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder.egg-info/SOURCES.txt +0 -0
  66. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder.egg-info/dependency_links.txt +0 -0
  67. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder.egg-info/entry_points.txt +0 -0
  68. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder.egg-info/requires.txt +0 -0
  69. {qcoder-0.5.0a3 → qcoder-0.5.0a4}/src/qcoder.egg-info/top_level.txt +0 -0
@@ -1,6 +1,17 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## 0.5.0a4
5
+
6
+ - Add public Student aliases: `qcoder student status` and `qcoder student demo`.
7
+ - Add `qcoder student evidence` for the existing deterministic hosted guided-evidence endpoint.
8
+ - Polish Student CLI output so `status` is access-framed, `demo` is the built-in teaching demo, and `evidence` renders learner-friendly summaries by default.
9
+ - Hide hosted meta fields in default Student output; add `--json` on Student subcommands for intentional raw payload output.
10
+ - Add `QCODER_STUDENT_BASE_URL` / `QCODER_STUDENT_TOKEN` aliases while preserving Preview/Pro env compatibility.
11
+ - Student aliases reuse the hosted Preview client configuration: `QCODER_PREVIEW_BASE_URL` / `QCODER_PREVIEW_TOKEN`, with `QCODER_PRO_API_URL` / `QCODER_PRO_TOKEN` compatibility fallbacks.
12
+ - No service deployment change is included in this public package slice.
13
+ - Existing `qcoder pro preview status` and `qcoder pro preview demo` commands remain compatible.
14
+
4
15
  ## 0.5.0a3
5
16
 
6
17
  - Add public hosted Preview client commands: `qcoder pro preview status` and `qcoder pro preview demo`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qcoder
3
- Version: 0.5.0a3
3
+ Version: 0.5.0a4
4
4
  Summary: Quantum circuit analysis and structured feature extraction tools.
5
5
  Author-email: Quantum Ready Solutions <support@qcoder.ai>
6
6
  Maintainer-email: Quantum Ready Solutions <support@qcoder.ai>
@@ -41,12 +41,13 @@ Free `qcoder` commands run offline and do not call hosted services, upload telem
41
41
  - `qcoder context`
42
42
  - `qcoder review`
43
43
  - `qcoder pro` (Pro bootstrap and client contract; non-confidential local plumbing only)
44
+ - `qcoder student` (Student status/demo/evidence aliases for hosted Preview connectivity checks)
44
45
 
45
46
  ## Pro Preview boundaries
46
47
 
47
48
  Public `qcoder` ships **Free local commands** plus a **Pro bootstrap/client contract**. It is **not** the sellable hosted Pro product.
48
49
 
49
- For a public, pilot-safe walkthrough, see the [Pro Preview pilot walkthrough](https://qcoder.ai/manual/pro-preview-pilot-walkthrough/). The current public PyPI alpha client surface for Pro Preview is **`qcoder==0.5.0a2`**. It is not Pro V0.0 and not a sellable launched Pro product.
50
+ For a public, pilot-safe walkthrough, see the [Pro Preview pilot walkthrough](https://qcoder.ai/manual/pro-preview-pilot-walkthrough/). The current public PyPI alpha client surface for Pro Preview is **`qcoder==0.5.0a4`**. It is not Pro V0.0 and not a sellable launched Pro product.
50
51
 
51
52
  - **Free commands** (`analyze`, `batch`, `context`, `review`) are Apache-2.0, local-first/offline, and useful without Pro. They do not upload data, call a qCoder hosted service, or run QPU/simulator jobs.
52
53
  - **`qcoder pro` bootstrap** (`signup`, `login`, `install`, `status`, `validate`) stores local token/config only. It does not upload circuits, run confidential analysis, or generate Pro cards locally.
@@ -85,6 +86,9 @@ qcoder pro install --token <token-if-provided>
85
86
  qcoder pro status
86
87
  qcoder pro validate
87
88
  qcoder pro workflow --qasm path/to/circuit.qasm --dry-run-manifest pro.workflow.manifest.json
89
+ qcoder student status
90
+ qcoder student demo
91
+ qcoder student evidence
88
92
  ```
89
93
 
90
94
  Use **`--dry-run-manifest`** unless QRS has given you a non-default service URL and token for contract rehearsal. Manifest-only submit (no artifact upload) is opt-in:
@@ -11,12 +11,13 @@ Free `qcoder` commands run offline and do not call hosted services, upload telem
11
11
  - `qcoder context`
12
12
  - `qcoder review`
13
13
  - `qcoder pro` (Pro bootstrap and client contract; non-confidential local plumbing only)
14
+ - `qcoder student` (Student status/demo/evidence aliases for hosted Preview connectivity checks)
14
15
 
15
16
  ## Pro Preview boundaries
16
17
 
17
18
  Public `qcoder` ships **Free local commands** plus a **Pro bootstrap/client contract**. It is **not** the sellable hosted Pro product.
18
19
 
19
- For a public, pilot-safe walkthrough, see the [Pro Preview pilot walkthrough](https://qcoder.ai/manual/pro-preview-pilot-walkthrough/). The current public PyPI alpha client surface for Pro Preview is **`qcoder==0.5.0a2`**. It is not Pro V0.0 and not a sellable launched Pro product.
20
+ For a public, pilot-safe walkthrough, see the [Pro Preview pilot walkthrough](https://qcoder.ai/manual/pro-preview-pilot-walkthrough/). The current public PyPI alpha client surface for Pro Preview is **`qcoder==0.5.0a4`**. It is not Pro V0.0 and not a sellable launched Pro product.
20
21
 
21
22
  - **Free commands** (`analyze`, `batch`, `context`, `review`) are Apache-2.0, local-first/offline, and useful without Pro. They do not upload data, call a qCoder hosted service, or run QPU/simulator jobs.
22
23
  - **`qcoder pro` bootstrap** (`signup`, `login`, `install`, `status`, `validate`) stores local token/config only. It does not upload circuits, run confidential analysis, or generate Pro cards locally.
@@ -55,6 +56,9 @@ qcoder pro install --token <token-if-provided>
55
56
  qcoder pro status
56
57
  qcoder pro validate
57
58
  qcoder pro workflow --qasm path/to/circuit.qasm --dry-run-manifest pro.workflow.manifest.json
59
+ qcoder student status
60
+ qcoder student demo
61
+ qcoder student evidence
58
62
  ```
59
63
 
60
64
  Use **`--dry-run-manifest`** unless QRS has given you a non-default service URL and token for contract rehearsal. Manifest-only submit (no artifact upload) is opt-in:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qcoder"
7
- version = "0.5.0a3"
7
+ version = "0.5.0a4"
8
8
  description = "Quantum circuit analysis and structured feature extraction tools."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  __all__ = []
2
- __version__ = "0.5.0a3"
2
+ __version__ = "0.5.0a4"
3
3
  file = __file__
@@ -20,6 +20,7 @@ from qcoder.pro_preview.client import ProServiceClient, ProServiceClientError
20
20
  from qcoder.pro_preview.client import (
21
21
  PreviewClientNetworkError,
22
22
  call_builtin_review_demo,
23
+ call_student_guided_evidence,
23
24
  resolve_preview_client_config,
24
25
  summarize_demo_payload,
25
26
  )
@@ -254,48 +255,359 @@ def _cmd_review(argv: list[str]) -> int:
254
255
  return 0
255
256
 
256
257
 
257
- def _run_pro_preview_demo_check(*, base_url_override: str | None) -> int:
258
+ def _run_pro_preview_demo_check(
259
+ *,
260
+ base_url_override: str | None,
261
+ label: str = "qCoder Pro Preview demo",
262
+ error_prefix: str = "qcoder pro preview",
263
+ ) -> int:
258
264
  try:
259
- config = resolve_preview_client_config(base_url_override=base_url_override)
265
+ config = resolve_preview_client_config(
266
+ base_url_override=base_url_override,
267
+ include_student_aliases=True,
268
+ )
260
269
  except ValueError as exc:
261
- print(f"qcoder pro preview: {exc}", file=sys.stderr)
270
+ print(f"{error_prefix}: {exc}", file=sys.stderr)
262
271
  return 2
263
272
 
264
273
  try:
265
274
  response = call_builtin_review_demo(config)
266
275
  except PreviewClientNetworkError:
267
276
  print(
268
- "qCoder Pro Preview demo: FAIL (network). Base URL may be unreachable.",
277
+ f"{label}: FAIL (network). Base URL may be unreachable.",
269
278
  file=sys.stderr,
270
279
  )
271
280
  print(f" base_url: {config.base_url}", file=sys.stderr)
272
281
  return 2
273
282
 
274
283
  if response.status_code == 200:
275
- print("qCoder Pro Preview demo: PASS (HTTP 200).")
284
+ print(f"{label}: PASS (HTTP 200).")
276
285
  print(f" base_url: {config.base_url}")
277
286
  for line in summarize_demo_payload(response.payload):
278
287
  print(f" {line}")
279
288
  return 0
280
289
  if response.status_code == 401:
281
290
  print(
282
- "qCoder Pro Preview demo: FAIL (HTTP 401). Token is missing, invalid, or revoked.",
291
+ f"{label}: FAIL (HTTP 401). Token is missing, invalid, or revoked.",
283
292
  file=sys.stderr,
284
293
  )
285
294
  return 1
286
295
  if response.status_code == 403:
287
296
  print(
288
- "qCoder Pro Preview demo: FAIL (HTTP 403). Private/outer service access may be blocking access.",
297
+ f"{label}: FAIL (HTTP 403). Private/outer service access may be blocking access.",
289
298
  file=sys.stderr,
290
299
  )
291
300
  return 1
292
301
 
293
- print(f"qCoder Pro Preview demo: FAIL (HTTP {response.status_code}).", file=sys.stderr)
302
+ print(f"{label}: FAIL (HTTP {response.status_code}).", file=sys.stderr)
294
303
  for line in summarize_demo_payload(response.payload):
295
304
  print(f" {line}", file=sys.stderr)
296
305
  return 2
297
306
 
298
307
 
308
+ def _format_summary_value(value: str | int | float | bool) -> str:
309
+ if isinstance(value, bool):
310
+ return str(value).lower()
311
+ return str(value)
312
+
313
+
314
+ def _print_raw_payload_json(payload: dict[str, object] | None) -> None:
315
+ print(json.dumps(payload or {}, indent=2, sort_keys=True))
316
+
317
+
318
+ def _is_scalar(value: object) -> bool:
319
+ return isinstance(value, (str, int, float, bool))
320
+
321
+
322
+ def _sample_count(payload: dict[str, object] | None) -> int | None:
323
+ if not payload:
324
+ return None
325
+ samples = payload.get("samples")
326
+ if isinstance(samples, list):
327
+ return len(samples)
328
+ sample_count = payload.get("sample_count")
329
+ if isinstance(sample_count, int):
330
+ return sample_count
331
+ return None
332
+
333
+
334
+ def _format_beginner_metrics(value: object) -> str | None:
335
+ if not isinstance(value, dict):
336
+ return None
337
+ parts: list[str] = []
338
+ for key in sorted(value):
339
+ metric = value[key]
340
+ if _is_scalar(metric):
341
+ parts.append(f"{key}={_format_summary_value(metric)}")
342
+ return ", ".join(parts) if parts else None
343
+
344
+
345
+ def _summarize_student_glossary(value: object) -> list[str]:
346
+ lines: list[str] = []
347
+ if isinstance(value, dict):
348
+ for term in sorted(value)[:4]:
349
+ definition = value[term]
350
+ if isinstance(definition, str):
351
+ lines.append(f" {term}: {definition}")
352
+ elif isinstance(definition, dict):
353
+ text = definition.get("definition") or definition.get("summary")
354
+ if isinstance(text, str):
355
+ lines.append(f" {term}: {text}")
356
+ elif isinstance(value, list):
357
+ for item in value[:4]:
358
+ if isinstance(item, dict):
359
+ term = item.get("term") or item.get("name")
360
+ definition = item.get("definition") or item.get("summary")
361
+ if isinstance(term, str) and isinstance(definition, str):
362
+ lines.append(f" {term}: {definition}")
363
+ elif isinstance(item, str):
364
+ lines.append(f" {item}")
365
+ return lines
366
+
367
+
368
+ def _summarize_student_demo_payload(payload: dict[str, object] | None) -> list[str]:
369
+ if not payload:
370
+ return ["summary: built-in teaching demo reached; no summary payload returned"]
371
+ lines: list[str] = []
372
+ summary = payload.get("student_summary")
373
+ if isinstance(summary, str):
374
+ lines.append(f"summary: {summary}")
375
+ elif isinstance(summary, dict):
376
+ for key in ("title", "summary", "next_step"):
377
+ value = summary.get(key)
378
+ if _is_scalar(value):
379
+ lines.append(f"{key}: {_format_summary_value(value)}")
380
+ mode = payload.get("mode")
381
+ if _is_scalar(mode):
382
+ lines.append(f"mode: {_format_summary_value(mode)}")
383
+ count = _sample_count(payload)
384
+ if count is not None:
385
+ lines.append(f"samples: {count}")
386
+ samples = payload.get("samples")
387
+ if isinstance(samples, list):
388
+ for index, sample in enumerate(samples[:2], start=1):
389
+ if isinstance(sample, dict):
390
+ plain_summary = sample.get("plain_summary")
391
+ if isinstance(plain_summary, str):
392
+ lines.append(f"sample {index}: {plain_summary}")
393
+ return lines
394
+
395
+
396
+ def _summarize_student_evidence_payload(payload: dict[str, object] | None) -> list[str]:
397
+ if not payload:
398
+ return ["summary: Student evidence endpoint reached; no summary payload returned"]
399
+ lines: list[str] = []
400
+ summary = payload.get("student_summary")
401
+ if isinstance(summary, str):
402
+ lines.append(f"summary: {summary}")
403
+ elif isinstance(summary, dict):
404
+ for key in ("title", "summary", "what_this_means", "next_step"):
405
+ value = summary.get(key)
406
+ if _is_scalar(value):
407
+ lines.append(f"{key}: {_format_summary_value(value)}")
408
+ anatomy_label = payload.get("anatomy_label")
409
+ if _is_scalar(anatomy_label):
410
+ lines.append(f"anatomy_label: {_format_summary_value(anatomy_label)}")
411
+ count = _sample_count(payload)
412
+ if count is not None:
413
+ lines.append(f"samples: {count}")
414
+ samples = payload.get("samples")
415
+ if isinstance(samples, list):
416
+ for index, sample in enumerate(samples[:3], start=1):
417
+ if not isinstance(sample, dict):
418
+ continue
419
+ plain_summary = sample.get("plain_summary")
420
+ if isinstance(plain_summary, str):
421
+ lines.append(f"sample {index}: {plain_summary}")
422
+ sample_anatomy = sample.get("anatomy_label")
423
+ if _is_scalar(sample_anatomy):
424
+ lines.append(f"sample {index} anatomy: {_format_summary_value(sample_anatomy)}")
425
+ metrics = _format_beginner_metrics(sample.get("beginner_metrics"))
426
+ if metrics:
427
+ lines.append(f"sample {index} beginner_metrics: {metrics}")
428
+ metrics = _format_beginner_metrics(payload.get("beginner_metrics"))
429
+ if metrics:
430
+ lines.append(f"beginner_metrics: {metrics}")
431
+ glossary = _summarize_student_glossary(payload.get("glossary"))
432
+ if glossary:
433
+ lines.append("glossary:")
434
+ lines.extend(glossary)
435
+ return lines
436
+
437
+
438
+ def _run_student_builtin_review_check(
439
+ *,
440
+ base_url_override: str | None,
441
+ mode: str,
442
+ json_output: bool,
443
+ ) -> int:
444
+ try:
445
+ config = resolve_preview_client_config(
446
+ base_url_override=base_url_override,
447
+ include_student_aliases=True,
448
+ )
449
+ except ValueError as exc:
450
+ print(f"qcoder student: {exc}", file=sys.stderr)
451
+ return 2
452
+
453
+ try:
454
+ response = call_builtin_review_demo(config)
455
+ except PreviewClientNetworkError:
456
+ print(
457
+ "qCoder Student: FAIL (network). The service may be unreachable; check your base URL.",
458
+ file=sys.stderr,
459
+ )
460
+ print(f" base_url: {config.base_url}", file=sys.stderr)
461
+ return 2
462
+
463
+ if json_output:
464
+ _print_raw_payload_json(response.payload)
465
+ return 0 if response.status_code == 200 else 1 if response.status_code in {401, 403} else 2
466
+
467
+ if response.status_code == 200:
468
+ if mode == "status":
469
+ print("qCoder Student access: OK")
470
+ print(f" http_status: {response.status_code}")
471
+ print(" service: available")
472
+ count = _sample_count(response.payload)
473
+ if count is not None:
474
+ print(f" teaching_demo_samples: {count}")
475
+ print("Next: try qcoder student demo, then qcoder student evidence.")
476
+ else:
477
+ print("qCoder Student built-in teaching demo: PASS (HTTP 200).")
478
+ for line in _summarize_student_demo_payload(response.payload):
479
+ print(f" {line}")
480
+ return 0
481
+ if response.status_code == 401:
482
+ print(
483
+ "qCoder Student: FAIL (HTTP 401). Your token is missing, invalid, revoked, or lacks Student access.",
484
+ file=sys.stderr,
485
+ )
486
+ return 1
487
+ if response.status_code == 403:
488
+ print(
489
+ "qCoder Student: FAIL (HTTP 403). Your token does not have access to this Student command.",
490
+ file=sys.stderr,
491
+ )
492
+ return 1
493
+
494
+ print(f"qCoder Student: FAIL (HTTP {response.status_code}).", file=sys.stderr)
495
+ if mode == "demo":
496
+ for line in _summarize_student_demo_payload(response.payload):
497
+ print(f" {line}", file=sys.stderr)
498
+ return 2
499
+
500
+
501
+ def _run_student_evidence_check(*, base_url_override: str | None, json_output: bool) -> int:
502
+ try:
503
+ config = resolve_preview_client_config(
504
+ base_url_override=base_url_override,
505
+ include_student_aliases=True,
506
+ )
507
+ except ValueError as exc:
508
+ print(f"qcoder student: {exc}", file=sys.stderr)
509
+ return 2
510
+
511
+ try:
512
+ response = call_student_guided_evidence(config)
513
+ except PreviewClientNetworkError:
514
+ print(
515
+ "qCoder Student evidence: FAIL (network). The service may be unreachable; check your base URL.",
516
+ file=sys.stderr,
517
+ )
518
+ print(f" base_url: {config.base_url}", file=sys.stderr)
519
+ return 2
520
+
521
+ if json_output:
522
+ _print_raw_payload_json(response.payload)
523
+ return 0 if response.status_code == 200 else 1 if response.status_code in {401, 403} else 2
524
+
525
+ if response.status_code == 200:
526
+ print("qCoder Student evidence: PASS (HTTP 200).")
527
+ for line in _summarize_student_evidence_payload(response.payload):
528
+ print(f" {line}")
529
+ return 0
530
+ if response.status_code == 401:
531
+ print(
532
+ "qCoder Student evidence: FAIL (HTTP 401). Your token is missing, invalid, revoked, or lacks Student evidence access.",
533
+ file=sys.stderr,
534
+ )
535
+ return 1
536
+ if response.status_code == 403:
537
+ print(
538
+ "qCoder Student evidence: FAIL (HTTP 403). Your token does not have access to Student evidence.",
539
+ file=sys.stderr,
540
+ )
541
+ return 1
542
+
543
+ print(f"qCoder Student evidence: FAIL (HTTP {response.status_code}).", file=sys.stderr)
544
+ for line in _summarize_student_evidence_payload(response.payload):
545
+ print(f" {line}", file=sys.stderr)
546
+ return 2
547
+
548
+
549
+ def _cmd_student(argv: list[str]) -> int:
550
+ p = argparse.ArgumentParser(
551
+ prog="qcoder student",
552
+ add_help=True,
553
+ description="Hosted Student status/demo/evidence connectivity checks.",
554
+ )
555
+ sub = p.add_subparsers(dest="student_command")
556
+
557
+ p_status = sub.add_parser(
558
+ "status",
559
+ help="Check qCoder Student access and print the next step.",
560
+ )
561
+ p_status.add_argument(
562
+ "--base-url",
563
+ default=None,
564
+ help="Override qCoder Student base URL (default env: QCODER_STUDENT_BASE_URL, QCODER_PREVIEW_BASE_URL, or QCODER_PRO_API_URL).",
565
+ )
566
+ p_status.add_argument("--json", action="store_true", help="Emit raw service payload as JSON.")
567
+ p_status.set_defaults(student_command="status")
568
+
569
+ p_demo = sub.add_parser(
570
+ "demo",
571
+ help="Run the built-in qCoder Student teaching demo.",
572
+ )
573
+ p_demo.add_argument(
574
+ "--base-url",
575
+ default=None,
576
+ help="Override qCoder Student base URL (default env: QCODER_STUDENT_BASE_URL, QCODER_PREVIEW_BASE_URL, or QCODER_PRO_API_URL).",
577
+ )
578
+ p_demo.add_argument("--json", action="store_true", help="Emit raw service payload as JSON.")
579
+ p_demo.set_defaults(student_command="demo")
580
+
581
+ p_evidence = sub.add_parser(
582
+ "evidence",
583
+ help="Call hosted Student guided-evidence endpoint and print safe summary.",
584
+ )
585
+ p_evidence.add_argument(
586
+ "--base-url",
587
+ default=None,
588
+ help="Override qCoder Student base URL (default env: QCODER_STUDENT_BASE_URL, QCODER_PREVIEW_BASE_URL, or QCODER_PRO_API_URL).",
589
+ )
590
+ p_evidence.add_argument("--json", action="store_true", help="Emit raw service payload as JSON.")
591
+ p_evidence.set_defaults(student_command="evidence")
592
+
593
+ args = p.parse_args(argv)
594
+ if args.student_command is None:
595
+ p.print_help()
596
+ return 0
597
+
598
+ if args.student_command in {"status", "demo"}:
599
+ return _run_student_builtin_review_check(
600
+ base_url_override=args.base_url,
601
+ mode=args.student_command,
602
+ json_output=args.json,
603
+ )
604
+ if args.student_command == "evidence":
605
+ return _run_student_evidence_check(base_url_override=args.base_url, json_output=args.json)
606
+
607
+ p.print_help()
608
+ return 0
609
+
610
+
299
611
  def _cmd_pro(argv: list[str]) -> int:
300
612
  p = argparse.ArgumentParser(
301
613
  prog="qcoder pro",
@@ -674,15 +986,16 @@ def _cmd_pro(argv: list[str]) -> int:
674
986
 
675
987
  def _print_root_help() -> None:
676
988
  print(
677
- "usage: qcoder [--version | -V] [-h] {analyze,batch,context,review,pro} ...\n\n"
989
+ "usage: qcoder [--version | -V] [-h] {analyze,batch,context,review,pro,student} ...\n\n"
678
990
  "Quantum circuit analysis CLI.\n\n"
679
991
  "positional arguments:\n"
680
- " {analyze,batch,context,review,pro} subcommand\n\n"
992
+ " {analyze,batch,context,review,pro,student} subcommand\n\n"
681
993
  " analyze Analyze a QASM file (feature extraction + metadata + run config).\n"
682
994
  " batch Batch extract a directory to JSONL (requires --out).\n"
683
995
  " context Build deterministic preflight context artifacts.\n"
684
996
  " review Build deterministic execution review artifacts from counts.\n"
685
- " pro Service-backed Pro shell (signup/install/status/validate/workflow).\n\n"
997
+ " pro Service-backed Pro shell (signup/install/status/validate/workflow).\n"
998
+ " student Hosted Student status/demo/evidence connectivity checks.\n\n"
686
999
  "Run `qcoder <subcommand> --help` for subcommand options.",
687
1000
  end="",
688
1001
  )
@@ -713,9 +1026,11 @@ def main(argv: list[str] | None = None) -> int:
713
1026
  return _cmd_review(rest)
714
1027
  if cmd == "pro":
715
1028
  return _cmd_pro(rest)
1029
+ if cmd == "student":
1030
+ return _cmd_student(rest)
716
1031
 
717
1032
  print(
718
- f"qcoder: unknown subcommand {cmd!r} (expected analyze, batch, context, review, or pro)",
1033
+ f"qcoder: unknown subcommand {cmd!r} (expected analyze, batch, context, review, pro, or student)",
719
1034
  file=sys.stderr,
720
1035
  )
721
1036
  print("Run `qcoder --help` for usage.", file=sys.stderr)
@@ -124,9 +124,12 @@ def _parse_http_error(exc: HTTPError) -> ServiceErrorDetail:
124
124
 
125
125
  PREVIEW_BASE_URL_ENV = "QCODER_PREVIEW_BASE_URL"
126
126
  PREVIEW_TOKEN_ENV = "QCODER_PREVIEW_TOKEN"
127
+ STUDENT_BASE_URL_ENV = "QCODER_STUDENT_BASE_URL"
128
+ STUDENT_TOKEN_ENV = "QCODER_STUDENT_TOKEN"
127
129
  PRO_API_URL_ENV = "QCODER_PRO_API_URL"
128
130
  PRO_TOKEN_ENV = "QCODER_PRO_TOKEN"
129
131
  BUILTIN_REVIEW_PATH = "/v0/demo/builtin-review"
132
+ STUDENT_GUIDED_EVIDENCE_PATH = "/v0/student/guided-evidence"
130
133
 
131
134
 
132
135
  @dataclass(frozen=True)
@@ -146,17 +149,38 @@ class PreviewClientNetworkError(RuntimeError):
146
149
 
147
150
 
148
151
  def resolve_preview_client_config(
149
- *, base_url_override: str | None = None, env_map: Mapping[str, str] | None = None
152
+ *,
153
+ base_url_override: str | None = None,
154
+ env_map: Mapping[str, str] | None = None,
155
+ include_student_aliases: bool = False,
150
156
  ) -> PreviewClientConfig:
151
157
  env = os.environ if env_map is None else env_map
158
+ if include_student_aliases:
159
+ env_base_url = str(
160
+ env.get(STUDENT_BASE_URL_ENV)
161
+ or env.get(PREVIEW_BASE_URL_ENV)
162
+ or env.get(PRO_API_URL_ENV)
163
+ or ""
164
+ )
165
+ token = str(env.get(STUDENT_TOKEN_ENV) or env.get(PREVIEW_TOKEN_ENV) or env.get(PRO_TOKEN_ENV) or "").strip()
166
+ else:
167
+ env_base_url = str(env.get(PREVIEW_BASE_URL_ENV) or env.get(PRO_API_URL_ENV) or "")
168
+ token = str(env.get(PREVIEW_TOKEN_ENV) or env.get(PRO_TOKEN_ENV) or "").strip()
152
169
  base_url = _normalize_base_url(
153
170
  base_url_override
154
171
  if base_url_override is not None
155
- else str(env.get(PREVIEW_BASE_URL_ENV) or env.get(PRO_API_URL_ENV) or "")
172
+ else env_base_url,
173
+ include_student_aliases=include_student_aliases,
156
174
  )
157
- token = str(env.get(PREVIEW_TOKEN_ENV) or env.get(PRO_TOKEN_ENV) or "").strip()
158
175
  if not token:
159
- raise ValueError("missing hosted Preview token; set QCODER_PREVIEW_TOKEN or QCODER_PRO_TOKEN")
176
+ if include_student_aliases:
177
+ raise ValueError(
178
+ "missing qCoder Student token; set QCODER_STUDENT_TOKEN, "
179
+ "QCODER_PREVIEW_TOKEN, or QCODER_PRO_TOKEN"
180
+ )
181
+ raise ValueError(
182
+ "missing hosted Preview token; set QCODER_PREVIEW_TOKEN or QCODER_PRO_TOKEN"
183
+ )
160
184
  return PreviewClientConfig(base_url=base_url, token=token)
161
185
 
162
186
 
@@ -185,6 +209,28 @@ def call_builtin_review_demo(
185
209
  except URLError as exc:
186
210
  raise PreviewClientNetworkError(str(exc)) from exc
187
211
 
212
+
213
+ def call_student_guided_evidence(
214
+ config: PreviewClientConfig, *, timeout_s: float = 10.0
215
+ ) -> PreviewClientResponse:
216
+ """Call the hosted Student guided-evidence endpoint."""
217
+ request = Request(
218
+ _join_service_url(config.base_url, STUDENT_GUIDED_EVIDENCE_PATH),
219
+ headers={"Authorization": f"Bearer {config.token}"},
220
+ method="GET",
221
+ )
222
+ try:
223
+ with urlopen(request, timeout=timeout_s) as response:
224
+ body = response.read().decode("utf-8", errors="replace")
225
+ status_code = int(getattr(response, "status", 200))
226
+ return PreviewClientResponse(status_code=status_code, payload=_safe_json_decode(body))
227
+ except HTTPError as err:
228
+ body = err.read().decode("utf-8", errors="replace")
229
+ return PreviewClientResponse(status_code=int(err.code), payload=_safe_json_decode(body))
230
+ except URLError as exc:
231
+ raise PreviewClientNetworkError(str(exc)) from exc
232
+
233
+
188
234
  def summarize_demo_payload(payload: dict[str, Any] | None) -> list[str]:
189
235
  if not payload:
190
236
  return []
@@ -199,9 +245,14 @@ def summarize_demo_payload(payload: dict[str, Any] | None) -> list[str]:
199
245
  return lines
200
246
 
201
247
 
202
- def _normalize_base_url(raw_base_url: str) -> str:
248
+ def _normalize_base_url(raw_base_url: str, *, include_student_aliases: bool = False) -> str:
203
249
  base_url = raw_base_url.strip().rstrip("/")
204
250
  if not base_url:
251
+ if include_student_aliases:
252
+ raise ValueError(
253
+ "missing qCoder Student base URL; set QCODER_STUDENT_BASE_URL, "
254
+ "QCODER_PREVIEW_BASE_URL, or QCODER_PRO_API_URL, or pass --base-url"
255
+ )
205
256
  raise ValueError(
206
257
  "missing hosted Preview base URL; set QCODER_PREVIEW_BASE_URL or "
207
258
  "QCODER_PRO_API_URL, or pass --base-url"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qcoder
3
- Version: 0.5.0a3
3
+ Version: 0.5.0a4
4
4
  Summary: Quantum circuit analysis and structured feature extraction tools.
5
5
  Author-email: Quantum Ready Solutions <support@qcoder.ai>
6
6
  Maintainer-email: Quantum Ready Solutions <support@qcoder.ai>
@@ -41,12 +41,13 @@ Free `qcoder` commands run offline and do not call hosted services, upload telem
41
41
  - `qcoder context`
42
42
  - `qcoder review`
43
43
  - `qcoder pro` (Pro bootstrap and client contract; non-confidential local plumbing only)
44
+ - `qcoder student` (Student status/demo/evidence aliases for hosted Preview connectivity checks)
44
45
 
45
46
  ## Pro Preview boundaries
46
47
 
47
48
  Public `qcoder` ships **Free local commands** plus a **Pro bootstrap/client contract**. It is **not** the sellable hosted Pro product.
48
49
 
49
- For a public, pilot-safe walkthrough, see the [Pro Preview pilot walkthrough](https://qcoder.ai/manual/pro-preview-pilot-walkthrough/). The current public PyPI alpha client surface for Pro Preview is **`qcoder==0.5.0a2`**. It is not Pro V0.0 and not a sellable launched Pro product.
50
+ For a public, pilot-safe walkthrough, see the [Pro Preview pilot walkthrough](https://qcoder.ai/manual/pro-preview-pilot-walkthrough/). The current public PyPI alpha client surface for Pro Preview is **`qcoder==0.5.0a4`**. It is not Pro V0.0 and not a sellable launched Pro product.
50
51
 
51
52
  - **Free commands** (`analyze`, `batch`, `context`, `review`) are Apache-2.0, local-first/offline, and useful without Pro. They do not upload data, call a qCoder hosted service, or run QPU/simulator jobs.
52
53
  - **`qcoder pro` bootstrap** (`signup`, `login`, `install`, `status`, `validate`) stores local token/config only. It does not upload circuits, run confidential analysis, or generate Pro cards locally.
@@ -85,6 +86,9 @@ qcoder pro install --token <token-if-provided>
85
86
  qcoder pro status
86
87
  qcoder pro validate
87
88
  qcoder pro workflow --qasm path/to/circuit.qasm --dry-run-manifest pro.workflow.manifest.json
89
+ qcoder student status
90
+ qcoder student demo
91
+ qcoder student evidence
88
92
  ```
89
93
 
90
94
  Use **`--dry-run-manifest`** unless QRS has given you a non-default service URL and token for contract rehearsal. Manifest-only submit (no artifact upload) is opt-in:
File without changes
File without changes
File without changes
File without changes