@orkestrel/scaffold 0.0.107 → 0.0.109
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/agents/skills/orkestrel-dispatch/scripts/bench.js +326 -91
- package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +110 -14
- package/dist/agents/skills/orkestrel-dispatch/scripts/chain.js +280 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +188 -26
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +142 -43
- package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +240 -62
- package/dist/agents/skills/orkestrel-dispatch/scripts/patch.js +323 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +169 -32
- package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +368 -27
- package/dist/agents/skills/orkestrel-falsify/scripts/mutate.js +560 -0
- package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +1052 -205
- package/dist/agents/skills/orkestrel-harden/scripts/measure.js +361 -0
- package/dist/agents/skills/orkestrel-harden/scripts/titles.js +455 -80
- package/dist/agents/skills/orkestrel-parallel/scripts/cases.js +327 -0
- package/dist/agents/skills/orkestrel-publish/scripts/compare.js +298 -51
- package/dist/agents/skills/orkestrel-publish/scripts/pins.js +170 -13
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +956 -191
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +707 -112
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +235 -41
- package/dist/agents/skills/orkestrel-scout/scripts/map.js +341 -54
- package/dist/agents/templates/brief.md +1 -1
- package/dist/bin/main.js +51 -46
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +26 -20
- package/dist/host/agents/orchestration.md +98 -58
- package/dist/host/agents/platforms/posix.md +15 -0
- package/dist/host/agents/platforms/windows.md +37 -0
- package/dist/host/agents/skills/orkestrel-align/SKILL.md +10 -13
- package/dist/host/agents/skills/orkestrel-align/references/fleet.md +4 -10
- package/dist/host/agents/skills/orkestrel-align/references/integration.md +1 -1
- package/dist/host/agents/skills/orkestrel-build/SKILL.md +16 -48
- package/dist/host/agents/skills/orkestrel-build/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +19 -16
- package/dist/host/agents/skills/orkestrel-debrief/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +15 -28
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +5 -6
- package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +12 -14
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +36 -24
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +14 -11
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +366 -75
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +126 -16
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/chain.ts +323 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +220 -31
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +176 -33
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +294 -59
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/patch.ts +401 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +199 -34
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +428 -27
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +10 -8
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +1 -5
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/scripts/mutate.ts +701 -0
- package/dist/host/agents/skills/orkestrel-harden/SKILL.md +16 -12
- package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +23 -81
- package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +4 -5
- package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/references/document.md +33 -0
- package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +6 -3
- package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +8 -4
- package/dist/host/agents/skills/orkestrel-harden/references/research.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +1272 -279
- package/dist/host/agents/skills/orkestrel-harden/scripts/measure.ts +425 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +603 -98
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +75 -98
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +54 -17
- package/dist/host/agents/skills/orkestrel-journey/references/decide.md +1 -20
- package/dist/host/agents/skills/orkestrel-journey/references/layer.md +21 -31
- package/dist/host/agents/skills/orkestrel-journey/references/styles.md +1 -1
- package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +91 -30
- package/dist/host/agents/skills/orkestrel-parallel/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +34 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/t3.md +31 -0
- package/dist/host/agents/skills/orkestrel-parallel/scripts/cases.ts +389 -0
- package/dist/host/agents/skills/orkestrel-polish/SKILL.md +16 -20
- package/dist/host/agents/skills/orkestrel-polish/references/capture-harness.md +16 -26
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +12 -26
- package/dist/host/agents/skills/orkestrel-publish/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-publish/references/adopt.md +23 -0
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +4 -4
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +12 -10
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +20 -25
- package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +348 -59
- package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +202 -22
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1061 -191
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +812 -140
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +280 -38
- package/dist/host/agents/skills/orkestrel-scout/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +428 -70
- package/dist/host/agents/templates/brief.md +1 -1
- package/dist/host/agents/transports/claude-cli.md +3 -3
- package/dist/host/agents/transports/codex.md +3 -8
- package/dist/host/agents/transports/cursor.md +2 -3
- package/dist/host/claude/AGENTS.md +7 -10
- package/dist/host/claude/agents/analyst.md +3 -3
- package/dist/host/claude/agents/astra.md +3 -3
- package/dist/host/claude/agents/builder.md +1 -1
- package/dist/host/claude/agents/checker.md +1 -1
- package/dist/host/claude/agents/distiller.md +1 -1
- package/dist/host/claude/agents/grok.md +1 -1
- package/dist/host/claude/agents/opus.md +2 -2
- package/dist/host/claude/agents/orkestrel.md +57 -90
- package/dist/host/claude/agents/researcher.md +1 -1
- package/dist/host/claude/agents/reviewer.md +4 -0
- package/dist/host/claude/agents/scout.md +1 -1
- package/dist/host/claude/agents/verifier.md +1 -1
- package/dist/host/claude/rules/application.md +14 -21
- package/dist/host/claude/rules/architecture.md +34 -44
- package/dist/host/claude/rules/browser.md +3 -4
- package/dist/host/claude/rules/campaign.md +55 -0
- package/dist/host/claude/rules/documentation.md +26 -11
- package/dist/host/claude/rules/names.md +9 -4
- package/dist/host/claude/rules/patterns.md +6 -7
- package/dist/host/claude/rules/portability.md +2 -3
- package/dist/host/claude/rules/quality.md +4 -5
- package/dist/host/claude/rules/styles.md +2 -2
- package/dist/host/claude/rules/tests.md +57 -114
- package/dist/host/claude/rules/typescript.md +8 -10
- package/dist/host/claude/rules/workspace.md +21 -19
- package/dist/host/claude/rules/writing.md +3 -3
- package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-harden/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-parallel/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-publish/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-scout/SKILL.md +1 -1
- package/dist/host/codex/agents/analyst.toml +9 -3
- package/dist/host/codex/agents/astra.toml +3 -4
- package/dist/host/codex/agents/builder.toml +1 -1
- package/dist/host/codex/agents/opus.toml +3 -4
- package/dist/host/codex/agents/orkestrel.toml +1 -1
- package/dist/host/codex/agents/researcher.toml +2 -2
- package/dist/host/codex/agents/verifier.toml +1 -1
- package/dist/host/codex/config.toml +3 -3
- package/dist/host/configs/helpers.ts +21 -1
- package/dist/host/configs/policy.ts +405 -16
- package/dist/host/cursor/rules/orchestration.mdc +1 -0
- package/dist/host/dotfiles/oxlintrc.json +141 -5
- package/dist/host/guides/agent.md +111 -134
- package/dist/host/guides/capture.md +801 -0
- package/dist/host/guides/probe.md +27 -8
- package/dist/host/guides/scaffold.md +152 -19
- package/dist/host/guides/study.md +534 -440
- package/dist/host/guides/test.md +111 -44
- package/dist/host/manifest.json +199 -242
- package/dist/host/tests/config.test.ts +1558 -219
- package/dist/host/tests/policy.test.ts +127 -1
- package/dist/host/tests/setupPolicy.ts +1389 -39
- package/dist/src/core/index.cjs +239 -87
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +123 -39
- package/dist/src/core/index.d.ts +123 -39
- package/dist/src/core/index.js +234 -86
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +20 -27
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +21 -28
- package/dist/src/server/index.js.map +1 -1
- package/package.json +11 -11
- package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +0 -283
- package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +0 -25
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +0 -333
- package/dist/host/agents/skills/orkestrel-parallel/references/harnesses.md +0 -19
- package/dist/host/guides/supervisor.md +0 -4890
|
@@ -1,40 +1,201 @@
|
|
|
1
1
|
// Recover a bench login. Run from the checkout root:
|
|
2
2
|
// node .agents/skills/orkestrel-dispatch/scripts/login.ts --codex [--cap SECONDS]
|
|
3
3
|
// node .agents/skills/orkestrel-dispatch/scripts/login.ts --claude
|
|
4
|
-
// --codex reads `codex login status`;
|
|
5
|
-
// `codex login --device-auth` with its output journaled to
|
|
6
|
-
// verification URL and the one-time code the moment the journal
|
|
7
|
-
// `codex login status` every 5 seconds until it answers or the cap (default 600
|
|
8
|
-
// kills the login by its pid.
|
|
9
|
-
//
|
|
4
|
+
// --codex reads `codex login status`; a Codex that cannot start reports live false at once. When the
|
|
5
|
+
// status reports no login, the script runs `codex login --device-auth` with its output journaled to
|
|
6
|
+
// tmp/codex/login.log, prints the verification URL and the one-time code the moment the journal
|
|
7
|
+
// carries them, polls `codex login status` every 5 seconds until it answers or the cap (default 600
|
|
8
|
+
// seconds) ends, then kills the login by its pid. Each status read gets the time left to the cap
|
|
9
|
+
// and is ended when it passes. --cap is a number of seconds above 0 and at most 2147483, checked
|
|
10
|
+
// before anything starts.
|
|
11
|
+
// --claude reads `claude auth status` and prints it. Name one bench per run; the script refuses a
|
|
12
|
+
// flag it does not read, the `--flag=value` form included. The JSON summary carries `live`; exit 0
|
|
13
|
+
// when the bench is logged in, 3 when it is not, 64 on usage.
|
|
10
14
|
import { spawn, spawnSync } from 'node:child_process'
|
|
11
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync } from 'node:fs'
|
|
12
|
-
import {
|
|
15
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync } from 'node:fs'
|
|
16
|
+
import { dirname } from 'node:path'
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const URL_PATTERN = /https?:\/\/[^\s"'<>]+/u
|
|
17
|
-
const CODE_PATTERN = /\b([A-Z0-9]{4,5}-[A-Z0-9]{4,5})\b/u
|
|
18
|
+
/** Names the file that journals the device-auth login output. */
|
|
19
|
+
export const JOURNAL = 'tmp/codex/login.log'
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
/** Holds the milliseconds between two reads of the login status. */
|
|
22
|
+
export const POLL_MS = 5_000
|
|
23
|
+
|
|
24
|
+
/** Holds the seconds the poll runs when the call gives no cap. */
|
|
25
|
+
export const DEFAULT_CAP = 600
|
|
26
|
+
|
|
27
|
+
/** Holds the longest cap in whole seconds, so the cap in milliseconds stays a finite timer value. */
|
|
28
|
+
export const MAX_CAP_SECONDS = 2_147_483
|
|
29
|
+
|
|
30
|
+
/** Holds the fewest milliseconds one status read gets, so a read that starts at the cap can still answer. */
|
|
31
|
+
export const STATUS_GRACE_MS = 1_000
|
|
32
|
+
|
|
33
|
+
/** Matches the verification URL the login prints. */
|
|
34
|
+
export const URL_PATTERN = /https?:\/\/[^\s"'<>]+/u
|
|
35
|
+
|
|
36
|
+
/** Matches the one-time code the login prints, such as `ABCD-12345`. */
|
|
37
|
+
export const CODE_PATTERN = /\b([A-Z0-9]{4,5}-[A-Z0-9]{4,5})\b/u
|
|
38
|
+
|
|
39
|
+
/** Lists the flags that take a value. */
|
|
40
|
+
export const VALUE_FLAGS: readonly string[] = ['--cap']
|
|
41
|
+
|
|
42
|
+
/** Lists every flag the script reads, value flags and switches alike. */
|
|
43
|
+
export const FLAGS: readonly string[] = [...VALUE_FLAGS, '--codex', '--claude']
|
|
44
|
+
|
|
45
|
+
/** Describes whether a bench's status command started, whether it reports a login, and its text. */
|
|
46
|
+
export interface Status {
|
|
47
|
+
readonly started: boolean
|
|
20
48
|
readonly live: boolean
|
|
21
49
|
readonly text: string
|
|
22
50
|
}
|
|
23
51
|
|
|
24
|
-
|
|
52
|
+
/** Describes what one poll of the login saw: the URL, the code, and whether the bench is logged in. */
|
|
53
|
+
export interface Poll {
|
|
25
54
|
readonly url: string | undefined
|
|
26
55
|
readonly code: string | undefined
|
|
27
56
|
readonly live: boolean
|
|
28
57
|
}
|
|
29
58
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
59
|
+
/** Describes a command to run: the executable and the arguments it takes. */
|
|
60
|
+
export interface Invocation {
|
|
61
|
+
readonly command: string
|
|
62
|
+
readonly args: readonly string[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Describes the commands and files a Codex login drives, and how often it polls. */
|
|
66
|
+
export interface Flow {
|
|
67
|
+
readonly status: Invocation
|
|
68
|
+
readonly device: Invocation
|
|
69
|
+
readonly journal: string
|
|
70
|
+
readonly intervalMs: number
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Describes what a login decided: the JSON report to print and the exit code to return. */
|
|
74
|
+
export interface Outcome {
|
|
75
|
+
readonly report: Record<string, unknown>
|
|
76
|
+
readonly exit: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Holds the real Codex commands and the journal and interval the script uses. */
|
|
80
|
+
export const CODEX_FLOW: Flow = {
|
|
81
|
+
status: { command: 'codex', args: ['login', 'status'] },
|
|
82
|
+
device: { command: 'codex', args: ['login', '--device-auth'] },
|
|
83
|
+
journal: JOURNAL,
|
|
84
|
+
intervalMs: POLL_MS,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Holds the real Claude status command. */
|
|
88
|
+
export const CLAUDE_STATUS: Invocation = { command: 'claude', args: ['auth', 'status'] }
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Reads the value that follows a flag.
|
|
92
|
+
*
|
|
93
|
+
* @remarks
|
|
94
|
+
* The first occurrence of a flag wins, a following value that opens with `--` is a missing value,
|
|
95
|
+
* and the `--flag=value` form is not read.
|
|
96
|
+
*
|
|
97
|
+
* @param argv - The arguments after the script path.
|
|
98
|
+
* @param name - The flag, such as `--cap`.
|
|
99
|
+
* @returns The value after the flag, or undefined when the flag is absent, last, or followed by
|
|
100
|
+
* another flag.
|
|
101
|
+
*/
|
|
102
|
+
export function readOption(argv: readonly string[], name: string): string | undefined {
|
|
103
|
+
const index = argv.indexOf(name)
|
|
104
|
+
const value = index === -1 ? undefined : argv[index + 1]
|
|
105
|
+
return value === undefined || value.startsWith('--') ? undefined : value
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Names the value flags a caller gave with no value.
|
|
110
|
+
*
|
|
111
|
+
* @param argv - The arguments after the script path.
|
|
112
|
+
* @param names - The flags that take a value.
|
|
113
|
+
* @returns Each named flag that appears last or is followed by another flag, in argument order.
|
|
114
|
+
*/
|
|
115
|
+
export function readMissingFlags(
|
|
116
|
+
argv: readonly string[],
|
|
117
|
+
names: readonly string[],
|
|
118
|
+
): readonly string[] {
|
|
119
|
+
return names.filter((name) => argv.includes(name) && readOption(argv, name) === undefined)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Names the flags a caller gave that the script does not read.
|
|
124
|
+
*
|
|
125
|
+
* @remarks
|
|
126
|
+
* A `--flag=value` argument is unknown, because the script reads no such form. A bare `--` ends the
|
|
127
|
+
* scan.
|
|
128
|
+
*
|
|
129
|
+
* @param argv - The arguments after the script path.
|
|
130
|
+
* @param flags - Every flag the script reads, value flags and switches alike.
|
|
131
|
+
* @returns Each argument that opens with `--` and is not a read flag, in argument order.
|
|
132
|
+
*/
|
|
133
|
+
export function readUnknownFlags(
|
|
134
|
+
argv: readonly string[],
|
|
135
|
+
flags: readonly string[],
|
|
136
|
+
): readonly string[] {
|
|
137
|
+
const end = argv.indexOf('--')
|
|
138
|
+
return (end === -1 ? argv : argv.slice(0, end)).filter(
|
|
139
|
+
(argument) => argument.startsWith('--') && !flags.includes(argument),
|
|
140
|
+
)
|
|
35
141
|
}
|
|
36
142
|
|
|
37
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Parses the `--cap` value into a number of seconds.
|
|
145
|
+
*
|
|
146
|
+
* @param text - The value after `--cap`, or undefined when the flag is absent.
|
|
147
|
+
* @returns The default cap when the flag is absent, the number when it is finite, above 0, and at
|
|
148
|
+
* most the longest cap, and undefined otherwise.
|
|
149
|
+
*/
|
|
150
|
+
export function parseCap(text: string | undefined): number | undefined {
|
|
151
|
+
if (text === undefined) return DEFAULT_CAP
|
|
152
|
+
const cap = Number(text)
|
|
153
|
+
return Number.isFinite(cap) && cap > 0 && cap <= MAX_CAP_SECONDS ? cap : undefined
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Runs a status command and reads whether it reports a login.
|
|
158
|
+
*
|
|
159
|
+
* @param command - The executable.
|
|
160
|
+
* @param args - The status arguments.
|
|
161
|
+
* @param timeoutMs - The milliseconds the command may run before it is ended, above 0; omit it for no
|
|
162
|
+
* bound.
|
|
163
|
+
* @returns Live when the command exits 0, with the text it printed, the reason it could not start,
|
|
164
|
+
* or the note that it did not answer in time.
|
|
165
|
+
*/
|
|
166
|
+
export function readLoginStatus(
|
|
167
|
+
command: string,
|
|
168
|
+
args: readonly string[],
|
|
169
|
+
timeoutMs?: number,
|
|
170
|
+
): Status {
|
|
171
|
+
const result = spawnSync(command, args, {
|
|
172
|
+
encoding: 'utf8',
|
|
173
|
+
windowsHide: true,
|
|
174
|
+
timeout: timeoutMs,
|
|
175
|
+
})
|
|
176
|
+
if (result.error !== undefined) {
|
|
177
|
+
if ('code' in result.error && result.error.code === 'ETIMEDOUT') {
|
|
178
|
+
return { started: true, live: false, text: `${command} did not answer within the cap` }
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
started: false,
|
|
182
|
+
live: false,
|
|
183
|
+
text: `${command} could not start: ${result.error.message}`,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
started: true,
|
|
188
|
+
live: result.status === 0,
|
|
189
|
+
text: `${result.stdout ?? ''}${result.stderr ?? ''}`.trim(),
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Asks a process and the processes it started to end.
|
|
195
|
+
*
|
|
196
|
+
* @param pid - The process to end. A process that is already gone is not an error.
|
|
197
|
+
*/
|
|
198
|
+
export function terminateTree(pid: number): void {
|
|
38
199
|
if (process.platform === 'win32') {
|
|
39
200
|
spawnSync('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore' })
|
|
40
201
|
return
|
|
@@ -44,71 +205,126 @@ function killTree(pid: number): void {
|
|
|
44
205
|
} catch {}
|
|
45
206
|
}
|
|
46
207
|
|
|
47
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Waits a number of milliseconds.
|
|
210
|
+
*
|
|
211
|
+
* @param ms - The milliseconds to wait.
|
|
212
|
+
* @returns A promise that resolves when the time has passed.
|
|
213
|
+
*/
|
|
214
|
+
export function waitFor(ms: number): Promise<void> {
|
|
48
215
|
return new Promise((resolve) => {
|
|
49
216
|
setTimeout(resolve, ms)
|
|
50
217
|
})
|
|
51
218
|
}
|
|
52
219
|
|
|
53
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Polls the login journal and the login status until the bench is logged in or the cap ends.
|
|
222
|
+
*
|
|
223
|
+
* @param started - The time the login began, in milliseconds since the epoch.
|
|
224
|
+
* @param capSeconds - The seconds the poll may run.
|
|
225
|
+
* @param flow - The status command, the journal to read, and the milliseconds between polls.
|
|
226
|
+
* @param announce - Receives each line the poll tells the user, such as the URL to open.
|
|
227
|
+
* @param url - The verification URL already read, if any.
|
|
228
|
+
* @param code - The one-time code already read, if any.
|
|
229
|
+
* @returns The URL, the code, and whether the bench is logged in.
|
|
230
|
+
*/
|
|
231
|
+
export async function pollLogin(
|
|
54
232
|
started: number,
|
|
55
233
|
capSeconds: number,
|
|
234
|
+
flow: Flow,
|
|
235
|
+
announce: (line: string) => void,
|
|
56
236
|
url?: string,
|
|
57
237
|
code?: string,
|
|
58
238
|
): Promise<Poll> {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
239
|
+
const deadline = started + capSeconds * 1000
|
|
240
|
+
if (Date.now() >= deadline) return { url, code, live: false }
|
|
241
|
+
await waitFor(flow.intervalMs)
|
|
242
|
+
const text = existsSync(flow.journal) ? readFileSync(flow.journal, 'utf8') : ''
|
|
62
243
|
const nextUrl = url ?? text.match(URL_PATTERN)?.[0]
|
|
63
|
-
if (url === undefined && nextUrl !== undefined)
|
|
244
|
+
if (url === undefined && nextUrl !== undefined) announce(`login: open ${nextUrl}`)
|
|
64
245
|
const nextCode = code ?? text.match(CODE_PATTERN)?.[1]
|
|
65
|
-
if (code === undefined && nextCode !== undefined) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (readStatus('codex', ['login', 'status']).live) {
|
|
246
|
+
if (code === undefined && nextCode !== undefined) announce(`login: enter the code ${nextCode}`)
|
|
247
|
+
const remaining = Math.max(deadline - Date.now(), STATUS_GRACE_MS)
|
|
248
|
+
if (readLoginStatus(flow.status.command, flow.status.args, remaining).live) {
|
|
69
249
|
return { url: nextUrl, code: nextCode, live: true }
|
|
70
250
|
}
|
|
71
|
-
return pollLogin(started, capSeconds, nextUrl, nextCode)
|
|
251
|
+
return pollLogin(started, capSeconds, flow, announce, nextUrl, nextCode)
|
|
72
252
|
}
|
|
73
253
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Recovers the Codex login with the device-auth flow.
|
|
256
|
+
*
|
|
257
|
+
* @param capSeconds - The seconds the poll may run.
|
|
258
|
+
* @param flow - The status and device-auth commands, the journal, and the poll interval.
|
|
259
|
+
* @param announce - Receives each line the poll tells the user, such as the URL to open.
|
|
260
|
+
* @returns Exit 0 when logged in, 3 when not, with the report to print.
|
|
261
|
+
*/
|
|
262
|
+
export async function loginCodex(
|
|
263
|
+
capSeconds: number,
|
|
264
|
+
flow: Flow,
|
|
265
|
+
announce: (line: string) => void,
|
|
266
|
+
): Promise<Outcome> {
|
|
267
|
+
const before = readLoginStatus(flow.status.command, flow.status.args, capSeconds * 1000)
|
|
268
|
+
// A Codex that cannot start cannot log in either, so the device-auth login never spawns.
|
|
269
|
+
if (before.live || !before.started) {
|
|
270
|
+
return {
|
|
271
|
+
report: { bench: 'codex', live: before.live, status: before.text },
|
|
272
|
+
exit: before.live ? 0 : 3,
|
|
273
|
+
}
|
|
79
274
|
}
|
|
80
|
-
mkdirSync(
|
|
81
|
-
const journal = openSync(
|
|
275
|
+
mkdirSync(dirname(flow.journal), { recursive: true })
|
|
276
|
+
const journal = openSync(flow.journal, 'w')
|
|
82
277
|
const started = Date.now()
|
|
83
|
-
const child = spawn(
|
|
278
|
+
const child = spawn(flow.device.command, flow.device.args, {
|
|
84
279
|
stdio: ['ignore', journal, journal],
|
|
85
280
|
windowsHide: true,
|
|
86
281
|
})
|
|
87
|
-
const { url, code, live } = await pollLogin(started, capSeconds)
|
|
88
|
-
if (child.exitCode === null && child.pid !== undefined)
|
|
282
|
+
const { url, code, live } = await pollLogin(started, capSeconds, flow, announce)
|
|
283
|
+
if (child.exitCode === null && child.pid !== undefined) terminateTree(child.pid)
|
|
89
284
|
closeSync(journal)
|
|
90
|
-
|
|
91
|
-
|
|
285
|
+
return {
|
|
286
|
+
report: {
|
|
92
287
|
bench: 'codex',
|
|
93
288
|
live,
|
|
94
289
|
url,
|
|
95
290
|
code,
|
|
96
291
|
durationMs: Date.now() - started,
|
|
97
|
-
journal:
|
|
98
|
-
status:
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
292
|
+
journal: flow.journal,
|
|
293
|
+
status: readLoginStatus(flow.status.command, flow.status.args, STATUS_GRACE_MS).text,
|
|
294
|
+
},
|
|
295
|
+
exit: live ? 0 : 3,
|
|
296
|
+
}
|
|
102
297
|
}
|
|
103
298
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
299
|
+
/**
|
|
300
|
+
* Reads the Claude login status.
|
|
301
|
+
*
|
|
302
|
+
* @param status - The command that reports the Claude login.
|
|
303
|
+
* @returns Exit 0 when logged in, 3 when not, with the report to print.
|
|
304
|
+
*/
|
|
305
|
+
export function loginClaude(status: Invocation): Outcome {
|
|
306
|
+
const read = readLoginStatus(status.command, status.args)
|
|
307
|
+
return {
|
|
308
|
+
report: { bench: 'claude', live: read.live, status: read.text },
|
|
309
|
+
exit: read.live ? 0 : 3,
|
|
310
|
+
}
|
|
108
311
|
}
|
|
109
312
|
|
|
110
|
-
|
|
111
|
-
|
|
313
|
+
/**
|
|
314
|
+
* Runs the script on its arguments.
|
|
315
|
+
*
|
|
316
|
+
* @param argv - The arguments after the script path.
|
|
317
|
+
* @returns The exit code: 0 when logged in, 3 when not, 64 on usage.
|
|
318
|
+
*/
|
|
319
|
+
export async function main(argv: readonly string[]): Promise<number> {
|
|
320
|
+
const unknown = readUnknownFlags(argv, FLAGS)
|
|
321
|
+
if (unknown.length > 0) {
|
|
322
|
+
console.error(
|
|
323
|
+
`login: ${unknown.join(', ')} is not a flag this script reads; usage: login.ts --codex [--cap SECONDS] | --claude`,
|
|
324
|
+
)
|
|
325
|
+
return 64
|
|
326
|
+
}
|
|
327
|
+
const missing = readMissingFlags(argv, VALUE_FLAGS)
|
|
112
328
|
if (missing.length > 0) {
|
|
113
329
|
console.error(`login: ${missing.join(', ')} given with no value`)
|
|
114
330
|
return 64
|
|
@@ -117,12 +333,31 @@ async function main(argv: readonly string[]): Promise<number> {
|
|
|
117
333
|
console.error('usage: login.ts names one bench: --codex [--cap SECONDS] | --claude')
|
|
118
334
|
return 64
|
|
119
335
|
}
|
|
120
|
-
|
|
121
|
-
if (
|
|
336
|
+
const cap = parseCap(readOption(argv, '--cap'))
|
|
337
|
+
if (cap === undefined) {
|
|
338
|
+
console.error(
|
|
339
|
+
`login: --cap takes a number of seconds above 0 and at most ${String(MAX_CAP_SECONDS)}`,
|
|
340
|
+
)
|
|
341
|
+
return 64
|
|
342
|
+
}
|
|
343
|
+
if (argv.includes('--codex')) {
|
|
344
|
+
const outcome = await loginCodex(cap, CODEX_FLOW, (line) => {
|
|
345
|
+
console.error(line)
|
|
346
|
+
})
|
|
347
|
+
console.log(JSON.stringify(outcome.report))
|
|
348
|
+
return outcome.exit
|
|
349
|
+
}
|
|
350
|
+
if (argv.includes('--claude')) {
|
|
351
|
+
const outcome = loginClaude(CLAUDE_STATUS)
|
|
352
|
+
console.log(JSON.stringify(outcome.report))
|
|
353
|
+
return outcome.exit
|
|
354
|
+
}
|
|
122
355
|
console.error('usage: login.ts --codex [--cap SECONDS] | --claude')
|
|
123
356
|
return 64
|
|
124
357
|
}
|
|
125
358
|
|
|
126
|
-
|
|
127
|
-
process.
|
|
128
|
-
|
|
359
|
+
if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === import.meta.filename) {
|
|
360
|
+
main(process.argv.slice(2)).then((code) => {
|
|
361
|
+
process.exitCode = code
|
|
362
|
+
})
|
|
363
|
+
}
|