shipwright-cli 1.10.0 → 2.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/README.md +221 -55
- package/completions/_shipwright +264 -32
- package/completions/shipwright.bash +118 -26
- package/completions/shipwright.fish +80 -2
- package/dashboard/server.ts +208 -0
- package/docs/strategy/01-market-research.md +619 -0
- package/docs/strategy/02-mission-and-brand.md +587 -0
- package/docs/strategy/03-gtm-and-roadmap.md +759 -0
- package/docs/strategy/QUICK-START.txt +289 -0
- package/docs/strategy/README.md +172 -0
- package/docs/tmux-research/TMUX-ARCHITECTURE.md +567 -0
- package/docs/tmux-research/TMUX-AUDIT.md +925 -0
- package/docs/tmux-research/TMUX-BEST-PRACTICES-2025-2026.md +829 -0
- package/docs/tmux-research/TMUX-QUICK-REFERENCE.md +543 -0
- package/docs/tmux-research/TMUX-RESEARCH-INDEX.md +438 -0
- package/package.json +4 -2
- package/scripts/lib/helpers.sh +7 -0
- package/scripts/sw +323 -2
- package/scripts/sw-activity.sh +500 -0
- package/scripts/sw-adaptive.sh +925 -0
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +613 -0
- package/scripts/sw-autonomous.sh +754 -0
- package/scripts/sw-changelog.sh +704 -0
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +698 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +44 -3
- package/scripts/sw-daemon.sh +568 -138
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +1380 -0
- package/scripts/sw-decompose.sh +539 -0
- package/scripts/sw-deps.sh +551 -0
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +412 -0
- package/scripts/sw-docs-agent.sh +539 -0
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +107 -1
- package/scripts/sw-dora.sh +615 -0
- package/scripts/sw-durable.sh +710 -0
- package/scripts/sw-e2e-orchestrator.sh +535 -0
- package/scripts/sw-eventbus.sh +393 -0
- package/scripts/sw-feedback.sh +479 -0
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +567 -0
- package/scripts/sw-fleet-viz.sh +404 -0
- package/scripts/sw-fleet.sh +8 -1
- package/scripts/sw-github-app.sh +596 -0
- package/scripts/sw-github-checks.sh +4 -4
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +569 -0
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +559 -0
- package/scripts/sw-incident.sh +656 -0
- package/scripts/sw-init.sh +237 -24
- package/scripts/sw-instrument.sh +699 -0
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +363 -28
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +267 -21
- package/scripts/sw-memory.sh +18 -1
- package/scripts/sw-mission-control.sh +487 -0
- package/scripts/sw-model-router.sh +545 -0
- package/scripts/sw-otel.sh +596 -0
- package/scripts/sw-oversight.sh +764 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +947 -35
- package/scripts/sw-pm.sh +758 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +8 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +2248 -0
- package/scripts/sw-regression.sh +642 -0
- package/scripts/sw-release-manager.sh +736 -0
- package/scripts/sw-release.sh +706 -0
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +520 -0
- package/scripts/sw-retro.sh +691 -0
- package/scripts/sw-scale.sh +444 -0
- package/scripts/sw-security-audit.sh +505 -0
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +263 -127
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +44 -2
- package/scripts/sw-strategic.sh +806 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +620 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +4 -4
- package/scripts/sw-testgen.sh +566 -0
- package/scripts/sw-tmux-pipeline.sh +554 -0
- package/scripts/sw-tmux-role-color.sh +58 -0
- package/scripts/sw-tmux-status.sh +128 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +485 -0
- package/scripts/sw-tracker-github.sh +188 -0
- package/scripts/sw-tracker-jira.sh +172 -0
- package/scripts/sw-tracker-linear.sh +251 -0
- package/scripts/sw-tracker.sh +117 -2
- package/scripts/sw-triage.sh +627 -0
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +677 -0
- package/scripts/sw-webhook.sh +627 -0
- package/scripts/sw-widgets.sh +530 -0
- package/scripts/sw-worktree.sh +1 -1
- package/templates/pipelines/autonomous.json +2 -2
- package/tmux/shipwright-overlay.conf +35 -17
- package/tmux/tmux.conf +23 -21
|
@@ -7,8 +7,13 @@
|
|
|
7
7
|
for cmd in shipwright sw cct
|
|
8
8
|
complete -c $cmd -f
|
|
9
9
|
|
|
10
|
-
# Top-level commands
|
|
11
|
-
set -l all_cmds session status ps logs templates doctor cleanup reaper upgrade loop pipeline worktree prep daemon memory cost init help version
|
|
10
|
+
# Top-level commands (includes groups and flat commands)
|
|
11
|
+
set -l all_cmds agent quality observe release intel session status ps logs templates doctor cleanup reaper upgrade loop pipeline worktree prep daemon memory cost init help version
|
|
12
|
+
complete -c $cmd -n "not __fish_seen_subcommand_from $all_cmds" -a "agent" -d "Agent management (recruit, swarm, standup, guild, oversight)"
|
|
13
|
+
complete -c $cmd -n "not __fish_seen_subcommand_from $all_cmds" -a "quality" -d "Quality & review (code-review, security-audit, testgen, hygiene)"
|
|
14
|
+
complete -c $cmd -n "not __fish_seen_subcommand_from $all_cmds" -a "observe" -d "Observability (vitals, dora, retro, stream, activity, replay)"
|
|
15
|
+
complete -c $cmd -n "not __fish_seen_subcommand_from $all_cmds" -a "release" -d "Release & deploy (release, release-manager, changelog, deploy)"
|
|
16
|
+
complete -c $cmd -n "not __fish_seen_subcommand_from $all_cmds" -a "intel" -d "Intelligence (predict, intelligence, strategic, optimize)"
|
|
12
17
|
complete -c $cmd -n "not __fish_seen_subcommand_from $all_cmds" -a "session" -d "Create a new tmux window for a Claude team"
|
|
13
18
|
complete -c $cmd -n "not __fish_seen_subcommand_from $all_cmds" -a "status" -d "Show dashboard of running teams and agents"
|
|
14
19
|
complete -c $cmd -n "not __fish_seen_subcommand_from $all_cmds" -a "ps" -d "Show running agent processes and status"
|
|
@@ -29,6 +34,40 @@ for cmd in shipwright sw cct
|
|
|
29
34
|
complete -c $cmd -n "not __fish_seen_subcommand_from $all_cmds" -a "help" -d "Show help message"
|
|
30
35
|
complete -c $cmd -n "not __fish_seen_subcommand_from $all_cmds" -a "version" -d "Show version"
|
|
31
36
|
|
|
37
|
+
# agent subcommands
|
|
38
|
+
complete -c $cmd -n "__fish_seen_subcommand_from agent" -a "recruit" -d "Agent recruitment & talent management"
|
|
39
|
+
complete -c $cmd -n "__fish_seen_subcommand_from agent" -a "swarm" -d "Dynamic agent swarm management"
|
|
40
|
+
complete -c $cmd -n "__fish_seen_subcommand_from agent" -a "standup" -d "Automated daily standups"
|
|
41
|
+
complete -c $cmd -n "__fish_seen_subcommand_from agent" -a "guild" -d "Knowledge guilds & cross-team learning"
|
|
42
|
+
complete -c $cmd -n "__fish_seen_subcommand_from agent" -a "oversight" -d "Quality oversight board"
|
|
43
|
+
|
|
44
|
+
# quality subcommands
|
|
45
|
+
complete -c $cmd -n "__fish_seen_subcommand_from quality" -a "code-review" -d "Clean code & architecture analysis"
|
|
46
|
+
complete -c $cmd -n "__fish_seen_subcommand_from quality" -a "security-audit" -d "Comprehensive security auditing"
|
|
47
|
+
complete -c $cmd -n "__fish_seen_subcommand_from quality" -a "testgen" -d "Autonomous test generation"
|
|
48
|
+
complete -c $cmd -n "__fish_seen_subcommand_from quality" -a "hygiene" -d "Repository organization & cleanup"
|
|
49
|
+
|
|
50
|
+
# observe subcommands
|
|
51
|
+
complete -c $cmd -n "__fish_seen_subcommand_from observe" -a "vitals" -d "Pipeline vitals — real-time scoring"
|
|
52
|
+
complete -c $cmd -n "__fish_seen_subcommand_from observe" -a "dora" -d "DORA metrics dashboard"
|
|
53
|
+
complete -c $cmd -n "__fish_seen_subcommand_from observe" -a "retro" -d "Sprint retrospective engine"
|
|
54
|
+
complete -c $cmd -n "__fish_seen_subcommand_from observe" -a "stream" -d "Live terminal output streaming"
|
|
55
|
+
complete -c $cmd -n "__fish_seen_subcommand_from observe" -a "activity" -d "Live agent activity stream"
|
|
56
|
+
complete -c $cmd -n "__fish_seen_subcommand_from observe" -a "replay" -d "Pipeline DVR — view past runs"
|
|
57
|
+
complete -c $cmd -n "__fish_seen_subcommand_from observe" -a "status" -d "Team status dashboard"
|
|
58
|
+
|
|
59
|
+
# release subcommands
|
|
60
|
+
complete -c $cmd -n "__fish_seen_subcommand_from release" -a "release" -d "Release train automation"
|
|
61
|
+
complete -c $cmd -n "__fish_seen_subcommand_from release" -a "release-manager" -d "Autonomous release pipeline"
|
|
62
|
+
complete -c $cmd -n "__fish_seen_subcommand_from release" -a "changelog" -d "Automated release notes"
|
|
63
|
+
complete -c $cmd -n "__fish_seen_subcommand_from release" -a "deploy" -d "Deployments — deployment history"
|
|
64
|
+
|
|
65
|
+
# intel subcommands
|
|
66
|
+
complete -c $cmd -n "__fish_seen_subcommand_from intel" -a "predict" -d "Predictive risk assessment"
|
|
67
|
+
complete -c $cmd -n "__fish_seen_subcommand_from intel" -a "intelligence" -d "Intelligence engine analysis"
|
|
68
|
+
complete -c $cmd -n "__fish_seen_subcommand_from intel" -a "strategic" -d "Strategic intelligence agent"
|
|
69
|
+
complete -c $cmd -n "__fish_seen_subcommand_from intel" -a "optimize" -d "Self-optimization based on DORA"
|
|
70
|
+
|
|
32
71
|
# pipeline subcommands
|
|
33
72
|
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -a "start" -d "Start a new pipeline run"
|
|
34
73
|
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -a "resume" -d "Resume from last stage"
|
|
@@ -81,16 +120,28 @@ for cmd in shipwright sw cct
|
|
|
81
120
|
complete -c $cmd -n "__fish_seen_subcommand_from prep" -l verbose -d "Verbose output"
|
|
82
121
|
|
|
83
122
|
# loop flags
|
|
123
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l repo -d "Change to directory before running" -r
|
|
124
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l local -d "Local-only mode (no GitHub)"
|
|
84
125
|
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l test-cmd -d "Test command to verify each iteration" -r
|
|
126
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l fast-test-cmd -d "Fast/subset test command" -r
|
|
127
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l fast-test-interval -d "Run full tests every N iterations" -r
|
|
85
128
|
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l max-iterations -d "Maximum loop iterations" -r
|
|
86
129
|
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l model -d "Claude model to use" -ra "opus sonnet haiku"
|
|
87
130
|
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l agents -d "Number of agents" -r
|
|
131
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l roles -d "Role per agent" -r
|
|
132
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l worktree -d "Use git worktrees for isolation"
|
|
88
133
|
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l audit -d "Enable self-reflection each iteration"
|
|
89
134
|
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l audit-agent -d "Use separate auditor agent"
|
|
90
135
|
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l quality-gates -d "Enable automated quality checks"
|
|
91
136
|
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l definition-of-done -d "Custom completion checklist" -rF
|
|
137
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l no-auto-extend -d "Disable auto-extension"
|
|
138
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l extension-size -d "Additional iterations per extension" -r
|
|
139
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l max-extensions -d "Max number of auto-extensions" -r
|
|
92
140
|
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l resume -d "Resume interrupted loop"
|
|
141
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l max-restarts -d "Max session restarts" -r
|
|
142
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l max-turns -d "Max API turns per session" -r
|
|
93
143
|
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l skip-permissions -d "Skip permission prompts"
|
|
144
|
+
complete -c $cmd -n "__fish_seen_subcommand_from loop" -l verbose -d "Show full Claude output"
|
|
94
145
|
|
|
95
146
|
# logs flags
|
|
96
147
|
complete -c $cmd -n "__fish_seen_subcommand_from logs" -l follow -d "Tail logs in real time"
|
|
@@ -104,4 +155,31 @@ for cmd in shipwright sw cct
|
|
|
104
155
|
|
|
105
156
|
# reaper flags
|
|
106
157
|
complete -c $cmd -n "__fish_seen_subcommand_from reaper" -l watch -d "Continuous watch mode"
|
|
158
|
+
|
|
159
|
+
# pipeline flags
|
|
160
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l issue -d "GitHub issue number" -r
|
|
161
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l goal -d "Goal description" -r
|
|
162
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l repo -d "Change to directory before running" -r
|
|
163
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l local -d "Local-only mode (no GitHub)"
|
|
164
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l pipeline -d "Pipeline template" -r
|
|
165
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l template -d "Pipeline template" -r
|
|
166
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l test-cmd -d "Test command to run" -r
|
|
167
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l model -d "AI model to use" -ra "opus sonnet haiku"
|
|
168
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l agents -d "Number of agents" -r
|
|
169
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l skip-gates -d "Auto-approve all gates"
|
|
170
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l base -d "Base branch for PR" -r
|
|
171
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l reviewers -d "PR reviewers" -r
|
|
172
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l labels -d "PR labels" -r
|
|
173
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l no-github -d "Disable GitHub integration"
|
|
174
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l no-github-label -d "Don't modify issue labels"
|
|
175
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l ci -d "CI mode (non-interactive)"
|
|
176
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l ignore-budget -d "Skip budget enforcement"
|
|
177
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l worktree -d "Run in isolated worktree"
|
|
178
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l dry-run -d "Show what would happen"
|
|
179
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l slack-webhook -d "Slack webhook URL" -r
|
|
180
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l self-heal -d "Build retry cycles" -r
|
|
181
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l max-iterations -d "Max build loop iterations" -r
|
|
182
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l max-restarts -d "Max session restarts" -r
|
|
183
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l fast-test-cmd -d "Fast/subset test command" -r
|
|
184
|
+
complete -c $cmd -n "__fish_seen_subcommand_from pipeline" -l completed-stages -d "Skip these stages" -r
|
|
107
185
|
end
|
package/dashboard/server.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "fs";
|
|
13
13
|
import { join, extname } from "path";
|
|
14
14
|
import { execSync } from "child_process";
|
|
15
|
+
import { Database } from "bun:sqlite";
|
|
15
16
|
|
|
16
17
|
// ─── Config ──────────────────────────────────────────────────────────
|
|
17
18
|
const PORT = parseInt(
|
|
@@ -40,6 +41,107 @@ const SESSION_SECRET = process.env.SESSION_SECRET || crypto.randomUUID();
|
|
|
40
41
|
const SESSION_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
41
42
|
const ALLOWED_PERMISSIONS = ["admin", "write"];
|
|
42
43
|
|
|
44
|
+
// ─── SQLite Database (optional) ──────────────────────────────────────
|
|
45
|
+
const DB_FILE = join(HOME, ".shipwright", "shipwright.db");
|
|
46
|
+
let db: Database | null = null;
|
|
47
|
+
|
|
48
|
+
function getDb(): Database | null {
|
|
49
|
+
if (db) return db;
|
|
50
|
+
try {
|
|
51
|
+
if (!existsSync(DB_FILE)) return null;
|
|
52
|
+
db = new Database(DB_FILE, { readonly: true });
|
|
53
|
+
db.exec("PRAGMA journal_mode=WAL;");
|
|
54
|
+
return db;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function dbQueryEvents(since?: number, limit = 200): DaemonEvent[] {
|
|
61
|
+
const conn = getDb();
|
|
62
|
+
if (!conn) return [];
|
|
63
|
+
try {
|
|
64
|
+
const cutoff = since || 0;
|
|
65
|
+
const rows = conn
|
|
66
|
+
.query(
|
|
67
|
+
`SELECT ts, ts_epoch, type, job_id, stage, status, duration_secs, metadata
|
|
68
|
+
FROM events WHERE ts_epoch >= ? ORDER BY ts_epoch DESC LIMIT ?`,
|
|
69
|
+
)
|
|
70
|
+
.all(cutoff, limit) as Array<Record<string, unknown>>;
|
|
71
|
+
return rows.map((r) => {
|
|
72
|
+
const base: DaemonEvent = {
|
|
73
|
+
ts: r.ts as string,
|
|
74
|
+
ts_epoch: r.ts_epoch as number,
|
|
75
|
+
type: r.type as string,
|
|
76
|
+
};
|
|
77
|
+
if (r.job_id)
|
|
78
|
+
base.issue = parseInt(String(r.job_id).replace(/\D/g, "")) || undefined;
|
|
79
|
+
if (r.stage) base.stage = r.stage as string;
|
|
80
|
+
if (r.duration_secs) base.duration_s = r.duration_secs as number;
|
|
81
|
+
if (r.status) base.result = r.status as string;
|
|
82
|
+
if (r.metadata) {
|
|
83
|
+
try {
|
|
84
|
+
Object.assign(base, JSON.parse(r.metadata as string));
|
|
85
|
+
} catch {
|
|
86
|
+
/* ignore malformed metadata */
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return base;
|
|
90
|
+
});
|
|
91
|
+
} catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function dbQueryJobs(status?: string): Array<Record<string, unknown>> {
|
|
97
|
+
const conn = getDb();
|
|
98
|
+
if (!conn) return [];
|
|
99
|
+
try {
|
|
100
|
+
if (status) {
|
|
101
|
+
return conn
|
|
102
|
+
.query(
|
|
103
|
+
"SELECT * FROM daemon_state WHERE status = ? ORDER BY started_at DESC",
|
|
104
|
+
)
|
|
105
|
+
.all(status) as Array<Record<string, unknown>>;
|
|
106
|
+
}
|
|
107
|
+
return conn
|
|
108
|
+
.query("SELECT * FROM daemon_state ORDER BY started_at DESC LIMIT 50")
|
|
109
|
+
.all() as Array<Record<string, unknown>>;
|
|
110
|
+
} catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function dbQueryCostsToday(): { total: number; count: number } {
|
|
116
|
+
const conn = getDb();
|
|
117
|
+
if (!conn) return { total: 0, count: 0 };
|
|
118
|
+
try {
|
|
119
|
+
const todayStart = new Date();
|
|
120
|
+
todayStart.setUTCHours(0, 0, 0, 0);
|
|
121
|
+
const epoch = Math.floor(todayStart.getTime() / 1000);
|
|
122
|
+
const row = conn
|
|
123
|
+
.query(
|
|
124
|
+
"SELECT COALESCE(SUM(cost_usd), 0) as total, COUNT(*) as count FROM cost_entries WHERE ts_epoch >= ?",
|
|
125
|
+
)
|
|
126
|
+
.get(epoch) as { total: number; count: number } | null;
|
|
127
|
+
return row || { total: 0, count: 0 };
|
|
128
|
+
} catch {
|
|
129
|
+
return { total: 0, count: 0 };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function dbQueryHeartbeats(): Array<Record<string, unknown>> {
|
|
134
|
+
const conn = getDb();
|
|
135
|
+
if (!conn) return [];
|
|
136
|
+
try {
|
|
137
|
+
return conn
|
|
138
|
+
.query("SELECT * FROM heartbeats ORDER BY updated_at DESC")
|
|
139
|
+
.all() as Array<Record<string, unknown>>;
|
|
140
|
+
} catch {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
43
145
|
// ─── ANSI helpers ────────────────────────────────────────────────────
|
|
44
146
|
const CYAN = "\x1b[38;2;0;212;255m";
|
|
45
147
|
const GREEN = "\x1b[38;2;74;222;128m";
|
|
@@ -602,6 +704,11 @@ function broadcastToClients(data: FleetState): void {
|
|
|
602
704
|
|
|
603
705
|
// ─── Data Collection ─────────────────────────────────────────────────
|
|
604
706
|
function readEvents(): DaemonEvent[] {
|
|
707
|
+
// Try SQLite first (faster for large event logs)
|
|
708
|
+
const dbEvents = dbQueryEvents(0, 10000);
|
|
709
|
+
if (dbEvents.length > 0) return dbEvents;
|
|
710
|
+
|
|
711
|
+
// Fallback to JSONL
|
|
605
712
|
if (!existsSync(EVENTS_FILE)) return [];
|
|
606
713
|
try {
|
|
607
714
|
const content = readFileSync(EVENTS_FILE, "utf-8").trim();
|
|
@@ -2610,6 +2717,107 @@ const server = Bun.serve({
|
|
|
2610
2717
|
});
|
|
2611
2718
|
}
|
|
2612
2719
|
|
|
2720
|
+
// ── SQLite DB API endpoints ─────────────────────────────────────
|
|
2721
|
+
|
|
2722
|
+
// REST: Events from DB with since/limit params
|
|
2723
|
+
if (pathname === "/api/db/events") {
|
|
2724
|
+
const since = parseInt(url.searchParams.get("since") || "0");
|
|
2725
|
+
const limit = Math.min(
|
|
2726
|
+
parseInt(url.searchParams.get("limit") || "200"),
|
|
2727
|
+
10000,
|
|
2728
|
+
);
|
|
2729
|
+
const events = dbQueryEvents(since, limit);
|
|
2730
|
+
return new Response(
|
|
2731
|
+
JSON.stringify({
|
|
2732
|
+
events,
|
|
2733
|
+
source: events.length > 0 ? "sqlite" : "none",
|
|
2734
|
+
}),
|
|
2735
|
+
{
|
|
2736
|
+
headers: { "Content-Type": "application/json", ...CORS_HEADERS },
|
|
2737
|
+
},
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
// REST: Active/completed jobs from DB
|
|
2742
|
+
if (pathname === "/api/db/jobs") {
|
|
2743
|
+
const status = url.searchParams.get("status") || undefined;
|
|
2744
|
+
const jobs = dbQueryJobs(status);
|
|
2745
|
+
return new Response(
|
|
2746
|
+
JSON.stringify({ jobs, source: jobs.length > 0 ? "sqlite" : "none" }),
|
|
2747
|
+
{
|
|
2748
|
+
headers: { "Content-Type": "application/json", ...CORS_HEADERS },
|
|
2749
|
+
},
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
// REST: Today's cost from DB
|
|
2754
|
+
if (pathname === "/api/db/costs/today") {
|
|
2755
|
+
const costs = dbQueryCostsToday();
|
|
2756
|
+
return new Response(JSON.stringify({ ...costs, source: "sqlite" }), {
|
|
2757
|
+
headers: { "Content-Type": "application/json", ...CORS_HEADERS },
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
// REST: Heartbeats from DB
|
|
2762
|
+
if (pathname === "/api/db/heartbeats") {
|
|
2763
|
+
const heartbeats = dbQueryHeartbeats();
|
|
2764
|
+
return new Response(
|
|
2765
|
+
JSON.stringify({
|
|
2766
|
+
heartbeats,
|
|
2767
|
+
source: heartbeats.length > 0 ? "sqlite" : "none",
|
|
2768
|
+
}),
|
|
2769
|
+
{
|
|
2770
|
+
headers: { "Content-Type": "application/json", ...CORS_HEADERS },
|
|
2771
|
+
},
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
// REST: DB health info
|
|
2776
|
+
if (pathname === "/api/db/health") {
|
|
2777
|
+
const conn = getDb();
|
|
2778
|
+
if (!conn) {
|
|
2779
|
+
return new Response(JSON.stringify({ available: false }), {
|
|
2780
|
+
headers: { "Content-Type": "application/json", ...CORS_HEADERS },
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
try {
|
|
2784
|
+
const version = conn
|
|
2785
|
+
.query("SELECT MAX(version) as v FROM _schema")
|
|
2786
|
+
.get() as { v: number } | null;
|
|
2787
|
+
const walMode = conn.query("PRAGMA journal_mode").get() as {
|
|
2788
|
+
journal_mode: string;
|
|
2789
|
+
} | null;
|
|
2790
|
+
const eventCount = conn
|
|
2791
|
+
.query("SELECT COUNT(*) as c FROM events")
|
|
2792
|
+
.get() as { c: number } | null;
|
|
2793
|
+
const runCount = conn
|
|
2794
|
+
.query("SELECT COUNT(*) as c FROM pipeline_runs")
|
|
2795
|
+
.get() as { c: number } | null;
|
|
2796
|
+
const costCount = conn
|
|
2797
|
+
.query("SELECT COUNT(*) as c FROM cost_entries")
|
|
2798
|
+
.get() as { c: number } | null;
|
|
2799
|
+
|
|
2800
|
+
return new Response(
|
|
2801
|
+
JSON.stringify({
|
|
2802
|
+
available: true,
|
|
2803
|
+
schema_version: version?.v || 0,
|
|
2804
|
+
wal_mode: walMode?.journal_mode || "unknown",
|
|
2805
|
+
events: eventCount?.c || 0,
|
|
2806
|
+
runs: runCount?.c || 0,
|
|
2807
|
+
costs: costCount?.c || 0,
|
|
2808
|
+
}),
|
|
2809
|
+
{ headers: { "Content-Type": "application/json", ...CORS_HEADERS } },
|
|
2810
|
+
);
|
|
2811
|
+
} catch {
|
|
2812
|
+
return new Response(
|
|
2813
|
+
JSON.stringify({ available: false, error: "query failed" }),
|
|
2814
|
+
{
|
|
2815
|
+
headers: { "Content-Type": "application/json", ...CORS_HEADERS },
|
|
2816
|
+
},
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2613
2821
|
// REST: Memory failure patterns for a specific issue
|
|
2614
2822
|
if (pathname.startsWith("/api/memory/failures/")) {
|
|
2615
2823
|
const issueNum = parseInt(pathname.split("/")[4] || "0");
|