delimit-cli 4.1.25 → 4.1.26

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.
@@ -3133,23 +3133,56 @@ program
3133
3133
  process.exit(1);
3134
3134
  }
3135
3135
 
3136
+ console.log(chalk.gray(' Validating license key...'));
3137
+
3138
+ // Validate against Lemon Squeezy API
3139
+ let validated = false;
3140
+ let licenseId = null;
3141
+ let customerEmail = '';
3142
+ try {
3143
+ const resp = await axios.post('https://api.lemonsqueezy.com/v1/licenses/validate', {
3144
+ license_key: key,
3145
+ }, {
3146
+ headers: { 'Accept': 'application/json' },
3147
+ timeout: 10000,
3148
+ });
3149
+ if (resp.data && resp.data.valid) {
3150
+ validated = true;
3151
+ licenseId = resp.data.license_key?.id;
3152
+ customerEmail = resp.data.meta?.customer_email || '';
3153
+ console.log(chalk.green(' License valid.'));
3154
+ } else {
3155
+ console.log(chalk.red(` License invalid: ${resp.data?.error || 'unknown error'}`));
3156
+ process.exit(1);
3157
+ }
3158
+ } catch (err) {
3159
+ // If API unreachable, accept locally (grace period)
3160
+ console.log(chalk.yellow(' Could not reach license server. Activating locally (7-day grace).'));
3161
+ validated = true;
3162
+ }
3163
+
3136
3164
  // Write license file
3137
3165
  const crypto = require('crypto');
3138
3166
  const machineHash = crypto.createHash('sha256').update(os.homedir()).digest('hex').slice(0, 16);
3139
3167
  const licenseData = {
3140
3168
  key: key,
3141
3169
  tier: 'pro',
3142
- valid: true,
3170
+ valid: validated,
3171
+ license_id: licenseId,
3172
+ customer_email: customerEmail,
3143
3173
  activated_at: Date.now() / 1000,
3144
3174
  machine_hash: machineHash,
3175
+ validated_at: Date.now() / 1000,
3145
3176
  };
3146
3177
 
3147
3178
  if (!fs.existsSync(licenseDir)) {
3148
3179
  fs.mkdirSync(licenseDir, { recursive: true });
3149
3180
  }
3150
3181
  fs.writeFileSync(licensePath, JSON.stringify(licenseData, null, 2));
3151
- console.log(chalk.green('License activated successfully.'));
3152
- console.log(chalk.dim('Tier: pro'));
3182
+ console.log(chalk.green('\n License activated successfully.'));
3183
+ console.log(chalk.dim(` Tier: pro`));
3184
+ if (customerEmail) console.log(chalk.dim(` Email: ${customerEmail}`));
3185
+ console.log('');
3153
3186
  });
3154
3187
 
3155
3188
  // ---------------------------------------------------------------------------
@@ -5756,16 +5756,108 @@ def delimit_quickstart(project_path: str = ".") -> Dict[str, Any]:
5756
5756
  "models": enabled_models,
5757
5757
  })
5758
5758
 
5759
+ # Step 6: First Governance Run -- show value with bundled example specs
5760
+ demo_result: Dict[str, Any] = {"skipped": False}
5761
+ examples_dir = Path(__file__).resolve().parent.parent / "examples"
5762
+ petstore_v1 = examples_dir / "petstore-v1.yaml"
5763
+ petstore_v2 = examples_dir / "petstore-v2.yaml"
5764
+ if petstore_v1.is_file() and petstore_v2.is_file():
5765
+ from backends.gateway_core import run_lint as _qs_run_lint, run_spec_health as _qs_run_spec_health
5766
+
5767
+ # 6a: Lint petstore v1 vs v2 to show breaking change detection
5768
+ try:
5769
+ lint_demo = _qs_run_lint(
5770
+ old_spec=str(petstore_v1),
5771
+ new_spec=str(petstore_v2),
5772
+ )
5773
+ breaking_count = len(lint_demo.get("breaking", lint_demo.get("violations", [])))
5774
+ total_changes = lint_demo.get("total_changes", 0)
5775
+ demo_result["lint"] = {
5776
+ "breaking_changes": breaking_count,
5777
+ "total_changes": total_changes,
5778
+ "status": lint_demo.get("status", "unknown"),
5779
+ "sample_violations": [
5780
+ v.get("message", v.get("type", "unknown"))
5781
+ for v in lint_demo.get("breaking", lint_demo.get("violations", []))[:3]
5782
+ ],
5783
+ }
5784
+ except Exception as e:
5785
+ demo_result["lint"] = {"error": str(e)}
5786
+
5787
+ # 6b: Spec health score on petstore v1
5788
+ try:
5789
+ health_demo = _qs_run_spec_health(spec_path=str(petstore_v1))
5790
+ demo_result["spec_health"] = {
5791
+ "score": health_demo.get("score", health_demo.get("overall_score")),
5792
+ "grade": health_demo.get("grade", health_demo.get("letter_grade")),
5793
+ "dimensions": {
5794
+ k: v for k, v in health_demo.get("dimensions", {}).items()
5795
+ } if health_demo.get("dimensions") else {},
5796
+ "recommendations_count": len(health_demo.get("recommendations", [])),
5797
+ }
5798
+ except Exception as e:
5799
+ demo_result["spec_health"] = {"error": str(e)}
5800
+ else:
5801
+ demo_result["skipped"] = True
5802
+ demo_result["reason"] = "Example specs not found"
5803
+
5804
+ steps_completed.append({
5805
+ "step": 6,
5806
+ "name": "First Governance Run (Demo)",
5807
+ "result": demo_result,
5808
+ })
5809
+
5810
+ # Step 7: Project Spec Discovery -- check if this project has OpenAPI specs
5811
+ project_specs: List[str] = []
5812
+ project_lint_result: Optional[Dict[str, Any]] = None
5813
+ spec_patterns = [
5814
+ "**/openapi.yaml", "**/openapi.yml", "**/openapi.json",
5815
+ "**/swagger.yaml", "**/swagger.yml", "**/swagger.json",
5816
+ ]
5817
+ for pattern in spec_patterns:
5818
+ for match in p.glob(pattern):
5819
+ rel = str(match.relative_to(p))
5820
+ if "node_modules" not in rel and ".next" not in rel and "venv" not in rel:
5821
+ project_specs.append(str(match))
5822
+ project_specs = list(set(project_specs))[:5]
5823
+
5824
+ if project_specs:
5825
+ # Run spec_health on the first discovered spec
5826
+ try:
5827
+ from backends.gateway_core import run_spec_health as _qs_health
5828
+ proj_health = _qs_health(spec_path=project_specs[0])
5829
+ project_lint_result = {
5830
+ "spec": project_specs[0],
5831
+ "score": proj_health.get("score", proj_health.get("overall_score")),
5832
+ "grade": proj_health.get("grade", proj_health.get("letter_grade")),
5833
+ }
5834
+ except Exception as e:
5835
+ project_lint_result = {"spec": project_specs[0], "error": str(e)}
5836
+
5837
+ steps_completed.append({
5838
+ "step": 7,
5839
+ "name": "Project Spec Discovery",
5840
+ "specs_found": len(project_specs),
5841
+ "spec_files": project_specs,
5842
+ "health_result": project_lint_result,
5843
+ })
5844
+
5759
5845
  # Build suggested next actions based on findings
5760
5846
  next_actions = []
5761
- if scan_result.get("findings"):
5762
- for f in scan_result["findings"]:
5763
- if f.get("type") == "openapi_specs":
5764
- next_actions.append("Run `delimit_lint` on your OpenAPI spec to check for breaking changes")
5765
- if f.get("type") == "security_concerns":
5766
- next_actions.append("Run `delimit_security_scan` to audit for vulnerabilities")
5767
- if f.get("type") == "tests_found":
5768
- next_actions.append("Run `delimit_test_smoke` to verify tests pass")
5847
+ if project_specs:
5848
+ next_actions.append(f"Run `delimit_spec_health` on {project_specs[0]} to see your full quality report")
5849
+ if len(project_specs) > 1:
5850
+ next_actions.append(f"You have {len(project_specs)} OpenAPI specs -- run `delimit_lint` to compare versions")
5851
+ next_actions.append("Add the Delimit GitHub Action to catch breaking changes on every PR")
5852
+ else:
5853
+ if scan_result.get("findings"):
5854
+ for f in scan_result["findings"]:
5855
+ if f.get("type") == "openapi_specs":
5856
+ next_actions.append("Run `delimit_lint` on your OpenAPI spec to check for breaking changes")
5857
+ if f.get("type") == "security_concerns":
5858
+ next_actions.append("Run `delimit_security_scan` to audit for vulnerabilities")
5859
+ if f.get("type") == "tests_found":
5860
+ next_actions.append("Run `delimit_test_smoke` to verify tests pass")
5769
5861
 
5770
5862
  if not deliberation_ready:
5771
5863
  next_actions.append("Add more AI models for multi-model deliberation: say 'configure delimit models'")
@@ -5773,16 +5865,54 @@ def delimit_quickstart(project_path: str = ".") -> Dict[str, Any]:
5773
5865
  next_actions.append("Say 'add to ledger: [task]' to start tracking work across sessions")
5774
5866
  next_actions.append("Say 'deliberate [question]' to get AI consensus on a decision")
5775
5867
 
5868
+ # Build the "wow moment" summary
5869
+ wow_moment: Dict[str, Any] = {}
5870
+ lint_data = demo_result.get("lint", {})
5871
+ health_data = demo_result.get("spec_health", {})
5872
+ if lint_data and not lint_data.get("error"):
5873
+ wow_moment["breaking_changes_caught"] = lint_data.get("breaking_changes", 0)
5874
+ wow_moment["total_api_changes"] = lint_data.get("total_changes", 0)
5875
+ wow_moment["sample_catches"] = lint_data.get("sample_violations", [])
5876
+ if health_data and not health_data.get("error"):
5877
+ wow_moment["spec_health_grade"] = health_data.get("grade")
5878
+ wow_moment["spec_health_score"] = health_data.get("score")
5879
+ wow_moment["governance_gates"] = [
5880
+ "Breaking change detection (CI/CD)",
5881
+ "Spec health scoring (quality)",
5882
+ "Policy enforcement (custom rules)",
5883
+ "Semver classification (automated)",
5884
+ "Contract ledger (audit trail)",
5885
+ ]
5886
+ if project_specs:
5887
+ wow_moment["your_project"] = {
5888
+ "specs_found": len(project_specs),
5889
+ "ready_to_govern": True,
5890
+ }
5891
+ if project_lint_result and not project_lint_result.get("error"):
5892
+ wow_moment["your_project"]["health_grade"] = project_lint_result.get("grade")
5893
+ wow_moment["your_project"]["health_score"] = project_lint_result.get("score")
5894
+
5895
+ bc = wow_moment.get("breaking_changes_caught", 0)
5896
+ grade = wow_moment.get("spec_health_grade", "N/A")
5897
+ msg_parts = [
5898
+ f"Quickstart complete! {len(steps_completed)} steps run.",
5899
+ f"Demo: {bc} breaking changes caught, spec health grade: {grade}.",
5900
+ f"5 governance gates ready.",
5901
+ ]
5902
+ if project_specs:
5903
+ msg_parts.append(f"Found {len(project_specs)} OpenAPI spec(s) in your project -- ready to govern.")
5904
+
5776
5905
  return _with_next_steps("quickstart", {
5777
5906
  "tool": "quickstart",
5778
5907
  "status": "complete",
5779
5908
  "project": str(p),
5780
5909
  "steps": steps_completed,
5910
+ "wow_moment": wow_moment,
5781
5911
  "environment": environment,
5782
5912
  "scan_findings": scan_result.get("findings", []),
5783
5913
  "scan_suggestions": scan_result.get("suggestions", []),
5784
5914
  "next_actions": next_actions,
5785
- "message": f"Quickstart complete! {len(steps_completed)} steps run. {len(next_actions)} suggested next actions.",
5915
+ "message": " ".join(msg_parts),
5786
5916
  })
5787
5917
 
5788
5918
 
@@ -6817,7 +6947,7 @@ def delimit_build_loop(action: str = "run", session_id: str = "") -> Dict[str, A
6817
6947
  """Execute the governed continuous build loop (LED-239).
6818
6948
 
6819
6949
  Requirements:
6820
- - root ledger in ~/.delimit is authoritative
6950
+ - root ledger in /root/.delimit is authoritative
6821
6951
  - select only build-safe open items
6822
6952
  - resolve venture + repo before dispatch
6823
6953
  - use Delimit swarm/governance as control plane
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
3
  "mcpName": "io.github.delimit-ai/delimit-mcp-server",
4
- "version": "4.1.25",
4
+ "version": "4.1.26",
5
5
  "description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
6
6
  "main": "index.js",
7
7
  "files": [