mirror of
https://github.com/ZSeven-W/openpencil.git
synced 2026-05-31 19:04:29 +07:00
chore(release): merge v0.7.5 pre-release into main
Brings in three issue fixes from the 0.7.5 branch: - fix(ai): unwrap fetch error.cause for actionable network failures (#121) - fix(ci): always source-build agent-native and bundle to napi/ root - docs: drop op export from CLI docs and clarify pen-mcp usage (#116, #117) Release: https://github.com/ZSeven-W/openpencil/releases/tag/v0.7.5
This commit is contained in:
commit
902cc6d227
59 changed files with 326 additions and 336 deletions
16
.github/workflows/build-electron.yml
vendored
16
.github/workflows/build-electron.yml
vendored
|
|
@ -22,15 +22,19 @@ jobs:
|
|||
- os: macos-latest
|
||||
platform: mac-arm64
|
||||
build_args: --mac --arm64
|
||||
zig_target: aarch64-macos
|
||||
- os: macos-latest
|
||||
platform: mac-x64
|
||||
build_args: --mac --x64
|
||||
zig_target: x86_64-macos
|
||||
- os: windows-latest
|
||||
platform: win
|
||||
build_args: --win
|
||||
zig_target: x86_64-windows
|
||||
- os: ubuntu-latest
|
||||
platform: linux
|
||||
build_args: --linux
|
||||
zig_target: x86_64-linux
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -50,8 +54,20 @@ jobs:
|
|||
version: 0.15.2
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
OPENPENCIL_REQUIRE_AGENT_NATIVE: '1'
|
||||
ZIG_TARGET: ${{ matrix.zig_target }}
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Verify agent-native binary
|
||||
shell: bash
|
||||
run: |
|
||||
if [ ! -f packages/agent-native/napi/agent_napi.node ]; then
|
||||
echo "::error::packages/agent-native/napi/agent_napi.node missing — electron-builder would ship without it."
|
||||
exit 1
|
||||
fi
|
||||
ls -la packages/agent-native/napi/agent_napi.node
|
||||
|
||||
- name: Build web (electron target)
|
||||
run: bun --bun run build
|
||||
env:
|
||||
|
|
|
|||
2
.github/workflows/publish-cli.yml
vendored
2
.github/workflows/publish-cli.yml
vendored
|
|
@ -34,6 +34,8 @@ jobs:
|
|||
version: 0.15.2
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
OPENPENCIL_SKIP_AGENT_NATIVE: '1'
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Get version
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@ The `op` command-line tool controls the desktop app or web server from the termi
|
|||
- **Design:** `op design <dsl|@file|->` — batch design DSL operations
|
||||
- **Document:** `op open`, `op save`, `op get`, `op selection`
|
||||
- **Nodes:** `op insert`, `op update`, `op delete`, `op move`, `op copy`, `op replace`
|
||||
- **Export:** `op export <react|html|vue|svelte|flutter|swiftui|compose|rn|css>`
|
||||
- **Cross-platform:** macOS, Windows (NSIS/portable), Linux (AppImage/deb/snap/flatpak)
|
||||
|
||||
### CI / CD
|
||||
|
|
|
|||
|
|
@ -129,7 +129,6 @@ The `op` command-line tool controls the desktop app or web server from the termi
|
|||
- **Design:** `op design <dsl|@file|->` — batch design DSL operations
|
||||
- **Document:** `op open`, `op save`, `op get`, `op selection`
|
||||
- **Nodes:** `op insert`, `op update`, `op delete`, `op move`, `op copy`, `op replace`
|
||||
- **Export:** `op export <react|html|vue|svelte|flutter|swiftui|compose|rn|css>`
|
||||
- **Cross-platform:** macOS, Windows (NSIS/portable), Linux (AppImage/deb/snap/flatpak)
|
||||
|
||||
### CI / CD
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Web-App + native Desktop-Anwendung auf macOS, Windows und Linux über Electron.
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
Steuern Sie das Design-Tool vom Terminal aus. `op design`, `op insert`, `op export` — Batch-Design-DSL, Knotenmanipulation, Code-Export. Pipe-Eingabe von Dateien oder stdin. Funktioniert mit der Desktop-App oder dem Webserver.
|
||||
Steuern Sie das Design-Tool vom Terminal aus. `op design`, `op insert` — Batch-Design-DSL, Knotenmanipulation. Pipe-Eingabe von Dateien oder stdin. Funktioniert mit der Desktop-App oder dem Webserver.
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # Desktop-App starten
|
||||
op design @landing.txt # Batch-Design aus Datei
|
||||
op insert '{"type":"RECT"}' # Knoten einfügen
|
||||
op export react --out . # Nach React + Tailwind exportieren
|
||||
op import:figma design.fig # Figma-Datei importieren
|
||||
cat design.dsl | op design - # Pipe von stdin
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ Unterstützt drei Eingabemethoden: Inline-String, `@filepath` (aus Datei lesen)
|
|||
| **State** | Zustand v5 |
|
||||
| **Server** | Nitro |
|
||||
| **Desktop** | Electron 35 |
|
||||
| **CLI** | `op` — Terminal-Steuerung, Batch-Design-DSL, Code-Export |
|
||||
| **CLI** | `op` — Terminal-Steuerung, Batch-Design-DSL |
|
||||
| **KI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **Laufzeit** | Bun · Vite 7 |
|
||||
| **Dateiformat** | `.op` — JSON-basiert, menschenlesbar, Git-freundlich |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Aplicación web + escritorio nativo en macOS, Windows y Linux mediante Electron.
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
Controla la herramienta de diseño desde la terminal. `op design`, `op insert`, `op export` — DSL de diseño por lotes, manipulación de nodos, exportación de código. Entrada por pipe desde archivos o stdin. Funciona con la app de escritorio o el servidor web.
|
||||
Controla la herramienta de diseño desde la terminal. `op design`, `op insert` — DSL de diseño por lotes, manipulación de nodos. Entrada por pipe desde archivos o stdin. Funciona con la app de escritorio o el servidor web.
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # Iniciar la app de escritorio
|
||||
op design @landing.txt # Diseño por lotes desde archivo
|
||||
op insert '{"type":"RECT"}' # Insertar un nodo
|
||||
op export react --out . # Exportar a React + Tailwind
|
||||
op import:figma design.fig # Importar archivo de Figma
|
||||
cat design.dsl | op design - # Entrada por pipe desde stdin
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ Soporta tres métodos de entrada: cadena inline, `@filepath` (leer desde archivo
|
|||
| **Estado** | Zustand v5 |
|
||||
| **Servidor** | Nitro |
|
||||
| **Escritorio** | Electron 35 |
|
||||
| **CLI** | `op` — control desde terminal, DSL de diseño por lotes, exportación de código |
|
||||
| **CLI** | `op` — control desde terminal, DSL de diseño por lotes |
|
||||
| **IA** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **Runtime** | Bun · Vite 7 |
|
||||
| **Formato de archivo** | `.op` — basado en JSON, legible por humanos, compatible con Git |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Application web + bureau natif sur macOS, Windows et Linux via Electron. Mises
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
Contrôlez l'outil de design depuis le terminal. `op design`, `op insert`, `op export` — DSL de design par lots, manipulation de nœuds, export de code. Entrée par pipe depuis des fichiers ou stdin. Fonctionne avec l'app de bureau ou le serveur web.
|
||||
Contrôlez l'outil de design depuis le terminal. `op design`, `op insert` — DSL de design par lots, manipulation de nœuds. Entrée par pipe depuis des fichiers ou stdin. Fonctionne avec l'app de bureau ou le serveur web.
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # Lancer l'app de bureau
|
||||
op design @landing.txt # Design par lots depuis un fichier
|
||||
op insert '{"type":"RECT"}' # Insérer un nœud
|
||||
op export react --out . # Exporter en React + Tailwind
|
||||
op import:figma design.fig # Importer un fichier Figma
|
||||
cat design.dsl | op design - # Pipe depuis stdin
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ Supporte trois méthodes d'entrée : chaîne en ligne, `@filepath` (lecture depu
|
|||
| **État** | Zustand v5 |
|
||||
| **Serveur** | Nitro |
|
||||
| **Bureau** | Electron 35 |
|
||||
| **CLI** | `op` — contrôle depuis le terminal, DSL de design par lots, export de code |
|
||||
| **CLI** | `op` — contrôle depuis le terminal, DSL de design par lots |
|
||||
| **IA** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **Runtime** | Bun · Vite 7 |
|
||||
| **Format de fichier** | `.op` — basé sur JSON, lisible par l'humain, compatible Git |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Claude Code, Codex, Gemini, OpenCode, Kiro, या Copilot CLIs में वन
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
अपने टर्मिनल से डिज़ाइन टूल को नियंत्रित करें। `op design`, `op insert`, `op export` — बैच डिज़ाइन DSL, नोड मैनिपुलेशन, कोड एक्सपोर्ट। फ़ाइलों या stdin से पाइप करें। डेस्कटॉप ऐप या वेब सर्वर के साथ काम करता है।
|
||||
अपने टर्मिनल से डिज़ाइन टूल को नियंत्रित करें। `op design`, `op insert` — बैच डिज़ाइन DSL, नोड मैनिपुलेशन। फ़ाइलों या stdin से पाइप करें। डेस्कटॉप ऐप या वेब सर्वर के साथ काम करता है।
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # डेस्कटॉप ऐप लॉन्च करें
|
||||
op design @landing.txt # फ़ाइल से बैच डिज़ाइन
|
||||
op insert '{"type":"RECT"}' # एक नोड डालें
|
||||
op export react --out . # React + Tailwind में एक्सपोर्ट
|
||||
op import:figma design.fig # Figma फ़ाइल इम्पोर्ट करें
|
||||
cat design.dsl | op design - # stdin से पाइप करें
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # stdin से पाइप करें
|
|||
| **स्टेट** | Zustand v5 |
|
||||
| **सर्वर** | Nitro |
|
||||
| **डेस्कटॉप** | Electron 35 |
|
||||
| **CLI** | `op` — टर्मिनल नियंत्रण, बैच डिज़ाइन DSL, कोड एक्सपोर्ट |
|
||||
| **CLI** | `op` — टर्मिनल नियंत्रण, बैच डिज़ाइन DSL |
|
||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **रनटाइम** | Bun · Vite 7 |
|
||||
| **फ़ाइल फ़ॉर्मेट** | `.op` — JSON-आधारित, मानव-पठनीय, Git-फ्रेंडली |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Aplikasi web + desktop native di macOS, Windows, dan Linux melalui Electron. Pem
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
Kontrol alat desain dari terminal Anda. `op design`, `op insert`, `op export` — batch design DSL, manipulasi node, ekspor kode. Pipe dari file atau stdin. Bekerja dengan aplikasi desktop atau web server.
|
||||
Kontrol alat desain dari terminal Anda. `op design`, `op insert` — batch design DSL, manipulasi node. Pipe dari file atau stdin. Bekerja dengan aplikasi desktop atau web server.
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # Jalankan aplikasi desktop
|
||||
op design @landing.txt # Desain batch dari file
|
||||
op insert '{"type":"RECT"}' # Sisipkan sebuah node
|
||||
op export react --out . # Ekspor ke React + Tailwind
|
||||
op import:figma design.fig # Impor file Figma
|
||||
cat design.dsl | op design - # Pipe dari stdin
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ Mendukung tiga metode input: string inline, `@filepath` (baca dari file), atau `
|
|||
| **State** | Zustand v5 |
|
||||
| **Server** | Nitro |
|
||||
| **Desktop** | Electron 35 |
|
||||
| **CLI** | `op` — kontrol terminal, batch design DSL, ekspor kode |
|
||||
| **CLI** | `op` — kontrol terminal, batch design DSL |
|
||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **Runtime** | Bun · Vite 7 |
|
||||
| **Format file** | `.op` — berbasis JSON, mudah dibaca manusia, ramah Git |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Web アプリ + Electron による macOS・Windows・Linux ネイティブデス
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
ターミナルからデザインツールを操作。`op design`、`op insert`、`op export` — バッチデザインDSL、ノード操作、コードエクスポート。ファイルやstdinからのパイプ入力に対応。デスクトップアプリまたはWebサーバーと連携。
|
||||
ターミナルからデザインツールを操作。`op design`、`op insert` — バッチデザインDSL、ノード操作。ファイルやstdinからのパイプ入力に対応。デスクトップアプリまたはWebサーバーと連携。
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # デスクトップアプリを起動
|
||||
op design @landing.txt # ファイルからバッチデザイン
|
||||
op insert '{"type":"RECT"}' # ノードを挿入
|
||||
op export react --out . # React + Tailwind にエクスポート
|
||||
op import:figma design.fig # Figma ファイルをインポート
|
||||
cat design.dsl | op design - # stdin からパイプ入力
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # stdin からパイプ入力
|
|||
| **状態管理** | Zustand v5 |
|
||||
| **サーバー** | Nitro |
|
||||
| **デスクトップ** | Electron 35 |
|
||||
| **CLI** | `op` — ターミナル制御、バッチデザインDSL、コードエクスポート |
|
||||
| **CLI** | `op` — ターミナル制御、バッチデザインDSL |
|
||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **ランタイム** | Bun · Vite 7 |
|
||||
| **ファイル形式** | `.op` — JSON ベース、人間が読みやすく、Git フレンドリー |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Claude Code, Codex, Gemini, OpenCode, Kiro 또는 Copilot CLI에 원클릭 설
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
터미널에서 디자인 도구 제어. `op design`, `op insert`, `op export` — 배치 디자인 DSL, 노드 조작, 코드 내보내기. 파일이나 stdin에서 파이프 입력 지원. 데스크톱 앱 또는 웹 서버와 연동.
|
||||
터미널에서 디자인 도구 제어. `op design`, `op insert` — 배치 디자인 DSL, 노드 조작. 파일이나 stdin에서 파이프 입력 지원. 데스크톱 앱 또는 웹 서버와 연동.
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # 데스크톱 앱 실행
|
||||
op design @landing.txt # 파일에서 배치 디자인
|
||||
op insert '{"type":"RECT"}' # 노드 삽입
|
||||
op export react --out . # React + Tailwind로 내보내기
|
||||
op import:figma design.fig # Figma 파일 가져오기
|
||||
cat design.dsl | op design - # stdin에서 파이프 입력
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # stdin에서 파이프 입력
|
|||
| **상태 관리** | Zustand v5 |
|
||||
| **서버** | Nitro |
|
||||
| **데스크톱** | Electron 35 |
|
||||
| **CLI** | `op` — 터미널 제어, 배치 디자인 DSL, 코드 내보내기 |
|
||||
| **CLI** | `op` — 터미널 제어, 배치 디자인 DSL |
|
||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **런타임** | Bun · Vite 7 |
|
||||
| **파일 형식** | `.op` — JSON 기반, 사람이 읽을 수 있는, Git 친화적 |
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ Web app + native desktop on macOS, Windows, and Linux via Electron. Auto-updates
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
Control the design tool from your terminal. `op design`, `op insert`, `op export` — batch design DSL, node manipulation, code export. Pipe in from files or stdin. Works with desktop app or web server.
|
||||
Control the design tool from your terminal. `op design`, `op insert` — batch design DSL, node manipulation. Pipe in from files or stdin. Works with desktop app or web server.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -275,7 +275,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # Launch desktop app
|
||||
op design @landing.txt # Batch design from file
|
||||
op insert '{"type":"RECT"}' # Insert a node
|
||||
op export react --out . # Export to React + Tailwind
|
||||
op import:figma design.fig # Import Figma file
|
||||
cat design.dsl | op design - # Pipe from stdin
|
||||
```
|
||||
|
|
@ -350,7 +349,7 @@ Supports three input methods: inline string, `@filepath` (read from file), or `-
|
|||
| **State** | Zustand v5 |
|
||||
| **Server** | Nitro |
|
||||
| **Desktop** | Electron 35 |
|
||||
| **CLI** | `op` — terminal control, batch design DSL, code export |
|
||||
| **CLI** | `op` — terminal control, batch design DSL |
|
||||
| **AI** | agent-native (Zig NAPI) · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **Runtime** | Bun · Vite 7 |
|
||||
| **Lint** | oxlint · oxfmt |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ App web + desktop nativo no macOS, Windows e Linux via Electron. Atualização a
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
Controle a ferramenta de design pelo terminal. `op design`, `op insert`, `op export` — DSL de design em lote, manipulação de nós, exportação de código. Entrada por pipe de arquivos ou stdin. Funciona com o app desktop ou servidor web.
|
||||
Controle a ferramenta de design pelo terminal. `op design`, `op insert` — DSL de design em lote, manipulação de nós. Entrada por pipe de arquivos ou stdin. Funciona com o app desktop ou servidor web.
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # Iniciar app desktop
|
||||
op design @landing.txt # Design em lote a partir de arquivo
|
||||
op insert '{"type":"RECT"}' # Inserir um nó
|
||||
op export react --out . # Exportar para React + Tailwind
|
||||
op import:figma design.fig # Importar arquivo Figma
|
||||
cat design.dsl | op design - # Entrada por pipe via stdin
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ Suporta três métodos de entrada: string inline, `@filepath` (ler de arquivo) o
|
|||
| **Estado** | Zustand v5 |
|
||||
| **Servidor** | Nitro |
|
||||
| **Desktop** | Electron 35 |
|
||||
| **CLI** | `op` — controle pelo terminal, DSL de design em lote, exportação de código |
|
||||
| **CLI** | `op` — controle pelo terminal, DSL de design em lote |
|
||||
| **IA** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **Runtime** | Bun · Vite 7 |
|
||||
| **Formato de arquivo** | `.op` — baseado em JSON, legível por humanos, compatível com Git |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
Управляйте инструментом дизайна из терминала. `op design`, `op insert`, `op export` — пакетный DSL дизайна, манипуляция узлами, экспорт кода. Ввод через pipe из файлов или stdin. Работает с десктопным приложением или веб-сервером.
|
||||
Управляйте инструментом дизайна из терминала. `op design`, `op insert` — пакетный DSL дизайна, манипуляция узлами. Ввод через pipe из файлов или stdin. Работает с десктопным приложением или веб-сервером.
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # Запустить десктопное приложение
|
||||
op design @landing.txt # Пакетный дизайн из файла
|
||||
op insert '{"type":"RECT"}' # Вставить узел
|
||||
op export react --out . # Экспорт в React + Tailwind
|
||||
op import:figma design.fig # Импортировать файл Figma
|
||||
cat design.dsl | op design - # Передача через stdin
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # Передача через stdin
|
|||
| **Состояние** | Zustand v5 |
|
||||
| **Сервер** | Nitro |
|
||||
| **Десктоп** | Electron 35 |
|
||||
| **CLI** | `op` — управление из терминала, пакетный DSL дизайна, экспорт кода |
|
||||
| **CLI** | `op` — управление из терминала, пакетный DSL дизайна |
|
||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **Среда выполнения** | Bun · Vite 7 |
|
||||
| **Формат файла** | `.op` — на основе JSON, удобочитаемый, дружественный к Git |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Orchestrator แบ่งหน้าที่ซับซ้อนออกเ
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
ควบคุมเครื่องมือออกแบบจาก terminal ของคุณ `op design`, `op insert`, `op export` — batch design DSL, จัดการ node, ส่งออกโค้ด Pipe จากไฟล์หรือ stdin ทำงานร่วมกับแอปเดสก์ท็อปหรือ web server
|
||||
ควบคุมเครื่องมือออกแบบจาก terminal ของคุณ `op design`, `op insert` — batch design DSL, จัดการ node Pipe จากไฟล์หรือ stdin ทำงานร่วมกับแอปเดสก์ท็อปหรือ web server
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # เปิดแอปเดสก์ท็อป
|
||||
op design @landing.txt # ออกแบบแบบ batch จากไฟล์
|
||||
op insert '{"type":"RECT"}' # แทรก node
|
||||
op export react --out . # ส่งออกเป็น React + Tailwind
|
||||
op import:figma design.fig # นำเข้าไฟล์ Figma
|
||||
cat design.dsl | op design - # Pipe จาก stdin
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # Pipe จาก stdin
|
|||
| **State** | Zustand v5 |
|
||||
| **Server** | Nitro |
|
||||
| **Desktop** | Electron 35 |
|
||||
| **CLI** | `op` — ควบคุมจาก terminal, batch design DSL, ส่งออกโค้ด |
|
||||
| **CLI** | `op` — ควบคุมจาก terminal, batch design DSL |
|
||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **Runtime** | Bun · Vite 7 |
|
||||
| **รูปแบบไฟล์** | `.op` — ใช้ JSON, อ่านได้โดยมนุษย์, Git-friendly |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Web uygulaması + Electron ile macOS, Windows ve Linux'ta yerel masaüstü. GitH
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
Tasarım aracını terminalinizden kontrol edin. `op design`, `op insert`, `op export` — toplu tasarım DSL, düğüm manipülasyonu, kod dışa aktarımı. Dosyalardan veya stdin'den pipe ile besleyin. Masaüstü uygulama veya web sunucusuyla çalışır.
|
||||
Tasarım aracını terminalinizden kontrol edin. `op design`, `op insert` — toplu tasarım DSL, düğüm manipülasyonu. Dosyalardan veya stdin'den pipe ile besleyin. Masaüstü uygulama veya web sunucusuyla çalışır.
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # Masaüstü uygulamayı başlat
|
||||
op design @landing.txt # Dosyadan toplu tasarım
|
||||
op insert '{"type":"RECT"}' # Bir düğüm ekle
|
||||
op export react --out . # React + Tailwind'e dışa aktar
|
||||
op import:figma design.fig # Figma dosyasını içe aktar
|
||||
cat design.dsl | op design - # stdin'den pipe ile besle
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # stdin'den pipe ile besle
|
|||
| **Durum Yönetimi** | Zustand v5 |
|
||||
| **Sunucu** | Nitro |
|
||||
| **Masaüstü** | Electron 35 |
|
||||
| **CLI** | `op` — terminal kontrolü, toplu tasarım DSL, kod dışa aktarımı |
|
||||
| **CLI** | `op` — terminal kontrolü, toplu tasarım DSL |
|
||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **Çalışma Ortamı** | Bun · Vite 7 |
|
||||
| **Dosya Formatı** | `.op` — JSON tabanlı, insan tarafından okunabilir, Git dostu |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Tệp `.op` là JSON — dễ đọc, thân thiện Git, dễ so sánh khác bi
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
Điều khiển công cụ thiết kế từ terminal của bạn. `op design`, `op insert`, `op export` — batch design DSL, thao tác node, xuất mã. Pipe từ tệp hoặc stdin. Hoạt động với ứng dụng desktop hoặc web server.
|
||||
Điều khiển công cụ thiết kế từ terminal của bạn. `op design`, `op insert` — batch design DSL, thao tác node. Pipe từ tệp hoặc stdin. Hoạt động với ứng dụng desktop hoặc web server.
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # Khởi chạy ứng dụng desktop
|
||||
op design @landing.txt # Thiết kế hàng loạt từ tệp
|
||||
op insert '{"type":"RECT"}' # Chèn một node
|
||||
op export react --out . # Xuất sang React + Tailwind
|
||||
op import:figma design.fig # Nhập tệp Figma
|
||||
cat design.dsl | op design - # Pipe từ stdin
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ Hỗ trợ ba phương thức nhập liệu: chuỗi inline, `@filepath` (đọc
|
|||
| **Trạng thái** | Zustand v5 |
|
||||
| **Máy chủ** | Nitro |
|
||||
| **Desktop** | Electron 35 |
|
||||
| **CLI** | `op` — điều khiển từ terminal, batch design DSL, xuất mã |
|
||||
| **CLI** | `op` — điều khiển từ terminal, batch design DSL |
|
||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **Runtime** | Bun · Vite 7 |
|
||||
| **Định dạng tệp** | `.op` — dựa trên JSON, dễ đọc, thân thiện với Git |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Web 應用程式 + 透過 Electron 在 macOS、Windows 和 Linux 上原生執行
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
從終端機控制設計工具。`op design`、`op insert`、`op export` — 批次設計 DSL、節點操作、程式碼匯出。支援從檔案或 stdin 管道輸入。可搭配桌面應用程式或 Web 伺服器使用。
|
||||
從終端機控制設計工具。`op design`、`op insert` — 批次設計 DSL、節點操作。支援從檔案或 stdin 管道輸入。可搭配桌面應用程式或 Web 伺服器使用。
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # 啟動桌面應用程式
|
||||
op design @landing.txt # 從檔案批次設計
|
||||
op insert '{"type":"RECT"}' # 插入節點
|
||||
op export react --out . # 匯出為 React + Tailwind
|
||||
op import:figma design.fig # 匯入 Figma 檔案
|
||||
cat design.dsl | op design - # 從 stdin 管道輸入
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # 從 stdin 管道輸入
|
|||
| **狀態管理** | Zustand v5 |
|
||||
| **伺服器** | Nitro |
|
||||
| **桌面端** | Electron 35 |
|
||||
| **CLI** | `op` — 終端機控制、批次設計 DSL、程式碼匯出 |
|
||||
| **CLI** | `op` — 終端機控制、批次設計 DSL |
|
||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **執行環境** | Bun · Vite 7 |
|
||||
| **檔案格式** | `.op` — 基於 JSON,人類可讀,對 Git 友好 |
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ Web 应用 + 通过 Electron 支持 macOS、Windows 和 Linux 原生桌面端。
|
|||
|
||||
### ⌨️ CLI — `op`
|
||||
|
||||
从终端控制设计工具。`op design`、`op insert`、`op export` — 批量设计 DSL、节点操作、代码导出。支持从文件或 stdin 管道输入。可搭配桌面应用或 Web 服务器使用。
|
||||
从终端控制设计工具。`op design`、`op insert` — 批量设计 DSL、节点操作。支持从文件或 stdin 管道输入。可搭配桌面应用或 Web 服务器使用。
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
|||
op start # 启动桌面应用
|
||||
op design @landing.txt # 从文件批量设计
|
||||
op insert '{"type":"RECT"}' # 插入节点
|
||||
op export react --out . # 导出为 React + Tailwind
|
||||
op import:figma design.fig # 导入 Figma 文件
|
||||
cat design.dsl | op design - # 从 stdin 管道输入
|
||||
```
|
||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # 从 stdin 管道输入
|
|||
| **状态管理** | Zustand v5 |
|
||||
| **服务器** | Nitro |
|
||||
| **桌面端** | Electron 35 |
|
||||
| **CLI** | `op` — 终端控制、批量设计 DSL、代码导出 |
|
||||
| **CLI** | `op` — 终端控制、批量设计 DSL |
|
||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||
| **运行时** | Bun · Vite 7 |
|
||||
| **文件格式** | `.op` — 基于 JSON,人类可读,对 Git 友好 |
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ apps/cli/
|
|||
│ ├── app.ts start, stop, status
|
||||
│ ├── design.ts design, design:skeleton, design:content, design:refine
|
||||
│ ├── document.ts open, save, get, selection
|
||||
│ ├── export.ts export (react, html, vue, svelte, flutter, swiftui, compose, rn, css)
|
||||
│ ├── codegen.ts codegen:plan, codegen:submit, codegen:assemble, codegen:clean
|
||||
│ ├── import.ts import:svg, import:figma
|
||||
│ ├── install.ts install, uninstall (openpencil-skill for AI agents)
|
||||
│ ├── layout.ts layout, find-space
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### Code-Export
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# Formate: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### Variablen und Themes
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### Exportacion de codigo
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# Formatos: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### Variables y temas
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### Export de code
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# Formats : react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### Variables et themes
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### कोड निर्यात
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# प्रारूप: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### वेरिएबल और थीम
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### Ekspor Kode
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# Format: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### Variabel & Tema
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### コードエクスポート
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# フォーマット: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### 変数とテーマ
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### 코드 내보내기
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# 형식: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### 변수 및 테마
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### Code Export
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# Formats: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### Variables & Themes
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### Exportacao de Codigo
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# Formatos: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### Variaveis e Temas
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### Экспорт кода
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# Форматы: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### Переменные и темы
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### การส่งออกโค้ด
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# รูปแบบ: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### ตัวแปรและธีม
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### Kod Disari Aktarimi
|
||||
|
||||
```bash
|
||||
op export <format> [--out dosya]
|
||||
# Formatlar: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### Degiskenler ve Temalar
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### Xuất mã nguồn
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# Định dạng: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### Biến và giao diện
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### 程式碼匯出
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# 格式:react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### 變數與主題
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
|||
op replace <id> <json> [--post-process]
|
||||
```
|
||||
|
||||
### 代码导出
|
||||
|
||||
```bash
|
||||
op export <format> [--out file]
|
||||
# 格式:react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
||||
```
|
||||
|
||||
### 变量与主题
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/openpencil",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"description": "CLI for OpenPencil — control the design tool from your terminal",
|
||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/apps/cli",
|
||||
"bugs": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/desktop",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"private": true,
|
||||
"type": "module"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/web",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
buildProviderModelsURL,
|
||||
formatFetchError,
|
||||
normalizeBaseURL,
|
||||
normalizeMemberBaseURL,
|
||||
normalizeOptionalBaseURL,
|
||||
|
|
@ -63,3 +64,65 @@ describe('provider-url helpers', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatFetchError', () => {
|
||||
it('unwraps undici "fetch failed" by reading error.cause', () => {
|
||||
const cause = Object.assign(new Error('Client network socket disconnected'), {
|
||||
code: 'ECONNRESET',
|
||||
});
|
||||
const err = Object.assign(new TypeError('fetch failed'), { cause });
|
||||
expect(formatFetchError(err)).toBe('ECONNRESET: Client network socket disconnected');
|
||||
});
|
||||
|
||||
it('skips the prefix when the cause message already contains the code', () => {
|
||||
const cause = Object.assign(new Error('getaddrinfo ENOTFOUND api.example.com'), {
|
||||
code: 'ENOTFOUND',
|
||||
});
|
||||
const err = Object.assign(new TypeError('fetch failed'), { cause });
|
||||
expect(formatFetchError(err)).toBe('getaddrinfo ENOTFOUND api.example.com');
|
||||
});
|
||||
|
||||
it('returns just the cause code when message is missing', () => {
|
||||
const cause = Object.assign(new Error(''), { code: 'ECONNREFUSED' });
|
||||
const err = Object.assign(new TypeError('fetch failed'), { cause });
|
||||
expect(formatFetchError(err)).toBe('ECONNREFUSED');
|
||||
});
|
||||
|
||||
it('returns just the cause message when no code', () => {
|
||||
const cause = new Error('self-signed certificate in certificate chain');
|
||||
const err = Object.assign(new TypeError('fetch failed'), { cause });
|
||||
expect(formatFetchError(err)).toBe('self-signed certificate in certificate chain');
|
||||
});
|
||||
|
||||
it('falls back to error.message when no cause', () => {
|
||||
expect(formatFetchError(new Error('Provider returned 401'))).toBe('Provider returned 401');
|
||||
});
|
||||
|
||||
it('handles non-Error inputs', () => {
|
||||
expect(formatFetchError('boom')).toBe('Unknown error');
|
||||
expect(formatFetchError(undefined)).toBe('Unknown error');
|
||||
});
|
||||
|
||||
it('walks nested cause chains', () => {
|
||||
const root = Object.assign(new Error('Hostname does not match certificate'), {
|
||||
code: 'ERR_TLS_CERT_ALTNAME_INVALID',
|
||||
});
|
||||
const wrapper = Object.assign(new Error('TLS handshake failed'), { cause: root });
|
||||
const outer = Object.assign(new TypeError('fetch failed'), { cause: wrapper });
|
||||
expect(formatFetchError(outer)).toBe(
|
||||
'ERR_TLS_CERT_ALTNAME_INVALID: Hostname does not match certificate',
|
||||
);
|
||||
});
|
||||
|
||||
it('unwraps AggregateError so per-IP attempt reasons reach the user', () => {
|
||||
const a = Object.assign(new Error('connect ECONNREFUSED 127.0.0.1:8080'), {
|
||||
code: 'ECONNREFUSED',
|
||||
});
|
||||
const b = Object.assign(new Error('connect ECONNREFUSED ::1:8080'), { code: 'ECONNREFUSED' });
|
||||
const agg = Object.assign(new AggregateError([a, b], 'all attempts failed'));
|
||||
const err = Object.assign(new TypeError('fetch failed'), { cause: agg });
|
||||
expect(formatFetchError(err)).toBe(
|
||||
'connect ECONNREFUSED 127.0.0.1:8080; connect ECONNREFUSED ::1:8080',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ import {
|
|||
buildSpawnClaudeCodeProcess,
|
||||
getClaudeAgentDebugFilePath,
|
||||
} from '../../utils/resolve-claude-agent-env';
|
||||
import { normalizeOptionalBaseURL, requireOpenAICompatBaseURL } from './provider-url';
|
||||
import {
|
||||
formatFetchError,
|
||||
normalizeOptionalBaseURL,
|
||||
requireOpenAICompatBaseURL,
|
||||
} from './provider-url';
|
||||
// SENSITIVE_LOG_PATTERN + readDebugTail are now canonical in @zseven-w/pen-mcp.
|
||||
// Re-export here to keep existing consumers (tests, other modules) working.
|
||||
import { SENSITIVE_LOG_PATTERN, readDebugTail } from '@zseven-w/pen-mcp';
|
||||
|
|
@ -1101,7 +1105,7 @@ function streamViaBuiltin(body: ChatBody) {
|
|||
destroyProvider(builtinProvider);
|
||||
}
|
||||
} catch (error) {
|
||||
const content = error instanceof Error ? error.message : 'Unknown error';
|
||||
const content = formatFetchError(error);
|
||||
controller.enqueue(
|
||||
encoder.encode(`data: ${JSON.stringify({ type: 'error', content })}\n\n`),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -254,17 +254,29 @@ async function fetchFromOpenverse(
|
|||
}
|
||||
}
|
||||
|
||||
const res = await fetch(url.toString(), { headers });
|
||||
if (res.status === 429) {
|
||||
// Rate limited — signal fallback
|
||||
return null;
|
||||
}
|
||||
if (!res.ok) {
|
||||
return null;
|
||||
}
|
||||
// Network failures (ConnectTimeoutError on restricted networks, DNS
|
||||
// failures, etc.) need to behave like a 429: return null so the caller
|
||||
// falls back to Wikimedia. Without this, fetch() throws, the throw
|
||||
// bubbles up to nitro's default handler, and the user sees a 500
|
||||
// instead of placeholder images.
|
||||
try {
|
||||
const res = await fetch(url.toString(), {
|
||||
headers,
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (res.status === 429) {
|
||||
// Rate limited — signal fallback
|
||||
return null;
|
||||
}
|
||||
if (!res.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = (await res.json()) as OpenverseSearchResponse;
|
||||
return (data.results ?? []).map(mapOpenverseResult);
|
||||
const data = (await res.json()) as OpenverseSearchResponse;
|
||||
return (data.results ?? []).map(mapOpenverseResult);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchFromWikimedia(query: string, count: number): Promise<ImageSearchResult[]> {
|
||||
|
|
@ -280,14 +292,21 @@ async function fetchFromWikimedia(query: string, count: number): Promise<ImageSe
|
|||
url.searchParams.set('format', 'json');
|
||||
url.searchParams.set('origin', '*');
|
||||
|
||||
const res = await fetch(url.toString());
|
||||
if (!res.ok) return [];
|
||||
// Same network-failure shielding as Openverse: an empty result is the
|
||||
// documented fallback signal, returning [] keeps the endpoint at 200
|
||||
// with placeholder-friendly results instead of bubbling the throw.
|
||||
try {
|
||||
const res = await fetch(url.toString(), { signal: AbortSignal.timeout(8000) });
|
||||
if (!res.ok) return [];
|
||||
|
||||
const data = (await res.json()) as WikimediaQueryResponse;
|
||||
const pages = data.query?.pages;
|
||||
if (!pages) return [];
|
||||
const data = (await res.json()) as WikimediaQueryResponse;
|
||||
const pages = data.query?.pages;
|
||||
if (!pages) return [];
|
||||
|
||||
return mapWikimediaPages(pages);
|
||||
return mapWikimediaPages(pages);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { defineEventHandler, readBody } from 'h3';
|
||||
import { buildProviderModelsURL, normalizeOptionalBaseURL } from './provider-url';
|
||||
import { buildProviderModelsURL, formatFetchError, normalizeOptionalBaseURL } from './provider-url';
|
||||
|
||||
interface ProviderModelsBody {
|
||||
baseURL: string;
|
||||
|
|
@ -63,7 +63,6 @@ export default defineEventHandler(async (event) => {
|
|||
|
||||
return { models };
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Unknown error';
|
||||
return { models: [], error: message };
|
||||
return { models: [], error: formatFetchError(err) };
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -63,3 +63,44 @@ export function normalizeMemberBaseURL(
|
|||
export function buildProviderModelsURL(baseURL: string): string {
|
||||
return `${normalizeOpenAICompatBaseURL(baseURL) ?? normalizeBaseURL(baseURL)}/models`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node's `fetch` (undici) collapses every network-level failure — DNS, TLS,
|
||||
* refused connection, timeout — into a single opaque `TypeError: fetch failed`.
|
||||
* The real reason lives on `error.cause` as a SystemError with `code`/`syscall`/
|
||||
* `hostname`. Surface that so users can act on it ("ENOTFOUND api.foo.com",
|
||||
* "self-signed certificate", "ECONNREFUSED 127.0.0.1:443") instead of staring
|
||||
* at "fetch failed".
|
||||
*
|
||||
* Walks the `cause` chain (some failures wrap multiple times) and unwraps
|
||||
* AggregateError (undici emits one when DNS returns multiple A records and
|
||||
* every connect attempt fails) so each leaf reason makes it to the user.
|
||||
*/
|
||||
export function formatFetchError(error: unknown): string {
|
||||
const reasons = collectFetchErrorReasons(error);
|
||||
if (reasons.length === 0) {
|
||||
return error instanceof Error ? error.message || 'Unknown error' : 'Unknown error';
|
||||
}
|
||||
return Array.from(new Set(reasons)).join('; ');
|
||||
}
|
||||
|
||||
function collectFetchErrorReasons(error: unknown, depth = 0): string[] {
|
||||
if (depth > 5 || !(error instanceof Error)) return [];
|
||||
|
||||
const aggregate = (error as { errors?: unknown }).errors;
|
||||
if (Array.isArray(aggregate) && aggregate.length > 0) {
|
||||
return aggregate.flatMap((sub) => collectFetchErrorReasons(sub, depth + 1));
|
||||
}
|
||||
|
||||
const cause = (error as { cause?: unknown }).cause;
|
||||
if (cause instanceof Error) {
|
||||
return collectFetchErrorReasons(cause, depth + 1);
|
||||
}
|
||||
|
||||
const code = (error as { code?: string }).code;
|
||||
const message = error.message?.trim();
|
||||
if (code && message && !message.includes(code)) return [`${code}: ${message}`];
|
||||
if (message) return [message];
|
||||
if (code) return [code];
|
||||
return [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ export const BUILTIN_MODEL_LISTS: Partial<
|
|||
{ id: 'doubao-seed-2.0-pro', name: 'Doubao Seed 2.0 Pro' },
|
||||
{ id: 'doubao-seed-2.0-lite', name: 'Doubao Seed 2.0 Lite' },
|
||||
{ id: 'glm-4.7', name: 'GLM-4.7' },
|
||||
{ id: 'deepseek-v3.2', name: 'DeepSeek V3.2' },
|
||||
{ id: 'deepseek-v4-pro', name: 'DeepSeek V4 Pro' },
|
||||
{ id: 'deepseek-v4-flash', name: 'DeepSeek V4 Flash' },
|
||||
{ id: 'kimi-k2.5', name: 'Kimi K2.5' },
|
||||
{ id: 'minimax-m2.5', name: 'MiniMax M2.5' },
|
||||
],
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const BUILTIN_PROVIDER_PRESETS: Record<BuiltinProviderPreset, BuiltinPres
|
|||
altBaseURL: 'https://api.deepseek.com/anthropic',
|
||||
altType: 'anthropic',
|
||||
placeholder: 'sk-...',
|
||||
modelPlaceholder: 'deepseek-chat',
|
||||
modelPlaceholder: 'deepseek-v4-pro',
|
||||
},
|
||||
gemini: {
|
||||
label: 'Google Gemini',
|
||||
|
|
|
|||
|
|
@ -44,7 +44,42 @@ const MODEL_PROFILES: ModelProfile[] = [
|
|||
},
|
||||
{ match: 'gemini-pro', tier: 'standard', thinkingMode: 'disabled', label: 'Gemini Pro' },
|
||||
{ match: /^gemini-2/, tier: 'standard', thinkingMode: 'disabled', label: 'Gemini 2' },
|
||||
{ match: 'deepseek', tier: 'standard', thinkingMode: 'disabled', label: 'DeepSeek' },
|
||||
// DeepSeek v4 series — v4-pro and v4-flash default to thinking enabled;
|
||||
// the API toggles it via `{"thinking":{"type":"disabled"}}`. Mark
|
||||
// these as disabled so the app keeps its fast/non-thinking default —
|
||||
// server reasoning paths that wire DeepSeek's toggle will honor it.
|
||||
// The Zig openai-compat path doesn't emit the toggle yet, so calls
|
||||
// through that path still see provider-default thinking until the
|
||||
// parameter is wired there.
|
||||
{
|
||||
match: 'deepseek-v4-pro',
|
||||
tier: 'full',
|
||||
thinkingMode: 'disabled',
|
||||
// Until the Zig openai-compat path actually sends
|
||||
// `thinking:{type:disabled}`, v4-pro keeps reasoning enabled on
|
||||
// every request and reasoning tokens explode on long planning
|
||||
// prompts. Double the timeout windows so orchestrator planning
|
||||
// doesn't fall back on the first big request. Drop this back to 1
|
||||
// once the toggle is wired.
|
||||
timeoutMultiplier: 2,
|
||||
label: 'DeepSeek V4 Pro',
|
||||
},
|
||||
{
|
||||
match: 'deepseek-v4-flash',
|
||||
tier: 'standard',
|
||||
thinkingMode: 'disabled',
|
||||
label: 'DeepSeek V4 Flash',
|
||||
},
|
||||
// Legacy aliases — exact match only so future deepseek-* variants
|
||||
// (e.g. a hypothetical deepseek-r2 with native reasoning) don't
|
||||
// inherit a forced disabled thinkingMode. These two sunset 2026-07-24
|
||||
// and DeepSeek auto-routes them to v4-flash today.
|
||||
{
|
||||
match: /^deepseek-(chat|reasoner)$/,
|
||||
tier: 'standard',
|
||||
thinkingMode: 'disabled',
|
||||
label: 'DeepSeek (legacy)',
|
||||
},
|
||||
|
||||
// Basic tier — disable thinking, use simplified prompt
|
||||
{ match: 'claude-haiku', tier: 'basic', thinkingMode: 'disabled', label: 'Claude Haiku' },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "openpencil",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"private": true,
|
||||
"description": "The world's first open-source AI-native vector design tool and the first to feature concurrent Agent Teams. Design-as-Code. Turn prompts into UI directly on the live canvas. A modern alternative to Pencil.",
|
||||
"author": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/pen-acp",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"description": "ACP (Agent Client Protocol) client for OpenPencil — connect to external ACP agents",
|
||||
"files": [
|
||||
"src"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/pen-ai-skills",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-ai-skills",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ZSeven-W/openpencil/issues"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/pen-core",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"description": "Core document operations, tree utils, variables, layout engine for OpenPencil",
|
||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-core",
|
||||
"bugs": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/pen-engine",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"description": "Headless design engine for OpenPencil — zero framework dependencies",
|
||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-engine",
|
||||
"bugs": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/pen-figma",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"description": "Figma .fig file parser and converter for OpenPencil",
|
||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-figma",
|
||||
"bugs": {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,7 @@
|
|||
|
||||
[MCP](https://modelcontextprotocol.io/) server for [OpenPencil](https://github.com/ZSeven-W/openpencil) — enables Claude, GPT, Gemini, and other LLMs to read, create, and modify designs through a standard tool protocol.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @zseven-w/pen-mcp
|
||||
# or
|
||||
bun add @zseven-w/pen-mcp
|
||||
```
|
||||
> **Note:** `pen-mcp` is shipped as part of the OpenPencil app (desktop + web) and is **not a standalone CLI**. The published package ships TypeScript source against workspace-only dependencies and has no `bin` entry, so `npx @zseven-w/pen-mcp` does not work. Run the server from the OpenPencil monorepo or connect external clients to the HTTP endpoint exposed by a running OpenPencil instance.
|
||||
|
||||
## Overview
|
||||
|
||||
|
|
@ -22,28 +16,26 @@ Three workflows are supported:
|
|||
| **Layered** | `design_skeleton` → `design_content` × N → `design_refine` | Full-page designs with high fidelity |
|
||||
| **CRUD** | `batch_get` → `update_node` / `delete_node` | Reading & modifying existing content |
|
||||
|
||||
## Quick Start
|
||||
## Running the MCP Server
|
||||
|
||||
The server supports both **stdio** and **streamable HTTP** transports. The default HTTP endpoint is `http://localhost:3100/mcp`.
|
||||
|
||||
### From the monorepo (development)
|
||||
|
||||
```bash
|
||||
# Run as stdio MCP server (for Claude Desktop, Cursor, etc.)
|
||||
npx @zseven-w/pen-mcp
|
||||
|
||||
# Or connect to a running OpenPencil instance
|
||||
op mcp:dev
|
||||
git clone https://github.com/ZSeven-W/openpencil.git
|
||||
cd openpencil && bun install
|
||||
bun run mcp:dev # starts stdio + HTTP on port 3100
|
||||
# flags: --http (HTTP only), --stdio (stdio only), --port <n>
|
||||
```
|
||||
|
||||
### Claude Desktop Configuration
|
||||
### Built-in to the OpenPencil app
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"openpencil": {
|
||||
"command": "npx",
|
||||
"args": ["@zseven-w/pen-mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Launching the desktop or web app automatically starts the MCP server in the background. External MCP clients should connect over HTTP to the running instance — no separate install required.
|
||||
|
||||
### Connecting an MCP client
|
||||
|
||||
Most MCP-aware clients (Claude Desktop, Cursor, Continue, etc.) accept an HTTP URL pointing at a running server. Point them at `http://localhost:3100/mcp` while the OpenPencil app or `bun run mcp:dev` is running.
|
||||
|
||||
## Tools
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/pen-mcp",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"description": "MCP server, document manager, and tools for OpenPencil",
|
||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-mcp",
|
||||
"bugs": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/pen-react",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"description": "React UI SDK for OpenPencil — hooks, components, and state bridges for pen-engine",
|
||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-react",
|
||||
"bugs": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/pen-renderer",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"description": "Standalone CanvasKit/Skia renderer for OpenPencil (.op) design files",
|
||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-renderer",
|
||||
"bugs": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/pen-sdk",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"description": "OpenPencil SDK — parse, manipulate, and generate code from .op design files",
|
||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-sdk",
|
||||
"bugs": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@zseven-w/pen-types",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"description": "Type definitions for OpenPencil document model",
|
||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-types",
|
||||
"bugs": {
|
||||
|
|
|
|||
|
|
@ -1,165 +1,105 @@
|
|||
#!/usr/bin/env node
|
||||
// Ensures the Zig NAPI addon binary exists.
|
||||
// Provisions the Zig NAPI addon binary by building it from source.
|
||||
//
|
||||
// Strategy (fastest to slowest):
|
||||
// 1. Already built / bundled — use it.
|
||||
// 2. Download prebuilt from the ZSeven-W/agent release whose tag points at
|
||||
// the submodule's currently checked-out commit. This means CI and local
|
||||
// installs never need Zig installed, as long as whoever bumped the
|
||||
// submodule also tagged + published a matching release.
|
||||
// 3. Build from source with local Zig (slow but authoritative).
|
||||
// We always build from source on the host so the resulting `agent_napi.node`
|
||||
// matches the runner's platform/arch. Earlier revisions also tried to download
|
||||
// a prebuilt from a sibling release repo, but that path was racy: when the
|
||||
// prebuilt was missing for the current submodule SHA the build fell through
|
||||
// to source compilation, deposited the binary at `zig-out/napi/...`, and
|
||||
// electron-builder (which only ships `packages/agent-native/napi/`) silently
|
||||
// shipped without the addon — every chat request then died at the dynamic
|
||||
// `@zseven-w/agent-native` import.
|
||||
//
|
||||
// Failing all of those is non-fatal — the postinstall wrapper swallows exit
|
||||
// codes so `bun install` never breaks. Tests / runtime will surface a clear
|
||||
// "could not locate agent_napi.node" error instead, with instructions.
|
||||
// Build prerequisite: Zig 0.15+ on PATH. CI workflows install it via
|
||||
// `mlugg/setup-zig`; local devs install once via their package manager.
|
||||
//
|
||||
// Set OPENPENCIL_REQUIRE_AGENT_NATIVE=1 to fail the install when the build
|
||||
// can't run (electron CI uses this to surface missing prerequisites early).
|
||||
//
|
||||
// Set OPENPENCIL_SKIP_AGENT_NATIVE=1 to no-op the script entirely. Useful for
|
||||
// workflows (npm publish, lint-only CI) that never load the addon at runtime
|
||||
// and would otherwise pay for a Zig build on every install.
|
||||
//
|
||||
// Set ZIG_TARGET to cross-compile for a non-host triple (e.g. on a macOS arm64
|
||||
// runner build for x86_64-macos with `ZIG_TARGET=x86_64-macos`). Without it
|
||||
// the build follows the host arch — fine for native runs, wrong when the
|
||||
// runner doesn't match the artifact you intend to ship.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const REPO = 'ZSeven-W/agent';
|
||||
const NAPI_DIR = path.join(__dirname, '..', 'packages', 'agent-native', 'napi');
|
||||
const AGENT_DIR = path.join(__dirname, '..', 'packages', 'agent-native');
|
||||
const NAPI_DIR = path.join(AGENT_DIR, 'napi');
|
||||
const ZIG_OUT = path.join(AGENT_DIR, 'zig-out', 'napi', 'agent_napi.node');
|
||||
const BUNDLED = path.join(NAPI_DIR, 'agent_napi.node');
|
||||
const STRICT = process.env.OPENPENCIL_REQUIRE_AGENT_NATIVE === '1';
|
||||
|
||||
function log(msg) {
|
||||
console.log(`[agent-native] ${msg}`);
|
||||
}
|
||||
|
||||
function assetNameForHost() {
|
||||
const p = process.platform; // 'darwin' | 'linux' | 'win32'
|
||||
const a = process.arch; // 'arm64' | 'x64'
|
||||
const os = p === 'darwin' ? 'macos' : p === 'win32' ? 'windows' : 'linux';
|
||||
return `agent_napi-${os}-${a}.node`;
|
||||
function fail(msg) {
|
||||
log(msg);
|
||||
return STRICT ? 1 : 0;
|
||||
}
|
||||
|
||||
function readSubmoduleSha() {
|
||||
try {
|
||||
return execSync('git rev-parse HEAD', {
|
||||
cwd: AGENT_DIR,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
}).trim();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function ghJson(url) {
|
||||
const headers = ['-H', 'Accept: application/vnd.github+json'];
|
||||
if (process.env.GITHUB_TOKEN) {
|
||||
headers.push('-H', `Authorization: Bearer ${process.env.GITHUB_TOKEN}`);
|
||||
}
|
||||
const raw = execSync(`curl -sLf ${headers.join(' ')} "${url}"`, { encoding: 'utf8' });
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
function findMatchingRelease(submoduleSha) {
|
||||
if (!submoduleSha) return null;
|
||||
// Walk tags (paginated) until we find one whose commit SHA matches the
|
||||
// submodule pointer. Tags are listed newest first, so for a freshly bumped
|
||||
// submodule we almost always hit on the first page.
|
||||
for (let page = 1; page <= 3; page += 1) {
|
||||
let tags;
|
||||
try {
|
||||
tags = ghJson(`https://api.github.com/repos/${REPO}/tags?per_page=30&page=${page}`);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (!Array.isArray(tags) || tags.length === 0) return null;
|
||||
for (const t of tags) {
|
||||
if (t?.commit?.sha === submoduleSha) return t.name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function downloadPrebuilt(tagName) {
|
||||
let release;
|
||||
try {
|
||||
release = ghJson(`https://api.github.com/repos/${REPO}/releases/tags/${tagName}`);
|
||||
} catch (err) {
|
||||
log(`No release for tag ${tagName}: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
const assetName = assetNameForHost();
|
||||
const asset = release.assets?.find((a) => a.name === assetName);
|
||||
if (!asset) {
|
||||
log(
|
||||
`Release ${tagName} has no asset ${assetName} (built: ${(release.assets ?? []).map((a) => a.name).join(', ') || 'none'}).`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
log(`Downloading ${assetName} from release ${tagName}…`);
|
||||
try {
|
||||
fs.mkdirSync(NAPI_DIR, { recursive: true });
|
||||
execSync(`curl -sLf -o "${BUNDLED}" "${asset.browser_download_url}"`, { stdio: 'inherit' });
|
||||
} catch (err) {
|
||||
log(`Download failed: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
return fs.existsSync(BUNDLED);
|
||||
function bundleBinary() {
|
||||
fs.mkdirSync(NAPI_DIR, { recursive: true });
|
||||
fs.copyFileSync(ZIG_OUT, BUNDLED);
|
||||
log(`Bundled binary at ${BUNDLED}.`);
|
||||
}
|
||||
|
||||
function buildFromSource() {
|
||||
try {
|
||||
execSync('zig version', { stdio: 'ignore' });
|
||||
} catch {
|
||||
log('Zig not installed; cannot build from source.');
|
||||
return false;
|
||||
return fail(
|
||||
'Zig not installed (need 0.15+). Skipping. Install Zig and re-run `bun run agent:build`.',
|
||||
);
|
||||
}
|
||||
log('Building NAPI addon from source (zig build napi)…');
|
||||
const target = process.env.ZIG_TARGET?.trim();
|
||||
const targetFlag = target ? ` -Dtarget=${target}` : '';
|
||||
log(`Building NAPI addon (zig build napi -Doptimize=ReleaseFast${targetFlag})…`);
|
||||
try {
|
||||
execSync('zig build napi -Doptimize=ReleaseFast', {
|
||||
execSync(`zig build napi -Doptimize=ReleaseFast${targetFlag}`, {
|
||||
cwd: AGENT_DIR,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch (err) {
|
||||
log(`Source build failed: ${err.message}`);
|
||||
return false;
|
||||
return fail(`Zig build failed: ${err.message}`);
|
||||
}
|
||||
return fs.existsSync(ZIG_OUT);
|
||||
if (!fs.existsSync(ZIG_OUT)) {
|
||||
return fail(`Zig build produced no output at ${ZIG_OUT}.`);
|
||||
}
|
||||
bundleBinary();
|
||||
return 0;
|
||||
}
|
||||
|
||||
function main() {
|
||||
// 1. Already have it?
|
||||
if (fs.existsSync(ZIG_OUT) || fs.existsSync(BUNDLED)) {
|
||||
log('Binary already present, skipping.');
|
||||
if (process.env.OPENPENCIL_SKIP_AGENT_NATIVE === '1') {
|
||||
log('OPENPENCIL_SKIP_AGENT_NATIVE=1, skipping native binary provisioning.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Submodule initialized?
|
||||
if (!fs.existsSync(path.join(NAPI_DIR, 'package.json'))) {
|
||||
log('Submodule not initialized; run `git submodule update --init`. Skipping.');
|
||||
return fail('Submodule not initialized; run `git submodule update --init`. Skipping.');
|
||||
}
|
||||
|
||||
// Fast path: binary already in place. Make sure both lookup locations are
|
||||
// populated so electron-builder (`napi/`) and the runtime loader (which
|
||||
// checks `zig-out/` first) both find it without re-running the build.
|
||||
if (fs.existsSync(BUNDLED)) {
|
||||
log('Binary already present, skipping rebuild.');
|
||||
return 0;
|
||||
}
|
||||
if (fs.existsSync(ZIG_OUT)) {
|
||||
log('Binary already built; copying into napi/ for electron-builder.');
|
||||
bundleBinary();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 2. Prebuilt release matching submodule SHA?
|
||||
const sha = readSubmoduleSha();
|
||||
if (sha) {
|
||||
const tag = findMatchingRelease(sha);
|
||||
if (tag) {
|
||||
if (downloadPrebuilt(tag)) {
|
||||
log(`Prebuilt ready at ${BUNDLED}.`);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
log(`No release tag matches submodule ${sha.slice(0, 7)}.`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Source build fallback.
|
||||
if (buildFromSource()) {
|
||||
log(`Built at ${ZIG_OUT}.`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
log('Could not provision agent_napi.node. Tests / runtime will fail loudly until resolved.');
|
||||
log(
|
||||
'Options: bump + tag the agent submodule, or install Zig 0.15+ and run `bun run agent:build`.',
|
||||
);
|
||||
return 0; // Non-fatal: let the wrapper keep install green, real error surfaces at test time.
|
||||
return buildFromSource();
|
||||
}
|
||||
|
||||
process.exit(main());
|
||||
|
|
|
|||
Loading…
Reference in a new issue