bwx 0.1.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.
package/src/cli/io.ts ADDED
@@ -0,0 +1,72 @@
1
+ import pc from "picocolors";
2
+ import { CliError, ExitCode } from "./errors.ts";
3
+ import type { GlobalOptions } from "./types.ts";
4
+
5
+ export function emitData(value: unknown, opts: GlobalOptions): void {
6
+ if (opts.json) {
7
+ process.stdout.write(JSON.stringify({ data: value }, null, 2) + "\n");
8
+ } else if (opts.plain) {
9
+ if (typeof value === "string") {
10
+ process.stdout.write(value + "\n");
11
+ } else if (Array.isArray(value)) {
12
+ for (const item of value) {
13
+ process.stdout.write(String(item) + "\n");
14
+ }
15
+ } else {
16
+ process.stdout.write(JSON.stringify(value) + "\n");
17
+ }
18
+ } else {
19
+ if (typeof value === "string") {
20
+ process.stdout.write(value + "\n");
21
+ } else {
22
+ process.stdout.write(JSON.stringify(value, null, 2) + "\n");
23
+ }
24
+ }
25
+ }
26
+
27
+ export function emitLog(message: string, opts: GlobalOptions): void {
28
+ if (opts.quiet || opts.json) return;
29
+ process.stderr.write(message + "\n");
30
+ }
31
+
32
+ export function emitSuccess(message: string, opts: GlobalOptions): void {
33
+ if (opts.quiet || opts.json) return;
34
+ process.stderr.write(pc.green("✓") + " " + message + "\n");
35
+ }
36
+
37
+ export function emitWarn(message: string, opts: GlobalOptions): void {
38
+ if (opts.quiet || opts.json) return;
39
+ process.stderr.write(pc.yellow("⚠") + " " + message + "\n");
40
+ }
41
+
42
+ export function handleFatalError(err: unknown, opts: GlobalOptions): never {
43
+ const cliErr =
44
+ err instanceof CliError
45
+ ? err
46
+ : new CliError(
47
+ err instanceof Error ? err.message : String(err),
48
+ ExitCode.Unknown,
49
+ );
50
+
51
+ if (opts.json) {
52
+ process.stdout.write(
53
+ JSON.stringify(
54
+ {
55
+ error: {
56
+ message: cliErr.message,
57
+ exitCode: cliErr.exitCode,
58
+ },
59
+ },
60
+ null,
61
+ 2,
62
+ ) + "\n",
63
+ );
64
+ } else {
65
+ process.stderr.write(pc.red("error:") + " " + cliErr.message + "\n");
66
+ if (opts.verbose && cliErr.stack) {
67
+ process.stderr.write(pc.dim(cliErr.stack) + "\n");
68
+ }
69
+ }
70
+
71
+ process.exit(cliErr.exitCode);
72
+ }
@@ -0,0 +1,55 @@
1
+ import { Command } from "commander";
2
+ import type { GlobalOptions } from "./types.ts";
3
+ import { registerGet } from "./commands/get.ts";
4
+ import { registerField } from "./commands/field.ts";
5
+ import { registerSearch } from "./commands/search.ts";
6
+ import { registerCreate } from "./commands/create.ts";
7
+ import { registerEdit } from "./commands/edit.ts";
8
+ import { registerDelete } from "./commands/delete.ts";
9
+ import { registerStatus } from "./commands/status.ts";
10
+ import { registerSync } from "./commands/sync.ts";
11
+ import { registerUnlock } from "./commands/unlock.ts";
12
+ import { registerLock } from "./commands/lock.ts";
13
+ import { registerConfig } from "./commands/config.ts";
14
+
15
+ export function buildProgram(): Command {
16
+ const program = new Command();
17
+
18
+ program
19
+ .name("bwx")
20
+ .description("Bitwarden Extended CLI")
21
+ .version("0.1.0")
22
+ .option("--json", "JSON output")
23
+ .option("--plain", "Plain parseable output")
24
+ .option("-q, --quiet", "Suppress log output")
25
+ .option("-v, --verbose", "Verbose output")
26
+ .exitOverride()
27
+ .configureOutput({
28
+ writeOut: (str) => process.stdout.write(str),
29
+ writeErr: () => {}, // silenced — we handle all errors in main()
30
+ });
31
+
32
+ registerStatus(program);
33
+ registerUnlock(program);
34
+ registerLock(program);
35
+ registerGet(program);
36
+ registerField(program);
37
+ registerSearch(program);
38
+ registerSync(program);
39
+ registerCreate(program);
40
+ registerEdit(program);
41
+ registerDelete(program);
42
+ registerConfig(program);
43
+
44
+ return program;
45
+ }
46
+
47
+ export function getGlobalOpts(cmd: Command): GlobalOptions {
48
+ const root = cmd.optsWithGlobals();
49
+ return {
50
+ json: root.json ?? false,
51
+ plain: root.plain ?? false,
52
+ quiet: root.quiet ?? false,
53
+ verbose: root.verbose ?? false,
54
+ };
55
+ }
@@ -0,0 +1,10 @@
1
+ export interface GlobalOptions {
2
+ json?: boolean;
3
+ plain?: boolean;
4
+ quiet?: boolean;
5
+ verbose?: boolean;
6
+ }
7
+
8
+ export interface CliContext {
9
+ opts: GlobalOptions;
10
+ }
@@ -0,0 +1,7 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const ConfigSchema = z.object({
4
+ email: z.string().optional(),
5
+ });
6
+
7
+ export type Config = z.infer<typeof ConfigSchema>;
@@ -0,0 +1,21 @@
1
+ import { ConfigSchema, type Config } from "./schema.ts";
2
+
3
+ const CONFIG_DIR =
4
+ process.env.XDG_CONFIG_HOME ?? `${process.env.HOME}/.config`;
5
+ const CONFIG_PATH = `${CONFIG_DIR}/bwx/config.json`;
6
+
7
+ export async function loadConfig(): Promise<Config> {
8
+ try {
9
+ const text = await Bun.file(CONFIG_PATH).text();
10
+ return ConfigSchema.parse(JSON.parse(text));
11
+ } catch {
12
+ return ConfigSchema.parse({});
13
+ }
14
+ }
15
+
16
+ export async function saveConfig(config: Config): Promise<void> {
17
+ const dir = CONFIG_PATH.replace(/\/[^/]+$/, "");
18
+ const { mkdirSync } = await import("node:fs");
19
+ mkdirSync(dir, { recursive: true });
20
+ await Bun.write(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
21
+ }
package/src/index.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { CommanderError } from "commander";
2
+ import { buildProgram } from "./cli/program.ts";
3
+ import { handleFatalError } from "./cli/io.ts";
4
+ import { CliError, ExitCode } from "./cli/errors.ts";
5
+ import type { GlobalOptions } from "./cli/types.ts";
6
+
7
+ export async function main(argv: string[]): Promise<void> {
8
+ const program = buildProgram();
9
+
10
+ // Parse global opts early for error formatting
11
+ let opts: GlobalOptions = {};
12
+ try {
13
+ program.parseOptions(argv.slice(2));
14
+ const rawOpts = program.opts();
15
+ opts = {
16
+ json: rawOpts.json ?? false,
17
+ plain: rawOpts.plain ?? false,
18
+ quiet: rawOpts.quiet ?? false,
19
+ verbose: rawOpts.verbose ?? false,
20
+ };
21
+ } catch {
22
+ // Fall through — full parse below will handle
23
+ }
24
+
25
+ try {
26
+ await program.parseAsync(argv);
27
+ } catch (err) {
28
+ if (err instanceof CommanderError) {
29
+ if (err.exitCode === 0) process.exit(0); // --help, --version
30
+ // Commander prefixes messages with "error: " — strip it
31
+ const msg = err.message.replace(/^error:\s*/i, "");
32
+ handleFatalError(new CliError(msg, ExitCode.BadArgs), opts);
33
+ }
34
+ handleFatalError(err, opts);
35
+ }
36
+ }