tmux-ide 1.2.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/bin/cli.js +4 -216
  2. package/bin/cli.ts +536 -0
  3. package/package.json +33 -14
  4. package/src/__tests__/support.ts +186 -0
  5. package/src/{attach.js → attach.ts} +7 -4
  6. package/src/cli.test.ts +314 -0
  7. package/src/command-center/discovery.test.ts +265 -0
  8. package/src/command-center/discovery.ts +296 -0
  9. package/src/command-center/index.ts +31 -0
  10. package/src/command-center/schemas.ts +57 -0
  11. package/src/command-center/server.test.ts +818 -0
  12. package/src/command-center/server.ts +1426 -0
  13. package/src/config-cli.test.ts +86 -0
  14. package/src/config.test.ts +205 -0
  15. package/src/config.ts +313 -0
  16. package/src/detect.test.ts +142 -0
  17. package/src/{detect.js → detect.ts} +31 -15
  18. package/src/dispatch.ts +102 -0
  19. package/src/doctor.test.ts +119 -0
  20. package/src/doctor.ts +167 -0
  21. package/src/init.test.ts +101 -0
  22. package/src/init.ts +144 -0
  23. package/src/inspect.test.ts +67 -0
  24. package/src/{inspect.js → inspect.ts} +95 -10
  25. package/src/integration.test.ts.skip +276 -0
  26. package/src/js-yaml.d.ts +10 -0
  27. package/src/launch.test.ts +222 -0
  28. package/src/launch.ts +494 -0
  29. package/src/lib/auth/auth-service.test.ts +178 -0
  30. package/src/lib/auth/auth-service.ts +227 -0
  31. package/src/lib/auth/middleware.test.ts +119 -0
  32. package/src/lib/auth/middleware.ts +56 -0
  33. package/src/lib/auth/types.ts +13 -0
  34. package/src/lib/authorship.test.ts +242 -0
  35. package/src/lib/authorship.ts +280 -0
  36. package/src/lib/cast/recorder.test.ts +127 -0
  37. package/src/lib/cast/recorder.ts +148 -0
  38. package/src/lib/daemon-watchdog.ts +102 -0
  39. package/src/lib/daemon.ts +417 -0
  40. package/src/lib/dot-path.test.ts +66 -0
  41. package/src/lib/dot-path.ts +17 -0
  42. package/src/lib/errors.ts +44 -0
  43. package/src/lib/event-log.test.ts +189 -0
  44. package/src/lib/event-log.ts +237 -0
  45. package/src/lib/github-pr.ts +182 -0
  46. package/src/lib/hq/client.test.ts +154 -0
  47. package/src/lib/hq/client.ts +142 -0
  48. package/src/lib/hq/mdns.test.ts +54 -0
  49. package/src/lib/hq/mdns.ts +88 -0
  50. package/src/lib/hq/registry.test.ts +237 -0
  51. package/src/lib/hq/registry.ts +145 -0
  52. package/src/lib/hq/types.ts +44 -0
  53. package/src/lib/ipc/socket-protocol.test.ts +126 -0
  54. package/src/lib/ipc/socket-protocol.ts +324 -0
  55. package/src/lib/launch-plan.test.ts +119 -0
  56. package/src/lib/launch-plan.ts +90 -0
  57. package/src/lib/log.test.ts +39 -0
  58. package/src/lib/log.ts +71 -0
  59. package/src/lib/metrics.test.ts +213 -0
  60. package/src/lib/metrics.ts +345 -0
  61. package/src/lib/orchestrator.test.ts +2358 -0
  62. package/src/lib/orchestrator.ts +1792 -0
  63. package/src/lib/{output.js → output.ts} +18 -10
  64. package/src/lib/plan-store.test.ts +148 -0
  65. package/src/lib/plan-store.ts +131 -0
  66. package/src/lib/research.test.ts +208 -0
  67. package/src/lib/research.ts +475 -0
  68. package/src/lib/round-trip.test.ts +341 -0
  69. package/src/lib/session-monitor.test.ts +152 -0
  70. package/src/lib/session-monitor.ts +282 -0
  71. package/src/lib/session-options.test.ts +146 -0
  72. package/src/lib/{session-options.js → session-options.ts} +11 -6
  73. package/src/lib/shell.test.ts +20 -0
  74. package/src/lib/shell.ts +8 -0
  75. package/src/lib/sizes.test.ts +73 -0
  76. package/src/lib/{sizes.js → sizes.ts} +4 -4
  77. package/src/lib/skill-registry.test.ts +134 -0
  78. package/src/lib/skill-registry.ts +86 -0
  79. package/src/lib/slugify.test.ts +35 -0
  80. package/src/lib/slugify.ts +10 -0
  81. package/src/lib/task-store.ts +352 -0
  82. package/src/lib/tmux.test.ts +500 -0
  83. package/src/lib/tmux.ts +372 -0
  84. package/src/lib/token-tracker.test.ts +102 -0
  85. package/src/lib/token-tracker.ts +70 -0
  86. package/src/lib/tunnels/cloudflare.test.ts +73 -0
  87. package/src/lib/tunnels/cloudflare.ts +254 -0
  88. package/src/lib/tunnels/manager.test.ts +63 -0
  89. package/src/lib/tunnels/manager.ts +132 -0
  90. package/src/lib/tunnels/ngrok.test.ts +94 -0
  91. package/src/lib/tunnels/ngrok.ts +261 -0
  92. package/src/lib/tunnels/tailscale.test.ts +103 -0
  93. package/src/lib/tunnels/tailscale.ts +1048 -0
  94. package/src/lib/tunnels/types.ts +44 -0
  95. package/src/lib/validation.test.ts +289 -0
  96. package/src/lib/validation.ts +123 -0
  97. package/src/lib/webhook.ts +51 -0
  98. package/src/lib/workflow-store.ts +209 -0
  99. package/src/lib/ws-v3/protocol.test.ts +127 -0
  100. package/src/lib/ws-v3/protocol.ts +160 -0
  101. package/src/lib/yaml-io.test.ts +125 -0
  102. package/src/lib/{yaml-io.js → yaml-io.ts} +5 -4
  103. package/src/ls.test.ts +76 -0
  104. package/src/{ls.js → ls.ts} +4 -4
  105. package/src/metrics-cli.ts +177 -0
  106. package/src/notify.ts +85 -0
  107. package/src/orchestrator-status.ts +228 -0
  108. package/src/plan.ts +117 -0
  109. package/src/postinstall.test.ts +79 -0
  110. package/src/remote.test.ts +115 -0
  111. package/src/remote.ts +148 -0
  112. package/src/restart.ts +24 -0
  113. package/src/schemas/domain.ts +422 -0
  114. package/src/schemas/ide-config.ts +148 -0
  115. package/src/schemas/index.ts +53 -0
  116. package/src/send.test.ts +70 -0
  117. package/src/send.ts +181 -0
  118. package/src/skill.test.ts +114 -0
  119. package/src/skill.ts +138 -0
  120. package/src/status.ts +66 -0
  121. package/src/stop.test.ts +176 -0
  122. package/src/stop.ts +47 -0
  123. package/src/task.test.ts +1466 -0
  124. package/src/task.ts +1143 -0
  125. package/src/tunnel.test.ts +80 -0
  126. package/src/tunnel.ts +133 -0
  127. package/src/types.ts +15 -0
  128. package/src/ui/index.ts +32 -0
  129. package/src/ui/terminal/index.ts +9 -0
  130. package/src/ui/types.ts +91 -0
  131. package/src/ui/web/base.css +80 -0
  132. package/src/ui/web/components/Box.tsx +59 -0
  133. package/src/ui/web/components/Input.tsx +32 -0
  134. package/src/ui/web/components/ScrollBox.tsx +60 -0
  135. package/src/ui/web/components/Text.tsx +28 -0
  136. package/src/ui/web/hooks.ts +106 -0
  137. package/src/ui/web/index.ts +27 -0
  138. package/src/ui/web/render.ts +77 -0
  139. package/src/ui/web/utils/color.test.ts +93 -0
  140. package/src/ui/web/utils/color.ts +27 -0
  141. package/src/validate.test.ts +385 -0
  142. package/src/validate.ts +217 -0
  143. package/src/widgets/changes/index.tsx +576 -0
  144. package/src/widgets/config/index.tsx +440 -0
  145. package/src/widgets/costs/index.tsx +216 -0
  146. package/src/widgets/explorer/breadcrumbs.tsx +77 -0
  147. package/src/widgets/explorer/footer.tsx +20 -0
  148. package/src/widgets/explorer/header.tsx +23 -0
  149. package/src/widgets/explorer/index.tsx +408 -0
  150. package/src/widgets/explorer/tree-model.test.ts +214 -0
  151. package/src/widgets/explorer/tree-model.ts +103 -0
  152. package/src/widgets/explorer/tree.tsx +156 -0
  153. package/src/widgets/lib/config-model.ts +116 -0
  154. package/src/widgets/lib/files.test.ts +103 -0
  155. package/src/widgets/lib/files.ts +88 -0
  156. package/src/widgets/lib/git.test.ts +151 -0
  157. package/src/widgets/lib/git.ts +88 -0
  158. package/src/widgets/lib/pane-comms.test.ts +178 -0
  159. package/src/widgets/lib/pane-comms.ts +192 -0
  160. package/src/widgets/lib/theme.ts +151 -0
  161. package/src/widgets/lib/watcher.ts +50 -0
  162. package/src/widgets/mission-control/activity-feed.tsx +59 -0
  163. package/src/widgets/mission-control/agent-panel.tsx +132 -0
  164. package/src/widgets/mission-control/command-bar.tsx +63 -0
  165. package/src/widgets/mission-control/index.tsx +751 -0
  166. package/src/widgets/mission-control/task-panel.tsx +238 -0
  167. package/src/widgets/preview/index.tsx +415 -0
  168. package/src/widgets/resolve.ts +40 -0
  169. package/src/widgets/setup/agent-naming.tsx +112 -0
  170. package/src/widgets/setup/config-tree.tsx +238 -0
  171. package/src/widgets/setup/detect-panel.tsx +72 -0
  172. package/src/widgets/setup/field-editor.tsx +265 -0
  173. package/src/widgets/setup/footer.tsx +107 -0
  174. package/src/widgets/setup/index.tsx +340 -0
  175. package/src/widgets/setup/layout-picker.tsx +96 -0
  176. package/src/widgets/setup/orchestrator-panel.tsx +200 -0
  177. package/src/widgets/setup/review-panel.tsx +140 -0
  178. package/src/widgets/setup/setup-model.test.ts +303 -0
  179. package/src/widgets/setup/setup-model.ts +188 -0
  180. package/src/widgets/tasks/index.tsx +129 -0
  181. package/src/widgets/tasks/task-detail.tsx +309 -0
  182. package/src/widgets/tasks/task-form.tsx +193 -0
  183. package/src/widgets/tasks/task-list.tsx +205 -0
  184. package/src/widgets/tasks/task-model.test.ts +473 -0
  185. package/src/widgets/tasks/task-model.ts +157 -0
  186. package/templates/AGENTS.md +20 -0
  187. package/templates/missions.yml +41 -0
  188. package/templates/skills/backend.md +30 -0
  189. package/templates/skills/frontend.md +30 -0
  190. package/templates/skills/general-worker.md +30 -0
  191. package/templates/skills/researcher.md +69 -0
  192. package/templates/skills/reviewer.md +30 -0
  193. package/src/config.js +0 -341
  194. package/src/doctor.js +0 -91
  195. package/src/init.js +0 -73
  196. package/src/launch.js +0 -245
  197. package/src/lib/dot-path.js +0 -16
  198. package/src/lib/errors.js +0 -35
  199. package/src/lib/launch-plan.js +0 -49
  200. package/src/lib/session-monitor.js +0 -179
  201. package/src/lib/tmux.js +0 -237
  202. package/src/restart.js +0 -16
  203. package/src/status.js +0 -35
  204. package/src/stop.js +0 -25
  205. package/src/validate.js +0 -154
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: backend
3
+ specialties: [backend, api, database, server]
4
+ role: teammate
5
+ description: Backend development specialist
6
+ ---
7
+
8
+ You are a backend development specialist.
9
+
10
+ ## Capabilities
11
+
12
+ - Implement API endpoints and server-side workflows
13
+ - Design or update database interactions and data models
14
+ - Improve backend correctness, performance, and operational safety
15
+
16
+ ## Workflow
17
+
18
+ 1. Read the assigned task and surrounding system context
19
+ 2. Identify the affected API, database, or server paths
20
+ 3. Implement the change with focused tests
21
+ 4. Verify behavior against acceptance criteria
22
+ 5. Report the result, evidence, and any operational concerns
23
+
24
+ ## Completion Protocol
25
+
26
+ When done, run:
27
+ tmux-ide task done <TASK_ID> --proof "what changed and how you verified it" --summary "backend outcome"
28
+
29
+ If you find out-of-scope issues, report them:
30
+ tmux-ide task update <TASK_ID> --discovered-issues "description of issue"
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: frontend
3
+ specialties: [frontend, react, css, ui, components]
4
+ role: teammate
5
+ description: Frontend development specialist
6
+ ---
7
+
8
+ You are a frontend development specialist.
9
+
10
+ ## Capabilities
11
+
12
+ - Build and refine components and user interfaces
13
+ - Implement styling, layout, and interaction details
14
+ - Manage client-side state and data flow cleanly
15
+
16
+ ## Workflow
17
+
18
+ 1. Read the assigned task and any linked context
19
+ 2. Inspect the existing UI patterns and conventions
20
+ 3. Implement the smallest coherent change
21
+ 4. Run relevant tests or validation checks
22
+ 5. Report what changed, what you verified, and any follow-up risks
23
+
24
+ ## Completion Protocol
25
+
26
+ When done, run:
27
+ tmux-ide task done <TASK_ID> --proof "what changed and how you verified it" --summary "frontend outcome"
28
+
29
+ If you find out-of-scope issues, report them:
30
+ tmux-ide task update <TASK_ID> --discovered-issues "description of issue"
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: general-worker
3
+ specialties: []
4
+ role: teammate
5
+ description: General-purpose development agent
6
+ ---
7
+
8
+ You are a general-purpose development agent.
9
+
10
+ ## Capabilities
11
+
12
+ - Read, write, and refactor code across the full stack
13
+ - Run tests and fix failures
14
+ - Follow project conventions and style guides
15
+
16
+ ## Workflow
17
+
18
+ 1. Read the task description and any linked context
19
+ 2. Plan your approach before writing code
20
+ 3. Implement the solution with tests
21
+ 4. Verify all tests pass
22
+ 5. Report completion with proof and a summary of key learnings
23
+
24
+ ## Completion Protocol
25
+
26
+ When done, run:
27
+ tmux-ide task done <TASK_ID> --proof "what you accomplished" --summary "key learnings for future tasks"
28
+
29
+ If you discover out-of-scope issues, note them:
30
+ tmux-ide task update <TASK_ID> --discovered-issues "description of issue"
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: researcher
3
+ specialties: [research, audit, analysis]
4
+ role: researcher
5
+ description: Continuous internal auditing agent
6
+ ---
7
+
8
+ You are the continuous internal auditing agent.
9
+
10
+ ## Mission
11
+
12
+ Surface useful findings early without stalling delivery. Prefer high-signal observations, concrete evidence, and actionable next steps over broad commentary.
13
+
14
+ ## Workflows
15
+
16
+ ### codebase-analysis
17
+
18
+ - Map the current implementation shape
19
+ - Identify hidden coupling, missing boundaries, and drift from stated mission goals
20
+ - Recommend the smallest set of follow-up tasks that materially reduce risk
21
+
22
+ ### code-review
23
+
24
+ - Review recent changes for correctness, regressions, and weak assumptions
25
+ - Prioritize concrete defects and missing tests
26
+ - Avoid style-only feedback unless it affects maintainability or safety
27
+
28
+ ### architecture-audit
29
+
30
+ - Check whether the current design still matches the mission and milestone plan
31
+ - Flag structural risks, brittle ownership boundaries, and missing operational constraints
32
+ - Write durable findings suitable for `.tmux-ide/library/architecture.md`
33
+
34
+ ### test-coverage
35
+
36
+ - Evaluate whether tests cover the real behavior and failure modes
37
+ - Highlight blind spots, flaky areas, and missing assertions
38
+ - Recommend specific tests, not generic “add coverage” advice
39
+
40
+ ### contract-audit
41
+
42
+ - Compare implementation and tasks against the validation contract
43
+ - Look for unclaimed assertions, weak evidence, and ambiguous acceptance criteria
44
+ - Call out contract drift explicitly
45
+
46
+ ### incident-analysis
47
+
48
+ - Investigate retry clusters, stalls, or recurring failure patterns
49
+ - Focus on root causes, enabling constraints, and the best remediation path
50
+ - Prefer decompositions that reduce repeated agent failure
51
+
52
+ ### issue-triage
53
+
54
+ - Assess newly discovered issues for severity, scope, and urgency
55
+ - Recommend whether to fix now, defer, or isolate with a separate task
56
+ - Provide the clearest next action for the lead agent
57
+
58
+ ## Reporting Protocol
59
+
60
+ When you finish, record the result on your assigned task:
61
+
62
+ `tmux-ide task done <TASK_ID> --proof "findings, evidence, severity, next actions" --summary "short research takeaway"`
63
+
64
+ Your proof should include:
65
+
66
+ - What you inspected
67
+ - The most important findings
68
+ - Why they matter
69
+ - The exact next action you recommend
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: reviewer
3
+ specialties: [review, validation]
4
+ role: validator
5
+ description: Code review and validation agent
6
+ ---
7
+
8
+ You are a code review and validation agent.
9
+
10
+ ## Capabilities
11
+
12
+ - Review code changes for correctness, security, and style
13
+ - Run test suites and verify assertions
14
+ - Verify that implementation matches acceptance criteria
15
+
16
+ ## Workflow
17
+
18
+ 1. Read the validation contract at .tasks/validation-contract.md
19
+ 2. For each assertion, verify the implementation satisfies it
20
+ 3. Run relevant tests or manual checks
21
+ 4. Report each assertion's status
22
+
23
+ ## Assertion Reporting
24
+
25
+ For each assertion:
26
+ tmux-ide validate assert <ASSERT_ID> --status passing --evidence "what you verified"
27
+ tmux-ide validate assert <ASSERT_ID> --status failing --evidence "what's wrong"
28
+ tmux-ide validate assert <ASSERT_ID> --status blocked --evidence "why it's blocked"
29
+
30
+ The orchestrator will detect your results and advance the milestone automatically.
package/src/config.js DELETED
@@ -1,341 +0,0 @@
1
- import { resolve } from "node:path";
2
- import { readConfig, writeConfig } from "./lib/yaml-io.js";
3
- import { setByPath } from "./lib/dot-path.js";
4
- import { outputError } from "./lib/output.js";
5
-
6
- export async function config(targetDir, { json, action, args } = {}) {
7
- const dir = resolve(targetDir ?? ".");
8
-
9
- switch (action) {
10
- case "dump":
11
- return dumpConfig(dir, { json });
12
- case "set":
13
- return setConfig(dir, args, { json });
14
- case "add-pane":
15
- return addPane(dir, args, { json });
16
- case "remove-pane":
17
- return removePane(dir, args, { json });
18
- case "add-row":
19
- return addRow(dir, args, { json });
20
- case "enable-team":
21
- return enableTeam(dir, args, { json });
22
- case "disable-team":
23
- return disableTeam(dir, { json });
24
- default:
25
- return dumpConfig(dir, { json });
26
- }
27
- }
28
-
29
- function dumpConfig(dir, { json }) {
30
- let cfg;
31
- try {
32
- ({ config: cfg } = readConfig(dir));
33
- } catch (e) {
34
- outputError(`Cannot read ide.yml: ${e.message}`, "READ_ERROR");
35
- return;
36
- }
37
-
38
- if (json) {
39
- console.log(JSON.stringify(cfg, null, 2));
40
- } else {
41
- // Pretty print for humans
42
- console.log(JSON.stringify(cfg, null, 2));
43
- }
44
- }
45
-
46
- function setConfig(dir, args, { json }) {
47
- const [dotpath, ...rest] = args;
48
- if (!dotpath || rest.length === 0) {
49
- outputError("Usage: tmux-ide config set <dotpath> <value>", "USAGE");
50
- return;
51
- }
52
-
53
- let cfg;
54
- try {
55
- ({ config: cfg } = readConfig(dir));
56
- } catch (e) {
57
- outputError(`Cannot read ide.yml: ${e.message}`, "READ_ERROR");
58
- return;
59
- }
60
-
61
- if (!isConfigObject(cfg)) {
62
- outputError("Invalid ide.yml: config root must be an object", "INVALID_CONFIG");
63
- return;
64
- }
65
-
66
- let value = rest.join(" ");
67
- // Try to parse as number or boolean
68
- if (value === "true") value = true;
69
- else if (value === "false") value = false;
70
- else if (/^\d+$/.test(value)) value = parseInt(value);
71
-
72
- setByPath(cfg, dotpath, value);
73
- writeConfig(dir, cfg);
74
-
75
- if (json) {
76
- console.log(JSON.stringify({ ok: true, path: dotpath, value }, null, 2));
77
- } else {
78
- console.log(`Set ${dotpath} = ${JSON.stringify(value)}`);
79
- }
80
- }
81
-
82
- function addPane(dir, args, { json }) {
83
- const { row, title, command, size } = parseNamedArgs(args);
84
- if (row === undefined) {
85
- outputError(
86
- "Usage: tmux-ide config add-pane --row <N> --title <T> [--command <C>] [--size <S>]",
87
- "USAGE",
88
- );
89
- return;
90
- }
91
-
92
- let cfg;
93
- try {
94
- ({ config: cfg } = readConfig(dir));
95
- } catch (e) {
96
- outputError(`Cannot read ide.yml: ${e.message}`, "READ_ERROR");
97
- return;
98
- }
99
-
100
- if (!Array.isArray(cfg?.rows)) {
101
- outputError("Invalid ide.yml: 'rows' must be an array", "INVALID_CONFIG");
102
- return;
103
- }
104
-
105
- const rowIdx = parseIndex(row);
106
- if (rowIdx == null) {
107
- outputError(`Invalid row index "${row}"`, "USAGE");
108
- return;
109
- }
110
-
111
- if (!cfg.rows[rowIdx]) {
112
- outputError(`Row ${rowIdx} does not exist`, "INVALID_ROW");
113
- return;
114
- }
115
-
116
- if (!Array.isArray(cfg.rows[rowIdx].panes)) {
117
- outputError(`Invalid ide.yml: row ${rowIdx} panes must be an array`, "INVALID_CONFIG");
118
- return;
119
- }
120
-
121
- const pane = {};
122
- if (title) pane.title = title;
123
- if (command) pane.command = command;
124
- if (size) pane.size = size;
125
-
126
- cfg.rows[rowIdx].panes.push(pane);
127
- writeConfig(dir, cfg);
128
-
129
- if (json) {
130
- console.log(JSON.stringify({ ok: true, row: rowIdx, pane }, null, 2));
131
- } else {
132
- console.log(`Added pane "${title ?? "untitled"}" to row ${rowIdx}`);
133
- }
134
- }
135
-
136
- function removePane(dir, args, { json }) {
137
- const { row, pane } = parseNamedArgs(args);
138
- if (row === undefined || pane === undefined) {
139
- outputError("Usage: tmux-ide config remove-pane --row <N> --pane <M>", "USAGE");
140
- return;
141
- }
142
-
143
- let cfg;
144
- try {
145
- ({ config: cfg } = readConfig(dir));
146
- } catch (e) {
147
- outputError(`Cannot read ide.yml: ${e.message}`, "READ_ERROR");
148
- return;
149
- }
150
-
151
- if (!Array.isArray(cfg?.rows)) {
152
- outputError("Invalid ide.yml: 'rows' must be an array", "INVALID_CONFIG");
153
- return;
154
- }
155
-
156
- const rowIdx = parseIndex(row);
157
- const paneIdx = parseIndex(pane);
158
- if (rowIdx == null || paneIdx == null) {
159
- outputError("Usage: tmux-ide config remove-pane --row <N> --pane <M>", "USAGE");
160
- return;
161
- }
162
-
163
- if (!Array.isArray(cfg.rows[rowIdx]?.panes)) {
164
- outputError(`Invalid ide.yml: row ${rowIdx} panes must be an array`, "INVALID_CONFIG");
165
- return;
166
- }
167
-
168
- if (!cfg.rows[rowIdx].panes[paneIdx]) {
169
- outputError(`Pane ${paneIdx} in row ${rowIdx} does not exist`, "INVALID_PANE");
170
- return;
171
- }
172
-
173
- const removed = cfg.rows[rowIdx].panes.splice(paneIdx, 1)[0];
174
- writeConfig(dir, cfg);
175
-
176
- if (json) {
177
- console.log(JSON.stringify({ ok: true, row: rowIdx, pane: paneIdx, removed }, null, 2));
178
- } else {
179
- console.log(`Removed pane ${paneIdx} ("${removed.title ?? "untitled"}") from row ${rowIdx}`);
180
- }
181
- }
182
-
183
- function addRow(dir, args, { json }) {
184
- const { size } = parseNamedArgs(args);
185
-
186
- let cfg;
187
- try {
188
- ({ config: cfg } = readConfig(dir));
189
- } catch (e) {
190
- outputError(`Cannot read ide.yml: ${e.message}`, "READ_ERROR");
191
- return;
192
- }
193
-
194
- if (!isConfigObject(cfg)) {
195
- outputError("Invalid ide.yml: config root must be an object", "INVALID_CONFIG");
196
- return;
197
- }
198
-
199
- if (cfg.rows !== undefined && !Array.isArray(cfg.rows)) {
200
- outputError("Invalid ide.yml: 'rows' must be an array", "INVALID_CONFIG");
201
- return;
202
- }
203
-
204
- const row = { panes: [{ title: "Shell" }] };
205
- if (size) row.size = size;
206
-
207
- cfg.rows = cfg.rows ?? [];
208
- cfg.rows.push(row);
209
- writeConfig(dir, cfg);
210
-
211
- const rowIdx = cfg.rows.length - 1;
212
- if (json) {
213
- console.log(JSON.stringify({ ok: true, row: rowIdx, size: size ?? null }, null, 2));
214
- } else {
215
- console.log(`Added row ${rowIdx}${size ? ` (${size})` : ""}`);
216
- }
217
- }
218
-
219
- function enableTeam(dir, args, { json }) {
220
- const { name } = parseNamedArgs(args);
221
-
222
- let cfg;
223
- try {
224
- ({ config: cfg } = readConfig(dir));
225
- } catch (e) {
226
- outputError(`Cannot read ide.yml: ${e.message}`, "READ_ERROR");
227
- return;
228
- }
229
-
230
- if (!isConfigObject(cfg)) {
231
- outputError("Invalid ide.yml: config root must be an object", "INVALID_CONFIG");
232
- return;
233
- }
234
-
235
- if (cfg.rows !== undefined && !Array.isArray(cfg.rows)) {
236
- outputError("Invalid ide.yml: 'rows' must be an array", "INVALID_CONFIG");
237
- return;
238
- }
239
-
240
- const teamName = name ?? cfg.name ?? "my-team";
241
- cfg.team = { name: teamName };
242
-
243
- // Find claude panes and assign roles: first as lead, rest as teammates
244
- let leadAssigned = false;
245
- for (const row of cfg.rows ?? []) {
246
- for (const pane of row.panes ?? []) {
247
- if (pane.command === "claude" || pane.role === "lead" || pane.role === "teammate") {
248
- if (!leadAssigned) {
249
- pane.role = "lead";
250
- leadAssigned = true;
251
- } else {
252
- pane.role = "teammate";
253
- }
254
- }
255
- }
256
- }
257
- if (!leadAssigned) {
258
- delete cfg.team;
259
- outputError("Cannot enable agent team: no Claude panes found", "INVALID_CONFIG");
260
- return;
261
- }
262
-
263
- writeConfig(dir, cfg);
264
-
265
- if (json) {
266
- console.log(JSON.stringify({ ok: true, team: cfg.team, roles: summarizeRoles(cfg) }, null, 2));
267
- } else {
268
- console.log(`Enabled agent team "${teamName}"`);
269
- }
270
- }
271
-
272
- function disableTeam(dir, { json }) {
273
- let cfg;
274
- try {
275
- ({ config: cfg } = readConfig(dir));
276
- } catch (e) {
277
- outputError(`Cannot read ide.yml: ${e.message}`, "READ_ERROR");
278
- return;
279
- }
280
-
281
- if (!isConfigObject(cfg)) {
282
- outputError("Invalid ide.yml: config root must be an object", "INVALID_CONFIG");
283
- return;
284
- }
285
-
286
- if (cfg.rows !== undefined && !Array.isArray(cfg.rows)) {
287
- outputError("Invalid ide.yml: 'rows' must be an array", "INVALID_CONFIG");
288
- return;
289
- }
290
-
291
- delete cfg.team;
292
- for (const row of cfg.rows ?? []) {
293
- if (!Array.isArray(row?.panes)) continue;
294
- for (const pane of row.panes ?? []) {
295
- delete pane.role;
296
- delete pane.task;
297
- }
298
- }
299
-
300
- writeConfig(dir, cfg);
301
-
302
- if (json) {
303
- console.log(JSON.stringify({ ok: true, disabled: true }, null, 2));
304
- } else {
305
- console.log("Disabled agent team");
306
- }
307
- }
308
-
309
- function summarizeRoles(cfg) {
310
- const roles = [];
311
- for (let i = 0; i < (cfg.rows ?? []).length; i++) {
312
- for (let j = 0; j < (cfg.rows[i].panes ?? []).length; j++) {
313
- const p = cfg.rows[i].panes[j];
314
- if (p.role) {
315
- roles.push({ row: i, pane: j, title: p.title ?? null, role: p.role });
316
- }
317
- }
318
- }
319
- return roles;
320
- }
321
-
322
- function parseNamedArgs(args) {
323
- const result = {};
324
- for (let i = 0; i < args.length; i++) {
325
- if (args[i].startsWith("--") && i + 1 < args.length) {
326
- const key = args[i].slice(2);
327
- result[key] = args[i + 1];
328
- i++;
329
- }
330
- }
331
- return result;
332
- }
333
-
334
- function isConfigObject(value) {
335
- return value != null && typeof value === "object" && !Array.isArray(value);
336
- }
337
-
338
- function parseIndex(value) {
339
- if (!/^\d+$/.test(String(value))) return null;
340
- return Number.parseInt(value, 10);
341
- }
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
- }