get-shit-done-cc-ui 0.2.2
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 +90 -0
- package/bin/get-shit-done-cc-ui +19 -0
- package/bin/get-shit-done-cc-ui.cmd +15 -0
- package/lib/download.js +272 -0
- package/package.json +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# get-shit-done-cc-ui
|
|
2
|
+
|
|
3
|
+
> GSD-UI: Visual panel for Claude Code terminal workflows
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Run directly with npx (no installation needed):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx get-shit-done-cc-ui
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or install globally:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g get-shit-done-cc-ui
|
|
17
|
+
get-shit-done-cc-ui
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**First-run note:** The first time you run this command, it will download the platform-specific binary (~15MB). After the download completes, run the command again to start the application.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Start the GSD panel
|
|
26
|
+
npx get-shit-done-cc-ui
|
|
27
|
+
|
|
28
|
+
# The panel will open in your default browser at http://localhost:6942
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- Node.js 16 or higher
|
|
34
|
+
- Internet connection (for first-run download only)
|
|
35
|
+
|
|
36
|
+
## Supported Platforms
|
|
37
|
+
|
|
38
|
+
- Linux x64
|
|
39
|
+
- macOS Intel (x64)
|
|
40
|
+
- macOS Apple Silicon (arm64)
|
|
41
|
+
- Windows x64
|
|
42
|
+
- WSL (uses Linux binary)
|
|
43
|
+
|
|
44
|
+
## Environment Variables
|
|
45
|
+
|
|
46
|
+
- `GSD_UI_PLATFORM` - Override platform detection (e.g., `darwin-arm64`, `linux-x64`, `win32-x64`)
|
|
47
|
+
- `NO_COLOR` - Disable colored output
|
|
48
|
+
|
|
49
|
+
## Links
|
|
50
|
+
|
|
51
|
+
- [GitHub Repository](https://github.com/glennin-codes/get-shit-done-cc-ui)
|
|
52
|
+
- [Issue Tracker](https://github.com/glennin-codes/get-shit-done-cc-ui/issues)
|
|
53
|
+
|
|
54
|
+
## Release Process (Maintainers)
|
|
55
|
+
|
|
56
|
+
### Prerequisites
|
|
57
|
+
|
|
58
|
+
1. **Create NPM_TOKEN:**
|
|
59
|
+
- Go to [npmjs.com](https://www.npmjs.com/) > Account > Access Tokens
|
|
60
|
+
- Click "Generate New Token" > "Automation" (for CI/CD)
|
|
61
|
+
- Copy the token value
|
|
62
|
+
|
|
63
|
+
2. **Configure GitHub secret:**
|
|
64
|
+
- Go to GitHub repo > Settings > Secrets and variables > Actions
|
|
65
|
+
- Click "New repository secret"
|
|
66
|
+
- Name: `NPM_TOKEN`
|
|
67
|
+
- Value: paste the token from step 1
|
|
68
|
+
|
|
69
|
+
### Releasing a new version
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Update version in npm/package.json
|
|
73
|
+
./scripts/bump-version.sh X.Y.Z
|
|
74
|
+
|
|
75
|
+
# Commit the version bump
|
|
76
|
+
git commit -am "chore: bump version to vX.Y.Z"
|
|
77
|
+
|
|
78
|
+
# Create and push tag
|
|
79
|
+
git tag vX.Y.Z
|
|
80
|
+
git push && git push --tags
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This triggers the release pipeline:
|
|
84
|
+
1. Build binaries for all platforms
|
|
85
|
+
2. Upload binaries to GitHub Releases
|
|
86
|
+
3. Publish npm package
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
AGPL-3.0 - see LICENSE file in the repository
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
3
|
+
BINARY="$SCRIPT_DIR/gsd-ui-web"
|
|
4
|
+
|
|
5
|
+
# Check if binary exists
|
|
6
|
+
if [ ! -f "$BINARY" ]; then
|
|
7
|
+
echo "Binary not found. Downloading for your platform..."
|
|
8
|
+
# Run download script via node
|
|
9
|
+
node "$SCRIPT_DIR/../lib/download.js"
|
|
10
|
+
DOWNLOAD_EXIT=$?
|
|
11
|
+
if [ $DOWNLOAD_EXIT -ne 0 ]; then
|
|
12
|
+
exit $DOWNLOAD_EXIT
|
|
13
|
+
fi
|
|
14
|
+
echo ""
|
|
15
|
+
echo "Download complete! Run 'npx get-shit-done-cc-ui' again to start."
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
exec "$BINARY" "$@"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal
|
|
3
|
+
set "SCRIPT_DIR=%~dp0"
|
|
4
|
+
set "BINARY=%SCRIPT_DIR%gsd-ui-web.exe"
|
|
5
|
+
|
|
6
|
+
if not exist "%BINARY%" (
|
|
7
|
+
echo Binary not found. Downloading for your platform...
|
|
8
|
+
node "%SCRIPT_DIR%..\lib\download.js"
|
|
9
|
+
if errorlevel 1 exit /b 1
|
|
10
|
+
echo.
|
|
11
|
+
echo Download complete! Run 'npx get-shit-done-cc-ui' again to start.
|
|
12
|
+
exit /b 0
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
"%BINARY%" %*
|
package/lib/download.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
const supportsColor = require('supports-color');
|
|
9
|
+
|
|
10
|
+
const VERSION = require('../package.json').version;
|
|
11
|
+
const GITHUB_REPO = 'glennin-codes/get-shit-done-cc-ui';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detect platform and return binary name
|
|
15
|
+
* @returns {string} Binary name (e.g., 'gsd-ui-web-linux-x64', 'gsd-ui-web.exe')
|
|
16
|
+
*/
|
|
17
|
+
function detectPlatform() {
|
|
18
|
+
// Allow manual override via environment variable
|
|
19
|
+
const override = process.env.GSD_UI_PLATFORM;
|
|
20
|
+
if (override) {
|
|
21
|
+
return getPlatformBinaryName(override);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let platform = process.platform;
|
|
25
|
+
let arch = process.arch;
|
|
26
|
+
|
|
27
|
+
// WSL detection - check if running on Windows but with Linux kernel
|
|
28
|
+
if (platform === 'linux' && fs.existsSync('/proc/version')) {
|
|
29
|
+
const procVersion = fs.readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
30
|
+
if (procVersion.includes('microsoft') || procVersion.includes('wsl')) {
|
|
31
|
+
// WSL detected, use Linux binary
|
|
32
|
+
platform = 'linux';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if running on Windows but actually WSL (alternate detection)
|
|
37
|
+
if (platform === 'win32' && process.env.WSL_DISTRO_NAME) {
|
|
38
|
+
platform = 'linux';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const platformKey = `${platform}-${arch}`;
|
|
42
|
+
return getPlatformBinaryName(platformKey);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Map platform key to binary name
|
|
47
|
+
* @param {string} platformKey Platform identifier (e.g., 'linux-x64', 'darwin-arm64')
|
|
48
|
+
* @returns {string} Binary name
|
|
49
|
+
*/
|
|
50
|
+
function getPlatformBinaryName(platformKey) {
|
|
51
|
+
const mapping = {
|
|
52
|
+
'linux-x64': 'gsd-ui-web-linux-x64',
|
|
53
|
+
'darwin-x64': 'gsd-ui-web-darwin-x64',
|
|
54
|
+
'darwin-arm64': 'gsd-ui-web-darwin-arm64',
|
|
55
|
+
'win32-x64': 'gsd-ui-web-win32-x64.exe',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const binaryName = mapping[platformKey];
|
|
59
|
+
|
|
60
|
+
if (!binaryName) {
|
|
61
|
+
const supportsColorStderr = supportsColor.stderr;
|
|
62
|
+
const red = supportsColorStderr && !process.env.NO_COLOR ? '\x1b[31m' : '';
|
|
63
|
+
const reset = supportsColorStderr && !process.env.NO_COLOR ? '\x1b[0m' : '';
|
|
64
|
+
|
|
65
|
+
console.error(`${red}Error: Unsupported platform: ${platformKey}${reset}`);
|
|
66
|
+
console.error('');
|
|
67
|
+
console.error('Supported platforms:');
|
|
68
|
+
console.error(' - linux-x64');
|
|
69
|
+
console.error(' - darwin-x64 (macOS Intel)');
|
|
70
|
+
console.error(' - darwin-arm64 (macOS Apple Silicon)');
|
|
71
|
+
console.error(' - win32-x64');
|
|
72
|
+
console.error('');
|
|
73
|
+
console.error('To build from source, visit:');
|
|
74
|
+
console.error('https://github.com/glennin-codes/get-shit-done-cc-ui#building-from-source');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return binaryName;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Download file from URL with progress
|
|
83
|
+
* @param {string} url URL to download from
|
|
84
|
+
* @param {string} destPath Destination file path
|
|
85
|
+
* @param {ora.Ora} spinner Spinner instance for progress updates
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
function downloadFile(url, destPath, spinner) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
https.get(url, { followRedirect: true }, (response) => {
|
|
91
|
+
// Handle redirects
|
|
92
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
93
|
+
return downloadFile(response.headers.location, destPath, spinner)
|
|
94
|
+
.then(resolve)
|
|
95
|
+
.catch(reject);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (response.statusCode !== 200) {
|
|
99
|
+
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const totalSize = parseInt(response.headers['content-length'], 10);
|
|
104
|
+
let downloadedSize = 0;
|
|
105
|
+
|
|
106
|
+
const file = fs.createWriteStream(destPath);
|
|
107
|
+
|
|
108
|
+
response.on('data', (chunk) => {
|
|
109
|
+
downloadedSize += chunk.length;
|
|
110
|
+
if (totalSize) {
|
|
111
|
+
const progress = ((downloadedSize / totalSize) * 100).toFixed(1);
|
|
112
|
+
const downloadedMB = (downloadedSize / 1024 / 1024).toFixed(1);
|
|
113
|
+
const totalMB = (totalSize / 1024 / 1024).toFixed(1);
|
|
114
|
+
spinner.text = `Downloading... ${downloadedMB}MB/${totalMB}MB (${progress}%)`;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
response.pipe(file);
|
|
119
|
+
|
|
120
|
+
file.on('finish', () => {
|
|
121
|
+
file.close();
|
|
122
|
+
resolve();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
file.on('error', (err) => {
|
|
126
|
+
fs.unlink(destPath, () => {}); // Clean up partial download
|
|
127
|
+
reject(err);
|
|
128
|
+
});
|
|
129
|
+
}).on('error', reject);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Verify file checksum
|
|
135
|
+
* @param {string} filePath Path to file to verify
|
|
136
|
+
* @param {string} expectedChecksum Expected SHA256 checksum
|
|
137
|
+
* @returns {Promise<boolean>}
|
|
138
|
+
*/
|
|
139
|
+
async function verifyChecksum(filePath, expectedChecksum) {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
const hash = crypto.createHash('sha256');
|
|
142
|
+
const stream = fs.createReadStream(filePath);
|
|
143
|
+
|
|
144
|
+
stream.on('data', (data) => hash.update(data));
|
|
145
|
+
stream.on('end', () => {
|
|
146
|
+
const actualChecksum = hash.digest('hex');
|
|
147
|
+
|
|
148
|
+
// Use timing-safe comparison to prevent timing attacks
|
|
149
|
+
const expected = Buffer.from(expectedChecksum, 'hex');
|
|
150
|
+
const actual = Buffer.from(actualChecksum, 'hex');
|
|
151
|
+
|
|
152
|
+
if (expected.length !== actual.length) {
|
|
153
|
+
resolve(false);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const isValid = crypto.timingSafeEqual(expected, actual);
|
|
159
|
+
resolve(isValid);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
reject(err);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
stream.on('error', reject);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Download and verify binary
|
|
170
|
+
* @returns {Promise<boolean>} True on success
|
|
171
|
+
*/
|
|
172
|
+
async function downloadBinary() {
|
|
173
|
+
const supportsColorStdout = supportsColor.stdout;
|
|
174
|
+
const supportsColorStderr = supportsColor.stderr;
|
|
175
|
+
const hasColor = supportsColorStdout && !process.env.NO_COLOR;
|
|
176
|
+
const red = supportsColorStderr && !process.env.NO_COLOR ? '\x1b[31m' : '';
|
|
177
|
+
const yellow = supportsColorStderr && !process.env.NO_COLOR ? '\x1b[33m' : '';
|
|
178
|
+
const reset = supportsColorStderr && !process.env.NO_COLOR ? '\x1b[0m' : '';
|
|
179
|
+
|
|
180
|
+
const binaryName = detectPlatform();
|
|
181
|
+
const isWindows = binaryName.endsWith('.exe');
|
|
182
|
+
const outputName = isWindows ? 'gsd-ui-web.exe' : 'gsd-ui-web';
|
|
183
|
+
|
|
184
|
+
const binDir = path.join(__dirname, '..', 'bin');
|
|
185
|
+
const binaryPath = path.join(binDir, outputName);
|
|
186
|
+
|
|
187
|
+
// Ensure bin directory exists
|
|
188
|
+
if (!fs.existsSync(binDir)) {
|
|
189
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const binaryUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${VERSION}/${binaryName}`;
|
|
193
|
+
const checksumUrl = `${binaryUrl}.sha256`;
|
|
194
|
+
|
|
195
|
+
const spinner = ora({
|
|
196
|
+
text: 'Downloading binary...',
|
|
197
|
+
color: hasColor ? 'cyan' : undefined,
|
|
198
|
+
spinner: 'dots',
|
|
199
|
+
}).start();
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// Download checksum file first
|
|
203
|
+
spinner.text = 'Downloading checksum...';
|
|
204
|
+
const checksumPath = path.join(binDir, `${outputName}.sha256`);
|
|
205
|
+
await downloadFile(checksumUrl, checksumPath, spinner);
|
|
206
|
+
const expectedChecksum = fs.readFileSync(checksumPath, 'utf8').trim().split(/\s+/)[0];
|
|
207
|
+
|
|
208
|
+
// Download binary
|
|
209
|
+
spinner.text = 'Downloading binary...';
|
|
210
|
+
await downloadFile(binaryUrl, binaryPath, spinner);
|
|
211
|
+
|
|
212
|
+
// Verify checksum
|
|
213
|
+
spinner.text = 'Verifying checksum...';
|
|
214
|
+
const isValid = await verifyChecksum(binaryPath, expectedChecksum);
|
|
215
|
+
|
|
216
|
+
if (!isValid) {
|
|
217
|
+
spinner.fail('Checksum verification failed');
|
|
218
|
+
console.error(`${red}Error: Downloaded binary checksum does not match expected value.${reset}`);
|
|
219
|
+
console.error('This could indicate a corrupted download or security issue.');
|
|
220
|
+
console.error('');
|
|
221
|
+
console.error('Retry: npx get-shit-done-cc-ui');
|
|
222
|
+
fs.unlinkSync(binaryPath); // Remove invalid binary
|
|
223
|
+
fs.unlinkSync(checksumPath); // Remove checksum file
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Set executable permissions on Unix
|
|
228
|
+
if (!isWindows) {
|
|
229
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Clean up checksum file
|
|
233
|
+
fs.unlinkSync(checksumPath);
|
|
234
|
+
|
|
235
|
+
spinner.succeed('Binary downloaded and verified successfully!');
|
|
236
|
+
return true;
|
|
237
|
+
|
|
238
|
+
} catch (error) {
|
|
239
|
+
spinner.fail('Download failed');
|
|
240
|
+
console.error('');
|
|
241
|
+
console.error(`${red}Error: ${error.message}${reset}`);
|
|
242
|
+
console.error('');
|
|
243
|
+
console.error('Check your internet connection and retry:');
|
|
244
|
+
console.error(' npx get-shit-done-cc-ui');
|
|
245
|
+
|
|
246
|
+
// Clean up any partial downloads
|
|
247
|
+
if (fs.existsSync(binaryPath)) {
|
|
248
|
+
fs.unlinkSync(binaryPath);
|
|
249
|
+
}
|
|
250
|
+
const checksumPath = path.join(binDir, `${outputName}.sha256`);
|
|
251
|
+
if (fs.existsSync(checksumPath)) {
|
|
252
|
+
fs.unlinkSync(checksumPath);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Allow running directly for testing
|
|
260
|
+
if (require.main === module) {
|
|
261
|
+
if (process.argv.includes('--test-platform')) {
|
|
262
|
+
console.log(`Detected platform: ${detectPlatform()}`);
|
|
263
|
+
process.exit(0);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
downloadBinary().catch((error) => {
|
|
267
|
+
console.error('Fatal error:', error);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = { downloadBinary };
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "get-shit-done-cc-ui",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "GSD-UI - Visual panel for Claude Code terminal workflows",
|
|
5
|
+
"bin": {
|
|
6
|
+
"get-shit-done-cc-ui": "./bin/get-shit-done-cc-ui"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"ora": "^8.1.1",
|
|
10
|
+
"supports-color": "^9.4.0"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/glennin-codes/get-shit-done-cc-ui.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["claude", "ai", "cli", "gsd", "terminal", "ui"],
|
|
17
|
+
"license": "AGPL-3.0",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=16"
|
|
20
|
+
}
|
|
21
|
+
}
|