@khuongdo/vibe-kanban 0.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +182 -0
- package/bin/cli.js +738 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Vibe Kanban
|
|
2
|
+
|
|
3
|
+
> A visual project management tool for developers that integrates with git repositories and coding agents like Claude Code and Amp.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
Run vibe kanban instantly without installation:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx vibe-kanban
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This will launch the application locally and open it in your browser automatically.
|
|
14
|
+
|
|
15
|
+
Helpful entrypoints:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx vibe-kanban --help
|
|
19
|
+
npx vibe-kanban --version
|
|
20
|
+
npx vibe-kanban review --help
|
|
21
|
+
npx vibe-kanban mcp --help
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What is Vibe Kanban?
|
|
25
|
+
|
|
26
|
+
Vibe Kanban is a modern project management tool designed specifically for developers. It helps you organize your coding projects with kanban-style task management while providing powerful integrations with git repositories and AI coding agents.
|
|
27
|
+
|
|
28
|
+
### ✨ Key Features
|
|
29
|
+
|
|
30
|
+
**🗂️ Project Management**
|
|
31
|
+
|
|
32
|
+
- Add git repositories as projects (existing or create new ones)
|
|
33
|
+
- Automatic git integration and repository validation
|
|
34
|
+
- Project search functionality across all files
|
|
35
|
+
- Custom setup and development scripts per project
|
|
36
|
+
|
|
37
|
+
**📋 Task Management**
|
|
38
|
+
|
|
39
|
+
- Create and manage tasks with kanban-style boards
|
|
40
|
+
- Task status tracking (Todo, In Progress, Done)
|
|
41
|
+
- Rich task descriptions and notes
|
|
42
|
+
- Task execution with multiple AI agents
|
|
43
|
+
|
|
44
|
+
**🤖 AI Agent Integration**
|
|
45
|
+
|
|
46
|
+
- **Claude**: Advanced AI coding assistant
|
|
47
|
+
- **Amp**: Powerful development agent
|
|
48
|
+
- **Echo**: Simple testing/debugging agent
|
|
49
|
+
- Create tasks and immediately start agent execution
|
|
50
|
+
- Follow-up task execution for iterative development
|
|
51
|
+
|
|
52
|
+
**⚡ Development Workflow**
|
|
53
|
+
|
|
54
|
+
- Create isolated git worktrees for each task attempt
|
|
55
|
+
- View diffs of changes made by agents
|
|
56
|
+
- Merge successful changes back to main branch
|
|
57
|
+
- Rebase task branches to stay up-to-date
|
|
58
|
+
- Manual file editing and deletion
|
|
59
|
+
- Integrated development server support
|
|
60
|
+
|
|
61
|
+
**🎛️ Developer Tools**
|
|
62
|
+
|
|
63
|
+
- Browse and validate git repositories from filesystem
|
|
64
|
+
- Open task worktrees in your preferred editor (VS Code, Cursor, Windsurf, IntelliJ, Zed)
|
|
65
|
+
- Real-time execution monitoring and process control
|
|
66
|
+
- Stop running processes individually or all at once
|
|
67
|
+
- Sound notifications for task completion
|
|
68
|
+
|
|
69
|
+
## How It Works
|
|
70
|
+
|
|
71
|
+
1. **Add Projects**: Import existing git repositories or create new ones
|
|
72
|
+
2. **Create Tasks**: Define what needs to be built or fixed
|
|
73
|
+
3. **Execute with AI**: Let coding agents work on your tasks in isolated environments
|
|
74
|
+
4. **Review Changes**: See exactly what was modified using git diffs
|
|
75
|
+
5. **Merge Results**: Incorporate successful changes into your main codebase
|
|
76
|
+
|
|
77
|
+
## Core Functionality
|
|
78
|
+
|
|
79
|
+
Vibe Kanban provides a complete project management experience with these key capabilities:
|
|
80
|
+
|
|
81
|
+
**Project Repository Management**
|
|
82
|
+
|
|
83
|
+
- Full CRUD operations for managing coding projects
|
|
84
|
+
- Automatic git repository detection and validation
|
|
85
|
+
- Initialize new repositories or import existing ones
|
|
86
|
+
- Project-wide file search functionality
|
|
87
|
+
|
|
88
|
+
**Task Lifecycle Management**
|
|
89
|
+
|
|
90
|
+
- Create, update, and delete tasks with rich descriptions
|
|
91
|
+
- Track task progress through customizable status workflows
|
|
92
|
+
- One-click task creation with immediate AI agent execution
|
|
93
|
+
- Task attempt tracking with detailed execution history
|
|
94
|
+
|
|
95
|
+
**AI Agent Execution Environment**
|
|
96
|
+
|
|
97
|
+
- Isolated git worktrees for safe code experimentation
|
|
98
|
+
- Real-time execution monitoring and activity logging
|
|
99
|
+
- Process management with ability to stop individual or all processes
|
|
100
|
+
- Support for follow-up executions to iterate on solutions
|
|
101
|
+
|
|
102
|
+
**Code Change Management**
|
|
103
|
+
|
|
104
|
+
- View detailed diffs of all changes made during task execution
|
|
105
|
+
- Branch status monitoring to track divergence from main
|
|
106
|
+
- One-click merging of successful changes back to main branch
|
|
107
|
+
- Automatic rebasing to keep task branches up-to-date
|
|
108
|
+
- Manual file deletion and cleanup capabilities
|
|
109
|
+
|
|
110
|
+
**Development Integration**
|
|
111
|
+
|
|
112
|
+
- Open task worktrees directly in your preferred code editor
|
|
113
|
+
- Start and manage development servers for testing changes
|
|
114
|
+
- Browse local filesystem to add new projects
|
|
115
|
+
- Health monitoring for service availability
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
Vibe Kanban supports customization through its configuration system:
|
|
120
|
+
|
|
121
|
+
- **Editor Integration**: Choose your preferred code editor
|
|
122
|
+
- **Sound Notifications**: Customize completion sounds
|
|
123
|
+
- **Project Defaults**: Set default setup and development scripts
|
|
124
|
+
|
|
125
|
+
## Technical Architecture
|
|
126
|
+
|
|
127
|
+
- **Backend**: Rust with Axum web framework
|
|
128
|
+
- **Frontend**: React with TypeScript
|
|
129
|
+
- **Database**: SQLite for local data storage
|
|
130
|
+
- **Git Integration**: Native git operations for repository management
|
|
131
|
+
- **Process Management**: Tokio-based async execution monitoring
|
|
132
|
+
|
|
133
|
+
## Requirements
|
|
134
|
+
|
|
135
|
+
- Node.js (for npx execution)
|
|
136
|
+
- Git (for repository operations)
|
|
137
|
+
- Your preferred code editor (optional, for opening task worktrees)
|
|
138
|
+
|
|
139
|
+
## Supported Platforms
|
|
140
|
+
|
|
141
|
+
- Linux x64
|
|
142
|
+
- Windows x64
|
|
143
|
+
- macOS x64 (Intel)
|
|
144
|
+
- macOS ARM64 (Apple Silicon)
|
|
145
|
+
|
|
146
|
+
## Use Cases
|
|
147
|
+
|
|
148
|
+
**🔧 Bug Fixes**
|
|
149
|
+
|
|
150
|
+
- Create a task describing the bug
|
|
151
|
+
- Let an AI agent analyze and fix the issue
|
|
152
|
+
- Review the proposed changes
|
|
153
|
+
- Merge if satisfied, or provide follow-up instructions
|
|
154
|
+
|
|
155
|
+
**✨ Feature Development**
|
|
156
|
+
|
|
157
|
+
- Break down features into manageable tasks
|
|
158
|
+
- Use agents for initial implementation
|
|
159
|
+
- Iterate with follow-up executions
|
|
160
|
+
- Test using integrated development servers
|
|
161
|
+
|
|
162
|
+
**🚀 Project Setup**
|
|
163
|
+
|
|
164
|
+
- Bootstrap new projects with AI assistance
|
|
165
|
+
- Set up development environments
|
|
166
|
+
- Configure build and deployment scripts
|
|
167
|
+
|
|
168
|
+
**📚 Code Documentation**
|
|
169
|
+
|
|
170
|
+
- Generate documentation for existing code
|
|
171
|
+
- Create README files and API documentation
|
|
172
|
+
- Maintain up-to-date project information
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
**Ready to supercharge your development workflow?**
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npx vibe-kanban
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
_Start managing your projects with the power of AI coding agents today!_
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
|
|
29
|
+
// package.json
|
|
30
|
+
var require_package = __commonJS({
|
|
31
|
+
"package.json"(exports2, module2) {
|
|
32
|
+
module2.exports = {
|
|
33
|
+
name: "@khuongdo/vibe-kanban",
|
|
34
|
+
private: false,
|
|
35
|
+
version: "0.1.45",
|
|
36
|
+
main: "index.js",
|
|
37
|
+
bin: {
|
|
38
|
+
"vibe-kanban": "bin/cli.js"
|
|
39
|
+
},
|
|
40
|
+
scripts: {
|
|
41
|
+
build: 'esbuild src/cli.ts --bundle --platform=node --target=node20 --format=cjs --outfile=bin/cli.js --external:adm-zip --banner:js="#!/usr/bin/env node"',
|
|
42
|
+
check: "tsc --noEmit -p tsconfig.json"
|
|
43
|
+
},
|
|
44
|
+
keywords: [],
|
|
45
|
+
author: "bloop",
|
|
46
|
+
repository: {
|
|
47
|
+
type: "git",
|
|
48
|
+
url: "https://github.com/khuongdo/vibe-kanban"
|
|
49
|
+
},
|
|
50
|
+
engines: {
|
|
51
|
+
node: ">=20.19.0"
|
|
52
|
+
},
|
|
53
|
+
license: "",
|
|
54
|
+
description: "NPX wrapper around vibe-kanban and vibe-kanban-mcp",
|
|
55
|
+
devDependencies: {
|
|
56
|
+
esbuild: "^0.27.2"
|
|
57
|
+
},
|
|
58
|
+
dependencies: {
|
|
59
|
+
"adm-zip": "^0.5.16",
|
|
60
|
+
cac: "^7.0.0"
|
|
61
|
+
},
|
|
62
|
+
files: [
|
|
63
|
+
"bin",
|
|
64
|
+
"dist"
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// src/cli.ts
|
|
71
|
+
var import_child_process2 = require("child_process");
|
|
72
|
+
var import_path3 = __toESM(require("path"));
|
|
73
|
+
var import_fs3 = __toESM(require("fs"));
|
|
74
|
+
var import_cac = require("cac");
|
|
75
|
+
|
|
76
|
+
// src/download.ts
|
|
77
|
+
var import_https = __toESM(require("https"));
|
|
78
|
+
var import_fs = __toESM(require("fs"));
|
|
79
|
+
var import_path = __toESM(require("path"));
|
|
80
|
+
var import_crypto = __toESM(require("crypto"));
|
|
81
|
+
var import_os = __toESM(require("os"));
|
|
82
|
+
var R2_BASE_URL = "__R2_PUBLIC_URL__";
|
|
83
|
+
var BINARY_TAG = "__BINARY_TAG__";
|
|
84
|
+
var CACHE_DIR = import_path.default.join(import_os.default.homedir(), ".vibe-kanban", "bin");
|
|
85
|
+
var LOCAL_DIST_DIR = import_path.default.join(__dirname, "..", "dist");
|
|
86
|
+
var LOCAL_DEV_MODE = import_fs.default.existsSync(LOCAL_DIST_DIR) || process.env.VIBE_KANBAN_LOCAL === "1";
|
|
87
|
+
function fetchJson(url) {
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
import_https.default.get(url, (res) => {
|
|
90
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
91
|
+
return fetchJson(res.headers.location).then(resolve).catch(reject);
|
|
92
|
+
}
|
|
93
|
+
if (res.statusCode !== 200) {
|
|
94
|
+
return reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
|
|
95
|
+
}
|
|
96
|
+
let data = "";
|
|
97
|
+
res.on("data", (chunk) => data += chunk);
|
|
98
|
+
res.on("end", () => {
|
|
99
|
+
try {
|
|
100
|
+
resolve(JSON.parse(data));
|
|
101
|
+
} catch {
|
|
102
|
+
reject(new Error(`Failed to parse JSON from ${url}`));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}).on("error", reject);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function downloadFile(url, destPath, expectedSha256, onProgress) {
|
|
109
|
+
const tempPath = destPath + ".tmp";
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const file = import_fs.default.createWriteStream(tempPath);
|
|
112
|
+
const hash = import_crypto.default.createHash("sha256");
|
|
113
|
+
const cleanup = () => {
|
|
114
|
+
try {
|
|
115
|
+
import_fs.default.unlinkSync(tempPath);
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
import_https.default.get(url, (res) => {
|
|
120
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
121
|
+
file.close();
|
|
122
|
+
cleanup();
|
|
123
|
+
return downloadFile(
|
|
124
|
+
res.headers.location,
|
|
125
|
+
destPath,
|
|
126
|
+
expectedSha256,
|
|
127
|
+
onProgress
|
|
128
|
+
).then(resolve).catch(reject);
|
|
129
|
+
}
|
|
130
|
+
if (res.statusCode !== 200) {
|
|
131
|
+
file.close();
|
|
132
|
+
cleanup();
|
|
133
|
+
return reject(
|
|
134
|
+
new Error(`HTTP ${res.statusCode} downloading ${url}`)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const totalSize = parseInt(
|
|
138
|
+
res.headers["content-length"] || "0",
|
|
139
|
+
10
|
|
140
|
+
);
|
|
141
|
+
let downloadedSize = 0;
|
|
142
|
+
res.on("data", (chunk) => {
|
|
143
|
+
downloadedSize += chunk.length;
|
|
144
|
+
hash.update(chunk);
|
|
145
|
+
if (onProgress) onProgress(downloadedSize, totalSize);
|
|
146
|
+
});
|
|
147
|
+
res.pipe(file);
|
|
148
|
+
file.on("finish", () => {
|
|
149
|
+
file.close();
|
|
150
|
+
const actualSha256 = hash.digest("hex");
|
|
151
|
+
if (expectedSha256 && actualSha256 !== expectedSha256) {
|
|
152
|
+
cleanup();
|
|
153
|
+
reject(
|
|
154
|
+
new Error(
|
|
155
|
+
`Checksum mismatch: expected ${expectedSha256}, got ${actualSha256}`
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
try {
|
|
160
|
+
import_fs.default.renameSync(tempPath, destPath);
|
|
161
|
+
resolve(destPath);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
cleanup();
|
|
164
|
+
reject(err);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}).on("error", (err) => {
|
|
169
|
+
file.close();
|
|
170
|
+
cleanup();
|
|
171
|
+
reject(err);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async function ensureBinary(platform2, binaryName, onProgress) {
|
|
176
|
+
if (LOCAL_DEV_MODE) {
|
|
177
|
+
const localZipPath = import_path.default.join(
|
|
178
|
+
LOCAL_DIST_DIR,
|
|
179
|
+
platform2,
|
|
180
|
+
`${binaryName}.zip`
|
|
181
|
+
);
|
|
182
|
+
if (import_fs.default.existsSync(localZipPath)) {
|
|
183
|
+
return localZipPath;
|
|
184
|
+
}
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Local binary not found: ${localZipPath}
|
|
187
|
+
Run ./local-build.sh first to build the binaries.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const cacheDir = import_path.default.join(CACHE_DIR, BINARY_TAG, platform2);
|
|
191
|
+
const zipPath = import_path.default.join(cacheDir, `${binaryName}.zip`);
|
|
192
|
+
if (import_fs.default.existsSync(zipPath)) return zipPath;
|
|
193
|
+
import_fs.default.mkdirSync(cacheDir, { recursive: true });
|
|
194
|
+
const manifest = await fetchJson(
|
|
195
|
+
`${R2_BASE_URL}/binaries/${BINARY_TAG}/manifest.json`
|
|
196
|
+
);
|
|
197
|
+
const binaryInfo = manifest.platforms?.[platform2]?.[binaryName];
|
|
198
|
+
if (!binaryInfo) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Binary ${binaryName} not available for ${platform2}`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const url = `${R2_BASE_URL}/binaries/${BINARY_TAG}/${platform2}/${binaryName}.zip`;
|
|
204
|
+
await downloadFile(url, zipPath, binaryInfo.sha256, onProgress);
|
|
205
|
+
return zipPath;
|
|
206
|
+
}
|
|
207
|
+
var DESKTOP_CACHE_DIR = import_path.default.join(
|
|
208
|
+
import_os.default.homedir(),
|
|
209
|
+
".vibe-kanban",
|
|
210
|
+
"desktop"
|
|
211
|
+
);
|
|
212
|
+
async function ensureDesktopBundle(tauriPlatform, onProgress) {
|
|
213
|
+
if (LOCAL_DEV_MODE) {
|
|
214
|
+
const localDir = import_path.default.join(LOCAL_DIST_DIR, "tauri", tauriPlatform);
|
|
215
|
+
if (import_fs.default.existsSync(localDir)) {
|
|
216
|
+
const files = import_fs.default.readdirSync(localDir);
|
|
217
|
+
const archive = files.find(
|
|
218
|
+
(f) => f.endsWith(".tar.gz") || f.endsWith("-setup.exe")
|
|
219
|
+
);
|
|
220
|
+
return {
|
|
221
|
+
dir: localDir,
|
|
222
|
+
archivePath: archive ? import_path.default.join(localDir, archive) : null,
|
|
223
|
+
type: null
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Local desktop bundle not found: ${localDir}
|
|
228
|
+
Run './local-build.sh --desktop' first to build the Tauri app.`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
const cacheDir = import_path.default.join(
|
|
232
|
+
DESKTOP_CACHE_DIR,
|
|
233
|
+
BINARY_TAG,
|
|
234
|
+
tauriPlatform
|
|
235
|
+
);
|
|
236
|
+
const sentinelPath = import_path.default.join(cacheDir, ".installed");
|
|
237
|
+
if (import_fs.default.existsSync(sentinelPath)) {
|
|
238
|
+
return { dir: cacheDir, archivePath: null, type: null };
|
|
239
|
+
}
|
|
240
|
+
import_fs.default.mkdirSync(cacheDir, { recursive: true });
|
|
241
|
+
const manifest = await fetchJson(
|
|
242
|
+
`${R2_BASE_URL}/binaries/${BINARY_TAG}/tauri/desktop-manifest.json`
|
|
243
|
+
);
|
|
244
|
+
const platformInfo = manifest.platforms?.[tauriPlatform];
|
|
245
|
+
if (!platformInfo) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`Desktop app not available for platform: ${tauriPlatform}`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
const destPath = import_path.default.join(cacheDir, platformInfo.file);
|
|
251
|
+
if (!import_fs.default.existsSync(destPath)) {
|
|
252
|
+
const url = `${R2_BASE_URL}/binaries/${BINARY_TAG}/tauri/${tauriPlatform}/${platformInfo.file}`;
|
|
253
|
+
await downloadFile(url, destPath, platformInfo.sha256, onProgress);
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
archivePath: destPath,
|
|
257
|
+
dir: cacheDir,
|
|
258
|
+
type: platformInfo.type
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async function getLatestVersion() {
|
|
262
|
+
const manifest = await fetchJson(
|
|
263
|
+
`${R2_BASE_URL}/binaries/manifest.json`
|
|
264
|
+
);
|
|
265
|
+
return manifest.latest;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/desktop.ts
|
|
269
|
+
var import_child_process = require("child_process");
|
|
270
|
+
var import_path2 = __toESM(require("path"));
|
|
271
|
+
var import_fs2 = __toESM(require("fs"));
|
|
272
|
+
var import_os2 = __toESM(require("os"));
|
|
273
|
+
var PLATFORM_MAP = {
|
|
274
|
+
"macos-arm64": "darwin-aarch64",
|
|
275
|
+
"macos-x64": "darwin-x86_64",
|
|
276
|
+
"linux-x64": "linux-x86_64",
|
|
277
|
+
"linux-arm64": "linux-aarch64",
|
|
278
|
+
"windows-x64": "windows-x86_64",
|
|
279
|
+
"windows-arm64": "windows-aarch64"
|
|
280
|
+
};
|
|
281
|
+
function getTauriPlatform(npxPlatformDir) {
|
|
282
|
+
return PLATFORM_MAP[npxPlatformDir] || null;
|
|
283
|
+
}
|
|
284
|
+
function extractTarGz(archivePath, destDir) {
|
|
285
|
+
(0, import_child_process.execSync)(`tar -xzf "${archivePath}" -C "${destDir}"`, {
|
|
286
|
+
stdio: "pipe"
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
function writeSentinel(dir, meta) {
|
|
290
|
+
import_fs2.default.writeFileSync(
|
|
291
|
+
import_path2.default.join(dir, ".installed"),
|
|
292
|
+
JSON.stringify(meta)
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
function readSentinel(dir) {
|
|
296
|
+
const sentinelPath = import_path2.default.join(dir, ".installed");
|
|
297
|
+
if (!import_fs2.default.existsSync(sentinelPath)) return null;
|
|
298
|
+
try {
|
|
299
|
+
return JSON.parse(
|
|
300
|
+
import_fs2.default.readFileSync(sentinelPath, "utf-8")
|
|
301
|
+
);
|
|
302
|
+
} catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function tryCopyApp(srcAppPath, destDir) {
|
|
307
|
+
try {
|
|
308
|
+
const appName = import_path2.default.basename(srcAppPath);
|
|
309
|
+
const destAppPath = import_path2.default.join(destDir, appName);
|
|
310
|
+
import_fs2.default.mkdirSync(destDir, { recursive: true });
|
|
311
|
+
if (import_fs2.default.existsSync(destAppPath)) {
|
|
312
|
+
import_fs2.default.rmSync(destAppPath, { recursive: true, force: true });
|
|
313
|
+
}
|
|
314
|
+
(0, import_child_process.execSync)(`cp -R "${srcAppPath}" "${destAppPath}"`, {
|
|
315
|
+
stdio: "pipe"
|
|
316
|
+
});
|
|
317
|
+
return destAppPath;
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function installAndLaunchMacOS(bundleInfo) {
|
|
323
|
+
const { archivePath, dir } = bundleInfo;
|
|
324
|
+
const sentinel = readSentinel(dir);
|
|
325
|
+
if (sentinel?.appPath && import_fs2.default.existsSync(sentinel.appPath)) {
|
|
326
|
+
return launchMacOSApp(sentinel.appPath);
|
|
327
|
+
}
|
|
328
|
+
if (!archivePath || !import_fs2.default.existsSync(archivePath)) {
|
|
329
|
+
throw new Error("No archive to extract for macOS desktop app");
|
|
330
|
+
}
|
|
331
|
+
extractTarGz(archivePath, dir);
|
|
332
|
+
const appName = import_fs2.default.readdirSync(dir).find((f) => f.endsWith(".app"));
|
|
333
|
+
if (!appName) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`No .app bundle found in ${dir} after extraction`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
const extractedAppPath = import_path2.default.join(dir, appName);
|
|
339
|
+
const userApplications = import_path2.default.join(import_os2.default.homedir(), "Applications");
|
|
340
|
+
const finalAppPath = tryCopyApp(extractedAppPath, "/Applications") ?? tryCopyApp(extractedAppPath, userApplications) ?? extractedAppPath;
|
|
341
|
+
if (finalAppPath !== extractedAppPath) {
|
|
342
|
+
try {
|
|
343
|
+
import_fs2.default.rmSync(extractedAppPath, { recursive: true, force: true });
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
(0, import_child_process.execSync)(`xattr -rd com.apple.quarantine "${finalAppPath}"`, {
|
|
349
|
+
stdio: "pipe"
|
|
350
|
+
});
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
writeSentinel(dir, { type: "app-tar-gz", appPath: finalAppPath });
|
|
354
|
+
return launchMacOSApp(finalAppPath);
|
|
355
|
+
}
|
|
356
|
+
function launchMacOSApp(appPath) {
|
|
357
|
+
const appName = import_path2.default.basename(appPath);
|
|
358
|
+
console.error(`Launching ${appName}...`);
|
|
359
|
+
const proc = (0, import_child_process.spawn)("open", ["--wait-apps", appPath], {
|
|
360
|
+
stdio: "inherit"
|
|
361
|
+
});
|
|
362
|
+
return new Promise((resolve) => {
|
|
363
|
+
proc.on("exit", (code) => resolve(code || 0));
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
async function installAndLaunchLinux(bundleInfo) {
|
|
367
|
+
const { archivePath, dir } = bundleInfo;
|
|
368
|
+
const sentinel = readSentinel(dir);
|
|
369
|
+
if (sentinel?.appPath && import_fs2.default.existsSync(sentinel.appPath)) {
|
|
370
|
+
return launchLinuxAppImage(sentinel.appPath);
|
|
371
|
+
}
|
|
372
|
+
if (!archivePath || !import_fs2.default.existsSync(archivePath)) {
|
|
373
|
+
throw new Error("No archive to extract for Linux desktop app");
|
|
374
|
+
}
|
|
375
|
+
extractTarGz(archivePath, dir);
|
|
376
|
+
const appImage = import_fs2.default.readdirSync(dir).find((f) => f.endsWith(".AppImage"));
|
|
377
|
+
if (!appImage) {
|
|
378
|
+
throw new Error(`No .AppImage found in ${dir} after extraction`);
|
|
379
|
+
}
|
|
380
|
+
const appImagePath = import_path2.default.join(dir, appImage);
|
|
381
|
+
import_fs2.default.chmodSync(appImagePath, 493);
|
|
382
|
+
writeSentinel(dir, {
|
|
383
|
+
type: "appimage-tar-gz",
|
|
384
|
+
appPath: appImagePath
|
|
385
|
+
});
|
|
386
|
+
return launchLinuxAppImage(appImagePath);
|
|
387
|
+
}
|
|
388
|
+
function launchLinuxAppImage(appImagePath) {
|
|
389
|
+
const appImage = import_path2.default.basename(appImagePath);
|
|
390
|
+
console.error(`Launching ${appImage}...`);
|
|
391
|
+
const proc = (0, import_child_process.spawn)(appImagePath, [], {
|
|
392
|
+
stdio: "inherit",
|
|
393
|
+
detached: false
|
|
394
|
+
});
|
|
395
|
+
return new Promise((resolve) => {
|
|
396
|
+
proc.on("exit", (code) => resolve(code || 0));
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
async function installAndLaunchWindows(bundleInfo) {
|
|
400
|
+
const { dir } = bundleInfo;
|
|
401
|
+
const sentinel = readSentinel(dir);
|
|
402
|
+
if (sentinel?.appPath) {
|
|
403
|
+
const appExe2 = import_path2.default.join(sentinel.appPath, "Vibe Kanban.exe");
|
|
404
|
+
if (import_fs2.default.existsSync(appExe2)) {
|
|
405
|
+
return launchWindowsApp(appExe2);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const files = import_fs2.default.readdirSync(dir);
|
|
409
|
+
const installer = files.find(
|
|
410
|
+
(f) => f.endsWith("-setup.exe") || f.endsWith(".exe") && f !== ".installed"
|
|
411
|
+
);
|
|
412
|
+
if (!installer) {
|
|
413
|
+
throw new Error(`No installer found in ${dir}`);
|
|
414
|
+
}
|
|
415
|
+
const installerPath = import_path2.default.join(dir, installer);
|
|
416
|
+
const installDir = import_path2.default.join(dir, "app");
|
|
417
|
+
console.error("Installing Vibe Kanban...");
|
|
418
|
+
try {
|
|
419
|
+
(0, import_child_process.execSync)(`"${installerPath}" /S /D="${installDir}"`, {
|
|
420
|
+
stdio: "inherit",
|
|
421
|
+
timeout: 12e4
|
|
422
|
+
});
|
|
423
|
+
} catch {
|
|
424
|
+
console.error(
|
|
425
|
+
"Silent install failed, launching interactive installer..."
|
|
426
|
+
);
|
|
427
|
+
(0, import_child_process.execSync)(`"${installerPath}"`, { stdio: "inherit" });
|
|
428
|
+
const defaultDir = import_path2.default.join(
|
|
429
|
+
process.env.LOCALAPPDATA || "",
|
|
430
|
+
"vibe-kanban"
|
|
431
|
+
);
|
|
432
|
+
if (import_fs2.default.existsSync(import_path2.default.join(defaultDir, "Vibe Kanban.exe"))) {
|
|
433
|
+
writeSentinel(dir, {
|
|
434
|
+
type: "nsis-exe",
|
|
435
|
+
appPath: defaultDir
|
|
436
|
+
});
|
|
437
|
+
return launchWindowsApp(
|
|
438
|
+
import_path2.default.join(defaultDir, "Vibe Kanban.exe")
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
console.error(
|
|
442
|
+
"Installation complete. Please launch Vibe Kanban from your Start menu."
|
|
443
|
+
);
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
446
|
+
writeSentinel(dir, { type: "nsis-exe", appPath: installDir });
|
|
447
|
+
const appExe = import_path2.default.join(installDir, "Vibe Kanban.exe");
|
|
448
|
+
if (import_fs2.default.existsSync(appExe)) {
|
|
449
|
+
return launchWindowsApp(appExe);
|
|
450
|
+
}
|
|
451
|
+
console.error(
|
|
452
|
+
"Installation complete. Please launch Vibe Kanban from your Start menu."
|
|
453
|
+
);
|
|
454
|
+
return 0;
|
|
455
|
+
}
|
|
456
|
+
function launchWindowsApp(appExe) {
|
|
457
|
+
console.error("Launching Vibe Kanban...");
|
|
458
|
+
(0, import_child_process.spawn)(appExe, [], { detached: true, stdio: "ignore" }).unref();
|
|
459
|
+
return 0;
|
|
460
|
+
}
|
|
461
|
+
async function installAndLaunch(bundleInfo, osPlatform) {
|
|
462
|
+
if (osPlatform === "darwin") {
|
|
463
|
+
return installAndLaunchMacOS(bundleInfo);
|
|
464
|
+
} else if (osPlatform === "linux") {
|
|
465
|
+
return installAndLaunchLinux(bundleInfo);
|
|
466
|
+
} else if (osPlatform === "win32") {
|
|
467
|
+
return installAndLaunchWindows(bundleInfo);
|
|
468
|
+
}
|
|
469
|
+
throw new Error(
|
|
470
|
+
`Desktop app not supported on platform: ${osPlatform}`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
function cleanOldDesktopVersions(desktopBaseDir, currentTag) {
|
|
474
|
+
try {
|
|
475
|
+
const entries = import_fs2.default.readdirSync(desktopBaseDir, {
|
|
476
|
+
withFileTypes: true
|
|
477
|
+
});
|
|
478
|
+
for (const entry of entries) {
|
|
479
|
+
if (entry.isDirectory() && entry.name !== currentTag) {
|
|
480
|
+
const oldDir = import_path2.default.join(desktopBaseDir, entry.name);
|
|
481
|
+
try {
|
|
482
|
+
import_fs2.default.rmSync(oldDir, { recursive: true, force: true });
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/cli.ts
|
|
492
|
+
var CLI_VERSION = require_package().version;
|
|
493
|
+
function getEffectiveArch() {
|
|
494
|
+
const platform2 = process.platform;
|
|
495
|
+
const nodeArch = process.arch;
|
|
496
|
+
if (platform2 === "darwin") {
|
|
497
|
+
if (nodeArch === "arm64") return "arm64";
|
|
498
|
+
try {
|
|
499
|
+
const translated = (0, import_child_process2.execSync)("sysctl -in sysctl.proc_translated", {
|
|
500
|
+
encoding: "utf8"
|
|
501
|
+
}).trim();
|
|
502
|
+
if (translated === "1") return "arm64";
|
|
503
|
+
} catch {
|
|
504
|
+
}
|
|
505
|
+
return "x64";
|
|
506
|
+
}
|
|
507
|
+
if (/arm/i.test(nodeArch)) return "arm64";
|
|
508
|
+
if (platform2 === "win32") {
|
|
509
|
+
const pa = process.env.PROCESSOR_ARCHITECTURE || "";
|
|
510
|
+
const paw = process.env.PROCESSOR_ARCHITEW6432 || "";
|
|
511
|
+
if (/arm/i.test(pa) || /arm/i.test(paw)) return "arm64";
|
|
512
|
+
}
|
|
513
|
+
return "x64";
|
|
514
|
+
}
|
|
515
|
+
var platform = process.platform;
|
|
516
|
+
var arch = getEffectiveArch();
|
|
517
|
+
function getPlatformDir() {
|
|
518
|
+
if (platform === "linux" && arch === "x64") return "linux-x64";
|
|
519
|
+
if (platform === "linux" && arch === "arm64") return "linux-arm64";
|
|
520
|
+
if (platform === "win32" && arch === "x64") return "windows-x64";
|
|
521
|
+
if (platform === "win32" && arch === "arm64") return "windows-arm64";
|
|
522
|
+
if (platform === "darwin" && arch === "x64") return "macos-x64";
|
|
523
|
+
if (platform === "darwin" && arch === "arm64") return "macos-arm64";
|
|
524
|
+
console.error(`Unsupported platform: ${platform}-${arch}`);
|
|
525
|
+
console.error("Supported platforms:");
|
|
526
|
+
console.error(" - Linux x64");
|
|
527
|
+
console.error(" - Linux ARM64");
|
|
528
|
+
console.error(" - Windows x64");
|
|
529
|
+
console.error(" - Windows ARM64");
|
|
530
|
+
console.error(" - macOS x64 (Intel)");
|
|
531
|
+
console.error(" - macOS ARM64 (Apple Silicon)");
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
function getBinaryName(base) {
|
|
535
|
+
return platform === "win32" ? `${base}.exe` : base;
|
|
536
|
+
}
|
|
537
|
+
var platformDir = getPlatformDir();
|
|
538
|
+
var versionCacheDir = LOCAL_DEV_MODE ? import_path3.default.join(LOCAL_DIST_DIR, platformDir) : import_path3.default.join(CACHE_DIR, BINARY_TAG, platformDir);
|
|
539
|
+
function cleanOldVersions() {
|
|
540
|
+
try {
|
|
541
|
+
const entries = import_fs3.default.readdirSync(CACHE_DIR, {
|
|
542
|
+
withFileTypes: true
|
|
543
|
+
});
|
|
544
|
+
for (const entry of entries) {
|
|
545
|
+
if (entry.isDirectory() && entry.name !== BINARY_TAG) {
|
|
546
|
+
const oldDir = import_path3.default.join(CACHE_DIR, entry.name);
|
|
547
|
+
import_fs3.default.rmSync(oldDir, { recursive: true, force: true });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
} catch {
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function showProgress(downloaded, total) {
|
|
554
|
+
const percent = total ? Math.round(downloaded / total * 100) : 0;
|
|
555
|
+
const mb = (downloaded / (1024 * 1024)).toFixed(1);
|
|
556
|
+
const totalMb = total ? (total / (1024 * 1024)).toFixed(1) : "?";
|
|
557
|
+
process.stderr.write(
|
|
558
|
+
`\r Downloading: ${mb}MB / ${totalMb}MB (${percent}%)`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
function buildMcpArgs(args) {
|
|
562
|
+
return args.length > 0 ? args : ["--mode", "global"];
|
|
563
|
+
}
|
|
564
|
+
async function extractAndRun(baseName, launch) {
|
|
565
|
+
const binName = getBinaryName(baseName);
|
|
566
|
+
const binPath = import_path3.default.join(versionCacheDir, binName);
|
|
567
|
+
const zipPath = import_path3.default.join(versionCacheDir, `${baseName}.zip`);
|
|
568
|
+
try {
|
|
569
|
+
if (import_fs3.default.existsSync(binPath)) {
|
|
570
|
+
import_fs3.default.unlinkSync(binPath);
|
|
571
|
+
}
|
|
572
|
+
} catch (err) {
|
|
573
|
+
if (process.env.VIBE_KANBAN_DEBUG) {
|
|
574
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
575
|
+
console.warn(`Warning: Could not delete existing binary: ${msg}`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (!import_fs3.default.existsSync(zipPath)) {
|
|
579
|
+
console.error(`Downloading ${baseName}...`);
|
|
580
|
+
try {
|
|
581
|
+
await ensureBinary(platformDir, baseName, showProgress);
|
|
582
|
+
console.error("");
|
|
583
|
+
} catch (err) {
|
|
584
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
585
|
+
console.error(`
|
|
586
|
+
Download failed: ${msg}`);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (!import_fs3.default.existsSync(binPath)) {
|
|
591
|
+
try {
|
|
592
|
+
const { default: AdmZip } = await import("adm-zip");
|
|
593
|
+
const zip = new AdmZip(zipPath);
|
|
594
|
+
zip.extractAllTo(versionCacheDir, true);
|
|
595
|
+
} catch (err) {
|
|
596
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
597
|
+
console.error("Extraction failed:", msg);
|
|
598
|
+
try {
|
|
599
|
+
import_fs3.default.unlinkSync(zipPath);
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
602
|
+
process.exit(1);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (!import_fs3.default.existsSync(binPath)) {
|
|
606
|
+
console.error(`Extracted binary not found at: ${binPath}`);
|
|
607
|
+
console.error(
|
|
608
|
+
"This usually indicates a corrupt download. Please try again."
|
|
609
|
+
);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
if (!LOCAL_DEV_MODE) {
|
|
613
|
+
cleanOldVersions();
|
|
614
|
+
}
|
|
615
|
+
if (platform !== "win32") {
|
|
616
|
+
try {
|
|
617
|
+
import_fs3.default.chmodSync(binPath, 493);
|
|
618
|
+
} catch {
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return launch(binPath);
|
|
622
|
+
}
|
|
623
|
+
function checkForUpdates() {
|
|
624
|
+
const hasValidR2Url = !R2_BASE_URL.startsWith("__");
|
|
625
|
+
if (LOCAL_DEV_MODE || !hasValidR2Url) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
getLatestVersion().then((latest) => {
|
|
629
|
+
if (latest && latest !== CLI_VERSION) {
|
|
630
|
+
setTimeout(() => {
|
|
631
|
+
console.log(`
|
|
632
|
+
Update available: ${CLI_VERSION} -> ${latest}`);
|
|
633
|
+
console.log(`Run: npx vibe-kanban@latest`);
|
|
634
|
+
}, 2e3);
|
|
635
|
+
}
|
|
636
|
+
}).catch(() => {
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
async function runMcp(args) {
|
|
640
|
+
await extractAndRun("vibe-kanban-mcp", (bin) => {
|
|
641
|
+
const proc = (0, import_child_process2.spawn)(bin, buildMcpArgs(args), {
|
|
642
|
+
stdio: "inherit"
|
|
643
|
+
});
|
|
644
|
+
proc.on("exit", (c) => process.exit(c || 0));
|
|
645
|
+
proc.on("error", (e) => {
|
|
646
|
+
console.error("MCP server error:", e.message);
|
|
647
|
+
process.exit(1);
|
|
648
|
+
});
|
|
649
|
+
process.on("SIGINT", () => {
|
|
650
|
+
proc.kill("SIGINT");
|
|
651
|
+
});
|
|
652
|
+
process.on("SIGTERM", () => proc.kill("SIGTERM"));
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
async function runReview(args) {
|
|
656
|
+
await extractAndRun("vibe-kanban-review", (bin) => {
|
|
657
|
+
const proc = (0, import_child_process2.spawn)(bin, args, { stdio: "inherit" });
|
|
658
|
+
proc.on("exit", (c) => process.exit(c || 0));
|
|
659
|
+
proc.on("error", (e) => {
|
|
660
|
+
console.error("Review CLI error:", e.message);
|
|
661
|
+
process.exit(1);
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
async function runMain(desktopMode) {
|
|
666
|
+
checkForUpdates();
|
|
667
|
+
const modeLabel = LOCAL_DEV_MODE ? " (local dev)" : "";
|
|
668
|
+
const tauriPlatform = getTauriPlatform(platformDir);
|
|
669
|
+
if (desktopMode && tauriPlatform) {
|
|
670
|
+
try {
|
|
671
|
+
console.log(
|
|
672
|
+
`Starting vibe-kanban desktop v${CLI_VERSION}${modeLabel}...`
|
|
673
|
+
);
|
|
674
|
+
const bundleInfo = await ensureDesktopBundle(tauriPlatform, showProgress);
|
|
675
|
+
console.error("");
|
|
676
|
+
if (!LOCAL_DEV_MODE) {
|
|
677
|
+
cleanOldDesktopVersions(DESKTOP_CACHE_DIR, BINARY_TAG);
|
|
678
|
+
}
|
|
679
|
+
const exitCode = await installAndLaunch(bundleInfo, platform);
|
|
680
|
+
process.exit(exitCode);
|
|
681
|
+
} catch (err) {
|
|
682
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
683
|
+
console.error(`Desktop app not available: ${msg}`);
|
|
684
|
+
console.error("Falling back to browser mode...");
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
console.log(`Starting vibe-kanban v${CLI_VERSION}${modeLabel}...`);
|
|
688
|
+
await extractAndRun("vibe-kanban", (bin) => {
|
|
689
|
+
(0, import_child_process2.execSync)(`"${bin}"`, { stdio: "inherit" });
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
function normalizeArgv(argv) {
|
|
693
|
+
const args = argv.slice(2);
|
|
694
|
+
const mcpFlagIndex = args.indexOf("--mcp");
|
|
695
|
+
if (mcpFlagIndex === -1) {
|
|
696
|
+
return argv;
|
|
697
|
+
}
|
|
698
|
+
const normalizedArgs = [
|
|
699
|
+
...args.slice(0, mcpFlagIndex),
|
|
700
|
+
"mcp",
|
|
701
|
+
...args.slice(mcpFlagIndex + 1)
|
|
702
|
+
];
|
|
703
|
+
return [...argv.slice(0, 2), ...normalizedArgs];
|
|
704
|
+
}
|
|
705
|
+
function runOrExit(task) {
|
|
706
|
+
void task.catch((err) => {
|
|
707
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
708
|
+
console.error("Fatal error:", msg);
|
|
709
|
+
if (process.env.VIBE_KANBAN_DEBUG && err instanceof Error) {
|
|
710
|
+
console.error(err.stack);
|
|
711
|
+
}
|
|
712
|
+
process.exit(1);
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
async function main() {
|
|
716
|
+
import_fs3.default.mkdirSync(versionCacheDir, { recursive: true });
|
|
717
|
+
const cli = (0, import_cac.cac)("vibe-kanban");
|
|
718
|
+
cli.command("[...args]", "Launch the local vibe-kanban app").option("--desktop", "Launch the desktop app instead of browser mode").allowUnknownOptions().action((_args, options) => {
|
|
719
|
+
runOrExit(runMain(Boolean(options.desktop)));
|
|
720
|
+
});
|
|
721
|
+
cli.command("review [...args]", "Run the review CLI").allowUnknownOptions().action((args) => {
|
|
722
|
+
runOrExit(runReview(args));
|
|
723
|
+
});
|
|
724
|
+
cli.command("mcp [...args]", "Run the MCP server").allowUnknownOptions().action((args) => {
|
|
725
|
+
runOrExit(runMcp(args));
|
|
726
|
+
});
|
|
727
|
+
cli.help();
|
|
728
|
+
cli.version(CLI_VERSION);
|
|
729
|
+
cli.parse(normalizeArgv(process.argv));
|
|
730
|
+
}
|
|
731
|
+
main().catch((err) => {
|
|
732
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
733
|
+
console.error("Fatal error:", msg);
|
|
734
|
+
if (process.env.VIBE_KANBAN_DEBUG && err instanceof Error) {
|
|
735
|
+
console.error(err.stack);
|
|
736
|
+
}
|
|
737
|
+
process.exit(1);
|
|
738
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@khuongdo/vibe-kanban",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.1.45",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vibe-kanban": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "esbuild src/cli.ts --bundle --platform=node --target=node20 --format=cjs --outfile=bin/cli.js --external:adm-zip --banner:js=\"#!/usr/bin/env node\"",
|
|
11
|
+
"check": "tsc --noEmit -p tsconfig.json"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "bloop",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/khuongdo/vibe-kanban"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20.19.0"
|
|
21
|
+
},
|
|
22
|
+
"license": "",
|
|
23
|
+
"description": "NPX wrapper around vibe-kanban and vibe-kanban-mcp",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"esbuild": "^0.27.2"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"adm-zip": "^0.5.16",
|
|
29
|
+
"cac": "^7.0.0"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"bin",
|
|
33
|
+
"dist"
|
|
34
|
+
]
|
|
35
|
+
}
|