passwd-sso-cli 0.4.3

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.
Files changed (63) hide show
  1. package/dist/commands/agent-decrypt.d.ts +11 -0
  2. package/dist/commands/agent-decrypt.js +317 -0
  3. package/dist/commands/agent.d.ts +13 -0
  4. package/dist/commands/agent.js +116 -0
  5. package/dist/commands/api-key.d.ts +22 -0
  6. package/dist/commands/api-key.js +118 -0
  7. package/dist/commands/decrypt.d.ts +17 -0
  8. package/dist/commands/decrypt.js +108 -0
  9. package/dist/commands/env.d.ts +15 -0
  10. package/dist/commands/env.js +102 -0
  11. package/dist/commands/export.d.ts +7 -0
  12. package/dist/commands/export.js +99 -0
  13. package/dist/commands/generate.d.ts +11 -0
  14. package/dist/commands/generate.js +45 -0
  15. package/dist/commands/get.d.ts +8 -0
  16. package/dist/commands/get.js +73 -0
  17. package/dist/commands/list.d.ts +6 -0
  18. package/dist/commands/list.js +66 -0
  19. package/dist/commands/login.d.ts +4 -0
  20. package/dist/commands/login.js +45 -0
  21. package/dist/commands/run.d.ts +12 -0
  22. package/dist/commands/run.js +97 -0
  23. package/dist/commands/status.d.ts +6 -0
  24. package/dist/commands/status.js +62 -0
  25. package/dist/commands/totp.d.ts +7 -0
  26. package/dist/commands/totp.js +57 -0
  27. package/dist/commands/unlock.d.ts +19 -0
  28. package/dist/commands/unlock.js +125 -0
  29. package/dist/index.d.ts +8 -0
  30. package/dist/index.js +298 -0
  31. package/dist/lib/api-client.d.ts +22 -0
  32. package/dist/lib/api-client.js +145 -0
  33. package/dist/lib/blocked-keys.d.ts +2 -0
  34. package/dist/lib/blocked-keys.js +23 -0
  35. package/dist/lib/clipboard.d.ts +8 -0
  36. package/dist/lib/clipboard.js +79 -0
  37. package/dist/lib/config.d.ts +18 -0
  38. package/dist/lib/config.js +110 -0
  39. package/dist/lib/crypto-aad.d.ts +5 -0
  40. package/dist/lib/crypto-aad.js +44 -0
  41. package/dist/lib/crypto.d.ts +23 -0
  42. package/dist/lib/crypto.js +148 -0
  43. package/dist/lib/migrate.d.ts +8 -0
  44. package/dist/lib/migrate.js +87 -0
  45. package/dist/lib/openssh-key-parser.d.ts +17 -0
  46. package/dist/lib/openssh-key-parser.js +273 -0
  47. package/dist/lib/output.d.ts +10 -0
  48. package/dist/lib/output.js +36 -0
  49. package/dist/lib/paths.d.ts +17 -0
  50. package/dist/lib/paths.js +39 -0
  51. package/dist/lib/secrets-config.d.ts +31 -0
  52. package/dist/lib/secrets-config.js +48 -0
  53. package/dist/lib/ssh-agent-protocol.d.ts +56 -0
  54. package/dist/lib/ssh-agent-protocol.js +108 -0
  55. package/dist/lib/ssh-agent-socket.d.ts +20 -0
  56. package/dist/lib/ssh-agent-socket.js +187 -0
  57. package/dist/lib/ssh-key-agent.d.ts +54 -0
  58. package/dist/lib/ssh-key-agent.js +197 -0
  59. package/dist/lib/totp.d.ts +10 -0
  60. package/dist/lib/totp.js +31 -0
  61. package/dist/lib/vault-state.d.ts +15 -0
  62. package/dist/lib/vault-state.js +37 -0
  63. package/package.json +56 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * `passwd-sso decrypt <id>` — Thin client that connects to the decrypt agent socket.
3
+ * Sends a decrypt request and prints the result to stdout.
4
+ * Used by Claude Code hooks to decrypt credentials without exposing them to the LLM.
5
+ *
6
+ * Usage:
7
+ * passwd-sso decrypt <entryId> --field password --mcp-client <mcpc_xxx>
8
+ * passwd-sso decrypt <entryId> --json --mcp-client <mcpc_xxx>
9
+ * passwd-sso decrypt abc123 --field password --mcp-client mcpc_xxx | curl --config - ...
10
+ *
11
+ * --json outputs all decrypted fields as JSON (ignores --field).
12
+ */
13
+ import { createConnection } from "node:net";
14
+ export async function decryptCommand(id, options) {
15
+ const socketPath = process.env.PSSO_AGENT_SOCK;
16
+ if (!socketPath) {
17
+ process.stderr.write("Error: Agent not running. Start with:\n" +
18
+ " eval $(passwd-sso agent --decrypt --eval)\n");
19
+ process.exit(1);
20
+ }
21
+ if (options.json && options.field) {
22
+ process.stderr.write("Error: --json and --field are mutually exclusive\n");
23
+ process.exitCode = 1;
24
+ return;
25
+ }
26
+ const field = options.json ? "_json" : (options.field ?? "password");
27
+ const request = JSON.stringify({
28
+ entryId: id,
29
+ clientId: options.mcpClient,
30
+ field,
31
+ });
32
+ await new Promise((resolve, reject) => {
33
+ const socket = createConnection(socketPath);
34
+ let responseBuffer = "";
35
+ let settled = false;
36
+ const finish = (err) => {
37
+ if (settled)
38
+ return;
39
+ settled = true;
40
+ socket.destroy();
41
+ if (err)
42
+ reject(err);
43
+ else
44
+ resolve();
45
+ };
46
+ socket.on("connect", () => {
47
+ socket.write(request + "\n");
48
+ });
49
+ socket.on("data", (chunk) => {
50
+ responseBuffer += chunk.toString("utf-8");
51
+ const lines = responseBuffer.split("\n");
52
+ responseBuffer = lines.pop() ?? "";
53
+ for (const line of lines) {
54
+ const trimmed = line.trim();
55
+ if (!trimmed)
56
+ continue;
57
+ try {
58
+ const res = JSON.parse(trimmed);
59
+ if (res.ok && res.value !== undefined) {
60
+ // Write value + newline to stdout.
61
+ // When piped (e.g., to curl), the trailing newline is typically
62
+ // consumed by the receiving process. For interactive use, it
63
+ // ensures the value is visible before the next shell prompt.
64
+ process.stdout.write(res.value + "\n");
65
+ finish();
66
+ }
67
+ else {
68
+ process.stderr.write(`Error: ${res.error ?? "Decrypt failed"}\n`);
69
+ process.exitCode = 1;
70
+ finish();
71
+ }
72
+ }
73
+ catch (err) {
74
+ process.stderr.write(`Error: Invalid response from agent: ${err instanceof Error ? err.message : "unknown"}\n`);
75
+ process.exitCode = 1;
76
+ finish();
77
+ }
78
+ }
79
+ });
80
+ socket.on("end", () => {
81
+ finish();
82
+ });
83
+ socket.on("error", (err) => {
84
+ if (err.code === "ENOENT") {
85
+ process.stderr.write("Error: Agent socket not found. Start the agent with:\n" +
86
+ " eval $(passwd-sso agent --decrypt --eval)\n");
87
+ }
88
+ else if (err.code === "ECONNREFUSED") {
89
+ process.stderr.write("Error: Agent is not running. Start with:\n" +
90
+ " eval $(passwd-sso agent --decrypt --eval)\n");
91
+ }
92
+ else {
93
+ process.stderr.write(`Error: Socket error: ${err.message}\n`);
94
+ }
95
+ process.exitCode = 1;
96
+ finish(err);
97
+ });
98
+ socket.setTimeout(10_000);
99
+ socket.on("timeout", () => {
100
+ process.stderr.write("Error: Agent request timed out\n");
101
+ process.exitCode = 1;
102
+ finish(new Error("timeout"));
103
+ });
104
+ }).catch(() => {
105
+ // exitCode already set above
106
+ });
107
+ }
108
+ //# sourceMappingURL=decrypt.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `passwd-sso env` — Output vault secrets as environment variables.
3
+ *
4
+ * Reads .passwd-sso-env.json, fetches & decrypts entries, then outputs
5
+ * env vars in the requested format (shell, dotenv, json).
6
+ *
7
+ * Requires the vault to be unlocked (encryption key in memory) OR
8
+ * an apiKey + PSSO_PASSPHRASE env var for non-interactive mode.
9
+ */
10
+ interface EnvOptions {
11
+ config?: string;
12
+ format: string;
13
+ }
14
+ export declare function envCommand(opts: EnvOptions): Promise<void>;
15
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * `passwd-sso env` — Output vault secrets as environment variables.
3
+ *
4
+ * Reads .passwd-sso-env.json, fetches & decrypts entries, then outputs
5
+ * env vars in the requested format (shell, dotenv, json).
6
+ *
7
+ * Requires the vault to be unlocked (encryption key in memory) OR
8
+ * an apiKey + PSSO_PASSPHRASE env var for non-interactive mode.
9
+ */
10
+ import { loadSecretsConfig, getPasswordPath } from "../lib/secrets-config.js";
11
+ import { getEncryptionKey, getUserId } from "../lib/vault-state.js";
12
+ import { autoUnlockIfNeeded } from "./unlock.js";
13
+ import { getToken } from "../lib/api-client.js";
14
+ import { decryptData } from "../lib/crypto.js";
15
+ import { buildPersonalEntryAAD } from "../lib/crypto-aad.js";
16
+ import { BLOCKED_KEYS } from "../lib/blocked-keys.js";
17
+ import * as output from "../lib/output.js";
18
+ export async function envCommand(opts) {
19
+ let config;
20
+ try {
21
+ config = loadSecretsConfig(opts.config);
22
+ }
23
+ catch (err) {
24
+ output.error(err instanceof Error ? err.message : "Failed to load config.");
25
+ return;
26
+ }
27
+ const useV1 = !!config.apiKey;
28
+ const baseUrl = config.server.replace(/\/$/, "");
29
+ // Determine auth header
30
+ let authHeader;
31
+ if (config.apiKey) {
32
+ authHeader = `Bearer ${config.apiKey}`;
33
+ }
34
+ else {
35
+ const token = await getToken();
36
+ if (!token) {
37
+ output.error("Not logged in. Run `passwd-sso login` first, or set apiKey in config.");
38
+ return;
39
+ }
40
+ authHeader = `Bearer ${token}`;
41
+ }
42
+ // Auto-unlock with PSSO_PASSPHRASE if needed
43
+ if (!await autoUnlockIfNeeded()) {
44
+ output.error("Vault is not unlocked. Run `passwd-sso unlock` first, or set PSSO_PASSPHRASE.");
45
+ return;
46
+ }
47
+ const encryptionKey = getEncryptionKey();
48
+ const userId = getUserId();
49
+ const result = {};
50
+ const entries = Object.entries(config.secrets);
51
+ for (const [envName, mapping] of entries) {
52
+ if (BLOCKED_KEYS.has(envName.toUpperCase())) {
53
+ output.error(`Blocked: cannot output '${envName}' (security restriction)`);
54
+ process.exit(1);
55
+ }
56
+ const path = getPasswordPath(mapping.entry, useV1);
57
+ const url = `${baseUrl}${path}`;
58
+ const res = await fetch(url, {
59
+ headers: { Authorization: authHeader },
60
+ });
61
+ if (!res.ok) {
62
+ output.error(`Failed to fetch entry ${mapping.entry}: HTTP ${res.status}`);
63
+ process.exit(1);
64
+ }
65
+ const data = (await res.json());
66
+ let additionalData;
67
+ if (data.aadVersion && data.aadVersion >= 1 && userId) {
68
+ additionalData = buildPersonalEntryAAD(userId, data.id);
69
+ }
70
+ const decrypted = await decryptData(data.encryptedBlob, encryptionKey, additionalData);
71
+ const blob = JSON.parse(decrypted);
72
+ const value = blob[mapping.field];
73
+ if (value === undefined || value === null) {
74
+ output.warn(`Field '${mapping.field}' not found in entry ${mapping.entry}`);
75
+ continue;
76
+ }
77
+ result[envName] = String(value);
78
+ }
79
+ // Output
80
+ switch (opts.format) {
81
+ case "json":
82
+ console.log(JSON.stringify(result, null, 2));
83
+ break;
84
+ case "dotenv":
85
+ for (const [k, v] of Object.entries(result)) {
86
+ console.log(`${k}=${shellEscape(v)}`);
87
+ }
88
+ break;
89
+ case "shell":
90
+ default:
91
+ for (const [k, v] of Object.entries(result)) {
92
+ console.log(`export ${k}=${shellEscape(v)}`);
93
+ }
94
+ break;
95
+ }
96
+ }
97
+ function shellEscape(s) {
98
+ if (/^[a-zA-Z0-9._\-/:@]+$/.test(s))
99
+ return s;
100
+ return `'${s.replace(/'/g, "'\\''")}'`;
101
+ }
102
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `passwd-sso export` — Export vault entries to JSON or CSV.
3
+ */
4
+ export declare function exportCommand(options: {
5
+ format?: string;
6
+ output?: string;
7
+ }): Promise<void>;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * `passwd-sso export` — Export vault entries to JSON or CSV.
3
+ */
4
+ import { resolve } from "node:path";
5
+ import { existsSync, lstatSync } from "node:fs";
6
+ import { apiRequest } from "../lib/api-client.js";
7
+ import { getEncryptionKey, getUserId } from "../lib/vault-state.js";
8
+ import { decryptData } from "../lib/crypto.js";
9
+ import { buildPersonalEntryAAD } from "../lib/crypto-aad.js";
10
+ import * as output from "../lib/output.js";
11
+ function escapeCSV(value) {
12
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
13
+ return `"${value.replace(/"/g, '""')}"`;
14
+ }
15
+ return value;
16
+ }
17
+ export async function exportCommand(options) {
18
+ const key = getEncryptionKey();
19
+ if (!key) {
20
+ output.error("Vault is locked. Run `unlock` first.");
21
+ return;
22
+ }
23
+ const format = options.format ?? "json";
24
+ if (format !== "json" && format !== "csv") {
25
+ output.error("Format must be 'json' or 'csv'.");
26
+ return;
27
+ }
28
+ const res = await apiRequest("/api/passwords?include=blob");
29
+ if (!res.ok) {
30
+ output.error(`Failed to fetch entries: ${res.status}`);
31
+ return;
32
+ }
33
+ const entries = res.data;
34
+ if (!Array.isArray(entries) || entries.length === 0) {
35
+ output.info("No entries to export.");
36
+ return;
37
+ }
38
+ // Validate and resolve output path before processing
39
+ const outputPath = options.output ? resolve(options.output) : undefined;
40
+ if (!outputPath) {
41
+ output.warn("Decrypted passwords will be printed to stdout.");
42
+ }
43
+ if (outputPath && existsSync(outputPath) && lstatSync(outputPath).isSymbolicLink()) {
44
+ output.error("Output path is a symlink — refusing to write.");
45
+ return;
46
+ }
47
+ const userId = getUserId();
48
+ const decrypted = [];
49
+ let failedCount = 0;
50
+ for (const entry of entries) {
51
+ try {
52
+ const aad = entry.aadVersion >= 1 && userId
53
+ ? buildPersonalEntryAAD(userId, entry.id)
54
+ : undefined;
55
+ const plaintext = await decryptData(entry.encryptedBlob, key, aad);
56
+ decrypted.push(JSON.parse(plaintext));
57
+ }
58
+ catch {
59
+ failedCount++;
60
+ }
61
+ }
62
+ if (failedCount > 0) {
63
+ output.warn(`${failedCount} entries failed to decrypt and were skipped.`);
64
+ }
65
+ if (format === "json") {
66
+ const out = JSON.stringify(decrypted, null, 2);
67
+ if (outputPath) {
68
+ const { writeFileSync } = await import("node:fs");
69
+ writeFileSync(outputPath, out, { encoding: "utf-8", mode: 0o600 });
70
+ output.success(`Exported ${decrypted.length} entries to ${outputPath}`);
71
+ }
72
+ else {
73
+ console.log(out);
74
+ }
75
+ }
76
+ else {
77
+ const headers = ["title", "username", "password", "url", "notes", "totp"];
78
+ const csvRows = [headers.join(",")];
79
+ for (const entry of decrypted) {
80
+ csvRows.push(headers.map((h) => {
81
+ const val = entry[h];
82
+ if (h === "totp" && val && typeof val === "object") {
83
+ return escapeCSV(val.secret ?? "");
84
+ }
85
+ return escapeCSV(String(val ?? ""));
86
+ }).join(","));
87
+ }
88
+ const csvOut = csvRows.join("\n");
89
+ if (outputPath) {
90
+ const { writeFileSync } = await import("node:fs");
91
+ writeFileSync(outputPath, csvOut, { encoding: "utf-8", mode: 0o600 });
92
+ output.success(`Exported ${decrypted.length} entries to ${outputPath}`);
93
+ }
94
+ else {
95
+ console.log(csvOut);
96
+ }
97
+ }
98
+ }
99
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `passwd-sso generate` — Generate a secure password (offline).
3
+ */
4
+ export declare function generateCommand(options: {
5
+ length?: number;
6
+ noUppercase?: boolean;
7
+ noDigits?: boolean;
8
+ noSymbols?: boolean;
9
+ copy?: boolean;
10
+ json?: boolean;
11
+ }): Promise<void>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `passwd-sso generate` — Generate a secure password (offline).
3
+ */
4
+ import { randomBytes } from "node:crypto";
5
+ import { copyToClipboard } from "../lib/clipboard.js";
6
+ import * as output from "../lib/output.js";
7
+ const LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
8
+ const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
9
+ const DIGITS = "0123456789";
10
+ const SYMBOLS = "!@#$%^&*()_+-=[]{}|;:',.<>?/`~";
11
+ export async function generateCommand(options) {
12
+ const length = options.length ?? 20;
13
+ if (length < 4 || length > 128) {
14
+ output.error("Length must be between 4 and 128.");
15
+ return;
16
+ }
17
+ let charset = LOWERCASE;
18
+ if (!options.noUppercase)
19
+ charset += UPPERCASE;
20
+ if (!options.noDigits)
21
+ charset += DIGITS;
22
+ if (!options.noSymbols)
23
+ charset += SYMBOLS;
24
+ // Rejection sampling to avoid modulo bias
25
+ const limit = Math.floor(0x100000000 / charset.length) * charset.length;
26
+ const password = Array.from({ length }, () => {
27
+ let val;
28
+ do {
29
+ val = randomBytes(4).readUInt32BE(0);
30
+ } while (val >= limit);
31
+ return charset[val % charset.length];
32
+ }).join("");
33
+ if (options.json) {
34
+ output.json({ password });
35
+ return;
36
+ }
37
+ if (options.copy) {
38
+ await copyToClipboard(password);
39
+ output.success("Password generated and copied to clipboard (auto-clears in 30s).");
40
+ }
41
+ else {
42
+ console.log(password);
43
+ }
44
+ }
45
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `passwd-sso get <id>` — Show a single entry (decrypted blob).
3
+ */
4
+ export declare function getCommand(id: string, options: {
5
+ copy?: boolean;
6
+ json?: boolean;
7
+ field?: string;
8
+ }): Promise<void>;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `passwd-sso get <id>` — Show a single entry (decrypted blob).
3
+ */
4
+ import { apiRequest } from "../lib/api-client.js";
5
+ import { getEncryptionKey, getUserId } from "../lib/vault-state.js";
6
+ import { decryptData } from "../lib/crypto.js";
7
+ import { buildPersonalEntryAAD } from "../lib/crypto-aad.js";
8
+ import { copyToClipboard } from "../lib/clipboard.js";
9
+ import * as output from "../lib/output.js";
10
+ export async function getCommand(id, options) {
11
+ const key = getEncryptionKey();
12
+ if (!key) {
13
+ output.error("Vault is locked. Run `unlock` first.");
14
+ return;
15
+ }
16
+ const userId = getUserId();
17
+ const res = await apiRequest(`/api/passwords/${id}`);
18
+ if (!res.ok) {
19
+ output.error(`Entry not found: ${res.status}`);
20
+ return;
21
+ }
22
+ const entry = res.data;
23
+ try {
24
+ const aad = entry.aadVersion >= 1 && userId
25
+ ? buildPersonalEntryAAD(userId, entry.id)
26
+ : undefined;
27
+ const plaintext = await decryptData(entry.encryptedBlob, key, aad);
28
+ const blob = JSON.parse(plaintext);
29
+ if (options.field) {
30
+ const value = blob[options.field];
31
+ if (value === undefined) {
32
+ output.error(`Field "${options.field}" not found.`);
33
+ return;
34
+ }
35
+ const str = typeof value === "object" && value !== null
36
+ ? JSON.stringify(value)
37
+ : String(value);
38
+ if (options.copy) {
39
+ await copyToClipboard(str);
40
+ output.success(`Copied ${options.field} to clipboard (auto-clears in 30s).`);
41
+ }
42
+ else {
43
+ console.log(str);
44
+ }
45
+ return;
46
+ }
47
+ if (options.json) {
48
+ output.json(blob);
49
+ return;
50
+ }
51
+ // Formatted display
52
+ if (blob.title)
53
+ console.log(`Title: ${blob.title}`);
54
+ if (blob.username)
55
+ console.log(`Username: ${blob.username}`);
56
+ if (blob.password)
57
+ console.log(`Password: ${output.masked(blob.password)}`);
58
+ if (blob.url)
59
+ console.log(`URL: ${blob.url}`);
60
+ if (blob.notes)
61
+ console.log(`Notes: ${blob.notes}`);
62
+ if (blob.totp)
63
+ console.log(`TOTP: (configured)`);
64
+ if (options.copy && blob.password) {
65
+ await copyToClipboard(blob.password);
66
+ output.success("Password copied to clipboard (auto-clears in 30s).");
67
+ }
68
+ }
69
+ catch {
70
+ output.error("Failed to decrypt entry.");
71
+ }
72
+ }
73
+ //# sourceMappingURL=get.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `passwd-sso list` — List vault entries (decrypted overview).
3
+ */
4
+ export declare function listCommand(options: {
5
+ json?: boolean;
6
+ }): Promise<void>;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `passwd-sso list` — List vault entries (decrypted overview).
3
+ */
4
+ import { apiRequest } from "../lib/api-client.js";
5
+ import { getEncryptionKey, getUserId } from "../lib/vault-state.js";
6
+ import { decryptData } from "../lib/crypto.js";
7
+ import { buildPersonalEntryAAD } from "../lib/crypto-aad.js";
8
+ import * as output from "../lib/output.js";
9
+ export async function listCommand(options) {
10
+ const key = getEncryptionKey();
11
+ if (!key) {
12
+ output.error("Vault is locked. Run `unlock` first.");
13
+ return;
14
+ }
15
+ const userId = getUserId();
16
+ const res = await apiRequest("/api/passwords");
17
+ if (!res.ok) {
18
+ output.error(`Failed to fetch entries: ${res.status}`);
19
+ return;
20
+ }
21
+ const entries = res.data;
22
+ if (!Array.isArray(entries) || entries.length === 0) {
23
+ output.info("No entries found.");
24
+ return;
25
+ }
26
+ const decrypted = [];
27
+ for (const entry of entries) {
28
+ try {
29
+ const aad = entry.aadVersion >= 1 && userId
30
+ ? buildPersonalEntryAAD(userId, entry.id)
31
+ : undefined;
32
+ const plaintext = await decryptData(entry.encryptedOverview, key, aad);
33
+ const overview = JSON.parse(plaintext);
34
+ decrypted.push({
35
+ id: entry.id,
36
+ title: overview.title ?? "(untitled)",
37
+ username: overview.username ?? "",
38
+ url: overview.urlHost ?? "",
39
+ type: entry.entryType ?? "LOGIN",
40
+ });
41
+ }
42
+ catch {
43
+ decrypted.push({
44
+ id: entry.id,
45
+ title: "(decryption failed)",
46
+ username: "",
47
+ url: "",
48
+ type: entry.entryType ?? "?",
49
+ });
50
+ }
51
+ }
52
+ if (options.json) {
53
+ output.json(decrypted);
54
+ }
55
+ else {
56
+ output.table(["ID", "Type", "Title", "Username", "URL"], decrypted.map((d) => [
57
+ d.id,
58
+ d.type,
59
+ d.title,
60
+ d.username,
61
+ d.url,
62
+ ]));
63
+ output.info(`${decrypted.length} entries`);
64
+ }
65
+ }
66
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1,4 @@
1
+ /**
2
+ * `passwd-sso login` — Configure server URL and Bearer token.
3
+ */
4
+ export declare function loginCommand(): Promise<void>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `passwd-sso login` — Configure server URL and Bearer token.
3
+ */
4
+ import { createInterface } from "node:readline";
5
+ import { loadConfig, saveConfig, saveToken } from "../lib/config.js";
6
+ import { setTokenCache } from "../lib/api-client.js";
7
+ import * as output from "../lib/output.js";
8
+ async function prompt(question) {
9
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
10
+ return new Promise((resolve) => {
11
+ rl.question(question, (answer) => {
12
+ rl.close();
13
+ resolve(answer.trim());
14
+ });
15
+ });
16
+ }
17
+ export async function loginCommand() {
18
+ const config = loadConfig();
19
+ const serverUrl = await prompt(`Server URL${config.serverUrl ? ` [${config.serverUrl}]` : ""}: `);
20
+ if (serverUrl) {
21
+ config.serverUrl = serverUrl.replace(/\/$/, "");
22
+ }
23
+ if (!config.serverUrl) {
24
+ output.error("Server URL is required.");
25
+ return;
26
+ }
27
+ output.info("Open your browser and go to the token page to generate a CLI token.");
28
+ output.info(` ${config.serverUrl}/dashboard/settings`);
29
+ console.log();
30
+ const token = await prompt("Paste your token: ");
31
+ if (!token) {
32
+ output.error("Token is required.");
33
+ return;
34
+ }
35
+ // Approximate token expiry (15 min from now)
36
+ config.tokenExpiresAt = new Date(Date.now() + 15 * 60 * 1000).toISOString();
37
+ saveConfig(config);
38
+ const storage = await saveToken(token);
39
+ setTokenCache(token, config.tokenExpiresAt);
40
+ if (storage === "file") {
41
+ output.warn("Token saved to file (plaintext). Consider installing keytar for OS keychain storage.");
42
+ }
43
+ output.success(`Logged in to ${config.serverUrl}`);
44
+ }
45
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `passwd-sso run` — Inject vault secrets into a command's environment.
3
+ *
4
+ * Uses child_process.execFile (NOT shell) for security.
5
+ * Blocks certain dangerous env var names from being overwritten.
6
+ */
7
+ interface RunOptions {
8
+ config?: string;
9
+ command: string[];
10
+ }
11
+ export declare function runCommand(opts: RunOptions): Promise<void>;
12
+ export {};