aimux-cli 0.1.19 → 0.1.20
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 +13 -4
- package/bin/aimux +4 -0
- package/bin/aimux-dev +2 -6
- package/dist/agent-output-parser-audit.d.ts +23 -0
- package/dist/agent-output-parser-audit.js +187 -0
- package/dist/agent-output-parser-contract.d.ts +9 -0
- package/dist/agent-output-parser-contract.js +33 -0
- package/dist/agent-output-parser-fixtures.d.ts +15 -0
- package/dist/agent-output-parser-fixtures.js +593 -0
- package/dist/agent-output-parser-harness.d.ts +21 -0
- package/dist/agent-output-parser-harness.js +43 -0
- package/dist/agent-output-parser-test-utils.d.ts +1 -0
- package/dist/agent-output-parser-test-utils.js +7 -0
- package/dist/agent-output-parser.js +215 -35
- package/dist/atomic-write.d.ts +15 -0
- package/dist/atomic-write.js +69 -4
- package/dist/attachment-store.d.ts +7 -0
- package/dist/attachment-store.js +64 -5
- package/dist/backend-session-discovery.d.ts +17 -0
- package/dist/backend-session-discovery.js +57 -0
- package/dist/config.js +9 -4
- package/dist/connection-targets.js +20 -1
- package/dist/context/context-bridge.js +4 -1
- package/dist/credentials.js +3 -6
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.js +16 -0
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.js +1 -0
- package/dist/dashboard/targets.js +14 -2
- package/dist/dashboard/ui-state-store.js +4 -3
- package/dist/last-used.js +3 -2
- package/dist/launcher-env.d.ts +4 -0
- package/dist/launcher-env.js +70 -0
- package/dist/main.js +16 -1
- package/dist/metadata-server.d.ts +13 -2
- package/dist/metadata-server.js +60 -4
- package/dist/metadata-store.js +4 -3
- package/dist/mobile-push-bridge.d.ts +8 -0
- package/dist/mobile-push-bridge.js +22 -0
- package/dist/mobile-push-throttle.d.ts +23 -0
- package/dist/mobile-push-throttle.js +53 -0
- package/dist/multiplexer/dashboard-model.js +3 -2
- package/dist/multiplexer/dashboard-ops.d.ts +3 -2
- package/dist/multiplexer/dashboard-ops.js +2 -2
- package/dist/multiplexer/dashboard-tail-methods.d.ts +3 -2
- package/dist/multiplexer/dashboard-tail-methods.js +2 -2
- package/dist/multiplexer/dashboard-view-methods.js +2 -0
- package/dist/multiplexer/index.d.ts +1 -1
- package/dist/multiplexer/index.js +4 -4
- package/dist/multiplexer/persistence-methods.js +2 -1
- package/dist/multiplexer/runtime-lifecycle-methods.js +6 -2
- package/dist/multiplexer/runtime-state.js +13 -1
- package/dist/multiplexer/service-state-snapshot.js +4 -2
- package/dist/multiplexer/services.js +5 -4
- package/dist/multiplexer/session-launch.d.ts +1 -1
- package/dist/multiplexer/session-launch.js +18 -6
- package/dist/multiplexer/session-runtime-core.js +9 -2
- package/dist/multiplexer/tool-picker.d.ts +2 -1
- package/dist/multiplexer/tool-picker.js +29 -21
- package/dist/notify.d.ts +1 -1
- package/dist/notify.js +8 -5
- package/dist/paths.js +50 -4
- package/dist/project-takeover.d.ts +1 -0
- package/dist/project-takeover.js +117 -0
- package/dist/relay-client.d.ts +10 -0
- package/dist/relay-client.js +5 -0
- package/dist/runtime-core/backend-id-reconcile.d.ts +13 -0
- package/dist/runtime-core/backend-id-reconcile.js +23 -0
- package/dist/runtime-core/exchange-store.js +3 -8
- package/dist/runtime-core/topology-store.js +3 -8
- package/dist/runtime-owner.d.ts +3 -0
- package/dist/runtime-owner.js +10 -0
- package/dist/shell-args.d.ts +13 -0
- package/dist/shell-args.js +25 -0
- package/dist/shell-hooks.d.ts +1 -0
- package/dist/shell-hooks.js +1 -0
- package/dist/team.js +4 -3
- package/dist/tmux/runtime-manager.js +2 -0
- package/dist/tui/screens/dashboard-renderers.js +6 -6
- package/dist/vitest.setup.d.ts +1 -0
- package/dist/vitest.setup.js +9 -0
- package/dist-ui/_expo/static/css/web-8782287775683e5a944b821b854d0f60.css +1 -0
- package/dist-ui/_expo/static/js/web/{entry-477c745b2adc79367a4380ecf07d9ff6.js → entry-90d00d223eefabe5cc21e4329b274fa5.js} +260 -252
- package/dist-ui/index.html +2 -2
- package/package.json +3 -1
- package/dist-ui/_expo/static/css/web-30453ede1678c16acb08b97e83e8646d.css +0 -1
package/dist/notify.js
CHANGED
|
@@ -3,6 +3,7 @@ import { execFile } from "node:child_process";
|
|
|
3
3
|
import { loadConfig } from "./config.js";
|
|
4
4
|
import { debug } from "./debug.js";
|
|
5
5
|
import { shouldSuppressNotification } from "./notification-context.js";
|
|
6
|
+
import { forwardAlertToMobilePush } from "./mobile-push-bridge.js";
|
|
6
7
|
let cachedConfig = null;
|
|
7
8
|
function getNotifyConfig() {
|
|
8
9
|
if (!cachedConfig) {
|
|
@@ -68,9 +69,9 @@ export function notifyComplete(sessionId) {
|
|
|
68
69
|
export function notifyAlert(event) {
|
|
69
70
|
const config = getNotifyConfig();
|
|
70
71
|
if (!config.enabled)
|
|
71
|
-
return;
|
|
72
|
+
return false;
|
|
72
73
|
if (shouldSuppressNotification(event))
|
|
73
|
-
return;
|
|
74
|
+
return false;
|
|
74
75
|
if ((event.kind === "notification" ||
|
|
75
76
|
event.kind === "needs_input" ||
|
|
76
77
|
event.kind === "message_waiting" ||
|
|
@@ -78,13 +79,15 @@ export function notifyAlert(event) {
|
|
|
78
79
|
event.kind === "task_assigned" ||
|
|
79
80
|
event.kind === "review_waiting") &&
|
|
80
81
|
!config.onPrompt) {
|
|
81
|
-
return;
|
|
82
|
+
return false;
|
|
82
83
|
}
|
|
83
84
|
if (event.kind === "task_done" && !config.onComplete)
|
|
84
|
-
return;
|
|
85
|
+
return false;
|
|
85
86
|
if ((event.kind === "task_failed" || event.kind === "blocked") && !config.onError)
|
|
86
|
-
return;
|
|
87
|
+
return false;
|
|
87
88
|
send(event.title || "aimux", event.message || event.sessionId || event.kind);
|
|
89
|
+
forwardAlertToMobilePush(event);
|
|
90
|
+
return true;
|
|
88
91
|
}
|
|
89
92
|
export function notifyRemoteClientConnected(input) {
|
|
90
93
|
const title = typeof input.title === "string" && input.title.trim().length > 0 ? input.title : "aimux remote access";
|
package/dist/paths.js
CHANGED
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
14
|
import { execSync } from "node:child_process";
|
|
15
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
15
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
16
16
|
import { join, basename, resolve, dirname, sep } from "node:path";
|
|
17
|
-
import { homedir } from "node:os";
|
|
17
|
+
import { homedir, tmpdir } from "node:os";
|
|
18
|
+
import { quarantineCorruptFile, writeJsonAtomic } from "./atomic-write.js";
|
|
18
19
|
// ── Cached state (populated by initPaths) ──────────────────────────
|
|
19
20
|
let _repoRoot = null;
|
|
20
21
|
let _projectId = null;
|
|
@@ -264,25 +265,69 @@ export function getGlobalTeamPath() {
|
|
|
264
265
|
export function getProjectsRegistryPath() {
|
|
265
266
|
return join(getGlobalAimuxDir(), "projects.json");
|
|
266
267
|
}
|
|
268
|
+
const MAX_PROJECT_REGISTRY_ENTRIES = 500;
|
|
269
|
+
const OS_TMP_DIR = tmpdir();
|
|
270
|
+
const TEMP_DIR_CANDIDATES = [OS_TMP_DIR, "/tmp", "/private/tmp", "/var/tmp"];
|
|
271
|
+
const TEMP_DIRS = (() => {
|
|
272
|
+
const dirs = new Set();
|
|
273
|
+
for (const dir of TEMP_DIR_CANDIDATES) {
|
|
274
|
+
const resolved = resolve(dir);
|
|
275
|
+
dirs.add(resolved);
|
|
276
|
+
try {
|
|
277
|
+
dirs.add(realpathSync(resolved));
|
|
278
|
+
}
|
|
279
|
+
catch { }
|
|
280
|
+
}
|
|
281
|
+
return [...dirs];
|
|
282
|
+
})();
|
|
283
|
+
function isPathInsideDir(path, dir) {
|
|
284
|
+
return path === dir || path.startsWith(`${dir}${sep}`);
|
|
285
|
+
}
|
|
286
|
+
function isEphemeralTempProjectRoot(repoRoot) {
|
|
287
|
+
const resolved = resolve(repoRoot);
|
|
288
|
+
return basename(resolved).startsWith("aimux-") && TEMP_DIRS.some((dir) => isPathInsideDir(resolved, dir));
|
|
289
|
+
}
|
|
290
|
+
function assertRegistryWithinCap(projects) {
|
|
291
|
+
if (projects.length <= MAX_PROJECT_REGISTRY_ENTRIES)
|
|
292
|
+
return;
|
|
293
|
+
throw new Error(`aimux project registry has ${projects.length} entries; cap is ${MAX_PROJECT_REGISTRY_ENTRIES}. ` +
|
|
294
|
+
`Refusing to continue because the registry is likely polluted. Remove stale entries from ${getProjectsRegistryPath()}.`);
|
|
295
|
+
}
|
|
296
|
+
function normalizeRegistry(registry) {
|
|
297
|
+
const projectsById = new Map();
|
|
298
|
+
for (const project of registry.projects) {
|
|
299
|
+
if (isEphemeralTempProjectRoot(project.repoRoot))
|
|
300
|
+
continue;
|
|
301
|
+
projectsById.set(project.id, project);
|
|
302
|
+
}
|
|
303
|
+
const projects = [...projectsById.values()];
|
|
304
|
+
assertRegistryWithinCap(projects);
|
|
305
|
+
return { version: 1, projects };
|
|
306
|
+
}
|
|
267
307
|
function loadRegistry() {
|
|
268
308
|
const path = getProjectsRegistryPath();
|
|
269
309
|
if (!existsSync(path))
|
|
270
310
|
return { version: 1, projects: [] };
|
|
311
|
+
let registry;
|
|
271
312
|
try {
|
|
272
|
-
|
|
313
|
+
registry = JSON.parse(readFileSync(path, "utf-8"));
|
|
273
314
|
}
|
|
274
315
|
catch {
|
|
316
|
+
quarantineCorruptFile(path);
|
|
275
317
|
return { version: 1, projects: [] };
|
|
276
318
|
}
|
|
319
|
+
return normalizeRegistry(registry);
|
|
277
320
|
}
|
|
278
321
|
function saveRegistry(registry) {
|
|
279
322
|
const dir = getGlobalAimuxDir();
|
|
280
323
|
if (!existsSync(dir))
|
|
281
324
|
mkdirSync(dir, { recursive: true });
|
|
282
|
-
|
|
325
|
+
writeJsonAtomic(getProjectsRegistryPath(), registry);
|
|
283
326
|
}
|
|
284
327
|
function registerProject() {
|
|
285
328
|
assertInitialized();
|
|
329
|
+
if (isEphemeralTempProjectRoot(_repoRoot))
|
|
330
|
+
return;
|
|
286
331
|
const registry = loadRegistry();
|
|
287
332
|
const idx = registry.projects.findIndex((p) => p.id === _projectId);
|
|
288
333
|
const entry = {
|
|
@@ -297,6 +342,7 @@ function registerProject() {
|
|
|
297
342
|
else {
|
|
298
343
|
registry.projects.push(entry);
|
|
299
344
|
}
|
|
345
|
+
assertRegistryWithinCap(registry.projects);
|
|
300
346
|
saveRegistry(registry);
|
|
301
347
|
}
|
|
302
348
|
export function listProjects() {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function takeOverProjectFromOtherOwners(projectRoot: string): Promise<void>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { getGlobalAimuxDir, getProjectIdFor } from "./paths.js";
|
|
6
|
+
import { writeJsonAtomic } from "./atomic-write.js";
|
|
7
|
+
import { requestJson } from "./http-client.js";
|
|
8
|
+
import { log } from "./debug.js";
|
|
9
|
+
function isPidAlive(pid) {
|
|
10
|
+
try {
|
|
11
|
+
process.kill(pid, 0);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function knownOwners() {
|
|
19
|
+
return [
|
|
20
|
+
{ home: join(homedir(), ".aimux"), port: 43190 },
|
|
21
|
+
{ home: join(homedir(), ".aimux-dev"), port: 43191 },
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
function readJson(path) {
|
|
25
|
+
if (!existsSync(path))
|
|
26
|
+
return null;
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function writeJson(path, value) {
|
|
35
|
+
writeJsonAtomic(path, value);
|
|
36
|
+
}
|
|
37
|
+
function isAimuxProjectServiceProcess(pid) {
|
|
38
|
+
try {
|
|
39
|
+
const args = execFileSync("ps", ["-o", "args=", "-p", String(pid)], {
|
|
40
|
+
encoding: "utf8",
|
|
41
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
42
|
+
});
|
|
43
|
+
return args.includes("__project-service-internal");
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function requestOtherOwnerStop(owner, projectRoot) {
|
|
50
|
+
const info = readJson(join(owner.home, "daemon", "daemon.json"));
|
|
51
|
+
const port = info?.port ?? owner.port;
|
|
52
|
+
if (!info?.pid || !isPidAlive(info.pid))
|
|
53
|
+
return false;
|
|
54
|
+
try {
|
|
55
|
+
const { status, json } = await requestJson(`http://127.0.0.1:${port}/projects/stop`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "content-type": "application/json" },
|
|
58
|
+
body: { projectRoot },
|
|
59
|
+
timeoutMs: 1500,
|
|
60
|
+
});
|
|
61
|
+
return status >= 200 && status < 300 && json?.ok !== false;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function cleanOtherOwnerProjectState(owner, projectId) {
|
|
68
|
+
const statePath = join(owner.home, "daemon", "state.json");
|
|
69
|
+
const state = readJson(statePath);
|
|
70
|
+
const entry = state?.projects?.[projectId];
|
|
71
|
+
if (entry?.pid && isPidAlive(entry.pid)) {
|
|
72
|
+
if (isAimuxProjectServiceProcess(entry.pid)) {
|
|
73
|
+
try {
|
|
74
|
+
process.kill(entry.pid, "SIGTERM");
|
|
75
|
+
}
|
|
76
|
+
catch { }
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
log.warn("skipping stale takeover pid with unverified identity", "daemon", {
|
|
80
|
+
ownerHome: owner.home,
|
|
81
|
+
projectId,
|
|
82
|
+
projectRoot: entry.projectRoot,
|
|
83
|
+
pid: entry.pid,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (state?.projects?.[projectId]) {
|
|
88
|
+
delete state.projects[projectId];
|
|
89
|
+
writeJson(statePath, state);
|
|
90
|
+
}
|
|
91
|
+
const projectStateDir = join(owner.home, "projects", projectId);
|
|
92
|
+
for (const file of ["metadata-api.json", "metadata-api.txt", "host.json"]) {
|
|
93
|
+
try {
|
|
94
|
+
rmSync(join(projectStateDir, file), { force: true });
|
|
95
|
+
}
|
|
96
|
+
catch { }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export async function takeOverProjectFromOtherOwners(projectRoot) {
|
|
100
|
+
const currentHome = resolve(getGlobalAimuxDir());
|
|
101
|
+
const projectId = getProjectIdFor(projectRoot);
|
|
102
|
+
for (const owner of knownOwners()) {
|
|
103
|
+
if (resolve(owner.home) === currentHome)
|
|
104
|
+
continue;
|
|
105
|
+
try {
|
|
106
|
+
await requestOtherOwnerStop(owner, projectRoot);
|
|
107
|
+
cleanOtherOwnerProjectState(owner, projectId);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
log.warn("project takeover cleanup failed for alternate owner", "daemon", {
|
|
111
|
+
ownerHome: owner.home,
|
|
112
|
+
projectRoot,
|
|
113
|
+
error: error instanceof Error ? error.message : String(error),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
package/dist/relay-client.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { AimuxDaemon } from "./daemon.js";
|
|
2
|
+
export interface RelayNotificationPush {
|
|
3
|
+
title: string;
|
|
4
|
+
body: string;
|
|
5
|
+
kind?: string;
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
projectId?: string;
|
|
8
|
+
projectRoot?: string;
|
|
9
|
+
dedupeKey?: string;
|
|
10
|
+
}
|
|
2
11
|
export type RelayConnectionStatus = "connected" | "connecting" | "reconnecting" | "disconnected" | "auth_failed";
|
|
3
12
|
export interface RelayStatusSnapshot {
|
|
4
13
|
status: RelayConnectionStatus;
|
|
@@ -22,6 +31,7 @@ export declare class RelayClient {
|
|
|
22
31
|
constructor(relayUrl: string, token: string, daemon: AimuxDaemon);
|
|
23
32
|
getStatus(): RelayStatusSnapshot;
|
|
24
33
|
connect(): void;
|
|
34
|
+
pushNotification(notification: RelayNotificationPush): void;
|
|
25
35
|
disconnect(): void;
|
|
26
36
|
private handleMessage;
|
|
27
37
|
private sendRaw;
|
package/dist/relay-client.js
CHANGED
|
@@ -100,6 +100,11 @@ export class RelayClient {
|
|
|
100
100
|
catch { }
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
+
pushNotification(notification) {
|
|
104
|
+
if (!notification.title)
|
|
105
|
+
return;
|
|
106
|
+
this.sendRaw(JSON.stringify({ type: "notification_push", notification }));
|
|
107
|
+
}
|
|
103
108
|
disconnect() {
|
|
104
109
|
this.stopped = true;
|
|
105
110
|
this.status = "disconnected";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface BackendIdReconcileResult {
|
|
2
|
+
reconciled: Array<{
|
|
3
|
+
id: string;
|
|
4
|
+
backendSessionId: string;
|
|
5
|
+
}>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Backfill missing backend session ids for offline sessions from each tool's
|
|
9
|
+
* on-disk session store, so a crash that lost the id before capture does not
|
|
10
|
+
* leave the agent unresumable. Idempotent: sessions that already have an id,
|
|
11
|
+
* or whose id cannot be found on disk, are left untouched.
|
|
12
|
+
*/
|
|
13
|
+
export declare function reconcileOfflineBackendSessionIds(projectRoot?: string): BackendIdReconcileResult;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { discoverBackendSessionId } from "../backend-session-discovery.js";
|
|
2
|
+
import { getRepoRoot } from "../paths.js";
|
|
3
|
+
import { listTopologySessionStates, upsertTopologySession } from "./topology-sessions.js";
|
|
4
|
+
/**
|
|
5
|
+
* Backfill missing backend session ids for offline sessions from each tool's
|
|
6
|
+
* on-disk session store, so a crash that lost the id before capture does not
|
|
7
|
+
* leave the agent unresumable. Idempotent: sessions that already have an id,
|
|
8
|
+
* or whose id cannot be found on disk, are left untouched.
|
|
9
|
+
*/
|
|
10
|
+
export function reconcileOfflineBackendSessionIds(projectRoot = getRepoRoot()) {
|
|
11
|
+
const reconciled = [];
|
|
12
|
+
for (const session of listTopologySessionStates({ statuses: ["offline"] })) {
|
|
13
|
+
if (session.backendSessionId)
|
|
14
|
+
continue;
|
|
15
|
+
const cwd = session.worktreePath ?? projectRoot;
|
|
16
|
+
const discovered = discoverBackendSessionId(session.toolConfigKey, cwd);
|
|
17
|
+
if (!discovered)
|
|
18
|
+
continue;
|
|
19
|
+
upsertTopologySession({ ...session, backendSessionId: discovered }, "offline", { projectRoot });
|
|
20
|
+
reconciled.push({ id: session.id, backendSessionId: discovered });
|
|
21
|
+
}
|
|
22
|
+
return { reconciled };
|
|
23
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { parse, stringify } from "yaml";
|
|
4
|
+
import { atomicWrite } from "../atomic-write.js";
|
|
4
5
|
import { getRuntimeExchangePath } from "../paths.js";
|
|
5
6
|
export const RUNTIME_EXCHANGE_VERSION = 1;
|
|
6
7
|
const UPDATE_LOCK_TIMEOUT_MS = 5_000;
|
|
@@ -376,14 +377,8 @@ export class RuntimeExchangeStore {
|
|
|
376
377
|
return coerceRuntimeExchange(parsed);
|
|
377
378
|
}
|
|
378
379
|
write(exchange) {
|
|
379
|
-
mkdirSync(dirname(this.path), { recursive: true });
|
|
380
380
|
const normalized = coerceRuntimeExchange(exchange);
|
|
381
|
-
|
|
382
|
-
writeFileSync(tmpPath, stringify(normalized, {
|
|
383
|
-
lineWidth: 120,
|
|
384
|
-
sortMapEntries: false,
|
|
385
|
-
}));
|
|
386
|
-
renameSync(tmpPath, this.path);
|
|
381
|
+
atomicWrite(this.path, stringify(normalized, { lineWidth: 120, sortMapEntries: false }));
|
|
387
382
|
return normalized;
|
|
388
383
|
}
|
|
389
384
|
acquireUpdateLock() {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { parse, stringify } from "yaml";
|
|
4
|
+
import { atomicWrite } from "../atomic-write.js";
|
|
4
5
|
import { getRuntimeTopologyPath } from "../paths.js";
|
|
5
6
|
export const RUNTIME_TOPOLOGY_VERSION = 1;
|
|
6
7
|
const UPDATE_LOCK_TIMEOUT_MS = 5_000;
|
|
@@ -370,14 +371,8 @@ export class RuntimeTopologyStore {
|
|
|
370
371
|
return coerceRuntimeTopology(parsed);
|
|
371
372
|
}
|
|
372
373
|
write(topology) {
|
|
373
|
-
mkdirSync(dirname(this.path), { recursive: true });
|
|
374
374
|
const normalized = coerceRuntimeTopology(topology);
|
|
375
|
-
|
|
376
|
-
writeFileSync(tmpPath, stringify(normalized, {
|
|
377
|
-
lineWidth: 120,
|
|
378
|
-
sortMapEntries: false,
|
|
379
|
-
}));
|
|
380
|
-
renameSync(tmpPath, this.path);
|
|
375
|
+
atomicWrite(this.path, stringify(normalized, { lineWidth: 120, sortMapEntries: false }));
|
|
381
376
|
return normalized;
|
|
382
377
|
}
|
|
383
378
|
acquireUpdateLock() {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getGlobalAimuxDir } from "./paths.js";
|
|
2
|
+
const DEFAULT_DAEMON_PORT = "43190";
|
|
3
|
+
export const TMUX_RUNTIME_OWNER_OPTION = "@aimux-runtime-owner";
|
|
4
|
+
export const TMUX_DASHBOARD_OWNER_OPTION = "@aimux-dashboard-owner";
|
|
5
|
+
export function getRuntimeOwnerId() {
|
|
6
|
+
return JSON.stringify({
|
|
7
|
+
home: getGlobalAimuxDir(),
|
|
8
|
+
port: process.env.AIMUX_DAEMON_PORT?.trim() || DEFAULT_DAEMON_PORT,
|
|
9
|
+
});
|
|
10
|
+
}
|
package/dist/shell-args.d.ts
CHANGED
|
@@ -1 +1,14 @@
|
|
|
1
|
+
export interface LaunchOverride {
|
|
2
|
+
/** Binary to exec. May differ from the tool's configured command. */
|
|
3
|
+
command: string;
|
|
4
|
+
/** Full argument list (replaces the tool's default args, not appended). */
|
|
5
|
+
args: string[];
|
|
6
|
+
/** Extra env vars merged into the managed launch environment. */
|
|
7
|
+
env?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Parse a full command line where leading NAME=VALUE tokens are environment
|
|
11
|
+
* assignments, the next token is the command, and the rest are its args.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseLaunchCommandLine(input: string): LaunchOverride;
|
|
1
14
|
export declare function parseShellArgs(input: string): string[];
|
package/dist/shell-args.js
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
const ENV_ASSIGNMENT = /^([A-Za-z_][A-Za-z0-9_]*)=([\s\S]*)$/;
|
|
2
|
+
/**
|
|
3
|
+
* Parse a full command line where leading NAME=VALUE tokens are environment
|
|
4
|
+
* assignments, the next token is the command, and the rest are its args.
|
|
5
|
+
*/
|
|
6
|
+
export function parseLaunchCommandLine(input) {
|
|
7
|
+
const tokens = parseShellArgs(input);
|
|
8
|
+
const env = {};
|
|
9
|
+
let i = 0;
|
|
10
|
+
for (; i < tokens.length; i++) {
|
|
11
|
+
const match = ENV_ASSIGNMENT.exec(tokens[i]);
|
|
12
|
+
if (!match)
|
|
13
|
+
break;
|
|
14
|
+
env[match[1]] = match[2];
|
|
15
|
+
}
|
|
16
|
+
const command = tokens[i];
|
|
17
|
+
if (!command) {
|
|
18
|
+
throw new Error("no command to launch");
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
command,
|
|
22
|
+
args: tokens.slice(i + 1),
|
|
23
|
+
env: Object.keys(env).length ? env : undefined,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
1
26
|
export function parseShellArgs(input) {
|
|
2
27
|
const args = [];
|
|
3
28
|
let current = "";
|
package/dist/shell-hooks.d.ts
CHANGED
package/dist/shell-hooks.js
CHANGED
|
@@ -163,6 +163,7 @@ export function prepareShellIntegration(projectRoot, shellPath = process.env.SHE
|
|
|
163
163
|
export function wrapCommandWithShellIntegration(opts) {
|
|
164
164
|
const prepared = prepareShellIntegration(opts.projectRoot, opts.shellPath);
|
|
165
165
|
const envArgs = [
|
|
166
|
+
...Object.entries(opts.extraEnv ?? {}).map(([key, value]) => `${key}=${value}`),
|
|
166
167
|
`AIMUX_SESSION_ID=${opts.sessionId}`,
|
|
167
168
|
`AIMUX_TOOL=${opts.tool}`,
|
|
168
169
|
`AIMUX_METADATA_ENDPOINT_FILE=${join(getProjectStateDirFor(opts.projectRoot), "metadata-api.txt")}`,
|
package/dist/team.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync,
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { getLocalAimuxDir, getProjectTeamPath, getGlobalTeamPath, getGlobalAimuxDir } from "./paths.js";
|
|
3
|
+
import { writeJsonAtomic } from "./atomic-write.js";
|
|
3
4
|
export function isTeammateSession(session) {
|
|
4
5
|
return Boolean(session?.team?.parentSessionId);
|
|
5
6
|
}
|
|
@@ -91,7 +92,7 @@ export function saveTeamConfig(config) {
|
|
|
91
92
|
if (!existsSync(dir)) {
|
|
92
93
|
mkdirSync(dir, { recursive: true });
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
+
writeJsonAtomic(getProjectTeamPath(), config);
|
|
95
96
|
}
|
|
96
97
|
/**
|
|
97
98
|
* Save team config at the global level (~/.aimux/team.json).
|
|
@@ -101,7 +102,7 @@ export function saveGlobalTeamConfig(config) {
|
|
|
101
102
|
if (!existsSync(dir)) {
|
|
102
103
|
mkdirSync(dir, { recursive: true });
|
|
103
104
|
}
|
|
104
|
-
|
|
105
|
+
writeJsonAtomic(getGlobalTeamPath(), config);
|
|
105
106
|
}
|
|
106
107
|
/**
|
|
107
108
|
* Build a role-specific preamble string for injection into an agent's system prompt.
|
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { loadConfig } from "../config.js";
|
|
8
8
|
import { debug, log } from "../debug.js";
|
|
9
9
|
import { getProjectStateDirFor } from "../paths.js";
|
|
10
|
+
import { getRuntimeOwnerId, TMUX_RUNTIME_OWNER_OPTION } from "../runtime-owner.js";
|
|
10
11
|
export function isDashboardWindowName(name) {
|
|
11
12
|
return name === "dashboard" || name.startsWith("dashboard-");
|
|
12
13
|
}
|
|
@@ -677,6 +678,7 @@ export class TmuxRuntimeManager {
|
|
|
677
678
|
const statuslineCommand = this.getStatuslineCommandSpec();
|
|
678
679
|
const projectStateDir = getProjectStateDirFor(projectRoot);
|
|
679
680
|
this.exec(["set-option", "-t", sessionName, "@aimux-project-root", projectRoot]);
|
|
681
|
+
this.exec(["set-option", "-t", sessionName, TMUX_RUNTIME_OWNER_OPTION, getRuntimeOwnerId()]);
|
|
680
682
|
this.exec(["set-option", "-t", sessionName, "prefix", MANAGED_TMUX_SESSION_OPTIONS.prefix]);
|
|
681
683
|
this.exec(["set-option", "-t", sessionName, "prefix2", MANAGED_TMUX_SESSION_OPTIONS.prefix2]);
|
|
682
684
|
this.exec(["set-option", "-t", sessionName, "mouse", MANAGED_TMUX_SESSION_OPTIONS.mouse]);
|
|
@@ -422,12 +422,12 @@ export function renderDashboardFrame(state, cols, rows) {
|
|
|
422
422
|
lines.push("");
|
|
423
423
|
return lines.slice(0, height);
|
|
424
424
|
};
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
"─".repeat(Math.max(0, cols))
|
|
429
|
-
"",
|
|
430
|
-
];
|
|
425
|
+
const devBadge = state.isDevRuntime ? "\x1b[1;30;43m DEV \x1b[0m " : "";
|
|
426
|
+
const title = `${devBadge}\x1b[1maimux\x1b[0m — agent multiplexer${state.runtimeLabel ? ` \x1b[32m● ${state.runtimeLabel}\x1b[0m` : ""}`;
|
|
427
|
+
const divider = state.isDevRuntime
|
|
428
|
+
? `\x1b[33m${"─".repeat(Math.max(0, cols))}\x1b[0m`
|
|
429
|
+
: "─".repeat(Math.max(0, cols));
|
|
430
|
+
const header = ["", centerInBlock(title), divider, ""];
|
|
431
431
|
const content = [];
|
|
432
432
|
const operationFailures = state.operationFailures ?? [];
|
|
433
433
|
if (operationFailures.length > 0) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterAll } from "vitest";
|
|
5
|
+
const aimuxTestHome = mkdtempSync(join(tmpdir(), "aimux-vitest-home-"));
|
|
6
|
+
process.env.AIMUX_HOME = aimuxTestHome;
|
|
7
|
+
afterAll(() => {
|
|
8
|
+
rmSync(aimuxTestHome, { recursive: true, force: true });
|
|
9
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::-ms-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border:0 solid #e5e7eb}:before,:after{--tw-content:""}html,:host{-webkit-text-size-adjust:100%;tab-size:4;-webkit-font-feature-settings:normal;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{line-height:inherit;margin:0}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{-webkit-font-feature-settings:normal;font-feature-settings:normal;font-variation-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{-webkit-font-feature-settings:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}dialog{padding:0}textarea{resize:vertical}input::-webkit-input-placeholder{opacity:1;color:#9ca3af}textarea::-webkit-input-placeholder{opacity:1;color:#9ca3af}:is(input:-ms-placeholder-shown,textarea:-ms-placeholder-shown){opacity:1;color:#9ca3af}:is(input:placeholder-shown,textarea:placeholder-shown){opacity:1;color:#9ca3af}input::-moz-placeholder{opacity:1;color:#9ca3af}textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--css-interop-darkMode:class dark;--css-interop:true;--css-interop-nativewind:true;--background:0 0% 100%;--foreground:240 10% 3.9%;--card:0 0% 100%;--card-foreground:240 10% 3.9%;--primary:240 5.9% 10%;--primary-foreground:0 0% 98%;--secondary:240 4.8% 95.9%;--secondary-foreground:240 5.9% 10%;--muted:240 4.8% 95.9%;--muted-foreground:240 3.8% 46.1%;--accent:240 4.8% 95.9%;--accent-foreground:240 5.9% 10%;--destructive:0 84.2% 60.2%;--destructive-foreground:0 0% 98%;--border:240 5.9% 90%;--input:240 5.9% 90%;--ring:240 5.9% 10%}.dark:root{--background:240 10% 3.9%;--foreground:0 0% 98%;--card:240 6% 10%;--card-foreground:0 0% 98%;--primary:0 0% 98%;--primary-foreground:240 5.9% 10%;--secondary:240 5% 13%;--secondary-foreground:0 0% 98%;--muted:240 5% 13%;--muted-foreground:240 5% 64.9%;--accent:240 5% 16%;--accent-foreground:0 0% 98%;--destructive:0 62.8% 30.6%;--destructive-foreground:0 0% 98%;--border:240 4% 22%;--input:240 4% 22%;--ring:240 4.9% 83.9%}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.-right-1{right:-.25rem}.-top-1{top:-.25rem}.right-0{right:0}.right-1{right:.25rem}.right-2{right:.5rem}.top-0{top:0}.top-1{top:.25rem}.top-full{top:100%}.z-30{z-index:30}.z-50{z-index:50}.m-4{margin:1rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-3{margin-left:.75rem;margin-right:.75rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-5{margin-top:1.25rem;margin-bottom:1.25rem}.-ml-1{margin-left:-.25rem}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-1\.5{margin-left:.375rem}.ml-2{margin-left:.5rem}.ml-2\.5{margin-left:.625rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-\[20px\]{margin-left:20px}.ml-\[22px\]{margin-left:22px}.ml-auto{margin-left:auto}.mr-1\.5{margin-right:.375rem}.mr-2{margin-right:.5rem}.mr-2\.5{margin-right:.625rem}.mr-3{margin-right:.75rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-12{margin-top:3rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-7{margin-top:1.75rem}.inline{display:inline}.flex{display:flex}.table{display:table}.h-0\.5{height:.125rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[6px\]{height:6px}.h-\[7px\]{height:7px}.h-full{height:100%}.h-px{height:1px}.max-h-32{max-height:8rem}.max-h-\[620px\]{max-height:620px}.min-h-11{min-height:2.75rem}.min-h-\[220px\]{min-height:220px}.min-h-\[400px\]{min-height:400px}.min-h-\[40px\]{min-height:40px}.min-h-\[420px\]{min-height:420px}.w-1\.5{width:.375rem}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-24{width:6rem}.w-28{width:7rem}.w-3{width:.75rem}.w-5{width:1.25rem}.w-56{width:14rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-\[13px\]{width:13px}.w-\[14px\]{width:14px}.w-\[6px\]{width:6px}.w-\[7px\]{width:7px}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0}.min-w-\[112px\]{min-width:112px}.min-w-\[18px\]{min-width:18px}.min-w-\[62px\]{min-width:62px}.max-w-\[280px\]{max-width:280px}.max-w-\[360px\]{max-width:360px}.max-w-\[380px\]{max-width:380px}.max-w-\[400px\]{max-width:400px}.max-w-\[420px\]{max-width:420px}.max-w-\[80\%\]{max-width:80%}.max-w-\[90\%\]{max-width:90%}.flex-1{flex:1}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.rotate-45{--tw-rotate:45deg;-webkit-transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{-webkit-transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.self-start{align-self:flex-start}.self-end{align-self:flex-end}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-\[1\.5px\]{border-radius:1.5px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-\[1\.5px\]{border-width:1.5px}.border-b{border-bottom-width:1px}.border-b-\[1\.5px\]{border-bottom-width:1.5px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-\[\#2a2b31\]{--tw-border-opacity:1;border-color:rgb(42 43 49/var(--tw-border-opacity,1))}.border-\[\#3a3c44\]{--tw-border-opacity:1;border-color:rgb(58 60 68/var(--tw-border-opacity,1))}.border-\[\#44464e\]{--tw-border-opacity:1;border-color:rgb(68 70 78/var(--tw-border-opacity,1))}.border-\[\#4ade80\]{--tw-border-opacity:1;border-color:rgb(74 222 128/var(--tw-border-opacity,1))}.border-\[\#6b6d75\]{--tw-border-opacity:1;border-color:rgb(107 109 117/var(--tw-border-opacity,1))}.border-\[\#edeef0\]{--tw-border-opacity:1;border-color:rgb(237 238 240/var(--tw-border-opacity,1))}.border-amber-400{--tw-border-opacity:1;border-color:rgb(251 191 36/var(--tw-border-opacity,1))}.border-amber-500\/30{border-color:#f59e0b4d}.border-amber-500\/40{border-color:#f59e0b66}.border-border{border-color:hsl(var(--border))}.border-destructive\/50{border-color:hsl(var(--destructive) / .5)}.border-emerald-500\/40{border-color:#10b98166}.border-foreground{border-color:hsl(var(--foreground))}.border-primary-foreground\/35{border-color:hsl(var(--primary-foreground) / .35)}.border-red-500\/40{border-color:#ef444466}.border-ring{border-color:hsl(var(--ring))}.border-sky-500\/40{border-color:#0ea5e966}.border-transparent{border-color:#0000}.border-violet-500\/40{border-color:#8b5cf666}.bg-\[\#161719\]{--tw-bg-opacity:1;background-color:rgb(22 23 25/var(--tw-bg-opacity,1))}.bg-\[\#1f2025\]{--tw-bg-opacity:1;background-color:rgb(31 32 37/var(--tw-bg-opacity,1))}.bg-\[\#26272d\]{--tw-bg-opacity:1;background-color:rgb(38 39 45/var(--tw-bg-opacity,1))}.bg-\[\#4ade80\]{--tw-bg-opacity:1;background-color:rgb(74 222 128/var(--tw-bg-opacity,1))}.bg-\[\#5b5d66\]{--tw-bg-opacity:1;background-color:rgb(91 93 102/var(--tw-bg-opacity,1))}.bg-accent{background-color:hsl(var(--accent))}.bg-amber-400{--tw-bg-opacity:1;background-color:rgb(251 191 36/var(--tw-bg-opacity,1))}.bg-amber-500{--tw-bg-opacity:1;background-color:rgb(245 158 11/var(--tw-bg-opacity,1))}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-amber-500\/15{background-color:#f59e0b26}.bg-background{background-color:hsl(var(--background))}.bg-background\/60{background-color:hsl(var(--background) / .6)}.bg-background\/90{background-color:hsl(var(--background) / .9)}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-destructive\/10{background-color:hsl(var(--destructive) / .1)}.bg-emerald-500{--tw-bg-opacity:1;background-color:rgb(16 185 129/var(--tw-bg-opacity,1))}.bg-emerald-500\/10{background-color:#10b9811a}.bg-emerald-500\/15{background-color:#10b98126}.bg-foreground{background-color:hsl(var(--foreground))}.bg-green-100{--tw-bg-opacity:1;background-color:rgb(220 252 231/var(--tw-bg-opacity,1))}.bg-muted{background-color:hsl(var(--muted))}.bg-primary{background-color:hsl(var(--primary))}.bg-primary-foreground\/15{background-color:hsl(var(--primary-foreground) / .15)}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-red-950\/20{background-color:#450a0a33}.bg-secondary{background-color:hsl(var(--secondary))}.bg-secondary\/40{background-color:hsl(var(--secondary) / .4)}.bg-sky-500\/10{background-color:#0ea5e91a}.bg-transparent{background-color:#0000}.bg-violet-500\/10{background-color:#8b5cf61a}.bg-zinc-400{--tw-bg-opacity:1;background-color:rgb(161 161 170/var(--tw-bg-opacity,1))}.bg-zinc-500{--tw-bg-opacity:1;background-color:rgb(113 113 122/var(--tw-bg-opacity,1))}.bg-zinc-500\/10{background-color:#71717a1a}.bg-zinc-500\/15{background-color:#71717a26}.bg-zinc-600{--tw-bg-opacity:1;background-color:rgb(82 82 91/var(--tw-bg-opacity,1))}.p-0{padding:0}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-0\.5{padding-left:.125rem;padding-right:.125rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-12{padding-left:3rem;padding-right:3rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-px{padding-top:1px;padding-bottom:1px}.pb-2{padding-bottom:.5rem}.pb-3\.5{padding-bottom:.875rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pr-2\.5{padding-right:.625rem}.pr-4{padding-right:1rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12\.5px\]{font-size:12.5px}.text-\[12px\]{font-size:12px}.text-\[13\.5px\]{font-size:13.5px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.text-\[16px\]{font-size:16px}.text-\[17px\]{font-size:17px}.text-\[18px\]{font-size:18px}.text-\[22px\]{font-size:22px}.text-\[28px\]{font-size:28px}.text-\[32px\]{font-size:32px}.text-\[44px\]{font-size:44px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-5{line-height:1.25rem}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-\[0\.5em\]{letter-spacing:.5em}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-\[\#4ade80\]{--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity,1))}.text-\[\#787a83\]{--tw-text-opacity:1;color:rgb(120 122 131/var(--tw-text-opacity,1))}.text-\[\#a6a8b0\]{--tw-text-opacity:1;color:rgb(166 168 176/var(--tw-text-opacity,1))}.text-\[\#edeef0\]{--tw-text-opacity:1;color:rgb(237 238 240/var(--tw-text-opacity,1))}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-amber-500{--tw-text-opacity:1;color:rgb(245 158 11/var(--tw-text-opacity,1))}.text-background{color:hsl(var(--background))}.text-background\/70{color:hsl(var(--background) / .7)}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity,1))}.text-card-foreground{color:hsl(var(--card-foreground))}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-emerald-400{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.text-foreground{color:hsl(var(--foreground))}.text-foreground\/90{color:hsl(var(--foreground) / .9)}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity,1))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-muted-foreground\/70{color:hsl(var(--muted-foreground) / .7)}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-zinc-300{--tw-text-opacity:1;color:rgb(212 212 216/var(--tw-text-opacity,1))}.text-zinc-400{--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.text-zinc-500{--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.decoration-muted-foreground{-webkit-text-decoration-color:hsl(var(--muted-foreground));text-decoration-color:hsl(var(--muted-foreground))}.opacity-50{opacity:.5}.outline{outline-style:solid}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow,0 0 #0000)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;scrollbar-width:thin}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}body{background-color:#fff}.dark body{background-color:#09090b}body.has-navbar:before{content:"";z-index:-1;pointer-events:none;background-color:#f2f2f2;border-bottom:1px solid #e4e4e7;height:137px;position:fixed;top:0;left:0;right:0}.dark body.has-navbar:before{background-color:#010101;border-bottom-color:#27272a}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:#8080804d;border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#80808080}.placeholder\:text-muted-foreground::-webkit-input-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground:-ms-placeholder-shown{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.last\:border-b-0:last-child{border-bottom-width:0}.hover\:bg-\[\#232429\]:hover{--tw-bg-opacity:1;background-color:rgb(35 36 41/var(--tw-bg-opacity,1))}.active\:bg-\[\#26272d\]:active{--tw-bg-opacity:1;background-color:rgb(38 39 45/var(--tw-bg-opacity,1))}.active\:bg-accent:active{background-color:hsl(var(--accent))}.active\:bg-accent\/50:active{background-color:hsl(var(--accent) / .5)}.active\:bg-accent\/60:active{background-color:hsl(var(--accent) / .6)}.active\:opacity-70:active{opacity:.7}.active\:opacity-80:active{opacity:.8}.disabled\:opacity-40:disabled{opacity:.4}.dark\:bg-green-900:is(.dark *){--tw-bg-opacity:1;background-color:rgb(20 83 45/var(--tw-bg-opacity,1))}.dark\:text-green-200:is(.dark *){--tw-text-opacity:1;color:rgb(187 247 208/var(--tw-text-opacity,1))}@media (width>=768px){.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:py-7{padding-top:1.75rem;padding-bottom:1.75rem}}@media (width>=1024px){.lg\:w-\[320px\]{width:320px}.lg\:flex-row{flex-direction:row}}
|