@leanspec/cli 0.3.0
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 +267 -0
- package/bin/lean-spec-rust.js +201 -0
- package/bin/lean-spec.js +9 -0
- package/bin/leanspec-rust.js +193 -0
- package/bin/leanspec.js +9 -0
- package/binaries/darwin-arm64/leanspec +0 -0
- package/binaries/darwin-arm64/leanspec-http +0 -0
- package/binaries/darwin-arm64/leanspec-mcp +0 -0
- package/binaries/darwin-arm64/package.json +24 -0
- package/binaries/darwin-arm64/postinstall.js +17 -0
- package/binaries/darwin-x64/leanspec +0 -0
- package/binaries/darwin-x64/leanspec-http +0 -0
- package/binaries/darwin-x64/leanspec-mcp +0 -0
- package/binaries/darwin-x64/package.json +24 -0
- package/binaries/darwin-x64/postinstall.js +17 -0
- package/binaries/linux-x64/leanspec +0 -0
- package/binaries/linux-x64/leanspec-http +0 -0
- package/binaries/linux-x64/leanspec-mcp +0 -0
- package/binaries/linux-x64/package.json +24 -0
- package/binaries/linux-x64/postinstall.js +17 -0
- package/binaries/windows-x64/leanspec-http.exe +0 -0
- package/binaries/windows-x64/leanspec-mcp.exe +0 -0
- package/binaries/windows-x64/leanspec.exe +0 -0
- package/binaries/windows-x64/package.json +24 -0
- package/binaries/windows-x64/postinstall.js +6 -0
- package/package.json +48 -0
- package/templates/detailed/AGENTS-minimal.md +9 -0
- package/templates/detailed/AGENTS.md +114 -0
- package/templates/detailed/README.md +28 -0
- package/templates/detailed/config.json +20 -0
- package/templates/detailed/files/DESIGN.md +43 -0
- package/templates/detailed/files/PLAN.md +59 -0
- package/templates/detailed/files/README.md +30 -0
- package/templates/detailed/files/TEST.md +71 -0
- package/templates/examples/api-refactor/README.md +81 -0
- package/templates/examples/api-refactor/package.json +16 -0
- package/templates/examples/api-refactor/src/app.js +40 -0
- package/templates/examples/api-refactor/src/services/currencyService.js +43 -0
- package/templates/examples/api-refactor/src/services/timezoneService.js +41 -0
- package/templates/examples/api-refactor/src/services/weatherService.js +42 -0
- package/templates/examples/dark-theme/README.md +66 -0
- package/templates/examples/dark-theme/package.json +16 -0
- package/templates/examples/dark-theme/src/public/app.js +277 -0
- package/templates/examples/dark-theme/src/public/index.html +225 -0
- package/templates/examples/dark-theme/src/public/style.css +625 -0
- package/templates/examples/dark-theme/src/server.js +18 -0
- package/templates/examples/dashboard-widgets/README.md +70 -0
- package/templates/examples/dashboard-widgets/index.html +12 -0
- package/templates/examples/dashboard-widgets/package.json +22 -0
- package/templates/examples/dashboard-widgets/src/App.css +20 -0
- package/templates/examples/dashboard-widgets/src/App.jsx +16 -0
- package/templates/examples/dashboard-widgets/src/components/Dashboard.css +17 -0
- package/templates/examples/dashboard-widgets/src/components/Dashboard.jsx +15 -0
- package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.css +23 -0
- package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.jsx +16 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.css +33 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.jsx +28 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.css +24 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.jsx +22 -0
- package/templates/examples/dashboard-widgets/src/index.css +13 -0
- package/templates/examples/dashboard-widgets/src/main.jsx +10 -0
- package/templates/examples/dashboard-widgets/src/utils/mockData.js +30 -0
- package/templates/examples/dashboard-widgets/vite.config.js +6 -0
- package/templates/standard/AGENTS-minimal.md +10 -0
- package/templates/standard/AGENTS.md +114 -0
- package/templates/standard/README.md +25 -0
- package/templates/standard/config.json +18 -0
- package/templates/standard/files/README.md +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# LeanSpec
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://github.com/codervisor/lean-spec-docs/blob/main/static/img/logo-with-bg.svg" alt="LeanSpec Logo" width="120" height="120">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://github.com/codervisor/lean-spec/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/codervisor/lean-spec/ci.yml?branch=main" alt="CI Status"></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/lean-spec"><img src="https://img.shields.io/npm/v/lean-spec.svg" alt="npm version"></a>
|
|
10
|
+
<a href="https://www.npmjs.com/package/lean-spec"><img src="https://img.shields.io/npm/dm/lean-spec.svg" alt="npm downloads"></a>
|
|
11
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://www.lean-spec.dev"><strong>Documentation</strong></a>
|
|
16
|
+
•
|
|
17
|
+
<a href="https://www.lean-spec.dev/zh-Hans/docs/guide/"><strong>中文文档</strong></a>
|
|
18
|
+
•
|
|
19
|
+
<a href="https://web.lean-spec.dev"><strong>Live Examples</strong></a>
|
|
20
|
+
•
|
|
21
|
+
<a href="https://www.lean-spec.dev/docs/tutorials/first-spec-with-ai"><strong>Tutorials</strong></a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
**The tool-agnostic spec framework. Use any spec backend — your workflow, your rules.**
|
|
27
|
+
|
|
28
|
+
LeanSpec is a spec coding framework that works with whatever spec workflow you already use. GitHub Issues for personal projects, ADO Work Items for enterprise, Jira, Linear, or plain markdown — LeanSpec provides the unified interface, AI integration, and intelligence layer on top.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Markdown specs (default — works out of the box)
|
|
36
|
+
npm install -g @leanspec/cli && leanspec init
|
|
37
|
+
|
|
38
|
+
# Or try with a tutorial project
|
|
39
|
+
npx -p @leanspec/cli leanspec init --example dark-theme
|
|
40
|
+
cd dark-theme && npm install && npm start
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Configure your spec backend:**
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
# leanspec.provider.yaml
|
|
47
|
+
|
|
48
|
+
# Option 1: Markdown files (default, zero config)
|
|
49
|
+
provider: markdown
|
|
50
|
+
directory: specs
|
|
51
|
+
|
|
52
|
+
# Option 2: GitHub Issues as specs
|
|
53
|
+
# provider: github
|
|
54
|
+
# owner: myuser
|
|
55
|
+
# repo: myproject
|
|
56
|
+
|
|
57
|
+
# Option 3: Azure DevOps Work Items as specs
|
|
58
|
+
# provider: ado
|
|
59
|
+
# organization: mycompany
|
|
60
|
+
# project: myproject
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Visualize your project (works with any backend):**
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
leanspec board # Kanban view
|
|
67
|
+
leanspec stats # Project metrics
|
|
68
|
+
leanspec ui # Web UI at localhost:3000
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Next:** [Your First Spec with AI](https://www.lean-spec.dev/docs/tutorials/first-spec-with-ai) (10 min tutorial)
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Why LeanSpec?
|
|
76
|
+
|
|
77
|
+
**Your workflow, not ours.** Other SDD frameworks force you to adopt their spec format and tooling. LeanSpec adapts to whatever you already use:
|
|
78
|
+
|
|
79
|
+
- **Tool-agnostic** - GitHub Issues, ADO, Jira, Linear, Notion, or plain markdown
|
|
80
|
+
- **One interface** - Same CLI, MCP, and UI regardless of backend
|
|
81
|
+
- **AI-native** - Structured spec data for any AI coding assistant
|
|
82
|
+
- **Fast iteration** - Living documents that grow with your code
|
|
83
|
+
- **Context economy** - Small specs (<2K tokens) = better AI output
|
|
84
|
+
|
|
85
|
+
📖 [Compare with Spec Kit, OpenSpec, Kiro →](https://www.lean-spec.dev/docs/guide/why-leanspec)
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## AI Integration
|
|
90
|
+
|
|
91
|
+
Works with any AI coding assistant via MCP or CLI:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"leanspec": { "command": "npx", "args": ["@leanspec/mcp"] }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Compatible with:** VS Code Copilot, Claude Code, Gemini CLI, Cursor, Windsurf, Kiro CLI, Kimi CLI, Qodo CLI, Amp, Trae Agent, Qwen Code, Droid, and more.
|
|
102
|
+
|
|
103
|
+
📖 [Full AI integration guide →](https://www.lean-spec.dev/docs/guide/usage/ai-coding-workflow)
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Spec Providers
|
|
108
|
+
|
|
109
|
+
LeanSpec connects to your existing spec workflow through a provider architecture:
|
|
110
|
+
|
|
111
|
+
| Provider | Backend | Status |
|
|
112
|
+
|----------|---------|--------|
|
|
113
|
+
| `markdown` | Local `specs/` directory (default) | **Available** |
|
|
114
|
+
| `github` | GitHub Issues + Projects | Planned |
|
|
115
|
+
| `ado` | Azure DevOps Work Items | Planned |
|
|
116
|
+
| `jira` | Jira tickets | Future |
|
|
117
|
+
| `linear` | Linear issues | Future |
|
|
118
|
+
|
|
119
|
+
Core LeanSpec concepts map naturally to each backend:
|
|
120
|
+
|
|
121
|
+
| Concept | GitHub Issues | ADO Work Items | Markdown |
|
|
122
|
+
|---------|--------------|----------------|----------|
|
|
123
|
+
| Spec ID | Issue number | Work Item ID | Directory name |
|
|
124
|
+
| Status | Labels | State field | Frontmatter |
|
|
125
|
+
| Priority | Labels | Priority field | Frontmatter |
|
|
126
|
+
| Tags | Labels | Tags | Frontmatter |
|
|
127
|
+
| Assignee | Assignees | Assigned To | Frontmatter |
|
|
128
|
+
| Content | Issue body | Description | Markdown body |
|
|
129
|
+
|
|
130
|
+
📖 [Provider architecture →](https://www.lean-spec.dev/docs/guide/providers)
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Agent Skills
|
|
135
|
+
|
|
136
|
+
Teach your AI assistant the Spec-Driven Development methodology:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Install the leanspec skill
|
|
140
|
+
npx skills add codervisor/lean-spec@leanspec
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This installs the **leanspec** skill which teaches AI agents:
|
|
144
|
+
- When to create specs vs. implement directly
|
|
145
|
+
- How to discover existing specs before creating new ones
|
|
146
|
+
- Best practices for context economy and progressive disclosure
|
|
147
|
+
- Complete SDD workflow (Discover → Design → Implement → Validate)
|
|
148
|
+
|
|
149
|
+
**Compatible with:** Claude Code, Cursor, Windsurf, GitHub Copilot, and other [Agent Skills](https://skills.sh/) compatible tools.
|
|
150
|
+
|
|
151
|
+
📖 [Skill source →](skills/leanspec/SKILL.md)
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Features
|
|
156
|
+
|
|
157
|
+
| Feature | Description |
|
|
158
|
+
| ------------------- | ------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| **📊 Kanban Board** | `leanspec board` - visual project tracking |
|
|
160
|
+
| **🔍 Smart Search** | `leanspec search` - find specs by content or metadata |
|
|
161
|
+
| **🔗 Dependencies** | Track spec relationships with `depends_on` and `related` |
|
|
162
|
+
| **🎨 Web UI** | `leanspec ui` - browser-based dashboard |
|
|
163
|
+
| **📈 Project Stats** | `leanspec stats` - health metrics and bottleneck detection |
|
|
164
|
+
| **🤖 AI-Native** | MCP server + CLI for AI assistants |
|
|
165
|
+
| **🖥️ Desktop App** | Desktop app repo: [codervisor/lean-spec-desktop](https://github.com/codervisor/lean-spec-desktop) |
|
|
166
|
+
|
|
167
|
+
<p align="center">
|
|
168
|
+
<img src="https://github.com/codervisor/lean-spec-docs/blob/main/static/img/ui/ui-board-view.png" alt="Kanban Board View" width="800">
|
|
169
|
+
</p>
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Requirements
|
|
174
|
+
|
|
175
|
+
### Runtime
|
|
176
|
+
- **Node.js**: `>= 20.0.0`
|
|
177
|
+
- **pnpm**: `>= 10.0.0` (preferred package manager)
|
|
178
|
+
|
|
179
|
+
### Development
|
|
180
|
+
- **Node.js**: `>= 20.0.0`
|
|
181
|
+
- **Rust**: `>= 1.70` (for building CLI/MCP/HTTP binaries)
|
|
182
|
+
- **pnpm**: `>= 10.0.0`
|
|
183
|
+
|
|
184
|
+
**Quick Check:**
|
|
185
|
+
```bash
|
|
186
|
+
node --version # Should be v20.0.0 or higher
|
|
187
|
+
pnpm --version # Should be 10.0.0 or higher
|
|
188
|
+
rustc --version # Should be 1.70 or higher (dev only)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Desktop App
|
|
194
|
+
|
|
195
|
+
The desktop application has moved to a dedicated repository:
|
|
196
|
+
|
|
197
|
+
- https://github.com/codervisor/lean-spec-desktop
|
|
198
|
+
|
|
199
|
+
Use that repository for desktop development, CI, and release workflows.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Developer Workflow
|
|
204
|
+
|
|
205
|
+
Common development tasks using `pnpm`:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Development
|
|
209
|
+
pnpm install # Install dependencies
|
|
210
|
+
pnpm build # Build all packages
|
|
211
|
+
pnpm dev # Start dev mode (UI + Core)
|
|
212
|
+
pnpm dev:web # UI only
|
|
213
|
+
pnpm dev:cli # CLI only
|
|
214
|
+
|
|
215
|
+
# Testing
|
|
216
|
+
pnpm test # Run all tests
|
|
217
|
+
pnpm test:ui # Tests with UI
|
|
218
|
+
pnpm test:coverage # Coverage report
|
|
219
|
+
pnpm typecheck # Type check all packages
|
|
220
|
+
|
|
221
|
+
# Rust
|
|
222
|
+
pnpm rust:build # Build Rust packages (release)
|
|
223
|
+
pnpm rust:build:dev # Build Rust (dev, faster)
|
|
224
|
+
pnpm rust:test # Run Rust tests
|
|
225
|
+
pnpm rust:check # Quick Rust check
|
|
226
|
+
pnpm rust:clippy # Rust linting
|
|
227
|
+
pnpm rust:fmt # Format Rust code
|
|
228
|
+
|
|
229
|
+
# CLI (run locally)
|
|
230
|
+
pnpm cli board # Show spec board
|
|
231
|
+
pnpm cli list # List specs
|
|
232
|
+
pnpm cli create my-feat # Create new spec
|
|
233
|
+
pnpm cli validate # Validate specs
|
|
234
|
+
|
|
235
|
+
# Documentation
|
|
236
|
+
pnpm docs:dev # Start docs site
|
|
237
|
+
pnpm docs:build # Build docs
|
|
238
|
+
|
|
239
|
+
# Release
|
|
240
|
+
pnpm pre-release # Run all pre-release checks
|
|
241
|
+
pnpm prepare-publish # Prepare for npm publish
|
|
242
|
+
pnpm restore-packages # Restore after publish
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
See [package.json](package.json) for all available scripts.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Documentation
|
|
250
|
+
|
|
251
|
+
📖 [Full Documentation](https://www.lean-spec.dev) · [CLI Reference](https://www.lean-spec.dev/docs/reference/cli) · [First Principles](https://www.lean-spec.dev/docs/advanced/first-principles) · [FAQ](https://www.lean-spec.dev/docs/faq) · [中文文档](https://www.lean-spec.dev/zh-Hans/)
|
|
252
|
+
|
|
253
|
+
## Community
|
|
254
|
+
|
|
255
|
+
💬 [Discussions](https://github.com/codervisor/lean-spec/discussions) · 🐛 [Issues](https://github.com/codervisor/lean-spec/issues) · 🤝 [Contributing](CONTRIBUTING.md) · 📋 [Changelog](CHANGELOG.md) · 📄 [LICENSE](LICENSE)
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### Contact Me | 联系我
|
|
260
|
+
|
|
261
|
+
If you find LeanSpec helpful, feel free to add me on WeChat (note "LeanSpec") to join the discussion group.
|
|
262
|
+
|
|
263
|
+
如果您觉得 LeanSpec 对您有帮助,欢迎添加微信(备注 "LeanSpec")加入交流群。
|
|
264
|
+
|
|
265
|
+
<p align="center">
|
|
266
|
+
<img src="https://github.com/codervisor/lean-spec-docs/blob/main/static/img/qr-code.png" alt="WeChat Contact | 微信联系" height="280">
|
|
267
|
+
</p>
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* LeanSpec CLI Binary Wrapper
|
|
4
|
+
*
|
|
5
|
+
* This script detects the current platform and architecture,
|
|
6
|
+
* then spawns the appropriate Rust binary with the provided arguments.
|
|
7
|
+
*
|
|
8
|
+
* The wrapper looks for binaries in the following locations:
|
|
9
|
+
* 1. Rust target/debug binary (for local development)
|
|
10
|
+
* 2. Rust target/release binary (for local development)
|
|
11
|
+
* 3. Platform-specific npm package (lean-spec-darwin-x64, etc.)
|
|
12
|
+
* 4. Local binaries directory (fallback)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawn } from 'child_process';
|
|
16
|
+
import { createRequire } from 'module';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
import { dirname, join } from 'path';
|
|
19
|
+
import { accessSync, openSync, readSync, closeSync } from 'fs';
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = dirname(__filename);
|
|
24
|
+
|
|
25
|
+
// Debug mode - enable with LEANSPEC_DEBUG=1
|
|
26
|
+
const DEBUG = process.env.LEANSPEC_DEBUG === '1';
|
|
27
|
+
const debug = (...args) => DEBUG && console.error('[lean-spec debug]', ...args);
|
|
28
|
+
|
|
29
|
+
// Platform detection mapping
|
|
30
|
+
const PLATFORM_MAP = {
|
|
31
|
+
darwin: { x64: 'darwin-x64', arm64: 'darwin-arm64' },
|
|
32
|
+
linux: { x64: 'linux-x64' },
|
|
33
|
+
win32: { x64: 'windows-x64' }
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const MACHO_MAGICS = new Set([
|
|
37
|
+
0xfeedface,
|
|
38
|
+
0xfeedfacf,
|
|
39
|
+
0xcefaedfe,
|
|
40
|
+
0xcffaedfe,
|
|
41
|
+
0xcafebabe,
|
|
42
|
+
0xbebafeca,
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
function readHeaderBytes(filePath) {
|
|
46
|
+
const fd = openSync(filePath, 'r');
|
|
47
|
+
try {
|
|
48
|
+
const buffer = Buffer.alloc(4);
|
|
49
|
+
const bytesRead = readSync(fd, buffer, 0, 4, 0);
|
|
50
|
+
return bytesRead === 4 ? buffer : null;
|
|
51
|
+
} finally {
|
|
52
|
+
closeSync(fd);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isValidBinaryHeader(filePath, platform) {
|
|
57
|
+
try {
|
|
58
|
+
const header = readHeaderBytes(filePath);
|
|
59
|
+
if (!header) return false;
|
|
60
|
+
|
|
61
|
+
if (platform === 'linux') {
|
|
62
|
+
return header[0] === 0x7f && header[1] === 0x45 && header[2] === 0x4c && header[3] === 0x46;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (platform === 'win32') {
|
|
66
|
+
return header[0] === 0x4d && header[1] === 0x5a;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (platform === 'darwin') {
|
|
70
|
+
const magicBE = header.readUInt32BE(0);
|
|
71
|
+
const magicLE = header.readUInt32LE(0);
|
|
72
|
+
return MACHO_MAGICS.has(magicBE) || MACHO_MAGICS.has(magicLE);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return false;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
debug('Failed to read binary header:', error.message);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isExecutableBinary(filePath, platform) {
|
|
83
|
+
if (!isValidBinaryHeader(filePath, platform)) {
|
|
84
|
+
debug('Invalid binary header:', filePath);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getBinaryPath() {
|
|
91
|
+
const platform = process.platform;
|
|
92
|
+
const arch = process.arch;
|
|
93
|
+
|
|
94
|
+
debug('Platform detection:', { platform, arch });
|
|
95
|
+
|
|
96
|
+
const platformKey = PLATFORM_MAP[platform]?.[arch];
|
|
97
|
+
if (!platformKey) {
|
|
98
|
+
console.error(`Unsupported platform: ${platform}-${arch}`);
|
|
99
|
+
console.error('Supported: macOS (x64/arm64), Linux (x64), Windows (x64)');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const isWindows = platform === 'win32';
|
|
104
|
+
const binaryName = isWindows ? 'lean-spec.exe' : 'lean-spec';
|
|
105
|
+
const packageName = `@leanspec/cli-${platformKey}`;
|
|
106
|
+
|
|
107
|
+
debug('Binary info:', { platformKey, binaryName, packageName });
|
|
108
|
+
|
|
109
|
+
// Try rust/target/debug directory first (for local development with `pnpm build:rust`)
|
|
110
|
+
try {
|
|
111
|
+
const rustDebugPath = join(__dirname, '..', '..', '..', 'rust', 'target', 'debug', binaryName);
|
|
112
|
+
debug('Trying rust debug binary:', rustDebugPath);
|
|
113
|
+
accessSync(rustDebugPath);
|
|
114
|
+
if (isExecutableBinary(rustDebugPath, platform)) {
|
|
115
|
+
debug('Found rust debug binary:', rustDebugPath);
|
|
116
|
+
return rustDebugPath;
|
|
117
|
+
}
|
|
118
|
+
debug('Rust debug binary is invalid:', rustDebugPath);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
debug('Rust debug binary not found:', e.message);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Try rust/target/release directory (for local development with `pnpm build:rust:release`)
|
|
124
|
+
try {
|
|
125
|
+
const rustTargetPath = join(__dirname, '..', '..', '..', 'rust', 'target', 'release', binaryName);
|
|
126
|
+
debug('Trying rust release binary:', rustTargetPath);
|
|
127
|
+
accessSync(rustTargetPath);
|
|
128
|
+
if (isExecutableBinary(rustTargetPath, platform)) {
|
|
129
|
+
debug('Found rust release binary:', rustTargetPath);
|
|
130
|
+
return rustTargetPath;
|
|
131
|
+
}
|
|
132
|
+
debug('Rust release binary is invalid:', rustTargetPath);
|
|
133
|
+
} catch (e) {
|
|
134
|
+
debug('Rust release binary not found:', e.message);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Try to resolve platform package
|
|
138
|
+
try {
|
|
139
|
+
const resolvedPath = require.resolve(`${packageName}/${binaryName}`);
|
|
140
|
+
if (isExecutableBinary(resolvedPath, platform)) {
|
|
141
|
+
debug('Found platform package binary:', resolvedPath);
|
|
142
|
+
return resolvedPath;
|
|
143
|
+
}
|
|
144
|
+
debug('Platform package binary is invalid:', resolvedPath);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
debug('Platform package not found:', packageName, '-', e.message);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Try local binaries directory (fallback)
|
|
150
|
+
try {
|
|
151
|
+
const localPath = join(__dirname, '..', 'binaries', platformKey, binaryName);
|
|
152
|
+
debug('Trying local binary:', localPath);
|
|
153
|
+
accessSync(localPath);
|
|
154
|
+
if (isExecutableBinary(localPath, platform)) {
|
|
155
|
+
debug('Found local binary:', localPath);
|
|
156
|
+
return localPath;
|
|
157
|
+
}
|
|
158
|
+
debug('Local binary is invalid:', localPath);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
debug('Local binary not found:', e.message);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.error(`Binary not found for ${platform}-${arch}`);
|
|
164
|
+
console.error(`Expected package: ${packageName}`);
|
|
165
|
+
console.error('');
|
|
166
|
+
console.error('Detected missing or corrupted binary.');
|
|
167
|
+
console.error('If you installed globally, reinstall to restore the binary:');
|
|
168
|
+
console.error(' npm uninstall -g lean-spec && npm install -g lean-spec');
|
|
169
|
+
console.error('');
|
|
170
|
+
console.error('If your npm config omits optional dependencies, enable them and reinstall.');
|
|
171
|
+
console.error('');
|
|
172
|
+
console.error('To install:');
|
|
173
|
+
console.error(' npm install -g lean-spec');
|
|
174
|
+
console.error('');
|
|
175
|
+
console.error('If you installed globally, try:');
|
|
176
|
+
console.error(' npm uninstall -g lean-spec && npm install -g lean-spec');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Execute binary
|
|
181
|
+
const binaryPath = getBinaryPath();
|
|
182
|
+
const args = process.argv.slice(2);
|
|
183
|
+
|
|
184
|
+
debug('Spawning binary:', binaryPath);
|
|
185
|
+
debug('Arguments:', args);
|
|
186
|
+
|
|
187
|
+
const child = spawn(binaryPath, args, {
|
|
188
|
+
stdio: 'inherit',
|
|
189
|
+
windowsHide: true,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
child.on('exit', (code) => {
|
|
193
|
+
debug('Binary exited with code:', code);
|
|
194
|
+
process.exit(code ?? 1);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
child.on('error', (err) => {
|
|
198
|
+
console.error('Failed to start lean-spec:', err.message);
|
|
199
|
+
debug('Spawn error:', err);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|
package/bin/lean-spec.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* LeanSpec CLI (deprecated entry point)
|
|
4
|
+
*
|
|
5
|
+
* The `lean-spec` command has been renamed to `leanspec`.
|
|
6
|
+
* This wrapper shows a deprecation notice and delegates to the new entry point.
|
|
7
|
+
*/
|
|
8
|
+
console.error('\x1b[33m⚠ "lean-spec" is deprecated. Use "leanspec" instead.\x1b[0m');
|
|
9
|
+
import './leanspec-rust.js';
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* LeanSpec CLI Binary Wrapper
|
|
4
|
+
*
|
|
5
|
+
* This script detects the current platform and architecture,
|
|
6
|
+
* then spawns the appropriate Rust binary with the provided arguments.
|
|
7
|
+
*
|
|
8
|
+
* The wrapper looks for binaries in the following locations:
|
|
9
|
+
* 1. Rust target/debug binary (for local development)
|
|
10
|
+
* 2. Rust target/release binary (for local development)
|
|
11
|
+
* 3. Platform-specific npm package (leanspec-darwin-x64, etc.)
|
|
12
|
+
* 4. Local binaries directory (fallback)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawn } from 'child_process';
|
|
16
|
+
import { createRequire } from 'module';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
import { dirname, join } from 'path';
|
|
19
|
+
import { accessSync, openSync, readSync, closeSync } from 'fs';
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = dirname(__filename);
|
|
24
|
+
|
|
25
|
+
// Debug mode - enable with LEANSPEC_DEBUG=1
|
|
26
|
+
const DEBUG = process.env.LEANSPEC_DEBUG === '1';
|
|
27
|
+
const debug = (...args) => DEBUG && console.error('[leanspec debug]', ...args);
|
|
28
|
+
|
|
29
|
+
// Platform detection mapping
|
|
30
|
+
const PLATFORM_MAP = {
|
|
31
|
+
darwin: { x64: 'darwin-x64', arm64: 'darwin-arm64' },
|
|
32
|
+
linux: { x64: 'linux-x64' },
|
|
33
|
+
win32: { x64: 'windows-x64' }
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const MACHO_MAGICS = new Set([
|
|
37
|
+
0xfeedface,
|
|
38
|
+
0xfeedfacf,
|
|
39
|
+
0xcefaedfe,
|
|
40
|
+
0xcffaedfe,
|
|
41
|
+
0xcafebabe,
|
|
42
|
+
0xbebafeca,
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
function readHeaderBytes(filePath) {
|
|
46
|
+
const fd = openSync(filePath, 'r');
|
|
47
|
+
try {
|
|
48
|
+
const buffer = Buffer.alloc(4);
|
|
49
|
+
const bytesRead = readSync(fd, buffer, 0, 4, 0);
|
|
50
|
+
return bytesRead === 4 ? buffer : null;
|
|
51
|
+
} finally {
|
|
52
|
+
closeSync(fd);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isValidBinaryHeader(filePath, platform) {
|
|
57
|
+
try {
|
|
58
|
+
const header = readHeaderBytes(filePath);
|
|
59
|
+
if (!header) return false;
|
|
60
|
+
|
|
61
|
+
if (platform === 'linux') {
|
|
62
|
+
return header[0] === 0x7f && header[1] === 0x45 && header[2] === 0x4c && header[3] === 0x46;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (platform === 'win32') {
|
|
66
|
+
return header[0] === 0x4d && header[1] === 0x5a;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (platform === 'darwin') {
|
|
70
|
+
const magicBE = header.readUInt32BE(0);
|
|
71
|
+
const magicLE = header.readUInt32LE(0);
|
|
72
|
+
return MACHO_MAGICS.has(magicBE) || MACHO_MAGICS.has(magicLE);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return false;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
debug('Failed to read binary header:', error.message);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isExecutableBinary(filePath, platform) {
|
|
83
|
+
if (!isValidBinaryHeader(filePath, platform)) {
|
|
84
|
+
debug('Invalid binary header:', filePath);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getBinaryPath() {
|
|
91
|
+
const platform = process.platform;
|
|
92
|
+
const arch = process.arch;
|
|
93
|
+
|
|
94
|
+
debug('Platform detection:', { platform, arch });
|
|
95
|
+
|
|
96
|
+
const platformKey = PLATFORM_MAP[platform]?.[arch];
|
|
97
|
+
if (!platformKey) {
|
|
98
|
+
console.error(`Unsupported platform: ${platform}-${arch}`);
|
|
99
|
+
console.error('Supported: macOS (x64/arm64), Linux (x64), Windows (x64)');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const isWindows = platform === 'win32';
|
|
104
|
+
const binaryName = isWindows ? 'leanspec.exe' : 'leanspec';
|
|
105
|
+
const legacyBinaryName = isWindows ? 'lean-spec.exe' : 'lean-spec';
|
|
106
|
+
const packageName = `@leanspec/cli-${platformKey}`;
|
|
107
|
+
|
|
108
|
+
debug('Binary info:', { platformKey, binaryName, packageName });
|
|
109
|
+
|
|
110
|
+
// Try rust/target/debug directory first (for local development with `pnpm build:rust`)
|
|
111
|
+
for (const targetDir of ['debug', 'release']) {
|
|
112
|
+
for (const name of [binaryName, legacyBinaryName]) {
|
|
113
|
+
try {
|
|
114
|
+
const rustPath = join(__dirname, '..', '..', '..', 'rust', 'target', targetDir, name);
|
|
115
|
+
debug(`Trying rust ${targetDir} binary:`, rustPath);
|
|
116
|
+
accessSync(rustPath);
|
|
117
|
+
if (isExecutableBinary(rustPath, platform)) {
|
|
118
|
+
debug(`Found rust ${targetDir} binary:`, rustPath);
|
|
119
|
+
return rustPath;
|
|
120
|
+
}
|
|
121
|
+
debug(`Rust ${targetDir} binary is invalid:`, rustPath);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
debug(`Rust ${targetDir} binary not found:`, e.message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Try to resolve platform package (try new name first, then legacy)
|
|
129
|
+
for (const name of [binaryName, legacyBinaryName]) {
|
|
130
|
+
try {
|
|
131
|
+
const resolvedPath = require.resolve(`${packageName}/${name}`);
|
|
132
|
+
if (isExecutableBinary(resolvedPath, platform)) {
|
|
133
|
+
debug('Found platform package binary:', resolvedPath);
|
|
134
|
+
return resolvedPath;
|
|
135
|
+
}
|
|
136
|
+
debug('Platform package binary is invalid:', resolvedPath);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
debug('Platform package not found:', packageName, name, '-', e.message);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Try local binaries directory (fallback, try new name first then legacy)
|
|
143
|
+
for (const name of [binaryName, legacyBinaryName]) {
|
|
144
|
+
try {
|
|
145
|
+
const localPath = join(__dirname, '..', 'binaries', platformKey, name);
|
|
146
|
+
debug('Trying local binary:', localPath);
|
|
147
|
+
accessSync(localPath);
|
|
148
|
+
if (isExecutableBinary(localPath, platform)) {
|
|
149
|
+
debug('Found local binary:', localPath);
|
|
150
|
+
return localPath;
|
|
151
|
+
}
|
|
152
|
+
debug('Local binary is invalid:', localPath);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
debug('Local binary not found:', e.message);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.error(`Binary not found for ${platform}-${arch}`);
|
|
159
|
+
console.error(`Expected package: ${packageName}`);
|
|
160
|
+
console.error('');
|
|
161
|
+
console.error('Detected missing or corrupted binary.');
|
|
162
|
+
console.error('If you installed globally, reinstall to restore the binary:');
|
|
163
|
+
console.error(' npm uninstall -g leanspec && npm install -g leanspec');
|
|
164
|
+
console.error('');
|
|
165
|
+
console.error('If your npm config omits optional dependencies, enable them and reinstall.');
|
|
166
|
+
console.error('');
|
|
167
|
+
console.error('To install:');
|
|
168
|
+
console.error(' npm install -g leanspec');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Execute binary
|
|
173
|
+
const binaryPath = getBinaryPath();
|
|
174
|
+
const args = process.argv.slice(2);
|
|
175
|
+
|
|
176
|
+
debug('Spawning binary:', binaryPath);
|
|
177
|
+
debug('Arguments:', args);
|
|
178
|
+
|
|
179
|
+
const child = spawn(binaryPath, args, {
|
|
180
|
+
stdio: 'inherit',
|
|
181
|
+
windowsHide: true,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
child.on('exit', (code) => {
|
|
185
|
+
debug('Binary exited with code:', code);
|
|
186
|
+
process.exit(code ?? 1);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
child.on('error', (err) => {
|
|
190
|
+
console.error('Failed to start leanspec:', err.message);
|
|
191
|
+
debug('Spawn error:', err);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
});
|
package/bin/leanspec.js
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|