@detergent-software/conductor 3.0.0-dev.9 → 3.1.0-dev.1

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 (212) hide show
  1. package/CHANGELOG.md +486 -0
  2. package/README.md +272 -126
  3. package/build/brief-cli.js +5 -12
  4. package/build/brief-cli.js.map +1 -1
  5. package/build/brief.js +4 -3
  6. package/build/brief.js.map +1 -1
  7. package/build/broker-cli.js +41 -14
  8. package/build/broker-cli.js.map +1 -1
  9. package/build/broker.js +1 -1
  10. package/build/broker.js.map +1 -1
  11. package/build/cli.js +226 -64
  12. package/build/cli.js.map +1 -1
  13. package/build/clones.js +92 -0
  14. package/build/clones.js.map +1 -0
  15. package/build/code-host-cli.js +172 -40
  16. package/build/code-host-cli.js.map +1 -1
  17. package/build/code-host-service.js +35 -2
  18. package/build/code-host-service.js.map +1 -1
  19. package/build/code-host.js +138 -16
  20. package/build/code-host.js.map +1 -1
  21. package/build/conditions.js +33 -16
  22. package/build/conditions.js.map +1 -1
  23. package/build/config.js +58 -28
  24. package/build/config.js.map +1 -1
  25. package/build/console.js +17 -27
  26. package/build/console.js.map +1 -1
  27. package/build/credentials.js +86 -54
  28. package/build/credentials.js.map +1 -1
  29. package/build/daemon.js +54 -65
  30. package/build/daemon.js.map +1 -1
  31. package/build/file-lock.js +155 -40
  32. package/build/file-lock.js.map +1 -1
  33. package/build/follow-ups.js +2 -2
  34. package/build/follow-ups.js.map +1 -1
  35. package/build/help.js +242 -83
  36. package/build/help.js.map +1 -1
  37. package/build/history.js +10 -4
  38. package/build/history.js.map +1 -1
  39. package/build/host/attach-clients.js +56 -0
  40. package/build/host/attach-clients.js.map +1 -0
  41. package/build/host/credential-manager.js +109 -0
  42. package/build/host/credential-manager.js.map +1 -0
  43. package/build/host/keychain.js +44 -0
  44. package/build/host/keychain.js.map +1 -0
  45. package/build/host/launchd.js +118 -0
  46. package/build/host/launchd.js.map +1 -0
  47. package/build/host/libsecret.js +40 -0
  48. package/build/host/libsecret.js.map +1 -0
  49. package/build/host/macos-terminal.js +63 -0
  50. package/build/host/macos-terminal.js.map +1 -0
  51. package/build/host/platform.js +4 -0
  52. package/build/host/platform.js.map +1 -0
  53. package/build/host/secrets.js +46 -0
  54. package/build/host/secrets.js.map +1 -0
  55. package/build/host/shell.js +26 -0
  56. package/build/host/shell.js.map +1 -0
  57. package/build/host/startup.js +26 -0
  58. package/build/host/startup.js.map +1 -0
  59. package/build/host/systemd.js +91 -0
  60. package/build/host/systemd.js.map +1 -0
  61. package/build/host/task-scheduler.js +78 -0
  62. package/build/host/task-scheduler.js.map +1 -0
  63. package/build/host/terminal.js +47 -0
  64. package/build/host/terminal.js.map +1 -0
  65. package/build/host/tmux.js +39 -0
  66. package/build/host/tmux.js.map +1 -0
  67. package/build/host/windows-terminal.js +46 -0
  68. package/build/host/windows-terminal.js.map +1 -0
  69. package/build/in-process.js +5 -4
  70. package/build/in-process.js.map +1 -1
  71. package/build/inbox.js +47 -35
  72. package/build/inbox.js.map +1 -1
  73. package/build/init.js +509 -0
  74. package/build/init.js.map +1 -0
  75. package/build/landing.js +107 -10
  76. package/build/landing.js.map +1 -1
  77. package/build/lib/providers/ado/code-host.js +5 -1
  78. package/build/lib/providers/ado/code-host.js.map +1 -1
  79. package/build/lib/providers/ado/pipelines.js +138 -26
  80. package/build/lib/providers/ado/pipelines.js.map +1 -1
  81. package/build/lib/providers/ado/trigger.js +5 -0
  82. package/build/lib/providers/ado/trigger.js.map +1 -1
  83. package/build/lib/providers/ado/writes.js +17 -2
  84. package/build/lib/providers/ado/writes.js.map +1 -1
  85. package/build/lib/providers/github/actions.js +90 -3
  86. package/build/lib/providers/github/actions.js.map +1 -1
  87. package/build/lib/providers/github/code-host.js +2 -0
  88. package/build/lib/providers/github/code-host.js.map +1 -1
  89. package/build/lib/providers/github/writes.js +25 -2
  90. package/build/lib/providers/github/writes.js.map +1 -1
  91. package/build/lib/providers/index.js +3 -2
  92. package/build/lib/providers/index.js.map +1 -1
  93. package/build/lib/providers/npm/stage.js +14 -1
  94. package/build/lib/providers/npm/stage.js.map +1 -1
  95. package/build/lib/providers/timing.js +10 -0
  96. package/build/lib/providers/timing.js.map +1 -0
  97. package/build/lib/providers/verdicts/pr.js +47 -6
  98. package/build/lib/providers/verdicts/pr.js.map +1 -1
  99. package/build/lib/providers/verdicts/runs.js +12 -2
  100. package/build/lib/providers/verdicts/runs.js.map +1 -1
  101. package/build/logins.js +13 -6
  102. package/build/logins.js.map +1 -1
  103. package/build/manager.js +200 -55
  104. package/build/manager.js.map +1 -1
  105. package/build/note.js +5 -15
  106. package/build/note.js.map +1 -1
  107. package/build/notes.js +130 -23
  108. package/build/notes.js.map +1 -1
  109. package/build/notify.js +104 -42
  110. package/build/notify.js.map +1 -1
  111. package/build/npm-reads.js +151 -22
  112. package/build/npm-reads.js.map +1 -1
  113. package/build/ownership.js +2 -2
  114. package/build/paths.js +44 -1
  115. package/build/paths.js.map +1 -1
  116. package/build/plugin.js +4 -2
  117. package/build/plugin.js.map +1 -1
  118. package/build/profile-fields.js +153 -0
  119. package/build/profile-fields.js.map +1 -0
  120. package/build/profile-layers.js +264 -0
  121. package/build/profile-layers.js.map +1 -0
  122. package/build/profile.js +69 -49
  123. package/build/profile.js.map +1 -1
  124. package/build/project-check-cli.js +17 -19
  125. package/build/project-check-cli.js.map +1 -1
  126. package/build/project-check.js +166 -104
  127. package/build/project-check.js.map +1 -1
  128. package/build/protocol.js +5 -21
  129. package/build/protocol.js.map +1 -1
  130. package/build/pushover.js +5 -5
  131. package/build/pushover.js.map +1 -1
  132. package/build/queue.js +4 -4
  133. package/build/queue.js.map +1 -1
  134. package/build/registry.js +195 -36
  135. package/build/registry.js.map +1 -1
  136. package/build/release-kinds.js +34 -0
  137. package/build/release-kinds.js.map +1 -0
  138. package/build/rule-actions.js +8 -6
  139. package/build/rule-actions.js.map +1 -1
  140. package/build/rules-service.js +83 -44
  141. package/build/rules-service.js.map +1 -1
  142. package/build/rules.js +23 -23
  143. package/build/rules.js.map +1 -1
  144. package/build/run.js +20 -7
  145. package/build/run.js.map +1 -1
  146. package/build/runs.js +218 -68
  147. package/build/runs.js.map +1 -1
  148. package/build/scheduler.js +78 -12
  149. package/build/scheduler.js.map +1 -1
  150. package/build/search.js +2 -1
  151. package/build/search.js.map +1 -1
  152. package/build/server.js +51 -14
  153. package/build/server.js.map +1 -1
  154. package/build/spend.js +1 -1
  155. package/build/spend.js.map +1 -1
  156. package/build/store.js +79 -20
  157. package/build/store.js.map +1 -1
  158. package/build/supervise.js +1 -1
  159. package/build/supervise.js.map +1 -1
  160. package/build/timeline.js +104 -0
  161. package/build/timeline.js.map +1 -0
  162. package/build/tracker-reads-cli.js +4 -3
  163. package/build/tracker-reads-cli.js.map +1 -1
  164. package/build/trackers.js +56 -10
  165. package/build/trackers.js.map +1 -1
  166. package/build/train-cli.js +6 -5
  167. package/build/train-cli.js.map +1 -1
  168. package/build/train.js +2 -2
  169. package/build/train.js.map +1 -1
  170. package/build/upgrade.js +8 -9
  171. package/build/upgrade.js.map +1 -1
  172. package/build/wait.js +10 -10
  173. package/build/wait.js.map +1 -1
  174. package/build/watchers/ado.js +42 -19
  175. package/build/watchers/ado.js.map +1 -1
  176. package/build/watchers/github.js +18 -4
  177. package/build/watchers/github.js.map +1 -1
  178. package/build/watchers/index.js +2 -2
  179. package/build/watchers/index.js.map +1 -1
  180. package/build/watchers/linear-queue.js +47 -14
  181. package/build/watchers/linear-queue.js.map +1 -1
  182. package/build/watchers/linear-writes.js +26 -12
  183. package/build/watchers/linear-writes.js.map +1 -1
  184. package/build/watchers/linear.js +2 -2
  185. package/build/watchers/linear.js.map +1 -1
  186. package/build/watchers/source.js +5 -1
  187. package/build/watchers/source.js.map +1 -1
  188. package/build/webhook.js +80 -0
  189. package/build/webhook.js.map +1 -0
  190. package/build/why.js +1 -1
  191. package/build/why.js.map +1 -1
  192. package/build/worktrees.js +56 -93
  193. package/build/worktrees.js.map +1 -1
  194. package/package.json +61 -61
  195. package/plugin/.claude-plugin/plugin.json +1 -1
  196. package/plugin/hooks/model.ts +55 -6
  197. package/plugin/hooks/register.tsx +43 -4
  198. package/plugin/skills/conductor-console/SKILL.md +26 -27
  199. package/plugin/skills/work-item/SKILL.md +58 -54
  200. package/plugin/skills/work-item/trackers/ado-boards.md +2 -2
  201. package/plugin/skills/work-item/trackers/github-issues.md +1 -1
  202. package/plugin/skills/work-item/trackers/linear.md +2 -2
  203. package/plugin/types/index.d.ts +1 -1
  204. package/protocol/PROTOCOL.md +3 -9
  205. package/build/checkout-sync.js +0 -249
  206. package/build/checkout-sync.js.map +0 -1
  207. package/build/credential.js +0 -66
  208. package/build/credential.js.map +0 -1
  209. package/build/startup.js +0 -92
  210. package/build/startup.js.map +0 -1
  211. package/build/tab.js +0 -32
  212. package/build/tab.js.map +0 -1
@@ -0,0 +1,109 @@
1
+ // Windows generic credentials (Credential Manager). Node can't read Credential Manager and `cmdkey`
2
+ // can't print a secret, so PowerShell calls advapi32's CredRead. The secret is only returned: never
3
+ // logged, stored or put on a command line.
4
+ // The machine credentials the conductor named with an ATK prefix before ATK-529. A read of the new
5
+ // name that finds none copies the old one to it, once, with its user name, secret and persistence,
6
+ // and leaves the old one where it is. After that the new one is found, so nothing reads the old name.
7
+ export const RENAMED_TARGETS = {
8
+ 'conductor-linear': 'atk-conductor-linear',
9
+ 'conductor-pushover': 'atk-conductor-pushover',
10
+ };
11
+ const NOT_FOUND = 3;
12
+ // Win32 ERROR_NOT_FOUND: CredRead's "no such credential". Any other failure is a broken read.
13
+ const ERROR_NOT_FOUND = 1168;
14
+ // Writes the user name, a newline, then the password. With CONDUCTOR_CRED_FROM set, a missing target
15
+ // is copied from that credential first; a copy that fails still returns the secret, and the next read
16
+ // tries the copy again.
17
+ const CRED_READ = `
18
+ $ErrorActionPreference = 'Stop'
19
+ try {
20
+ Add-Type -Namespace Conductor -Name Cred -MemberDefinition @'
21
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
22
+ public static extern bool CredRead(string target, int type, int flags, out System.IntPtr cred);
23
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
24
+ public static extern bool CredWrite(ref CREDENTIAL cred, int flags);
25
+ [DllImport("advapi32.dll")]
26
+ public static extern void CredFree(System.IntPtr cred);
27
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
28
+ public struct CREDENTIAL { public int Flags; public int Type; public string TargetName; public string Comment;
29
+ public long LastWritten; public int CredentialBlobSize; public System.IntPtr CredentialBlob; public int Persist;
30
+ public int AttributeCount; public System.IntPtr Attributes; public string TargetAlias; public string UserName; }
31
+ '@
32
+ $p = [IntPtr]::Zero
33
+ $copy = $false
34
+ if (-not [Conductor.Cred]::CredRead($env:CONDUCTOR_CRED_TARGET, 1, 0, [ref]$p)) {
35
+ $code = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
36
+ if ($code -ne ${ERROR_NOT_FOUND}) { [Console]::Error.WriteLine("CredRead failed with Win32 error $code"); exit 4 }
37
+ if (-not $env:CONDUCTOR_CRED_FROM) { exit ${NOT_FOUND} }
38
+ if (-not [Conductor.Cred]::CredRead($env:CONDUCTOR_CRED_FROM, 1, 0, [ref]$p)) {
39
+ $code = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
40
+ if ($code -eq ${ERROR_NOT_FOUND}) { exit ${NOT_FOUND} }
41
+ [Console]::Error.WriteLine("CredRead failed with Win32 error $code"); exit 4
42
+ }
43
+ $copy = $true
44
+ }
45
+ try {
46
+ $c = [Runtime.InteropServices.Marshal]::PtrToStructure($p, [type][Conductor.Cred+CREDENTIAL])
47
+ if ($copy) {
48
+ $n = $c
49
+ $n.Flags = 0; $n.TargetName = $env:CONDUCTOR_CRED_TARGET; $n.TargetAlias = $null; $n.LastWritten = 0
50
+ $n.AttributeCount = 0; $n.Attributes = [IntPtr]::Zero
51
+ if (-not [Conductor.Cred]::CredWrite([ref]$n, 0)) {
52
+ [Console]::Error.WriteLine("CredWrite failed with Win32 error " + [Runtime.InteropServices.Marshal]::GetLastWin32Error())
53
+ }
54
+ }
55
+ [Console]::Out.Write([string]$c.UserName + [char]10)
56
+ [Console]::Out.Write([Runtime.InteropServices.Marshal]::PtrToStringUni($c.CredentialBlob, $c.CredentialBlobSize / 2))
57
+ } finally { [Conductor.Cred]::CredFree($p) }
58
+ } catch { [Console]::Error.WriteLine($_.Exception.Message); exit 4 }
59
+ `;
60
+ export function credentialManager(run, renamed = RENAMED_TARGETS) {
61
+ // Whether `target` is saved, from `cmdkey /list:<target>`, which prints no secret and starts no
62
+ // PowerShell.
63
+ const listed = async (target, env) => {
64
+ const result = await run('cmdkey', [`/list:${target}`], { env, timeoutMs: 15_000 });
65
+ if (result.code !== 0)
66
+ return 'unknown';
67
+ if (/\*\s*NONE\s*\*/.test(result.stdout))
68
+ return 'missing';
69
+ return /^\s*Target:/m.test(result.stdout) ? 'saved' : 'unknown';
70
+ };
71
+ return {
72
+ name: 'Credential Manager',
73
+ read: async (target, env) => {
74
+ const from = renamed[target];
75
+ const result = await run('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(CRED_READ, 'utf16le').toString('base64')], {
76
+ // Always set, so a CONDUCTOR_CRED_FROM in the daemon's own env can never start a copy.
77
+ env: { ...env, CONDUCTOR_CRED_FROM: from ?? '', CONDUCTOR_CRED_TARGET: target },
78
+ timeoutMs: 30_000,
79
+ });
80
+ if (result.code === 0) {
81
+ const newline = result.stdout.indexOf('\n');
82
+ const secret = result.stdout.slice(newline + 1).trim();
83
+ if (newline >= 0 && secret)
84
+ return { credential: { secret, user: result.stdout.slice(0, newline).trim() }, found: true };
85
+ }
86
+ if (result.code === NOT_FOUND)
87
+ return { found: false, missing: true, reason: `no credential ${target}` };
88
+ // Never echo stdout here: on a partial failure it could hold the secret.
89
+ const line = result.stderr
90
+ .split(/\r?\n/)
91
+ .map((l) => l.trim())
92
+ .find((l) => l && !l.startsWith('#< CLIXML') && !l.startsWith('<Objs'));
93
+ return {
94
+ found: false,
95
+ missing: false,
96
+ reason: `the credential ${target} couldn't be read (${result.error ?? line ?? `exit ${result.code}`})`,
97
+ };
98
+ },
99
+ saveCommand: (target, user) => `cmdkey /generic:${target} /user:${user} /pass`,
100
+ // A new name that isn't saved yet counts as saved while its old name is, so the read that
101
+ // copies it runs.
102
+ saved: async (target, env) => {
103
+ const saved = await listed(target, env);
104
+ const from = renamed[target];
105
+ return saved === 'missing' && from !== undefined ? listed(from, env) : saved;
106
+ },
107
+ };
108
+ }
109
+ //# sourceMappingURL=credential-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-manager.js","sourceRoot":"","sources":["../../src/host/credential-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ // macOS generic passwords in the login keychain, through `security`: the target is the item's
2
+ // service, its user name the account. `security` answers 44 for an item that isn't there.
3
+ const NOT_FOUND = 44;
4
+ // The item's account from `security find-generic-password`'s attributes: `"acct"<blob>="linear"`.
5
+ // An account saved as hex (not printable) reads as none.
6
+ export function account(attributes) {
7
+ return /"acct"<blob>="((?:[^"\\]|\\.)*)"/.exec(attributes)?.[1] ?? '';
8
+ }
9
+ export function keychain(run, keychainFile) {
10
+ // A keychain file of its own, for the real-OS tests; else the user's search list.
11
+ const where = keychainFile === undefined ? [] : [keychainFile];
12
+ const find = (args, env) => run('security', ['find-generic-password', ...args, ...where], { env, timeoutMs: 30_000 });
13
+ // Never echo stdout in a reason: on a partial failure it could hold the secret.
14
+ const failed = (target, result) => {
15
+ const why = result.error ?? (result.stderr.trim() || `exit ${result.code}`);
16
+ if (/ENOENT/.test(why))
17
+ return { found: false, missing: true, reason: 'no `security` command to ask' };
18
+ return { found: false, missing: false, reason: `the keychain item ${target} couldn't be read (${why})` };
19
+ };
20
+ return {
21
+ name: 'the login keychain',
22
+ // The attributes first (no secret is printed without -w), then the secret of the item with that
23
+ // account, so the two come from one item even when another has the same service.
24
+ read: async (target, env) => {
25
+ const attributes = await find(['-s', target], env);
26
+ if (attributes.code === NOT_FOUND)
27
+ return { found: false, missing: true, reason: `no keychain item ${target}` };
28
+ if (attributes.code !== 0)
29
+ return failed(target, attributes);
30
+ const user = account(attributes.stdout);
31
+ const result = await find(['-s', target, ...(user ? ['-a', user] : []), '-w'], env);
32
+ const secret = result.stdout.replace(/\r?\n$/, '');
33
+ if (result.code !== 0 || !secret)
34
+ return failed(target, result);
35
+ return { credential: { secret, user }, found: true };
36
+ },
37
+ // Delete first: an item is one per service and account, so saving under another user name would
38
+ // leave the old item beside it. -w last with no value: security asks for the password.
39
+ saveCommand: (target, user) => `security delete-generic-password -s ${target}; security add-generic-password -s ${target} -a ${user} -w`,
40
+ // A missing item costs read one call, the same as asking first.
41
+ saved: async () => 'unknown',
42
+ };
43
+ }
44
+ //# sourceMappingURL=keychain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../src/host/keychain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { ConductorError } from "../errors.js";
4
+ import { outputOf } from "../run.js";
5
+ export const LAUNCHD_LABEL = 'software.detergent.conductor';
6
+ // How long launchd gives supervise to stop its daemon after a SIGTERM before it kills it: a stop
7
+ // waits up to 20 s, and launchd's own default is 20 s.
8
+ const EXIT_TIMEOUT_S = 60;
9
+ const UNLOAD_POLL_MS = 250;
10
+ // A launchd agent in ~/Library/LaunchAgents that runs `conduct supervise` as the user logs in
11
+ // (RunAtLoad). launchd loads it into the login session's gui/<uid> domain, so loading it starts it
12
+ // too. supervise restarts the daemon and conduct stop must stick, so no KeepAlive; and launchd
13
+ // would kill the job's process group as supervise ends, workers included, without
14
+ // AbandonProcessGroup. Its output goes to ~/.conductor/logs/startup.log.
15
+ // Unloading signals supervise and returns before launchd has removed the job, so the reload waits
16
+ // until it has: by then supervise has stopped its daemon and the lock is free.
17
+ export function launchd({ home, run, uid }) {
18
+ const plist = join(home, 'Library', 'LaunchAgents', `${LAUNCHD_LABEL}.plist`);
19
+ const domain = `gui/${uid}`;
20
+ const service = `${domain}/${LAUNCHD_LABEL}`;
21
+ const launchctl = (args) => run('launchctl', args, { timeoutMs: 60_000 });
22
+ const loaded = async () => (await launchctl(['print', service])).code === 0;
23
+ const bootstrap = async () => {
24
+ const result = await launchctl(['bootstrap', domain, plist]);
25
+ if (result.code !== 0)
26
+ throw launchctlFailed('could not load the launchd agent', result);
27
+ };
28
+ const text = () => (existsSync(plist) ? readFileSync(plist, 'utf8') : undefined);
29
+ return {
30
+ install: async (spec) => {
31
+ const logs = join(home, '.conductor', 'logs');
32
+ mkdirSync(dirname(plist), { recursive: true });
33
+ // launchd opens the log before supervise runs, and makes no folder for it.
34
+ mkdirSync(logs, { recursive: true });
35
+ writeFileSync(plist, launchdPlist(spec, join(logs, 'startup.log')));
36
+ // A loaded agent keeps the definition it was loaded with, so it goes first: that ends the
37
+ // daemon it started, and loading starts it again.
38
+ if (await loaded()) {
39
+ const unloaded = await launchctl(['bootout', service]);
40
+ const until = Date.now() + (EXIT_TIMEOUT_S + 30) * 1000;
41
+ while (await loaded()) {
42
+ if (Date.now() > until)
43
+ throw launchctlFailed('could not unload the launchd agent', unloaded);
44
+ await new Promise((done) => setTimeout(done, UNLOAD_POLL_MS));
45
+ }
46
+ }
47
+ await bootstrap();
48
+ return ['launchd started it as it loaded it'];
49
+ },
50
+ name: `the launchd agent ${LAUNCHD_LABEL}`,
51
+ // The plist alone goes: the loaded agent stays until logout, so a daemon it started keeps
52
+ // running, as on Windows.
53
+ remove: async () => {
54
+ if (!existsSync(plist))
55
+ return false;
56
+ rmSync(plist);
57
+ return true;
58
+ },
59
+ // An agent unloaded since logon (or never loaded) starts as it loads. kickstart does nothing to
60
+ // a supervise still ending, and the start falls back after its wait, as with the Windows task.
61
+ run: async () => {
62
+ if (!(await loaded()))
63
+ return bootstrap();
64
+ const result = await launchctl(['kickstart', service]);
65
+ if (result.code !== 0)
66
+ throw launchctlFailed('could not start the launchd agent', result);
67
+ },
68
+ runsCli: async (cliPath) => text()?.includes(`<string>${xml(cliPath)}</string>`) ?? false,
69
+ supervises: async () => /<string>supervise<\/string>\s*<\/array>/.test(text() ?? ''),
70
+ };
71
+ }
72
+ // The agent's plist.
73
+ export function launchdPlist({ cliPath, nodePath, path }, log) {
74
+ const string = (value) => `<string>${xml(value)}</string>`;
75
+ return [
76
+ '<?xml version="1.0" encoding="UTF-8"?>',
77
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
78
+ '<plist version="1.0">',
79
+ '<dict>',
80
+ ' <key>Label</key>',
81
+ ` ${string(LAUNCHD_LABEL)}`,
82
+ ' <key>ProgramArguments</key>',
83
+ ' <array>',
84
+ ...[nodePath, cliPath, 'supervise'].map((arg) => ` ${string(arg)}`),
85
+ ' </array>',
86
+ ' <key>EnvironmentVariables</key>',
87
+ ' <dict>',
88
+ ' <key>PATH</key>',
89
+ ` ${string(path)}`,
90
+ ' </dict>',
91
+ ' <key>RunAtLoad</key>',
92
+ ' <true/>',
93
+ ' <key>AbandonProcessGroup</key>',
94
+ ' <true/>',
95
+ ' <key>ExitTimeOut</key>',
96
+ ` <integer>${EXIT_TIMEOUT_S}</integer>`,
97
+ ' <key>StandardOutPath</key>',
98
+ ` ${string(log)}`,
99
+ ' <key>StandardErrorPath</key>',
100
+ ` ${string(log)}`,
101
+ '</dict>',
102
+ '</plist>',
103
+ '',
104
+ ].join('\n');
105
+ }
106
+ function launchctlFailed(what, result) {
107
+ if (/ENOENT/.test(result.error ?? '')) {
108
+ return new ConductorError(`${what}: no launchctl`, ['start `conduct supervise` from your own login items']);
109
+ }
110
+ return new ConductorError(what, [
111
+ outputOf(result, 1000),
112
+ 'launchd loads an agent into a logged-in session: run it in a terminal on the Mac, not over ssh alone',
113
+ ]);
114
+ }
115
+ function xml(text) {
116
+ return text.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
117
+ }
118
+ //# sourceMappingURL=launchd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launchd.js","sourceRoot":"","sources":["../../src/host/launchd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ // Linux secrets in the Secret Service (GNOME Keyring, KeePassXC and others), through libsecret's
2
+ // `secret-tool`: an item with the attributes service=conductor and target=<target>, its user name in
3
+ // a `user` attribute. secret-tool needs a D-Bus session with a Secret Service on it; a headless box
4
+ // without one can't read, and the env fallback (secrets.ts) stands in.
5
+ const SERVICE = 'conductor';
6
+ export function libsecret(run) {
7
+ const tool = (args, env) => run('secret-tool', args, { env, timeoutMs: 30_000 });
8
+ const lookup = (target, env) => tool(['lookup', 'service', SERVICE, 'target', target], env);
9
+ return {
10
+ name: 'libsecret',
11
+ read: async (target, env) => {
12
+ const result = await lookup(target, env);
13
+ const secret = result.stdout.replace(/\r?\n$/, '');
14
+ if (result.code === 0 && secret) {
15
+ // search prints the item's attributes, and its secret, which only the user line is read from.
16
+ const found = await tool(['search', 'service', SERVICE, 'target', target], env);
17
+ return { credential: { secret, user: userOf(`${found.stdout}\n${found.stderr}`) }, found: true };
18
+ }
19
+ if (result.error !== null && /ENOENT/.test(result.error))
20
+ return { found: false, missing: true, reason: 'no secret-tool (libsecret-tools) to ask' };
21
+ // lookup exits 1 and says nothing for an item that isn't there.
22
+ if (result.code === 1 && !result.stderr.trim())
23
+ return { found: false, missing: true, reason: `no libsecret item ${target}` };
24
+ // Never echo stdout here: on a partial failure it could hold the secret.
25
+ const why = result.error ?? (result.stderr.trim().split(/\r?\n/)[0] || `exit ${result.code}`);
26
+ return { found: false, missing: false, reason: `the libsecret item ${target} couldn't be read (${why})` };
27
+ },
28
+ // Clear first: store replaces only an item whose attributes are all the same, so saving under
29
+ // another user name would leave the old item beside it, and lookup and search could each pick
30
+ // another one. secret-tool store asks for the secret.
31
+ saveCommand: (target, user) => `secret-tool clear service ${SERVICE} target ${target}; secret-tool store --label=${target} service ${SERVICE} target ${target} user ${user}`,
32
+ // Asking would be a lookup, which reads the secret: read decides instead.
33
+ saved: async () => 'unknown',
34
+ };
35
+ }
36
+ // The `user` attribute from `secret-tool search`: `attribute.user = linear`.
37
+ export function userOf(search) {
38
+ return /^attribute\.user = (.*)$/m.exec(search)?.[1]?.trim() ?? '';
39
+ }
40
+ //# sourceMappingURL=libsecret.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"libsecret.js","sourceRoot":"","sources":["../../src/host/libsecret.ts"],"names":[],"mappings":"AAEA,iGAAiG;AACjG,qGAAqG;AACrG,oGAAoG;AACpG,uEAAuE;AACvE,MAAM,OAAO,GAAG,WAAW,CAAC;AAE5B,MAAM,UAAU,SAAS,CAAC,GAAQ;IAChC,MAAM,IAAI,GAAG,CAAC,IAAuB,EAAE,GAAsB,EAAE,EAAE,CAC/D,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,GAAsB,EAAE,EAAE,CACxD,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;IAE9D,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAA2B,EAAE;YACnD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACnD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;gBAChC,8FAA8F;gBAC9F,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;gBAChF,OAAO,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YACnG,CAAC;YACD,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;gBACtD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,yCAAyC,EAAE,CAAC;YAC5F,gEAAgE;YAChE,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;gBAC5C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,qBAAqB,MAAM,EAAE,EAAE,CAAC;YAChF,yEAAyE;YACzE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAC9F,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,MAAM,sBAAsB,GAAG,GAAG,EAAE,CAAC;QAC5G,CAAC;QACD,8FAA8F;QAC9F,8FAA8F;QAC9F,sDAAsD;QACtD,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAC5B,6BAA6B,OAAO,WAAW,MAAM,+BAA+B,MAAM,YAAY,OAAO,WAAW,MAAM,SAAS,IAAI,EAAE;QAC/I,0EAA0E;QAC1E,KAAK,EAAE,KAAK,IAA8B,EAAE,CAAC,SAAS;KACvD,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,MAAM,CAAC,MAAc;IACnC,OAAO,2BAA2B,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACrE,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { outputOf } from "../run.js";
2
+ import { shLine, shQuote, viaSh } from "./shell.js";
3
+ // A string as AppleScript reads it, in double quotes.
4
+ export function appleString(text) {
5
+ return `"${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
6
+ }
7
+ // Terminal.app: a new window per tab (Terminal has no named windows to add a tab to), titled.
8
+ export function appleTerminal(run) {
9
+ return macTerminal(run, 'Terminal', (tab) => [
10
+ 'tell application "Terminal"',
11
+ 'activate',
12
+ `set t to do script ${appleString(macLine(tab))}`,
13
+ `set custom title of t to ${appleString(cleanTitle(tab.title))}`,
14
+ 'end tell',
15
+ ]);
16
+ }
17
+ // iTerm: a tab in its front window, or a new window when none is open, named after the tab. An
18
+ // iTerm that wasn't running opens a window of its own as it starts: the tab takes that window's
19
+ // session instead of adding one beside it.
20
+ export function iterm(run) {
21
+ return macTerminal(run, 'iTerm', (tab) => [
22
+ 'set wasRunning to application "iTerm" is running',
23
+ 'tell application "iTerm"',
24
+ 'activate',
25
+ 'if (count of windows) is 0 then',
26
+ 'set w to (create window with default profile)',
27
+ 'else',
28
+ 'set w to current window',
29
+ 'if wasRunning then tell w to create tab with default profile',
30
+ 'end if',
31
+ 'tell current session of w',
32
+ `set name to ${appleString(cleanTitle(tab.title))}`,
33
+ `write text ${appleString(macLine(tab))}`,
34
+ 'end tell',
35
+ 'end tell',
36
+ ]);
37
+ }
38
+ // What a macOS tab types into its shell: the folder and the command through sh, then `exit`, so the
39
+ // shell ends with the command as a Windows Terminal or tmux tab does (Terminal closes the window
40
+ // then if its profile says to). The shell is the user's login shell, in its own environment, so
41
+ // `env` goes on the line.
42
+ export function macLine(tab) {
43
+ return `${viaSh(`cd ${shQuote(tab.cwd)} && ${shLine(tab)}`)}; exit`;
44
+ }
45
+ function cleanTitle(title) {
46
+ return title.replace(/\s+/g, ' ').trim().slice(0, 80);
47
+ }
48
+ // Runs the AppleScript, one `-e` per line: osascript takes it as arguments, never through a shell.
49
+ function macTerminal(run, name, script) {
50
+ return {
51
+ attachHint: () => null,
52
+ name,
53
+ open: async (tab) => {
54
+ const result = await run('osascript', script(tab).flatMap((line) => ['-e', line]), { timeoutMs: 60_000 });
55
+ if (result.code === 0)
56
+ return null;
57
+ return result.error !== null && /ENOENT/.test(result.error)
58
+ ? 'no osascript to open it with'
59
+ : outputOf(result, 500);
60
+ },
61
+ };
62
+ }
63
+ //# sourceMappingURL=macos-terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"macos-terminal.js","sourceRoot":"","sources":["../../src/host/macos-terminal.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEpD,sDAAsD;AACtD,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;AACjE,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,aAAa,CAAC,GAAQ;IACpC,OAAO,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC;QAC3C,6BAA6B;QAC7B,UAAU;QACV,sBAAsB,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE;QACjD,4BAA4B,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE;QAChE,UAAU;KACX,CAAC,CAAC;AACL,CAAC;AAED,+FAA+F;AAC/F,gGAAgG;AAChG,2CAA2C;AAC3C,MAAM,UAAU,KAAK,CAAC,GAAQ;IAC5B,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC;QACxC,kDAAkD;QAClD,0BAA0B;QAC1B,UAAU;QACV,iCAAiC;QACjC,+CAA+C;QAC/C,MAAM;QACN,yBAAyB;QACzB,8DAA8D;QAC9D,QAAQ;QACR,2BAA2B;QAC3B,eAAe,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE;QACnD,cAAc,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE;QACzC,UAAU;QACV,UAAU;KACX,CAAC,CAAC;AACL,CAAC;AAED,oGAAoG;AACpG,iGAAiG;AACjG,gGAAgG;AAChG,0BAA0B;AAC1B,MAAM,UAAU,OAAO,CAAC,GAAyD;IAC/E,OAAO,GAAG,KAAK,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC;AACtE,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,mGAAmG;AACnG,SAAS,WAAW,CAAC,GAAQ,EAAE,IAAY,EAAE,MAAqC;IAChF,OAAO;QACL,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI;QACtB,IAAI;QACJ,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,MAAM,GAAG,MAAM,GAAG,CACtB,WAAW,EACX,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAC3C,EAAE,SAAS,EAAE,MAAM,EAAE,CACtB,CAAC;YACF,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACnC,OAAO,MAAM,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;gBACzD,CAAC,CAAC,8BAA8B;gBAChC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC5B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export function hostPlatform(platform = process.platform) {
2
+ return platform === 'win32' || platform === 'darwin' ? platform : 'linux';
3
+ }
4
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/host/platform.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,YAAY,CAAC,WAA4B,OAAO,CAAC,QAAQ;IACvE,OAAO,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { run as runCommand } from "../run.js";
2
+ import { credentialManager } from "./credential-manager.js";
3
+ import { keychain } from "./keychain.js";
4
+ import { libsecret } from "./libsecret.js";
5
+ import { hostPlatform } from "./platform.js";
6
+ // The env var that stands in for the credential `target` where the store has none, or can't be
7
+ // read (a headless Linux box with no keyring): CONDUCTOR_SECRET_ and the target in upper snake
8
+ // case, with its user name, when one is needed, in the same name plus _USER.
9
+ export function secretEnvName(target) {
10
+ return `CONDUCTOR_SECRET_${target
11
+ .toUpperCase()
12
+ .replace(/[^A-Z0-9]+/g, '_')
13
+ .replace(/^_+|_+$/g, '')}`;
14
+ }
15
+ // This platform's store, with the env fallback.
16
+ export function secretStore({ platform = hostPlatform(), run = runCommand, } = {}) {
17
+ const stores = {
18
+ darwin: () => keychain(run),
19
+ linux: () => libsecret(run),
20
+ win32: () => credentialManager(run),
21
+ };
22
+ return withEnvFallback(stores[platform]());
23
+ }
24
+ // The store, else secretEnvName(target) from the environment.
25
+ export function withEnvFallback(store) {
26
+ const fromEnv = (target, env) => {
27
+ const name = secretEnvName(target);
28
+ const secret = env[name]?.trim();
29
+ return secret ? { secret, user: env[`${name}_USER`]?.trim() ?? '' } : null;
30
+ };
31
+ return {
32
+ name: store.name,
33
+ read: async (target, env) => {
34
+ const got = await store.read(target, env);
35
+ if (got.found)
36
+ return got;
37
+ const credential = fromEnv(target, env);
38
+ return credential
39
+ ? { credential, found: true }
40
+ : { ...got, reason: `${got.reason}, and no ${secretEnvName(target)}` };
41
+ },
42
+ saveCommand: (target, user) => store.saveCommand(target, user),
43
+ saved: async (target, env) => (fromEnv(target, env) ? 'saved' : store.saved(target, env)),
44
+ };
45
+ }
46
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/host/secrets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,IAAI,UAAU,EAAmC,MAAM,WAAW,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAqB,YAAY,EAAE,MAAM,eAAe,CAAC;AA8BhE,+FAA+F;AAC/F,+FAA+F;AAC/F,6EAA6E;AAC7E,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,oBAAoB,MAAM;SAC9B,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,WAAW,CAAC,EAC1B,QAAQ,GAAG,YAAY,EAAE,EACzB,GAAG,GAAG,UAAU,MAC0B,EAAE;IAC5C,MAAM,MAAM,GAA4C;QACtD,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC;QAC3B,KAAK,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC;QAC3B,KAAK,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC;KACpC,CAAC;IACF,OAAO,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,MAAM,OAAO,GAAG,CAAC,MAAc,EAAE,GAAsB,EAAqB,EAAE;QAC5E,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;QACjC,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,IAAI,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7E,CAAC,CAAC;IACF,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;YAC1B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC1C,IAAI,GAAG,CAAC,KAAK;gBAAE,OAAO,GAAG,CAAC;YAC1B,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACxC,OAAO,UAAU;gBACf,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE;gBAC7B,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,YAAY,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QAC3E,CAAC;QACD,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC;QAC9D,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KAC1F,CAAC;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { win32 } from 'node:path';
2
+ // What a held tab says when its command fails, such as "claude attach failed, press Enter to close
3
+ // this tab". win32's basename splits on both / and \, so a Windows path reads the same on any host.
4
+ export function failedText(command) {
5
+ const [program = '', verb] = command;
6
+ const name = win32.basename(program).replace(/\.(exe|cmd)$/i, '');
7
+ return `${verb === undefined ? name : `${name} ${verb}`} failed, press Enter to close this tab`;
8
+ }
9
+ // The tab's command as one sh line: its env first, and with `hold`, a failure that waits for Enter
10
+ // before the tab closes.
11
+ export function shLine(tab) {
12
+ const env = Object.entries(tab.env).map(([name, value]) => `${name}=${shQuote(value)}`);
13
+ const line = [...(env.length > 0 ? ['env', ...env] : []), ...tab.command.map(shQuote)].join(' ');
14
+ return tab.hold ? `${line} || { echo ${shQuote(failedText(tab.command))}; read _; }` : line;
15
+ }
16
+ // A word as sh reads it: bare when it can't be taken for anything else, else in single quotes.
17
+ export function shQuote(word) {
18
+ return /^[\w%+,./:=@-]+$/.test(word) ? word : `'${word.replace(/'/g, `'\\''`)}'`;
19
+ }
20
+ // A line run through sh, as one word for the shell that runs it. tmux's default-shell and a macOS
21
+ // login shell are the user's, which may not be sh (fish has no `{ …; }`); `sh -c '…'` reads the same
22
+ // in each.
23
+ export function viaSh(line) {
24
+ return `sh -c ${shQuote(line)}`;
25
+ }
26
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/host/shell.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAIlC,mGAAmG;AACnG,oGAAoG;AACpG,MAAM,UAAU,UAAU,CAAC,OAA0B;IACnD,MAAM,CAAC,OAAO,GAAG,EAAE,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IAClE,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,wCAAwC,CAAC;AAClG,CAAC;AAED,mGAAmG;AACnG,yBAAyB;AACzB,MAAM,UAAU,MAAM,CAAC,GAAiD;IACtE,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACxF,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjG,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,cAAc,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9F,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACnF,CAAC;AAED,kGAAkG;AAClG,qGAAqG;AACrG,WAAW;AACX,MAAM,UAAU,KAAK,CAAC,IAAY;IAChC,OAAO,SAAS,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { homedir, userInfo } from 'node:os';
2
+ import { run as runCommand } from "../run.js";
3
+ import { launchd } from "./launchd.js";
4
+ import { hostPlatform } from "./platform.js";
5
+ import {} from "./secrets.js";
6
+ import { systemd } from "./systemd.js";
7
+ import { taskScheduler } from "./task-scheduler.js";
8
+ // This platform's startup.
9
+ export function startup({ env = process.env, home = homedir(), platform = hostPlatform(), run = runCommand, uid = process.getuid?.() ?? 0, user = userName(), } = {}) {
10
+ const startups = {
11
+ darwin: () => launchd({ home, run, uid }),
12
+ linux: () => systemd({ env, home, run, user }),
13
+ win32: () => taskScheduler(run),
14
+ };
15
+ return startups[platform]();
16
+ }
17
+ // userInfo throws for a uid with no passwd entry, such as a container's.
18
+ function userName() {
19
+ try {
20
+ return userInfo().username;
21
+ }
22
+ catch {
23
+ return process.env.USER ?? '';
24
+ }
25
+ }
26
+ //# sourceMappingURL=startup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup.js","sourceRoot":"","sources":["../../src/host/startup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAqB,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAY,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAuCpD,2BAA2B;AAC3B,MAAM,UAAU,OAAO,CAAC,EACtB,GAAG,GAAG,OAAO,CAAC,GAAG,EACjB,IAAI,GAAG,OAAO,EAAE,EAChB,QAAQ,GAAG,YAAY,EAAE,EACzB,GAAG,GAAG,UAAU,EAChB,GAAG,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,EAC7B,IAAI,GAAG,QAAQ,EAAE,MACF,EAAE;IACjB,MAAM,QAAQ,GAAwC;QACpD,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;QACzC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;QAC9C,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC;KAChC,CAAC;IACF,OAAO,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC9B,CAAC;AAED,yEAAyE;AACzE,SAAS,QAAQ;IACf,IAAI,CAAC;QACH,OAAO,QAAQ,EAAE,CAAC,QAAQ,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IAChC,CAAC;AACH,CAAC"}