@napisani/scute 0.0.1
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/LICENSE +21 -0
- package/README.md +327 -0
- package/dist/scute +0 -0
- package/package.json +87 -0
- package/scripts/install.sh +73 -0
- package/scripts/postinstall.ts +112 -0
- package/scripts/update-brew.sh +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Nick
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# scute - AI Shell Assistant
|
|
2
|
+
|
|
3
|
+
## Purpose / Goal
|
|
4
|
+
|
|
5
|
+
`scute` is a CLI companion for your shell. It adds fast, context-aware command generation, suggestion, and explanation directly in your terminal workflow. The goal is to reduce friction when crafting commands by:
|
|
6
|
+
|
|
7
|
+
- Generating commands from natural language prompts
|
|
8
|
+
- Suggesting completions for partially typed commands
|
|
9
|
+
- Explaining commands without disrupting your prompt
|
|
10
|
+
- Integrating through lightweight keybindings and shell hooks
|
|
11
|
+
|
|
12
|
+
The name comes from the scute, the protective shell plate on a turtle, and the tool itself is meant to assist with shell commands.
|
|
13
|
+
Scute is built as a single native binary (via Bun) so it can be distributed and updated easily.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Supported platforms: macOS and Linux (x86_64 only for now).
|
|
18
|
+
|
|
19
|
+
### A. Install via curl (install.sh)
|
|
20
|
+
|
|
21
|
+
Convenience installer (requires `curl` and `tar`):
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
curl -fsSL https://raw.githubusercontent.com/napisani/scute/main/scripts/install.sh | bash
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
By default it installs into `/usr/local/bin` and pulls the latest release. Pass `vX.Y.Z` and a custom directory to override.
|
|
28
|
+
|
|
29
|
+
### B. Homebrew
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
brew tap napisani/scute https://github.com/napisani/scute
|
|
33
|
+
brew install scute
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### C. npm / npx
|
|
37
|
+
|
|
38
|
+
Install globally:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
npm install -g @napisani/scute
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or run once with:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npx @napisani/scute --help
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> The npm package runs a Bun-based `postinstall` script to download the matching release binary, so Bun must be available on your `PATH`.
|
|
51
|
+
|
|
52
|
+
### D. Nix
|
|
53
|
+
|
|
54
|
+
Add the repo as an input and use it in your Home Manager flake:
|
|
55
|
+
|
|
56
|
+
```nix
|
|
57
|
+
inputs.scute.url = "github:napisani/scute";
|
|
58
|
+
|
|
59
|
+
outputs = { self, nixpkgs, scute, ... }: {
|
|
60
|
+
homeConfigurations.me = nixpkgs.lib.homeManagerConfiguration {
|
|
61
|
+
# ...
|
|
62
|
+
packages = [ scute.packages.${pkgs.system}.default ];
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> The repository ships an intentionally minimal `flake.nix`. Run `nix flake lock --update-input scute` inside your own workspace to pin exact revisions.
|
|
68
|
+
|
|
69
|
+
### E. Prebuilt binaries (manual)
|
|
70
|
+
|
|
71
|
+
Every Git tag publishes `x86_64` macOS and Linux archives on the [GitHub Releases](https://github.com/napisani/scute/releases) page. Download the archive for your platform, unpack it, and move the `scute` binary onto your `PATH`:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
curl -L -o scute.tar.gz "https://github.com/napisani/scute/releases/download/vX.Y.Z/scute-vX.Y.Z-macos-x86_64.tar.gz"
|
|
75
|
+
tar -xzf scute.tar.gz
|
|
76
|
+
sudo mv scute /usr/local/bin/
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Verify downloads with the checksums shipped alongside each release:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
curl -LO https://github.com/napisani/scute/releases/download/vX.Y.Z/checksums.txt
|
|
83
|
+
grep scute-vX.Y.Z-macos-x86_64.tar.gz checksums.txt | shasum -a 256 -c -
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### F. Install from source
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
git clone https://github.com/napisani/scute.git
|
|
90
|
+
cd scute
|
|
91
|
+
bun install --frozen-lockfile
|
|
92
|
+
bun run build:bin
|
|
93
|
+
sudo mv dist/scute /usr/local/bin/
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If you prefer Make targets:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
make build
|
|
100
|
+
sudo mv dist/scute /usr/local/bin/
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
Scute reads configuration from `~/.config/scute/config.yaml` by default. You can override it per invocation with `--config <path>`.
|
|
106
|
+
|
|
107
|
+
### Precedence
|
|
108
|
+
|
|
109
|
+
1. `--config <path>` (explicit CLI override)
|
|
110
|
+
2. Environment variables (provider keys, defaults)
|
|
111
|
+
3. Config file defaults (schema defaults)
|
|
112
|
+
|
|
113
|
+
Notes:
|
|
114
|
+
- `dotenv/config` is loaded at startup, so values in `.env` will be respected.
|
|
115
|
+
- Provider env vars (e.g., `OPENAI_API_KEY`) merge into `providers` and override matching entries.
|
|
116
|
+
|
|
117
|
+
### Minimal config example
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
# ~/.config/scute/config.yaml
|
|
121
|
+
|
|
122
|
+
# Use a compact view for the token editor
|
|
123
|
+
viewMode: horizontal
|
|
124
|
+
|
|
125
|
+
# Define at least one provider for prompts
|
|
126
|
+
providers:
|
|
127
|
+
- name: openai
|
|
128
|
+
apiKey: ${OPENAI_API_KEY}
|
|
129
|
+
|
|
130
|
+
# Optional: adjust shell keybindings (universal syntax)
|
|
131
|
+
shellKeybindings:
|
|
132
|
+
explain: "Ctrl+E"
|
|
133
|
+
build: "Ctrl+G"
|
|
134
|
+
suggest: "Ctrl+Shift+E"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Fully configured example
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
# ~/.config/scute/config.yaml
|
|
141
|
+
|
|
142
|
+
# Layout for the interactive token editor
|
|
143
|
+
viewMode: horizontal # horizontal -> annotated view, vertical -> list view
|
|
144
|
+
|
|
145
|
+
# Clipboard command for output channel "clipboard"
|
|
146
|
+
clipboardCommand: "pbcopy"
|
|
147
|
+
|
|
148
|
+
# Providers used by prompts (env vars override these)
|
|
149
|
+
providers:
|
|
150
|
+
- name: openai
|
|
151
|
+
apiKey: ${OPENAI_API_KEY}
|
|
152
|
+
- name: anthropic
|
|
153
|
+
apiKey: ${ANTHROPIC_API_KEY}
|
|
154
|
+
- name: gemini
|
|
155
|
+
apiKey: ${GEMINI_API_KEY}
|
|
156
|
+
- name: ollama
|
|
157
|
+
baseUrl: ${OLLAMA_BASE_URL}
|
|
158
|
+
|
|
159
|
+
# Keybindings for the interactive token editor UI
|
|
160
|
+
keybindings:
|
|
161
|
+
up: ["up"]
|
|
162
|
+
down: ["down"]
|
|
163
|
+
left: ["left", "h"]
|
|
164
|
+
right: ["right", "l"]
|
|
165
|
+
wordForward: ["w"]
|
|
166
|
+
wordBackward: ["b"]
|
|
167
|
+
lineStart: ["0", "^"]
|
|
168
|
+
lineEnd: ["$"]
|
|
169
|
+
firstToken: ["g"]
|
|
170
|
+
lastToken: ["G"]
|
|
171
|
+
appendLine: ["A"]
|
|
172
|
+
explain: ["e"]
|
|
173
|
+
toggleView: ["m"]
|
|
174
|
+
insert: ["i"]
|
|
175
|
+
append: ["a"]
|
|
176
|
+
change: ["c"]
|
|
177
|
+
exitInsert: ["escape"]
|
|
178
|
+
save: ["return"]
|
|
179
|
+
|
|
180
|
+
# Shell keybindings in universal syntax (rendered by scute init)
|
|
181
|
+
shellKeybindings:
|
|
182
|
+
explain: "Ctrl+E"
|
|
183
|
+
build: "Ctrl+G"
|
|
184
|
+
suggest: "Ctrl+Shift+E"
|
|
185
|
+
generate: [] # disable if you do not want a binding
|
|
186
|
+
|
|
187
|
+
# Theme colors (catppuccin defaults shown)
|
|
188
|
+
theme:
|
|
189
|
+
tokenColors:
|
|
190
|
+
command: "#A6E3A1"
|
|
191
|
+
option: "#FAB387"
|
|
192
|
+
argument: "#89B4FA"
|
|
193
|
+
assignment: "#CBA6F7"
|
|
194
|
+
pipe: "#94E2D5"
|
|
195
|
+
controlOperator: "#F38BA8"
|
|
196
|
+
redirect: "#CDD6F4"
|
|
197
|
+
unknown: "#6C7086"
|
|
198
|
+
tokenDescription: "#CDD6F4"
|
|
199
|
+
markerColor: "#CDD6F4"
|
|
200
|
+
|
|
201
|
+
# Prompt behavior per command
|
|
202
|
+
prompts:
|
|
203
|
+
explain:
|
|
204
|
+
provider: openai
|
|
205
|
+
model: gpt-4
|
|
206
|
+
temperature: 0.7
|
|
207
|
+
maxTokens: 128000
|
|
208
|
+
userPrompt: ""
|
|
209
|
+
systemPromptOverride: ""
|
|
210
|
+
suggest:
|
|
211
|
+
provider: openai
|
|
212
|
+
model: gpt-4
|
|
213
|
+
temperature: 0.7
|
|
214
|
+
maxTokens: 128000
|
|
215
|
+
generate:
|
|
216
|
+
provider: openai
|
|
217
|
+
model: gpt-4
|
|
218
|
+
temperature: 0.7
|
|
219
|
+
maxTokens: 128000
|
|
220
|
+
describeTokens:
|
|
221
|
+
provider: openai
|
|
222
|
+
model: gpt-4
|
|
223
|
+
temperature: 0.7
|
|
224
|
+
maxTokens: 128000
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Environment variables
|
|
228
|
+
|
|
229
|
+
Provider credentials and defaults:
|
|
230
|
+
|
|
231
|
+
- `OPENAI_API_KEY`
|
|
232
|
+
- `ANTHROPIC_API_KEY`
|
|
233
|
+
- `GEMINI_API_KEY`
|
|
234
|
+
- `OLLAMA_BASE_URL`
|
|
235
|
+
- `SCUTE_DEFAULT_PROVIDER`
|
|
236
|
+
- `SCUTE_DEFAULT_MODEL`
|
|
237
|
+
|
|
238
|
+
Runtime behavior:
|
|
239
|
+
|
|
240
|
+
- `SCUTE_DEBUG` (set to `1` or `true` for verbose logging)
|
|
241
|
+
- `SCUTE_SHELL` (override detected shell name)
|
|
242
|
+
- `SHELL` (standard shell env var)
|
|
243
|
+
- `READLINE_LINE` (readline current line, when present)
|
|
244
|
+
|
|
245
|
+
## Shell Integration
|
|
246
|
+
|
|
247
|
+
`scute` integrates with your shell via a script that needs to be loaded by your shell's configuration file (e.g., `.bashrc`).
|
|
248
|
+
|
|
249
|
+
### For Bash
|
|
250
|
+
|
|
251
|
+
Add the following line to the end of your `~/.bashrc` file:
|
|
252
|
+
|
|
253
|
+
```sh
|
|
254
|
+
eval "$(scute init bash)"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
After adding the line, restart your terminal or run `source ~/.bashrc` to apply the changes.
|
|
258
|
+
|
|
259
|
+
### For Nix/home-manager Users
|
|
260
|
+
|
|
261
|
+
If you use `home-manager` to manage your dotfiles, you cannot edit `.bashrc` directly. Instead, add the following to your `home.nix` configuration:
|
|
262
|
+
|
|
263
|
+
```nix
|
|
264
|
+
programs.bash = {
|
|
265
|
+
enable = true;
|
|
266
|
+
bashrcExtra = ''
|
|
267
|
+
eval "$(scute init bash)"
|
|
268
|
+
'';
|
|
269
|
+
};
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Then, run `home-manager switch` to apply the configuration.
|
|
273
|
+
|
|
274
|
+
## Debug Logging
|
|
275
|
+
|
|
276
|
+
Set `SCUTE_DEBUG=1` to enable verbose logs. When enabled, `scute` writes detailed traces to `/tmp/scute.log`:
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
export SCUTE_DEBUG=1
|
|
280
|
+
tail -f /tmp/scute.log
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Inspect Resolved Configuration
|
|
284
|
+
|
|
285
|
+
Use the `config-debug` subcommand to print the fully resolved configuration (including environment overrides). This is useful when troubleshooting provider settings or custom config files:
|
|
286
|
+
|
|
287
|
+
```sh
|
|
288
|
+
scute --config configs/ollama-config.yml config-debug
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The command prints a JSON payload containing the merged configuration and relevant environment variables.
|
|
292
|
+
|
|
293
|
+
## Testing
|
|
294
|
+
|
|
295
|
+
Test files use the `.test.ts` suffix. Evaluation tests use `.eval.test.ts` and live in the `evals/` directory.
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
# Run standard unit tests
|
|
299
|
+
bun run test
|
|
300
|
+
|
|
301
|
+
# Run evaluation tests
|
|
302
|
+
bun run evals
|
|
303
|
+
|
|
304
|
+
# Or via Make targets
|
|
305
|
+
make test
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Usage
|
|
309
|
+
|
|
310
|
+
Once installed and configured, you can use the following keyboard shortcuts in your terminal:
|
|
311
|
+
|
|
312
|
+
- **`Ctrl + G`**: **Suggest Completion**. Takes the current command you are typing, sends it to the AI for completion, and replaces the line with the AI's suggestion.
|
|
313
|
+
- **`Ctrl + P`**: **Suggest from Prompt**. Replaces your current line with a command generated by the AI based on a prompt. (Note: The current implementation is a stub).
|
|
314
|
+
- **`Ctrl + E`**: **Explain Command**. Reads the command on the current line and displays a helpful, non-interfering explanation on the line below your prompt.
|
|
315
|
+
|
|
316
|
+
## Release Process (Maintainers)
|
|
317
|
+
|
|
318
|
+
1. Update `package.json` version.
|
|
319
|
+
2. Ensure your working tree is clean and Bun is installed.
|
|
320
|
+
3. Run `make release`.
|
|
321
|
+
- Runs lint and tests, builds the binary, tags `vX.Y.Z`, pushes the tag, and publishes to npm.
|
|
322
|
+
4. GitHub Actions builds macOS/Linux archives and uploads them to the release.
|
|
323
|
+
5. Refresh Homebrew checksums:
|
|
324
|
+
|
|
325
|
+
```sh
|
|
326
|
+
make update-brew VERSION=vX.Y.Z
|
|
327
|
+
```
|
package/dist/scute
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@napisani/scute",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "AI-powered shell assistant",
|
|
5
|
+
"module": "index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"shell",
|
|
9
|
+
"ai",
|
|
10
|
+
"explain",
|
|
11
|
+
"bash",
|
|
12
|
+
"zsh",
|
|
13
|
+
"suggestion",
|
|
14
|
+
"tui"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/napisani/scute",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/napisani/scute/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/napisani/scute.git"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Nick Pisani",
|
|
26
|
+
"main": "index.js",
|
|
27
|
+
"bin": {
|
|
28
|
+
"scute": "dist/scute"
|
|
29
|
+
},
|
|
30
|
+
"directories": {
|
|
31
|
+
"test": "tests"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/",
|
|
35
|
+
"scripts/",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "bun run build:bin",
|
|
41
|
+
"build:bin": "bun build ./src/index.ts --compile --outfile dist/scute",
|
|
42
|
+
"clean": "rm -rf dist",
|
|
43
|
+
"lint": "bunx tsc --noEmit",
|
|
44
|
+
"test": "bun test tests/",
|
|
45
|
+
"coverage": "bun test tests/ --coverage",
|
|
46
|
+
"evals": "bun test evals --pattern \\\"\\.eval\\.test\\.ts$\\\"",
|
|
47
|
+
"prepublishOnly": "bun run clean && bun install --frozen-lockfile && bun run lint && bun run test",
|
|
48
|
+
"postinstall": "bun scripts/postinstall.ts"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@opentui/core": "^0.1.75",
|
|
52
|
+
"@opentui/react": "^0.1.75",
|
|
53
|
+
"@tanstack/ai": "^0.2.2",
|
|
54
|
+
"@tanstack/ai-anthropic": "^0.2.0",
|
|
55
|
+
"@tanstack/ai-gemini": "^0.3.2",
|
|
56
|
+
"@tanstack/ai-ollama": "^0.3.0",
|
|
57
|
+
"@tanstack/ai-openai": "^0.2.1",
|
|
58
|
+
"chalk": "^5.6.2",
|
|
59
|
+
"commander": "^14.0.2",
|
|
60
|
+
"dotenv": "^17.2.3",
|
|
61
|
+
"js-yaml": "^4.1.1",
|
|
62
|
+
"react": "^19.2.4",
|
|
63
|
+
"shell-quote": "^1.8.3"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@biomejs/biome": "2.3.12",
|
|
67
|
+
"@testing-library/react": "^16.3.2",
|
|
68
|
+
"@types/bun": "latest",
|
|
69
|
+
"@types/js-yaml": "^4.0.9",
|
|
70
|
+
"@types/jsdom": "^27.0.0",
|
|
71
|
+
"@types/node": "^25.0.10",
|
|
72
|
+
"@types/react": "^19.2.9",
|
|
73
|
+
"@types/shell-quote": "^1.7.5",
|
|
74
|
+
"happy-dom": "^20.5.0",
|
|
75
|
+
"jsdom": "^28.0.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"typescript": "^5"
|
|
79
|
+
},
|
|
80
|
+
"engines": {
|
|
81
|
+
"node": ">=18"
|
|
82
|
+
},
|
|
83
|
+
"os": [
|
|
84
|
+
"darwin",
|
|
85
|
+
"linux"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
REPO="napisani/scute"
|
|
5
|
+
|
|
6
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
7
|
+
echo "curl is required" >&2
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
case "$VERSION" in
|
|
12
|
+
v*) ;;
|
|
13
|
+
*) VERSION="v$VERSION" ;;
|
|
14
|
+
esac
|
|
15
|
+
|
|
16
|
+
if ! command -v tar >/dev/null 2>&1; then
|
|
17
|
+
echo "tar is required" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [ "${1:-}" = "--help" ]; then
|
|
22
|
+
cat <<EOF
|
|
23
|
+
Usage: install.sh [version] [install-dir]
|
|
24
|
+
|
|
25
|
+
version Git tag to install (default: latest release)
|
|
26
|
+
install-dir Directory to place the scute binary (default: /usr/local/bin)
|
|
27
|
+
EOF
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
VERSION="${1:-latest}"
|
|
32
|
+
INSTALL_DIR="${2:-/usr/local/bin}"
|
|
33
|
+
|
|
34
|
+
if [ "$VERSION" = "latest" ]; then
|
|
35
|
+
VERSION=$(curl -fsSL https://api.github.com/repos/${REPO}/releases/latest | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)
|
|
36
|
+
if [ -z "$VERSION" ]; then
|
|
37
|
+
echo "Unable to determine latest release tag" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
ARCH=$(uname -m)
|
|
43
|
+
OS=$(uname -s)
|
|
44
|
+
|
|
45
|
+
case "$OS" in
|
|
46
|
+
Linux) PLATFORM="linux" ;;
|
|
47
|
+
Darwin) PLATFORM="macos" ;;
|
|
48
|
+
*)
|
|
49
|
+
echo "Unsupported OS: $OS" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
;;
|
|
52
|
+
esac
|
|
53
|
+
|
|
54
|
+
if [ "$ARCH" != "x86_64" ]; then
|
|
55
|
+
echo "Currently only x86_64 binaries are published." >&2
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
TARBALL="scute-${VERSION}-${PLATFORM}-${ARCH}.tar.gz"
|
|
60
|
+
URL="https://github.com/${REPO}/releases/download/${VERSION}/${TARBALL}"
|
|
61
|
+
|
|
62
|
+
TMP_DIR=$(mktemp -d)
|
|
63
|
+
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
64
|
+
|
|
65
|
+
echo "Downloading ${URL}"
|
|
66
|
+
curl -fsSL "$URL" -o "$TMP_DIR/$TARBALL"
|
|
67
|
+
|
|
68
|
+
tar -xzf "$TMP_DIR/$TARBALL" -C "$TMP_DIR"
|
|
69
|
+
|
|
70
|
+
mkdir -p "$INSTALL_DIR"
|
|
71
|
+
install -m 755 "$TMP_DIR/scute" "$INSTALL_DIR/scute"
|
|
72
|
+
|
|
73
|
+
echo "scute installed to $INSTALL_DIR/scute"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { access, chmod, mkdir, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
async function buildFromSource(distDir: string, binaryPath: string) {
|
|
8
|
+
console.warn(
|
|
9
|
+
`[scute] No prebuilt binary available for ${process.platform}/${process.arch}. Building from source...`,
|
|
10
|
+
);
|
|
11
|
+
await mkdir(distDir, { recursive: true });
|
|
12
|
+
const build = Bun.spawn(["bun", "run", "build:bin"], {
|
|
13
|
+
stdout: "inherit",
|
|
14
|
+
stderr: "inherit",
|
|
15
|
+
});
|
|
16
|
+
const exitCode = await build.exited;
|
|
17
|
+
if (exitCode !== 0) {
|
|
18
|
+
build.kill();
|
|
19
|
+
throw new Error("Failed to build binary from source.");
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
await access(binaryPath);
|
|
23
|
+
} catch {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"Build completed but the scute binary was not created at dist/scute.",
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
await chmod(binaryPath, 0o755);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function main() {
|
|
32
|
+
const version = Bun.env.npm_package_version;
|
|
33
|
+
if (!version || version === "0.0.0-development") {
|
|
34
|
+
console.warn("[scute] Skipping postinstall: unknown package version.");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const platform = process.platform;
|
|
39
|
+
if (platform !== "darwin" && platform !== "linux") {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Unsupported platform '${platform}'. scute distributes binaries for macOS and Linux only.`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Ensure tar is available
|
|
46
|
+
const tarCheck = Bun.spawn(["tar", "--version"], {
|
|
47
|
+
stdout: "ignore",
|
|
48
|
+
stderr: "ignore",
|
|
49
|
+
});
|
|
50
|
+
if ((await tarCheck.exited) !== 0) {
|
|
51
|
+
tarCheck.kill();
|
|
52
|
+
throw new Error(
|
|
53
|
+
"`tar` is required to install scute. Please ensure it is available on PATH.",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const archMap: Record<string, string> = {
|
|
58
|
+
x64: "x86_64",
|
|
59
|
+
};
|
|
60
|
+
const distDir = join(import.meta.dir, "..", "dist");
|
|
61
|
+
await mkdir(distDir, { recursive: true });
|
|
62
|
+
|
|
63
|
+
const binaryPath = join(distDir, "scute");
|
|
64
|
+
try {
|
|
65
|
+
await access(binaryPath);
|
|
66
|
+
return; // Already present (e.g. local install)
|
|
67
|
+
} catch {
|
|
68
|
+
// continue
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const detected = archMap[process.arch] ?? null;
|
|
72
|
+
if (!detected) {
|
|
73
|
+
await buildFromSource(distDir, binaryPath);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const osName = platform === "darwin" ? "macos" : "linux";
|
|
78
|
+
const tag = version.startsWith("v") ? version : `v${version}`;
|
|
79
|
+
const tarball = `scute-${tag}-${osName}-${detected}.tar.gz`;
|
|
80
|
+
const url = `https://github.com/napisani/scute/releases/download/${tag}/${tarball}`;
|
|
81
|
+
|
|
82
|
+
console.log(`[scute] Downloading ${url}`);
|
|
83
|
+
const response = await fetch(url);
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
if (response.status === 404) {
|
|
86
|
+
await buildFromSource(distDir, binaryPath);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
throw new Error(
|
|
90
|
+
`[scute] Failed to download binary from ${url} (${response.status} ${response.statusText})`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const tmpFile = join(tmpdir(), `${tarball}.${process.pid}.tmp`);
|
|
95
|
+
const buffer = await response.arrayBuffer();
|
|
96
|
+
await Bun.write(tmpFile, buffer);
|
|
97
|
+
|
|
98
|
+
const extract = Bun.spawn(["tar", "-xzf", tmpFile, "-C", distDir]);
|
|
99
|
+
const exitCode = await extract.exited;
|
|
100
|
+
if (exitCode !== 0) {
|
|
101
|
+
extract.kill();
|
|
102
|
+
throw new Error("[scute] Failed to extract downloaded archive.");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await chmod(binaryPath, 0o755);
|
|
106
|
+
await rm(tmpFile, { force: true });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await main().catch((error) => {
|
|
110
|
+
console.error(`[scute] Postinstall error: ${error.message}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
if [ $# -ne 1 ]; then
|
|
6
|
+
echo "Usage: $0 <version>" >&2
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
VERSION="$1"
|
|
11
|
+
case "$VERSION" in
|
|
12
|
+
v*) TAG="$VERSION" ;;
|
|
13
|
+
*) TAG="v$VERSION" ;;
|
|
14
|
+
esac
|
|
15
|
+
|
|
16
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
17
|
+
echo "curl is required" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if ! command -v sha256sum >/dev/null 2>&1; then
|
|
22
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
23
|
+
sha256sum() { shasum -a 256 "$@"; }
|
|
24
|
+
else
|
|
25
|
+
echo "sha256sum or shasum is required" >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
REPO="napisani/scute"
|
|
31
|
+
|
|
32
|
+
download_and_sha() {
|
|
33
|
+
local platform="$1"
|
|
34
|
+
local arch="x86_64"
|
|
35
|
+
local url="https://github.com/${REPO}/releases/download/${TAG}/scute-${TAG}-${platform}-${arch}.tar.gz"
|
|
36
|
+
local tmp
|
|
37
|
+
tmp=$(mktemp)
|
|
38
|
+
curl -fsSL "$url" -o "$tmp"
|
|
39
|
+
local sum
|
|
40
|
+
sum=$(sha256sum "$tmp" | awk '{print $1}')
|
|
41
|
+
rm -f "$tmp"
|
|
42
|
+
echo "$sum"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
MAC_SHA=$(download_and_sha macos)
|
|
46
|
+
LINUX_SHA=$(download_and_sha linux)
|
|
47
|
+
|
|
48
|
+
FORMULA="Formula/scute.rb"
|
|
49
|
+
|
|
50
|
+
perl -0pi -e 's/version "[^"]+"/version "'"${TAG#v}"'"/' "$FORMULA"
|
|
51
|
+
perl -0pi -e 's/(on_macos do\n\s+url ".*"\n\s+sha256 ")[^"]+("/\1'"$MAC_SHA"'\2/' "$FORMULA"
|
|
52
|
+
perl -0pi -e 's/(on_linux do\n\s+url ".*"\n\s+sha256 ")[^"]+("/\1'"$LINUX_SHA"'\2/' "$FORMULA"
|
|
53
|
+
|
|
54
|
+
echo "Updated $FORMULA with version ${TAG#v}"
|