@keelcode-ai/keelcode 0.1.6 → 0.1.7-darwin-x64
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/bin/keelcode +0 -0
- package/bin/rg +0 -0
- package/package.json +11 -36
- package/CHANGELOG.md +0 -120
- package/README.md +0 -255
- package/SECURITY.md +0 -23
- package/bin/kc.cjs +0 -330
- package/bin/kcode.cjs +0 -330
- package/bin/keel.cjs +0 -330
- package/bin/keelcode.cjs +0 -330
package/bin/keel.cjs
DELETED
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
":" //# ; if command -v bun >/dev/null 2>&1; then exec bun "$0" "$@"; elif command -v node >/dev/null 2>&1; then exec node "$0" "$@"; else echo "keelcode: install Bun or Node 18+ to run keelcode" >&2; exit 127; fi
|
|
3
|
-
// Line 2 is a POSIX polyglot. Invoked through the "#!/bin/sh" shebang (a symlink or
|
|
4
|
-
// direct call), /bin/sh re-execs this same file under whichever of bun or node is on
|
|
5
|
-
// PATH. Invoked directly by bun or node, line 2 is a bare string literal followed by a
|
|
6
|
-
// "//" line comment, so the runtime ignores it and continues to the module below. This
|
|
7
|
-
// lets one published launcher run under either runtime, installed by any package manager.
|
|
8
|
-
|
|
9
|
-
const { spawnSync } = require('node:child_process')
|
|
10
|
-
const {
|
|
11
|
-
lstatSync,
|
|
12
|
-
readFileSync,
|
|
13
|
-
readdirSync,
|
|
14
|
-
realpathSync,
|
|
15
|
-
statSync,
|
|
16
|
-
symlinkSync,
|
|
17
|
-
unlinkSync,
|
|
18
|
-
writeFileSync,
|
|
19
|
-
} = require('node:fs')
|
|
20
|
-
const { createInterface } = require('node:readline')
|
|
21
|
-
const path = require('node:path')
|
|
22
|
-
|
|
23
|
-
const VERSION = "0.1.6"
|
|
24
|
-
const TARGETS = {
|
|
25
|
-
"darwin-arm64": {
|
|
26
|
-
"packageName": "@keelcode-ai/keelcode-darwin-arm64",
|
|
27
|
-
"packageVersion": "0.1.6-darwin-arm64",
|
|
28
|
-
"executableName": "keelcode",
|
|
29
|
-
"ripgrepExecutableName": "rg"
|
|
30
|
-
},
|
|
31
|
-
"darwin-x64": {
|
|
32
|
-
"packageName": "@keelcode-ai/keelcode-darwin-x64",
|
|
33
|
-
"packageVersion": "0.1.6-darwin-x64",
|
|
34
|
-
"executableName": "keelcode",
|
|
35
|
-
"ripgrepExecutableName": "rg"
|
|
36
|
-
},
|
|
37
|
-
"linux-arm64": {
|
|
38
|
-
"packageName": "@keelcode-ai/keelcode-linux-arm64",
|
|
39
|
-
"packageVersion": "0.1.6-linux-arm64",
|
|
40
|
-
"executableName": "keelcode",
|
|
41
|
-
"ripgrepExecutableName": "rg"
|
|
42
|
-
},
|
|
43
|
-
"linux-arm64-musl": {
|
|
44
|
-
"packageName": "@keelcode-ai/keelcode-linux-arm64-musl",
|
|
45
|
-
"packageVersion": "0.1.6-linux-arm64-musl",
|
|
46
|
-
"executableName": "keelcode",
|
|
47
|
-
"ripgrepExecutableName": "rg"
|
|
48
|
-
},
|
|
49
|
-
"linux-x64": {
|
|
50
|
-
"packageName": "@keelcode-ai/keelcode-linux-x64",
|
|
51
|
-
"packageVersion": "0.1.6-linux-x64",
|
|
52
|
-
"executableName": "keelcode",
|
|
53
|
-
"ripgrepExecutableName": "rg"
|
|
54
|
-
},
|
|
55
|
-
"linux-x64-musl": {
|
|
56
|
-
"packageName": "@keelcode-ai/keelcode-linux-x64-musl",
|
|
57
|
-
"packageVersion": "0.1.6-linux-x64-musl",
|
|
58
|
-
"executableName": "keelcode",
|
|
59
|
-
"ripgrepExecutableName": "rg"
|
|
60
|
-
},
|
|
61
|
-
"win32-arm64": {
|
|
62
|
-
"packageName": "@keelcode-ai/keelcode-win32-arm64",
|
|
63
|
-
"packageVersion": "0.1.6-win32-arm64",
|
|
64
|
-
"executableName": "keelcode.exe",
|
|
65
|
-
"ripgrepExecutableName": "rg.exe"
|
|
66
|
-
},
|
|
67
|
-
"win32-x64": {
|
|
68
|
-
"packageName": "@keelcode-ai/keelcode-win32-x64",
|
|
69
|
-
"packageVersion": "0.1.6-win32-x64",
|
|
70
|
-
"executableName": "keelcode.exe",
|
|
71
|
-
"ripgrepExecutableName": "rg.exe"
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
const DEFAULT_ALIASES = ['keelcode', 'kc', 'kcode', 'keel']
|
|
75
|
-
const ALIAS_PATTERN = /^[a-z][a-z0-9_-]{0,31}$/u
|
|
76
|
-
const WINDOWS_ALIAS_MARKER = 'REM keelcode-managed-alias'
|
|
77
|
-
|
|
78
|
-
function detectTargetId() {
|
|
79
|
-
const platform = process.platform
|
|
80
|
-
const arch = process.arch
|
|
81
|
-
if (platform === 'linux') {
|
|
82
|
-
let glibc = false
|
|
83
|
-
try {
|
|
84
|
-
glibc = Boolean(process.report && process.report.getReport().header.glibcVersionRuntime)
|
|
85
|
-
} catch {}
|
|
86
|
-
return 'linux-' + arch + (glibc ? '' : '-musl')
|
|
87
|
-
}
|
|
88
|
-
return platform + '-' + arch
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function fail(message) {
|
|
92
|
-
process.stderr.write('keelcode: ' + message + '\n')
|
|
93
|
-
process.exit(1)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function pathEntryExists(filename) {
|
|
97
|
-
try {
|
|
98
|
-
lstatSync(filename)
|
|
99
|
-
return true
|
|
100
|
-
} catch {
|
|
101
|
-
return false
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function commandCandidates(directory, name) {
|
|
106
|
-
if (process.platform !== 'win32') return [path.join(directory, name)]
|
|
107
|
-
return ['.exe', '.cmd', '.ps1', ''].map((extension) => path.join(directory, name + extension))
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function findLauncherBinDirectory() {
|
|
111
|
-
const launcher = realpathSync(__filename)
|
|
112
|
-
let windowsFallback
|
|
113
|
-
for (const directory of (process.env.PATH || '').split(path.delimiter).filter(Boolean)) {
|
|
114
|
-
for (const name of DEFAULT_ALIASES) {
|
|
115
|
-
for (const candidate of commandCandidates(directory, name)) {
|
|
116
|
-
if (!pathEntryExists(candidate)) continue
|
|
117
|
-
if (process.platform !== 'win32') {
|
|
118
|
-
try {
|
|
119
|
-
if (realpathSync(candidate) === launcher) return { directory, command: path.basename(candidate) }
|
|
120
|
-
} catch {}
|
|
121
|
-
continue
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
windowsFallback ||= { directory, command: path.basename(candidate) }
|
|
125
|
-
if (!/\.(?:cmd|ps1)$/iu.test(candidate)) continue
|
|
126
|
-
try {
|
|
127
|
-
const shim = readFileSync(candidate, 'utf8').replaceAll('\\', '/')
|
|
128
|
-
const expected = __filename.replaceAll('\\', '/')
|
|
129
|
-
if (shim.includes(expected) || shim.includes('node_modules/keelcode/bin/keelcode.cjs')) {
|
|
130
|
-
return { directory, command: path.basename(candidate) }
|
|
131
|
-
}
|
|
132
|
-
} catch {}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (windowsFallback) return windowsFallback
|
|
137
|
-
fail('could not find the active package-manager bin directory on PATH')
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function validateAliasName(name) {
|
|
141
|
-
if (DEFAULT_ALIASES.includes(name)) fail('"' + name + '" is a built-in alias and cannot be changed')
|
|
142
|
-
if (!ALIAS_PATTERN.test(name)) {
|
|
143
|
-
fail('alias "' + name + '" must start with a lowercase letter and contain only lowercase letters, digits, - or _ (32 characters max)')
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function aliasConflicts(directory, name) {
|
|
148
|
-
return commandCandidates(directory, name).some(pathEntryExists)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function addAliases(names) {
|
|
152
|
-
const aliases = [...new Set(names)]
|
|
153
|
-
if (aliases.length === 0) fail('usage: keelcode alias add <name> [name...]')
|
|
154
|
-
for (const name of aliases) validateAliasName(name)
|
|
155
|
-
|
|
156
|
-
const location = findLauncherBinDirectory()
|
|
157
|
-
for (const name of aliases) {
|
|
158
|
-
if (aliasConflicts(location.directory, name)) {
|
|
159
|
-
fail('refusing to overwrite existing command "' + name + '" in ' + location.directory)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const created = []
|
|
164
|
-
try {
|
|
165
|
-
for (const name of aliases) {
|
|
166
|
-
if (process.platform === 'win32') {
|
|
167
|
-
const target = path.join(location.directory, location.command)
|
|
168
|
-
if (!/\.(?:exe|cmd)$/iu.test(target)) {
|
|
169
|
-
fail('the active Windows package-manager shim is not compatible with custom aliases')
|
|
170
|
-
}
|
|
171
|
-
const destination = path.join(location.directory, name + '.cmd')
|
|
172
|
-
const body = '@echo off\r\n' + WINDOWS_ALIAS_MARKER + '\r\n"%~dp0' + path.basename(target) + '" %*\r\n'
|
|
173
|
-
writeFileSync(destination, body, { flag: 'wx', mode: 0o755 })
|
|
174
|
-
created.push(destination)
|
|
175
|
-
} else {
|
|
176
|
-
const destination = path.join(location.directory, name)
|
|
177
|
-
symlinkSync(location.command, destination)
|
|
178
|
-
created.push(destination)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
} catch (error) {
|
|
182
|
-
for (const filename of created.reverse()) {
|
|
183
|
-
try { unlinkSync(filename) } catch {}
|
|
184
|
-
}
|
|
185
|
-
fail(error instanceof Error ? error.message : String(error))
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
process.stdout.write('Added custom alias' + (aliases.length === 1 ? '' : 'es') + ': ' + aliases.join(', ') + '\n')
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function managedAliases(location) {
|
|
192
|
-
const aliases = new Set()
|
|
193
|
-
for (const entry of readdirSync(location.directory)) {
|
|
194
|
-
if (process.platform === 'win32') {
|
|
195
|
-
if (!entry.toLowerCase().endsWith('.cmd')) continue
|
|
196
|
-
const name = entry.slice(0, -4)
|
|
197
|
-
if (DEFAULT_ALIASES.includes(name) || !ALIAS_PATTERN.test(name)) continue
|
|
198
|
-
try {
|
|
199
|
-
if (readFileSync(path.join(location.directory, entry), 'utf8').includes(WINDOWS_ALIAS_MARKER)) aliases.add(name)
|
|
200
|
-
} catch {}
|
|
201
|
-
continue
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (DEFAULT_ALIASES.includes(entry) || !ALIAS_PATTERN.test(entry)) continue
|
|
205
|
-
const filename = path.join(location.directory, entry)
|
|
206
|
-
try {
|
|
207
|
-
if (lstatSync(filename).isSymbolicLink() && realpathSync(filename) === realpathSync(__filename)) aliases.add(entry)
|
|
208
|
-
} catch {}
|
|
209
|
-
}
|
|
210
|
-
return [...aliases].sort()
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function listAliases() {
|
|
214
|
-
const aliases = managedAliases(findLauncherBinDirectory())
|
|
215
|
-
process.stdout.write('Built-in aliases: ' + DEFAULT_ALIASES.join(', ') + '\n')
|
|
216
|
-
process.stdout.write('Custom aliases: ' + (aliases.length > 0 ? aliases.join(', ') : 'none') + '\n')
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function removeAliases(names) {
|
|
220
|
-
const aliases = [...new Set(names)]
|
|
221
|
-
if (aliases.length === 0) fail('usage: keelcode alias remove <name> [name...]')
|
|
222
|
-
for (const name of aliases) validateAliasName(name)
|
|
223
|
-
|
|
224
|
-
const location = findLauncherBinDirectory()
|
|
225
|
-
const managed = new Set(managedAliases(location))
|
|
226
|
-
for (const name of aliases) {
|
|
227
|
-
if (!managed.has(name)) fail('"' + name + '" is not a Keelcode-managed custom alias')
|
|
228
|
-
}
|
|
229
|
-
for (const name of aliases) {
|
|
230
|
-
unlinkSync(path.join(location.directory, name + (process.platform === 'win32' ? '.cmd' : '')))
|
|
231
|
-
}
|
|
232
|
-
process.stdout.write('Removed custom alias' + (aliases.length === 1 ? '' : 'es') + ': ' + aliases.join(', ') + '\n')
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function printAliasHelp() {
|
|
236
|
-
process.stdout.write([
|
|
237
|
-
'Manage Keelcode command aliases',
|
|
238
|
-
'',
|
|
239
|
-
' keelcode alias setup',
|
|
240
|
-
' keelcode alias list',
|
|
241
|
-
' keelcode alias add <name> [name...]',
|
|
242
|
-
' keelcode alias remove <name> [name...]',
|
|
243
|
-
'',
|
|
244
|
-
'Built in: ' + DEFAULT_ALIASES.join(', '),
|
|
245
|
-
'Names must start with a lowercase letter and use only a-z, 0-9, - or _.',
|
|
246
|
-
'',
|
|
247
|
-
].join('\n'))
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async function setupAliases() {
|
|
251
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
252
|
-
fail('alias setup needs an interactive terminal; use keelcode alias add <name> instead')
|
|
253
|
-
}
|
|
254
|
-
process.stdout.write('Built-in aliases are already active: ' + DEFAULT_ALIASES.join(', ') + '\n')
|
|
255
|
-
const readline = createInterface({ input: process.stdin, output: process.stdout })
|
|
256
|
-
const answer = await new Promise((resolve) => {
|
|
257
|
-
readline.question('Custom aliases (comma or space separated; blank to skip): ', resolve)
|
|
258
|
-
})
|
|
259
|
-
readline.close()
|
|
260
|
-
const aliases = String(answer).trim().split(/[\s,]+/u).filter(Boolean)
|
|
261
|
-
if (aliases.length === 0) {
|
|
262
|
-
process.stdout.write('No custom aliases added.\n')
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
addAliases(aliases)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async function handleAliasCommand(args) {
|
|
269
|
-
const action = args[0] || 'help'
|
|
270
|
-
if (action === 'help' || action === '--help' || action === '-h') return printAliasHelp()
|
|
271
|
-
if (action === 'setup') return setupAliases()
|
|
272
|
-
if (action === 'list') return listAliases()
|
|
273
|
-
if (action === 'add') return addAliases(args.slice(1))
|
|
274
|
-
if (action === 'remove') return removeAliases(args.slice(1))
|
|
275
|
-
fail('unknown alias action "' + action + '"; use keelcode alias --help')
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function launchNative(args) {
|
|
279
|
-
const targetId = detectTargetId()
|
|
280
|
-
const target = TARGETS[targetId]
|
|
281
|
-
if (!target) fail('unsupported platform ' + targetId)
|
|
282
|
-
|
|
283
|
-
let manifestPath
|
|
284
|
-
try {
|
|
285
|
-
manifestPath = require.resolve(target.packageName + '/package.json')
|
|
286
|
-
} catch {
|
|
287
|
-
fail('native package ' + target.packageName + ' is missing; reinstall keelcode@' + VERSION + ' with optional dependencies enabled')
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
let nativeManifest
|
|
291
|
-
try {
|
|
292
|
-
nativeManifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
293
|
-
} catch {
|
|
294
|
-
fail('could not read native package metadata for ' + target.packageName)
|
|
295
|
-
}
|
|
296
|
-
if (nativeManifest.name !== '@keelcode-ai/keelcode' || nativeManifest.version !== target.packageVersion) {
|
|
297
|
-
fail('native package version mismatch: expected @keelcode-ai/keelcode@' + target.packageVersion + ', found ' + (nativeManifest.name || 'unknown') + '@' + (nativeManifest.version || 'unknown'))
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const packageRoot = path.dirname(manifestPath)
|
|
301
|
-
const executable = path.join(packageRoot, 'bin', target.executableName)
|
|
302
|
-
const ripgrep = path.join(packageRoot, 'bin', target.ripgrepExecutableName)
|
|
303
|
-
for (const [label, filename] of [['native executable', executable], ['ripgrep sidecar', ripgrep]]) {
|
|
304
|
-
try {
|
|
305
|
-
if (!statSync(filename).isFile()) throw new Error('not a file')
|
|
306
|
-
} catch {
|
|
307
|
-
fail(label + ' is missing from ' + target.packageName + '; reinstall keelcode')
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const result = spawnSync(executable, args, {
|
|
312
|
-
stdio: 'inherit',
|
|
313
|
-
env: { ...process.env, KEELCODE_RG_PATH: ripgrep },
|
|
314
|
-
windowsHide: false,
|
|
315
|
-
})
|
|
316
|
-
if (result.error) fail(result.error.message)
|
|
317
|
-
if (result.signal && process.platform !== 'win32') process.kill(process.pid, result.signal)
|
|
318
|
-
process.exit(result.status === null ? 1 : result.status)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
async function main() {
|
|
322
|
-
const args = process.argv.slice(2)
|
|
323
|
-
if (args[0] === 'alias') {
|
|
324
|
-
await handleAliasCommand(args.slice(1))
|
|
325
|
-
return
|
|
326
|
-
}
|
|
327
|
-
launchNative(args)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
main().catch((error) => fail(error instanceof Error ? error.message : String(error)))
|
package/bin/keelcode.cjs
DELETED
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
":" //# ; if command -v bun >/dev/null 2>&1; then exec bun "$0" "$@"; elif command -v node >/dev/null 2>&1; then exec node "$0" "$@"; else echo "keelcode: install Bun or Node 18+ to run keelcode" >&2; exit 127; fi
|
|
3
|
-
// Line 2 is a POSIX polyglot. Invoked through the "#!/bin/sh" shebang (a symlink or
|
|
4
|
-
// direct call), /bin/sh re-execs this same file under whichever of bun or node is on
|
|
5
|
-
// PATH. Invoked directly by bun or node, line 2 is a bare string literal followed by a
|
|
6
|
-
// "//" line comment, so the runtime ignores it and continues to the module below. This
|
|
7
|
-
// lets one published launcher run under either runtime, installed by any package manager.
|
|
8
|
-
|
|
9
|
-
const { spawnSync } = require('node:child_process')
|
|
10
|
-
const {
|
|
11
|
-
lstatSync,
|
|
12
|
-
readFileSync,
|
|
13
|
-
readdirSync,
|
|
14
|
-
realpathSync,
|
|
15
|
-
statSync,
|
|
16
|
-
symlinkSync,
|
|
17
|
-
unlinkSync,
|
|
18
|
-
writeFileSync,
|
|
19
|
-
} = require('node:fs')
|
|
20
|
-
const { createInterface } = require('node:readline')
|
|
21
|
-
const path = require('node:path')
|
|
22
|
-
|
|
23
|
-
const VERSION = "0.1.6"
|
|
24
|
-
const TARGETS = {
|
|
25
|
-
"darwin-arm64": {
|
|
26
|
-
"packageName": "@keelcode-ai/keelcode-darwin-arm64",
|
|
27
|
-
"packageVersion": "0.1.6-darwin-arm64",
|
|
28
|
-
"executableName": "keelcode",
|
|
29
|
-
"ripgrepExecutableName": "rg"
|
|
30
|
-
},
|
|
31
|
-
"darwin-x64": {
|
|
32
|
-
"packageName": "@keelcode-ai/keelcode-darwin-x64",
|
|
33
|
-
"packageVersion": "0.1.6-darwin-x64",
|
|
34
|
-
"executableName": "keelcode",
|
|
35
|
-
"ripgrepExecutableName": "rg"
|
|
36
|
-
},
|
|
37
|
-
"linux-arm64": {
|
|
38
|
-
"packageName": "@keelcode-ai/keelcode-linux-arm64",
|
|
39
|
-
"packageVersion": "0.1.6-linux-arm64",
|
|
40
|
-
"executableName": "keelcode",
|
|
41
|
-
"ripgrepExecutableName": "rg"
|
|
42
|
-
},
|
|
43
|
-
"linux-arm64-musl": {
|
|
44
|
-
"packageName": "@keelcode-ai/keelcode-linux-arm64-musl",
|
|
45
|
-
"packageVersion": "0.1.6-linux-arm64-musl",
|
|
46
|
-
"executableName": "keelcode",
|
|
47
|
-
"ripgrepExecutableName": "rg"
|
|
48
|
-
},
|
|
49
|
-
"linux-x64": {
|
|
50
|
-
"packageName": "@keelcode-ai/keelcode-linux-x64",
|
|
51
|
-
"packageVersion": "0.1.6-linux-x64",
|
|
52
|
-
"executableName": "keelcode",
|
|
53
|
-
"ripgrepExecutableName": "rg"
|
|
54
|
-
},
|
|
55
|
-
"linux-x64-musl": {
|
|
56
|
-
"packageName": "@keelcode-ai/keelcode-linux-x64-musl",
|
|
57
|
-
"packageVersion": "0.1.6-linux-x64-musl",
|
|
58
|
-
"executableName": "keelcode",
|
|
59
|
-
"ripgrepExecutableName": "rg"
|
|
60
|
-
},
|
|
61
|
-
"win32-arm64": {
|
|
62
|
-
"packageName": "@keelcode-ai/keelcode-win32-arm64",
|
|
63
|
-
"packageVersion": "0.1.6-win32-arm64",
|
|
64
|
-
"executableName": "keelcode.exe",
|
|
65
|
-
"ripgrepExecutableName": "rg.exe"
|
|
66
|
-
},
|
|
67
|
-
"win32-x64": {
|
|
68
|
-
"packageName": "@keelcode-ai/keelcode-win32-x64",
|
|
69
|
-
"packageVersion": "0.1.6-win32-x64",
|
|
70
|
-
"executableName": "keelcode.exe",
|
|
71
|
-
"ripgrepExecutableName": "rg.exe"
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
const DEFAULT_ALIASES = ['keelcode', 'kc', 'kcode', 'keel']
|
|
75
|
-
const ALIAS_PATTERN = /^[a-z][a-z0-9_-]{0,31}$/u
|
|
76
|
-
const WINDOWS_ALIAS_MARKER = 'REM keelcode-managed-alias'
|
|
77
|
-
|
|
78
|
-
function detectTargetId() {
|
|
79
|
-
const platform = process.platform
|
|
80
|
-
const arch = process.arch
|
|
81
|
-
if (platform === 'linux') {
|
|
82
|
-
let glibc = false
|
|
83
|
-
try {
|
|
84
|
-
glibc = Boolean(process.report && process.report.getReport().header.glibcVersionRuntime)
|
|
85
|
-
} catch {}
|
|
86
|
-
return 'linux-' + arch + (glibc ? '' : '-musl')
|
|
87
|
-
}
|
|
88
|
-
return platform + '-' + arch
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function fail(message) {
|
|
92
|
-
process.stderr.write('keelcode: ' + message + '\n')
|
|
93
|
-
process.exit(1)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function pathEntryExists(filename) {
|
|
97
|
-
try {
|
|
98
|
-
lstatSync(filename)
|
|
99
|
-
return true
|
|
100
|
-
} catch {
|
|
101
|
-
return false
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function commandCandidates(directory, name) {
|
|
106
|
-
if (process.platform !== 'win32') return [path.join(directory, name)]
|
|
107
|
-
return ['.exe', '.cmd', '.ps1', ''].map((extension) => path.join(directory, name + extension))
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function findLauncherBinDirectory() {
|
|
111
|
-
const launcher = realpathSync(__filename)
|
|
112
|
-
let windowsFallback
|
|
113
|
-
for (const directory of (process.env.PATH || '').split(path.delimiter).filter(Boolean)) {
|
|
114
|
-
for (const name of DEFAULT_ALIASES) {
|
|
115
|
-
for (const candidate of commandCandidates(directory, name)) {
|
|
116
|
-
if (!pathEntryExists(candidate)) continue
|
|
117
|
-
if (process.platform !== 'win32') {
|
|
118
|
-
try {
|
|
119
|
-
if (realpathSync(candidate) === launcher) return { directory, command: path.basename(candidate) }
|
|
120
|
-
} catch {}
|
|
121
|
-
continue
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
windowsFallback ||= { directory, command: path.basename(candidate) }
|
|
125
|
-
if (!/\.(?:cmd|ps1)$/iu.test(candidate)) continue
|
|
126
|
-
try {
|
|
127
|
-
const shim = readFileSync(candidate, 'utf8').replaceAll('\\', '/')
|
|
128
|
-
const expected = __filename.replaceAll('\\', '/')
|
|
129
|
-
if (shim.includes(expected) || shim.includes('node_modules/keelcode/bin/keelcode.cjs')) {
|
|
130
|
-
return { directory, command: path.basename(candidate) }
|
|
131
|
-
}
|
|
132
|
-
} catch {}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (windowsFallback) return windowsFallback
|
|
137
|
-
fail('could not find the active package-manager bin directory on PATH')
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function validateAliasName(name) {
|
|
141
|
-
if (DEFAULT_ALIASES.includes(name)) fail('"' + name + '" is a built-in alias and cannot be changed')
|
|
142
|
-
if (!ALIAS_PATTERN.test(name)) {
|
|
143
|
-
fail('alias "' + name + '" must start with a lowercase letter and contain only lowercase letters, digits, - or _ (32 characters max)')
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function aliasConflicts(directory, name) {
|
|
148
|
-
return commandCandidates(directory, name).some(pathEntryExists)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function addAliases(names) {
|
|
152
|
-
const aliases = [...new Set(names)]
|
|
153
|
-
if (aliases.length === 0) fail('usage: keelcode alias add <name> [name...]')
|
|
154
|
-
for (const name of aliases) validateAliasName(name)
|
|
155
|
-
|
|
156
|
-
const location = findLauncherBinDirectory()
|
|
157
|
-
for (const name of aliases) {
|
|
158
|
-
if (aliasConflicts(location.directory, name)) {
|
|
159
|
-
fail('refusing to overwrite existing command "' + name + '" in ' + location.directory)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const created = []
|
|
164
|
-
try {
|
|
165
|
-
for (const name of aliases) {
|
|
166
|
-
if (process.platform === 'win32') {
|
|
167
|
-
const target = path.join(location.directory, location.command)
|
|
168
|
-
if (!/\.(?:exe|cmd)$/iu.test(target)) {
|
|
169
|
-
fail('the active Windows package-manager shim is not compatible with custom aliases')
|
|
170
|
-
}
|
|
171
|
-
const destination = path.join(location.directory, name + '.cmd')
|
|
172
|
-
const body = '@echo off\r\n' + WINDOWS_ALIAS_MARKER + '\r\n"%~dp0' + path.basename(target) + '" %*\r\n'
|
|
173
|
-
writeFileSync(destination, body, { flag: 'wx', mode: 0o755 })
|
|
174
|
-
created.push(destination)
|
|
175
|
-
} else {
|
|
176
|
-
const destination = path.join(location.directory, name)
|
|
177
|
-
symlinkSync(location.command, destination)
|
|
178
|
-
created.push(destination)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
} catch (error) {
|
|
182
|
-
for (const filename of created.reverse()) {
|
|
183
|
-
try { unlinkSync(filename) } catch {}
|
|
184
|
-
}
|
|
185
|
-
fail(error instanceof Error ? error.message : String(error))
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
process.stdout.write('Added custom alias' + (aliases.length === 1 ? '' : 'es') + ': ' + aliases.join(', ') + '\n')
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function managedAliases(location) {
|
|
192
|
-
const aliases = new Set()
|
|
193
|
-
for (const entry of readdirSync(location.directory)) {
|
|
194
|
-
if (process.platform === 'win32') {
|
|
195
|
-
if (!entry.toLowerCase().endsWith('.cmd')) continue
|
|
196
|
-
const name = entry.slice(0, -4)
|
|
197
|
-
if (DEFAULT_ALIASES.includes(name) || !ALIAS_PATTERN.test(name)) continue
|
|
198
|
-
try {
|
|
199
|
-
if (readFileSync(path.join(location.directory, entry), 'utf8').includes(WINDOWS_ALIAS_MARKER)) aliases.add(name)
|
|
200
|
-
} catch {}
|
|
201
|
-
continue
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (DEFAULT_ALIASES.includes(entry) || !ALIAS_PATTERN.test(entry)) continue
|
|
205
|
-
const filename = path.join(location.directory, entry)
|
|
206
|
-
try {
|
|
207
|
-
if (lstatSync(filename).isSymbolicLink() && realpathSync(filename) === realpathSync(__filename)) aliases.add(entry)
|
|
208
|
-
} catch {}
|
|
209
|
-
}
|
|
210
|
-
return [...aliases].sort()
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function listAliases() {
|
|
214
|
-
const aliases = managedAliases(findLauncherBinDirectory())
|
|
215
|
-
process.stdout.write('Built-in aliases: ' + DEFAULT_ALIASES.join(', ') + '\n')
|
|
216
|
-
process.stdout.write('Custom aliases: ' + (aliases.length > 0 ? aliases.join(', ') : 'none') + '\n')
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function removeAliases(names) {
|
|
220
|
-
const aliases = [...new Set(names)]
|
|
221
|
-
if (aliases.length === 0) fail('usage: keelcode alias remove <name> [name...]')
|
|
222
|
-
for (const name of aliases) validateAliasName(name)
|
|
223
|
-
|
|
224
|
-
const location = findLauncherBinDirectory()
|
|
225
|
-
const managed = new Set(managedAliases(location))
|
|
226
|
-
for (const name of aliases) {
|
|
227
|
-
if (!managed.has(name)) fail('"' + name + '" is not a Keelcode-managed custom alias')
|
|
228
|
-
}
|
|
229
|
-
for (const name of aliases) {
|
|
230
|
-
unlinkSync(path.join(location.directory, name + (process.platform === 'win32' ? '.cmd' : '')))
|
|
231
|
-
}
|
|
232
|
-
process.stdout.write('Removed custom alias' + (aliases.length === 1 ? '' : 'es') + ': ' + aliases.join(', ') + '\n')
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function printAliasHelp() {
|
|
236
|
-
process.stdout.write([
|
|
237
|
-
'Manage Keelcode command aliases',
|
|
238
|
-
'',
|
|
239
|
-
' keelcode alias setup',
|
|
240
|
-
' keelcode alias list',
|
|
241
|
-
' keelcode alias add <name> [name...]',
|
|
242
|
-
' keelcode alias remove <name> [name...]',
|
|
243
|
-
'',
|
|
244
|
-
'Built in: ' + DEFAULT_ALIASES.join(', '),
|
|
245
|
-
'Names must start with a lowercase letter and use only a-z, 0-9, - or _.',
|
|
246
|
-
'',
|
|
247
|
-
].join('\n'))
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async function setupAliases() {
|
|
251
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
252
|
-
fail('alias setup needs an interactive terminal; use keelcode alias add <name> instead')
|
|
253
|
-
}
|
|
254
|
-
process.stdout.write('Built-in aliases are already active: ' + DEFAULT_ALIASES.join(', ') + '\n')
|
|
255
|
-
const readline = createInterface({ input: process.stdin, output: process.stdout })
|
|
256
|
-
const answer = await new Promise((resolve) => {
|
|
257
|
-
readline.question('Custom aliases (comma or space separated; blank to skip): ', resolve)
|
|
258
|
-
})
|
|
259
|
-
readline.close()
|
|
260
|
-
const aliases = String(answer).trim().split(/[\s,]+/u).filter(Boolean)
|
|
261
|
-
if (aliases.length === 0) {
|
|
262
|
-
process.stdout.write('No custom aliases added.\n')
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
addAliases(aliases)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async function handleAliasCommand(args) {
|
|
269
|
-
const action = args[0] || 'help'
|
|
270
|
-
if (action === 'help' || action === '--help' || action === '-h') return printAliasHelp()
|
|
271
|
-
if (action === 'setup') return setupAliases()
|
|
272
|
-
if (action === 'list') return listAliases()
|
|
273
|
-
if (action === 'add') return addAliases(args.slice(1))
|
|
274
|
-
if (action === 'remove') return removeAliases(args.slice(1))
|
|
275
|
-
fail('unknown alias action "' + action + '"; use keelcode alias --help')
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function launchNative(args) {
|
|
279
|
-
const targetId = detectTargetId()
|
|
280
|
-
const target = TARGETS[targetId]
|
|
281
|
-
if (!target) fail('unsupported platform ' + targetId)
|
|
282
|
-
|
|
283
|
-
let manifestPath
|
|
284
|
-
try {
|
|
285
|
-
manifestPath = require.resolve(target.packageName + '/package.json')
|
|
286
|
-
} catch {
|
|
287
|
-
fail('native package ' + target.packageName + ' is missing; reinstall keelcode@' + VERSION + ' with optional dependencies enabled')
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
let nativeManifest
|
|
291
|
-
try {
|
|
292
|
-
nativeManifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
293
|
-
} catch {
|
|
294
|
-
fail('could not read native package metadata for ' + target.packageName)
|
|
295
|
-
}
|
|
296
|
-
if (nativeManifest.name !== '@keelcode-ai/keelcode' || nativeManifest.version !== target.packageVersion) {
|
|
297
|
-
fail('native package version mismatch: expected @keelcode-ai/keelcode@' + target.packageVersion + ', found ' + (nativeManifest.name || 'unknown') + '@' + (nativeManifest.version || 'unknown'))
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const packageRoot = path.dirname(manifestPath)
|
|
301
|
-
const executable = path.join(packageRoot, 'bin', target.executableName)
|
|
302
|
-
const ripgrep = path.join(packageRoot, 'bin', target.ripgrepExecutableName)
|
|
303
|
-
for (const [label, filename] of [['native executable', executable], ['ripgrep sidecar', ripgrep]]) {
|
|
304
|
-
try {
|
|
305
|
-
if (!statSync(filename).isFile()) throw new Error('not a file')
|
|
306
|
-
} catch {
|
|
307
|
-
fail(label + ' is missing from ' + target.packageName + '; reinstall keelcode')
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const result = spawnSync(executable, args, {
|
|
312
|
-
stdio: 'inherit',
|
|
313
|
-
env: { ...process.env, KEELCODE_RG_PATH: ripgrep },
|
|
314
|
-
windowsHide: false,
|
|
315
|
-
})
|
|
316
|
-
if (result.error) fail(result.error.message)
|
|
317
|
-
if (result.signal && process.platform !== 'win32') process.kill(process.pid, result.signal)
|
|
318
|
-
process.exit(result.status === null ? 1 : result.status)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
async function main() {
|
|
322
|
-
const args = process.argv.slice(2)
|
|
323
|
-
if (args[0] === 'alias') {
|
|
324
|
-
await handleAliasCommand(args.slice(1))
|
|
325
|
-
return
|
|
326
|
-
}
|
|
327
|
-
launchNative(args)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
main().catch((error) => fail(error instanceof Error ? error.message : String(error)))
|