@towles/tool 0.0.109 → 0.0.110
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/package.json +9 -4
- package/{plugins/tt-agentboard → packages/agentboard}/README.md +1 -1
- package/{plugins/tt-agentboard → packages/agentboard}/apps/server/package.json +2 -1
- package/{plugins/tt-agentboard → packages/agentboard}/apps/server/src/main.ts +6 -20
- package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/package.json +4 -0
- package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/DetailPanel.tsx +3 -2
- package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/StatusBar.tsx +35 -0
- package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/constants.ts +1 -0
- package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/index.tsx +204 -225
- package/packages/agentboard/apps/tui/src/session-status.test.ts +70 -0
- package/packages/agentboard/apps/tui/src/session-status.ts +19 -0
- package/{plugins/tt-agentboard → packages/agentboard}/package.json +2 -6
- package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/package.json +3 -0
- package/{plugins/tt-agentboard/packages/runtime/test → packages/agentboard/packages/runtime/src/agents}/tracker.test.ts +2 -2
- package/packages/agentboard/packages/runtime/src/agents/watchers/claude-code.test.ts +63 -0
- package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/claude-code.ts +26 -2
- package/packages/agentboard/packages/runtime/src/config.test.ts +107 -0
- package/packages/agentboard/packages/runtime/src/config.ts +80 -0
- package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/index.ts +1 -1
- package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/plugins/loader.ts +1 -33
- package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/git-info.ts +3 -2
- package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/index.ts +23 -37
- package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/launcher.ts +6 -18
- package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/pane-scanner.ts +6 -0
- package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/shared.ts +2 -0
- package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/tsconfig.json +1 -1
- package/packages/shared/package.json +15 -0
- package/packages/shared/src/git/exec.ts +41 -0
- package/{src/utils → packages/shared/src}/git/gh-cli-wrapper.ts +13 -18
- package/packages/shared/src/index.ts +8 -0
- package/packages/shared/tsconfig.json +16 -0
- package/src/cli.ts +1 -1
- package/src/commands/agentboard.ts +42 -59
- package/src/{lib → commands}/auto-claude/claude-cli.ts +1 -1
- package/src/commands/auto-claude/config-init-helpers.ts +79 -0
- package/src/commands/auto-claude/config-init.test.ts +137 -0
- package/src/commands/auto-claude/config-init.ts +159 -0
- package/src/{lib → commands}/auto-claude/config.ts +4 -8
- package/src/{lib → commands}/auto-claude/e2e.test.ts +6 -6
- package/src/commands/auto-claude/explain.test.ts +58 -0
- package/src/commands/auto-claude/explain.ts +97 -0
- package/src/commands/auto-claude/index.ts +37 -14
- package/src/{lib → commands}/auto-claude/labels.ts +1 -1
- package/src/commands/auto-claude/list.ts +5 -4
- package/src/{lib → commands}/auto-claude/pipeline-execution.test.ts +1 -1
- package/src/{lib → commands}/auto-claude/pipeline.ts +1 -3
- package/src/commands/auto-claude/retry.test.ts +2 -2
- package/src/commands/auto-claude/retry.ts +5 -5
- package/src/commands/auto-claude/shell.ts +3 -0
- package/src/commands/auto-claude/status.test.ts +2 -2
- package/src/commands/auto-claude/status.ts +4 -4
- package/src/{lib → commands}/auto-claude/steps/create-pr.ts +1 -3
- package/src/{lib → commands}/auto-claude/steps/fetch-issues.ts +1 -1
- package/src/{lib → commands}/auto-claude/steps/implement.ts +1 -2
- package/src/{lib → commands}/auto-claude/utils-execution.test.ts +6 -6
- package/src/{lib → commands}/auto-claude/utils.ts +10 -4
- package/src/{lib/install → commands}/claude-settings.ts +1 -1
- package/src/commands/config/config.test.ts +129 -0
- package/src/commands/config/index.ts +11 -0
- package/src/commands/config/reset.ts +53 -0
- package/src/commands/config/schema.ts +19 -0
- package/src/commands/{config.ts → config/show.ts} +2 -2
- package/src/commands/config/validate.ts +51 -0
- package/src/commands/doctor/checks.ts +167 -0
- package/src/commands/doctor/format.test.ts +63 -0
- package/src/commands/doctor/format.ts +5 -0
- package/src/commands/doctor/history.test.ts +161 -0
- package/src/commands/doctor/history.ts +130 -0
- package/src/commands/doctor.ts +80 -151
- package/src/commands/gh/branch-clean.ts +4 -4
- package/src/commands/gh/branch.test.ts +4 -5
- package/src/commands/gh/branch.ts +10 -5
- package/src/commands/gh/pr.ts +6 -7
- package/src/{lib → commands}/graph/analyzer.test.ts +4 -4
- package/src/commands/graph/format.test.ts +130 -0
- package/src/commands/graph/format.ts +94 -0
- package/src/commands/graph/index.ts +69 -41
- package/src/{lib → commands}/graph/labels.ts +4 -4
- package/src/{lib → commands}/graph/server.ts +2 -2
- package/src/{lib → commands}/graph/types.ts +2 -0
- package/src/commands/graph.test.ts +1 -1
- package/src/commands/install.ts +6 -6
- package/src/commands/journal/daily-notes.ts +4 -7
- package/src/{lib → commands}/journal/fs.ts +1 -1
- package/src/commands/journal/index.ts +2 -0
- package/src/commands/journal/list.test.ts +174 -0
- package/src/commands/journal/list.ts +213 -0
- package/src/commands/journal/meeting.ts +4 -7
- package/src/commands/journal/note.ts +4 -7
- package/src/{lib → commands}/journal/paths.ts +1 -1
- package/src/commands/journal/search.test.ts +156 -0
- package/src/commands/journal/search.ts +256 -0
- package/src/{lib → commands}/journal/templates.ts +1 -1
- package/src/config/settings.ts +35 -26
- package/plugins/tt-agentboard/bun.lock +0 -444
- package/plugins/tt-agentboard/packages/runtime/src/config.ts +0 -70
- package/plugins/tt-agentboard/packages/runtime/test/config.test.ts +0 -83
- package/plugins/tt-auto-claude/.claude-plugin/plugin.json +0 -8
- package/plugins/tt-auto-claude/commands/create-issue.md +0 -20
- package/plugins/tt-auto-claude/commands/list.md +0 -21
- package/plugins/tt-auto-claude/skills/auto-claude/SKILL.md +0 -71
- package/plugins/tt-core/promptfooconfig.interview-me.yaml +0 -155
- package/plugins/tt-core/promptfooconfig.refine-text.yaml +0 -242
- package/plugins/tt-core/promptfooconfig.tdd.yaml +0 -144
- package/plugins/tt-core/promptfooconfig.write-prd.yaml +0 -145
- package/src/commands/config.test.ts +0 -9
- package/src/lib/auto-claude/index.ts +0 -15
- package/src/lib/auto-claude/shell.ts +0 -6
- package/src/lib/graph/index.ts +0 -24
- package/src/lib/journal/index.ts +0 -11
- package/src/utils/git/exec.ts +0 -18
- /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/build.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/bunfig.toml +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/scripts/sessionizer.sh +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/DiffStats.tsx +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/components/SessionCard.tsx +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/detail-panel-height.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/src/mux-context.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/apps/tui/tsconfig.json +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/package.json +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/src/client.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/src/index.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/src/provider.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/mux-tmux/tsconfig.json +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/tracker.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/amp.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/codex.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/agents/watchers/opencode.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/agent-watcher.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/agent.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/index.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/contracts/mux.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/debug.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/mux/detect.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/mux/registry.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/context.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/metadata-store.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/port-scanner.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/session-order.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/sidebar-manager.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/server/sidebar-width-sync.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/packages/runtime/src/themes.ts +0 -0
- /package/{plugins/tt-agentboard → packages/agentboard}/tsconfig.json +0 -0
- /package/{plugins/tt-core → packages/core}/.claude-plugin/plugin.json +0 -0
- /package/{plugins/tt-core → packages/core}/README.md +0 -0
- /package/{plugins/tt-core → packages/core}/commands/improve-architecture.md +0 -0
- /package/{plugins/tt-core → packages/core}/commands/interview-me.md +0 -0
- /package/{plugins/tt-core → packages/core}/commands/prd-to-issues.md +0 -0
- /package/{plugins/tt-core → packages/core}/commands/refine-text.md +0 -0
- /package/{plugins/tt-core → packages/core}/commands/task.md +0 -0
- /package/{plugins/tt-core → packages/core}/commands/tdd.md +0 -0
- /package/{plugins/tt-core → packages/core}/commands/write-prd.md +0 -0
- /package/{plugins/tt-core → packages/core}/skills/towles-tool/SKILL.md +0 -0
- /package/{src/utils → packages/shared/src}/date-utils.test.ts +0 -0
- /package/{src/utils → packages/shared/src}/date-utils.ts +0 -0
- /package/{src/utils → packages/shared/src}/fs.ts +0 -0
- /package/{src/utils → packages/shared/src}/git/branch-name.test.ts +0 -0
- /package/{src/utils → packages/shared/src}/git/branch-name.ts +0 -0
- /package/{src/utils → packages/shared/src}/git/gh-cli-wrapper.test.ts +0 -0
- /package/{src/utils → packages/shared/src}/render.test.ts +0 -0
- /package/{src/utils → packages/shared/src}/render.ts +0 -0
- /package/src/{lib → commands}/auto-claude/config.test.ts +0 -0
- /package/src/{lib → commands}/auto-claude/labels.test.ts +0 -0
- /package/src/{lib → commands}/auto-claude/pipeline.test.ts +0 -0
- /package/src/{lib → commands}/auto-claude/prompt-templates/01_plan.prompt.md +0 -0
- /package/src/{lib → commands}/auto-claude/prompt-templates/02_implement.prompt.md +0 -0
- /package/src/{lib → commands}/auto-claude/prompt-templates/03_simplify.prompt.md +0 -0
- /package/src/{lib → commands}/auto-claude/prompt-templates/04_review.prompt.md +0 -0
- /package/src/{lib → commands}/auto-claude/prompt-templates/CLAUDE.md +0 -0
- /package/src/{lib → commands}/auto-claude/prompt-templates/index.test.ts +0 -0
- /package/src/{lib → commands}/auto-claude/prompt-templates/index.ts +0 -0
- /package/src/{lib → commands}/auto-claude/run-claude.test.ts +0 -0
- /package/src/{lib → commands}/auto-claude/spawn-claude.ts +0 -0
- /package/src/{lib → commands}/auto-claude/steps/simple-steps.ts +0 -0
- /package/src/{lib → commands}/auto-claude/steps/steps.test.ts +0 -0
- /package/src/{lib → commands}/auto-claude/stream-parser.test.ts +0 -0
- /package/src/{lib → commands}/auto-claude/stream-parser.ts +0 -0
- /package/src/{lib → commands}/auto-claude/templates.test.ts +0 -0
- /package/src/{lib → commands}/auto-claude/templates.ts +0 -0
- /package/src/{lib → commands}/auto-claude/test-helpers.ts +0 -0
- /package/src/{lib → commands}/auto-claude/utils.test.ts +0 -0
- /package/src/{lib → commands}/graph/analyzer.ts +0 -0
- /package/src/{lib → commands}/graph/graph-template.html +0 -0
- /package/src/{lib → commands}/graph/parser.test.ts +0 -0
- /package/src/{lib → commands}/graph/parser.ts +0 -0
- /package/src/{lib → commands}/graph/render.ts +0 -0
- /package/src/{lib → commands}/graph/sessions.ts +0 -0
- /package/src/{lib → commands}/graph/tools.ts +0 -0
- /package/src/{lib → commands}/graph/treemap.ts +0 -0
- /package/src/{lib → commands}/journal/editor.ts +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface XResult {
|
|
2
|
+
stdout: string;
|
|
3
|
+
stderr: string;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface XOptions {
|
|
8
|
+
throwOnError?: boolean;
|
|
9
|
+
cwd?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function run(cmd: string, args: string[] = [], options?: XOptions): Promise<XResult> {
|
|
13
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
14
|
+
const proc = Bun.spawn([cmd, ...args], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
15
|
+
const exitCode = await proc.exited;
|
|
16
|
+
const [stdout, stderr] = await Promise.all([
|
|
17
|
+
new Response(proc.stdout).text(),
|
|
18
|
+
new Response(proc.stderr).text(),
|
|
19
|
+
]);
|
|
20
|
+
if (options?.throwOnError && exitCode !== 0) {
|
|
21
|
+
throw new Error(`Command failed (exit ${exitCode}): ${cmd} ${args.join(" ")}\n${stderr}`);
|
|
22
|
+
}
|
|
23
|
+
return { stdout, stderr, exitCode };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function exec(cmd: string, args: string[]): Promise<string> {
|
|
27
|
+
const result = await run(cmd, args, { throwOnError: true });
|
|
28
|
+
return result.stdout.trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function execSafe(
|
|
32
|
+
cmd: string,
|
|
33
|
+
args: string[],
|
|
34
|
+
): Promise<{ stdout: string; ok: boolean }> {
|
|
35
|
+
const result = await run(cmd, args);
|
|
36
|
+
return { stdout: result.stdout.trim(), ok: result.exitCode === 0 };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function git(args: string[]): Promise<string> {
|
|
40
|
+
return exec("git", args);
|
|
41
|
+
}
|
|
@@ -1,36 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import type { Output } from "tinyexec";
|
|
4
|
-
|
|
5
|
-
import { execSafe } from "./exec.js";
|
|
1
|
+
import { exec, execSafe, run as defaultX } from "./exec.js";
|
|
2
|
+
import type { XResult } from "./exec.js";
|
|
6
3
|
|
|
7
4
|
export type XFn = (
|
|
8
5
|
cmd: string,
|
|
9
6
|
args?: string[],
|
|
10
7
|
opts?: Record<string, unknown>,
|
|
11
|
-
) => PromiseLike<
|
|
8
|
+
) => PromiseLike<XResult>;
|
|
12
9
|
|
|
13
|
-
export async function isGithubCliInstalled(
|
|
10
|
+
export async function isGithubCliInstalled(execFn: XFn = defaultX): Promise<boolean> {
|
|
14
11
|
try {
|
|
15
|
-
const proc = await
|
|
12
|
+
const proc = await execFn("gh", ["--version"]);
|
|
16
13
|
return proc.stdout.includes("https://github.com/cli/cli");
|
|
17
14
|
} catch {
|
|
18
|
-
// gh CLI not installed or not accessible
|
|
19
15
|
return false;
|
|
20
16
|
}
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
export async function gh<T = unknown>(args: string[]): Promise<T> {
|
|
24
|
-
const
|
|
25
|
-
return JSON.parse(
|
|
20
|
+
const stdout = await exec("gh", args);
|
|
21
|
+
return JSON.parse(stdout) as T;
|
|
26
22
|
}
|
|
27
23
|
|
|
28
24
|
export async function ghRaw(
|
|
29
25
|
args: string[],
|
|
30
|
-
|
|
26
|
+
execFn?: (cmd: string, args: string[]) => Promise<{ stdout: string; ok: boolean }>,
|
|
31
27
|
): Promise<string> {
|
|
32
|
-
const
|
|
33
|
-
const result = await
|
|
28
|
+
const fn = execFn ?? execSafe;
|
|
29
|
+
const result = await fn("gh", args);
|
|
34
30
|
return result.stdout;
|
|
35
31
|
}
|
|
36
32
|
|
|
@@ -48,7 +44,7 @@ export async function getIssues({
|
|
|
48
44
|
assignedToMe,
|
|
49
45
|
cwd,
|
|
50
46
|
label,
|
|
51
|
-
exec =
|
|
47
|
+
exec: execFn = defaultX,
|
|
52
48
|
}: {
|
|
53
49
|
assignedToMe?: boolean;
|
|
54
50
|
cwd: string;
|
|
@@ -65,9 +61,8 @@ export async function getIssues({
|
|
|
65
61
|
args.push("--label", label);
|
|
66
62
|
}
|
|
67
63
|
|
|
68
|
-
const result = await
|
|
69
|
-
|
|
70
|
-
const stripped = stripAnsi(result.stdout);
|
|
64
|
+
const result = await execFn("gh", args);
|
|
65
|
+
const stripped = Bun.stripANSI(result.stdout);
|
|
71
66
|
|
|
72
67
|
try {
|
|
73
68
|
return JSON.parse(stripped) as Issue[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { ensureDir, fileExists, readFile, writeFile } from "./fs.js";
|
|
2
|
+
export { getTerminalColumns, limitText, printWithHexColor } from "./render.js";
|
|
3
|
+
export { formatDate, generateJournalFilename, getMondayOfWeek, getWeekInfo } from "./date-utils.js";
|
|
4
|
+
export { exec, execSafe, git, run } from "./git/exec.js";
|
|
5
|
+
export type { XResult, XOptions } from "./git/exec.js";
|
|
6
|
+
export { gh, ghRaw, getIssues, isGithubCliInstalled } from "./git/gh-cli-wrapper.js";
|
|
7
|
+
export type { Issue, XFn } from "./git/gh-cli-wrapper.js";
|
|
8
|
+
export { createBranchNameFromIssue } from "./git/branch-name.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"strictNullChecks": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"types": ["bun-types"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts"]
|
|
16
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { version } from "../package.json";
|
|
|
4
4
|
export const main = defineCommand({
|
|
5
5
|
meta: { name: "tt", version, description: "towles-tool — personal CLI utilities" },
|
|
6
6
|
subCommands: {
|
|
7
|
-
config: () => import("./commands/config.js").then((m) => m.default),
|
|
7
|
+
config: () => import("./commands/config/index.js").then((m) => m.default),
|
|
8
8
|
doctor: () => import("./commands/doctor.js").then((m) => m.default),
|
|
9
9
|
install: () => import("./commands/install.js").then((m) => m.default),
|
|
10
10
|
agentboard: () => import("./commands/agentboard.js").then((m) => m.default),
|
|
@@ -9,8 +9,6 @@ import { debugArg } from "./shared.js";
|
|
|
9
9
|
const SERVER_HOST = "127.0.0.1";
|
|
10
10
|
const SERVER_PORT = 4201;
|
|
11
11
|
|
|
12
|
-
const PLUGIN_DIR = resolve(import.meta.dirname, "../../plugins/tt-agentboard");
|
|
13
|
-
|
|
14
12
|
// Keybinding defaults
|
|
15
13
|
const DEFAULT_KEY = "a";
|
|
16
14
|
const TMUX_BINDINGS = { toggle: "t", focus: "s" } as const;
|
|
@@ -20,37 +18,18 @@ const MARKER = "# agentboard";
|
|
|
20
18
|
function findTmuxConf(): string | null {
|
|
21
19
|
const candidates = [resolve(process.env.HOME ?? "~", ".config/tmux/tmux.conf")];
|
|
22
20
|
for (const path of candidates) {
|
|
23
|
-
|
|
24
|
-
const real = existsSync(path) ? path : null;
|
|
25
|
-
if (real) return real;
|
|
26
|
-
} catch {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
21
|
+
if (existsSync(path)) return path;
|
|
29
22
|
}
|
|
30
23
|
return null;
|
|
31
24
|
}
|
|
32
25
|
|
|
33
|
-
function
|
|
26
|
+
function ensureBun(): void {
|
|
34
27
|
try {
|
|
35
28
|
execSync("bun --version", { stdio: "pipe" });
|
|
36
29
|
} catch {
|
|
37
30
|
consola.error("bun is required but not found. Install: https://bun.sh");
|
|
38
31
|
process.exit(1);
|
|
39
32
|
}
|
|
40
|
-
|
|
41
|
-
if (!existsSync(PLUGIN_DIR)) {
|
|
42
|
-
consola.error(`Agentboard plugin directory not found: ${PLUGIN_DIR}`);
|
|
43
|
-
consola.error(
|
|
44
|
-
"If installed globally, ensure the 'plugins' directory is included in the package.",
|
|
45
|
-
);
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const runtimeNodeModules = resolve(PLUGIN_DIR, "packages/runtime/node_modules");
|
|
50
|
-
if (!existsSync(runtimeNodeModules)) {
|
|
51
|
-
consola.info("Installing agentboard dependencies...");
|
|
52
|
-
execSync("bun install", { cwd: PLUGIN_DIR, stdio: "inherit" });
|
|
53
|
-
}
|
|
54
33
|
}
|
|
55
34
|
|
|
56
35
|
function reloadTmux(): void {
|
|
@@ -111,8 +90,6 @@ function showKeys(): void {
|
|
|
111
90
|
}
|
|
112
91
|
|
|
113
92
|
function setup(): void {
|
|
114
|
-
ensureDeps();
|
|
115
|
-
|
|
116
93
|
const confPath = findTmuxConf();
|
|
117
94
|
if (!confPath) {
|
|
118
95
|
consola.warn("No tmux.conf found. Add this line manually:");
|
|
@@ -186,18 +163,15 @@ function uninstall(): void {
|
|
|
186
163
|
}
|
|
187
164
|
|
|
188
165
|
function startServer(): void {
|
|
189
|
-
|
|
166
|
+
ensureBun();
|
|
190
167
|
|
|
191
|
-
const
|
|
168
|
+
const agentboardDir = resolve(import.meta.dirname, "../../packages/agentboard");
|
|
169
|
+
const serverEntry = resolve(agentboardDir, "apps/server/src/main.ts");
|
|
192
170
|
consola.info("Starting agentboard server (foreground, Ctrl+C to stop)...");
|
|
193
171
|
|
|
194
172
|
execSync(`bun run ${serverEntry}`, {
|
|
195
173
|
stdio: "inherit",
|
|
196
|
-
cwd:
|
|
197
|
-
env: {
|
|
198
|
-
...process.env,
|
|
199
|
-
AGENTBOARD_DIR: PLUGIN_DIR,
|
|
200
|
-
},
|
|
174
|
+
cwd: agentboardDir,
|
|
201
175
|
});
|
|
202
176
|
}
|
|
203
177
|
|
|
@@ -215,13 +189,10 @@ async function serverAlive(): Promise<boolean> {
|
|
|
215
189
|
async function ensureServerUp(): Promise<boolean> {
|
|
216
190
|
if (await serverAlive()) return true;
|
|
217
191
|
|
|
218
|
-
const serverEntry = resolve(PLUGIN_DIR, "apps/server/src/main.ts");
|
|
219
192
|
consola.info("Starting agentboard server...");
|
|
220
|
-
const child = spawn("
|
|
193
|
+
const child = spawn("tt", ["agentboard", "server"], {
|
|
221
194
|
stdio: "ignore",
|
|
222
|
-
cwd: PLUGIN_DIR,
|
|
223
195
|
detached: true,
|
|
224
|
-
env: { ...process.env, AGENTBOARD_DIR: PLUGIN_DIR },
|
|
225
196
|
});
|
|
226
197
|
child.unref();
|
|
227
198
|
|
|
@@ -262,7 +233,9 @@ function findSidebarPane(windowId: string): string | null {
|
|
|
262
233
|
const [paneId, title] = line.split(" ", 2);
|
|
263
234
|
if (title === "agentboard-sidebar" && paneId) return paneId;
|
|
264
235
|
}
|
|
265
|
-
} catch {
|
|
236
|
+
} catch (err) {
|
|
237
|
+
consola.debug(`findSidebarPane failed for window ${windowId}:`, err);
|
|
238
|
+
}
|
|
266
239
|
return null;
|
|
267
240
|
}
|
|
268
241
|
|
|
@@ -381,7 +354,7 @@ async function runFocus(): Promise<void> {
|
|
|
381
354
|
}
|
|
382
355
|
|
|
383
356
|
async function restart(): Promise<void> {
|
|
384
|
-
|
|
357
|
+
ensureBun();
|
|
385
358
|
|
|
386
359
|
// 1. Kill stash sessions left over from hidden sidebars
|
|
387
360
|
try {
|
|
@@ -407,35 +380,47 @@ async function restart(): Promise<void> {
|
|
|
407
380
|
}
|
|
408
381
|
consola.success("Server is running");
|
|
409
382
|
|
|
410
|
-
// 3.
|
|
383
|
+
// 3. Bootstrap sidebars — refresh session list, then ensure-sidebar for
|
|
384
|
+
// each active client's window so sidebars appear without interaction.
|
|
385
|
+
const base = `http://${SERVER_HOST}:${SERVER_PORT}`;
|
|
386
|
+
await fetch(`${base}/refresh`, { method: "POST", signal: AbortSignal.timeout(2000) }).catch(
|
|
387
|
+
() => {},
|
|
388
|
+
);
|
|
389
|
+
|
|
411
390
|
try {
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
391
|
+
const clients = spawnSync(
|
|
392
|
+
"tmux",
|
|
393
|
+
["list-clients", "-F", "#{client_tty}|#{session_name}|#{window_id}"],
|
|
394
|
+
{
|
|
395
|
+
encoding: "utf8",
|
|
396
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
const lines = (clients.stdout ?? "").trim().split("\n").filter(Boolean);
|
|
400
|
+
await Promise.allSettled(
|
|
401
|
+
lines.map((ctx) =>
|
|
402
|
+
fetch(`${base}/ensure-sidebar`, {
|
|
403
|
+
method: "POST",
|
|
404
|
+
body: ctx,
|
|
405
|
+
signal: AbortSignal.timeout(2000),
|
|
406
|
+
}),
|
|
407
|
+
),
|
|
408
|
+
);
|
|
409
|
+
consola.success(`Sidebars ensured for ${lines.length} client(s)`);
|
|
422
410
|
} catch (err) {
|
|
423
|
-
consola.error("Failed to
|
|
411
|
+
consola.error("Failed to bootstrap sidebars:", err);
|
|
424
412
|
}
|
|
425
413
|
}
|
|
426
414
|
|
|
427
415
|
function startTui(): void {
|
|
428
|
-
|
|
416
|
+
ensureBun();
|
|
429
417
|
|
|
430
|
-
const
|
|
418
|
+
const agentboardDir = resolve(import.meta.dirname, "../../packages/agentboard");
|
|
419
|
+
const tuiEntry = resolve(agentboardDir, "apps/tui/src/index.tsx");
|
|
431
420
|
|
|
432
421
|
execSync(`bun run ${tuiEntry}`, {
|
|
433
422
|
stdio: "inherit",
|
|
434
|
-
cwd: resolve(
|
|
435
|
-
env: {
|
|
436
|
-
...process.env,
|
|
437
|
-
AGENTBOARD_DIR: PLUGIN_DIR,
|
|
438
|
-
},
|
|
423
|
+
cwd: resolve(agentboardDir, "apps/tui"),
|
|
439
424
|
});
|
|
440
425
|
}
|
|
441
426
|
|
|
@@ -463,8 +448,6 @@ export default defineCommand({
|
|
|
463
448
|
startServer();
|
|
464
449
|
break;
|
|
465
450
|
case "tui":
|
|
466
|
-
startTui();
|
|
467
|
-
break;
|
|
468
451
|
case "start":
|
|
469
452
|
startTui();
|
|
470
453
|
break;
|
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import consola from "consola";
|
|
5
5
|
import pc from "picocolors";
|
|
6
6
|
|
|
7
|
-
import { readFile } from "
|
|
7
|
+
import { readFile } from "@towles/shared";
|
|
8
8
|
import { getConfig } from "./config.js";
|
|
9
9
|
import { sleep } from "./shell.js";
|
|
10
10
|
import { spawnClaude as defaultSpawnClaude } from "./spawn-claude.js";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { AutoClaudeConfigSchema } from "./config.js";
|
|
2
|
+
import type { AutoClaudeConfig } from "./config.js";
|
|
3
|
+
|
|
4
|
+
export const CONFIG_DIR = ".auto-claude";
|
|
5
|
+
export const CONFIG_FILENAME = "config.json";
|
|
6
|
+
export const CONFIG_PATH = `${CONFIG_DIR}/${CONFIG_FILENAME}`;
|
|
7
|
+
|
|
8
|
+
export interface ConfigInitInput {
|
|
9
|
+
triggerLabel: string;
|
|
10
|
+
mainBranch: string;
|
|
11
|
+
scopePath: string;
|
|
12
|
+
model: string;
|
|
13
|
+
repo: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build a full AutoClaudeConfig from user inputs, applying schema defaults.
|
|
18
|
+
*/
|
|
19
|
+
export function buildConfig(input: ConfigInitInput): AutoClaudeConfig {
|
|
20
|
+
return AutoClaudeConfigSchema.parse({
|
|
21
|
+
triggerLabel: input.triggerLabel,
|
|
22
|
+
mainBranch: input.mainBranch,
|
|
23
|
+
scopePath: input.scopePath,
|
|
24
|
+
model: input.model,
|
|
25
|
+
repo: input.repo,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validate that a trigger label is non-empty and contains no spaces.
|
|
31
|
+
*/
|
|
32
|
+
export function validateTriggerLabel(label: string): string | true {
|
|
33
|
+
const trimmed = label.trim();
|
|
34
|
+
if (trimmed.length === 0) return "Trigger label cannot be empty";
|
|
35
|
+
if (/\s/.test(trimmed)) return "Trigger label cannot contain spaces";
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate that a branch name is non-empty.
|
|
41
|
+
*/
|
|
42
|
+
export function validateBranchName(name: string): string | true {
|
|
43
|
+
const trimmed = name.trim();
|
|
44
|
+
if (trimmed.length === 0) return "Branch name cannot be empty";
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate that a scope path is non-empty.
|
|
50
|
+
*/
|
|
51
|
+
export function validateScopePath(path: string): string | true {
|
|
52
|
+
const trimmed = path.trim();
|
|
53
|
+
if (trimmed.length === 0) return "Scope path cannot be empty";
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Format the config as a human-readable summary for confirmation.
|
|
59
|
+
*/
|
|
60
|
+
export function formatConfigSummary(config: AutoClaudeConfig): string {
|
|
61
|
+
return [
|
|
62
|
+
` repo: ${config.repo}`,
|
|
63
|
+
` triggerLabel: ${config.triggerLabel}`,
|
|
64
|
+
` mainBranch: ${config.mainBranch}`,
|
|
65
|
+
` scopePath: ${config.scopePath}`,
|
|
66
|
+
` model: ${config.model}`,
|
|
67
|
+
` remote: ${config.remote}`,
|
|
68
|
+
` maxIterations: ${config.maxImplementIterations}`,
|
|
69
|
+
` maxReviewRetries: ${config.maxReviewRetries}`,
|
|
70
|
+
` loopInterval: ${config.loopIntervalMinutes}min`,
|
|
71
|
+
].join("\n");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Serialize config to JSON for writing to disk.
|
|
76
|
+
*/
|
|
77
|
+
export function serializeConfig(config: AutoClaudeConfig): string {
|
|
78
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
79
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildConfig,
|
|
5
|
+
formatConfigSummary,
|
|
6
|
+
serializeConfig,
|
|
7
|
+
validateBranchName,
|
|
8
|
+
validateScopePath,
|
|
9
|
+
validateTriggerLabel,
|
|
10
|
+
} from "./config-init-helpers";
|
|
11
|
+
|
|
12
|
+
describe("validateTriggerLabel", () => {
|
|
13
|
+
it("accepts a valid label", () => {
|
|
14
|
+
expect(validateTriggerLabel("auto-claude")).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("rejects empty string", () => {
|
|
18
|
+
expect(validateTriggerLabel("")).toBe("Trigger label cannot be empty");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("rejects whitespace-only", () => {
|
|
22
|
+
expect(validateTriggerLabel(" ")).toBe("Trigger label cannot be empty");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("rejects label with spaces", () => {
|
|
26
|
+
expect(validateTriggerLabel("auto claude")).toBe("Trigger label cannot contain spaces");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("validateBranchName", () => {
|
|
31
|
+
it("accepts a valid branch name", () => {
|
|
32
|
+
expect(validateBranchName("main")).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("rejects empty string", () => {
|
|
36
|
+
expect(validateBranchName("")).toBe("Branch name cannot be empty");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("validateScopePath", () => {
|
|
41
|
+
it("accepts a valid path", () => {
|
|
42
|
+
expect(validateScopePath(".")).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("accepts a subdirectory", () => {
|
|
46
|
+
expect(validateScopePath("src/lib")).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("rejects empty string", () => {
|
|
50
|
+
expect(validateScopePath("")).toBe("Scope path cannot be empty");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("buildConfig", () => {
|
|
55
|
+
it("builds config with provided values", () => {
|
|
56
|
+
const config = buildConfig({
|
|
57
|
+
triggerLabel: "my-label",
|
|
58
|
+
mainBranch: "develop",
|
|
59
|
+
scopePath: "src/",
|
|
60
|
+
model: "sonnet",
|
|
61
|
+
repo: "owner/repo",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(config.triggerLabel).toBe("my-label");
|
|
65
|
+
expect(config.mainBranch).toBe("develop");
|
|
66
|
+
expect(config.scopePath).toBe("src/");
|
|
67
|
+
expect(config.model).toBe("sonnet");
|
|
68
|
+
expect(config.repo).toBe("owner/repo");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("applies schema defaults for fields not in input", () => {
|
|
72
|
+
const config = buildConfig({
|
|
73
|
+
triggerLabel: "auto-claude",
|
|
74
|
+
mainBranch: "main",
|
|
75
|
+
scopePath: ".",
|
|
76
|
+
model: "opus",
|
|
77
|
+
repo: "owner/repo",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(config.remote).toBe("origin");
|
|
81
|
+
expect(config.maxImplementIterations).toBe(5);
|
|
82
|
+
expect(config.maxReviewRetries).toBe(2);
|
|
83
|
+
expect(config.loopIntervalMinutes).toBe(30);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("formatConfigSummary", () => {
|
|
88
|
+
it("includes all key fields", () => {
|
|
89
|
+
const config = buildConfig({
|
|
90
|
+
triggerLabel: "auto-claude",
|
|
91
|
+
mainBranch: "main",
|
|
92
|
+
scopePath: ".",
|
|
93
|
+
model: "opus",
|
|
94
|
+
repo: "owner/repo",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const summary = formatConfigSummary(config);
|
|
98
|
+
expect(summary).toContain("owner/repo");
|
|
99
|
+
expect(summary).toContain("auto-claude");
|
|
100
|
+
expect(summary).toContain("main");
|
|
101
|
+
expect(summary).toContain("opus");
|
|
102
|
+
expect(summary).toContain("origin");
|
|
103
|
+
expect(summary).toContain("30min");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("serializeConfig", () => {
|
|
108
|
+
it("returns valid JSON with trailing newline", () => {
|
|
109
|
+
const config = buildConfig({
|
|
110
|
+
triggerLabel: "auto-claude",
|
|
111
|
+
mainBranch: "main",
|
|
112
|
+
scopePath: ".",
|
|
113
|
+
model: "opus",
|
|
114
|
+
repo: "owner/repo",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const json = serializeConfig(config);
|
|
118
|
+
expect(json.endsWith("\n")).toBe(true);
|
|
119
|
+
|
|
120
|
+
const parsed = JSON.parse(json);
|
|
121
|
+
expect(parsed.repo).toBe("owner/repo");
|
|
122
|
+
expect(parsed.triggerLabel).toBe("auto-claude");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("is pretty-printed with 2-space indent", () => {
|
|
126
|
+
const config = buildConfig({
|
|
127
|
+
triggerLabel: "test",
|
|
128
|
+
mainBranch: "main",
|
|
129
|
+
scopePath: ".",
|
|
130
|
+
model: "opus",
|
|
131
|
+
repo: "o/r",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const json = serializeConfig(config);
|
|
135
|
+
expect(json).toContain(' "repo"');
|
|
136
|
+
});
|
|
137
|
+
});
|