tmux-agent 0.1.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 (161) hide show
  1. package/.codex/skills/speckit/SKILL.md +173 -0
  2. package/.codex/skills/speckit/assets/templates/checklist-template.md +49 -0
  3. package/.codex/skills/speckit/assets/templates/notes-entrypoints-template.md +11 -0
  4. package/.codex/skills/speckit/assets/templates/notes-questions-template.md +7 -0
  5. package/.codex/skills/speckit/assets/templates/notes-readme-template.md +36 -0
  6. package/.codex/skills/speckit/assets/templates/notes-session-template.md +21 -0
  7. package/.codex/skills/speckit/assets/templates/plan-template.md +126 -0
  8. package/.codex/skills/speckit/assets/templates/spec-template.md +135 -0
  9. package/.codex/skills/speckit/assets/templates/tasks-template.md +269 -0
  10. package/.codex/skills/speckit/references/acceptance.md +183 -0
  11. package/.codex/skills/speckit/references/analyze.md +186 -0
  12. package/.codex/skills/speckit/references/checklist.md +302 -0
  13. package/.codex/skills/speckit/references/clarify-auto.md +69 -0
  14. package/.codex/skills/speckit/references/clarify-detailed.md +78 -0
  15. package/.codex/skills/speckit/references/clarify.md +189 -0
  16. package/.codex/skills/speckit/references/constitution.md +90 -0
  17. package/.codex/skills/speckit/references/group.md +89 -0
  18. package/.codex/skills/speckit/references/implement-task.md +115 -0
  19. package/.codex/skills/speckit/references/implement.md +129 -0
  20. package/.codex/skills/speckit/references/notes.md +82 -0
  21. package/.codex/skills/speckit/references/plan-deep.md +87 -0
  22. package/.codex/skills/speckit/references/plan-from-questions.md +115 -0
  23. package/.codex/skills/speckit/references/plan-from-review.md +89 -0
  24. package/.codex/skills/speckit/references/plan.md +97 -0
  25. package/.codex/skills/speckit/references/review-plan.md +156 -0
  26. package/.codex/skills/speckit/references/specify.md +246 -0
  27. package/.codex/skills/speckit/references/tasks.md +155 -0
  28. package/.codex/skills/speckit/references/taskstoissues.md +33 -0
  29. package/.codex/skills/speckit/scripts/bash/check-prerequisites.sh +206 -0
  30. package/.codex/skills/speckit/scripts/bash/common.sh +191 -0
  31. package/.codex/skills/speckit/scripts/bash/create-new-feature.sh +259 -0
  32. package/.codex/skills/speckit/scripts/bash/extract-coded-points.sh +322 -0
  33. package/.codex/skills/speckit/scripts/bash/extract-spec-ids.sh +238 -0
  34. package/.codex/skills/speckit/scripts/bash/extract-tasks.sh +295 -0
  35. package/.codex/skills/speckit/scripts/bash/extract-user-stories.sh +312 -0
  36. package/.codex/skills/speckit/scripts/bash/setup-notes.sh +182 -0
  37. package/.codex/skills/speckit/scripts/bash/setup-plan.sh +110 -0
  38. package/.codex/skills/speckit/scripts/bash/show-todo-tasks.sh +257 -0
  39. package/.codex/skills/speckit/scripts/bash/spec-group-checklist.sh +402 -0
  40. package/.codex/skills/speckit/scripts/bash/spec-group-members.sh +215 -0
  41. package/.codex/skills/speckit/scripts/bash/spec-registry-graph.sh +399 -0
  42. package/.specify/memory/constitution.md +67 -0
  43. package/.specify/templates/agent-file-template.md +28 -0
  44. package/.specify/templates/checklist-template.md +49 -0
  45. package/.specify/templates/plan-template.md +126 -0
  46. package/.specify/templates/spec-template.md +135 -0
  47. package/.specify/templates/tasks-template.md +269 -0
  48. package/README.md +128 -0
  49. package/README.zh-CN.md +127 -0
  50. package/bun.lock +269 -0
  51. package/dist/cli/commands/codex/forkHome.js +88 -0
  52. package/dist/cli/commands/codex/send.js +55 -0
  53. package/dist/cli/commands/codex/sessionInfo.js +42 -0
  54. package/dist/cli/commands/codex/spawn.js +68 -0
  55. package/dist/cli/commands/find.js +26 -0
  56. package/dist/cli/commands/paneKill.js +33 -0
  57. package/dist/cli/commands/paneSpawn.js +40 -0
  58. package/dist/cli/commands/paneTitle.js +33 -0
  59. package/dist/cli/commands/read.js +34 -0
  60. package/dist/cli/commands/send.js +51 -0
  61. package/dist/cli/commands/snapshot.js +19 -0
  62. package/dist/cli/commands/ui/select.js +41 -0
  63. package/dist/cli/commands/windowKill.js +25 -0
  64. package/dist/cli/commands/windowLs.js +15 -0
  65. package/dist/cli/commands/windowNew.js +28 -0
  66. package/dist/cli/commands/windowRename.js +25 -0
  67. package/dist/cli/index.js +365 -0
  68. package/dist/cli/parse.js +39 -0
  69. package/dist/lib/codex/forkHome.js +101 -0
  70. package/dist/lib/codex/isCodexPane.js +55 -0
  71. package/dist/lib/codex/send.js +58 -0
  72. package/dist/lib/codex/sessionInfo.js +449 -0
  73. package/dist/lib/codex/spawn.js +246 -0
  74. package/dist/lib/contracts/types.js +2 -0
  75. package/dist/lib/fs/safeRm.js +32 -0
  76. package/dist/lib/io/readStdin.js +14 -0
  77. package/dist/lib/os/process.js +55 -0
  78. package/dist/lib/output/format.js +95 -0
  79. package/dist/lib/proc/lsof.js +42 -0
  80. package/dist/lib/proc/ps.js +60 -0
  81. package/dist/lib/targeting/errors.js +13 -0
  82. package/dist/lib/targeting/resolvePaneTarget.js +91 -0
  83. package/dist/lib/targeting/resolveWindowTarget.js +40 -0
  84. package/dist/lib/targeting/scope.js +58 -0
  85. package/dist/lib/tmux/capturePane.js +20 -0
  86. package/dist/lib/tmux/exec.js +66 -0
  87. package/dist/lib/tmux/paneOps.js +29 -0
  88. package/dist/lib/tmux/paste.js +23 -0
  89. package/dist/lib/tmux/sendKeys.js +47 -0
  90. package/dist/lib/tmux/session.js +29 -0
  91. package/dist/lib/tmux/snapshotPanes.js +46 -0
  92. package/dist/lib/tmux/snapshotWindows.js +24 -0
  93. package/dist/lib/tmux/windowOps.js +32 -0
  94. package/dist/lib/ui/popupSelect.js +432 -0
  95. package/dist/lib/ui/popupSupport.js +76 -0
  96. package/package.json +23 -0
  97. package/src/cli/commands/codex/forkHome.ts +141 -0
  98. package/src/cli/commands/codex/send.ts +83 -0
  99. package/src/cli/commands/codex/sessionInfo.ts +59 -0
  100. package/src/cli/commands/codex/spawn.ts +90 -0
  101. package/src/cli/commands/find.ts +40 -0
  102. package/src/cli/commands/paneKill.ts +49 -0
  103. package/src/cli/commands/paneSpawn.ts +53 -0
  104. package/src/cli/commands/paneTitle.ts +50 -0
  105. package/src/cli/commands/read.ts +48 -0
  106. package/src/cli/commands/send.ts +71 -0
  107. package/src/cli/commands/snapshot.ts +28 -0
  108. package/src/cli/commands/ui/select.ts +49 -0
  109. package/src/cli/commands/windowKill.ts +35 -0
  110. package/src/cli/commands/windowLs.ts +20 -0
  111. package/src/cli/commands/windowNew.ts +40 -0
  112. package/src/cli/commands/windowRename.ts +36 -0
  113. package/src/cli/index.ts +430 -0
  114. package/src/lib/codex/forkHome.ts +148 -0
  115. package/src/lib/codex/isCodexPane.ts +56 -0
  116. package/src/lib/codex/send.ts +84 -0
  117. package/src/lib/codex/sessionInfo.ts +521 -0
  118. package/src/lib/codex/spawn.ts +305 -0
  119. package/src/lib/contracts/types.ts +30 -0
  120. package/src/lib/fs/safeRm.ts +32 -0
  121. package/src/lib/io/readStdin.ts +11 -0
  122. package/src/lib/output/format.ts +105 -0
  123. package/src/lib/proc/lsof.ts +44 -0
  124. package/src/lib/proc/ps.ts +70 -0
  125. package/src/lib/targeting/errors.ts +25 -0
  126. package/src/lib/targeting/resolvePaneTarget.ts +106 -0
  127. package/src/lib/targeting/resolveWindowTarget.ts +45 -0
  128. package/src/lib/targeting/scope.ts +76 -0
  129. package/src/lib/tmux/capturePane.ts +21 -0
  130. package/src/lib/tmux/exec.ts +90 -0
  131. package/src/lib/tmux/paneOps.ts +35 -0
  132. package/src/lib/tmux/paste.ts +20 -0
  133. package/src/lib/tmux/sendKeys.ts +72 -0
  134. package/src/lib/tmux/session.ts +27 -0
  135. package/src/lib/tmux/snapshotPanes.ts +52 -0
  136. package/src/lib/tmux/snapshotWindows.ts +23 -0
  137. package/src/lib/tmux/windowOps.ts +43 -0
  138. package/src/lib/ui/popupSelect.ts +561 -0
  139. package/src/lib/ui/popupSupport.ts +84 -0
  140. package/tests/e2e/codexForkHome.test.ts +146 -0
  141. package/tests/e2e/codexSessionInfo.test.ts +112 -0
  142. package/tests/e2e/codexTuiSend.test.ts +68 -0
  143. package/tests/integration/codexSpawn.test.ts +113 -0
  144. package/tests/integration/paneOps.test.ts +60 -0
  145. package/tests/integration/sendRead.test.ts +52 -0
  146. package/tests/integration/snapshot.test.ts +39 -0
  147. package/tests/integration/tmuxHarness.ts +39 -0
  148. package/tests/integration/windowOps.test.ts +60 -0
  149. package/tests/unit/codexSend.test.ts +105 -0
  150. package/tests/unit/codexSessionInfo.test.ts +88 -0
  151. package/tests/unit/codexSpawn.test.ts +34 -0
  152. package/tests/unit/keys.test.ts +30 -0
  153. package/tests/unit/outputFormat.test.ts +52 -0
  154. package/tests/unit/popupSelect.test.ts +77 -0
  155. package/tests/unit/popupSupport.test.ts +109 -0
  156. package/tests/unit/resolvePaneTarget.test.ts +43 -0
  157. package/tests/unit/resolveWindowTarget.test.ts +36 -0
  158. package/tests/unit/safeRm.test.ts +41 -0
  159. package/tests/unit/scope.test.ts +57 -0
  160. package/tsconfig.json +14 -0
  161. package/vitest.config.ts +16 -0
@@ -0,0 +1,71 @@
1
+ import { formatJson } from "../../lib/output/format";
2
+ import { readStdin } from "../../lib/io/readStdin";
3
+ import { resolveSessionScope, resolveWindowScope } from "../../lib/targeting/scope";
4
+ import { resolvePaneTarget } from "../../lib/targeting/resolvePaneTarget";
5
+ import { snapshotPanes } from "../../lib/tmux/snapshotPanes";
6
+ import { sendKeys } from "../../lib/tmux/sendKeys";
7
+ import { isCodexPane } from "../../lib/codex/isCodexPane";
8
+
9
+ function isPaneIdTarget(target: string): boolean {
10
+ return target.startsWith("%") || /^@[^%]+%[^%]+$/.test(target);
11
+ }
12
+
13
+ export type SendOptions = {
14
+ json?: boolean;
15
+ session?: string;
16
+ window?: string;
17
+ noEnter?: boolean;
18
+ enterDelayMs?: number;
19
+ };
20
+
21
+ export async function send(
22
+ target: string,
23
+ textArg: string,
24
+ options: SendOptions = {}
25
+ ): Promise<string> {
26
+ if (!target || !textArg) {
27
+ throw new Error("send requires <paneTarget> <text>");
28
+ }
29
+
30
+ const scope = await resolveSessionScope(options.session?.trim() || undefined);
31
+
32
+ let windowId: string | undefined;
33
+ if (!isPaneIdTarget(target)) {
34
+ const window = await resolveWindowScope(
35
+ scope.session,
36
+ options.window?.trim() || undefined
37
+ );
38
+ windowId = window.wid;
39
+ }
40
+
41
+ const panes = await snapshotPanes({ session: scope.session });
42
+ const resolved = resolvePaneTarget(panes, target, { windowId });
43
+
44
+ const text = textArg === "-" ? await readStdin() : textArg;
45
+ const noEnter = Boolean(options.noEnter);
46
+ const explicitEnterDelayMs =
47
+ options.enterDelayMs !== undefined ? Math.max(0, Math.floor(options.enterDelayMs)) : undefined;
48
+ const resolvedPane = panes.find((pane) => pane.id === resolved.id);
49
+ let enterDelayMs = 0;
50
+ if (!noEnter) {
51
+ if (explicitEnterDelayMs !== undefined) {
52
+ enterDelayMs = explicitEnterDelayMs;
53
+ } else if (resolvedPane) {
54
+ enterDelayMs = (await isCodexPane(resolvedPane)) ? 250 : 0;
55
+ }
56
+ }
57
+ const sent = await sendKeys(resolved.id, text, {
58
+ noEnter,
59
+ enterDelayMs
60
+ });
61
+
62
+ if (options.json) {
63
+ return formatJson({
64
+ requested: { target },
65
+ resolved: { id: resolved.id, ...(resolved.idx !== undefined ? { idx: resolved.idx } : {}) },
66
+ sent
67
+ });
68
+ }
69
+
70
+ return resolved.id;
71
+ }
@@ -0,0 +1,28 @@
1
+ import { formatJson, formatPaneTable } from "../../lib/output/format";
2
+ import { resolveSessionScope, resolveWindowScope } from "../../lib/targeting/scope";
3
+ import { snapshotPanes } from "../../lib/tmux/snapshotPanes";
4
+
5
+ export type SnapshotOptions = {
6
+ json?: boolean;
7
+ session?: string;
8
+ window?: string;
9
+ };
10
+
11
+ export async function snapshot(options: SnapshotOptions = {}): Promise<string> {
12
+ const scope = await resolveSessionScope(options.session?.trim() || undefined);
13
+ const window = await resolveWindowScope(
14
+ scope.session,
15
+ options.window?.trim() || undefined
16
+ );
17
+ const panes = await snapshotPanes({ windowId: window.wid });
18
+
19
+ if (options.json) {
20
+ return formatJson({
21
+ session: { name: scope.session },
22
+ window: { widx: window.widx, wid: window.wid, name: window.name },
23
+ panes
24
+ });
25
+ }
26
+
27
+ return formatPaneTable(panes);
28
+ }
@@ -0,0 +1,49 @@
1
+ import { formatJson } from "../../../lib/output/format";
2
+ import { readStdin } from "../../../lib/io/readStdin";
3
+ import { popupSelect, type PopupSelectSpec } from "../../../lib/ui/popupSelect";
4
+
5
+ function parseJsonObject(raw: string): PopupSelectSpec {
6
+ let parsed: unknown;
7
+ try {
8
+ parsed = JSON.parse(raw);
9
+ } catch {
10
+ throw new Error("spec must be valid JSON");
11
+ }
12
+ if (!parsed || typeof parsed !== "object") {
13
+ throw new Error("spec must be a JSON object");
14
+ }
15
+ return parsed as PopupSelectSpec;
16
+ }
17
+
18
+ export type UiSelectOptions = {
19
+ json?: boolean;
20
+ spec: string;
21
+ };
22
+
23
+ export async function uiSelectCommand(options: UiSelectOptions): Promise<string> {
24
+ const specArg = String(options.spec || "").trim();
25
+ if (!specArg) {
26
+ throw new Error("ui select requires --spec -");
27
+ }
28
+ if (specArg !== "-") {
29
+ throw new Error("spec must be '-' to read from stdin");
30
+ }
31
+
32
+ const spec = parseJsonObject(await readStdin());
33
+ const result = await popupSelect(spec);
34
+
35
+ const selectedValues =
36
+ result.customInput && result.customInput.trim()
37
+ ? [result.customInput.trim()]
38
+ : result.selectedValues;
39
+
40
+ if (options.json) {
41
+ return formatJson({
42
+ mode: result.mode,
43
+ selected: { values: selectedValues },
44
+ ...(result.customInput ? { custom_input: result.customInput, custom_input_via: result.customInputVia } : {})
45
+ });
46
+ }
47
+
48
+ return selectedValues.join("\n");
49
+ }
@@ -0,0 +1,35 @@
1
+ import { formatJson } from "../../lib/output/format";
2
+ import { resolveSessionScope } from "../../lib/targeting/scope";
3
+ import { resolveWindowTarget } from "../../lib/targeting/resolveWindowTarget";
4
+ import { snapshotWindows } from "../../lib/tmux/snapshotWindows";
5
+ import { windowKill } from "../../lib/tmux/windowOps";
6
+
7
+ export type WindowKillOptions = {
8
+ json?: boolean;
9
+ session?: string;
10
+ };
11
+
12
+ export async function windowKillCommand(
13
+ target: string,
14
+ options: WindowKillOptions = {}
15
+ ): Promise<string> {
16
+ if (!target) {
17
+ throw new Error("window kill requires <windowTarget>");
18
+ }
19
+
20
+ const scope = await resolveSessionScope(options.session?.trim() || undefined);
21
+
22
+ const windows = await snapshotWindows(scope.session);
23
+ const resolved = resolveWindowTarget(windows, target);
24
+ await windowKill(resolved.wid);
25
+
26
+ if (options.json) {
27
+ return formatJson({
28
+ requested: { target },
29
+ resolved: { wid: resolved.wid, widx: resolved.widx },
30
+ killed: true
31
+ });
32
+ }
33
+
34
+ return resolved.wid;
35
+ }
@@ -0,0 +1,20 @@
1
+ import { formatJson, formatWindowTable } from "../../lib/output/format";
2
+ import { resolveSessionScope } from "../../lib/targeting/scope";
3
+ import { snapshotWindows } from "../../lib/tmux/snapshotWindows";
4
+
5
+ export type WindowLsOptions = {
6
+ json?: boolean;
7
+ session?: string;
8
+ };
9
+
10
+ export async function windowLs(options: WindowLsOptions = {}): Promise<string> {
11
+ const session = String(options.session ?? "");
12
+ const scope = await resolveSessionScope(session.trim() || undefined);
13
+ const windows = await snapshotWindows(scope.session);
14
+
15
+ if (options.json) {
16
+ return formatJson({ session: { name: scope.session }, windows });
17
+ }
18
+
19
+ return formatWindowTable(windows);
20
+ }
@@ -0,0 +1,40 @@
1
+ import { formatJson } from "../../lib/output/format";
2
+ import { readStdin } from "../../lib/io/readStdin";
3
+ import { resolveSessionScope } from "../../lib/targeting/scope";
4
+ import { tmuxExec } from "../../lib/tmux/exec";
5
+ import { pasteText } from "../../lib/tmux/paste";
6
+ import { windowNew } from "../../lib/tmux/windowOps";
7
+
8
+ export type WindowNewOptions = {
9
+ json?: boolean;
10
+ session?: string;
11
+ command?: string;
12
+ };
13
+
14
+ export async function windowNewCommand(
15
+ name: string,
16
+ options: WindowNewOptions = {}
17
+ ): Promise<string> {
18
+ if (!name) {
19
+ throw new Error("window new requires <name>");
20
+ }
21
+
22
+ const scope = await resolveSessionScope(options.session?.trim() || undefined);
23
+
24
+ let command = options.command?.trim() || undefined;
25
+ if (command === "-") {
26
+ command = await readStdin();
27
+ }
28
+
29
+ const created = await windowNew(scope.session, name);
30
+ if (command) {
31
+ await pasteText(created.paneId, command);
32
+ await tmuxExec(["send-keys", "-t", created.paneId, "Enter"]);
33
+ }
34
+
35
+ if (options.json) {
36
+ return formatJson({ created: { wid: created.wid, widx: created.widx, name: created.name } });
37
+ }
38
+
39
+ return created.wid;
40
+ }
@@ -0,0 +1,36 @@
1
+ import { formatJson } from "../../lib/output/format";
2
+ import { resolveSessionScope } from "../../lib/targeting/scope";
3
+ import { resolveWindowTarget } from "../../lib/targeting/resolveWindowTarget";
4
+ import { snapshotWindows } from "../../lib/tmux/snapshotWindows";
5
+ import { windowRename } from "../../lib/tmux/windowOps";
6
+
7
+ export type WindowRenameOptions = {
8
+ json?: boolean;
9
+ session?: string;
10
+ };
11
+
12
+ export async function windowRenameCommand(
13
+ target: string,
14
+ name: string,
15
+ options: WindowRenameOptions = {}
16
+ ): Promise<string> {
17
+ if (!target || !name) {
18
+ throw new Error("window rename requires <windowTarget> <name>");
19
+ }
20
+
21
+ const scope = await resolveSessionScope(options.session?.trim() || undefined);
22
+
23
+ const windows = await snapshotWindows(scope.session);
24
+ const resolved = resolveWindowTarget(windows, target);
25
+ await windowRename(resolved.wid, name);
26
+
27
+ if (options.json) {
28
+ return formatJson({
29
+ requested: { target, name },
30
+ resolved: { wid: resolved.wid, widx: resolved.widx },
31
+ renamed: { name }
32
+ });
33
+ }
34
+
35
+ return resolved.wid;
36
+ }