@towles/tool 0.0.108 → 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/{plugins/tt-core → packages/core}/.claude-plugin/plugin.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 +3 -2
- 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}/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,161 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { loadHistory, saveHistory, diffRuns } from "./history.js";
|
|
6
|
+
import type { DoctorRunResult } from "./checks.js";
|
|
7
|
+
|
|
8
|
+
function makeResult(overrides: Partial<DoctorRunResult> = {}): DoctorRunResult {
|
|
9
|
+
return {
|
|
10
|
+
timestamp: new Date().toISOString(),
|
|
11
|
+
tools: [
|
|
12
|
+
{ name: "git", version: "2.40.0", ok: true },
|
|
13
|
+
{ name: "node", version: "20.11.0", ok: true },
|
|
14
|
+
{ name: "bun", version: "1.1.0", ok: true },
|
|
15
|
+
],
|
|
16
|
+
ghAuth: true,
|
|
17
|
+
plugins: [{ name: "code-simplifier", ok: true }],
|
|
18
|
+
agentboard: [{ name: "database", ok: true }],
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("loadHistory / saveHistory", () => {
|
|
24
|
+
let tmpDir: string;
|
|
25
|
+
let historyPath: string;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
tmpDir = resolve(tmpdir(), `doctor-test-${Date.now()}`);
|
|
29
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
30
|
+
historyPath = resolve(tmpDir, "doctor-history.json");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("returns empty array when no file exists", () => {
|
|
38
|
+
expect(loadHistory(historyPath)).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("saves and loads a run", () => {
|
|
42
|
+
const result = makeResult();
|
|
43
|
+
saveHistory(result, historyPath);
|
|
44
|
+
|
|
45
|
+
const history = loadHistory(historyPath);
|
|
46
|
+
expect(history).toHaveLength(1);
|
|
47
|
+
expect(history[0].timestamp).toBe(result.timestamp);
|
|
48
|
+
expect(history[0].tools).toEqual(result.tools);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("appends multiple runs", () => {
|
|
52
|
+
saveHistory(makeResult({ timestamp: "2024-01-01T00:00:00Z" }), historyPath);
|
|
53
|
+
saveHistory(makeResult({ timestamp: "2024-01-02T00:00:00Z" }), historyPath);
|
|
54
|
+
|
|
55
|
+
const history = loadHistory(historyPath);
|
|
56
|
+
expect(history).toHaveLength(2);
|
|
57
|
+
expect(history[0].timestamp).toBe("2024-01-01T00:00:00Z");
|
|
58
|
+
expect(history[1].timestamp).toBe("2024-01-02T00:00:00Z");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("caps history at 50 entries", () => {
|
|
62
|
+
for (let i = 0; i < 55; i++) {
|
|
63
|
+
saveHistory(
|
|
64
|
+
makeResult({ timestamp: `2024-01-${String(i + 1).padStart(2, "0")}T00:00:00Z` }),
|
|
65
|
+
historyPath,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const history = loadHistory(historyPath);
|
|
70
|
+
expect(history).toHaveLength(50);
|
|
71
|
+
expect(history[0].timestamp).toBe("2024-01-06T00:00:00Z");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("handles corrupted JSON gracefully", () => {
|
|
75
|
+
writeFileSync(historyPath, "not json", "utf-8");
|
|
76
|
+
expect(loadHistory(historyPath)).toEqual([]);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("diffRuns", () => {
|
|
81
|
+
it("detects version upgrades", () => {
|
|
82
|
+
const prev = makeResult({ tools: [{ name: "bun", version: "1.0.0", ok: true }] });
|
|
83
|
+
const curr = makeResult({ tools: [{ name: "bun", version: "1.1.0", ok: true }] });
|
|
84
|
+
|
|
85
|
+
const diffs = diffRuns(prev, curr);
|
|
86
|
+
const bunDiff = diffs.find((d) => d.name === "bun" && d.change === "upgraded");
|
|
87
|
+
expect(bunDiff).toBeDefined();
|
|
88
|
+
expect(bunDiff!.oldValue).toBe("1.0.0");
|
|
89
|
+
expect(bunDiff!.newValue).toBe("1.1.0");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("detects version downgrades", () => {
|
|
93
|
+
const prev = makeResult({ tools: [{ name: "node", version: "22.0.0", ok: true }] });
|
|
94
|
+
const curr = makeResult({ tools: [{ name: "node", version: "20.11.0", ok: true }] });
|
|
95
|
+
|
|
96
|
+
const diffs = diffRuns(prev, curr);
|
|
97
|
+
const nodeDiff = diffs.find((d) => d.name === "node" && d.change === "downgraded");
|
|
98
|
+
expect(nodeDiff).toBeDefined();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("detects new tools", () => {
|
|
102
|
+
const prev = makeResult({ tools: [] });
|
|
103
|
+
const curr = makeResult({ tools: [{ name: "git", version: "2.40.0", ok: true }] });
|
|
104
|
+
|
|
105
|
+
const diffs = diffRuns(prev, curr);
|
|
106
|
+
expect(diffs).toContainEqual(
|
|
107
|
+
expect.objectContaining({ name: "git", change: "added", newValue: "2.40.0" }),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("detects removed tools", () => {
|
|
112
|
+
const prev = makeResult({ tools: [{ name: "ttyd", version: "1.7.0", ok: true }] });
|
|
113
|
+
const curr = makeResult({ tools: [] });
|
|
114
|
+
|
|
115
|
+
const diffs = diffRuns(prev, curr);
|
|
116
|
+
expect(diffs).toContainEqual(
|
|
117
|
+
expect.objectContaining({ name: "ttyd", change: "removed", oldValue: "1.7.0" }),
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("detects check status flips", () => {
|
|
122
|
+
const prev = makeResult({ tools: [{ name: "bun", version: "1.1.0", ok: true }] });
|
|
123
|
+
const curr = makeResult({ tools: [{ name: "bun", version: "1.1.0", ok: false }] });
|
|
124
|
+
|
|
125
|
+
const diffs = diffRuns(prev, curr);
|
|
126
|
+
expect(diffs).toContainEqual(expect.objectContaining({ name: "bun", change: "failed" }));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("detects gh auth changes", () => {
|
|
130
|
+
const prev = makeResult({ ghAuth: true });
|
|
131
|
+
const curr = makeResult({ ghAuth: false });
|
|
132
|
+
|
|
133
|
+
const diffs = diffRuns(prev, curr);
|
|
134
|
+
expect(diffs).toContainEqual(expect.objectContaining({ name: "gh auth", change: "failed" }));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("detects plugin status changes", () => {
|
|
138
|
+
const prev = makeResult({ plugins: [{ name: "code-simplifier", ok: false }] });
|
|
139
|
+
const curr = makeResult({ plugins: [{ name: "code-simplifier", ok: true }] });
|
|
140
|
+
|
|
141
|
+
const diffs = diffRuns(prev, curr);
|
|
142
|
+
expect(diffs).toContainEqual(
|
|
143
|
+
expect.objectContaining({ category: "plugin", name: "code-simplifier", change: "passed" }),
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("detects agentboard status changes", () => {
|
|
148
|
+
const prev = makeResult({ agentboard: [{ name: "database", ok: false }] });
|
|
149
|
+
const curr = makeResult({ agentboard: [{ name: "database", ok: true }] });
|
|
150
|
+
|
|
151
|
+
const diffs = diffRuns(prev, curr);
|
|
152
|
+
expect(diffs).toContainEqual(
|
|
153
|
+
expect.objectContaining({ category: "agentboard", name: "database", change: "passed" }),
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("returns empty array when nothing changed", () => {
|
|
158
|
+
const run = makeResult();
|
|
159
|
+
expect(diffRuns(run, run)).toEqual([]);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import { readFile, writeFile, fileExists } from "@towles/shared";
|
|
4
|
+
import type { DoctorRunResult } from "./checks.js";
|
|
5
|
+
|
|
6
|
+
const MAX_HISTORY = 50;
|
|
7
|
+
|
|
8
|
+
export interface DiffEntry {
|
|
9
|
+
category: string;
|
|
10
|
+
name: string;
|
|
11
|
+
change: "added" | "removed" | "upgraded" | "downgraded" | "passed" | "failed" | "unchanged";
|
|
12
|
+
oldValue?: string | null;
|
|
13
|
+
newValue?: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getHistoryPath(): string {
|
|
17
|
+
const configDir = process.env.XDG_CONFIG_HOME ?? resolve(process.env.HOME ?? "~", ".config");
|
|
18
|
+
return resolve(configDir, "tt", "doctor-history.json");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function loadHistory(historyPath?: string): DoctorRunResult[] {
|
|
22
|
+
const path = historyPath ?? getHistoryPath();
|
|
23
|
+
if (!fileExists(path)) return [];
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(readFile(path));
|
|
26
|
+
} catch (err) {
|
|
27
|
+
consola.debug(`Failed to parse doctor history at ${path}:`, err);
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function saveHistory(result: DoctorRunResult, historyPath?: string): void {
|
|
33
|
+
const path = historyPath ?? getHistoryPath();
|
|
34
|
+
const history = loadHistory(path);
|
|
35
|
+
history.push(result);
|
|
36
|
+
const trimmed = history.slice(-MAX_HISTORY);
|
|
37
|
+
writeFile(path, JSON.stringify(trimmed, null, 2));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function diffRuns(previous: DoctorRunResult, current: DoctorRunResult): DiffEntry[] {
|
|
41
|
+
const entries: DiffEntry[] = [];
|
|
42
|
+
|
|
43
|
+
const prevToolMap = new Map(previous.tools.map((t) => [t.name, t]));
|
|
44
|
+
const currToolMap = new Map(current.tools.map((t) => [t.name, t]));
|
|
45
|
+
|
|
46
|
+
for (const [name, curr] of currToolMap) {
|
|
47
|
+
const prev = prevToolMap.get(name);
|
|
48
|
+
if (!prev) {
|
|
49
|
+
entries.push({ category: "tool", name, change: "added", newValue: curr.version });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (prev.version !== curr.version && prev.version && curr.version) {
|
|
53
|
+
entries.push({
|
|
54
|
+
category: "tool",
|
|
55
|
+
name,
|
|
56
|
+
change: compareVersions(prev.version, curr.version) > 0 ? "downgraded" : "upgraded",
|
|
57
|
+
oldValue: prev.version,
|
|
58
|
+
newValue: curr.version,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (prev.ok !== curr.ok) {
|
|
62
|
+
entries.push({
|
|
63
|
+
category: "tool",
|
|
64
|
+
name,
|
|
65
|
+
change: curr.ok ? "passed" : "failed",
|
|
66
|
+
oldValue: prev.ok ? "pass" : "fail",
|
|
67
|
+
newValue: curr.ok ? "pass" : "fail",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const [name, prev] of prevToolMap) {
|
|
73
|
+
if (!currToolMap.has(name)) {
|
|
74
|
+
entries.push({ category: "tool", name, change: "removed", oldValue: prev.version });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (previous.ghAuth !== current.ghAuth) {
|
|
79
|
+
entries.push({
|
|
80
|
+
category: "auth",
|
|
81
|
+
name: "gh auth",
|
|
82
|
+
change: current.ghAuth ? "passed" : "failed",
|
|
83
|
+
oldValue: previous.ghAuth ? "pass" : "fail",
|
|
84
|
+
newValue: current.ghAuth ? "pass" : "fail",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const prevPluginMap = new Map(previous.plugins.map((p) => [p.name, p]));
|
|
89
|
+
for (const curr of current.plugins) {
|
|
90
|
+
const prev = prevPluginMap.get(curr.name);
|
|
91
|
+
if (!prev) {
|
|
92
|
+
entries.push({ category: "plugin", name: curr.name, change: "added" });
|
|
93
|
+
} else if (prev.ok !== curr.ok) {
|
|
94
|
+
entries.push({
|
|
95
|
+
category: "plugin",
|
|
96
|
+
name: curr.name,
|
|
97
|
+
change: curr.ok ? "passed" : "failed",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const prevAbMap = new Map(previous.agentboard.map((a) => [a.name, a]));
|
|
103
|
+
for (const curr of current.agentboard) {
|
|
104
|
+
const prev = prevAbMap.get(curr.name);
|
|
105
|
+
if (!prev) {
|
|
106
|
+
entries.push({ category: "agentboard", name: curr.name, change: "added" });
|
|
107
|
+
} else if (prev.ok !== curr.ok) {
|
|
108
|
+
entries.push({
|
|
109
|
+
category: "agentboard",
|
|
110
|
+
name: curr.name,
|
|
111
|
+
change: curr.ok ? "passed" : "failed",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return entries;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function compareVersions(a: string, b: string): number {
|
|
120
|
+
const pa = a.split(".").map(Number);
|
|
121
|
+
const pb = b.split(".").map(Number);
|
|
122
|
+
const len = Math.max(pa.length, pb.length);
|
|
123
|
+
for (let i = 0; i < len; i++) {
|
|
124
|
+
const na = pa[i] ?? 0;
|
|
125
|
+
const nb = pb[i] ?? 0;
|
|
126
|
+
if (na > nb) return 1;
|
|
127
|
+
if (na < nb) return -1;
|
|
128
|
+
}
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
package/src/commands/doctor.ts
CHANGED
|
@@ -1,159 +1,66 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { resolve, join } from "node:path";
|
|
3
1
|
import { defineCommand } from "citty";
|
|
4
2
|
import consola from "consola";
|
|
5
|
-
import { x } from "tinyexec";
|
|
6
3
|
import { colors } from "consola/utils";
|
|
7
4
|
import { debugArg } from "./shared.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
ok: true,
|
|
30
|
-
};
|
|
31
|
-
} catch {
|
|
32
|
-
consola.debug(`Tool check failed for "${name}"`);
|
|
33
|
-
return {
|
|
34
|
-
name,
|
|
35
|
-
version: null,
|
|
36
|
-
ok: optional,
|
|
37
|
-
warning: optional ? "optional, not installed" : undefined,
|
|
38
|
-
};
|
|
5
|
+
import { runAllChecks, checkAgentBoard, checkClaudePlugins } from "./doctor/checks.js";
|
|
6
|
+
import { formatDoctorJson } from "./doctor/format.js";
|
|
7
|
+
import { loadHistory, saveHistory, diffRuns } from "./doctor/history.js";
|
|
8
|
+
import type { DiffEntry } from "./doctor/history.js";
|
|
9
|
+
|
|
10
|
+
function formatDiffEntry(entry: DiffEntry): string {
|
|
11
|
+
switch (entry.change) {
|
|
12
|
+
case "added":
|
|
13
|
+
return `${colors.green("+")} ${entry.category}/${entry.name}: added${entry.newValue ? ` (${entry.newValue})` : ""}`;
|
|
14
|
+
case "removed":
|
|
15
|
+
return `${colors.red("-")} ${entry.category}/${entry.name}: removed${entry.oldValue ? ` (was ${entry.oldValue})` : ""}`;
|
|
16
|
+
case "upgraded":
|
|
17
|
+
return `${colors.green("↑")} ${entry.category}/${entry.name}: ${entry.oldValue} → ${entry.newValue}`;
|
|
18
|
+
case "downgraded":
|
|
19
|
+
return `${colors.yellow("↓")} ${entry.category}/${entry.name}: ${entry.oldValue} → ${entry.newValue}`;
|
|
20
|
+
case "passed":
|
|
21
|
+
return `${colors.green("✓")} ${entry.category}/${entry.name}: now passing`;
|
|
22
|
+
case "failed":
|
|
23
|
+
return `${colors.red("✗")} ${entry.category}/${entry.name}: now failing`;
|
|
24
|
+
default:
|
|
25
|
+
return ` ${entry.category}/${entry.name}: unchanged`;
|
|
39
26
|
}
|
|
40
27
|
}
|
|
41
28
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function checkAgentBoard(): {
|
|
53
|
-
name: string;
|
|
54
|
-
value: string;
|
|
55
|
-
ok: boolean;
|
|
56
|
-
warning?: string;
|
|
57
|
-
hint?: string;
|
|
58
|
-
}[] {
|
|
59
|
-
const results: { name: string; value: string; ok: boolean; warning?: string; hint?: string }[] =
|
|
60
|
-
[];
|
|
61
|
-
|
|
62
|
-
const defaultDataDir = resolve(
|
|
63
|
-
process.env.XDG_CONFIG_HOME ?? resolve(process.env.HOME ?? "~", ".config"),
|
|
64
|
-
"towles-tool",
|
|
65
|
-
"agentboard",
|
|
66
|
-
);
|
|
67
|
-
const dataDir = process.env.AGENTBOARD_DATA_DIR ?? defaultDataDir;
|
|
68
|
-
const dbPath = join(dataDir, "agentboard.db");
|
|
69
|
-
const configPath = join(dataDir, "config.json");
|
|
70
|
-
|
|
71
|
-
const dbExists = existsSync(dbPath);
|
|
72
|
-
results.push({
|
|
73
|
-
name: "database",
|
|
74
|
-
value: dbExists ? dbPath : "not found",
|
|
75
|
-
ok: dbExists,
|
|
76
|
-
hint: dbExists ? undefined : "Run: tt ag (starts server and creates DB automatically)",
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
let repoPaths: string[] = [];
|
|
80
|
-
if (existsSync(configPath)) {
|
|
81
|
-
try {
|
|
82
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
83
|
-
repoPaths = config.repoPaths ?? [];
|
|
84
|
-
} catch {
|
|
85
|
-
// Corrupted config
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
results.push({
|
|
90
|
-
name: "scan paths",
|
|
91
|
-
value: repoPaths.length > 0 ? repoPaths.join(", ") : "none configured",
|
|
92
|
-
ok: repoPaths.length > 0,
|
|
93
|
-
warning: repoPaths.length === 0 ? "no scan paths" : undefined,
|
|
94
|
-
hint:
|
|
95
|
-
repoPaths.length === 0
|
|
96
|
-
? "Run: tt ag → open Workspaces → run the onboarding wizard"
|
|
97
|
-
: undefined,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
results.push({
|
|
101
|
-
name: "data dir",
|
|
102
|
-
value: dataDir,
|
|
103
|
-
ok: true,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
return results;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function checkClaudePlugins(): Promise<
|
|
110
|
-
{ name: string; ok: boolean; installHint?: string }[]
|
|
111
|
-
> {
|
|
112
|
-
const requiredPlugins = [
|
|
113
|
-
{
|
|
114
|
-
id: "code-simplifier@claude-plugins-official",
|
|
115
|
-
name: "code-simplifier",
|
|
116
|
-
installCmd: "claude plugin install code-simplifier@claude-plugins-official --scope user",
|
|
29
|
+
export default defineCommand({
|
|
30
|
+
meta: { name: "doctor", description: "Check system dependencies and environment" },
|
|
31
|
+
args: {
|
|
32
|
+
debug: debugArg,
|
|
33
|
+
track: {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
description: "Save check results to history",
|
|
36
|
+
default: false,
|
|
117
37
|
},
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
38
|
+
diff: {
|
|
39
|
+
type: "boolean",
|
|
40
|
+
description: "Compare current run against last tracked run",
|
|
41
|
+
default: false,
|
|
42
|
+
},
|
|
43
|
+
format: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Output format: text (default) or json",
|
|
46
|
+
default: "text",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
async run({ args }) {
|
|
50
|
+
const isJson = args.format === "json";
|
|
124
51
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
installHint: installedIds.has(p.id) ? undefined : `Run: ${p.installCmd}`,
|
|
129
|
-
}));
|
|
130
|
-
} catch {
|
|
131
|
-
consola.debug("Failed to list Claude plugins");
|
|
132
|
-
return requiredPlugins.map((p) => ({
|
|
133
|
-
name: p.name,
|
|
134
|
-
ok: false,
|
|
135
|
-
installHint: `Run: ${p.installCmd}`,
|
|
136
|
-
}));
|
|
137
|
-
}
|
|
138
|
-
}
|
|
52
|
+
if (!isJson) {
|
|
53
|
+
consola.info("Checking dependencies...\n");
|
|
54
|
+
}
|
|
139
55
|
|
|
140
|
-
|
|
141
|
-
meta: { name: "doctor", description: "Check system dependencies and environment" },
|
|
142
|
-
args: { debug: debugArg },
|
|
143
|
-
async run() {
|
|
144
|
-
consola.info("Checking dependencies...\n");
|
|
56
|
+
const result = await runAllChecks();
|
|
145
57
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
checkCommand("bun", ["--version"], /([\d.]+)/),
|
|
151
|
-
checkCommand("claude", ["--version"], /([\d.]+)/),
|
|
152
|
-
checkCommand("tmux", ["-V"], /tmux ([\d.]+)/),
|
|
153
|
-
checkCommand("ttyd", ["--version"], /ttyd version ([\d.]+)/, true),
|
|
154
|
-
]);
|
|
58
|
+
if (isJson) {
|
|
59
|
+
console.log(formatDoctorJson(result));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
155
62
|
|
|
156
|
-
for (const check of
|
|
63
|
+
for (const check of result.tools) {
|
|
157
64
|
const icon = check.ok
|
|
158
65
|
? colors.green("✓")
|
|
159
66
|
: check.warning
|
|
@@ -167,14 +74,13 @@ export default defineCommand({
|
|
|
167
74
|
}
|
|
168
75
|
|
|
169
76
|
consola.log("");
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (!ghAuth.ok) {
|
|
77
|
+
const authIcon = result.ghAuth ? colors.green("✓") : colors.yellow("⚠");
|
|
78
|
+
consola.log(`${authIcon} gh auth: ${result.ghAuth ? "authenticated" : "not authenticated"}`);
|
|
79
|
+
if (!result.ghAuth) {
|
|
174
80
|
consola.log(` ${colors.dim("Run: gh auth login")}`);
|
|
175
81
|
}
|
|
176
82
|
|
|
177
|
-
const nodeCheck =
|
|
83
|
+
const nodeCheck = result.tools.find((c) => c.name === "node");
|
|
178
84
|
if (nodeCheck?.version) {
|
|
179
85
|
const major = Number.parseInt(nodeCheck.version.split(".")[0], 10);
|
|
180
86
|
if (major < 18) {
|
|
@@ -210,8 +116,8 @@ export default defineCommand({
|
|
|
210
116
|
}
|
|
211
117
|
|
|
212
118
|
const allOk =
|
|
213
|
-
|
|
214
|
-
ghAuth
|
|
119
|
+
result.tools.every((c) => c.ok || !!c.warning) &&
|
|
120
|
+
result.ghAuth &&
|
|
215
121
|
pluginChecks.every((c) => c.ok) &&
|
|
216
122
|
agentboardChecks.every((c) => c.ok || !!c.warning);
|
|
217
123
|
consola.log("");
|
|
@@ -220,5 +126,28 @@ export default defineCommand({
|
|
|
220
126
|
} else {
|
|
221
127
|
consola.log(colors.yellow("Some checks failed. See above for details."));
|
|
222
128
|
}
|
|
129
|
+
|
|
130
|
+
if (args.track) {
|
|
131
|
+
saveHistory(result);
|
|
132
|
+
consola.log(colors.dim("\nResults saved to history."));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (args.diff) {
|
|
136
|
+
const history = loadHistory();
|
|
137
|
+
if (history.length === 0) {
|
|
138
|
+
consola.log(colors.yellow("\nNo previous runs tracked. Use --track to save a run first."));
|
|
139
|
+
} else {
|
|
140
|
+
const previous = history[history.length - 1];
|
|
141
|
+
const diffs = diffRuns(previous, result);
|
|
142
|
+
consola.log(colors.bold(`\nChanges since last tracked run (${previous.timestamp}):`));
|
|
143
|
+
if (diffs.length === 0) {
|
|
144
|
+
consola.log(colors.dim(" No changes detected."));
|
|
145
|
+
} else {
|
|
146
|
+
for (const entry of diffs) {
|
|
147
|
+
consola.log(` ${formatDiffEntry(entry)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
223
152
|
},
|
|
224
153
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
2
|
import { colors } from "consola/utils";
|
|
3
3
|
import consola from "consola";
|
|
4
|
-
import {
|
|
4
|
+
import { run } from "@towles/shared";
|
|
5
5
|
|
|
6
6
|
import { debugArg } from "../shared.js";
|
|
7
7
|
|
|
@@ -34,11 +34,11 @@ export default defineCommand({
|
|
|
34
34
|
const baseBranch = args.base;
|
|
35
35
|
|
|
36
36
|
// Get current branch
|
|
37
|
-
const currentResult = await
|
|
37
|
+
const currentResult = await run("git", ["branch", "--show-current"]);
|
|
38
38
|
const currentBranch = currentResult.stdout.trim();
|
|
39
39
|
|
|
40
40
|
// Get merged branches
|
|
41
|
-
const mergedResult = await
|
|
41
|
+
const mergedResult = await run("git", ["branch", "--merged", baseBranch]);
|
|
42
42
|
const allMerged = mergedResult.stdout
|
|
43
43
|
.split("\n")
|
|
44
44
|
.map((b) => b.trim().replace(/^\* /, ""))
|
|
@@ -81,7 +81,7 @@ export default defineCommand({
|
|
|
81
81
|
|
|
82
82
|
for (const branch of toDelete) {
|
|
83
83
|
try {
|
|
84
|
-
await
|
|
84
|
+
await run("git", ["branch", "-d", branch]);
|
|
85
85
|
consola.log(colors.green(`✓ Deleted ${branch}`));
|
|
86
86
|
deleted++;
|
|
87
87
|
} catch {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import
|
|
3
|
-
import type { Issue } from "../../utils/git/gh-cli-wrapper";
|
|
2
|
+
import type { Issue } from "@towles/shared";
|
|
4
3
|
import { buildIssueChoices, computeColumnLayout } from "./branch";
|
|
5
4
|
|
|
6
5
|
const issues: Issue[] = [
|
|
@@ -92,13 +91,13 @@ describe("buildIssueChoices", () => {
|
|
|
92
91
|
|
|
93
92
|
it("includes issue title text in description", () => {
|
|
94
93
|
const choices = buildIssueChoices(issues, layout);
|
|
95
|
-
const desc =
|
|
94
|
+
const desc = Bun.stripANSI(choices[0].description!);
|
|
96
95
|
expect(desc).toContain("Short bug");
|
|
97
96
|
});
|
|
98
97
|
|
|
99
98
|
it("includes label names in description", () => {
|
|
100
99
|
const choices = buildIssueChoices(issues, layout);
|
|
101
|
-
const desc =
|
|
100
|
+
const desc = Bun.stripANSI(choices[1].description!);
|
|
102
101
|
expect(desc).toContain("enhancement");
|
|
103
102
|
expect(desc).toContain("priority");
|
|
104
103
|
});
|
|
@@ -106,7 +105,7 @@ describe("buildIssueChoices", () => {
|
|
|
106
105
|
it("handles issues with no labels", () => {
|
|
107
106
|
const choices = buildIssueChoices(issues, layout);
|
|
108
107
|
// Issue #7 has no labels — description should still contain the title
|
|
109
|
-
const desc =
|
|
108
|
+
const desc = Bun.stripANSI(choices[2].description!);
|
|
110
109
|
expect(desc).toContain("Docs update");
|
|
111
110
|
});
|
|
112
111
|
|
|
@@ -6,11 +6,16 @@ import { colors } from "consola/utils";
|
|
|
6
6
|
import { Fzf } from "fzf";
|
|
7
7
|
|
|
8
8
|
import { debugArg } from "../shared.js";
|
|
9
|
-
import type { Issue } from "
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
import type { Issue } from "@towles/shared";
|
|
10
|
+
import {
|
|
11
|
+
createBranchNameFromIssue,
|
|
12
|
+
getIssues,
|
|
13
|
+
getTerminalColumns,
|
|
14
|
+
git,
|
|
15
|
+
isGithubCliInstalled,
|
|
16
|
+
limitText,
|
|
17
|
+
printWithHexColor,
|
|
18
|
+
} from "@towles/shared";
|
|
14
19
|
|
|
15
20
|
export interface ColumnLayout {
|
|
16
21
|
longestNumber: number;
|