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.
Files changed (64) hide show
  1. package/README.md +549 -0
  2. package/dist/api/agentTank.js +27 -0
  3. package/dist/api/agents.js +201 -0
  4. package/dist/api/client.js +284 -0
  5. package/dist/api/errors.js +145 -0
  6. package/dist/api/implement.js +147 -0
  7. package/dist/api/index.js +26 -0
  8. package/dist/api/logs.js +59 -0
  9. package/dist/api/plans.js +160 -0
  10. package/dist/api/relay.js +73 -0
  11. package/dist/api/repos.js +243 -0
  12. package/dist/api/settings.js +219 -0
  13. package/dist/api/system.js +53 -0
  14. package/dist/api/tasks.js +140 -0
  15. package/dist/api/todos.js +77 -0
  16. package/dist/api/types.js +6 -0
  17. package/dist/assets/.env.example +183 -0
  18. package/dist/assets/env.example.txt +198 -0
  19. package/dist/commands/agentCommands.js +405 -0
  20. package/dist/commands/checkCommands.js +384 -0
  21. package/dist/commands/implementCommands.js +178 -0
  22. package/dist/commands/index.js +22 -0
  23. package/dist/commands/initCommands.js +167 -0
  24. package/dist/commands/initStack.js +193 -0
  25. package/dist/commands/logCommands.js +170 -0
  26. package/dist/commands/planCommands.js +552 -0
  27. package/dist/commands/relayCommands.js +149 -0
  28. package/dist/commands/repoCommands.js +526 -0
  29. package/dist/commands/settingCommands.js +237 -0
  30. package/dist/commands/stackCommands.js +86 -0
  31. package/dist/commands/startCommand.js +36 -0
  32. package/dist/commands/systemCommands.js +221 -0
  33. package/dist/commands/tankCommands.js +55 -0
  34. package/dist/commands/taskCommands.js +554 -0
  35. package/dist/commands/todoCommands.js +620 -0
  36. package/dist/commands/uiDocsCommands.js +69 -0
  37. package/dist/config/ConfigManager.js +360 -0
  38. package/dist/config/index.js +8 -0
  39. package/dist/config/types.js +16 -0
  40. package/dist/index.js +276 -0
  41. package/dist/orchestrator/format.js +31 -0
  42. package/dist/orchestrator/index.js +102 -0
  43. package/dist/orchestrator/manifest.json +16 -0
  44. package/dist/orchestrator/orchestrator.mjs +798 -0
  45. package/dist/orchestrator/types.js +10 -0
  46. package/dist/tui/StartApp.js +175 -0
  47. package/dist/tui/app.js +9 -0
  48. package/dist/tui/render.js +87 -0
  49. package/dist/utils/envFile.js +65 -0
  50. package/dist/utils/index.js +8 -0
  51. package/dist/utils/io.js +186 -0
  52. package/dist/utils/parseState.js +14 -0
  53. package/dist/utils/resolveProject.js +50 -0
  54. package/dist/vendor/shared/demoMode.js +6 -0
  55. package/dist/vendor/shared/events.js +30 -0
  56. package/dist/vendor/shared/githubAuthMode.js +35 -0
  57. package/dist/vendor/shared/index.js +15 -0
  58. package/dist/vendor/shared/labelUtils.js +32 -0
  59. package/dist/vendor/shared/modelDefinitions.js +146 -0
  60. package/dist/vendor/shared/reviewPrompt.js +18 -0
  61. package/dist/vendor/shared/usageTypes.js +13 -0
  62. package/dist/vendor/shared/userWhitelist.js +30 -0
  63. package/dist/vendor/shared/validateRelayUrl.js +21 -0
  64. 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
+ }