@join-pantheon/cli 0.21.3 → 0.23.0
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/README.md +25 -9
- package/dist/cli.js +163 -29
- package/dist/client.js +34 -0
- package/dist/config.js +15 -3
- package/dist/doctor.js +13 -0
- package/dist/email.js +47 -23
- package/dist/history-view.js +51 -0
- package/dist/host-env.js +57 -0
- package/dist/mcp.js +4 -2
- package/dist/profile.js +8 -0
- package/dist/protocol.js +10 -1
- package/dist/quickstart.js +14 -4
- package/dist/relay.js +180 -74
- package/dist/roster-file.js +19 -0
- package/dist/service.js +25 -2
- package/dist/setup-guide.js +64 -24
- package/dist/tray.js +8 -2
- package/dist/worker.js +7 -2
- package/helper/macos/menubar.swift +110 -43
- package/package.json +2 -2
package/dist/email.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
function subjectAndBody(code, purpose) {
|
|
2
2
|
const action = purpose === "signup" ? "confirm your email and finish signing up" : "add this device to your account";
|
|
3
3
|
const eyebrow = purpose === "signup" ? "Confirm your email" : "New device sign-in";
|
|
4
|
-
const subject = `Your
|
|
4
|
+
const subject = `Your Pantheon code: ${code}`;
|
|
5
5
|
const text = [
|
|
6
|
-
`
|
|
6
|
+
`Pantheon — ${eyebrow.toLowerCase()}`,
|
|
7
7
|
``,
|
|
8
8
|
`Your verification code is ${code}.`,
|
|
9
9
|
``,
|
|
@@ -12,37 +12,61 @@ function subjectAndBody(code, purpose) {
|
|
|
12
12
|
``,
|
|
13
13
|
`The code expires in 10 minutes and works once. If you did not request it,`,
|
|
14
14
|
`ignore this email — nothing happens without the code.`,
|
|
15
|
+
``,
|
|
16
|
+
`joinpantheon.network — the execution layer for companies`,
|
|
15
17
|
].join("\n");
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
+
const paper = "#eae4da", card = "#f2ede5", stone = "#ddd4c3", ink = "#292927", faint = "#7c766d", gold = "#d8a33a";
|
|
19
|
+
const mono = `'SF Mono',Menlo,Consolas,'Liberation Mono',monospace`;
|
|
20
|
+
const serif = `Georgia,'Times New Roman',Times,serif`;
|
|
18
21
|
const html = `<!doctype html>
|
|
19
22
|
<html lang="en">
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
<head>
|
|
24
|
+
<meta charset="utf-8">
|
|
25
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
26
|
+
<meta name="color-scheme" content="light">
|
|
27
|
+
<meta name="supported-color-schemes" content="light">
|
|
28
|
+
<title>Your Pantheon code</title>
|
|
29
|
+
</head>
|
|
30
|
+
<body style="margin:0;padding:0;background:${paper}">
|
|
31
|
+
<div style="display:none;max-height:0;overflow:hidden;opacity:0">Your code is ${code}. It expires in 10 minutes and works once. ‌ ‌ ‌ ‌ ‌</div>
|
|
32
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:${paper};margin:0;padding:0">
|
|
33
|
+
<tr><td align="center" style="padding:40px 16px">
|
|
34
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:480px;width:100%">
|
|
25
35
|
|
|
26
|
-
<tr><td style="padding:0
|
|
27
|
-
<
|
|
36
|
+
<tr><td align="left" style="padding:0 0 28px 0">
|
|
37
|
+
<img src="https://www.joinpantheon.network/email/mark.png" width="28" height="28" alt="Pantheon" style="display:inline-block;vertical-align:middle;border:0">
|
|
38
|
+
<span style="display:inline-block;vertical-align:middle;padding-left:10px;font-family:${serif};font-size:19px;letter-spacing:.01em;color:${ink}">Pantheon</span>
|
|
28
39
|
</td></tr>
|
|
29
40
|
|
|
30
|
-
<tr><td style="background
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
<tr><td style="background:${card};border:1px solid ${stone};border-radius:2px;padding:36px 32px 32px 32px">
|
|
42
|
+
|
|
43
|
+
<p style="margin:0 0 22px 0;font-family:${mono};font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:${faint}">${eyebrow}</p>
|
|
44
|
+
|
|
45
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
46
|
+
<tr><td align="center" style="background:${paper};border:1px solid ${stone};border-radius:2px;padding:22px 12px">
|
|
47
|
+
<span style="font-family:${mono};font-size:34px;line-height:1.1;letter-spacing:.22em;color:${ink};font-weight:600">${code}</span>
|
|
35
48
|
</td></tr>
|
|
36
|
-
<tr><td style="font-family:${serif};font-size:16px;line-height:1.55;color:#3B4A4D;padding:18px 0 12px">Enter it where the CLI is waiting, to ${action}:</td></tr>
|
|
37
|
-
<tr><td style="background-color:#DFE5E4;border:1px solid #C3CDCD;padding:11px 14px;font-family:${mono};font-size:13px;color:#101A1C"><span style="color:#0E6F78">$</span> pantheon verify ${code}</td></tr>
|
|
38
|
-
<tr><td style="padding-top:18px"></td></tr>
|
|
39
|
-
<tr><td style="font-family:${mono};font-size:12px;line-height:1.7;color:#5A686A;padding-top:14px;border-top:1px solid #C3CDCD">Expires in 10 minutes. Works once.<br>Didn't request it? Ignore this email — nothing happens without the code.</td></tr>
|
|
40
49
|
</table>
|
|
50
|
+
|
|
51
|
+
<p style="margin:24px 0 14px 0;font-family:${serif};font-size:16px;line-height:1.55;color:${ink}">Enter it where the CLI is waiting, to ${action}:</p>
|
|
52
|
+
|
|
53
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
54
|
+
<tr><td style="background:${paper};border-left:2px solid ${gold};padding:13px 16px">
|
|
55
|
+
<span style="font-family:${mono};font-size:14px;color:${ink}">pantheon verify ${code}</span>
|
|
56
|
+
</td></tr>
|
|
57
|
+
</table>
|
|
58
|
+
|
|
59
|
+
<p style="margin:26px 0 0 0;padding-top:20px;border-top:1px solid ${stone};font-family:${serif};font-size:14px;line-height:1.6;color:${faint}">
|
|
60
|
+
Expires in 10 minutes. Works once.<br>
|
|
61
|
+
Didn't request it? Ignore this email — nothing happens without the code.
|
|
62
|
+
</p>
|
|
63
|
+
|
|
41
64
|
</td></tr>
|
|
42
65
|
|
|
43
|
-
<tr><td align="
|
|
44
|
-
<a href="https://joinpantheon.network" style="color
|
|
45
|
-
|
|
66
|
+
<tr><td align="left" style="padding:22px 2px 0 2px;font-family:${mono};font-size:11.5px;letter-spacing:.04em;color:${faint}">
|
|
67
|
+
<a href="https://www.joinpantheon.network" style="color:${faint};text-decoration:underline">joinpantheon.network</a>
|
|
68
|
+
<span style="color:${gold};padding:0 8px">·</span>
|
|
69
|
+
The execution layer for companies
|
|
46
70
|
</td></tr>
|
|
47
71
|
|
|
48
72
|
</table>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function selectActs(acts, opts = {}) {
|
|
2
|
+
const kept = acts.filter((a) => (opts.presence || (a.act !== "JOIN" && a.act !== "LEAVE"))
|
|
3
|
+
&& (opts.goal === undefined || a.goal === opts.goal)
|
|
4
|
+
&& (opts.from === undefined || a.from === opts.from)
|
|
5
|
+
&& (opts.since === undefined || a.seq > opts.since));
|
|
6
|
+
const n = opts.last ?? 20;
|
|
7
|
+
return kept.slice(Math.max(0, kept.length - n));
|
|
8
|
+
}
|
|
9
|
+
const when = (ts) => new Date(ts).toISOString().slice(0, 19).replace("T", " ") + "Z";
|
|
10
|
+
export function formatAct(a, opts = {}) {
|
|
11
|
+
const who = `${a.from}${a.role && a.role !== "interactive" ? ` (${a.role})` : ""}`;
|
|
12
|
+
const head = `[${a.seq}] ${a.act} from ${who}${a.to?.length ? ` to ${a.to.join(",")}` : ""} ${when(a.ts)} id=${a.id}${a.ref ? ` ref=${a.ref}` : ""}${a.goal ? ` goal=${a.goal}${opts.goalTitle ? ` "${opts.goalTitle}"` : ""}` : ""}`;
|
|
13
|
+
if (opts.short)
|
|
14
|
+
return `${head}\n ${(a.summary ?? a.body).split("\n")[0].slice(0, 160)}`;
|
|
15
|
+
const meta = a.meta && Object.keys(a.meta).length ? `\n meta: ${JSON.stringify(a.meta)}` : "";
|
|
16
|
+
const summary = a.summary ? `\n >> ${a.summary}` : "";
|
|
17
|
+
return `${head}${summary}\n${a.body.split("\n").map((l) => ` ${l}`).join("\n")}${meta}`;
|
|
18
|
+
}
|
|
19
|
+
const REPLIES_TO = {
|
|
20
|
+
ANSWER: ["ASK", "ESCALATE", "HUMAN_REQUIRED"],
|
|
21
|
+
ACCEPT: ["DELIVERED", "PROPOSE", "CONTRACT"],
|
|
22
|
+
OBJECT: ["DELIVERED", "PROPOSE", "CONTRACT"],
|
|
23
|
+
};
|
|
24
|
+
export function resolveRef(acts, ref, act, goal, me) {
|
|
25
|
+
const inGoal = (a) => goal === null || a.goal === goal;
|
|
26
|
+
if (ref && /^a_/.test(ref)) {
|
|
27
|
+
const hit = acts.find((a) => a.id === ref);
|
|
28
|
+
return { id: ref, goal: hit?.goal ?? goal };
|
|
29
|
+
}
|
|
30
|
+
if (ref && /^\d+$/.test(ref)) {
|
|
31
|
+
const hit = acts.find((a) => a.seq === Number(ref));
|
|
32
|
+
return hit ? { id: hit.id, goal: hit.goal ?? goal, note: `replying to [${hit.seq}] ${hit.act} from ${hit.from}` } : { id: null, goal, error: `no act with sequence ${ref} in what this relay still holds` };
|
|
33
|
+
}
|
|
34
|
+
const others = acts.filter((a) => a.from !== me && a.act !== "JOIN" && a.act !== "LEAVE" && inGoal(a));
|
|
35
|
+
if (ref === "last") {
|
|
36
|
+
const hit = others[others.length - 1];
|
|
37
|
+
return hit ? { id: hit.id, goal: hit.goal ?? goal, note: `replying to [${hit.seq}] ${hit.act} from ${hit.from}` } : { id: null, goal, error: "nothing from a peer to reply to" };
|
|
38
|
+
}
|
|
39
|
+
if (ref)
|
|
40
|
+
return { id: ref, goal };
|
|
41
|
+
const wants = REPLIES_TO[act];
|
|
42
|
+
if (!wants)
|
|
43
|
+
return { id: null, goal };
|
|
44
|
+
for (const kind of wants) {
|
|
45
|
+
const hit = [...others].reverse().find((a) => a.act === kind && (!a.to?.length || a.to.includes(me)));
|
|
46
|
+
if (hit)
|
|
47
|
+
return { id: hit.id, goal: hit.goal ?? goal, note: `replying to [${hit.seq}] ${hit.act} from ${hit.from} (pass --ref to pick another)` };
|
|
48
|
+
}
|
|
49
|
+
return { id: null, goal, error: `${act} must reference the act it answers — none from a peer found${goal ? ` in ${goal}` : ""}. See pantheon history, then --ref <id or sequence number>` };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=history-view.js.map
|
package/dist/host-env.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
const CLOUD_VARS = [
|
|
3
|
+
["CODESPACES", "GitHub Codespaces ($CODESPACES)"],
|
|
4
|
+
["REMOTE_CONTAINERS", "a VS Code dev container ($REMOTE_CONTAINERS)"],
|
|
5
|
+
["REMOTE_CONTAINERS_IPC", "a VS Code dev container ($REMOTE_CONTAINERS_IPC)"],
|
|
6
|
+
["DEVCONTAINER", "a dev container ($DEVCONTAINER)"],
|
|
7
|
+
["GITPOD_WORKSPACE_ID", "a Gitpod workspace ($GITPOD_WORKSPACE_ID)"],
|
|
8
|
+
["CLOUD_SHELL", "a cloud shell ($CLOUD_SHELL)"],
|
|
9
|
+
];
|
|
10
|
+
export function hostEnv(env = process.env, plat = process.platform, exists = existsSync, read = (p) => readFileSync(p, "utf8")) {
|
|
11
|
+
if (plat === "darwin") {
|
|
12
|
+
return { platform: plat, kind: "local", evidence: [], ephemeral: false, desktop: true };
|
|
13
|
+
}
|
|
14
|
+
const evidence = [];
|
|
15
|
+
let kind = "local";
|
|
16
|
+
for (const [name, label] of CLOUD_VARS) {
|
|
17
|
+
if (env[name]) {
|
|
18
|
+
evidence.push(label);
|
|
19
|
+
kind = "cloud-workspace";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (exists("/.dockerenv"))
|
|
23
|
+
evidence.push("/.dockerenv exists");
|
|
24
|
+
if (exists("/run/.containerenv"))
|
|
25
|
+
evidence.push("/run/.containerenv exists (podman)");
|
|
26
|
+
let cgroup = "";
|
|
27
|
+
try {
|
|
28
|
+
cgroup = exists("/proc/1/cgroup") ? read("/proc/1/cgroup") : "";
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
if (/docker|containerd|kubepods|lxc/.test(cgroup))
|
|
32
|
+
evidence.push("pid 1 is in a container cgroup (/proc/1/cgroup)");
|
|
33
|
+
if (evidence.length && kind === "local")
|
|
34
|
+
kind = "container";
|
|
35
|
+
return {
|
|
36
|
+
platform: plat,
|
|
37
|
+
kind,
|
|
38
|
+
evidence,
|
|
39
|
+
ephemeral: kind !== "local",
|
|
40
|
+
desktop: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function ephemeralWarning(host) {
|
|
44
|
+
return `this looks like ${host.kind === "container" ? "a container" : "a cloud workspace"}, not the computer you work on `
|
|
45
|
+
+ `(${host.evidence.join("; ")}). That is why this machine reports ${host.platform}: your editor's agent is running `
|
|
46
|
+
+ `somewhere else, so the menu bar and desktop alerts cannot be installed, and anything installed here disappears with `
|
|
47
|
+
+ `the session. Your account and team are fine — run the same setup from a terminal on your own computer to finish it.`;
|
|
48
|
+
}
|
|
49
|
+
export function desktopUnavailable(host, what) {
|
|
50
|
+
if (host.desktop)
|
|
51
|
+
return null;
|
|
52
|
+
if (host.ephemeral) {
|
|
53
|
+
return `not available — ${what} ${what === "desktop alerts" ? "need" : "needs"} your own computer, and this is ${host.kind === "container" ? "a container" : "a cloud workspace"} reporting ${host.platform}`;
|
|
54
|
+
}
|
|
55
|
+
return `not available — ${what} ${host.platform === "darwin" ? "could not be built here" : `${what === "desktop alerts" ? "are" : "is"} macOS-only, and this machine is ${host.platform}`}`;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=host-env.js.map
|
package/dist/mcp.js
CHANGED
|
@@ -9,7 +9,7 @@ import { VERSION } from "./version.js";
|
|
|
9
9
|
import { backendFromLabel } from "./agents.js";
|
|
10
10
|
import { agentHookStates } from "./notify.js";
|
|
11
11
|
import { reportMcpBusy, reportMcpIdle } from "./mcp-activity.js";
|
|
12
|
-
import { loadProfile, organizationProfile, profileInitialized, routeDecision } from "./profile.js";
|
|
12
|
+
import { loadProfile, organizationProfile, profileInitialized, routeDecision, reservedDecisionsNote } from "./profile.js";
|
|
13
13
|
import { identityLine, renderBrief } from "./brief.js";
|
|
14
14
|
import { recentRuns } from "./spend.js";
|
|
15
15
|
const ACT_HELP = {
|
|
@@ -60,6 +60,7 @@ YOU WERE WOKEN BY A PEER. No human is present in this session.
|
|
|
60
60
|
`
|
|
61
61
|
: "";
|
|
62
62
|
const profile = loadProfile(cfg);
|
|
63
|
+
const reserved = reservedDecisionsNote(profile);
|
|
63
64
|
const identity = profileInitialized(profile)
|
|
64
65
|
? ` You work as ${identityLine({
|
|
65
66
|
member: cfg.member,
|
|
@@ -87,7 +88,8 @@ RULES
|
|
|
87
88
|
The organization directory — not job-title guesswork — determines whose human or agent owns it.
|
|
88
89
|
When a peer's human personally must decide and their agent may be offline, use HUMAN_REQUIRED and name that one peer in to.
|
|
89
90
|
Do not use HUMAN_REQUIRED instead of ASK for something their delegate can answer. Their delegate settles it with ANSWER.
|
|
90
|
-
ESCALATE and HUMAN_REQUIRED outlive a goal close until the named human has actually answered
|
|
91
|
+
ESCALATE and HUMAN_REQUIRED outlive a goal close until the named human has actually answered.${reserved ? `
|
|
92
|
+
- ${reserved}` : ""}
|
|
91
93
|
- Agree the interface BEFORE writing code that crosses it: PROPOSE, then CONTRACT once accepted.
|
|
92
94
|
- Answer every ASK addressed to you. An unanswered ASK blocks a peer and eventually stalls the goal.
|
|
93
95
|
- ACCEPT means you actually checked. If something is wrong, OBJECT with the reason.
|
package/dist/profile.js
CHANGED
|
@@ -33,6 +33,14 @@ export function defaultProfile(cfg) {
|
|
|
33
33
|
updated_at: 0,
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
+
export function reservedDecisionsNote(profile) {
|
|
37
|
+
const mine = Object.entries(profile.authority).filter(([, m]) => m === "human_required").map(([d]) => d);
|
|
38
|
+
if (!mine.length)
|
|
39
|
+
return "";
|
|
40
|
+
return `YOUR HUMAN DECIDES THESE PERSONALLY: ${mine.join(", ")}. If a peer asks you to settle one — approve a
|
|
41
|
+
deploy, agree a price, sign off a release — do not answer it yourself, however well you have checked the
|
|
42
|
+
work. Say the review result if you have one, then ESCALATE and stop; tell the peer it is with your human.`;
|
|
43
|
+
}
|
|
36
44
|
export function wakeDefault(profile) {
|
|
37
45
|
return (profile.agent.wake_default ?? "anyone") === "anyone";
|
|
38
46
|
}
|
package/dist/protocol.js
CHANGED
|
@@ -132,6 +132,15 @@ export const MakeInvite = z.object({
|
|
|
132
132
|
may_wake: z.boolean().default(true),
|
|
133
133
|
ttl_minutes: z.number().int().min(1).max(10080).default(60),
|
|
134
134
|
});
|
|
135
|
+
export const ListInvites = z.object({
|
|
136
|
+
t: z.literal("invites"),
|
|
137
|
+
cid: z.string().max(64).optional(),
|
|
138
|
+
});
|
|
139
|
+
export const RevokeInvite = z.object({
|
|
140
|
+
t: z.literal("revoke_invite"),
|
|
141
|
+
cid: z.string().max(64).optional(),
|
|
142
|
+
token: z.string().min(6).max(64),
|
|
143
|
+
});
|
|
135
144
|
export const Admit = z.object({
|
|
136
145
|
t: z.literal("admit"),
|
|
137
146
|
cid: z.string().max(64).optional(),
|
|
@@ -183,7 +192,7 @@ export const WorkerState = z.object({
|
|
|
183
192
|
detail: z.string().max(200).optional(),
|
|
184
193
|
until: z.number().int().positive().optional(),
|
|
185
194
|
});
|
|
186
|
-
export const Inbound = z.discriminatedUnion("t", [Hello, SendAct, OpenGoal, Claim, Admit, MakeInvite, PublishProfile, BindRepo, DeleteSpace, WorkerState, Ping]);
|
|
195
|
+
export const Inbound = z.discriminatedUnion("t", [Hello, SendAct, OpenGoal, Claim, Admit, MakeInvite, ListInvites, RevokeInvite, PublishProfile, BindRepo, DeleteSpace, WorkerState, Ping]);
|
|
187
196
|
export function shouldDeferToHuman(sessions, work, opts = {}) {
|
|
188
197
|
const now = opts.now ?? Date.now();
|
|
189
198
|
const idleMs = opts.idleMs ?? LIMITS.workerIdleMs;
|
package/dist/quickstart.js
CHANGED
|
@@ -12,6 +12,7 @@ import { detectAvailable, agentById } from "./agents.js";
|
|
|
12
12
|
import { installService } from "./service.js";
|
|
13
13
|
import { ensureNativeNotifier } from "./notifier.js";
|
|
14
14
|
import { ensureTrayBinary, writeTrayCommands } from "./tray.js";
|
|
15
|
+
import { desktopUnavailable, ephemeralWarning, hostEnv } from "./host-env.js";
|
|
15
16
|
import { existsSync } from "node:fs";
|
|
16
17
|
import { grant as grantCap, loadPolicy, policyPath, savePolicy } from "./worker-policy.js";
|
|
17
18
|
import { Relay } from "./relay.js";
|
|
@@ -27,9 +28,12 @@ export function wireUp(opts, deps = {}) {
|
|
|
27
28
|
const installSvc = deps.installService ?? installService;
|
|
28
29
|
const ensureNotifier = deps.ensureNotifier ?? ensureNativeNotifier;
|
|
29
30
|
const ensureTray = deps.ensureTray ?? ensureTrayBinary;
|
|
31
|
+
const host = deps.host ?? hostEnv();
|
|
30
32
|
const tools = installForUser().map((t) => ({ tool: t.tool, status: t.status }));
|
|
31
33
|
const granted = [];
|
|
32
34
|
const warnings = [];
|
|
35
|
+
if (host.ephemeral)
|
|
36
|
+
warnings.push(ephemeralWarning(host));
|
|
33
37
|
if (opts.hook) {
|
|
34
38
|
try {
|
|
35
39
|
const { agents } = installHooks("user");
|
|
@@ -47,7 +51,10 @@ export function wireUp(opts, deps = {}) {
|
|
|
47
51
|
else {
|
|
48
52
|
tools.push({ tool: "wake-up hook", status: "skipped (--no-hook) — a peer's message waits until you type" });
|
|
49
53
|
}
|
|
50
|
-
if (opts.notifications) {
|
|
54
|
+
if (opts.notifications && !host.desktop) {
|
|
55
|
+
tools.push({ tool: "notifications", status: desktopUnavailable(host, "desktop alerts") });
|
|
56
|
+
}
|
|
57
|
+
else if (opts.notifications) {
|
|
51
58
|
try {
|
|
52
59
|
ensureNotifier();
|
|
53
60
|
installSvc("notifier", homedir(), []);
|
|
@@ -61,7 +68,10 @@ export function wireUp(opts, deps = {}) {
|
|
|
61
68
|
else {
|
|
62
69
|
tools.push({ tool: "notifications", status: "off (--no-notifications) — human escalations only appear in pantheon status" });
|
|
63
70
|
}
|
|
64
|
-
if (opts.menubar) {
|
|
71
|
+
if (opts.menubar && !host.desktop) {
|
|
72
|
+
tools.push({ tool: "menu bar", status: desktopUnavailable(host, "the menu bar") });
|
|
73
|
+
}
|
|
74
|
+
else if (opts.menubar) {
|
|
65
75
|
try {
|
|
66
76
|
ensureTray();
|
|
67
77
|
writeTrayCommands();
|
|
@@ -241,7 +251,7 @@ export async function start(opts) {
|
|
|
241
251
|
const wired = wireUp({
|
|
242
252
|
hook: opts.hook !== false,
|
|
243
253
|
notifications: opts.notifications !== false,
|
|
244
|
-
menubar: opts.menubar !== false
|
|
254
|
+
menubar: opts.menubar !== false,
|
|
245
255
|
unattended: opts.unattended ?? null,
|
|
246
256
|
...(opts.workerHow ? { workerHow: opts.workerHow } : {}),
|
|
247
257
|
});
|
|
@@ -290,7 +300,7 @@ export async function join(opts) {
|
|
|
290
300
|
const wired = wireUp({
|
|
291
301
|
hook: opts.hook !== false,
|
|
292
302
|
notifications: opts.notifications !== false,
|
|
293
|
-
menubar: opts.menubar !== false
|
|
303
|
+
menubar: opts.menubar !== false,
|
|
294
304
|
unattended: opts.unattended ?? null,
|
|
295
305
|
...(opts.workerHow ? { workerHow: opts.workerHow } : {}),
|
|
296
306
|
});
|