@digitalpresence/cliclaw 0.2.1 → 0.3.0
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/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/cron.d.ts.map +1 -1
- package/dist/commands/cron.js +1 -0
- package/dist/commands/cron.js.map +1 -1
- package/dist/commands/github.d.ts +6 -0
- package/dist/commands/github.d.ts.map +1 -0
- package/dist/commands/github.js +179 -0
- package/dist/commands/github.js.map +1 -0
- package/dist/cron/progress.d.ts +1 -0
- package/dist/cron/progress.d.ts.map +1 -1
- package/dist/cron/progress.js.map +1 -1
- package/dist/cron/ralph-wiggum.d.ts +1 -0
- package/dist/cron/ralph-wiggum.d.ts.map +1 -1
- package/dist/cron/ralph-wiggum.js +65 -19
- package/dist/cron/ralph-wiggum.js.map +1 -1
- package/dist/github/api.d.ts +5 -0
- package/dist/github/api.d.ts.map +1 -0
- package/dist/github/api.js +54 -0
- package/dist/github/api.js.map +1 -0
- package/dist/github/handlers.d.ts +18 -0
- package/dist/github/handlers.d.ts.map +1 -0
- package/dist/github/handlers.js +191 -0
- package/dist/github/handlers.js.map +1 -0
- package/package.json +11 -11
- package/src/cli.ts +3 -1
- package/src/commands/cron.ts +1 -0
- package/src/commands/github.ts +214 -0
- package/src/cron/progress.ts +1 -0
- package/src/cron/ralph-wiggum.ts +72 -20
- package/src/github/api.ts +73 -0
- package/src/github/handlers.ts +325 -0
- package/.turbo/turbo-dev.log +0 -7
package/src/cron/ralph-wiggum.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { createInterface } from "readline";
|
|
|
3
3
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import type { AgentStore, CronJobConfig } from "@digitalpresence/cliclaw-auth";
|
|
6
|
+
import { generateUniversalClaudeMd, generateContextMd } from "@digitalpresence/cliclaw-auth";
|
|
6
7
|
import { getProgressFilePath, ensureCronDirs, writeRunningMarker, clearRunningMarker } from "./progress.js";
|
|
7
8
|
import { cronLog } from "./logger.js";
|
|
8
9
|
|
|
@@ -18,9 +19,18 @@ export interface RalphWiggumResult {
|
|
|
18
19
|
completed: boolean;
|
|
19
20
|
iterations: number;
|
|
20
21
|
totalCostUsd: number;
|
|
22
|
+
report?: string;
|
|
21
23
|
transcript: TranscriptBlock[];
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
function readReport(instancePath: string): string | undefined {
|
|
27
|
+
const reportPath = join(instancePath, "workspace", "REPORT.md");
|
|
28
|
+
if (existsSync(reportPath)) {
|
|
29
|
+
return readFileSync(reportPath, "utf-8").trim() || undefined;
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
function loadTaskContent(store: AgentStore, agentName: string, job: CronJobConfig): string {
|
|
25
35
|
const taskFilePath = join(store.workspacePath(agentName), "cron-tasks", job.taskFile);
|
|
26
36
|
if (existsSync(taskFilePath)) {
|
|
@@ -45,18 +55,39 @@ async function runContainerIteration(
|
|
|
45
55
|
"utf-8",
|
|
46
56
|
);
|
|
47
57
|
|
|
58
|
+
// Ensure persistent Claude state directory for session resumption
|
|
59
|
+
const claudeStatePath = join(instancePath, ".claude-state");
|
|
60
|
+
if (!existsSync(claudeStatePath)) {
|
|
61
|
+
mkdirSync(claudeStatePath, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Mount cliclaw config if available (portal injects this for Google OAuth)
|
|
65
|
+
const cliclawConfigPath = join(instancePath, ".cliclaw-config");
|
|
66
|
+
|
|
48
67
|
const args = [
|
|
49
68
|
"run", "--rm", "-i",
|
|
50
69
|
"-v", `${instancePath}:/instance`,
|
|
70
|
+
"-v", `${claudeStatePath}:/home/agent/.claude`,
|
|
71
|
+
...(existsSync(cliclawConfigPath)
|
|
72
|
+
? ["-v", `${cliclawConfigPath}:/home/agent/.cliclaw`]
|
|
73
|
+
: []),
|
|
51
74
|
"--network=host",
|
|
52
75
|
"--cpus=2", "--memory=2g",
|
|
53
76
|
];
|
|
54
77
|
|
|
55
|
-
// Pass
|
|
56
|
-
if (process.env.
|
|
78
|
+
// Pass auth env vars
|
|
79
|
+
if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
80
|
+
args.push("-e", `CLAUDE_CODE_OAUTH_TOKEN=${process.env.CLAUDE_CODE_OAUTH_TOKEN}`);
|
|
81
|
+
} else if (process.env.ANTHROPIC_API_KEY) {
|
|
57
82
|
args.push("-e", `ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`);
|
|
58
83
|
}
|
|
59
84
|
|
|
85
|
+
// Pass tokens path if set (portal injects this for client integrations)
|
|
86
|
+
if (process.env.CLICLAW_TOKENS_PATH) {
|
|
87
|
+
const containerTokensPath = process.env.CLICLAW_TOKENS_PATH.replace(instancePath, "/instance");
|
|
88
|
+
args.push("-e", `CLICLAW_TOKENS_PATH=${containerTokensPath}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
60
91
|
args.push(CONTAINER_IMAGE);
|
|
61
92
|
|
|
62
93
|
return new Promise((resolve) => {
|
|
@@ -109,20 +140,21 @@ export async function executeRalphWiggumLoop(
|
|
|
109
140
|
const instancesDir = join(store.workspacePath(agentName), "..", "..", "instances");
|
|
110
141
|
const cronInstancePath = join(instancesDir, agentName, "_cron");
|
|
111
142
|
|
|
112
|
-
// Ensure cron instance exists with
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
143
|
+
// Ensure cron instance exists with unified CLAUDE.md
|
|
144
|
+
mkdirSync(join(cronInstancePath, "workspace"), { recursive: true });
|
|
145
|
+
mkdirSync(join(cronInstancePath, "memory"), { recursive: true });
|
|
146
|
+
|
|
147
|
+
// Generate unified CLAUDE.md with soul/role/context inlined
|
|
148
|
+
const config = store.get(agentName)!;
|
|
149
|
+
const agentDir = store.workspacePath(agentName);
|
|
150
|
+
const soul = existsSync(join(agentDir, "SOUL.md")) ? readFileSync(join(agentDir, "SOUL.md"), "utf-8") : "";
|
|
151
|
+
const role = existsSync(join(agentDir, "ROLE.md")) ? readFileSync(join(agentDir, "ROLE.md"), "utf-8") : "";
|
|
152
|
+
const context = generateContextMd(config, []);
|
|
153
|
+
writeFileSync(
|
|
154
|
+
join(cronInstancePath, "CLAUDE.md"),
|
|
155
|
+
generateUniversalClaudeMd({ soul, role, context }),
|
|
156
|
+
"utf-8",
|
|
157
|
+
);
|
|
126
158
|
|
|
127
159
|
const progressFile = getProgressFilePath(agentName, job.id);
|
|
128
160
|
const taskContent = loadTaskContent(store, agentName, job);
|
|
@@ -150,7 +182,9 @@ export async function executeRalphWiggumLoop(
|
|
|
150
182
|
``,
|
|
151
183
|
taskContent,
|
|
152
184
|
``,
|
|
153
|
-
`
|
|
185
|
+
`You MUST write two files:`,
|
|
186
|
+
`1. /instance/workspace/progress.md — working notes and intermediate state`,
|
|
187
|
+
`2. /instance/workspace/REPORT.md — a clean, final summary of what you did and found (this is shown to the user)`,
|
|
154
188
|
``,
|
|
155
189
|
`If you CANNOT finish the task in this iteration and need to be re-invoked, write "${CONTINUE_MARKER}" anywhere in the progress file. Otherwise, just complete the task normally — no special signal is needed.`,
|
|
156
190
|
].join("\n");
|
|
@@ -162,6 +196,10 @@ export async function executeRalphWiggumLoop(
|
|
|
162
196
|
`Original task:`,
|
|
163
197
|
taskContent,
|
|
164
198
|
``,
|
|
199
|
+
`You MUST write two files:`,
|
|
200
|
+
`1. /instance/workspace/progress.md — working notes and intermediate state`,
|
|
201
|
+
`2. /instance/workspace/REPORT.md — a clean, final summary of what you did and found (this is shown to the user)`,
|
|
202
|
+
``,
|
|
165
203
|
`If you CANNOT finish the task in this iteration and need to be re-invoked, write "${CONTINUE_MARKER}" anywhere in the progress file. Otherwise, just complete the task normally — no special signal is needed.`,
|
|
166
204
|
].join("\n");
|
|
167
205
|
}
|
|
@@ -199,7 +237,19 @@ export async function executeRalphWiggumLoop(
|
|
|
199
237
|
}
|
|
200
238
|
currentToolInput = "";
|
|
201
239
|
}
|
|
202
|
-
} else if (event.type === "
|
|
240
|
+
} else if (event.type === "assistant" && event.message?.content) {
|
|
241
|
+
// Full assistant message — extract text blocks
|
|
242
|
+
for (const block of event.message.content) {
|
|
243
|
+
if (block.type === "text" && block.text) {
|
|
244
|
+
const last = transcript[transcript.length - 1];
|
|
245
|
+
if (last?.type === "assistant") {
|
|
246
|
+
last.content = block.text; // Replace with full text
|
|
247
|
+
} else {
|
|
248
|
+
transcript.push({ type: "assistant", content: block.text });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} else if (event.type === "tool_result" || event.type === "tool_use_summary") {
|
|
203
253
|
const lastTool = [...transcript].reverse().find((b) => b.type === "tool" && !b.done);
|
|
204
254
|
if (lastTool && lastTool.type === "tool") {
|
|
205
255
|
lastTool.done = true;
|
|
@@ -226,14 +276,16 @@ export async function executeRalphWiggumLoop(
|
|
|
226
276
|
|
|
227
277
|
if (!needsMore && !needsMoreOriginal) {
|
|
228
278
|
cronLog("info", `Task completed at iteration ${iteration}`, agentName, job.id);
|
|
229
|
-
|
|
279
|
+
const report = readReport(cronInstancePath);
|
|
280
|
+
return { completed: true, iterations: iteration, totalCostUsd, report, transcript };
|
|
230
281
|
}
|
|
231
282
|
|
|
232
283
|
cronLog("info", `Agent requested continuation (${CONTINUE_MARKER} found in progress file)`, agentName, job.id);
|
|
233
284
|
}
|
|
234
285
|
|
|
235
286
|
cronLog("warn", `Max iterations (${job.maxIterations}) reached`, agentName, job.id);
|
|
236
|
-
|
|
287
|
+
const report = readReport(cronInstancePath);
|
|
288
|
+
return { completed: false, iterations: job.maxIterations, totalCostUsd, report, transcript };
|
|
237
289
|
} finally {
|
|
238
290
|
clearRunningMarker(agentName, job.id);
|
|
239
291
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { TokenStore } from "@digitalpresence/cliclaw-auth";
|
|
2
|
+
import { outputAuthRequired, outputError } from "../lib/output.js";
|
|
3
|
+
|
|
4
|
+
const GITHUB_API = "https://api.github.com";
|
|
5
|
+
|
|
6
|
+
export function getToken(tokenStore: TokenStore, account: string): string {
|
|
7
|
+
const tokenKey = `github:${account}`;
|
|
8
|
+
const creds = tokenStore.get(tokenKey);
|
|
9
|
+
if (!creds?.access_token) {
|
|
10
|
+
outputAuthRequired("github");
|
|
11
|
+
}
|
|
12
|
+
return creds.access_token as string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function githubFetch(
|
|
16
|
+
token: string,
|
|
17
|
+
path: string,
|
|
18
|
+
options: RequestInit = {},
|
|
19
|
+
): Promise<unknown> {
|
|
20
|
+
const url = path.startsWith("http") ? path : `${GITHUB_API}${path}`;
|
|
21
|
+
const res = await fetch(url, {
|
|
22
|
+
...options,
|
|
23
|
+
headers: {
|
|
24
|
+
Authorization: `Bearer ${token}`,
|
|
25
|
+
Accept: "application/vnd.github+json",
|
|
26
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
27
|
+
...options.headers,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const body = await res.text().catch(() => "");
|
|
33
|
+
outputError("github_api_error", `${res.status} ${res.statusText}: ${body}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return res.json();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function githubFetchPaginated(
|
|
40
|
+
token: string,
|
|
41
|
+
path: string,
|
|
42
|
+
maxResults: number,
|
|
43
|
+
): Promise<unknown[]> {
|
|
44
|
+
const results: unknown[] = [];
|
|
45
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
46
|
+
let url: string | null = `${path}${separator}per_page=${Math.min(maxResults, 100)}`;
|
|
47
|
+
|
|
48
|
+
while (url && results.length < maxResults) {
|
|
49
|
+
const fullUrl: string = url.startsWith("http") ? url : `${GITHUB_API}${url}`;
|
|
50
|
+
const res: Response = await fetch(fullUrl, {
|
|
51
|
+
headers: {
|
|
52
|
+
Authorization: `Bearer ${token}`,
|
|
53
|
+
Accept: "application/vnd.github+json",
|
|
54
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
const body = await res.text().catch(() => "");
|
|
60
|
+
outputError("github_api_error", `${res.status} ${res.statusText}: ${body}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
results.push(...(Array.isArray(data) ? data : [data]));
|
|
65
|
+
|
|
66
|
+
// Parse Link header for next page
|
|
67
|
+
const link: string | null = res.headers.get("link");
|
|
68
|
+
const next: RegExpMatchArray | null | undefined = link?.match(/<([^>]+)>;\s*rel="next"/);
|
|
69
|
+
url = next ? next[1] : null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return results.slice(0, maxResults);
|
|
73
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import type { TokenStore } from "@digitalpresence/cliclaw-auth";
|
|
2
|
+
import { getToken, githubFetch, githubFetchPaginated } from "./api.js";
|
|
3
|
+
import { outputJson, outputError } from "../lib/output.js";
|
|
4
|
+
|
|
5
|
+
// --- User ---
|
|
6
|
+
|
|
7
|
+
export async function handleWhoami(tokenStore: TokenStore, account: string): Promise<void> {
|
|
8
|
+
const token = getToken(tokenStore, account);
|
|
9
|
+
try {
|
|
10
|
+
const user = await githubFetch(token, "/user");
|
|
11
|
+
outputJson(user);
|
|
12
|
+
} catch (err) {
|
|
13
|
+
outputError("whoami_failed", err instanceof Error ? err.message : String(err));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// --- Repos ---
|
|
18
|
+
|
|
19
|
+
export async function handleRepos(
|
|
20
|
+
tokenStore: TokenStore,
|
|
21
|
+
account: string,
|
|
22
|
+
maxResults: number,
|
|
23
|
+
type: string,
|
|
24
|
+
sort: string,
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
const token = getToken(tokenStore, account);
|
|
27
|
+
try {
|
|
28
|
+
const repos = await githubFetchPaginated(
|
|
29
|
+
token,
|
|
30
|
+
`/user/repos?type=${type}&sort=${sort}`,
|
|
31
|
+
maxResults,
|
|
32
|
+
);
|
|
33
|
+
outputJson(repos);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
outputError("repos_failed", err instanceof Error ? err.message : String(err));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function handleRepoGet(
|
|
40
|
+
tokenStore: TokenStore,
|
|
41
|
+
account: string,
|
|
42
|
+
owner: string,
|
|
43
|
+
repo: string,
|
|
44
|
+
): Promise<void> {
|
|
45
|
+
const token = getToken(tokenStore, account);
|
|
46
|
+
try {
|
|
47
|
+
const data = await githubFetch(token, `/repos/${owner}/${repo}`);
|
|
48
|
+
outputJson(data);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
outputError("repo_get_failed", err instanceof Error ? err.message : String(err));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Issues ---
|
|
55
|
+
|
|
56
|
+
export async function handleIssues(
|
|
57
|
+
tokenStore: TokenStore,
|
|
58
|
+
account: string,
|
|
59
|
+
owner: string,
|
|
60
|
+
repo: string,
|
|
61
|
+
state: string,
|
|
62
|
+
maxResults: number,
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
const token = getToken(tokenStore, account);
|
|
65
|
+
try {
|
|
66
|
+
const issues = await githubFetchPaginated(
|
|
67
|
+
token,
|
|
68
|
+
`/repos/${owner}/${repo}/issues?state=${state}`,
|
|
69
|
+
maxResults,
|
|
70
|
+
);
|
|
71
|
+
outputJson(issues);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
outputError("issues_failed", err instanceof Error ? err.message : String(err));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function handleIssueGet(
|
|
78
|
+
tokenStore: TokenStore,
|
|
79
|
+
account: string,
|
|
80
|
+
owner: string,
|
|
81
|
+
repo: string,
|
|
82
|
+
number: number,
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
const token = getToken(tokenStore, account);
|
|
85
|
+
try {
|
|
86
|
+
const issue = await githubFetch(token, `/repos/${owner}/${repo}/issues/${number}`);
|
|
87
|
+
outputJson(issue);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
outputError("issue_get_failed", err instanceof Error ? err.message : String(err));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function handleIssueCreate(
|
|
94
|
+
tokenStore: TokenStore,
|
|
95
|
+
account: string,
|
|
96
|
+
owner: string,
|
|
97
|
+
repo: string,
|
|
98
|
+
title: string,
|
|
99
|
+
body?: string,
|
|
100
|
+
labels?: string[],
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
const token = getToken(tokenStore, account);
|
|
103
|
+
try {
|
|
104
|
+
const payload: Record<string, unknown> = { title };
|
|
105
|
+
if (body) payload.body = body;
|
|
106
|
+
if (labels?.length) payload.labels = labels;
|
|
107
|
+
const issue = await githubFetch(token, `/repos/${owner}/${repo}/issues`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: { "Content-Type": "application/json" },
|
|
110
|
+
body: JSON.stringify(payload),
|
|
111
|
+
});
|
|
112
|
+
outputJson(issue);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
outputError("issue_create_failed", err instanceof Error ? err.message : String(err));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function handleIssueComment(
|
|
119
|
+
tokenStore: TokenStore,
|
|
120
|
+
account: string,
|
|
121
|
+
owner: string,
|
|
122
|
+
repo: string,
|
|
123
|
+
number: number,
|
|
124
|
+
body: string,
|
|
125
|
+
): Promise<void> {
|
|
126
|
+
const token = getToken(tokenStore, account);
|
|
127
|
+
try {
|
|
128
|
+
const comment = await githubFetch(
|
|
129
|
+
token,
|
|
130
|
+
`/repos/${owner}/${repo}/issues/${number}/comments`,
|
|
131
|
+
{
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers: { "Content-Type": "application/json" },
|
|
134
|
+
body: JSON.stringify({ body }),
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
outputJson(comment);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
outputError("issue_comment_failed", err instanceof Error ? err.message : String(err));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function handleIssueClose(
|
|
144
|
+
tokenStore: TokenStore,
|
|
145
|
+
account: string,
|
|
146
|
+
owner: string,
|
|
147
|
+
repo: string,
|
|
148
|
+
number: number,
|
|
149
|
+
): Promise<void> {
|
|
150
|
+
const token = getToken(tokenStore, account);
|
|
151
|
+
try {
|
|
152
|
+
const issue = await githubFetch(token, `/repos/${owner}/${repo}/issues/${number}`, {
|
|
153
|
+
method: "PATCH",
|
|
154
|
+
headers: { "Content-Type": "application/json" },
|
|
155
|
+
body: JSON.stringify({ state: "closed" }),
|
|
156
|
+
});
|
|
157
|
+
outputJson(issue);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
outputError("issue_close_failed", err instanceof Error ? err.message : String(err));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// --- Pull Requests ---
|
|
164
|
+
|
|
165
|
+
export async function handlePulls(
|
|
166
|
+
tokenStore: TokenStore,
|
|
167
|
+
account: string,
|
|
168
|
+
owner: string,
|
|
169
|
+
repo: string,
|
|
170
|
+
state: string,
|
|
171
|
+
maxResults: number,
|
|
172
|
+
): Promise<void> {
|
|
173
|
+
const token = getToken(tokenStore, account);
|
|
174
|
+
try {
|
|
175
|
+
const prs = await githubFetchPaginated(
|
|
176
|
+
token,
|
|
177
|
+
`/repos/${owner}/${repo}/pulls?state=${state}`,
|
|
178
|
+
maxResults,
|
|
179
|
+
);
|
|
180
|
+
outputJson(prs);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
outputError("pulls_failed", err instanceof Error ? err.message : String(err));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function handlePullGet(
|
|
187
|
+
tokenStore: TokenStore,
|
|
188
|
+
account: string,
|
|
189
|
+
owner: string,
|
|
190
|
+
repo: string,
|
|
191
|
+
number: number,
|
|
192
|
+
): Promise<void> {
|
|
193
|
+
const token = getToken(tokenStore, account);
|
|
194
|
+
try {
|
|
195
|
+
const pr = await githubFetch(token, `/repos/${owner}/${repo}/pulls/${number}`);
|
|
196
|
+
outputJson(pr);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
outputError("pull_get_failed", err instanceof Error ? err.message : String(err));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function handlePullReviews(
|
|
203
|
+
tokenStore: TokenStore,
|
|
204
|
+
account: string,
|
|
205
|
+
owner: string,
|
|
206
|
+
repo: string,
|
|
207
|
+
number: number,
|
|
208
|
+
): Promise<void> {
|
|
209
|
+
const token = getToken(tokenStore, account);
|
|
210
|
+
try {
|
|
211
|
+
const reviews = await githubFetch(
|
|
212
|
+
token,
|
|
213
|
+
`/repos/${owner}/${repo}/pulls/${number}/reviews`,
|
|
214
|
+
);
|
|
215
|
+
outputJson(reviews);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
outputError("pull_reviews_failed", err instanceof Error ? err.message : String(err));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export async function handlePullMerge(
|
|
222
|
+
tokenStore: TokenStore,
|
|
223
|
+
account: string,
|
|
224
|
+
owner: string,
|
|
225
|
+
repo: string,
|
|
226
|
+
number: number,
|
|
227
|
+
method: string,
|
|
228
|
+
): Promise<void> {
|
|
229
|
+
const token = getToken(tokenStore, account);
|
|
230
|
+
try {
|
|
231
|
+
const result = await githubFetch(
|
|
232
|
+
token,
|
|
233
|
+
`/repos/${owner}/${repo}/pulls/${number}/merge`,
|
|
234
|
+
{
|
|
235
|
+
method: "PUT",
|
|
236
|
+
headers: { "Content-Type": "application/json" },
|
|
237
|
+
body: JSON.stringify({ merge_method: method }),
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
outputJson(result);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
outputError("pull_merge_failed", err instanceof Error ? err.message : String(err));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// --- Notifications ---
|
|
247
|
+
|
|
248
|
+
export async function handleNotifications(
|
|
249
|
+
tokenStore: TokenStore,
|
|
250
|
+
account: string,
|
|
251
|
+
maxResults: number,
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
const token = getToken(tokenStore, account);
|
|
254
|
+
try {
|
|
255
|
+
const notifications = await githubFetchPaginated(
|
|
256
|
+
token,
|
|
257
|
+
"/notifications",
|
|
258
|
+
maxResults,
|
|
259
|
+
);
|
|
260
|
+
outputJson(notifications);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
outputError("notifications_failed", err instanceof Error ? err.message : String(err));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// --- Search ---
|
|
267
|
+
|
|
268
|
+
export async function handleSearch(
|
|
269
|
+
tokenStore: TokenStore,
|
|
270
|
+
account: string,
|
|
271
|
+
query: string,
|
|
272
|
+
type: string,
|
|
273
|
+
maxResults: number,
|
|
274
|
+
): Promise<void> {
|
|
275
|
+
const token = getToken(tokenStore, account);
|
|
276
|
+
try {
|
|
277
|
+
const data = await githubFetch(
|
|
278
|
+
token,
|
|
279
|
+
`/search/${type}?q=${encodeURIComponent(query)}&per_page=${Math.min(maxResults, 100)}`,
|
|
280
|
+
);
|
|
281
|
+
outputJson(data);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
outputError("search_failed", err instanceof Error ? err.message : String(err));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// --- Actions ---
|
|
288
|
+
|
|
289
|
+
export async function handleWorkflowRuns(
|
|
290
|
+
tokenStore: TokenStore,
|
|
291
|
+
account: string,
|
|
292
|
+
owner: string,
|
|
293
|
+
repo: string,
|
|
294
|
+
maxResults: number,
|
|
295
|
+
): Promise<void> {
|
|
296
|
+
const token = getToken(tokenStore, account);
|
|
297
|
+
try {
|
|
298
|
+
const data = await githubFetch(
|
|
299
|
+
token,
|
|
300
|
+
`/repos/${owner}/${repo}/actions/runs?per_page=${Math.min(maxResults, 100)}`,
|
|
301
|
+
);
|
|
302
|
+
outputJson(data);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
outputError("workflow_runs_failed", err instanceof Error ? err.message : String(err));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export async function handleWorkflowRunLogs(
|
|
309
|
+
tokenStore: TokenStore,
|
|
310
|
+
account: string,
|
|
311
|
+
owner: string,
|
|
312
|
+
repo: string,
|
|
313
|
+
runId: number,
|
|
314
|
+
): Promise<void> {
|
|
315
|
+
const token = getToken(tokenStore, account);
|
|
316
|
+
try {
|
|
317
|
+
const jobs = await githubFetch(
|
|
318
|
+
token,
|
|
319
|
+
`/repos/${owner}/${repo}/actions/runs/${runId}/jobs`,
|
|
320
|
+
);
|
|
321
|
+
outputJson(jobs);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
outputError("workflow_logs_failed", err instanceof Error ? err.message : String(err));
|
|
324
|
+
}
|
|
325
|
+
}
|
package/.turbo/turbo-dev.log
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @digitalpresence/cliclaw@0.1.0 dev /Users/markshteyn/emdash-projects/worktrees/dark-doodles-rest-8y8/packages/cliclaw
|
|
3
|
-
> tsx src/cli.ts
|
|
4
|
-
|
|
5
|
-
sh: tsx: command not found
|
|
6
|
-
ELIFECYCLE Command failed.
|
|
7
|
-
WARN Local package.json exists, but node_modules missing, did you mean to install?
|