@drael/code 0.3.0 → 0.4.0

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/code.js +45 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drael/code",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Point your coding client at Drael.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/code.js CHANGED
@@ -41,12 +41,15 @@ import { isCancel, PasswordPrompt, SelectPrompt } from '@clack/core'
41
41
 
42
42
  /* ───────────────────────────────────────────────────────────── where things live ── */
43
43
 
44
- const CONFIG_HOME =
45
- process.env.XDG_CONFIG_HOME ??
46
- (platform() === 'win32'
47
- ? join(process.env.APPDATA ?? homedir(), 'Config')
48
- : join(homedir(), '.config'))
49
-
44
+ // Home-relative on every platform, Windows included. The clients here document
45
+ // `~/.config/<them>` literally and read nowhere else, so the earlier `%APPDATA%\\Config`
46
+ // branch wrote a valid file into a directory nothing opens: the worst failure this tool
47
+ // has, because it reports success.
48
+ const CONFIG_HOME = process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config')
49
+
50
+ // The backups do not follow, and that is deliberate: this directory is ours rather than a
51
+ // client's, so it was never in the wrong place, and moving it would strand the records
52
+ // written by an earlier version. An uninstall that cannot find them is not an uninstall.
50
53
  const STATE_HOME =
51
54
  process.env.XDG_STATE_HOME ??
52
55
  (platform() === 'win32'
@@ -55,8 +58,8 @@ const STATE_HOME =
55
58
 
56
59
  const BACKUPS = join(STATE_HOME, 'drael', 'installer')
57
60
 
58
- // Kilo documents this literal path on every platform, Windows included, so this one
59
- // does not go through CONFIG_HOME the way the others do.
61
+ // Kilo documents this literal path, so unlike the others it is not moved by
62
+ // XDG_CONFIG_HOME either.
60
63
  const KILO_CONFIG = join(homedir(), '.config', 'kilo', 'kilo.jsonc')
61
64
 
62
65
  // There is one origin (ADR 0017), and this package is published by the people who run
@@ -279,17 +282,24 @@ const askClient = () =>
279
282
  * product itself. The one call this makes, and the only one: it is to the host being
280
283
  * configured, with the key being configured, and a host that does not answer is not a
281
284
  * reason to refuse to write anything, so the published id stands in.
285
+ *
286
+ * It is also the only check the reader needs, which is why the frame reports what came
287
+ * back instead of handing out a curl line: that line carried the key in clear text, on
288
+ * screen and into the scrollback, one row under the prompt that had just masked it.
282
289
  */
283
- async function servedModel(host, key) {
290
+ async function askHost(host, key) {
284
291
  try {
285
292
  const response = await fetch(`${host}/v1/models`, {
286
293
  headers: { Authorization: `Bearer ${key}` },
287
294
  signal: AbortSignal.timeout(4000),
288
295
  })
296
+ if (response.status === 401 || response.status === 403) {
297
+ return { refused: true }
298
+ }
289
299
  const served = (await response.json())?.data?.[0]?.id
290
- return typeof served === 'string' && served ? served : MODEL
300
+ return { model: typeof served === 'string' && served ? served : null }
291
301
  } catch {
292
- return MODEL
302
+ return {}
293
303
  }
294
304
  }
295
305
 
@@ -327,8 +337,9 @@ async function install(args) {
327
337
  fail(`unknown client: ${name} (try --list)`)
328
338
  }
329
339
 
340
+ const answer = await askHost(host, key)
330
341
  const file = client.file()
331
- const contents = client.contents(host, key, await servedModel(host, key))
342
+ const contents = client.contents(host, key, answer.model ?? MODEL)
332
343
 
333
344
  // An answered prompt leaves its frame on screen and needs a gap after it. With every
334
345
  // answer given as a flag there was no frame, and the masthead's own gap is already it.
@@ -367,11 +378,25 @@ async function install(args) {
367
378
  console.log()
368
379
  console.log(rule())
369
380
  console.log()
370
- console.log(row('check it', dim(`curl ${host}/v1/models -H "Authorization: Bearer ${key}"`)))
381
+ console.log(row('the host', dim(`${host}/v1, ${said(answer)}`)))
382
+ if (answer.refused) {
383
+ console.log(under(dim(`create one at ${host}/developers, then run this again`)))
384
+ }
371
385
  console.log(row('undo it', dim('npx @drael/code --uninstall')))
372
386
  console.log()
373
387
  }
374
388
 
389
+ /** What the one call came back with, in the frame's own voice. */
390
+ function said(answer) {
391
+ if (answer.refused) {
392
+ return 'which refused that key'
393
+ }
394
+ if (answer.model) {
395
+ return `serving ${answer.model}`
396
+ }
397
+ return `which did not answer, so ${MODEL} was written`
398
+ }
399
+
375
400
  /**
376
401
  * Every write is preceded by a copy. An uninstall that cannot put the file back is not
377
402
  * an uninstall, it is a deletion with better manners. It reports what it found rather
@@ -497,7 +522,13 @@ function parse(argv) {
497
522
  parsed[name] = true
498
523
  continue
499
524
  }
500
- parsed[name] = argv[++i]
525
+ // `--client --key dk-…` used to swallow the next flag as the value and then report
526
+ // `unknown client: --key`, which names neither the mistake nor the fix.
527
+ const value = argv[++i]
528
+ if (value === undefined || value.startsWith('--')) {
529
+ fail(`--${name} needs a value`)
530
+ }
531
+ parsed[name] = value
501
532
  }
502
533
  return parsed
503
534
  }