claude-teammate 0.1.2 → 0.1.3
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/README.md +35 -5
- package/package.json +3 -2
- package/src/claude.js +356 -0
- package/src/cli.js +32 -3
- package/src/commands/start.js +74 -139
- package/src/commands/status.js +48 -0
- package/src/commands/stop.js +45 -0
- package/src/commands/worker.js +742 -0
- package/src/config.js +140 -0
- package/src/github.js +81 -0
- package/src/jira.js +403 -0
- package/src/logger.js +54 -0
- package/src/memory.js +398 -0
- package/src/repo.js +61 -0
- package/src/runtime.js +80 -0
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ memory/
|
|
|
85
85
|
| `repos` | Target GitHub repositories |
|
|
86
86
|
| `status` | Done, in-flight, blocked |
|
|
87
87
|
| `notes` | Architectural decisions, reviewer preferences, conventions |
|
|
88
|
-
| `
|
|
88
|
+
| `guardrails` | Recurring bugs, fragile areas, and what not to do |
|
|
89
89
|
|
|
90
90
|
The bot carries context across tickets, PRs, and sessions.
|
|
91
91
|
|
|
@@ -100,7 +100,7 @@ npm install -g claude-teammate
|
|
|
100
100
|
claude-teammate start
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
`start` creates `.env` on the first run, runs a 5 second Claude CLI preflight check that expects a plain `OK`, then launches a background worker for the current project. If `.env` already exists with the required values, setup is skipped and your credentials are not prompted for again.
|
|
104
104
|
|
|
105
105
|
To upgrade to latest version
|
|
106
106
|
|
|
@@ -108,12 +108,42 @@ To upgrade to latest version
|
|
|
108
108
|
npm install -g claude-teammate@latest
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
Check or stop the worker with:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
claude-teammate status
|
|
115
|
+
claude-teammate stop
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Per-project runtime files are stored in:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
.claude-teammate/
|
|
122
|
+
├── repos/
|
|
123
|
+
├── worker.log
|
|
124
|
+
├── worker.pid
|
|
125
|
+
└── state.json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Issue memory is stored in:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
memory/
|
|
132
|
+
└── {domain}/
|
|
133
|
+
└── {workspace}/
|
|
134
|
+
├── epic-{id}.md
|
|
135
|
+
└── issue-{id}.md
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`epic-{id}.md` stores shared epic facts such as related repositories. `issue-{id}.md` stores issue-specific workflow state, clarification history, and GitHub issue tracking.
|
|
139
|
+
|
|
140
|
+
The worker polls Jira once per minute. It transitions `To Do` issues to `In Progress`, asks for repo URLs when epic memory lacks them, clones all referenced repos into `.claude-teammate/repos/`, uses Claude CLI to clarify requirements from Jira comments across those repos, and creates GitHub issues once the requirements are clear enough.
|
|
141
|
+
|
|
111
142
|
```env
|
|
112
143
|
# .env file generated by `claude-teammate start`
|
|
113
144
|
JIRA_BASE_URL=https://yourorg.atlassian.net
|
|
114
|
-
JIRA_EMAIL=you@example.com
|
|
115
|
-
JIRA_API_TOKEN=...
|
|
116
145
|
JIRA_BOT_EMAIL=bot@yourorg.com
|
|
146
|
+
JIRA_BOT_API_TOKEN=...
|
|
117
147
|
GITHUB_PAT=ghp_...
|
|
118
148
|
```
|
|
119
149
|
|
|
@@ -138,7 +168,7 @@ It reads the actual repository before writing anything - structure, conventions,
|
|
|
138
168
|
## Roadmap
|
|
139
169
|
|
|
140
170
|
- 🟢 Design the workflow
|
|
141
|
-
-
|
|
171
|
+
- 🟢 Jira integration - read tickets, comment, detect assignee
|
|
142
172
|
- ⚪ GitHub integration - issues, PRs, review requests
|
|
143
173
|
- ⚪ Epic memory - persistent context per epic
|
|
144
174
|
- ⚪ End-to-end: Jira ticket → approved plan → merged PR
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-teammate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "CLI bootstrapper for Claude Teammate.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"lint": "eslint .",
|
|
20
|
-
"start": "node ./bin/claude-teammate.js start"
|
|
20
|
+
"start": "node ./bin/claude-teammate.js start",
|
|
21
|
+
"test": "node --test"
|
|
21
22
|
},
|
|
22
23
|
"keywords": [
|
|
23
24
|
"cli",
|
package/src/claude.js
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
4
|
+
const STARTUP_CHECK_TIMEOUT_MS = 15 * 1000;
|
|
5
|
+
const CHILD_CLEANUP_WAIT_MS = 1_000;
|
|
6
|
+
const activeClaudeChildren = new Set();
|
|
7
|
+
const DEFAULT_MODEL = "sonnet";
|
|
8
|
+
|
|
9
|
+
const OUTPUT_SCHEMA = {
|
|
10
|
+
type: "object",
|
|
11
|
+
additionalProperties: false,
|
|
12
|
+
properties: {
|
|
13
|
+
decision: {
|
|
14
|
+
type: "string",
|
|
15
|
+
enum: ["needs_clarification", "requirements_clear"]
|
|
16
|
+
},
|
|
17
|
+
clarification_summary: {
|
|
18
|
+
type: "string"
|
|
19
|
+
},
|
|
20
|
+
questions: {
|
|
21
|
+
type: "array",
|
|
22
|
+
items: {
|
|
23
|
+
type: "string"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
plan_title: {
|
|
27
|
+
type: "string"
|
|
28
|
+
},
|
|
29
|
+
plan_body: {
|
|
30
|
+
type: "string"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
required: ["decision", "clarification_summary", "questions", "plan_title", "plan_body"]
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export async function runClaudeClarification(input) {
|
|
37
|
+
if (!Array.isArray(input.repoPaths) || input.repoPaths.length === 0) {
|
|
38
|
+
throw new Error("Claude clarification requires at least one local repository path.");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const args = [
|
|
42
|
+
"--print",
|
|
43
|
+
"--model",
|
|
44
|
+
input.model || DEFAULT_MODEL,
|
|
45
|
+
"--permission-mode",
|
|
46
|
+
input.permissionMode || "dontAsk",
|
|
47
|
+
"--strict-mcp-config",
|
|
48
|
+
"--tools",
|
|
49
|
+
"Read,Grep,Glob",
|
|
50
|
+
"--effort",
|
|
51
|
+
"low",
|
|
52
|
+
"--output-format",
|
|
53
|
+
"json",
|
|
54
|
+
"--json-schema",
|
|
55
|
+
JSON.stringify(OUTPUT_SCHEMA),
|
|
56
|
+
"--append-system-prompt",
|
|
57
|
+
buildSystemPrompt()
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
args.push(
|
|
61
|
+
buildUserPrompt(input)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
let stdout;
|
|
65
|
+
const workingDirectory = getClaudeWorkspaceRoot(input.repoPaths);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
({ stdout } = await runClaudeCommand("claude", args, {
|
|
69
|
+
cwd: workingDirectory,
|
|
70
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
71
|
+
timeout: input.timeoutMs || DEFAULT_TIMEOUT_MS
|
|
72
|
+
}));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new Error(formatClaudeInvocationError(error, input.timeoutMs || DEFAULT_TIMEOUT_MS));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const parsed = parseClaudeOutput(stdout);
|
|
78
|
+
return validateClaudeResult(parsed);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function verifyClaudeCli(input = {}) {
|
|
82
|
+
const args = [
|
|
83
|
+
"--print",
|
|
84
|
+
"--output-format",
|
|
85
|
+
"text",
|
|
86
|
+
"Reply with exactly OK. Output only OK."
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
let stdout;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
({ stdout } = await runClaudeCommand("claude", args, {
|
|
93
|
+
cwd: input.cwd,
|
|
94
|
+
maxBuffer: 1024 * 1024,
|
|
95
|
+
timeout: input.timeoutMs || STARTUP_CHECK_TIMEOUT_MS
|
|
96
|
+
}));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
throw new Error(formatClaudeInvocationError(error, input.timeoutMs || STARTUP_CHECK_TIMEOUT_MS));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const normalizedOutput = stdout.trim().replace(/[.!]+$/u, "");
|
|
102
|
+
if (normalizedOutput !== "OK") {
|
|
103
|
+
throw new Error(`Claude CLI startup check returned unexpected output: ${stdout.trim() || "(empty)"}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function parseClaudeOutput(output) {
|
|
108
|
+
const trimmed = output.trim();
|
|
109
|
+
if (!trimmed) {
|
|
110
|
+
throw new Error("Claude CLI returned empty output.");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const direct = JSON.parse(trimmed);
|
|
114
|
+
if (direct && typeof direct === "object") {
|
|
115
|
+
if ("structured_output" in direct && direct.structured_output && typeof direct.structured_output === "object") {
|
|
116
|
+
return direct.structured_output;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if ("decision" in direct) {
|
|
120
|
+
return direct;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if ("result" in direct && typeof direct.result === "string") {
|
|
124
|
+
return JSON.parse(direct.result);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if ("content" in direct && typeof direct.content === "string") {
|
|
128
|
+
return JSON.parse(direct.content);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throw new Error("Claude CLI returned an unsupported JSON payload.");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function cleanupClaudeProcesses() {
|
|
136
|
+
const children = [...activeClaudeChildren];
|
|
137
|
+
if (children.length === 0) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await Promise.all(children.map((child) => terminateChildProcess(child)));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function validateClaudeResult(result) {
|
|
145
|
+
if (
|
|
146
|
+
!result ||
|
|
147
|
+
typeof result !== "object" ||
|
|
148
|
+
!["needs_clarification", "requirements_clear"].includes(result.decision)
|
|
149
|
+
) {
|
|
150
|
+
throw new Error("Claude CLI returned an invalid decision payload.");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
decision: result.decision,
|
|
155
|
+
clarification_summary: String(result.clarification_summary ?? "").trim(),
|
|
156
|
+
questions: Array.isArray(result.questions)
|
|
157
|
+
? result.questions.map((question) => String(question).trim()).filter(Boolean)
|
|
158
|
+
: [],
|
|
159
|
+
plan_title: String(result.plan_title ?? "").trim(),
|
|
160
|
+
plan_body: String(result.plan_body ?? "").trim()
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatClaudeInvocationError(error, timeoutMs) {
|
|
165
|
+
const stderr = error instanceof Error && "stderr" in error ? String(error.stderr || "") : "";
|
|
166
|
+
const output = error instanceof Error && "stdout" in error ? String(error.stdout || "") : "";
|
|
167
|
+
const timeout = Boolean(error && typeof error === "object" && "killed" in error && error.killed);
|
|
168
|
+
const signal = error && typeof error === "object" && "signal" in error ? String(error.signal || "") : "";
|
|
169
|
+
const details = [stderr.trim(), output.trim()].filter(Boolean).join("\n").slice(0, 1000);
|
|
170
|
+
return `Claude CLI invocation failed${timeout ? ` after ${timeoutMs}ms` : ""}${signal ? ` (${signal})` : ""}${details ? `: ${details}` : "."}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function runClaudeCommand(command, args, options) {
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
175
|
+
const child = spawn(command, args, {
|
|
176
|
+
cwd: options.cwd,
|
|
177
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
178
|
+
});
|
|
179
|
+
activeClaudeChildren.add(child);
|
|
180
|
+
|
|
181
|
+
let stdout = "";
|
|
182
|
+
let stderr = "";
|
|
183
|
+
let settled = false;
|
|
184
|
+
let timedOut = false;
|
|
185
|
+
let killedBySignal = "";
|
|
186
|
+
|
|
187
|
+
const timer = setTimeout(() => {
|
|
188
|
+
timedOut = true;
|
|
189
|
+
child.kill("SIGTERM");
|
|
190
|
+
}, options.timeout);
|
|
191
|
+
|
|
192
|
+
child.stdout.setEncoding("utf8");
|
|
193
|
+
child.stderr.setEncoding("utf8");
|
|
194
|
+
|
|
195
|
+
child.stdout.on("data", (chunk) => {
|
|
196
|
+
stdout += chunk;
|
|
197
|
+
if (stdout.length > options.maxBuffer) {
|
|
198
|
+
killedBySignal = "SIGTERM";
|
|
199
|
+
child.kill("SIGTERM");
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
child.stderr.on("data", (chunk) => {
|
|
204
|
+
stderr += chunk;
|
|
205
|
+
if (stderr.length > options.maxBuffer) {
|
|
206
|
+
killedBySignal = "SIGTERM";
|
|
207
|
+
child.kill("SIGTERM");
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
child.on("error", (error) => {
|
|
212
|
+
if (settled) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
settled = true;
|
|
216
|
+
activeClaudeChildren.delete(child);
|
|
217
|
+
clearTimeout(timer);
|
|
218
|
+
reject({
|
|
219
|
+
...error,
|
|
220
|
+
stdout,
|
|
221
|
+
stderr,
|
|
222
|
+
killed: timedOut,
|
|
223
|
+
signal: killedBySignal
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
child.on("close", (code, signal) => {
|
|
228
|
+
if (settled) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
settled = true;
|
|
232
|
+
activeClaudeChildren.delete(child);
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
|
|
235
|
+
if (code === 0) {
|
|
236
|
+
resolve({ stdout, stderr });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
reject({
|
|
241
|
+
stdout,
|
|
242
|
+
stderr,
|
|
243
|
+
killed: timedOut,
|
|
244
|
+
signal: signal || killedBySignal
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function terminateChildProcess(child) {
|
|
251
|
+
return new Promise((resolve) => {
|
|
252
|
+
if (child.exitCode !== null || child.killed) {
|
|
253
|
+
activeClaudeChildren.delete(child);
|
|
254
|
+
resolve();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let finished = false;
|
|
259
|
+
const finish = () => {
|
|
260
|
+
if (finished) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
finished = true;
|
|
264
|
+
activeClaudeChildren.delete(child);
|
|
265
|
+
resolve();
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
child.once("close", finish);
|
|
269
|
+
child.kill("SIGTERM");
|
|
270
|
+
|
|
271
|
+
setTimeout(() => {
|
|
272
|
+
if (finished) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
child.kill("SIGKILL");
|
|
276
|
+
finish();
|
|
277
|
+
}, CHILD_CLEANUP_WAIT_MS);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function buildSystemPrompt() {
|
|
282
|
+
return [
|
|
283
|
+
"You are clarifying a Jira ticket for an implementation planning workflow.",
|
|
284
|
+
"You may inspect the provided repository if that helps determine whether the requirement is clear enough or helps write a better implementation plan.",
|
|
285
|
+
"Use tools selectively; only inspect code when it materially helps the decision or plan.",
|
|
286
|
+
"Do not edit files, do not run mutating commands, and do not implement anything.",
|
|
287
|
+
"Return only structured output that matches the provided schema.",
|
|
288
|
+
"If requirements are unclear, ask concise clarification questions for the Jira user.",
|
|
289
|
+
"If requirements are clear, produce a concise implementation plan title and body suitable for a GitHub issue.",
|
|
290
|
+
"The plan_title must use exactly this format: <type>: <title> [issue-id].",
|
|
291
|
+
"Allowed types are only: feat, fix, docs, style, refactor, test, chore.",
|
|
292
|
+
"Use the provided Jira issue key as the issue-id suffix."
|
|
293
|
+
].join(" ");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function buildUserPrompt(input) {
|
|
297
|
+
const recentComments = input.comments
|
|
298
|
+
.slice(-10)
|
|
299
|
+
.map(
|
|
300
|
+
(comment) =>
|
|
301
|
+
`- [${comment.id}] ${comment.author.displayName || comment.author.emailAddress || "Unknown"}: ${comment.bodyText}`
|
|
302
|
+
)
|
|
303
|
+
.join("\n");
|
|
304
|
+
|
|
305
|
+
return `Clarify this Jira issue.
|
|
306
|
+
|
|
307
|
+
Issue key: ${input.issue.key}
|
|
308
|
+
Issue URL: ${input.issue.url}
|
|
309
|
+
Summary: ${input.issue.summary}
|
|
310
|
+
Status: ${input.issue.status}
|
|
311
|
+
Description:
|
|
312
|
+
${input.issue.descriptionText || "(none)"}
|
|
313
|
+
|
|
314
|
+
Memory snapshot:
|
|
315
|
+
${JSON.stringify(input.memory, null, 2)}
|
|
316
|
+
|
|
317
|
+
Recent Jira comments:
|
|
318
|
+
${recentComments || "(none)"}
|
|
319
|
+
|
|
320
|
+
Repository path:
|
|
321
|
+
${input.repoPaths.join("\n")}
|
|
322
|
+
|
|
323
|
+
Decide whether the requirements are clear enough to plan implementation. Read code only if it helps.`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function getClaudeWorkspaceRoot(repoPaths) {
|
|
327
|
+
if (repoPaths.length === 1) {
|
|
328
|
+
return repoPaths[0];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let current = repoPaths[0];
|
|
332
|
+
for (const repoPath of repoPaths.slice(1)) {
|
|
333
|
+
current = commonAncestorPath(current, repoPath);
|
|
334
|
+
}
|
|
335
|
+
return current;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function commonAncestorPath(left, right) {
|
|
339
|
+
const leftParts = path.resolve(left).split(path.sep);
|
|
340
|
+
const rightParts = path.resolve(right).split(path.sep);
|
|
341
|
+
const shared = [];
|
|
342
|
+
const maxLength = Math.min(leftParts.length, rightParts.length);
|
|
343
|
+
|
|
344
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
345
|
+
if (leftParts[index] !== rightParts[index]) {
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
shared.push(leftParts[index]);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (shared.length === 0) {
|
|
352
|
+
return path.parse(path.resolve(left)).root;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return shared.join(path.sep) || path.sep;
|
|
356
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { runStartCommand } from "./commands/start.js";
|
|
4
|
+
import { runStatusCommand } from "./commands/status.js";
|
|
5
|
+
import { runStopCommand } from "./commands/stop.js";
|
|
6
|
+
import { runWorkerCommand } from "./commands/worker.js";
|
|
4
7
|
|
|
5
8
|
const HELP_TEXT = `claude-teammate
|
|
6
9
|
|
|
7
10
|
Usage:
|
|
8
11
|
claude-teammate start
|
|
12
|
+
claude-teammate stop
|
|
13
|
+
claude-teammate status
|
|
9
14
|
claude-teammate --help
|
|
10
15
|
|
|
11
16
|
Commands:
|
|
12
|
-
start
|
|
17
|
+
start Start Claude Teammate in the background for this project
|
|
18
|
+
stop Stop the background worker for this project
|
|
19
|
+
status Show worker status, logs, and last Jira poll state
|
|
13
20
|
`;
|
|
14
21
|
|
|
15
22
|
export async function runCli(args) {
|
|
16
23
|
const [command] = args;
|
|
24
|
+
const projectRoot = process.cwd();
|
|
25
|
+
const entrypointPath = process.argv[1];
|
|
17
26
|
|
|
18
27
|
if (!command || command === "--help" || command === "-h") {
|
|
19
28
|
process.stdout.write(`${HELP_TEXT}\n`);
|
|
@@ -21,7 +30,27 @@ export async function runCli(args) {
|
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
if (command === "start") {
|
|
24
|
-
await
|
|
33
|
+
await runStartCommand({ projectRoot, entrypointPath });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (command === "stop") {
|
|
38
|
+
await runStopCommand({ projectRoot });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (command === "status") {
|
|
43
|
+
await runStatusCommand({ projectRoot });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (command === "run-worker") {
|
|
48
|
+
const workerProjectRoot = args[1];
|
|
49
|
+
if (!workerProjectRoot) {
|
|
50
|
+
throw new Error("Missing project root for run-worker.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await runWorkerCommand({ projectRoot: workerProjectRoot });
|
|
25
54
|
return;
|
|
26
55
|
}
|
|
27
56
|
|