openthrottle 0.1.4 → 0.1.5

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/index.mjs DELETED
@@ -1,346 +0,0 @@
1
- #!/usr/bin/env node
2
- // =============================================================================
3
- // openthrottle — CLI for Open Throttle.
4
- //
5
- // Usage: npx openthrottle <command>
6
- //
7
- // Commands:
8
- // ship <file.md> [--base <branch>] Ship a prompt to a Daytona sandbox
9
- // status Show running, queued, and completed tasks
10
- // logs Show recent GitHub Actions workflow runs
11
- // =============================================================================
12
-
13
- import { readFileSync, existsSync } from 'node:fs';
14
- import { execFileSync } from 'node:child_process';
15
- import { join, resolve } from 'node:path';
16
- import { fileURLToPath } from 'node:url';
17
-
18
- // ---------------------------------------------------------------------------
19
- // 1. Constants + helpers
20
- // ---------------------------------------------------------------------------
21
-
22
- const EXIT_OK = 0;
23
- const EXIT_USER_ERROR = 1;
24
- const EXIT_MISSING_DEP = 2;
25
-
26
- function die(message, code = EXIT_USER_ERROR) {
27
- console.error(`error: ${message}`);
28
- process.exit(code);
29
- }
30
-
31
- function gh(args, { quiet = false } = {}) {
32
- try {
33
- return execFileSync('gh', args, {
34
- encoding: 'utf8',
35
- stdio: ['pipe', 'pipe', 'pipe'],
36
- }).trim();
37
- } catch (err) {
38
- const stderr = err.stderr?.toString().trim() || '';
39
- if (stderr.includes('auth login')) {
40
- die('gh auth expired -- run: gh auth login', EXIT_MISSING_DEP);
41
- }
42
- if (quiet) {
43
- // Exit code 1 with no stderr = no matching results (expected)
44
- if (err.status === 1 && !stderr) return '';
45
- // Real failure — warn but don't crash
46
- console.error(`warning: gh ${args.slice(0, 2).join(' ')} failed: ${stderr || err.message}`);
47
- return '';
48
- }
49
- throw err;
50
- }
51
- }
52
-
53
- function preflight() {
54
- try {
55
- execFileSync('gh', ['auth', 'status'], { stdio: 'pipe' });
56
- } catch {
57
- die(
58
- 'gh CLI not found or not authenticated.\n Install: https://cli.github.com\n Auth: gh auth login',
59
- EXIT_MISSING_DEP,
60
- );
61
- }
62
- }
63
-
64
- function detectRepo() {
65
- try {
66
- const url = execFileSync('git', ['remote', 'get-url', 'origin'], {
67
- encoding: 'utf8',
68
- stdio: ['pipe', 'pipe', 'pipe'],
69
- }).trim();
70
- const match = url.match(/github\.com[:/](.+?\/.+?)(?:\.git)?$/);
71
- if (match) return match[1];
72
- } catch {}
73
- die('Could not detect GitHub repo. Run from a git repo with a github.com remote.');
74
- }
75
-
76
- function readConfig() {
77
- const configPath = join(process.cwd(), '.openthrottle.yml');
78
- if (!existsSync(configPath)) {
79
- return { baseBranch: 'main', snapshot: 'openthrottle' };
80
- }
81
- let content;
82
- try {
83
- content = readFileSync(configPath, 'utf8');
84
- } catch (err) {
85
- die(`Could not read .openthrottle.yml: ${err.message}`);
86
- }
87
- const get = (key) => {
88
- const match = content.match(new RegExp(`^${key}:\\s*(.+)`, 'm'));
89
- if (!match) return undefined;
90
- return match[1].replace(/#.*$/, '').trim().replace(/^["']|["']$/g, '');
91
- };
92
- return {
93
- baseBranch: get('base_branch') || 'main',
94
- snapshot: get('snapshot') || 'openthrottle',
95
- };
96
- }
97
-
98
- // ---------------------------------------------------------------------------
99
- // 2. Command: ship
100
- // ---------------------------------------------------------------------------
101
-
102
- function cmdShip(args) {
103
- let file = null;
104
- let baseBranch = null;
105
-
106
- for (let i = 0; i < args.length; i++) {
107
- if (args[i] === '--base' && args[i + 1]) {
108
- baseBranch = args[++i];
109
- } else if (!file) {
110
- file = args[i];
111
- }
112
- }
113
-
114
- if (!file) die('Usage: openthrottle ship <file.md> [--base <branch>]');
115
-
116
- file = resolve(file);
117
- if (!existsSync(file)) die(`File not found: ${file}`);
118
- if (!file.endsWith('.md')) die(`Expected a markdown file, got: ${file}`);
119
-
120
- const config = readConfig();
121
- const base = baseBranch || config.baseBranch;
122
- const repo = detectRepo();
123
-
124
- // Extract title from first markdown heading
125
- const content = readFileSync(file, 'utf8');
126
- const headingMatch = content.match(/^#{1,6}\s+(.+)/m);
127
- let title = headingMatch ? headingMatch[1].trim() : file.replace(/\.md$/, '');
128
- if (!title.startsWith('PRD:')) title = `PRD: ${title}`;
129
-
130
- // Ensure labels exist (idempotent)
131
- const labels = [
132
- 'prd-queued', 'prd-running', 'prd-complete', 'prd-failed',
133
- 'needs-review', 'reviewing',
134
- 'bug-queued', 'bug-running', 'bug-complete', 'bug-failed',
135
- ];
136
- for (const label of labels) {
137
- try {
138
- gh(['label', 'create', label, '--repo', repo, '--force']);
139
- } catch (err) {
140
- const stderr = err.stderr?.toString().trim() || err.message;
141
- console.error(`warning: failed to create label "${label}": ${stderr}`);
142
- }
143
- }
144
-
145
- // Build label list
146
- let issueLabels = 'prd-queued';
147
- if (base !== 'main') issueLabels += `,base:${base}`;
148
-
149
- // Create the issue
150
- let issueUrl;
151
- try {
152
- issueUrl = gh([
153
- 'issue', 'create',
154
- '--repo', repo,
155
- '--title', title,
156
- '--body-file', file,
157
- '--label', issueLabels,
158
- ]);
159
- } catch (err) {
160
- const msg = err.stderr?.toString().trim() || err.message;
161
- die(`Failed to create issue: ${msg}`);
162
- }
163
-
164
- // Show queue position
165
- let queueCount = 0;
166
- try {
167
- const raw = gh([
168
- 'issue', 'list', '--repo', repo,
169
- '--label', 'prd-queued', '--state', 'open',
170
- '--json', 'number', '--jq', 'length',
171
- ]);
172
- queueCount = parseInt(raw, 10) || 0;
173
- } catch {}
174
-
175
- let runningInfo = '';
176
- try {
177
- runningInfo = gh([
178
- 'issue', 'list', '--repo', repo,
179
- '--label', 'prd-running', '--state', 'open',
180
- '--json', 'number,title',
181
- '--jq', '.[0] | "#\\(.number) -- \\(.title)"',
182
- ]);
183
- } catch {}
184
-
185
- console.log(`Shipped: ${issueUrl}`);
186
- if (queueCount > 1) {
187
- console.log(`Queue: ${queueCount} queued`);
188
- } else {
189
- console.log('Status: starting');
190
- }
191
- if (runningInfo) {
192
- console.log(`Running: ${runningInfo}`);
193
- }
194
- }
195
-
196
- // ---------------------------------------------------------------------------
197
- // 3. Command: status
198
- // ---------------------------------------------------------------------------
199
-
200
- function cmdStatus() {
201
- const repo = detectRepo();
202
-
203
- console.log('RUNNING');
204
- const running = gh([
205
- 'issue', 'list', '--repo', repo,
206
- '--label', 'prd-running', '--state', 'open',
207
- '--json', 'number,title',
208
- '--jq', '.[] | " #\\(.number) -- \\(.title)"',
209
- ], { quiet: true });
210
- console.log(running || ' (none)');
211
-
212
- console.log('\nQUEUE');
213
- const queued = gh([
214
- 'issue', 'list', '--repo', repo,
215
- '--label', 'prd-queued', '--state', 'open',
216
- '--json', 'number,title',
217
- '--jq', '.[] | " #\\(.number) -- \\(.title)"',
218
- ], { quiet: true });
219
- console.log(queued || ' (none)');
220
-
221
- console.log('\nREVIEW');
222
- const pending = gh([
223
- 'pr', 'list', '--repo', repo,
224
- '--label', 'needs-review',
225
- '--json', 'number,title',
226
- '--jq', '.[] | " pending: #\\(.number) -- \\(.title)"',
227
- ], { quiet: true });
228
- const reviewing = gh([
229
- 'pr', 'list', '--repo', repo,
230
- '--label', 'reviewing',
231
- '--json', 'number,title',
232
- '--jq', '.[] | " active: #\\(.number) -- \\(.title)"',
233
- ], { quiet: true });
234
- const fixes = gh([
235
- 'pr', 'list', '--repo', repo,
236
- '--search', 'review:changes_requested',
237
- '--json', 'number,title',
238
- '--jq', '.[] | " fixes: #\\(.number) -- \\(.title)"',
239
- ], { quiet: true });
240
- const reviewOutput = [pending, reviewing, fixes].filter(Boolean).join('\n');
241
- console.log(reviewOutput || ' (none)');
242
-
243
- console.log('\nCOMPLETED (recent)');
244
- const completed = gh([
245
- 'issue', 'list', '--repo', repo,
246
- '--label', 'prd-complete', '--state', 'closed',
247
- '--limit', '5',
248
- '--json', 'number,title',
249
- '--jq', '.[] | " #\\(.number) -- \\(.title)"',
250
- ], { quiet: true });
251
- console.log(completed || ' (none)');
252
- }
253
-
254
- // ---------------------------------------------------------------------------
255
- // 4. Command: logs
256
- // ---------------------------------------------------------------------------
257
-
258
- function cmdLogs() {
259
- const repo = detectRepo();
260
-
261
- let output;
262
- try {
263
- output = gh([
264
- 'run', 'list',
265
- '--repo', repo,
266
- '--workflow', 'Wake Sandbox',
267
- '--limit', '10',
268
- ]);
269
- } catch {
270
- try {
271
- output = gh([
272
- 'run', 'list',
273
- '--repo', repo,
274
- '--limit', '10',
275
- ]);
276
- } catch (err) {
277
- die(`Failed to list workflow runs: ${err.stderr?.toString().trim() || err.message}`);
278
- }
279
- }
280
-
281
- if (!output) {
282
- console.log('No workflow runs found.');
283
- return;
284
- }
285
-
286
- console.log(output);
287
- }
288
-
289
- // ---------------------------------------------------------------------------
290
- // 5. Main
291
- // ---------------------------------------------------------------------------
292
-
293
- const HELP = `Usage: openthrottle <command>
294
-
295
- Commands:
296
- init Set up Open Throttle in your project
297
- ship <file.md> [--base <branch>] Create a GitHub issue to trigger a sandbox
298
- status Show running, queued, and completed tasks
299
- logs Show recent GitHub Actions workflow runs
300
-
301
- Options:
302
- --help, -h Show this help message
303
- --version, -v Show version`;
304
-
305
- async function main() {
306
- const args = process.argv.slice(2);
307
- const command = args[0];
308
-
309
- if (!command || command === '--help' || command === '-h') {
310
- console.log(HELP);
311
- process.exit(EXIT_OK);
312
- }
313
-
314
- if (command === '--version' || command === '-v') {
315
- const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
316
- console.log(pkg.version);
317
- process.exit(EXIT_OK);
318
- }
319
-
320
- if (command === 'init') {
321
- const { default: init } = await import('./init.mjs');
322
- await init();
323
- return;
324
- }
325
-
326
- preflight();
327
-
328
- switch (command) {
329
- case 'ship':
330
- cmdShip(args.slice(1));
331
- break;
332
- case 'status':
333
- cmdStatus();
334
- break;
335
- case 'logs':
336
- cmdLogs();
337
- break;
338
- default:
339
- die(`Unknown command: ${command}\n Run "openthrottle --help" for usage.`);
340
- }
341
- }
342
-
343
- main().catch((err) => {
344
- console.error(`error: ${err.message}`);
345
- process.exit(1);
346
- });