@stupify/cli 0.0.16 → 0.1.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/.review/CORPUS.md +73 -0
- package/.review/REVIEW-PROMPT.md +52 -0
- package/.review/RUBRIC.md +46 -0
- package/LICENSE +1 -1
- package/README.md +41 -39
- package/package.json +24 -25
- package/src/cli.ts +358 -0
- package/src/review-sweep.ts +492 -0
- package/dist/analysis.d.ts +0 -16
- package/dist/analysis.js +0 -168
- package/dist/cache.d.ts +0 -2
- package/dist/cache.js +0 -57
- package/dist/checks.d.ts +0 -4
- package/dist/checks.js +0 -228
- package/dist/command.d.ts +0 -2
- package/dist/command.js +0 -147
- package/dist/constants.d.ts +0 -4
- package/dist/constants.js +0 -53
- package/dist/counter-scout.d.ts +0 -21
- package/dist/counter-scout.js +0 -167
- package/dist/diff.d.ts +0 -1
- package/dist/diff.js +0 -10
- package/dist/doctor.d.ts +0 -16
- package/dist/doctor.js +0 -143
- package/dist/git.d.ts +0 -17
- package/dist/git.js +0 -368
- package/dist/hooks.d.ts +0 -5
- package/dist/hooks.js +0 -135
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/model.d.ts +0 -11
- package/dist/model.js +0 -296
- package/dist/prompts.d.ts +0 -8
- package/dist/prompts.js +0 -89
- package/dist/render.d.ts +0 -6
- package/dist/render.js +0 -295
- package/dist/repomix-provider.d.ts +0 -12
- package/dist/repomix-provider.js +0 -196
- package/dist/search-bench.d.ts +0 -1
- package/dist/search-bench.js +0 -677
- package/dist/search-profile.d.ts +0 -6
- package/dist/search-profile.js +0 -73
- package/dist/sem-provider.d.ts +0 -2
- package/dist/sem-provider.js +0 -255
- package/dist/stupify.d.ts +0 -38
- package/dist/stupify.js +0 -505
- package/dist/trace.d.ts +0 -31
- package/dist/trace.js +0 -86
- package/dist/types.d.ts +0 -341
- package/dist/types.js +0 -6
- package/dist/ui.d.ts +0 -34
- package/dist/ui.js +0 -143
- package/src/analysis.ts +0 -223
- package/src/cache.ts +0 -63
- package/src/checks.ts +0 -231
- package/src/command.ts +0 -173
- package/src/constants.ts +0 -56
- package/src/counter-scout.ts +0 -195
- package/src/diff.ts +0 -9
- package/src/doctor.ts +0 -166
- package/src/git.ts +0 -380
- package/src/hooks.ts +0 -151
- package/src/index.ts +0 -1
- package/src/model.ts +0 -367
- package/src/prompts.ts +0 -100
- package/src/render.ts +0 -328
- package/src/repomix-provider.ts +0 -219
- package/src/search-bench.ts +0 -783
- package/src/search-profile.ts +0 -89
- package/src/sem-provider.ts +0 -300
- package/src/stupify.ts +0 -604
- package/src/trace.ts +0 -126
- package/src/types.ts +0 -362
- package/src/ui.ts +0 -187
package/src/cli.ts
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* stupify — a code reviewer that talks like an idiot and catches real bugs.
|
|
4
|
+
*
|
|
5
|
+
* `stupify` (no args) → the interactive setup wizard: checks your tools, finds your repo, asks for your
|
|
6
|
+
* exe.dev integration, and installs the cron sweep. On exe.dev there are no creds to
|
|
7
|
+
* manage (Codex → exe-llm gateway, gh → your GitHub integration).
|
|
8
|
+
* `stupify run [--dry]` → run one review sweep right now.
|
|
9
|
+
*/
|
|
10
|
+
import { spawnSync } from 'node:child_process'
|
|
11
|
+
import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
12
|
+
import { homedir } from 'node:os'
|
|
13
|
+
import { dirname, join } from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
import { cancel, confirm, intro, isCancel, log, note, outro, spinner, text } from '@clack/prompts'
|
|
16
|
+
import pc from 'picocolors'
|
|
17
|
+
|
|
18
|
+
const PKG_DIR = dirname(fileURLToPath(import.meta.url))
|
|
19
|
+
const HOME = process.env.STUPIFY_HOME ?? join(homedir(), '.stupify')
|
|
20
|
+
const STATE = join(HOME, 'state')
|
|
21
|
+
const REQUIRED = ['bun', 'gh', 'codex', 'git'] as const
|
|
22
|
+
|
|
23
|
+
function bail<T>(value: T | symbol): asserts value is T {
|
|
24
|
+
if (isCancel(value)) {
|
|
25
|
+
cancel('aborted.')
|
|
26
|
+
process.exit(0)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function which(bin: string): string | null {
|
|
31
|
+
return Bun.which(bin)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// A bun path the cron can rely on. Under `bunx`, the running bun lives in an EPHEMERAL /tmp/bun-node-… dir
|
|
35
|
+
// that's deleted after install — so never bake that into the crontab. Prefer a stable install location.
|
|
36
|
+
function stableBun(): string {
|
|
37
|
+
const running = which('bun')
|
|
38
|
+
if (running && !running.includes('/bun-node-') && !running.startsWith('/tmp/')) return running
|
|
39
|
+
for (const c of [join(homedir(), '.bun/bin/bun'), '/usr/local/bin/bun', '/usr/bin/bun']) {
|
|
40
|
+
if (existsSync(c)) return c
|
|
41
|
+
}
|
|
42
|
+
return running ?? 'bun'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function detectRepo(): string | null {
|
|
46
|
+
const r = spawnSync('git', ['config', '--get', 'remote.origin.url'], { encoding: 'utf8' })
|
|
47
|
+
if (r.status !== 0) return null
|
|
48
|
+
const slug = (r.stdout ?? '')
|
|
49
|
+
.trim()
|
|
50
|
+
.replace(/^[a-z]+:\/\/[^/]+\//, '')
|
|
51
|
+
.replace(/^git@[^:]+:/, '')
|
|
52
|
+
.replace(/\.git$/, '')
|
|
53
|
+
return /^[^/]+\/[^/]+$/.test(slug) ? slug : null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function installCron(opts: { ghHost: string }): string {
|
|
57
|
+
const bun = stableBun()
|
|
58
|
+
const prefix = opts.ghHost ? `GH_HOST=${opts.ghHost} ` : ''
|
|
59
|
+
// No flock — the sweep self-locks (state/sweep.lock), so overlapping cron ticks no-op on their own.
|
|
60
|
+
const line = `*/1 * * * * ${prefix}${bun} ${join(HOME, 'review-sweep.ts')} >> ${STATE}/cron.log 2>&1`
|
|
61
|
+
const current = spawnSync('crontab', ['-l'], { encoding: 'utf8' }).stdout ?? ''
|
|
62
|
+
const kept = current
|
|
63
|
+
.split('\n')
|
|
64
|
+
.filter((l) => l.trim() && !l.includes('review-sweep.ts'))
|
|
65
|
+
const next = [...kept, line].join('\n') + '\n'
|
|
66
|
+
const wrote = spawnSync('crontab', ['-'], { input: next })
|
|
67
|
+
if (wrote.status !== 0) throw new Error('could not write crontab')
|
|
68
|
+
return line
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function setup(argv: { repo?: string; host?: string; yes: boolean }): Promise<void> {
|
|
72
|
+
console.clear()
|
|
73
|
+
intro(pc.bgMagenta(pc.black(' stupify ')) + pc.dim(' — sounds dumb, reviews sharp'))
|
|
74
|
+
|
|
75
|
+
// 1. tools
|
|
76
|
+
const s = spinner()
|
|
77
|
+
s.start('checking your tools')
|
|
78
|
+
const missing = REQUIRED.filter((b) => !which(b))
|
|
79
|
+
if (missing.length) {
|
|
80
|
+
s.stop(pc.red(`missing: ${missing.join(', ')}`))
|
|
81
|
+
note(
|
|
82
|
+
`install them first:\n bun → ${pc.cyan('bun.sh')}\n gh → ${pc.cyan('cli.github.com')}\n codex → ${pc.cyan('github.com/openai/codex')}`,
|
|
83
|
+
'missing tools',
|
|
84
|
+
)
|
|
85
|
+
process.exit(1)
|
|
86
|
+
}
|
|
87
|
+
s.stop(pc.green('bun, gh, codex, git') + pc.dim(' — all here'))
|
|
88
|
+
|
|
89
|
+
// 2. repo (auto-detect, else ask)
|
|
90
|
+
let repo = argv.repo ?? ''
|
|
91
|
+
if (!repo) {
|
|
92
|
+
const detected = detectRepo()
|
|
93
|
+
if (detected) {
|
|
94
|
+
if (argv.yes) {
|
|
95
|
+
repo = detected
|
|
96
|
+
log.success(`repo ${pc.bold(detected)} ${pc.dim('(from this checkout)')}`)
|
|
97
|
+
} else {
|
|
98
|
+
const keep = await confirm({ message: `Review ${pc.bold(detected)}? ${pc.dim('(detected from git remote)')}` })
|
|
99
|
+
bail(keep)
|
|
100
|
+
if (keep) repo = detected
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!repo) {
|
|
105
|
+
const answer = await text({
|
|
106
|
+
message: 'GitHub repo to review',
|
|
107
|
+
placeholder: 'owner/repo',
|
|
108
|
+
validate: (v) => (/^[^/]+\/[^/]+$/.test((v ?? '').trim()) ? undefined : 'expected owner/repo'),
|
|
109
|
+
})
|
|
110
|
+
bail(answer)
|
|
111
|
+
repo = answer.trim()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 3. integration host (exe.dev) — can't be detected
|
|
115
|
+
let host = argv.host ?? process.env.GH_HOST ?? ''
|
|
116
|
+
if (!host && !argv.yes) {
|
|
117
|
+
const answer = await text({
|
|
118
|
+
message: 'exe.dev integration host',
|
|
119
|
+
placeholder: 'your-integration.int.exe.xyz',
|
|
120
|
+
defaultValue: '',
|
|
121
|
+
})
|
|
122
|
+
bail(answer)
|
|
123
|
+
host = answer.trim()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 4. plan + confirm
|
|
127
|
+
note(
|
|
128
|
+
[
|
|
129
|
+
`${pc.dim('repo ')} ${pc.bold(repo)}`,
|
|
130
|
+
host
|
|
131
|
+
? `${pc.dim('auth ')} exe.dev integration ${pc.bold(host)} ${pc.dim('— exe-llm gateway, no keys')}`
|
|
132
|
+
: `${pc.dim('auth ')} your own gh + codex ${pc.dim('(run `gh auth login` first)')}`,
|
|
133
|
+
`${pc.dim('cadence')} every ~60s via cron`,
|
|
134
|
+
`${pc.dim('home ')} ${HOME}`,
|
|
135
|
+
].join('\n'),
|
|
136
|
+
'plan',
|
|
137
|
+
)
|
|
138
|
+
if (!argv.yes) {
|
|
139
|
+
const go = await confirm({ message: 'Set it up?' })
|
|
140
|
+
bail(go)
|
|
141
|
+
if (!go) {
|
|
142
|
+
cancel('aborted.')
|
|
143
|
+
process.exit(0)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 5. install
|
|
148
|
+
const s2 = spinner()
|
|
149
|
+
s2.start('installing')
|
|
150
|
+
mkdirSync(STATE, { recursive: true })
|
|
151
|
+
copyFileSync(join(PKG_DIR, 'review-sweep.ts'), join(HOME, 'review-sweep.ts'))
|
|
152
|
+
const cfg = [`REPO_SLUG=${repo}`, host ? `GH_HOST=${host}` : '', '# tune anything else here — see the README']
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.join('\n')
|
|
155
|
+
writeFileSync(join(HOME, 'config.env'), cfg + '\n')
|
|
156
|
+
installCron({ ghHost: host })
|
|
157
|
+
s2.stop(pc.green('installed') + pc.dim(` → ${HOME}`))
|
|
158
|
+
|
|
159
|
+
// 6. success + the two steps to a first review
|
|
160
|
+
note(
|
|
161
|
+
[
|
|
162
|
+
`${pc.bold('1.')} give it your taste — add a ${pc.cyan('.review/')} dir to ${pc.bold(repo)}`,
|
|
163
|
+
` (copy this repo's ${pc.cyan('.review/')} and point ${pc.cyan('CORPUS.md')} at YOUR best files)`,
|
|
164
|
+
`${pc.bold('2.')} label any open PR ${pc.cyan('codex-review')} ${pc.dim('(or add .github/workflows/autolabel.yml)')}`,
|
|
165
|
+
``,
|
|
166
|
+
`${pc.dim('→ a review lands within ~60s. preview anytime:')} ${pc.cyan(`DRY_RUN=1 bun ${join(HOME, 'review-sweep.ts')}`)}`,
|
|
167
|
+
].join('\n'),
|
|
168
|
+
'two steps to your first review',
|
|
169
|
+
)
|
|
170
|
+
outro(pc.green('stupify is watching ') + pc.bold(repo) + pc.green(' 👀'))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function run(dry: boolean): void {
|
|
174
|
+
const sweep = join(HOME, 'review-sweep.ts')
|
|
175
|
+
if (!Bun.file(sweep).size) {
|
|
176
|
+
log.error(`not set up yet — run ${pc.cyan('stupify')} first`)
|
|
177
|
+
process.exit(1)
|
|
178
|
+
}
|
|
179
|
+
const env = { ...process.env, ...(dry ? { DRY_RUN: '1' } : {}) }
|
|
180
|
+
const r = spawnSync('bun', [sweep], { stdio: 'inherit', env })
|
|
181
|
+
process.exit(r.status ?? 1)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --- provision: spin up an exe.dev VM that runs stupify, from your laptop ---
|
|
185
|
+
|
|
186
|
+
function exe(args: string[], input = ''): { ok: boolean; out: string } {
|
|
187
|
+
const r = spawnSync('ssh', ['-o', 'ConnectTimeout=25', 'exe.dev', ...args], {
|
|
188
|
+
input,
|
|
189
|
+
encoding: 'utf8',
|
|
190
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
191
|
+
})
|
|
192
|
+
return { ok: r.status === 0, out: (r.stdout ?? '') + (r.stderr ?? '') }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function githubIntegrationFor(repo: string): string | null {
|
|
196
|
+
const r = exe(['int', 'list', '--json'])
|
|
197
|
+
if (!r.ok) return null
|
|
198
|
+
try {
|
|
199
|
+
const list: { name: string; type: string; config?: { repositories?: string[] } }[] = JSON.parse(r.out)
|
|
200
|
+
return list.find((i) => i.type === 'github' && (i.config?.repositories ?? []).includes(repo))?.name ?? null
|
|
201
|
+
} catch {
|
|
202
|
+
return null
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const vmNameFor = (repo: string): string => 'stupify-' + repo.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
|
|
207
|
+
|
|
208
|
+
async function provision(argv: { repo?: string; yes: boolean }): Promise<void> {
|
|
209
|
+
console.clear()
|
|
210
|
+
intro(pc.bgMagenta(pc.black(' stupify ')) + pc.dim(' — provision a reviewer on exe.dev'))
|
|
211
|
+
|
|
212
|
+
// 1. onboarded to exe.dev?
|
|
213
|
+
const s = spinner()
|
|
214
|
+
s.start('checking exe.dev')
|
|
215
|
+
const who = exe(['whoami'])
|
|
216
|
+
if (!who.ok) {
|
|
217
|
+
s.stop(pc.red('not connected to exe.dev'))
|
|
218
|
+
note(`onboarding is one step — run this once, then re-run stupify:\n\n ${pc.cyan('ssh exe.dev')}`, 'connect exe.dev')
|
|
219
|
+
process.exit(1)
|
|
220
|
+
}
|
|
221
|
+
s.stop(pc.green('exe.dev ready') + pc.dim(` — ${(who.out.match(/[\w.+-]+@[\w.-]+/) ?? [''])[0]}`))
|
|
222
|
+
|
|
223
|
+
// 2. repo (auto-detect, else ask)
|
|
224
|
+
let repo = argv.repo ?? ''
|
|
225
|
+
if (!repo) {
|
|
226
|
+
const detected = detectRepo()
|
|
227
|
+
if (detected) {
|
|
228
|
+
if (argv.yes) repo = detected
|
|
229
|
+
else {
|
|
230
|
+
const keep = await confirm({ message: `Review ${pc.bold(detected)}? ${pc.dim('(detected from git remote)')}` })
|
|
231
|
+
bail(keep)
|
|
232
|
+
if (keep) repo = detected
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (!repo) {
|
|
237
|
+
const answer = await text({
|
|
238
|
+
message: 'GitHub repo to review',
|
|
239
|
+
placeholder: 'owner/repo',
|
|
240
|
+
validate: (v) => (/^[^/]+\/[^/]+$/.test((v ?? '').trim()) ? undefined : 'expected owner/repo'),
|
|
241
|
+
})
|
|
242
|
+
bail(answer)
|
|
243
|
+
repo = answer.trim()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 3. GitHub integration — reuse an existing one, else create it (needs your GitHub linked once, on the web)
|
|
247
|
+
const s2 = spinner()
|
|
248
|
+
s2.start('finding your GitHub integration')
|
|
249
|
+
let integration = githubIntegrationFor(repo)
|
|
250
|
+
if (integration) {
|
|
251
|
+
s2.stop(pc.green(`using integration ${pc.bold(integration)}`))
|
|
252
|
+
} else {
|
|
253
|
+
const name = vmNameFor(repo)
|
|
254
|
+
const add = exe(['integrations', 'add', 'github', '--name', name, '--repository', repo])
|
|
255
|
+
if (add.ok) {
|
|
256
|
+
integration = name
|
|
257
|
+
s2.stop(pc.green(`created integration ${pc.bold(name)}`))
|
|
258
|
+
} else {
|
|
259
|
+
s2.stop(pc.red(`no GitHub integration for ${repo}`))
|
|
260
|
+
note(`link your GitHub account once (web), then re-run stupify:\n\n ${pc.cyan('https://exe.dev/integrations')}\n\n${pc.dim(add.out.trim().slice(0, 200))}`, 'connect GitHub')
|
|
261
|
+
process.exit(1)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const host = `${integration}.int.exe.xyz`
|
|
265
|
+
|
|
266
|
+
// 4. plan + confirm
|
|
267
|
+
note(
|
|
268
|
+
[
|
|
269
|
+
`${pc.dim('repo')} ${pc.bold(repo)}`,
|
|
270
|
+
`${pc.dim('vm ')} a small always-on exe.dev VM on your account`,
|
|
271
|
+
`${pc.dim('auth')} integration ${pc.bold(integration)} ${pc.dim('— no keys, no tokens')}`,
|
|
272
|
+
].join('\n'),
|
|
273
|
+
'plan',
|
|
274
|
+
)
|
|
275
|
+
if (!argv.yes) {
|
|
276
|
+
const go = await confirm({ message: 'Provision it?' })
|
|
277
|
+
bail(go)
|
|
278
|
+
if (!go) {
|
|
279
|
+
cancel('aborted.')
|
|
280
|
+
process.exit(0)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 5. create the VM with a first-boot setup-script that installs stupify
|
|
285
|
+
const s3 = spinner()
|
|
286
|
+
s3.start('provisioning VM + installing stupify')
|
|
287
|
+
const vm = vmNameFor(repo)
|
|
288
|
+
const script = [
|
|
289
|
+
'export PATH="$HOME/.bun/bin:/usr/local/bin:$PATH"',
|
|
290
|
+
'command -v bun >/dev/null 2>&1 || curl -fsSL https://bun.sh/install | bash',
|
|
291
|
+
'export PATH="$HOME/.bun/bin:$PATH"',
|
|
292
|
+
`exec bunx github:Octember/stupif.ai setup ${repo} --host ${host} --yes`,
|
|
293
|
+
].join('\n')
|
|
294
|
+
const created = exe(['new', '--name', vm, '--integration', integration, '--json', '--setup-script', '/dev/stdin'], script)
|
|
295
|
+
if (!created.ok) {
|
|
296
|
+
s3.stop(pc.red('provision failed'))
|
|
297
|
+
log.error(created.out.trim().slice(0, 400))
|
|
298
|
+
process.exit(1)
|
|
299
|
+
}
|
|
300
|
+
let dest = `${vm}.exe.xyz`
|
|
301
|
+
try {
|
|
302
|
+
dest = (JSON.parse(created.out) as { ssh_dest?: string }).ssh_dest ?? dest
|
|
303
|
+
} catch {
|
|
304
|
+
/* keep the derived dest */
|
|
305
|
+
}
|
|
306
|
+
s3.stop(pc.green(`VM ${pc.bold(vm)} created`) + pc.dim(` (${dest})`))
|
|
307
|
+
|
|
308
|
+
// 6. success
|
|
309
|
+
note(
|
|
310
|
+
[
|
|
311
|
+
`${pc.bold(vm)} is booting and installing stupify ${pc.dim('(~15s)')}.`,
|
|
312
|
+
``,
|
|
313
|
+
`${pc.bold('1.')} add a ${pc.cyan('.review/')} dir to ${pc.bold(repo)} — copy this repo's .review/, point CORPUS.md at YOUR best files`,
|
|
314
|
+
`${pc.bold('2.')} label any open PR ${pc.cyan('codex-review')} ${pc.dim('(or add .github/workflows/autolabel.yml)')}`,
|
|
315
|
+
``,
|
|
316
|
+
`${pc.dim('watch:')} ${pc.cyan(`ssh ${dest} 'tail -f ~/.stupify/state/sweep.log'`)}`,
|
|
317
|
+
`${pc.dim('stop: ')} ${pc.cyan(`ssh exe.dev rm ${vm}`)}`,
|
|
318
|
+
].join('\n'),
|
|
319
|
+
'done',
|
|
320
|
+
)
|
|
321
|
+
outro(pc.green('stupify is provisioned for ') + pc.bold(repo) + pc.green(' 👀'))
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function help(): void {
|
|
325
|
+
console.log(`${pc.bold('stupify')} — a code reviewer that talks like an idiot and catches real bugs
|
|
326
|
+
|
|
327
|
+
${pc.dim('Usage')} ${pc.dim('(run from your laptop)')}
|
|
328
|
+
stupify provision an exe.dev VM that reviews your repo ${pc.dim('(the magic)')}
|
|
329
|
+
stupify <owner/repo> provision for a specific repo
|
|
330
|
+
stupify setup [repo] install on THIS machine instead of provisioning a VM
|
|
331
|
+
stupify run [--dry] run one review sweep now (where stupify is installed)
|
|
332
|
+
stupify --help
|
|
333
|
+
|
|
334
|
+
${pc.dim('Flags')}
|
|
335
|
+
--host <h.int.exe.xyz> integration host (for 'setup')
|
|
336
|
+
--yes, -y accept detected defaults, no prompts (for CI / scripts)
|
|
337
|
+
|
|
338
|
+
${pc.dim("Provisioning rides exe.dev — onboard once with 'ssh exe.dev', then one command does the rest.")} https://stupif.ai`)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// --- routing ---
|
|
342
|
+
const args = process.argv.slice(2)
|
|
343
|
+
const yes = args.includes('--yes') || args.includes('-y')
|
|
344
|
+
const hostIdx = args.indexOf('--host')
|
|
345
|
+
const host = hostIdx >= 0 ? args[hostIdx + 1] : undefined
|
|
346
|
+
const positional = args.filter((a, i) => !a.startsWith('-') && args[i - 1] !== '--host')
|
|
347
|
+
const cmd = positional[0]
|
|
348
|
+
|
|
349
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
350
|
+
help()
|
|
351
|
+
} else if (cmd === 'run') {
|
|
352
|
+
run(args.includes('--dry'))
|
|
353
|
+
} else if (cmd === 'setup') {
|
|
354
|
+
await setup({ repo: positional[1], host, yes })
|
|
355
|
+
} else {
|
|
356
|
+
// default (and explicit `provision`): provision an exe.dev VM
|
|
357
|
+
await provision({ repo: cmd === 'provision' ? positional[1] : cmd, yes })
|
|
358
|
+
}
|