favacli 0.0.23 → 0.0.26
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.d.mts +4 -1
- package/build/BaseCommand.mjs +28 -6
- package/build/BaseListOutputCommand.d.mts +1 -2
- package/build/BaseListOutputCommand.mjs +13 -12
- package/build/commands/entries/getTokenById.d.mts +9 -0
- package/build/commands/entries/getTokenById.mjs +39 -0
- package/build/formatters/index.d.mts +6 -2
- package/build/formatters/index.mjs +2 -1
- package/build/formatters/rofi.d.mts +7 -0
- package/build/formatters/rofi.mjs +17 -0
- package/build/main.mjs +3 -1
- package/package.json +2 -2
package/build/BaseCommand.d.mts
CHANGED
|
@@ -11,11 +11,14 @@ declare abstract class BaseCommand extends Command {
|
|
|
11
11
|
abstract requireFavaLib: boolean;
|
|
12
12
|
errors: ErrorInCommand[];
|
|
13
13
|
preFormattedOutput: boolean;
|
|
14
|
-
|
|
14
|
+
rawOutput: boolean;
|
|
15
|
+
format: string | undefined;
|
|
15
16
|
verbose: boolean | undefined;
|
|
16
17
|
lockedRepresentationString: LockedRepresentationString;
|
|
17
18
|
settings: Settings;
|
|
18
19
|
favaLib: FavaLib;
|
|
20
|
+
get machineOutput(): boolean;
|
|
21
|
+
protected validFormats(): string[];
|
|
19
22
|
output(string: string): void;
|
|
20
23
|
execute(): Promise<number>;
|
|
21
24
|
private addError;
|
package/build/BaseCommand.mjs
CHANGED
|
@@ -6,19 +6,35 @@ class BaseCommand extends Command {
|
|
|
6
6
|
super(...arguments);
|
|
7
7
|
this.errors = [];
|
|
8
8
|
this.preFormattedOutput = false;
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// when true, exec()'s result is a raw string to write to stdout verbatim
|
|
10
|
+
// (no JSON.stringify, no escaping/trimming) — e.g. rofi script-mode output
|
|
11
|
+
this.rawOutput = false;
|
|
12
|
+
this.format = Option.String('--format', {
|
|
13
|
+
description: 'output format (e.g. json)',
|
|
11
14
|
});
|
|
12
15
|
this.verbose = Option.Boolean('--verbose', {
|
|
13
16
|
description: 'verbose output',
|
|
14
17
|
});
|
|
15
18
|
}
|
|
19
|
+
// machine-readable mode: any --format suppresses human output and triggers
|
|
20
|
+
// serialized output
|
|
21
|
+
get machineOutput() {
|
|
22
|
+
return this.format !== undefined;
|
|
23
|
+
}
|
|
24
|
+
// the set of --format values this command accepts; subclasses extend it
|
|
25
|
+
validFormats() {
|
|
26
|
+
return ['json'];
|
|
27
|
+
}
|
|
16
28
|
output(string) {
|
|
17
|
-
if (!this.
|
|
29
|
+
if (!this.machineOutput) {
|
|
18
30
|
this.context.stdout.write(string);
|
|
19
31
|
}
|
|
20
32
|
}
|
|
21
33
|
async execute() {
|
|
34
|
+
if (this.format !== undefined &&
|
|
35
|
+
!this.validFormats().includes(this.format)) {
|
|
36
|
+
throw new Error(`Unknown format: ${this.format}. Valid formats: ${this.validFormats().join(', ')}`);
|
|
37
|
+
}
|
|
22
38
|
const { lockedRepresentationString, settings } = await init();
|
|
23
39
|
this.settings = settings;
|
|
24
40
|
this.lockedRepresentationString = lockedRepresentationString;
|
|
@@ -34,10 +50,16 @@ class BaseCommand extends Command {
|
|
|
34
50
|
if (this.favaLib?.sync) {
|
|
35
51
|
this.favaLib.sync.closeServerConnection();
|
|
36
52
|
}
|
|
37
|
-
if (this.
|
|
53
|
+
if (this.machineOutput) {
|
|
38
54
|
// output is already formatted, don't add the result & errors bit
|
|
39
55
|
if (this.preFormattedOutput) {
|
|
40
|
-
this.
|
|
56
|
+
if (this.rawOutput) {
|
|
57
|
+
// raw passthrough: write the formatter's string exactly as-is
|
|
58
|
+
this.context.stdout.write(result);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
this.context.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
62
|
+
}
|
|
41
63
|
}
|
|
42
64
|
else {
|
|
43
65
|
this.context.stdout.write(JSON.stringify({ result, errors: this.errors }, null, 2) + '\n');
|
|
@@ -46,7 +68,7 @@ class BaseCommand extends Command {
|
|
|
46
68
|
return 0;
|
|
47
69
|
}
|
|
48
70
|
addError(err) {
|
|
49
|
-
if (this.
|
|
71
|
+
if (this.machineOutput && !this.verbose) {
|
|
50
72
|
this.errors.push({ timestamp: Date.now(), message: err.message });
|
|
51
73
|
}
|
|
52
74
|
else {
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { EntryMeta, EntryMetaWithToken } from 'favalib';
|
|
2
2
|
import BaseCommand from './BaseCommand.mjs';
|
|
3
3
|
import type { ErrorInCommand } from './BaseCommand.mjs';
|
|
4
|
-
import formatters from './formatters/index.mjs';
|
|
5
4
|
import { Jsonifiable } from 'type-fest';
|
|
6
5
|
export type Formatter = (entries: (EntryMeta | EntryMetaWithToken)[], errors: ErrorInCommand[]) => Jsonifiable;
|
|
7
6
|
declare abstract class BaseListOutputCommand extends BaseCommand {
|
|
8
7
|
abstract getList(): Promise<(EntryMeta | EntryMetaWithToken)[]>;
|
|
9
|
-
|
|
8
|
+
protected validFormats(): string[];
|
|
10
9
|
exec(): Promise<string | number | boolean | {
|
|
11
10
|
[x: string]: Jsonifiable | undefined;
|
|
12
11
|
} | {
|
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import { Option } from 'clipanion';
|
|
2
1
|
import BaseCommand from './BaseCommand.mjs';
|
|
3
2
|
import generateEntriesTable from './utils/generateEntriesTable.mjs';
|
|
4
3
|
import formatters from './formatters/index.mjs';
|
|
5
|
-
const formattersMap = new Map(formatters.map((f) => [f.name, f
|
|
4
|
+
const formattersMap = new Map(formatters.map((f) => [f.name, f]));
|
|
6
5
|
class BaseListOutputCommand extends BaseCommand {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
10
|
-
this.format = Option.String('--format', {
|
|
11
|
-
description: 'formatter',
|
|
12
|
-
});
|
|
6
|
+
validFormats() {
|
|
7
|
+
return ['json', ...formatters.map((f) => f.name)];
|
|
13
8
|
}
|
|
14
9
|
async exec() {
|
|
15
|
-
this.preFormattedOutput = this.format !== undefined;
|
|
16
10
|
let formatter = (json) => json;
|
|
17
|
-
|
|
11
|
+
// 'json' (and no --format) uses the default identity formatter and is
|
|
12
|
+
// wrapped in { result, errors } by BaseCommand; named formatters produce
|
|
13
|
+
// pre-formatted output instead
|
|
14
|
+
if (this.format && this.format !== 'json') {
|
|
18
15
|
const selectedFormatter = formattersMap.get(this.format);
|
|
19
16
|
if (!selectedFormatter) {
|
|
20
17
|
throw new Error(`Formatter ${this.format} not found`);
|
|
21
18
|
}
|
|
22
|
-
formatter = selectedFormatter;
|
|
19
|
+
formatter = selectedFormatter.formatter;
|
|
20
|
+
this.preFormattedOutput = true;
|
|
21
|
+
this.rawOutput = 'raw' in selectedFormatter && selectedFormatter.raw;
|
|
23
22
|
}
|
|
24
23
|
const list = await this.getList();
|
|
25
24
|
if (list.length === 0) {
|
|
26
|
-
this
|
|
25
|
+
// output() suppresses this when --format is set, so it can't corrupt the
|
|
26
|
+
// machine-readable formatter output
|
|
27
|
+
this.output('No entries\n');
|
|
27
28
|
return formatter([], this.errors);
|
|
28
29
|
}
|
|
29
30
|
this.output(generateEntriesTable(list));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import BaseCommand from '../../BaseCommand.mjs';
|
|
2
|
+
declare class EntriesGetTokenByIdCommand extends BaseCommand {
|
|
3
|
+
static paths: string[][];
|
|
4
|
+
static usage: import("clipanion").Usage;
|
|
5
|
+
requireFavaLib: boolean;
|
|
6
|
+
id: string;
|
|
7
|
+
exec(): Promise<string | null>;
|
|
8
|
+
}
|
|
9
|
+
export default EntriesGetTokenByIdCommand;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Option } from 'clipanion';
|
|
2
|
+
import { EntryNotFoundError } from 'favalib';
|
|
3
|
+
import BaseCommand from '../../BaseCommand.mjs';
|
|
4
|
+
class EntriesGetTokenByIdCommand extends BaseCommand {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(...arguments);
|
|
7
|
+
this.requireFavaLib = true;
|
|
8
|
+
this.id = Option.String({ required: true });
|
|
9
|
+
}
|
|
10
|
+
static { this.paths = [['entries', 'get-token-by-id']]; }
|
|
11
|
+
static { this.usage = BaseCommand.Usage({
|
|
12
|
+
category: 'Entries',
|
|
13
|
+
description: 'Get the current TOTP token for an entry by its id',
|
|
14
|
+
details: `
|
|
15
|
+
This command generates and prints the current TOTP code for a single
|
|
16
|
+
entry, identified by its id.
|
|
17
|
+
`,
|
|
18
|
+
examples: [
|
|
19
|
+
['Get the current token for an entry', 'entries get-token-by-id <id>'],
|
|
20
|
+
],
|
|
21
|
+
}); }
|
|
22
|
+
async exec() {
|
|
23
|
+
const id = this.id;
|
|
24
|
+
try {
|
|
25
|
+
const token = await this.favaLib.vault.generateTokenForEntry(id);
|
|
26
|
+
this.output(`${token.otp}\n`);
|
|
27
|
+
return token.otp;
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
if (err instanceof EntryNotFoundError) {
|
|
31
|
+
this.output(`No entry found with id ${this.id}\n`);
|
|
32
|
+
this.errors.push({ timestamp: Date.now(), message: err.message });
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export default EntriesGetTokenByIdCommand;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
declare const formatters: {
|
|
1
|
+
declare const formatters: ({
|
|
2
2
|
name: "alfred";
|
|
3
3
|
formatter: import("../BaseListOutputCommand.mjs").Formatter;
|
|
4
|
-
}
|
|
4
|
+
} | {
|
|
5
|
+
name: "rofi";
|
|
6
|
+
formatter: import("../BaseListOutputCommand.mjs").Formatter;
|
|
7
|
+
raw: boolean;
|
|
8
|
+
})[];
|
|
5
9
|
export default formatters;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const NUL = '\0'; // separates display text from the row's options
|
|
2
|
+
const US = '\x1f'; // separates option key/value pairs
|
|
3
|
+
const rofiFormatter = (entries, errors) => {
|
|
4
|
+
const lines = [`${NUL}prompt${US}2FA`];
|
|
5
|
+
if (errors.length > 0) {
|
|
6
|
+
const msg = errors.map((e, i) => `[${i}] ${e.message}`).join(' ');
|
|
7
|
+
lines.push(`${NUL}message${US}${msg}`);
|
|
8
|
+
}
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const display = `${entry.issuer}: ${entry.name}`;
|
|
11
|
+
lines.push(`${display}${NUL}info${US}${entry.id}${US}meta${US}${entry.issuer} ${entry.name}`);
|
|
12
|
+
}
|
|
13
|
+
return lines.join('\n');
|
|
14
|
+
};
|
|
15
|
+
// raw: rofi script mode consumes line-based text with embedded control bytes,
|
|
16
|
+
// so this output must be written to stdout verbatim, not JSON-stringified.
|
|
17
|
+
export default { name: 'rofi', formatter: rofiFormatter, raw: true };
|
package/build/main.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import VaultRestorePasswordCommand from './commands/vault/restorePassword.mjs';
|
|
|
6
6
|
import EntriesAddCommand from './commands/entries/add.mjs';
|
|
7
7
|
import EntriesListCommand from './commands/entries/list.mjs';
|
|
8
8
|
import EntriesSearchCommand from './commands/entries/search.mjs';
|
|
9
|
+
import EntriesGetTokenByIdCommand from './commands/entries/getTokenById.mjs';
|
|
9
10
|
import SyncSetServerUrlCommand from './commands/sync/setServerUrl.mjs';
|
|
10
11
|
import SyncConnect from './commands/sync/connect.mjs';
|
|
11
12
|
import SyncResilver from './commands/sync/resilver.mjs';
|
|
@@ -23,7 +24,7 @@ const [, , ...args] = process.argv;
|
|
|
23
24
|
const cli = new Cli({
|
|
24
25
|
binaryLabel: 'FavaCli',
|
|
25
26
|
binaryName: `favacli`,
|
|
26
|
-
binaryVersion: '0.0.
|
|
27
|
+
binaryVersion: '0.0.26',
|
|
27
28
|
});
|
|
28
29
|
cli.register(VaultCreateCommand);
|
|
29
30
|
cli.register(VaultDeleteCommand);
|
|
@@ -31,6 +32,7 @@ cli.register(VaultRestorePasswordCommand);
|
|
|
31
32
|
cli.register(EntriesAddCommand);
|
|
32
33
|
cli.register(EntriesListCommand);
|
|
33
34
|
cli.register(EntriesSearchCommand);
|
|
35
|
+
cli.register(EntriesGetTokenByIdCommand);
|
|
34
36
|
cli.register(SyncSetServerUrlCommand);
|
|
35
37
|
cli.register(SyncConnect);
|
|
36
38
|
cli.register(SyncResilver);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "favacli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"keytar": "^7.9.0",
|
|
14
14
|
"tty-table": "^5.0.0",
|
|
15
15
|
"typanion": "^3.14.0",
|
|
16
|
-
"favalib": "0.0.
|
|
16
|
+
"favalib": "0.0.18"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"type-fest": "^5.6.0"
|