@keelcode-ai/keelcode 0.1.9-win32-x64 → 0.1.9
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 +154 -0
- package/README.md +255 -0
- package/SECURITY.md +23 -0
- package/bin/kc.cjs +330 -0
- package/bin/kcode.cjs +330 -0
- package/bin/keel.cjs +330 -0
- package/bin/keelcode.cjs +330 -0
- package/package.json +36 -11
- package/bin/keelcode.exe +0 -0
- package/bin/rg.exe +0 -0
package/bin/keel.cjs
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
// Node shebang: npm/pnpm/yarn/bun all generate a working launcher shim from this line, on
|
|
4
|
+
// every OS including Windows, because Node is present wherever those managers run. (A
|
|
5
|
+
// POSIX-shell shebang would break on Windows, which has no shell to run it.) A machine with
|
|
6
|
+
// Bun but no Node installs via the native curl / install.ps1 script instead, which needs no
|
|
7
|
+
// runtime at all. The body is plain CommonJS and also runs unchanged under Bun.
|
|
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.9"
|
|
24
|
+
const TARGETS = {
|
|
25
|
+
"darwin-arm64": {
|
|
26
|
+
"packageName": "@keelcode-ai/keelcode-darwin-arm64",
|
|
27
|
+
"packageVersion": "0.1.9-darwin-arm64",
|
|
28
|
+
"executableName": "keelcode",
|
|
29
|
+
"ripgrepExecutableName": "rg"
|
|
30
|
+
},
|
|
31
|
+
"darwin-x64": {
|
|
32
|
+
"packageName": "@keelcode-ai/keelcode-darwin-x64",
|
|
33
|
+
"packageVersion": "0.1.9-darwin-x64",
|
|
34
|
+
"executableName": "keelcode",
|
|
35
|
+
"ripgrepExecutableName": "rg"
|
|
36
|
+
},
|
|
37
|
+
"linux-arm64": {
|
|
38
|
+
"packageName": "@keelcode-ai/keelcode-linux-arm64",
|
|
39
|
+
"packageVersion": "0.1.9-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.9-linux-arm64-musl",
|
|
46
|
+
"executableName": "keelcode",
|
|
47
|
+
"ripgrepExecutableName": "rg"
|
|
48
|
+
},
|
|
49
|
+
"linux-x64": {
|
|
50
|
+
"packageName": "@keelcode-ai/keelcode-linux-x64",
|
|
51
|
+
"packageVersion": "0.1.9-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.9-linux-x64-musl",
|
|
58
|
+
"executableName": "keelcode",
|
|
59
|
+
"ripgrepExecutableName": "rg"
|
|
60
|
+
},
|
|
61
|
+
"win32-arm64": {
|
|
62
|
+
"packageName": "@keelcode-ai/keelcode-win32-arm64",
|
|
63
|
+
"packageVersion": "0.1.9-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.9-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
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
// Node shebang: npm/pnpm/yarn/bun all generate a working launcher shim from this line, on
|
|
4
|
+
// every OS including Windows, because Node is present wherever those managers run. (A
|
|
5
|
+
// POSIX-shell shebang would break on Windows, which has no shell to run it.) A machine with
|
|
6
|
+
// Bun but no Node installs via the native curl / install.ps1 script instead, which needs no
|
|
7
|
+
// runtime at all. The body is plain CommonJS and also runs unchanged under Bun.
|
|
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.9"
|
|
24
|
+
const TARGETS = {
|
|
25
|
+
"darwin-arm64": {
|
|
26
|
+
"packageName": "@keelcode-ai/keelcode-darwin-arm64",
|
|
27
|
+
"packageVersion": "0.1.9-darwin-arm64",
|
|
28
|
+
"executableName": "keelcode",
|
|
29
|
+
"ripgrepExecutableName": "rg"
|
|
30
|
+
},
|
|
31
|
+
"darwin-x64": {
|
|
32
|
+
"packageName": "@keelcode-ai/keelcode-darwin-x64",
|
|
33
|
+
"packageVersion": "0.1.9-darwin-x64",
|
|
34
|
+
"executableName": "keelcode",
|
|
35
|
+
"ripgrepExecutableName": "rg"
|
|
36
|
+
},
|
|
37
|
+
"linux-arm64": {
|
|
38
|
+
"packageName": "@keelcode-ai/keelcode-linux-arm64",
|
|
39
|
+
"packageVersion": "0.1.9-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.9-linux-arm64-musl",
|
|
46
|
+
"executableName": "keelcode",
|
|
47
|
+
"ripgrepExecutableName": "rg"
|
|
48
|
+
},
|
|
49
|
+
"linux-x64": {
|
|
50
|
+
"packageName": "@keelcode-ai/keelcode-linux-x64",
|
|
51
|
+
"packageVersion": "0.1.9-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.9-linux-x64-musl",
|
|
58
|
+
"executableName": "keelcode",
|
|
59
|
+
"ripgrepExecutableName": "rg"
|
|
60
|
+
},
|
|
61
|
+
"win32-arm64": {
|
|
62
|
+
"packageName": "@keelcode-ai/keelcode-win32-arm64",
|
|
63
|
+
"packageVersion": "0.1.9-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.9-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)))
|