@detergent-software/conductor 3.0.0-dev.3 → 3.0.0-dev.30
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 +241 -0
- package/README.md +201 -63
- 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 +143 -63
- 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 +116 -32
- package/build/code-host-cli.js.map +1 -1
- package/build/code-host-service.js +8 -0
- package/build/code-host-service.js.map +1 -1
- package/build/code-host.js +22 -11
- package/build/code-host.js.map +1 -1
- package/build/config.js +58 -14
- package/build/config.js.map +1 -1
- package/build/console.js +40 -24
- package/build/console.js.map +1 -1
- package/build/credentials.js +42 -29
- package/build/credentials.js.map +1 -1
- package/build/daemon.js +38 -22
- package/build/daemon.js.map +1 -1
- package/build/help.js +179 -42
- package/build/help.js.map +1 -1
- package/build/history.js +4 -2
- package/build/history.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/launchd.js +118 -0
- package/build/host/launchd.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/startup.js +26 -0
- package/build/host/startup.js.map +1 -0
- package/build/host/systemd.js +91 -0
- package/build/host/systemd.js.map +1 -0
- package/build/host/task-scheduler.js +78 -0
- package/build/host/task-scheduler.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 +63 -17
- package/build/inbox.js.map +1 -1
- package/build/init.js +508 -0
- package/build/init.js.map +1 -0
- package/build/landing.js +86 -7
- package/build/landing.js.map +1 -1
- package/build/lib/providers/ado/code-host.js +5 -1
- package/build/lib/providers/ado/code-host.js.map +1 -1
- package/build/lib/providers/ado/pipelines.js +130 -22
- 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/ado/writes.js +10 -1
- package/build/lib/providers/ado/writes.js.map +1 -1
- package/build/lib/providers/github/actions.js +90 -3
- package/build/lib/providers/github/actions.js.map +1 -1
- package/build/lib/providers/github/code-host.js +2 -0
- package/build/lib/providers/github/code-host.js.map +1 -1
- package/build/lib/providers/github/writes.js +24 -2
- package/build/lib/providers/github/writes.js.map +1 -1
- package/build/lib/providers/index.js +4 -3
- package/build/lib/providers/index.js.map +1 -1
- package/build/lib/providers/npm/stage.js +28 -5
- 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/pr.js +47 -6
- package/build/lib/providers/verdicts/pr.js.map +1 -1
- 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/notify.js +101 -39
- package/build/notify.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 +70 -44
- 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 +187 -98
- 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/release-kinds.js +34 -0
- package/build/release-kinds.js.map +1 -0
- package/build/rule-actions.js +4 -2
- 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 +15 -15
- package/build/rules.js.map +1 -1
- package/build/run.js +20 -7
- package/build/run.js.map +1 -1
- package/build/runs.js +200 -45
- package/build/runs.js.map +1 -1
- package/build/scheduler.js +11 -1
- package/build/scheduler.js.map +1 -1
- package/build/server.js +6 -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 +8 -9
- 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 +11 -3
- package/build/watchers/ado.js.map +1 -1
- package/build/watchers/github.js +15 -4
- 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/webhook.js +80 -0
- package/build/webhook.js.map +1 -0
- 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 +4 -4
- package/plugin/skills/work-item/SKILL.md +13 -11
- 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/startup.js +0 -92
- package/build/startup.js.map +0 -1
- package/build/tab.js +0 -32
- package/build/tab.js.map +0 -1
|
@@ -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,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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
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 SYSTEMD_UNIT = 'conductor.service';
|
|
6
|
+
// A systemd user unit that runs `conduct supervise` when the user's service manager starts: at
|
|
7
|
+
// login, or at boot with lingering on. supervise restarts the daemon and conduct stop must stick,
|
|
8
|
+
// so no Restart=; and KillMode=process, or the unit ending (a stop, an upgrade) would kill its whole
|
|
9
|
+
// cgroup, the workers and tmux the daemon started included. Its output goes to the journal
|
|
10
|
+
// (journalctl --user -u conductor).
|
|
11
|
+
export function systemd({ env, home, run, user, }) {
|
|
12
|
+
// systemd reads user units from $XDG_CONFIG_HOME/systemd/user, ~/.config without it.
|
|
13
|
+
const unit = join(env.XDG_CONFIG_HOME || join(home, '.config'), 'systemd', 'user', SYSTEMD_UNIT);
|
|
14
|
+
const systemctl = async (what, args) => {
|
|
15
|
+
const result = await run('systemctl', ['--user', ...args], { timeoutMs: 60_000 });
|
|
16
|
+
if (result.code !== 0)
|
|
17
|
+
throw systemctlFailed(what, result);
|
|
18
|
+
};
|
|
19
|
+
const text = () => (existsSync(unit) ? readFileSync(unit, 'utf8') : undefined);
|
|
20
|
+
return {
|
|
21
|
+
install: async (spec) => {
|
|
22
|
+
mkdirSync(dirname(unit), { recursive: true });
|
|
23
|
+
writeFileSync(unit, systemdUnit(spec));
|
|
24
|
+
await systemctl('could not load the systemd unit', ['daemon-reload']);
|
|
25
|
+
await systemctl('could not enable the systemd unit', ['enable', SYSTEMD_UNIT]);
|
|
26
|
+
// A running one keeps the command it started with until it restarts.
|
|
27
|
+
await systemctl('could not start the systemd unit', ['restart', SYSTEMD_UNIT]);
|
|
28
|
+
const linger = await run('loginctl', ['show-user', user, '--property=Linger', '--value'], { timeoutMs: 30_000 });
|
|
29
|
+
return [
|
|
30
|
+
'systemd started it now',
|
|
31
|
+
...(linger.stdout.trim() === 'yes'
|
|
32
|
+
? []
|
|
33
|
+
: [
|
|
34
|
+
`it starts at your login; to start it at boot on a machine nobody logs in to, run \`loginctl enable-linger ${user || '$USER'}\``,
|
|
35
|
+
]),
|
|
36
|
+
];
|
|
37
|
+
},
|
|
38
|
+
name: `the systemd user unit ${SYSTEMD_UNIT}`,
|
|
39
|
+
// Disabled and gone, but not stopped: a daemon it started keeps running, as on Windows.
|
|
40
|
+
remove: async () => {
|
|
41
|
+
if (!existsSync(unit))
|
|
42
|
+
return false;
|
|
43
|
+
await systemctl('could not disable the systemd unit', ['disable', SYSTEMD_UNIT]);
|
|
44
|
+
rmSync(unit);
|
|
45
|
+
await run('systemctl', ['--user', 'daemon-reload'], { timeoutMs: 60_000 });
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
run: () => systemctl('could not start the systemd unit', ['start', SYSTEMD_UNIT]),
|
|
49
|
+
runsCli: async (cliPath) => execStart(text())?.includes(` ${unitWord(cliPath)} `) ?? false,
|
|
50
|
+
supervises: async () => / supervise$/.test(execStart(text()) ?? ''),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// The unit file.
|
|
54
|
+
export function systemdUnit({ cliPath, nodePath, path }) {
|
|
55
|
+
return [
|
|
56
|
+
'[Unit]',
|
|
57
|
+
'Description=The conductor daemon (conduct supervise)',
|
|
58
|
+
'',
|
|
59
|
+
'[Service]',
|
|
60
|
+
`ExecStart=${[nodePath, cliPath].map((word) => unitWord(word)).join(' ')} supervise`,
|
|
61
|
+
`Environment=${unitWord(`PATH=${path}`, false)}`,
|
|
62
|
+
'KillMode=process',
|
|
63
|
+
'',
|
|
64
|
+
'[Install]',
|
|
65
|
+
'WantedBy=default.target',
|
|
66
|
+
'',
|
|
67
|
+
].join('\n');
|
|
68
|
+
}
|
|
69
|
+
function execStart(unit) {
|
|
70
|
+
return /^ExecStart=(.*)$/m.exec(unit ?? '')?.[1];
|
|
71
|
+
}
|
|
72
|
+
function systemctlFailed(what, result) {
|
|
73
|
+
if (/ENOENT/.test(result.error ?? '')) {
|
|
74
|
+
return new ConductorError(`${what}: no systemctl`, ['start `conduct supervise` from your own login items']);
|
|
75
|
+
}
|
|
76
|
+
return new ConductorError(what, [
|
|
77
|
+
outputOf(result, 1000),
|
|
78
|
+
"systemctl --user needs your login session's service manager: run it in a session you logged in to, not after su",
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
// A word in quotes, as ExecStart= and Environment= read it: `%` starts a specifier, and in
|
|
82
|
+
// ExecStart= `$` an environment variable.
|
|
83
|
+
function unitWord(text, command = true) {
|
|
84
|
+
const escaped = text
|
|
85
|
+
.replaceAll('\\', '\\\\')
|
|
86
|
+
.replaceAll('"', '\\"')
|
|
87
|
+
.replaceAll('%', '%%')
|
|
88
|
+
.replaceAll('$', () => (command ? '$$' : '$'));
|
|
89
|
+
return `"${escaped}"`;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=systemd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"systemd.js","sourceRoot":"","sources":["../../src/host/systemd.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ConductorError } from "../errors.js";
|
|
2
|
+
import { outputOf } from "../run.js";
|
|
3
|
+
export const TASK_NAME = 'Conductor';
|
|
4
|
+
// The task's <Arguments> quote the cli.js path (install); XML escapes it, and Windows paths compare
|
|
5
|
+
// without case.
|
|
6
|
+
export function taskRunsCli(xml, cliPath) {
|
|
7
|
+
const args = taskArguments(xml);
|
|
8
|
+
return args !== undefined && args.toLowerCase().includes(`"${cliPath.toLowerCase()}"`);
|
|
9
|
+
}
|
|
10
|
+
// A per-user Task Scheduler task that runs `conduct supervise` at logon, which keeps `conduct serve`
|
|
11
|
+
// running (supervise.ts). No elevation is needed for a task that runs as the current user.
|
|
12
|
+
// conhost --headless keeps a console window (or a Windows Terminal tab) from opening, and the zero
|
|
13
|
+
// time limit stops Task Scheduler from ending the daemon after its default 72 hours. A second start
|
|
14
|
+
// while one runs exits on the pid lock, and its supervisor with it. Installing doesn't start it.
|
|
15
|
+
export function taskScheduler(run) {
|
|
16
|
+
const schtasks = (args) => run('schtasks', args, { timeoutMs: 60_000 });
|
|
17
|
+
const taskXml = async () => {
|
|
18
|
+
const result = await schtasks(['/Query', '/TN', TASK_NAME, '/XML']);
|
|
19
|
+
return result.code === 0 ? result.stdout : undefined;
|
|
20
|
+
};
|
|
21
|
+
const installed = async () => (await schtasks(['/Query', '/TN', TASK_NAME])).code === 0;
|
|
22
|
+
return {
|
|
23
|
+
install: async ({ cliPath, nodePath }) => {
|
|
24
|
+
const script = [
|
|
25
|
+
"$ErrorActionPreference = 'Stop'",
|
|
26
|
+
'$user = "$env:USERDOMAIN\\$env:USERNAME"',
|
|
27
|
+
'$action = New-ScheduledTaskAction -Execute "$env:SystemRoot\\System32\\conhost.exe" -Argument $env:CONDUCTOR_TASK_ARGS',
|
|
28
|
+
'$trigger = New-ScheduledTaskTrigger -AtLogOn -User $user',
|
|
29
|
+
'$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew -ExecutionTimeLimit ([TimeSpan]::Zero)',
|
|
30
|
+
'$principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Limited',
|
|
31
|
+
`Register-ScheduledTask -TaskName '${TASK_NAME}' -Description 'Starts the conductor daemon (conduct supervise) at logon.' -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force | Out-Null`,
|
|
32
|
+
].join('\n');
|
|
33
|
+
const result = await run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
34
|
+
env: { ...process.env, CONDUCTOR_TASK_ARGS: `--headless "${nodePath}" "${cliPath}" supervise` },
|
|
35
|
+
timeoutMs: 60_000,
|
|
36
|
+
});
|
|
37
|
+
if (result.code !== 0)
|
|
38
|
+
throw new ConductorError('could not register the logon task', [outputOf(result, 1000)]);
|
|
39
|
+
return [];
|
|
40
|
+
},
|
|
41
|
+
name: `the "${TASK_NAME}" logon task`,
|
|
42
|
+
remove: async () => {
|
|
43
|
+
if (!(await installed()))
|
|
44
|
+
return false;
|
|
45
|
+
const result = await schtasks(['/Delete', '/TN', TASK_NAME, '/F']);
|
|
46
|
+
if (result.code !== 0)
|
|
47
|
+
throw new ConductorError('could not remove the logon task', [outputOf(result, 1000)]);
|
|
48
|
+
return true;
|
|
49
|
+
},
|
|
50
|
+
run: async () => {
|
|
51
|
+
const result = await schtasks(['/Run', '/TN', TASK_NAME]);
|
|
52
|
+
if (result.code !== 0)
|
|
53
|
+
throw new ConductorError(`could not run the "${TASK_NAME}" logon task`, [outputOf(result, 1000)]);
|
|
54
|
+
},
|
|
55
|
+
runsCli: async (cliPath) => {
|
|
56
|
+
const xml = await taskXml();
|
|
57
|
+
return xml !== undefined && taskRunsCli(xml, cliPath);
|
|
58
|
+
},
|
|
59
|
+
supervises: async () => {
|
|
60
|
+
const xml = await taskXml();
|
|
61
|
+
return xml !== undefined && taskSupervises(xml);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function taskSupervises(xml) {
|
|
66
|
+
return /\ssupervise\s*$/.test(taskArguments(xml) ?? '');
|
|
67
|
+
}
|
|
68
|
+
// The task action's <Arguments>, XML unescaped.
|
|
69
|
+
function taskArguments(xml) {
|
|
70
|
+
return /<Arguments>([^<]*)<\/Arguments>/
|
|
71
|
+
.exec(xml)?.[1]
|
|
72
|
+
?.replaceAll('"', '"')
|
|
73
|
+
.replaceAll(''', "'")
|
|
74
|
+
.replaceAll('<', '<')
|
|
75
|
+
.replaceAll('>', '>')
|
|
76
|
+
.replaceAll('&', '&');
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=task-scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-scheduler.js","sourceRoot":"","sources":["../../src/host/task-scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -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"}
|