propr-cli 0.8.3 → 0.8.4

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.
Files changed (38) hide show
  1. package/README.md +4 -4
  2. package/dist/api/relay.js +10 -0
  3. package/dist/assets/env.example.txt +93 -57
  4. package/dist/auth/githubLogin.js +66 -0
  5. package/dist/commands/agentCommands.js +74 -0
  6. package/dist/commands/agentValidation.js +548 -0
  7. package/dist/commands/checkCommands.js +981 -76
  8. package/dist/commands/imageCommands.js +60 -0
  9. package/dist/commands/index.js +2 -0
  10. package/dist/commands/initStack.js +50 -1
  11. package/dist/commands/relayCommands.js +45 -12
  12. package/dist/commands/setup/agents.js +185 -0
  13. package/dist/commands/setup/engine.js +956 -0
  14. package/dist/commands/setup/github.js +181 -0
  15. package/dist/commands/setup/sequential.js +501 -0
  16. package/dist/commands/setup/state.js +242 -0
  17. package/dist/commands/setup/types.js +85 -0
  18. package/dist/commands/setupCommand.js +85 -0
  19. package/dist/commands/systemCommands.js +49 -2
  20. package/dist/index.js +13 -45
  21. package/dist/orchestrator/manifest.json +10 -10
  22. package/dist/orchestrator/orchestrator.mjs +513 -61
  23. package/dist/tui/AgentTableApp.js +86 -0
  24. package/dist/tui/CheckApp.js +202 -0
  25. package/dist/tui/SetupApp.js +586 -0
  26. package/dist/tui/SetupApp.test.js +172 -0
  27. package/dist/tui/app.js +84 -0
  28. package/dist/tui/render.js +11 -0
  29. package/dist/utils/envFile.js +45 -0
  30. package/dist/vendor/shared/githubEventIntakeMode.js +41 -0
  31. package/dist/vendor/shared/index.js +16 -0
  32. package/dist/vendor/shared/intakeModePrerequisites.js +76 -0
  33. package/dist/vendor/shared/modelDefinitions.js +4 -4
  34. package/dist/vendor/shared/proprServiceUrls.js +27 -0
  35. package/dist/vendor/shared/statusKeys.js +14 -0
  36. package/dist/vendor/shared/validateRoutingUrl.js +46 -0
  37. package/package.json +2 -2
  38. package/dist/assets/.env.example +0 -183
@@ -0,0 +1,86 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Live Ink table for `propr check` agent validation. Renders one row per agent
4
+ * with spinner placeholders, then fills each cell (version, host, image) in as
5
+ * the corresponding check resolves — mirroring the streaming system-check view.
6
+ */
7
+ import { useEffect, useReducer, useState } from "react";
8
+ import { Box, Text, useApp } from "ink";
9
+ const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
10
+ const VW = 10; // version column width
11
+ const DW = 6; // drift column width
12
+ const SW = 7; // status column width
13
+ const GAP = 2;
14
+ /** Pub/sub bridge between the streaming validator and the React tree. */
15
+ export class AgentTableHub {
16
+ listeners = new Set();
17
+ history = [];
18
+ emit(event) {
19
+ this.history.push(event);
20
+ for (const listener of [...this.listeners])
21
+ listener(event);
22
+ }
23
+ subscribe(listener) {
24
+ this.listeners.add(listener);
25
+ for (const event of this.history)
26
+ listener(event);
27
+ return () => {
28
+ this.listeners.delete(listener);
29
+ };
30
+ }
31
+ }
32
+ function reducer(rows, event) {
33
+ if (event.type !== "update")
34
+ return rows;
35
+ return rows.map((r) => {
36
+ if (r.type !== event.agent)
37
+ return r;
38
+ const u = event.update;
39
+ if (u.field === "version")
40
+ return { ...r, versionDone: true, hostVersion: u.hostVersion, imageVersion: u.imageVersion, drift: u.drift };
41
+ if (u.field === "host")
42
+ return { ...r, host: u.cell };
43
+ return { ...r, image: u.cell };
44
+ });
45
+ }
46
+ function StatusCell({ cell, frame }) {
47
+ if (!cell)
48
+ return _jsxs(Text, { color: "cyan", children: [SPINNER[frame % SPINNER.length], " run"] });
49
+ if (cell.status === "ok")
50
+ return _jsx(Text, { color: "green", children: "\u2713 ok" });
51
+ if (cell.status === "fail")
52
+ return _jsx(Text, { color: "red", children: "\u2717 fail" });
53
+ return _jsx(Text, { color: "yellow", children: "\u2014 skip" });
54
+ }
55
+ export function AgentTableApp({ agents, hub }) {
56
+ const { exit } = useApp();
57
+ const [rows, dispatch] = useReducer(reducer, agents.map((type) => ({ type, versionDone: false })));
58
+ const [frame, setFrame] = useState(0);
59
+ const [done, setDone] = useState(false);
60
+ useEffect(() => {
61
+ return hub.subscribe((event) => {
62
+ if (event.type === "done")
63
+ setDone(true);
64
+ else
65
+ dispatch(event);
66
+ });
67
+ }, [hub]);
68
+ useEffect(() => {
69
+ if (done)
70
+ return;
71
+ const timer = setInterval(() => setFrame((f) => f + 1), 80);
72
+ return () => clearInterval(timer);
73
+ }, [done]);
74
+ useEffect(() => {
75
+ if (!done)
76
+ return;
77
+ const timer = setTimeout(() => exit(), 20);
78
+ return () => clearTimeout(timer);
79
+ }, [done, exit]);
80
+ const agentW = Math.max("Agent".length, ...agents.map((a) => a.length));
81
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Box, { width: agentW + GAP, children: _jsx(Text, { bold: true, children: "Agent" }) }), _jsx(Box, { width: VW + GAP, children: _jsx(Text, { bold: true, children: "Host ver" }) }), _jsx(Box, { width: VW + GAP, children: _jsx(Text, { bold: true, children: "Image ver" }) }), _jsx(Box, { width: DW + GAP, children: _jsx(Text, { bold: true, children: "Drift" }) }), _jsx(Box, { width: SW + GAP, children: _jsx(Text, { bold: true, children: "Host" }) }), _jsx(Box, { width: SW, children: _jsx(Text, { bold: true, children: "Image" }) })] }), rows.map((r) => {
82
+ const drift = r.versionDone ? (r.drift ?? (r.hostVersion && r.imageVersion ? "same" : "")) : "";
83
+ const driftColor = drift === "older" ? "yellow" : "gray";
84
+ return (_jsxs(Box, { children: [_jsx(Box, { width: agentW + GAP, children: _jsx(Text, { children: r.type }) }), _jsx(Box, { width: VW + GAP, children: r.versionDone ? _jsx(Text, { children: r.hostVersion ?? "—" }) : _jsx(Text, { color: "cyan", children: SPINNER[frame % SPINNER.length] }) }), _jsx(Box, { width: VW + GAP, children: _jsx(Text, { children: r.versionDone ? (r.imageVersion ?? "—") : "" }) }), _jsx(Box, { width: DW + GAP, children: _jsx(Text, { color: driftColor, children: drift }) }), _jsx(Box, { width: SW + GAP, children: _jsx(StatusCell, { cell: r.host, frame: frame }) }), _jsx(Box, { width: SW, children: _jsx(StatusCell, { cell: r.image, frame: frame }) })] }, r.type));
85
+ })] }));
86
+ }
@@ -0,0 +1,202 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * Live Ink renderer for `propr check` in interactive terminals.
4
+ *
5
+ * Rather than rendering a finished report, this subscribes to a CheckHub fed by
6
+ * the streaming check engine: rows appear as each check resolves, slow checks
7
+ * (image freshness) show an animated spinner while they run, and the summary
8
+ * header updates live. With --fix, it ends in an arrow-key remediation menu;
9
+ * the selected action's key is reported to the caller, which runs it (and any
10
+ * console output) outside the Ink tree before re-rendering a fresh pass.
11
+ */
12
+ import { useEffect, useReducer, useState } from "react";
13
+ import { Box, Text, useApp, useInput } from "ink";
14
+ import { CHECK_GROUPS, GROUP_DESCRIPTIONS, GROUP_TITLES, plural, } from "../commands/checkCommands.js";
15
+ /** Minimal pub/sub bridge between the async engine and the React tree. */
16
+ export class CheckHub {
17
+ listeners = new Set();
18
+ history = [];
19
+ emit(event) {
20
+ this.history.push(event);
21
+ for (const listener of [...this.listeners])
22
+ listener(event);
23
+ }
24
+ subscribe(listener) {
25
+ this.listeners.add(listener);
26
+ for (const event of this.history)
27
+ listener(event);
28
+ return () => {
29
+ this.listeners.delete(listener);
30
+ };
31
+ }
32
+ }
33
+ const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
34
+ const STATUS_GLYPH = { ok: "✓", warn: "!", fail: "✗" };
35
+ const STATUS_LABEL = { ok: "OK", warn: "WARN", fail: "FAIL" };
36
+ function statusColor(status) {
37
+ if (status === "ok")
38
+ return "green";
39
+ if (status === "warn")
40
+ return "yellow";
41
+ return "red";
42
+ }
43
+ function countRowStatuses(rows) {
44
+ return rows.reduce((acc, row) => {
45
+ acc[row.status] += 1;
46
+ return acc;
47
+ }, { ok: 0, warn: 0, fail: 0 });
48
+ }
49
+ function rowReducer(state, event) {
50
+ if (event.type === "pending") {
51
+ if (state.pendingByName.has(event.slot.name))
52
+ return state;
53
+ const id = state.nextId;
54
+ const pendingByName = new Map(state.pendingByName).set(event.slot.name, id);
55
+ return {
56
+ rows: [...state.rows, { id, name: event.slot.name, group: event.slot.group, status: "pending" }],
57
+ pendingByName,
58
+ nextId: id + 1,
59
+ };
60
+ }
61
+ if (event.type === "result") {
62
+ const { result } = event;
63
+ const pendingId = state.pendingByName.get(result.name);
64
+ if (pendingId !== undefined) {
65
+ const pendingByName = new Map(state.pendingByName);
66
+ pendingByName.delete(result.name);
67
+ return {
68
+ rows: state.rows.map((row) => row.id === pendingId
69
+ ? { id: pendingId, name: result.name, group: result.group, status: result.status, detail: result.detail, fix: result.fix }
70
+ : row),
71
+ pendingByName,
72
+ nextId: state.nextId,
73
+ };
74
+ }
75
+ const id = state.nextId;
76
+ return {
77
+ rows: [...state.rows, { id, name: result.name, group: result.group, status: result.status, detail: result.detail, fix: result.fix }],
78
+ pendingByName: state.pendingByName,
79
+ nextId: id + 1,
80
+ };
81
+ }
82
+ return state;
83
+ }
84
+ function StatusBadge({ status, frame }) {
85
+ if (status === "pending") {
86
+ return (_jsxs(Text, { color: "cyan", bold: true, children: [SPINNER[frame % SPINNER.length], " \u00B7\u00B7\u00B7\u00B7"] }));
87
+ }
88
+ return (_jsxs(Text, { color: statusColor(status), bold: true, children: [STATUS_GLYPH[status], " ", STATUS_LABEL[status].padEnd(4)] }));
89
+ }
90
+ function ResultRow({ row, nameWidth, frame }) {
91
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(StatusBadge, { status: row.status, frame: frame }), _jsxs(Text, { children: [" ", row.name.padEnd(nameWidth), " "] })] }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { children: row.status === "pending" ? _jsx(Text, { dimColor: true, children: "checking\u2026" }) : row.detail }) })] }), row.fix && row.status !== "ok" && row.status !== "pending" ? (_jsxs(Box, { paddingLeft: 9, children: [_jsx(Box, { marginRight: 1, children: _jsx(Text, { dimColor: true, children: "\u21B3" }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { children: row.fix }) })] })) : null] }));
92
+ }
93
+ function SummaryLine({ rows, running }) {
94
+ const finalized = rows.filter((row) => row.status !== "pending");
95
+ const counts = countRowStatuses(finalized);
96
+ return (_jsxs(Box, { children: [_jsx(Text, { children: "Summary: " }), _jsx(Text, { color: counts.fail > 0 ? "red" : undefined, bold: counts.fail > 0, children: plural(counts.fail, "failure") }), _jsx(Text, { children: ", " }), _jsx(Text, { color: counts.warn > 0 ? "yellow" : undefined, bold: counts.warn > 0, children: plural(counts.warn, "warning") }), _jsx(Text, { children: ", " }), _jsxs(Text, { color: "green", children: [counts.ok, " ok"] }), running ? _jsx(Text, { dimColor: true, children: " \u00B7 checking\u2026" }) : null] }));
97
+ }
98
+ function Groups({ rows, frame }) {
99
+ const sections = [];
100
+ const groupsInOrder = [...CHECK_GROUPS, undefined];
101
+ for (const group of groupsInOrder) {
102
+ const groupRows = rows.filter((row) => row.group === group);
103
+ if (groupRows.length === 0)
104
+ continue;
105
+ const nameWidth = Math.max(18, ...groupRows.map((row) => row.name.length));
106
+ const finalized = groupRows.filter((row) => row.status !== "pending");
107
+ const groupCounts = countRowStatuses(finalized);
108
+ const countSuffix = groupCounts.fail > 0 || groupCounts.warn > 0
109
+ ? ` (${plural(groupCounts.fail, "failure")}, ${plural(groupCounts.warn, "warning")})`
110
+ : "";
111
+ sections.push(_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: "cyan", bold: true, children: [group ? GROUP_TITLES[group] : "Other", countSuffix] }), group ? _jsxs(Text, { dimColor: true, children: [" ", GROUP_DESCRIPTIONS[group]] }) : null, groupRows.map((row) => (_jsx(ResultRow, { row: row, nameWidth: nameWidth, frame: frame }, row.id)))] }, group ?? "Other"));
112
+ }
113
+ return _jsx(_Fragment, { children: sections });
114
+ }
115
+ function Menu({ items, selected }) {
116
+ return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Remediations" }), items.map((item, index) => {
117
+ const active = index === selected;
118
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: active ? "cyan" : undefined, bold: active, children: [active ? "❯ " : " ", item.label] }), _jsx(Box, { paddingLeft: 4, children: _jsx(Text, { dimColor: true, children: item.detail }) })] }, item.key));
119
+ }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 select \u00B7 enter choose \u00B7 q quit" }) })] }));
120
+ }
121
+ export function CheckApp({ hub, fix, getActions, onSelect, onCancel, showAgentValidationHint }) {
122
+ const { exit } = useApp();
123
+ const [rowState, dispatch] = useReducer(rowReducer, { rows: [], pendingByName: new Map(), nextId: 0 });
124
+ const [phase, setPhase] = useState("running");
125
+ const [outcome, setOutcome] = useState(null);
126
+ const [actions, setActions] = useState([]);
127
+ const [selected, setSelected] = useState(0);
128
+ const [frame, setFrame] = useState(0);
129
+ const [errored, setErrored] = useState(null);
130
+ useEffect(() => {
131
+ return hub.subscribe((event) => {
132
+ if (event.type === "done") {
133
+ setOutcome(event.outcome);
134
+ const available = fix ? getActions(event.outcome) : [];
135
+ setActions(available);
136
+ if (fix && available.length > 0) {
137
+ setPhase("menu");
138
+ }
139
+ else {
140
+ setPhase("done");
141
+ }
142
+ }
143
+ else if (event.type === "error") {
144
+ setErrored(event.error);
145
+ setPhase("done");
146
+ }
147
+ else {
148
+ dispatch(event);
149
+ }
150
+ });
151
+ }, [hub, fix, getActions]);
152
+ // Animate the spinner only while checks are in flight.
153
+ useEffect(() => {
154
+ if (phase !== "running")
155
+ return;
156
+ const timer = setInterval(() => setFrame((f) => f + 1), 80);
157
+ return () => clearInterval(timer);
158
+ }, [phase]);
159
+ // Leave a short beat for the final frame to paint before unmounting.
160
+ useEffect(() => {
161
+ if (phase !== "done")
162
+ return;
163
+ onSelect(undefined);
164
+ const timer = setTimeout(() => exit(errored ?? undefined), 20);
165
+ return () => clearTimeout(timer);
166
+ }, [phase, exit, onSelect, errored]);
167
+ useInput((input, key) => {
168
+ if (key.ctrl && input === "c") {
169
+ onSelect(undefined);
170
+ onCancel?.();
171
+ exit();
172
+ return;
173
+ }
174
+ if (phase === "action-confirm") {
175
+ if (input.toLowerCase() === "y") {
176
+ onSelect(actions[selected]?.key);
177
+ exit();
178
+ }
179
+ else {
180
+ setPhase("menu");
181
+ }
182
+ return;
183
+ }
184
+ if (phase !== "menu")
185
+ return;
186
+ if (key.upArrow)
187
+ setSelected((index) => (index - 1 + actions.length) % actions.length);
188
+ else if (key.downArrow)
189
+ setSelected((index) => (index + 1) % actions.length);
190
+ else if (key.return) {
191
+ setPhase("action-confirm");
192
+ }
193
+ else if (input === "q" || key.escape) {
194
+ onSelect(undefined);
195
+ exit();
196
+ }
197
+ });
198
+ const rows = rowState.rows;
199
+ const running = phase === "running";
200
+ const showRemediationHint = !fix && phase === "done" && outcome != null && getActions(outcome).length > 0;
201
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "ProPR environment check" }), outcome ? _jsxs(Text, { dimColor: true, children: [" (stack root: ", outcome.rootDir, ")"] }) : null] }), _jsx(SummaryLine, { rows: rows, running: running }), _jsx(Groups, { rows: rows, frame: frame }), errored ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error running checks: ", errored.message] }) })) : null, phase === "menu" ? _jsx(Menu, { items: actions, selected: selected }) : null, phase === "done" || phase === "action-confirm" ? (_jsx(Box, { marginTop: 1, children: _jsx(SummaryLine, { rows: rows, running: running }) })) : null, phase === "done" && showAgentValidationHint ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Agent validation" }), _jsx(Text, { children: "Run `propr check agents` to validate agents only, or `propr check all` to include system checks." }), _jsx(Text, { dimColor: true, children: "Agent validation makes live, billable LLM calls." })] })) : null, phase === "action-confirm" ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "Confirm remediation" }), _jsx(Text, { children: actions[selected]?.confirm }), _jsx(Text, { dimColor: true, children: "Press y to run, any other key to go back." })] })) : null, showRemediationHint ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Run `propr check --fix` to review interactive remediation options." }) })) : null] }));
202
+ }