favacli 0.0.17 → 0.0.19

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.
@@ -2,9 +2,15 @@ import { Command } from 'clipanion';
2
2
  import type { Jsonifiable } from 'type-fest';
3
3
  import type { LockedRepresentationString, TwoFaLib } from 'favalib';
4
4
  import { Settings } from './utils/init.mjs';
5
+ export interface ErrorInCommand {
6
+ timestamp: number;
7
+ message: string;
8
+ }
5
9
  declare abstract class BaseCommand extends Command {
6
10
  abstract exec(): Promise<Jsonifiable>;
7
11
  abstract requireTwoFaLib: boolean;
12
+ errors: ErrorInCommand[];
13
+ preFormattedOutput: boolean;
8
14
  json: boolean | undefined;
9
15
  verbose: boolean | undefined;
10
16
  lockedRepresentationString: LockedRepresentationString;
@@ -12,5 +18,6 @@ declare abstract class BaseCommand extends Command {
12
18
  twoFaLib: TwoFaLib;
13
19
  output(string: string): void;
14
20
  execute(): Promise<number>;
21
+ private addError;
15
22
  }
16
23
  export default BaseCommand;
@@ -4,6 +4,8 @@ import init from './utils/init.mjs';
4
4
  class BaseCommand extends Command {
5
5
  constructor() {
6
6
  super(...arguments);
7
+ this.errors = [];
8
+ this.preFormattedOutput = false;
7
9
  this.json = Option.Boolean('--json', {
8
10
  description: 'output as json',
9
11
  });
@@ -20,21 +22,35 @@ class BaseCommand extends Command {
20
22
  const { lockedRepresentationString, settings } = await init();
21
23
  this.settings = settings;
22
24
  if (lockedRepresentationString && this.requireTwoFaLib) {
23
- this.twoFaLib = await loadVault(lockedRepresentationString, settings, this.verbose);
25
+ this.twoFaLib = await loadVault(lockedRepresentationString, settings, this.addError.bind(this), this.verbose);
24
26
  }
25
27
  else {
26
28
  if (this.requireTwoFaLib) {
27
29
  throw new Error('No vault loaded, was it created?');
28
30
  }
29
31
  }
30
- const json = await this.exec();
31
- if (this.json) {
32
- this.context.stdout.write(JSON.stringify(json, null, 2) + '\n');
33
- }
32
+ const result = await this.exec();
34
33
  if (this.twoFaLib?.sync) {
35
34
  this.twoFaLib.sync.closeServerConnection();
36
35
  }
36
+ if (this.json) {
37
+ // output is already formatted, don't add the result & errors bit
38
+ if (this.preFormattedOutput) {
39
+ this.context.stdout.write(JSON.stringify(result, null, 2) + '\n');
40
+ }
41
+ else {
42
+ this.context.stdout.write(JSON.stringify({ result, errors: this.errors }, null, 2) + '\n');
43
+ }
44
+ }
37
45
  return 0;
38
46
  }
47
+ addError(err) {
48
+ if (this.json && !this.verbose) {
49
+ this.errors.push({ timestamp: Date.now(), message: err.message });
50
+ }
51
+ else {
52
+ console.error(err);
53
+ }
54
+ }
39
55
  }
40
56
  export default BaseCommand;
@@ -1,8 +1,9 @@
1
1
  import type { EntryMeta, EntryMetaWithToken } from 'favalib';
2
2
  import BaseCommand from './BaseCommand.mjs';
3
+ import type { ErrorInCommand } from './BaseCommand.mjs';
3
4
  import formatters from './formatters/index.mjs';
4
5
  import { Jsonifiable } from 'type-fest';
5
- export type Formatter = (entries: (EntryMeta | EntryMetaWithToken)[]) => Jsonifiable;
6
+ export type Formatter = (entries: (EntryMeta | EntryMetaWithToken)[], errors: ErrorInCommand[]) => Jsonifiable;
6
7
  declare abstract class BaseListOutputCommand extends BaseCommand {
7
8
  abstract getList(): EntryMeta[] | EntryMetaWithToken[];
8
9
  format: (typeof formatters)[0]["name"] | undefined;
@@ -13,6 +13,7 @@ class BaseListOutputCommand extends BaseCommand {
13
13
  }
14
14
  // eslint-disable-next-line @typescript-eslint/require-await
15
15
  async exec() {
16
+ this.preFormattedOutput = this.format !== undefined;
16
17
  let formatter = (json) => json;
17
18
  if (this.format) {
18
19
  const selectedFormatter = formattersMap.get(this.format);
@@ -24,10 +25,10 @@ class BaseListOutputCommand extends BaseCommand {
24
25
  const list = this.getList();
25
26
  if (list.length === 0) {
26
27
  this.context.stdout.write('No entries\n');
27
- return formatter([]);
28
+ return formatter([], this.errors);
28
29
  }
29
30
  this.output(generateEntriesTable(list));
30
- return formatter(list);
31
+ return formatter(list, this.errors);
31
32
  }
32
33
  }
33
34
  export default BaseListOutputCommand;
@@ -0,0 +1,12 @@
1
+ import BaseCommand from '../../BaseCommand.mjs';
2
+ declare class GetInfoCommand extends BaseCommand {
3
+ static paths: string[][];
4
+ requireTwoFaLib: boolean;
5
+ static usage: import("clipanion").Usage;
6
+ exec(): Promise<{
7
+ connected: boolean;
8
+ friendlyName: string;
9
+ serverUrl: string;
10
+ }>;
11
+ }
12
+ export default GetInfoCommand;
@@ -0,0 +1,27 @@
1
+ import BaseCommand from '../../BaseCommand.mjs';
2
+ class GetInfoCommand extends BaseCommand {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.requireTwoFaLib = true;
6
+ }
7
+ static { this.paths = [['sync', 'get-info']]; }
8
+ static { this.usage = BaseCommand.Usage({
9
+ category: 'Sync',
10
+ description: 'Get sync info for the current device',
11
+ details: `This command returns the friendly name and sync server URL for the current device in your vault sync configuration.`,
12
+ examples: [['Get sync info', 'sync get-info']],
13
+ }); }
14
+ exec() {
15
+ if (!this.twoFaLib.sync) {
16
+ throw new Error('No server url set');
17
+ }
18
+ const connected = this.twoFaLib.sync.webSocketConnected || false;
19
+ const friendlyName = this.twoFaLib.meta.deviceFriendlyName || '(none)';
20
+ const serverUrl = this.twoFaLib.sync.serverUrl || '(none)';
21
+ this.output(`Connected: ${connected ? 'yes' : 'no'}\n`);
22
+ this.output(`Device friendly name: ${friendlyName}\n`);
23
+ this.output(`Sync server URL: ${serverUrl}\n`);
24
+ return Promise.resolve({ connected, friendlyName, serverUrl });
25
+ }
26
+ }
27
+ export default GetInfoCommand;
@@ -1,8 +1,15 @@
1
- const alfredFormatter = (entries) => ({
2
- items: entries.map((entry) => ({
1
+ const alfredFormatter = (entries, errors) => {
2
+ const items = entries.map((entry) => ({
3
3
  title: entry.issuer,
4
4
  subtitle: entry.name,
5
5
  arg: entry.token?.otp,
6
- })),
7
- });
6
+ }));
7
+ if (errors) {
8
+ items.unshift({
9
+ title: `Favacli encountered ${errors.length} errors while processing your command`,
10
+ subtitle: errors.map((e, i) => `[${i}]: ${e.message}`).join(' '),
11
+ });
12
+ }
13
+ return { items: items };
14
+ };
8
15
  export default { name: 'alfred', formatter: alfredFormatter };
package/build/main.mjs CHANGED
@@ -11,6 +11,7 @@ import SyncResilver from './commands/sync/resilver.mjs';
11
11
  import SyncListDevices from './commands/sync/listDevices.mjs';
12
12
  import SyncSetFriendlyNameCommand from './commands/sync/setFriendlyName.mjs';
13
13
  import ExportTextCommand from './commands/export/text.mjs';
14
+ import SyncGetInfoCommand from './commands/sync/getInfo.mjs';
14
15
  // check node version
15
16
  const nodeRuntimeMajorVersion = parseInt(process.version.split('.')[0]);
16
17
  if (nodeRuntimeMajorVersion < 20) {
@@ -20,7 +21,7 @@ const [, , ...args] = process.argv;
20
21
  const cli = new Cli({
21
22
  binaryLabel: 'FavaCli',
22
23
  binaryName: `favacli`,
23
- binaryVersion: '0.0.17',
24
+ binaryVersion: '0.0.18',
24
25
  });
25
26
  cli.register(VaultCreateCommand);
26
27
  cli.register(VaultDeleteCommand);
@@ -33,5 +34,6 @@ cli.register(SyncResilver);
33
34
  cli.register(SyncListDevices);
34
35
  cli.register(ExportTextCommand);
35
36
  cli.register(SyncSetFriendlyNameCommand);
37
+ cli.register(SyncGetInfoCommand);
36
38
  cli.register(Builtins.HelpCommand);
37
39
  void cli.runExit(args);
@@ -1,4 +1,4 @@
1
1
  import { type LockedRepresentationString } from 'favalib';
2
2
  import { Settings } from './init.mjs';
3
- declare const loadVault: (vaultData: LockedRepresentationString, settings: Settings, verbose?: boolean) => Promise<import("favalib").TwoFaLib>;
3
+ declare const loadVault: (vaultData: LockedRepresentationString, settings: Settings, addError: (err: Error) => void, verbose?: boolean) => Promise<import("favalib").TwoFaLib>;
4
4
  export default loadVault;
@@ -3,7 +3,7 @@ import keytar from 'keytar';
3
3
  import { getTwoFaLibVaultCreationUtils, TwoFaLibEvent, } from 'favalib';
4
4
  import NodeCryptoProvider from 'favalib/cryptoProviders/node';
5
5
  const cryptoLib = new NodeCryptoProvider();
6
- const loadVault = async (vaultData, settings, verbose = false) => {
6
+ const loadVault = async (vaultData, settings, addError, verbose = false) => {
7
7
  const saveFunction = async (newLockedRepresentationString) => {
8
8
  const tempFile = `${settings.vaultLocation}.tmp`;
9
9
  const backupFile = `${settings.vaultLocation}.backup`;
@@ -34,8 +34,12 @@ const loadVault = async (vaultData, settings, verbose = false) => {
34
34
  const passphrase = (await keytar.getPassword('favacli', 'vault-passphrase'));
35
35
  const twoFaLib = await twoFaLibVaultCreationUtils.loadTwoFaLibFromLockedRepesentation(vaultData, passphrase);
36
36
  twoFaLib.addEventListener(TwoFaLibEvent.Log, (ev) => {
37
+ if (ev.detail.severity === 'warning') {
38
+ addError(new Error(ev.detail.message));
39
+ return;
40
+ }
37
41
  if (ev.detail.severity !== 'info' || verbose) {
38
- console.log(ev.detail);
42
+ console.log(ev.detail.message);
39
43
  }
40
44
  });
41
45
  await twoFaLib.ready;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "favacli",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "",
@@ -10,7 +10,7 @@
10
10
  "bufferutil": "^4.0.9",
11
11
  "clipanion": "^4.0.0-rc.4",
12
12
  "env-paths": "^3.0.0",
13
- "favalib": "^0.0.10",
13
+ "favalib": "^0.0.11",
14
14
  "keytar": "^7.9.0",
15
15
  "tty-table": "^4.2.3",
16
16
  "typanion": "^3.14.0"