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.
- package/bin/cli.js +4 -216
- package/bin/cli.ts +536 -0
- package/package.json +33 -14
- package/src/__tests__/support.ts +186 -0
- package/src/{attach.js → attach.ts} +7 -4
- package/src/cli.test.ts +314 -0
- package/src/command-center/discovery.test.ts +265 -0
- package/src/command-center/discovery.ts +296 -0
- package/src/command-center/index.ts +31 -0
- package/src/command-center/schemas.ts +57 -0
- package/src/command-center/server.test.ts +818 -0
- package/src/command-center/server.ts +1426 -0
- package/src/config-cli.test.ts +86 -0
- package/src/config.test.ts +205 -0
- package/src/config.ts +313 -0
- package/src/detect.test.ts +142 -0
- package/src/{detect.js → detect.ts} +31 -15
- package/src/dispatch.ts +102 -0
- package/src/doctor.test.ts +119 -0
- package/src/doctor.ts +167 -0
- package/src/init.test.ts +101 -0
- package/src/init.ts +144 -0
- package/src/inspect.test.ts +67 -0
- package/src/{inspect.js → inspect.ts} +95 -10
- package/src/integration.test.ts.skip +276 -0
- package/src/js-yaml.d.ts +10 -0
- package/src/launch.test.ts +222 -0
- package/src/launch.ts +494 -0
- package/src/lib/auth/auth-service.test.ts +178 -0
- package/src/lib/auth/auth-service.ts +227 -0
- package/src/lib/auth/middleware.test.ts +119 -0
- package/src/lib/auth/middleware.ts +56 -0
- package/src/lib/auth/types.ts +13 -0
- package/src/lib/authorship.test.ts +242 -0
- package/src/lib/authorship.ts +280 -0
- package/src/lib/cast/recorder.test.ts +127 -0
- package/src/lib/cast/recorder.ts +148 -0
- package/src/lib/daemon-watchdog.ts +102 -0
- package/src/lib/daemon.ts +417 -0
- package/src/lib/dot-path.test.ts +66 -0
- package/src/lib/dot-path.ts +17 -0
- package/src/lib/errors.ts +44 -0
- package/src/lib/event-log.test.ts +189 -0
- package/src/lib/event-log.ts +237 -0
- package/src/lib/github-pr.ts +182 -0
- package/src/lib/hq/client.test.ts +154 -0
- package/src/lib/hq/client.ts +142 -0
- package/src/lib/hq/mdns.test.ts +54 -0
- package/src/lib/hq/mdns.ts +88 -0
- package/src/lib/hq/registry.test.ts +237 -0
- package/src/lib/hq/registry.ts +145 -0
- package/src/lib/hq/types.ts +44 -0
- package/src/lib/ipc/socket-protocol.test.ts +126 -0
- package/src/lib/ipc/socket-protocol.ts +324 -0
- package/src/lib/launch-plan.test.ts +119 -0
- package/src/lib/launch-plan.ts +90 -0
- package/src/lib/log.test.ts +39 -0
- package/src/lib/log.ts +71 -0
- package/src/lib/metrics.test.ts +213 -0
- package/src/lib/metrics.ts +345 -0
- package/src/lib/orchestrator.test.ts +2358 -0
- package/src/lib/orchestrator.ts +1792 -0
- package/src/lib/{output.js → output.ts} +18 -10
- package/src/lib/plan-store.test.ts +148 -0
- package/src/lib/plan-store.ts +131 -0
- package/src/lib/research.test.ts +208 -0
- package/src/lib/research.ts +475 -0
- package/src/lib/round-trip.test.ts +341 -0
- package/src/lib/session-monitor.test.ts +152 -0
- package/src/lib/session-monitor.ts +282 -0
- package/src/lib/session-options.test.ts +146 -0
- package/src/lib/{session-options.js → session-options.ts} +11 -6
- package/src/lib/shell.test.ts +20 -0
- package/src/lib/shell.ts +8 -0
- package/src/lib/sizes.test.ts +73 -0
- package/src/lib/{sizes.js → sizes.ts} +4 -4
- package/src/lib/skill-registry.test.ts +134 -0
- package/src/lib/skill-registry.ts +86 -0
- package/src/lib/slugify.test.ts +35 -0
- package/src/lib/slugify.ts +10 -0
- package/src/lib/task-store.ts +352 -0
- package/src/lib/tmux.test.ts +500 -0
- package/src/lib/tmux.ts +372 -0
- package/src/lib/token-tracker.test.ts +102 -0
- package/src/lib/token-tracker.ts +70 -0
- package/src/lib/tunnels/cloudflare.test.ts +73 -0
- package/src/lib/tunnels/cloudflare.ts +254 -0
- package/src/lib/tunnels/manager.test.ts +63 -0
- package/src/lib/tunnels/manager.ts +132 -0
- package/src/lib/tunnels/ngrok.test.ts +94 -0
- package/src/lib/tunnels/ngrok.ts +261 -0
- package/src/lib/tunnels/tailscale.test.ts +103 -0
- package/src/lib/tunnels/tailscale.ts +1048 -0
- package/src/lib/tunnels/types.ts +44 -0
- package/src/lib/validation.test.ts +289 -0
- package/src/lib/validation.ts +123 -0
- package/src/lib/webhook.ts +51 -0
- package/src/lib/workflow-store.ts +209 -0
- package/src/lib/ws-v3/protocol.test.ts +127 -0
- package/src/lib/ws-v3/protocol.ts +160 -0
- package/src/lib/yaml-io.test.ts +125 -0
- package/src/lib/{yaml-io.js → yaml-io.ts} +5 -4
- package/src/ls.test.ts +76 -0
- package/src/{ls.js → ls.ts} +4 -4
- package/src/metrics-cli.ts +177 -0
- package/src/notify.ts +85 -0
- package/src/orchestrator-status.ts +228 -0
- package/src/plan.ts +117 -0
- package/src/postinstall.test.ts +79 -0
- package/src/remote.test.ts +115 -0
- package/src/remote.ts +148 -0
- package/src/restart.ts +24 -0
- package/src/schemas/domain.ts +422 -0
- package/src/schemas/ide-config.ts +148 -0
- package/src/schemas/index.ts +53 -0
- package/src/send.test.ts +70 -0
- package/src/send.ts +181 -0
- package/src/skill.test.ts +114 -0
- package/src/skill.ts +138 -0
- package/src/status.ts +66 -0
- package/src/stop.test.ts +176 -0
- package/src/stop.ts +47 -0
- package/src/task.test.ts +1466 -0
- package/src/task.ts +1143 -0
- package/src/tunnel.test.ts +80 -0
- package/src/tunnel.ts +133 -0
- package/src/types.ts +15 -0
- package/src/ui/index.ts +32 -0
- package/src/ui/terminal/index.ts +9 -0
- package/src/ui/types.ts +91 -0
- package/src/ui/web/base.css +80 -0
- package/src/ui/web/components/Box.tsx +59 -0
- package/src/ui/web/components/Input.tsx +32 -0
- package/src/ui/web/components/ScrollBox.tsx +60 -0
- package/src/ui/web/components/Text.tsx +28 -0
- package/src/ui/web/hooks.ts +106 -0
- package/src/ui/web/index.ts +27 -0
- package/src/ui/web/render.ts +77 -0
- package/src/ui/web/utils/color.test.ts +93 -0
- package/src/ui/web/utils/color.ts +27 -0
- package/src/validate.test.ts +385 -0
- package/src/validate.ts +217 -0
- package/src/widgets/changes/index.tsx +576 -0
- package/src/widgets/config/index.tsx +440 -0
- package/src/widgets/costs/index.tsx +216 -0
- package/src/widgets/explorer/breadcrumbs.tsx +77 -0
- package/src/widgets/explorer/footer.tsx +20 -0
- package/src/widgets/explorer/header.tsx +23 -0
- package/src/widgets/explorer/index.tsx +408 -0
- package/src/widgets/explorer/tree-model.test.ts +214 -0
- package/src/widgets/explorer/tree-model.ts +103 -0
- package/src/widgets/explorer/tree.tsx +156 -0
- package/src/widgets/lib/config-model.ts +116 -0
- package/src/widgets/lib/files.test.ts +103 -0
- package/src/widgets/lib/files.ts +88 -0
- package/src/widgets/lib/git.test.ts +151 -0
- package/src/widgets/lib/git.ts +88 -0
- package/src/widgets/lib/pane-comms.test.ts +178 -0
- package/src/widgets/lib/pane-comms.ts +192 -0
- package/src/widgets/lib/theme.ts +151 -0
- package/src/widgets/lib/watcher.ts +50 -0
- package/src/widgets/mission-control/activity-feed.tsx +59 -0
- package/src/widgets/mission-control/agent-panel.tsx +132 -0
- package/src/widgets/mission-control/command-bar.tsx +63 -0
- package/src/widgets/mission-control/index.tsx +751 -0
- package/src/widgets/mission-control/task-panel.tsx +238 -0
- package/src/widgets/preview/index.tsx +415 -0
- package/src/widgets/resolve.ts +40 -0
- package/src/widgets/setup/agent-naming.tsx +112 -0
- package/src/widgets/setup/config-tree.tsx +238 -0
- package/src/widgets/setup/detect-panel.tsx +72 -0
- package/src/widgets/setup/field-editor.tsx +265 -0
- package/src/widgets/setup/footer.tsx +107 -0
- package/src/widgets/setup/index.tsx +340 -0
- package/src/widgets/setup/layout-picker.tsx +96 -0
- package/src/widgets/setup/orchestrator-panel.tsx +200 -0
- package/src/widgets/setup/review-panel.tsx +140 -0
- package/src/widgets/setup/setup-model.test.ts +303 -0
- package/src/widgets/setup/setup-model.ts +188 -0
- package/src/widgets/tasks/index.tsx +129 -0
- package/src/widgets/tasks/task-detail.tsx +309 -0
- package/src/widgets/tasks/task-form.tsx +193 -0
- package/src/widgets/tasks/task-list.tsx +205 -0
- package/src/widgets/tasks/task-model.test.ts +473 -0
- package/src/widgets/tasks/task-model.ts +157 -0
- package/templates/AGENTS.md +20 -0
- package/templates/missions.yml +41 -0
- package/templates/skills/backend.md +30 -0
- package/templates/skills/frontend.md +30 -0
- package/templates/skills/general-worker.md +30 -0
- package/templates/skills/researcher.md +69 -0
- package/templates/skills/reviewer.md +30 -0
- package/src/config.js +0 -341
- package/src/doctor.js +0 -91
- package/src/init.js +0 -73
- package/src/launch.js +0 -245
- package/src/lib/dot-path.js +0 -16
- package/src/lib/errors.js +0 -35
- package/src/lib/launch-plan.js +0 -49
- package/src/lib/session-monitor.js +0 -179
- package/src/lib/tmux.js +0 -237
- package/src/restart.js +0 -16
- package/src/status.js +0 -35
- package/src/stop.js +0 -25
- 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
|
-
}
|