favacli 0.0.7 → 0.0.10
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/build/BaseCommand.mjs +3 -3
- package/build/BaseListOutputCommand.d.mts +11 -0
- package/build/BaseListOutputCommand.mjs +33 -0
- package/build/commands/entries/list.d.mts +3 -3
- package/build/commands/entries/list.mjs +6 -12
- package/build/commands/entries/search.d.mts +3 -4
- package/build/commands/entries/search.mjs +6 -13
- package/build/commands/vault/create.mjs +3 -3
- package/build/commands/vault/delete.d.mts +14 -0
- package/build/commands/vault/delete.mjs +33 -0
- package/build/formatters/alfred.d.mts +6 -0
- package/build/formatters/alfred.mjs +6 -0
- package/build/formatters/index.d.mts +5 -0
- package/build/formatters/index.mjs +3 -0
- package/build/main.mjs +4 -6
- package/build/utils/init.d.mts +4 -3
- package/build/utils/init.mjs +17 -5
- package/build/utils/loadVault.d.mts +2 -1
- package/build/utils/loadVault.mjs +3 -3
- package/package.json +3 -2
package/build/BaseCommand.mjs
CHANGED
|
@@ -19,12 +19,12 @@ class BaseCommand extends Command {
|
|
|
19
19
|
async execute() {
|
|
20
20
|
const { lockedRepresentationString, settings } = await init();
|
|
21
21
|
this.settings = settings;
|
|
22
|
-
if (lockedRepresentationString) {
|
|
23
|
-
this.twoFaLib = await loadVault(lockedRepresentationString, this.verbose);
|
|
22
|
+
if (lockedRepresentationString && this.requireTwoFaLib) {
|
|
23
|
+
this.twoFaLib = await loadVault(lockedRepresentationString, settings, this.verbose);
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
26
26
|
if (this.requireTwoFaLib) {
|
|
27
|
-
throw new Error('
|
|
27
|
+
throw new Error('No vault loaded, was it created?');
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
const json = await this.exec();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { EntryMeta, EntryMetaWithToken } from 'favalib';
|
|
2
|
+
import BaseCommand from './BaseCommand.mjs';
|
|
3
|
+
import formatters from './formatters/index.mjs';
|
|
4
|
+
import { JsonArray } from 'type-fest';
|
|
5
|
+
export type Formatter = (entries: (EntryMeta | EntryMetaWithToken)[]) => JsonArray;
|
|
6
|
+
declare abstract class BaseListOutputCommand extends BaseCommand {
|
|
7
|
+
abstract getList(): EntryMeta[] | EntryMetaWithToken[];
|
|
8
|
+
format: (typeof formatters)[0]["name"] | undefined;
|
|
9
|
+
exec(): Promise<JsonArray>;
|
|
10
|
+
}
|
|
11
|
+
export default BaseListOutputCommand;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Option } from 'clipanion';
|
|
2
|
+
import BaseCommand from './BaseCommand.mjs';
|
|
3
|
+
import generateEntriesTable from './utils/generateEntriesTable.mjs';
|
|
4
|
+
import formatters from './formatters/index.mjs';
|
|
5
|
+
const formattersMap = new Map(formatters.map((f) => [f.name, f.formatter]));
|
|
6
|
+
class BaseListOutputCommand extends BaseCommand {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
10
|
+
this.format = Option.String('--format', {
|
|
11
|
+
description: 'formatter',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
15
|
+
async exec() {
|
|
16
|
+
let formatter = (json) => json;
|
|
17
|
+
if (this.format) {
|
|
18
|
+
const selectedFormatter = formattersMap.get(this.format);
|
|
19
|
+
if (!selectedFormatter) {
|
|
20
|
+
throw new Error(`Formatter ${this.format} not found`);
|
|
21
|
+
}
|
|
22
|
+
formatter = selectedFormatter;
|
|
23
|
+
}
|
|
24
|
+
const list = this.getList();
|
|
25
|
+
if (list.length === 0) {
|
|
26
|
+
this.context.stdout.write('No entries\n');
|
|
27
|
+
return formatter([]);
|
|
28
|
+
}
|
|
29
|
+
this.output(generateEntriesTable(list));
|
|
30
|
+
return formatter(list);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export default BaseListOutputCommand;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
declare class EntriesListCommand extends
|
|
1
|
+
import BaseListOutputCommand from '../../BaseListOutputCommand.mjs';
|
|
2
|
+
declare class EntriesListCommand extends BaseListOutputCommand {
|
|
3
3
|
static paths: string[][];
|
|
4
4
|
static usage: import("clipanion").Usage;
|
|
5
5
|
requireTwoFaLib: boolean;
|
|
6
6
|
withTokens: boolean | undefined;
|
|
7
|
-
|
|
7
|
+
getList(): import("favalib").EntryMeta[];
|
|
8
8
|
}
|
|
9
9
|
export default EntriesListCommand;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Option } from 'clipanion';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
class EntriesListCommand extends BaseCommand {
|
|
2
|
+
import BaseListOutputCommand from '../../BaseListOutputCommand.mjs';
|
|
3
|
+
class EntriesListCommand extends BaseListOutputCommand {
|
|
5
4
|
constructor() {
|
|
6
5
|
super(...arguments);
|
|
7
6
|
this.requireTwoFaLib = true;
|
|
@@ -10,12 +9,12 @@ class EntriesListCommand extends BaseCommand {
|
|
|
10
9
|
});
|
|
11
10
|
}
|
|
12
11
|
static { this.paths = [['entries', 'list']]; }
|
|
13
|
-
static { this.usage =
|
|
12
|
+
static { this.usage = BaseListOutputCommand.Usage({
|
|
14
13
|
category: 'Entries',
|
|
15
14
|
description: 'List all stored 2FA entries',
|
|
16
15
|
details: `
|
|
17
16
|
This command displays a table of all stored two-factor authentication entries.
|
|
18
|
-
|
|
17
|
+
|
|
19
18
|
When used with --withTokens, it will also show the current TOTP codes for each entry.
|
|
20
19
|
`,
|
|
21
20
|
examples: [
|
|
@@ -23,7 +22,7 @@ class EntriesListCommand extends BaseCommand {
|
|
|
23
22
|
['List entries with current TOTP tokens', 'entries list --withTokens'],
|
|
24
23
|
],
|
|
25
24
|
}); }
|
|
26
|
-
|
|
25
|
+
getList() {
|
|
27
26
|
let entries;
|
|
28
27
|
if (this.withTokens) {
|
|
29
28
|
entries = this.twoFaLib.vault.listEntriesMetas(true);
|
|
@@ -31,12 +30,7 @@ class EntriesListCommand extends BaseCommand {
|
|
|
31
30
|
else {
|
|
32
31
|
entries = this.twoFaLib.vault.listEntriesMetas(false);
|
|
33
32
|
}
|
|
34
|
-
|
|
35
|
-
this.context.stdout.write('No entries\n');
|
|
36
|
-
return 0;
|
|
37
|
-
}
|
|
38
|
-
this.output(generateEntriesTable(entries));
|
|
39
|
-
return Promise.resolve(entries);
|
|
33
|
+
return entries;
|
|
40
34
|
}
|
|
41
35
|
}
|
|
42
36
|
export default EntriesListCommand;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
declare class EntriesSearchCommand extends BaseCommand {
|
|
1
|
+
import BaseListOutputCommand from '../../BaseListOutputCommand.mjs';
|
|
2
|
+
declare class EntriesSearchCommand extends BaseListOutputCommand {
|
|
4
3
|
static paths: string[][];
|
|
5
4
|
static usage: import("clipanion").Usage;
|
|
6
5
|
requireTwoFaLib: boolean;
|
|
7
6
|
withTokens: boolean | undefined;
|
|
8
7
|
query: string;
|
|
9
|
-
|
|
8
|
+
getList(): import("favalib").EntryMeta[];
|
|
10
9
|
}
|
|
11
10
|
export default EntriesSearchCommand;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Option } from 'clipanion';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
class EntriesSearchCommand extends BaseCommand {
|
|
2
|
+
import BaseListOutputCommand from '../../BaseListOutputCommand.mjs';
|
|
3
|
+
class EntriesSearchCommand extends BaseListOutputCommand {
|
|
5
4
|
constructor() {
|
|
6
5
|
super(...arguments);
|
|
7
6
|
this.requireTwoFaLib = true;
|
|
@@ -11,12 +10,12 @@ class EntriesSearchCommand extends BaseCommand {
|
|
|
11
10
|
this.query = Option.String({ required: true });
|
|
12
11
|
}
|
|
13
12
|
static { this.paths = [['entries', 'search']]; }
|
|
14
|
-
static { this.usage =
|
|
13
|
+
static { this.usage = BaseListOutputCommand.Usage({
|
|
15
14
|
category: 'Entries',
|
|
16
15
|
description: 'Search for stored 2FA entries',
|
|
17
16
|
details: `
|
|
18
17
|
This command searches through all stored two-factor authentication entries.
|
|
19
|
-
|
|
18
|
+
|
|
20
19
|
The search query will match against entry names and issuers.
|
|
21
20
|
When used with --withTokens, it will also show the current TOTP codes for matching entries.
|
|
22
21
|
`,
|
|
@@ -28,7 +27,7 @@ class EntriesSearchCommand extends BaseCommand {
|
|
|
28
27
|
],
|
|
29
28
|
],
|
|
30
29
|
}); }
|
|
31
|
-
|
|
30
|
+
getList() {
|
|
32
31
|
let filteredEntries;
|
|
33
32
|
if (this.withTokens) {
|
|
34
33
|
filteredEntries = this.twoFaLib.vault.searchEntriesMetas(this.query, true);
|
|
@@ -36,13 +35,7 @@ class EntriesSearchCommand extends BaseCommand {
|
|
|
36
35
|
else {
|
|
37
36
|
filteredEntries = this.twoFaLib.vault.searchEntriesMetas(this.query, false);
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
this.output('No matching entries found.\n');
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
43
|
-
const out = generateEntriesTable(filteredEntries);
|
|
44
|
-
this.output(out);
|
|
45
|
-
return Promise.resolve(filteredEntries);
|
|
38
|
+
return filteredEntries;
|
|
46
39
|
}
|
|
47
40
|
}
|
|
48
41
|
export default EntriesSearchCommand;
|
|
@@ -17,7 +17,7 @@ class VaultCreateCommand extends BaseCommand {
|
|
|
17
17
|
description: 'Create a new encrypted vault',
|
|
18
18
|
details: `
|
|
19
19
|
This command creates a new encrypted vault file to store your 2FA entries.
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
You will be prompted to enter a passphrase that will be used to encrypt the vault.
|
|
22
22
|
The passphrase will be securely stored in your system's keychain.
|
|
23
23
|
`,
|
|
@@ -30,11 +30,11 @@ class VaultCreateCommand extends BaseCommand {
|
|
|
30
30
|
}));
|
|
31
31
|
const { twoFaLib } = await twoFaLibVaultCreationUtils.createNewTwoFaLibVault(passphrase);
|
|
32
32
|
twoFaLib.addEventListener(TwoFaLibEvent.Changed, (ev) => {
|
|
33
|
-
return fs.writeFile(
|
|
33
|
+
return fs.writeFile(this.settings.vaultLocation, ev.detail.newLockedRepresentationString);
|
|
34
34
|
});
|
|
35
35
|
await Promise.all([
|
|
36
36
|
twoFaLib.forceSave(),
|
|
37
|
-
keytar.setPassword('
|
|
37
|
+
keytar.setPassword('favacli', 'vault-passphrase', passphrase),
|
|
38
38
|
]);
|
|
39
39
|
return { success: true };
|
|
40
40
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
|
+
declare class VaultDeleteCommand extends BaseCommand {
|
|
3
|
+
static paths: string[][];
|
|
4
|
+
requireTwoFaLib: boolean;
|
|
5
|
+
static usage: import("clipanion").Usage;
|
|
6
|
+
exec(): Promise<{
|
|
7
|
+
success: boolean;
|
|
8
|
+
cancelled: boolean;
|
|
9
|
+
} | {
|
|
10
|
+
success: boolean;
|
|
11
|
+
cancelled?: undefined;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export default VaultDeleteCommand;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { confirm } from '@inquirer/prompts';
|
|
3
|
+
import BaseCommand from '../../BaseCommand.mjs';
|
|
4
|
+
class VaultDeleteCommand extends BaseCommand {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(...arguments);
|
|
7
|
+
this.requireTwoFaLib = false;
|
|
8
|
+
}
|
|
9
|
+
static { this.paths = [['vault', 'delete']]; }
|
|
10
|
+
static { this.usage = BaseCommand.Usage({
|
|
11
|
+
category: 'Vault',
|
|
12
|
+
description: 'Delete the current vault',
|
|
13
|
+
details: `
|
|
14
|
+
This command deletes the current vault file.
|
|
15
|
+
You will be asked to confirm before deletion.
|
|
16
|
+
`,
|
|
17
|
+
examples: [['Delete the current vault', 'vault delete']],
|
|
18
|
+
}); }
|
|
19
|
+
async exec() {
|
|
20
|
+
const shouldDelete = await confirm({
|
|
21
|
+
message: 'Are you sure you want to delete the vault? This action cannot be undone.',
|
|
22
|
+
default: false,
|
|
23
|
+
});
|
|
24
|
+
if (!shouldDelete) {
|
|
25
|
+
this.output('Vault deletion cancelled.\n');
|
|
26
|
+
return { success: false, cancelled: true };
|
|
27
|
+
}
|
|
28
|
+
await fs.rm(this.settings.vaultLocation);
|
|
29
|
+
this.output('Vault deleted successfully.\n');
|
|
30
|
+
return { success: true };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export default VaultDeleteCommand;
|
package/build/main.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Cli, Builtins } from 'clipanion';
|
|
3
3
|
import VaultCreateCommand from './commands/vault/create.mjs';
|
|
4
|
+
import VaultDeleteCommand from './commands/vault/delete.mjs';
|
|
4
5
|
import EntriesAddCommand from './commands/entries/add.mjs';
|
|
5
6
|
import EntriesListCommand from './commands/entries/list.mjs';
|
|
6
7
|
import EntriesSearchCommand from './commands/entries/search.mjs';
|
|
@@ -11,21 +12,18 @@ const nodeRuntimeMajorVersion = parseInt(process.version.split('.')[0]);
|
|
|
11
12
|
if (nodeRuntimeMajorVersion < 20) {
|
|
12
13
|
throw new Error('Node.js version must be 20 or higher');
|
|
13
14
|
}
|
|
14
|
-
const [...args] = process.argv;
|
|
15
|
+
const [, , ...args] = process.argv;
|
|
15
16
|
const cli = new Cli({
|
|
16
17
|
binaryLabel: 'FavaCli',
|
|
17
18
|
binaryName: `favacli`,
|
|
18
|
-
binaryVersion: '0.0.
|
|
19
|
+
binaryVersion: '0.0.10',
|
|
19
20
|
});
|
|
20
21
|
cli.register(VaultCreateCommand);
|
|
22
|
+
cli.register(VaultDeleteCommand);
|
|
21
23
|
cli.register(EntriesAddCommand);
|
|
22
24
|
cli.register(EntriesListCommand);
|
|
23
25
|
cli.register(EntriesSearchCommand);
|
|
24
26
|
cli.register(SyncSetServerUrlCommand);
|
|
25
27
|
cli.register(SyncConnect);
|
|
26
28
|
cli.register(Builtins.HelpCommand);
|
|
27
|
-
if (args[0].endsWith('node')) {
|
|
28
|
-
args.splice(0, 1);
|
|
29
|
-
args.splice(0, 1);
|
|
30
|
-
}
|
|
31
29
|
void cli.runExit(args);
|
package/build/utils/init.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { LockedRepresentationString } from 'favalib';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
export interface Settings {
|
|
3
|
+
vaultLocation: string;
|
|
4
|
+
}
|
|
4
5
|
declare const init: () => Promise<{
|
|
5
|
-
settings:
|
|
6
|
+
settings: Settings;
|
|
6
7
|
lockedRepresentationString: LockedRepresentationString;
|
|
7
8
|
}>;
|
|
8
9
|
export default init;
|
package/build/utils/init.mjs
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
-
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import envPaths from 'env-paths';
|
|
4
|
+
const getDefaultSettings = () => ({
|
|
5
|
+
vaultLocation: path.join(envPaths('favacli').data, 'vault.json'),
|
|
6
|
+
});
|
|
3
7
|
const readFile = async (path) => {
|
|
4
8
|
try {
|
|
5
9
|
return (await fs.readFile(path)).toString();
|
|
@@ -18,13 +22,21 @@ const readJSONFile = async (path) => {
|
|
|
18
22
|
}
|
|
19
23
|
return JSON.parse(contents);
|
|
20
24
|
};
|
|
25
|
+
const ensureDirectoriesExists = async (...paths) => {
|
|
26
|
+
for (const p of paths) {
|
|
27
|
+
const directory = path.dirname(p);
|
|
28
|
+
await fs.mkdir(directory, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
};
|
|
21
31
|
const init = async () => {
|
|
22
|
-
|
|
32
|
+
const settingsLocation = path.join(envPaths('favacli').config, 'settings.json');
|
|
33
|
+
let settings = await readJSONFile(settingsLocation);
|
|
23
34
|
if (!settings) {
|
|
24
|
-
settings =
|
|
25
|
-
await
|
|
35
|
+
settings = getDefaultSettings();
|
|
36
|
+
await ensureDirectoriesExists(settingsLocation, settings.vaultLocation);
|
|
37
|
+
await fs.writeFile(settingsLocation, JSON.stringify(settings, null, 2));
|
|
26
38
|
}
|
|
27
|
-
const lockedRepresentationString = (await readFile(
|
|
39
|
+
const lockedRepresentationString = (await readFile(settings.vaultLocation));
|
|
28
40
|
return { settings, lockedRepresentationString };
|
|
29
41
|
};
|
|
30
42
|
export default init;
|
|
@@ -1,3 +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
4
|
export default loadVault;
|
|
@@ -4,11 +4,11 @@ import { getTwoFaLibVaultCreationUtils, TwoFaLibEvent, } from 'favalib';
|
|
|
4
4
|
import NodeCryptoProvider from 'favalib/cryptoProviders/node';
|
|
5
5
|
const cryptoLib = new NodeCryptoProvider();
|
|
6
6
|
const twoFaLibVaultCreationUtils = getTwoFaLibVaultCreationUtils(cryptoLib, 'cli', ['cli']);
|
|
7
|
-
const loadVault = async (vaultData, verbose = false) => {
|
|
8
|
-
const passphrase = (await keytar.getPassword('
|
|
7
|
+
const loadVault = async (vaultData, settings, verbose = false) => {
|
|
8
|
+
const passphrase = (await keytar.getPassword('favacli', 'vault-passphrase'));
|
|
9
9
|
const twoFaLib = await twoFaLibVaultCreationUtils.loadTwoFaLibFromLockedRepesentation(vaultData, passphrase);
|
|
10
10
|
twoFaLib.addEventListener(TwoFaLibEvent.Changed, (ev) => {
|
|
11
|
-
return fs.writeFile(
|
|
11
|
+
return fs.writeFile(settings.vaultLocation, ev.detail.newLockedRepresentationString);
|
|
12
12
|
});
|
|
13
13
|
twoFaLib.addEventListener(TwoFaLibEvent.Log, (ev) => {
|
|
14
14
|
if (ev.detail.severity !== 'info' || verbose) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "favacli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"@inquirer/prompts": "^7.0.1",
|
|
10
10
|
"bufferutil": "^4.0.8",
|
|
11
11
|
"clipanion": "^4.0.0-rc.4",
|
|
12
|
-
"
|
|
12
|
+
"env-paths": "^3.0.0",
|
|
13
|
+
"favalib": "^0.0.2",
|
|
13
14
|
"keytar": "^7.9.0",
|
|
14
15
|
"node-loader": "^2.0.0",
|
|
15
16
|
"ts-loader": "^9.5.1",
|