propr-cli 0.8.3
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 +549 -0
- package/dist/api/agentTank.js +27 -0
- package/dist/api/agents.js +201 -0
- package/dist/api/client.js +284 -0
- package/dist/api/errors.js +145 -0
- package/dist/api/implement.js +147 -0
- package/dist/api/index.js +26 -0
- package/dist/api/logs.js +59 -0
- package/dist/api/plans.js +160 -0
- package/dist/api/relay.js +73 -0
- package/dist/api/repos.js +243 -0
- package/dist/api/settings.js +219 -0
- package/dist/api/system.js +53 -0
- package/dist/api/tasks.js +140 -0
- package/dist/api/todos.js +77 -0
- package/dist/api/types.js +6 -0
- package/dist/assets/.env.example +183 -0
- package/dist/assets/env.example.txt +198 -0
- package/dist/commands/agentCommands.js +405 -0
- package/dist/commands/checkCommands.js +384 -0
- package/dist/commands/implementCommands.js +178 -0
- package/dist/commands/index.js +22 -0
- package/dist/commands/initCommands.js +167 -0
- package/dist/commands/initStack.js +193 -0
- package/dist/commands/logCommands.js +170 -0
- package/dist/commands/planCommands.js +552 -0
- package/dist/commands/relayCommands.js +149 -0
- package/dist/commands/repoCommands.js +526 -0
- package/dist/commands/settingCommands.js +237 -0
- package/dist/commands/stackCommands.js +86 -0
- package/dist/commands/startCommand.js +36 -0
- package/dist/commands/systemCommands.js +221 -0
- package/dist/commands/tankCommands.js +55 -0
- package/dist/commands/taskCommands.js +554 -0
- package/dist/commands/todoCommands.js +620 -0
- package/dist/commands/uiDocsCommands.js +69 -0
- package/dist/config/ConfigManager.js +360 -0
- package/dist/config/index.js +8 -0
- package/dist/config/types.js +16 -0
- package/dist/index.js +276 -0
- package/dist/orchestrator/format.js +31 -0
- package/dist/orchestrator/index.js +102 -0
- package/dist/orchestrator/manifest.json +16 -0
- package/dist/orchestrator/orchestrator.mjs +798 -0
- package/dist/orchestrator/types.js +10 -0
- package/dist/tui/StartApp.js +175 -0
- package/dist/tui/app.js +9 -0
- package/dist/tui/render.js +87 -0
- package/dist/utils/envFile.js +65 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/io.js +186 -0
- package/dist/utils/parseState.js +14 -0
- package/dist/utils/resolveProject.js +50 -0
- package/dist/vendor/shared/demoMode.js +6 -0
- package/dist/vendor/shared/events.js +30 -0
- package/dist/vendor/shared/githubAuthMode.js +35 -0
- package/dist/vendor/shared/index.js +15 -0
- package/dist/vendor/shared/labelUtils.js +32 -0
- package/dist/vendor/shared/modelDefinitions.js +146 -0
- package/dist/vendor/shared/reviewPrompt.js +18 -0
- package/dist/vendor/shared/usageTypes.js +13 -0
- package/dist/vendor/shared/userWhitelist.js +30 -0
- package/dist/vendor/shared/validateRelayUrl.js +21 -0
- package/package.json +31 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Settings Commands
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for managing system settings using the ProPR backend.
|
|
5
|
+
* Provides the `setting` command group with `get` and `update` subcommands.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { getSettings, updateSetting, isValidSettingKey, parseSettingValue, VALID_SETTING_KEYS, } from "../api/index.js";
|
|
9
|
+
import { printOutput, } from "../utils/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Formats a setting value for display.
|
|
12
|
+
*/
|
|
13
|
+
function formatValue(value) {
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
if (value.length === 0) {
|
|
16
|
+
return "(empty)";
|
|
17
|
+
}
|
|
18
|
+
return value.join(", ");
|
|
19
|
+
}
|
|
20
|
+
if (value === null || value === undefined || value === "") {
|
|
21
|
+
return "(not set)";
|
|
22
|
+
}
|
|
23
|
+
return String(value);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gets a human-readable description for a setting key.
|
|
27
|
+
*/
|
|
28
|
+
function getSettingDescription(key) {
|
|
29
|
+
const descriptions = {
|
|
30
|
+
default_agent_alias: "Alias of the default implementation agent",
|
|
31
|
+
worker_concurrency: "Number of concurrent workers for processing tasks",
|
|
32
|
+
github_user_whitelist: "GitHub usernames allowed to use the system",
|
|
33
|
+
analysis_model_fast: "Model for fast analysis operations",
|
|
34
|
+
planner_context_model: "Model for planner context generation",
|
|
35
|
+
planner_generation_model: "Model for planner generation",
|
|
36
|
+
auto_followup_score_threshold: "Score threshold (0-9) for auto-followup",
|
|
37
|
+
auto_resolve_merge_conflicts: "Automatically resolve merge conflicts",
|
|
38
|
+
pr_review_model: "Model for full PR reviews",
|
|
39
|
+
pr_review_prompt: "Override for the PR review prompt guidance (empty = built-in default)",
|
|
40
|
+
ultrafix_rating_goal: "Target quality rating for ultrafix cycles",
|
|
41
|
+
ultrafix_max_cycles: "Maximum number of ultrafix cycles",
|
|
42
|
+
ultrafix_pause_seconds: "Pause duration between ultrafix cycles",
|
|
43
|
+
};
|
|
44
|
+
return descriptions[key];
|
|
45
|
+
}
|
|
46
|
+
function formatSettingKeysHelp() {
|
|
47
|
+
return VALID_SETTING_KEYS
|
|
48
|
+
.map((key) => ` ${key.padEnd(32)} ${getSettingDescription(key)}`)
|
|
49
|
+
.join("\n");
|
|
50
|
+
}
|
|
51
|
+
function printValidSettingKeys(includeDescriptions = false) {
|
|
52
|
+
console.log("Valid setting keys:");
|
|
53
|
+
for (const validKey of VALID_SETTING_KEYS) {
|
|
54
|
+
const description = includeDescriptions ? `: ${getSettingDescription(validKey)}` : "";
|
|
55
|
+
console.log(` - ${validKey}${description}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Displays all settings in a formatted table.
|
|
60
|
+
*/
|
|
61
|
+
function displaySettingsTable(settings) {
|
|
62
|
+
const keys = Object.keys(settings);
|
|
63
|
+
const keyWidth = Math.max("Setting".length, ...keys.map((k) => k.length));
|
|
64
|
+
const valueWidth = Math.max("Value".length, ...keys.map((k) => formatValue(settings[k]).length));
|
|
65
|
+
const header = [
|
|
66
|
+
"Setting".padEnd(keyWidth),
|
|
67
|
+
"Value".padEnd(valueWidth),
|
|
68
|
+
].join(" ");
|
|
69
|
+
console.log(header);
|
|
70
|
+
console.log("-".repeat(header.length));
|
|
71
|
+
for (const key of keys) {
|
|
72
|
+
const value = settings[key];
|
|
73
|
+
const row = [
|
|
74
|
+
key.padEnd(keyWidth),
|
|
75
|
+
formatValue(value).padEnd(valueWidth),
|
|
76
|
+
].join(" ");
|
|
77
|
+
console.log(row);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Displays detailed information about a single setting.
|
|
82
|
+
*/
|
|
83
|
+
function displaySettingDetail(key, value) {
|
|
84
|
+
console.log("");
|
|
85
|
+
console.log("=".repeat(50));
|
|
86
|
+
console.log(`Setting: ${key}`);
|
|
87
|
+
console.log("=".repeat(50));
|
|
88
|
+
console.log("");
|
|
89
|
+
console.log(`Description: ${getSettingDescription(key)}`);
|
|
90
|
+
console.log(`Value: ${formatValue(value)}`);
|
|
91
|
+
console.log("");
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Creates the `setting` command group.
|
|
95
|
+
*/
|
|
96
|
+
export function createSettingCommand() {
|
|
97
|
+
const setting = new Command("setting")
|
|
98
|
+
.description("Manage system settings")
|
|
99
|
+
.addHelpText("after", `
|
|
100
|
+
Examples:
|
|
101
|
+
$ propr setting get # Show all settings
|
|
102
|
+
$ propr setting get -k worker_concurrency # Show specific setting
|
|
103
|
+
$ propr setting update worker_concurrency 10 # Update a setting
|
|
104
|
+
`);
|
|
105
|
+
// setting get
|
|
106
|
+
setting
|
|
107
|
+
.command("get")
|
|
108
|
+
.description("View current system settings for ProPR backend")
|
|
109
|
+
.option("-k, --key <key>", "Show only a specific setting key")
|
|
110
|
+
.option("-j, --json", "Output settings as JSON")
|
|
111
|
+
.addHelpText("after", `
|
|
112
|
+
Valid Setting Keys:
|
|
113
|
+
${formatSettingKeysHelp()}
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
$ propr setting get # Show all settings
|
|
117
|
+
$ propr setting get -k worker_concurrency # Show specific setting
|
|
118
|
+
$ propr setting get --json # Output as JSON
|
|
119
|
+
`)
|
|
120
|
+
.action(async (options) => {
|
|
121
|
+
try {
|
|
122
|
+
const settings = await getSettings();
|
|
123
|
+
if (options.json) {
|
|
124
|
+
if (options.key) {
|
|
125
|
+
if (!isValidSettingKey(options.key)) {
|
|
126
|
+
console.error(`Error: Invalid setting key: ${options.key}`);
|
|
127
|
+
console.log("");
|
|
128
|
+
printValidSettingKeys();
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
printOutput({ [options.key]: settings[options.key] }, true);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
printOutput(settings, true);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (options.key) {
|
|
139
|
+
if (!isValidSettingKey(options.key)) {
|
|
140
|
+
console.error(`Error: Invalid setting key: ${options.key}`);
|
|
141
|
+
console.log("");
|
|
142
|
+
printValidSettingKeys();
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
displaySettingDetail(options.key, settings[options.key]);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
console.log("Fetching system settings...");
|
|
149
|
+
console.log("");
|
|
150
|
+
displaySettingsTable(settings);
|
|
151
|
+
console.log("");
|
|
152
|
+
console.log(`Total: ${Object.keys(settings).length} setting(s)`);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
const errorMessage = error.message;
|
|
156
|
+
if (errorMessage.includes("401") ||
|
|
157
|
+
errorMessage.includes("unauthorized")) {
|
|
158
|
+
console.error("Error: Unauthorized. Please run 'propr login' first.");
|
|
159
|
+
}
|
|
160
|
+
else if (errorMessage.includes("403") ||
|
|
161
|
+
errorMessage.includes("forbidden")) {
|
|
162
|
+
console.error("Error: Access denied. You do not have permission to view settings.");
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.error(`Error fetching settings: ${errorMessage}`);
|
|
166
|
+
}
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// setting update
|
|
171
|
+
setting
|
|
172
|
+
.command("update <key> <value>")
|
|
173
|
+
.description("Update a system setting")
|
|
174
|
+
.addHelpText("after", `
|
|
175
|
+
Valid setting keys:
|
|
176
|
+
${formatSettingKeysHelp()}
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
$ propr setting update worker_concurrency 10
|
|
180
|
+
$ propr setting update auto_followup_score_threshold 7
|
|
181
|
+
$ propr setting update github_user_whitelist "user1,user2,user3"
|
|
182
|
+
$ propr setting update analysis_model_fast claude-3-5-sonnet-20241022
|
|
183
|
+
`)
|
|
184
|
+
.action(async (key, value) => {
|
|
185
|
+
try {
|
|
186
|
+
if (!isValidSettingKey(key)) {
|
|
187
|
+
console.error(`Error: Invalid setting key: ${key}`);
|
|
188
|
+
console.log("");
|
|
189
|
+
printValidSettingKeys(true);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
let parsedValue;
|
|
193
|
+
try {
|
|
194
|
+
parsedValue = parseSettingValue(key, value);
|
|
195
|
+
}
|
|
196
|
+
catch (parseError) {
|
|
197
|
+
console.error(`Error: ${parseError.message}`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
console.log(`Updating setting: ${key}...`);
|
|
201
|
+
const result = await updateSetting(key, parsedValue);
|
|
202
|
+
if (result.success) {
|
|
203
|
+
console.log("");
|
|
204
|
+
console.log(`Successfully updated setting: ${key}`);
|
|
205
|
+
console.log(` New value: ${formatValue(parsedValue)}`);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
console.error("Failed to update setting.");
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
const errorMessage = error.message;
|
|
214
|
+
if (errorMessage.includes("400")) {
|
|
215
|
+
console.error(`Error: Invalid value for setting "${key}".`);
|
|
216
|
+
console.log("");
|
|
217
|
+
console.log(`Description: ${getSettingDescription(key)}`);
|
|
218
|
+
}
|
|
219
|
+
else if (errorMessage.includes("401") ||
|
|
220
|
+
errorMessage.includes("unauthorized")) {
|
|
221
|
+
console.error("Error: Unauthorized. Please run 'propr login' first.");
|
|
222
|
+
}
|
|
223
|
+
else if (errorMessage.includes("403") ||
|
|
224
|
+
errorMessage.includes("forbidden")) {
|
|
225
|
+
console.error("Error: Access denied. You do not have permission to update settings.");
|
|
226
|
+
}
|
|
227
|
+
else if (errorMessage.includes("409")) {
|
|
228
|
+
console.error("Error: Configuration is being updated. Please try again.");
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
console.error(`Error updating setting: ${errorMessage}`);
|
|
232
|
+
}
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
return setting;
|
|
237
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-plane stack commands: `propr status` and `propr stop`.
|
|
3
|
+
*
|
|
4
|
+
* (`propr start` lives in ../tui because it renders a live dashboard.)
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { createConfigManager } from "../config/index.js";
|
|
8
|
+
import { getHostConfig } from "../orchestrator/index.js";
|
|
9
|
+
import { renderStatusTable } from "../orchestrator/format.js";
|
|
10
|
+
import { printOutput } from "../utils/index.js";
|
|
11
|
+
/** Creates the `status` command — local stack status. */
|
|
12
|
+
export function createStackStatusCommand() {
|
|
13
|
+
return new Command("status")
|
|
14
|
+
.description("Show the status of the local ProPR stack")
|
|
15
|
+
.option("--root <dir>", "Stack root directory")
|
|
16
|
+
.option("--json", "Output raw JSON")
|
|
17
|
+
.addHelpText("after", `
|
|
18
|
+
Examples:
|
|
19
|
+
$ propr status
|
|
20
|
+
$ propr status --json
|
|
21
|
+
|
|
22
|
+
(For backend job/queue status of a remote ProPR server, use 'propr remote-status'.)
|
|
23
|
+
`)
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
try {
|
|
26
|
+
const configManager = await createConfigManager();
|
|
27
|
+
const { orch, cfg } = await getHostConfig({ configManager, root: options.root });
|
|
28
|
+
if (!orch.dockerAvailable()) {
|
|
29
|
+
console.error("Error: cannot reach the Docker daemon. Run 'propr check'.");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const status = orch.getStackStatus(cfg);
|
|
33
|
+
if (printOutput(status, options.json ?? false))
|
|
34
|
+
return;
|
|
35
|
+
console.log("");
|
|
36
|
+
console.log(renderStatusTable(status));
|
|
37
|
+
console.log("");
|
|
38
|
+
if (!status.running) {
|
|
39
|
+
console.log("Stack is not running. Start it with: propr start");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error(`Error reading stack status: ${error.message}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** Creates the `stop` command — stop (and remove) the local stack. */
|
|
49
|
+
export function createStopCommand() {
|
|
50
|
+
return new Command("stop")
|
|
51
|
+
.description("Stop the local ProPR stack")
|
|
52
|
+
.option("--root <dir>", "Stack root directory")
|
|
53
|
+
.option("--keep", "Stop containers without removing them")
|
|
54
|
+
.addHelpText("after", `
|
|
55
|
+
Examples:
|
|
56
|
+
$ propr stop
|
|
57
|
+
$ propr stop --keep
|
|
58
|
+
`)
|
|
59
|
+
.action(async (options) => {
|
|
60
|
+
try {
|
|
61
|
+
const configManager = await createConfigManager();
|
|
62
|
+
const { orch, cfg } = await getHostConfig({ configManager, root: options.root });
|
|
63
|
+
if (!orch.dockerAvailable()) {
|
|
64
|
+
console.error("Error: cannot reach the Docker daemon. Run 'propr check'.");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const before = orch.getStackStatus(cfg);
|
|
68
|
+
if (!before.services.some((s) => s.exists)) {
|
|
69
|
+
console.log("No ProPR stack containers found — nothing to stop.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log("Stopping ProPR stack…");
|
|
73
|
+
const { failed } = orch.stopStack(cfg, { remove: !options.keep, removeNetwork: !options.keep, onLog: (l) => console.log(l) });
|
|
74
|
+
if (failed.length > 0) {
|
|
75
|
+
console.error(`\nError: ${failed.length} container(s) could not be stopped: ${failed.join(", ")}`);
|
|
76
|
+
console.error("Inspect them with `docker ps` / `docker logs` and retry `propr stop`.");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
console.log(options.keep ? "Stack stopped (containers kept)." : "Stack stopped and removed.");
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error(`Error stopping stack: ${error.message}`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `propr start` — launch the local ProPR stack with a live dashboard.
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { createConfigManager } from "../config/index.js";
|
|
6
|
+
import { runStart } from "../tui/render.js";
|
|
7
|
+
export function createStartCommand() {
|
|
8
|
+
return new Command("start")
|
|
9
|
+
.description("Start the local ProPR stack and show a live status dashboard")
|
|
10
|
+
.option("--root <dir>", "Stack root directory (where .env/data/logs/repos live)")
|
|
11
|
+
.option("--no-tui", "Skip the live dashboard; start and print a status snapshot")
|
|
12
|
+
.option("--no-pull", "Skip pulling images before starting")
|
|
13
|
+
.option("--restart", "Recreate services if the stack is already running")
|
|
14
|
+
.addHelpText("after", `
|
|
15
|
+
Keys (live dashboard):
|
|
16
|
+
b background (keep the stack running) q stop the stack
|
|
17
|
+
l follow logs for the selected service ↑/↓ select a service
|
|
18
|
+
u toggle the UI service r refresh ? help
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
$ propr start
|
|
22
|
+
$ propr start --restart
|
|
23
|
+
$ propr start --no-tui
|
|
24
|
+
$ propr start --root ~/propr
|
|
25
|
+
`)
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
try {
|
|
28
|
+
const configManager = await createConfigManager();
|
|
29
|
+
await runStart(configManager, options);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error(`Error starting stack: ${error.message}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Status Commands
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for checking system health and queue statistics.
|
|
5
|
+
* Provides `status` and `queue` as top-level commands.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { getSystemStatus, getQueueStats, } from "../api/index.js";
|
|
9
|
+
import { printOutput } from "../utils/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Formats a status value with color-like indicators for terminal display.
|
|
12
|
+
*/
|
|
13
|
+
function formatStatusIndicator(status) {
|
|
14
|
+
const normalizedStatus = status.toLowerCase();
|
|
15
|
+
if (normalizedStatus === "healthy" ||
|
|
16
|
+
normalizedStatus === "connected" ||
|
|
17
|
+
normalizedStatus === "running") {
|
|
18
|
+
return `[OK] ${status}`;
|
|
19
|
+
}
|
|
20
|
+
if (normalizedStatus === "disconnected" ||
|
|
21
|
+
normalizedStatus === "stopped" ||
|
|
22
|
+
normalizedStatus === "unknown") {
|
|
23
|
+
return `[!!] ${status}`;
|
|
24
|
+
}
|
|
25
|
+
return `[--] ${status}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Displays the system status in a formatted table.
|
|
29
|
+
*/
|
|
30
|
+
function displaySystemStatus(status) {
|
|
31
|
+
console.log("");
|
|
32
|
+
console.log("=".repeat(50));
|
|
33
|
+
console.log("System Status");
|
|
34
|
+
console.log("=".repeat(50));
|
|
35
|
+
console.log("");
|
|
36
|
+
const labels = [
|
|
37
|
+
"API",
|
|
38
|
+
"Redis",
|
|
39
|
+
"Daemon",
|
|
40
|
+
"Worker",
|
|
41
|
+
"Workers Active",
|
|
42
|
+
"GitHub Auth",
|
|
43
|
+
"Claude Auth",
|
|
44
|
+
"Timestamp",
|
|
45
|
+
];
|
|
46
|
+
const maxLabelWidth = Math.max(...labels.map((l) => l.length));
|
|
47
|
+
console.log(`${"API".padEnd(maxLabelWidth)} ${formatStatusIndicator(status.api)}`);
|
|
48
|
+
console.log(`${"Redis".padEnd(maxLabelWidth)} ${formatStatusIndicator(status.redis)}`);
|
|
49
|
+
console.log(`${"Daemon".padEnd(maxLabelWidth)} ${formatStatusIndicator(status.daemon)}`);
|
|
50
|
+
console.log(`${"Worker".padEnd(maxLabelWidth)} ${formatStatusIndicator(status.worker)}`);
|
|
51
|
+
if (status.workerCount !== undefined) {
|
|
52
|
+
console.log(`${"Workers Active".padEnd(maxLabelWidth)} ${status.workerCount}`);
|
|
53
|
+
}
|
|
54
|
+
console.log(`${"GitHub Auth".padEnd(maxLabelWidth)} ${formatStatusIndicator(status.githubAuth)}`);
|
|
55
|
+
console.log(`${"Claude Auth".padEnd(maxLabelWidth)} ${formatStatusIndicator(status.claudeAuth)}`);
|
|
56
|
+
console.log("");
|
|
57
|
+
console.log(`${"Timestamp".padEnd(maxLabelWidth)} ${new Date(status.timestamp).toLocaleString()}`);
|
|
58
|
+
console.log("");
|
|
59
|
+
console.log("=".repeat(50));
|
|
60
|
+
const allHealthy = status.api === "healthy" &&
|
|
61
|
+
status.redis === "connected" &&
|
|
62
|
+
status.daemon === "running" &&
|
|
63
|
+
status.worker === "running" &&
|
|
64
|
+
status.githubAuth === "connected";
|
|
65
|
+
console.log("");
|
|
66
|
+
if (allHealthy) {
|
|
67
|
+
console.log("All systems operational.");
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.log("Some components require attention.");
|
|
71
|
+
if (status.redis !== "connected") {
|
|
72
|
+
console.log(" - Redis is not connected. Check Redis server.");
|
|
73
|
+
}
|
|
74
|
+
if (status.daemon !== "running") {
|
|
75
|
+
console.log(" - Daemon is not running. Start the daemon service.");
|
|
76
|
+
}
|
|
77
|
+
if (status.worker !== "running") {
|
|
78
|
+
console.log(" - No workers active. Check worker processes.");
|
|
79
|
+
}
|
|
80
|
+
if (status.githubAuth !== "connected") {
|
|
81
|
+
console.log(" - GitHub auth not configured. Check GH_APP_ID, GH_PRIVATE_KEY_PATH, and GH_INSTALLATION_ID.");
|
|
82
|
+
}
|
|
83
|
+
if (status.claudeAuth !== "connected") {
|
|
84
|
+
console.log(" - Claude auth status unknown or no recent activity.");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Displays the queue statistics in a formatted table.
|
|
90
|
+
*/
|
|
91
|
+
function displayQueueStats(stats) {
|
|
92
|
+
console.log("");
|
|
93
|
+
console.log("=".repeat(50));
|
|
94
|
+
console.log("Queue Statistics");
|
|
95
|
+
console.log("=".repeat(50));
|
|
96
|
+
console.log("");
|
|
97
|
+
const labels = ["Waiting", "Active", "Completed", "Failed", "Delayed", "Total"];
|
|
98
|
+
const maxLabelWidth = Math.max(...labels.map((l) => l.length));
|
|
99
|
+
const formatNumber = (n) => n.toLocaleString();
|
|
100
|
+
console.log(`${"Waiting".padEnd(maxLabelWidth)} ${formatNumber(stats.waiting)}`);
|
|
101
|
+
console.log(`${"Active".padEnd(maxLabelWidth)} ${formatNumber(stats.active)}`);
|
|
102
|
+
console.log(`${"Completed".padEnd(maxLabelWidth)} ${formatNumber(stats.completed)}`);
|
|
103
|
+
console.log(`${"Failed".padEnd(maxLabelWidth)} ${formatNumber(stats.failed)}`);
|
|
104
|
+
console.log(`${"Delayed".padEnd(maxLabelWidth)} ${formatNumber(stats.delayed)}`);
|
|
105
|
+
console.log("-".repeat(30));
|
|
106
|
+
console.log(`${"Total".padEnd(maxLabelWidth)} ${formatNumber(stats.total)}`);
|
|
107
|
+
console.log("");
|
|
108
|
+
console.log("=".repeat(50));
|
|
109
|
+
console.log("");
|
|
110
|
+
if (stats.active > 0) {
|
|
111
|
+
console.log(`Currently processing ${stats.active} job(s).`);
|
|
112
|
+
}
|
|
113
|
+
if (stats.waiting > 0) {
|
|
114
|
+
console.log(`${stats.waiting} job(s) waiting in queue.`);
|
|
115
|
+
}
|
|
116
|
+
if (stats.failed > 0) {
|
|
117
|
+
const failRate = stats.total > 0
|
|
118
|
+
? ((stats.failed / stats.total) * 100).toFixed(1)
|
|
119
|
+
: "0.0";
|
|
120
|
+
console.log(`${stats.failed} job(s) failed (${failRate}% failure rate).`);
|
|
121
|
+
}
|
|
122
|
+
if (stats.total === 0) {
|
|
123
|
+
console.log("Queue is empty.");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Creates the `remote-status` command.
|
|
128
|
+
*
|
|
129
|
+
* Reports the health of a remote ProPR backend (API/Redis/daemon/worker) over
|
|
130
|
+
* HTTP. The top-level `propr status` now reports the local Docker stack, so this
|
|
131
|
+
* backend health view is exposed as `remote-status`.
|
|
132
|
+
*/
|
|
133
|
+
export function createRemoteStatusCommand() {
|
|
134
|
+
return new Command("remote-status")
|
|
135
|
+
.description("Display the health status of a remote ProPR backend (API, Redis, daemon, worker)")
|
|
136
|
+
.option("--json", "Output raw JSON response")
|
|
137
|
+
.addHelpText("after", `
|
|
138
|
+
Components Checked:
|
|
139
|
+
- API health
|
|
140
|
+
- Redis connection
|
|
141
|
+
- Daemon status
|
|
142
|
+
- Worker status
|
|
143
|
+
- GitHub authentication
|
|
144
|
+
- Claude authentication
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
$ propr remote-status # Human-readable output
|
|
148
|
+
$ propr remote-status --json # JSON output for scripting
|
|
149
|
+
`)
|
|
150
|
+
.action(async (options) => {
|
|
151
|
+
try {
|
|
152
|
+
const status = await getSystemStatus();
|
|
153
|
+
if (printOutput(status, options.json ?? false)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
console.log("Checking system status...");
|
|
157
|
+
displaySystemStatus(status);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
const errorMessage = error.message;
|
|
161
|
+
if (errorMessage.includes("401") ||
|
|
162
|
+
errorMessage.includes("unauthorized")) {
|
|
163
|
+
console.error("Error: Unauthorized. Please run 'propr login' first.");
|
|
164
|
+
}
|
|
165
|
+
else if (errorMessage.includes("ECONNREFUSED") ||
|
|
166
|
+
errorMessage.includes("network")) {
|
|
167
|
+
console.error("Error: Cannot connect to ProPR backend. Is the server running?");
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
console.error(`Error checking system status: ${errorMessage}`);
|
|
171
|
+
}
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Creates the `queue` command.
|
|
178
|
+
*/
|
|
179
|
+
export function createQueueCommand() {
|
|
180
|
+
return new Command("queue")
|
|
181
|
+
.description("Display job queue statistics and counts")
|
|
182
|
+
.option("--json", "Output raw JSON response")
|
|
183
|
+
.addHelpText("after", `
|
|
184
|
+
Statistics Shown:
|
|
185
|
+
- Waiting jobs
|
|
186
|
+
- Active jobs
|
|
187
|
+
- Completed jobs
|
|
188
|
+
- Failed jobs
|
|
189
|
+
- Delayed jobs
|
|
190
|
+
- Failure rate
|
|
191
|
+
|
|
192
|
+
Examples:
|
|
193
|
+
$ propr queue # Human-readable output
|
|
194
|
+
$ propr queue --json # JSON output for scripting
|
|
195
|
+
`)
|
|
196
|
+
.action(async (options) => {
|
|
197
|
+
try {
|
|
198
|
+
const stats = await getQueueStats();
|
|
199
|
+
if (printOutput(stats, options.json ?? false)) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
console.log("Fetching queue statistics...");
|
|
203
|
+
displayQueueStats(stats);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
const errorMessage = error.message;
|
|
207
|
+
if (errorMessage.includes("401") ||
|
|
208
|
+
errorMessage.includes("unauthorized")) {
|
|
209
|
+
console.error("Error: Unauthorized. Please run 'propr login' first.");
|
|
210
|
+
}
|
|
211
|
+
else if (errorMessage.includes("ECONNREFUSED") ||
|
|
212
|
+
errorMessage.includes("network")) {
|
|
213
|
+
console.error("Error: Cannot connect to ProPR backend. Is the server running?");
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
console.error(`Error fetching queue statistics: ${errorMessage}`);
|
|
217
|
+
}
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `propr tank on|off` — toggle Agent Tank LLM usage tracking.
|
|
3
|
+
*
|
|
4
|
+
* Agent Tank is an external service, not a stack container, so this is a backend
|
|
5
|
+
* setting flip routed through the running ProPR API.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { getAgentTank, setAgentTank } from "../api/agentTank.js";
|
|
9
|
+
import { NetworkError, UnauthorizedError } from "../api/errors.js";
|
|
10
|
+
import { parseOnOffState, ParseStateError } from "../utils/index.js";
|
|
11
|
+
function handleApiError(error) {
|
|
12
|
+
if (error instanceof NetworkError) {
|
|
13
|
+
console.error("Error: cannot reach the ProPR backend. Start the stack first: propr start");
|
|
14
|
+
}
|
|
15
|
+
else if (error instanceof UnauthorizedError) {
|
|
16
|
+
console.error("Error: Unauthorized. Please run 'propr login' first.");
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
console.error(`Error updating Agent Tank: ${error.message}`);
|
|
20
|
+
}
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
export function createTankCommand() {
|
|
24
|
+
const tank = new Command("tank")
|
|
25
|
+
.description("Toggle Agent Tank LLM usage tracking (requires the stack running)")
|
|
26
|
+
.argument("[state]", "on or off (omit to show current setting)")
|
|
27
|
+
.option("--url <url>", "Agent Tank service URL")
|
|
28
|
+
.addHelpText("after", `
|
|
29
|
+
Examples:
|
|
30
|
+
$ propr tank # show current setting
|
|
31
|
+
$ propr tank on
|
|
32
|
+
$ propr tank off
|
|
33
|
+
$ propr tank on --url http://127.0.0.1:3456
|
|
34
|
+
`)
|
|
35
|
+
.action(async (state, options) => {
|
|
36
|
+
try {
|
|
37
|
+
if (!state) {
|
|
38
|
+
const current = await getAgentTank();
|
|
39
|
+
console.log(`Agent Tank: ${current.enabled ? "on" : "off"}${current.url ? ` (${current.url})` : ""}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const enable = parseOnOffState(state);
|
|
43
|
+
const result = await setAgentTank(enable, options.url);
|
|
44
|
+
console.log(`Agent Tank ${result.enabled ? "enabled" : "disabled"}${result.url ? ` (${result.url})` : ""}.`);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof ParseStateError) {
|
|
48
|
+
console.error(`Error: ${error.message}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
handleApiError(error);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return tank;
|
|
55
|
+
}
|