caveat-cli 0.21.1 → 0.21.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.
@@ -4,8 +4,8 @@ const require = __caveat_createRequire(import.meta.url);
4
4
 
5
5
  // ../../packages/core/src/managedDeploymentRuntime.ts
6
6
  import { createHash as createHash2 } from "node:crypto";
7
- import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
8
- import { join as join2, resolve, relative, sep } from "node:path";
7
+ import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync, realpathSync } from "node:fs";
8
+ import { join as join3, resolve, relative, dirname as dirname2, basename as basename2, sep } from "node:path";
9
9
  import { homedir } from "node:os";
10
10
 
11
11
  // ../../packages/core/src/platform.ts
@@ -16,6 +16,34 @@ function isWindows(platform = process.platform) {
16
16
  return platform === "win32";
17
17
  }
18
18
 
19
+ // ../../packages/core/src/managedDeploymentLegacy.ts
20
+ import { lstatSync } from "node:fs";
21
+ import { join as join2, dirname, basename } from "node:path";
22
+ function existsSync(path) {
23
+ try {
24
+ lstatSync(path);
25
+ return true;
26
+ } catch (error) {
27
+ if (error.code === "ENOENT") return false;
28
+ throw error;
29
+ }
30
+ }
31
+ var legacyManagedRoot = (home) => join2(home, ".local", "share", "caveat", "managed-v1");
32
+ function legacyHomeFromRoot(root) {
33
+ const parent = dirname(root);
34
+ return basename(root) === "managed-v1" && basename(parent) === "caveat" && basename(dirname(parent)) === "share" && basename(dirname(dirname(parent))) === ".local" ? dirname(dirname(dirname(parent))) : void 0;
35
+ }
36
+ function selectExistingDeploymentRoot(home, modern) {
37
+ const legacy = legacyManagedRoot(home);
38
+ const modernActive = existsSync(join2(modern, "active.json")), legacyActive = existsSync(join2(legacy, "active.json"));
39
+ if (modernActive && legacyActive) throw Error("managed_deployment_multiple_active_roots");
40
+ if (modernActive) return modern;
41
+ if (legacyActive) return legacy;
42
+ const modernExists = existsSync(modern), legacyExists = existsSync(legacy);
43
+ if (modernExists && legacyExists) throw Error("managed_deployment_multiple_staging_roots");
44
+ return legacyExists ? legacy : modern;
45
+ }
46
+
19
47
  // ../../packages/core/src/managedDeploymentRuntime.ts
20
48
  var sha = (data) => createHash2("sha256").update(data).digest("hex");
21
49
  var releasePattern = /^\d+\.\d+\.\d+-[a-f0-9]{20}$/;
@@ -26,54 +54,67 @@ function fail(reason) {
26
54
  }
27
55
  function canonicalHome(userHome = homedir()) {
28
56
  const path = resolve(userHome);
29
- return existsSync(path) ? realpathSync(path) : path;
57
+ return existsSync2(path) ? realpathSync(path) : path;
30
58
  }
31
- function managedDeploymentPaths(userHome = homedir()) {
32
- const root = join2(canonicalHome(userHome), ".local", "share", "caveat", "managed-v1");
59
+ function managedDeploymentPaths(userHome = homedir(), explicitRoot) {
60
+ const home = canonicalHome(userHome);
61
+ const modern = join3(home, ".caveat-deploy", "managed-v1");
62
+ const legacy = legacyManagedRoot(home);
63
+ const root = explicitRoot ?? selectExistingDeploymentRoot(home, modern);
64
+ if (root !== modern && root !== legacy) fail("invalid_root");
33
65
  return {
34
66
  root,
35
- releases: join2(root, "releases"),
36
- entry: join2(root, "entry"),
37
- launcher: join2(root, "entry", "caveat.mjs"),
38
- runtime: join2(root, "entry", "managed-deployment.js"),
39
- pointer: join2(root, "active.json"),
40
- lock: join2(root, "installer.lock")
67
+ releases: join3(root, "releases"),
68
+ entry: join3(root, "entry"),
69
+ launcher: join3(root, "entry", "caveat.mjs"),
70
+ runtime: join3(root, "entry", "managed-deployment.js"),
71
+ pointer: join3(root, "active.json"),
72
+ lock: join3(root, "installer.lock")
41
73
  };
42
74
  }
75
+ function readManagedDeploymentAtRoot(root) {
76
+ const path = resolve(root), parent = dirname2(path);
77
+ if (basename2(path) !== "managed-v1") fail("invalid_root");
78
+ let userHome;
79
+ if (basename2(parent) === ".caveat-deploy") userHome = dirname2(parent);
80
+ else if (legacyHomeFromRoot(path)) userHome = legacyHomeFromRoot(path);
81
+ else return fail("invalid_root");
82
+ return readManagedDeployment({ userHome, root: path });
83
+ }
43
84
  function ownerStat(path, directory, options) {
44
- const s = lstatSync(path);
85
+ const s = lstatSync2(path);
45
86
  if (s.isSymbolicLink() || (directory ? !s.isDirectory() : !s.isFile())) fail("unsafe_path");
46
87
  if (!(options.windows ?? isWindows()) && ((s.mode & 18) !== 0 || typeof process.getuid === "function" && s.uid !== process.getuid())) fail("unsafe_owner_or_mode");
47
88
  return s;
48
89
  }
49
90
  function assertAncestors(options) {
50
- const paths = managedDeploymentPaths(options.userHome);
91
+ const paths = managedDeploymentPaths(options.userHome, options.root);
51
92
  const home = canonicalHome(options.userHome);
52
93
  ownerStat(home, true, options);
53
94
  let path = home;
54
95
  for (const part of relative(home, paths.root).split(sep)) {
55
- path = join2(path, part);
96
+ path = join3(path, part);
56
97
  ownerStat(path, true, options);
57
98
  }
58
99
  if (realpathSync(paths.root) !== paths.root) fail("root_redirected");
59
100
  return paths;
60
101
  }
61
102
  function checkReleaseMetadata(pointer, options) {
62
- const paths = managedDeploymentPaths(options.userHome);
103
+ const paths = managedDeploymentPaths(options.userHome, options.root);
63
104
  if (pointer.schema !== 1 || !releasePattern.test(pointer.release) || !/^[a-f0-9]{64}$/.test(pointer.manifestSha256)) fail("invalid_pointer");
64
- const releaseRoot = join2(paths.releases, pointer.release);
105
+ const releaseRoot = join3(paths.releases, pointer.release);
65
106
  ownerStat(paths.releases, true, options);
66
107
  ownerStat(releaseRoot, true, options);
67
- const manifestPath = join2(releaseRoot, "manifest.json");
108
+ const manifestPath = join3(releaseRoot, "manifest.json");
68
109
  if (ownerStat(manifestPath, false, options).size > 1024 * 1024) fail("manifest_too_large");
69
110
  const bytes = readFileSync(manifestPath);
70
111
  if (sha(bytes) !== pointer.manifestSha256) fail("manifest_changed");
71
112
  const manifest = JSON.parse(bytes.toString());
72
113
  if (manifest.schema !== 1 || !versionPattern.test(manifest.version) || !pointer.release.startsWith(manifest.version + "-") || !Array.isArray(manifest.files) || manifest.files.length > MAX_FILES) fail("invalid_manifest");
73
- const packageRoot = join2(releaseRoot, "package");
114
+ const packageRoot = join3(releaseRoot, "package");
74
115
  ownerStat(packageRoot, true, options);
75
- ownerStat(join2(packageRoot, "dist"), true, options);
76
- const cliScriptPath = join2(packageRoot, "dist", "caveat.js");
116
+ ownerStat(join3(packageRoot, "dist"), true, options);
117
+ const cliScriptPath = join3(packageRoot, "dist", "caveat.js");
77
118
  ownerStat(cliScriptPath, false, options);
78
119
  return { pointer, manifest, packageRoot, cliScriptPath, launcher: paths.launcher };
79
120
  }
@@ -88,8 +129,8 @@ function readManagedDeployment(options = {}) {
88
129
  return checkReleaseMetadata(pointer, options);
89
130
  }
90
131
  function managedRegistrationPath(cliScriptPath, options = {}) {
91
- const paths = managedDeploymentPaths(options.userHome);
92
- if (!existsSync(paths.pointer)) return cliScriptPath;
132
+ const paths = managedDeploymentPaths(options.userHome, options.root);
133
+ if (!existsSync2(paths.pointer)) return cliScriptPath;
93
134
  const active = readManagedDeployment(options);
94
135
  return active.launcher;
95
136
  }
@@ -98,6 +139,7 @@ export {
98
139
  managedDeploymentInternal,
99
140
  managedDeploymentPaths,
100
141
  managedRegistrationPath,
101
- readManagedDeployment
142
+ readManagedDeployment,
143
+ readManagedDeploymentAtRoot
102
144
  };
103
145
  //# sourceMappingURL=managed-deployment.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../packages/core/src/managedDeploymentRuntime.ts","../../../packages/core/src/platform.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';\nimport { join, resolve, relative, isAbsolute, sep } from 'node:path';\nimport { homedir } from 'node:os';\nimport { isWindows } from './platform.js';\nimport type { runWindowsAcl } from './privateWindowsAcl.js';\n\nexport type FileRecord = { path: string; sha256: string; bytes: number };\nexport type Manifest = { schema: 1; version: string; files: FileRecord[] };\nexport type Pointer = { schema: 1; release: string; manifestSha256: string };\nexport type ManagedRuntimeOptions = { userHome?: string; windows?: boolean; aclRunner?: typeof runWindowsAcl };\nconst sha = (data: string | Buffer) => createHash('sha256').update(data).digest('hex');\nconst releasePattern = /^\\d+\\.\\d+\\.\\d+-[a-f0-9]{20}$/;\nconst versionPattern = /^\\d+\\.\\d+\\.\\d+$/;\nconst MAX_FILES = 4096;\nfunction fail(reason: string): never { throw Error(`managed_deployment_${reason}`); }\nfunction canonicalHome(userHome = homedir()) {\n const path=resolve(userHome);\n // macOS /var -> /private/var and other OS-level aliases may precede the\n // user profile. Descendants of the canonical home still reject redirects.\n return existsSync(path) ? realpathSync(path) : path;\n}\nexport function managedDeploymentPaths(userHome = homedir()) {\n const root = join(canonicalHome(userHome), '.local', 'share', 'caveat', 'managed-v1');\n return { root, releases: join(root, 'releases'), entry: join(root, 'entry'),\n launcher: join(root, 'entry', 'caveat.mjs'), runtime: join(root, 'entry', 'managed-deployment.js'),\n pointer: join(root, 'active.json'), lock: join(root, 'installer.lock') };\n}\nfunction ownerStat(path: string, directory: boolean, options: ManagedRuntimeOptions) {\n const s = lstatSync(path);\n if (s.isSymbolicLink() || (directory ? !s.isDirectory() : !s.isFile())) fail('unsafe_path');\n if (!(options.windows ?? isWindows()) && ((s.mode & 0o022) !== 0 || (typeof process.getuid === 'function' && s.uid !== process.getuid()))) fail('unsafe_owner_or_mode');\n return s;\n}\nfunction assertAncestors(options: ManagedRuntimeOptions) {\n const paths = managedDeploymentPaths(options.userHome);\n const home = canonicalHome(options.userHome);\n ownerStat(home, true, options);\n let path = home;\n for (const part of relative(home, paths.root).split(sep)) {\n path = join(path, part); ownerStat(path, true, options);\n }\n if (realpathSync(paths.root) !== paths.root) fail('root_redirected');\n return paths;\n}\nfunction checkReleaseMetadata(pointer: Pointer, options: ManagedRuntimeOptions) {\n const paths = managedDeploymentPaths(options.userHome);\n if (pointer.schema !== 1 || !releasePattern.test(pointer.release) || !/^[a-f0-9]{64}$/.test(pointer.manifestSha256)) fail('invalid_pointer');\n const releaseRoot = join(paths.releases, pointer.release);\n ownerStat(paths.releases, true, options); ownerStat(releaseRoot, true, options);\n const manifestPath = join(releaseRoot, 'manifest.json');\n if (ownerStat(manifestPath, false, options).size > 1024 * 1024) fail('manifest_too_large');\n const bytes = readFileSync(manifestPath);\n if (sha(bytes) !== pointer.manifestSha256) fail('manifest_changed');\n const manifest = JSON.parse(bytes.toString()) as Manifest;\n if (manifest.schema !== 1 || !versionPattern.test(manifest.version) || !pointer.release.startsWith(manifest.version + '-') || !Array.isArray(manifest.files) || manifest.files.length > MAX_FILES) fail('invalid_manifest');\n const packageRoot = join(releaseRoot, 'package');\n ownerStat(packageRoot, true, options); ownerStat(join(packageRoot, 'dist'), true, options);\n const cliScriptPath = join(packageRoot, 'dist', 'caveat.js'); ownerStat(cliScriptPath, false, options);\n return { pointer, manifest, packageRoot, cliScriptPath, launcher: paths.launcher };\n}\n/** Startup checks only: no process creation or package-wide content scan. */\nexport function readManagedDeployment(options: ManagedRuntimeOptions = {}) {\n const paths = assertAncestors(options);\n ownerStat(paths.entry, true, options); ownerStat(paths.launcher, false, options); ownerStat(paths.runtime, false, options);\n if (ownerStat(paths.pointer, false, options).size > 1024) fail('pointer_too_large');\n const pointer = JSON.parse(readFileSync(paths.pointer, 'utf8')) as Pointer;\n if (Object.keys(pointer).sort().join(',') !== 'manifestSha256,release,schema') fail('invalid_pointer');\n return checkReleaseMetadata(pointer, options);\n}\n/** Direct npm callers register the active stable entry when it owns this package. */\nexport function managedRegistrationPath(cliScriptPath: string, options: ManagedRuntimeOptions = {}) {\n const paths = managedDeploymentPaths(options.userHome);\n if (!existsSync(paths.pointer)) return cliScriptPath;\n const active = readManagedDeployment(options);\n // A managed installation explicitly selects its active release. An npm-bin\n // caller must not rewrite registrations back to its own version directory.\n return active.launcher;\n}\n\nexport const managedDeploymentInternal = { sha, fail, canonicalHome, ownerStat, assertAncestors, checkReleaseMetadata, versionPattern, releasePattern };\n","import type { Stats } from 'node:fs';\nimport { spawnSync } from 'node:child_process';\nimport { XMLParser } from 'fast-xml-parser';\nimport { createHash } from 'node:crypto';\nimport { join, isAbsolute, win32 } from 'node:path';\n\n/**\n * OS 依存の判定はこのモジュールに集約する。他のファイルで\n * `process.platform` / `win32` を直接分岐しない(データとして記録する\n * fingerprint 等は除く)。全関数は platform を引数で受けてテスト可能にする。\n */\n\nexport function isWindows(platform: NodeJS.Platform = process.platform): boolean {\n return platform === 'win32';\n}\n\n/**\n * env を考慮した Windows 判定。`OS=Windows_NT` は Windows native shell が必ず持つ\n * 環境変数で、テストが env 注入だけで Windows 経路を選べるようにする\n * (runtimeErrors が独自に持っていた判定をここへ集約。v0.17.5)。\n */\nexport function isWindowsEnv(\n env: NodeJS.ProcessEnv,\n platform: NodeJS.Platform = process.platform,\n): boolean {\n return env.OS === 'Windows_NT' || isWindows(platform);\n}\n\n/**\n * owner-only 所有チェック。Node の POSIX mode/uid フィールドは Windows では\n * ACL のビューにならないため、Windows は構造チェック(呼び出し側のマーカー・\n * スキーマ検証や親ディレクトリの ACL)に委ねて素通しする。\n */\nexport function isPrivateOwnerStat(\n stat: Stats,\n uid: number | undefined,\n platform: NodeJS.Platform = process.platform,\n): boolean {\n return isWindows(platform)\n || ((stat.mode & 0o077) === 0 && (uid === undefined || stat.uid === uid));\n}\n\n/**\n * Windows native Codex hook command は quoted Node パスを PowerShell で\n * 実行するため call operator `&` が必要(v0.17.1)。POSIX は不要。\n */\nexport function powershellCallPrefix(platform: NodeJS.Platform = process.platform): string {\n return isWindows(platform) ? '& ' : '';\n}\n\n/** PATH 走査で node 実行ファイルとして探す名前の候補。 */\nexport function nodeExecutableNames(platform: NodeJS.Platform = process.platform): string[] {\n return isWindows(platform)\n ? ['node.exe', 'node.cmd', 'node.bat', 'node']\n : ['node'];\n}\n\n/**\n * ファイルシステムが case-insensitive な Windows ではパス比較用に小文字化する。\n * 呼び出し側で区切り文字の正規化(\\\\ → /)を済ませてから渡す。\n */\nexport function normalizePathCase(\n normalizedPath: string,\n platform: NodeJS.Platform = process.platform,\n): string {\n return isWindows(platform) ? normalizedPath.toLowerCase() : normalizedPath;\n}\n\nexport interface ScheduleCommand { command: string; args: string[] }\n/** Resolve the current token, never a guessed USERDOMAIN/account pair. */\nexport function nativeScheduleUserId(\n username: string,\n platform: NodeJS.Platform = process.platform,\n run: (command: ScheduleCommand) => { status: number; stdout: string } = command => {\n const result = spawnSync(command.command, command.args, { encoding: 'utf8', timeout: 30_000, windowsHide: true });\n if (result.error) throw Error('schedule_identity_spawn_failed');\n return { status: result.status ?? -1, stdout: result.stdout ?? '' };\n },\n systemRoot = process.env.SystemRoot,\n): string {\n if (!isWindows(platform)) return username;\n let phase: 'root' | 'spawn' | 'exit' | 'output' = 'root';\n try {\n if (!systemRoot || !win32.isAbsolute(systemRoot) || /[\\x00-\\x1f]/.test(systemRoot)) throw Error('schedule_system_root_unavailable');\n phase = 'spawn';\n const result = run({ command: win32.join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'), args: ['-NoProfile', '-NonInteractive', '-Command',\n \"$ErrorActionPreference='Stop';try{[Console]::Write([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value);exit 0}catch{exit 1}\"] });\n phase = 'exit';\n if (result.status !== 0) throw Error('schedule_identity_exit_failed');\n phase = 'output';\n const sid = result.stdout.trim();\n if (/^S-1-\\d+(?:-\\d+){1,15}$/.test(sid)) return sid;\n } catch { /* Never fall back to environment/account-name guesses. */ }\n throw Error(`schedule_user_identity_unavailable_${phase}`);\n}\n\nexport interface NativeSyncSchedule {\n id: string;\n kind: 'systemd-user' | 'launchd-user' | 'windows-task';\n files: { path: string; content: string; encoding?: 'utf16le' }[];\n query: ScheduleCommand;\n inspect: ScheduleCommand;\n install: ScheduleCommand[];\n remove: ScheduleCommand[];\n reload: ScheduleCommand[];\n argv: string[];\n}\n/** Exact persisted bytes; absent encoding preserves legacy UTF-8 ownership. */\nexport function nativeScheduleFileBytes(file: NativeSyncSchedule['files'][number]): Buffer {\n if (file.encoding === 'utf16le') return Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from(file.content, 'utf16le')]);\n if (file.encoding !== undefined) throw Error('schedule_definition_encoding_invalid');\n return Buffer.from(file.content, 'utf8');\n}\n\nexport interface NativeSyncScheduleOptions {\n caveatHome: string; userHome: string; nodePath: string; cliPath: string;\n platform?: NodeJS.Platform; uid?: number; userId?: string;\n}\n\nconst xml = (value: string) => value.replace(/[&<>\"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&apos;' })[c]!);\nconst unitArg = (value: string) => `\"${value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/%/g, '%%').replace(/\\$/g, () => '$$')}\"`;\n/** Standard Windows argv quoting, without invoking cmd.exe or PowerShell. */\nexport function quoteWindowsArgument(value: string): string {\n return `\"${value.replace(/(\\\\*)\"/g, '$1$1\\\\\"').replace(/(\\\\+)$/g, '$1$1')}\"`;\n}\n\nexport function nativeSyncSchedule(opts: NativeSyncScheduleOptions): NativeSyncSchedule {\n const platform = opts.platform ?? process.platform;\n const absolute = platform === 'win32' ? win32.isAbsolute : isAbsolute;\n for (const value of [opts.caveatHome, opts.userHome, opts.nodePath, opts.cliPath]) {\n if (!absolute(value) || /[\\x00-\\x1f]/.test(value)) throw Error('schedule_absolute_path_required');\n }\n const suffix = createHash('sha256').update(normalizePathCase(opts.caveatHome, platform)).digest('hex').slice(0, 16);\n const id = `caveat-autosync-${suffix}`;\n const argv = [opts.nodePath, opts.cliPath, 'autosync', 'run', '--json', '--caveat-home', opts.caveatHome, '--user-home', opts.userHome];\n const command = (name: string, ...args: string[]): ScheduleCommand => ({ command: name, args });\n if (platform === 'linux') {\n const service = join(opts.userHome, '.config', 'systemd', 'user', `${id}.service`);\n const timer = join(opts.userHome, '.config', 'systemd', 'user', `${id}.timer`);\n return { id, kind: 'systemd-user', argv, files: [\n { path: service, content: `[Unit]\\nDescription=Caveat private knowledge reception\\n[Service]\\nType=oneshot\\nTimeoutStartSec=10min\\nExecStart=${argv.map(unitArg).join(' ')}\\nSuccessExitStatus=2\\n` },\n { path: timer, content: `[Unit]\\nDescription=Caveat reception timer\\n[Timer]\\nOnBootSec=60s\\nOnUnitInactiveSec=60s\\nUnit=${id}.service\\n[Install]\\nWantedBy=timers.target\\n` },\n ], query: command('systemctl', '--user', 'is-active', `${id}.timer`),\n inspect: command('systemctl', '--user', 'show', `${id}.timer`, '--property=LoadState', '--value'),\n install: [command('systemctl', '--user', 'link', service, timer), command('systemctl', '--user', 'daemon-reload'), command('systemctl', '--user', 'enable', '--now', `${id}.timer`)],\n remove: [command('systemctl', '--user', 'stop', `${id}.timer`, `${id}.service`), command('systemctl', '--user', 'disable', `${id}.timer`, `${id}.service`)],\n reload: [command('systemctl', '--user', 'daemon-reload')] };\n }\n if (platform === 'darwin') {\n if (!Number.isInteger(opts.uid) || opts.uid! < 0) throw Error('schedule_uid_required');\n const label = `dev.caveat.${id}`;\n const file = join(opts.userHome, 'Library', 'LaunchAgents', `${label}.plist`);\n const domain = `gui/${opts.uid}`;\n return { id: label, kind: 'launchd-user', argv, files: [{ path: file, content: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<plist version=\"1.0\"><dict><key>Label</key><string>${label}</string><key>ProgramArguments</key><array>${argv.map(a => `<string>${xml(a)}</string>`).join('')}</array><key>StartInterval</key><integer>60</integer></dict></plist>\\n` }],\n query: command('launchctl', 'print', `${domain}/${label}`),\n inspect: command('launchctl', 'print', `${domain}/${label}`),\n install: [command('launchctl', 'bootstrap', domain, file)],\n remove: [command('launchctl', 'bootout', `${domain}/${label}`)], reload: [] };\n }\n if (platform === 'win32') {\n if (!opts.userId || /[\\x00-\\x1f]/.test(opts.userId)) throw Error('schedule_user_id_required');\n const file = join(opts.caveatHome, 'sync', `${id}.xml`);\n // schtasks imports a BOM-marked UTF-16LE file, matching its Unicode XML\n // declaration; BOM-less UTF-8 corrupted non-ASCII paths on Windows.\n // Daily repetition avoids locale-sensitive date arguments to schtasks.\n const content = `<?xml version=\"1.0\" encoding=\"UTF-16\"?>\\n<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\"><RegistrationInfo><Description>Caveat private knowledge reception ${id}</Description></RegistrationInfo><Triggers><CalendarTrigger><Repetition><Interval>PT1M</Interval><Duration>P1D</Duration><StopAtDurationEnd>false</StopAtDurationEnd></Repetition><StartBoundary>2020-01-01T00:00:00</StartBoundary><Enabled>true</Enabled><ScheduleByDay><DaysInterval>1</DaysInterval></ScheduleByDay></CalendarTrigger></Triggers><Principals><Principal id=\"CurrentUser\"><UserId>${xml(opts.userId)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals><Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><ExecutionTimeLimit>PT10M</ExecutionTimeLimit><Enabled>true</Enabled></Settings><Actions Context=\"CurrentUser\"><Exec><Command>${xml(opts.nodePath)}</Command><Arguments>${xml(argv.slice(1).map(quoteWindowsArgument).join(' '))}</Arguments></Exec></Actions></Task>\\n`;\n return { id, kind: 'windows-task', argv, files: [{ path: file, content, encoding: 'utf16le' }],\n query: command('powershell.exe', '-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(\n `$ErrorActionPreference = 'Stop'; try { $scheduler = New-Object -ComObject Schedule.Service; $scheduler.Connect(); $task = $scheduler.GetFolder('\\\\').GetTask('${id}'); $bytes = (New-Object System.Text.UTF8Encoding($false)).GetBytes($task.Xml); $stdout = [Console]::OpenStandardOutput(); $stdout.Write($bytes, 0, $bytes.Length); $stdout.Flush(); exit 0 } catch { exit 1 }`, 'utf16le').toString('base64')),\n inspect: command('powershell.exe', '-NoProfile', '-NonInteractive', '-Command',\n `try { $scheduler = New-Object -ComObject Schedule.Service; $scheduler.Connect(); $null = $scheduler.GetFolder('\\\\').GetTask('${id}'); exit 0 } catch { $failure = $_.Exception; while ($failure.InnerException) { $failure = $failure.InnerException }; if ($failure.HResult -eq -2147024894) { exit 3 }; exit 1 }`),\n install: [command('schtasks.exe', '/Create', '/TN', id, '/XML', file)],\n remove: [command('schtasks.exe', '/Delete', '/TN', id, '/F')], reload: [] };\n }\n throw Error('schedule_platform_unsupported');\n}\n\n/** Presence only; command failures are not a successful registration. */\nexport function nativeSchedulePresent(kind: NativeSyncSchedule['kind'], status: number, stdout: string): boolean {\n if (kind === 'systemd-user') {\n if (stdout.trim() === 'loaded') return true;\n if (stdout.trim() === 'not-found') return false;\n throw Error('schedule_inspection_failed');\n }\n if (status === 0) return true;\n if (kind === 'launchd-user' && status === 113) return false;\n // The fixed COM probe maps only HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)\n // to 3. Access denial/service errors must never mean an absent task.\n if (kind === 'windows-task' && status === 3) return false;\n throw Error('schedule_inspection_failed');\n}\n\n/** Compare the native readback without depending on locale-specific error text. */\nexport function nativeScheduleReadbackMatches(plan: NativeSyncSchedule, stdout: string): boolean {\n if (plan.kind !== 'windows-task') return true;\n try {\n const parser = new XMLParser({ ignoreAttributes: false, removeNSPrefix: true, trimValues: false, parseTagValue: false });\n const actual = parser.parse(stdout).Task;\n const expected = parser.parse(plan.files[0]!.content).Task;\n // Task Scheduler exports omit these schema defaults. Explicit changes still fail.\n // https://learn.microsoft.com/windows/win32/taskschd/task-scheduler-schema\n const defaulted = (value: unknown, fallback: string) => value === undefined ? fallback : value;\n const triggerFields = (task: typeof actual) => {\n const trigger = task.Triggers.CalendarTrigger;\n return [trigger.StartBoundary, defaulted(trigger.Enabled, 'true'), trigger.ScheduleByDay.DaysInterval,\n trigger.Repetition.Interval, trigger.Repetition.Duration, defaulted(trigger.Repetition.StopAtDurationEnd, 'false')];\n };\n const values = (task: typeof actual) => [task.Actions.Exec.Command, task.Actions.Exec.Arguments,\n ...triggerFields(task), task.Principals.Principal.LogonType, defaulted(task.Principals.Principal.RunLevel, 'LeastPrivilege'),\n task.Settings.MultipleInstancesPolicy, task.Settings.ExecutionTimeLimit, defaulted(task.Settings.Enabled, 'true')];\n return JSON.stringify(values(actual)) === JSON.stringify(values(expected));\n } catch { return false; }\n}\n\n/** systemd can load linked units before enabling their timer succeeds. */\nexport function nativeScheduleCanRecover(plan: NativeSyncSchedule, readback: () => boolean): boolean {\n return plan.kind === 'systemd-user' || readback();\n}\n"],"mappings":";;;;;AAAA,SAAS,cAAAA,mBAAkB;AAC3B,SAAS,YAAY,WAAW,cAAc,oBAAoB;AAClE,SAAS,QAAAC,OAAM,SAAS,UAAsB,WAAW;AACzD,SAAS,eAAe;;;ACFxB,SAAS,iBAAiB;AAE1B,SAAS,kBAAkB;AAC3B,SAAS,MAAM,YAAY,aAAa;AAQjC,SAAS,UAAU,WAA4B,QAAQ,UAAmB;AAC/E,SAAO,aAAa;AACtB;;;ADHA,IAAM,MAAM,CAAC,SAA0BC,YAAW,QAAQ,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AACrF,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AACvB,IAAM,YAAY;AAClB,SAAS,KAAK,QAAuB;AAAE,QAAM,MAAM,sBAAsB,MAAM,EAAE;AAAG;AACpF,SAAS,cAAc,WAAW,QAAQ,GAAG;AAC3C,QAAM,OAAK,QAAQ,QAAQ;AAG3B,SAAO,WAAW,IAAI,IAAI,aAAa,IAAI,IAAI;AACjD;AACO,SAAS,uBAAuB,WAAW,QAAQ,GAAG;AAC3D,QAAM,OAAOC,MAAK,cAAc,QAAQ,GAAG,UAAU,SAAS,UAAU,YAAY;AACpF,SAAO;AAAA,IAAE;AAAA,IAAM,UAAUA,MAAK,MAAM,UAAU;AAAA,IAAG,OAAOA,MAAK,MAAM,OAAO;AAAA,IACxE,UAAUA,MAAK,MAAM,SAAS,YAAY;AAAA,IAAG,SAASA,MAAK,MAAM,SAAS,uBAAuB;AAAA,IACjG,SAASA,MAAK,MAAM,aAAa;AAAA,IAAG,MAAMA,MAAK,MAAM,gBAAgB;AAAA,EAAE;AAC3E;AACA,SAAS,UAAU,MAAc,WAAoB,SAAgC;AACnF,QAAM,IAAI,UAAU,IAAI;AACxB,MAAI,EAAE,eAAe,MAAM,YAAY,CAAC,EAAE,YAAY,IAAI,CAAC,EAAE,OAAO,GAAI,MAAK,aAAa;AAC1F,MAAI,EAAE,QAAQ,WAAW,UAAU,QAAQ,EAAE,OAAO,QAAW,KAAM,OAAO,QAAQ,WAAW,cAAc,EAAE,QAAQ,QAAQ,OAAO,GAAK,MAAK,sBAAsB;AACtK,SAAO;AACT;AACA,SAAS,gBAAgB,SAAgC;AACvD,QAAM,QAAQ,uBAAuB,QAAQ,QAAQ;AACrD,QAAM,OAAO,cAAc,QAAQ,QAAQ;AAC3C,YAAU,MAAM,MAAM,OAAO;AAC7B,MAAI,OAAO;AACX,aAAW,QAAQ,SAAS,MAAM,MAAM,IAAI,EAAE,MAAM,GAAG,GAAG;AACxD,WAAOA,MAAK,MAAM,IAAI;AAAG,cAAU,MAAM,MAAM,OAAO;AAAA,EACxD;AACA,MAAI,aAAa,MAAM,IAAI,MAAM,MAAM,KAAM,MAAK,iBAAiB;AACnE,SAAO;AACT;AACA,SAAS,qBAAqB,SAAkB,SAAgC;AAC9E,QAAM,QAAQ,uBAAuB,QAAQ,QAAQ;AACrD,MAAI,QAAQ,WAAW,KAAK,CAAC,eAAe,KAAK,QAAQ,OAAO,KAAK,CAAC,iBAAiB,KAAK,QAAQ,cAAc,EAAG,MAAK,iBAAiB;AAC3I,QAAM,cAAcA,MAAK,MAAM,UAAU,QAAQ,OAAO;AACxD,YAAU,MAAM,UAAU,MAAM,OAAO;AAAG,YAAU,aAAa,MAAM,OAAO;AAC9E,QAAM,eAAeA,MAAK,aAAa,eAAe;AACtD,MAAI,UAAU,cAAc,OAAO,OAAO,EAAE,OAAO,OAAO,KAAM,MAAK,oBAAoB;AACzF,QAAM,QAAQ,aAAa,YAAY;AACvC,MAAI,IAAI,KAAK,MAAM,QAAQ,eAAgB,MAAK,kBAAkB;AAClE,QAAM,WAAW,KAAK,MAAM,MAAM,SAAS,CAAC;AAC5C,MAAI,SAAS,WAAW,KAAK,CAAC,eAAe,KAAK,SAAS,OAAO,KAAK,CAAC,QAAQ,QAAQ,WAAW,SAAS,UAAU,GAAG,KAAK,CAAC,MAAM,QAAQ,SAAS,KAAK,KAAK,SAAS,MAAM,SAAS,UAAW,MAAK,kBAAkB;AAC1N,QAAM,cAAcA,MAAK,aAAa,SAAS;AAC/C,YAAU,aAAa,MAAM,OAAO;AAAG,YAAUA,MAAK,aAAa,MAAM,GAAG,MAAM,OAAO;AACzF,QAAM,gBAAgBA,MAAK,aAAa,QAAQ,WAAW;AAAG,YAAU,eAAe,OAAO,OAAO;AACrG,SAAO,EAAE,SAAS,UAAU,aAAa,eAAe,UAAU,MAAM,SAAS;AACnF;AAEO,SAAS,sBAAsB,UAAiC,CAAC,GAAG;AACzE,QAAM,QAAQ,gBAAgB,OAAO;AACrC,YAAU,MAAM,OAAO,MAAM,OAAO;AAAG,YAAU,MAAM,UAAU,OAAO,OAAO;AAAG,YAAU,MAAM,SAAS,OAAO,OAAO;AACzH,MAAI,UAAU,MAAM,SAAS,OAAO,OAAO,EAAE,OAAO,KAAM,MAAK,mBAAmB;AAClF,QAAM,UAAU,KAAK,MAAM,aAAa,MAAM,SAAS,MAAM,CAAC;AAC9D,MAAI,OAAO,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,gCAAiC,MAAK,iBAAiB;AACrG,SAAO,qBAAqB,SAAS,OAAO;AAC9C;AAEO,SAAS,wBAAwB,eAAuB,UAAiC,CAAC,GAAG;AAClG,QAAM,QAAQ,uBAAuB,QAAQ,QAAQ;AACrD,MAAI,CAAC,WAAW,MAAM,OAAO,EAAG,QAAO;AACvC,QAAM,SAAS,sBAAsB,OAAO;AAG5C,SAAO,OAAO;AAChB;AAEO,IAAM,4BAA4B,EAAE,KAAK,MAAM,eAAe,WAAW,iBAAiB,sBAAsB,gBAAgB,eAAe;","names":["createHash","join","createHash","join"]}
1
+ {"version":3,"sources":["../../../packages/core/src/managedDeploymentRuntime.ts","../../../packages/core/src/platform.ts","../../../packages/core/src/managedDeploymentLegacy.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';\nimport { join, resolve, relative, isAbsolute, dirname, basename, sep } from 'node:path';\nimport { homedir } from 'node:os';\nimport { isWindows } from './platform.js';\nimport { legacyManagedRoot, legacyHomeFromRoot, selectExistingDeploymentRoot } from './managedDeploymentLegacy.js';\nimport type { runWindowsAcl } from './privateWindowsAcl.js';\n\nexport type FileRecord = { path: string; sha256: string; bytes: number };\nexport type Manifest = { schema: 1; version: string; files: FileRecord[] };\nexport type Pointer = { schema: 1; release: string; manifestSha256: string };\nexport type ManagedRuntimeOptions = { userHome?: string; root?: string; windows?: boolean; aclRunner?: typeof runWindowsAcl };\nconst sha = (data: string | Buffer) => createHash('sha256').update(data).digest('hex');\nconst releasePattern = /^\\d+\\.\\d+\\.\\d+-[a-f0-9]{20}$/;\nconst versionPattern = /^\\d+\\.\\d+\\.\\d+$/;\nconst MAX_FILES = 4096;\nfunction fail(reason: string): never { throw Error(`managed_deployment_${reason}`); }\nfunction canonicalHome(userHome = homedir()) {\n const path=resolve(userHome);\n // macOS /var -> /private/var and other OS-level aliases may precede the\n // user profile. Descendants of the canonical home still reject redirects.\n return existsSync(path) ? realpathSync(path) : path;\n}\nexport function managedDeploymentPaths(userHome = homedir(), explicitRoot?: string) {\n const home = canonicalHome(userHome);\n const modern = join(home, '.caveat-deploy', 'managed-v1');\n const legacy = legacyManagedRoot(home);\n const root = explicitRoot ?? selectExistingDeploymentRoot(home, modern);\n if (root !== modern && root !== legacy) fail('invalid_root');\n return { root, releases: join(root, 'releases'), entry: join(root, 'entry'),\n launcher: join(root, 'entry', 'caveat.mjs'), runtime: join(root, 'entry', 'managed-deployment.js'),\n pointer: join(root, 'active.json'), lock: join(root, 'installer.lock') };\n}\n/** The loaded entry selects its own protected root, independent of cwd/HOME. */\nexport function readManagedDeploymentAtRoot(root: string) {\n const path = resolve(root), parent = dirname(path);\n if (basename(path) !== 'managed-v1') fail('invalid_root');\n let userHome: string;\n if (basename(parent) === '.caveat-deploy') userHome = dirname(parent);\n else if (legacyHomeFromRoot(path)) userHome = legacyHomeFromRoot(path)!;\n else return fail('invalid_root');\n return readManagedDeployment({ userHome, root: path });\n}\nfunction ownerStat(path: string, directory: boolean, options: ManagedRuntimeOptions) {\n const s = lstatSync(path);\n if (s.isSymbolicLink() || (directory ? !s.isDirectory() : !s.isFile())) fail('unsafe_path');\n if (!(options.windows ?? isWindows()) && ((s.mode & 0o022) !== 0 || (typeof process.getuid === 'function' && s.uid !== process.getuid()))) fail('unsafe_owner_or_mode');\n return s;\n}\nfunction assertAncestors(options: ManagedRuntimeOptions) {\n const paths = managedDeploymentPaths(options.userHome, options.root);\n const home = canonicalHome(options.userHome);\n ownerStat(home, true, options);\n let path = home;\n for (const part of relative(home, paths.root).split(sep)) {\n path = join(path, part); ownerStat(path, true, options);\n }\n if (realpathSync(paths.root) !== paths.root) fail('root_redirected');\n return paths;\n}\nfunction checkReleaseMetadata(pointer: Pointer, options: ManagedRuntimeOptions) {\n const paths = managedDeploymentPaths(options.userHome, options.root);\n if (pointer.schema !== 1 || !releasePattern.test(pointer.release) || !/^[a-f0-9]{64}$/.test(pointer.manifestSha256)) fail('invalid_pointer');\n const releaseRoot = join(paths.releases, pointer.release);\n ownerStat(paths.releases, true, options); ownerStat(releaseRoot, true, options);\n const manifestPath = join(releaseRoot, 'manifest.json');\n if (ownerStat(manifestPath, false, options).size > 1024 * 1024) fail('manifest_too_large');\n const bytes = readFileSync(manifestPath);\n if (sha(bytes) !== pointer.manifestSha256) fail('manifest_changed');\n const manifest = JSON.parse(bytes.toString()) as Manifest;\n if (manifest.schema !== 1 || !versionPattern.test(manifest.version) || !pointer.release.startsWith(manifest.version + '-') || !Array.isArray(manifest.files) || manifest.files.length > MAX_FILES) fail('invalid_manifest');\n const packageRoot = join(releaseRoot, 'package');\n ownerStat(packageRoot, true, options); ownerStat(join(packageRoot, 'dist'), true, options);\n const cliScriptPath = join(packageRoot, 'dist', 'caveat.js'); ownerStat(cliScriptPath, false, options);\n return { pointer, manifest, packageRoot, cliScriptPath, launcher: paths.launcher };\n}\n/** Startup checks only: no process creation or package-wide content scan. */\nexport function readManagedDeployment(options: ManagedRuntimeOptions = {}) {\n const paths = assertAncestors(options);\n ownerStat(paths.entry, true, options); ownerStat(paths.launcher, false, options); ownerStat(paths.runtime, false, options);\n if (ownerStat(paths.pointer, false, options).size > 1024) fail('pointer_too_large');\n const pointer = JSON.parse(readFileSync(paths.pointer, 'utf8')) as Pointer;\n if (Object.keys(pointer).sort().join(',') !== 'manifestSha256,release,schema') fail('invalid_pointer');\n return checkReleaseMetadata(pointer, options);\n}\n/** Direct npm callers register the active stable entry when it owns this package. */\nexport function managedRegistrationPath(cliScriptPath: string, options: ManagedRuntimeOptions = {}) {\n const paths = managedDeploymentPaths(options.userHome, options.root);\n if (!existsSync(paths.pointer)) return cliScriptPath;\n const active = readManagedDeployment(options);\n // A managed installation explicitly selects its active release. An npm-bin\n // caller must not rewrite registrations back to its own version directory.\n return active.launcher;\n}\n\nexport const managedDeploymentInternal = { sha, fail, canonicalHome, ownerStat, assertAncestors, checkReleaseMetadata, versionPattern, releasePattern };\n","import type { Stats } from 'node:fs';\nimport { spawnSync } from 'node:child_process';\nimport { XMLParser } from 'fast-xml-parser';\nimport { createHash } from 'node:crypto';\nimport { join, isAbsolute, win32 } from 'node:path';\n\n/**\n * OS 依存の判定はこのモジュールに集約する。他のファイルで\n * `process.platform` / `win32` を直接分岐しない(データとして記録する\n * fingerprint 等は除く)。全関数は platform を引数で受けてテスト可能にする。\n */\n\nexport function isWindows(platform: NodeJS.Platform = process.platform): boolean {\n return platform === 'win32';\n}\n\n/**\n * env を考慮した Windows 判定。`OS=Windows_NT` は Windows native shell が必ず持つ\n * 環境変数で、テストが env 注入だけで Windows 経路を選べるようにする\n * (runtimeErrors が独自に持っていた判定をここへ集約。v0.17.5)。\n */\nexport function isWindowsEnv(\n env: NodeJS.ProcessEnv,\n platform: NodeJS.Platform = process.platform,\n): boolean {\n return env.OS === 'Windows_NT' || isWindows(platform);\n}\n\n/**\n * owner-only 所有チェック。Node の POSIX mode/uid フィールドは Windows では\n * ACL のビューにならないため、Windows は構造チェック(呼び出し側のマーカー・\n * スキーマ検証や親ディレクトリの ACL)に委ねて素通しする。\n */\nexport function isPrivateOwnerStat(\n stat: Stats,\n uid: number | undefined,\n platform: NodeJS.Platform = process.platform,\n): boolean {\n return isWindows(platform)\n || ((stat.mode & 0o077) === 0 && (uid === undefined || stat.uid === uid));\n}\n\n/**\n * Windows native Codex hook command は quoted Node パスを PowerShell で\n * 実行するため call operator `&` が必要(v0.17.1)。POSIX は不要。\n */\nexport function powershellCallPrefix(platform: NodeJS.Platform = process.platform): string {\n return isWindows(platform) ? '& ' : '';\n}\n\n/** PATH 走査で node 実行ファイルとして探す名前の候補。 */\nexport function nodeExecutableNames(platform: NodeJS.Platform = process.platform): string[] {\n return isWindows(platform)\n ? ['node.exe', 'node.cmd', 'node.bat', 'node']\n : ['node'];\n}\n\n/**\n * ファイルシステムが case-insensitive な Windows ではパス比較用に小文字化する。\n * 呼び出し側で区切り文字の正規化(\\\\ → /)を済ませてから渡す。\n */\nexport function normalizePathCase(\n normalizedPath: string,\n platform: NodeJS.Platform = process.platform,\n): string {\n return isWindows(platform) ? normalizedPath.toLowerCase() : normalizedPath;\n}\n\nexport interface ScheduleCommand { command: string; args: string[] }\n/** Resolve the current token, never a guessed USERDOMAIN/account pair. */\nexport function nativeScheduleUserId(\n username: string,\n platform: NodeJS.Platform = process.platform,\n run: (command: ScheduleCommand) => { status: number; stdout: string } = command => {\n const result = spawnSync(command.command, command.args, { encoding: 'utf8', timeout: 30_000, windowsHide: true });\n if (result.error) throw Error('schedule_identity_spawn_failed');\n return { status: result.status ?? -1, stdout: result.stdout ?? '' };\n },\n systemRoot = process.env.SystemRoot,\n): string {\n if (!isWindows(platform)) return username;\n let phase: 'root' | 'spawn' | 'exit' | 'output' = 'root';\n try {\n if (!systemRoot || !win32.isAbsolute(systemRoot) || /[\\x00-\\x1f]/.test(systemRoot)) throw Error('schedule_system_root_unavailable');\n phase = 'spawn';\n const result = run({ command: win32.join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'), args: ['-NoProfile', '-NonInteractive', '-Command',\n \"$ErrorActionPreference='Stop';try{[Console]::Write([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value);exit 0}catch{exit 1}\"] });\n phase = 'exit';\n if (result.status !== 0) throw Error('schedule_identity_exit_failed');\n phase = 'output';\n const sid = result.stdout.trim();\n if (/^S-1-\\d+(?:-\\d+){1,15}$/.test(sid)) return sid;\n } catch { /* Never fall back to environment/account-name guesses. */ }\n throw Error(`schedule_user_identity_unavailable_${phase}`);\n}\n\nexport interface NativeSyncSchedule {\n id: string;\n kind: 'systemd-user' | 'launchd-user' | 'windows-task';\n files: { path: string; content: string; encoding?: 'utf16le' }[];\n query: ScheduleCommand;\n inspect: ScheduleCommand;\n install: ScheduleCommand[];\n remove: ScheduleCommand[];\n reload: ScheduleCommand[];\n argv: string[];\n}\n/** Exact persisted bytes; absent encoding preserves legacy UTF-8 ownership. */\nexport function nativeScheduleFileBytes(file: NativeSyncSchedule['files'][number]): Buffer {\n if (file.encoding === 'utf16le') return Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from(file.content, 'utf16le')]);\n if (file.encoding !== undefined) throw Error('schedule_definition_encoding_invalid');\n return Buffer.from(file.content, 'utf8');\n}\n\nexport interface NativeSyncScheduleOptions {\n caveatHome: string; userHome: string; nodePath: string; cliPath: string;\n platform?: NodeJS.Platform; uid?: number; userId?: string;\n}\n\nconst xml = (value: string) => value.replace(/[&<>\"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&apos;' })[c]!);\nconst unitArg = (value: string) => `\"${value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/%/g, '%%').replace(/\\$/g, () => '$$')}\"`;\n/** Standard Windows argv quoting, without invoking cmd.exe or PowerShell. */\nexport function quoteWindowsArgument(value: string): string {\n return `\"${value.replace(/(\\\\*)\"/g, '$1$1\\\\\"').replace(/(\\\\+)$/g, '$1$1')}\"`;\n}\n\nexport function nativeSyncSchedule(opts: NativeSyncScheduleOptions): NativeSyncSchedule {\n const platform = opts.platform ?? process.platform;\n const absolute = platform === 'win32' ? win32.isAbsolute : isAbsolute;\n for (const value of [opts.caveatHome, opts.userHome, opts.nodePath, opts.cliPath]) {\n if (!absolute(value) || /[\\x00-\\x1f]/.test(value)) throw Error('schedule_absolute_path_required');\n }\n const suffix = createHash('sha256').update(normalizePathCase(opts.caveatHome, platform)).digest('hex').slice(0, 16);\n const id = `caveat-autosync-${suffix}`;\n const argv = [opts.nodePath, opts.cliPath, 'autosync', 'run', '--json', '--caveat-home', opts.caveatHome, '--user-home', opts.userHome];\n const command = (name: string, ...args: string[]): ScheduleCommand => ({ command: name, args });\n if (platform === 'linux') {\n const service = join(opts.userHome, '.config', 'systemd', 'user', `${id}.service`);\n const timer = join(opts.userHome, '.config', 'systemd', 'user', `${id}.timer`);\n return { id, kind: 'systemd-user', argv, files: [\n { path: service, content: `[Unit]\\nDescription=Caveat private knowledge reception\\n[Service]\\nType=oneshot\\nTimeoutStartSec=10min\\nExecStart=${argv.map(unitArg).join(' ')}\\nSuccessExitStatus=2\\n` },\n { path: timer, content: `[Unit]\\nDescription=Caveat reception timer\\n[Timer]\\nOnBootSec=60s\\nOnUnitInactiveSec=60s\\nUnit=${id}.service\\n[Install]\\nWantedBy=timers.target\\n` },\n ], query: command('systemctl', '--user', 'is-active', `${id}.timer`),\n inspect: command('systemctl', '--user', 'show', `${id}.timer`, '--property=LoadState', '--value'),\n install: [command('systemctl', '--user', 'link', service, timer), command('systemctl', '--user', 'daemon-reload'), command('systemctl', '--user', 'enable', '--now', `${id}.timer`)],\n remove: [command('systemctl', '--user', 'stop', `${id}.timer`, `${id}.service`), command('systemctl', '--user', 'disable', `${id}.timer`, `${id}.service`)],\n reload: [command('systemctl', '--user', 'daemon-reload')] };\n }\n if (platform === 'darwin') {\n if (!Number.isInteger(opts.uid) || opts.uid! < 0) throw Error('schedule_uid_required');\n const label = `dev.caveat.${id}`;\n const file = join(opts.userHome, 'Library', 'LaunchAgents', `${label}.plist`);\n const domain = `gui/${opts.uid}`;\n return { id: label, kind: 'launchd-user', argv, files: [{ path: file, content: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<plist version=\"1.0\"><dict><key>Label</key><string>${label}</string><key>ProgramArguments</key><array>${argv.map(a => `<string>${xml(a)}</string>`).join('')}</array><key>StartInterval</key><integer>60</integer></dict></plist>\\n` }],\n query: command('launchctl', 'print', `${domain}/${label}`),\n inspect: command('launchctl', 'print', `${domain}/${label}`),\n install: [command('launchctl', 'bootstrap', domain, file)],\n remove: [command('launchctl', 'bootout', `${domain}/${label}`)], reload: [] };\n }\n if (platform === 'win32') {\n if (!opts.userId || /[\\x00-\\x1f]/.test(opts.userId)) throw Error('schedule_user_id_required');\n const file = join(opts.caveatHome, 'sync', `${id}.xml`);\n // schtasks imports a BOM-marked UTF-16LE file, matching its Unicode XML\n // declaration; BOM-less UTF-8 corrupted non-ASCII paths on Windows.\n // Daily repetition avoids locale-sensitive date arguments to schtasks.\n const content = `<?xml version=\"1.0\" encoding=\"UTF-16\"?>\\n<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\"><RegistrationInfo><Description>Caveat private knowledge reception ${id}</Description></RegistrationInfo><Triggers><CalendarTrigger><Repetition><Interval>PT1M</Interval><Duration>P1D</Duration><StopAtDurationEnd>false</StopAtDurationEnd></Repetition><StartBoundary>2020-01-01T00:00:00</StartBoundary><Enabled>true</Enabled><ScheduleByDay><DaysInterval>1</DaysInterval></ScheduleByDay></CalendarTrigger></Triggers><Principals><Principal id=\"CurrentUser\"><UserId>${xml(opts.userId)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals><Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><ExecutionTimeLimit>PT10M</ExecutionTimeLimit><Enabled>true</Enabled></Settings><Actions Context=\"CurrentUser\"><Exec><Command>${xml(opts.nodePath)}</Command><Arguments>${xml(argv.slice(1).map(quoteWindowsArgument).join(' '))}</Arguments></Exec></Actions></Task>\\n`;\n return { id, kind: 'windows-task', argv, files: [{ path: file, content, encoding: 'utf16le' }],\n query: command('powershell.exe', '-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(\n `$ErrorActionPreference = 'Stop'; try { $scheduler = New-Object -ComObject Schedule.Service; $scheduler.Connect(); $task = $scheduler.GetFolder('\\\\').GetTask('${id}'); $bytes = (New-Object System.Text.UTF8Encoding($false)).GetBytes($task.Xml); $stdout = [Console]::OpenStandardOutput(); $stdout.Write($bytes, 0, $bytes.Length); $stdout.Flush(); exit 0 } catch { exit 1 }`, 'utf16le').toString('base64')),\n inspect: command('powershell.exe', '-NoProfile', '-NonInteractive', '-Command',\n `try { $scheduler = New-Object -ComObject Schedule.Service; $scheduler.Connect(); $null = $scheduler.GetFolder('\\\\').GetTask('${id}'); exit 0 } catch { $failure = $_.Exception; while ($failure.InnerException) { $failure = $failure.InnerException }; if ($failure.HResult -eq -2147024894) { exit 3 }; exit 1 }`),\n install: [command('schtasks.exe', '/Create', '/TN', id, '/XML', file)],\n remove: [command('schtasks.exe', '/Delete', '/TN', id, '/F')], reload: [] };\n }\n throw Error('schedule_platform_unsupported');\n}\n\n/** Presence only; command failures are not a successful registration. */\nexport function nativeSchedulePresent(kind: NativeSyncSchedule['kind'], status: number, stdout: string): boolean {\n if (kind === 'systemd-user') {\n if (stdout.trim() === 'loaded') return true;\n if (stdout.trim() === 'not-found') return false;\n throw Error('schedule_inspection_failed');\n }\n if (status === 0) return true;\n if (kind === 'launchd-user' && status === 113) return false;\n // The fixed COM probe maps only HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)\n // to 3. Access denial/service errors must never mean an absent task.\n if (kind === 'windows-task' && status === 3) return false;\n throw Error('schedule_inspection_failed');\n}\n\n/** Compare the native readback without depending on locale-specific error text. */\nexport function nativeScheduleReadbackMatches(plan: NativeSyncSchedule, stdout: string): boolean {\n if (plan.kind !== 'windows-task') return true;\n try {\n const parser = new XMLParser({ ignoreAttributes: false, removeNSPrefix: true, trimValues: false, parseTagValue: false });\n const actual = parser.parse(stdout).Task;\n const expected = parser.parse(plan.files[0]!.content).Task;\n // Task Scheduler exports omit these schema defaults. Explicit changes still fail.\n // https://learn.microsoft.com/windows/win32/taskschd/task-scheduler-schema\n const defaulted = (value: unknown, fallback: string) => value === undefined ? fallback : value;\n const triggerFields = (task: typeof actual) => {\n const trigger = task.Triggers.CalendarTrigger;\n return [trigger.StartBoundary, defaulted(trigger.Enabled, 'true'), trigger.ScheduleByDay.DaysInterval,\n trigger.Repetition.Interval, trigger.Repetition.Duration, defaulted(trigger.Repetition.StopAtDurationEnd, 'false')];\n };\n const values = (task: typeof actual) => [task.Actions.Exec.Command, task.Actions.Exec.Arguments,\n ...triggerFields(task), task.Principals.Principal.LogonType, defaulted(task.Principals.Principal.RunLevel, 'LeastPrivilege'),\n task.Settings.MultipleInstancesPolicy, task.Settings.ExecutionTimeLimit, defaulted(task.Settings.Enabled, 'true')];\n return JSON.stringify(values(actual)) === JSON.stringify(values(expected));\n } catch { return false; }\n}\n\n/** systemd can load linked units before enabling their timer succeeds. */\nexport function nativeScheduleCanRecover(plan: NativeSyncSchedule, readback: () => boolean): boolean {\n return plan.kind === 'systemd-user' || readback();\n}\n","// Published 0.21.1 compatibility only. New deployments use the private home root.\nimport { lstatSync } from 'node:fs';\nfunction existsSync(path: string) { try { lstatSync(path); return true; } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false; throw error; } }\nimport { join, dirname, basename } from 'node:path';\nexport const legacyEntryHashes = {\n 'caveat.mjs': '6e39713485eec74d8348f9a20d6b720c8a47ea5b7c485348953ca96031f284d8',\n 'managed-deployment.js': '553762cc5e180c0da3ef856150530e9b7ba225ed0a6537afd1fd6e07aceab738',\n};\nexport const legacyManagedRoot = (home: string) => join(home, '.local', 'share', 'caveat', 'managed-v1');\nexport function legacyHomeFromRoot(root: string): string | undefined {\n const parent = dirname(root);\n return basename(root) === 'managed-v1' && basename(parent) === 'caveat' && basename(dirname(parent)) === 'share' && basename(dirname(dirname(parent))) === '.local'\n ? dirname(dirname(dirname(parent))) : undefined;\n}\n/** Selection never changes the safety checks or replaces an existing reader. */\nexport function selectExistingDeploymentRoot(home: string, modern: string) {\n const legacy = legacyManagedRoot(home);\n const modernActive = existsSync(join(modern, 'active.json')), legacyActive = existsSync(join(legacy, 'active.json'));\n if (modernActive && legacyActive) throw Error('managed_deployment_multiple_active_roots');\n if (modernActive) return modern;\n if (legacyActive) return legacy;\n const modernExists = existsSync(modern), legacyExists = existsSync(legacy);\n if (modernExists && legacyExists) throw Error('managed_deployment_multiple_staging_roots');\n return legacyExists ? legacy : modern;\n}\n"],"mappings":"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","names":["createHash","existsSync","lstatSync","join","dirname","basename","join","join","createHash","existsSync","join","dirname","basename","lstatSync"]}