@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
|
@@ -3,9 +3,15 @@ import { join } from "node:path";
|
|
|
3
3
|
import consola from "consola";
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import {
|
|
7
|
+
createBranchNameFromIssue,
|
|
8
|
+
ensureDir,
|
|
9
|
+
execSafe,
|
|
10
|
+
fileExists,
|
|
11
|
+
git,
|
|
12
|
+
readFile,
|
|
13
|
+
writeFile,
|
|
14
|
+
} from "@towles/shared";
|
|
9
15
|
import { runClaude } from "./claude-cli.js";
|
|
10
16
|
import { getConfig } from "./config.js";
|
|
11
17
|
import { ARTIFACTS } from "./prompt-templates/index.js";
|
|
@@ -14,7 +20,7 @@ import { resolveTemplate } from "./templates.js";
|
|
|
14
20
|
import type { SpawnClaudeFn } from "./spawn-claude.js";
|
|
15
21
|
import type { TokenValues } from "./templates.js";
|
|
16
22
|
|
|
17
|
-
export { ensureDir, fileExists, readFile, writeFile } from "
|
|
23
|
+
export { ensureDir, fileExists, readFile, writeFile } from "@towles/shared";
|
|
18
24
|
|
|
19
25
|
// ── Issue context ──
|
|
20
26
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { writeFile } from "
|
|
4
|
+
import { writeFile } from "@towles/shared";
|
|
5
5
|
|
|
6
6
|
export interface ClaudeSettings {
|
|
7
7
|
cleanupPeriodDays?: number;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
+
import { writeFile, mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { runCommand } from "citty";
|
|
6
|
+
|
|
7
|
+
describe("config show", () => {
|
|
8
|
+
it("runs without throwing", async () => {
|
|
9
|
+
const { default: showCmd } = await import("./show.js");
|
|
10
|
+
await expect(runCommand(showCmd, { rawArgs: [] })).resolves.toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("config validate", () => {
|
|
15
|
+
let dir: string;
|
|
16
|
+
|
|
17
|
+
async function writeTempFile(content: string): Promise<string> {
|
|
18
|
+
dir = await mkdtemp(join(tmpdir(), "tt-config-test-"));
|
|
19
|
+
const filePath = join(dir, "settings.json");
|
|
20
|
+
await writeFile(filePath, content, "utf-8");
|
|
21
|
+
return filePath;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
if (dir) await rm(dir, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("reports valid config as ok", async () => {
|
|
29
|
+
const filePath = await writeTempFile(JSON.stringify({ preferredEditor: "vim" }));
|
|
30
|
+
const { default: validateCmd } = await import("./validate.js");
|
|
31
|
+
|
|
32
|
+
process.exitCode = 0;
|
|
33
|
+
await runCommand(validateCmd, { rawArgs: ["--path", filePath] });
|
|
34
|
+
expect(process.exitCode).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("reports invalid JSON", async () => {
|
|
38
|
+
const filePath = await writeTempFile("not json {{{");
|
|
39
|
+
const { default: validateCmd } = await import("./validate.js");
|
|
40
|
+
|
|
41
|
+
process.exitCode = 0;
|
|
42
|
+
await runCommand(validateCmd, { rawArgs: ["--path", filePath] });
|
|
43
|
+
expect(process.exitCode).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("reports schema validation errors", async () => {
|
|
47
|
+
const filePath = await writeTempFile(JSON.stringify({ preferredEditor: 123 }));
|
|
48
|
+
const { default: validateCmd } = await import("./validate.js");
|
|
49
|
+
|
|
50
|
+
process.exitCode = 0;
|
|
51
|
+
await runCommand(validateCmd, { rawArgs: ["--path", filePath] });
|
|
52
|
+
expect(process.exitCode).toBe(1);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("reports missing file", async () => {
|
|
56
|
+
const { default: validateCmd } = await import("./validate.js");
|
|
57
|
+
|
|
58
|
+
process.exitCode = 0;
|
|
59
|
+
await runCommand(validateCmd, { rawArgs: ["--path", "/tmp/nonexistent-tt-settings.json"] });
|
|
60
|
+
expect(process.exitCode).toBe(1);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("config reset", () => {
|
|
65
|
+
let dir: string;
|
|
66
|
+
|
|
67
|
+
async function writeTempSettings(content: string): Promise<string> {
|
|
68
|
+
dir = await mkdtemp(join(tmpdir(), "tt-config-reset-"));
|
|
69
|
+
const filePath = join(dir, "settings.json");
|
|
70
|
+
await writeFile(filePath, content, "utf-8");
|
|
71
|
+
return filePath;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
afterEach(async () => {
|
|
75
|
+
if (dir) await rm(dir, { recursive: true, force: true });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("without --confirm exits with code 1 and shows diff", async () => {
|
|
79
|
+
await writeTempSettings(JSON.stringify({ preferredEditor: "nvim" }));
|
|
80
|
+
|
|
81
|
+
const { default: resetCmd } = await import("./reset.js");
|
|
82
|
+
|
|
83
|
+
process.exitCode = 0;
|
|
84
|
+
await runCommand(resetCmd, { rawArgs: [] });
|
|
85
|
+
// Without --confirm it should set exitCode=1 (unless settings already match defaults)
|
|
86
|
+
expect([0, 1]).toContain(process.exitCode);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("with --confirm resets settings to defaults", async () => {
|
|
90
|
+
const { default: resetCmd } = await import("./reset.js");
|
|
91
|
+
|
|
92
|
+
process.exitCode = 0;
|
|
93
|
+
await runCommand(resetCmd, { rawArgs: ["--confirm"] });
|
|
94
|
+
expect(process.exitCode).toBe(0);
|
|
95
|
+
|
|
96
|
+
// Verify the file was written with defaults by loading it
|
|
97
|
+
const { readFile: rf } = await import("node:fs/promises");
|
|
98
|
+
const { USER_SETTINGS_PATH: settingsPath, UserSettingsSchema } =
|
|
99
|
+
await import("../../config/settings.js");
|
|
100
|
+
const content = JSON.parse(await rf(settingsPath, "utf-8"));
|
|
101
|
+
const defaults = UserSettingsSchema.parse({});
|
|
102
|
+
expect(JSON.stringify(content)).toBe(JSON.stringify(defaults));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("config schema", () => {
|
|
107
|
+
it("outputs valid JSON schema", async () => {
|
|
108
|
+
const { default: schemaCmd } = await import("./schema.js");
|
|
109
|
+
|
|
110
|
+
const chunks: string[] = [];
|
|
111
|
+
const origWrite = process.stdout.write;
|
|
112
|
+
process.stdout.write = ((chunk: string) => {
|
|
113
|
+
chunks.push(chunk);
|
|
114
|
+
return true;
|
|
115
|
+
}) as typeof process.stdout.write;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await runCommand(schemaCmd, { rawArgs: [] });
|
|
119
|
+
} finally {
|
|
120
|
+
process.stdout.write = origWrite;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const output = chunks.join("");
|
|
124
|
+
const schema = JSON.parse(output);
|
|
125
|
+
expect(schema.type).toBe("object");
|
|
126
|
+
expect(schema.properties).toBeDefined();
|
|
127
|
+
expect(schema.properties.preferredEditor).toBeDefined();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
|
|
3
|
+
export default defineCommand({
|
|
4
|
+
meta: { name: "config", description: "Configuration management commands" },
|
|
5
|
+
subCommands: {
|
|
6
|
+
show: () => import("./show.js").then((m) => m.default),
|
|
7
|
+
validate: () => import("./validate.js").then((m) => m.default),
|
|
8
|
+
schema: () => import("./schema.js").then((m) => m.default),
|
|
9
|
+
reset: () => import("./reset.js").then((m) => m.default),
|
|
10
|
+
},
|
|
11
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import { colors } from "consola/utils";
|
|
4
|
+
import {
|
|
5
|
+
UserSettingsSchema,
|
|
6
|
+
USER_SETTINGS_PATH,
|
|
7
|
+
saveSettings,
|
|
8
|
+
loadSettings,
|
|
9
|
+
} from "../../config/settings.js";
|
|
10
|
+
|
|
11
|
+
export default defineCommand({
|
|
12
|
+
meta: { name: "reset", description: "Reset settings to defaults" },
|
|
13
|
+
args: {
|
|
14
|
+
confirm: {
|
|
15
|
+
type: "boolean",
|
|
16
|
+
description: "Confirm the reset (required to actually reset)",
|
|
17
|
+
default: false,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
async run({ args }) {
|
|
21
|
+
const defaults = UserSettingsSchema.parse({});
|
|
22
|
+
|
|
23
|
+
if (!args.confirm) {
|
|
24
|
+
consola.log(`${colors.bold("Current settings vs defaults:")}\n`);
|
|
25
|
+
|
|
26
|
+
let currentSettings: Record<string, unknown>;
|
|
27
|
+
try {
|
|
28
|
+
const { settings } = await loadSettings();
|
|
29
|
+
currentSettings = settings as unknown as Record<string, unknown>;
|
|
30
|
+
} catch {
|
|
31
|
+
currentSettings = {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const currentJson = JSON.stringify(currentSettings, null, 2);
|
|
35
|
+
const defaultJson = JSON.stringify(defaults, null, 2);
|
|
36
|
+
|
|
37
|
+
if (currentJson === defaultJson) {
|
|
38
|
+
consola.log(`${colors.green("✓")} Settings already match defaults. Nothing to reset.`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
consola.log(`${colors.red("Current:")} ${currentJson}\n`);
|
|
43
|
+
consola.log(`${colors.green("Default:")} ${defaultJson}\n`);
|
|
44
|
+
|
|
45
|
+
consola.warn("Run with --confirm to reset settings to defaults.");
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await saveSettings({ path: USER_SETTINGS_PATH, settings: defaults });
|
|
51
|
+
consola.log(`${colors.green("✓")} Settings reset to defaults: ${USER_SETTINGS_PATH}`);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
import { UserSettingsRawSchema } from "../../config/settings.js";
|
|
4
|
+
|
|
5
|
+
export default defineCommand({
|
|
6
|
+
meta: { name: "schema", description: "Export JSON Schema for the settings file" },
|
|
7
|
+
args: {
|
|
8
|
+
pretty: {
|
|
9
|
+
type: "boolean",
|
|
10
|
+
description: "Pretty-print the JSON output",
|
|
11
|
+
default: true,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
async run({ args }) {
|
|
15
|
+
const jsonSchema = z.toJSONSchema(UserSettingsRawSchema, { target: "draft-2020-12" });
|
|
16
|
+
const indent = args.pretty ? 2 : undefined;
|
|
17
|
+
process.stdout.write(JSON.stringify(jsonSchema, null, indent) + "\n");
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
2
|
import consola from "consola";
|
|
3
|
-
import { withSettings, debugArg } from "
|
|
3
|
+
import { withSettings, debugArg } from "../shared.js";
|
|
4
4
|
|
|
5
5
|
export default defineCommand({
|
|
6
|
-
meta: { name: "
|
|
6
|
+
meta: { name: "show", description: "Display current configuration settings" },
|
|
7
7
|
args: { debug: debugArg },
|
|
8
8
|
async run({ args }) {
|
|
9
9
|
const { settingsFile, settings } = await withSettings(args.debug);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { defineCommand } from "citty";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import { colors } from "consola/utils";
|
|
5
|
+
import { UserSettingsSchema, USER_SETTINGS_PATH } from "../../config/settings.js";
|
|
6
|
+
import { debugArg } from "../shared.js";
|
|
7
|
+
|
|
8
|
+
export default defineCommand({
|
|
9
|
+
meta: { name: "validate", description: "Validate settings file against the config schema" },
|
|
10
|
+
args: {
|
|
11
|
+
debug: debugArg,
|
|
12
|
+
path: {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "Path to settings file (defaults to standard location)",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
async run({ args }) {
|
|
18
|
+
const filePath = args.path ?? USER_SETTINGS_PATH;
|
|
19
|
+
|
|
20
|
+
let raw: string;
|
|
21
|
+
try {
|
|
22
|
+
raw = await readFile(filePath, "utf-8");
|
|
23
|
+
} catch {
|
|
24
|
+
consola.error(`Settings file not found: ${filePath}`);
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let parsed: unknown;
|
|
30
|
+
try {
|
|
31
|
+
parsed = JSON.parse(raw);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
consola.error(`Invalid JSON in ${filePath}: ${(e as Error).message}`);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const result = UserSettingsSchema.safeParse(parsed);
|
|
39
|
+
|
|
40
|
+
if (result.success) {
|
|
41
|
+
consola.log(`${colors.green("✓")} ${filePath} is valid`);
|
|
42
|
+
} else {
|
|
43
|
+
consola.log(`${colors.red("✗")} ${filePath} has validation errors:\n`);
|
|
44
|
+
for (const issue of result.error.issues) {
|
|
45
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
46
|
+
consola.log(` ${colors.red("•")} ${colors.bold(path)}: ${issue.message}`);
|
|
47
|
+
}
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import { run } from "@towles/shared";
|
|
5
|
+
|
|
6
|
+
export interface CheckResult {
|
|
7
|
+
name: string;
|
|
8
|
+
version: string | null;
|
|
9
|
+
ok: boolean;
|
|
10
|
+
warning?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface DoctorRunResult {
|
|
14
|
+
timestamp: string;
|
|
15
|
+
tools: CheckResult[];
|
|
16
|
+
ghAuth: boolean;
|
|
17
|
+
plugins: { name: string; ok: boolean }[];
|
|
18
|
+
agentboard: { name: string; ok: boolean }[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function checkCommand(
|
|
22
|
+
name: string,
|
|
23
|
+
args: string[],
|
|
24
|
+
versionPattern: RegExp,
|
|
25
|
+
optional = false,
|
|
26
|
+
): Promise<CheckResult> {
|
|
27
|
+
try {
|
|
28
|
+
const result = await run(name, args);
|
|
29
|
+
const output = result.stdout + result.stderr;
|
|
30
|
+
const match = output.match(versionPattern);
|
|
31
|
+
return {
|
|
32
|
+
name,
|
|
33
|
+
version: match?.[1] ?? output.trim().slice(0, 20),
|
|
34
|
+
ok: true,
|
|
35
|
+
};
|
|
36
|
+
} catch {
|
|
37
|
+
consola.debug(`Tool check failed for "${name}"`);
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
version: null,
|
|
41
|
+
ok: optional,
|
|
42
|
+
warning: optional ? "optional, not installed" : undefined,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function checkGhAuth(): Promise<{ ok: boolean }> {
|
|
48
|
+
try {
|
|
49
|
+
const result = await run("gh", ["auth", "status"]);
|
|
50
|
+
return { ok: result.exitCode === 0 };
|
|
51
|
+
} catch {
|
|
52
|
+
consola.debug("GitHub CLI auth check failed");
|
|
53
|
+
return { ok: false };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function checkAgentBoard(): {
|
|
58
|
+
name: string;
|
|
59
|
+
value: string;
|
|
60
|
+
ok: boolean;
|
|
61
|
+
warning?: string;
|
|
62
|
+
hint?: string;
|
|
63
|
+
}[] {
|
|
64
|
+
const results: { name: string; value: string; ok: boolean; warning?: string; hint?: string }[] =
|
|
65
|
+
[];
|
|
66
|
+
|
|
67
|
+
const defaultDataDir = resolve(
|
|
68
|
+
process.env.XDG_CONFIG_HOME ?? resolve(process.env.HOME ?? "~", ".config"),
|
|
69
|
+
"towles-tool",
|
|
70
|
+
"agentboard",
|
|
71
|
+
);
|
|
72
|
+
const dataDir = process.env.AGENTBOARD_DATA_DIR ?? defaultDataDir;
|
|
73
|
+
const dbPath = join(dataDir, "agentboard.db");
|
|
74
|
+
const configPath = join(dataDir, "config.json");
|
|
75
|
+
|
|
76
|
+
const dbExists = existsSync(dbPath);
|
|
77
|
+
results.push({
|
|
78
|
+
name: "database",
|
|
79
|
+
value: dbExists ? dbPath : "not found",
|
|
80
|
+
ok: dbExists,
|
|
81
|
+
hint: dbExists ? undefined : "Run: tt ag (starts server and creates DB automatically)",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
let repoPaths: string[] = [];
|
|
85
|
+
if (existsSync(configPath)) {
|
|
86
|
+
try {
|
|
87
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
88
|
+
repoPaths = config.repoPaths ?? [];
|
|
89
|
+
} catch {
|
|
90
|
+
// Corrupted config
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
results.push({
|
|
95
|
+
name: "scan paths",
|
|
96
|
+
value: repoPaths.length > 0 ? repoPaths.join(", ") : "none configured",
|
|
97
|
+
ok: repoPaths.length > 0,
|
|
98
|
+
warning: repoPaths.length === 0 ? "no scan paths" : undefined,
|
|
99
|
+
hint:
|
|
100
|
+
repoPaths.length === 0
|
|
101
|
+
? "Run: tt ag → open Workspaces → run the onboarding wizard"
|
|
102
|
+
: undefined,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
results.push({
|
|
106
|
+
name: "data dir",
|
|
107
|
+
value: dataDir,
|
|
108
|
+
ok: true,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return results;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function checkClaudePlugins(): Promise<
|
|
115
|
+
{ name: string; ok: boolean; installHint?: string }[]
|
|
116
|
+
> {
|
|
117
|
+
const requiredPlugins = [
|
|
118
|
+
{
|
|
119
|
+
id: "code-simplifier@claude-plugins-official",
|
|
120
|
+
name: "code-simplifier",
|
|
121
|
+
installCmd: "claude plugin install code-simplifier@claude-plugins-official --scope user",
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const result = await run("claude", ["plugin", "list", "--json"]);
|
|
127
|
+
const plugins: { id: string }[] = JSON.parse(result.stdout);
|
|
128
|
+
const installedIds = new Set(plugins.map((p) => p.id));
|
|
129
|
+
|
|
130
|
+
return requiredPlugins.map((p) => ({
|
|
131
|
+
name: p.name,
|
|
132
|
+
ok: installedIds.has(p.id),
|
|
133
|
+
installHint: installedIds.has(p.id) ? undefined : `Run: ${p.installCmd}`,
|
|
134
|
+
}));
|
|
135
|
+
} catch {
|
|
136
|
+
consola.debug("Failed to list Claude plugins");
|
|
137
|
+
return requiredPlugins.map((p) => ({
|
|
138
|
+
name: p.name,
|
|
139
|
+
ok: false,
|
|
140
|
+
installHint: `Run: ${p.installCmd}`,
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function runAllChecks(): Promise<DoctorRunResult> {
|
|
146
|
+
const tools = await Promise.all([
|
|
147
|
+
checkCommand("git", ["--version"], /git version ([\d.]+)/),
|
|
148
|
+
checkCommand("gh", ["--version"], /gh version ([\d.]+)/),
|
|
149
|
+
checkCommand("node", ["--version"], /v?([\d.]+)/),
|
|
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
|
+
]);
|
|
155
|
+
|
|
156
|
+
const ghAuth = await checkGhAuth();
|
|
157
|
+
const pluginChecks = await checkClaudePlugins();
|
|
158
|
+
const agentboardChecks = checkAgentBoard();
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
timestamp: new Date().toISOString(),
|
|
162
|
+
tools,
|
|
163
|
+
ghAuth: ghAuth.ok,
|
|
164
|
+
plugins: pluginChecks.map(({ name, ok }) => ({ name, ok })),
|
|
165
|
+
agentboard: agentboardChecks.map(({ name, ok }) => ({ name, ok })),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { formatDoctorJson } from "./format.js";
|
|
3
|
+
import type { DoctorRunResult } from "./checks.js";
|
|
4
|
+
|
|
5
|
+
function makeResult(overrides: Partial<DoctorRunResult> = {}): DoctorRunResult {
|
|
6
|
+
return {
|
|
7
|
+
timestamp: "2024-06-01T12:00:00.000Z",
|
|
8
|
+
tools: [
|
|
9
|
+
{ name: "git", version: "2.40.0", ok: true },
|
|
10
|
+
{ name: "node", version: "20.11.0", ok: true },
|
|
11
|
+
{ name: "bun", version: "1.1.0", ok: true },
|
|
12
|
+
],
|
|
13
|
+
ghAuth: true,
|
|
14
|
+
plugins: [{ name: "code-simplifier", ok: true }],
|
|
15
|
+
agentboard: [{ name: "database", ok: true }],
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("formatDoctorJson", () => {
|
|
21
|
+
it("returns valid JSON with all fields", () => {
|
|
22
|
+
const result = makeResult();
|
|
23
|
+
const output = formatDoctorJson(result);
|
|
24
|
+
const parsed = JSON.parse(output);
|
|
25
|
+
|
|
26
|
+
expect(parsed.timestamp).toBe("2024-06-01T12:00:00.000Z");
|
|
27
|
+
expect(parsed.tools).toHaveLength(3);
|
|
28
|
+
expect(parsed.ghAuth).toBe(true);
|
|
29
|
+
expect(parsed.plugins).toHaveLength(1);
|
|
30
|
+
expect(parsed.agentboard).toHaveLength(1);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("includes tool details", () => {
|
|
34
|
+
const result = makeResult();
|
|
35
|
+
const parsed = JSON.parse(formatDoctorJson(result));
|
|
36
|
+
const git = parsed.tools.find((t: { name: string }) => t.name === "git");
|
|
37
|
+
|
|
38
|
+
expect(git).toEqual({ name: "git", version: "2.40.0", ok: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("includes warning field when present", () => {
|
|
42
|
+
const result = makeResult({
|
|
43
|
+
tools: [{ name: "ttyd", version: null, ok: true, warning: "optional, not installed" }],
|
|
44
|
+
});
|
|
45
|
+
const parsed = JSON.parse(formatDoctorJson(result));
|
|
46
|
+
|
|
47
|
+
expect(parsed.tools[0].warning).toBe("optional, not installed");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("preserves ghAuth false", () => {
|
|
51
|
+
const result = makeResult({ ghAuth: false });
|
|
52
|
+
const parsed = JSON.parse(formatDoctorJson(result));
|
|
53
|
+
|
|
54
|
+
expect(parsed.ghAuth).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("roundtrips through JSON.parse", () => {
|
|
58
|
+
const result = makeResult();
|
|
59
|
+
const parsed = JSON.parse(formatDoctorJson(result));
|
|
60
|
+
|
|
61
|
+
expect(parsed).toEqual(result);
|
|
62
|
+
});
|
|
63
|
+
});
|