tmux-ide 1.2.0 → 1.3.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.
Files changed (67) hide show
  1. package/bin/cli.js +14 -14
  2. package/dist/attach.d.ts +3 -0
  3. package/dist/attach.js +14 -0
  4. package/dist/config.d.ts +5 -0
  5. package/dist/config.js +269 -0
  6. package/dist/detect.d.ts +15 -0
  7. package/dist/detect.js +228 -0
  8. package/dist/doctor.d.ts +3 -0
  9. package/dist/doctor.js +66 -0
  10. package/dist/init.d.ts +4 -0
  11. package/dist/init.js +67 -0
  12. package/dist/inspect.d.ts +54 -0
  13. package/dist/inspect.js +118 -0
  14. package/dist/launch.d.ts +22 -0
  15. package/dist/launch.js +174 -0
  16. package/dist/lib/dot-path.d.ts +2 -0
  17. package/dist/lib/dot-path.js +17 -0
  18. package/dist/lib/errors.d.ts +29 -0
  19. package/dist/lib/errors.js +37 -0
  20. package/dist/lib/launch-plan.d.ts +6 -0
  21. package/dist/lib/launch-plan.js +40 -0
  22. package/dist/lib/output.d.ts +9 -0
  23. package/dist/lib/output.js +65 -0
  24. package/dist/lib/session-monitor.d.ts +12 -0
  25. package/dist/lib/session-monitor.js +160 -0
  26. package/dist/lib/session-options.d.ts +13 -0
  27. package/dist/lib/session-options.js +85 -0
  28. package/dist/lib/sizes.d.ts +16 -0
  29. package/dist/lib/sizes.js +36 -0
  30. package/dist/lib/tmux.d.ts +42 -0
  31. package/dist/lib/tmux.js +225 -0
  32. package/dist/lib/yaml-io.d.ts +10 -0
  33. package/dist/lib/yaml-io.js +24 -0
  34. package/dist/ls.d.ts +3 -0
  35. package/dist/ls.js +35 -0
  36. package/dist/restart.d.ts +4 -0
  37. package/dist/restart.js +13 -0
  38. package/dist/status.d.ts +3 -0
  39. package/dist/status.js +29 -0
  40. package/dist/stop.d.ts +3 -0
  41. package/dist/stop.js +21 -0
  42. package/dist/types.d.ts +41 -0
  43. package/dist/types.js +1 -0
  44. package/dist/validate.d.ts +4 -0
  45. package/dist/validate.js +172 -0
  46. package/package.json +16 -10
  47. package/src/attach.js +0 -17
  48. package/src/config.js +0 -341
  49. package/src/detect.js +0 -232
  50. package/src/doctor.js +0 -91
  51. package/src/init.js +0 -73
  52. package/src/inspect.js +0 -127
  53. package/src/launch.js +0 -245
  54. package/src/lib/dot-path.js +0 -16
  55. package/src/lib/errors.js +0 -35
  56. package/src/lib/launch-plan.js +0 -49
  57. package/src/lib/output.js +0 -68
  58. package/src/lib/session-monitor.js +0 -179
  59. package/src/lib/session-options.js +0 -95
  60. package/src/lib/sizes.js +0 -36
  61. package/src/lib/tmux.js +0 -234
  62. package/src/lib/yaml-io.js +0 -26
  63. package/src/ls.js +0 -40
  64. package/src/restart.js +0 -16
  65. package/src/status.js +0 -35
  66. package/src/stop.js +0 -25
  67. package/src/validate.js +0 -154
package/src/detect.js DELETED
@@ -1,232 +0,0 @@
1
- import { resolve, basename } from "node:path";
2
- import { readFileSync, existsSync } from "node:fs";
3
- import { writeConfig } from "./lib/yaml-io.js";
4
-
5
- function fileExists(dir, name) {
6
- return existsSync(resolve(dir, name));
7
- }
8
-
9
- function readJson(dir, name) {
10
- try {
11
- return JSON.parse(readFileSync(resolve(dir, name), "utf-8"));
12
- } catch {
13
- return null;
14
- }
15
- }
16
-
17
- export function detectStack(dir) {
18
- const detected = {
19
- packageManager: null,
20
- frameworks: [],
21
- devCommand: null,
22
- language: null,
23
- reasons: [],
24
- };
25
-
26
- // Detect package manager from lockfile
27
- if (fileExists(dir, "pnpm-lock.yaml")) {
28
- detected.packageManager = "pnpm";
29
- detected.reasons.push('Detected pnpm from "pnpm-lock.yaml".');
30
- } else if (fileExists(dir, "bun.lockb") || fileExists(dir, "bun.lock")) {
31
- detected.packageManager = "bun";
32
- detected.reasons.push('Detected bun from "bun.lockb" or "bun.lock".');
33
- } else if (fileExists(dir, "yarn.lock")) {
34
- detected.packageManager = "yarn";
35
- detected.reasons.push('Detected yarn from "yarn.lock".');
36
- } else if (fileExists(dir, "package-lock.json")) {
37
- detected.packageManager = "npm";
38
- detected.reasons.push('Detected npm from "package-lock.json".');
39
- }
40
-
41
- const pkg = readJson(dir, "package.json");
42
- if (pkg) {
43
- detected.language = "javascript";
44
- detected.reasons.push('Detected JavaScript from "package.json".');
45
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
46
-
47
- if (deps["next"]) pushFramework(detected, "next", 'Found dependency "next".');
48
- if (deps["convex"]) pushFramework(detected, "convex", 'Found dependency "convex".');
49
- if (deps["vite"]) pushFramework(detected, "vite", 'Found dependency "vite".');
50
- if (deps["remix"] || deps["@remix-run/node"])
51
- pushFramework(detected, "remix", "Found Remix dependency.");
52
- if (deps["nuxt"]) pushFramework(detected, "nuxt", 'Found dependency "nuxt".');
53
- if (deps["astro"]) pushFramework(detected, "astro", 'Found dependency "astro".');
54
- if (deps["svelte"] || deps["@sveltejs/kit"])
55
- pushFramework(detected, "svelte", "Found Svelte dependency.");
56
-
57
- // Detect dev command
58
- const pm = detected.packageManager ?? "npm";
59
- const run = pm === "npm" ? "npm run" : pm;
60
- if (pkg.scripts?.dev) {
61
- detected.devCommand = `${run} dev`;
62
- detected.reasons.push(
63
- `Using dev command "${detected.devCommand}" from package.json scripts.`,
64
- );
65
- } else if (pkg.scripts?.start) {
66
- detected.devCommand = `${run} start`;
67
- detected.reasons.push(
68
- `Using start command "${detected.devCommand}" from package.json scripts.`,
69
- );
70
- }
71
- }
72
-
73
- // Python
74
- if (fileExists(dir, "pyproject.toml") || fileExists(dir, "requirements.txt")) {
75
- detected.language = detected.language ?? "python";
76
- detected.reasons.push('Detected Python from "pyproject.toml" or "requirements.txt".');
77
- try {
78
- const pyproject = readFileSync(resolve(dir, "pyproject.toml"), "utf-8");
79
- if (pyproject.includes("fastapi"))
80
- pushFramework(detected, "fastapi", 'Found "fastapi" in pyproject.toml.');
81
- else if (pyproject.includes("django"))
82
- pushFramework(detected, "django", 'Found "django" in pyproject.toml.');
83
- else if (pyproject.includes("flask"))
84
- pushFramework(detected, "flask", 'Found "flask" in pyproject.toml.');
85
- } catch {
86
- // Ignore missing or unreadable pyproject metadata.
87
- }
88
- }
89
-
90
- // Rust
91
- if (fileExists(dir, "Cargo.toml")) {
92
- detected.language = detected.language ?? "rust";
93
- detected.reasons.push('Detected Rust from "Cargo.toml".');
94
- pushFramework(detected, "cargo", 'Using Cargo workflow from "Cargo.toml".');
95
- }
96
-
97
- // Go
98
- if (fileExists(dir, "go.mod")) {
99
- detected.language = detected.language ?? "go";
100
- detected.reasons.push('Detected Go from "go.mod".');
101
- pushFramework(detected, "go", 'Using Go workflow from "go.mod".');
102
- }
103
-
104
- // Docker
105
- if (fileExists(dir, "docker-compose.yml") || fileExists(dir, "docker-compose.yaml")) {
106
- pushFramework(
107
- detected,
108
- "docker",
109
- 'Detected Docker from "docker-compose.yml" or "docker-compose.yaml".',
110
- );
111
- }
112
-
113
- if (detected.reasons.length === 0) {
114
- detected.reasons.push("No framework-specific signals found; using the generic layout.");
115
- }
116
-
117
- return detected;
118
- }
119
-
120
- export function suggestConfig(dir, detected) {
121
- const name = basename(dir);
122
- const pm = detected.packageManager ?? "npm";
123
- const run = pm === "npm" ? "npm run" : pm;
124
-
125
- // Default: 2 claude panes + shell
126
- const config = {
127
- name,
128
- rows: [
129
- {
130
- size: "70%",
131
- panes: [
132
- { title: "Claude 1", command: "claude" },
133
- { title: "Claude 2", command: "claude" },
134
- ],
135
- },
136
- {
137
- panes: [],
138
- },
139
- ],
140
- };
141
-
142
- const bottom = config.rows[1].panes;
143
- const frameworks = detected.frameworks;
144
-
145
- // Add 3rd claude pane for complex stacks
146
- if (frameworks.length >= 2) {
147
- config.rows[0].panes.push({ title: "Claude 3", command: "claude" });
148
- }
149
-
150
- // Add dev servers
151
- if (frameworks.includes("next")) {
152
- bottom.push({ title: "Next.js", command: `${run} dev` });
153
- } else if (frameworks.includes("vite")) {
154
- bottom.push({ title: "Vite", command: `${run} dev` });
155
- } else if (frameworks.includes("nuxt")) {
156
- bottom.push({ title: "Nuxt", command: `${run} dev` });
157
- } else if (frameworks.includes("remix")) {
158
- bottom.push({ title: "Remix", command: `${run} dev` });
159
- } else if (frameworks.includes("astro")) {
160
- bottom.push({ title: "Astro", command: `${run} dev` });
161
- } else if (frameworks.includes("svelte")) {
162
- bottom.push({ title: "SvelteKit", command: `${run} dev` });
163
- } else if (frameworks.includes("fastapi")) {
164
- bottom.push({ title: "FastAPI", command: "uvicorn main:app --reload" });
165
- } else if (frameworks.includes("django")) {
166
- bottom.push({ title: "Django", command: "python manage.py runserver" });
167
- } else if (frameworks.includes("flask")) {
168
- bottom.push({ title: "Flask", command: "flask run --reload" });
169
- } else if (frameworks.includes("cargo")) {
170
- bottom.push({ title: "Cargo", command: "cargo watch -x run" });
171
- } else if (frameworks.includes("go")) {
172
- bottom.push({ title: "Go", command: "go run ." });
173
- } else if (detected.devCommand) {
174
- bottom.push({ title: "Dev Server", command: detected.devCommand });
175
- }
176
-
177
- if (frameworks.includes("convex")) {
178
- bottom.push({ title: "Convex", command: "npx convex dev" });
179
- }
180
-
181
- // Always add shell
182
- bottom.push({ title: "Shell" });
183
-
184
- return config;
185
- }
186
-
187
- export async function detect(targetDir, { json, write } = {}) {
188
- const dir = resolve(targetDir ?? ".");
189
- const detected = detectStack(dir);
190
- const suggested = suggestConfig(dir, detected);
191
-
192
- if (write) {
193
- writeConfig(dir, suggested);
194
- if (json) {
195
- console.log(JSON.stringify({ detected, suggestedConfig: suggested, written: true }, null, 2));
196
- } else {
197
- const desc =
198
- detected.frameworks.length > 0
199
- ? detected.frameworks.join(" + ")
200
- : (detected.language ?? "generic project");
201
- console.log(`Detected ${desc}. Created ide.yml.`);
202
- console.log("\nWhy this layout:");
203
- for (const reason of detected.reasons) {
204
- console.log(` - ${reason}`);
205
- }
206
- }
207
- return;
208
- }
209
-
210
- if (json) {
211
- console.log(JSON.stringify({ detected, suggestedConfig: suggested }, null, 2));
212
- return;
213
- }
214
-
215
- console.log("Detected stack:");
216
- if (detected.packageManager) console.log(` Package manager: ${detected.packageManager}`);
217
- if (detected.language) console.log(` Language: ${detected.language}`);
218
- if (detected.frameworks.length) console.log(` Frameworks: ${detected.frameworks.join(", ")}`);
219
- if (detected.devCommand) console.log(` Dev command: ${detected.devCommand}`);
220
- console.log("\nReasoning:");
221
- for (const reason of detected.reasons) {
222
- console.log(` - ${reason}`);
223
- }
224
- console.log("\nRun with --write to create ide.yml, or --json to see the suggested config.");
225
- }
226
-
227
- function pushFramework(detected, framework, reason) {
228
- if (!detected.frameworks.includes(framework)) {
229
- detected.frameworks.push(framework);
230
- }
231
- detected.reasons.push(reason);
232
- }
package/src/doctor.js DELETED
@@ -1,91 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { resolve } from "node:path";
4
-
5
- function check(label, fn, { optional = false } = {}) {
6
- try {
7
- const result = fn();
8
- return { label, pass: true, detail: result, optional };
9
- } catch (e) {
10
- return { label, pass: false, detail: e.message, optional };
11
- }
12
- }
13
-
14
- export async function doctor({ json } = {}) {
15
- const checks = [];
16
-
17
- checks.push(
18
- check("tmux installed", () => {
19
- execSync("which tmux", { stdio: "ignore" });
20
- return "found";
21
- }),
22
- );
23
-
24
- checks.push(
25
- check("tmux version ≥ 3.0", () => {
26
- const version = execSync("tmux -V", { encoding: "utf-8" }).trim();
27
- const num = parseFloat(version.replace(/[^0-9.]/g, ""));
28
- if (num < 3.0) throw new Error(`${version} (need ≥ 3.0)`);
29
- return version;
30
- }),
31
- );
32
-
33
- checks.push(
34
- check("Node.js ≥ 18", () => {
35
- const major = parseInt(process.versions.node.split(".")[0]);
36
- if (major < 18) throw new Error(`Node ${process.versions.node} (need ≥ 18)`);
37
- return `v${process.versions.node}`;
38
- }),
39
- );
40
-
41
- checks.push(
42
- check("256-color terminal", () => {
43
- const term = process.env.TERM ?? "";
44
- if (
45
- !term.includes("256color") &&
46
- !term.includes("ghostty") &&
47
- !term.includes("kitty") &&
48
- term !== "tmux-256color"
49
- ) {
50
- throw new Error(`$TERM is "${term}"`);
51
- }
52
- return term;
53
- }),
54
- );
55
-
56
- checks.push(
57
- check("ide.yml exists", () => {
58
- const path = resolve(".", "ide.yml");
59
- if (!existsSync(path)) throw new Error("not found in current directory");
60
- return "found";
61
- }),
62
- );
63
-
64
- checks.push(
65
- check(
66
- "Claude Code agent teams",
67
- () => {
68
- if (process.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
69
- throw new Error("not set (enable with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)");
70
- }
71
- return "enabled";
72
- },
73
- { optional: true },
74
- ),
75
- );
76
-
77
- const allPass = checks.every((c) => c.pass || c.optional);
78
-
79
- if (json) {
80
- console.log(JSON.stringify({ ok: allPass, checks }, null, 2));
81
- return;
82
- }
83
-
84
- for (const c of checks) {
85
- const icon = c.pass ? "✓" : c.optional ? "○" : "✗";
86
- const color = c.pass ? "\x1b[32m" : c.optional ? "\x1b[33m" : "\x1b[31m";
87
- console.log(`${color}${icon}\x1b[0m ${c.label} — ${c.detail}`);
88
- }
89
-
90
- if (!allPass) process.exitCode = 1;
91
- }
package/src/init.js DELETED
@@ -1,73 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { resolve, basename, dirname } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const __dirname = dirname(fileURLToPath(import.meta.url));
6
- import { detectStack, suggestConfig } from "./detect.js";
7
- import { outputError, printLayout } from "./lib/output.js";
8
-
9
- export async function init({ template, json } = {}) {
10
- const dir = process.cwd();
11
- const configPath = resolve(dir, "ide.yml");
12
-
13
- if (existsSync(configPath)) {
14
- outputError("ide.yml already exists in this directory", "EXISTS");
15
- }
16
-
17
- // If a specific template is requested, use it
18
- if (template) {
19
- const templatePath = resolve(__dirname, "..", "templates", `${template}.yml`);
20
- if (!existsSync(templatePath)) {
21
- outputError(`Template "${template}" not found`, "NOT_FOUND");
22
- }
23
-
24
- let content = readFileSync(templatePath, "utf-8");
25
- const name = basename(dir);
26
- content = content.replace(/^name: .+/m, `name: ${name}`);
27
- writeFileSync(configPath, content);
28
-
29
- if (json) {
30
- console.log(JSON.stringify({ created: true, template, name }));
31
- } else {
32
- console.log(`Created ide.yml from "${template}" template for "${name}"`);
33
- const yaml = (await import("js-yaml")).default;
34
- printLayout(yaml.load(content));
35
- }
36
- return;
37
- }
38
-
39
- // Smart detection
40
- const detected = detectStack(dir);
41
- const name = basename(dir);
42
-
43
- if (detected.frameworks.length > 0) {
44
- // Use detected stack to generate config
45
- const config = suggestConfig(dir, detected);
46
- const yaml = (await import("js-yaml")).default;
47
- writeFileSync(configPath, yaml.dump(config, { lineWidth: -1, noRefs: true, quotingType: '"' }));
48
-
49
- const desc = detected.frameworks.join(" + ");
50
- if (json) {
51
- console.log(JSON.stringify({ created: true, detected: detected.frameworks, name }));
52
- } else {
53
- console.log(`Detected ${desc}. Created ide.yml for "${name}".`);
54
- printLayout(config);
55
- console.log("Edit it to customize, then run: tmux-ide");
56
- }
57
- } else {
58
- // Fallback to default template
59
- const templatePath = resolve(__dirname, "..", "templates", "default.yml");
60
- let content = readFileSync(templatePath, "utf-8");
61
- content = content.replace(/^name: .+/m, `name: ${name}`);
62
- writeFileSync(configPath, content);
63
-
64
- if (json) {
65
- console.log(JSON.stringify({ created: true, template: "default", name }));
66
- } else {
67
- console.log(`Created ide.yml for "${name}"`);
68
- const yaml = (await import("js-yaml")).default;
69
- printLayout(yaml.load(content));
70
- console.log("Edit it to configure your workspace, then run: tmux-ide");
71
- }
72
- }
73
- }
package/src/inspect.js DELETED
@@ -1,127 +0,0 @@
1
- import { resolve, basename } from "node:path";
2
- import { readConfig } from "./lib/yaml-io.js";
3
- import { validateConfig } from "./validate.js";
4
- import { outputError } from "./lib/output.js";
5
- import { getSessionState, listPanes } from "./lib/tmux.js";
6
-
7
- export function buildInspection(dir, { config, configPath, running, panes }) {
8
- const errors = validateConfig(config);
9
- const rows = Array.isArray(config?.rows) ? config.rows : [];
10
- const resolvedRows = rows.map((row, rowIndex) => ({
11
- index: rowIndex,
12
- size: row.size ?? null,
13
- panes: (Array.isArray(row?.panes) ? row.panes : []).map((pane, paneIndex) => ({
14
- index: paneIndex,
15
- title: pane.title ?? null,
16
- command: pane.command ?? null,
17
- dir: pane.dir ?? ".",
18
- size: pane.size ?? null,
19
- focus: pane.focus === true,
20
- role: pane.role ?? null,
21
- task: pane.task ?? null,
22
- env: pane.env ?? {},
23
- })),
24
- }));
25
-
26
- const focusPane =
27
- resolvedRows
28
- .flatMap((row) => row.panes.map((pane) => ({ row: row.index, pane })))
29
- .find(({ pane }) => pane.focus) ?? null;
30
- const session = config?.name ?? basename(dir);
31
-
32
- return {
33
- dir,
34
- configPath,
35
- valid: errors.length === 0,
36
- errors,
37
- session,
38
- before: config?.before ?? null,
39
- summary: {
40
- rows: resolvedRows.length,
41
- panes: resolvedRows.reduce((sum, row) => sum + row.panes.length, 0),
42
- focus: focusPane ? `rows.${focusPane.row}.panes.${focusPane.pane.index}` : null,
43
- },
44
- team: config?.team ?? null,
45
- theme: config?.theme ?? null,
46
- focus: focusPane
47
- ? {
48
- row: focusPane.row,
49
- pane: focusPane.pane.index,
50
- title: focusPane.pane.title,
51
- }
52
- : null,
53
- rows: resolvedRows,
54
- rawConfig: config,
55
- tmux: {
56
- running,
57
- panes,
58
- },
59
- };
60
- }
61
-
62
- export async function inspect(targetDir, { json } = {}) {
63
- const dir = resolve(targetDir ?? ".");
64
-
65
- let config;
66
- let configPath;
67
- try {
68
- ({ config, configPath } = readConfig(dir));
69
- } catch (error) {
70
- outputError(`Cannot read ide.yml: ${error.message}`, "READ_ERROR");
71
- return;
72
- }
73
-
74
- const session = config?.name ?? basename(dir);
75
- const state = getSessionState(session);
76
- const panes = state.running ? listPanes(session) : [];
77
- const data = buildInspection(dir, { config, configPath, running: state.running, panes });
78
-
79
- if (json) {
80
- console.log(JSON.stringify(data, null, 2));
81
- return;
82
- }
83
-
84
- console.log(`Directory: ${data.dir}`);
85
- console.log(`Config: ${data.configPath}`);
86
- console.log(`Valid: ${data.valid ? "yes" : "no"}`);
87
- console.log(`Session: ${data.session}`);
88
- console.log(`Running: ${data.tmux.running ? "yes" : "no"}`);
89
- console.log(`Rows: ${data.summary.rows}`);
90
- console.log(`Panes: ${data.summary.panes}`);
91
- console.log(`Team: ${data.team ? data.team.name : "disabled"}`);
92
-
93
- if (data.focus) {
94
- console.log(
95
- `Focus: row ${data.focus.row}, pane ${data.focus.pane}${data.focus.title ? ` (${data.focus.title})` : ""}`,
96
- );
97
- }
98
-
99
- if (!data.valid) {
100
- console.log("\nValidation Errors:");
101
- for (const error of data.errors) {
102
- console.log(` - ${error}`);
103
- }
104
- }
105
-
106
- console.log("\nResolved Layout:");
107
- for (const row of data.rows) {
108
- console.log(` Row ${row.index}${row.size ? ` (${row.size})` : ""}`);
109
- for (const pane of row.panes) {
110
- const parts = [];
111
- if (pane.title) parts.push(pane.title);
112
- if (pane.command) parts.push(`cmd=${pane.command}`);
113
- if (pane.dir && pane.dir !== ".") parts.push(`dir=${pane.dir}`);
114
- if (pane.role) parts.push(`role=${pane.role}`);
115
- if (pane.focus) parts.push("focus");
116
- console.log(` - pane ${pane.index}: ${parts.join(" | ") || "shell"}`);
117
- }
118
- }
119
-
120
- if (data.tmux.running && data.tmux.panes.length > 0) {
121
- console.log("\nLive Panes:");
122
- for (const pane of data.tmux.panes) {
123
- const active = pane.active ? " (active)" : "";
124
- console.log(` ${pane.index}: ${pane.title} [${pane.width}x${pane.height}]${active}`);
125
- }
126
- }
127
- }