@detergent-software/conductor 3.0.0-dev.2 → 3.0.0-dev.21
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.
- package/CHANGELOG.md +169 -0
- package/README.md +158 -45
- package/build/brief-cli.js +1 -1
- package/build/brief-cli.js.map +1 -1
- package/build/broker-cli.js +24 -25
- package/build/broker-cli.js.map +1 -1
- package/build/broker.js +66 -92
- package/build/broker.js.map +1 -1
- package/build/claude.js +50 -7
- package/build/claude.js.map +1 -1
- package/build/cli.js +119 -54
- package/build/cli.js.map +1 -1
- package/build/clones.js +92 -0
- package/build/clones.js.map +1 -0
- package/build/code-host-cli.js +101 -26
- package/build/code-host-cli.js.map +1 -1
- package/build/code-host.js +13 -8
- package/build/code-host.js.map +1 -1
- package/build/config.js +31 -12
- package/build/config.js.map +1 -1
- package/build/console.js +40 -24
- package/build/console.js.map +1 -1
- package/build/credentials.js +26 -24
- package/build/credentials.js.map +1 -1
- package/build/daemon.js +35 -21
- package/build/daemon.js.map +1 -1
- package/build/help.js +141 -30
- package/build/help.js.map +1 -1
- package/build/host/credential-manager.js +109 -0
- package/build/host/credential-manager.js.map +1 -0
- package/build/host/keychain.js +44 -0
- package/build/host/keychain.js.map +1 -0
- package/build/host/libsecret.js +40 -0
- package/build/host/libsecret.js.map +1 -0
- package/build/host/macos-terminal.js +63 -0
- package/build/host/macos-terminal.js.map +1 -0
- package/build/host/platform.js +4 -0
- package/build/host/platform.js.map +1 -0
- package/build/host/secrets.js +46 -0
- package/build/host/secrets.js.map +1 -0
- package/build/host/shell.js +26 -0
- package/build/host/shell.js.map +1 -0
- package/build/host/terminal.js +46 -0
- package/build/host/terminal.js.map +1 -0
- package/build/host/tmux.js +39 -0
- package/build/host/tmux.js.map +1 -0
- package/build/host/windows-terminal.js +46 -0
- package/build/host/windows-terminal.js.map +1 -0
- package/build/in-process.js +6 -5
- package/build/in-process.js.map +1 -1
- package/build/inbox.js +50 -5
- package/build/inbox.js.map +1 -1
- package/build/init.js +508 -0
- package/build/init.js.map +1 -0
- package/build/lib/providers/ado/pipelines.js +102 -17
- package/build/lib/providers/ado/pipelines.js.map +1 -1
- package/build/lib/providers/ado/trigger.js +5 -0
- package/build/lib/providers/ado/trigger.js.map +1 -1
- package/build/lib/providers/github/actions.js +85 -2
- package/build/lib/providers/github/actions.js.map +1 -1
- package/build/lib/providers/index.js +3 -2
- package/build/lib/providers/index.js.map +1 -1
- package/build/lib/providers/npm/stage.js +14 -4
- package/build/lib/providers/npm/stage.js.map +1 -1
- package/build/lib/providers/timing.js +10 -0
- package/build/lib/providers/timing.js.map +1 -0
- package/build/lib/providers/verdicts/runs.js +12 -2
- package/build/lib/providers/verdicts/runs.js.map +1 -1
- package/build/local-files.js +95 -0
- package/build/local-files.js.map +1 -0
- package/build/manager.js +171 -47
- package/build/manager.js.map +1 -1
- package/build/note.js +1 -1
- package/build/note.js.map +1 -1
- package/build/npm-reads.js +323 -0
- package/build/npm-reads.js.map +1 -0
- package/build/paths.js +44 -1
- package/build/paths.js.map +1 -1
- package/build/plugin.js +10 -0
- package/build/plugin.js.map +1 -1
- package/build/profile-fields.js +149 -0
- package/build/profile-fields.js.map +1 -0
- package/build/profile-layers.js +264 -0
- package/build/profile-layers.js.map +1 -0
- package/build/profile.js +60 -38
- package/build/profile.js.map +1 -1
- package/build/project-check-cli.js +18 -10
- package/build/project-check-cli.js.map +1 -1
- package/build/project-check.js +179 -97
- package/build/project-check.js.map +1 -1
- package/build/pushover.js +5 -5
- package/build/pushover.js.map +1 -1
- package/build/registry.js +195 -36
- package/build/registry.js.map +1 -1
- package/build/rule-actions.js +3 -1
- package/build/rule-actions.js.map +1 -1
- package/build/rules-service.js +101 -44
- package/build/rules-service.js.map +1 -1
- package/build/rules.js +11 -2
- package/build/rules.js.map +1 -1
- package/build/runs.js +88 -14
- package/build/runs.js.map +1 -1
- package/build/scheduler.js +11 -1
- package/build/scheduler.js.map +1 -1
- package/build/server.js +4 -25
- package/build/server.js.map +1 -1
- package/build/spend.js +4 -0
- package/build/spend.js.map +1 -1
- package/build/store.js +82 -25
- package/build/store.js.map +1 -1
- package/build/timeline.js +104 -0
- package/build/timeline.js.map +1 -0
- package/build/tracker-reads-cli.js +4 -5
- package/build/tracker-reads-cli.js.map +1 -1
- package/build/train-cli.js +6 -5
- package/build/train-cli.js.map +1 -1
- package/build/upgrade.js +16 -1
- package/build/upgrade.js.map +1 -1
- package/build/wait.js +6 -6
- package/build/wait.js.map +1 -1
- package/build/watchers/ado.js +3 -2
- package/build/watchers/ado.js.map +1 -1
- package/build/watchers/github.js +3 -2
- package/build/watchers/github.js.map +1 -1
- package/build/watchers/npm.js +6 -4
- package/build/watchers/npm.js.map +1 -1
- package/build/worktrees.js +199 -92
- package/build/worktrees.js.map +1 -1
- package/package.json +61 -61
- package/plugin/skills/conductor-console/SKILL.md +3 -3
- package/plugin/skills/work-item/SKILL.md +8 -6
- package/build/checkout-sync.js +0 -249
- package/build/checkout-sync.js.map +0 -1
- package/build/credential.js +0 -66
- package/build/credential.js.map +0 -1
- package/build/tab.js +0 -32
- 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,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 @@
|
|
|
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,46 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { run as runCommand } from "../run.js";
|
|
5
|
+
import { appleTerminal, iterm } from "./macos-terminal.js";
|
|
6
|
+
import { hostPlatform } from "./platform.js";
|
|
7
|
+
import { tmux } from "./tmux.js";
|
|
8
|
+
import { windowsTerminal } from "./windows-terminal.js";
|
|
9
|
+
// config.json's workers.terminal: auto is Windows Terminal on Windows, iTerm on macOS where it is
|
|
10
|
+
// installed and Terminal where it isn't, and tmux elsewhere.
|
|
11
|
+
export const TERMINALS = ['auto', 'windows-terminal', 'terminal', 'iterm', 'tmux'];
|
|
12
|
+
const ITERM_APPS = ['/Applications/iTerm.app', join(homedir(), 'Applications', 'iTerm.app')];
|
|
13
|
+
// This platform's terminal, or the one config.json names.
|
|
14
|
+
export function terminal({ choice = 'auto', installed = existsSync, platform = hostPlatform(), run = runCommand, } = {}) {
|
|
15
|
+
const picked = choice !== 'auto'
|
|
16
|
+
? choice
|
|
17
|
+
: platform === 'win32'
|
|
18
|
+
? 'windows-terminal'
|
|
19
|
+
: platform === 'darwin'
|
|
20
|
+
? ITERM_APPS.some((app) => installed(app))
|
|
21
|
+
? 'iterm'
|
|
22
|
+
: 'terminal'
|
|
23
|
+
: 'tmux';
|
|
24
|
+
const terminals = {
|
|
25
|
+
iterm: () => iterm(run),
|
|
26
|
+
terminal: () => appleTerminal(run),
|
|
27
|
+
tmux: () => tmux(run),
|
|
28
|
+
'windows-terminal': () => windowsTerminal(run),
|
|
29
|
+
};
|
|
30
|
+
return terminals[picked]();
|
|
31
|
+
}
|
|
32
|
+
export function workerTabs(where, onLaunch) {
|
|
33
|
+
return {
|
|
34
|
+
hint: (window) => where.attachHint(window),
|
|
35
|
+
onLaunch,
|
|
36
|
+
open: (tab) => where.open({
|
|
37
|
+
command: ['claude', 'attach', tab.handle],
|
|
38
|
+
cwd: tab.cwd,
|
|
39
|
+
env: {},
|
|
40
|
+
hold: true,
|
|
41
|
+
title: tab.title,
|
|
42
|
+
window: tab.window,
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/host/terminal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAqB,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,kGAAkG;AAClG,6DAA6D;AAC7D,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,kBAAkB,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AA4C5F,MAAM,UAAU,GAAG,CAAC,yBAAyB,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;AAE7F,0DAA0D;AAC1D,MAAM,UAAU,QAAQ,CAAC,EACvB,MAAM,GAAG,MAAM,EACf,SAAS,GAAG,UAAU,EACtB,QAAQ,GAAG,YAAY,EAAE,EACzB,GAAG,GAAG,UAAU,MAOd,EAAE;IACJ,MAAM,MAAM,GACV,MAAM,KAAK,MAAM;QACf,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,QAAQ,KAAK,OAAO;YACpB,CAAC,CAAC,kBAAkB;YACpB,CAAC,CAAC,QAAQ,KAAK,QAAQ;gBACrB,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;oBACxC,CAAC,CAAC,OAAO;oBACT,CAAC,CAAC,UAAU;gBACd,CAAC,CAAC,MAAM,CAAC;IACjB,MAAM,SAAS,GAA0C;QACvD,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC;QACvB,QAAQ,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC;QAClC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;QACrB,kBAAkB,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC;KAC/C,CAAC;IACF,OAAO,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAe,EAAE,QAAiB;IAC3D,OAAO;QACL,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;QAC1C,QAAQ;QACR,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CACZ,KAAK,CAAC,IAAI,CAAC;YACT,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC;YACzC,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,GAAG,EAAE,EAAE;YACP,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,MAAM,EAAE,GAAG,CAAC,MAAM;SACnB,CAAC;KACL,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { outputOf } from "../run.js";
|
|
2
|
+
import { shLine, viaSh } from "./shell.js";
|
|
3
|
+
// tmux: a detached session per window, made by its first tab, and a tmux window per tab. It runs
|
|
4
|
+
// where there is no desktop (a server over ssh), so a person reaches it with `tmux attach`. tmux
|
|
5
|
+
// starts a tab in the tmux server's environment, so `env` goes on the command line, and runs it in
|
|
6
|
+
// its default-shell, so the line goes through sh.
|
|
7
|
+
export function tmux(run) {
|
|
8
|
+
return {
|
|
9
|
+
attachHint: (window) => `tmux attach -t ${tmuxSession(window)}`,
|
|
10
|
+
name: 'tmux',
|
|
11
|
+
open: async (tab) => {
|
|
12
|
+
const session = tmuxSession(tab.window);
|
|
13
|
+
// `=` matches the name exactly, not as a prefix.
|
|
14
|
+
const has = await run('tmux', ['has-session', '-t', `=${session}`], { timeoutMs: 30_000 });
|
|
15
|
+
if (has.error !== null && /ENOENT/.test(has.error))
|
|
16
|
+
return 'no tmux to open it in';
|
|
17
|
+
// tmux expands formats in -n and -c, and `#(…)` in one runs a command: `##` is a plain `#`.
|
|
18
|
+
const literal = (text) => text.replace(/#/g, '##');
|
|
19
|
+
const title = literal(tab.title.replace(/\s+/g, ' ').trim().slice(0, 80));
|
|
20
|
+
const open = (where) => run('tmux', [...where, '-n', title, '-c', literal(tab.cwd), viaSh(shLine(tab))], { timeoutMs: 30_000 });
|
|
21
|
+
const inSession = ['new-window', '-t', `=${session}:`];
|
|
22
|
+
let result = await open(has.code === 0 ? inSession : ['new-session', '-d', '-s', session]);
|
|
23
|
+
// Another tab made the session since has-session asked.
|
|
24
|
+
if (has.code !== 0 && /duplicate session/.test(result.stderr))
|
|
25
|
+
result = await open(inSession);
|
|
26
|
+
return result.code === 0 ? null : outputOf(result, 500);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// The tmux session a window's tabs open in: its name in lower case, with a dash for anything tmux
|
|
31
|
+
// reads in a target (`.` and `:`) and anything a person would have to quote. "Conductor · atk"
|
|
32
|
+
// becomes conductor-atk.
|
|
33
|
+
export function tmuxSession(window) {
|
|
34
|
+
return (window
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
37
|
+
.replace(/^-+|-+$/g, '') || 'conductor');
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=tmux.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux.js","sourceRoot":"","sources":["../../src/host/tmux.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAE3C,iGAAiG;AACjG,iGAAiG;AACjG,mGAAmG;AACnG,kDAAkD;AAClD,MAAM,UAAU,IAAI,CAAC,GAAQ;IAC3B,OAAO;QACL,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,kBAAkB,WAAW,CAAC,MAAM,CAAC,EAAE;QAC/D,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACxC,iDAAiD;YACjD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;YAC3F,IAAI,GAAG,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO,uBAAuB,CAAC;YACnF,4FAA4F;YAC5F,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACnE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAC1E,MAAM,IAAI,GAAG,CAAC,KAAwB,EAAE,EAAE,CACxC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;YAC1G,MAAM,SAAS,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,OAAO,GAAG,CAAC,CAAC;YACvD,IAAI,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;YAC3F,wDAAwD;YACxD,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;gBAAE,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;YAC9F,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC1D,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,+FAA+F;AAC/F,yBAAyB;AACzB,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,OAAO,CACL,MAAM;SACH,WAAW,EAAE;SACb,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC;SAC7B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,WAAW,CAC1C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { outputOf } from "../run.js";
|
|
2
|
+
import { failedText } from "./shell.js";
|
|
3
|
+
// Windows Terminal (wt.exe). Not hidden: when the named window doesn't exist yet, Windows Terminal
|
|
4
|
+
// creates it with the spawn's SW_HIDE, and the tab runs where nobody sees it (ATK-469, ATK-336).
|
|
5
|
+
export function windowsTerminal(run) {
|
|
6
|
+
return {
|
|
7
|
+
attachHint: () => null,
|
|
8
|
+
name: 'Windows Terminal',
|
|
9
|
+
open: async (tab) => {
|
|
10
|
+
const args = windowsTerminalArgs(tab);
|
|
11
|
+
if (typeof args === 'string')
|
|
12
|
+
return args;
|
|
13
|
+
const result = await run('wt.exe', args, { cwd: tab.cwd, timeoutMs: 60_000, windowsHide: false });
|
|
14
|
+
if (result.code === 0)
|
|
15
|
+
return null;
|
|
16
|
+
return result.error !== null && /ENOENT/.test(result.error)
|
|
17
|
+
? 'no Windows Terminal (wt.exe) to open it in'
|
|
18
|
+
: outputOf(result, 500);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// wt.exe's arguments for a tab in the named window. wt.exe splits its own arguments on `;`, so the
|
|
23
|
+
// window and title lose it, a folder with one is left out (the tab starts in its profile's, as
|
|
24
|
+
// tabs did before ATK-529), and a command with one can't open (a string says why). A held command
|
|
25
|
+
// runs in pwsh and chains its prompt with `||`. The tab gets wt.exe's environment, which is the
|
|
26
|
+
// caller's even in a window already open, so `env` needs no setting.
|
|
27
|
+
export function windowsTerminalArgs(tab) {
|
|
28
|
+
const split = tab.command.find((part) => part.includes(';'));
|
|
29
|
+
if (split !== undefined)
|
|
30
|
+
return `a Windows Terminal tab can't take ";": ${split}`;
|
|
31
|
+
const clean = (text) => text.replace(/;/g, ',').replace(/\s+/g, ' ').trim();
|
|
32
|
+
const command = tab.hold
|
|
33
|
+
? ['pwsh', '-Command', `${psLine(tab.command)} || Read-Host ${psQuote(failedText(tab.command))}`]
|
|
34
|
+
: tab.command;
|
|
35
|
+
const folder = tab.cwd.includes(';') ? [] : ['-d', tab.cwd];
|
|
36
|
+
return ['-w', clean(tab.window), 'new-tab', '--title', clean(tab.title).slice(0, 80), ...folder, ...command];
|
|
37
|
+
}
|
|
38
|
+
// A command as pwsh runs it: words bare where they can be, and a quoted program called with `&`.
|
|
39
|
+
function psLine(command) {
|
|
40
|
+
const words = command.map((word) => (/^[\w.:/\\-]+$/.test(word) ? word : psQuote(word)));
|
|
41
|
+
return words[0]?.startsWith("'") ? `& ${words.join(' ')}` : words.join(' ');
|
|
42
|
+
}
|
|
43
|
+
function psQuote(text) {
|
|
44
|
+
return `'${text.replace(/'/g, "''")}'`;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=windows-terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows-terminal.js","sourceRoot":"","sources":["../../src/host/windows-terminal.ts"],"names":[],"mappings":"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"}
|
package/build/in-process.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { loadConfig } from "./config.js";
|
|
2
2
|
import { providerCredentials } from "./credentials.js";
|
|
3
|
-
import {
|
|
3
|
+
import { profileHere } from "./profile-layers.js";
|
|
4
|
+
import { adoOrganization, profileProblem, trackerKey } from "./profile.js";
|
|
4
5
|
import { gitToplevel } from "./wait.js";
|
|
5
6
|
// What a tracker verb run in-process (no daemon) needs: the project of the checkout it runs in, and
|
|
6
7
|
// the machine's accounts. `conduct note` and `conduct brief` share it.
|
|
7
8
|
// The profile of the checkout around `cwd`, when `key` (an item key, checked already) is one of its
|
|
8
9
|
// items; else why not, after `why` (why it runs here), for the verb's refusal. `purpose` ends the
|
|
9
10
|
// sentence: "post it from", "read it from".
|
|
10
|
-
export async function checkoutProject(cwd, key, why, purpose) {
|
|
11
|
+
export async function checkoutProject(paths, cwd, key, why, purpose) {
|
|
11
12
|
const checkout = await gitToplevel(cwd);
|
|
12
13
|
if (checkout === null)
|
|
13
14
|
return { refusal: `${why}, and this isn't a project checkout to ${purpose}` };
|
|
14
15
|
let profile;
|
|
15
16
|
try {
|
|
16
|
-
profile =
|
|
17
|
+
profile = profileHere(checkout, paths);
|
|
17
18
|
}
|
|
18
|
-
catch {
|
|
19
|
-
return { refusal: `${why}, and ${checkout} has no readable ${
|
|
19
|
+
catch (error) {
|
|
20
|
+
return { refusal: `${why}, and ${checkout} has no readable profile: ${profileProblem(error)}` };
|
|
20
21
|
}
|
|
21
22
|
if (key.split('-')[0] !== trackerKey(profile).toUpperCase())
|
|
22
23
|
return { refusal: `${why}, and ${key} isn't an item of ${profile.name}, this checkout's project` };
|
package/build/in-process.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"in-process.js","sourceRoot":"","sources":["../src/in-process.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAoB,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"in-process.js","sourceRoot":"","sources":["../src/in-process.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAoB,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAgB,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,oGAAoG;AACpG,uEAAuE;AAEvE,oGAAoG;AACpG,kGAAkG;AAClG,4CAA4C;AAC5C,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAuC,EACvC,GAAW,EACX,GAAW,EACX,GAAW,EACX,OAAe;IAEf,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,0CAA0C,OAAO,EAAE,EAAE,CAAC;IACrG,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,SAAS,QAAQ,6BAA6B,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IAClG,CAAC;IACD,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE;QACzD,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,SAAS,GAAG,qBAAqB,OAAO,CAAC,IAAI,2BAA2B,EAAE,CAAC;IACrG,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC;AAED,8FAA8F;AAC9F,kGAAkG;AAClG,4BAA4B;AAC5B,MAAM,UAAU,gBAAgB,CAAC,KAAqB,EAAE,KAAmC;IACzF,OAAO,mBAAmB,CAAC;QACzB,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9E,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,QAAQ;KAC5C,CAAC,CAAC;AACL,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,kBAAkB,CAAC,KAAkC;IACnE,OAAO,CAAC,OAAO,EAAE,EAAE;QACjB,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC;QACtF,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC"}
|