@towles/tool 0.0.95 → 0.0.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/run.ts +4 -3
- package/package.json +8 -37
- package/src/cli.ts +19 -0
- package/src/commands/agentboard.ts +327 -0
- package/src/commands/auto-claude/index.ts +74 -91
- package/src/commands/auto-claude/list.ts +33 -43
- package/src/commands/auto-claude/retry.test.ts +10 -6
- package/src/commands/auto-claude/retry.ts +26 -39
- package/src/commands/auto-claude/status.ts +10 -17
- package/src/commands/config.test.ts +4 -10
- package/src/commands/config.ts +14 -28
- package/src/commands/doctor.ts +156 -178
- package/src/commands/gh/branch-clean.ts +28 -43
- package/src/commands/gh/branch.ts +22 -37
- package/src/commands/gh/index.ts +10 -0
- package/src/commands/gh/pr.ts +82 -100
- package/src/commands/graph/index.ts +59 -70
- package/src/commands/install.ts +91 -115
- package/src/commands/journal/daily-notes.ts +16 -24
- package/src/commands/journal/index.ts +10 -0
- package/src/commands/journal/meeting.ts +16 -34
- package/src/commands/journal/note.ts +16 -34
- package/src/commands/shared.ts +21 -0
- package/src/lib/auto-claude/templates.test.ts +16 -11
- package/src/lib/graph/parser.test.ts +11 -10
- package/src/utils/git/gh-cli-wrapper.test.ts +6 -5
- package/src/commands/agentboard2.ts +0 -280
- package/src/commands/base.ts +0 -32
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { Mock } from "vitest";
|
|
2
3
|
|
|
3
4
|
import type { ReadFileFn } from "./parser";
|
|
4
5
|
import { calculateCutoffMs, filterByDays, parseJsonl, quickTokenCount } from "./parser";
|
|
5
6
|
|
|
6
|
-
const mockReadFileSync
|
|
7
|
+
const mockReadFileSync = vi.fn() as Mock & ReadFileFn;
|
|
7
8
|
|
|
8
9
|
// ── Pure functions (no mocking needed) ──
|
|
9
10
|
|
|
@@ -67,7 +68,7 @@ describe("filterByDays", () => {
|
|
|
67
68
|
|
|
68
69
|
describe("parseJsonl", () => {
|
|
69
70
|
it("parses valid JSONL lines", () => {
|
|
70
|
-
|
|
71
|
+
mockReadFileSync.mockReturnValue(
|
|
71
72
|
'{"type":"user","sessionId":"s1","timestamp":"2025-01-01T00:00:00Z"}\n{"type":"assistant","sessionId":"s1","timestamp":"2025-01-01T00:01:00Z"}\n',
|
|
72
73
|
);
|
|
73
74
|
const entries = parseJsonl("/fake/path.jsonl", mockReadFileSync);
|
|
@@ -77,7 +78,7 @@ describe("parseJsonl", () => {
|
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
it("skips empty lines", () => {
|
|
80
|
-
|
|
81
|
+
mockReadFileSync.mockReturnValue(
|
|
81
82
|
'{"type":"user","sessionId":"s1","timestamp":"t"}\n\n\n{"type":"assistant","sessionId":"s1","timestamp":"t"}\n',
|
|
82
83
|
);
|
|
83
84
|
const entries = parseJsonl("/fake/path.jsonl", mockReadFileSync);
|
|
@@ -85,7 +86,7 @@ describe("parseJsonl", () => {
|
|
|
85
86
|
});
|
|
86
87
|
|
|
87
88
|
it("skips invalid JSON lines", () => {
|
|
88
|
-
|
|
89
|
+
mockReadFileSync.mockReturnValue(
|
|
89
90
|
'{"type":"user","sessionId":"s1","timestamp":"t"}\nnot-json\n{"type":"assistant","sessionId":"s1","timestamp":"t"}\n',
|
|
90
91
|
);
|
|
91
92
|
const entries = parseJsonl("/fake/path.jsonl", mockReadFileSync);
|
|
@@ -93,7 +94,7 @@ describe("parseJsonl", () => {
|
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
it("returns empty array for empty file", () => {
|
|
96
|
-
|
|
97
|
+
mockReadFileSync.mockReturnValue("");
|
|
97
98
|
const entries = parseJsonl("/fake/path.jsonl", mockReadFileSync);
|
|
98
99
|
expect(entries).toHaveLength(0);
|
|
99
100
|
});
|
|
@@ -109,7 +110,7 @@ describe("quickTokenCount", () => {
|
|
|
109
110
|
message: { usage: { input_tokens: 200, output_tokens: 75 } },
|
|
110
111
|
}),
|
|
111
112
|
].join("\n");
|
|
112
|
-
|
|
113
|
+
mockReadFileSync.mockReturnValue(lines);
|
|
113
114
|
expect(quickTokenCount("/fake/path.jsonl", mockReadFileSync)).toBe(425);
|
|
114
115
|
});
|
|
115
116
|
|
|
@@ -118,12 +119,12 @@ describe("quickTokenCount", () => {
|
|
|
118
119
|
JSON.stringify({ message: { content: "text" } }),
|
|
119
120
|
JSON.stringify({ message: { usage: { input_tokens: 100, output_tokens: 50 } } }),
|
|
120
121
|
].join("\n");
|
|
121
|
-
|
|
122
|
+
mockReadFileSync.mockReturnValue(lines);
|
|
122
123
|
expect(quickTokenCount("/fake/path.jsonl", mockReadFileSync)).toBe(150);
|
|
123
124
|
});
|
|
124
125
|
|
|
125
126
|
it("returns 0 for unreadable files", () => {
|
|
126
|
-
|
|
127
|
+
mockReadFileSync.mockImplementation(() => {
|
|
127
128
|
throw new Error("ENOENT");
|
|
128
129
|
});
|
|
129
130
|
expect(quickTokenCount("/missing/file.jsonl", mockReadFileSync)).toBe(0);
|
|
@@ -133,12 +134,12 @@ describe("quickTokenCount", () => {
|
|
|
133
134
|
const lines = JSON.stringify({
|
|
134
135
|
message: { usage: { input_tokens: 100 } },
|
|
135
136
|
});
|
|
136
|
-
|
|
137
|
+
mockReadFileSync.mockReturnValue(lines);
|
|
137
138
|
expect(quickTokenCount("/fake/path.jsonl", mockReadFileSync)).toBe(100);
|
|
138
139
|
});
|
|
139
140
|
|
|
140
141
|
it("skips invalid JSON lines gracefully", () => {
|
|
141
|
-
|
|
142
|
+
mockReadFileSync.mockReturnValue(
|
|
142
143
|
'{"message":{"usage":{"input_tokens":50,"output_tokens":50}}}\nbadline\n',
|
|
143
144
|
);
|
|
144
145
|
expect(quickTokenCount("/fake/path.jsonl", mockReadFileSync)).toBe(100);
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { Mock } from "vitest";
|
|
2
3
|
|
|
3
4
|
import type { XFn } from "./gh-cli-wrapper";
|
|
4
5
|
import { getIssues, isGithubCliInstalled } from "./gh-cli-wrapper";
|
|
5
6
|
|
|
6
|
-
const mockX
|
|
7
|
+
const mockX = vi.fn().mockResolvedValue({ stdout: "[]", stderr: "", exitCode: 0 }) as Mock & XFn;
|
|
7
8
|
|
|
8
9
|
describe("gh-cli-wrapper", () => {
|
|
9
10
|
beforeEach(() => {
|
|
10
11
|
vi.clearAllMocks();
|
|
11
|
-
|
|
12
|
+
mockX.mockResolvedValue({ stdout: "[]", stderr: "", exitCode: 0 });
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
describe("getIssues", () => {
|
|
@@ -21,7 +22,7 @@ describe("gh-cli-wrapper", () => {
|
|
|
21
22
|
it("does not pass --label flag when label not provided", async () => {
|
|
22
23
|
await getIssues({ cwd: ".", exec: mockX });
|
|
23
24
|
|
|
24
|
-
const args =
|
|
25
|
+
const args = mockX.mock.calls[0]![1] as string[];
|
|
25
26
|
expect(args).not.toContain("--label");
|
|
26
27
|
});
|
|
27
28
|
|
|
@@ -34,7 +35,7 @@ describe("gh-cli-wrapper", () => {
|
|
|
34
35
|
|
|
35
36
|
describe("isGithubCliInstalled", () => {
|
|
36
37
|
it("returns true when gh CLI outputs expected string", async () => {
|
|
37
|
-
|
|
38
|
+
mockX.mockResolvedValue({
|
|
38
39
|
stdout: "gh version 2.0.0 (https://github.com/cli/cli)",
|
|
39
40
|
stderr: "",
|
|
40
41
|
exitCode: 0,
|
|
@@ -45,7 +46,7 @@ describe("gh-cli-wrapper", () => {
|
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
it("returns false when gh CLI is not available", async () => {
|
|
48
|
-
|
|
49
|
+
mockX.mockRejectedValue(new Error("command not found"));
|
|
49
50
|
|
|
50
51
|
const result = await isGithubCliInstalled(mockX);
|
|
51
52
|
expect(result).toBe(false);
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { Args } from "@oclif/core";
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
3
|
-
import { readFileSync, writeFileSync, existsSync, realpathSync } from "node:fs";
|
|
4
|
-
import { resolve } from "node:path";
|
|
5
|
-
import consola from "consola";
|
|
6
|
-
import { colors } from "consola/utils";
|
|
7
|
-
import { BaseCommand } from "./base.js";
|
|
8
|
-
|
|
9
|
-
const PLUGIN_DIR = resolve(import.meta.dirname, "../../plugins/tt-agentboard2");
|
|
10
|
-
|
|
11
|
-
// Keybinding defaults
|
|
12
|
-
const DEFAULT_KEY = "a";
|
|
13
|
-
const TMUX_BINDINGS = { toggle: "t", focus: "s" } as const;
|
|
14
|
-
const RUN_SHELL_LINE = `run-shell '${PLUGIN_DIR}/agentboard2.tmux'`;
|
|
15
|
-
const MARKER = "# agentboard2";
|
|
16
|
-
|
|
17
|
-
function findTmuxConf(): string | null {
|
|
18
|
-
const candidates = [
|
|
19
|
-
resolve(process.env.HOME ?? "~", ".tmux.conf"),
|
|
20
|
-
resolve(process.env.HOME ?? "~", ".config/tmux/tmux.conf"),
|
|
21
|
-
];
|
|
22
|
-
for (const path of candidates) {
|
|
23
|
-
try {
|
|
24
|
-
const real = existsSync(path) ? path : null;
|
|
25
|
-
if (real) return real;
|
|
26
|
-
} catch {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default class Agentboard2 extends BaseCommand {
|
|
34
|
-
static override aliases = ["ag2"];
|
|
35
|
-
static override description = "AgentBoard2 — opensessions-style tmux TUI sidebar";
|
|
36
|
-
|
|
37
|
-
static override examples = [
|
|
38
|
-
{
|
|
39
|
-
description: "Install agentboard2 into tmux",
|
|
40
|
-
command: "<%= config.bin %> agentboard2 setup",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
description: "Uninstall from tmux",
|
|
44
|
-
command: "<%= config.bin %> agentboard2 uninstall",
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
description: "Launch the server",
|
|
48
|
-
command: "<%= config.bin %> agentboard2 server",
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
description: "Launch the TUI directly",
|
|
52
|
-
command: "<%= config.bin %> agentboard2 tui",
|
|
53
|
-
},
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
static override args = {
|
|
57
|
-
subcommand: Args.string({
|
|
58
|
-
description: "Subcommand: setup, uninstall, server, tui, keys",
|
|
59
|
-
required: false,
|
|
60
|
-
options: ["setup", "uninstall", "server", "tui", "start", "keys"],
|
|
61
|
-
}),
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
async run(): Promise<void> {
|
|
65
|
-
const { args } = await this.parse(Agentboard2);
|
|
66
|
-
|
|
67
|
-
switch (args.subcommand) {
|
|
68
|
-
case "setup":
|
|
69
|
-
this.setup();
|
|
70
|
-
break;
|
|
71
|
-
case "uninstall":
|
|
72
|
-
this.uninstall();
|
|
73
|
-
break;
|
|
74
|
-
case "server":
|
|
75
|
-
this.startServer();
|
|
76
|
-
break;
|
|
77
|
-
case "tui":
|
|
78
|
-
this.startTui();
|
|
79
|
-
break;
|
|
80
|
-
case "start":
|
|
81
|
-
// For backwards compat, start = tui
|
|
82
|
-
this.startTui();
|
|
83
|
-
break;
|
|
84
|
-
case "keys":
|
|
85
|
-
this.showKeys();
|
|
86
|
-
break;
|
|
87
|
-
default:
|
|
88
|
-
this.showKeys();
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private ensureDeps(): void {
|
|
94
|
-
// Check bun is installed
|
|
95
|
-
try {
|
|
96
|
-
execSync("bun --version", { stdio: "pipe" });
|
|
97
|
-
} catch {
|
|
98
|
-
this.error("bun is required but not found. Install: https://bun.sh");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Install deps if needed for runtime package
|
|
102
|
-
const runtimeNodeModules = resolve(PLUGIN_DIR, "packages/runtime/node_modules");
|
|
103
|
-
if (!existsSync(runtimeNodeModules)) {
|
|
104
|
-
consola.info("Installing agentboard2 dependencies...");
|
|
105
|
-
execSync("pnpm install", { cwd: PLUGIN_DIR, stdio: "inherit" });
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private setup(): void {
|
|
110
|
-
this.ensureDeps();
|
|
111
|
-
|
|
112
|
-
// Find tmux.conf
|
|
113
|
-
const confPath = findTmuxConf();
|
|
114
|
-
if (!confPath) {
|
|
115
|
-
consola.warn("No tmux.conf found. Add this line manually:");
|
|
116
|
-
consola.info(colors.cyan(` ${RUN_SHELL_LINE}`));
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// If it's a symlink, resolve to the real file for editing
|
|
121
|
-
let editPath = confPath;
|
|
122
|
-
try {
|
|
123
|
-
editPath = realpathSync(confPath);
|
|
124
|
-
} catch {
|
|
125
|
-
// keep confPath
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Check if already installed
|
|
129
|
-
const content = readFileSync(editPath, "utf8");
|
|
130
|
-
if (content.includes("agentboard2.tmux")) {
|
|
131
|
-
consola.success("Already installed in tmux.conf");
|
|
132
|
-
this.reloadTmux();
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Add run-shell line before TPM init
|
|
137
|
-
const tpmLine = "run '~/.config/tmux/plugins/tpm/tpm'";
|
|
138
|
-
const altTpmLine = "run-shell '~/.tmux/plugins/tpm/tpm'";
|
|
139
|
-
const insertLines = `\n${MARKER}\n${RUN_SHELL_LINE}\n`;
|
|
140
|
-
|
|
141
|
-
let newContent: string;
|
|
142
|
-
if (content.includes(tpmLine)) {
|
|
143
|
-
newContent = content.replace(tpmLine, `${insertLines}\n${tpmLine}`);
|
|
144
|
-
} else if (content.includes(altTpmLine)) {
|
|
145
|
-
newContent = content.replace(altTpmLine, `${insertLines}\n${altTpmLine}`);
|
|
146
|
-
} else {
|
|
147
|
-
// No TPM found, append to end
|
|
148
|
-
newContent = content + insertLines;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
writeFileSync(editPath, newContent);
|
|
152
|
-
consola.success(`Added agentboard2 to ${editPath}`);
|
|
153
|
-
|
|
154
|
-
this.reloadTmux();
|
|
155
|
-
this.showKeys();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private uninstall(): void {
|
|
159
|
-
const confPath = findTmuxConf();
|
|
160
|
-
if (!confPath) {
|
|
161
|
-
consola.info("No tmux.conf found.");
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let editPath = confPath;
|
|
166
|
-
try {
|
|
167
|
-
editPath = realpathSync(confPath);
|
|
168
|
-
} catch {
|
|
169
|
-
// keep confPath
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const content = readFileSync(editPath, "utf8");
|
|
173
|
-
if (!content.includes("agentboard2")) {
|
|
174
|
-
consola.info("agentboard2 not found in tmux.conf");
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Remove the marker line and run-shell line
|
|
179
|
-
const newContent = content
|
|
180
|
-
.split("\n")
|
|
181
|
-
.filter((line) => !line.includes("agentboard2"))
|
|
182
|
-
.join("\n")
|
|
183
|
-
.replace(/\n{3,}/g, "\n\n");
|
|
184
|
-
|
|
185
|
-
writeFileSync(editPath, newContent);
|
|
186
|
-
consola.success("Removed agentboard2 from tmux.conf");
|
|
187
|
-
this.reloadTmux();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Foreground command — blocks until server exits (Ctrl+C to stop)
|
|
191
|
-
private startServer(): void {
|
|
192
|
-
this.ensureDeps();
|
|
193
|
-
|
|
194
|
-
const serverEntry = resolve(PLUGIN_DIR, "apps/server/src/main.ts");
|
|
195
|
-
consola.info("Starting agentboard2 server (foreground, Ctrl+C to stop)...");
|
|
196
|
-
|
|
197
|
-
execSync(`bun run ${serverEntry}`, {
|
|
198
|
-
stdio: "inherit",
|
|
199
|
-
cwd: PLUGIN_DIR,
|
|
200
|
-
env: {
|
|
201
|
-
...process.env,
|
|
202
|
-
AGENTBOARD2_DIR: PLUGIN_DIR,
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Foreground command — blocks until TUI exits
|
|
208
|
-
private startTui(): void {
|
|
209
|
-
this.ensureDeps();
|
|
210
|
-
|
|
211
|
-
const tuiEntry = resolve(PLUGIN_DIR, "apps/tui/src/index.tsx");
|
|
212
|
-
|
|
213
|
-
execSync(`bun run ${tuiEntry}`, {
|
|
214
|
-
stdio: "inherit",
|
|
215
|
-
cwd: resolve(PLUGIN_DIR, "apps/tui"),
|
|
216
|
-
env: {
|
|
217
|
-
...process.env,
|
|
218
|
-
AGENTBOARD2_DIR: PLUGIN_DIR,
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
private showKeys(): void {
|
|
224
|
-
// Get tmux prefix and agentboard2 key from tmux
|
|
225
|
-
let prefix = "C-a";
|
|
226
|
-
let key = DEFAULT_KEY;
|
|
227
|
-
try {
|
|
228
|
-
prefix = execSync("tmux show-option -gv prefix", {
|
|
229
|
-
encoding: "utf8",
|
|
230
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
231
|
-
}).trim();
|
|
232
|
-
const ab2Key = execSync(
|
|
233
|
-
`tmux show-option -gv @agentboard2-key 2>/dev/null || echo ${DEFAULT_KEY}`,
|
|
234
|
-
{
|
|
235
|
-
encoding: "utf8",
|
|
236
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
237
|
-
},
|
|
238
|
-
).trim();
|
|
239
|
-
if (ab2Key) key = ab2Key;
|
|
240
|
-
} catch {
|
|
241
|
-
// use defaults
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const { toggle, focus } = TMUX_BINDINGS;
|
|
245
|
-
consola.box(
|
|
246
|
-
[
|
|
247
|
-
`${colors.bold("AgentBoard2 Keybindings")}\n`,
|
|
248
|
-
`${colors.cyan(`tmux (prefix = ${prefix}, C = Ctrl):`)}`,
|
|
249
|
-
` ${prefix} ${key} ${toggle} toggle sidebar`,
|
|
250
|
-
` ${prefix} ${key} ${focus} focus sidebar`,
|
|
251
|
-
` ${prefix} ${key} 1-9 jump to session\n`,
|
|
252
|
-
`${colors.cyan("In sidebar:")}`,
|
|
253
|
-
` Tab cycle sessions`,
|
|
254
|
-
` j / ↓ move down`,
|
|
255
|
-
` k / ↑ move up`,
|
|
256
|
-
` Enter / l switch to selected session`,
|
|
257
|
-
` 1-9 jump to session`,
|
|
258
|
-
` d hide session`,
|
|
259
|
-
` x kill session`,
|
|
260
|
-
` t theme picker`,
|
|
261
|
-
` r refresh`,
|
|
262
|
-
` q quit`,
|
|
263
|
-
].join("\n"),
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
private reloadTmux(): void {
|
|
268
|
-
try {
|
|
269
|
-
execSync(
|
|
270
|
-
"tmux source-file ~/.config/tmux/tmux.conf 2>/dev/null || tmux source-file ~/.tmux.conf 2>/dev/null",
|
|
271
|
-
{
|
|
272
|
-
stdio: "pipe",
|
|
273
|
-
},
|
|
274
|
-
);
|
|
275
|
-
consola.success("tmux config reloaded");
|
|
276
|
-
} catch {
|
|
277
|
-
consola.info("Reload tmux manually: tmux source-file ~/.config/tmux/tmux.conf");
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
package/src/commands/base.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Command, Flags } from "@oclif/core";
|
|
2
|
-
import type { SettingsFile } from "../config/settings.js";
|
|
3
|
-
import { loadSettings } from "../config/settings.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Base command that all towles-tool commands extend.
|
|
7
|
-
* Provides shared functionality like settings loading and debug flag.
|
|
8
|
-
*/
|
|
9
|
-
export abstract class BaseCommand extends Command {
|
|
10
|
-
static baseFlags = {
|
|
11
|
-
debug: Flags.boolean({
|
|
12
|
-
char: "d",
|
|
13
|
-
description: "Enable debug output",
|
|
14
|
-
default: false,
|
|
15
|
-
}),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
protected settingsFile!: SettingsFile;
|
|
19
|
-
|
|
20
|
-
/** Shortcut to avoid `this.settingsFile.settings.X` stutter */
|
|
21
|
-
protected get userSettings() {
|
|
22
|
-
return this.settingsFile.settings;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Called before run(). Loads user settings.
|
|
27
|
-
*/
|
|
28
|
-
async init(): Promise<void> {
|
|
29
|
-
await super.init();
|
|
30
|
-
this.settingsFile = await loadSettings();
|
|
31
|
-
}
|
|
32
|
-
}
|