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,149 @@
1
+ /**
2
+ * `propr relay` — manage GitHub token relay enrollment (shared-app auth path).
3
+ *
4
+ * enroll → calls the relay to mint a durable relay token (proving identity with
5
+ * the GitHub token from `propr login`) and writes PROPR_GH_RELAY_URL /
6
+ * PROPR_GH_RELAY_TOKEN into the stack .env so the daemon can use it.
7
+ * list → lists relay tokens for the installation.
8
+ * revoke → revokes a relay token by id.
9
+ */
10
+ import { Command } from "commander";
11
+ import { hostname } from "node:os";
12
+ import { join } from "node:path";
13
+ import { validateRelayUrl } from "../vendor/shared/index.js";
14
+ import { createConfigManager } from "../config/index.js";
15
+ import { loadOrchestrator, resolveStackRoot } from "../orchestrator/index.js";
16
+ import { upsertEnvVars } from "../utils/envFile.js";
17
+ import { enrollRelayToken, listRelayTokens, revokeRelayToken, } from "../api/relay.js";
18
+ async function resolveContext(options) {
19
+ const configManager = await createConfigManager();
20
+ const rootDir = resolveStackRoot(configManager, options.root);
21
+ const envPath = join(rootDir, ".env");
22
+ const orch = await loadOrchestrator();
23
+ const fileEnv = orch.readEnvFile(envPath);
24
+ const relayBaseUrl = options.url ?? process.env.PROPR_GH_RELAY_URL ?? fileEnv.PROPR_GH_RELAY_URL;
25
+ if (!relayBaseUrl) {
26
+ throw new Error("No relay URL. Pass --url <https://relay/v1> or set PROPR_GH_RELAY_URL in .env (run `propr init stack` first).");
27
+ }
28
+ const urlError = validateRelayUrl(relayBaseUrl);
29
+ if (urlError) {
30
+ throw new Error(urlError);
31
+ }
32
+ const installationId = options.installation ?? process.env.GH_INSTALLATION_ID ?? fileEnv.GH_INSTALLATION_ID;
33
+ if (!installationId) {
34
+ throw new Error("No installation id. Pass --installation <id> or set GH_INSTALLATION_ID in .env.");
35
+ }
36
+ const githubToken = configManager.getGithubToken();
37
+ if (!githubToken) {
38
+ throw new Error("Not logged in to GitHub. Run `propr login` first.");
39
+ }
40
+ return {
41
+ rootDir,
42
+ envPath,
43
+ relayBaseUrl,
44
+ installationId,
45
+ client: { baseUrl: relayBaseUrl, githubToken },
46
+ };
47
+ }
48
+ export function createRelayCommand() {
49
+ const relay = new Command("relay")
50
+ .description("Manage GitHub token relay enrollment (shared-app auth path)")
51
+ .addHelpText("after", `
52
+ The relay lets a shared-app stack obtain GitHub installation tokens without
53
+ holding the App's private key. Enroll once; the token is saved to your .env.
54
+
55
+ Examples:
56
+ $ propr relay enroll --url https://relay.propr.dev/v1
57
+ $ propr relay list
58
+ $ propr relay revoke <token-id>
59
+ `);
60
+ relay
61
+ .command("enroll")
62
+ .description("Mint a relay token and save it to the stack .env")
63
+ .option("--root <dir>", "Stack root directory")
64
+ .option("--url <url>", "Relay base URL incl. version prefix (e.g. https://relay/v1)")
65
+ .option("--installation <id>", "GitHub App installation id")
66
+ .option("--label <label>", "Label for the relay token (default: hostname)")
67
+ .action(async (options) => {
68
+ try {
69
+ const ctx = await resolveContext(options);
70
+ const label = options.label ?? hostname();
71
+ const result = await enrollRelayToken(ctx.client, {
72
+ installationId: ctx.installationId,
73
+ label,
74
+ });
75
+ // GH_AUTH_MODE=relay is implied by URL+token, but writing it makes the
76
+ // .env self-describing and keeps relay mode selected even if GitHub App
77
+ // credentials are also present.
78
+ upsertEnvVars(ctx.envPath, {
79
+ GH_AUTH_MODE: "relay",
80
+ PROPR_GH_RELAY_URL: ctx.relayBaseUrl,
81
+ PROPR_GH_RELAY_TOKEN: result.token,
82
+ GH_INSTALLATION_ID: ctx.installationId,
83
+ });
84
+ console.log("Relay enrollment complete.");
85
+ console.log(` token id: ${result.token_id}`);
86
+ console.log(` token prefix: ${result.token_prefix}…`);
87
+ console.log(` label: ${result.label ?? label}`);
88
+ console.log(` saved to: ${ctx.envPath} (GH_AUTH_MODE, PROPR_GH_RELAY_URL, PROPR_GH_RELAY_TOKEN)`);
89
+ console.log("");
90
+ console.log("Next steps:");
91
+ console.log(" propr check # confirm relay mode is ready");
92
+ console.log(" propr start # launch the stack (no private key needed)");
93
+ }
94
+ catch (error) {
95
+ console.error(`Error enrolling with relay: ${error.message}`);
96
+ process.exit(1);
97
+ }
98
+ });
99
+ relay
100
+ .command("list")
101
+ .description("List relay tokens for the installation")
102
+ .option("--root <dir>", "Stack root directory")
103
+ .option("--url <url>", "Relay base URL")
104
+ .option("--installation <id>", "GitHub App installation id")
105
+ .option("--json", "Output raw JSON")
106
+ .action(async (options) => {
107
+ try {
108
+ const ctx = await resolveContext(options);
109
+ const result = await listRelayTokens(ctx.client, ctx.installationId);
110
+ if (options.json) {
111
+ console.log(JSON.stringify(result, null, 2));
112
+ return;
113
+ }
114
+ if (result.tokens.length === 0) {
115
+ console.log("No relay tokens for this installation.");
116
+ return;
117
+ }
118
+ console.log("");
119
+ console.log(`${"TOKEN ID".padEnd(38)} ${"PREFIX".padEnd(14)} ${"STATE".padEnd(8)} LABEL`);
120
+ for (const t of result.tokens) {
121
+ const state = t.revoked ? "revoked" : "active";
122
+ console.log(`${t.token_id.padEnd(38)} ${`${t.token_prefix}…`.padEnd(14)} ${state.padEnd(8)} ${t.label ?? ""}`);
123
+ }
124
+ console.log("");
125
+ }
126
+ catch (error) {
127
+ console.error(`Error listing relay tokens: ${error.message}`);
128
+ process.exit(1);
129
+ }
130
+ });
131
+ relay
132
+ .command("revoke <token-id>")
133
+ .description("Revoke a relay token by id")
134
+ .option("--root <dir>", "Stack root directory")
135
+ .option("--url <url>", "Relay base URL")
136
+ .option("--installation <id>", "GitHub App installation id")
137
+ .action(async (tokenId, options) => {
138
+ try {
139
+ const ctx = await resolveContext(options);
140
+ await revokeRelayToken(ctx.client, { installationId: ctx.installationId, tokenId });
141
+ console.log(`Revoked relay token ${tokenId}.`);
142
+ }
143
+ catch (error) {
144
+ console.error(`Error revoking relay token: ${error.message}`);
145
+ process.exit(1);
146
+ }
147
+ });
148
+ return relay;
149
+ }
@@ -0,0 +1,526 @@
1
+ /**
2
+ * Repository Management Commands
3
+ *
4
+ * CLI commands for managing monitored repositories using the ProPR backend.
5
+ * Provides the `repo` command group with `list`, `add`, `remove`, `toggle`, `index`, and `status` subcommands.
6
+ */
7
+ import { Command } from "commander";
8
+ import { getRepos, addRepo, removeRepo, updateRepo, triggerIndexing, getIndexingStatus, } from "../api/index.js";
9
+ import { printOutput } from "../utils/index.js";
10
+ /**
11
+ * Formats the enabled status for display.
12
+ */
13
+ function formatEnabled(enabled) {
14
+ return enabled ? "Enabled" : "Disabled";
15
+ }
16
+ /**
17
+ * Truncates a string to a maximum length.
18
+ */
19
+ function truncate(str, maxLen) {
20
+ if (!str)
21
+ return "";
22
+ if (str.length <= maxLen)
23
+ return str;
24
+ return str.substring(0, maxLen - 3) + "...";
25
+ }
26
+ /**
27
+ * Formats the indexing status for display.
28
+ */
29
+ function formatIndexingStatus(status) {
30
+ switch (status) {
31
+ case "indexing":
32
+ return "Indexing";
33
+ case "completed":
34
+ return "Completed";
35
+ case "failed":
36
+ return "Failed";
37
+ case "idle":
38
+ default:
39
+ return "Idle";
40
+ }
41
+ }
42
+ /**
43
+ * Formats token usage for display.
44
+ */
45
+ function formatTokens(inputTokens, outputTokens) {
46
+ const total = inputTokens + outputTokens;
47
+ if (total === 0)
48
+ return "-";
49
+ const formatNum = (n) => {
50
+ if (n >= 1000) {
51
+ return `${(n / 1000).toFixed(1)}K`;
52
+ }
53
+ return n.toString();
54
+ };
55
+ return `${formatNum(inputTokens)}/${formatNum(outputTokens)}`;
56
+ }
57
+ /**
58
+ * Displays a table of repository indexing statuses with clean formatting.
59
+ */
60
+ function displayIndexingStatusTable(statuses) {
61
+ const repoWidth = Math.max("Repository".length, ...statuses.map((s) => truncate(s.full_name, 40).length));
62
+ const branchWidth = Math.max("Branch".length, ...statuses.map((s) => truncate(s.branch, 15).length || 1));
63
+ const statusWidth = Math.max("Status".length, ...statuses.map((s) => formatIndexingStatus(s.indexing_status).length));
64
+ const progressWidth = Math.max("Progress".length, 10);
65
+ const tokensWidth = Math.max("Tokens (In/Out)".length, ...statuses.map((s) => {
66
+ if (s.progress) {
67
+ return formatTokens(s.progress.inputTokens, s.progress.outputTokens).length;
68
+ }
69
+ return 1;
70
+ }));
71
+ const header = [
72
+ "Repository".padEnd(repoWidth),
73
+ "Branch".padEnd(branchWidth),
74
+ "Status".padEnd(statusWidth),
75
+ "Progress".padEnd(progressWidth),
76
+ "Tokens (In/Out)".padEnd(tokensWidth),
77
+ ].join(" ");
78
+ console.log(header);
79
+ console.log("-".repeat(header.length));
80
+ for (const status of statuses) {
81
+ let progressStr = "-";
82
+ if (status.progress) {
83
+ progressStr = `${status.progress.percentComplete.toFixed(1)}%`;
84
+ }
85
+ else if (status.indexing_status === "completed") {
86
+ progressStr = "100%";
87
+ }
88
+ const tokensStr = status.progress
89
+ ? formatTokens(status.progress.inputTokens, status.progress.outputTokens)
90
+ : "-";
91
+ const row = [
92
+ truncate(status.full_name, 40).padEnd(repoWidth),
93
+ (truncate(status.branch, 15) || "-").padEnd(branchWidth),
94
+ formatIndexingStatus(status.indexing_status).padEnd(statusWidth),
95
+ progressStr.padEnd(progressWidth),
96
+ tokensStr.padEnd(tokensWidth),
97
+ ].join(" ");
98
+ console.log(row);
99
+ }
100
+ }
101
+ /**
102
+ * Displays a table of repositories with clean formatting.
103
+ */
104
+ function displayReposTable(repos) {
105
+ const nameWidth = Math.max("Repository".length, ...repos.map((r) => truncate(r.name, 40).length));
106
+ const aliasWidth = Math.max("Alias".length, ...repos.map((r) => truncate(r.alias, 20).length || 1));
107
+ const branchWidth = Math.max("Branch".length, ...repos.map((r) => truncate(r.baseBranch, 20).length || 1));
108
+ const statusWidth = Math.max("Status".length, ...repos.map((r) => formatEnabled(r.enabled).length));
109
+ const header = [
110
+ "Repository".padEnd(nameWidth),
111
+ "Alias".padEnd(aliasWidth),
112
+ "Branch".padEnd(branchWidth),
113
+ "Status".padEnd(statusWidth),
114
+ ].join(" ");
115
+ console.log(header);
116
+ console.log("-".repeat(header.length));
117
+ for (const repo of repos) {
118
+ const row = [
119
+ truncate(repo.name, 40).padEnd(nameWidth),
120
+ (truncate(repo.alias, 20) || "-").padEnd(aliasWidth),
121
+ (truncate(repo.baseBranch, 20) || "-").padEnd(branchWidth),
122
+ formatEnabled(repo.enabled).padEnd(statusWidth),
123
+ ].join(" ");
124
+ console.log(row);
125
+ }
126
+ }
127
+ /**
128
+ * Creates the `repo` command group.
129
+ */
130
+ export function createRepoCommand() {
131
+ const repo = new Command("repo")
132
+ .description("Manage monitored repositories")
133
+ .addHelpText("after", `
134
+ Examples:
135
+ $ propr repo list # List repositories
136
+ $ propr repo add myorg/myrepo # Add a repository
137
+ $ propr repo remove myorg/myrepo # Remove a repository
138
+ $ propr repo toggle myorg/myrepo --enable # Enable monitoring
139
+ $ propr repo index myorg/myrepo # Trigger indexing
140
+ $ propr repo status # View indexing status
141
+ `);
142
+ // repo list
143
+ repo
144
+ .command("list")
145
+ .description("List all repositories being monitored by ProPR")
146
+ .option("-j, --json", "Output as JSON for programmatic use")
147
+ .addHelpText("after", `
148
+ Examples:
149
+ $ propr repo list
150
+ $ propr repo list --json
151
+ `)
152
+ .action(async (options) => {
153
+ try {
154
+ const result = await getRepos();
155
+ if (printOutput(result, options.json ?? false)) {
156
+ return;
157
+ }
158
+ console.log("Fetching monitored repositories...");
159
+ if (result.repos_to_monitor.length === 0) {
160
+ console.log("");
161
+ console.log("No repositories are currently being monitored.");
162
+ console.log("");
163
+ console.log("To add a repository, use:");
164
+ console.log(" propr repo add <owner/repo>");
165
+ return;
166
+ }
167
+ console.log("");
168
+ displayReposTable(result.repos_to_monitor);
169
+ console.log("");
170
+ console.log(`Total: ${result.repos_to_monitor.length} repository(ies)`);
171
+ }
172
+ catch (error) {
173
+ const errorMessage = error.message;
174
+ if (errorMessage.includes("401") ||
175
+ errorMessage.includes("unauthorized")) {
176
+ console.error("Error: Unauthorized. Please run 'propr login' first.");
177
+ }
178
+ else if (errorMessage.includes("403") ||
179
+ errorMessage.includes("forbidden")) {
180
+ console.error("Error: Access denied. You do not have permission to view repositories.");
181
+ }
182
+ else {
183
+ console.error(`Error listing repositories: ${errorMessage}`);
184
+ }
185
+ process.exit(1);
186
+ }
187
+ });
188
+ // repo add
189
+ repo
190
+ .command("add <fullName>")
191
+ .description("Add a repository to the monitored list for ProPR")
192
+ .option("-a, --alias <alias>", "Display alias for the repository")
193
+ .option("-b, --branch <branch>", "Base branch name (default: main/master)")
194
+ .addHelpText("after", `
195
+ Argument:
196
+ fullName Repository in owner/repo format
197
+
198
+ Examples:
199
+ $ propr repo add myorg/myrepo
200
+ $ propr repo add myorg/myrepo -a "My Project" -b develop
201
+ `)
202
+ .action(async (fullName, options) => {
203
+ try {
204
+ if (!fullName.includes("/")) {
205
+ console.error("Error: Repository name must be in 'owner/repo' format.");
206
+ console.log("");
207
+ console.log("Example: propr repo add integry/gitfix");
208
+ process.exit(1);
209
+ }
210
+ const parts = fullName.split("/");
211
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
212
+ console.error("Error: Invalid repository format. Expected 'owner/repo'.");
213
+ process.exit(1);
214
+ }
215
+ console.log(`Adding repository: ${fullName}...`);
216
+ const result = await addRepo(fullName, {
217
+ alias: options.alias,
218
+ baseBranch: options.branch,
219
+ enabled: true,
220
+ });
221
+ if (result.success) {
222
+ console.log("");
223
+ console.log(`Successfully added repository: ${fullName}`);
224
+ if (options.alias) {
225
+ console.log(` Alias: ${options.alias}`);
226
+ }
227
+ if (options.branch) {
228
+ console.log(` Base branch: ${options.branch}`);
229
+ }
230
+ console.log("");
231
+ console.log(`Total monitored repositories: ${result.repos_to_monitor.length}`);
232
+ }
233
+ else {
234
+ console.error("Failed to add repository.");
235
+ process.exit(1);
236
+ }
237
+ }
238
+ catch (error) {
239
+ const errorMessage = error.message;
240
+ if (errorMessage.includes("already being monitored")) {
241
+ console.error(`Error: Repository "${fullName}" is already being monitored.`);
242
+ console.log("");
243
+ console.log("To update the repository settings, you can:");
244
+ console.log(` 1. Remove it first: propr repo remove ${fullName}`);
245
+ console.log(` 2. Add it again with new options: propr repo add ${fullName} [options]`);
246
+ }
247
+ else if (errorMessage.includes("401") ||
248
+ errorMessage.includes("unauthorized")) {
249
+ console.error("Error: Unauthorized. Please run 'propr login' first.");
250
+ }
251
+ else if (errorMessage.includes("403") ||
252
+ errorMessage.includes("forbidden")) {
253
+ console.error("Error: Access denied. You do not have permission to add repositories.");
254
+ }
255
+ else {
256
+ console.error(`Error adding repository: ${errorMessage}`);
257
+ }
258
+ process.exit(1);
259
+ }
260
+ });
261
+ // repo remove
262
+ repo
263
+ .command("remove <fullName>")
264
+ .description("Remove a repository from the monitored list")
265
+ .addHelpText("after", `
266
+ Argument:
267
+ fullName Repository in owner/repo format
268
+
269
+ Example:
270
+ $ propr repo remove myorg/myrepo
271
+ `)
272
+ .action(async (fullName) => {
273
+ try {
274
+ if (!fullName.includes("/")) {
275
+ console.error("Error: Repository name must be in 'owner/repo' format.");
276
+ console.log("");
277
+ console.log("Example: propr repo remove integry/gitfix");
278
+ process.exit(1);
279
+ }
280
+ console.log(`Removing repository: ${fullName}...`);
281
+ const result = await removeRepo(fullName);
282
+ if (result.success) {
283
+ console.log("");
284
+ console.log(`Successfully removed repository: ${fullName}`);
285
+ console.log("");
286
+ console.log(`Remaining monitored repositories: ${result.repos_to_monitor.length}`);
287
+ }
288
+ else {
289
+ console.error("Failed to remove repository.");
290
+ process.exit(1);
291
+ }
292
+ }
293
+ catch (error) {
294
+ const errorMessage = error.message;
295
+ if (errorMessage.includes("not being monitored")) {
296
+ console.error(`Error: Repository "${fullName}" is not being monitored.`);
297
+ console.log("");
298
+ console.log("Use 'propr repo list' to see currently monitored repositories.");
299
+ }
300
+ else if (errorMessage.includes("401") ||
301
+ errorMessage.includes("unauthorized")) {
302
+ console.error("Error: Unauthorized. Please run 'propr login' first.");
303
+ }
304
+ else if (errorMessage.includes("403") ||
305
+ errorMessage.includes("forbidden")) {
306
+ console.error("Error: Access denied. You do not have permission to remove repositories.");
307
+ }
308
+ else {
309
+ console.error(`Error removing repository: ${errorMessage}`);
310
+ }
311
+ process.exit(1);
312
+ }
313
+ });
314
+ // repo toggle
315
+ repo
316
+ .command("toggle <fullName>")
317
+ .description("Enable or disable monitoring for a repository")
318
+ .option("--enable", "Enable monitoring for the repository")
319
+ .option("--disable", "Disable monitoring for the repository")
320
+ .addHelpText("after", `
321
+ Argument:
322
+ fullName Repository in owner/repo format
323
+
324
+ Note:
325
+ Exactly one of --enable or --disable must be specified.
326
+
327
+ Examples:
328
+ $ propr repo toggle myorg/myrepo --enable
329
+ $ propr repo toggle myorg/myrepo --disable
330
+ `)
331
+ .action(async (fullName, options) => {
332
+ try {
333
+ if (options.enable && options.disable) {
334
+ console.error("Error: Cannot specify both --enable and --disable.");
335
+ process.exit(1);
336
+ }
337
+ if (!options.enable && !options.disable) {
338
+ console.error("Error: Must specify either --enable or --disable.");
339
+ console.log("");
340
+ console.log("Usage:");
341
+ console.log(` propr repo toggle ${fullName} --enable`);
342
+ console.log(` propr repo toggle ${fullName} --disable`);
343
+ process.exit(1);
344
+ }
345
+ if (!fullName.includes("/")) {
346
+ console.error("Error: Repository name must be in 'owner/repo' format.");
347
+ console.log("");
348
+ console.log("Example: propr repo toggle integry/gitfix --enable");
349
+ process.exit(1);
350
+ }
351
+ const enableState = options.enable ? true : false;
352
+ const actionWord = enableState ? "Enabling" : "Disabling";
353
+ console.log(`${actionWord} monitoring for repository: ${fullName}...`);
354
+ const result = await updateRepo(fullName, { enabled: enableState });
355
+ if (result.success) {
356
+ const statusWord = enableState ? "enabled" : "disabled";
357
+ console.log("");
358
+ console.log(`Successfully ${statusWord} monitoring for repository: ${fullName}`);
359
+ }
360
+ else {
361
+ console.error("Failed to update repository.");
362
+ process.exit(1);
363
+ }
364
+ }
365
+ catch (error) {
366
+ const errorMessage = error.message;
367
+ if (errorMessage.includes("not being monitored")) {
368
+ console.error(`Error: Repository "${fullName}" is not being monitored.`);
369
+ console.log("");
370
+ console.log("Use 'propr repo list' to see currently monitored repositories.");
371
+ console.log("To add a new repository, use 'propr repo add <owner/repo>'.");
372
+ }
373
+ else if (errorMessage.includes("401") ||
374
+ errorMessage.includes("unauthorized")) {
375
+ console.error("Error: Unauthorized. Please run 'propr login' first.");
376
+ }
377
+ else if (errorMessage.includes("403") ||
378
+ errorMessage.includes("forbidden")) {
379
+ console.error("Error: Access denied. You do not have permission to update repositories.");
380
+ }
381
+ else {
382
+ console.error(`Error updating repository: ${errorMessage}`);
383
+ }
384
+ process.exit(1);
385
+ }
386
+ });
387
+ // repo index
388
+ repo
389
+ .command("index <fullName>")
390
+ .description("Trigger codebase indexing for a repository")
391
+ .option("-b, --branch <branch>", "Specify the base branch to index")
392
+ .option("--incremental", "Perform incremental indexing instead of full reindex")
393
+ .addHelpText("after", `
394
+ Argument:
395
+ fullName Repository in owner/repo format
396
+
397
+ Indexing Modes:
398
+ Full (default) Re-index the entire repository
399
+ Incremental Only index changes since last index
400
+
401
+ Examples:
402
+ $ propr repo index myorg/myrepo # Full reindex
403
+ $ propr repo index myorg/myrepo --incremental # Incremental index
404
+ $ propr repo index myorg/myrepo -b develop # Index specific branch
405
+ `)
406
+ .action(async (fullName, options) => {
407
+ try {
408
+ if (!fullName.includes("/")) {
409
+ console.error("Error: Repository name must be in 'owner/repo' format.");
410
+ console.log("");
411
+ console.log("Example: propr repo index integry/gitfix");
412
+ process.exit(1);
413
+ }
414
+ const parts = fullName.split("/");
415
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
416
+ console.error("Error: Invalid repository format. Expected 'owner/repo'.");
417
+ process.exit(1);
418
+ }
419
+ const indexType = options.incremental ? "incremental" : "full";
420
+ console.log(`Triggering ${indexType} indexing for repository: ${fullName}...`);
421
+ const result = await triggerIndexing(fullName, {
422
+ fullReindex: !options.incremental,
423
+ baseBranch: options.branch,
424
+ });
425
+ if (result.success) {
426
+ console.log("");
427
+ console.log(`Successfully triggered indexing for repository: ${fullName}`);
428
+ if (result.jobId) {
429
+ console.log(` Job ID: ${result.jobId}`);
430
+ }
431
+ if (result.correlationId) {
432
+ console.log(` Correlation ID: ${result.correlationId}`);
433
+ }
434
+ if (options.branch) {
435
+ console.log(` Branch: ${options.branch}`);
436
+ }
437
+ console.log(` Mode: ${indexType} reindex`);
438
+ console.log("");
439
+ console.log("Use 'propr repo status <fullName>' to check indexing progress.");
440
+ }
441
+ else {
442
+ console.error(`Failed to trigger indexing: ${result.error || "Unknown error"}`);
443
+ process.exit(1);
444
+ }
445
+ }
446
+ catch (error) {
447
+ const errorMessage = error.message;
448
+ if (errorMessage.includes("already queued")) {
449
+ console.error(`Error: Indexing for "${fullName}" is already in progress or queued.`);
450
+ console.log("");
451
+ console.log("Use 'propr repo status' to check the current indexing status.");
452
+ }
453
+ else if (errorMessage.includes("401") ||
454
+ errorMessage.includes("unauthorized")) {
455
+ console.error("Error: Unauthorized. Please run 'propr login' first.");
456
+ }
457
+ else if (errorMessage.includes("403") ||
458
+ errorMessage.includes("forbidden")) {
459
+ console.error("Error: Access denied. You do not have permission to trigger indexing.");
460
+ }
461
+ else {
462
+ console.error(`Error triggering indexing: ${errorMessage}`);
463
+ }
464
+ process.exit(1);
465
+ }
466
+ });
467
+ // repo status
468
+ repo
469
+ .command("status [fullName]")
470
+ .description("View indexing status and progress for repositories")
471
+ .option("-j, --json", "Output as JSON for programmatic use")
472
+ .addHelpText("after", `
473
+ Argument:
474
+ fullName (Optional) Repository in owner/repo format
475
+
476
+ Examples:
477
+ $ propr repo status # Show all repositories
478
+ $ propr repo status myorg/myrepo # Show specific repository
479
+ $ propr repo status --json # JSON output
480
+ `)
481
+ .action(async (fullName, options) => {
482
+ try {
483
+ const result = await getIndexingStatus(fullName);
484
+ if (printOutput(result, options.json ?? false)) {
485
+ return;
486
+ }
487
+ console.log("Fetching indexing status...");
488
+ if (result.repositories.length === 0) {
489
+ console.log("");
490
+ if (fullName) {
491
+ console.log(`No indexing status found for repository: ${fullName}`);
492
+ console.log("");
493
+ console.log("Make sure the repository is being monitored:");
494
+ console.log(" propr repo list");
495
+ }
496
+ else {
497
+ console.log("No repositories are currently being tracked for indexing.");
498
+ console.log("");
499
+ console.log("To add a repository, use:");
500
+ console.log(" propr repo add <owner/repo>");
501
+ }
502
+ return;
503
+ }
504
+ console.log("");
505
+ displayIndexingStatusTable(result.repositories);
506
+ console.log("");
507
+ console.log(`Total: ${result.repositories.length} repository(ies)`);
508
+ }
509
+ catch (error) {
510
+ const errorMessage = error.message;
511
+ if (errorMessage.includes("401") ||
512
+ errorMessage.includes("unauthorized")) {
513
+ console.error("Error: Unauthorized. Please run 'propr login' first.");
514
+ }
515
+ else if (errorMessage.includes("403") ||
516
+ errorMessage.includes("forbidden")) {
517
+ console.error("Error: Access denied. You do not have permission to view indexing status.");
518
+ }
519
+ else {
520
+ console.error(`Error fetching indexing status: ${errorMessage}`);
521
+ }
522
+ process.exit(1);
523
+ }
524
+ });
525
+ return repo;
526
+ }