@aiwg/cockpit 2026.8.3 → 2026.8.5

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.
@@ -3,7 +3,6 @@ import { platform } from 'node:os';
3
3
 
4
4
  const SERVICE = 'aiwg-cockpit-bridge';
5
5
  const FOLDER = 'AIWG Cockpit';
6
- const WALLET = process.env.AIWG_COCKPIT_KWALLET || 'kdewallet';
7
6
 
8
7
  function collect(cmd, args, input, timeoutMs = 2_000) {
9
8
  return new Promise((resolve, reject) => {
@@ -37,74 +36,94 @@ async function canRun(cmd) {
37
36
  }
38
37
  }
39
38
 
40
- async function windowsPowerShell() {
41
- if (await canRun('powershell')) return 'powershell';
42
- if (await canRun('pwsh')) return 'pwsh';
43
- throw new Error('no supported Windows PowerShell command found');
44
- }
39
+ /**
40
+ * Build the cross-platform keychain adapter around injectable platform and
41
+ * process seams. Production uses the defaults below; tests exercise the exact
42
+ * argv/stdin contract for every OS without pretending the CI host is macOS or
43
+ * Windows.
44
+ */
45
+ export function createKeychainAdapter({
46
+ os = platform(),
47
+ env = process.env,
48
+ run = collect,
49
+ commandAvailable = canRun,
50
+ } = {}) {
51
+ const wallet = env.AIWG_COCKPIT_KWALLET || 'kdewallet';
45
52
 
46
- async function storeWindowsCredential(token, account) {
47
- const ps = await windowsPowerShell();
48
- const script = [
49
- '[void][Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]',
50
- '$vault = New-Object Windows.Security.Credentials.PasswordVault',
51
- 'try { $vault.Remove($vault.Retrieve($args[0], $args[1])) } catch {}',
52
- '$password = [Console]::In.ReadToEnd()',
53
- '$credential = New-Object Windows.Security.Credentials.PasswordCredential -ArgumentList $args[0], $args[1], $password',
54
- '$vault.Add($credential)',
55
- ].join('; ');
56
- await collect(ps, ['-NoProfile', '-NonInteractive', '-Command', script, SERVICE, account], token);
57
- return { backend: 'windows-credential-manager', service: SERVICE, account, target: `${SERVICE}:${account}` };
58
- }
53
+ async function windowsPowerShell() {
54
+ if (await commandAvailable('powershell')) return 'powershell';
55
+ if (await commandAvailable('pwsh')) return 'pwsh';
56
+ throw new Error('no supported Windows PowerShell command found');
57
+ }
58
+
59
+ async function storeWindowsCredential(token, account) {
60
+ const ps = await windowsPowerShell();
61
+ const script = [
62
+ '[void][Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]',
63
+ '$vault = New-Object Windows.Security.Credentials.PasswordVault',
64
+ 'try { $vault.Remove($vault.Retrieve($args[0], $args[1])) } catch {}',
65
+ '$password = [Console]::In.ReadToEnd()',
66
+ '$credential = New-Object Windows.Security.Credentials.PasswordCredential -ArgumentList $args[0], $args[1], $password',
67
+ '$vault.Add($credential)',
68
+ ].join('; ');
69
+ await run(ps, ['-NoProfile', '-NonInteractive', '-Command', script, SERVICE, account], token);
70
+ return { backend: 'windows-credential-manager', service: SERVICE, account, target: `${SERVICE}:${account}` };
71
+ }
59
72
 
60
- async function readWindowsCredential(ref) {
61
- const ps = await windowsPowerShell();
62
- const script = [
63
- '[void][Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]',
64
- '$vault = New-Object Windows.Security.Credentials.PasswordVault',
65
- '$credential = $vault.Retrieve($args[0], $args[1])',
66
- '$credential.RetrievePassword()',
67
- '[Console]::Out.Write($credential.Password)',
68
- ].join('; ');
69
- return (await collect(ps, ['-NoProfile', '-NonInteractive', '-Command', script, ref.service || SERVICE, ref.account])).trim();
73
+ async function readWindowsCredential(ref) {
74
+ const ps = await windowsPowerShell();
75
+ const script = [
76
+ '[void][Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]',
77
+ '$vault = New-Object Windows.Security.Credentials.PasswordVault',
78
+ '$credential = $vault.Retrieve($args[0], $args[1])',
79
+ '$credential.RetrievePassword()',
80
+ '[Console]::Out.Write($credential.Password)',
81
+ ].join('; ');
82
+ return (await run(ps, ['-NoProfile', '-NonInteractive', '-Command', script, ref.service || SERVICE, ref.account])).trim();
83
+ }
84
+
85
+ return {
86
+ async store(token, account = `bridge-${process.pid}`) {
87
+ if (env.AIWG_COCKPIT_KEYCHAIN_DISABLED === '1') {
88
+ throw new Error('OS keychain disabled by AIWG_COCKPIT_KEYCHAIN_DISABLED');
89
+ }
90
+ if (os === 'darwin' && await commandAvailable('security')) {
91
+ await run('security', ['add-generic-password', '-a', account, '-s', SERVICE, '-w', token, '-U']);
92
+ return { backend: 'macos-keychain', service: SERVICE, account };
93
+ }
94
+ if (os === 'win32') return storeWindowsCredential(token, account);
95
+ if (await commandAvailable('secret-tool')) {
96
+ await run('secret-tool', ['store', '--label', 'AIWG Cockpit Bridge', 'service', SERVICE, 'account', account], token);
97
+ return { backend: 'libsecret', service: SERVICE, account };
98
+ }
99
+ if (env.AIWG_COCKPIT_ENABLE_KWALLET === '1' && await commandAvailable('kwallet-query')) {
100
+ await run('kwallet-query', ['-f', FOLDER, '-w', account, wallet], token);
101
+ return { backend: 'kwallet', service: SERVICE, account, wallet, folder: FOLDER };
102
+ }
103
+ throw new Error('no supported OS keychain command found');
104
+ },
105
+
106
+ async read(ref) {
107
+ if (!ref || typeof ref !== 'object') throw new Error('missing keychain reference');
108
+ if (ref.backend === 'macos-keychain') {
109
+ return (await run('security', ['find-generic-password', '-a', ref.account, '-s', ref.service || SERVICE, '-w'])).trim();
110
+ }
111
+ if (ref.backend === 'windows-credential-manager') return readWindowsCredential(ref);
112
+ if (ref.backend === 'libsecret') {
113
+ return (await run('secret-tool', ['lookup', 'service', ref.service || SERVICE, 'account', ref.account])).trim();
114
+ }
115
+ if (ref.backend === 'kwallet') {
116
+ return (await run('kwallet-query', ['-f', ref.folder || FOLDER, '-r', ref.account, ref.wallet || wallet])).trim();
117
+ }
118
+ throw new Error(`unsupported keychain backend: ${ref.backend}`);
119
+ },
120
+ };
70
121
  }
71
122
 
72
123
  export async function storeCockpitToken(token, account = `bridge-${process.pid}`) {
73
- if (process.env.AIWG_COCKPIT_KEYCHAIN_DISABLED === '1') {
74
- throw new Error('OS keychain disabled by AIWG_COCKPIT_KEYCHAIN_DISABLED');
75
- }
76
- const os = platform();
77
- if (os === 'darwin' && await canRun('security')) {
78
- await collect('security', ['add-generic-password', '-a', account, '-s', SERVICE, '-w', token, '-U']);
79
- return { backend: 'macos-keychain', service: SERVICE, account };
80
- }
81
- if (os === 'win32') {
82
- return storeWindowsCredential(token, account);
83
- }
84
- if (await canRun('secret-tool')) {
85
- await collect('secret-tool', ['store', '--label', 'AIWG Cockpit Bridge', 'service', SERVICE, 'account', account], token);
86
- return { backend: 'libsecret', service: SERVICE, account };
87
- }
88
- if (process.env.AIWG_COCKPIT_ENABLE_KWALLET === '1' && await canRun('kwallet-query')) {
89
- await collect('kwallet-query', ['-f', FOLDER, '-w', account, WALLET], token);
90
- return { backend: 'kwallet', service: SERVICE, account, wallet: WALLET, folder: FOLDER };
91
- }
92
- throw new Error('no supported OS keychain command found');
124
+ return createKeychainAdapter().store(token, account);
93
125
  }
94
126
 
95
127
  export async function readCockpitToken(ref) {
96
- if (!ref || typeof ref !== 'object') throw new Error('missing keychain reference');
97
- if (ref.backend === 'macos-keychain') {
98
- return (await collect('security', ['find-generic-password', '-a', ref.account, '-s', ref.service || SERVICE, '-w'])).trim();
99
- }
100
- if (ref.backend === 'windows-credential-manager') {
101
- return readWindowsCredential(ref);
102
- }
103
- if (ref.backend === 'libsecret') {
104
- return (await collect('secret-tool', ['lookup', 'service', ref.service || SERVICE, 'account', ref.account])).trim();
105
- }
106
- if (ref.backend === 'kwallet') {
107
- return (await collect('kwallet-query', ['-f', ref.folder || FOLDER, '-r', ref.account, ref.wallet || WALLET])).trim();
108
- }
109
- throw new Error(`unsupported keychain backend: ${ref.backend}`);
128
+ return createKeychainAdapter().read(ref);
110
129
  }