@os-eco/overstory-cli 0.6.1
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/LICENSE +21 -0
- package/README.md +381 -0
- package/agents/builder.md +137 -0
- package/agents/coordinator.md +263 -0
- package/agents/lead.md +301 -0
- package/agents/merger.md +160 -0
- package/agents/monitor.md +214 -0
- package/agents/reviewer.md +140 -0
- package/agents/scout.md +119 -0
- package/agents/supervisor.md +423 -0
- package/package.json +47 -0
- package/src/agents/checkpoint.test.ts +88 -0
- package/src/agents/checkpoint.ts +101 -0
- package/src/agents/hooks-deployer.test.ts +2040 -0
- package/src/agents/hooks-deployer.ts +607 -0
- package/src/agents/identity.test.ts +603 -0
- package/src/agents/identity.ts +384 -0
- package/src/agents/lifecycle.test.ts +196 -0
- package/src/agents/lifecycle.ts +183 -0
- package/src/agents/manifest.test.ts +746 -0
- package/src/agents/manifest.ts +354 -0
- package/src/agents/overlay.test.ts +676 -0
- package/src/agents/overlay.ts +308 -0
- package/src/beads/client.test.ts +217 -0
- package/src/beads/client.ts +202 -0
- package/src/beads/molecules.test.ts +338 -0
- package/src/beads/molecules.ts +198 -0
- package/src/commands/agents.test.ts +322 -0
- package/src/commands/agents.ts +287 -0
- package/src/commands/clean.test.ts +670 -0
- package/src/commands/clean.ts +618 -0
- package/src/commands/completions.test.ts +342 -0
- package/src/commands/completions.ts +887 -0
- package/src/commands/coordinator.test.ts +1530 -0
- package/src/commands/coordinator.ts +733 -0
- package/src/commands/costs.test.ts +1119 -0
- package/src/commands/costs.ts +564 -0
- package/src/commands/dashboard.test.ts +308 -0
- package/src/commands/dashboard.ts +838 -0
- package/src/commands/doctor.test.ts +294 -0
- package/src/commands/doctor.ts +213 -0
- package/src/commands/errors.test.ts +647 -0
- package/src/commands/errors.ts +248 -0
- package/src/commands/feed.test.ts +578 -0
- package/src/commands/feed.ts +361 -0
- package/src/commands/group.test.ts +262 -0
- package/src/commands/group.ts +511 -0
- package/src/commands/hooks.test.ts +458 -0
- package/src/commands/hooks.ts +253 -0
- package/src/commands/init.test.ts +347 -0
- package/src/commands/init.ts +650 -0
- package/src/commands/inspect.test.ts +670 -0
- package/src/commands/inspect.ts +431 -0
- package/src/commands/log.test.ts +1454 -0
- package/src/commands/log.ts +724 -0
- package/src/commands/logs.test.ts +379 -0
- package/src/commands/logs.ts +546 -0
- package/src/commands/mail.test.ts +1270 -0
- package/src/commands/mail.ts +771 -0
- package/src/commands/merge.test.ts +670 -0
- package/src/commands/merge.ts +355 -0
- package/src/commands/metrics.test.ts +444 -0
- package/src/commands/metrics.ts +143 -0
- package/src/commands/monitor.test.ts +191 -0
- package/src/commands/monitor.ts +390 -0
- package/src/commands/nudge.test.ts +230 -0
- package/src/commands/nudge.ts +372 -0
- package/src/commands/prime.test.ts +470 -0
- package/src/commands/prime.ts +381 -0
- package/src/commands/replay.test.ts +741 -0
- package/src/commands/replay.ts +360 -0
- package/src/commands/run.test.ts +431 -0
- package/src/commands/run.ts +351 -0
- package/src/commands/sling.test.ts +657 -0
- package/src/commands/sling.ts +661 -0
- package/src/commands/spec.test.ts +203 -0
- package/src/commands/spec.ts +168 -0
- package/src/commands/status.test.ts +430 -0
- package/src/commands/status.ts +398 -0
- package/src/commands/stop.test.ts +420 -0
- package/src/commands/stop.ts +151 -0
- package/src/commands/supervisor.test.ts +187 -0
- package/src/commands/supervisor.ts +535 -0
- package/src/commands/trace.test.ts +745 -0
- package/src/commands/trace.ts +325 -0
- package/src/commands/watch.test.ts +145 -0
- package/src/commands/watch.ts +247 -0
- package/src/commands/worktree.test.ts +786 -0
- package/src/commands/worktree.ts +311 -0
- package/src/config.test.ts +822 -0
- package/src/config.ts +829 -0
- package/src/doctor/agents.test.ts +454 -0
- package/src/doctor/agents.ts +396 -0
- package/src/doctor/config-check.test.ts +190 -0
- package/src/doctor/config-check.ts +183 -0
- package/src/doctor/consistency.test.ts +651 -0
- package/src/doctor/consistency.ts +294 -0
- package/src/doctor/databases.test.ts +290 -0
- package/src/doctor/databases.ts +218 -0
- package/src/doctor/dependencies.test.ts +184 -0
- package/src/doctor/dependencies.ts +175 -0
- package/src/doctor/logs.test.ts +251 -0
- package/src/doctor/logs.ts +295 -0
- package/src/doctor/merge-queue.test.ts +216 -0
- package/src/doctor/merge-queue.ts +144 -0
- package/src/doctor/structure.test.ts +291 -0
- package/src/doctor/structure.ts +198 -0
- package/src/doctor/types.ts +37 -0
- package/src/doctor/version.test.ts +136 -0
- package/src/doctor/version.ts +129 -0
- package/src/e2e/init-sling-lifecycle.test.ts +277 -0
- package/src/errors.ts +217 -0
- package/src/events/store.test.ts +660 -0
- package/src/events/store.ts +369 -0
- package/src/events/tool-filter.test.ts +330 -0
- package/src/events/tool-filter.ts +126 -0
- package/src/index.ts +316 -0
- package/src/insights/analyzer.test.ts +466 -0
- package/src/insights/analyzer.ts +203 -0
- package/src/logging/color.test.ts +142 -0
- package/src/logging/color.ts +71 -0
- package/src/logging/logger.test.ts +813 -0
- package/src/logging/logger.ts +266 -0
- package/src/logging/reporter.test.ts +259 -0
- package/src/logging/reporter.ts +109 -0
- package/src/logging/sanitizer.test.ts +190 -0
- package/src/logging/sanitizer.ts +57 -0
- package/src/mail/broadcast.test.ts +203 -0
- package/src/mail/broadcast.ts +92 -0
- package/src/mail/client.test.ts +773 -0
- package/src/mail/client.ts +223 -0
- package/src/mail/store.test.ts +705 -0
- package/src/mail/store.ts +387 -0
- package/src/merge/queue.test.ts +359 -0
- package/src/merge/queue.ts +231 -0
- package/src/merge/resolver.test.ts +1345 -0
- package/src/merge/resolver.ts +645 -0
- package/src/metrics/store.test.ts +667 -0
- package/src/metrics/store.ts +445 -0
- package/src/metrics/summary.test.ts +398 -0
- package/src/metrics/summary.ts +178 -0
- package/src/metrics/transcript.test.ts +356 -0
- package/src/metrics/transcript.ts +175 -0
- package/src/mulch/client.test.ts +671 -0
- package/src/mulch/client.ts +332 -0
- package/src/sessions/compat.test.ts +280 -0
- package/src/sessions/compat.ts +104 -0
- package/src/sessions/store.test.ts +873 -0
- package/src/sessions/store.ts +494 -0
- package/src/test-helpers.test.ts +124 -0
- package/src/test-helpers.ts +126 -0
- package/src/tracker/beads.ts +56 -0
- package/src/tracker/factory.test.ts +80 -0
- package/src/tracker/factory.ts +64 -0
- package/src/tracker/seeds.ts +182 -0
- package/src/tracker/types.ts +52 -0
- package/src/types.ts +724 -0
- package/src/watchdog/daemon.test.ts +1975 -0
- package/src/watchdog/daemon.ts +671 -0
- package/src/watchdog/health.test.ts +431 -0
- package/src/watchdog/health.ts +264 -0
- package/src/watchdog/triage.test.ts +164 -0
- package/src/watchdog/triage.ts +179 -0
- package/src/worktree/manager.test.ts +439 -0
- package/src/worktree/manager.ts +198 -0
- package/src/worktree/tmux.test.ts +1009 -0
- package/src/worktree/tmux.ts +509 -0
- package/templates/CLAUDE.md.tmpl +89 -0
- package/templates/hooks.json.tmpl +105 -0
- package/templates/overlay.md.tmpl +81 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: overstory worktree list | clean [--completed] [--all]
|
|
3
|
+
*
|
|
4
|
+
* List shows worktrees with agent status.
|
|
5
|
+
* Clean removes worktree dirs, branch refs (if merged), and tmux sessions.
|
|
6
|
+
* Logs are never auto-deleted.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { loadConfig } from "../config.ts";
|
|
11
|
+
import { ValidationError } from "../errors.ts";
|
|
12
|
+
import { createMailStore } from "../mail/store.ts";
|
|
13
|
+
import { openSessionStore } from "../sessions/compat.ts";
|
|
14
|
+
import type { AgentSession } from "../types.ts";
|
|
15
|
+
import { isBranchMerged, listWorktrees, removeWorktree } from "../worktree/manager.ts";
|
|
16
|
+
import { isSessionAlive, killSession } from "../worktree/tmux.ts";
|
|
17
|
+
|
|
18
|
+
function hasFlag(args: string[], flag: string): boolean {
|
|
19
|
+
return args.includes(flag);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Handle `overstory worktree list`.
|
|
24
|
+
*/
|
|
25
|
+
async function handleList(root: string, json: boolean): Promise<void> {
|
|
26
|
+
const worktrees = await listWorktrees(root);
|
|
27
|
+
const overstoryDir = join(root, ".overstory");
|
|
28
|
+
const { store } = openSessionStore(overstoryDir);
|
|
29
|
+
let sessions: AgentSession[];
|
|
30
|
+
try {
|
|
31
|
+
sessions = store.getAll();
|
|
32
|
+
} finally {
|
|
33
|
+
store.close();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const overstoryWts = worktrees.filter((wt) => wt.branch.startsWith("overstory/"));
|
|
37
|
+
|
|
38
|
+
if (json) {
|
|
39
|
+
const entries = overstoryWts.map((wt) => {
|
|
40
|
+
const session = sessions.find((s) => s.worktreePath === wt.path);
|
|
41
|
+
return {
|
|
42
|
+
path: wt.path,
|
|
43
|
+
branch: wt.branch,
|
|
44
|
+
head: wt.head,
|
|
45
|
+
agentName: session?.agentName ?? null,
|
|
46
|
+
state: session?.state ?? null,
|
|
47
|
+
beadId: session?.beadId ?? null,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
process.stdout.write(`${JSON.stringify(entries, null, "\t")}\n`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (overstoryWts.length === 0) {
|
|
55
|
+
process.stdout.write("No agent worktrees found.\n");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
process.stdout.write(`🌳 Agent worktrees: ${overstoryWts.length}\n\n`);
|
|
60
|
+
for (const wt of overstoryWts) {
|
|
61
|
+
const session = sessions.find((s) => s.worktreePath === wt.path);
|
|
62
|
+
const state = session?.state ?? "unknown";
|
|
63
|
+
const agent = session?.agentName ?? "?";
|
|
64
|
+
const bead = session?.beadId ?? "?";
|
|
65
|
+
process.stdout.write(` ${wt.branch}\n`);
|
|
66
|
+
process.stdout.write(` Agent: ${agent} | State: ${state} | Task: ${bead}\n`);
|
|
67
|
+
process.stdout.write(` Path: ${wt.path}\n\n`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Handle `overstory worktree clean [--completed] [--all] [--force]`.
|
|
73
|
+
*/
|
|
74
|
+
async function handleClean(
|
|
75
|
+
args: string[],
|
|
76
|
+
root: string,
|
|
77
|
+
json: boolean,
|
|
78
|
+
canonicalBranch: string,
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
const all = hasFlag(args, "--all");
|
|
81
|
+
const force = hasFlag(args, "--force");
|
|
82
|
+
const completedOnly = hasFlag(args, "--completed") || !all;
|
|
83
|
+
|
|
84
|
+
const worktrees = await listWorktrees(root);
|
|
85
|
+
const overstoryDir = join(root, ".overstory");
|
|
86
|
+
const { store } = openSessionStore(overstoryDir);
|
|
87
|
+
|
|
88
|
+
let sessions: AgentSession[];
|
|
89
|
+
try {
|
|
90
|
+
sessions = store.getAll();
|
|
91
|
+
} catch {
|
|
92
|
+
store.close();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const overstoryWts = worktrees.filter((wt) => wt.branch.startsWith("overstory/"));
|
|
97
|
+
const cleaned: string[] = [];
|
|
98
|
+
const failed: string[] = [];
|
|
99
|
+
const skipped: string[] = [];
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
for (const wt of overstoryWts) {
|
|
103
|
+
const session = sessions.find((s) => s.worktreePath === wt.path);
|
|
104
|
+
|
|
105
|
+
// If --completed (default), only clean worktrees whose agent is done/zombie
|
|
106
|
+
if (completedOnly && session && session.state !== "completed" && session.state !== "zombie") {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check if the branch has been merged into the canonical branch (unless --force)
|
|
111
|
+
if (!force && wt.branch.length > 0) {
|
|
112
|
+
let merged = false;
|
|
113
|
+
try {
|
|
114
|
+
merged = await isBranchMerged(root, wt.branch, canonicalBranch);
|
|
115
|
+
} catch {
|
|
116
|
+
// If we can't determine merge status, treat as unmerged (safe default)
|
|
117
|
+
merged = false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!merged) {
|
|
121
|
+
skipped.push(wt.branch);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// If --all, clean everything
|
|
127
|
+
// Kill tmux session if still alive
|
|
128
|
+
if (session?.tmuxSession) {
|
|
129
|
+
const alive = await isSessionAlive(session.tmuxSession);
|
|
130
|
+
if (alive) {
|
|
131
|
+
try {
|
|
132
|
+
await killSession(session.tmuxSession);
|
|
133
|
+
} catch {
|
|
134
|
+
// Best effort
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Warn about force-deleting unmerged branch
|
|
140
|
+
if (force && wt.branch.length > 0) {
|
|
141
|
+
let merged = false;
|
|
142
|
+
try {
|
|
143
|
+
merged = await isBranchMerged(root, wt.branch, canonicalBranch);
|
|
144
|
+
} catch {
|
|
145
|
+
merged = false;
|
|
146
|
+
}
|
|
147
|
+
if (!merged && !json) {
|
|
148
|
+
process.stdout.write(`⚠️ Force-deleting unmerged branch: ${wt.branch}\n`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Remove worktree and its branch.
|
|
153
|
+
// Always force worktree removal since deployed .claude/ files create untracked
|
|
154
|
+
// files that cause non-forced removal to fail.
|
|
155
|
+
// Always force-delete the branch since we're cleaning up finished/zombie agents
|
|
156
|
+
// whose branches are typically unmerged.
|
|
157
|
+
try {
|
|
158
|
+
await removeWorktree(root, wt.path, { force: true, forceBranch: true });
|
|
159
|
+
cleaned.push(wt.branch);
|
|
160
|
+
|
|
161
|
+
if (!json) {
|
|
162
|
+
process.stdout.write(`🗑️ Removed: ${wt.branch}\n`);
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
failed.push(wt.branch);
|
|
166
|
+
if (!json) {
|
|
167
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
168
|
+
process.stderr.write(`⚠️ Failed to remove ${wt.branch}: ${msg}\n`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Purge mail for cleaned agents
|
|
174
|
+
let mailPurged = 0;
|
|
175
|
+
if (cleaned.length > 0) {
|
|
176
|
+
const mailDbPath = join(root, ".overstory", "mail.db");
|
|
177
|
+
const mailDbFile = Bun.file(mailDbPath);
|
|
178
|
+
if (await mailDbFile.exists()) {
|
|
179
|
+
const mailStore = createMailStore(mailDbPath);
|
|
180
|
+
try {
|
|
181
|
+
for (const branch of cleaned) {
|
|
182
|
+
const session = sessions.find((s) => s.branchName === branch);
|
|
183
|
+
if (session) {
|
|
184
|
+
mailPurged += mailStore.purge({ agent: session.agentName });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} finally {
|
|
188
|
+
mailStore.close();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Mark cleaned sessions as zombie in the SessionStore
|
|
194
|
+
for (const branch of cleaned) {
|
|
195
|
+
const session = sessions.find((s) => s.branchName === branch);
|
|
196
|
+
if (session) {
|
|
197
|
+
store.updateState(session.agentName, "zombie");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Prune zombie entries whose worktree paths no longer exist on disk.
|
|
202
|
+
// This prevents the session store from growing unbounded with stale entries.
|
|
203
|
+
const remainingWorktrees = await listWorktrees(root);
|
|
204
|
+
const worktreePaths = new Set(remainingWorktrees.map((wt) => wt.path));
|
|
205
|
+
let pruneCount = 0;
|
|
206
|
+
|
|
207
|
+
// Re-read sessions after state updates to get current zombie list
|
|
208
|
+
const currentSessions = store.getAll();
|
|
209
|
+
for (const session of currentSessions) {
|
|
210
|
+
if (session.state === "zombie" && !worktreePaths.has(session.worktreePath)) {
|
|
211
|
+
store.remove(session.agentName);
|
|
212
|
+
pruneCount++;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (json) {
|
|
217
|
+
process.stdout.write(
|
|
218
|
+
`${JSON.stringify({ cleaned, failed, skipped, pruned: pruneCount, mailPurged })}\n`,
|
|
219
|
+
);
|
|
220
|
+
} else if (
|
|
221
|
+
cleaned.length === 0 &&
|
|
222
|
+
pruneCount === 0 &&
|
|
223
|
+
failed.length === 0 &&
|
|
224
|
+
skipped.length === 0
|
|
225
|
+
) {
|
|
226
|
+
process.stdout.write("No worktrees to clean.\n");
|
|
227
|
+
} else {
|
|
228
|
+
if (cleaned.length > 0) {
|
|
229
|
+
process.stdout.write(
|
|
230
|
+
`\nCleaned ${cleaned.length} worktree${cleaned.length === 1 ? "" : "s"}.\n`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (failed.length > 0) {
|
|
234
|
+
process.stdout.write(
|
|
235
|
+
`Failed to clean ${failed.length} worktree${failed.length === 1 ? "" : "s"}.\n`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if (mailPurged > 0) {
|
|
239
|
+
process.stdout.write(
|
|
240
|
+
`Purged ${mailPurged} mail message${mailPurged === 1 ? "" : "s"} from cleaned agents.\n`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (pruneCount > 0) {
|
|
244
|
+
process.stdout.write(
|
|
245
|
+
`Pruned ${pruneCount} zombie session${pruneCount === 1 ? "" : "s"} from store.\n`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (skipped.length > 0) {
|
|
249
|
+
process.stdout.write(
|
|
250
|
+
`\n⚠️ Skipped ${skipped.length} worktree${skipped.length === 1 ? "" : "s"} with unmerged branches:\n`,
|
|
251
|
+
);
|
|
252
|
+
for (const branch of skipped) {
|
|
253
|
+
process.stdout.write(` ${branch}\n`);
|
|
254
|
+
}
|
|
255
|
+
process.stdout.write("Use --force to delete unmerged branches.\n");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} finally {
|
|
259
|
+
store.close();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Entry point for `overstory worktree <subcommand> [flags]`.
|
|
265
|
+
*
|
|
266
|
+
* Subcommands: list, clean.
|
|
267
|
+
*/
|
|
268
|
+
const WORKTREE_HELP = `overstory worktree — Manage agent worktrees
|
|
269
|
+
|
|
270
|
+
Usage: overstory worktree <subcommand> [flags]
|
|
271
|
+
|
|
272
|
+
Subcommands:
|
|
273
|
+
list List worktrees with agent status
|
|
274
|
+
clean Remove completed worktrees
|
|
275
|
+
[--completed] Only finished agents (default)
|
|
276
|
+
[--all] Force remove all
|
|
277
|
+
[--force] Delete even if branches are unmerged
|
|
278
|
+
|
|
279
|
+
Options:
|
|
280
|
+
--json Output as JSON
|
|
281
|
+
--help, -h Show this help`;
|
|
282
|
+
|
|
283
|
+
export async function worktreeCommand(args: string[]): Promise<void> {
|
|
284
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
285
|
+
process.stdout.write(`${WORKTREE_HELP}\n`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const subcommand = args[0];
|
|
290
|
+
const subArgs = args.slice(1);
|
|
291
|
+
const jsonFlag = hasFlag(args, "--json");
|
|
292
|
+
|
|
293
|
+
const cwd = process.cwd();
|
|
294
|
+
const config = await loadConfig(cwd);
|
|
295
|
+
const root = config.project.root;
|
|
296
|
+
const canonicalBranch = config.project.canonicalBranch;
|
|
297
|
+
|
|
298
|
+
switch (subcommand) {
|
|
299
|
+
case "list":
|
|
300
|
+
await handleList(root, jsonFlag);
|
|
301
|
+
break;
|
|
302
|
+
case "clean":
|
|
303
|
+
await handleClean(subArgs, root, jsonFlag, canonicalBranch);
|
|
304
|
+
break;
|
|
305
|
+
default:
|
|
306
|
+
throw new ValidationError(
|
|
307
|
+
`Unknown worktree subcommand: ${subcommand ?? "(none)"}. Use: list, clean`,
|
|
308
|
+
{ field: "subcommand" },
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|