@longshot/cli 0.0.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 +8 -0
- package/dist/agent.js +214 -0
- package/dist/cli.js +172 -0
- package/dist/git.js +291 -0
- package/dist/index.js +1250 -0
- package/dist/profile.js +79 -0
- package/dist/projects.js +337 -0
- package/dist/queue.js +868 -0
- package/dist/services.js +194 -0
- package/dist/store.js +612 -0
- package/dist/views/agent-progress.js +242 -0
- package/dist/views/branches.js +191 -0
- package/dist/views/chat.js +386 -0
- package/dist/views/diff.js +321 -0
- package/dist/views/history.js +124 -0
- package/dist/views/layout.js +121 -0
- package/dist/views/run.js +92 -0
- package/dist/views/services.js +230 -0
- package/dist/views/spec.js +18 -0
- package/dist/views/tasks.js +898 -0
- package/dist/views/verify.js +209 -0
- package/package.json +36 -0
- package/public/style.css +2088 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { layout } from "./layout.js";
|
|
2
|
+
function escapeHtml(str) {
|
|
3
|
+
return str
|
|
4
|
+
.replace(/&/g, "&")
|
|
5
|
+
.replace(/</g, "<")
|
|
6
|
+
.replace(/>/g, ">")
|
|
7
|
+
.replace(/"/g, """);
|
|
8
|
+
}
|
|
9
|
+
function statusBadge(status) {
|
|
10
|
+
const colors = {
|
|
11
|
+
running: "accent-yellow",
|
|
12
|
+
pass: "accent-green",
|
|
13
|
+
fail: "accent-red",
|
|
14
|
+
error: "accent-red",
|
|
15
|
+
};
|
|
16
|
+
const color = colors[status] || "text-muted";
|
|
17
|
+
return `<span class="verify-status verify-status-${status}" style="color: var(--${color})">${status.toUpperCase()}</span>`;
|
|
18
|
+
}
|
|
19
|
+
function renderResult(result, taskId) {
|
|
20
|
+
const artifactHtml = result.artifacts
|
|
21
|
+
.map((a) => {
|
|
22
|
+
if (a.match(/\.(png|jpg|jpeg|gif|webp)$/i)) {
|
|
23
|
+
return `<div class="verify-artifact">
|
|
24
|
+
<img src="/task/${taskId}/verify/artifact/${result.runNumber}/${encodeURIComponent(a)}"
|
|
25
|
+
alt="${escapeHtml(a)}" class="verify-image" loading="lazy">
|
|
26
|
+
<div class="verify-artifact-name">${escapeHtml(a)}</div>
|
|
27
|
+
</div>`;
|
|
28
|
+
}
|
|
29
|
+
return `<div class="verify-artifact">
|
|
30
|
+
<a href="/task/${taskId}/verify/artifact/${result.runNumber}/${encodeURIComponent(a)}"
|
|
31
|
+
class="verify-artifact-link">${escapeHtml(a)}</a>
|
|
32
|
+
</div>`;
|
|
33
|
+
})
|
|
34
|
+
.join("\n");
|
|
35
|
+
return `<div class="verify-result verify-result-${result.status}">
|
|
36
|
+
<div class="verify-result-header">
|
|
37
|
+
<span class="verify-run-num">#${result.runNumber}</span>
|
|
38
|
+
${statusBadge(result.status)}
|
|
39
|
+
<span class="verify-time">${new Date(result.startedAt).toLocaleTimeString()}</span>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="verify-prompt">${escapeHtml(result.prompt)}</div>
|
|
42
|
+
${result.summary ? `<div class="verify-summary">${escapeHtml(result.summary)}</div>` : ""}
|
|
43
|
+
${artifactHtml ? `<div class="verify-artifacts">${artifactHtml}</div>` : ""}
|
|
44
|
+
<a href="/task/${taskId}/verify/${result.runNumber}/log" class="verify-log-link">View log</a>
|
|
45
|
+
</div>`;
|
|
46
|
+
}
|
|
47
|
+
export function verifyPage(taskId, diffId, taskSummary, results, runningVerifications) {
|
|
48
|
+
const resultsHtml = results.map((r) => renderResult(r, taskId)).join("\n");
|
|
49
|
+
const runningForTask = runningVerifications.filter((v) => v.taskId === taskId);
|
|
50
|
+
const body = `
|
|
51
|
+
<div class="verify-container">
|
|
52
|
+
<div class="verify-header">
|
|
53
|
+
<a href="/diff/${diffId}" class="back-link">Back to Diff</a>
|
|
54
|
+
<h1>Verify Task</h1>
|
|
55
|
+
<p class="verify-task-summary">${escapeHtml(taskSummary)}</p>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="verify-launch">
|
|
59
|
+
<form id="verify-form">
|
|
60
|
+
<textarea
|
|
61
|
+
id="verify-prompt"
|
|
62
|
+
placeholder="What to verify... (e.g. 'run the tests', 'screenshot the homepage')"
|
|
63
|
+
rows="2"
|
|
64
|
+
></textarea>
|
|
65
|
+
<button type="submit" class="btn btn-primary" id="verify-btn">Run Verification</button>
|
|
66
|
+
</form>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="verify-results" id="verify-results">
|
|
70
|
+
${runningForTask.map((v) => `
|
|
71
|
+
<div class="verify-result verify-result-running" id="verify-run-${v.runNumber}">
|
|
72
|
+
<div class="verify-result-header">
|
|
73
|
+
<span class="verify-run-num">#${v.runNumber}</span>
|
|
74
|
+
${statusBadge("running")}
|
|
75
|
+
<span class="agent-status-indicator">Working...</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="verify-progress" id="verify-progress-${v.runNumber}"></div>
|
|
78
|
+
</div>
|
|
79
|
+
`).join("\n")}
|
|
80
|
+
${resultsHtml}
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div class="verify-actions">
|
|
84
|
+
<a href="/diff/${diffId}" class="btn btn-small">Back to Diff</a>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<script>
|
|
89
|
+
const taskId = ${taskId};
|
|
90
|
+
const diffId = ${JSON.stringify(diffId)};
|
|
91
|
+
const form = document.getElementById('verify-form');
|
|
92
|
+
const promptInput = document.getElementById('verify-prompt');
|
|
93
|
+
const verifyBtn = document.getElementById('verify-btn');
|
|
94
|
+
const resultsContainer = document.getElementById('verify-results');
|
|
95
|
+
|
|
96
|
+
// Track running verifications
|
|
97
|
+
let runningRuns = ${JSON.stringify(runningForTask.map((v) => v.runNumber))};
|
|
98
|
+
let pollCounts = {};
|
|
99
|
+
runningRuns.forEach(n => { pollCounts[n] = 0; });
|
|
100
|
+
|
|
101
|
+
form.addEventListener('submit', async (e) => {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
const prompt = promptInput.value.trim();
|
|
104
|
+
if (!prompt) return;
|
|
105
|
+
|
|
106
|
+
verifyBtn.disabled = true;
|
|
107
|
+
verifyBtn.textContent = 'Starting...';
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const resp = await fetch('/task/' + taskId + '/verify', {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: { 'Content-Type': 'application/json' },
|
|
113
|
+
body: JSON.stringify({ prompt, diffId }),
|
|
114
|
+
});
|
|
115
|
+
const data = await resp.json();
|
|
116
|
+
if (data.ok) {
|
|
117
|
+
promptInput.value = '';
|
|
118
|
+
// Add running card
|
|
119
|
+
const runNum = data.runNumber;
|
|
120
|
+
const card = document.createElement('div');
|
|
121
|
+
card.className = 'verify-result verify-result-running';
|
|
122
|
+
card.id = 'verify-run-' + runNum;
|
|
123
|
+
card.innerHTML = '<div class="verify-result-header"><span class="verify-run-num">#' + runNum + '</span><span class="verify-status verify-status-running" style="color: var(--accent-yellow)">RUNNING</span><span class="agent-status-indicator">Working...</span></div><div class="verify-progress" id="verify-progress-' + runNum + '"></div>';
|
|
124
|
+
resultsContainer.insertBefore(card, resultsContainer.firstChild);
|
|
125
|
+
runningRuns.push(runNum);
|
|
126
|
+
pollCounts[runNum] = 0;
|
|
127
|
+
} else {
|
|
128
|
+
alert('Error: ' + (data.error || 'unknown'));
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
alert('Failed to start verification');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
verifyBtn.disabled = false;
|
|
135
|
+
verifyBtn.textContent = 'Run Verification';
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
function escapeHtml(str) {
|
|
139
|
+
const div = document.createElement('div');
|
|
140
|
+
div.textContent = str;
|
|
141
|
+
return div.innerHTML;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function pollRunning() {
|
|
145
|
+
for (const runNum of [...runningRuns]) {
|
|
146
|
+
try {
|
|
147
|
+
const resp = await fetch('/task/' + taskId + '/verify/' + runNum + '/status');
|
|
148
|
+
const data = await resp.json();
|
|
149
|
+
|
|
150
|
+
// Update progress
|
|
151
|
+
const progressEl = document.getElementById('verify-progress-' + runNum);
|
|
152
|
+
if (progressEl && data.lastEvent) {
|
|
153
|
+
progressEl.textContent = data.lastEvent;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (data.done) {
|
|
157
|
+
runningRuns = runningRuns.filter(n => n !== runNum);
|
|
158
|
+
// Reload to get rendered result
|
|
159
|
+
location.reload();
|
|
160
|
+
}
|
|
161
|
+
} catch {}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (runningRuns.length > 0) {
|
|
165
|
+
setTimeout(pollRunning, 2000);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (runningRuns.length > 0) {
|
|
170
|
+
setTimeout(pollRunning, 2000);
|
|
171
|
+
}
|
|
172
|
+
</script>`;
|
|
173
|
+
return layout("Verify", "tasks", body, "");
|
|
174
|
+
}
|
|
175
|
+
export function verifyLogPage(taskId, runNumber, events, diffId) {
|
|
176
|
+
const eventsHtml = events
|
|
177
|
+
.map((ev) => {
|
|
178
|
+
const time = new Date(ev.ts).toLocaleTimeString();
|
|
179
|
+
switch (ev.type) {
|
|
180
|
+
case "user_message":
|
|
181
|
+
return `<div class="run-event run-event-user"><div class="run-event-marker">U</div><div class="run-event-body"><div class="run-event-label">Prompt <span class="run-event-time">${time}</span></div><div class="run-event-content">${escapeHtml((ev.content || "").slice(0, 500))}</div></div></div>`;
|
|
182
|
+
case "text":
|
|
183
|
+
return `<div class="run-event run-event-text"><div class="run-event-marker">T</div><div class="run-event-body"><div class="run-event-label">Text <span class="run-event-time">${time}</span></div><div class="run-event-content">${escapeHtml((ev.content || "").slice(0, 500))}</div></div></div>`;
|
|
184
|
+
case "tool_use":
|
|
185
|
+
return `<div class="run-event run-event-tool-use"><div class="run-event-marker">⚙</div><div class="run-event-body"><div class="run-event-label">${escapeHtml(ev.name || "tool")} <span class="run-event-time">${time}</span></div></div></div>`;
|
|
186
|
+
case "tool_result":
|
|
187
|
+
return `<div class="run-event run-event-tool-result"><div class="run-event-marker">✓</div><div class="run-event-body"><div class="run-event-label">Result <span class="run-event-time">${time}</span></div></div></div>`;
|
|
188
|
+
case "done":
|
|
189
|
+
return `<div class="run-event run-event-done"><div class="run-event-marker">●</div><div class="run-event-body"><div class="run-event-label">Done <span class="run-event-time">${time}</span></div></div></div>`;
|
|
190
|
+
case "error":
|
|
191
|
+
return `<div class="run-event run-event-error"><div class="run-event-marker">!</div><div class="run-event-body"><div class="run-event-label">Error <span class="run-event-time">${time}</span></div><div class="run-event-content">${escapeHtml((ev.message || "").slice(0, 500))}</div></div></div>`;
|
|
192
|
+
default:
|
|
193
|
+
return "";
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
.filter(Boolean)
|
|
197
|
+
.join("\n");
|
|
198
|
+
const body = `
|
|
199
|
+
<div class="run-container">
|
|
200
|
+
<div class="run-header">
|
|
201
|
+
<a href="/task/${taskId}/verify?diffId=${diffId}" class="back-link">Back to Verify</a>
|
|
202
|
+
<h1>Verification Run #${runNumber}</h1>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="run-timeline">
|
|
205
|
+
${eventsHtml}
|
|
206
|
+
</div>
|
|
207
|
+
</div>`;
|
|
208
|
+
return layout("Verify Log", "tasks", body);
|
|
209
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@longshot/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Mobile-first Claude orchestrator with spec-driven workflow",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"longshot": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"public/",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "tsx watch src/index.ts",
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"test": "tsx --test test/**/*.test.ts"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [],
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "UNLICENSED",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@hono/node-server": "^1.19.9",
|
|
25
|
+
"diff2html": "^3.4.56",
|
|
26
|
+
"hono": "^4.11.9",
|
|
27
|
+
"inquirer": "^13.2.4",
|
|
28
|
+
"marked": "^17.0.2"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/inquirer": "^9.0.9",
|
|
32
|
+
"@types/node": "^25.2.3",
|
|
33
|
+
"tsx": "^4.21.0",
|
|
34
|
+
"typescript": "^5.9.3"
|
|
35
|
+
}
|
|
36
|
+
}
|