@hivefloor/cli 0.1.3 → 0.1.5

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 (38) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -1
  3. package/dist/{chunk-HT5KXWXM.js → chunk-3N5JYP5O.js} +7 -3
  4. package/dist/{chunk-TH4ANHSG.js → chunk-4DRHR7LR.js} +365 -129
  5. package/dist/{chunk-DQV6FAJU.js → chunk-6MDJB3BV.js} +97 -26
  6. package/dist/{chunk-NLZXB26Y.js → chunk-6XVIEX75.js} +293 -71
  7. package/dist/{chunk-KHHSRIMU.js → chunk-C627OKTW.js} +2 -2
  8. package/dist/chunk-H6NYMF2D.js +50 -0
  9. package/dist/{chunk-T7TG6WA4.js → chunk-IJRHCDIG.js} +1 -1
  10. package/dist/{chunk-3YOTXIFP.js → chunk-IWIDKNWC.js} +109 -29
  11. package/dist/{chunk-E4V47VEV.js → chunk-J36OIJBM.js} +41 -23
  12. package/dist/{chunk-O6VSA2JT.js → chunk-MZGGJ6ZS.js} +87 -13
  13. package/dist/{chunk-YRNMECUF.js → chunk-RYGL6XU4.js} +5 -3
  14. package/dist/chunk-SOXRITAW.js +139 -0
  15. package/dist/{chunk-VNCQFFQO.js → chunk-TPMFQUAW.js} +102 -1
  16. package/dist/chunk-U6RRUAC2.js +229 -0
  17. package/dist/chunk-VX7SCIF2.js +65 -0
  18. package/dist/{chunk-O2TC3WSS.js → chunk-X6B6R32A.js} +1 -1
  19. package/dist/cli-JCBZICR3.js +243 -0
  20. package/dist/connect-2HLVPEOL.js +23 -0
  21. package/dist/{disconnect-FI76IEZJ.js → disconnect-WQMDCUXT.js} +41 -27
  22. package/dist/{hook-N4ZTQEWM.js → hook-AYJNRGIY.js} +25 -9
  23. package/dist/index.js +2 -2
  24. package/dist/{link-5WSZHKBE.js → link-ORT4LID6.js} +29 -16
  25. package/dist/{machine-connect-GQJ4SB3D.js → machine-connect-N52CKQEW.js} +21 -21
  26. package/dist/{mcp-EUBTVJNX.js → mcp-2SF6Y73M.js} +27 -30
  27. package/dist/{run-BEE2ATEM.js → run-ZAZ45MKQ.js} +22 -10
  28. package/dist/{status-SMAVPW4D.js → status-GOCFNO7K.js} +25 -13
  29. package/dist/{token-AUEAWQDH.js → token-EMM4AJAU.js} +15 -6
  30. package/dist/voice-F5MIS3IZ.js +262 -0
  31. package/dist/{where-7NI7UEEF.js → where-XFXRTP7E.js} +16 -7
  32. package/package.json +2 -2
  33. package/dist/chunk-2IMJECYD.js +0 -71
  34. package/dist/chunk-DWHMBUCA.js +0 -30
  35. package/dist/chunk-NKMVO5BP.js +0 -132
  36. package/dist/chunk-PY74LT2W.js +0 -41
  37. package/dist/cli-MAHYTCYV.js +0 -103
  38. package/dist/connect-QA3QIEGL.js +0 -23
@@ -8,11 +8,15 @@ import {
8
8
  isRecord,
9
9
  readText,
10
10
  removeFile
11
- } from "./chunk-VNCQFFQO.js";
11
+ } from "./chunk-TPMFQUAW.js";
12
12
  import {
13
- cliNpxCommand,
14
- runCmd
15
- } from "./chunk-TH4ANHSG.js";
13
+ normalizeHost,
14
+ runCmd,
15
+ sameOrigin
16
+ } from "./chunk-SOXRITAW.js";
17
+ import {
18
+ cliNpxCommand
19
+ } from "./chunk-4DRHR7LR.js";
16
20
 
17
21
  // src/service/units.ts
18
22
  import os from "os";
@@ -215,8 +219,13 @@ async function uninstallService(svc, run, uid) {
215
219
  }
216
220
 
217
221
  // src/credentials.ts
222
+ import { createHash } from "crypto";
218
223
  import fs from "fs";
219
- var SERVICE = "hivefloor";
224
+ var KEYCHAIN_SERVICE = "hivefloor";
225
+ var TOKEN_FP_HEX = 16;
226
+ function tokenFingerprint(token) {
227
+ return createHash("sha256").update(token, "utf8").digest("hex").slice(0, TOKEN_FP_HEX);
228
+ }
220
229
  var realKeyring = async () => {
221
230
  if (process.env.HIVEFLOOR_KEYRING === "0") return void 0;
222
231
  try {
@@ -227,6 +236,19 @@ var realKeyring = async () => {
227
236
  };
228
237
  var loadKeyring = realKeyring;
229
238
  var account = (host, tool) => `${host}#${tool}`;
239
+ function canonicalHost(host) {
240
+ try {
241
+ return normalizeHost(host);
242
+ } catch {
243
+ return void 0;
244
+ }
245
+ }
246
+ function hostKey(creds, host) {
247
+ const want = canonicalHost(host);
248
+ if (want === void 0) return creds.hosts[host] ? host : void 0;
249
+ if (creds.hosts[want]) return want;
250
+ return Object.keys(creds.hosts).find((k) => k === want || sameOrigin(k, want));
251
+ }
230
252
  function readCredentials() {
231
253
  const text = readText(credentialsFile());
232
254
  const empty = { version: 1, hosts: {} };
@@ -256,33 +278,53 @@ function writeCredentials(c) {
256
278
  }
257
279
  atomicWrite(file, JSON.stringify(c, null, 2) + "\n", 384);
258
280
  }
281
+ var windowsAclError = () => new Error("could not restrict the Hivefloor config to your Windows user with icacls; the token was not kept");
259
282
  async function secureWindowsCredentials(run, username) {
260
283
  if (!username) throw new Error("could not determine the Windows user; the token was not kept");
261
284
  const dir = prepareConfigDir();
262
285
  const d = await run("icacls", [dir, "/inheritance:r", "/grant:r", `${username}:(OI)(CI)F`]);
263
- if (d.code !== 0) throw new Error("could not restrict the Hivefloor config to your Windows user with icacls; the token was not kept");
286
+ if (d.code !== 0) throw windowsAclError();
287
+ }
288
+ async function secureWindowsCredentialsFile(run, username) {
289
+ const f = await run("icacls", [credentialsFile(), "/inheritance:r", "/grant:r", `${username}:F`]);
290
+ if (f.code !== 0) throw windowsAclError();
264
291
  }
265
292
  async function saveToken(host, tool, token, meta = {}, opts = {}) {
266
293
  const creds = readCredentials();
267
- const h = creds.hosts[host] ??= { tools: {} };
294
+ const key = hostKey(creds, host) ?? canonicalHost(host) ?? host;
295
+ const h = creds.hosts[key] ??= { tools: {} };
268
296
  let storage = "file";
269
297
  const kr = await loadKeyring();
270
298
  if (kr) {
271
299
  try {
272
- const entry = new kr.Entry(SERVICE, account(host, tool));
300
+ const entry = new kr.Entry(KEYCHAIN_SERVICE, account(key, tool));
273
301
  entry.setPassword(token);
274
302
  if (entry.getPassword() === token) storage = "keychain";
275
303
  } catch {
276
304
  storage = "file";
277
305
  }
278
306
  }
279
- if (storage === "file" && (opts.platform ?? process.platform) === "win32") {
280
- await secureWindowsCredentials(opts.run ?? runCmd, opts.username ?? windowsUser(process.env));
307
+ const winFile = storage === "file" && (opts.platform ?? process.platform) === "win32";
308
+ const run = opts.run ?? runCmd;
309
+ const username = opts.username ?? windowsUser(process.env);
310
+ if (winFile) {
311
+ await secureWindowsCredentials(run, username);
281
312
  }
313
+ const previous = winFile ? readText(credentialsFile()) : void 0;
282
314
  h.tools[tool] = { ...storage === "keychain" ? { keychain: true } : { token }, ...meta, saved_at: (/* @__PURE__ */ new Date()).toISOString() };
283
315
  h.last_tool = tool;
284
- creds.default_host = host;
316
+ h.token_fp = tokenFingerprint(token);
317
+ creds.default_host = key;
285
318
  writeCredentials(creds);
319
+ if (winFile) {
320
+ try {
321
+ await secureWindowsCredentialsFile(run, username);
322
+ } catch (e) {
323
+ if (previous === void 0) fs.rmSync(credentialsFile(), { force: true });
324
+ else atomicWrite(credentialsFile(), previous, 384);
325
+ throw e;
326
+ }
327
+ }
286
328
  return storage;
287
329
  }
288
330
  function pickTool(h, tool) {
@@ -291,8 +333,10 @@ function pickTool(h, tool) {
291
333
  return Object.keys(h.tools)[0];
292
334
  }
293
335
  async function loadToken(host, tool) {
294
- const h = readCredentials().hosts[host];
295
- if (!h) return void 0;
336
+ const creds = readCredentials();
337
+ const key = hostKey(creds, host);
338
+ const h = key ? creds.hosts[key] : void 0;
339
+ if (!key || !h) return void 0;
296
340
  const t = pickTool(h, tool);
297
341
  const c = t ? h.tools[t] : void 0;
298
342
  if (!t || !c) return void 0;
@@ -301,7 +345,7 @@ async function loadToken(host, tool) {
301
345
  const kr = await loadKeyring();
302
346
  if (!kr) return void 0;
303
347
  try {
304
- const v = new kr.Entry(SERVICE, account(host, t)).getPassword();
348
+ const v = new kr.Entry(KEYCHAIN_SERVICE, account(key, t)).getPassword();
305
349
  if (v) return { token: v, tool: t };
306
350
  } catch {
307
351
  }
@@ -312,34 +356,57 @@ function defaultHost() {
312
356
  const c = readCredentials();
313
357
  return c.default_host && c.hosts[c.default_host] ? c.default_host : Object.keys(c.hosts)[0];
314
358
  }
359
+ function deletedKeychainEntry(kr, host, tool) {
360
+ try {
361
+ const e = new kr.Entry(KEYCHAIN_SERVICE, account(host, tool));
362
+ let dropped;
363
+ if (e.deletePassword) dropped = e.deletePassword();
364
+ else if (e.deleteCredential) dropped = e.deleteCredential();
365
+ else return false;
366
+ if (dropped !== false) return true;
367
+ try {
368
+ const still = e.getPassword();
369
+ return still === null || still === void 0;
370
+ } catch {
371
+ return false;
372
+ }
373
+ } catch {
374
+ return false;
375
+ }
376
+ }
315
377
  async function deleteCredentials(host, tool) {
316
378
  const creds = readCredentials();
317
379
  const kr = await loadKeyring();
318
- let n = 0;
380
+ let removed = 0;
381
+ const leftover = [];
319
382
  for (const [h, hc] of Object.entries(creds.hosts)) {
320
- if (host && h !== host) continue;
383
+ if (host && h !== host && !sameOrigin(h, host)) continue;
384
+ let removedHere = 0;
321
385
  for (const [t, c] of Object.entries(hc.tools)) {
322
386
  if (tool && t !== tool) continue;
323
- if (c.keychain && kr) {
324
- try {
325
- const e = new kr.Entry(SERVICE, account(h, t));
326
- if (e.deletePassword) e.deletePassword();
327
- else e.deleteCredential?.();
328
- } catch {
329
- }
387
+ if (c.keychain && !(kr && deletedKeychainEntry(kr, h, t))) {
388
+ leftover.push(account(h, t));
389
+ continue;
330
390
  }
331
391
  delete hc.tools[t];
332
- n += 1;
392
+ removed += 1;
393
+ removedHere += 1;
333
394
  }
395
+ if (removedHere === 0) continue;
334
396
  if (hc.last_tool && !hc.tools[hc.last_tool]) hc.last_tool = Object.keys(hc.tools)[0];
335
397
  if (Object.keys(hc.tools).length === 0) delete creds.hosts[h];
398
+ else {
399
+ const last = hc.last_tool ? hc.tools[hc.last_tool] : void 0;
400
+ if (last?.token) hc.token_fp = tokenFingerprint(last.token);
401
+ else delete hc.token_fp;
402
+ }
336
403
  }
337
404
  if (creds.default_host && !creds.hosts[creds.default_host]) creds.default_host = Object.keys(creds.hosts)[0];
338
- if (n > 0) {
405
+ if (removed > 0) {
339
406
  if (Object.keys(creds.hosts).length === 0) fs.rmSync(credentialsFile(), { force: true });
340
407
  else writeCredentials(creds);
341
408
  }
342
- return n;
409
+ return { removed, leftover };
343
410
  }
344
411
 
345
412
  export {
@@ -348,6 +415,10 @@ export {
348
415
  installService,
349
416
  windowsServiceStatus,
350
417
  uninstallService,
418
+ KEYCHAIN_SERVICE,
419
+ TOKEN_FP_HEX,
420
+ tokenFingerprint,
421
+ hostKey,
351
422
  readCredentials,
352
423
  saveToken,
353
424
  loadToken,