@keelcode-ai/keelcode 0.1.3-win32-x64 → 0.1.3
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/CHANGELOG.md +83 -0
- package/README.md +220 -0
- package/SECURITY.md +23 -0
- package/bin/kc.cjs +325 -0
- package/bin/kcode.cjs +325 -0
- package/bin/keel.cjs +325 -0
- package/bin/keelcode.cjs +325 -0
- package/package.json +39 -11
- package/bin/keelcode.exe +0 -0
- package/bin/rg.exe +0 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Keelcode will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on Keep a Changelog, and this project follows Semantic
|
|
6
|
+
Versioning.
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add `kc`, `kcode`, and `keel` as packaged aliases for the `keelcode` command.
|
|
13
|
+
- Add safe custom command aliases with `keelcode alias setup|add|list|remove`.
|
|
14
|
+
- Show client-observed TTFT and provider-token TPS in interactive and headless
|
|
15
|
+
output without mixing tool time into model throughput; keep interactive
|
|
16
|
+
context and performance telemetry immediately above the input box.
|
|
17
|
+
- Make the CLI hosted-only: `keelcode login` now owns the API origin and bearer
|
|
18
|
+
session, while customer provider keys, custom endpoints, and `ANTHROPIC_*`
|
|
19
|
+
transport configuration are ignored.
|
|
20
|
+
- Add `/ship`, an objective-bound deliverable workflow whose runtime withholds
|
|
21
|
+
completion until a persisted passing `Verify` receipt, a read-only final
|
|
22
|
+
review, the cited receipt ID, and a stabilized Git fingerprint all agree.
|
|
23
|
+
- Discover layered `AGENTS.md` and `KEELCODE.md` instructions from the Git root
|
|
24
|
+
to the active directory with source provenance and bounded, symlink-safe reads.
|
|
25
|
+
- Add stable `json` and streaming `jsonl` headless output formats with explicit
|
|
26
|
+
terminal outcomes and automation-friendly exit codes.
|
|
27
|
+
- Add lease-checked detached Git worktree primitives for repository-state
|
|
28
|
+
isolation without claiming operating-system sandboxing.
|
|
29
|
+
- Add standalone `proof list/show` and `worktree create/status/cleanup` commands
|
|
30
|
+
so automation can inspect receipts and manage isolated runs without an API key.
|
|
31
|
+
- Add an opt-in-aware World Cup match surface with a startup Match Centre,
|
|
32
|
+
compact in-session scores, scorer-specific goal takeovers, and `/football`
|
|
33
|
+
controls. Live provider traffic stays in the Keelcode backend.
|
|
34
|
+
|
|
35
|
+
### Security
|
|
36
|
+
|
|
37
|
+
- Treat checked-in `.keelcode/settings.json` and `.keelcode/settings.local.json`
|
|
38
|
+
as untrusted input: repositories can add restrictions and presentation
|
|
39
|
+
preferences, but cannot enable prompt-free modes, auto-allow tools, inject
|
|
40
|
+
environment variables, or launch MCP processes.
|
|
41
|
+
- Store explicitly approved per-project MCP and allow settings in an owner-only
|
|
42
|
+
directory outside the checkout, and pass MCP processes a minimal environment
|
|
43
|
+
instead of every credential held by the CLI.
|
|
44
|
+
- Require an explicit permission decision for filesystem access outside the
|
|
45
|
+
active workspace, including symbolic-link escapes, and make delegated agents
|
|
46
|
+
inherit the parent permission gate and deny rules.
|
|
47
|
+
- Pin each WebFetch HTTPS connection to the public address that was validated,
|
|
48
|
+
revalidating every redirect to prevent DNS-rebinding SSRF.
|
|
49
|
+
- Require approval for Git commands that write through `--output` or `-o`.
|
|
50
|
+
- Redact and cap persisted verification output, store receipts and worktree
|
|
51
|
+
leases with owner-only POSIX permissions, and bound gate count and execution time.
|
|
52
|
+
- Make Verify approvals fully scrollable and auditable, hard-limit observed
|
|
53
|
+
output, show exact executable command text, reject destructive checks and
|
|
54
|
+
common background syntax, detect residual children, and anchor local receipt
|
|
55
|
+
bytes per ship run.
|
|
56
|
+
- Hash actual tracked/untracked worktree bytes and submodules independently of
|
|
57
|
+
Git stat-cache, filter, assume-unchanged, and skip-worktree behavior; isolate
|
|
58
|
+
Git subprocesses from ambient `GIT_*` injection.
|
|
59
|
+
- Reject symlinked private receipt roots and revalidate directory/file identity
|
|
60
|
+
across receipt publication and reads.
|
|
61
|
+
- Block WebFetch access to unsafe URL schemes, credentials, local/private
|
|
62
|
+
networks, and unsafe redirect targets.
|
|
63
|
+
- Store user transcripts, mission artifacts, caches, and crash logs with
|
|
64
|
+
owner-only POSIX permissions.
|
|
65
|
+
- Clarify that `bypassPermissions` is an explicit no-prompt mode while deny
|
|
66
|
+
rules and tool hard denials remain enforced.
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- Reject Windows UNC paths before POSIX normalization can hide their form.
|
|
71
|
+
- Run the npm launcher with Bun so globally installed commands work on Bun-only
|
|
72
|
+
machines without a `node` executable.
|
|
73
|
+
- Avoid misclassifying an already-exiting Windows verification child as a
|
|
74
|
+
residual background process when no process tree was actually terminated.
|
|
75
|
+
|
|
76
|
+
### Build
|
|
77
|
+
|
|
78
|
+
- Standardize development, security auditing, packing, and CI on Bun.
|
|
79
|
+
- Replace the unpublished JavaScript npm artifact with a small launcher and
|
|
80
|
+
exact platform-suffixed versions of the single `keelcode` package name for
|
|
81
|
+
macOS, Linux, and Windows on x64/arm64.
|
|
82
|
+
- Fail release packing when source files, source maps, private metadata,
|
|
83
|
+
upstream endpoints, or secret-shaped credentials appear in an npm artifact.
|
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# keelcode
|
|
2
|
+
|
|
3
|
+
Keelcode is a hosted coding agent for your terminal. It can inspect a project,
|
|
4
|
+
edit files, run commands, search the web, use MCP servers, and verify its work.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
Keelcode requires Bun 1.3.14 or newer. It does not require Node.js. Install it
|
|
9
|
+
globally with Bun:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add --global @keelcode-ai/keelcode
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The package provides four equivalent commands:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
keelcode
|
|
19
|
+
kc
|
|
20
|
+
kcode
|
|
21
|
+
keel
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
To add your own command name after installation, run the guided setup or add
|
|
25
|
+
one or more aliases directly:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
keelcode alias setup
|
|
29
|
+
keelcode alias add mycode kk
|
|
30
|
+
keelcode alias list
|
|
31
|
+
keelcode alias remove kk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Custom aliases are created beside the package-manager shims. Keelcode refuses
|
|
35
|
+
to overwrite an existing command and never edits shell startup files. Package
|
|
36
|
+
installation stays non-interactive so it remains reliable in terminals, CI,
|
|
37
|
+
containers, and package managers that disable lifecycle scripts.
|
|
38
|
+
|
|
39
|
+
The `@keelcode-ai/keelcode` npm package is a small launcher that installs the matching
|
|
40
|
+
platform-suffixed version of that same package name for the current operating
|
|
41
|
+
system, CPU, and Linux libc. Keelcode's
|
|
42
|
+
application JavaScript, TypeScript, source maps, tests, and build configuration
|
|
43
|
+
are not included in the public package.
|
|
44
|
+
|
|
45
|
+
## Sign in and start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
keelcode login
|
|
49
|
+
keelcode
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
If the machine cannot open a browser:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
keelcode login --no-browser
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Keelcode will also start sign-in automatically when an interactive session has
|
|
59
|
+
no usable account. Authentication uses a short-lived device approval; provider
|
|
60
|
+
credentials are never stored in the CLI.
|
|
61
|
+
|
|
62
|
+
Useful account commands:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
keelcode whoami
|
|
66
|
+
keelcode usage
|
|
67
|
+
keelcode logout
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Everyday use
|
|
71
|
+
|
|
72
|
+
Start in the current project:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
keelcode
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Run one prompt without opening the TUI:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
keelcode -p "find and fix the failing test"
|
|
82
|
+
git diff | keelcode -p "review this diff"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Resume work:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
keelcode -c
|
|
89
|
+
keelcode sessions
|
|
90
|
+
keelcode --resume 2
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Choose a model at launch:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
keelcode --model <model-alias>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Inside the TUI, use `/models` to open the current model picker and `/usage` to
|
|
100
|
+
see the remaining hosted allowance. Run `/help` for the complete command list.
|
|
101
|
+
The footer reports client-observed `ttft` and `tps` for the latest model turn.
|
|
102
|
+
TTFT includes network and gateway latency; TPS uses provider-reported output
|
|
103
|
+
tokens and excludes tool execution. A dash is shown when the stream does not
|
|
104
|
+
contain enough timing or usage information for an honest measurement.
|
|
105
|
+
|
|
106
|
+
Measure local launcher startup:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
keelcode bench
|
|
110
|
+
keelcode bench --json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The benchmark runs the installed launcher repeatedly, reports median/p90/min/max
|
|
114
|
+
latency, and is useful for comparing terminals, package managers, and releases.
|
|
115
|
+
|
|
116
|
+
## Common options
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
-m, --model <alias> choose a Keelcode model
|
|
120
|
+
--permission-mode <m> default | plan | acceptEdits | bypassPermissions
|
|
121
|
+
--yolo bypass permission prompts in trusted environments
|
|
122
|
+
--cwd <dir> set the working directory
|
|
123
|
+
--icons <set> unicode | nerd | ascii | auto
|
|
124
|
+
--no-alt-screen keep output in the main terminal buffer
|
|
125
|
+
-c, --continue resume the newest session in this directory
|
|
126
|
+
--resume [id] resume by id/index; no argument lists sessions
|
|
127
|
+
-p, --print [prompt] run one headless prompt
|
|
128
|
+
--output-format <f> text | json | jsonl
|
|
129
|
+
-h, --help show help
|
|
130
|
+
-v, --version show the installed version
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`bypassPermissions` and `--yolo` remove confirmation prompts for operations
|
|
134
|
+
inside the workspace. Crossing the workspace boundary still requires explicit
|
|
135
|
+
approval and fails closed in headless mode.
|
|
136
|
+
|
|
137
|
+
## Configuration
|
|
138
|
+
|
|
139
|
+
Settings merge in this order, with later files winning:
|
|
140
|
+
|
|
141
|
+
1. `~/.keelcode/settings.json`
|
|
142
|
+
2. `<project>/.keelcode/settings.json` and `settings.local.json` (sanitized,
|
|
143
|
+
capability-reducing fields only)
|
|
144
|
+
3. trusted per-project overrides under `~/.keelcode/projects/<workspace-hash>/`
|
|
145
|
+
4. command-line flags
|
|
146
|
+
|
|
147
|
+
Example project configuration:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"model": "<model-alias>",
|
|
152
|
+
"permissionMode": "default",
|
|
153
|
+
"maxTurns": 100,
|
|
154
|
+
"timeoutMs": 600000,
|
|
155
|
+
"permissions": {
|
|
156
|
+
"ask": ["Bash", "Write", "Edit"],
|
|
157
|
+
"deny": ["Write(/etc/**)"]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Committed or in-tree settings cannot enable MCP processes, inject environment
|
|
163
|
+
variables, add allow rules, select `acceptEdits`/`bypassPermissions`, or define
|
|
164
|
+
unknown future capabilities. Explicit per-project choices made through the CLI
|
|
165
|
+
are stored outside the repository. Keelcode does not support bring-your-own-provider keys.
|
|
166
|
+
|
|
167
|
+
## MCP and skills
|
|
168
|
+
|
|
169
|
+
Inspect extension health:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
keelcode doctor
|
|
173
|
+
keelcode mcp list
|
|
174
|
+
keelcode skills list
|
|
175
|
+
keelcode import list
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Use `keelcode mcp --help` and `keelcode skills --help` for management commands.
|
|
179
|
+
MCP tools pass through the same permission checks as built-in tools.
|
|
180
|
+
|
|
181
|
+
## Automation
|
|
182
|
+
|
|
183
|
+
For scripts and CI, prefer structured headless output:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
keelcode -p "run the tests and summarize failures" --output-format json
|
|
187
|
+
keelcode -p "review this repository" --output-format jsonl
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Human progress is written to stderr. Machine-readable results are written to
|
|
191
|
+
stdout, so redirecting stdout does not mix logs into the protocol.
|
|
192
|
+
Model performance is written to stderr in text mode and included as structured
|
|
193
|
+
`performance` data in JSON/JSONL output.
|
|
194
|
+
|
|
195
|
+
## Local data and privacy
|
|
196
|
+
|
|
197
|
+
Keelcode stores account sessions, settings, conversations, caches, and local
|
|
198
|
+
receipts under `~/.keelcode`. Sensitive state uses owner-only permissions on
|
|
199
|
+
supported POSIX systems.
|
|
200
|
+
|
|
201
|
+
Anonymous product analytics are opt-in and can be controlled at any time:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
keelcode telemetry status
|
|
205
|
+
keelcode telemetry enable
|
|
206
|
+
keelcode telemetry disable
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Do not paste production secrets into prompts or commit `.keelcode` personal
|
|
210
|
+
state to a repository.
|
|
211
|
+
|
|
212
|
+
## Security
|
|
213
|
+
|
|
214
|
+
Report suspected vulnerabilities privately to `security@keelcode.ai`. Do not
|
|
215
|
+
include real credentials or unrelated private data in a report.
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
Keelcode is proprietary software. Installation and use are governed by the
|
|
220
|
+
license included with this package and the applicable Keelcode service terms.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Security fixes are provided for the latest published version of Keelcode.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
Please do not disclose an unpatched vulnerability publicly. Email
|
|
10
|
+
`security@keelcode.ai` with the affected version, reproduction steps, impact,
|
|
11
|
+
and any suggested mitigation.
|
|
12
|
+
|
|
13
|
+
Avoid including real API keys, proprietary source code, or other sensitive data
|
|
14
|
+
in a report. We will acknowledge a complete report as soon as practical and
|
|
15
|
+
coordinate disclosure after a fix is available.
|
|
16
|
+
|
|
17
|
+
## Local data
|
|
18
|
+
|
|
19
|
+
Keelcode persists conversations, mission records, downloaded skills, model
|
|
20
|
+
metadata, and crash diagnostics under `~/.keelcode` by default. On POSIX systems,
|
|
21
|
+
Keelcode creates its private state directories with mode `0700` and sensitive
|
|
22
|
+
files with mode `0600`. Users remain responsible for securing backups and any
|
|
23
|
+
custom path supplied through `KEELCODE_CONFIG_DIR`.
|
package/bin/kc.cjs
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('node:child_process')
|
|
5
|
+
const {
|
|
6
|
+
lstatSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
statSync,
|
|
11
|
+
symlinkSync,
|
|
12
|
+
unlinkSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
} = require('node:fs')
|
|
15
|
+
const { createInterface } = require('node:readline')
|
|
16
|
+
const path = require('node:path')
|
|
17
|
+
|
|
18
|
+
const VERSION = "0.1.3"
|
|
19
|
+
const TARGETS = {
|
|
20
|
+
"darwin-arm64": {
|
|
21
|
+
"packageName": "@keelcode-ai/keelcode-darwin-arm64",
|
|
22
|
+
"packageVersion": "0.1.3-darwin-arm64",
|
|
23
|
+
"executableName": "keelcode",
|
|
24
|
+
"ripgrepExecutableName": "rg"
|
|
25
|
+
},
|
|
26
|
+
"darwin-x64": {
|
|
27
|
+
"packageName": "@keelcode-ai/keelcode-darwin-x64",
|
|
28
|
+
"packageVersion": "0.1.3-darwin-x64",
|
|
29
|
+
"executableName": "keelcode",
|
|
30
|
+
"ripgrepExecutableName": "rg"
|
|
31
|
+
},
|
|
32
|
+
"linux-arm64": {
|
|
33
|
+
"packageName": "@keelcode-ai/keelcode-linux-arm64",
|
|
34
|
+
"packageVersion": "0.1.3-linux-arm64",
|
|
35
|
+
"executableName": "keelcode",
|
|
36
|
+
"ripgrepExecutableName": "rg"
|
|
37
|
+
},
|
|
38
|
+
"linux-arm64-musl": {
|
|
39
|
+
"packageName": "@keelcode-ai/keelcode-linux-arm64-musl",
|
|
40
|
+
"packageVersion": "0.1.3-linux-arm64-musl",
|
|
41
|
+
"executableName": "keelcode",
|
|
42
|
+
"ripgrepExecutableName": "rg"
|
|
43
|
+
},
|
|
44
|
+
"linux-x64": {
|
|
45
|
+
"packageName": "@keelcode-ai/keelcode-linux-x64",
|
|
46
|
+
"packageVersion": "0.1.3-linux-x64",
|
|
47
|
+
"executableName": "keelcode",
|
|
48
|
+
"ripgrepExecutableName": "rg"
|
|
49
|
+
},
|
|
50
|
+
"linux-x64-musl": {
|
|
51
|
+
"packageName": "@keelcode-ai/keelcode-linux-x64-musl",
|
|
52
|
+
"packageVersion": "0.1.3-linux-x64-musl",
|
|
53
|
+
"executableName": "keelcode",
|
|
54
|
+
"ripgrepExecutableName": "rg"
|
|
55
|
+
},
|
|
56
|
+
"win32-arm64": {
|
|
57
|
+
"packageName": "@keelcode-ai/keelcode-win32-arm64",
|
|
58
|
+
"packageVersion": "0.1.3-win32-arm64",
|
|
59
|
+
"executableName": "keelcode.exe",
|
|
60
|
+
"ripgrepExecutableName": "rg.exe"
|
|
61
|
+
},
|
|
62
|
+
"win32-x64": {
|
|
63
|
+
"packageName": "@keelcode-ai/keelcode-win32-x64",
|
|
64
|
+
"packageVersion": "0.1.3-win32-x64",
|
|
65
|
+
"executableName": "keelcode.exe",
|
|
66
|
+
"ripgrepExecutableName": "rg.exe"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const DEFAULT_ALIASES = ['keelcode', 'kc', 'kcode', 'keel']
|
|
70
|
+
const ALIAS_PATTERN = /^[a-z][a-z0-9_-]{0,31}$/u
|
|
71
|
+
const WINDOWS_ALIAS_MARKER = 'REM keelcode-managed-alias'
|
|
72
|
+
|
|
73
|
+
function detectTargetId() {
|
|
74
|
+
const platform = process.platform
|
|
75
|
+
const arch = process.arch
|
|
76
|
+
if (platform === 'linux') {
|
|
77
|
+
let glibc = false
|
|
78
|
+
try {
|
|
79
|
+
glibc = Boolean(process.report && process.report.getReport().header.glibcVersionRuntime)
|
|
80
|
+
} catch {}
|
|
81
|
+
return 'linux-' + arch + (glibc ? '' : '-musl')
|
|
82
|
+
}
|
|
83
|
+
return platform + '-' + arch
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function fail(message) {
|
|
87
|
+
process.stderr.write('keelcode: ' + message + '\n')
|
|
88
|
+
process.exit(1)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function pathEntryExists(filename) {
|
|
92
|
+
try {
|
|
93
|
+
lstatSync(filename)
|
|
94
|
+
return true
|
|
95
|
+
} catch {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function commandCandidates(directory, name) {
|
|
101
|
+
if (process.platform !== 'win32') return [path.join(directory, name)]
|
|
102
|
+
return ['.exe', '.cmd', '.ps1', ''].map((extension) => path.join(directory, name + extension))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function findLauncherBinDirectory() {
|
|
106
|
+
const launcher = realpathSync(__filename)
|
|
107
|
+
let windowsFallback
|
|
108
|
+
for (const directory of (process.env.PATH || '').split(path.delimiter).filter(Boolean)) {
|
|
109
|
+
for (const name of DEFAULT_ALIASES) {
|
|
110
|
+
for (const candidate of commandCandidates(directory, name)) {
|
|
111
|
+
if (!pathEntryExists(candidate)) continue
|
|
112
|
+
if (process.platform !== 'win32') {
|
|
113
|
+
try {
|
|
114
|
+
if (realpathSync(candidate) === launcher) return { directory, command: path.basename(candidate) }
|
|
115
|
+
} catch {}
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
windowsFallback ||= { directory, command: path.basename(candidate) }
|
|
120
|
+
if (!/\.(?:cmd|ps1)$/iu.test(candidate)) continue
|
|
121
|
+
try {
|
|
122
|
+
const shim = readFileSync(candidate, 'utf8').replaceAll('\\', '/')
|
|
123
|
+
const expected = __filename.replaceAll('\\', '/')
|
|
124
|
+
if (shim.includes(expected) || shim.includes('node_modules/keelcode/bin/keelcode.cjs')) {
|
|
125
|
+
return { directory, command: path.basename(candidate) }
|
|
126
|
+
}
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (windowsFallback) return windowsFallback
|
|
132
|
+
fail('could not find the active package-manager bin directory on PATH')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function validateAliasName(name) {
|
|
136
|
+
if (DEFAULT_ALIASES.includes(name)) fail('"' + name + '" is a built-in alias and cannot be changed')
|
|
137
|
+
if (!ALIAS_PATTERN.test(name)) {
|
|
138
|
+
fail('alias "' + name + '" must start with a lowercase letter and contain only lowercase letters, digits, - or _ (32 characters max)')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function aliasConflicts(directory, name) {
|
|
143
|
+
return commandCandidates(directory, name).some(pathEntryExists)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function addAliases(names) {
|
|
147
|
+
const aliases = [...new Set(names)]
|
|
148
|
+
if (aliases.length === 0) fail('usage: keelcode alias add <name> [name...]')
|
|
149
|
+
for (const name of aliases) validateAliasName(name)
|
|
150
|
+
|
|
151
|
+
const location = findLauncherBinDirectory()
|
|
152
|
+
for (const name of aliases) {
|
|
153
|
+
if (aliasConflicts(location.directory, name)) {
|
|
154
|
+
fail('refusing to overwrite existing command "' + name + '" in ' + location.directory)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const created = []
|
|
159
|
+
try {
|
|
160
|
+
for (const name of aliases) {
|
|
161
|
+
if (process.platform === 'win32') {
|
|
162
|
+
const target = path.join(location.directory, location.command)
|
|
163
|
+
if (!/\.(?:exe|cmd)$/iu.test(target)) {
|
|
164
|
+
fail('the active Windows package-manager shim is not compatible with custom aliases')
|
|
165
|
+
}
|
|
166
|
+
const destination = path.join(location.directory, name + '.cmd')
|
|
167
|
+
const body = '@echo off\r\n' + WINDOWS_ALIAS_MARKER + '\r\n"%~dp0' + path.basename(target) + '" %*\r\n'
|
|
168
|
+
writeFileSync(destination, body, { flag: 'wx', mode: 0o755 })
|
|
169
|
+
created.push(destination)
|
|
170
|
+
} else {
|
|
171
|
+
const destination = path.join(location.directory, name)
|
|
172
|
+
symlinkSync(location.command, destination)
|
|
173
|
+
created.push(destination)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
for (const filename of created.reverse()) {
|
|
178
|
+
try { unlinkSync(filename) } catch {}
|
|
179
|
+
}
|
|
180
|
+
fail(error instanceof Error ? error.message : String(error))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
process.stdout.write('Added custom alias' + (aliases.length === 1 ? '' : 'es') + ': ' + aliases.join(', ') + '\n')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function managedAliases(location) {
|
|
187
|
+
const aliases = new Set()
|
|
188
|
+
for (const entry of readdirSync(location.directory)) {
|
|
189
|
+
if (process.platform === 'win32') {
|
|
190
|
+
if (!entry.toLowerCase().endsWith('.cmd')) continue
|
|
191
|
+
const name = entry.slice(0, -4)
|
|
192
|
+
if (DEFAULT_ALIASES.includes(name) || !ALIAS_PATTERN.test(name)) continue
|
|
193
|
+
try {
|
|
194
|
+
if (readFileSync(path.join(location.directory, entry), 'utf8').includes(WINDOWS_ALIAS_MARKER)) aliases.add(name)
|
|
195
|
+
} catch {}
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (DEFAULT_ALIASES.includes(entry) || !ALIAS_PATTERN.test(entry)) continue
|
|
200
|
+
const filename = path.join(location.directory, entry)
|
|
201
|
+
try {
|
|
202
|
+
if (lstatSync(filename).isSymbolicLink() && realpathSync(filename) === realpathSync(__filename)) aliases.add(entry)
|
|
203
|
+
} catch {}
|
|
204
|
+
}
|
|
205
|
+
return [...aliases].sort()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function listAliases() {
|
|
209
|
+
const aliases = managedAliases(findLauncherBinDirectory())
|
|
210
|
+
process.stdout.write('Built-in aliases: ' + DEFAULT_ALIASES.join(', ') + '\n')
|
|
211
|
+
process.stdout.write('Custom aliases: ' + (aliases.length > 0 ? aliases.join(', ') : 'none') + '\n')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function removeAliases(names) {
|
|
215
|
+
const aliases = [...new Set(names)]
|
|
216
|
+
if (aliases.length === 0) fail('usage: keelcode alias remove <name> [name...]')
|
|
217
|
+
for (const name of aliases) validateAliasName(name)
|
|
218
|
+
|
|
219
|
+
const location = findLauncherBinDirectory()
|
|
220
|
+
const managed = new Set(managedAliases(location))
|
|
221
|
+
for (const name of aliases) {
|
|
222
|
+
if (!managed.has(name)) fail('"' + name + '" is not a Keelcode-managed custom alias')
|
|
223
|
+
}
|
|
224
|
+
for (const name of aliases) {
|
|
225
|
+
unlinkSync(path.join(location.directory, name + (process.platform === 'win32' ? '.cmd' : '')))
|
|
226
|
+
}
|
|
227
|
+
process.stdout.write('Removed custom alias' + (aliases.length === 1 ? '' : 'es') + ': ' + aliases.join(', ') + '\n')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function printAliasHelp() {
|
|
231
|
+
process.stdout.write([
|
|
232
|
+
'Manage Keelcode command aliases',
|
|
233
|
+
'',
|
|
234
|
+
' keelcode alias setup',
|
|
235
|
+
' keelcode alias list',
|
|
236
|
+
' keelcode alias add <name> [name...]',
|
|
237
|
+
' keelcode alias remove <name> [name...]',
|
|
238
|
+
'',
|
|
239
|
+
'Built in: ' + DEFAULT_ALIASES.join(', '),
|
|
240
|
+
'Names must start with a lowercase letter and use only a-z, 0-9, - or _.',
|
|
241
|
+
'',
|
|
242
|
+
].join('\n'))
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function setupAliases() {
|
|
246
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
247
|
+
fail('alias setup needs an interactive terminal; use keelcode alias add <name> instead')
|
|
248
|
+
}
|
|
249
|
+
process.stdout.write('Built-in aliases are already active: ' + DEFAULT_ALIASES.join(', ') + '\n')
|
|
250
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout })
|
|
251
|
+
const answer = await new Promise((resolve) => {
|
|
252
|
+
readline.question('Custom aliases (comma or space separated; blank to skip): ', resolve)
|
|
253
|
+
})
|
|
254
|
+
readline.close()
|
|
255
|
+
const aliases = String(answer).trim().split(/[\s,]+/u).filter(Boolean)
|
|
256
|
+
if (aliases.length === 0) {
|
|
257
|
+
process.stdout.write('No custom aliases added.\n')
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
addAliases(aliases)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function handleAliasCommand(args) {
|
|
264
|
+
const action = args[0] || 'help'
|
|
265
|
+
if (action === 'help' || action === '--help' || action === '-h') return printAliasHelp()
|
|
266
|
+
if (action === 'setup') return setupAliases()
|
|
267
|
+
if (action === 'list') return listAliases()
|
|
268
|
+
if (action === 'add') return addAliases(args.slice(1))
|
|
269
|
+
if (action === 'remove') return removeAliases(args.slice(1))
|
|
270
|
+
fail('unknown alias action "' + action + '"; use keelcode alias --help')
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function launchNative(args) {
|
|
274
|
+
const targetId = detectTargetId()
|
|
275
|
+
const target = TARGETS[targetId]
|
|
276
|
+
if (!target) fail('unsupported platform ' + targetId)
|
|
277
|
+
|
|
278
|
+
let manifestPath
|
|
279
|
+
try {
|
|
280
|
+
manifestPath = require.resolve(target.packageName + '/package.json')
|
|
281
|
+
} catch {
|
|
282
|
+
fail('native package ' + target.packageName + ' is missing; reinstall keelcode@' + VERSION + ' with optional dependencies enabled')
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let nativeManifest
|
|
286
|
+
try {
|
|
287
|
+
nativeManifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
288
|
+
} catch {
|
|
289
|
+
fail('could not read native package metadata for ' + target.packageName)
|
|
290
|
+
}
|
|
291
|
+
if (nativeManifest.name !== '@keelcode-ai/keelcode' || nativeManifest.version !== target.packageVersion) {
|
|
292
|
+
fail('native package version mismatch: expected @keelcode-ai/keelcode@' + target.packageVersion + ', found ' + (nativeManifest.name || 'unknown') + '@' + (nativeManifest.version || 'unknown'))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const packageRoot = path.dirname(manifestPath)
|
|
296
|
+
const executable = path.join(packageRoot, 'bin', target.executableName)
|
|
297
|
+
const ripgrep = path.join(packageRoot, 'bin', target.ripgrepExecutableName)
|
|
298
|
+
for (const [label, filename] of [['native executable', executable], ['ripgrep sidecar', ripgrep]]) {
|
|
299
|
+
try {
|
|
300
|
+
if (!statSync(filename).isFile()) throw new Error('not a file')
|
|
301
|
+
} catch {
|
|
302
|
+
fail(label + ' is missing from ' + target.packageName + '; reinstall keelcode')
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const result = spawnSync(executable, args, {
|
|
307
|
+
stdio: 'inherit',
|
|
308
|
+
env: { ...process.env, KEELCODE_RG_PATH: ripgrep },
|
|
309
|
+
windowsHide: false,
|
|
310
|
+
})
|
|
311
|
+
if (result.error) fail(result.error.message)
|
|
312
|
+
if (result.signal && process.platform !== 'win32') process.kill(process.pid, result.signal)
|
|
313
|
+
process.exit(result.status === null ? 1 : result.status)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function main() {
|
|
317
|
+
const args = process.argv.slice(2)
|
|
318
|
+
if (args[0] === 'alias') {
|
|
319
|
+
await handleAliasCommand(args.slice(1))
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
launchNative(args)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
main().catch((error) => fail(error instanceof Error ? error.message : String(error)))
|