tmux-ide 2.6.0 → 2.6.1
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/bin/cli.js +13 -5
- package/bin/cli.ts +1 -1
- package/bunfig.toml +4 -0
- package/package.json +11 -3
- package/packages/contracts/package.json +22 -0
- package/packages/contracts/src/__tests__/ide-config.test.ts +46 -0
- package/packages/contracts/src/__tests__/terminals.test.ts +87 -0
- package/packages/contracts/src/actions-contract.ts +310 -0
- package/packages/contracts/src/actions-errors.ts +41 -0
- package/packages/contracts/src/domain.ts +36 -0
- package/packages/contracts/src/ide-config.ts +170 -0
- package/packages/contracts/src/index.ts +24 -0
- package/packages/contracts/src/lib-internal/auth.ts +13 -0
- package/packages/contracts/src/lib-internal/hq.ts +38 -0
- package/packages/contracts/src/terminals.ts +116 -0
- package/packages/contracts/src/tmux.ts +60 -0
- package/packages/contracts/src/workspace.ts +67 -0
- package/packages/daemon/src/agent-explain.ts +270 -0
- package/packages/daemon/src/attach.ts +20 -0
- package/packages/daemon/src/bin.ts +4 -0
- package/packages/daemon/src/canonical.ts +7 -0
- package/packages/daemon/src/cli.ts +499 -0
- package/packages/daemon/src/command-center/actions/contract.ts +2 -0
- package/packages/daemon/src/command-center/actions/dispatcher.ts +137 -0
- package/packages/daemon/src/command-center/actions/errors.ts +105 -0
- package/packages/daemon/src/command-center/actions/handlers/_project-context.ts +30 -0
- package/packages/daemon/src/command-center/actions/handlers/_resolve-project.ts +78 -0
- package/packages/daemon/src/command-center/actions/handlers/app-set-remote-access.ts +118 -0
- package/packages/daemon/src/command-center/actions/handlers/config-actions.ts +113 -0
- package/packages/daemon/src/command-center/actions/handlers/daemon-shutdown.ts +38 -0
- package/packages/daemon/src/command-center/actions/handlers/project-activate.ts +30 -0
- package/packages/daemon/src/command-center/actions/handlers/project-launch.ts +70 -0
- package/packages/daemon/src/command-center/actions/handlers/project-open-terminal.ts +87 -0
- package/packages/daemon/src/command-center/actions/handlers/project-restart.ts +38 -0
- package/packages/daemon/src/command-center/actions/handlers/project-stop.ts +62 -0
- package/packages/daemon/src/command-center/actions/handlers/terminal-respawn.ts +119 -0
- package/packages/daemon/src/command-center/actions/handlers/terminal-stop.ts +35 -0
- package/packages/daemon/src/command-center/actions/registry.ts +149 -0
- package/packages/daemon/src/command-center/discovery.ts +96 -0
- package/packages/daemon/src/command-center/index.ts +31 -0
- package/packages/daemon/src/command-center/schemas.ts +85 -0
- package/packages/daemon/src/command-center/server.ts +1260 -0
- package/packages/daemon/src/command-center/ws-events.ts +316 -0
- package/packages/daemon/src/config.ts +549 -0
- package/packages/daemon/src/detect.ts +248 -0
- package/packages/daemon/src/doctor.ts +242 -0
- package/packages/daemon/src/embed.ts +5 -0
- package/packages/daemon/src/index.ts +12 -0
- package/packages/daemon/src/init.ts +211 -0
- package/packages/daemon/src/inspect.ts +178 -0
- package/packages/daemon/src/js-yaml.d.ts +10 -0
- package/packages/daemon/src/launch.ts +349 -0
- package/packages/daemon/src/lib/active-projects.ts +49 -0
- package/packages/daemon/src/lib/agent-discovery.ts +121 -0
- package/packages/daemon/src/lib/app-config.ts +336 -0
- package/packages/daemon/src/lib/app-settings.ts +53 -0
- package/packages/daemon/src/lib/auth/auth-service.ts +227 -0
- package/packages/daemon/src/lib/auth/middleware.ts +56 -0
- package/packages/daemon/src/lib/auth/types.ts +2 -0
- package/packages/daemon/src/lib/auth-token.ts +5 -0
- package/packages/daemon/src/lib/authorship.ts +280 -0
- package/packages/daemon/src/lib/canonical-daemon.ts +122 -0
- package/packages/daemon/src/lib/cli-action-bridge.ts +216 -0
- package/packages/daemon/src/lib/daemon-embed.ts +782 -0
- package/packages/daemon/src/lib/daemon-watchdog.ts +111 -0
- package/packages/daemon/src/lib/daemon.ts +79 -0
- package/packages/daemon/src/lib/dot-path.ts +17 -0
- package/packages/daemon/src/lib/errors.ts +67 -0
- package/packages/daemon/src/lib/filesystem-browser.ts +292 -0
- package/packages/daemon/src/lib/launch-plan.ts +90 -0
- package/packages/daemon/src/lib/log.ts +134 -0
- package/packages/daemon/src/lib/output.ts +76 -0
- package/packages/daemon/src/lib/project-init-runner.ts +150 -0
- package/packages/daemon/src/lib/project-inspect.ts +80 -0
- package/packages/daemon/src/lib/project-onboard.ts +149 -0
- package/packages/daemon/src/lib/project-probe.ts +92 -0
- package/packages/daemon/src/lib/project-registry.ts +296 -0
- package/packages/daemon/src/lib/session-monitor.ts +122 -0
- package/packages/daemon/src/lib/session-options.ts +100 -0
- package/packages/daemon/src/lib/shell.ts +8 -0
- package/packages/daemon/src/lib/sizes.ts +36 -0
- package/packages/daemon/src/lib/skill-sync.ts +155 -0
- package/packages/daemon/src/lib/slugify.ts +10 -0
- package/packages/daemon/src/lib/terminals-store.ts +125 -0
- package/packages/daemon/src/lib/update-check.ts +298 -0
- package/packages/daemon/src/lib/update.ts +158 -0
- package/packages/daemon/src/lib/workspace-registry.ts +229 -0
- package/packages/daemon/src/lib/worktree.ts +289 -0
- package/packages/daemon/src/lib/yaml-io.ts +27 -0
- package/packages/daemon/src/ls.ts +40 -0
- package/packages/daemon/src/restart.ts +24 -0
- package/packages/daemon/src/restore.ts +514 -0
- package/packages/daemon/src/schemas/domain.ts +2 -0
- package/packages/daemon/src/schemas/filesystem.ts +34 -0
- package/packages/daemon/src/schemas/ide-config.ts +2 -0
- package/packages/daemon/src/schemas/index.ts +59 -0
- package/packages/daemon/src/schemas/inspect.ts +67 -0
- package/packages/daemon/src/schemas/registry.ts +55 -0
- package/packages/daemon/src/schemas/ws-events.ts +135 -0
- package/packages/daemon/src/send.ts +171 -0
- package/packages/daemon/src/server/README.md +15 -0
- package/packages/daemon/src/server/index.ts +74 -0
- package/packages/daemon/src/server/pty-bridge.ts +532 -0
- package/packages/daemon/src/server/standalone.ts +18 -0
- package/packages/daemon/src/server/ws-route.ts +483 -0
- package/packages/daemon/src/status.ts +59 -0
- package/packages/daemon/src/stop.ts +28 -0
- package/packages/daemon/src/terminal/NodePtyAdapter.ts +271 -0
- package/packages/daemon/src/terminal/PtyAdapter.ts +140 -0
- package/packages/daemon/src/terminal/README.md +92 -0
- package/packages/daemon/src/tui/chrome/cheatsheet.ts +260 -0
- package/packages/daemon/src/tui/chrome/chip.ts +30 -0
- package/packages/daemon/src/tui/chrome/events.ts +119 -0
- package/packages/daemon/src/tui/chrome/kitty-keys.ts +55 -0
- package/packages/daemon/src/tui/chrome/menu.ts +289 -0
- package/packages/daemon/src/tui/chrome/notify.ts +195 -0
- package/packages/daemon/src/tui/chrome/panels.ts +111 -0
- package/packages/daemon/src/tui/chrome/sidebar.ts +222 -0
- package/packages/daemon/src/tui/chrome/snapshot.ts +425 -0
- package/packages/daemon/src/tui/chrome/statusline.ts +595 -0
- package/packages/daemon/src/tui/chrome/updater.ts +428 -0
- package/packages/daemon/src/tui/chrome/welcome.ts +124 -0
- package/packages/daemon/src/tui/compiled.ts +113 -0
- package/packages/daemon/src/tui/detect/classify.ts +193 -0
- package/packages/daemon/src/tui/detect/manifest-loader.ts +192 -0
- package/packages/daemon/src/tui/detect/manifest.ts +184 -0
- package/packages/daemon/src/tui/detect/manifests.ts +226 -0
- package/packages/daemon/src/tui/detect/process-tree.ts +197 -0
- package/packages/daemon/src/tui/detect/snapshot.ts +70 -0
- package/packages/daemon/src/tui/integrations/claude.ts +176 -0
- package/packages/daemon/src/tui/integrations/offer.ts +145 -0
- package/packages/daemon/src/tui/main.ts +70 -0
- package/packages/daemon/src/tui/mirror/control-client.ts +143 -0
- package/packages/daemon/src/tui/mirror/control.ts +97 -0
- package/packages/daemon/src/tui/mirror/pane-mirror.ts +81 -0
- package/packages/daemon/src/tui/mirror/viewer.tsx +166 -0
- package/packages/daemon/src/tui/team/CONTROL.md +50 -0
- package/packages/daemon/src/tui/team/entry.ts +11 -0
- package/packages/daemon/src/tui/team/fuzzy.ts +133 -0
- package/packages/daemon/src/tui/team/home.ts +170 -0
- package/packages/daemon/src/tui/team/index.tsx +1521 -0
- package/packages/daemon/src/tui/team/input.ts +34 -0
- package/packages/daemon/src/tui/team/keymap.ts +127 -0
- package/packages/daemon/src/tui/team/mouse.ts +29 -0
- package/packages/daemon/src/tui/team/nav.ts +31 -0
- package/packages/daemon/src/tui/team/preview.ts +34 -0
- package/packages/daemon/src/tui/team/projects.ts +191 -0
- package/packages/daemon/src/tui/team/report.ts +73 -0
- package/packages/daemon/src/tui/team/sessions.ts +344 -0
- package/packages/daemon/src/tui/team/tree.ts +62 -0
- package/packages/daemon/src/types.ts +13 -0
- package/packages/daemon/src/ui/index.ts +32 -0
- package/packages/daemon/src/ui/terminal/index.ts +9 -0
- package/packages/daemon/src/ui/types.ts +91 -0
- package/packages/daemon/src/ui/web/base.css +80 -0
- package/packages/daemon/src/ui/web/components/Box.tsx +59 -0
- package/packages/daemon/src/ui/web/components/Input.tsx +32 -0
- package/packages/daemon/src/ui/web/components/ScrollBox.tsx +60 -0
- package/packages/daemon/src/ui/web/components/Text.tsx +28 -0
- package/packages/daemon/src/ui/web/hooks.ts +106 -0
- package/packages/daemon/src/ui/web/index.ts +27 -0
- package/packages/daemon/src/ui/web/render.ts +77 -0
- package/packages/daemon/src/ui/web/utils/color.ts +27 -0
- package/packages/daemon/src/validate.ts +217 -0
- package/packages/daemon/src/widgets/changes/README.md +3 -0
- package/packages/daemon/src/widgets/changes/index.tsx +691 -0
- package/packages/daemon/src/widgets/config/README.md +3 -0
- package/packages/daemon/src/widgets/config/index.tsx +481 -0
- package/packages/daemon/src/widgets/explorer/README.md +3 -0
- package/packages/daemon/src/widgets/explorer/breadcrumbs.tsx +77 -0
- package/packages/daemon/src/widgets/explorer/footer.tsx +20 -0
- package/packages/daemon/src/widgets/explorer/header.tsx +23 -0
- package/packages/daemon/src/widgets/explorer/index.tsx +456 -0
- package/packages/daemon/src/widgets/explorer/tree-model.ts +103 -0
- package/packages/daemon/src/widgets/explorer/tree.tsx +165 -0
- package/packages/daemon/src/widgets/lib/config-model.ts +116 -0
- package/packages/daemon/src/widgets/lib/files.ts +88 -0
- package/packages/daemon/src/widgets/lib/git.ts +88 -0
- package/packages/daemon/src/widgets/lib/grammar.ts +126 -0
- package/packages/daemon/src/widgets/lib/help-overlay.tsx +101 -0
- package/packages/daemon/src/widgets/lib/pane-comms.ts +209 -0
- package/packages/daemon/src/widgets/lib/theme.ts +194 -0
- package/packages/daemon/src/widgets/lib/watcher.ts +132 -0
- package/packages/daemon/src/widgets/preview/README.md +3 -0
- package/packages/daemon/src/widgets/preview/index.tsx +416 -0
- package/packages/daemon/src/widgets/resolve.ts +121 -0
- package/packages/daemon/src/widgets/setup/README.md +3 -0
- package/packages/daemon/src/widgets/setup/agent-naming.tsx +112 -0
- package/packages/daemon/src/widgets/setup/config-tree.tsx +246 -0
- package/packages/daemon/src/widgets/setup/detect-panel.tsx +72 -0
- package/packages/daemon/src/widgets/setup/field-editor.tsx +265 -0
- package/packages/daemon/src/widgets/setup/footer.tsx +107 -0
- package/packages/daemon/src/widgets/setup/index.tsx +341 -0
- package/packages/daemon/src/widgets/setup/layout-picker.tsx +96 -0
- package/packages/daemon/src/widgets/setup/orchestrator-panel.tsx +200 -0
- package/packages/daemon/src/widgets/setup/review-panel.tsx +140 -0
- package/packages/daemon/src/widgets/setup/setup-model.ts +188 -0
- package/packages/daemon/src/widgets/sidebar/index.tsx +527 -0
- package/packages/tmux-bridge/package.json +22 -0
- package/packages/tmux-bridge/src/errors.ts +28 -0
- package/packages/tmux-bridge/src/index.ts +31 -0
- package/packages/tmux-bridge/src/monitor.ts +77 -0
- package/packages/tmux-bridge/src/panes.ts +136 -0
- package/packages/tmux-bridge/src/runner.test.ts +501 -0
- package/packages/tmux-bridge/src/runner.ts +91 -0
- package/packages/tmux-bridge/src/sessions.ts +126 -0
- package/packages/tmux-bridge/src/targeting.test.ts +107 -0
- package/packages/tmux-bridge/src/targeting.ts +90 -0
- package/scripts/postinstall.js +26 -2
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { resolve, basename } from "node:path";
|
|
2
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { writeConfig } from "./lib/yaml-io.ts";
|
|
4
|
+
import type { IdeConfig } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
interface DetectedStack {
|
|
7
|
+
packageManager: string | null;
|
|
8
|
+
frameworks: string[];
|
|
9
|
+
devCommand: string | null;
|
|
10
|
+
language: string | null;
|
|
11
|
+
reasons: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function fileExists(dir: string, name: string): boolean {
|
|
15
|
+
return existsSync(resolve(dir, name));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readJson(dir: string, name: string): Record<string, unknown> | null {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(resolve(dir, name), "utf-8")) as Record<string, unknown>;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function detectStack(dir: string): DetectedStack {
|
|
27
|
+
const detected: DetectedStack = {
|
|
28
|
+
packageManager: null,
|
|
29
|
+
frameworks: [],
|
|
30
|
+
devCommand: null,
|
|
31
|
+
language: null,
|
|
32
|
+
reasons: [],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Detect package manager from lockfile
|
|
36
|
+
if (fileExists(dir, "pnpm-lock.yaml")) {
|
|
37
|
+
detected.packageManager = "pnpm";
|
|
38
|
+
detected.reasons.push('Detected pnpm from "pnpm-lock.yaml".');
|
|
39
|
+
} else if (fileExists(dir, "bun.lockb") || fileExists(dir, "bun.lock")) {
|
|
40
|
+
detected.packageManager = "bun";
|
|
41
|
+
detected.reasons.push('Detected bun from "bun.lockb" or "bun.lock".');
|
|
42
|
+
} else if (fileExists(dir, "yarn.lock")) {
|
|
43
|
+
detected.packageManager = "yarn";
|
|
44
|
+
detected.reasons.push('Detected yarn from "yarn.lock".');
|
|
45
|
+
} else if (fileExists(dir, "package-lock.json")) {
|
|
46
|
+
detected.packageManager = "npm";
|
|
47
|
+
detected.reasons.push('Detected npm from "package-lock.json".');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const pkg = readJson(dir, "package.json");
|
|
51
|
+
if (pkg) {
|
|
52
|
+
detected.language = "javascript";
|
|
53
|
+
detected.reasons.push('Detected JavaScript from "package.json".');
|
|
54
|
+
const deps = {
|
|
55
|
+
...(pkg.dependencies as Record<string, unknown> | undefined),
|
|
56
|
+
...(pkg.devDependencies as Record<string, unknown> | undefined),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (deps["next"]) pushFramework(detected, "next", 'Found dependency "next".');
|
|
60
|
+
if (deps["convex"]) pushFramework(detected, "convex", 'Found dependency "convex".');
|
|
61
|
+
if (deps["vite"]) pushFramework(detected, "vite", 'Found dependency "vite".');
|
|
62
|
+
if (deps["remix"] || deps["@remix-run/node"])
|
|
63
|
+
pushFramework(detected, "remix", "Found Remix dependency.");
|
|
64
|
+
if (deps["nuxt"]) pushFramework(detected, "nuxt", 'Found dependency "nuxt".');
|
|
65
|
+
if (deps["astro"]) pushFramework(detected, "astro", 'Found dependency "astro".');
|
|
66
|
+
if (deps["svelte"] || deps["@sveltejs/kit"])
|
|
67
|
+
pushFramework(detected, "svelte", "Found Svelte dependency.");
|
|
68
|
+
|
|
69
|
+
// Detect dev command
|
|
70
|
+
const pm = detected.packageManager ?? "npm";
|
|
71
|
+
const run = pm === "npm" ? "npm run" : pm;
|
|
72
|
+
const scripts = pkg.scripts as Record<string, unknown> | undefined;
|
|
73
|
+
if (scripts?.dev) {
|
|
74
|
+
detected.devCommand = `${run} dev`;
|
|
75
|
+
detected.reasons.push(
|
|
76
|
+
`Using dev command "${detected.devCommand}" from package.json scripts.`,
|
|
77
|
+
);
|
|
78
|
+
} else if (scripts?.start) {
|
|
79
|
+
detected.devCommand = `${run} start`;
|
|
80
|
+
detected.reasons.push(
|
|
81
|
+
`Using start command "${detected.devCommand}" from package.json scripts.`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Python
|
|
87
|
+
if (fileExists(dir, "pyproject.toml") || fileExists(dir, "requirements.txt")) {
|
|
88
|
+
detected.language = detected.language ?? "python";
|
|
89
|
+
detected.reasons.push('Detected Python from "pyproject.toml" or "requirements.txt".');
|
|
90
|
+
try {
|
|
91
|
+
const pyproject = readFileSync(resolve(dir, "pyproject.toml"), "utf-8");
|
|
92
|
+
if (pyproject.includes("fastapi"))
|
|
93
|
+
pushFramework(detected, "fastapi", 'Found "fastapi" in pyproject.toml.');
|
|
94
|
+
else if (pyproject.includes("django"))
|
|
95
|
+
pushFramework(detected, "django", 'Found "django" in pyproject.toml.');
|
|
96
|
+
else if (pyproject.includes("flask"))
|
|
97
|
+
pushFramework(detected, "flask", 'Found "flask" in pyproject.toml.');
|
|
98
|
+
} catch {
|
|
99
|
+
// Ignore missing or unreadable pyproject metadata.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Rust
|
|
104
|
+
if (fileExists(dir, "Cargo.toml")) {
|
|
105
|
+
detected.language = detected.language ?? "rust";
|
|
106
|
+
detected.reasons.push('Detected Rust from "Cargo.toml".');
|
|
107
|
+
pushFramework(detected, "cargo", 'Using Cargo workflow from "Cargo.toml".');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Go
|
|
111
|
+
if (fileExists(dir, "go.mod")) {
|
|
112
|
+
detected.language = detected.language ?? "go";
|
|
113
|
+
detected.reasons.push('Detected Go from "go.mod".');
|
|
114
|
+
pushFramework(detected, "go", 'Using Go workflow from "go.mod".');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Docker
|
|
118
|
+
if (fileExists(dir, "docker-compose.yml") || fileExists(dir, "docker-compose.yaml")) {
|
|
119
|
+
pushFramework(
|
|
120
|
+
detected,
|
|
121
|
+
"docker",
|
|
122
|
+
'Detected Docker from "docker-compose.yml" or "docker-compose.yaml".',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (detected.reasons.length === 0) {
|
|
127
|
+
detected.reasons.push("No framework-specific signals found; using the generic layout.");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return detected;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function suggestConfig(dir: string, detected: DetectedStack): IdeConfig {
|
|
134
|
+
const name = basename(dir);
|
|
135
|
+
const pm = detected.packageManager ?? "npm";
|
|
136
|
+
const run = pm === "npm" ? "npm run" : pm;
|
|
137
|
+
|
|
138
|
+
// Default: 2 claude panes + shell
|
|
139
|
+
const config: IdeConfig = {
|
|
140
|
+
name,
|
|
141
|
+
rows: [
|
|
142
|
+
{
|
|
143
|
+
size: "70%",
|
|
144
|
+
panes: [
|
|
145
|
+
{ title: "Claude 1", command: "claude" },
|
|
146
|
+
{ title: "Claude 2", command: "claude" },
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
panes: [],
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const bottom = config.rows[1]!.panes;
|
|
156
|
+
const frameworks = detected.frameworks;
|
|
157
|
+
|
|
158
|
+
// Add 3rd claude pane for complex stacks
|
|
159
|
+
if (frameworks.length >= 2) {
|
|
160
|
+
config.rows[0]!.panes.push({ title: "Claude 3", command: "claude" });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add dev servers
|
|
164
|
+
if (frameworks.includes("next")) {
|
|
165
|
+
bottom.push({ title: "Next.js", command: `${run} dev` });
|
|
166
|
+
} else if (frameworks.includes("vite")) {
|
|
167
|
+
bottom.push({ title: "Vite", command: `${run} dev` });
|
|
168
|
+
} else if (frameworks.includes("nuxt")) {
|
|
169
|
+
bottom.push({ title: "Nuxt", command: `${run} dev` });
|
|
170
|
+
} else if (frameworks.includes("remix")) {
|
|
171
|
+
bottom.push({ title: "Remix", command: `${run} dev` });
|
|
172
|
+
} else if (frameworks.includes("astro")) {
|
|
173
|
+
bottom.push({ title: "Astro", command: `${run} dev` });
|
|
174
|
+
} else if (frameworks.includes("svelte")) {
|
|
175
|
+
bottom.push({ title: "SvelteKit", command: `${run} dev` });
|
|
176
|
+
} else if (frameworks.includes("fastapi")) {
|
|
177
|
+
bottom.push({ title: "FastAPI", command: "uvicorn main:app --reload" });
|
|
178
|
+
} else if (frameworks.includes("django")) {
|
|
179
|
+
bottom.push({ title: "Django", command: "python manage.py runserver" });
|
|
180
|
+
} else if (frameworks.includes("flask")) {
|
|
181
|
+
bottom.push({ title: "Flask", command: "flask run --reload" });
|
|
182
|
+
} else if (frameworks.includes("cargo")) {
|
|
183
|
+
bottom.push({ title: "Cargo", command: "cargo watch -x run" });
|
|
184
|
+
} else if (frameworks.includes("go")) {
|
|
185
|
+
bottom.push({ title: "Go", command: "go run ." });
|
|
186
|
+
} else if (detected.devCommand) {
|
|
187
|
+
bottom.push({ title: "Dev Server", command: detected.devCommand });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (frameworks.includes("convex")) {
|
|
191
|
+
bottom.push({ title: "Convex", command: "npx convex dev" });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Always add shell
|
|
195
|
+
bottom.push({ title: "Shell" });
|
|
196
|
+
|
|
197
|
+
return config;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function detect(
|
|
201
|
+
targetDir: string | undefined,
|
|
202
|
+
{ json, write }: { json?: boolean; write?: boolean } = {},
|
|
203
|
+
): Promise<void> {
|
|
204
|
+
const dir = resolve(targetDir ?? ".");
|
|
205
|
+
const detected = detectStack(dir);
|
|
206
|
+
const suggested = suggestConfig(dir, detected);
|
|
207
|
+
|
|
208
|
+
if (write) {
|
|
209
|
+
writeConfig(dir, suggested);
|
|
210
|
+
if (json) {
|
|
211
|
+
console.log(JSON.stringify({ detected, suggestedConfig: suggested, written: true }, null, 2));
|
|
212
|
+
} else {
|
|
213
|
+
const desc =
|
|
214
|
+
detected.frameworks.length > 0
|
|
215
|
+
? detected.frameworks.join(" + ")
|
|
216
|
+
: (detected.language ?? "generic project");
|
|
217
|
+
console.log(`Detected ${desc}. Created ide.yml.`);
|
|
218
|
+
console.log("\nWhy this layout:");
|
|
219
|
+
for (const reason of detected.reasons) {
|
|
220
|
+
console.log(` - ${reason}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (json) {
|
|
227
|
+
console.log(JSON.stringify({ detected, suggestedConfig: suggested }, null, 2));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.log("Detected stack:");
|
|
232
|
+
if (detected.packageManager) console.log(` Package manager: ${detected.packageManager}`);
|
|
233
|
+
if (detected.language) console.log(` Language: ${detected.language}`);
|
|
234
|
+
if (detected.frameworks.length) console.log(` Frameworks: ${detected.frameworks.join(", ")}`);
|
|
235
|
+
if (detected.devCommand) console.log(` Dev command: ${detected.devCommand}`);
|
|
236
|
+
console.log("\nReasoning:");
|
|
237
|
+
for (const reason of detected.reasons) {
|
|
238
|
+
console.log(` - ${reason}`);
|
|
239
|
+
}
|
|
240
|
+
console.log("\nRun with --write to create ide.yml, or --json to see the suggested config.");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function pushFramework(detected: DetectedStack, framework: string, reason: string): void {
|
|
244
|
+
if (!detected.frameworks.includes(framework)) {
|
|
245
|
+
detected.frameworks.push(framework);
|
|
246
|
+
}
|
|
247
|
+
detected.reasons.push(reason);
|
|
248
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { resolve, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { getCurrentVersion, getUpdateStatus } from "./lib/update-check.ts";
|
|
6
|
+
import { installedSkillVersion } from "./lib/skill-sync.ts";
|
|
7
|
+
import { discoverAgents, presentAgents, type DiscoveredAgent } from "./lib/agent-discovery.ts";
|
|
8
|
+
import { findCompiledTui, isBunAvailable } from "./tui/compiled.ts";
|
|
9
|
+
|
|
10
|
+
interface CheckResult {
|
|
11
|
+
label: string;
|
|
12
|
+
pass: boolean;
|
|
13
|
+
detail: string;
|
|
14
|
+
optional: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* PURE — the "agent integrations" doctor rows, one per DISCOVERED agent (absent
|
|
19
|
+
* agents produce nothing — no noise). All rows are optional (informational, they
|
|
20
|
+
* never fail the overall check):
|
|
21
|
+
* - `claude` + integration installed → a passing ✓ "integration installed ✓".
|
|
22
|
+
* - `claude` on PATH but NOT installed → a ○ hint pointing at the installer.
|
|
23
|
+
* - any other agent on PATH → a passing ✓ noting screen-manifest detection is
|
|
24
|
+
* active but there's no lifecycle integration yet.
|
|
25
|
+
*/
|
|
26
|
+
export function agentIntegrationRows(agents: DiscoveredAgent[]): CheckResult[] {
|
|
27
|
+
return presentAgents(agents).map((agent) => {
|
|
28
|
+
const label = `agent: ${agent.id}`;
|
|
29
|
+
if (agent.integration) {
|
|
30
|
+
return agent.installed
|
|
31
|
+
? { label, pass: true, detail: "integration installed ✓", optional: true }
|
|
32
|
+
: {
|
|
33
|
+
label,
|
|
34
|
+
pass: false,
|
|
35
|
+
detail: `found on PATH — run \`tmux-ide integration install ${agent.id}\` for ground-truth status`,
|
|
36
|
+
optional: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
label,
|
|
41
|
+
pass: true,
|
|
42
|
+
detail: "found — screen-manifest detection active (no lifecycle integration yet)",
|
|
43
|
+
optional: true,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function check(
|
|
49
|
+
label: string,
|
|
50
|
+
fn: () => string,
|
|
51
|
+
{ optional = false }: { optional?: boolean } = {},
|
|
52
|
+
): CheckResult {
|
|
53
|
+
try {
|
|
54
|
+
const result = fn();
|
|
55
|
+
return { label, pass: true, detail: result, optional };
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return { label, pass: false, detail: (e as Error).message, optional };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function doctor({
|
|
62
|
+
json,
|
|
63
|
+
}: {
|
|
64
|
+
json?: boolean;
|
|
65
|
+
} = {}): Promise<void> {
|
|
66
|
+
const checks: CheckResult[] = [];
|
|
67
|
+
|
|
68
|
+
checks.push(
|
|
69
|
+
check("tmux installed", () => {
|
|
70
|
+
execSync("which tmux", { stdio: "ignore" });
|
|
71
|
+
return "found";
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
checks.push(
|
|
76
|
+
check("tmux version ≥ 3.0", () => {
|
|
77
|
+
const version = execSync("tmux -V", { encoding: "utf-8" }).trim();
|
|
78
|
+
const num = parseFloat(version.replace(/[^0-9.]/g, ""));
|
|
79
|
+
if (num < 3.0) throw new Error(`${version} (need ≥ 3.0)`);
|
|
80
|
+
return version;
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
checks.push(
|
|
85
|
+
check("Node.js ≥ 18", () => {
|
|
86
|
+
const major = parseInt(process.versions.node.split(".")[0]!);
|
|
87
|
+
if (major < 18) throw new Error(`Node ${process.versions.node} (need ≥ 18)`);
|
|
88
|
+
return `v${process.versions.node}`;
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
checks.push(
|
|
93
|
+
check(
|
|
94
|
+
"256-color terminal",
|
|
95
|
+
() => {
|
|
96
|
+
const term = process.env.TERM ?? "";
|
|
97
|
+
if (
|
|
98
|
+
!term.includes("256color") &&
|
|
99
|
+
!term.includes("ghostty") &&
|
|
100
|
+
!term.includes("kitty") &&
|
|
101
|
+
term !== "tmux-256color"
|
|
102
|
+
) {
|
|
103
|
+
throw new Error(`$TERM is "${term}"`);
|
|
104
|
+
}
|
|
105
|
+
return term;
|
|
106
|
+
},
|
|
107
|
+
{ optional: true },
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
checks.push(
|
|
112
|
+
check("ide.yml exists", () => {
|
|
113
|
+
const path = resolve(".", "ide.yml");
|
|
114
|
+
if (!existsSync(path)) throw new Error("not found in current directory");
|
|
115
|
+
return "found";
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
checks.push(
|
|
120
|
+
check(
|
|
121
|
+
"TUI surfaces (cockpit / widgets)",
|
|
122
|
+
() => {
|
|
123
|
+
// The OpenTUI/Solid surfaces run either from a dev checkout (bun + the
|
|
124
|
+
// `.tsx` sources) or, when installed, from the compiled `tmux-ide-tui`
|
|
125
|
+
// binary. Report which path is live so a "nothing renders" install is
|
|
126
|
+
// diagnosable instead of silent. Mirrors resolveTuiLaunch's order.
|
|
127
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
128
|
+
const checkoutEntry = [
|
|
129
|
+
resolve(here, "../packages/daemon/src/tui/team/index.tsx"),
|
|
130
|
+
resolve(here, "tui/team/index.tsx"),
|
|
131
|
+
].find(existsSync);
|
|
132
|
+
const binary = findCompiledTui();
|
|
133
|
+
if (checkoutEntry && isBunAvailable()) return "dev checkout (bun)";
|
|
134
|
+
if (binary) return `compiled binary (${binary})`;
|
|
135
|
+
throw new Error(
|
|
136
|
+
"no dev checkout+bun and no compiled binary — build one with `pnpm build:tui` or install a release that ships it",
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
{ optional: true },
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
checks.push(
|
|
144
|
+
check(
|
|
145
|
+
"Claude Code agent teams",
|
|
146
|
+
() => {
|
|
147
|
+
if (process.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
|
|
148
|
+
throw new Error("not set (enable with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)");
|
|
149
|
+
}
|
|
150
|
+
return "enabled";
|
|
151
|
+
},
|
|
152
|
+
{ optional: true },
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
checks.push(
|
|
157
|
+
check(
|
|
158
|
+
"tailscale CLI",
|
|
159
|
+
() => {
|
|
160
|
+
const version = execSync("tailscale version", { encoding: "utf-8" }).trim().split("\n")[0]!;
|
|
161
|
+
return version;
|
|
162
|
+
},
|
|
163
|
+
{ optional: true },
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
checks.push(
|
|
168
|
+
check(
|
|
169
|
+
"ngrok CLI",
|
|
170
|
+
() => {
|
|
171
|
+
const version = execSync("ngrok version", { encoding: "utf-8" }).trim();
|
|
172
|
+
return version;
|
|
173
|
+
},
|
|
174
|
+
{ optional: true },
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
checks.push(
|
|
179
|
+
check(
|
|
180
|
+
"cloudflared CLI",
|
|
181
|
+
() => {
|
|
182
|
+
const version = execSync("cloudflared --version", { encoding: "utf-8" }).trim();
|
|
183
|
+
return version;
|
|
184
|
+
},
|
|
185
|
+
{ optional: true },
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
checks.push(
|
|
190
|
+
check(
|
|
191
|
+
"tmux-ide up to date",
|
|
192
|
+
() => {
|
|
193
|
+
const current = getCurrentVersion();
|
|
194
|
+
const { latest, updateAvailable } = getUpdateStatus({ currentVersion: current });
|
|
195
|
+
if (updateAvailable) {
|
|
196
|
+
throw new Error(`v${current} — v${latest} available (run \`tmux-ide update\`)`);
|
|
197
|
+
}
|
|
198
|
+
return latest ? `v${current} (latest)` : `v${current} (latest unknown)`;
|
|
199
|
+
},
|
|
200
|
+
{ optional: true },
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
checks.push(
|
|
205
|
+
check(
|
|
206
|
+
"Claude Code skill",
|
|
207
|
+
() => {
|
|
208
|
+
// The managed skill copy under ~/.claude/skills/tmux-ide should track the
|
|
209
|
+
// CLI version. Stale/missing → point at the one-command fix; installs and
|
|
210
|
+
// `tmux-ide update` refresh it automatically, so this is only a nudge.
|
|
211
|
+
const installed = installedSkillVersion();
|
|
212
|
+
const current = getCurrentVersion();
|
|
213
|
+
if (installed === null) {
|
|
214
|
+
throw new Error("not installed — run `tmux-ide skill-sync`");
|
|
215
|
+
}
|
|
216
|
+
if (installed !== current) {
|
|
217
|
+
throw new Error(`v${installed} (CLI v${current}) — run \`tmux-ide skill-sync\``);
|
|
218
|
+
}
|
|
219
|
+
return `in sync (v${installed})`;
|
|
220
|
+
},
|
|
221
|
+
{ optional: true },
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Agent integrations: one row per agent discovered on PATH (absent → no row).
|
|
226
|
+
checks.push(...agentIntegrationRows(discoverAgents()));
|
|
227
|
+
|
|
228
|
+
const allPass = checks.every((c) => c.pass || c.optional);
|
|
229
|
+
|
|
230
|
+
if (json) {
|
|
231
|
+
console.log(JSON.stringify({ ok: allPass, checks }, null, 2));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (const c of checks) {
|
|
236
|
+
const icon = c.pass ? "✓" : c.optional ? "○" : "✗";
|
|
237
|
+
const color = c.pass ? "\x1b[32m" : c.optional ? "\x1b[33m" : "\x1b[31m";
|
|
238
|
+
console.log(`${color}${icon}\x1b[0m ${c.label} — ${c.detail}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!allPass) process.exitCode = 1;
|
|
242
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
startEmbeddedDaemon,
|
|
3
|
+
type EmbeddedDaemonHandle,
|
|
4
|
+
type EmbeddedDaemonOptions,
|
|
5
|
+
} from "./embed.ts";
|
|
6
|
+
export {
|
|
7
|
+
clearCanonicalDaemonInfo,
|
|
8
|
+
isCanonicalDaemonAlive,
|
|
9
|
+
readCanonicalDaemonInfo,
|
|
10
|
+
writeCanonicalDaemonInfo,
|
|
11
|
+
} from "./canonical.ts";
|
|
12
|
+
export type { CanonicalDaemonInfo } from "./canonical.ts";
|