mirror of
https://github.com/ZSeven-W/openpencil.git
synced 2026-06-01 03:14: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
|
- os: macos-latest
|
||||||
platform: mac-arm64
|
platform: mac-arm64
|
||||||
build_args: --mac --arm64
|
build_args: --mac --arm64
|
||||||
|
zig_target: aarch64-macos
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
platform: mac-x64
|
platform: mac-x64
|
||||||
build_args: --mac --x64
|
build_args: --mac --x64
|
||||||
|
zig_target: x86_64-macos
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
platform: win
|
platform: win
|
||||||
build_args: --win
|
build_args: --win
|
||||||
|
zig_target: x86_64-windows
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
platform: linux
|
platform: linux
|
||||||
build_args: --linux
|
build_args: --linux
|
||||||
|
zig_target: x86_64-linux
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
@ -50,8 +54,20 @@ jobs:
|
||||||
version: 0.15.2
|
version: 0.15.2
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
OPENPENCIL_REQUIRE_AGENT_NATIVE: '1'
|
||||||
|
ZIG_TARGET: ${{ matrix.zig_target }}
|
||||||
run: bun install --frozen-lockfile
|
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)
|
- name: Build web (electron target)
|
||||||
run: bun --bun run build
|
run: bun --bun run build
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
2
.github/workflows/publish-cli.yml
vendored
2
.github/workflows/publish-cli.yml
vendored
|
|
@ -34,6 +34,8 @@ jobs:
|
||||||
version: 0.15.2
|
version: 0.15.2
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
OPENPENCIL_SKIP_AGENT_NATIVE: '1'
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
|
|
||||||
- name: Get version
|
- 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
|
- **Design:** `op design <dsl|@file|->` — batch design DSL operations
|
||||||
- **Document:** `op open`, `op save`, `op get`, `op selection`
|
- **Document:** `op open`, `op save`, `op get`, `op selection`
|
||||||
- **Nodes:** `op insert`, `op update`, `op delete`, `op move`, `op copy`, `op replace`
|
- **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)
|
- **Cross-platform:** macOS, Windows (NSIS/portable), Linux (AppImage/deb/snap/flatpak)
|
||||||
|
|
||||||
### CI / CD
|
### 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
|
- **Design:** `op design <dsl|@file|->` — batch design DSL operations
|
||||||
- **Document:** `op open`, `op save`, `op get`, `op selection`
|
- **Document:** `op open`, `op save`, `op get`, `op selection`
|
||||||
- **Nodes:** `op insert`, `op update`, `op delete`, `op move`, `op copy`, `op replace`
|
- **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)
|
- **Cross-platform:** macOS, Windows (NSIS/portable), Linux (AppImage/deb/snap/flatpak)
|
||||||
|
|
||||||
### CI / CD
|
### CI / CD
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ Web-App + native Desktop-Anwendung auf macOS, Windows und Linux über Electron.
|
||||||
|
|
||||||
### ⌨️ CLI — `op`
|
### ⌨️ 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>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # Desktop-App starten
|
op start # Desktop-App starten
|
||||||
op design @landing.txt # Batch-Design aus Datei
|
op design @landing.txt # Batch-Design aus Datei
|
||||||
op insert '{"type":"RECT"}' # Knoten einfügen
|
op insert '{"type":"RECT"}' # Knoten einfügen
|
||||||
op export react --out . # Nach React + Tailwind exportieren
|
|
||||||
op import:figma design.fig # Figma-Datei importieren
|
op import:figma design.fig # Figma-Datei importieren
|
||||||
cat design.dsl | op design - # Pipe von stdin
|
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 |
|
| **State** | Zustand v5 |
|
||||||
| **Server** | Nitro |
|
| **Server** | Nitro |
|
||||||
| **Desktop** | Electron 35 |
|
| **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 |
|
| **KI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **Laufzeit** | Bun · Vite 7 |
|
| **Laufzeit** | Bun · Vite 7 |
|
||||||
| **Dateiformat** | `.op` — JSON-basiert, menschenlesbar, Git-freundlich |
|
| **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`
|
### ⌨️ 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>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # Iniciar la app de escritorio
|
op start # Iniciar la app de escritorio
|
||||||
op design @landing.txt # Diseño por lotes desde archivo
|
op design @landing.txt # Diseño por lotes desde archivo
|
||||||
op insert '{"type":"RECT"}' # Insertar un nodo
|
op insert '{"type":"RECT"}' # Insertar un nodo
|
||||||
op export react --out . # Exportar a React + Tailwind
|
|
||||||
op import:figma design.fig # Importar archivo de Figma
|
op import:figma design.fig # Importar archivo de Figma
|
||||||
cat design.dsl | op design - # Entrada por pipe desde stdin
|
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 |
|
| **Estado** | Zustand v5 |
|
||||||
| **Servidor** | Nitro |
|
| **Servidor** | Nitro |
|
||||||
| **Escritorio** | Electron 35 |
|
| **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 |
|
| **IA** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **Runtime** | Bun · Vite 7 |
|
| **Runtime** | Bun · Vite 7 |
|
||||||
| **Formato de archivo** | `.op` — basado en JSON, legible por humanos, compatible con Git |
|
| **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`
|
### ⌨️ 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>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # Lancer l'app de bureau
|
op start # Lancer l'app de bureau
|
||||||
op design @landing.txt # Design par lots depuis un fichier
|
op design @landing.txt # Design par lots depuis un fichier
|
||||||
op insert '{"type":"RECT"}' # Insérer un nœud
|
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
|
op import:figma design.fig # Importer un fichier Figma
|
||||||
cat design.dsl | op design - # Pipe depuis stdin
|
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 |
|
| **État** | Zustand v5 |
|
||||||
| **Serveur** | Nitro |
|
| **Serveur** | Nitro |
|
||||||
| **Bureau** | Electron 35 |
|
| **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 |
|
| **IA** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **Runtime** | Bun · Vite 7 |
|
| **Runtime** | Bun · Vite 7 |
|
||||||
| **Format de fichier** | `.op` — basé sur JSON, lisible par l'humain, compatible Git |
|
| **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`
|
### ⌨️ CLI — `op`
|
||||||
|
|
||||||
अपने टर्मिनल से डिज़ाइन टूल को नियंत्रित करें। `op design`, `op insert`, `op export` — बैच डिज़ाइन DSL, नोड मैनिपुलेशन, कोड एक्सपोर्ट। फ़ाइलों या stdin से पाइप करें। डेस्कटॉप ऐप या वेब सर्वर के साथ काम करता है।
|
अपने टर्मिनल से डिज़ाइन टूल को नियंत्रित करें। `op design`, `op insert` — बैच डिज़ाइन DSL, नोड मैनिपुलेशन। फ़ाइलों या stdin से पाइप करें। डेस्कटॉप ऐप या वेब सर्वर के साथ काम करता है।
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # डेस्कटॉप ऐप लॉन्च करें
|
op start # डेस्कटॉप ऐप लॉन्च करें
|
||||||
op design @landing.txt # फ़ाइल से बैच डिज़ाइन
|
op design @landing.txt # फ़ाइल से बैच डिज़ाइन
|
||||||
op insert '{"type":"RECT"}' # एक नोड डालें
|
op insert '{"type":"RECT"}' # एक नोड डालें
|
||||||
op export react --out . # React + Tailwind में एक्सपोर्ट
|
|
||||||
op import:figma design.fig # Figma फ़ाइल इम्पोर्ट करें
|
op import:figma design.fig # Figma फ़ाइल इम्पोर्ट करें
|
||||||
cat design.dsl | op design - # stdin से पाइप करें
|
cat design.dsl | op design - # stdin से पाइप करें
|
||||||
```
|
```
|
||||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # stdin से पाइप करें
|
||||||
| **स्टेट** | Zustand v5 |
|
| **स्टेट** | Zustand v5 |
|
||||||
| **सर्वर** | Nitro |
|
| **सर्वर** | Nitro |
|
||||||
| **डेस्कटॉप** | Electron 35 |
|
| **डेस्कटॉप** | Electron 35 |
|
||||||
| **CLI** | `op` — टर्मिनल नियंत्रण, बैच डिज़ाइन DSL, कोड एक्सपोर्ट |
|
| **CLI** | `op` — टर्मिनल नियंत्रण, बैच डिज़ाइन DSL |
|
||||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **रनटाइम** | Bun · Vite 7 |
|
| **रनटाइम** | Bun · Vite 7 |
|
||||||
| **फ़ाइल फ़ॉर्मेट** | `.op` — JSON-आधारित, मानव-पठनीय, Git-फ्रेंडली |
|
| **फ़ाइल फ़ॉर्मेट** | `.op` — JSON-आधारित, मानव-पठनीय, Git-फ्रेंडली |
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ Aplikasi web + desktop native di macOS, Windows, dan Linux melalui Electron. Pem
|
||||||
|
|
||||||
### ⌨️ CLI — `op`
|
### ⌨️ 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>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # Jalankan aplikasi desktop
|
op start # Jalankan aplikasi desktop
|
||||||
op design @landing.txt # Desain batch dari file
|
op design @landing.txt # Desain batch dari file
|
||||||
op insert '{"type":"RECT"}' # Sisipkan sebuah node
|
op insert '{"type":"RECT"}' # Sisipkan sebuah node
|
||||||
op export react --out . # Ekspor ke React + Tailwind
|
|
||||||
op import:figma design.fig # Impor file Figma
|
op import:figma design.fig # Impor file Figma
|
||||||
cat design.dsl | op design - # Pipe dari stdin
|
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 |
|
| **State** | Zustand v5 |
|
||||||
| **Server** | Nitro |
|
| **Server** | Nitro |
|
||||||
| **Desktop** | Electron 35 |
|
| **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 |
|
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **Runtime** | Bun · Vite 7 |
|
| **Runtime** | Bun · Vite 7 |
|
||||||
| **Format file** | `.op` — berbasis JSON, mudah dibaca manusia, ramah Git |
|
| **Format file** | `.op` — berbasis JSON, mudah dibaca manusia, ramah Git |
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ Web アプリ + Electron による macOS・Windows・Linux ネイティブデス
|
||||||
|
|
||||||
### ⌨️ CLI — `op`
|
### ⌨️ CLI — `op`
|
||||||
|
|
||||||
ターミナルからデザインツールを操作。`op design`、`op insert`、`op export` — バッチデザインDSL、ノード操作、コードエクスポート。ファイルやstdinからのパイプ入力に対応。デスクトップアプリまたはWebサーバーと連携。
|
ターミナルからデザインツールを操作。`op design`、`op insert` — バッチデザインDSL、ノード操作。ファイルやstdinからのパイプ入力に対応。デスクトップアプリまたはWebサーバーと連携。
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # デスクトップアプリを起動
|
op start # デスクトップアプリを起動
|
||||||
op design @landing.txt # ファイルからバッチデザイン
|
op design @landing.txt # ファイルからバッチデザイン
|
||||||
op insert '{"type":"RECT"}' # ノードを挿入
|
op insert '{"type":"RECT"}' # ノードを挿入
|
||||||
op export react --out . # React + Tailwind にエクスポート
|
|
||||||
op import:figma design.fig # Figma ファイルをインポート
|
op import:figma design.fig # Figma ファイルをインポート
|
||||||
cat design.dsl | op design - # stdin からパイプ入力
|
cat design.dsl | op design - # stdin からパイプ入力
|
||||||
```
|
```
|
||||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # stdin からパイプ入力
|
||||||
| **状態管理** | Zustand v5 |
|
| **状態管理** | Zustand v5 |
|
||||||
| **サーバー** | Nitro |
|
| **サーバー** | Nitro |
|
||||||
| **デスクトップ** | Electron 35 |
|
| **デスクトップ** | Electron 35 |
|
||||||
| **CLI** | `op` — ターミナル制御、バッチデザインDSL、コードエクスポート |
|
| **CLI** | `op` — ターミナル制御、バッチデザインDSL |
|
||||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **ランタイム** | Bun · Vite 7 |
|
| **ランタイム** | Bun · Vite 7 |
|
||||||
| **ファイル形式** | `.op` — JSON ベース、人間が読みやすく、Git フレンドリー |
|
| **ファイル形式** | `.op` — JSON ベース、人間が読みやすく、Git フレンドリー |
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ Claude Code, Codex, Gemini, OpenCode, Kiro 또는 Copilot CLI에 원클릭 설
|
||||||
|
|
||||||
### ⌨️ CLI — `op`
|
### ⌨️ CLI — `op`
|
||||||
|
|
||||||
터미널에서 디자인 도구 제어. `op design`, `op insert`, `op export` — 배치 디자인 DSL, 노드 조작, 코드 내보내기. 파일이나 stdin에서 파이프 입력 지원. 데스크톱 앱 또는 웹 서버와 연동.
|
터미널에서 디자인 도구 제어. `op design`, `op insert` — 배치 디자인 DSL, 노드 조작. 파일이나 stdin에서 파이프 입력 지원. 데스크톱 앱 또는 웹 서버와 연동.
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # 데스크톱 앱 실행
|
op start # 데스크톱 앱 실행
|
||||||
op design @landing.txt # 파일에서 배치 디자인
|
op design @landing.txt # 파일에서 배치 디자인
|
||||||
op insert '{"type":"RECT"}' # 노드 삽입
|
op insert '{"type":"RECT"}' # 노드 삽입
|
||||||
op export react --out . # React + Tailwind로 내보내기
|
|
||||||
op import:figma design.fig # Figma 파일 가져오기
|
op import:figma design.fig # Figma 파일 가져오기
|
||||||
cat design.dsl | op design - # stdin에서 파이프 입력
|
cat design.dsl | op design - # stdin에서 파이프 입력
|
||||||
```
|
```
|
||||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # stdin에서 파이프 입력
|
||||||
| **상태 관리** | Zustand v5 |
|
| **상태 관리** | Zustand v5 |
|
||||||
| **서버** | Nitro |
|
| **서버** | Nitro |
|
||||||
| **데스크톱** | Electron 35 |
|
| **데스크톱** | Electron 35 |
|
||||||
| **CLI** | `op` — 터미널 제어, 배치 디자인 DSL, 코드 내보내기 |
|
| **CLI** | `op` — 터미널 제어, 배치 디자인 DSL |
|
||||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **런타임** | Bun · Vite 7 |
|
| **런타임** | Bun · Vite 7 |
|
||||||
| **파일 형식** | `.op` — JSON 기반, 사람이 읽을 수 있는, Git 친화적 |
|
| **파일 형식** | `.op` — JSON 기반, 사람이 읽을 수 있는, Git 친화적 |
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ Web app + native desktop on macOS, Windows, and Linux via Electron. Auto-updates
|
||||||
|
|
||||||
### ⌨️ CLI — `op`
|
### ⌨️ 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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -275,7 +275,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # Launch desktop app
|
op start # Launch desktop app
|
||||||
op design @landing.txt # Batch design from file
|
op design @landing.txt # Batch design from file
|
||||||
op insert '{"type":"RECT"}' # Insert a node
|
op insert '{"type":"RECT"}' # Insert a node
|
||||||
op export react --out . # Export to React + Tailwind
|
|
||||||
op import:figma design.fig # Import Figma file
|
op import:figma design.fig # Import Figma file
|
||||||
cat design.dsl | op design - # Pipe from stdin
|
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 |
|
| **State** | Zustand v5 |
|
||||||
| **Server** | Nitro |
|
| **Server** | Nitro |
|
||||||
| **Desktop** | Electron 35 |
|
| **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 |
|
| **AI** | agent-native (Zig NAPI) · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **Runtime** | Bun · Vite 7 |
|
| **Runtime** | Bun · Vite 7 |
|
||||||
| **Lint** | oxlint · oxfmt |
|
| **Lint** | oxlint · oxfmt |
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ App web + desktop nativo no macOS, Windows e Linux via Electron. Atualização a
|
||||||
|
|
||||||
### ⌨️ CLI — `op`
|
### ⌨️ 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>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # Iniciar app desktop
|
op start # Iniciar app desktop
|
||||||
op design @landing.txt # Design em lote a partir de arquivo
|
op design @landing.txt # Design em lote a partir de arquivo
|
||||||
op insert '{"type":"RECT"}' # Inserir um nó
|
op insert '{"type":"RECT"}' # Inserir um nó
|
||||||
op export react --out . # Exportar para React + Tailwind
|
|
||||||
op import:figma design.fig # Importar arquivo Figma
|
op import:figma design.fig # Importar arquivo Figma
|
||||||
cat design.dsl | op design - # Entrada por pipe via stdin
|
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 |
|
| **Estado** | Zustand v5 |
|
||||||
| **Servidor** | Nitro |
|
| **Servidor** | Nitro |
|
||||||
| **Desktop** | Electron 35 |
|
| **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 |
|
| **IA** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **Runtime** | Bun · Vite 7 |
|
| **Runtime** | Bun · Vite 7 |
|
||||||
| **Formato de arquivo** | `.op` — baseado em JSON, legível por humanos, compatível com Git |
|
| **Formato de arquivo** | `.op` — baseado em JSON, legível por humanos, compatível com Git |
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@
|
||||||
|
|
||||||
### ⌨️ CLI — `op`
|
### ⌨️ CLI — `op`
|
||||||
|
|
||||||
Управляйте инструментом дизайна из терминала. `op design`, `op insert`, `op export` — пакетный DSL дизайна, манипуляция узлами, экспорт кода. Ввод через pipe из файлов или stdin. Работает с десктопным приложением или веб-сервером.
|
Управляйте инструментом дизайна из терминала. `op design`, `op insert` — пакетный DSL дизайна, манипуляция узлами. Ввод через pipe из файлов или stdin. Работает с десктопным приложением или веб-сервером.
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # Запустить десктопное приложение
|
op start # Запустить десктопное приложение
|
||||||
op design @landing.txt # Пакетный дизайн из файла
|
op design @landing.txt # Пакетный дизайн из файла
|
||||||
op insert '{"type":"RECT"}' # Вставить узел
|
op insert '{"type":"RECT"}' # Вставить узел
|
||||||
op export react --out . # Экспорт в React + Tailwind
|
|
||||||
op import:figma design.fig # Импортировать файл Figma
|
op import:figma design.fig # Импортировать файл Figma
|
||||||
cat design.dsl | op design - # Передача через stdin
|
cat design.dsl | op design - # Передача через stdin
|
||||||
```
|
```
|
||||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # Передача через stdin
|
||||||
| **Состояние** | Zustand v5 |
|
| **Состояние** | Zustand v5 |
|
||||||
| **Сервер** | Nitro |
|
| **Сервер** | Nitro |
|
||||||
| **Десктоп** | Electron 35 |
|
| **Десктоп** | Electron 35 |
|
||||||
| **CLI** | `op` — управление из терминала, пакетный DSL дизайна, экспорт кода |
|
| **CLI** | `op` — управление из терминала, пакетный DSL дизайна |
|
||||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **Среда выполнения** | Bun · Vite 7 |
|
| **Среда выполнения** | Bun · Vite 7 |
|
||||||
| **Формат файла** | `.op` — на основе JSON, удобочитаемый, дружественный к Git |
|
| **Формат файла** | `.op` — на основе JSON, удобочитаемый, дружественный к Git |
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ Orchestrator แบ่งหน้าที่ซับซ้อนออกเ
|
||||||
|
|
||||||
### ⌨️ CLI — `op`
|
### ⌨️ 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>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # เปิดแอปเดสก์ท็อป
|
op start # เปิดแอปเดสก์ท็อป
|
||||||
op design @landing.txt # ออกแบบแบบ batch จากไฟล์
|
op design @landing.txt # ออกแบบแบบ batch จากไฟล์
|
||||||
op insert '{"type":"RECT"}' # แทรก node
|
op insert '{"type":"RECT"}' # แทรก node
|
||||||
op export react --out . # ส่งออกเป็น React + Tailwind
|
|
||||||
op import:figma design.fig # นำเข้าไฟล์ Figma
|
op import:figma design.fig # นำเข้าไฟล์ Figma
|
||||||
cat design.dsl | op design - # Pipe จาก stdin
|
cat design.dsl | op design - # Pipe จาก stdin
|
||||||
```
|
```
|
||||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # Pipe จาก stdin
|
||||||
| **State** | Zustand v5 |
|
| **State** | Zustand v5 |
|
||||||
| **Server** | Nitro |
|
| **Server** | Nitro |
|
||||||
| **Desktop** | Electron 35 |
|
| **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 |
|
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **Runtime** | Bun · Vite 7 |
|
| **Runtime** | Bun · Vite 7 |
|
||||||
| **รูปแบบไฟล์** | `.op` — ใช้ JSON, อ่านได้โดยมนุษย์, Git-friendly |
|
| **รูปแบบไฟล์** | `.op` — ใช้ JSON, อ่านได้โดยมนุษย์, Git-friendly |
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ Web uygulaması + Electron ile macOS, Windows ve Linux'ta yerel masaüstü. GitH
|
||||||
|
|
||||||
### ⌨️ CLI — `op`
|
### ⌨️ 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>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # Masaüstü uygulamayı başlat
|
op start # Masaüstü uygulamayı başlat
|
||||||
op design @landing.txt # Dosyadan toplu tasarım
|
op design @landing.txt # Dosyadan toplu tasarım
|
||||||
op insert '{"type":"RECT"}' # Bir düğüm ekle
|
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
|
op import:figma design.fig # Figma dosyasını içe aktar
|
||||||
cat design.dsl | op design - # stdin'den pipe ile besle
|
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 |
|
| **Durum Yönetimi** | Zustand v5 |
|
||||||
| **Sunucu** | Nitro |
|
| **Sunucu** | Nitro |
|
||||||
| **Masaüstü** | Electron 35 |
|
| **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 |
|
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **Çalışma Ortamı** | Bun · Vite 7 |
|
| **Çalışma Ortamı** | Bun · Vite 7 |
|
||||||
| **Dosya Formatı** | `.op` — JSON tabanlı, insan tarafından okunabilir, Git dostu |
|
| **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`
|
### ⌨️ 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>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # Khởi chạy ứng dụng desktop
|
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 design @landing.txt # Thiết kế hàng loạt từ tệp
|
||||||
op insert '{"type":"RECT"}' # Chèn một node
|
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
|
op import:figma design.fig # Nhập tệp Figma
|
||||||
cat design.dsl | op design - # Pipe từ stdin
|
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 |
|
| **Trạng thái** | Zustand v5 |
|
||||||
| **Máy chủ** | Nitro |
|
| **Máy chủ** | Nitro |
|
||||||
| **Desktop** | Electron 35 |
|
| **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 |
|
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **Runtime** | Bun · Vite 7 |
|
| **Runtime** | Bun · Vite 7 |
|
||||||
| **Định dạng tệp** | `.op` — dựa trên JSON, dễ đọc, thân thiện với Git |
|
| **Đị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`
|
### ⌨️ CLI — `op`
|
||||||
|
|
||||||
從終端機控制設計工具。`op design`、`op insert`、`op export` — 批次設計 DSL、節點操作、程式碼匯出。支援從檔案或 stdin 管道輸入。可搭配桌面應用程式或 Web 伺服器使用。
|
從終端機控制設計工具。`op design`、`op insert` — 批次設計 DSL、節點操作。支援從檔案或 stdin 管道輸入。可搭配桌面應用程式或 Web 伺服器使用。
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # 啟動桌面應用程式
|
op start # 啟動桌面應用程式
|
||||||
op design @landing.txt # 從檔案批次設計
|
op design @landing.txt # 從檔案批次設計
|
||||||
op insert '{"type":"RECT"}' # 插入節點
|
op insert '{"type":"RECT"}' # 插入節點
|
||||||
op export react --out . # 匯出為 React + Tailwind
|
|
||||||
op import:figma design.fig # 匯入 Figma 檔案
|
op import:figma design.fig # 匯入 Figma 檔案
|
||||||
cat design.dsl | op design - # 從 stdin 管道輸入
|
cat design.dsl | op design - # 從 stdin 管道輸入
|
||||||
```
|
```
|
||||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # 從 stdin 管道輸入
|
||||||
| **狀態管理** | Zustand v5 |
|
| **狀態管理** | Zustand v5 |
|
||||||
| **伺服器** | Nitro |
|
| **伺服器** | Nitro |
|
||||||
| **桌面端** | Electron 35 |
|
| **桌面端** | Electron 35 |
|
||||||
| **CLI** | `op` — 終端機控制、批次設計 DSL、程式碼匯出 |
|
| **CLI** | `op` — 終端機控制、批次設計 DSL |
|
||||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **執行環境** | Bun · Vite 7 |
|
| **執行環境** | Bun · Vite 7 |
|
||||||
| **檔案格式** | `.op` — 基於 JSON,人類可讀,對 Git 友好 |
|
| **檔案格式** | `.op` — 基於 JSON,人類可讀,對 Git 友好 |
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ Web 应用 + 通过 Electron 支持 macOS、Windows 和 Linux 原生桌面端。
|
||||||
|
|
||||||
### ⌨️ CLI — `op`
|
### ⌨️ CLI — `op`
|
||||||
|
|
||||||
从终端控制设计工具。`op design`、`op insert`、`op export` — 批量设计 DSL、节点操作、代码导出。支持从文件或 stdin 管道输入。可搭配桌面应用或 Web 服务器使用。
|
从终端控制设计工具。`op design`、`op insert` — 批量设计 DSL、节点操作。支持从文件或 stdin 管道输入。可搭配桌面应用或 Web 服务器使用。
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td width="50%">
|
<td width="50%">
|
||||||
|
|
@ -220,7 +220,6 @@ npm install -g @zseven-w/openpencil
|
||||||
op start # 启动桌面应用
|
op start # 启动桌面应用
|
||||||
op design @landing.txt # 从文件批量设计
|
op design @landing.txt # 从文件批量设计
|
||||||
op insert '{"type":"RECT"}' # 插入节点
|
op insert '{"type":"RECT"}' # 插入节点
|
||||||
op export react --out . # 导出为 React + Tailwind
|
|
||||||
op import:figma design.fig # 导入 Figma 文件
|
op import:figma design.fig # 导入 Figma 文件
|
||||||
cat design.dsl | op design - # 从 stdin 管道输入
|
cat design.dsl | op design - # 从 stdin 管道输入
|
||||||
```
|
```
|
||||||
|
|
@ -294,7 +293,7 @@ cat design.dsl | op design - # 从 stdin 管道输入
|
||||||
| **状态管理** | Zustand v5 |
|
| **状态管理** | Zustand v5 |
|
||||||
| **服务器** | Nitro |
|
| **服务器** | Nitro |
|
||||||
| **桌面端** | Electron 35 |
|
| **桌面端** | Electron 35 |
|
||||||
| **CLI** | `op` — 终端控制、批量设计 DSL、代码导出 |
|
| **CLI** | `op` — 终端控制、批量设计 DSL |
|
||||||
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
| **AI** | Vercel AI SDK v6 · Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
|
||||||
| **运行时** | Bun · Vite 7 |
|
| **运行时** | Bun · Vite 7 |
|
||||||
| **文件格式** | `.op` — 基于 JSON,人类可读,对 Git 友好 |
|
| **文件格式** | `.op` — 基于 JSON,人类可读,对 Git 友好 |
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ apps/cli/
|
||||||
│ ├── app.ts start, stop, status
|
│ ├── app.ts start, stop, status
|
||||||
│ ├── design.ts design, design:skeleton, design:content, design:refine
|
│ ├── design.ts design, design:skeleton, design:content, design:refine
|
||||||
│ ├── document.ts open, save, get, selection
|
│ ├── 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
|
│ ├── import.ts import:svg, import:figma
|
||||||
│ ├── install.ts install, uninstall (openpencil-skill for AI agents)
|
│ ├── install.ts install, uninstall (openpencil-skill for AI agents)
|
||||||
│ ├── layout.ts layout, find-space
|
│ ├── layout.ts layout, find-space
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
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
|
### Variablen und Themes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
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
|
### Variables y temas
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
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
|
### Variables et themes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
op replace <id> <json> [--post-process]
|
||||||
```
|
```
|
||||||
|
|
||||||
### कोड निर्यात
|
|
||||||
|
|
||||||
```bash
|
|
||||||
op export <format> [--out file]
|
|
||||||
# प्रारूप: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
|
||||||
```
|
|
||||||
|
|
||||||
### वेरिएबल और थीम
|
### वेरिएबल और थीम
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
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
|
### Variabel & Tema
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
op replace <id> <json> [--post-process]
|
||||||
```
|
```
|
||||||
|
|
||||||
### コードエクスポート
|
|
||||||
|
|
||||||
```bash
|
|
||||||
op export <format> [--out file]
|
|
||||||
# フォーマット: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
|
||||||
```
|
|
||||||
|
|
||||||
### 変数とテーマ
|
### 変数とテーマ
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
op replace <id> <json> [--post-process]
|
||||||
```
|
```
|
||||||
|
|
||||||
### 코드 내보내기
|
|
||||||
|
|
||||||
```bash
|
|
||||||
op export <format> [--out file]
|
|
||||||
# 형식: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
|
||||||
```
|
|
||||||
|
|
||||||
### 변수 및 테마
|
### 변수 및 테마
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
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
|
### Variables & Themes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
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
|
### Variaveis e Temas
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
op replace <id> <json> [--post-process]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Экспорт кода
|
|
||||||
|
|
||||||
```bash
|
|
||||||
op export <format> [--out file]
|
|
||||||
# Форматы: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
|
||||||
```
|
|
||||||
|
|
||||||
### Переменные и темы
|
### Переменные и темы
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
op replace <id> <json> [--post-process]
|
||||||
```
|
```
|
||||||
|
|
||||||
### การส่งออกโค้ด
|
|
||||||
|
|
||||||
```bash
|
|
||||||
op export <format> [--out file]
|
|
||||||
# รูปแบบ: react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
|
||||||
```
|
|
||||||
|
|
||||||
### ตัวแปรและธีม
|
### ตัวแปรและธีม
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
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
|
### Degiskenler ve Temalar
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
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
|
### Biến và giao diện
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
op replace <id> <json> [--post-process]
|
||||||
```
|
```
|
||||||
|
|
||||||
### 程式碼匯出
|
|
||||||
|
|
||||||
```bash
|
|
||||||
op export <format> [--out file]
|
|
||||||
# 格式:react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
|
||||||
```
|
|
||||||
|
|
||||||
### 變數與主題
|
### 變數與主題
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,6 @@ op copy <id> [--parent P]
|
||||||
op replace <id> <json> [--post-process]
|
op replace <id> <json> [--post-process]
|
||||||
```
|
```
|
||||||
|
|
||||||
### 代码导出
|
|
||||||
|
|
||||||
```bash
|
|
||||||
op export <format> [--out file]
|
|
||||||
# 格式:react, html, vue, svelte, flutter, swiftui, compose, rn, css
|
|
||||||
```
|
|
||||||
|
|
||||||
### 变量与主题
|
### 变量与主题
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/openpencil",
|
"name": "@zseven-w/openpencil",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"description": "CLI for OpenPencil — control the design tool from your terminal",
|
"description": "CLI for OpenPencil — control the design tool from your terminal",
|
||||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/apps/cli",
|
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/apps/cli",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/desktop",
|
"name": "@zseven-w/desktop",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/web",
|
"name": "@zseven-w/web",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import {
|
import {
|
||||||
buildProviderModelsURL,
|
buildProviderModelsURL,
|
||||||
|
formatFetchError,
|
||||||
normalizeBaseURL,
|
normalizeBaseURL,
|
||||||
normalizeMemberBaseURL,
|
normalizeMemberBaseURL,
|
||||||
normalizeOptionalBaseURL,
|
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,
|
buildSpawnClaudeCodeProcess,
|
||||||
getClaudeAgentDebugFilePath,
|
getClaudeAgentDebugFilePath,
|
||||||
} from '../../utils/resolve-claude-agent-env';
|
} 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.
|
// SENSITIVE_LOG_PATTERN + readDebugTail are now canonical in @zseven-w/pen-mcp.
|
||||||
// Re-export here to keep existing consumers (tests, other modules) working.
|
// Re-export here to keep existing consumers (tests, other modules) working.
|
||||||
import { SENSITIVE_LOG_PATTERN, readDebugTail } from '@zseven-w/pen-mcp';
|
import { SENSITIVE_LOG_PATTERN, readDebugTail } from '@zseven-w/pen-mcp';
|
||||||
|
|
@ -1101,7 +1105,7 @@ function streamViaBuiltin(body: ChatBody) {
|
||||||
destroyProvider(builtinProvider);
|
destroyProvider(builtinProvider);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const content = error instanceof Error ? error.message : 'Unknown error';
|
const content = formatFetchError(error);
|
||||||
controller.enqueue(
|
controller.enqueue(
|
||||||
encoder.encode(`data: ${JSON.stringify({ type: 'error', content })}\n\n`),
|
encoder.encode(`data: ${JSON.stringify({ type: 'error', content })}\n\n`),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -254,17 +254,29 @@ async function fetchFromOpenverse(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(url.toString(), { headers });
|
// Network failures (ConnectTimeoutError on restricted networks, DNS
|
||||||
if (res.status === 429) {
|
// failures, etc.) need to behave like a 429: return null so the caller
|
||||||
// Rate limited — signal fallback
|
// falls back to Wikimedia. Without this, fetch() throws, the throw
|
||||||
return null;
|
// bubbles up to nitro's default handler, and the user sees a 500
|
||||||
}
|
// instead of placeholder images.
|
||||||
if (!res.ok) {
|
try {
|
||||||
return null;
|
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;
|
const data = (await res.json()) as OpenverseSearchResponse;
|
||||||
return (data.results ?? []).map(mapOpenverseResult);
|
return (data.results ?? []).map(mapOpenverseResult);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchFromWikimedia(query: string, count: number): Promise<ImageSearchResult[]> {
|
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('format', 'json');
|
||||||
url.searchParams.set('origin', '*');
|
url.searchParams.set('origin', '*');
|
||||||
|
|
||||||
const res = await fetch(url.toString());
|
// Same network-failure shielding as Openverse: an empty result is the
|
||||||
if (!res.ok) return [];
|
// 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 data = (await res.json()) as WikimediaQueryResponse;
|
||||||
const pages = data.query?.pages;
|
const pages = data.query?.pages;
|
||||||
if (!pages) return [];
|
if (!pages) return [];
|
||||||
|
|
||||||
return mapWikimediaPages(pages);
|
return mapWikimediaPages(pages);
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { defineEventHandler, readBody } from 'h3';
|
import { defineEventHandler, readBody } from 'h3';
|
||||||
import { buildProviderModelsURL, normalizeOptionalBaseURL } from './provider-url';
|
import { buildProviderModelsURL, formatFetchError, normalizeOptionalBaseURL } from './provider-url';
|
||||||
|
|
||||||
interface ProviderModelsBody {
|
interface ProviderModelsBody {
|
||||||
baseURL: string;
|
baseURL: string;
|
||||||
|
|
@ -63,7 +63,6 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
return { models };
|
return { models };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : 'Unknown error';
|
return { models: [], error: formatFetchError(err) };
|
||||||
return { models: [], error: message };
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -63,3 +63,44 @@ export function normalizeMemberBaseURL(
|
||||||
export function buildProviderModelsURL(baseURL: string): string {
|
export function buildProviderModelsURL(baseURL: string): string {
|
||||||
return `${normalizeOpenAICompatBaseURL(baseURL) ?? normalizeBaseURL(baseURL)}/models`;
|
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-pro', name: 'Doubao Seed 2.0 Pro' },
|
||||||
{ id: 'doubao-seed-2.0-lite', name: 'Doubao Seed 2.0 Lite' },
|
{ id: 'doubao-seed-2.0-lite', name: 'Doubao Seed 2.0 Lite' },
|
||||||
{ id: 'glm-4.7', name: 'GLM-4.7' },
|
{ 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: 'kimi-k2.5', name: 'Kimi K2.5' },
|
||||||
{ id: 'minimax-m2.5', name: 'MiniMax M2.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',
|
altBaseURL: 'https://api.deepseek.com/anthropic',
|
||||||
altType: 'anthropic',
|
altType: 'anthropic',
|
||||||
placeholder: 'sk-...',
|
placeholder: 'sk-...',
|
||||||
modelPlaceholder: 'deepseek-chat',
|
modelPlaceholder: 'deepseek-v4-pro',
|
||||||
},
|
},
|
||||||
gemini: {
|
gemini: {
|
||||||
label: 'Google Gemini',
|
label: 'Google Gemini',
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,42 @@ const MODEL_PROFILES: ModelProfile[] = [
|
||||||
},
|
},
|
||||||
{ match: 'gemini-pro', tier: 'standard', thinkingMode: 'disabled', label: 'Gemini Pro' },
|
{ match: 'gemini-pro', tier: 'standard', thinkingMode: 'disabled', label: 'Gemini Pro' },
|
||||||
{ match: /^gemini-2/, tier: 'standard', thinkingMode: 'disabled', label: 'Gemini 2' },
|
{ 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
|
// Basic tier — disable thinking, use simplified prompt
|
||||||
{ match: 'claude-haiku', tier: 'basic', thinkingMode: 'disabled', label: 'Claude Haiku' },
|
{ match: 'claude-haiku', tier: 'basic', thinkingMode: 'disabled', label: 'Claude Haiku' },
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "openpencil",
|
"name": "openpencil",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"private": true,
|
"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.",
|
"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": {
|
"author": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/pen-acp",
|
"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",
|
"description": "ACP (Agent Client Protocol) client for OpenPencil — connect to external ACP agents",
|
||||||
"files": [
|
"files": [
|
||||||
"src"
|
"src"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/pen-ai-skills",
|
"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",
|
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-ai-skills",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/ZSeven-W/openpencil/issues"
|
"url": "https://github.com/ZSeven-W/openpencil/issues"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/pen-core",
|
"name": "@zseven-w/pen-core",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"description": "Core document operations, tree utils, variables, layout engine for OpenPencil",
|
"description": "Core document operations, tree utils, variables, layout engine for OpenPencil",
|
||||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-core",
|
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-core",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/pen-engine",
|
"name": "@zseven-w/pen-engine",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"description": "Headless design engine for OpenPencil — zero framework dependencies",
|
"description": "Headless design engine for OpenPencil — zero framework dependencies",
|
||||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-engine",
|
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-engine",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/pen-figma",
|
"name": "@zseven-w/pen-figma",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"description": "Figma .fig file parser and converter for OpenPencil",
|
"description": "Figma .fig file parser and converter for OpenPencil",
|
||||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-figma",
|
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-figma",
|
||||||
"bugs": {
|
"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.
|
[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
|
> **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.
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @zseven-w/pen-mcp
|
|
||||||
# or
|
|
||||||
bun add @zseven-w/pen-mcp
|
|
||||||
```
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|
@ -22,28 +16,26 @@ Three workflows are supported:
|
||||||
| **Layered** | `design_skeleton` → `design_content` × N → `design_refine` | Full-page designs with high fidelity |
|
| **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 |
|
| **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
|
```bash
|
||||||
# Run as stdio MCP server (for Claude Desktop, Cursor, etc.)
|
git clone https://github.com/ZSeven-W/openpencil.git
|
||||||
npx @zseven-w/pen-mcp
|
cd openpencil && bun install
|
||||||
|
bun run mcp:dev # starts stdio + HTTP on port 3100
|
||||||
# Or connect to a running OpenPencil instance
|
# flags: --http (HTTP only), --stdio (stdio only), --port <n>
|
||||||
op mcp:dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Claude Desktop Configuration
|
### Built-in to the OpenPencil app
|
||||||
|
|
||||||
```json
|
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.
|
||||||
{
|
|
||||||
"mcpServers": {
|
### Connecting an MCP client
|
||||||
"openpencil": {
|
|
||||||
"command": "npx",
|
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.
|
||||||
"args": ["@zseven-w/pen-mcp"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/pen-mcp",
|
"name": "@zseven-w/pen-mcp",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"description": "MCP server, document manager, and tools for OpenPencil",
|
"description": "MCP server, document manager, and tools for OpenPencil",
|
||||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-mcp",
|
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-mcp",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/pen-react",
|
"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",
|
"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",
|
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-react",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/pen-renderer",
|
"name": "@zseven-w/pen-renderer",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"description": "Standalone CanvasKit/Skia renderer for OpenPencil (.op) design files",
|
"description": "Standalone CanvasKit/Skia renderer for OpenPencil (.op) design files",
|
||||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-renderer",
|
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-renderer",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/pen-sdk",
|
"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",
|
"description": "OpenPencil SDK — parse, manipulate, and generate code from .op design files",
|
||||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-sdk",
|
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-sdk",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@zseven-w/pen-types",
|
"name": "@zseven-w/pen-types",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"description": "Type definitions for OpenPencil document model",
|
"description": "Type definitions for OpenPencil document model",
|
||||||
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-types",
|
"homepage": "https://github.com/ZSeven-W/openpencil/tree/main/packages/pen-types",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
||||||
|
|
@ -1,165 +1,105 @@
|
||||||
#!/usr/bin/env node
|
#!/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):
|
// We always build from source on the host so the resulting `agent_napi.node`
|
||||||
// 1. Already built / bundled — use it.
|
// matches the runner's platform/arch. Earlier revisions also tried to download
|
||||||
// 2. Download prebuilt from the ZSeven-W/agent release whose tag points at
|
// a prebuilt from a sibling release repo, but that path was racy: when the
|
||||||
// the submodule's currently checked-out commit. This means CI and local
|
// prebuilt was missing for the current submodule SHA the build fell through
|
||||||
// installs never need Zig installed, as long as whoever bumped the
|
// to source compilation, deposited the binary at `zig-out/napi/...`, and
|
||||||
// submodule also tagged + published a matching release.
|
// electron-builder (which only ships `packages/agent-native/napi/`) silently
|
||||||
// 3. Build from source with local Zig (slow but authoritative).
|
// 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
|
// Build prerequisite: Zig 0.15+ on PATH. CI workflows install it via
|
||||||
// codes so `bun install` never breaks. Tests / runtime will surface a clear
|
// `mlugg/setup-zig`; local devs install once via their package manager.
|
||||||
// "could not locate agent_napi.node" error instead, with instructions.
|
//
|
||||||
|
// 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 fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { execSync } = require('child_process');
|
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 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 ZIG_OUT = path.join(AGENT_DIR, 'zig-out', 'napi', 'agent_napi.node');
|
||||||
const BUNDLED = path.join(NAPI_DIR, 'agent_napi.node');
|
const BUNDLED = path.join(NAPI_DIR, 'agent_napi.node');
|
||||||
|
const STRICT = process.env.OPENPENCIL_REQUIRE_AGENT_NATIVE === '1';
|
||||||
|
|
||||||
function log(msg) {
|
function log(msg) {
|
||||||
console.log(`[agent-native] ${msg}`);
|
console.log(`[agent-native] ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assetNameForHost() {
|
function fail(msg) {
|
||||||
const p = process.platform; // 'darwin' | 'linux' | 'win32'
|
log(msg);
|
||||||
const a = process.arch; // 'arm64' | 'x64'
|
return STRICT ? 1 : 0;
|
||||||
const os = p === 'darwin' ? 'macos' : p === 'win32' ? 'windows' : 'linux';
|
|
||||||
return `agent_napi-${os}-${a}.node`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function readSubmoduleSha() {
|
function bundleBinary() {
|
||||||
try {
|
fs.mkdirSync(NAPI_DIR, { recursive: true });
|
||||||
return execSync('git rev-parse HEAD', {
|
fs.copyFileSync(ZIG_OUT, BUNDLED);
|
||||||
cwd: AGENT_DIR,
|
log(`Bundled binary at ${BUNDLED}.`);
|
||||||
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 buildFromSource() {
|
function buildFromSource() {
|
||||||
try {
|
try {
|
||||||
execSync('zig version', { stdio: 'ignore' });
|
execSync('zig version', { stdio: 'ignore' });
|
||||||
} catch {
|
} catch {
|
||||||
log('Zig not installed; cannot build from source.');
|
return fail(
|
||||||
return false;
|
'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 {
|
try {
|
||||||
execSync('zig build napi -Doptimize=ReleaseFast', {
|
execSync(`zig build napi -Doptimize=ReleaseFast${targetFlag}`, {
|
||||||
cwd: AGENT_DIR,
|
cwd: AGENT_DIR,
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(`Source build failed: ${err.message}`);
|
return fail(`Zig build failed: ${err.message}`);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
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() {
|
function main() {
|
||||||
// 1. Already have it?
|
if (process.env.OPENPENCIL_SKIP_AGENT_NATIVE === '1') {
|
||||||
if (fs.existsSync(ZIG_OUT) || fs.existsSync(BUNDLED)) {
|
log('OPENPENCIL_SKIP_AGENT_NATIVE=1, skipping native binary provisioning.');
|
||||||
log('Binary already present, skipping.');
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submodule initialized?
|
|
||||||
if (!fs.existsSync(path.join(NAPI_DIR, 'package.json'))) {
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Prebuilt release matching submodule SHA?
|
return buildFromSource();
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exit(main());
|
process.exit(main());
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue