tmux-ide 2.7.0 → 2.8.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 +22 -5
- package/bin/cli.js +3532 -1090
- package/bin/cli.ts +368 -71
- package/package.json +2 -1
- package/packages/contracts/src/__tests__/control.test.ts +154 -0
- package/packages/contracts/src/control.ts +217 -0
- package/packages/contracts/src/index.ts +1 -0
- package/packages/daemon/dist/control/client.d.ts +23 -0
- package/packages/daemon/dist/control/client.js +105 -0
- package/packages/daemon/dist/control/dispatch.d.ts +34 -0
- package/packages/daemon/dist/control/dispatch.js +83 -0
- package/packages/daemon/dist/control/fanout.d.ts +19 -0
- package/packages/daemon/dist/control/fanout.js +37 -0
- package/packages/daemon/dist/control/frames.d.ts +23 -0
- package/packages/daemon/dist/control/frames.js +37 -0
- package/packages/daemon/dist/control/lifecycle.d.ts +45 -0
- package/packages/daemon/dist/control/lifecycle.js +114 -0
- package/packages/daemon/dist/control/server.d.ts +16 -0
- package/packages/daemon/dist/control/server.js +214 -0
- package/packages/daemon/dist/control/verbs.d.ts +11 -0
- package/packages/daemon/dist/control/verbs.js +91 -0
- package/packages/daemon/dist/doctor.d.ts +18 -0
- package/packages/daemon/dist/doctor.js +105 -15
- package/packages/daemon/dist/lib/agent-discovery.d.ts +27 -2
- package/packages/daemon/dist/lib/agent-discovery.js +29 -14
- package/packages/daemon/dist/lib/app-config.d.ts +106 -0
- package/packages/daemon/dist/lib/app-config.js +104 -5
- package/packages/daemon/dist/lib/manifest-pack.d.ts +79 -0
- package/packages/daemon/dist/lib/manifest-pack.js +232 -0
- package/packages/daemon/dist/lib/state-home.d.ts +2 -0
- package/packages/daemon/dist/lib/state-home.js +12 -0
- package/packages/daemon/dist/lib/update-check.js +5 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +34 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/PkgInfo +1 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/AppIcon.icns +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +139 -0
- package/packages/daemon/dist/restore.d.ts +35 -8
- package/packages/daemon/dist/restore.js +52 -15
- package/packages/daemon/dist/send.d.ts +33 -1
- package/packages/daemon/dist/send.js +32 -19
- package/packages/daemon/src/control/client.ts +128 -0
- package/packages/daemon/src/control/dispatch.ts +107 -0
- package/packages/daemon/src/control/fanout.ts +44 -0
- package/packages/daemon/src/control/frames.ts +40 -0
- package/packages/daemon/src/control/lifecycle.ts +151 -0
- package/packages/daemon/src/control/server.ts +237 -0
- package/packages/daemon/src/control/verbs.ts +118 -0
- package/packages/daemon/src/doctor.ts +113 -28
- package/packages/daemon/src/lib/agent-discovery.ts +53 -13
- package/packages/daemon/src/lib/app-config.ts +103 -5
- package/packages/daemon/src/lib/manifest-pack.ts +255 -0
- package/packages/daemon/src/lib/state-home.ts +13 -0
- package/packages/daemon/src/lib/update-check.ts +5 -0
- package/packages/daemon/src/restore.ts +53 -15
- package/packages/daemon/src/send.ts +55 -21
- package/packages/daemon/src/tui/chrome/events.ts +4 -4
- package/packages/daemon/src/tui/chrome/front-door.ts +39 -0
- package/packages/daemon/src/tui/chrome/notify-prefs.ts +58 -0
- package/packages/daemon/src/tui/chrome/notify-state.ts +76 -0
- package/packages/daemon/src/tui/chrome/notify.ts +582 -84
- package/packages/daemon/src/tui/chrome/updater.ts +268 -62
- package/packages/daemon/src/tui/detect/classify.ts +34 -0
- package/packages/daemon/src/tui/detect/manifest-loader.ts +54 -5
- package/packages/daemon/src/tui/detect/manifest.ts +24 -3
- package/packages/daemon/src/tui/detect/manifests.ts +240 -6
- package/packages/daemon/src/tui/detect/process-tree.ts +13 -3
- package/packages/daemon/src/tui/detect/session-id.ts +503 -0
- package/packages/daemon/src/tui/integrations/opencode.ts +121 -0
- package/packages/daemon/src/tui/mirror/agent-chip.ts +40 -11
- package/packages/daemon/src/tui/mirror/agent-lifecycle.ts +437 -0
- package/packages/daemon/src/tui/mirror/agent-rows.ts +27 -5
- package/packages/daemon/src/tui/mirror/app-state.ts +171 -8
- package/packages/daemon/src/tui/mirror/app.tsx +2182 -399
- package/packages/daemon/src/tui/mirror/attention.ts +110 -0
- package/packages/daemon/src/tui/mirror/dialog-stack.ts +17 -4
- package/packages/daemon/src/tui/mirror/diff-model.ts +279 -4
- package/packages/daemon/src/tui/mirror/file-tree.ts +231 -6
- package/packages/daemon/src/tui/mirror/host-terminal.ts +49 -0
- package/packages/daemon/src/tui/mirror/hosted.ts +205 -0
- package/packages/daemon/src/tui/mirror/layout-parse.ts +154 -0
- package/packages/daemon/src/tui/mirror/menu-model.ts +27 -4
- package/packages/daemon/src/tui/mirror/palette.ts +299 -9
- package/packages/daemon/src/tui/mirror/pane-mirror.ts +82 -4
- package/packages/daemon/src/tui/mirror/pane-surface.tsx +18 -11
- package/packages/daemon/src/tui/mirror/perf-tap.ts +29 -3
- package/packages/daemon/src/tui/mirror/selection.ts +122 -8
- package/packages/daemon/src/tui/mirror/session-mirror.ts +349 -68
- package/packages/daemon/src/tui/mirror/settings-model.ts +96 -16
- package/packages/daemon/src/tui/mirror/sidebar.tsx +218 -0
- package/packages/daemon/src/tui/mirror/size-truth.ts +53 -0
- package/packages/daemon/src/tui/mirror/theme.ts +45 -0
- package/packages/daemon/src/tui/team/fuzzy.ts +20 -0
- package/packages/daemon/src/tui/team/sessions.ts +85 -7
- package/packages/daemon/src/tui/team/wait.ts +144 -0
- package/scripts/build-macos-notifier.mjs +160 -0
- package/scripts/postinstall.js +8 -1
- package/scripts/prepublish-check.mjs +37 -1
- package/scripts/publish-tap.sh +55 -0
- package/skill/SKILL.md +88 -2
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { accessSync, constants, existsSync } from "node:fs";
|
|
3
3
|
import { resolve, dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { getCurrentVersion, getUpdateStatus } from "./lib/update-check.js";
|
|
6
6
|
import { installedSkillVersion } from "./lib/skill-sync.js";
|
|
7
7
|
import { discoverAgents, presentAgents } from "./lib/agent-discovery.js";
|
|
8
8
|
import { findCompiledTui, isBunAvailable } from "./tui/compiled.js";
|
|
9
|
+
import { claudeSettingsPath } from "./tui/integrations/claude.js";
|
|
10
|
+
import { readNotificationPrefs, resolveNativeMacosNotifierPath } from "./tui/chrome/notify.js";
|
|
9
11
|
/**
|
|
10
12
|
* PURE — the "agent integrations" doctor rows, one per DISCOVERED agent (absent
|
|
11
13
|
* agents produce nothing — no noise). All rows are optional (informational, they
|
|
@@ -19,12 +21,18 @@ export function agentIntegrationRows(agents) {
|
|
|
19
21
|
return presentAgents(agents).map((agent) => {
|
|
20
22
|
const label = `agent: ${agent.id}`;
|
|
21
23
|
if (agent.integration) {
|
|
24
|
+
// What installing actually buys, honestly per mechanism: claude's hooks
|
|
25
|
+
// give ground-truth status (and record resume ids); opencode's plugin
|
|
26
|
+
// records resume ids only.
|
|
27
|
+
const benefit = agent.capture === "hooks"
|
|
28
|
+
? "for ground-truth status"
|
|
29
|
+
: "to record session ids for restore --resume-agents";
|
|
22
30
|
return agent.installed
|
|
23
31
|
? { label, pass: true, detail: "integration installed ✓", optional: true }
|
|
24
32
|
: {
|
|
25
33
|
label,
|
|
26
34
|
pass: false,
|
|
27
|
-
detail: `found on PATH — run \`tmux-ide integration install ${agent.id}\`
|
|
35
|
+
detail: `found on PATH — run \`tmux-ide integration install ${agent.id}\` ${benefit}`,
|
|
28
36
|
optional: true,
|
|
29
37
|
};
|
|
30
38
|
}
|
|
@@ -36,6 +44,52 @@ export function agentIntegrationRows(agents) {
|
|
|
36
44
|
};
|
|
37
45
|
});
|
|
38
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* PURE — the "Claude hooks target writable" row from observed facts. Both
|
|
49
|
+
* `integration install claude` and the npm postinstall write the settings
|
|
50
|
+
* file; surface a permissions problem BEFORE an install fails halfway.
|
|
51
|
+
* Optional: a machine without Claude Code shouldn't fail doctor over this.
|
|
52
|
+
*/
|
|
53
|
+
export function hooksTargetRow(facts) {
|
|
54
|
+
const label = "Claude hooks target writable";
|
|
55
|
+
if (facts.writable) {
|
|
56
|
+
return {
|
|
57
|
+
label,
|
|
58
|
+
pass: true,
|
|
59
|
+
detail: facts.fileExists ? facts.settingsPath : `${facts.settingsPath} (will be created)`,
|
|
60
|
+
optional: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
label,
|
|
65
|
+
pass: false,
|
|
66
|
+
detail: `cannot write ${facts.settingsPath} — fix its permissions (chown/chmod), or point TMUX_IDE_CLAUDE_SETTINGS at a writable path`,
|
|
67
|
+
optional: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* PURE — native notifier health, shown only when macOS notifications are ON.
|
|
72
|
+
* Release packages always include the helper; absence means an incomplete or
|
|
73
|
+
* old installation, which can still fall back to the unbranded AppleScript path.
|
|
74
|
+
* Optional — informational, never fails doctor.
|
|
75
|
+
*/
|
|
76
|
+
export function notifierRow(present) {
|
|
77
|
+
const label = "native macOS notifications";
|
|
78
|
+
if (present) {
|
|
79
|
+
return {
|
|
80
|
+
label,
|
|
81
|
+
pass: true,
|
|
82
|
+
detail: "bundled — branded banners and click-to-jump are ready",
|
|
83
|
+
optional: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
label,
|
|
88
|
+
pass: false,
|
|
89
|
+
detail: "native helper missing — reinstall tmux-ide; unbranded AppleScript banners remain available",
|
|
90
|
+
optional: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
39
93
|
function check(label, fn, { optional = false } = {}) {
|
|
40
94
|
try {
|
|
41
95
|
const result = fn();
|
|
@@ -48,7 +102,12 @@ function check(label, fn, { optional = false } = {}) {
|
|
|
48
102
|
export async function doctor({ json, } = {}) {
|
|
49
103
|
const checks = [];
|
|
50
104
|
checks.push(check("tmux installed", () => {
|
|
51
|
-
|
|
105
|
+
try {
|
|
106
|
+
execSync("which tmux", { stdio: "ignore" });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
throw new Error("not found on PATH — install it (macOS: `brew install tmux`; Debian/Ubuntu: `sudo apt install tmux`)");
|
|
110
|
+
}
|
|
52
111
|
return "found";
|
|
53
112
|
}));
|
|
54
113
|
checks.push(check("tmux version ≥ 3.0", () => {
|
|
@@ -103,18 +162,45 @@ export async function doctor({ json, } = {}) {
|
|
|
103
162
|
}
|
|
104
163
|
return "enabled";
|
|
105
164
|
}, { optional: true }));
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
165
|
+
// Optional tunnel CLIs: probe quietly (a missing binary must not leak
|
|
166
|
+
// "command not found" stderr into doctor's own output) and fail with a
|
|
167
|
+
// plain hint rather than execSync's raw error.
|
|
168
|
+
const tunnelCli = (label, cmd) => check(label, () => {
|
|
169
|
+
try {
|
|
170
|
+
return execSync(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] })
|
|
171
|
+
.trim()
|
|
172
|
+
.split("\n")[0];
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
throw new Error("not found (optional — used for remote access tunnels)");
|
|
176
|
+
}
|
|
177
|
+
}, { optional: true });
|
|
178
|
+
checks.push(tunnelCli("tailscale CLI", "tailscale version"));
|
|
179
|
+
checks.push(tunnelCli("ngrok CLI", "ngrok version"));
|
|
180
|
+
checks.push(tunnelCli("cloudflared CLI", "cloudflared --version"));
|
|
181
|
+
checks.push((() => {
|
|
182
|
+
// Claude hooks target: the settings file `integration install claude`
|
|
183
|
+
// (and the npm postinstall) will write. Writability of the file itself
|
|
184
|
+
// when it exists, else of its nearest existing ancestor directory.
|
|
185
|
+
const settingsPath = claudeSettingsPath();
|
|
186
|
+
const fileExists = existsSync(settingsPath);
|
|
187
|
+
let probe = fileExists ? settingsPath : dirname(settingsPath);
|
|
188
|
+
while (!existsSync(probe)) {
|
|
189
|
+
const parent = dirname(probe);
|
|
190
|
+
if (parent === probe)
|
|
191
|
+
break;
|
|
192
|
+
probe = parent;
|
|
193
|
+
}
|
|
194
|
+
let writable = false;
|
|
195
|
+
try {
|
|
196
|
+
accessSync(probe, constants.W_OK);
|
|
197
|
+
writable = true;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// leave writable=false — the row explains the fix
|
|
201
|
+
}
|
|
202
|
+
return hooksTargetRow({ settingsPath, fileExists, writable });
|
|
203
|
+
})());
|
|
118
204
|
checks.push(check("tmux-ide up to date", () => {
|
|
119
205
|
const current = getCurrentVersion();
|
|
120
206
|
const { latest, updateAvailable } = getUpdateStatus({ currentVersion: current });
|
|
@@ -137,6 +223,10 @@ export async function doctor({ json, } = {}) {
|
|
|
137
223
|
}
|
|
138
224
|
return `in sync (v${installed})`;
|
|
139
225
|
}, { optional: true }));
|
|
226
|
+
// Native branded sender: only when the macOS channel is actually on.
|
|
227
|
+
if (process.platform === "darwin" && readNotificationPrefs().macos) {
|
|
228
|
+
checks.push(notifierRow(resolveNativeMacosNotifierPath() !== null));
|
|
229
|
+
}
|
|
140
230
|
// Agent integrations: one row per agent discovered on PATH (absent → no row).
|
|
141
231
|
checks.push(...agentIntegrationRows(discoverAgents()));
|
|
142
232
|
const allPass = checks.every((c) => c.pass || c.optional);
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a kind's `@agent_session_id` (the `restore --resume-agents` key) gets
|
|
3
|
+
* captured:
|
|
4
|
+
* - `"hooks"` — the agent's own lifecycle hooks stamp it (claude; needs
|
|
5
|
+
* `integration install`).
|
|
6
|
+
* - `"plugin"` — an in-process plugin stamps it (opencode; needs
|
|
7
|
+
* `integration install`).
|
|
8
|
+
* - `"probe"` — the chrome updater discovers it from the agent's own on-disk
|
|
9
|
+
* session state (codex, cursor; automatic, nothing to install).
|
|
10
|
+
* - `null` — no defensible capture surface (see
|
|
11
|
+
* {@link ../tui/detect/session-id.ts} for the per-kind evidence).
|
|
12
|
+
*/
|
|
13
|
+
export type CaptureMechanism = "hooks" | "plugin" | "probe" | null;
|
|
1
14
|
/** A coding agent tmux-ide knows how to detect. */
|
|
2
15
|
export interface KnownAgent {
|
|
3
16
|
/** Stable id — also the `tmux-ide integration <install|status> <id>` selector. */
|
|
@@ -6,11 +19,14 @@ export interface KnownAgent {
|
|
|
6
19
|
bin: string;
|
|
7
20
|
/** True → tmux-ide ships a lifecycle-integration installer for this agent. */
|
|
8
21
|
integration: boolean;
|
|
22
|
+
/** How this kind's session id is captured for `restore --resume-agents`. */
|
|
23
|
+
capture: CaptureMechanism;
|
|
9
24
|
}
|
|
10
25
|
/**
|
|
11
26
|
* The agents tmux-ide recognizes. `integration: true` means we HAVE an installer
|
|
12
|
-
* (
|
|
13
|
-
* screen-manifest scraping only
|
|
27
|
+
* (claude's lifecycle hooks, opencode's plugin); the rest are detected via
|
|
28
|
+
* screen-manifest scraping only. `capture` records each kind's session-id
|
|
29
|
+
* story ({@link CaptureMechanism}).
|
|
14
30
|
*/
|
|
15
31
|
export declare const KNOWN_AGENTS: readonly KnownAgent[];
|
|
16
32
|
/** One probed agent: its registry facts plus what the PATH/integration probe found. */
|
|
@@ -27,6 +43,15 @@ export interface DiscoveredAgent {
|
|
|
27
43
|
* integrate (there's nothing to install) and for any agent absent from PATH.
|
|
28
44
|
*/
|
|
29
45
|
installed: boolean;
|
|
46
|
+
/** How this kind's session id is captured (copied from the registry). */
|
|
47
|
+
capture: CaptureMechanism;
|
|
48
|
+
/**
|
|
49
|
+
* Whether session-id capture is LIVE for this kind on this machine:
|
|
50
|
+
* `"probe"` capture is automatic whenever the binary is present; hook/plugin
|
|
51
|
+
* capture requires the integration to be installed; `null` capture is never
|
|
52
|
+
* active.
|
|
53
|
+
*/
|
|
54
|
+
captureActive: boolean;
|
|
30
55
|
}
|
|
31
56
|
/** Resolve a binary to its absolute path, or null. Must never throw. */
|
|
32
57
|
export type WhichRunner = (bin: string) => string | null;
|
|
@@ -17,17 +17,21 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { execFileSync } from "node:child_process";
|
|
19
19
|
import { claudeIntegrationStatus } from "../tui/integrations/claude.js";
|
|
20
|
+
import { opencodeIntegrationStatus } from "../tui/integrations/opencode.js";
|
|
20
21
|
/**
|
|
21
22
|
* The agents tmux-ide recognizes. `integration: true` means we HAVE an installer
|
|
22
|
-
* (
|
|
23
|
-
* screen-manifest scraping only
|
|
23
|
+
* (claude's lifecycle hooks, opencode's plugin); the rest are detected via
|
|
24
|
+
* screen-manifest scraping only. `capture` records each kind's session-id
|
|
25
|
+
* story ({@link CaptureMechanism}).
|
|
24
26
|
*/
|
|
25
27
|
export const KNOWN_AGENTS = [
|
|
26
|
-
{ id: "claude", bin: "claude", integration: true },
|
|
27
|
-
{ id: "codex", bin: "codex", integration: false },
|
|
28
|
-
{ id: "opencode", bin: "opencode", integration:
|
|
29
|
-
{ id: "gemini", bin: "gemini", integration: false },
|
|
30
|
-
{ id: "aider", bin: "aider", integration: false },
|
|
28
|
+
{ id: "claude", bin: "claude", integration: true, capture: "hooks" },
|
|
29
|
+
{ id: "codex", bin: "codex", integration: false, capture: "probe" },
|
|
30
|
+
{ id: "opencode", bin: "opencode", integration: true, capture: "plugin" },
|
|
31
|
+
{ id: "gemini", bin: "gemini", integration: false, capture: null },
|
|
32
|
+
{ id: "aider", bin: "aider", integration: false, capture: null },
|
|
33
|
+
{ id: "cursor", bin: "cursor-agent", integration: false, capture: "probe" },
|
|
34
|
+
{ id: "copilot", bin: "copilot", integration: false, capture: null },
|
|
31
35
|
];
|
|
32
36
|
/**
|
|
33
37
|
* Default which-runner: `which <bin>`, hard-capped at 2s, swallowing every
|
|
@@ -51,15 +55,17 @@ const defaultWhich = (bin) => {
|
|
|
51
55
|
}
|
|
52
56
|
};
|
|
53
57
|
/**
|
|
54
|
-
* Default integration probe:
|
|
55
|
-
*
|
|
56
|
-
* discovery never throws.
|
|
58
|
+
* Default integration probe: claude's hooks and opencode's plugin are the two
|
|
59
|
+
* shipped installers. Reads the real settings/plugin file; any failure degrades
|
|
60
|
+
* to false so discovery never throws.
|
|
57
61
|
*/
|
|
58
62
|
const defaultIntegrationProbe = (agentId) => {
|
|
59
|
-
if (agentId !== "claude")
|
|
60
|
-
return false;
|
|
61
63
|
try {
|
|
62
|
-
|
|
64
|
+
if (agentId === "claude")
|
|
65
|
+
return claudeIntegrationStatus().installed;
|
|
66
|
+
if (agentId === "opencode")
|
|
67
|
+
return opencodeIntegrationStatus().installed;
|
|
68
|
+
return false;
|
|
63
69
|
}
|
|
64
70
|
catch {
|
|
65
71
|
return false;
|
|
@@ -76,7 +82,16 @@ export function discoverAgents(which = defaultWhich, isInstalled = defaultIntegr
|
|
|
76
82
|
const path = which(agent.bin);
|
|
77
83
|
const present = path !== null;
|
|
78
84
|
const installed = present && agent.integration ? isInstalled(agent.id) : false;
|
|
79
|
-
|
|
85
|
+
const captureActive = agent.capture === "probe" ? present : agent.capture !== null ? installed : false;
|
|
86
|
+
return {
|
|
87
|
+
id: agent.id,
|
|
88
|
+
bin: agent.bin,
|
|
89
|
+
integration: agent.integration,
|
|
90
|
+
path,
|
|
91
|
+
installed,
|
|
92
|
+
capture: agent.capture,
|
|
93
|
+
captureActive,
|
|
94
|
+
};
|
|
80
95
|
});
|
|
81
96
|
}
|
|
82
97
|
/** The subset of discovered agents actually present on PATH (path resolved). */
|
|
@@ -60,10 +60,29 @@ export interface AppUpdater {
|
|
|
60
60
|
/** Write a disaster-recovery snapshot every N ticks (default 15). */
|
|
61
61
|
snapshotEvery: number;
|
|
62
62
|
}
|
|
63
|
+
/** The sound channel's states: ping sound on blocked only, on every ping, or never. */
|
|
64
|
+
export type NotificationSound = "blocked" | "all" | "none";
|
|
63
65
|
/** Notification channel toggles. */
|
|
64
66
|
export interface AppNotifications {
|
|
65
67
|
toast: boolean;
|
|
66
68
|
macos: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Terminal-native banners (M25.2): an OSC 9 (kitty: OSC 99) escape written to
|
|
71
|
+
* each eligible attached client's tty, so terminals that surface them (iTerm2,
|
|
72
|
+
* Ghostty, WezTerm, kitty) show a native notification even over ssh. Default
|
|
73
|
+
* true — terminals without support ignore the sequence entirely.
|
|
74
|
+
*/
|
|
75
|
+
terminal: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Seconds the OS-LEVEL channels (banner / terminal escape / sound / BEL) wait
|
|
78
|
+
* before firing, re-verifying the pane is STILL in the notified state — a
|
|
79
|
+
* blocked→working flap inside the window fires nothing. 0 fires immediately.
|
|
80
|
+
* The in-app toast never waits. Tick-native: the delay rounds up to the
|
|
81
|
+
* updater's next tick at or after `delaySeconds`.
|
|
82
|
+
*/
|
|
83
|
+
delaySeconds: number;
|
|
84
|
+
/** Sound channel (M25.2) — `afplay`/`paplay` + a BEL to eligible client ttys. */
|
|
85
|
+
sound: NotificationSound;
|
|
67
86
|
}
|
|
68
87
|
/** Restore behaviour toggles. */
|
|
69
88
|
export interface AppRestore {
|
|
@@ -72,6 +91,14 @@ export interface AppRestore {
|
|
|
72
91
|
/** Update-check toggles (consumed later by the update-flow card). */
|
|
73
92
|
export interface AppUpdates {
|
|
74
93
|
check: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Whether the daily update check ALSO refreshes the remote agent-detection
|
|
96
|
+
* manifest pack into `~/.tmux-ide/agent-detection/pack/` (M25.4). Default
|
|
97
|
+
* FALSE — auto-installing detection rules is opt-in; `tmux-ide update
|
|
98
|
+
* --manifests` always works explicitly. Requires `check: true` (the refresh
|
|
99
|
+
* rides the same daily throttle — no timer of its own).
|
|
100
|
+
*/
|
|
101
|
+
manifests: boolean;
|
|
75
102
|
}
|
|
76
103
|
/** First-run welcome toggles. */
|
|
77
104
|
export interface AppWelcome {
|
|
@@ -92,6 +119,55 @@ export interface AppIntegrations {
|
|
|
92
119
|
*/
|
|
93
120
|
offer: boolean;
|
|
94
121
|
}
|
|
122
|
+
/** The unified-app front-door decision (M22.6). */
|
|
123
|
+
export interface AppApp {
|
|
124
|
+
/**
|
|
125
|
+
* Whether bare `tmux-ide` (no ide.yml here, no `--team`) launches the unified
|
|
126
|
+
* app (`tmux-ide app`) instead of the classic team cockpit. Default false —
|
|
127
|
+
* the flip is opt-in until the default-entry decision is made. `tmux-ide team`
|
|
128
|
+
* (the explicit cockpit) and a project's `ide.yml` auto-launch are unaffected.
|
|
129
|
+
*/
|
|
130
|
+
frontDoor: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Whether `tmux-ide app` runs HOSTED by default (M23.2): the app lives in an
|
|
133
|
+
* internal `_tmux-ide-app` tmux session and the invoking terminal attaches to
|
|
134
|
+
* it, so the cockpit survives the terminal and reattaches from anywhere (^q
|
|
135
|
+
* detaches instead of quitting). Default false — the same behavior as the
|
|
136
|
+
* explicit `--detachable` flag; `--detachable`/`--hosted` still force it on a
|
|
137
|
+
* single run.
|
|
138
|
+
*/
|
|
139
|
+
detachable: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* When a plain left drag on an app-mouse pane SELECTS locally instead of
|
|
142
|
+
* forwarding to the pane's app (M24.2). "agents" (default): panes our own
|
|
143
|
+
* detection matches to a fleet agent entry select; other app-mouse panes
|
|
144
|
+
* (vim/htop) forward. "always": every app-mouse pane selects on drag.
|
|
145
|
+
* "never": every app-mouse pane forwards (the pre-M24.2 behavior). A genuine
|
|
146
|
+
* click (press+release in one cell) is still forwarded on select-default
|
|
147
|
+
* panes; shift inverts a pane's default; the right-click pane menu overrides
|
|
148
|
+
* per pane for the session.
|
|
149
|
+
*/
|
|
150
|
+
dragSelect: "agents" | "always" | "never";
|
|
151
|
+
/**
|
|
152
|
+
* Where a Terminal-surface "New agent" spawn starts (M24.1): `"pane"`
|
|
153
|
+
* (default) inherits the FOCUSED pane's current working directory —
|
|
154
|
+
* `#{pane_current_path}`, so the agent lands where you are — while
|
|
155
|
+
* `"session"` keeps the session/project directory. Home/sidebar spawns
|
|
156
|
+
* always use the session/project dir (no focused pane exists there).
|
|
157
|
+
*/
|
|
158
|
+
newAgentCwd: NewAgentCwd;
|
|
159
|
+
/**
|
|
160
|
+
* Whether the unified app enables the kitty keyboard protocol on its host
|
|
161
|
+
* terminal (M24.4). On (default), kitty-capable terminals deliver ⌘-modified
|
|
162
|
+
* keys — ⌘K opens the command palette — and disambiguated escapes; the app
|
|
163
|
+
* re-encodes every key for the mirrored panes either way, and terminals
|
|
164
|
+
* without the protocol ignore the request entirely. Off restores the legacy
|
|
165
|
+
* key encoding for hosts where the protocol misbehaves.
|
|
166
|
+
*/
|
|
167
|
+
kittyKeys: boolean;
|
|
168
|
+
}
|
|
169
|
+
/** The two Terminal-spawn cwd policies (see {@link AppApp.newAgentCwd}). */
|
|
170
|
+
export type NewAgentCwd = "pane" | "session";
|
|
95
171
|
/** Worktree flow config (`tmux-ide worktree`). */
|
|
96
172
|
export interface AppWorktrees {
|
|
97
173
|
/**
|
|
@@ -112,6 +188,7 @@ export interface AppConfig {
|
|
|
112
188
|
welcome: AppWelcome;
|
|
113
189
|
integrations: AppIntegrations;
|
|
114
190
|
worktrees: AppWorktrees;
|
|
191
|
+
app: AppApp;
|
|
115
192
|
}
|
|
116
193
|
/** The built-in config — every field's fallback. */
|
|
117
194
|
export declare const DEFAULT_APP_CONFIG: AppConfig;
|
|
@@ -145,3 +222,32 @@ export declare function loadAppConfig(): AppConfig;
|
|
|
145
222
|
export declare function getAppConfig(): AppConfig;
|
|
146
223
|
/** Drop the cache so the next {@link getAppConfig} re-reads. Test-only. */
|
|
147
224
|
export declare function _resetForTests(): void;
|
|
225
|
+
/**
|
|
226
|
+
* io — the RAW parsed config file as the user wrote it (`{}` when missing or
|
|
227
|
+
* malformed). Writers merge over THIS, not over {@link parseAppConfig}'s output:
|
|
228
|
+
* round-tripping through the parser would materialize every default into the
|
|
229
|
+
* hand-editable file and silently DROP fields the typed shape doesn't model yet
|
|
230
|
+
* (e.g. the notification polish fields `notifications.enabled` /
|
|
231
|
+
* `notifications.quietHours` that {@link ../tui/chrome/notify.ts} reads raw).
|
|
232
|
+
*/
|
|
233
|
+
export declare function loadRawAppConfig(): Record<string, unknown>;
|
|
234
|
+
/** A partial config write: plain objects merge recursively, scalars/arrays
|
|
235
|
+
* replace, and an explicit `undefined` DELETES the key (how "reset to
|
|
236
|
+
* defaults" removes a block so the parser's defaults take over). */
|
|
237
|
+
export type AppConfigPatch = {
|
|
238
|
+
[key: string]: unknown;
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* PURE — merge `patch` into `raw` without touching either input. Objects merge
|
|
242
|
+
* key-by-key (recursively), anything else replaces, and a key whose patch value
|
|
243
|
+
* is `undefined` is REMOVED from the result. Unknown user fields survive — the
|
|
244
|
+
* merge only visits keys the patch names.
|
|
245
|
+
*/
|
|
246
|
+
export declare function mergeConfigPatch(raw: Record<string, unknown>, patch: AppConfigPatch): Record<string, unknown>;
|
|
247
|
+
/**
|
|
248
|
+
* io — apply a patch to the config file ATOMICALLY (temp + rename, the same
|
|
249
|
+
* discipline as the editor save): read raw → {@link mergeConfigPatch} → write.
|
|
250
|
+
* Busts the {@link getAppConfig} process cache and returns the new parsed
|
|
251
|
+
* config. Honors `TMUX_IDE_CONFIG`, so tests never touch the real file.
|
|
252
|
+
*/
|
|
253
|
+
export declare function updateAppConfig(patch: AppConfigPatch): AppConfig;
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
* per-concern readers ({@link ../tui/chrome/notify.ts}, {@link ../restore.ts})
|
|
21
21
|
* now delegate here.
|
|
22
22
|
*/
|
|
23
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
24
24
|
import { homedir } from "node:os";
|
|
25
|
-
import { join } from "node:path";
|
|
25
|
+
import { dirname, join } from "node:path";
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
27
|
// Defaults
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
@@ -50,12 +50,19 @@ export const DEFAULT_APP_CONFIG = {
|
|
|
50
50
|
glyphs: { active: "●", inactive: "○" },
|
|
51
51
|
},
|
|
52
52
|
updater: { tickMs: 2000, snapshotEvery: 15 },
|
|
53
|
-
notifications: { toast: true, macos: false },
|
|
53
|
+
notifications: { toast: true, macos: false, terminal: true, delaySeconds: 2, sound: "blocked" },
|
|
54
54
|
restore: { resumeAgents: false },
|
|
55
|
-
updates: { check: true },
|
|
55
|
+
updates: { check: true, manifests: false },
|
|
56
56
|
welcome: { show: true },
|
|
57
57
|
integrations: { offer: true },
|
|
58
58
|
worktrees: { dir: "" },
|
|
59
|
+
app: {
|
|
60
|
+
frontDoor: false,
|
|
61
|
+
detachable: false,
|
|
62
|
+
dragSelect: "agents",
|
|
63
|
+
newAgentCwd: "pane",
|
|
64
|
+
kittyKeys: true,
|
|
65
|
+
},
|
|
59
66
|
};
|
|
60
67
|
/** The default theme tokens — the fallback threaded into the pure builders. */
|
|
61
68
|
export const DEFAULT_THEME = DEFAULT_APP_CONFIG.theme;
|
|
@@ -82,6 +89,16 @@ function pickBool(value, fallback) {
|
|
|
82
89
|
function pickPosInt(value, fallback) {
|
|
83
90
|
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : fallback;
|
|
84
91
|
}
|
|
92
|
+
/** A non-negative integer (0 allowed — e.g. "no delay"), else the default. */
|
|
93
|
+
function pickNonNegInt(value, fallback) {
|
|
94
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
95
|
+
}
|
|
96
|
+
/** One of the allowed literals, else the default. */
|
|
97
|
+
function pickChoice(value, allowed, fallback) {
|
|
98
|
+
return typeof value === "string" && allowed.includes(value)
|
|
99
|
+
? value
|
|
100
|
+
: fallback;
|
|
101
|
+
}
|
|
85
102
|
/**
|
|
86
103
|
* PURE — merge an unknown (typically parsed JSON) over {@link DEFAULT_APP_CONFIG}.
|
|
87
104
|
* Every block/leaf is validated independently: a missing or mistyped field falls
|
|
@@ -103,6 +120,7 @@ export function parseAppConfig(input) {
|
|
|
103
120
|
const welcome = asObject(root.welcome);
|
|
104
121
|
const integrations = asObject(root.integrations);
|
|
105
122
|
const worktrees = asObject(root.worktrees);
|
|
123
|
+
const app = asObject(root.app);
|
|
106
124
|
return {
|
|
107
125
|
keys: {
|
|
108
126
|
popup: pickString(keys.popup, D.keys.popup),
|
|
@@ -139,12 +157,25 @@ export function parseAppConfig(input) {
|
|
|
139
157
|
notifications: {
|
|
140
158
|
toast: pickBool(notifications.toast, D.notifications.toast),
|
|
141
159
|
macos: pickBool(notifications.macos, D.notifications.macos),
|
|
160
|
+
terminal: pickBool(notifications.terminal, D.notifications.terminal),
|
|
161
|
+
delaySeconds: pickNonNegInt(notifications.delaySeconds, D.notifications.delaySeconds),
|
|
162
|
+
sound: pickChoice(notifications.sound, ["blocked", "all", "none"], D.notifications.sound),
|
|
142
163
|
},
|
|
143
164
|
restore: { resumeAgents: pickBool(restore.resumeAgents, D.restore.resumeAgents) },
|
|
144
|
-
updates: {
|
|
165
|
+
updates: {
|
|
166
|
+
check: pickBool(updates.check, D.updates.check),
|
|
167
|
+
manifests: pickBool(updates.manifests, D.updates.manifests),
|
|
168
|
+
},
|
|
145
169
|
welcome: { show: pickBool(welcome.show, D.welcome.show) },
|
|
146
170
|
integrations: { offer: pickBool(integrations.offer, D.integrations.offer) },
|
|
147
171
|
worktrees: { dir: pickString(worktrees.dir, D.worktrees.dir) },
|
|
172
|
+
app: {
|
|
173
|
+
frontDoor: pickBool(app.frontDoor, D.app.frontDoor),
|
|
174
|
+
detachable: pickBool(app.detachable, D.app.detachable),
|
|
175
|
+
dragSelect: pickChoice(app.dragSelect, ["agents", "always", "never"], D.app.dragSelect),
|
|
176
|
+
newAgentCwd: pickChoice(app.newAgentCwd, ["pane", "session"], D.app.newAgentCwd),
|
|
177
|
+
kittyKeys: pickBool(app.kittyKeys, D.app.kittyKeys),
|
|
178
|
+
},
|
|
148
179
|
};
|
|
149
180
|
}
|
|
150
181
|
// ---------------------------------------------------------------------------
|
|
@@ -189,3 +220,71 @@ export function getAppConfig() {
|
|
|
189
220
|
export function _resetForTests() {
|
|
190
221
|
cached = null;
|
|
191
222
|
}
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Writes (M22.4 — the settings surface persists through here)
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
/**
|
|
227
|
+
* io — the RAW parsed config file as the user wrote it (`{}` when missing or
|
|
228
|
+
* malformed). Writers merge over THIS, not over {@link parseAppConfig}'s output:
|
|
229
|
+
* round-tripping through the parser would materialize every default into the
|
|
230
|
+
* hand-editable file and silently DROP fields the typed shape doesn't model yet
|
|
231
|
+
* (e.g. the notification polish fields `notifications.enabled` /
|
|
232
|
+
* `notifications.quietHours` that {@link ../tui/chrome/notify.ts} reads raw).
|
|
233
|
+
*/
|
|
234
|
+
export function loadRawAppConfig() {
|
|
235
|
+
const path = appConfigPath();
|
|
236
|
+
if (!existsSync(path))
|
|
237
|
+
return {};
|
|
238
|
+
try {
|
|
239
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
240
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
241
|
+
? parsed
|
|
242
|
+
: {};
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return {};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function isPlainObject(value) {
|
|
249
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* PURE — merge `patch` into `raw` without touching either input. Objects merge
|
|
253
|
+
* key-by-key (recursively), anything else replaces, and a key whose patch value
|
|
254
|
+
* is `undefined` is REMOVED from the result. Unknown user fields survive — the
|
|
255
|
+
* merge only visits keys the patch names.
|
|
256
|
+
*/
|
|
257
|
+
export function mergeConfigPatch(raw, patch) {
|
|
258
|
+
const out = { ...raw };
|
|
259
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
260
|
+
if (value === undefined) {
|
|
261
|
+
delete out[key];
|
|
262
|
+
}
|
|
263
|
+
else if (isPlainObject(value) && isPlainObject(out[key])) {
|
|
264
|
+
out[key] = mergeConfigPatch(out[key], value);
|
|
265
|
+
}
|
|
266
|
+
else if (isPlainObject(value)) {
|
|
267
|
+
out[key] = mergeConfigPatch({}, value);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
out[key] = value;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* io — apply a patch to the config file ATOMICALLY (temp + rename, the same
|
|
277
|
+
* discipline as the editor save): read raw → {@link mergeConfigPatch} → write.
|
|
278
|
+
* Busts the {@link getAppConfig} process cache and returns the new parsed
|
|
279
|
+
* config. Honors `TMUX_IDE_CONFIG`, so tests never touch the real file.
|
|
280
|
+
*/
|
|
281
|
+
export function updateAppConfig(patch) {
|
|
282
|
+
const path = appConfigPath();
|
|
283
|
+
const merged = mergeConfigPatch(loadRawAppConfig(), patch);
|
|
284
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
285
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
286
|
+
writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf-8");
|
|
287
|
+
renameSync(tmp, path);
|
|
288
|
+
cached = null;
|
|
289
|
+
return parseAppConfig(merged);
|
|
290
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { AgentManifest } from "../tui/detect/manifest.ts";
|
|
2
|
+
/** The pack-format version this build understands. */
|
|
3
|
+
export declare const MANIFEST_PACK_SCHEMA = 1;
|
|
4
|
+
/** The release asset name a pack is published under. */
|
|
5
|
+
export declare const MANIFEST_PACK_ASSET = "agent-manifests.json";
|
|
6
|
+
/** Env override for the pack URL (tests/dev; may be `file://` or loopback http). */
|
|
7
|
+
export declare const MANIFEST_PACK_URL_ENV = "TMUX_IDE_MANIFEST_PACK_URL";
|
|
8
|
+
/** A fetched/validated manifest pack. */
|
|
9
|
+
export interface ManifestPack {
|
|
10
|
+
schema: number;
|
|
11
|
+
/** Pack version — informational (surfaced in logs), any non-empty string. */
|
|
12
|
+
pack: string;
|
|
13
|
+
manifests: AgentManifest[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* PURE — the default pack URL for a version:
|
|
17
|
+
* `https://github.com/<repo>/releases/download/v<version>/agent-manifests.json`
|
|
18
|
+
* (the same asset-URL construction as the TUI binary download).
|
|
19
|
+
*/
|
|
20
|
+
export declare function manifestPackUrl(version?: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* PURE — is `url` an acceptable pack source? `https:` always; `file:` and
|
|
23
|
+
* LOOPBACK `http:` (127.0.0.1 / localhost / [::1]) only so tests and local
|
|
24
|
+
* fixtures can drive the flow. Any other scheme/host is rejected.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isAllowedPackUrl(url: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* PURE — validate an untrusted pack document. Returns the typed pack or a
|
|
29
|
+
* human-readable reason. Every manifest entry must pass the SAME structural
|
|
30
|
+
* validation user override files do ({@link validateManifestShape}) — one bad
|
|
31
|
+
* entry rejects the whole pack (a partial install would be harder to reason
|
|
32
|
+
* about than a loud failure). Never throws.
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateManifestPack(value: unknown): {
|
|
35
|
+
ok: true;
|
|
36
|
+
pack: ManifestPack;
|
|
37
|
+
} | {
|
|
38
|
+
ok: false;
|
|
39
|
+
reason: string;
|
|
40
|
+
};
|
|
41
|
+
/** The installed pack's directory: `<agent-detection>/pack/`. */
|
|
42
|
+
export declare function packDir(): string;
|
|
43
|
+
/** The installed pack's path: `<agent-detection>/pack/manifest-pack.json`. */
|
|
44
|
+
export declare function packPath(): string;
|
|
45
|
+
/**
|
|
46
|
+
* io — fetch and validate a pack. `file://` URLs are read from disk (Node's
|
|
47
|
+
* fetch does not speak file:); everything else goes through fetch with a
|
|
48
|
+
* timeout. THROWS with an actionable message on a disallowed URL, a failed
|
|
49
|
+
* fetch, malformed JSON, or a schema-invalid pack — the caller decides whether
|
|
50
|
+
* that is loud (CLI) or swallowed (the background refresh).
|
|
51
|
+
*/
|
|
52
|
+
export declare function fetchManifestPack(url: string, timeoutMs?: number): Promise<ManifestPack>;
|
|
53
|
+
/**
|
|
54
|
+
* io — write a validated pack into place atomically (temp-in-same-dir →
|
|
55
|
+
* rename, so a crash never leaves a half-written pack the loader would then
|
|
56
|
+
* warn about every run). Returns the installed path.
|
|
57
|
+
*/
|
|
58
|
+
export declare function installManifestPack(pack: ManifestPack, dest?: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* io — the `tmux-ide update --manifests` flow: resolve the URL (env override →
|
|
61
|
+
* the release-asset default), fetch, validate, install. Throws loudly on any
|
|
62
|
+
* failure (the CLI surfaces it); returns what was installed for reporting.
|
|
63
|
+
*/
|
|
64
|
+
export declare function updateManifestPack(opts?: {
|
|
65
|
+
url?: string;
|
|
66
|
+
log?: (msg: string) => void;
|
|
67
|
+
}): Promise<{
|
|
68
|
+
path: string;
|
|
69
|
+
packVersion: string;
|
|
70
|
+
count: number;
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* io — the GATED background refresh the daily update check invokes (see
|
|
74
|
+
* `runUpdateCheck` in {@link ./update-check.ts}). Opt-in via
|
|
75
|
+
* `updates.manifests: true` (default false); NEVER throws — a failed refresh
|
|
76
|
+
* just keeps the previous pack, exactly like a failed version check keeps the
|
|
77
|
+
* previous `latest`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function maybeRefreshManifestPack(): Promise<void>;
|