fix: simplify headless flags, add missing imports, document headless mode
- Remove --verbose flag from headless (use --json for detailed output) - Remove redundant sawToolExecution state variable - Remove unused rejectCompletion - Add missing build*Prompt imports in auto.ts (fixes CI typecheck:extensions) - Document headless mode in README.md and docs/commands.md - Simplify help text with examples instead of exhaustive command catalog
This commit is contained in:
parent
8ddea154e5
commit
f56b8c69f0
5 changed files with 82 additions and 46 deletions
23
README.md
23
README.md
|
|
@ -221,6 +221,26 @@ gsd
|
|||
|
||||
Both terminals read and write the same `.gsd/` files on disk. Your decisions in terminal 2 are picked up automatically at the next phase boundary — no need to stop auto mode.
|
||||
|
||||
### Headless mode — CI and scripts
|
||||
|
||||
`gsd headless` runs any `/gsd` command without a TUI. Designed for CI pipelines, cron jobs, and scripted automation.
|
||||
|
||||
```bash
|
||||
# Run auto mode in CI
|
||||
gsd headless --timeout 600000
|
||||
|
||||
# One unit at a time (cron-friendly)
|
||||
gsd headless next
|
||||
|
||||
# Machine-readable status
|
||||
gsd headless --json status
|
||||
|
||||
# Force a specific pipeline phase
|
||||
gsd headless dispatch plan
|
||||
```
|
||||
|
||||
Headless auto-responds to interactive prompts, detects completion, and exits with structured codes: `0` complete, `1` error/timeout, `2` blocked. Pair with [remote questions](./docs/remote-questions.md) to route decisions to Slack or Discord when human input is needed.
|
||||
|
||||
### First launch
|
||||
|
||||
On first run, GSD launches a branded setup wizard that walks you through LLM provider selection (OAuth or API key), then optional tool API keys (Brave Search, Context7, Jina, Slack, Discord). Every step is skippable — press Enter to skip any. If you have an existing Pi installation, your provider credentials (LLM and tool keys) are imported automatically. Run `gsd config` anytime to re-run the wizard.
|
||||
|
|
@ -254,6 +274,8 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
|
|||
| `Ctrl+Alt+V` | Toggle voice transcription |
|
||||
| `Ctrl+Alt+B` | Show background shell processes |
|
||||
| `gsd config` | Re-run the setup wizard (LLM provider + tool keys) |
|
||||
| `gsd update` | Update GSD to the latest version |
|
||||
| `gsd headless [cmd]` | Run `/gsd` commands without TUI (CI, cron, scripts) |
|
||||
| `gsd --continue` (`-c`) | Resume the most recent session for the current directory |
|
||||
|
||||
---
|
||||
|
|
@ -482,6 +504,7 @@ GSD is a TypeScript application that embeds the Pi coding agent SDK.
|
|||
gsd (CLI binary)
|
||||
└─ loader.ts Sets PI_PACKAGE_DIR, GSD env vars, dynamic-imports cli.ts
|
||||
└─ cli.ts Wires SDK managers, loads extensions, starts InteractiveMode
|
||||
├─ headless.ts Headless orchestrator (spawns RPC child, auto-responds, detects completion)
|
||||
├─ onboarding.ts First-run setup wizard (LLM provider + tool keys)
|
||||
├─ wizard.ts Env hydration from stored auth.json credentials
|
||||
├─ app-paths.ts ~/.gsd/agent/, ~/.gsd/sessions/, auth.json
|
||||
|
|
|
|||
|
|
@ -69,5 +69,41 @@
|
|||
|------|-------------|
|
||||
| `gsd` | Start a new interactive session |
|
||||
| `gsd --continue` (`-c`) | Resume the most recent session for the current directory |
|
||||
| `gsd --model <id>` | Override the default model for this session |
|
||||
| `gsd --print "msg"` (`-p`) | Single-shot prompt mode (no TUI) |
|
||||
| `gsd --mode <text\|json\|rpc\|mcp>` | Output mode for non-interactive use |
|
||||
| `gsd --list-models [search]` | List available models and exit |
|
||||
| `gsd --debug` | Enable structured JSONL diagnostic logging for troubleshooting dispatch and state issues |
|
||||
| `gsd config` | Re-run the setup wizard (LLM provider + tool keys) |
|
||||
| `gsd update` | Update GSD to the latest version |
|
||||
|
||||
## Headless Mode
|
||||
|
||||
`gsd headless` runs `/gsd` commands without a TUI — designed for CI, cron jobs, and scripted automation. It spawns a child process in RPC mode, auto-responds to interactive prompts, detects completion, and exits with meaningful exit codes.
|
||||
|
||||
```bash
|
||||
# Run auto mode (default)
|
||||
gsd headless
|
||||
|
||||
# Run a single unit
|
||||
gsd headless next
|
||||
|
||||
# Machine-readable output
|
||||
gsd headless --json status
|
||||
|
||||
# With timeout for CI
|
||||
gsd headless --timeout 600000 auto
|
||||
|
||||
# Force a specific phase
|
||||
gsd headless dispatch plan
|
||||
```
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--timeout N` | Overall timeout in milliseconds (default: 300000 / 5 min) |
|
||||
| `--json` | Stream all events as JSONL to stdout |
|
||||
| `--model ID` | Override the model for the headless session |
|
||||
|
||||
**Exit codes:** `0` = complete, `1` = error or timeout, `2` = blocked.
|
||||
|
||||
Any `/gsd` subcommand works as a positional argument — `gsd headless status`, `gsd headless doctor`, `gsd headless dispatch execute`, etc.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import { RpcClient } from '../packages/pi-coding-agent/dist/modes/rpc/rpc-client
|
|||
export interface HeadlessOptions {
|
||||
timeout: number
|
||||
json: boolean
|
||||
verbose: boolean
|
||||
model?: string
|
||||
command: string
|
||||
commandArgs: string[]
|
||||
|
|
@ -58,7 +57,6 @@ export function parseHeadlessArgs(argv: string[]): HeadlessOptions {
|
|||
const options: HeadlessOptions = {
|
||||
timeout: 300_000,
|
||||
json: false,
|
||||
verbose: false,
|
||||
command: 'auto',
|
||||
commandArgs: [],
|
||||
}
|
||||
|
|
@ -79,9 +77,8 @@ export function parseHeadlessArgs(argv: string[]): HeadlessOptions {
|
|||
}
|
||||
} else if (arg === '--json') {
|
||||
options.json = true
|
||||
} else if (arg === '--verbose') {
|
||||
options.verbose = true
|
||||
} else if (arg === '--model' && i + 1 < args.length) {
|
||||
// --model can also be passed from the main CLI; headless-specific takes precedence
|
||||
options.model = args[++i]
|
||||
}
|
||||
} else if (!positionalStarted) {
|
||||
|
|
@ -147,23 +144,13 @@ function handleExtensionUIRequest(
|
|||
// Progress Formatter
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function formatProgress(
|
||||
event: Record<string, unknown>,
|
||||
verbose: boolean,
|
||||
): string | null {
|
||||
function formatProgress(event: Record<string, unknown>): string | null {
|
||||
const type = String(event.type ?? '')
|
||||
|
||||
switch (type) {
|
||||
case 'tool_execution_start':
|
||||
return `[tool] ${event.toolName ?? 'unknown'}`
|
||||
|
||||
case 'tool_execution_end':
|
||||
if (verbose) {
|
||||
const result = String(event.result ?? '').slice(0, 200)
|
||||
return `[tool:result] ${event.toolName ?? 'unknown'}: ${result}`
|
||||
}
|
||||
return null
|
||||
|
||||
case 'agent_start':
|
||||
return '[agent] Session started'
|
||||
|
||||
|
|
@ -176,14 +163,6 @@ function formatProgress(
|
|||
}
|
||||
return null
|
||||
|
||||
case 'message_update':
|
||||
if (verbose) {
|
||||
const msgEvent = event.assistantMessageEvent as Record<string, unknown> | undefined
|
||||
const text = String(msgEvent?.text ?? '').slice(0, 200)
|
||||
if (text) return `[assistant] ${text}`
|
||||
}
|
||||
return null
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
|
@ -258,7 +237,6 @@ export async function runHeadless(options: HeadlessOptions): Promise<void> {
|
|||
// Event tracking
|
||||
let totalEvents = 0
|
||||
let toolCallCount = 0
|
||||
let sawToolExecution = false
|
||||
let blocked = false
|
||||
let completed = false
|
||||
let exitCode = 0
|
||||
|
|
@ -270,7 +248,6 @@ export async function runHeadless(options: HeadlessOptions): Promise<void> {
|
|||
|
||||
if (type === 'tool_execution_start') {
|
||||
toolCallCount++
|
||||
sawToolExecution = true
|
||||
}
|
||||
|
||||
// Keep last 20 events for diagnostics
|
||||
|
|
@ -290,10 +267,8 @@ export async function runHeadless(options: HeadlessOptions): Promise<void> {
|
|||
|
||||
// Completion promise
|
||||
let resolveCompletion: () => void
|
||||
let rejectCompletion: (err: Error) => void
|
||||
const completionPromise = new Promise<void>((resolve, reject) => {
|
||||
const completionPromise = new Promise<void>((resolve) => {
|
||||
resolveCompletion = resolve
|
||||
rejectCompletion = reject
|
||||
})
|
||||
|
||||
// Idle timeout — fallback completion detection
|
||||
|
|
@ -301,7 +276,7 @@ export async function runHeadless(options: HeadlessOptions): Promise<void> {
|
|||
|
||||
function resetIdleTimer(): void {
|
||||
if (idleTimer) clearTimeout(idleTimer)
|
||||
if (sawToolExecution) {
|
||||
if (toolCallCount > 0) {
|
||||
idleTimer = setTimeout(() => {
|
||||
completed = true
|
||||
resolveCompletion()
|
||||
|
|
@ -327,7 +302,7 @@ export async function runHeadless(options: HeadlessOptions): Promise<void> {
|
|||
process.stdout.write(JSON.stringify(eventObj) + '\n')
|
||||
} else {
|
||||
// Progress output to stderr
|
||||
const line = formatProgress(eventObj, options.verbose)
|
||||
const line = formatProgress(eventObj)
|
||||
if (line) process.stderr.write(line + '\n')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,26 +37,16 @@ const SUBCOMMAND_HELP: Record<string, string> = {
|
|||
'',
|
||||
'Run /gsd commands without the TUI. Default command: auto',
|
||||
'',
|
||||
'Flags (before command):',
|
||||
'Flags:',
|
||||
' --timeout N Overall timeout in ms (default: 300000)',
|
||||
' --json JSONL event stream to stdout',
|
||||
' --verbose Detailed progress output',
|
||||
' --model ID Override model',
|
||||
'',
|
||||
'Commands:',
|
||||
' auto /gsd auto (default)',
|
||||
' next /gsd next — one unit',
|
||||
' status /gsd status',
|
||||
' queue /gsd queue',
|
||||
' discuss /gsd discuss',
|
||||
' doctor [mode] /gsd doctor [fix|heal|audit]',
|
||||
' steer "desc" /gsd steer',
|
||||
' dispatch <phase> Direct unit-type dispatch',
|
||||
' ... Any /gsd subcommand',
|
||||
'',
|
||||
'Dispatch phases:',
|
||||
' research, plan, execute, complete, reassess, uat, replan',
|
||||
' Also: research-milestone, plan-slice, execute-task, etc.',
|
||||
'Examples:',
|
||||
' gsd headless Run /gsd auto',
|
||||
' gsd headless next Run one unit',
|
||||
' gsd headless --json status Machine-readable status',
|
||||
' gsd headless --timeout 60000 With 1-minute timeout',
|
||||
'',
|
||||
'Exit codes: 0 = complete, 1 = error/timeout, 2 = blocked',
|
||||
].join('\n'),
|
||||
|
|
|
|||
|
|
@ -125,6 +125,18 @@ import {
|
|||
reconcileMergeState,
|
||||
} from "./auto-recovery.js";
|
||||
import { resolveDispatch, resetRewriteCircuitBreaker } from "./auto-dispatch.js";
|
||||
import {
|
||||
buildResearchSlicePrompt,
|
||||
buildResearchMilestonePrompt,
|
||||
buildPlanSlicePrompt,
|
||||
buildPlanMilestonePrompt,
|
||||
buildExecuteTaskPrompt,
|
||||
buildCompleteSlicePrompt,
|
||||
buildCompleteMilestonePrompt,
|
||||
buildReassessRoadmapPrompt,
|
||||
buildRunUatPrompt,
|
||||
buildReplanSlicePrompt,
|
||||
} from "./auto-prompts.js";
|
||||
import {
|
||||
type AutoDashboardData,
|
||||
updateProgressWidget as _updateProgressWidget,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue