@wlfi-agent/cli 1.4.15 → 1.4.17

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 (82) hide show
  1. package/Cargo.lock +22 -20
  2. package/Cargo.toml +2 -2
  3. package/README.md +10 -2
  4. package/crates/vault-cli-admin/src/main.rs +21 -2
  5. package/crates/vault-cli-admin/src/tui.rs +634 -129
  6. package/crates/vault-cli-daemon/Cargo.toml +1 -0
  7. package/crates/vault-cli-daemon/src/bin/wlfi-agent-system-keychain.rs +122 -8
  8. package/crates/vault-cli-daemon/src/main.rs +24 -4
  9. package/crates/vault-cli-daemon/src/relay_sync.rs +155 -35
  10. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +23 -18
  11. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +6 -0
  12. package/crates/vault-daemon/src/daemon_parts/types_api_rpc.rs +6 -0
  13. package/crates/vault-daemon/src/tests.rs +2 -2
  14. package/crates/vault-daemon/src/tests_parts/part4.rs +110 -0
  15. package/crates/vault-transport-unix/src/lib.rs +22 -3
  16. package/crates/vault-transport-xpc/src/lib.rs +20 -2
  17. package/dist/cli.cjs +20842 -25552
  18. package/dist/cli.cjs.map +1 -1
  19. package/package.json +5 -3
  20. package/packages/cache/.turbo/turbo-build.log +53 -52
  21. package/packages/cache/coverage/base.css +224 -0
  22. package/packages/cache/coverage/block-navigation.js +87 -0
  23. package/packages/cache/coverage/clover.xml +585 -0
  24. package/packages/cache/coverage/coverage-final.json +5 -0
  25. package/packages/cache/coverage/favicon.png +0 -0
  26. package/packages/cache/coverage/index.html +161 -0
  27. package/packages/cache/coverage/prettify.css +1 -0
  28. package/packages/cache/coverage/prettify.js +2 -0
  29. package/packages/cache/coverage/sort-arrow-sprite.png +0 -0
  30. package/packages/cache/coverage/sorter.js +210 -0
  31. package/packages/cache/coverage/src/client/index.html +116 -0
  32. package/packages/cache/coverage/src/client/index.ts.html +253 -0
  33. package/packages/cache/coverage/src/errors/index.html +116 -0
  34. package/packages/cache/coverage/src/errors/index.ts.html +244 -0
  35. package/packages/cache/coverage/src/index.html +116 -0
  36. package/packages/cache/coverage/src/index.ts.html +94 -0
  37. package/packages/cache/coverage/src/service/index.html +116 -0
  38. package/packages/cache/coverage/src/service/index.ts.html +2212 -0
  39. package/packages/cache/dist/{chunk-ALQ6H7KG.cjs → chunk-QF4XKEIA.cjs} +189 -45
  40. package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +1 -0
  41. package/packages/cache/dist/{chunk-FGJEEF5N.js → chunk-QNK6GOTI.js} +182 -38
  42. package/packages/cache/dist/chunk-QNK6GOTI.js.map +1 -0
  43. package/packages/cache/dist/index.cjs +2 -2
  44. package/packages/cache/dist/index.js +1 -1
  45. package/packages/cache/dist/service/index.cjs +2 -2
  46. package/packages/cache/dist/service/index.d.cts +2 -0
  47. package/packages/cache/dist/service/index.d.ts +2 -0
  48. package/packages/cache/dist/service/index.js +1 -1
  49. package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  50. package/packages/cache/src/service/index.test.ts +575 -0
  51. package/packages/cache/src/service/index.ts +234 -51
  52. package/packages/config/.turbo/turbo-build.log +2 -2
  53. package/packages/config/node_modules/.bin/tsc +2 -2
  54. package/packages/config/node_modules/.bin/tsserver +2 -2
  55. package/packages/config/node_modules/.bin/tsup +2 -2
  56. package/packages/config/node_modules/.bin/tsup-node +2 -2
  57. package/packages/rpc/.turbo/turbo-build.log +11 -11
  58. package/packages/rpc/node_modules/.bin/tsc +2 -2
  59. package/packages/rpc/node_modules/.bin/tsserver +2 -2
  60. package/packages/rpc/node_modules/.bin/tsup +2 -2
  61. package/packages/rpc/node_modules/.bin/tsup-node +2 -2
  62. package/packages/ui/.turbo/turbo-build.log +13 -13
  63. package/packages/ui/dist/components/badge.d.ts +1 -1
  64. package/packages/ui/dist/components/button.d.ts +1 -1
  65. package/scripts/install-rust-binaries.mjs +229 -58
  66. package/src/cli.ts +51 -39
  67. package/src/lib/admin-passthrough.js +1 -0
  68. package/src/lib/admin-reset.js +1 -0
  69. package/src/lib/admin-reset.ts +26 -16
  70. package/src/lib/admin-setup.js +1 -0
  71. package/src/lib/admin-setup.ts +32 -20
  72. package/src/lib/agent-auth-revoke.js +1 -0
  73. package/src/lib/agent-auth-rotate.js +1 -0
  74. package/src/lib/agent-auth.js +1 -0
  75. package/src/lib/config-mutation.js +1 -0
  76. package/src/lib/launchd-assets.js +1 -0
  77. package/src/lib/launchd-assets.ts +29 -0
  78. package/src/lib/local-admin-access.js +1 -0
  79. package/src/lib/rust.ts +1 -1
  80. package/src/lib/status-repair-cli.js +1 -0
  81. package/packages/cache/dist/chunk-ALQ6H7KG.cjs.map +0 -1
  82. package/packages/cache/dist/chunk-FGJEEF5N.js.map +0 -1
@@ -2,83 +2,254 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
-
6
- if (process.env.WLFI_SKIP_RUST_INSTALL === '1') {
7
- process.exit(0);
8
- }
5
+ import { pathToFileURL } from 'node:url';
9
6
 
10
7
  const repoRoot = new URL('..', import.meta.url).pathname;
11
- const wlfiHome = process.env.WLFI_HOME?.trim() || path.join(os.homedir(), '.wlfi_agent');
12
- const binDir = path.join(wlfiHome, 'bin');
13
8
  const extension = process.platform === 'win32' ? '.exe' : '';
9
+ const MIN_RUST_VERSION = {
10
+ major: 1,
11
+ minor: 87,
12
+ patch: 0,
13
+ };
14
14
  const rustBins = [
15
15
  'wlfi-agent-daemon',
16
16
  'wlfi-agent-admin',
17
17
  'wlfi-agent-agent',
18
- 'wlfi-agent-system-keychain'
18
+ 'wlfi-agent-system-keychain',
19
19
  ];
20
- const helperScripts = [
21
- {
22
- source: path.join(repoRoot, 'scripts', 'launchd', 'run-wlfi-agent-daemon.sh'),
23
- destination: path.join(binDir, 'run-wlfi-agent-daemon.sh')
20
+ const RERUN_INSTRUCTIONS =
21
+ 'After installing prerequisites, rerun `npm install -g @wlfi-agent/cli`.';
22
+
23
+ function decodeOutput(value) {
24
+ if (!value) {
25
+ return '';
24
26
  }
25
- ];
27
+ return value.toString().trim();
28
+ }
26
29
 
27
- fs.mkdirSync(binDir, { recursive: true, mode: 0o700 });
28
- const cargo = spawnSync('cargo', ['--version'], { cwd: repoRoot, stdio: 'pipe' });
29
- if (cargo.status !== 0) {
30
- console.warn('[wlfi-agent] cargo was not found; skipping Rust binary installation');
31
- console.warn('[wlfi-agent] install Rust from https://rustup.rs and rerun `npm run install:rust-binaries`.');
32
- process.exit(0);
30
+ function formatMinimumRustVersion() {
31
+ return `${MIN_RUST_VERSION.major}.${MIN_RUST_VERSION.minor}.${MIN_RUST_VERSION.patch}`;
33
32
  }
34
33
 
35
- const build = spawnSync(
36
- 'cargo',
37
- [
38
- 'build',
39
- '--release',
40
- '-p',
41
- 'wlfi-agent-daemon',
42
- '-p',
43
- 'wlfi-agent-admin',
44
- '-p',
45
- 'wlfi-agent-agent'
46
- ],
47
- { cwd: repoRoot, stdio: 'inherit' }
48
- );
49
- if (build.status !== 0) {
50
- process.exit(build.status ?? 1);
34
+ function parseRustVersion(output) {
35
+ const match = output.match(/\b(\d+)\.(\d+)\.(\d+)(?:[-+][^\s]+)?\b/);
36
+ if (!match) {
37
+ return null;
38
+ }
39
+ return {
40
+ major: Number(match[1]),
41
+ minor: Number(match[2]),
42
+ patch: Number(match[3]),
43
+ };
51
44
  }
52
45
 
53
- for (const binary of rustBins) {
54
- const source = path.join(repoRoot, 'target', 'release', binary + extension);
55
- const destination = path.join(binDir, binary + extension);
56
- fs.copyFileSync(source, destination);
57
- if (process.platform !== 'win32') {
58
- fs.chmodSync(destination, 0o755);
46
+ function compareVersions(left, right) {
47
+ if (left.major !== right.major) {
48
+ return left.major - right.major;
49
+ }
50
+ if (left.minor !== right.minor) {
51
+ return left.minor - right.minor;
59
52
  }
53
+ return left.patch - right.patch;
54
+ }
55
+
56
+ function runCheck(spawnSyncImpl, command, args, options = {}) {
57
+ return spawnSyncImpl(command, args, {
58
+ cwd: repoRoot,
59
+ stdio: 'pipe',
60
+ ...options,
61
+ });
62
+ }
63
+
64
+ function resolveWlfiPaths(env) {
65
+ const wlfiHome = env.WLFI_HOME?.trim() || path.join(os.homedir(), '.wlfi_agent');
66
+ return {
67
+ wlfiHome,
68
+ binDir: path.join(wlfiHome, 'bin'),
69
+ };
70
+ }
71
+
72
+ function resolveHelperScripts(binDir) {
73
+ return [
74
+ {
75
+ source: path.join(repoRoot, 'scripts', 'launchd', 'run-wlfi-agent-daemon.sh'),
76
+ destination: path.join(binDir, 'run-wlfi-agent-daemon.sh'),
77
+ },
78
+ {
79
+ source: path.join(repoRoot, 'scripts', 'launchd', 'install-user-daemon.sh'),
80
+ destination: path.join(binDir, 'install-user-daemon.sh'),
81
+ },
82
+ {
83
+ source: path.join(repoRoot, 'scripts', 'launchd', 'uninstall-user-daemon.sh'),
84
+ destination: path.join(binDir, 'uninstall-user-daemon.sh'),
85
+ },
86
+ ];
60
87
  }
61
88
 
62
- for (const script of helperScripts) {
63
- fs.copyFileSync(script.source, script.destination);
64
- if (process.platform !== 'win32') {
65
- fs.chmodSync(script.destination, 0o755);
89
+ function checkCargoAvailable(spawnSyncImpl) {
90
+ const result = runCheck(spawnSyncImpl, 'cargo', ['--version']);
91
+ if (result.status === 0) {
92
+ return;
93
+ }
94
+
95
+ const detail = decodeOutput(result.stderr) || decodeOutput(result.stdout);
96
+ const lines = [
97
+ '[wlfi-agent] Rust toolchain was not found on PATH.',
98
+ '[wlfi-agent] Install Rust from https://rustup.rs.',
99
+ ];
100
+ if (detail) {
101
+ lines.push(`[wlfi-agent] cargo check output: ${detail}`);
102
+ }
103
+ lines.push(`[wlfi-agent] ${RERUN_INSTRUCTIONS}`);
104
+ throw new Error(lines.join('\n'));
105
+ }
106
+
107
+ function checkRustcVersion(spawnSyncImpl) {
108
+ const result = runCheck(spawnSyncImpl, 'rustc', ['--version']);
109
+ if (result.status !== 0) {
110
+ const detail = decodeOutput(result.stderr) || decodeOutput(result.stdout);
111
+ const lines = [
112
+ '[wlfi-agent] Rust compiler was not found on PATH.',
113
+ `[wlfi-agent] Install Rust ${formatMinimumRustVersion()} or newer from https://rustup.rs.`,
114
+ ];
115
+ if (detail) {
116
+ lines.push(`[wlfi-agent] rustc check output: ${detail}`);
117
+ }
118
+ lines.push(`[wlfi-agent] ${RERUN_INSTRUCTIONS}`);
119
+ throw new Error(lines.join('\n'));
120
+ }
121
+
122
+ const versionOutput = decodeOutput(result.stdout) || decodeOutput(result.stderr);
123
+ const parsedVersion = parseRustVersion(versionOutput);
124
+ if (!parsedVersion) {
125
+ throw new Error(
126
+ `[wlfi-agent] Unable to determine the installed Rust compiler version from: ${versionOutput || '<empty output>'}`,
127
+ );
128
+ }
129
+
130
+ if (compareVersions(parsedVersion, MIN_RUST_VERSION) < 0) {
131
+ throw new Error(
132
+ `[wlfi-agent] Rust ${formatMinimumRustVersion()} or newer is required; found ${versionOutput}.\n` +
133
+ `[wlfi-agent] Update Rust with \`rustup update\`.\n` +
134
+ `[wlfi-agent] ${RERUN_INSTRUCTIONS}`,
135
+ );
66
136
  }
67
137
  }
68
138
 
69
- const configPath = path.join(wlfiHome, 'config.json');
70
- if (!fs.existsSync(configPath)) {
71
- fs.writeFileSync(
72
- configPath,
73
- JSON.stringify(
74
- {
75
- daemonSocket: path.join(wlfiHome, 'daemon.sock'),
76
- stateFile: path.join(wlfiHome, 'daemon-state.enc'),
77
- rustBinDir: binDir
78
- },
79
- null,
80
- 2
81
- ) + '\n',
82
- { mode: 0o600 }
139
+ function checkMacOsToolchainAvailable(spawnSyncImpl, platform) {
140
+ if (platform !== 'darwin') {
141
+ return;
142
+ }
143
+
144
+ const result = runCheck(spawnSyncImpl, 'xcrun', ['--sdk', 'macosx', '--find', 'clang']);
145
+ if (result.status === 0) {
146
+ return;
147
+ }
148
+
149
+ const detail = decodeOutput(result.stderr) || decodeOutput(result.stdout);
150
+ const lines = [
151
+ '[wlfi-agent] macOS Command Line Tools were not found or are not configured.',
152
+ '[wlfi-agent] Install them with `xcode-select --install`.',
153
+ ];
154
+ if (detail) {
155
+ lines.push(`[wlfi-agent] xcrun check output: ${detail}`);
156
+ }
157
+ lines.push(`[wlfi-agent] ${RERUN_INSTRUCTIONS}`);
158
+ throw new Error(lines.join('\n'));
159
+ }
160
+
161
+ export function verifyRustInstallPrerequisites({
162
+ spawnSyncImpl = spawnSync,
163
+ platform = process.platform,
164
+ } = {}) {
165
+ checkCargoAvailable(spawnSyncImpl);
166
+ checkRustcVersion(spawnSyncImpl);
167
+ checkMacOsToolchainAvailable(spawnSyncImpl, platform);
168
+ }
169
+
170
+ export function installRustBinaries({
171
+ spawnSyncImpl = spawnSync,
172
+ env = process.env,
173
+ platform = process.platform,
174
+ } = {}) {
175
+ if (env.WLFI_SKIP_RUST_INSTALL === '1') {
176
+ return 0;
177
+ }
178
+
179
+ verifyRustInstallPrerequisites({ spawnSyncImpl, platform });
180
+
181
+ const { wlfiHome, binDir } = resolveWlfiPaths(env);
182
+ const helperScripts = resolveHelperScripts(binDir);
183
+
184
+ fs.mkdirSync(binDir, { recursive: true, mode: 0o700 });
185
+ const build = spawnSyncImpl(
186
+ 'cargo',
187
+ [
188
+ 'build',
189
+ '--locked',
190
+ '--release',
191
+ '-p',
192
+ 'wlfi-agent-daemon',
193
+ '-p',
194
+ 'wlfi-agent-admin',
195
+ '-p',
196
+ 'wlfi-agent-agent',
197
+ ],
198
+ { cwd: repoRoot, stdio: 'inherit' },
199
+ );
200
+ if (build.status !== 0) {
201
+ return build.status ?? 1;
202
+ }
203
+
204
+ for (const binary of rustBins) {
205
+ const source = path.join(repoRoot, 'target', 'release', binary + extension);
206
+ const destination = path.join(binDir, binary + extension);
207
+ fs.copyFileSync(source, destination);
208
+ if (platform !== 'win32') {
209
+ fs.chmodSync(destination, 0o755);
210
+ }
211
+ }
212
+
213
+ for (const script of helperScripts) {
214
+ fs.copyFileSync(script.source, script.destination);
215
+ if (platform !== 'win32') {
216
+ fs.chmodSync(script.destination, 0o755);
217
+ }
218
+ }
219
+
220
+ const configPath = path.join(wlfiHome, 'config.json');
221
+ if (!fs.existsSync(configPath)) {
222
+ fs.writeFileSync(
223
+ configPath,
224
+ JSON.stringify(
225
+ {
226
+ daemonSocket: path.join(wlfiHome, 'daemon.sock'),
227
+ stateFile: path.join(wlfiHome, 'daemon-state.enc'),
228
+ rustBinDir: binDir,
229
+ },
230
+ null,
231
+ 2,
232
+ ) + '\n',
233
+ { mode: 0o600 },
234
+ );
235
+ }
236
+
237
+ return 0;
238
+ }
239
+
240
+ function isDirectExecution() {
241
+ return (
242
+ Boolean(process.argv[1]) &&
243
+ import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
83
244
  );
84
245
  }
246
+
247
+ if (isDirectExecution()) {
248
+ try {
249
+ process.exitCode = installRustBinaries();
250
+ } catch (error) {
251
+ const message = error instanceof Error ? error.message : String(error);
252
+ process.stderr.write(`${message}\n`);
253
+ process.exitCode = 1;
254
+ }
255
+ }
package/src/cli.ts CHANGED
@@ -1,41 +1,6 @@
1
- import {
2
- type ChainProfile,
3
- defaultDaemonSocketPath,
4
- deleteConfigKey,
5
- ensureWlfiHome,
6
- listBuiltinChains,
7
- listBuiltinTokens,
8
- listConfiguredChains,
9
- listConfiguredTokens,
10
- readConfig,
11
- redactConfig,
12
- removeChainProfile,
13
- removeTokenChainProfile,
14
- removeTokenProfile,
15
- resolveChainProfile,
16
- resolveConfigPath,
17
- resolveTokenProfile,
18
- saveChainProfile,
19
- saveTokenChainProfile,
20
- switchActiveChain,
21
- type TokenChainProfile,
22
- type WlfiConfig,
23
- writeConfig,
24
- } from '@wlfi-agent/config';
25
- import {
26
- broadcastRawTransaction,
27
- estimateFees,
28
- estimateGas,
29
- getAccountSnapshot,
30
- getChainInfo,
31
- getCodeAtAddress,
32
- getLatestBlockNumber,
33
- getNativeBalance,
34
- getNonce,
35
- getTokenBalance,
36
- getTransactionByHash,
37
- getTransactionReceiptByHash,
38
- } from '@wlfi-agent/rpc';
1
+ import * as configPackage from '../packages/config/src/index.js';
2
+ import * as rpcPackage from '../packages/rpc/src/index.ts';
3
+ import type { ChainProfile, TokenChainProfile, WlfiConfig } from '../packages/config/src/index.js';
39
4
  import { Command, Option } from 'commander';
40
5
  import { type Address, type Hex, isAddress, isHex } from 'viem';
41
6
  import { assertSafeRpcUrl } from '../packages/config/src/index.js';
@@ -118,6 +83,49 @@ import {
118
83
  walletProfileFromBootstrapSummary,
119
84
  } from './lib/wallet-profile.js';
120
85
 
86
+ const configExports = (
87
+ 'default' in configPackage ? configPackage.default : configPackage
88
+ ) as typeof import('../packages/config/src/index.js');
89
+ const {
90
+ defaultDaemonSocketPath,
91
+ deleteConfigKey,
92
+ ensureWlfiHome,
93
+ listBuiltinChains,
94
+ listBuiltinTokens,
95
+ listConfiguredChains,
96
+ listConfiguredTokens,
97
+ readConfig,
98
+ redactConfig,
99
+ removeChainProfile,
100
+ removeTokenChainProfile,
101
+ removeTokenProfile,
102
+ resolveChainProfile,
103
+ resolveConfigPath,
104
+ resolveTokenProfile,
105
+ saveChainProfile,
106
+ saveTokenChainProfile,
107
+ switchActiveChain,
108
+ writeConfig,
109
+ } = configExports;
110
+
111
+ const rpcExports = ('default' in rpcPackage ? rpcPackage.default : rpcPackage) as typeof import(
112
+ '../packages/rpc/src/index.ts'
113
+ );
114
+ const {
115
+ broadcastRawTransaction,
116
+ estimateFees,
117
+ estimateGas,
118
+ getAccountSnapshot,
119
+ getChainInfo,
120
+ getCodeAtAddress,
121
+ getLatestBlockNumber,
122
+ getNativeBalance,
123
+ getNonce,
124
+ getTokenBalance,
125
+ getTransactionByHash,
126
+ getTransactionReceiptByHash,
127
+ } = rpcExports;
128
+
121
129
  interface RustBroadcastOutput {
122
130
  command: string;
123
131
  network: string;
@@ -302,7 +310,11 @@ async function readTrimmedStdin(label: string): Promise<string> {
302
310
  }
303
311
 
304
312
  function formatJson(payload: unknown) {
305
- return JSON.stringify(payload, null, 2);
313
+ return JSON.stringify(
314
+ payload,
315
+ (_key, value) => (typeof value === 'bigint' ? value.toString() : value),
316
+ 2,
317
+ );
306
318
  }
307
319
 
308
320
  function stringifyOptionalValue(value: { toString(): string } | null | undefined): string | null {
@@ -0,0 +1 @@
1
+ export * from './admin-passthrough.ts';
@@ -0,0 +1 @@
1
+ export * from './admin-reset.ts';
@@ -20,6 +20,10 @@ import {
20
20
  DAEMON_PASSWORD_KEYCHAIN_SERVICE,
21
21
  deleteAgentAuthTokenFromKeychain,
22
22
  } from './keychain.js';
23
+ import {
24
+ LAUNCHD_UNINSTALL_SCRIPT_NAME,
25
+ resolveLaunchDaemonHelperScriptPath,
26
+ } from './launchd-assets.js';
23
27
  import { createSudoSession } from './sudo.js';
24
28
 
25
29
  const DEFAULT_LAUNCH_DAEMON_LABEL = 'com.wlfi.agent.daemon';
@@ -29,6 +33,7 @@ const DEFAULT_LAUNCH_DAEMON_PLIST = `/Library/LaunchDaemons/${DEFAULT_LAUNCH_DAE
29
33
  const DEFAULT_MANAGED_ROOT_DIR = '/Library/WLFI';
30
34
  const DEFAULT_MANAGED_STATE_DIR = '/var/db/wlfi-agent';
31
35
  const DEFAULT_MANAGED_LOG_DIR = '/var/log/wlfi-agent';
36
+ const DEFAULT_MANAGED_RELAY_DAEMON_TOKEN_FILE = `${DEFAULT_MANAGED_STATE_DIR}/relay-daemon-token`;
32
37
  const MAX_SECRET_STDIN_BYTES = 16 * 1024;
33
38
  const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
34
39
 
@@ -247,6 +252,7 @@ function printResetSummary(result: {
247
252
  daemon: {
248
253
  label: string;
249
254
  daemonSocket: string;
255
+ relayDaemonTokenFile: string;
250
256
  stateFile: string;
251
257
  };
252
258
  local: CleanupLocalAdminResetStateResult;
@@ -256,6 +262,7 @@ function printResetSummary(result: {
256
262
  `managed daemon removed: ${result.daemon.label}`,
257
263
  `managed state deleted: ${result.daemon.stateFile}`,
258
264
  `managed socket removed: ${result.daemon.daemonSocket}`,
265
+ `managed relay token removed: ${result.daemon.relayDaemonTokenFile}`,
259
266
  ];
260
267
 
261
268
  if (result.local.agentKeyId) {
@@ -284,20 +291,20 @@ function printResetSummary(result: {
284
291
  console.log(lines.join('\n'));
285
292
  }
286
293
 
287
- function resolveLaunchDaemonUninstallScriptPath(): string {
288
- const candidates = [
289
- path.resolve(__dirname, '../scripts/launchd/uninstall-user-daemon.sh'),
290
- path.resolve(__dirname, '../../scripts/launchd/uninstall-user-daemon.sh'),
291
- path.resolve(process.cwd(), 'scripts/launchd/uninstall-user-daemon.sh'),
292
- ];
294
+ function resolveLaunchDaemonUninstallScriptPath(config?: WlfiConfig): string {
295
+ return resolveLaunchDaemonHelperScriptPath(LAUNCHD_UNINSTALL_SCRIPT_NAME, config);
296
+ }
293
297
 
294
- for (const candidate of candidates) {
295
- if (fs.existsSync(candidate)) {
296
- return candidate;
297
- }
298
- }
298
+ function readConfigIfPresent(): WlfiConfig | undefined {
299
+ return fs.existsSync(resolveConfigPath()) ? readConfig() : undefined;
300
+ }
299
301
 
300
- return candidates[0];
302
+ export function managedDaemonResetArtifactPaths(): string[] {
303
+ return [
304
+ DEFAULT_MANAGED_STATE_FILE,
305
+ DEFAULT_MANAGED_DAEMON_SOCKET,
306
+ DEFAULT_MANAGED_RELAY_DAEMON_TOKEN_FILE,
307
+ ];
301
308
  }
302
309
 
303
310
  export function cleanupLocalAdminResetState(
@@ -480,12 +487,13 @@ async function runAdminReset(options: AdminResetOptions): Promise<void> {
480
487
  );
481
488
  }
482
489
  await sudoSession.prime();
490
+ const currentConfig = readConfigIfPresent();
483
491
 
484
492
  const uninstallProgress = createProgress('Uninstalling managed daemon', showProgress);
485
493
  let uninstallResult;
486
494
  try {
487
495
  uninstallResult = await sudoSession.run([
488
- resolveLaunchDaemonUninstallScriptPath(),
496
+ resolveLaunchDaemonUninstallScriptPath(currentConfig),
489
497
  '--label',
490
498
  DEFAULT_LAUNCH_DAEMON_LABEL,
491
499
  '--keychain-service',
@@ -514,8 +522,7 @@ async function runAdminReset(options: AdminResetOptions): Promise<void> {
514
522
  deleteRootArtifactsResult = await sudoSession.run([
515
523
  '/bin/rm',
516
524
  '-f',
517
- DEFAULT_MANAGED_STATE_FILE,
518
- DEFAULT_MANAGED_DAEMON_SOCKET,
525
+ ...managedDaemonResetArtifactPaths(),
519
526
  ]);
520
527
  } catch (error) {
521
528
  stateProgress.fail();
@@ -543,6 +550,7 @@ async function runAdminReset(options: AdminResetOptions): Promise<void> {
543
550
  launchdDomain: 'system',
544
551
  plist: DEFAULT_LAUNCH_DAEMON_PLIST,
545
552
  daemonSocket: DEFAULT_MANAGED_DAEMON_SOCKET,
553
+ relayDaemonTokenFile: DEFAULT_MANAGED_RELAY_DAEMON_TOKEN_FILE,
546
554
  stateFile: DEFAULT_MANAGED_STATE_FILE,
547
555
  systemKeychainPasswordService: DAEMON_PASSWORD_KEYCHAIN_SERVICE,
548
556
  systemKeychainPasswordAccount: keychainAccount,
@@ -560,6 +568,7 @@ async function runAdminReset(options: AdminResetOptions): Promise<void> {
560
568
  daemon: {
561
569
  label: result.daemon.label,
562
570
  daemonSocket: result.daemon.daemonSocket,
571
+ relayDaemonTokenFile: result.daemon.relayDaemonTokenFile,
563
572
  stateFile: result.daemon.stateFile,
564
573
  },
565
574
  local,
@@ -625,12 +634,13 @@ async function runAdminUninstall(options: AdminUninstallOptions): Promise<void>
625
634
  );
626
635
  }
627
636
  await sudoSession.prime();
637
+ const currentConfig = readConfigIfPresent();
628
638
 
629
639
  const uninstallProgress = createProgress('Uninstalling managed daemon', showProgress);
630
640
  let uninstallResult;
631
641
  try {
632
642
  uninstallResult = await sudoSession.run([
633
- resolveLaunchDaemonUninstallScriptPath(),
643
+ resolveLaunchDaemonUninstallScriptPath(currentConfig),
634
644
  '--label',
635
645
  DEFAULT_LAUNCH_DAEMON_LABEL,
636
646
  '--keychain-service',
@@ -0,0 +1 @@
1
+ export * from './admin-setup.ts';
@@ -21,6 +21,11 @@ import {
21
21
  assertTrustedRootPlannedPrivateFilePath,
22
22
  } from './fs-trust.js';
23
23
  import { DAEMON_PASSWORD_KEYCHAIN_SERVICE } from './keychain.js';
24
+ import {
25
+ LAUNCHD_INSTALL_SCRIPT_NAME,
26
+ LAUNCHD_RUNNER_SCRIPT_NAME,
27
+ resolveLaunchDaemonHelperScriptPath,
28
+ } from './launchd-assets.js';
24
29
  import { resolveCliNetworkProfile } from './network-selection.js';
25
30
  import { passthroughRustBinary, RustBinaryExitError, runRustBinary } from './rust.js';
26
31
  import { createSudoSession } from './sudo.js';
@@ -639,7 +644,7 @@ function resolveRustBinDir(config: WlfiConfig): string {
639
644
  function resolveSourceLaunchDaemonPaths(config: WlfiConfig): LaunchDaemonAssetPaths {
640
645
  const rustBinDir = resolveRustBinDir(config);
641
646
  return {
642
- runnerPath: path.join(rustBinDir, 'run-wlfi-agent-daemon.sh'),
647
+ runnerPath: path.join(rustBinDir, LAUNCHD_RUNNER_SCRIPT_NAME),
643
648
  daemonBin: path.join(
644
649
  rustBinDir,
645
650
  `wlfi-agent-daemon${process.platform === 'win32' ? '.exe' : ''}`,
@@ -662,20 +667,8 @@ export function resolveManagedLaunchDaemonPaths(): LaunchDaemonAssetPaths {
662
667
  };
663
668
  }
664
669
 
665
- function resolveLaunchDaemonInstallScriptPath(): string {
666
- const candidates = [
667
- path.resolve(__dirname, '../scripts/launchd/install-user-daemon.sh'),
668
- path.resolve(__dirname, '../../scripts/launchd/install-user-daemon.sh'),
669
- path.resolve(process.cwd(), 'scripts/launchd/install-user-daemon.sh'),
670
- ];
671
-
672
- for (const candidate of candidates) {
673
- if (fs.existsSync(candidate)) {
674
- return candidate;
675
- }
676
- }
677
-
678
- return candidates[0];
670
+ function resolveLaunchDaemonInstallScriptPath(config: WlfiConfig): string {
671
+ return resolveLaunchDaemonHelperScriptPath(LAUNCHD_INSTALL_SCRIPT_NAME, config);
679
672
  }
680
673
 
681
674
  function filesHaveIdenticalContents(leftPath: string, rightPath: string): boolean {
@@ -718,23 +711,24 @@ export function assertManagedDaemonInstallPreconditions(
718
711
  deps.assertTrustedRootPlannedDaemonSocketPath ?? assertTrustedRootPlannedDaemonSocketPath;
719
712
  const trustStateFilePath =
720
713
  deps.assertTrustedRootPlannedPrivateFilePath ?? assertTrustedRootPlannedPrivateFilePath;
721
- const installScript = (deps.resolveInstallScriptPath ?? resolveLaunchDaemonInstallScriptPath)();
714
+ const installScript =
715
+ (deps.resolveInstallScriptPath ?? (() => resolveLaunchDaemonInstallScriptPath(config)))();
722
716
  const sourcePaths = resolveSourceLaunchDaemonPaths(config);
723
717
  const managedPaths = resolveManagedLaunchDaemonPaths();
724
718
 
725
719
  if (!existsSync(sourcePaths.runnerPath)) {
726
720
  throw new Error(
727
- `daemon runner is not installed at ${sourcePaths.runnerPath}; rerun npm run install:rust-binaries`,
721
+ `daemon runner is not installed at ${sourcePaths.runnerPath}; reinstall @wlfi-agent/cli`,
728
722
  );
729
723
  }
730
724
  if (!existsSync(sourcePaths.daemonBin)) {
731
725
  throw new Error(
732
- `daemon binary is not installed at ${sourcePaths.daemonBin}; rerun npm run install:rust-binaries`,
726
+ `daemon binary is not installed at ${sourcePaths.daemonBin}; reinstall @wlfi-agent/cli`,
733
727
  );
734
728
  }
735
729
  if (!existsSync(sourcePaths.keychainHelperBin)) {
736
730
  throw new Error(
737
- `daemon keychain helper is not installed at ${sourcePaths.keychainHelperBin}; rerun npm run install:rust-binaries`,
731
+ `daemon keychain helper is not installed at ${sourcePaths.keychainHelperBin}; reinstall @wlfi-agent/cli`,
738
732
  );
739
733
  }
740
734
  if (!existsSync(installScript)) {
@@ -927,6 +921,17 @@ function plistContainsValue(plistContents: string, value: string): boolean {
927
921
  return plistContents.includes(`<string>${value}</string>`);
928
922
  }
929
923
 
924
+ export function launchDaemonPlistValue(
925
+ plistContents: string,
926
+ key: string,
927
+ ): string | null {
928
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
929
+ const match = plistContents.match(
930
+ new RegExp(`<key>${escapedKey}</key>\\s*<string>([^<]+)</string>`, 'u'),
931
+ );
932
+ return match?.[1] ?? null;
933
+ }
934
+
930
935
  function resolveInstalledLaunchDaemonPaths(
931
936
  config: WlfiConfig,
932
937
  plistContents: string,
@@ -961,6 +966,7 @@ function isManagedDaemonInstallCurrent(
961
966
  const sourcePaths = resolveSourceLaunchDaemonPaths(config);
962
967
  const keychainAccount = os.userInfo().username;
963
968
  const expectedAdminUid = String(process.getuid?.() ?? process.geteuid?.() ?? 0);
969
+ const expectedAgentUid = String(process.getuid?.() ?? process.geteuid?.() ?? 0);
964
970
 
965
971
  if (!fs.existsSync(DEFAULT_LAUNCH_DAEMON_PLIST)) {
966
972
  return false;
@@ -1003,6 +1009,13 @@ function isManagedDaemonInstallCurrent(
1003
1009
  return false;
1004
1010
  }
1005
1011
 
1012
+ if (
1013
+ launchDaemonPlistValue(plistContents, 'WLFI_ALLOW_ADMIN_EUID') !== expectedAdminUid ||
1014
+ launchDaemonPlistValue(plistContents, 'WLFI_ALLOW_AGENT_EUID') !== expectedAgentUid
1015
+ ) {
1016
+ return false;
1017
+ }
1018
+
1006
1019
  return [
1007
1020
  DEFAULT_LAUNCH_DAEMON_LABEL,
1008
1021
  installedPaths.runnerPath,
@@ -1013,7 +1026,6 @@ function isManagedDaemonInstallCurrent(
1013
1026
  DAEMON_PASSWORD_KEYCHAIN_SERVICE,
1014
1027
  keychainAccount,
1015
1028
  DEFAULT_SIGNER_BACKEND,
1016
- expectedAdminUid,
1017
1029
  ].every((value) => plistContainsValue(plistContents, value));
1018
1030
  }
1019
1031
 
@@ -0,0 +1 @@
1
+ export * from './agent-auth-revoke.ts';
@@ -0,0 +1 @@
1
+ export * from './agent-auth-rotate.ts';
@@ -0,0 +1 @@
1
+ export * from './agent-auth.ts';
@@ -0,0 +1 @@
1
+ export * from './config-mutation.ts';
@@ -0,0 +1 @@
1
+ export * from './launchd-assets.ts';