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,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
|
+
}
|