Skip to content

Commit 0e18344

Browse files
Add dcli status command (#378)
## Add `dcli status` command ### Problem Users who integrate `dcli` into scripts (e.g. password autofill in qutebrowser) currently have no lightweight way to check whether the CLI is logged in and whether the vault is unlocked. They resort to running commands like `dcli sync` or `dcli password` and parsing their output or exit codes, which results in redundant API calls and can trigger rate limits. ### Solution This MR adds a new top-level `dcli status` command that reports the current authentication and lock state of the CLI **without making any API calls**. It only reads from the local SQLite database and the OS keychain. #### Example output ``` ~> dcli status Logged in: yes Login: user@example.com Locked: no ``` If not logged in: ``` ~> dcli status Logged in: no ``` ### How it works 1. **Logged in** — Checks whether the local database file exists and contains a registered device configuration. 2. **Locked** — Determines lock state by checking: - Whether `shouldNotSaveMasterPassword` is set (always considered locked — user must type password each time). - Whether an encrypted master password is stored in the database. - Whether the local key can be retrieved from the OS keychain (`@napi-rs/keyring`). All checks are purely local — no network requests, no authentication prompts, no vault decryption. ### Files changed | File | Change | |---|---| | `src/command-handlers/status.ts` | **New** — `runStatus` handler and `isVaultLocked` helper | | `src/command-handlers/index.ts` | Added `status` module export | | `src/commands/index.ts` | Registered `dcli status` as a top-level command | ### Testing - Run `dcli status` when logged out → outputs `Logged in: no` - Run `dcli status` when logged in and unlocked → outputs `Logged in: yes`, `Login: <email>`, `Locked: no` - Run `dcli lock` then `dcli status` → outputs `Locked: yes` - Verify no network calls are made (e.g. via `--debug` flag or network monitoring) Closes: #315
2 parents c31dbef + b34af85 commit 0e18344

5 files changed

Lines changed: 74 additions & 2 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@dashlane/cli",
3-
"version": "6.2612.0",
3+
"version": "6.2614.0",
44
"description": "Manage your Dashlane vault through a CLI tool",
55
"type": "module",
66
"main": "dist/index.cjs",

src/cliVersion.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CliVersion } from './types.js';
22

3-
export const CLI_VERSION: CliVersion = { major: 6, minor: 2612, patch: 0 };
3+
export const CLI_VERSION: CliVersion = { major: 6, minor: 2614, patch: 0 };
44
export const breakingChangesVersions: CliVersion[] = [];
55

66
export const cliVersionToString = (version: CliVersion): string => {

src/command-handlers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export * from './read.js';
1111
export * from './publicAPI.js';
1212
export * from './secrets.js';
1313
export * from './secureNotes.js';
14+
export * from './status.js';
1415
export * from './sync.js';
1516
export * from './teamDarkWebInsightsReport.js';
1617
export * from './teamLogs.js';

src/command-handlers/status.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import Database from 'better-sqlite3';
2+
import { Entry } from '@napi-rs/keyring';
3+
import fs from 'fs';
4+
5+
import { connect, getDatabasePath } from '../modules/database/connect.js';
6+
import { DeviceConfiguration } from '../types.js';
7+
import { logger } from '../logger.js';
8+
9+
const SERVICE = 'dashlane-cli';
10+
11+
const isVaultLocked = (deviceConfiguration: DeviceConfiguration): boolean => {
12+
// If the master password should not be saved, the vault is always "locked"
13+
if (deviceConfiguration.shouldNotSaveMasterPassword) {
14+
return true;
15+
}
16+
17+
// If no encrypted master password is stored in the DB, the vault is locked
18+
if (!deviceConfiguration.masterPasswordEncrypted) {
19+
return true;
20+
}
21+
22+
// Try to retrieve the local key from the OS keychain
23+
try {
24+
const entry = new Entry(SERVICE, deviceConfiguration.login);
25+
const localKeyEncoded = entry.getPassword();
26+
if (localKeyEncoded) {
27+
return false;
28+
}
29+
} catch {
30+
logger.error('Unable to access the keychain to determine vault lock status');
31+
return true;
32+
}
33+
34+
return true;
35+
};
36+
37+
export const runStatus = (): void => {
38+
const dbPath = getDatabasePath();
39+
if (!fs.existsSync(dbPath)) {
40+
logger.content('Logged in: no');
41+
return;
42+
}
43+
44+
let db: Database.Database;
45+
try {
46+
db = connect();
47+
} catch {
48+
logger.error('Unable to access the database to determine login status');
49+
return;
50+
}
51+
52+
try {
53+
const deviceConfiguration = db.prepare('SELECT * FROM device LIMIT 1').get() as DeviceConfiguration | undefined;
54+
55+
if (!deviceConfiguration) {
56+
logger.content('Logged in: no');
57+
return;
58+
}
59+
60+
const locked = isVaultLocked(deviceConfiguration);
61+
62+
logger.content(`Logged in: yes`);
63+
logger.content(`Login: ${deviceConfiguration.login}`);
64+
logger.content(`Locked: ${locked ? 'yes' : 'no'}`);
65+
} finally {
66+
db.close();
67+
}
68+
};

src/commands/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
runExec,
1515
runBackup,
1616
runSecret,
17+
runStatus,
1718
} from '../command-handlers/index.js';
1819

1920
export const rootCommands = (params: { program: Command }) => {
@@ -25,6 +26,8 @@ export const rootCommands = (params: { program: Command }) => {
2526
.description('Manually synchronize the local vault with Dashlane')
2627
.action(runSync);
2728

29+
program.command('status').description('Get the status of the CLI (logged in, locked, etc.)').action(runStatus);
30+
2831
program
2932
.command('read')
3033
.description('Retrieve a secret from the local vault via its path (using <id> is much more efficient)')

0 commit comments

Comments
 (0)